/* =================================================================
   Tony Gutierrez — Ocala New Homes  ·  LUXURY PREVIEW THEME
   -----------------------------------------------------------------
   Quiet-luxury treatment: midnight + warm ivory, champagne gold used
   sparingly, elegant serif display over a clean geometric sans, and
   generous whitespace. Preview only — does not touch styles.css.
   ================================================================= */
:root{
  /* --- channel tokens -------------------------------------------------
     Every translucent brand colour is expressed as rgba(var(--x-rgb), α) so a
     hue lives in exactly ONE place. Chosen over color-mix() deliberately: this
     works everywhere, needs no fallback, and the census found 62 longhand
     rgba() values that collapse into these five with no visual change. */
  --midnight-rgb:   18,24,32;
  /* --- overlay scrim -----------------------------------------------------
     NOT the same value as --midnight, and that is deliberate. Hero scrims run
     DARKER than panel surfaces because they are the only thing holding hero
     text above AA over a bright Florida sky. Collapsing them into --midnight
     (a 4% lightening) pushed /floor-plans at 375px from 3.03 to 2.88 against a
     3.0 threshold — measured, not guessed. Contrast beats consolidation, so
     this is a legitimate second dark token, not a duplicate.
     One value for every scrim, which also makes hero darkening uniform. */
  --midnight-deep-rgb: 10,14,20;
  --midnight-2-rgb: 28,40,54;
  --ivory-rgb:      247,243,236;
  --gold-rgb:       182,151,95;
  --gold-soft-rgb:  203,180,137;
  --black-rgb:      0,0,0;
  --white-rgb:      255,255,255;
  --reduced-rgb:     138,47,56;
  --success-rgb:    53,208,127;
  /* Three greens were in use with three different meanings. --success is the
     live/online dot; --verified is the deeper green shared by the Move-In Ready
     badge and the pin-check centroid chip (those two were 3f7d54 and 3c7846 —
     a 22/765 difference nobody could see, so they are now one token). */
  --verified:       #3f7d54;
  --verified-rgb:   63,125,84;
  /* Not brand colours — named so they cannot be "cleaned up" into the palette.
     --map-surface is what shows THROUGH/behind Leaflet tiles while they load;
     warming it to ivory would make every map flash the wrong colour. */
  --map-surface:    #eef1f4;
  --midnight-3:     #243244;   /* third dark lift, hero placeholder gradients */
  --placeholder:    #b3ada0;   /* form placeholder text — lighter than --muted */
  /* Text-weight variants: the badge greens/ambers above are for fills and
     icons; these are the darker AA-contrast versions used as TEXT on a light
     chip (the shortlist match-quality labels). Same hue family, different job. */
  --verified-ink:     #1d6b45;
  --verified-ink-rgb: 29,107,69;
  --warning-ink:      #8a6a1f;
  --video-bg:         #000;    /* letterbox behind <video>, not a brand black */
  --reduced:    #8a2f38;   /* deep garnet — was a stray second :root block */
  --gold-deep: #8f7440;   /* was used only as a var() fallback, never defined */
  --success:   #35d07f;
  --warning:   #c98f2f;
  --midnight:  #121820;   /* deep charcoal-navy — headings, footer, primary */
  --midnight-2:#1c2836;   /* lifted panel / hover                           */
  --ink:       #2b2f35;   /* body text on ivory                             */
  --muted:     #8a8578;
  /* --muted is 3.68:1 on white — fine for decorative text, BELOW WCAG AA for
     anything you have to read. Small copy that carries information (prices,
     counts, specs) uses --muted-aa: 5.47:1 on white, 4.53:1 on cream. */
  --muted-aa:  #6e695c;   /* warm secondary text                            */
  --ivory:     #f7f3ec;   /* warm page background (not stark white)         */
  --cream:     #efe9df;   /* deeper tint for alternating sections           */
  --gold:      #b6975f;   /* muted champagne — thin lines & small accents   */
  --gold-soft: #cbb489;   /* lighter champagne for on-dark hairlines        */
  --line:      #e4ded1;   /* hairline borders on light                      */
  --white:     #ffffff;
  --wa:        #7fae8e;   /* muted, de-saturated whatsapp green             */
  --radius:    2px;       /* barely-there softness                          */
  --shadow:    0 18px 50px -24px rgba(var(--midnight-rgb),.28);
  --shadow-lift:0 30px 70px -30px rgba(var(--midnight-rgb),.40);
  --max: 1200px;
  --callbar-h: 56px;      /* height of the sticky mobile Call/Text bar        */
  --fab-h: 56px;          /* chat launcher height — MEASURED at 390px, and    */
                          /* fixed by its own padding+font, not by its label. */
                          /* The bottom stack reserves against this.          */
  --badge-h: 0px;         /* set by assets/agent-attrib.js from the mounted    */
                          /* badge's real height; 0 when no badge is on the    */
                          /* page, so an unbadged teaser does not float.       */
  --radius-pill: 99px;    /* only things that are genuinely pills             */

  /* Ceiling on the plan-detail overlay's media block on a phone, so the
     "Where you can sell the <Plan>" heading and the first community rows are
     on screen the moment it opens. Reported 2026-09-12 from an iPhone Safari
     screenshot of the Aria modal: the video filled the viewport and the
     heading was cut off below the fold.
     svh, NOT dvh, and that is the whole point of the unit choice — dvh grows
     when Safari retracts its toolbars, so a dvh cap lets the media reclaim
     exactly the space the heading needs on the one paint that matters. */
  --bv-media-cap: 42svh;

  /* ---- motion scale. Every transition picks one of these three; a one-off
     duration is a bug, not a decision. ------------------------------------ */
  --ease:     cubic-bezier(.22,.61,.36,1);   /* the house curve               */
  --ease-in:  cubic-bezier(.55,.06,.68,.19);
  --dur-1: 180ms;   /* micro — hover, focus, pressed                          */
  --dur-2: 240ms;   /* standard — modal, menu, lightbox, page fade            */
  --dur-3: 420ms;   /* deliberate — scroll reveals, nav background            */

  /* ---- elevation scale -------------------------------------------------- */
  --shadow-sm:   0 6px 18px -10px rgba(var(--midnight-rgb),.22);
  --shadow-modal:0 48px 110px -40px rgba(var(--midnight-rgb),.62);

  /* ---- THE LAYER SCALE --------------------------------------------------
     Every z-index in this stylesheet comes from here. A raw number anywhere
     else is a bug: three separate times a modal shipped underneath the sticky
     header, and each time it was "fixed" by inventing a bigger number, which
     is what produced 900 / 1200 / 1300 competing with 100 / 140 / 170.

     The order below IS the product decision, written once:
       a modal outranks the header. Always. On every page.

     The header deliberately sits LOW (100). It is page chrome, not a layer
     that competes with overlays — the old 900 is exactly what buried the
     Schedule modal's close button under .nav__inner. */
  --z-base:            0;    /* normal page content                          */
  /* The tallest thing pinned to the bottom of the viewport: the chat launcher
     at bottom:68px + its own 56px. The call bar (44px) and the partners .pbar
     (57px) both sit under it. Any page-end padding must clear THIS, not the
     bar alone. */
  --bottom-chrome: 124px;
  --z-sticky-header: 100;    /* .nav — sticky chrome, below every overlay     */
  --z-dropdown:      200;    /* menus/panels anchored to a trigger            */
  --z-chat-launcher: 300;    /* floats over the page, never over a modal      */
  --z-overlay-scrim: 900;    /* the dimming layer beneath a modal             */
  --z-modal:        1000;    /* dialogs, lightboxes, the wizard, the menu     */
  --z-toast:        1100;    /* transient messages — the only thing above     */

  /* Sticky bars that live UNDER the header (filter rails, partner subnav).
     Derived, so they can never drift past the header they hang from. */
  --z-sticky-sub: calc(var(--z-sticky-header) - 1);

  /* ---- LOCAL layer scale ------------------------------------------------
     A separate, deliberately tiny family for ordering siblings INSIDE one
     component's own stacking context (a caption over its photo, a badge over
     a card image). These never interact with the global scale — putting a
     card badge on --z-modal would be meaningless — so they are named apart
     rather than folded in. Two families, zero raw numbers. */
  --z-local-behind: -2;      /* background media under a scrim                */
  --z-local-under:  -1;      /* decorative wash behind content                */
  --z-local-base:    0;
  --z-local-raise:   1;      /* content above its own decoration              */
  --z-local-over:    2;      /* overlay strip within a component              */
  --z-local-control: 3;      /* the component's own controls, topmost         */

  /* --- spacing scale ---------------------------------------------------
     THE SCALE DID NOT EXIST BEFORE 2026-08-02. Vertical rhythm was a field
     of one-off rem literals — a census of .hd__* alone found .3/.35/.4/.5/
     .7/.9/1.6/2.5rem, none of which agreed with any other block. That is
     survivable while every fragment is its own island; it stops being
     survivable the moment fragments are consolidated INTO sections, because
     then the gaps sit next to each other and every disagreement is visible.

     Ratio is ~1.5 with a 4px base, so every step lands on a whole pixel at
     the root font size and two adjacent steps are always distinguishable.
     Use the token, not the literal — a value that needs to be between two
     steps is nearly always a sign the wrong step was chosen. */
  --sp-1:  .25rem;   /*  4px — icon-to-label, chip innards            */
  --sp-2:  .5rem;    /*  8px — inside a control                       */
  --sp-3:  .75rem;   /* 12px — between controls in a row              */
  --sp-4:  1rem;     /* 16px — label to value, tight stack            */
  --sp-5:  1.5rem;   /* 24px — between blocks inside one component    */
  --sp-6:  2rem;     /* 32px — component to component                 */
  --sp-7:  3rem;     /* 48px — sub-section separation                 */
  --sp-8:  4rem;     /* 64px — section head to section body           */
  --sp-9:  6rem;     /* 96px — section to section (.section padding)  */

  /* ---- VERTICAL RHYTHM, 2026-08-26 -------------------------------------
     THE SCROLL-DENSITY PASS. Section padding used to be three hard-coded
     literals — 6rem at every width down to 900px, then 4rem below 640px — so
     "how tall is a section gap" was a question you answered by grepping three
     media queries, and changing it meant changing all three in step. It is now
     ONE clamp() and the media-query overrides are deleted rather than kept in
     sync: a token whose value is still overridden two breakpoints later is not
     a token, it is a fourth literal with a nicer name.

     THE NUMBERS ARE A 40% CUT AT BOTH ENDS, and they are arithmetic, not taste:
       390px   4.2vw = 16.4px -> clamp floor 2.4rem = 38.4px   (was 64px, -40%)
       1440px  4.2vw = 60.5px -> clamp ceil  3.6rem = 57.6px   (was 96px, -40%)
     The viewport term only takes over between roughly 915px and 1370px, where
     it interpolates the two ends instead of stepping at a breakpoint.

     --sp-9 IS LEFT AT 6rem AND IS NO LONGER WHAT .section USES. It is still the
     honest name for "the old section rhythm" and other rules read it; retuning
     a shared token to fix one consumer is how a spacing scale stops meaning
     anything. */
  --sec-y:   clamp(2.4rem, 4.2vw, 3.6rem);   /* section padding, block  */
  --sec-x:   clamp(1.25rem, 4vw, 2rem);      /* section padding, inline */
  --sec-gap: clamp(1.5rem, 3vw, 2.5rem);     /* section head -> body    */
}

*{box-sizing:border-box;margin:0;padding:0}
/* ANCHOR LANDING. scroll-behavior:smooth plus a sticky header means every
   in-page anchor lands its target UNDERNEATH the header unless the scroll
   port is padded. There was no scroll-padding-top and no scroll-margin-top
   anywhere in this file until 2026-08-22. Measured on production that day:
   every /partners anchor put its heading at top:0 while the nav covered to
   y=172, and elementFromPoint at each heading's own top-left returned
   HEADER.nav -- the header was painted where the heading should have been.
   --nav-h is set on :root, which IS this element, so the calc resolves. */
html{scroll-behavior:smooth;overflow-x:clip;scroll-padding-top:calc(var(--nav-h) + 1rem)}
/* AND THE SAME OFFSET ON THE TARGETS THEMSELVES (2026-09-25). scroll-padding
   above is a property of the SCROLLING BOX and covers a fragment navigation;
   scroll-margin is a property of the TARGET and is what element.scrollIntoView()
   honours. This site calls scrollIntoView from script in several places — the
   filter panel, the shortlist, the plan modal — and those calls were landing
   their target underneath the sticky header, which is the same 87px the rule
   above already accounts for on the other path. Both, because they cover
   different callers; neither is redundant. */
[id]{scroll-margin-top:calc(var(--nav-h) + 1rem)}
@media (prefers-reduced-motion:reduce){html{scroll-behavior:auto}}
body{
  font-family:'Jost',system-ui,-apple-system,Segoe UI,Roboto,sans-serif;
  color:var(--ink);
  background:var(--ivory);
  line-height:1.7;
  font-weight:300;
  -webkit-font-smoothing:antialiased;
  text-rendering:optimizeLegibility;
  overflow-x:clip; /* contain the off-canvas mobile menu so nothing overflows sideways */
}
img{display:block;width:100%;height:100%;object-fit:cover}
a{color:inherit;text-decoration:none}
h1,h2,h3,h4{font-family:'Cormorant Garamond',Georgia,serif;line-height:1.12;color:var(--midnight);font-weight:500}

/* Small letter-spaced uppercase label — the luxury-hotel eyebrow */
.eyebrow,.section__eyebrow,.hero__eyebrow{
  font-family:'Jost',sans-serif;text-transform:uppercase;
  letter-spacing:.34em;font-size:0.75rem;font-weight:500;color:var(--gold);
}

/* ---------- Buttons ---------- */
.btn{
  display:inline-flex;align-items:center;justify-content:center;gap:0.5rem;
  font-family:'Jost',sans-serif;font-weight:500;text-transform:uppercase;
  letter-spacing:.2em;font-size:0.75rem;
  padding:1rem 2rem;border-radius:var(--radius);border:1px solid transparent;
  cursor:pointer;white-space:nowrap;
  transition:background var(--dur-2) var(--ease),color var(--dur-2) var(--ease),border-color var(--dur-2) var(--ease),transform var(--dur-2) var(--ease);
}
.btn svg{width:15px;height:15px;flex:0 0 auto}
/* Soft-solid midnight (primary on light) */
.btn--solid{background:var(--midnight);color:var(--ivory);border-color:var(--midnight)}
.btn--solid:hover{background:var(--midnight-2);border-color:var(--midnight-2)}
/* Soft-solid ivory (primary on dark hero) */
.btn--ivory{background:var(--ivory);color:var(--midnight);border-color:var(--ivory)}
.btn--ivory:hover{background:var(--gold);border-color:var(--gold);color:var(--midnight)}
/* Hairline outline on light */
.btn--line{background:transparent;color:var(--midnight);border-color:var(--midnight)}
.btn--line:hover{background:var(--midnight);color:var(--ivory)}
/* Gold hairline / ghost — for on-dark surfaces */
.btn--ghost{background:transparent;color:var(--ivory);border-color:rgba(var(--gold-soft-rgb),.5)}
.btn--ghost:hover{border-color:var(--gold-soft);background:rgba(var(--gold-soft-rgb),.08)}
/* Full-width buttons are block-level and carry sentence-length labels, so the
   base .btn `white-space:nowrap` must NOT apply here. It did, and the estimator's
   "Want real numbers..." CTA gave .est__output a 568px min-content — which forced
   the .est grid track to 568px inside a 327px column on every phone. The page
   never scrolled sideways (html/body are overflow-x:clip) so the right side was
   simply cut off. Wrapping is correct for every .btn--full usage. */
.btn--full{width:100%;white-space:normal;text-align:center;line-height:1.35}

/* Refined text link ("View Homes →") */
.link-more{
  display:inline-block;font-family:'Jost',sans-serif;text-transform:uppercase;
  letter-spacing:.2em;font-size:0.75rem;font-weight:500;color:var(--midnight);
  padding-bottom:3px;background-image:linear-gradient(var(--gold),var(--gold));
  background-size:0% 1px;background-repeat:no-repeat;background-position:0 100%;
  transition:background-size var(--dur-3) var(--ease),color var(--dur-2) var(--ease);
}
.link-more:hover{background-size:100% 1px;color:var(--gold)}
.link-more .arw{color:var(--gold);transition:transform var(--dur-2) var(--ease);display:inline-block}
.link-more:hover .arw{transform:translateX(4px)}

/* ---------- Bypass block (skip to content) ----------
   WCAG 2.4.1. Every page opens with the same sticky header and, on the
   generated pages, a nav of eight to ten links. A keyboard or switch user had
   to walk all of them on EVERY page before reaching the content — there was no
   bypass anywhere on the site.

   It is the first focusable thing in <body> and it targets the <main id="main">
   landmark that already exists on every page, so nothing else had to move.

   NOT display:none / visibility:hidden — both remove an element from the focus
   order, which is the one thing this control cannot afford. It is positioned
   off-screen and comes back on :focus, so it stays reachable while staying
   invisible to everyone who is not tabbing.

   z-index rides the --z-* scale (audit_stacking.py fails the build on a raw
   value). --z-toast because a focused skip link must clear the sticky header,
   and the header is the thing it exists to skip. */
.skip-link{
  position:absolute;left:1rem;top:-100%;
  z-index:var(--z-toast);
  display:inline-block;padding:.75rem 1.25rem;
  background:var(--midnight);color:var(--ivory);
  font-family:'Jost',sans-serif;font-size:.9rem;font-weight:400;letter-spacing:.02em;
  text-decoration:none;border:1px solid var(--gold-soft);border-radius:2px;
}
.skip-link:focus{top:1rem}
/* The offset move is the affordance, so it is the one thing that must not be
   animated away for reduced-motion users — there is no transition to disable. */

/* ---------- Nav ---------- */
/* ---- THE HEADER HEIGHT, MEASURED AND NAMED ONCE (§0-Q item 3) ----------
   THE HEADER IS 87px AND FOUR STICKY OFFSETS SAID 74px. Measured in Blink at
   1440x900 and 390x844, on both skins: `.nav` is 87px tall on every one. So
   anything pinned at top:74px sat 13px UNDER the header the whole time it was
   stuck, and the 13px is the top of whatever is pinned.

   That is §0-Q item 3, and the measurement says it is NOT an /a/-only defect —
   the agent skin only made it obvious. On /find-your-home.html at 1440, y=1400,
   the market chip row that §0-Q item 1 shipped is clipped by 21px on the MAIN
   host, and "All 407 homes" by 20px:

     .fyh__mapChips  'Gainesville 84 · Nature Coast 76 · Ocala 205'  [66..161]
     .flt__count     'All 407 homes'                                 [22..42]
     nav bottom edge                                                  87

   styles.css:689 said "the site has no --header-h token, and inventing one
   here would put two answers to the same question in the file." That was the
   right instinct and the wrong conclusion: the file already held five answers
   written as the same literal, and a literal repeated five times is not one
   answer — it is five that happen to agree until one of them is edited.

   NOT A GUESS THAT CAN GO STALE. build/audit_header_offset.py measures `.nav`
   in the browser at every tested width and fails when it disagrees with this
   token, so the day the wordmark wraps to a second line the gate says so
   instead of the chips quietly sliding under the bar again.

   THE TWO HERO RULES ARE DELIBERATELY NOT CONVERTED HERE. `.hero` and
   `.page-hero` carry `margin-top:-74px;padding-top:74px`, which is the same
   constant making the same claim, so their content also starts 13px too high.
   That is a real finding and it is REPORTED rather than folded in: changing it
   moves the hero on every page of both sites, which is a design change and
   Tony's call, not a side effect of fixing a clipped chip row. */
:root{--nav-h:87px}
.nav{position:sticky;top:0;z-index:var(--z-sticky-header);background:transparent;transition:background var(--dur-3) var(--ease),box-shadow var(--dur-3) var(--ease),backdrop-filter var(--dur-3) var(--ease)}
/* SCROLLED, THE HEADER IS OPAQUE (Tony, 2026-09-25). It was rgba(...,.85) —
   15% of the page showed through it, and at 390px the rules below made it .92
   in BOTH states, so content ghosted behind the wordmark on every scroll. The
   alpha is gone; `--midnight-rgb` at full opacity is the site's one surface
   colour (there is no dark-theme block in this stylesheet, so "light and dark"
   is a single token here).

   THE 1px RULE IS A BOX-SHADOW, NOT border-bottom, AND THAT IS DELIBERATE.
   `--nav-h:87px` is a written constant that .flt, .hd__cta and
   html{scroll-padding-top} all calc against; a real border adds a 88th pixel
   to the element and silently moves every one of them. A `0 1px 0` shadow
   paints the same 1px line and occupies no layout box.

   backdrop-filter STAYS even though nothing shows through any more: it is what
   makes .hsl the containing block for the position:fixed children noted at
   line 462, and removing it here would move them. */
.nav.scrolled{background:rgb(var(--midnight-rgb));backdrop-filter:blur(10px);box-shadow:0 1px 0 rgba(var(--gold-soft-rgb),.28)}
/* THE NOTCH. padding-top only — the safe area is inset ABOVE the bar, so this
   grows the painted region upward into the notch rather than moving --nav-h's
   87px of content. On a device with no inset the env() resolves to 0 and this
   rule is inert. */
.nav{padding-top:env(safe-area-inset-top,0px)}
/* Unscrolled, the header is transparent over whatever the hero happens to be.
   The Discover Ocala pasture is bright along its treeline and the nav links sat
   on top of it nearly unreadable; the homepage hero also cycles four photos, so
   this cannot depend on any one image being dark. A soft top-down scrim fixes
   every hero at once without flattening the photograph itself. */
.nav:not(.scrolled)::before{content:"";position:absolute;left:0;right:0;top:0;height:190%;
  background:linear-gradient(180deg,rgba(var(--midnight-deep-rgb),.66) 0%,rgba(var(--midnight-deep-rgb),.34) 55%,rgba(var(--midnight-deep-rgb),0) 100%);
  pointer-events:none;z-index:var(--z-local-under)}
.nav__inner{max-width:var(--max);margin:0 auto;display:flex;align-items:center;justify-content:space-between;padding:1.35rem 2rem}
/* Wordmark: Cormorant Garamond semibold, champagne on midnight (8.8:1). The
   text-shadow only matters in the unscrolled state, where the header is
   transparent over a hero photo and champagne has less headroom than the ivory
   it replaced. */
.nav__brand{font-family:'Cormorant Garamond',serif;font-weight:600;font-size:1.62rem;
  color:var(--gold-soft);letter-spacing:.055em;line-height:1;white-space:nowrap;
  text-shadow:0 1px 12px rgba(var(--midnight-rgb),.55);transition:color var(--dur-2) var(--ease)}
.nav__brand:hover{color:var(--ivory)}
.nav.scrolled .nav__brand{text-shadow:none}
/* PORTAL WORDMARK. "New Construction Ocala" is 22 characters against "Tony
   Gutierrez"'s 14 — 287px versus 176px at the shared 1.62rem, with
   white-space:nowrap holding it on one line. On the main site that leaves a
   46px gap to the phone icon at 375px; on the portal it left ZERO, and pushed
   the hamburger's right edge past the viewport. Every one of the portal's 461
   pages failed the header gate on it and not one main-site page did, which is
   the signature of a brand-length problem rather than a layout one.

   Scaled rather than wrapped: a second line would change the header's height,
   and 74px is a hard constant shared by styles.css, the estimator's scroll
   landing in assets/estimator.h69a796715f.js and the stacking gate. The clamp floor is set
   so the wordmark clears the same 46px gap the main site holds at 375px, and
   the ceiling returns it to the shared size once there is room. */
body.is-portal .nav__brand{font-size:clamp(1rem,3.6vw,1.62rem)}
@media (min-width:601px){body.is-portal .nav__brand{font-size:1.62rem}}
/* ---- NAV TYPE, 2026-08-13 (Tony's polish brief) -------------------------
   ONE SIZE FOR EVERY ITEM IN THE BAR. The row used to mix .7rem links at
   opacity .82 with two differently-shaped pills and a phone at a different
   weight, which is what made it read as asymmetric rather than merely small.

   .875rem (14px), not .7rem (11.2px). Premium comes from SPACING, so the
   letter-spacing goes up with the size (.12em base) and the weight only moves
   300 -> 400, which is the smallest step that stops Jost looking thin over a
   moving photograph.

   OPACITY .82 IS GONE, and that — not the scrim — is where the contrast came
   from. Measured over 30 frames of the hero video with both hero gradients AND
   .nav:not(.scrolled)::before composited, worst case per item (build/
   nav_contrast_probe.py): the shipped bar already cleared AA at 5.01 (phone) /
   5.02 (partners), so the brief's "deepen the scrim" was NOT needed and the
   hero is untouched. At opacity 1 the same positions measure 8.99-12.95 cream
   at rest and 4.94-7.11 gold on hover. Both states clear 4.5:1 everywhere,
   which is why gold is safe as the hover colour at this size. */
.nav__links{display:flex;align-items:center;gap:1.55rem}
.nav__links a{color:var(--ivory);font-family:'Jost',sans-serif;font-weight:400;text-transform:uppercase;letter-spacing:.12em;font-size:.875rem;white-space:nowrap;opacity:1;transition:opacity var(--dur-2) var(--ease),color var(--dur-2) var(--ease)}
.nav__links a:hover{opacity:1;color:var(--gold-soft)}
/* ---- TWO MATCHING PILLS -------------------------------------------------
   REALTOR PARTNERS and CONTACT are ONE treatment declared ONCE, so they cannot
   drift apart again. Measured on production before this change they differed on
   every property the brief names: 37px vs 39px tall, radius 999px vs --radius,
   border .5 vs .55 alpha, and gold text vs ivory text. Declaring them together
   is the fix — a shared rule is the only version of "the same pill" that a
   later edit cannot half-apply.
   The partner's status dot stays: it is an affordance on the agent door, not a
   difference in height, border, padding or type size. */
.nav__cta,.nav__partner{display:inline-flex;align-items:center;justify-content:center;
  min-height:40px;padding:.5rem 1.15rem;border:1px solid rgba(var(--gold-soft-rgb),.55);
  border-radius:var(--radius);background:transparent;
  color:var(--ivory)!important;opacity:1!important;
  font-size:.875rem;font-weight:400;letter-spacing:.12em;line-height:1;
  transition:background var(--dur-2) var(--ease),border-color var(--dur-2) var(--ease),color var(--dur-2) var(--ease)}
/* One hover, both pills. The box is the affordance here (border + wash), so the
   label stays ivory: a gold label on a gold-tinted ground is the one
   combination in this palette that measures worse on hover than at rest. */
.nav__cta:hover,.nav__partner:hover{background:rgba(var(--gold-soft-rgb),.1);border-color:var(--gold-soft)}
.nav__toggle{display:none;background:none;border:none;color:var(--ivory);font-size:1.5rem;cursor:pointer;padding:.2rem;line-height:0}
.nav__toggle svg{width:28px;height:28px;display:block}

/* ---------- Hero ---------- */
.hero{position:relative;min-height:70vh;display:flex;align-items:center;justify-content:center;text-align:center;color:var(--ivory);overflow:hidden;margin-top:-74px;padding-top:74px}
.hero__video{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;z-index:var(--z-local-behind)}
.hero__overlay{position:absolute;inset:0;z-index:var(--z-local-under);
  background:
    radial-gradient(120% 90% at 50% 40%,rgba(var(--midnight-deep-rgb),.30),rgba(var(--midnight-deep-rgb),.66) 70%,rgba(var(--midnight-deep-rgb),.82)),
    linear-gradient(180deg,rgba(var(--midnight-deep-rgb),.35),rgba(var(--midnight-deep-rgb),.60));
}
.hero__content{max-width:940px;padding:2rem 1.5rem}

/* --- Split hero: brand statement left, live Shortlist card right ---------- */
/* The Camden walkthrough video sits at this depth; nothing else in the hero's
   stacking order depends on what is behind it.                                */
/* Midnight underneath so the split second before the poster decodes is the
   brand's own dark, not white.                                                */
.hero--split .hero__media{position:absolute;inset:0;z-index:var(--z-local-behind);overflow:hidden;
  background:rgb(var(--midnight-deep-rgb)) url(/assets/photos/hero-poster-1440.h5db3f5e599.jpg) center/cover no-repeat}
/* THE POSTER MOVED OFF THE VIDEO POSTER ATTRIBUTE AND ONTO THIS BACKGROUND so a phone can
   be served a 1440x810 cut (174 KB) instead of the 2560x1440 desktop frame
   (455 KB) that was the measured LCP element at 13.09s. That attribute takes one URL
   and srcset is invisible to cachebust.py, so a media query is the only cut that
   both hosts and the hash pipeline can express. audit_resolution.py reads CSS
   background-image and honours background-size:cover, so the sharpness floor
   still sees it. #heroVideo paints over this the moment script.he6fbd5d35b.js attaches src. */
@media (min-width:768px){
  .hero--split .hero__media{background-image:url(/assets/photos/hero-poster.h90a25f9c24.jpg)}
}
/* Left-weighted darkening so the headline holds against a bright Florida sky,
   while the right stays light enough that the card still reads as glass.      */
.hero--split .hero__overlay{
  background:
    linear-gradient(90deg,rgba(var(--midnight-deep-rgb),.82) 0%,rgba(var(--midnight-deep-rgb),.52) 48%,rgba(var(--midnight-deep-rgb),.28) 100%),
    linear-gradient(180deg,rgba(var(--midnight-deep-rgb),.36),rgba(var(--midnight-deep-rgb),.10) 38%,rgba(var(--midnight-deep-rgb),.46));
}
/* Two rows on the left (statement, then buttons); the card spans both. Named
   areas so mobile can reorder the card above the buttons with one line.       */
.hero--split .hero__inner{width:100%;max-width:var(--max);margin:0 auto;padding:2rem 1.5rem;
  display:grid;grid-template-columns:1fr minmax(370px,430px);
  grid-template-areas:"copy card" "search card" "btns card";
  gap:1.5rem clamp(2rem,5vw,4rem);align-content:center}
/* THE AI SEARCH BAR IS A NAMED AREA, NOT AN AUTO-PLACED CHILD (2026-09-12).
   This grid has explicit areas, so a new child with no `grid-area` is
   auto-placed into an implicit row and lands wherever the algorithm puts it —
   which on this template is beside the card, at half width. Naming it is the
   whole fix, and it is why the two `grid-template-areas` declarations (here
   and in the ≤980px block) have to move together. */
.hero--split .ais{grid-area:search;margin:0;max-width:34rem}
.hero--split .hero__copy{grid-area:copy;text-align:left}
.hero--split .hsl{grid-area:card;align-self:center}
.hero--split .hero__headline{margin:0;text-wrap:balance}
.hero--split .hero__rule{margin:2rem 0}
.hero--split .hero__buttons{grid-area:btns;justify-content:flex-start;margin-top:0}
.hero__lede{font-size:1rem;font-weight:300;line-height:1.75;color:rgba(var(--ivory-rgb),.78);max-width:34em;margin:0}

/* --- The Shortlist, step 1, living in the hero --------------------------- */
/* NOTE: backdrop-filter makes .hsl the containing block for any position:fixed
   descendant. Everything the wizard renders in here is static — keep it that
   way, or a fixed child will silently anchor to this card instead of the page. */
.hsl{position:relative;text-align:left;color:var(--ivory);
  background:rgba(var(--midnight-rgb),.52);
  backdrop-filter:blur(22px) saturate(120%);
  -webkit-backdrop-filter:blur(22px) saturate(120%);
  border:1px solid rgba(var(--gold-soft-rgb),.26);border-radius:var(--radius);
  padding:2rem 2rem 1.5rem;box-shadow:0 40px 90px -42px rgba(var(--black-rgb),.8)}
.hsl__eyebrow{font-family:'Jost',sans-serif;text-transform:uppercase;letter-spacing:.28em;
  font-size:0.625rem;font-weight:500;color:var(--gold-soft);margin:0}
.hsl__rule{display:block;width:42px;height:1px;background:var(--gold-soft);opacity:.8;margin:0.75rem 0 1.5rem}
.hsl__body{min-height:224px}
/* pre-JS / JS-off content */
.hsl__title{font-family:'Cormorant Garamond',Georgia,serif;font-size:2rem;font-weight:600;
  color:var(--ivory);line-height:1.16;margin:0 0 0.5rem}
.hsl__sub{font-size:0.875rem;font-weight:300;line-height:1.65;color:rgba(var(--ivory-rgb),.72);margin:0 0 1.5rem}
.hsl__cta{width:100%}
.hsl__alt{display:block;margin-top:1rem;font-family:'Jost',sans-serif;font-size:0.75rem;
  letter-spacing:.12em;color:rgba(var(--ivory-rgb),.58);transition:color var(--dur-2) var(--ease)}
.hsl__alt:hover{color:var(--gold-soft)}
/* progress + honest inventory line */
.hsl__dots{display:flex;gap:0.5rem;margin-top:1.5rem}
.hsl__dots:empty{display:none}
.hsl__stats{font-family:'Jost',sans-serif;font-size:0.625rem;letter-spacing:.14em;text-transform:uppercase;
  color:rgba(var(--ivory-rgb),.5);margin:1rem 0 0;padding-top:1rem;font-variant-numeric:tabular-nums;
  border-top:1px solid rgba(var(--ivory-rgb),.12)}

/* Shared wizard chrome, restyled for on-dark glass. Same markup as the overlay. */
.hsl .sl__stepno{color:rgba(var(--ivory-rgb),.86);margin:0 0 0.5rem}
.hsl .sl__q{color:var(--ivory);font-size:1.5rem;margin:0 0 0.25rem}
.hsl .sl__sub{color:rgba(var(--ivory-rgb),.92);font-size:0.75rem;margin:0 0 1rem}
.hsl .sl__opts{gap:0.5rem;margin:1rem 0 1rem}
.hsl .sl__opts--photo{grid-template-columns:1fr}
.hsl .sl__optImg{display:none}          /* the hero background is the imagery */
.hsl .sl__opt{min-height:0;padding:0.75rem 0.75rem;background:rgba(var(--ivory-rgb),.06);
  border-color:rgba(var(--ivory-rgb),.18)}
.hsl .sl__opt:hover{background:rgba(var(--ivory-rgb),.11);border-color:var(--gold-soft);transform:none}
.hsl .sl__opt.is-on{background:rgba(var(--gold-soft-rgb),.20);border-color:var(--gold-soft);
  box-shadow:inset 0 0 0 1px var(--gold-soft)}
.hsl .sl__optLabel{color:var(--ivory);font-size:0.75rem}
.hsl .sl__btn{background:var(--ivory);border-color:var(--ivory);color:var(--midnight);padding:0.75rem 1rem}
.hsl .sl__btn:hover{background:var(--gold-soft);border-color:var(--gold-soft);color:var(--midnight)}
.hsl .sl__btn--ghost{background:none;border-color:rgba(var(--ivory-rgb),.28);color:rgba(var(--ivory-rgb),.8)}
.hsl .sl__btn--ghost:hover{background:none;border-color:var(--gold-soft);color:var(--gold-soft)}
.hsl .sl__dot{background:rgba(var(--ivory-rgb),.2)}
/* Shortlist bands/chips ON THE DARK HERO CARD.
   The rebuild added .sl__band* without hero overrides, so the price figures
   inherited color:var(--midnight) and rendered near-black on a dark glass card —
   effectively invisible. The scrim is 62% midnight rather than a light tint
   because the card sits over a PHOTO: measured against a bright sky behind, 45%
   gives 3.36:1 (fails AA) and 62% gives 5.44:1. */
/* Budget tabs + the Reg Z block on the dark hero card — same on-dark
   translation every other sl__ control gets here, or the midnight-on-midnight
   invisibility bug above repeats on the new controls. */
.hsl .sl__tab{color:var(--ivory);background:rgba(var(--midnight-rgb),.55);border-color:rgba(var(--ivory-rgb),.28)}
.hsl .sl__tab:hover{border-color:var(--gold-soft)}
.hsl .sl__tab.is-on{color:var(--ivory);background:rgba(var(--gold-soft-rgb),.22);border-color:var(--gold-soft);box-shadow:inset 0 0 0 1px var(--gold-soft)}
.hsl .regz{border-color:rgba(var(--ivory-rgb),.24);background:rgba(var(--midnight-rgb),.45)}
.hsl .regz__terms{color:rgba(var(--ivory-rgb),.92)}
.hsl .regz__standing{color:rgba(var(--ivory-rgb),.78)}
.hsl .sl__band{background:rgba(var(--midnight-rgb),.62);border-color:rgba(var(--ivory-rgb),.24)}
.hsl .sl__band:hover{background:rgba(var(--midnight-rgb),.72);border-color:var(--gold-soft);transform:none}
.hsl .sl__band.is-on{background:rgba(var(--gold-soft-rgb),.28);border-color:var(--gold-soft)}
.hsl .sl__bandLabel{color:var(--ivory);font-size:1rem;font-weight:400}
.hsl .sl__bandSub{color:rgba(var(--ivory-rgb),.92)}
.hsl .sl__bandN{color:rgba(var(--ivory-rgb),.92)}
.hsl .sl__label{color:var(--gold-soft)}
.hsl .sl__skip{color:rgba(var(--ivory-rgb),.82);border-color:rgba(var(--ivory-rgb),.34)}
.hsl .sl__skip:hover{color:var(--gold-soft);border-color:var(--gold-soft)}
.hsl .sl__optFrom{color:rgba(var(--ivory-rgb),.92)}
.hsl .sl__optN{color:rgba(var(--ivory-rgb),.92)}
.hsl .sl__bands{gap:0.5rem;margin:1rem 0 1rem}
/* Homepage split hero is always a photo (crossfading stills) — ivory, per above. */
.hero__eyebrow{display:block;margin-bottom:2rem;color:var(--ivory)}
/* Headline is now the hero focal point — elegant serif, homes-first */
.hero__headline{font-family:'Cormorant Garamond',Georgia,serif;color:var(--ivory);font-weight:500;font-size:clamp(1.7rem,4.4vw,3.15rem);letter-spacing:.015em;line-height:1.18;max-width:none;margin:0 auto;text-wrap:balance}
.hero__rule{display:block;width:64px;height:1px;background:var(--gold-soft);margin:3rem auto;opacity:.9}
/* Buttons are the point of the hero — give them presence */
.hero__buttons{display:flex;gap:1rem;justify-content:center;flex-wrap:wrap;margin-top:1rem}
.hero__buttons .btn{padding:1rem 2rem;font-size:0.75rem}
.hero__scroll{position:absolute;bottom:2rem;left:50%;transform:translateX(-50%);display:flex;flex-direction:column;align-items:center;gap:0.75rem;color:var(--gold-soft);font-family:'Jost',sans-serif;text-transform:uppercase;letter-spacing:.28em;font-size:0.75rem}
.hero__scroll::after{content:"";width:1px;height:34px;background:linear-gradient(var(--gold-soft),transparent)}
@media (prefers-reduced-motion:no-preference){
  .hero__scroll::after{animation:drift 2.4s var(--ease) infinite}
}
@keyframes drift{0%,100%{opacity:.3;transform:scaleY(.7)}50%{opacity:1;transform:scaleY(1)}}
/* Hero on phones: keep the eyebrow + headline from overflowing narrow screens */
@media (max-width:560px){
  .hero__eyebrow{letter-spacing:.16em;font-size:0.625rem;margin-bottom:1.5rem}
  .hero__headline{font-size:1.5rem;line-height:1.24;text-wrap:wrap}
  .hero__rule{margin:2rem auto}
  .hero__buttons .btn{padding:1rem 2rem}
}
/* Split hero stacks — and the card jumps AHEAD of the buttons so the thing
   you're meant to use is in the first screenful, not below three CTAs.
   Height goes fluid so a tall step (six options) can't overflow the viewport. */
@media (max-width:980px){
  .hero--split{min-height:0;padding-bottom:3rem;align-items:flex-start}
  /* CTA sits ABOVE the card on phones. Below it, its position depended on how
     tall the current wizard step was, and on a 360x740 screen that landed the
     primary CTA underneath the sticky Call/Text bar (measured: CTA 665-712,
     callbar 685-740). Above the card it is always in the top half. */
  .hero--split .hero__inner{grid-template-columns:1fr;
    grid-template-areas:"copy" "search" "btns" "card";gap:2rem;padding-top:3rem}
  .hero--split .ais{max-width:none}
  .hero--split .hero__copy{text-align:center}
  .hero--split .hero__rule{margin:1.5rem auto}
  .hero--split .hero__buttons{justify-content:center}
  .hero--split .hero__lede{margin:0 auto}
  .hero--split .hero__scroll{display:none}
}
@media (max-width:560px){
  /* gap tuned so the CTA's bottom stays clear of the fixed chat launcher
     (measured: launcher top 700px on a 375x812 screen) */
  .hero--split .hero__inner{padding:1rem 1rem 0;gap:1rem}
  /* the card's own copy says this better, and 90px of vertical is worth more */
  .hero--split .hero__lede{display:none}
  .hero--split .hero__rule{margin:1rem auto}
  /* Only the inventory link goes — it's the very next section and it's in the
     menu. "Schedule a Visit" STAYS: it is the homepage's only trigger for the
     visit modal, and hiding the whole row made that modal unreachable on phones
     entirely. (It is also now in the mobile menu, so it survives any scroll
     position.) One full-width button keeps the row short enough not to fight
     the chat launcher the way two did. */
  .hero--split .hero__buttons{gap:0.5rem}
  .hero--split .hero__buttons .btn--ghost{display:none}
  .hero--split .hero__buttons .btn{width:100%;padding:1rem 1.5rem;font-size:0.75rem;letter-spacing:.14em}
  .hsl{padding:1rem 1rem 1rem}
  .hsl__rule{margin:0.75rem 0 1rem}
  .hsl__body{min-height:200px}
  .hsl__title{font-size:1.5rem}
  .hsl__dots{margin-top:1rem}
  .hsl__stats{margin-top:0.75rem;padding-top:0.75rem}
  .hsl .sl__q{font-size:1.25rem}
}

/* ---------- Sections ---------- */
.section{max-width:var(--max);margin:0 auto;padding:var(--sec-y) var(--sec-x)}
.section--tint{max-width:none;background:var(--cream);padding-left:0;padding-right:0}
.section--tint > *{max-width:var(--max);margin-left:auto;margin-right:auto;padding-left:var(--sec-x);padding-right:var(--sec-x)}
.section__head{text-align:center;max-width:620px;margin:0 auto var(--sec-gap)}
.section__eyebrow{display:block;margin-bottom:1rem}
.section__title{font-size:clamp(2.2rem,4.5vw,3.2rem);font-weight:500;letter-spacing:.005em}
.section__head .section__title::after{content:"";display:block;width:54px;height:1px;background:var(--gold);margin:1.5rem auto 0}
.section__sub{color:var(--muted);margin-top:1.5rem;font-size:1rem;font-weight:300}
/* Book a Community Tour (/partners) — Cal.com popup, one trigger, no inline
   calendar. Generous vertical air sets the booking CTA apart as its own beat.
   Layout only: the button is a plain .btn--solid and the focus ring comes from
   the global :focus-visible rule, so no colour is restated here. */
.section--book{padding-top:calc(var(--sec-y)*1.25);padding-bottom:calc(var(--sec-y)*1.25);text-align:center}
.section--book .section__head{margin-bottom:calc(var(--sec-gap)*.8)}
/* WRAPS. On /partners this row holds one button and never needed to; the home
   detail pages put three side by side (book / text / call) and at 375px they
   summed to 457px and spilled the viewport. Wrapping is the fix that keeps both
   pages on one rule instead of forking the component. */
.section__book-cta{display:flex;justify-content:center;flex-wrap:wrap;gap:.75rem}
/* LEAD POSITION ONLY — booking now opens /partners, directly under the hero.
   The 7.5rem above was tuned for a section with cream on both sides; between the
   full-bleed hero and the dark quick-access bar the same value reads as two dead
   bands: 120px of empty cream before the eyebrow, and the CTA floating 120px
   above a hard colour edge. The adjacent-sibling selector keeps this to the
   section in that position — .section--book's own rule is untouched, so the
   block is unchanged anywhere it is not leading a page. */
.page-hero + .section--book{padding-top:var(--sec-y);padding-bottom:var(--sec-y)}

/* ---------- Community map entry (community pages) ----------
   Sits directly under the hero. Two entries at most — the builder's site plan
   (opens the brochure lightbox) and, on spot-lot communities, the interactive
   pin map further down. Both are the same control shape so neither reads as the
   primary; the labels carry the difference. */
/* ---- HOA dues, community pages ------------------------------------------
   Informational block. One row per DISTINCT due amount, labelled by the series
   and product line it covers. CDD is deliberately absent — it is an estimator
   input, not a display figure. */
.hoa{padding-top:3rem;padding-bottom:0}
.hoa__inner{max-width:var(--max);margin:0 auto;padding:0 2rem}
.hoa__eyebrow{text-align:center;margin-bottom:1.25rem}
.hoa__rows{display:flex;flex-wrap:wrap;gap:.75rem;justify-content:center}
.hoa__row{display:flex;flex-direction:column;align-items:center;gap:.15rem;
  min-width:11rem;padding:1rem 1.35rem;border:1px solid var(--line);
  border-radius:var(--radius);background:var(--cream)}
.hoa__amt{font-family:'Cormorant Garamond',Georgia,serif;font-size:1.55rem;
  line-height:1.1;color:var(--ink);font-weight:500}
.hoa__per{font-family:'Jost',sans-serif;font-size:.7rem;letter-spacing:.1em;
  color:var(--muted);margin-left:.15rem}
.hoa__ser{font-family:'Jost',sans-serif;text-transform:uppercase;
  letter-spacing:.13em;font-size:.625rem;color:var(--gold);font-weight:500;
  text-align:center}
.hoa__none{text-align:center;font-size:1.05rem;color:var(--ink)}
.hoa__note{margin-top:1rem;text-align:center;font-size:.8rem;color:var(--muted);
  font-weight:300;line-height:1.7;max-width:44rem;margin-left:auto;margin-right:auto}
@media(max-width:600px){.hoa__row{min-width:0;flex:1 1 8.5rem;padding:.8rem .6rem}
  .hoa__amt{font-size:1.3rem}}

.cmap{padding-top:3rem;padding-bottom:0}
.cmap__eyebrow{text-align:center;margin-bottom:1.25rem}
.cmap__row{display:flex;gap:1rem;justify-content:center;flex-wrap:wrap}
.cmap__item{display:grid;grid-template-columns:auto 1fr;grid-template-rows:auto auto auto;
  gap:0 .9rem;align-items:center;text-align:left;
  min-width:min(320px,100%);padding:1.05rem 1.35rem;
  background:var(--cream);border:1px solid rgba(var(--midnight-rgb),.10);border-radius:2px;
  color:inherit;text-decoration:none;cursor:pointer;font:inherit;
  transition:border-color var(--dur-2) ease,transform var(--dur-2) ease}
.cmap__item:hover{border-color:var(--gold);transform:translateY(-2px)}
.cmap__ico{grid-row:1/4;width:30px;height:30px;color:var(--gold)}
.cmap__label{font-weight:500;letter-spacing:.01em}
.cmap__sub{color:var(--muted);font-size:.86rem;font-weight:300}
.cmap__go{color:var(--gold);font-size:.82rem;letter-spacing:.06em;text-transform:uppercase;margin-top:.3rem}
@media (max-width:640px){
  .cmap{padding-top:2rem}
  .cmap__item{min-width:100%;padding:.95rem 1.1rem}
}

/* Product type on a card's identity line — present on every home and plan card.
   Same weight as the rest of the line: it is a fact, not a badge. */
.home__type,.plan__type{white-space:nowrap}

/* ---------- Home card -> home detail page ----------
   The identity-line anchor stretches over the whole card, so image, price and
   title all click through while only ONE link lands in the accessibility tree.
   Anything the buyer must still be able to press — carousel arrows and dots, the
   action row, the brochure/video buttons — is lifted above the overlay. Without
   these z-index lines the stretched link silently eats every control on the card. */
.home{position:relative}
.home__jump{color:inherit;text-decoration:none}
.home__jump::after{content:"";position:absolute;inset:0;z-index:var(--z-local-raise)}
.home:hover .home__jump{color:var(--gold)}
.home__actions,.home__assets{position:relative;z-index:var(--z-local-over)}
.home__media .hcar__arw,.home__media .hcar__dots{z-index:var(--z-local-control)}
/* ---- THE OVERLAY WAS EATING EVERY CONTROL ON THE CARD, 2026-08-13 ---------
   Reported as "tapping share just opens the home page". Measured on production
   with document.elementFromPoint() at the dead centre of each control, on all
   12 cards of /communities/autumn-glen.html: share DEAD, heart DEAD, carousel
   arrows DEAD, and SWIPE DEAD. Every one of them answered A.home__jump.

   assets/share-plan.js was never the bug — it calls preventDefault() and is
   correct. It never RAN: the click lands on the stretched link's ::after, so
   e.target.closest('[data-share-plan]') is null and the handler returns before
   it can stop the navigation.

   TWO SEPARATE OMISSIONS, ONE SYMPTOM.

   1. .card-acts is the row share-plan.js and save.js mount into (added
      2026-08-12 so the pair sit BESIDE each other). It was never added to the
      lift on the line above — it is position:static/z-index:auto, so the
      overlay at --z-local-raise paints and hit-tests over both buttons.

   2. .hcar__arw already asks for --z-local-control (3) one line up, and that
      line CANNOT WORK: .hcar carries mask-image AND isolation:isolate (the iOS
      slide-bleed mitigation, styles.css ~3930), and either one alone makes it a
      stacking context. z-index 3 is therefore scoped INSIDE .hcar and can never
      out-rank a sibling overlay at 1. Deleting isolation:isolate would not help
      — a mask isolates by itself. The only way out is to raise .hcar, so that
      is what this does.

   Raising the carousel costs photo-click-to-navigate, which assets/carousel.h84aac32175.js
   gives back as a non-swipe tap. STATIC media is deliberately NOT raised: a
   one-photograph card has nothing to reach inside it, so it keeps native
   click-through and this change cannot touch it. */
.card-acts{position:relative;z-index:var(--z-local-over)}
/* THE LIFT HAS TO APPLY TO THE BARE BUTTONS TOO — found 2026-08-14, live on
   production, and it predates the light card that surfaced it.
   `.card-acts` is created by assets/save.js. assets/share-plan.js does not
   create it; it appends its button to `.home__body` and joins the row only if
   save.js already made one. save.js is loaded on /homes.html and IS NOT LOADED
   ON THE HOMEPAGE, so on the homepage the share button has always been a bare
   child of .home__body with no stacking context of its own — and the stretched
   .home__jump::after (z-index 1) sat over it. Measured on production at 390px:
   every homepage card returns A.home__jump from elementFromPoint at the share
   button's centre. The button is painted and has never been touchable there.
   This is the 2026-08-13 defect surviving in the one place that fix did not
   reach, because that fix raised the ROW and this page has no row. */
.home__body>[data-share-plan],
.home__body>[data-save-home]{position:relative;z-index:var(--z-local-over)}
.home__media.hcar{z-index:var(--z-local-over)}

/* ---------- Home detail page ---------- */
.hd{max-width:var(--max);margin:0 auto;padding:var(--sp-6) var(--sp-6) 0}
/* .hd__grid is GONE. It was the two-column hero — gallery beside the price —
   and the 2026-08-02 restructure split those into two sections so the whole
   identity block can sit above the fold at 375px. The rule is deleted rather
   than left orphaned: a dead layout rule is the thing a later edit reattaches
   by accident. */

/* ==========================================================================
   THE DENSE BLOCK — price / specs / payment / CTA in one screen below the
   mosaic (Tony's brief, 2026-08-11).
   ==========================================================================
   TIGHTER, NOT SMALLER. The rhythm here is deliberately compressed against the
   rest of the page (--sp-3 between elements where the old stack used --sp-5),
   because the brief asks the block to read dense rather than airy. Type sizes
   are untouched: shrinking the price or the payment to win vertical space
   would trade legibility for a fold position, and the contrast floor and the
   Reg Z equal-weight rule both live in this block.

   THE PAY UNIT IS NOT COMPRESSED AND IS NOT SPLIT. build/pay_unit.py emits the
   figure, the note rate, the APR and the disclosure as ONE container, and
   audit_card_payments.py / audit_equal_weight.py are BLOCKING on exactly that
   adjacency (Reg Z 1026.24 / 1026.24(c)). The brief anticipated this — "if
   compression would separate them, do not compress" — so the unit keeps its
   own internal spacing and only the gaps AROUND it were reduced.
   ========================================================================== */
.hd--ident{padding-top:var(--sp-4);padding-bottom:var(--sp-4)}
.hd__dense{display:grid;grid-template-columns:minmax(0,1fr) 20.5rem;
  gap:var(--sp-5);align-items:start}
.hd__main{display:flex;flex-direction:column;align-items:flex-start;min-width:0}
/* A readable measure at 1440 instead of a price stretched across 1200px. */
.hd__main .hd__badges,.hd__main .hd__price,
.hd__main .hd__payunit,.hd__main .hd__ident,.hd__main .hd__sub{max-width:60ch}
.hd__main .hd__badges{margin-bottom:var(--sp-2)}
.hd__main .hd__ident{margin-top:var(--sp-3)}
.hd__main .hd__sub{margin-top:var(--sp-1)}

/* BEDS / BATHS / SQ FT — value over a small-caps label, three across. */
.hd__specs{display:flex;flex-wrap:wrap;gap:var(--sp-5);margin:var(--sp-3) 0 0;padding:0}
.hd__spec{margin:0}
.hd__spec dd{margin:0;font-size:1.35rem;line-height:1.15;color:var(--midnight)}
.hd__spec dt{margin-top:.15rem;color:var(--muted-aa);font-family:'Jost',sans-serif;
  font-size:.68rem;letter-spacing:.14em;text-transform:uppercase;line-height:1.3}

/* THE CTA CARD. Hairline border and radius from .btn / .card — no new tokens.
   STICKY ON DESKTOP ONLY: `position:sticky` inside a short grid row does
   nothing useful on a phone and, worse, a sticky panel that is taller than the
   viewport can never be scrolled past. The 900px breakpoint below drops both
   the second column and the stickiness together, so the two can never
   disagree. --z-local-raise keeps it under the site header, which owns
   --z-sticky-header. */
/* --nav-h is the header offset .flt already uses. The note that used to sit
   here said the site had no such token and that inventing one would put two
   answers in the file; the token now exists and this is one of its five call
   sites, which is what actually made it one answer. */
.hd__cta{position:sticky;top:calc(var(--nav-h) + var(--sp-3));
  z-index:var(--z-local-raise);
  border:1px solid var(--line);border-radius:var(--radius);
  background:var(--white);padding:var(--sp-4);
  display:flex;flex-direction:column;gap:var(--sp-2)}
.hd__cta-k{margin:0 0 var(--sp-1);font-family:'Jost',sans-serif;font-weight:500;
  text-transform:uppercase;letter-spacing:.2em;font-size:.6875rem;color:var(--muted-aa)}
.hd__cta .btn{width:100%}
.hd__cta-tel{margin:var(--sp-1) 0 0;text-align:center;font-family:'Jost',sans-serif;
  font-size:.78rem;color:var(--muted-aa)}
.hd__cta-tel:hover{color:var(--midnight)}

/* SHARE THIS HOME — three controls, one row, every host.

   THE ROW WRAPS RATHER THAN SCROLLING, and that is the whole of the layout
   decision. `overflow-x: clip` on html and body means a row wider than the
   viewport is CUT, not scrolled — the 26-dots defect of 2026-08-11, where 26
   44px touch targets in a flex row put roughly 750px of controls off the left
   edge of a 390px screen with no way to reach them. Three buttons at a 44px
   minimum cannot overflow 390 once they are allowed to wrap, so they are.

   Each control keeps the 44px touch target the 2026-08-10 pass established,
   and `flex:1 1 auto` with a min-width lets them share a line on desktop and
   fall to two rows on a narrow phone instead of shrinking below it. */
.hd--share{border-top:1px solid var(--line);margin-top:var(--sp-5);padding-top:var(--sp-4)}
.hd__share-k{margin:0 0 var(--sp-2);font-family:'Jost',sans-serif;font-weight:500;
  text-transform:uppercase;letter-spacing:.2em;font-size:.6875rem;color:var(--muted-aa)}
.hd__share-row{display:flex;flex-wrap:wrap;gap:var(--sp-2)}
.hd__share-btn{flex:1 1 auto;min-width:8.5rem;min-height:44px;
  display:inline-flex;align-items:center;justify-content:center}
/* The live region is a status line, not a layout element: it reserves its own
   height so a "Link copied" message cannot reflow the controls under the
   finger that just tapped them. */
.hd__share-live{margin:var(--sp-2) 0 0;min-height:1.2em;font-family:'Jost',sans-serif;
  font-size:.78rem;color:var(--muted-aa)}

/* THE FACT CHIPS — 3 across desktop, 2 on mobile, per the brief. */
.hd__chips{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));
  gap:1px;background:var(--line);border:1px solid var(--line);
  border-radius:var(--radius);overflow:hidden;margin:var(--sp-5) 0 0;padding:0;width:100%}
.hd__chip{background:var(--ivory);padding:var(--sp-3) var(--sp-4);margin:0;min-width:0}
.hd__chip dt{color:var(--muted-aa);font-family:'Jost',sans-serif;font-size:.68rem;
  letter-spacing:.12em;text-transform:uppercase;line-height:1.3}
.hd__chip dd{margin:var(--sp-1) 0 0;font-size:.98rem;line-height:1.3;
  overflow-wrap:anywhere}

@media (max-width:900px){
  /* ONE COLUMN AND NOT STICKY. Both change together — see .hd__cta. */
  .hd__dense{grid-template-columns:minmax(0,1fr);gap:var(--sp-4)}
  .hd__cta{position:static;top:auto}
}
@media (max-width:640px){
  .hd__chips{grid-template-columns:repeat(2,minmax(0,1fr))}
  .hd__specs{gap:var(--sp-4)}
  .hd__spec dd{font-size:1.2rem}
  .hd__chip{padding:var(--sp-2) var(--sp-3)}
  .hd__chip dd{font-size:.92rem}
}
/* The payment sits directly under the price it qualifies, and reads louder
   here than on a card: this is the page a buyer opened to see it. */
.hd__payunit{margin:var(--sp-3) 0 0}
.hd__payunit .home__payamt{font-size:1.375rem}
.hd__payunit .home__payterms,.hd__payunit .home__paydisc{max-width:56ch}
.hd--gallery{padding-top:var(--sp-6);padding-bottom:0}
.hd__media,.hd__media--ph{position:relative;aspect-ratio:4/3;overflow:hidden;border-radius:2px;background:var(--midnight)}
.hd__media img{width:100%;height:100%;object-fit:cover;display:block}
.hd__media--ph{display:grid;place-items:center;color:var(--ivory)}

/* ==========================================================================
   THE PHOTO MOSAIC — /homes/<community>/homesite-N/ (Tony's brief 2026-08-11)
   ==========================================================================
   REPLACES A DOT RAIL THAT WAS BROKEN AT 390px AND SILENT ABOUT IT. The detail
   page rendered one .hcar__dot per photograph — 26 of them on
   /homes/autumn-glen/homesite-57/ — and the touch-target floor at
   styles.css:3572 sizes each 44x44 in a flex row anchored `right:0`. That is
   1,144px of controls against a 390px viewport, so roughly 750px ran off the
   left edge. `overflow-x: clip` on html/body means such a row CUTS rather than
   scrolls, which is why no overflow check ever reported it and why a buyer saw
   a band of grey circles they could not reach.

   THE HERO IS 55% BY THE BRIEF. The 2x2 sits in the remaining space at the
   same gap, so the tiles are square-ish at desktop widths without being told a
   pixel size. `aspect-ratio` on the container (not on the cells) keeps the
   whole block the same shape the old single image had, so nothing below it
   moves.

   data-tiles IS WHY THERE ARE NEVER HOLES. A home with three photographs has
   two tiles, not a 2x2 with two gaps: the column and row counts are driven off
   the real count, so the shape degrades instead of breaking. Homes with a
   single image never reach this component at all (see _home_media).
   ========================================================================== */
/* 12/5 IS DERIVED, NOT PICKED. The hero is 55% of the width, and it should be
   the same 4:3 landscape the single photograph used to be, so the container
   height is 0.55W x 3/4 = 0.4125W and the container ratio is 1 / 0.4125 =
   2.42. 12/5 (2.40) is that, in whole numbers. It also lands the four tiles at
   (0.45W - gaps)/2 wide by 0.206W tall — square, which is what makes the 2x2
   read as a grid rather than as four letterboxes. Inheriting the old 4/3 here
   made the block 968px tall at 1440 and turned the hero portrait. */
.hmos{position:relative;aspect-ratio:12/5;overflow:hidden;border-radius:var(--radius);background:var(--midnight)}
.hmos__grid{display:grid;gap:var(--sp-1);height:100%;width:100%;
  grid-template-columns:55% 1fr 1fr;grid-template-rows:1fr 1fr}
.hmos__cell{padding:0;margin:0;border:0;background:var(--midnight);cursor:pointer;
  overflow:hidden;display:block;position:relative}
.hmos__cell img{width:100%;height:100%;object-fit:cover;display:block;
  transition:transform var(--dur-3) var(--ease)}
.hmos__cell--hero{grid-column:1;grid-row:1 / span 2}
@media (prefers-reduced-motion:no-preference){
  .hmos__cell:hover img{transform:scale(1.03)}
}
.hmos__cell:focus-visible{outline:2px solid var(--gold);outline-offset:-3px;z-index:2}
/* Fewer than four tiles: collapse the columns rather than leave a gap. */
.hmos[data-tiles="0"] .hmos__grid{grid-template-columns:1fr;grid-template-rows:1fr}
.hmos[data-tiles="1"] .hmos__grid{grid-template-columns:55% 1fr;grid-template-rows:1fr}
.hmos[data-tiles="1"] .hmos__cell--hero{grid-row:1}
.hmos[data-tiles="2"] .hmos__grid{grid-template-columns:55% 1fr}
.hmos[data-tiles="3"] .hmos__grid{grid-template-columns:55% 1fr 1fr}
/* Three tiles: hero left, one tall tile, two stacked — no empty quarter. */
.hmos[data-tiles="3"] .hmos__cell:nth-child(2){grid-column:2;grid-row:1 / span 2}
.hmos__pool{display:none}
.hmos__pool[hidden]{display:none}

/* "See all N photos" — THE CONTACT BUTTON'S OWN VALUES, not new ones.
   .btn (styles.css:173) is the family: Jost 500, uppercase, .2em tracking,
   .75rem, border-radius var(--radius); .btn--line is the hairline variant.
   Sized down one step because it sits ON a photograph rather than in a row of
   primary actions. Ivory-on-scrim rather than midnight-on-transparent for the
   same reason — the CONTACT button's transparent fill assumes a light page
   behind it, and there is a photograph behind this one. */
.hmos__all{position:absolute;right:var(--sp-3);bottom:var(--sp-3);z-index:2;
  display:inline-flex;align-items:center;justify-content:center;
  font-family:'Jost',sans-serif;font-weight:500;text-transform:uppercase;
  letter-spacing:.2em;font-size:.6875rem;line-height:1;
  padding:.7rem 1.1rem;min-height:44px;
  border:1px solid rgba(var(--gold-soft-rgb),.55);border-radius:var(--radius);
  background:rgba(var(--midnight-rgb),.72);backdrop-filter:blur(4px);
  color:var(--ivory);cursor:pointer;white-space:nowrap;
  transition:background var(--dur-2) var(--ease),border-color var(--dur-2) var(--ease)}
.hmos__all:hover{background:var(--midnight);border-color:var(--gold-soft)}
.hmos__all:focus-visible{outline:2px solid var(--gold);outline-offset:2px}

/* MOBILE: HERO ONLY. The brief is explicit — no grid, no rail, at 390px. The
   tiles are removed from the grid rather than shrunk, because four tiles in
   the ~175px left over from a 55% hero are thumbnails nobody can read, which
   is the problem the dots already were. */
@media (max-width:640px){
  .hmos{aspect-ratio:4/3}
  .hmos__grid{grid-template-columns:1fr!important;grid-template-rows:1fr!important}
  .hmos__cell{display:none}
  .hmos__cell--hero{display:block;grid-column:1!important;grid-row:1!important}
  .hmos__all{right:var(--sp-2);bottom:var(--sp-2);font-size:.625rem;
    letter-spacing:.16em;padding:.65rem .85rem}
}

/* ==========================================================================
   THE LIGHTBOX (assets/lightbox.h94f22869a1.js)
   ========================================================================== */
.lb{position:fixed;inset:0;z-index:var(--z-modal,90);display:grid;
  grid-template-columns:auto 1fr auto;align-items:center;gap:var(--sp-2);
  /* OPAQUE (Tony, 2026-08-22). .lb__x, .lb__count and the caption paint
     directly on this element, so anything less than alpha 1.0 prints the
     gallery page through the controls. Same finding as .bv above. */
  padding:var(--sp-4);background:rgb(12,14,17)}
.lb[hidden]{display:none}
.lb__fig{margin:0;display:grid;place-items:center;max-height:100%;min-width:0}
.lb__fig img{max-width:100%;max-height:82vh;width:auto;height:auto;
  object-fit:contain;display:block;border-radius:var(--radius)}
.lb__x{position:absolute;top:var(--sp-3);right:var(--sp-3);z-index:2;
  font-family:'Jost',sans-serif;font-weight:500;text-transform:uppercase;
  letter-spacing:.2em;font-size:.6875rem;line-height:1;
  padding:.7rem 1.1rem;min-height:44px;min-width:44px;
  border:1px solid rgba(var(--gold-soft-rgb),.5);border-radius:var(--radius);
  background:transparent;color:var(--ivory);cursor:pointer}
.lb__x:hover{border-color:var(--gold-soft);background:rgba(var(--gold-soft-rgb),.08)}
.lb__arw{width:44px;height:44px;flex:0 0 auto;border-radius:var(--radius);
  border:1px solid rgba(var(--gold-soft-rgb),.4);background:transparent;
  color:var(--ivory);font-size:1.5rem;line-height:1;cursor:pointer}
.lb__arw:hover{border-color:var(--gold-soft);background:rgba(var(--gold-soft-rgb),.08)}
.lb__x:focus-visible,.lb__arw:focus-visible{outline:2px solid var(--gold);outline-offset:2px}
.lb__count{position:absolute;left:0;right:0;bottom:var(--sp-3);margin:0;text-align:center;
  font-family:'Jost',sans-serif;font-size:.72rem;letter-spacing:.16em;
  text-transform:uppercase;color:var(--ivory);opacity:.85}
/* Touch: the arrows are redundant with swipe and cost width the photo wants. */
@media (max-width:640px){
  .lb{grid-template-columns:1fr;padding:var(--sp-2)}
  .lb__arw{display:none}
  .lb__fig img{max-height:78vh}
}
/* Breadcrumb on a LIGHT background. .crumb is champagne-on-photo and belongs to
   .page-hero; reusing it here put #cbb489 on #f7f3ec — 1.82:1, well under the
   4.5 small-text minimum, on all 392 pages. The contrast gate caught it. This
   variant is 16:1 and keeps the same type treatment. */
.hd__crumb{font-family:'Jost',sans-serif;text-transform:uppercase;letter-spacing:.2em;
  font-size:.75rem;color:var(--midnight);margin-bottom:1.25rem;display:inline-block}
.hd__crumb a{color:var(--midnight);text-underline-offset:3px;text-decoration:underline}
.hd__crumb a:hover{color:var(--gold)}
.hd__badges{display:flex;gap:.5rem;flex-wrap:wrap;margin:.9rem 0}
/* .badge is position:absolute/right:1rem by default — it is built to overlay a
   card's photo. In this inline row it must be a normal flow item, exactly as
   .home__badges already does it. Without this reset the badges anchored to the
   nearest positioned ancestor (none) and parked 1rem from the VIEWPORT edge:
   at 1024px they ended at 1008, 8px past the content limit, on every home page.
   The proportion gate caught it. */
.hd__badges .badge{position:static;top:auto;right:auto;left:auto}
/* The price ceiling came down from 2.8rem to 2.4rem when the payment line and
   the stat strip joined it above the fold. Measured, not guessed: at 375x812
   the old ceiling put the last stat cell 46px below the fold on the tallest
   homes (8 stats, two-line move-in). Nothing else in the block had slack. */
.hd__price{font-size:clamp(1.9rem,3.4vw,2.4rem);font-weight:500;letter-spacing:.005em;line-height:1.15}
.hd__ident{font-size:1.05rem;font-weight:400;line-height:1.5;margin-top:var(--sp-4)}
.hd__sub{color:var(--muted);margin-top:var(--sp-1)}
/* THE SPEC STRIP. The hairlines between cells used to be the CONTAINER's own
   dark background showing through a 1px gap. That works only while the cells
   fill every track — the moment the count doesn't divide by the column count
   (8 facts in a 7-wide row) the leftover track paints as a solid grey band
   instead of an empty tile. Each cell now draws its own hairline with a
   spread-only box-shadow, which cannot affect layout or scroll width, and the
   container is ivory so any leftover track reads as blank. 140px (not 150) so
   all eight facts still land in one row at desktop. */
.hd__facts{display:grid;grid-template-columns:repeat(auto-fit,minmax(140px,1fr));gap:1px;
  background:var(--ivory);border:1px solid rgba(var(--midnight-rgb),.10)}
.hd__fact{background:var(--ivory);padding:var(--sp-3) var(--sp-4);
  box-shadow:0 0 0 1px rgba(var(--midnight-rgb),.10)}
/* --muted is 3.68:1 and below AA for text you have to read. These labels name
   what the number means, so they use --muted-aa like every other informational
   small text on the site. */
.hd__fact dt{color:var(--muted-aa);font-size:.72rem;letter-spacing:.08em;text-transform:uppercase;line-height:1.3}
.hd__fact dd{margin-top:var(--sp-1);font-size:1rem;line-height:1.3}
/* Community map with THIS homesite marked. The marker's left/top come from the
   map PDF's own text layer (measured at ingest), so it lands on the printed lot
   number rather than near it. Percentage positioning means it stays correct at
   every width without re-measuring. */
.hd__map{margin:0 auto 2rem;max-width:820px}
.hd__mapBtn{display:block;position:relative;width:100%;padding:0;border:1px solid rgba(var(--midnight-rgb),.12);
  background:var(--ivory);cursor:pointer;border-radius:2px;overflow:hidden}
.hd__mapBtn img{display:block;width:100%;height:auto}
.hd__pin{position:absolute;transform:translate(-50%,-50%);z-index:var(--z-local-raise);
  min-width:26px;height:26px;padding:0 6px;border-radius:13px;
  background:var(--gold);color:var(--midnight);border:2px solid var(--ivory);
  display:grid;place-items:center;font-size:.72rem;font-weight:600;
  box-shadow:0 1px 6px rgba(0,0,0,.35);pointer-events:none}
.hd__pin::after{content:"";position:absolute;inset:-7px;border-radius:50%;
  border:2px solid var(--gold);opacity:.55}
.hd__map figcaption{margin-top:.7rem;text-align:center;color:var(--muted);font-size:.9rem}
@media (max-width:640px){ .hd__pin{min-width:22px;height:22px;font-size:.66rem} }

.hd__assets{display:flex;gap:.75rem;flex-wrap:wrap;justify-content:center}
/* THE ACTION ROW IN ITS NEW HOME, directly beneath .hd--ident (Tony,
   2026-08-23). Measured on production first: on homesite-69 these controls sat
   at y=6272 at 390x844, 7.4 viewports below a price a buyer had just been sent.
   Constrained to the same max-width as the block above it so the row lines up
   with the price/spec content rather than the full page gutter. */
.hd__assets--top{max-width:var(--max);margin:0 auto;padding:0 2rem 1.25rem;
  justify-content:flex-start}
@media (max-width:760px){
  .hd__assets--top{padding:0 1.2rem 1rem;justify-content:center}
}
.hd__form{max-width:620px;margin:2.5rem auto 0;text-align:left}
.hd__soldbtns{display:flex;gap:.75rem;justify-content:center;flex-wrap:wrap;margin-top:1.5rem}
.hd__sold{padding-top:5rem}

/* RECENTLY SOLD — the near-real-time note on a home-detail page.
   Rendered hidden by build/generate.py and unhidden by assets/sold-hide.js when
   the live feed names this homesite. Deliberately quiet: this is a correction
   to a page a buyer was SENT, not an alert. It states the fact, keeps the home
   below it visible so the link still shows what was promised, and offers the
   two routes onward. [hidden] is already display:none!important sitewide
   (line ~3436), so the hidden state needs no rule of its own here.
   Tap targets inherit .btn, which is already >=44px. */
.hd__soldnote{padding:0;margin:0}
.hd__soldnote-in{max-width:var(--wrap,1120px);margin:0 auto;padding:1rem 1.25rem 1.15rem;
  border:1px solid var(--line,#d9dde3);border-left:4px solid var(--gold,#b6975f);
  border-radius:6px;background:var(--band,#f7f8fa)}
.hd__soldnote-k{font-size:.75rem;letter-spacing:.14em;text-transform:uppercase;
  font-weight:700;color:var(--gold,#b6975f);margin:0 0 .3rem}
.hd__soldnote-t{font-size:1.0625rem;font-weight:600;line-height:1.3;margin:0 0 .35rem}
.hd__soldnote-s{font-size:1rem;line-height:1.5;color:var(--mut,#6a7380);margin:0}
.hd__soldnote-btns{display:flex;gap:.6rem;flex-wrap:wrap;margin-top:.9rem}
@media(max-width:480px){
  .hd__soldnote-in{padding:.9rem 1rem 1rem;border-radius:0;border-left-width:4px}
  .hd__soldnote-btns .btn{flex:1 1 100%;text-align:center}
}
@media (max-width:900px){
  .hd{padding:var(--sp-5) var(--sp-5) 0}
  .hd__facts{grid-template-columns:repeat(auto-fit,minmax(130px,1fr))}
}
/* PHONE: the strip goes THREE columns, not two. Two columns of 8 stats is four
   rows ~62px tall — 248px of the 738px a 375x812 phone has under the header,
   which is what pushed the last cell below the fold. Three columns is three
   rows, and the longest value in the set ("≈10,020 sq ft") still fits a 103px
   cell at this size. Below 340px it falls back to two rather than truncating. */
@media (max-width:640px){
  .hd--ident .hd__facts{grid-template-columns:repeat(auto-fit,minmax(96px,1fr))}
  .hd__fact{padding:var(--sp-2) var(--sp-3)}
  .hd__fact dt{font-size:.66rem;letter-spacing:.06em}
  .hd__fact dd{font-size:.92rem}
  .hd__payunit .home__payamt{font-size:1.25rem}
}

/* ---------- This Home's Actual Costs ---------- */
/* Three fixed costs, each with the reason it is that number. The "why" column
   is the point of the section: a property-tax figure with no rate beside it is
   a number a buyer has to take on faith, and this page's whole premise is that
   these three are the ones they do not have to. */
.hcost{max-width:760px;margin:0 auto;border:1px solid rgba(var(--midnight-rgb),.10)}
.hcost__row{display:grid;grid-template-columns:minmax(0,1fr) auto;
  column-gap:var(--sp-5);row-gap:var(--sp-1);padding:var(--sp-4) var(--sp-5);
  align-items:baseline}
.hcost__row + .hcost__row{border-top:1px solid rgba(var(--midnight-rgb),.10)}
.hcost__k{font-family:'Jost',sans-serif;text-transform:uppercase;letter-spacing:.09em;
  font-size:.75rem;color:var(--muted-aa)}
.hcost__v{font-size:1.15rem;font-weight:500;text-align:right;white-space:nowrap}
.hcost__why{grid-column:1 / -1;margin:0;font-size:.82rem;color:var(--muted-aa);line-height:1.5}
.hcost__note{max-width:760px;margin:var(--sp-4) auto 0;font-size:.82rem;
  color:var(--muted-aa);line-height:1.6;text-align:center}
@media (max-width:640px){
  .hcost__row{padding:var(--sp-3) var(--sp-4);column-gap:var(--sp-3)}
  .hcost__v{font-size:1.05rem}
}


/* ---------- Grids ---------- */
/* align-items:stretch is the grid default and is written out on purpose: every
   card in a row is the height of the tallest, which is the precondition that
   makes bottom-pinning an action block mean anything. Stated rather than
   inherited so a future `align-items:start` added for one grid cannot quietly
   retire it for all of them. build/audit_card_alignment.py asserts the result. */
.grid{display:grid;gap:2rem;align-items:stretch}
.grid--3{grid-template-columns:repeat(3,1fr)}
.grid--4{grid-template-columns:repeat(4,1fr)}

/* ---------- Community cards ---------- */
.card{background:var(--white);border:1px solid var(--line);border-radius:var(--radius);overflow:hidden;box-shadow:var(--shadow);transition:transform var(--dur-3) var(--ease),box-shadow var(--dur-3) var(--ease)}
.card:hover{transform:translateY(-6px);box-shadow:var(--shadow-lift)}
.card__media{position:relative;aspect-ratio:4/3;overflow:hidden}
.card__media img{transition:transform 1.1s var(--ease)}
.card:hover .card__media img{transform:scale(1.06)}
.card__tag{position:absolute;top:1rem;left:1rem;background:rgba(var(--midnight-rgb),.72);backdrop-filter:blur(4px);color:var(--ivory);font-family:'Jost',sans-serif;font-weight:400;font-size:0.625rem;padding:0.5rem 0.75rem;border:1px solid rgba(var(--gold-soft-rgb),.4);border-radius:var(--radius);text-transform:uppercase;letter-spacing:.22em}
.card__body{padding:2rem 2rem 2rem}
.card__title{font-size:1.5rem;font-weight:600}
/* Image and name are both routes to the community page. The image link fills
   its box so the whole photo is the target; the name underlines on hover so it
   reads as a link and not just a heading that happens to be clickable. */
.card__imglink{position:absolute;inset:0;display:block;cursor:pointer}
.card__media--ph .card__imglink{display:flex;align-items:center;justify-content:center}
.card__title a{color:inherit;text-decoration:none;cursor:pointer;background-image:linear-gradient(var(--gold),var(--gold));background-repeat:no-repeat;background-position:0 92%;background-size:0 1px;transition:background-size var(--dur-3) var(--ease),color var(--dur-3) var(--ease)}
.card:hover .card__title a,.card__title a:hover{background-size:100% 1px}
.card__title a:hover{color:var(--gold-deep,var(--gold-deep))}
.card__imglink:focus-visible,.card__title a:focus-visible{outline:2px solid var(--gold);outline-offset:3px}
.card__meta{color:var(--muted);font-size:0.875rem;text-transform:uppercase;letter-spacing:.12em;margin:0.5rem 0 1rem}
.card__price{font-family:'Cormorant Garamond',serif;color:var(--midnight);font-weight:600;font-size:1.5rem}
/* HOA beneath the price — same label treatment as "From", quieter figure. */
.card__hoa{font-family:'Jost',sans-serif;font-size:0.8rem;color:var(--ink);margin:.1rem 0 0;font-variant-numeric:tabular-nums}
.card__hoa span{font-size:0.7rem;letter-spacing:.08em;text-transform:uppercase;color:var(--muted-aa);margin-right:.3rem}
.card__price span{color:var(--gold);font-size:1rem;font-family:'Jost',sans-serif;font-weight:400;letter-spacing:.05em}
.card__specs{font-size:0.875rem;color:var(--muted);margin:0.25rem 0 1.5rem;font-weight:300}
.card__divider{height:1px;background:var(--line);margin:0 0 1.5rem}

/* ---------- Quick move-in homes ---------- */
.home{background:var(--white);border:1px solid var(--line);border-radius:var(--radius);overflow:hidden;box-shadow:var(--shadow);transition:transform var(--dur-3) var(--ease),box-shadow var(--dur-3) var(--ease)}
.home:hover{transform:translateY(-6px);box-shadow:var(--shadow-lift)}
.home__media{position:relative;aspect-ratio:16/10;overflow:hidden}
.home__media img{transition:transform 1.1s var(--ease)}
.home:hover .home__media img{transform:scale(1.06)}
.badge{position:absolute;top:1rem;right:1rem;background:rgba(var(--midnight-rgb),.72);backdrop-filter:blur(4px);color:var(--ivory);font-family:'Jost',sans-serif;font-weight:400;font-size:0.625rem;padding:0.5rem 0.75rem;border:1px solid rgba(var(--gold-soft-rgb),.4);border-radius:var(--radius);text-transform:uppercase;letter-spacing:.22em}
.badge--uc{border-color:rgba(var(--gold-soft-rgb),.7);color:var(--gold-soft)}
/* A FLEX COLUMN so the Share control (assets/share-plan.js appends it here as
   the last child, 2026-08-11) lands bottom-right OF THE TEXT BLOCK via
   align-self:flex-end. Deliberately NOT `flex:1` — `.home` is not a flex
   container (styles.css:827), so flex:1 here would be inert, and making it one
   to gain a bottom-pin would restyle every card grid on the site to win a few
   pixels of alignment. Every child of this body is a full-width stacked block,
   so switching block -> flex column moves nothing that was already here. */
.home__body{padding:2rem 2rem 2rem;display:flex;flex-direction:column}
.home__price{font-family:'Cormorant Garamond',serif;font-weight:600;font-size:2rem;color:var(--midnight)}

/* =============================================================================
   THE LIGHT CARD — curated "Move-in ready" strips only (Tony, 2026-08-14).

   Photo, price, the payment unit, community + plan, badge. Everything else is
   behind the click. The markup difference is build/generate.py home_card(
   light=True); this only handles the spacing that changes when three blocks
   come out of the body.

   NOTHING HERE HIDES ANYTHING. There is deliberately no `.home--light
   .home__pay-unit{display:none}` or similar — a light card that dropped the
   note rate or the APR while keeping the payment figure would be a Reg Z
   violation dressed as a design decision, and a CSS rule is exactly how that
   would arrive quietly. The light card omits blocks at EMISSION or not at all,
   so what a buyer sees and what the HTML says are the same thing, and
   audit_card_payments.py reads the same DOM either way.
   ========================================================================== */
.home--light .home__body{padding:1.5rem 1.5rem 1.25rem;gap:.5rem}
/* The identity line is last in a light card, so its own bottom margin would
   hang below the payment block with nothing under it to balance. */
.home--light .home__ident{margin-bottom:0}
/* The pay unit sits directly under the price here rather than after a spec
   list, so it loses the top margin that separated it from one. */
.home--light .home__pay-unit{margin-top:.25rem}
@media (max-width:430px){
  /* 390px is the gated width. The body tightens; the price does NOT shrink —
     it is the first thing scanned and the reason the strip exists. */
  .home--light .home__body{padding:1.25rem 1.25rem 1rem}
}

/* A2 — THE PAYMENT UNIT ON EVERY CARD.
   Figure, terms and disclosure are one visual block because they are one legal
   unit: a stated monthly payment is a Reg Z triggering term and its terms must
   be read WITH it, not after a scroll. Hence no collapse, no tooltip, no
   "details" — and a left rule tying the three lines together so the eye reads
   them as one thing rather than three stacked paragraphs. */
.home__pay-unit{margin:0.35rem 0 0.75rem;padding:0 0 0 0.7rem;
  border-left:2px solid rgba(var(--gold-soft-rgb),.55)}
.home__payfig{display:flex;align-items:baseline;flex-wrap:wrap;gap:0.4rem;margin:0}
/* THE AMOUNT AND ITS UNIT ARE ONE TOKEN. "$1,847" wrapping away from "/mo"
   states a different number than the one intended, so this never breaks —
   build/audit_token_integrity.py asserts it at 375/768/1280. */
.home__payamt{font-family:'Jost',sans-serif;font-size:1.125rem;font-weight:600;
  letter-spacing:.01em;color:var(--midnight);white-space:nowrap}
.home__paylbl{font-family:'Jost',sans-serif;font-size:0.7rem;letter-spacing:.1em;
  text-transform:uppercase;color:var(--muted-aa);white-space:nowrap}
.home__payterms{margin:0.2rem 0 0;font-family:'Jost',sans-serif;font-size:0.7rem;
  line-height:1.55;letter-spacing:.01em;color:var(--muted-aa)}
/* Kept for any non-figure <b> a future terms line might carry. The rate and
   the APR are NOT bold and no longer use <b> at all — they render through
   .regz__eq, plain and equal. See that rule for why. */
.home__payterms b{font-weight:600;color:var(--ink);white-space:nowrap}
.home__paydisc{margin:0.25rem 0 0;font-family:'Jost',sans-serif;font-size:0.64rem;
  line-height:1.5;letter-spacing:.01em;color:var(--muted-aa)}
@media (max-width:400px){
  .home__payamt{font-size:1.05rem}
  .home__paydisc{font-size:0.62rem}
}
/* THE IDENTITY LINE. One size for both lines and every surface — see
   _ident_line() in build/generate.py for why the structure exists. The size
   dropped from .875rem to .8125rem and the tracking from .12em to .09em when
   the line became two lines: the same text now has two line-boxes to live in,
   so it does not need the extra width, and the narrower setting is what buys
   "Silver Springs Shores" its no-wrap guarantee inside a 375px card. */
.home__address{font-size:0.8125rem;text-transform:uppercase;letter-spacing:.09em;
  margin:0.5rem 0 1rem;line-height:1.45}
.home__ident-comm{display:block;color:var(--midnight);font-weight:500;
  /* NOWRAP IS THE GUARANTEE, ellipsis is the seatbelt. The longest community
     name measures ~200px against ~270px of card at 375px, so the ellipsis is
     unreachable today; it exists so a future 30-character community truncates
     visibly instead of blowing the card's width out sideways. */
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.home__ident-meta{display:block;color:var(--muted-aa);font-weight:400}
/* Each token is atomic. The only breakable whitespace on line 2 is the single
   space between these two spans, which is the phrase boundary. */
.home__ident-plan,.home__ident-meta .home__type{white-space:nowrap}
.home:hover .home__ident-comm{color:var(--gold)}
.home__specs{list-style:none;display:flex;gap:1.5rem;flex-wrap:wrap;font-size:0.75rem;font-weight:400;text-transform:uppercase;letter-spacing:.1em;color:var(--ink);border-top:1px solid var(--line);padding-top:1rem}
.home__specs li{position:relative}
.home__specs li:not(:last-child)::after{content:"";position:absolute;right:-.75rem;top:50%;width:1px;height:12px;transform:translateY(-50%);background:var(--gold);opacity:.5}
.home__note{margin-top:1rem;font-size:0.75rem;text-transform:uppercase;letter-spacing:.12em;color:var(--gold)}
.home__link{display:inline-block;margin-top:1.5rem}

/* ---------- Floor plans ---------- */
/* Column layout so the action row can be pinned to the card's foot. The
   "Offered in" list runs 1–6 lines depending on the plan, which left the
   brochure buttons stepping up and down across a row. */
.plan{background:var(--white);border:1px solid var(--line);border-radius:var(--radius);overflow:hidden;box-shadow:var(--shadow);transition:transform var(--dur-3) var(--ease),box-shadow var(--dur-3) var(--ease);display:flex;flex-direction:column}
.plan:hover{transform:translateY(-6px);box-shadow:var(--shadow-lift)}
.plan__media{aspect-ratio:4/3;background:var(--cream);overflow:hidden}
.plan__media img{object-fit:cover;transition:transform 1.1s var(--ease)}
.plan:hover .plan__media img{transform:scale(1.06)}
.plan__body{padding:1.5rem 1.5rem 2rem;flex:1;display:flex;flex-direction:column}
.plan__title{font-size:1.5rem;font-weight:600}
.plan__specs{color:var(--muted);font-size:0.75rem;text-transform:uppercase;letter-spacing:.1em;margin:0.5rem 0 0.75rem}
.plan__price{font-family:'Cormorant Garamond',serif;color:var(--midnight);font-weight:600;font-size:1.5rem}
.plan__tag{margin-top:0.5rem;font-size:0.75rem;color:var(--muted);line-height:1.5;font-weight:300}

/* Floor-plan video-tour button */
.plan__media{position:relative;display:block;width:100%;padding:0;border:0;cursor:pointer}
button.plan__media{cursor:pointer}
.plan__play{position:absolute;bottom:1rem;left:1rem;background:rgba(var(--midnight-rgb),.78);backdrop-filter:blur(4px);color:var(--ivory);font-family:'Jost',sans-serif;font-weight:400;font-size:0.75rem;text-transform:uppercase;letter-spacing:.16em;padding:0.5rem 1rem;border:1px solid rgba(var(--gold-soft-rgb),.4);border-radius:var(--radius);display:inline-flex;align-items:center;gap:0.5rem;transition:background var(--dur-2) var(--ease),color var(--dur-2) var(--ease)}
.plan__play svg{width:11px;height:11px}
button.plan__media:hover .plan__play{background:var(--gold);color:var(--midnight);border-color:var(--gold)}

/* ---------- Why buy with me ---------- */
.why{display:grid;grid-template-columns:auto 1fr;gap:4rem;align-items:center}
.why__photo{position:relative;width:300px;aspect-ratio:4/5;flex:0 0 auto}
.why__photo img{border-radius:var(--radius);position:relative;z-index:var(--z-local-raise)}
/* Thin gold gallery frame, offset behind the portrait */
.why__photo::before{content:"";position:absolute;inset:0;transform:translate(16px,16px);border:1px solid var(--gold);border-radius:var(--radius);z-index:var(--z-local-base)}
.why__content .section__eyebrow{display:block;margin-bottom:1rem}
.why__content .section__title{font-size:clamp(2rem,4vw,2.8rem)}
.why__content .section__title::after{content:"";display:block;width:54px;height:1px;background:var(--gold);margin:1.5rem 0 0}
.why__list{list-style:none;display:grid;gap:2rem;margin-top:2rem}
.why__list li{display:flex;gap:1.5rem;align-items:flex-start}
.why__icon{flex:0 0 auto;width:40px;height:40px;display:flex;align-items:center;justify-content:center;border:1px solid var(--gold);border-radius:50%;color:var(--gold)}
.why__icon svg{width:19px;height:19px}
.why__list h3{font-size:1.5rem;font-weight:600;margin-bottom:0.25rem}
.why__list p{color:var(--muted);font-size:1rem;font-weight:300}

/* ---------- Contact ---------- */
/* Honeypot: pulled off-screen (not display:none, which naive bots skip) */
.hp{position:absolute!important;left:-9999px;width:1px;height:1px;overflow:hidden}
.contact{display:grid;grid-template-columns:1.35fr 1fr;gap:3rem;align-items:start}
.contact__form{background:var(--white);border:1px solid var(--line);border-radius:var(--radius);padding:3rem;box-shadow:var(--shadow);display:grid;gap:1.5rem}
.contact__form label{display:grid;gap:0.5rem;font-family:'Jost',sans-serif;font-weight:500;text-transform:uppercase;letter-spacing:.16em;font-size:0.75rem;color:var(--midnight)}
.contact__form input,.contact__form textarea{
  font-family:'Jost',sans-serif;font-weight:300;font-size:1rem;letter-spacing:.01em;
  padding:1rem 1rem;border:1px solid var(--line);border-radius:var(--radius);
  background:var(--ivory);color:var(--ink);transition:border-color var(--dur-2) var(--ease),box-shadow var(--dur-2) var(--ease);
}
.contact__form input::placeholder,.contact__form textarea::placeholder{color:var(--placeholder);text-transform:none;letter-spacing:normal}
.contact__form input:focus,.contact__form textarea:focus{outline:none;border-color:var(--gold);box-shadow:0 0 0 3px rgba(var(--gold-rgb),.12)}
.contact__form .btn{margin-top:0.5rem}
.contact__note{font-family:'Jost',sans-serif;font-size:0.875rem;font-weight:400;letter-spacing:.04em;text-align:center;min-height:1.2em}
.contact__side{background:var(--midnight);color:var(--ivory);border-radius:var(--radius);padding:3rem;display:grid;gap:1rem;align-content:start}
.contact__side h3{color:var(--ivory);font-size:1.5rem;font-weight:600;margin-bottom:0.75rem}
.contact__side .btn{justify-content:flex-start}
.contact__hours{margin-top:1.5rem;padding-top:1.5rem;border-top:1px solid rgba(var(--gold-soft-rgb),.25);font-size:0.875rem;font-weight:300;opacity:.85;line-height:1.7}
.contact__hours p{margin:0}
.contact__hours strong{font-family:'Jost',sans-serif;font-weight:500;text-transform:uppercase;letter-spacing:.16em;font-size:0.625rem;color:var(--gold-soft)}

/* ---------- Footer ---------- */
.footer{background:var(--midnight);color:rgba(var(--ivory-rgb),.7);padding:4rem 2rem 2rem}
.footer a{color:rgba(var(--ivory-rgb),.7);transition:color var(--dur-2) var(--ease)}
.footer a:hover{color:var(--gold-soft)}
.footer__grid{max-width:var(--max);margin:0 auto;display:grid;grid-template-columns:repeat(3,1fr);gap:2rem}
.footer h4{color:var(--ivory);margin-bottom:1rem;font-size:0.75rem;font-family:'Jost',sans-serif;font-weight:500;text-transform:uppercase;letter-spacing:.2em}
.footer__brand{font-family:'Cormorant Garamond',serif!important;font-size:1.5rem!important;font-weight:600!important;text-transform:none!important;letter-spacing:.03em!important;color:var(--ivory)!important}
.footer p{font-weight:300;font-size:0.875rem;line-height:1.9}
.footer__social{list-style:none;display:grid;gap:0.5rem;font-size:0.75rem;text-transform:uppercase;letter-spacing:.14em;font-weight:300}
.footer__social a{display:inline-flex;align-items:center;gap:0.5rem}
.footer__social svg{width:17px;height:17px;flex:0 0 auto}
.footer__eho{display:flex;flex-direction:column;align-items:flex-start;gap:0.75rem}
.footer__eho-logo{width:46px;height:46px;object-fit:contain;opacity:.8}
.footer__eho p{text-transform:uppercase;letter-spacing:.14em;font-size:0.75rem}
.footer__disclaimer{max-width:var(--max);margin:3rem auto 0;padding-top:2rem;border-top:1px solid rgba(var(--gold-soft-rgb),.16);font-size:0.75rem;color:rgba(var(--ivory-rgb),.42);line-height:1.8;font-weight:300}
.footer__disclaimer p{margin-bottom:0.75rem}
/* PORTAL SKIN ONLY (newconstructionocalafl.com). Operator identification and
   data provenance. Deliberately brighter than the rest of the fine print: on a
   product-first brand this is the line that says who is behind the site, and
   burying it at .42 opacity with the copyright would be disclosure in name
   only. Emitted by build/portal.py; absent from the main skin, which names
   Tony in the footer heading already. */
.footer__operator{color:rgba(var(--ivory-rgb),.78);font-size:0.8rem}
.footer__source{color:rgba(var(--ivory-rgb),.62);font-size:0.78rem}
/* The footer never had a mobile breakpoint — it stayed repeat(3,1fr) at every
   width, and grid items' min-width:auto widened the tracks until the Equal
   Housing column sat at 471px on a 375px screen, i.e. entirely off-screen.
   html/body overflow-x:clip hid the sideways scroll, so it just vanished. */
@media (max-width:760px){
  .footer__grid{grid-template-columns:1fr;gap:2rem}
  .footer__grid > *{min-width:0}
}

/* ---------- Sticky mobile call bar ---------- */
.mobile-callbar{display:none;position:fixed;bottom:0;left:0;right:0;z-index:var(--z-sticky-header);background:var(--midnight);color:var(--ivory);text-align:center;font-family:'Jost',sans-serif;font-weight:500;text-transform:uppercase;letter-spacing:.18em;font-size:0.75rem;padding:1rem;border-top:1px solid rgba(var(--gold-soft-rgb),.4)}

/* =================================================================
   RESPONSIVE
   ================================================================= */
@media (max-width:900px){
  .grid--4{grid-template-columns:repeat(2,1fr)}
  .why{grid-template-columns:1fr;gap:3rem;justify-items:center;text-align:center}
  .why__content .section__title::after{margin-left:auto;margin-right:auto}
  .contact{grid-template-columns:1fr}
  /* Hamburger from tablet down. The inline links are hidden entirely and replaced
     by the full-screen .mnav overlay — no partial/clipped dropdown. */
  .nav__links{display:none}
  /* OPAQUE ONCE SCROLLED (2026-09-25). This set BOTH states to .92, so at
     390px the page ghosted through the header the whole way down. The
     unscrolled state keeps its translucency on purpose — that is the
     transparent-over-hero treatment the ::before scrim above exists for. */
  .nav{background:rgba(var(--midnight-rgb),.92);backdrop-filter:blur(10px)}
  .nav.scrolled{background:rgb(var(--midnight-rgb));backdrop-filter:blur(10px);
    box-shadow:0 1px 0 rgba(var(--gold-soft-rgb),.28)}
  .nav__toggle{display:block}
}
/* ---------------------------------------------------------------------------
   HEADER — GAPS ARE STRUCTURAL, COLLAPSE IS MEASURED.

   This replaces four hand-tuned width bands. Those bands encoded, in pixels,
   how wide the nav *happened* to be with Jost at 16px root and Chrome's metrics.
   Every one of those assumptions is a user setting: bump the browser's default
   font size, zoom to 110%, or load with the webfont still swapping and the real
   content width moves while the breakpoints do not. That is how a header that
   passes at 1300 crowds at 1300 on somebody else's machine.

   So the pixel geometry is no longer expressed as breakpoints. It is expressed
   as MINIMUM SPACING that cannot be squeezed:
       32px  wordmark -> nav          .nav__links margin-left
       22px  between nav items        .nav__links gap        (floor is 16)
       22px  last item -> pill        same flex gap
       16px  nav -> phone/MORE        .nav__tel / .nav__toggle margin-left
       28px  viewport edge            .nav__inner padding
   Nothing tightens these. When the content no longer fits, items COLLAPSE into
   the MORE menu instead — which is the rule Tony set: collapsing early beats
   crowding, always. Deciding *when* needs the rendered width, which only the
   browser knows, so script.he6fbd5d35b.js measures it (see fitNav) and sets the classes
   below. The wordmark is never part of that negotiation: nav flexes, brand does
   not.

   Enforced at every width from 375 to 1920 in 50px steps by
   build/audit_header.py — as computed geometry, not screenshots. A header that
   is crowded but not overflowing fails that gate.
--------------------------------------------------------------------------- */
@media (min-width:901px){
  /* The header gets more room than the 1200px content column. Nothing else on
     the page moves; only .nav__inner widens. */
  .nav__inner{max-width:1340px;padding-left:1.75rem;padding-right:1.75rem}
  .nav__links{margin-left:32px;gap:22px}
  /* .1em, not .09em. The base is .12em; desktop buys a little width back at
     the larger size without dropping to the old cramped tracking. */
  .nav__links a{letter-spacing:.1em}
  .nav__tel{margin-left:16px}
  /* The phone number is the first thing to go — it is a duplicate of the icon,
     which stays tappable. fitNav adds this before it hides any destination. */
  .nav--tight .nav__telNum{display:none}
  /* 44px, not 38px. Once the label is gone this IS the call control, and a
     control that only exists at the tight widths is exactly the one that never
     gets measured — WCAG 2.5.8 applies to it like everything else. */
  .nav--tight .nav__tel{width:44px;height:44px;justify-content:center;
    border:1px solid rgba(var(--gold-soft-rgb),.38);border-radius:50%}
  /* Collapse: fitNav marks links itself, in priority order, because the hrefs
     differ page to page and the labels do not. Each step only ever REMOVES an
     item from the bar; every destination stays reachable in the MORE overlay,
     which already carries the full list. */
  .nav--auto .nav__links a.nav__drop{display:none}
  /* MORE appears the moment anything is hidden, and says so — a bare hamburger
     beside visible links reads as a duplicate rather than as "there is more". */
  /* MORE is a nav item and now reads as one: same size, weight, tracking and
     rest colour as its siblings (2026-08-13). At .68rem/300/opacity .82 it was
     the smallest and faintest thing in the bar while being the control that
     reveals everything the bar could not fit. */
  .nav--more .nav__toggle{display:inline-flex;align-items:center;gap:.4rem;
    margin-left:16px;color:var(--ivory);opacity:1;font-family:'Jost',sans-serif;
    font-weight:400;text-transform:uppercase;letter-spacing:.12em;font-size:.875rem;
    line-height:1;white-space:nowrap;flex:none}
  .nav--more .nav__toggle::after{content:"More"}
  .nav--more .nav__toggle svg{width:20px;height:20px}
  .nav--more .nav__toggle:hover{opacity:1;color:var(--gold-soft)}
  body.mnav-open .nav__tel{display:none}   /* never competes with the open menu */
  /* NO-JS FALLBACK. fitNav sets .nav--auto as soon as it runs; without it the
     old conservative band still applies, so a scripting failure degrades to a
     slightly sparse header rather than a crowded one. */
  .nav:not(.nav--auto) .nav__links a[href="#why"],
  .nav:not(.nav--auto) .nav__links a[href="/#why"]{display:none}
  .nav:not(.nav--auto) .nav__telNum{display:none}
}
@media (min-width:901px) and (max-width:1199px){
  .nav:not(.nav--auto) .nav__links a[href="/homes.html"]{display:none}
  .nav:not(.nav--auto) .nav__toggle{display:inline-flex;align-items:center;
    gap:.4rem;margin-left:16px;color:var(--ivory);opacity:1;
    font-family:'Jost',sans-serif;font-weight:400;text-transform:uppercase;
    letter-spacing:.12em;font-size:.875rem;line-height:1;white-space:nowrap}
  .nav:not(.nav--auto) .nav__toggle::after{content:"More"}
  .nav:not(.nav--auto) .nav__toggle svg{width:20px;height:20px}
}
@media (max-width:640px){
  .nav__links{display:none}
  /* OPAQUE ONCE SCROLLED (2026-09-25). This set BOTH states to .92, so at
     390px the page ghosted through the header the whole way down. The
     unscrolled state keeps its translucency on purpose — that is the
     transparent-over-hero treatment the ::before scrim above exists for. */
  .nav{background:rgba(var(--midnight-rgb),.92);backdrop-filter:blur(10px)}
  .nav.scrolled{background:rgb(var(--midnight-rgb));backdrop-filter:blur(10px);
    box-shadow:0 1px 0 rgba(var(--gold-soft-rgb),.28)}
  .nav__toggle{display:block}
  .grid--3,.grid--4{grid-template-columns:1fr}
  .hero{min-height:62svh}
  .why__photo{width:240px}
  /* BOTTOM SAFE AREA — 56px cleared the 44px call bar and nothing else. The chat
     launcher is pinned at bottom:68px and stands 56px tall, so the real
     obstructed band is 124px and the last row of any page sat inside it.
     Measured 2026-09-04 at 390x844: launcher box 107x56 at bottom:68.
     One token so the bar, the launcher and this padding cannot drift apart. */
  body{padding-bottom:var(--bottom-chrome)}
  /* `flex`, not `block` — the 44px touch-target rule near the end of this file
     centres the label with align-items/justify-content and needs a flex box to
     do it. It used to declare `display:flex` itself, at top level, which turned
     this mobile-only bar on at every width; see the comment there. THIS IS THE
     ONLY RULE THAT MAKES THE CALL BAR VISIBLE. */
  .mobile-callbar{display:flex}
}
@media (min-width:641px) and (max-width:900px){
  .grid--3{grid-template-columns:repeat(2,1fr)}
}

/* Lightbox (unchanged behaviour, refined chrome) */
.lightbox{position:fixed;inset:0;z-index:var(--z-modal);background:rgb(var(--midnight-rgb));display:none;align-items:center;justify-content:center;padding:1.5rem}
.lightbox.open{display:flex}
.lightbox__video{width:min(960px,100%);max-height:86dvh;border-radius:var(--radius);background:var(--video-bg);box-shadow:0 30px 80px rgba(var(--black-rgb),.6)}
.lightbox__close{position:absolute;top:1.2rem;right:1.5rem;background:none;border:none;color:var(--ivory);font-size:2rem;line-height:1;cursor:pointer;padding:0.25rem 0.5rem}
.lightbox__close:hover{color:var(--gold)}

/* Subtle scroll-in reveal. Fires once. `--i` is the stagger index within a
   group, set by script.he6fbd5d35b.js; 70ms apart reads as a wave, not a queue. */
@media (prefers-reduced-motion:no-preference){
  .reveal{opacity:0;transform:translateY(18px);
    transition:opacity var(--dur-3) var(--ease),transform var(--dur-3) var(--ease);
    transition-delay:calc(var(--i,0) * 70ms)}
  .reveal.in{opacity:1;transform:none}
}

/* =================================================================
   SUBPAGES — community detail, floor-plan library, partners
   ================================================================= */
/* HERO LAW, 2026-08-20 (the Tartan Farms cropped house).
   min-height:58svh + object-fit:cover over a STILL is what sliced a house in
   half on 2,506 pages. The fixed box is gone and so are the seven per-page
   `style="min-height:NNvh"` overrides that sat on top of it.

   THE ASPECT IS NOW DECLARED, NOT IMPOSED. 16/7 at desktop and 4/5 on a phone
   are the frames the footage is composed for, so the video fills them instead
   of being cut to fit a height somebody picked. There is still a cover fit —
   there has to be, the reel is one 16:9 cut — but a cropped MOVING frame reads
   as cinematography and a cropped still reads as a mistake, which is the whole
   difference the ruling is about.

   min-height is a FLOOR, not a height: short copy cannot collapse the band, and
   long copy grows it rather than being cropped out of it. That is what "no
   letterboxed crops at any viewport" needs — nothing is ever fitted into a box
   whose size was chosen before the content. */
/* HERO HEIGHT CAP (2026-08-31, corrected 09:30). The first cut added
   max-height:420px on top of aspect-ratio:16/7 — but once height is pinned,
   aspect-ratio solves for WIDTH, and the hero collapsed to 960px wide on every
   desktop, leaving a grey void down the right third (shipped 08:27, caught on
   /map at 09:17). The desktop band is a fixed height with no ratio at all:
   width is always 100%, nothing can solve for it. Phones keep their own 4/5
   rule below. min-height stays as a floor so long copy still grows the band. */
/* THE BAND IS A FLOOR, NOT A CEILING (2026-09-02).

   `height:420px` + `align-items:flex-end` + `overflow:hidden` is a clipping
   machine: when the content column is taller than the band, flex-end pushes the
   excess off the TOP and overflow:hidden eats it. Measured on /partners, the
   only page whose hero carries a crumb, an H1, a rule, a blurb AND the 6-tile
   partner strip: the H1 sat at y=-103 against a header bottom of 87 at 390px,
   and y=49 at 768px. Entirely invisible, on the page's own title.

   WHY THIS SURVIVED SINCE 2026-08-31. Commit 71d02b6714 verified at six widths
   on /map and /communities/tartan-farms. Neither page's hero content can reach
   the top of the band — measured, their H1s sit at y=320 and y=271 — so the
   check was run on the two surfaces that CANNOT exhibit the defect. The pages
   were fine and the verification was true; it was answering a narrower question
   than its name.

   min-height keeps 71d02b6714's intent intact — width:100%, no aspect-ratio, so
   the ratio cannot solve for width and reopen the grey void that commit fixed —
   while letting a tall hero grow instead of clip. A hero whose content fits is
   pixel-identical: /map and tartan-farms measure 488px at 390 before and after.

   PADDING-TOP NOW TRACKS THE REAL HEADER. It read 74px while :root has said
   --nav-h:87px; the 13px shortfall is a latent band of content sitting under
   the sticky header, invisible until something reaches the top of the hero. */
.page-hero{position:relative;width:100%;min-height:420px;display:flex;align-items:flex-end;color:var(--ivory);overflow:hidden;margin-top:calc(-1 * var(--nav-h));padding-top:var(--nav-h)}
/* 125vw IS the 4/5 ratio on a full-width element, expressed as a MINIMUM so a
   tall hero can grow past it. `aspect-ratio` pinned the height and was the
   mobile half of the clip. Renders identically wherever the content fits. */
@media(max-width:767px){.page-hero{height:auto;min-height:max(22rem,125vw)}}
.page-hero__img{position:absolute;inset:0;z-index:var(--z-local-behind)}
.page-hero__img img{width:100%;height:100%;object-fit:cover}
/* The motion layer. Same box, same cover fit, and the poster is what paints
   first — so a reduced-motion, Save-Data or JS-off visitor gets the photograph
   that used to be the whole hero, undegraded. */
.page-hero__video{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;
  z-index:var(--z-local-behind);background:var(--midnight)}
.page-hero__overlay{position:absolute;inset:0;z-index:var(--z-local-under);background:linear-gradient(180deg,rgba(var(--midnight-deep-rgb),.30) 0%,rgba(var(--midnight-deep-rgb),.55) 55%,rgba(var(--midnight-deep-rgb),.86) 100%)}
/* NARROW-VIEWPORT SCRIM. At 375px the hero title sits lower in the frame — the
   partners stat tiles are two-line and taller than the inline text they
   replaced — which moved the title over a brighter band of the photograph and
   dropped /partners page-hero__title from 7.47 to 6.08. Deepened here rather
   than re-baselined: 6.08 still clears the 3.0 floor, but the baseline exists
   to catch exactly this kind of silent drift. Deepening also darkens what sits
   behind the stat tiles, whose own rgba(0,0,0,.20) fill needs the help. */
@media(max-width:767px){
  .page-hero__overlay{background:linear-gradient(180deg,
    rgba(var(--midnight-deep-rgb),.46) 0%,
    rgba(var(--midnight-deep-rgb),.66) 55%,
    rgba(var(--midnight-deep-rgb),.90) 100%)}
}
/* The horse-country hero is 1.74:1 but the band it sits in runs from ~1:1 at
   375px to ~3.6:1 at 1440px. Holding the crop just above centre keeps the oak
   line and the board fence in frame at every width instead of landing on bare
   grass at desktop. */
.page-hero__img img{object-position:center 38%}
/* Attribution on a section photo (CC BY 2.0 requires it wherever the photo runs).
   Sits inside .dsc__media, which is position:relative, so it rides the image. */
.dsc__credit{position:absolute;right:.5rem;bottom:.4rem;z-index:var(--z-local-over);margin:0;
  font-family:'Jost',sans-serif;font-size:0.625rem;letter-spacing:.04em;
  color:rgba(255,255,255,.72);text-shadow:0 1px 3px rgba(0,0,0,.6)}
.dsc__credit a{color:inherit;text-decoration:underline;text-underline-offset:2px}
.dsc__credit a:hover{color:var(--ivory)}
.page-hero__credit{position:absolute;right:1rem;bottom:.5rem;z-index:var(--z-local-raise);margin:0;
  font-family:'Jost',sans-serif;font-size:0.625rem;letter-spacing:.08em;color:rgba(var(--ivory-rgb),.62)}
.page-hero__credit a{color:inherit;text-decoration:underline;text-underline-offset:2px}
.page-hero__credit a:hover{color:var(--ivory)}
@media (max-width:640px){.page-hero__credit{right:.7rem;font-size:0.625rem}}
.page-hero__inner{max-width:var(--max);margin:0 auto;width:100%;padding:0 2rem 4rem;position:relative}
/* TEXT-PROTECTION SCRIM — behind the copy ONLY, not the hero.
   The hero copy sits ~30-55% down the hero, inside the overlay's weakest band
   (.30), so ivory eyebrows measured 3.3-4.4 against a 4.5 need on the brighter
   community photos. This is NOT a global scrim: it is a soft plume anchored to
   the text column that fades to nothing well before the hero edges, so the
   photograph is untouched everywhere except immediately behind the words.
   Left-anchored to follow the left-set copy; every hero is still measured by
   the contrast gate at 375 and 1440. */
.page-hero__inner::before{content:"";position:absolute;z-index:var(--z-local-under);
  left:-8%;right:20%;top:-6%;bottom:-4%;pointer-events:none;
  background:radial-gradient(120% 90% at 22% 50%,
    rgba(var(--midnight-deep-rgb),.62) 0%,
    rgba(var(--midnight-deep-rgb),.42) 42%,
    rgba(var(--midnight-deep-rgb),0) 75%)}
@media (max-width:640px){
  .page-hero__inner::before{left:-12%;right:-6%;
    background:radial-gradient(150% 80% at 40% 55%,
      rgba(var(--midnight-deep-rgb),.66) 0%,
      rgba(var(--midnight-deep-rgb),.44) 46%,
      rgba(var(--midnight-deep-rgb),0) 80%)}
}
.page-hero__eyebrow{display:block;margin-bottom:1rem;color:var(--gold-soft)}
/* IVORY EYEBROWS ON PHOTO HEROES.
   Champagne #cbb489 cannot reach AA 4.5:1 over a bright hero photo (measured
   1.90-3.91). Ivory does, and it keeps the imagery — no global scrim bump.
   Champagne is retained ONLY on the placeholder-gradient heroes (.--ph), which
   are solid dark and where it already passes. Scoped with :has() so the rule
   follows the hero's actual backdrop, not a hand-maintained page list. Every
   one of the 17 heroes is still measured per-side by the contrast gate — this
   is the fix, the gate is the proof. */
.page-hero:not(:has(.page-hero__img--ph)) .page-hero__eyebrow,
.page-hero:not(:has(.page-hero__img--ph)) .crumb,
.page-hero:not(:has(.page-hero__img--ph)) .crumb a{color:var(--ivory)}
.page-hero__title{font-size:clamp(2.4rem,5.2vw,3.7rem);font-weight:500;color:var(--ivory);line-height:1.05;text-wrap:balance}
.page-hero__rule{display:block;width:56px;height:1px;background:var(--gold-soft);margin:1.5rem 0}
.page-hero__blurb{max-width:62ch;font-family:'Jost',sans-serif;font-weight:300;color:rgba(var(--ivory-rgb),.9);font-size:clamp(1rem,1.4vw,1.12rem)}
.crumb{font-family:'Jost',sans-serif;text-transform:uppercase;letter-spacing:.2em;font-size:0.75rem;color:var(--gold-soft);margin-bottom:1.5rem;display:inline-block}
.crumb a{color:var(--gold-soft)}
.crumb a:hover{color:var(--ivory)}

/* count line under the section head */
.section__count{font-family:'Jost',sans-serif;text-transform:uppercase;letter-spacing:.2em;font-size:0.75rem;color:var(--gold);margin-top:1rem}

/* THE DATA DATE (CLAUDE.md §2 — every surface showing inventory states it).
   Emitted by generate.data_date_line() and by agent_links.py's copy of the same
   sentence; the sentence itself is config.DATA_DATE_SENTENCE.
   Deliberately QUIETER than .section__count and not uppercase: it is a
   provenance stamp, not a headline, and it must not compete with the count it
   sits under. 0.78rem keeps it legible on a phone without claiming the eye —
   CLAUDE.md §3's 16px floor is for BODY text, and this is a caption, so it is
   sized against .section__count (0.75rem) which has always shipped beside it.
   margin-top is small because it belongs to the count above it, not to the
   filter bar below.
   --muted-aa, NOT --muted: the :root comment above says --muted is 3.68:1 and
   BELOW WCAG AA, and reserves it for decorative text. A date a buyer is meant
   to READ — it is the only thing on the page saying how old the prices are — is
   informational by definition, so it takes the 5.47:1 token. */
.section__asof{font-family:'Jost',sans-serif;letter-spacing:.06em;font-size:0.78rem;color:var(--muted-aa);margin-top:.35rem}

/* home cards without an interior photo — elegant text-forward variant */
.home--txt{display:flex;flex-direction:column;justify-content:space-between}
.home--txt .home__body{padding:2rem 2rem 2rem}
.home__plan{font-family:'Cormorant Garamond',serif;font-size:1.5rem;font-weight:600;color:var(--midnight);margin-top:0.25rem}
.home__badge-row{display:flex;align-items:center;gap:0.75rem;margin-bottom:1rem}
.home__badge{display:inline-block;font-family:'Jost',sans-serif;font-weight:400;font-size:0.625rem;padding:0.25rem 0.75rem;border:1px solid var(--line);border-radius:var(--radius);text-transform:uppercase;letter-spacing:.18em;color:var(--midnight)}
.home__badge--uc{color:var(--gold);border-color:rgba(var(--gold-rgb),.5)}
.home__badge--mir{color:var(--verified);border-color:rgba(var(--verified-rgb),.4)}
.home__eta{font-size:0.75rem;text-transform:uppercase;letter-spacing:.12em;color:var(--muted)}
.home--txt .home__media .home__badge{position:absolute;top:1rem;right:1rem;background:rgba(var(--midnight-rgb),.72);backdrop-filter:blur(4px);color:var(--ivory);border-color:rgba(var(--gold-soft-rgb),.4)}

/* coming-soon empty state */
.emptystate{text-align:center;max-width:560px;margin:0 auto;padding:3rem 0 1rem}
.emptystate h3{font-size:2rem;font-weight:600;margin-bottom:1rem}
.emptystate p{color:var(--muted);margin-bottom:2rem}

/* contact CTA band at the bottom of subpages */
.cta-band{background:var(--midnight);color:var(--ivory);text-align:center;padding:6rem 2rem}
.cta-band__eyebrow{display:block;margin-bottom:1rem;color:var(--gold-soft)}
.cta-band h2{color:var(--ivory);font-size:clamp(2rem,4vw,2.8rem);font-weight:500}
.cta-band__rule{display:block;width:54px;height:1px;background:var(--gold);margin:1.5rem auto 2rem}
.cta-band__btns{display:flex;gap:1rem;justify-content:center;flex-wrap:wrap;margin-top:0.5rem}

/* floor-plan library cards */
.plan__price-row{display:flex;align-items:baseline;justify-content:space-between;gap:0.5rem;margin-top:0.25rem}
.plan__from{font-family:'Cormorant Garamond',serif;color:var(--midnight);font-weight:600;font-size:1.5rem}
.plan__from span{color:var(--gold);font-size:0.75rem;font-family:'Jost',sans-serif;font-weight:400;letter-spacing:.05em}
.plan__stories{font-size:0.75rem;text-transform:uppercase;letter-spacing:.12em;color:var(--muted)}
/* THE AUTO MARGIN MOVED DOWN ONE ELEMENT, 2026-08-18, AND THERE IS NOW EXACTLY
   ONE OF THEM IN THIS COLUMN.

   This rule used to carry margin-bottom:auto, under a comment claiming it
   "pins .plan__actions to the foot of every card". It did — until
   assets/share-plan.js started appending a .share-btn as the LAST child of
   .plan__body, and that button was given margin-top:auto (styles.css, the
   .home__body/.plan__body pair below). TWO auto margins in one flex column do
   not both win: the free space is SPLIT between them, so the action block
   floated at a height that varied with how long each card's "Offered in" line
   ran. Measured on production, /a/will-bernal/floor-plans at 1280px, four cards
   in one row with an identical card bottom of 8083: their action blocks ended
   at 7938, 7944, 7982 and 8006. Nothing was pinned to anything.

   Both declarations were individually correct and each one's comment described
   a behaviour the other cancelled — the same shape as the .hcar z-index that
   was trapped in a stacking context (2026-08-13), four lines from a comment
   asserting it worked. The fix is not to add a third rule; it is that the
   column gets ONE auto margin and it belongs on the block being pinned. */
.plan__where{margin-top:1rem;padding-top:1rem;border-top:1px solid var(--line);font-size:0.875rem;color:var(--muted);font-weight:300;line-height:1.7}
/* always-visible realtor actions on a floor-plan card. margin-top:auto eats the
   leftover column space, so this block and the share row beneath it sit on the
   floor of the card in the fixed order View Brochure -> Watch Video Tour ->
   share. build/audit_card_alignment.py measures it at 390 and 1280. */
.plan__actions{display:flex;flex-wrap:wrap;gap:0.5rem;margin-top:auto;padding-top:1rem;border-top:1px solid var(--line)}
/* The reserved slot. See build/generate.py _act_ghost() for why it is a twin of
   the real control rather than a height: visibility:hidden keeps the box in
   flow and takes it out of paint, and the element is a <span> with aria-hidden,
   so it is neither focusable nor announced. */
.plan__act--ghost{visibility:hidden;pointer-events:none}
/* white-space:nowrap, added 2026-08-18 with the alignment fix, and it is doing
   layout work rather than typography. Two of the thirty-seven plans have no
   brochure PDF, so on the main host they carry `.plan__act--req` — "Brochure on
   request" — whose longer label WRAPPED to two lines at 210px in a 222px content
   box and made that one control 59px tall against every other control's 44px.
   The action block is bottom-pinned, so a taller first control starts HIGHER,
   and Alford and Darwin sat 15px above their neighbours for that reason alone
   once the two larger causes were fixed. Measured both ways at both widths
   before it was written: at 390 it is inert (every control is already 44px on
   one line, and body.scrollWidth stays 390 with zero element spill); at 1280 it
   takes the worst first-action spread from 15px to 0 and puts all 74 controls
   at 44px, again with zero spill. A control label that wraps is a control that
   changes the height of the row it is in. */
.plan__act{display:inline-flex;align-items:center;gap:0.5rem;font-family:'Jost',sans-serif;text-transform:uppercase;letter-spacing:.13em;font-size:0.75rem;font-weight:500;border-radius:var(--radius);padding:0.5rem 0.75rem;white-space:nowrap;cursor:pointer;transition:border-color var(--dur-2) var(--ease),color var(--dur-2) var(--ease),background var(--dur-2) var(--ease)}
.plan__act svg{width:14px;height:14px;flex:none}
.plan__act--dl{background:var(--midnight);color:var(--ivory);border:1px solid var(--midnight)}
.plan__act--dl:hover{background:var(--gold);border-color:var(--gold);color:var(--midnight)}
.plan__act--req{background:none;color:var(--muted);border:1px dashed var(--line)}
.plan__act--req:hover{border-color:var(--gold);color:var(--gold)}
.plan__act--vid{background:none;color:var(--midnight);border:1px solid var(--line)}
.plan__act--vid:hover{border-color:var(--gold);color:var(--gold)}
/* floor-plan library: download-all-brochures toolbar */
.fp-toolbar{display:flex;flex-wrap:wrap;align-items:center;gap:0.5rem 1rem;margin-bottom:1.5rem;padding:1rem 1rem;background:var(--midnight);border-radius:var(--radius)}
.fp-dlall{display:inline-flex;align-items:center;gap:0.5rem;font-family:'Jost',sans-serif;text-transform:uppercase;letter-spacing:.14em;font-size:0.75rem;font-weight:600;color:var(--midnight);background:var(--gold);border-radius:var(--radius);padding:0.75rem 1rem;transition:background var(--dur-2) var(--ease)}
.fp-dlall svg{width:16px;height:16px}
.fp-dlall:hover{background:var(--gold-soft)}
.fp-dlall__meta{font-weight:400;letter-spacing:.1em;opacity:.72}
.fp-toolbar__note{margin:0;color:var(--ivory);opacity:.82;font-size:0.875rem;font-weight:300;line-height:1.5}
/* Per-community HOA inside the "Offered in" list. A plan spans communities with
   different dues, so the figure sits next to the community it belongs to and
   the plan never carries one of its own. */
.plan__hoa{font-family:'Jost',sans-serif;font-size:.68rem;letter-spacing:.04em;
  color:var(--gold);white-space:nowrap}
.plan__where b{display:block;font-family:'Jost',sans-serif;text-transform:uppercase;letter-spacing:.16em;font-size:0.625rem;color:var(--gold);font-weight:500;margin-bottom:0.5rem}
.plan__where a{color:var(--ink);border-bottom:1px solid var(--line);transition:border-color var(--dur-2) var(--ease),color var(--dur-2) var(--ease)}
.plan__where a:hover{color:var(--gold);border-color:var(--gold)}
.plan__wmore{display:inline;margin-left:0.25rem;background:none;border:0;padding:0;cursor:pointer;font:inherit;color:var(--gold);border-bottom:1px solid var(--gold);line-height:inherit}
.plan__wmore:hover{color:var(--ink);border-color:var(--ink)}
.plan__media--still{cursor:default}

/* section intro paragraph + "view all" link */
.viewall{text-align:center;margin-top:3rem}

/* ---------- Partners page ---------- */
.partner-intro{max-width:760px}
.partner-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(280px,1fr));gap:1.5rem}
.resource{background:var(--white);border:1px solid var(--line);border-radius:var(--radius);padding:1.5rem 1.5rem;box-shadow:var(--shadow);display:flex;flex-direction:column;gap:0.75rem}
.resource h3{font-size:1.25rem;font-weight:600}
.resource__meta{font-size:0.875rem;color:var(--muted);text-transform:uppercase;letter-spacing:.12em}
.resource__links{display:flex;flex-wrap:wrap;gap:0.5rem;margin-top:0.5rem}
.chip{display:inline-flex;align-items:center;gap:0.5rem;font-family:'Jost',sans-serif;text-transform:uppercase;letter-spacing:.14em;font-size:0.75rem;font-weight:500;padding:0.5rem 1rem;border:1px solid var(--midnight);border-radius:var(--radius);color:var(--midnight);transition:background var(--dur-2) var(--ease),color var(--dur-2) var(--ease)}
.chip:hover{background:var(--midnight);color:var(--ivory)}
.chip--muted{border-color:var(--line);color:var(--muted);cursor:default;pointer-events:none}
.chip--muted:hover{background:transparent;color:var(--muted)}
.chip svg{width:13px;height:13px}

/* -----------------------------------------------------------------------------
   PER-COMMUNITY WALKTHROUGH BUNDLES — /partners #model-videos (2026-09-20)

   The chip carries a SIZE, and the size is the point. These bundles run from
   13 MB to 375 MB; a chip reading only "Download All Twisted Oaks Videos" asks
   for 198 MB without saying so, and the person tapping it is usually on a phone.
   So .chip__meta is a second line inside the control rather than a tooltip or an
   aria-only string — it has to be readable before the tap, not after it.

   THE CHIP GOES TWO-LINE, WHICH MEANS IT STOPS BEING THE BASE .chip's ROW. The
   base is `display:inline-flex; align-items:center` with an icon beside one line
   of text. Adding a block child to that lays the meta out as a third flex ITEM
   on the same row, which at 390px wraps the size into its own ragged column and
   makes every chip in the strip a different height. The grid template below is
   the fix: icon in a fixed first column, label and meta stacked in the second.

   min-height:44px is the 2026-08-10 touch-target floor and applies here even
   though the two-line content already clears it — a community whose name is
   short enough to fit one line must not drop under the floor.
   ----------------------------------------------------------------------------- */
.vidzips{margin:0 0 1.5rem}
.vidzips__lead{color:var(--muted);font-size:1rem;margin:0 0 0.75rem;text-align:center}
.vidzips__row{justify-content:center}
.vidzips__row .chip{
  display:grid;grid-template-columns:13px 1fr;align-items:center;
  gap:0.15rem 0.5rem;min-height:44px;text-align:left;
  padding:0.5rem 0.9rem;max-width:min(100%,22rem)}
/* align-self:START, not center. These labels wrap to two lines at most widths
   ("Download All Autumn Glen Videos" / "(ZIP)"), and an icon centred across
   both rows lands beside the SECOND line — where it reads as decorating
   "(ZIP)" rather than marking the control. Verified in a browser at 1367px;
   the 0.15em nudge sits it on the cap-height of the first line. */
.vidzips__row .chip svg{grid-row:1 / span 2;align-self:start;margin-top:.15em}
.vidzips__row .chip__meta{
  grid-column:2;font-size:0.7rem;letter-spacing:.08em;
  text-transform:none;color:var(--muted);font-weight:400}
.vidzips__row .chip:hover .chip__meta{color:var(--ivory)}

/* =============================================================================
   MOBILE MENU — full-screen overlay (replaces the old clipped dropdown).
   Desktop never sees it; from 900px down it's the only menu.
   ========================================================================== */
.mnav{display:none}
@media (max-width:900px){
  .mnav{display:flex;position:fixed;inset:0;z-index:var(--z-modal);background:var(--midnight);
    flex-direction:column;justify-content:center;padding:5.5rem 1.75rem 2.5rem;
    overflow-y:auto;-webkit-overflow-scrolling:touch;
    opacity:0;visibility:hidden;transform:translateY(-10px);
    transition:opacity var(--dur-2) var(--ease),transform var(--dur-2) var(--ease),visibility var(--dur-2)}
  .mnav.is-open{opacity:1;visibility:visible;transform:none}
  .mnav__close{position:absolute;top:.9rem;right:1rem;width:48px;height:48px;
    display:flex;align-items:center;justify-content:center;background:none;border:none;
    color:var(--ivory);font-size:2.1rem;line-height:1;cursor:pointer;opacity:.85}
  .mnav__close:active{opacity:1;color:var(--gold-soft)}
  .mnav__list{display:flex;flex-direction:column}
  .mnav__list a{display:flex;align-items:center;min-height:48px;padding:.5rem 0;
    font-family:'Cormorant Garamond',serif;font-size:1.95rem;font-weight:500;
    line-height:1.15;color:var(--ivory);letter-spacing:.01em}
  .mnav__list a:active{color:var(--gold-soft)}
  /* agents' entrance — gold, set apart by a thin rule */
  .mnav__list a.mnav__partner{color:var(--gold-soft);margin-top:.9rem;padding-top:1.3rem;
    border-top:1px solid rgba(var(--gold-soft-rgb),.32);
    flex-direction:column;align-items:flex-start;gap:.1rem}
  .mnav__tag{font-family:'Jost',sans-serif;font-size:.58rem;font-weight:500;line-height:1;
    letter-spacing:.2em;text-transform:uppercase;color:rgba(var(--gold-soft-rgb),.72)}
  body.mnav-open{overflow:hidden}
  /* nothing floats over the menu */
  body.mnav-open .tgchat-launch,
  body.mnav-open .tgchat-panel,
  body.mnav-open .mobile-callbar,
  body.mnav-open .pbar{display:none!important}
}
@media (max-width:380px){
  .mnav__list a{font-size:1.7rem;min-height:46px}
  .mnav{padding:5rem 1.4rem 2rem}
}

/* =============================================================================
   THE PROMO EXPIRY RULES ARE GONE (2026-08-14).

   There used to be a .reduced-off (formerly .redtag-off) block here that hid
   every badge, legend item, pill and toggle once an inline <head> script saw
   the ET date pass config.REDTAG_END_DATE. That was the right design for D.R.
   Horton's Red Tag EVENT, which had a close-by date and had to go quiet without
   a redeploy.

   It is the wrong design for what replaced it. "Price Reduced" is a per-home
   fact read from the workbook's Reduced column: a home is discounted off its
   Full Price this week or it is not, and next Monday's drop is the only thing
   that changes the answer. There is no date on which the site should stop
   saying so.

   THE RULES HAD TO GO WITH THE SCRIPT, not after it. REDTAG_END_DATE was
   2026-08-02 and had already passed, so leaving these selectors while the flag
   started working would have hidden all 138 badges with display:none!important
   — a defect whose only symptom is nothing appearing, on the one deploy that
   was supposed to make them appear.
   ========================================================================== */

/* =============================================================================
   CURRENT PROGRAMS (/partners only — the only place rates/APRs appear).
   Deliberately NOT a reproduction of the navy/red builder flyers: same quiet
   midnight/ivory/champagne language as the rest of the site.
   ========================================================================== */
.prog-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(250px,1fr));gap:1.5rem;align-items:stretch}
.prog{display:flex;flex-direction:column;background:var(--white);border:1px solid var(--line);
  border-radius:var(--radius);border-top:2px solid var(--gold);padding:2rem 1.5rem 1.5rem;
  box-shadow:var(--shadow);height:100%}
.prog--lead{background:linear-gradient(180deg,rgba(var(--gold-soft-rgb),.12),rgba(var(--gold-soft-rgb),0) 46%),var(--white);
  border-top-width:3px}
/* header: name + deadline badge (the ONLY place a date appears) */
.prog__top{display:flex;align-items:flex-start;justify-content:space-between;gap:0.75rem;min-height:2.4rem}
.prog__name{font-family:'Jost',sans-serif;font-size:0.75rem;font-weight:500;letter-spacing:.16em;
  text-transform:uppercase;color:var(--muted);margin:0;line-height:1.4}
.prog__badge{flex:none;font-family:'Jost',sans-serif;text-transform:uppercase;letter-spacing:.1em;
  font-size:0.625rem;font-weight:600;color:var(--midnight);background:var(--gold-soft);
  border-radius:2px;padding:0.25rem 0.5rem;line-height:1.2;max-width:52%;text-align:right}
.prog__badge--quiet{background:none;color:var(--muted);border:1px solid var(--line)}
/* the one number that carries the card */
.prog__hero-wrap{margin-top:1rem}
.prog__hero{font-family:'Cormorant Garamond',serif;font-size:clamp(2.4rem,4.8vw,3.75rem);font-weight:600;line-height:.95;
  color:var(--midnight);margin:0;letter-spacing:-.01em}
.prog__apr{font-family:'Jost',sans-serif;font-size:0.75rem;font-weight:500;letter-spacing:.1em;
  color:var(--gold);margin:0.25rem 0 0}
.prog__support{font-family:'Jost',sans-serif;font-size:0.75rem;font-weight:300;line-height:1.55;
  color:var(--muted);margin:0.75rem 0 0;max-width:24ch}
/* commission ladder: three steps, small -> large */
.prog__ladder{display:flex;align-items:flex-end;gap:0.5rem;margin-top:1rem}
.prog__step{flex:1;background:var(--cream);border-radius:var(--radius);padding:0.5rem 0.25rem;text-align:center}
.prog__step b{display:block;font-family:'Cormorant Garamond',serif;font-weight:600;color:var(--midnight);line-height:1}
.prog__step span{display:block;font-size:0.625rem;color:var(--muted);margin-top:0.25rem;line-height:1.25}
.prog__step--s b{font-size:1.25rem}
.prog__step--m b{font-size:1.5rem}
.prog__step--l b{font-size:2rem}
.prog__step--l{background:rgba(var(--gold-soft-rgb),.22)}
/* THE FLYER THUMBNAIL. A STRIP, NOT A PAGE.
   A program flyer is US Letter — 1:1.29 portrait — and rendered at the card's
   full width it is ~430px tall on a phone, three times over, which is most of a
   viewport spent on three pictures of small print nobody can read at that size.
   So it is capped at 132px and cropped from the TOP, where the rate, the APR and
   the offer name are; the readable version is one tap away in the viewer this
   button opens. object-position:top is the whole reason a crop is acceptable
   here — a centre crop would show the middle of a disclaimer. */
.prog__thumb{display:block;width:100%;max-height:132px;overflow:hidden;margin:1.25rem 0 0;
  padding:0;border:1px solid var(--line);border-radius:var(--radius);background:var(--white);
  cursor:pointer;position:relative;
  transition:border-color var(--dur-2) var(--ease)}
.prog__thumb img{display:block;width:100%;height:132px;object-fit:cover;object-position:top}
.prog__thumb:hover,.prog__thumb:focus-visible{border-color:var(--gold)}
/* actions pinned to the bottom so every card lines up */
.prog__actions{margin-top:auto;padding-top:1.5rem}
.prog__dl{display:inline-flex;align-items:center;gap:0.5rem;font-family:'Jost',sans-serif;text-transform:uppercase;
  letter-spacing:.12em;font-size:0.75rem;font-weight:500;color:var(--ivory);background:var(--midnight);
  border:1px solid var(--midnight);border-radius:var(--radius);padding:0.5rem 1rem;cursor:pointer;
  transition:background var(--dur-2) var(--ease),border-color var(--dur-2) var(--ease),color var(--dur-2) var(--ease)}
.prog__dl svg{width:14px;height:14px;flex:none}
.prog__dl:hover{background:var(--gold);border-color:var(--gold);color:var(--midnight)}
/* every word of legal text lives in here */
.prog__terms{border-top:1px solid var(--line);margin-top:1rem;padding-top:0.75rem}
.prog__terms summary{cursor:pointer;font-family:'Jost',sans-serif;text-transform:uppercase;letter-spacing:.13em;
  font-size:0.75rem;font-weight:500;color:var(--gold);list-style:none}
.prog__terms summary::-webkit-details-marker{display:none}
.prog__terms summary::after{content:" +";color:var(--muted)}
.prog__terms[open] summary::after{content:" –"}
.prog__terms p{margin-top:0.75rem;font-size:0.75rem;line-height:1.65;color:var(--muted);font-weight:300;
  max-height:240px;overflow-y:auto}
.prog__detail{color:var(--ink);font-size:0.75rem;max-height:none}
.prog-foot{display:flex;align-items:center;gap:1rem;margin-top:2rem;padding-top:1rem;border-top:1px solid var(--line)}
.prog-eho{width:34px;height:34px;flex:none;color:var(--midnight)}
.prog-foot p{font-size:0.75rem;color:var(--muted);font-weight:300;line-height:1.6;margin:0}
@media (max-width:560px){
  .prog-grid{grid-template-columns:1fr;gap:1rem}
  .prog{padding:1.5rem 1.5rem 1rem}
  .prog__hero{font-size:clamp(2.4rem,4.8vw,3.75rem)}
  .prog__support{max-width:none}
}

/* partners hero strip */
/* ---- PARTNER HERO STRIP — six interactive elements, one uniform row -------
   All six are real <a href>. The four stats were <span>s, which meant they were
   not keyboard reachable, showed no destination on hover, and sat on a
   different baseline from the two buttons.

   UNIFORMITY IS ENFORCED BY THE SHARED .partner-strip__el CLASS, which owns
   every property that must match across all six — height, vertical alignment,
   radius, transition, focus ring, tap target. A variant class may only change
   colour and fill. build/audit_partners_hero.py asserts identical computed
   height on all six at build time, so a divergence fails the build rather than
   shipping as a ragged row. */
.partner-strip{display:flex;flex-wrap:wrap;align-items:stretch;gap:.7rem;margin-top:1.8rem}
.partner-strip__el{
  /* the uniform contract — do not override these in a variant */
  display:flex;flex-direction:column;align-items:center;justify-content:center;
  min-height:44px;                      /* tap target floor, all six */
  padding:.62rem 1.15rem;
  border:1px solid transparent;         /* every variant reserves the same 1px */
  border-radius:var(--radius);
  font-family:'Jost',sans-serif;
  text-align:center;
  transition:background 180ms var(--ease),border-color 180ms var(--ease),
             transform 180ms var(--ease);
}
.partner-strip__el:hover{transform:translateY(-2px)}
.partner-strip__el:focus-visible{outline:2px solid var(--gold-soft);outline-offset:3px}
.partner-strip__cta{text-transform:uppercase;letter-spacing:.14em;font-size:.7rem;font-weight:500;color:var(--midnight);background:var(--gold-soft);border-color:var(--gold-soft)}
.partner-strip__cta:hover{background:var(--gold);border-color:rgba(var(--gold-soft-rgb),.7)}
.partner-strip__vcf{text-transform:uppercase;letter-spacing:.14em;font-size:.68rem;font-weight:500;color:var(--gold-soft);border-color:rgba(var(--gold-soft-rgb),.5)}
.partner-strip__vcf:hover{background:rgba(var(--gold-soft-rgb),.14);border-color:rgba(var(--gold-soft-rgb),.7)}
/* stat tiles: equal width, so four of them read as one set rather than four
   different-length labels */
.partner-strip__stat{flex:1 1 0;min-width:8.5rem;
  border-color:rgba(var(--gold-soft-rgb),.30);
  background:rgba(0,0,0,.20);backdrop-filter:blur(2px);-webkit-backdrop-filter:blur(2px)}
.partner-strip__stat:hover{border-color:rgba(var(--gold-soft-rgb),.70);background:rgba(0,0,0,.28)}
.partner-strip__stat b{font-family:'Cormorant Garamond',Georgia,serif;font-size:1.35rem;
  line-height:1;color:var(--gold-soft);font-weight:500}
.partner-strip__stat span{text-transform:uppercase;letter-spacing:.12em;font-size:.6rem;
  font-weight:500;color:rgba(var(--ivory-rgb),.80);margin-top:.22rem}
/* MOBILE: two buttons full width stacked, four stats in a 2x2 grid. Identical
   styling and tap targets — the row reflows, the treatment does not change. */
@media(max-width:767px){
  /* grid-auto-rows:1fr — EVERY ROW MATCHES THE TALLEST, 2026-09-02.
     The live-inventory tile carries an as-of date on a second line, which
     made its row 82px against 64px for the row beneath it. Rows in an
     auto-height grid size independently, so the strip rendered two
     different tile heights and audit_partners_hero.py's "all 6 render an
     identical computed height" went red at 390 while desktop (flex,
     align-items:stretch) was fine. This equalises them without pulling the
     date out of the tile the spec asked to carry it. */
  .partner-strip{display:grid;grid-template-columns:1fr 1fr;gap:.6rem;grid-auto-rows:1fr}
  .partner-strip__cta,.partner-strip__vcf{grid-column:1 / -1}
  .partner-strip__stat{min-width:0}
  /* Uniform height across all six. The two buttons are single-line (44px) and
     the stat tiles are two-line (number + label, 63px), so without this the row
     ships ragged. min-height, not height: a label that ever wraps grows the
     tile rather than clipping, and audit_partners_hero.py --render catches the
     resulting inequality. */
  .partner-strip__el{min-height:64px}
}
/* agent toolkit grid — auto-fit so 5 cards sit evenly and wrap cleanly */
.toolkit{display:grid;grid-template-columns:repeat(auto-fit,minmax(212px,1fr));gap:1.5rem;margin-top:0.5rem}
/* community flyer grid */
.flyer-actions{display:flex;justify-content:center;margin-bottom:1.5rem}
.flyer-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(240px,1fr));gap:0.75rem}
.flyer{display:flex;align-items:center;justify-content:space-between;gap:0.75rem;min-height:52px;padding:0.75rem 1rem;background:var(--white);border:1px solid var(--line);border-radius:var(--radius);box-shadow:var(--shadow);transition:border-color var(--dur-2) var(--ease),transform var(--dur-2) var(--ease)}
.flyer:hover{border-color:var(--gold);transform:translateY(-2px)}
.flyer__name{font-size:1rem;color:var(--ink);font-weight:400}
.flyer__dl{font-family:'Jost',sans-serif;font-size:0.75rem;font-weight:600;letter-spacing:.14em;color:var(--ivory);background:var(--midnight);border-radius:2px;padding:0.25rem 0.5rem;flex:none}
.flyer:hover .flyer__dl{background:var(--gold);color:var(--midnight)}
/* video walkthrough request */
.vidreq{max-width:760px;margin:0 auto;text-align:center}
.vidreq__copy{font-family:'Cormorant Garamond',serif;font-size:1.5rem;color:var(--midnight);line-height:1.4;margin-bottom:1.5rem}
.vidreq__actions{display:flex;flex-wrap:wrap;gap:0.75rem;justify-content:center}
.joinlist{margin-top:2rem;text-align:center}
.joinlist a{font-family:'Jost',sans-serif;text-transform:uppercase;letter-spacing:.14em;font-size:0.75rem;color:var(--gold);border-bottom:1px solid var(--gold);padding-bottom:0.25rem}
/* partners sticky action bar (mobile) — replaces the generic call bar here */
.pbar{display:none}
@media (max-width:640px){
  .pbar{display:grid;grid-template-columns:repeat(3,1fr);position:fixed;left:0;right:0;bottom:0;z-index:var(--z-sticky-header);
    background:var(--midnight);border-top:1px solid rgba(var(--gold-soft-rgb),.25);box-shadow:0 -8px 24px rgba(var(--black-rgb),.28)}
  .pbar__btn{display:flex;align-items:center;justify-content:center;min-height:56px;font-family:'Jost',sans-serif;
    text-transform:uppercase;letter-spacing:.12em;font-size:0.75rem;font-weight:500;color:var(--ivory)}
  .pbar__btn:active{background:rgba(var(--white-rgb),.07)}
  .pbar__btn--gold{color:var(--midnight);background:var(--gold-soft)}
  body.partners .mobile-callbar{display:none}   /* one sticky bar only */
  body.partners{padding-bottom:var(--bottom-chrome)}
}
.tk{background:var(--white);border:1px solid var(--line);border-radius:var(--radius);padding:1.5rem 1.5rem 1.5rem;box-shadow:var(--shadow);display:flex;flex-direction:column;gap:0.5rem;transition:transform var(--dur-2) var(--ease),box-shadow var(--dur-2) var(--ease),border-color var(--dur-2) var(--ease)}
.tk:hover{transform:translateY(-3px);box-shadow:0 18px 44px rgba(var(--midnight-rgb),.14);border-color:var(--gold-soft)}
.tk__ico{width:30px;height:30px;color:var(--gold);margin-bottom:0.25rem}
.tk h3{font-size:1.25rem;font-weight:600}
.tk p{font-size:0.875rem;color:var(--muted);font-weight:300;line-height:1.6;flex:1}
/* CTA block pins to the card's bottom edge, so every card's buttons sit on one
   shared baseline no matter how long its heading or paragraph runs. `flex:1` on
   .tk p alone did NOT do this — measured 2026-07-22, row 1 at 1440px had four
   equal-height (365px) cards whose CTA blocks started at 1471/1404/1451/1471.
   margin-top:auto is the only thing that actually bottom-pins the block; the
   padding-top keeps the old 1rem of breathing room above it. */
.tk__links{display:flex;flex-direction:column;gap:0.5rem;margin-top:auto;padding-top:1rem}
/* Uniform height is what actually puts the primary CTAs on one baseline. The
   toolkit columns are ~219px at 768px, so "Browse 26 communities" (187px of text)
   and "Request a walkthrough" (191px) CANNOT fit on one line there — they wrap to
   two while "Open the maps" stays at one, and the buttons land 20px apart. No
   amount of bottom-pinning fixes that; only equal box heights do. 3.6875rem is
   the natural two-line height at this type size, so a wrapped label fills it and
   a short label matches it. Bonus: 59px clears the 44px touch-target minimum. */
.tk__link{display:inline-flex;align-items:center;justify-content:center;text-align:center;min-height:3.6875rem;font-family:'Jost',sans-serif;text-transform:uppercase;letter-spacing:.13em;font-size:0.75rem;font-weight:500;padding:0.5rem 1rem;border-radius:var(--radius);border:1px solid var(--line);color:var(--midnight);transition:border-color var(--dur-2) var(--ease),color var(--dur-2) var(--ease),background var(--dur-2) var(--ease)}
.tk__link--solid{background:var(--midnight);color:var(--ivory);border-color:var(--midnight)}
.tk__link--solid:hover{background:var(--gold);border-color:var(--gold);color:var(--midnight)}
.tk__link:not(.tk__link--solid):hover{border-color:var(--gold);color:var(--gold)}
/* co-op steps */
.coop{margin-top:0.5rem}
.coop__step{padding:1.5rem 0}
.coop__n{font-family:'Cormorant Garamond',serif;font-size:2rem;color:var(--gold);font-weight:600;line-height:1;display:block;margin-bottom:0.5rem}
.coop__step h3{font-size:1.25rem;font-weight:600;margin-bottom:0.5rem}
.coop__step p{font-size:0.875rem;color:var(--muted);font-weight:300;line-height:1.65}
.coop__note{margin-top:1.5rem;color:var(--muted);font-size:0.875rem}
.coop__note a{color:var(--midnight);border-bottom:1px solid var(--gold);font-weight:400}
/* agent signup form */
.partner-form{max-width:680px;margin:0 auto}
.partner-form__row{display:grid;grid-template-columns:1fr 1fr;gap:1.5rem}
.partner-form .opt{text-transform:none;letter-spacing:0;color:var(--muted);font-size:.8em}
@media (max-width:560px){.partner-form__row{grid-template-columns:1fr}}

/* footer partners link (small, quiet) */
.footer__partners{margin-top:1rem;font-size:0.75rem}
.footer__partners a{font-family:'Jost',sans-serif;text-transform:uppercase;letter-spacing:.16em;color:rgba(var(--ivory-rgb),.5)}
.footer__partners a:hover{color:var(--gold-soft)}

@media (max-width:640px){
  /* max(), not a bare 52svh. This rule sits AFTER the 767px block, so once the
     mobile `aspect-ratio` came off it began winning outright and shrank the
     phone hero from 488px to 439px on every short-hero page — a proportion
     change nobody asked for, introduced by the clip fix. Both intents now
     hold: at least 52svh of viewport, and never narrower than the 4/5 band. */
  .page-hero{min-height:max(52svh,125vw)}
  .page-hero__inner{padding:0 1.5rem 2rem}
  .cta-band{padding:4rem 1.5rem}
}

/* =================================================================
   INTERACTIVE COMMUNITY MAP (Leaflet)
   ================================================================= */
.community-map{height:480px;width:100%;border:1px solid var(--line);border-radius:var(--radius);box-shadow:var(--shadow);background:var(--map-surface);z-index:var(--z-local-raise)}
/* STACKING: Leaflet builds internal panes at z-index 400-800. The sticky nav
   was at 50, so map tiles rendered OVER the header while scrolling. Two
   defences: the map gets its own stacking context so those 400s can never
   escape it, and the nav is lifted above anything Leaflet creates. */
.leaflet-container{font-family:'Jost',sans-serif;background:var(--map-surface);position:relative;z-index:var(--z-local-base);isolation:isolate}
.mappage,.spotmap,.am-wrap{position:relative;z-index:var(--z-local-base);isolation:isolate}
/* refined pin: midnight dot, gold ring */
/* clustered pins (luxury) — overlapping communities group into a count */
.cm-cluster{background:none;border:0}
.cm-cluster span{display:flex;align-items:center;justify-content:center;width:38px;height:38px;margin:2px;
  border-radius:50%;background:var(--midnight);color:var(--gold-soft);border:2px solid var(--gold);
  font-family:'Jost',sans-serif;font-size:0.875rem;font-weight:500;box-shadow:0 4px 12px rgba(var(--midnight-rgb),.5)}
.marker-cluster-small,.marker-cluster-medium,.marker-cluster-large{background:none}

/* ================= THE SHARED MAP SKIN — §0-Q item 2 =====================
   "ONE SHARED VISUAL LAYER across both map modules: clusters as navy discs
    with a gold ring and serif numerals; pins and popups matching the site's
    cards; styled zoom controls."

   WHAT THIS REPLACES, AND IT WAS NOT A STYLE DRIFT. /find-your-home.html's map
   shipped LEAFLET'S DEFAULTS — the blue teardrop marker and the blue-and-green
   markercluster discs, which are the visual signature of a map nobody designed
   — while /map.html had bespoke dots and the community spot map had a third
   treatment again. Three answers to one question on one site.

   THE SPOT MAP HAD IT RIGHT ALREADY (.cm-cluster / .cm-pin above): navy disc,
   gold ring, lift shadow. This block is that treatment, named so both other
   modules can call it through assets/map-skin.h62255d9c1f.js, plus the serif numerals the
   brief asks for — which the spot map now also inherits, so all three converge
   rather than two of three agreeing.

   OSM/CARTO ATTRIBUTION IS DELIBERATELY UNSTYLED AND UNTOUCHED. It is a
   licence term, not a design element. Nothing here hides, shrinks or
   repositions .leaflet-control-attribution.
   ------------------------------------------------------------------------ */
.tgmap-cluster{background:none;border:0}
.tgmap-cluster__n{display:flex;align-items:center;justify-content:center;
  width:100%;height:100%;border-radius:50%;
  background:var(--midnight);color:var(--ivory);border:2px solid var(--gold);
  font-family:'Cormorant Garamond',serif;font-weight:600;line-height:1;
  box-shadow:0 4px 14px rgba(var(--midnight-rgb),.45)}
.tgmap-cluster--sm .tgmap-cluster__n{font-size:1rem}
.tgmap-cluster--md .tgmap-cluster__n{font-size:1.15rem}
.tgmap-cluster--lg .tgmap-cluster__n{font-size:1.3rem}
/* The hit target is the disc, not the 50px box around it. */
.tgmap-cluster:hover .tgmap-cluster__n,
.tgmap-cluster:focus-visible .tgmap-cluster__n{border-color:var(--gold-soft)}

.tgmap-pin{background:none;border:0;display:flex;align-items:center;justify-content:center}
.tgmap-pin__dot{display:block;width:18px;height:18px;border-radius:50%;
  background:var(--midnight);border:2.5px solid var(--gold);
  box-shadow:0 3px 10px rgba(var(--midnight-rgb),.5);
  transition:transform var(--dur-1) var(--ease)}
.tgmap-pin:hover .tgmap-pin__dot,
.tgmap-pin:focus .tgmap-pin__dot{transform:scale(1.3)}
/* PRICE REDUCED, THE SAME GARNET THE CARDS USE. A second visual language for
   the same fact is how a buyer ends up asking which one is current. */
.tgmap-pin--rt .tgmap-pin__rt{position:absolute;top:1px;right:1px;
  width:9px;height:9px;border-radius:50%;
  background:var(--reduced);border:2px solid var(--ivory)}

/* POPUP CHROME, SHARED. Content rules stay with each module (.am-card,
   .cm-card) — this is the frame around them, and it is the card frame:
   ivory, one hairline, the site radius, the site lift. */
.tgmap-popup .leaflet-popup-content-wrapper{background:var(--ivory);
  border:1px solid var(--line);border-radius:var(--radius);
  box-shadow:var(--shadow-lift);padding:0;overflow:hidden}
.tgmap-popup .leaflet-popup-content{margin:0}
.tgmap-popup .leaflet-popup-tip{background:var(--ivory);border:1px solid var(--line)}
.tgmap-popup a.leaflet-popup-close-button{color:var(--muted);padding:8px 8px 0 0}
.tgmap-popup a.leaflet-popup-close-button:hover{color:var(--midnight);background:none}

/* ZOOM CONTROLS. Leaflet's default is a white box with a grey hairline and a
   system-font +/-. The 44px sizing further down this file was already applied;
   this is the rest of it. */
.tgmap .leaflet-control-zoom{border:1px solid var(--line);border-radius:var(--radius);
  box-shadow:0 6px 18px rgba(var(--midnight-rgb),.18);overflow:hidden}
.tgmap .leaflet-control-zoom a{background:var(--ivory);color:var(--midnight);
  border-bottom:1px solid var(--line);
  font-family:'Jost',sans-serif;font-weight:400;font-size:1.15rem}
.tgmap .leaflet-control-zoom a:last-child{border-bottom:0}
.tgmap .leaflet-control-zoom a:hover{background:var(--white);color:var(--gold)}
.tgmap .leaflet-control-zoom a:focus-visible{outline:2px solid var(--gold);outline-offset:-2px}
.tgmap .leaflet-control-zoom a.leaflet-disabled{background:var(--ivory);color:var(--line)}

/* The spot map's own numerals join the serif, so the three maps agree. */
.cm-cluster span{font-family:'Cormorant Garamond',serif;font-weight:600}
.cm-pin{background:none;border:0;display:flex;align-items:center;justify-content:center}
.cm-pin__dot{display:block;width:20px;height:20px;border-radius:50%;background:var(--midnight);border:2.5px solid var(--gold);box-shadow:0 3px 10px rgba(var(--midnight-rgb),.5);transition:transform var(--dur-1) var(--ease)}
.cm-pin:hover .cm-pin__dot,.cm-pin:focus .cm-pin__dot{transform:scale(1.3)}
/* instant name label on hover/tap */
.cm-tip.leaflet-tooltip{background:var(--midnight);color:var(--ivory);border:1px solid var(--gold-soft);border-radius:var(--radius);box-shadow:0 6px 18px rgba(var(--midnight-rgb),.4);font-family:'Jost',sans-serif;font-weight:400;font-size:0.75rem;letter-spacing:.08em;padding:0.5rem 0.75rem;white-space:nowrap}

/* ===================================================================
   CARD <-> PIN CROSS-HIGHLIGHT — docs/specs/4a-map-hover-labels.md
   Driven by assets/map-link.h8cf71162f6.js, which is inert on touch.

   ONE LABEL, TWO DIRECTIONS. The same element is opened by a card hover
   and by a pin hover, so the two can never drift into showing different
   things. The NAME is Cormorant — "a serif name label" is the spec, and
   it is also what the cluster numerals already use, so the map keeps one
   typographic voice.
   =================================================================== */
.tgmap-label.leaflet-tooltip{
  background:var(--midnight);border:1px solid var(--gold);
  border-radius:var(--radius);box-shadow:0 8px 22px rgba(var(--midnight-rgb),.45);
  padding:.45rem .7rem;white-space:nowrap;
  /* A label is an ANNOTATION, never a hit target: it must not intercept the
     mouseout that closes it, or a label opened near the cursor would flicker
     as the pointer crossed its own tooltip. map-link.js also sets
     interactive:false; this is the CSS half of the same statement. */
  pointer-events:none;
  max-width:min(15rem,60vw);
}
.tgmap-label.leaflet-tooltip::before{border-top-color:var(--gold)}
.tgmap-label__name{display:block;font-family:'Cormorant Garamond',Georgia,serif;
  font-weight:600;font-size:1.05rem;line-height:1.1;color:var(--ivory);
  overflow:hidden;text-overflow:ellipsis}
.tgmap-label__meta{display:block;margin-top:.15rem;font-family:'Jost',sans-serif;
  font-style:normal;font-size:.68rem;letter-spacing:.1em;text-transform:uppercase;
  color:var(--gold-soft)}

/* THE HIGHLIGHT ITSELF, on whatever is actually on screen for that community
   — the pin, or the cluster standing in for it. Both are listed because
   map-link.js does not know or care which one it just marked. */
.cm-pin.tgmap-is-linked .cm-pin__dot,
.tgmap-pin.tgmap-is-linked .tgmap-pin__dot{
  transform:scale(1.45);border-color:var(--gold-soft);
  box-shadow:0 0 0 6px rgba(var(--gold-rgb),.28),0 4px 14px rgba(var(--midnight-rgb),.55)}
.cm-cluster.tgmap-is-linked span,
.tgmap-cluster.tgmap-is-linked .tgmap-cluster__n{
  border-color:var(--gold-soft);
  box-shadow:0 0 0 6px rgba(var(--gold-rgb),.28),0 4px 14px rgba(var(--midnight-rgb),.55)}

/* THE CARD SIDE. A hairline and a lift, not a colour wash — the card already
   carries a photo and a price, and tinting it would read as a state change
   ("selected") rather than as a pointer. NO SCROLLING: the spec is explicit
   that hover never moves the viewport. */
.card.tgmap-card-linked{border-color:var(--gold);box-shadow:var(--shadow-lift)}

@media (prefers-reduced-motion:reduce){
  .cm-pin.tgmap-is-linked .cm-pin__dot,
  .tgmap-pin.tgmap-is-linked .tgmap-pin__dot{transform:none}
}
.cm-tip.leaflet-tooltip-top::before{border-top-color:var(--gold-soft)}
/* popup card */
.cm-popup .leaflet-popup-content-wrapper{background:var(--ivory);border:1px solid var(--line);border-radius:var(--radius);box-shadow:var(--shadow-lift)}
.cm-popup .leaflet-popup-content{margin:0}
.cm-popup .leaflet-popup-tip{background:var(--ivory);border:1px solid var(--line)}
/* SPOT-LOT MAP CARD — anatomy, not decoration.
   The Price Reduced badge and "Ask me about this home" used to be siblings in the
   normal flow: an inline-block immediately followed by the link. At the popup's
   natural width they wrapped onto the same line and touched. They now live in
   .cm-card__actions, a flex row of their own with a rule above it and a 16px
   minimum gap, and the row wraps to two lines rather than letting them collide.
   Padding is 20px on every side, matching the site's card anatomy. */
.cm-card{padding:20px;min-width:200px}
.cm-card__loc{font-family:'Jost',sans-serif;text-transform:uppercase;letter-spacing:.18em;font-size:0.625rem;font-weight:500;color:var(--gold)}
.cm-card__name{font-family:'Cormorant Garamond',serif;font-size:1.5rem;font-weight:600;color:var(--midnight);margin:0.25rem 0 0.25rem;line-height:1.1}
.cm-card__price{font-family:'Cormorant Garamond',serif;font-size:1rem;color:var(--gold);margin:0 0 0.35rem}
/* Lot size, straight from the county parcel record (build/parcel_lots.py). */
.cm-card__lot{font-family:'Jost',sans-serif;font-size:0.75rem;letter-spacing:.06em;
  color:var(--muted);margin:0 0 0.35rem}
.cm-card__actions{display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;
  gap:16px;margin-top:14px;padding-top:12px;border-top:1px solid var(--line)}
.cm-card__link{font-family:'Jost',sans-serif;text-transform:uppercase;letter-spacing:.18em;font-size:0.75rem;font-weight:500;color:var(--midnight);border:0;border-bottom:1px solid var(--gold);padding:0 0 2px;background:none;cursor:pointer;text-align:left;line-height:1.3}
.cm-card__link:hover{color:var(--gold)}
.cm-card__link:focus-visible{outline:2px solid var(--gold);outline-offset:3px}
/* Below ~260px of card the two cannot sit side by side with 16px between them,
   so the badge takes its own line rather than being squeezed. */
@media (max-width:400px){
  .cm-card__actions{flex-direction:column;align-items:flex-start;gap:12px}
}
.leaflet-bar a{color:var(--midnight)!important}
.leaflet-control-attribution{font-size:9px;opacity:.7}
@media (max-width:640px){ .community-map{height:380px} }
/* Marion Oaks spot-lot maps */
.marion-map{height:520px;width:100%;border:1px solid var(--line);border-radius:var(--radius);box-shadow:var(--shadow);background:var(--map-surface);z-index:var(--z-local-raise)}
.marion-map--partner{height:560px}
.marion-count{font-family:'Jost',sans-serif;text-transform:uppercase;letter-spacing:.18em;font-size:0.625rem;font-weight:500;color:var(--gold);margin-top:0.5rem}
.marion-legend{display:flex;gap:1.5rem;justify-content:center;flex-wrap:wrap;margin-top:1rem}
.marion-legend__item{display:inline-flex;align-items:center;gap:0.5rem;font-family:'Jost',sans-serif;font-size:0.75rem;letter-spacing:.06em;color:var(--muted)}
.cm-dot{width:14px;height:14px;border-radius:50%;display:inline-block}
.cm-dot--mir{background:var(--midnight);border:2px solid var(--gold)}
.cm-dot--uc{background:var(--gold);border:2px solid var(--midnight)}
.cm-pin--uc .cm-pin__dot{background:var(--gold);border-color:var(--midnight)}
.marion-un{max-width:720px;margin:2rem auto 0;text-align:center}
.marion-un__head{font-family:'Cormorant Garamond',serif;font-size:1.25rem;color:var(--midnight);margin:0 0 0.25rem}
.marion-un__note{font-family:'Jost',sans-serif;font-size:0.875rem;color:var(--muted);margin:0 0 1rem;line-height:1.5}
.marion-un__list{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:0.5rem}
.marion-un__item{display:flex;align-items:center;gap:1rem;flex-wrap:wrap;justify-content:center;padding:0.75rem 1rem;border:1px solid var(--line);border-radius:var(--radius);background:var(--cream)}
.marion-un__lead{font-family:'Jost',sans-serif;font-weight:500;color:var(--midnight);font-size:0.875rem}
.marion-un__sub{font-family:'Jost',sans-serif;color:var(--gold);font-size:0.875rem}
.marion-un .badge{position:static;font-size:0.625rem}
@media (max-width:640px){ .marion-map,.marion-map--partner{height:400px} }

/* ============ Price Reduced treatment (refined, no clearance-sale) ============ */
.badge--reduced{left:1rem;right:auto;background:var(--reduced);color:var(--ivory);
  border-color:rgba(var(--ivory-rgb),.35);letter-spacing:.2em;font-weight:500}
.cm-dot--rt{width:14px;height:14px;border-radius:50%;display:inline-block;background:var(--reduced);border:2px solid var(--ivory);box-shadow:0 0 0 1px var(--reduced)}
.cm-pin--rt .cm-pin__dot{box-shadow:0 0 0 3px var(--reduced),0 3px 10px rgba(var(--midnight-rgb),.5)}
.cm-card__rt{display:inline-block;background:var(--reduced);color:var(--ivory);font-family:'Jost',sans-serif;
  text-transform:uppercase;letter-spacing:.16em;font-size:0.625rem;font-weight:500;padding:0.25rem 0.5rem;border-radius:var(--radius);margin:0;flex:none;white-space:nowrap}
.plan__rt{display:inline-flex;align-items:center;gap:0.25rem;font-family:'Jost',sans-serif;text-transform:uppercase;
  letter-spacing:.14em;font-size:0.625rem;color:var(--reduced);font-weight:500;white-space:nowrap}
/* community-page Price Reduced toggle */
.rt-toggle{margin-top:1rem;display:inline-flex;align-items:center;gap:0.5rem;background:transparent;cursor:pointer;
  border:1px solid var(--line);border-radius:var(--radius);padding:0.5rem 1rem;font-family:'Jost',sans-serif;
  text-transform:uppercase;letter-spacing:.14em;font-size:0.75rem;color:var(--midnight);transition:all var(--dur-2) var(--ease)}
.rt-toggle:hover{border-color:var(--reduced)}
.rt-toggle.is-on{background:var(--reduced);color:var(--ivory);border-color:var(--reduced)}
.rt-toggle.is-on .cm-dot--rt{border-color:var(--ivory)}
.grid.rt-only .home[data-reduced="0"]{display:none}
/* community card: Price Reduced chip + photo-less placeholder */
.card__tag--rt{background:var(--reduced);color:var(--ivory);border-color:transparent;top:auto;bottom:1rem;right:1rem;left:auto}
.card__media--ph{display:flex;align-items:center;justify-content:center;position:relative;min-height:210px;
  background:linear-gradient(140deg,var(--midnight) 0%,var(--midnight-3) 100%)}
.card__ph-name{font-family:'Cormorant Garamond',serif;font-size:2rem;font-weight:600;color:var(--ivory);text-align:center;padding:0 1rem}
.card__media--ph::after{content:"";position:absolute;bottom:1.1rem;left:1.2rem;width:32px;height:1px;background:var(--gold)}
.page-hero__img--ph{position:absolute;inset:0;z-index:var(--z-local-behind);background:linear-gradient(140deg,var(--midnight) 0%,var(--midnight-3) 60%,var(--midnight-2) 100%)}

/* ============ area group headers (communities section) ============ */
.area-head{grid-column:1/-1;display:flex;align-items:baseline;gap:1rem;margin:3rem 0 0.5rem;padding-bottom:0.75rem;border-bottom:1px solid var(--line)}
.area-head:first-child{margin-top:0}
/* Each area reads as its own block: generous air, then a thin champagne rule,
   then the header. `.is-first` is set by filters.js on the first VISIBLE group,
   because filtering can hide the group that happens to be first in source. */
.cgroup{margin-top:4rem;padding-top:3rem;border-top:1px solid rgba(var(--gold-rgb),.5)}
.cgroup:first-child,.cgroup.is-first{margin-top:0;padding-top:0;border-top:0}
.cgroup .area-head{margin-top:0}
@media (max-width:760px){.cgroup{margin-top:3rem;padding-top:2rem}}
.area-head__eyebrow{font-family:'Jost',sans-serif;text-transform:uppercase;letter-spacing:.24em;font-size:0.625rem;color:var(--gold);font-weight:500}
.area-head__title{font-family:'Cormorant Garamond',serif;font-size:1.5rem;font-weight:600;color:var(--midnight);margin:0}
.area-head__count{margin-left:auto;font-family:'Jost',sans-serif;font-size:0.75rem;color:var(--muted);letter-spacing:.06em}

/* ============ pin-check tool ============ */
.pc-wrap{display:grid;grid-template-columns:1fr 320px;gap:1.5rem}
.pincheck-map{height:640px;border:1px solid var(--line);border-radius:var(--radius);box-shadow:var(--shadow);background:var(--map-surface);z-index:var(--z-local-raise)}
.pc-side{display:flex;flex-direction:column;gap:1rem;min-width:0}
.pc-paste-box{border:1px solid var(--line);border-radius:var(--radius);padding:1rem 1rem;background:var(--ivory)}
.pc-paste-box label{display:block;font-family:'Jost',sans-serif;text-transform:uppercase;letter-spacing:.12em;font-size:0.75rem;color:var(--gold);margin-bottom:0.5rem}
.pc-paste-row{display:flex;gap:0.5rem}
.pc-paste-row input{flex:1;min-width:0;border:1px solid var(--line);border-radius:var(--radius);padding:0.5rem 0.5rem;font-size:0.875rem;font-family:'Jost',sans-serif}
.pc-paste-row input.pc-paste--err{border-color:var(--reduced)}
.pc-paste-row button{background:var(--midnight);color:var(--ivory);border:none;border-radius:var(--radius);padding:0.5rem 1rem;font-size:0.75rem;text-transform:uppercase;letter-spacing:.1em;cursor:pointer}
.pc-readout{border:1px solid var(--line);border-radius:var(--radius);padding:1rem;background:var(--cream);min-height:120px}
.pc-readout__coord{font-family:'Cormorant Garamond',serif;font-size:1.5rem;color:var(--midnight);margin:0 0 0.25rem}
.pc-readout__near{font-family:'Jost',sans-serif;font-size:0.75rem;color:var(--muted);margin:0 0 0.75rem}
.pc-readout__copy{display:flex;gap:0.5rem;align-items:center}
.pc-readout__copy code{flex:1;font-size:0.75rem;background:var(--ivory);border:1px solid var(--line);padding:0.5rem 0.5rem;border-radius:var(--radius);overflow:auto;white-space:nowrap}
.pc-readout__copy button{background:var(--midnight);color:var(--ivory);border:none;border-radius:var(--radius);padding:0.5rem 0.75rem;font-size:0.75rem;text-transform:uppercase;letter-spacing:.1em;cursor:pointer}
.pc-readout__hint{font-family:'Jost',sans-serif;font-size:0.75rem;color:var(--muted);margin:0.5rem 0 0}
.pc-list{list-style:none;margin:0;padding:0;max-height:480px;overflow:auto;border:1px solid var(--line);border-radius:var(--radius)}
.pc-row{display:flex;align-items:center;gap:0.5rem;padding:0.5rem 0.75rem;border-bottom:1px solid var(--line);cursor:pointer;font-family:'Jost',sans-serif;font-size:0.75rem}
.pc-row:hover{background:var(--cream)}
.pc-row__name{flex:1;color:var(--midnight)}
.pc-row__src{font-size:0.75rem;text-transform:uppercase;letter-spacing:.1em;padding:0.25rem 0.5rem;border-radius:var(--radius)}
.pc-src--centroid{background:rgba(var(--verified-rgb),.14);color:var(--verified)}
.pc-src--fallback{background:rgba(var(--reduced-rgb),.14);color:var(--reduced)}
.pc-src--manual{background:rgba(var(--gold-rgb),.18);color:var(--gold)}
.pc-row__co{font-size:0.75rem;color:var(--muted);white-space:nowrap}
@media (max-width:820px){ .pc-wrap{grid-template-columns:1fr} .pincheck-map{height:440px} }

/* home card: elegant placeholder when a plan has no photo anywhere */
.home__media--ph{display:flex;align-items:center;justify-content:center;position:relative;
  background:linear-gradient(140deg,var(--midnight) 0%,var(--midnight-2) 100%)}
.home__media--ph::before{content:"D\00B7R\00B7 Horton Plan";position:absolute;top:1rem;left:1.1rem;
  font-family:'Jost',sans-serif;text-transform:uppercase;letter-spacing:.22em;font-size:0.75rem;color:var(--gold-soft)}
.home__media--ph::after{content:"";position:absolute;bottom:1.1rem;left:1.1rem;width:34px;height:1px;background:var(--gold)}
.home__ph-name{font-family:'Cormorant Garamond',serif;font-size:2rem;font-weight:600;color:var(--ivory);letter-spacing:.01em;text-align:center;padding:0 1rem}
/* Buyer-card meta as labelled stacked rows (Homesite / Lot / Move-in). The fixed
   rem label column aligns the three values; authored tracks are rem+fr (never px),
   so audit_proportions' fixed-track check does not flag it. */
.home__site{margin:0.6rem 0 0;display:flex;flex-direction:column;gap:0.3rem}
.home__meta-row{display:grid;grid-template-columns:5rem 1fr;align-items:baseline;column-gap:0.75rem}
.home__meta-k{font-size:0.625rem;text-transform:uppercase;letter-spacing:.14em;color:var(--muted);font-family:'Jost',sans-serif}
.home__meta-v{font-size:0.75rem;letter-spacing:.03em;color:var(--ink)}

/* =================================================================
   BATCH: filters, estimator, incentives, compare, home actions
   ================================================================= */
.home__actions{display:flex;flex-direction:column;align-items:flex-start;gap:0.75rem;margin-top:1.5rem}
.home__actions .home__link{margin-top:0}
/* ESTIMATE PAYMENT — the card's primary action, deliberately outweighing
   "View This Home" beneath it. The card's ::after overlay already carries the
   whole surface to the home page, so the text link is a duplicate route; the
   estimator is the only thing here that cannot be reached another way with this
   home's own price, HOA and CDD in it. Solid midnight against a hairline text
   link is the same primary/secondary pair the floor-plan cards use for
   brochure-vs-video, so the hierarchy reads the same everywhere.
   Full-width: at 3-up the label wraps to two lines on a narrow column, and a
   shrink-wrapped button made those two lines ragged against the price above. */
.home__pay{display:inline-flex;align-items:center;justify-content:center;gap:0.5rem;
  width:100%;box-sizing:border-box;padding:0.8rem 1rem;
  font-family:'Jost',sans-serif;text-transform:uppercase;letter-spacing:.16em;
  font-size:0.75rem;font-weight:600;text-align:center;line-height:1.35;
  color:var(--ivory);background:var(--midnight);border:1px solid var(--midnight);
  border-radius:var(--radius);
  transition:background var(--dur-2) var(--ease),border-color var(--dur-2) var(--ease),color var(--dur-2) var(--ease)}
.home__pay:hover{background:var(--gold);border-color:var(--gold);color:var(--midnight)}
.home__pay .arw{color:inherit;transition:transform var(--dur-2) var(--ease);display:inline-block}
.home__pay:hover .arw{transform:translateX(4px)}
/* The secondary route steps back so the pair reads as one primary + one aside,
   not two equal links — it was the same .link-more size and weight as the CTA. */
.home__actions .link-more{font-size:0.6875rem;letter-spacing:.18em;color:var(--muted-aa)}
.home__actions .link-more:hover{color:var(--gold)}
/* PAYMENT DOOR ONLY. /find-your-home prints a monthly payment on the card, so
   the estimator is a sideways route there, not the answer — it reverts to the
   quiet text link it has always been rather than competing with the figure and
   the door's own filters. See home_card(payment=True) in build/generate.py. */
.home__pay--quiet{width:auto;padding:0;background:none;border:0;color:var(--midnight);
  font-weight:500;letter-spacing:.2em;justify-content:flex-start;
  padding-bottom:3px;background-image:linear-gradient(var(--gold),var(--gold));
  background-size:0% 1px;background-repeat:no-repeat;background-position:0 100%;
  transition:background-size var(--dur-3) var(--ease),color var(--dur-2) var(--ease)}
.home__pay--quiet:hover{background:none;color:var(--gold);
  background-image:linear-gradient(var(--gold),var(--gold));background-size:100% 1px;
  background-repeat:no-repeat;background-position:0 100%}
.home__pay--quiet .arw{color:var(--gold)}
.home__broc{display:inline-flex;align-items:center;gap:0.5rem;margin-top:1rem;font-family:'Jost',sans-serif;text-transform:uppercase;letter-spacing:.11em;font-size:0.75rem;font-weight:500;color:var(--midnight);border:1px solid var(--line);border-radius:var(--radius);padding:0.5rem 0.75rem;transition:border-color var(--dur-2) var(--ease),color var(--dur-2) var(--ease)}
.home__broc svg{width:13px;height:13px;flex:none}
.home__broc:hover{border-color:var(--gold);color:var(--gold)}
.home__broc--req{border-style:dashed;color:var(--muted)}

/* ---- Floor-plan filter/sort bar ---- */
.fp-controls{margin-bottom:1.5rem}
.fp-toggle{display:none;font-family:'Jost',sans-serif;text-transform:uppercase;letter-spacing:.16em;font-size:0.75rem;font-weight:500;padding:1rem 1.5rem;border:1px solid var(--midnight);border-radius:var(--radius);background:transparent;color:var(--midnight);cursor:pointer;width:100%}
.fp-inner{display:flex;flex-wrap:wrap;align-items:center;gap:1rem 2rem;padding:1.5rem 1.5rem;background:var(--white);border:1px solid var(--line);border-radius:var(--radius);box-shadow:var(--shadow)}
.fp-group{display:flex;align-items:center;gap:0.75rem;flex-wrap:wrap}
.fp-label{font-family:'Jost',sans-serif;text-transform:uppercase;letter-spacing:.16em;font-size:0.625rem;font-weight:500;color:var(--gold)}
.fp-pills{display:flex;gap:0.5rem;flex-wrap:wrap}
.fp-pills button{font-family:'Jost',sans-serif;font-size:0.75rem;letter-spacing:.05em;padding:0.5rem 0.75rem;border:1px solid var(--line);border-radius:var(--radius);background:transparent;color:var(--ink);cursor:pointer;transition:background var(--dur-1) var(--ease),color var(--dur-1) var(--ease),border-color var(--dur-1) var(--ease)}
.fp-pills button:hover{border-color:var(--gold)}
.fp-pills button.is-on{background:var(--midnight);color:var(--ivory);border-color:var(--midnight)}
.fp-controls select{font-family:'Jost',sans-serif;font-size:0.75rem;padding:0.5rem 0.75rem;border:1px solid var(--line);border-radius:var(--radius);background:var(--ivory);color:var(--ink);cursor:pointer}
.fp-checks{gap:1rem}
.fp-checks label{display:inline-flex;align-items:center;gap:0.5rem;font-size:0.75rem;cursor:pointer;color:var(--ink)}
.fp-checks input{accent-color:var(--gold)}
.fp-clear{font-family:'Jost',sans-serif;text-transform:uppercase;letter-spacing:.14em;font-size:0.75rem;color:var(--muted);background:none;border:none;cursor:pointer;text-decoration:underline;text-underline-offset:3px}
.fp-clear:hover{color:var(--gold)}
.fp-count{text-align:center;font-family:'Jost',sans-serif;text-transform:uppercase;letter-spacing:.14em;font-size:0.75rem;color:var(--muted);margin:0 0 2rem}
@media (max-width:820px){
  .fp-toggle{display:inline-flex;justify-content:center}
  .fp-inner{display:none;flex-direction:column;align-items:flex-start;margin-top:0.75rem}
  .fp-controls.fp-open .fp-inner{display:flex}
}

/* ---- Payment estimator ---- */
.est{display:grid;grid-template-columns:1.15fr 1fr;gap:2rem;align-items:start;max-width:960px;margin:0 auto}
/* Grid items default to min-width:auto, so ANY unbreakable child can push a track
   wider than its column. Belt-and-braces after the .btn--full overflow above. */
.est > *{min-width:0}
.est__inputs{background:var(--white);border:1px solid var(--line);border-radius:var(--radius);padding:2rem;box-shadow:var(--shadow);display:grid;gap:1.5rem}
.est__field{display:grid;gap:0.5rem}
.est__field > span{font-family:'Jost',sans-serif;text-transform:uppercase;letter-spacing:.14em;font-size:0.75rem;font-weight:500;color:var(--midnight);display:flex;align-items:center}
.est__field > span b{color:var(--gold);font-weight:600}
.est__field input[type=text]{font-family:'Cormorant Garamond',serif;font-size:1.5rem;font-weight:600;padding:0.5rem 1rem;border:1px solid var(--line);border-radius:var(--radius);background:var(--ivory);color:var(--midnight)}
.est__pills{display:flex;gap:0.5rem;flex-wrap:wrap}
.est__pills button{font-family:'Jost',sans-serif;font-size:0.75rem;padding:0.5rem 0.75rem;border:1px solid var(--line);border-radius:var(--radius);background:transparent;color:var(--ink);cursor:pointer;transition:background var(--dur-1) var(--ease),color var(--dur-1) var(--ease),border-color var(--dur-1) var(--ease)}
.est__pills button.is-on{background:var(--midnight);color:var(--ivory);border-color:var(--midnight)}
/* DOWN-PAYMENT CHIPS carry two lines: the percentage, and what it costs at the
   current price. GRID, not the flex the other pill rows use — five two-line
   chips wrapped by flex come out ragged (2/2/1 with three different widths on a
   phone), and a price is easiest to compare down a column of equal boxes. The
   auto-fit is deliberate rather than a fixed `repeat(5,1fr)`: a track can never
   be narrower than its floor, so the row drops to two columns on a phone rather
   than squeezing five chips until "$65,998" spills — the 320px failure mode
   this section has hit before. 68px IS that widest amount (0.68rem) plus the
   chip's padding, and it lands five across in the desktop input column.
   Tabular numerals stop the amounts from jittering as the price is typed. */
.est__pills--down{display:grid;grid-template-columns:repeat(auto-fit,minmax(68px,1fr))}
.est__pills--down button{display:flex;flex-direction:column;align-items:center;
  justify-content:center;gap:0.15rem;line-height:1.2;min-width:0;padding:0.5rem}
.est__pill-amt{font-family:'Jost',sans-serif;font-size:0.68rem;letter-spacing:.01em;
  font-variant-numeric:tabular-nums;opacity:.68}
.est__pills--down button.is-on .est__pill-amt{opacity:.85}
/* LOAN TYPE (dark behind config.LOAN_TYPE_ENABLED). Two wide options rather
   than the compact down-payment grid: this is the choice that changes what
   every other control means, so it reads as a decision, not a nudge. The
   explanatory note sits under it because "FHA" and "Conventional" are not
   self-describing to the person this estimator is for — the difference in the
   payment is the whole reason the toggle exists, so it is stated in words
   beside the control that produces it. */
/* THREE PROGRAMS, THREE COLUMNS (VA added 2026-08-02). auto-fit with a 92px
   floor rather than repeat(3,1fr): at 375px inside the estimator's own padding
   three equal columns give each pill ~86px, and "Conventional" needs ~104px at
   this tracking — it wrapped mid-word. auto-fit drops to two columns and then
   one as the space demands, so the longest label is never broken. */
/* LOAN TYPE. The floor has to clear the WIDEST LABEL, not a round number.
   At 92px the auto-fit put two 102px tracks in the 212px estimator column on a
   homesite page at 390px, and "Conventional" is 112px at nowrap — so the button
   overflowed its own track by 12px and the word was clipped. A grid item with
   white-space:nowrap does not shrink below min-content, so no amount of 1fr
   rescues it.
   min(...,100%) is the standard guard: a track floor wider than the container
   makes the grid itself overflow, which trades a clipped word for a clipped
   row. Below ~124px of column the row drops to one full-width pill per line,
   which is the right answer on a phone anyway. */
.est__pills--loan{display:grid;
  grid-template-columns:repeat(auto-fit,minmax(min(7.25rem,100%),1fr));gap:0.5rem}
.est__pills--loan button{min-height:44px;letter-spacing:.06em;white-space:nowrap}
.est__loan-note{margin:0.15rem 0 0;font-family:'Jost',sans-serif;font-size:0.7rem;
  line-height:1.65;letter-spacing:.01em;color:var(--muted-aa)}
/* The VA paragraph sits under the FHA/Conventional one and carries the same
   treatment — it is a third explainer, not a footnote to the other two. The
   <b> runs (no monthly MI, the disability waiver) are the two facts a veteran
   is actually scanning for. */
.est__loan-note + .est__loan-note{margin-top:var(--sp-3)}
.est__loan-note b{font-weight:500;color:var(--ink)}
/* A hidden chip set must be GONE, not just invisible: .est__pills--down is a
   grid, and display:grid on a [hidden] element beats the UA sheet's
   display:none and would render both programs' chips at once. */
.est__pills--down[hidden]{display:none}
/* THIS HOME'S FIXED COSTS. Deliberately NOT a control: no border, no field
   affordance, no hover — it has to read as a printed fact beside the things
   that do move, or a visitor will hunt for the slider that isn't there. */
.est__fixed{gap:0.6rem}
.est__fixed-rows{display:grid;gap:0.35rem;margin:0;padding:0.75rem 0.9rem;
  background:var(--ivory);border-radius:var(--radius)}
.est__fixed-row{display:flex;align-items:baseline;justify-content:space-between;gap:1rem}
.est__fixed-row dt{font-family:'Jost',sans-serif;font-size:0.8rem;color:var(--muted)}
.est__fixed-row dd{margin:0;font-family:'Jost',sans-serif;font-size:0.85rem;
  font-weight:500;color:var(--midnight);font-variant-numeric:tabular-nums}
.est__fixed-note{font-family:'Jost',sans-serif;font-size:0.7rem;line-height:1.45;
  color:var(--muted);margin:0}
.est__fixed[hidden]{display:none}
.est__field input[type=range]{width:100%;accent-color:var(--gold)}
.est__output{background:var(--midnight);color:var(--ivory);border-radius:var(--radius);padding:2rem 2rem;text-align:center;box-shadow:var(--shadow-lift)}
.est__label{font-family:'Jost',sans-serif;text-transform:uppercase;letter-spacing:.16em;font-size:0.75rem;color:var(--gold-soft);margin-top:1.5rem}
.est__label:first-child{margin-top:0}
.est__big{font-family:'Cormorant Garamond',serif;font-size:clamp(1.75rem,7.2vw,2.75rem);font-weight:600;color:var(--ivory);line-height:1.05;white-space:nowrap;min-width:0;overflow-wrap:normal}
.est__big--sub{font-size:2rem;color:var(--gold-soft)}
.est__detail{font-size:0.75rem;letter-spacing:.02em;color:rgba(var(--ivory-rgb),.6);margin:0.75rem 0 0.5rem}
.est__basis{font-family:'Jost',sans-serif;font-size:0.75rem;letter-spacing:.02em;color:rgba(var(--ivory-rgb),.72);margin:0 0 0.5rem}
.est__note{font-family:'Jost',sans-serif;font-size:0.75rem;line-height:1.5;color:var(--gold-soft);margin:0 0 1.5rem;font-style:italic}
@media (max-width:760px){.est{grid-template-columns:1fr}}

/* ---- Incentives ---- */
.adv{background:var(--white);border:1px solid var(--line);border-radius:var(--radius);padding:2rem;box-shadow:var(--shadow)}
.adv h3{font-size:1.25rem;font-weight:600;margin-bottom:0.5rem}
.adv p{color:var(--muted);font-size:1rem;font-weight:300}
.adv--feature{grid-column:1/-1;background:var(--midnight);color:var(--ivory);display:flex;flex-direction:column;justify-content:center;padding:2rem}
.adv--feature h3{color:var(--ivory);font-size:clamp(1.6rem,3vw,2.2rem)}
.adv--feature p{color:rgba(var(--ivory-rgb),.86);font-size:1rem}
.adv__tag{display:inline-block;font-family:'Jost',sans-serif;text-transform:uppercase;letter-spacing:.22em;font-size:0.625rem;color:var(--gold-soft);margin-bottom:0.75rem}
.fineprint{max-width:760px;margin:2rem auto 0;text-align:center;font-size:0.75rem;color:var(--muted-aa);line-height:1.7}
/* A link in fineprint was indistinguishable from the text around it: the global
   rule is a{color:inherit;text-decoration:none}, so it inherited --muted-aa and
   carried no underline — same colour, no affordance, not discoverable as a link
   at all (WCAG 1.4.1). Same treatment as .dsc__credit a / .page-hero__credit a.
   The colour stays inherited ON PURPOSE: --muted-aa is 4.95:1 on --ivory (AA for
   normal text), where --gold is 2.50:1 and would have failed. The underline is
   the affordance; the contrast is unchanged from the text it sits in. */
.fineprint a{color:inherit;text-decoration:underline;text-underline-offset:2px}
.est__disclaimer{max-width:860px}

/* ---- Compare communities ---- */
.cmp-chips{display:flex;flex-wrap:wrap;gap:0.5rem;justify-content:center;margin-bottom:2rem}
.cmp-chip{font-family:'Jost',sans-serif;text-transform:uppercase;letter-spacing:.12em;font-size:0.75rem;font-weight:500;padding:0.5rem 1rem;border:1px solid var(--line);border-radius:var(--radius);background:transparent;color:var(--ink);cursor:pointer;transition:background var(--dur-1) var(--ease),color var(--dur-1) var(--ease),border-color var(--dur-1) var(--ease)}
.cmp-chip:hover{border-color:var(--gold)}
.cmp-chip.is-on{background:var(--midnight);color:var(--ivory);border-color:var(--midnight)}
.cmp-chip:disabled{opacity:.38;cursor:not-allowed}
.cmp-empty{text-align:center;color:var(--muted)}
.cmp-cards{display:grid;grid-template-columns:repeat(auto-fit,minmax(248px,1fr));gap:1.5rem;max-width:980px;margin:0 auto}
.cmp-card{background:var(--white);border:1px solid var(--line);border-radius:var(--radius);padding:2rem;box-shadow:var(--shadow);display:flex;flex-direction:column}
.cmp-card__loc{font-family:'Jost',sans-serif;text-transform:uppercase;letter-spacing:.16em;font-size:0.625rem;color:var(--gold)}
.cmp-card__name{font-family:'Cormorant Garamond',serif;font-size:1.5rem;font-weight:600;margin:0.25rem 0 1rem;line-height:1.1}
.cmp-dl{margin:0 0 1.5rem}
.cmp-row{padding:0.75rem 0;border-top:1px solid var(--line)}
.cmp-row dt{font-family:'Jost',sans-serif;text-transform:uppercase;letter-spacing:.12em;font-size:0.75rem;color:var(--muted);margin-bottom:0.25rem}
.cmp-row dd{font-size:1rem;color:var(--ink)}
.cmp-card .btn{margin-top:auto}

/* nav: Realtor Partners — subtle gold accent, last before Contact */
/* SHAPE, BORDER, PADDING, COLOUR AND TYPE ARE NOT DECLARED HERE ANY MORE.
   They come from the shared `.nav__cta,.nav__partner` rule above (2026-08-13),
   which is what makes the two pills the same pill. This rule now carries ONLY
   what is genuinely particular to the agent door: the gap for its status dot
   and the lift on hover. Re-adding a border-radius, padding or colour here is
   how the pair drifted apart the first time. */
.nav__partner{gap:.55rem}
.nav__partner::before{content:"";width:6px;height:6px;border-radius:50%;background:var(--gold);box-shadow:0 0 0 0 rgba(var(--gold-soft-rgb),.5);animation:navpulse 2.6s ease-in-out infinite;flex:none}
.nav__partner:hover{transform:translateY(-1px);box-shadow:0 6px 18px rgba(var(--black-rgb),.28)}
/* slow, subtle ring — a quiet breath, never a blink */
@keyframes navpulse{0%{box-shadow:0 0 0 0 rgba(var(--gold-soft-rgb),.45)}60%{box-shadow:0 0 0 5px rgba(var(--gold-soft-rgb),0)}100%{box-shadow:0 0 0 0 rgba(var(--gold-soft-rgb),0)}}
@media (prefers-reduced-motion:reduce){.nav__partner::before{animation:none}}
/* partners: why-partner strip */
.whypartner .wp{background:var(--white);border:1px solid var(--line);border-radius:var(--radius);padding:1.5rem;box-shadow:var(--shadow)}
.whypartner .wp h3{font-size:1.25rem;font-weight:600;margin-bottom:0.5rem}
.whypartner .wp p{color:var(--muted);font-size:0.875rem;font-weight:300}
@media (max-width:900px){.whypartner{grid-template-columns:repeat(2,1fr)}}
@media (max-width:560px){.whypartner{grid-template-columns:1fr}}

/* ============ Chat widget — "Tony's Assistant" ============ */
.tgchat-launch{position:fixed;right:1.5rem;bottom:1.5rem;z-index:var(--z-chat-launcher);display:inline-flex;align-items:center;gap:0.5rem;
  background:var(--midnight);color:var(--ivory);border:1px solid var(--gold);border-radius:40px;padding:0.75rem 1rem;
  font-family:'Jost',sans-serif;font-size:0.75rem;letter-spacing:.08em;cursor:pointer;box-shadow:0 10px 30px rgba(var(--midnight-rgb),.4);
  transition:transform var(--dur-2) var(--ease),background var(--dur-2) var(--ease),opacity var(--dur-1) var(--ease)}
.tgchat-launch svg{width:20px;height:20px;color:var(--gold-soft)}
.tgchat-launch:hover{transform:translateY(-2px);background:var(--midnight-2)}
.tgchat-launch.is-hidden{opacity:0;pointer-events:none;transform:translateY(10px)}
/* THE FACE ON THE BUTTON. The initials sit underneath and the photograph over
   them, so a 404 on the headshot degrades to "TG" in a champagne ring rather
   than to an empty circle. Both are the same 30px box, so nothing reflows. */
.tgchat-launch{padding-left:0.4rem}
.tgchat-launch__av{position:relative;width:30px;height:30px;flex:none;border-radius:50%;
  overflow:hidden;border:1.5px solid var(--gold-soft);background:var(--midnight-2)}
.tgchat-launch__av img{position:absolute;inset:0;width:100%;height:100%;
  object-fit:cover;object-position:50% 22%}
.tgchat-launch__ini{position:absolute;inset:0;display:grid;place-items:center;
  font-family:'Jost',sans-serif;font-size:0.6875rem;letter-spacing:.06em;color:var(--gold-soft)}
/* The negative margin absorbs the TRAILING letter-space. letter-spacing adds
   its gap after the last glyph too, so the label's content box is .14em wider
   than the text it draws — 4px at this size, which is exactly how much the
   launcher overflowed its own button at 390px (scrollWidth 110 vs clientWidth
   106). Nothing visible moves; the phantom gap stops being counted. */
.tgchat-launch__lbl{letter-spacing:.14em;text-transform:uppercase;
  margin-right:-.14em}
/* FIRST VISIT ONLY (assets/chat.js writes tg_chat_seen on first paint). A ring
   that expands and fades once every few seconds — an attention cue, not an
   alarm. Reduced-motion turns it off entirely rather than slowing it down:
   the cue is decorative and nothing depends on seeing it. */
.tgchat-launch__pulse{display:none}
.tgchat-launch.is-new .tgchat-launch__pulse{display:block;position:absolute;
  inset:-3px;border-radius:44px;border:1.5px solid var(--gold-soft);
  animation:tgring 2.6s ease-out infinite;pointer-events:none}
@keyframes tgring{0%{opacity:.75;transform:scale(1)}70%{opacity:0;transform:scale(1.14)}100%{opacity:0;transform:scale(1.14)}}
@media (prefers-reduced-motion:reduce){.tgchat-launch.is-new .tgchat-launch__pulse{animation:none;opacity:.5}}

/* ---- proactive teaser ----
   A message preview sitting directly above the launcher, which is itself
   already anchored above the Call/Text bar — so the teaser can never cover
   the call bar, and being bottom-anchored it can never cover a hero CTA.
   It is a peer of the launcher rather than a child, so hiding the launcher on
   open does not take a half-faded teaser with it. */
.tgchat-teaser{position:fixed;right:1.5rem;z-index:var(--z-chat-launcher);
  bottom:calc(1.5rem + 58px);display:flex;align-items:flex-start;gap:0.5rem;
  width:min(300px,calc(100vw - 3rem));padding:0.7rem 0.75rem;
  background:var(--white);border:1px solid var(--line);border-radius:12px;
  border-bottom-right-radius:3px;box-shadow:0 14px 40px rgba(var(--midnight-rgb),.28);
  opacity:0;transform:translateY(10px);pointer-events:none;
  transition:opacity var(--dur-2) var(--ease),transform var(--dur-2) var(--ease)}
.tgchat-teaser.is-in{opacity:1;transform:none;pointer-events:auto}
.tgchat-teaser__av{width:32px;height:32px;flex:none;border-radius:50%;object-fit:cover;
  object-position:50% 22%;border:1.5px solid var(--gold-soft)}
.tgchat-teaser__msg{flex:1;min-width:0;text-align:left;background:none;border:none;
  padding:0;cursor:pointer;font-family:'Jost',sans-serif;font-size:0.8125rem;
  line-height:1.45;color:var(--midnight)}
.tgchat-teaser__x{flex:none;background:none;border:none;cursor:pointer;
  font-size:1.1rem;line-height:1;color:var(--muted-aa);padding:0 0.15rem;
  /* 24px is under the 44px tap target the sweep prefers, so the hit area is
     expanded with padding-box rather than by making the glyph bigger. */
  min-width:28px;min-height:28px}
.tgchat-teaser__x:hover{color:var(--midnight)}
@media (prefers-reduced-motion:reduce){.tgchat-teaser{transition:none}}
.tgchat-panel{position:fixed;right:1.5rem;bottom:1.5rem;z-index:calc(var(--z-chat-launcher) + 1);width:380px;max-width:calc(100vw - 3rem);height:560px;max-height:calc(100dvh - 3rem);
  display:flex;flex-direction:column;background:var(--ivory);border:1px solid var(--line);border-radius:10px;overflow:hidden;
  box-shadow:0 24px 70px rgba(var(--midnight-rgb),.34);opacity:0;transform:translateY(16px) scale(.98);pointer-events:none;transition:opacity var(--dur-2) var(--ease),transform var(--dur-2) var(--ease)}
.tgchat-panel.is-open{opacity:1;transform:none;pointer-events:auto}
.tgchat-head{display:flex;align-items:center;gap:0.75rem;padding:1rem 1rem;background:var(--midnight);color:var(--ivory)}
.tgchat-head__avwrap{position:relative;width:44px;height:44px;flex:none;border-radius:50%;
  overflow:hidden;border:1.5px solid var(--gold-soft);background:var(--midnight-2);
  box-shadow:0 2px 8px rgba(var(--black-rgb),.28)}
.tgchat-head__ini{position:absolute;inset:0;display:grid;place-items:center;
  font-family:'Jost',sans-serif;font-size:0.875rem;letter-spacing:.06em;color:var(--gold-soft)}
.tgchat-head__avatar{position:absolute;inset:0;width:100%;height:100%;border-radius:0;
  object-fit:cover;object-position:50% 22%;border:none;box-shadow:none}
.tgchat-head__meta{flex:1;min-width:0}
.tgchat-head__name{font-family:'Cormorant Garamond',serif;font-size:1.25rem;font-weight:600;margin:0;line-height:1.1}
.tgchat-head__sub{display:flex;align-items:center;gap:0.5rem;font-family:'Jost',sans-serif;font-size:0.625rem;letter-spacing:.09em;text-transform:uppercase;color:var(--gold-soft);margin:0.25rem 0 0}
.tgchat-dot{width:8px;height:8px;border-radius:50%;background:var(--success);flex:none;box-shadow:0 0 0 0 rgba(var(--success-rgb),.7);animation:tgpulse 2s infinite}
@keyframes tgpulse{0%{box-shadow:0 0 0 0 rgba(var(--success-rgb),.55)}70%{box-shadow:0 0 0 6px rgba(var(--success-rgb),0)}100%{box-shadow:0 0 0 0 rgba(var(--success-rgb),0)}}
@media (prefers-reduced-motion:reduce){.tgchat-dot{animation:none}}
.tgchat-x{background:none;border:none;color:var(--ivory);font-size:1.5rem;line-height:1;cursor:pointer;opacity:.8;padding:0 0.25rem}
.tgchat-x:hover{opacity:1}
.tgchat-log{flex:1;min-height:0;overflow-y:auto;-webkit-overflow-scrolling:touch;overscroll-behavior:contain;padding:1rem;display:flex;flex-direction:column;gap:0.5rem;background:var(--cream)}
.tgchat-msg{max-width:84%;padding:0.75rem 1rem;border-radius:12px;font-family:'Jost',sans-serif;font-size:0.875rem;line-height:1.5;white-space:pre-wrap;word-wrap:break-word}
.tgchat-msg--assistant{align-self:flex-start;background:var(--white);color:var(--midnight);border:1px solid var(--line);border-bottom-left-radius:3px}
.tgchat-msg--user{align-self:flex-end;background:var(--midnight);color:var(--ivory);border-bottom-right-radius:3px}
/* Assistant messages ride in a row beside Tony's headshot. The bubble keeps its
   own styling; the row takes over the flex-column alignment it used to own. */
.tgchat-row{display:flex;align-items:flex-end;gap:0.5rem;align-self:flex-start;max-width:92%}
.tgchat-row .tgchat-msg--assistant{align-self:auto;max-width:100%}
.tgchat-av{width:28px;height:28px;border-radius:50%;object-fit:cover;object-position:50% 22%;flex:none;border:1.5px solid var(--gold-soft);box-shadow:0 1px 4px rgba(var(--black-rgb),.18);margin-bottom:2px}
.tgchat-typing{display:flex;gap:4px;align-items:center}
.tgchat-typing span{width:6px;height:6px;border-radius:50%;background:var(--muted);animation:tgblink 1.2s infinite}
.tgchat-typing span:nth-child(2){animation-delay:.2s}.tgchat-typing span:nth-child(3){animation-delay:.4s}
@keyframes tgblink{0%,60%,100%{opacity:.3}30%{opacity:1}}
/* REG Z INSIDE THE BUBBLE. Quiet, but not hidden: it is a full sentence at a
   readable size, sitting on a hairline under the figure it qualifies, inside
   the same bubble. Never a tooltip, never collapsed, never in the footer. */
.tgchat-terms{margin:0.55rem 0 0;padding-top:0.5rem;border-top:1px solid var(--line);
  font-size:0.6875rem;line-height:1.5;color:var(--muted-aa);white-space:normal}
/* ---- quick replies ----
   One scrolling row above the input. They are shortcuts, so they must not
   push the input field off a phone screen — one row, horizontally scrollable,
   no wrap. */
.tgchat-quick{display:flex;gap:0.4rem;padding:0.6rem 0.75rem 0;background:var(--ivory);
  overflow-x:auto;scrollbar-width:none;-webkit-overflow-scrolling:touch}
.tgchat-quick::-webkit-scrollbar{display:none}
.tgchat-quick:empty{display:none}
.tgchat-chip{flex:none;white-space:nowrap;font-family:'Jost',sans-serif;font-size:0.6875rem;
  letter-spacing:.05em;padding:0.45rem 0.7rem;min-height:32px;cursor:pointer;
  color:var(--midnight);background:var(--white);border:1px solid var(--line);
  border-radius:99px;text-decoration:none;display:inline-flex;align-items:center;
  transition:border-color var(--dur-1) var(--ease)}
.tgchat-chip:hover{border-color:var(--gold)}
/* The exit to a human reads as the exit to a human. */
.tgchat-chip--sms{border-color:var(--gold);color:var(--gold-deep)}
/* SMS consent row — sits between the quick replies and the composer, revealed
   by assets/chat.js only once a reply asks for a number. `hidden` must win over
   the flex display or the row shows on every conversation. */
.tgchat-consent{display:flex;align-items:flex-start;gap:0.5rem;
  padding:0.625rem 0.875rem 0;background:var(--ivory);
  font-family:'Jost',sans-serif;font-size:0.6875rem;line-height:1.5;font-weight:300;
  color:var(--muted);cursor:pointer}
.tgchat-consent[hidden]{display:none}
.tgchat-consent input{flex:none;width:17px;height:17px;margin-top:0.15rem;accent-color:var(--gold)}
.tgchat-consent a{color:var(--midnight);border-bottom:1px solid var(--gold)}
.tgchat-form{display:flex;gap:0.5rem;padding:0.75rem;border-top:1px solid var(--line);background:var(--ivory)}
.tgchat-input{flex:1;min-width:0;border:1px solid var(--line);border-radius:22px;padding:0.75rem 1rem;font-family:'Jost',sans-serif;font-size:16px;outline:none}
.tgchat-input:focus{border-color:var(--gold)}
.tgchat-send{flex:none;width:42px;height:42px;border-radius:50%;background:var(--midnight);color:var(--ivory);border:1px solid var(--gold);font-size:1rem;cursor:pointer}
.tgchat-send:hover{background:var(--midnight-2)}
.tgchat-foot{margin:0;padding:0.5rem 1rem 0.75rem;font-family:'Jost',sans-serif;font-size:0.625rem;color:var(--muted);text-align:center;background:var(--ivory)}
@media (max-width:560px){
  .tgchat-panel{right:0;bottom:0;width:100vw;max-width:100vw;height:100dvh;max-height:100dvh;border-radius:0}
  .tgchat-launch{right:1rem;bottom:1rem;padding:0.75rem 1rem}
  body.tgchat-lock{overflow:hidden}
}

/* Keep the chat widget clear of the sticky mobile Call/Text bar — chat moves, never the bar */
@media (max-width:640px){
  .tgchat-launch{bottom:calc(var(--callbar-h) + 12px)}
  .tgchat-panel{bottom:calc(var(--callbar-h) + 12px);max-height:calc(100dvh - var(--callbar-h) - 2.5rem)}
}
@media (max-width:560px){
  /* fullscreen panel must sit ABOVE the bar, never cover it */
  .tgchat-launch{right:1rem;bottom:calc(var(--callbar-h) + 12px)}
  .tgchat-panel{right:0;width:100vw;max-width:100vw;bottom:var(--callbar-h);
    height:calc(100dvh - var(--callbar-h));max-height:calc(100dvh - var(--callbar-h))}
  /* The quick-reply row is the first thing to give up space when the on-screen
     keyboard halves the sheet — but it is never dropped, because the exit to a
     human lives in it. */
  .tgchat-quick{padding:0.45rem 0.6rem 0}
  .tgchat-chip{font-size:0.65rem;padding:0.4rem 0.6rem}
}
/* THE TEASER TRACKS THE LAUNCHER, at every breakpoint, because the launcher is
   what is already positioned clear of the Call/Text bar (and of .pbar on
   /partners). 58px is the launcher's own height plus its 12px gap; keeping the
   teaser a fixed distance above it means neither can ever cover the bar, and
   the sweep's "control the user cannot escape" test has nothing to find. */
@media (max-width:640px){
  /* 58px was the launcher's height plus its gap, written as a literal. It is
     --fab-h + 2px now, from the same variable the badge reserves against, so the
     three cannot drift apart the way they did before 2026-08-19. */
  .tgchat-teaser{right:1rem;bottom:calc(var(--callbar-h) + 12px + var(--fab-h) + 2px);
    width:min(300px,calc(100vw - 2rem))}
  body.partners .tgchat-teaser{bottom:calc(var(--pbar-h) + 12px + var(--fab-h) + 2px)}
  /* AND IT CLEARS THE BADGE WHEN THERE IS ONE. .tg-badge-on is set by
     assets/agent-attrib.js at the moment the badge is appended, alongside
     --badge-h, which it measures off the mounted element rather than guessing:
     the pill's height depends on which of its three spans the 430px rule has
     hidden, so a literal here would be right at one width and wrong at the
     other. Without this the teaser opens directly on top of the agent's name,
     which is the same collision one row up. */
  html.tg-badge-on .tgchat-teaser{
    bottom:calc(var(--callbar-h) + env(safe-area-inset-bottom)
                + 12px + var(--fab-h) + 10px + var(--badge-h) + 10px)}
}
/* Never both at once: an open panel replaces the teaser. */
body.tgchat-open .tgchat-teaser{display:none}
/* video tour download — self-hosted mp4, styled to match the brochure action */
.plan__act--viddl{background:none;color:var(--muted);border:1px solid var(--line)}
.plan__act--viddl:hover{border-color:var(--gold);color:var(--gold)}

/* --- Price Reduced featured card: first thing a realtor sees on /partners --- */
.prog--feature{grid-column:1 / -1;border-top-width:3px;background:
  linear-gradient(180deg,rgba(var(--gold-soft-rgb),.10),rgba(var(--gold-soft-rgb),0) 55%),var(--white)}
.prog--feature .prog__name{font-size:2rem}
.prog--feature .prog__badge{font-size:0.625rem;padding:0.5rem 0.75rem}
.prog__figure{font-family:'Cormorant Garamond',serif;font-size:2.75rem;font-weight:600;color:var(--midnight);
  line-height:1;margin:0.25rem 0 0rem;display:flex;align-items:baseline;gap:0.5rem;flex-wrap:wrap}
.prog__figure span{font-family:'Jost',sans-serif;font-size:0.625rem;font-weight:500;letter-spacing:.18em;
  text-transform:uppercase;color:var(--muted)}
.prog__figure em{font-family:'Jost',sans-serif;font-style:normal;font-size:0.75rem;font-weight:400;
  letter-spacing:.06em;color:var(--gold);align-self:center}
@media (min-width:900px){ .prog--feature .prog__headline{font-size:1.5rem} }
/* inventory item: name + last-updated date */
.flyer__meta{display:flex;flex-direction:column;gap:0.25rem;min-width:0;text-align:left}
.flyer__date{font-family:'Jost',sans-serif;font-size:0.625rem;letter-spacing:.08em;color:var(--muted)}
.flyer{border:none;cursor:pointer;width:100%;font:inherit;text-align:left}
/* four-up sticky bar */
@media (max-width:640px){
  .pbar{grid-template-columns:repeat(4,1fr)}
  .pbar__btn{font-size:0.75rem;letter-spacing:.08em}
}
/* BUTTON RESET ONLY — never typography. `font:inherit` used to live here, and
   the shorthand quietly reset font-size, font-weight, font-style and
   line-height along with the family. Three of the four .home__broc emitters
   are <button> (brochure viewer, video tour) and one is an <a> (the
   brochure-on-request mailto), so the same control rendered at the inherited
   1rem/400 as a button and at .75rem/500 as a link — side by side in the same
   .home__assets row on every card that had a video. Uniform typography is the
   whole point of the class, so the reset is now scoped to the things a UA
   actually gets wrong on a <button>: chrome, cursor, and the fact that a
   button does not inherit font-family at all. Size, weight, tracking and case
   come from .home__broc above and from nowhere else. */
.home__broc{background:none;cursor:pointer;font-family:'Jost',sans-serif;
  font-size:0.75rem;font-weight:500;font-style:normal;line-height:1.2}

/* --- unified viewer: video mode --- */
.bv__video{display:none;width:100%;max-height:80dvh;background:var(--video-bg)}
.bv.is-video .bv__stage picture{display:none}
.bv.is-video .bv__video{display:block}
.bv.is-video .bv__stage{overflow:visible;touch-action:auto}
/* --- badge band: badges get their own zone, never over text --- */
.home__badges{position:absolute;top:0;left:0;right:0;z-index:var(--z-local-over);display:flex;align-items:flex-start;
  justify-content:flex-start;gap:0.5rem;padding:1rem;pointer-events:none}
.home__badges .badge{position:static;top:auto;right:auto;left:auto}
.home__badge--status{margin-left:auto}
/* ---- real listing photos: carousel + chip -------------------------------
   The chip sits at the BOTTOM-LEFT, structurally separate from the top badge
   band (Price Reduced left / status right), so the two can never collide no matter
   how long either label gets. Dots take the bottom-right for the same reason. */
/* .badge--real ("REAL PHOTOS") WAS REMOVED 2026-07-27, RULE AND STYLE BOTH.
   It was emitted whenever the photo manifest held any entry for a home, which
   included every card whose lead slide is the plan's elevation RENDERING — so
   it captioned artist's art as photography on a public page. The rule is gone
   as well as the markup, because a dead rule is how a component comes back.
   Any future photo-truth badge needs per-image provenance, not a manifest
   count; see build/generate.py home_card() and the gate in audit_cards.py. */
.hcar{touch-action:pan-y}
.hcar__track{display:flex;height:100%;width:100%;
  transition:transform var(--dur-3) var(--ease);will-change:transform}
.hcar__slide{flex:0 0 100%;width:100%;height:100%;display:block}
.hcar__slide img{width:100%;height:100%;object-fit:cover;display:block}
/* the dots sit on whatever the photo happens to be — a bright sky washed
   them out completely, so they carry their own scrim. */
.hcar__dots{position:absolute;bottom:.85rem;right:.85rem;z-index:var(--z-local-control);display:flex;gap:0.25rem;
  padding:0.25rem 0.5rem;border-radius:99px;background:rgba(var(--midnight-rgb),.42);backdrop-filter:blur(3px)}
.hcar__dot{width:7px;height:7px;border-radius:50%;border:0;padding:0;cursor:pointer;
  background:rgba(var(--ivory-rgb),.62);transition:background var(--dur-2) var(--ease),transform var(--dur-2) var(--ease)}
.hcar__dot.is-on{background:var(--gold);transform:scale(1.25)}
.hcar__arw{position:absolute;top:50%;transform:translateY(-50%);z-index:var(--z-local-control);width:34px;height:34px;
  border-radius:50%;border:1px solid rgba(var(--gold-soft-rgb),.5);background:rgba(var(--midnight-rgb),.55);
  backdrop-filter:blur(4px);color:var(--ivory);font-size:1rem;line-height:1;cursor:pointer;
  opacity:0;transition:opacity var(--dur-2) var(--ease),background var(--dur-2) var(--ease)}
.hcar__arw--prev{left:.6rem}
.hcar__arw--next{right:.6rem}
.hcar:hover .hcar__arw,.hcar__arw:focus-visible{opacity:1}
.hcar__arw:hover{background:var(--gold);color:var(--midnight)}
/* Touch devices have no hover, and a permanently-visible arrow over a photo
   looks like a mistake — swipe and the dots carry it there. */
@media (hover:none){ .hcar__arw{display:none} }
/* Placeholder cards put the badge band in NORMAL FLOW so the plan name can only
   ever occupy the space left over — overlap is impossible at any text size. */
.home__media--ph{display:flex;flex-direction:column;align-items:stretch;justify-content:flex-start;padding-top:0}
.home__media--ph .home__badges{position:static;flex:none}
.home__media--ph .home__ph-name{flex:1;min-height:0;display:flex;align-items:center;justify-content:center;
  padding:0.25rem 1rem 1rem;overflow:hidden}
.home__assets{display:flex;flex-wrap:wrap;gap:0.5rem;margin-top:1rem}
.home__assets .home__broc{margin-top:0}
.home__vid svg{width:13px;height:13px}
.flyer-actions{gap:0.75rem;flex-wrap:wrap}

/* --- desktop hover preview (pointer devices only) --- */
.bvpv{position:fixed;z-index:calc(var(--z-modal) + 1);width:380px;padding:6px;background:var(--white);
  border:1px solid var(--gold-soft);border-radius:var(--radius);box-shadow:0 18px 50px rgba(var(--midnight-rgb),.28);
  opacity:0;visibility:hidden;transform:translateY(4px);pointer-events:none;
  transition:opacity var(--dur-1) var(--ease),transform var(--dur-1) var(--ease),visibility var(--dur-1)}
.bvpv.is-on{opacity:1;visibility:visible;transform:none}
.bvpv__img{display:block;width:100%;height:auto;border-radius:1px}
@media (hover:none),(pointer:coarse){.bvpv{display:none!important}}

/* --- mobile chat viewport: sized from visualViewport so the keyboard can't
       cut off the header or lose scroll position (JS sets --tgvh/--tgvtop) --- */
body.tgchat-open{overflow:hidden;overscroll-behavior:none}
@media (max-width:560px){
  /* THE PANEL OPENS OVER THE PAGE, IT DOES NOT REPLACE IT (brief section 5).
     It used to take the full dynamic viewport — top:0, height:100dvh — so the
     homes list underneath was not merely covered but gone, and closing the
     chat read as a navigation back rather than a dismissal. Worse, --tgvh is
     set from the visual viewport, so when the keyboard closed and the value
     lagged a frame the sheet kept its shortened height against a full-height
     body and left a band of dead space below it with nothing rendered in it.

     A SHEET WITH A CEILING FIXES BOTH. It is bottom-anchored and capped at
     88% of the viewport, so the list is always visible above it — the page is
     still there, which is what makes this a panel and not a screen — and any
     lag in --tgvh shortens the sheet from a bottom edge that is pinned to the
     bottom of the screen, so the gap it would have left is off-screen instead
     of inside the layout. */
  .tgchat-panel.is-open{
    top:auto;
    bottom:var(--callbar-h,0px);
    height:auto;
    max-height:min(88dvh, calc(var(--tgvh, 100dvh) - 2.5rem));
    border-radius:14px 14px 0 0;
  }
  /* The call bar stays put. It was hidden so the panel could own the screen;
     the panel no longer owns the screen, and hiding a fixed element on open
     collapsed its height and shifted the page under the sheet. */
  body.tgchat-open .pbar{display:none!important}
}

/* =============================================================================
   BROCHURE VIEWER — full-screen page-image lightbox (no PDF embed).
   RESTORED: this block was deleted by a CSS block-replacement during the Programs
   redesign, which made the JS-injected overlay render inline as page content.
   ========================================================================== */
/* OPAQUE. ALPHA 1.0, NOT .97 (Tony, 2026-08-22).
   Reported from an iPhone: "content from the card underneath bleeds through the
   overlay text" — VIEW BROCHURE, VIDEO & AVAILABILITY and the plan spec line
   ghosting behind the community list. Three per cent of a bright card printed
   through body copy is not a subtle tint, it is two texts fighting, and it is
   worst exactly where it matters most: small type over a photograph.
   A backdrop that exists to separate two layers has no business being
   translucent. build/audit_overlay_opacity.py asserts alpha 1.0 on every
   backdrop that covers text, so this cannot drift back to a "nicer" value. */
.bv{position:fixed;inset:0;z-index:var(--z-modal);background:rgb(var(--midnight-rgb));
  display:flex;align-items:center;justify-content:center;
  opacity:0;visibility:hidden;transition:opacity var(--dur-2) var(--ease),visibility var(--dur-2)}
.bv.is-open{opacity:1;visibility:visible}
body.bv-open{overflow:hidden}
.bv__bar{position:absolute;top:0;left:0;right:0;display:flex;align-items:center;gap:1rem;
  padding:0.75rem 1rem;background:linear-gradient(to bottom,rgba(var(--midnight-rgb),.95),rgba(var(--midnight-rgb),0));z-index:var(--z-local-over)}
.bv__id{flex:1;min-width:0}
.bv__title{font-family:'Cormorant Garamond',serif;font-size:1.25rem;font-weight:600;color:var(--ivory);
  margin:0;line-height:1.1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.bv__count{font-family:'Jost',sans-serif;font-size:0.75rem;letter-spacing:.16em;text-transform:uppercase;
  color:var(--gold-soft);margin:0.25rem 0 0}
.bv__variants{display:flex;gap:0.5rem;flex:none}
.bv__variant{font-family:'Jost',sans-serif;font-size:0.75rem;letter-spacing:.12em;text-transform:uppercase;
  font-weight:500;color:var(--gold-soft);background:none;border:1px solid rgba(var(--gold-soft-rgb),.45);
  border-radius:999px;padding:0.5rem 0.75rem;cursor:pointer;transition:background var(--dur-1) var(--ease),color var(--dur-1) var(--ease)}
.bv__variant.is-on{background:var(--gold-soft);color:var(--midnight);border-color:var(--gold-soft)}
.bv__dl{display:inline-flex;align-items:center;gap:0.5rem;flex:none;font-family:'Jost',sans-serif;
  font-size:0.75rem;letter-spacing:.12em;text-transform:uppercase;font-weight:500;
  color:var(--midnight);background:var(--gold-soft);border-radius:var(--radius);padding:0.5rem 0.75rem}
.bv__dl svg{width:13px;height:13px}
.bv__dl:hover{background:var(--gold)}
/* SHARE, beside download (Tony's placement call, 2026-09-12). Outlined rather
   than filled so the two controls in the header are not two equal-weight
   calls to action — download is the one an agent came for.
   [hidden] IS RESTATED because `display:inline-flex` on the class would
   otherwise beat the attribute's UA `display:none`, and the failure mode is a
   share button on a community plat that copies a floor-plan URL. */
.bv__share{display:inline-flex;align-items:center;gap:0.4rem;flex:none;
  font-family:'Jost',sans-serif;font-size:0.75rem;letter-spacing:.12em;
  text-transform:uppercase;font-weight:500;cursor:pointer;
  color:var(--gold-soft);background:none;border:1px solid rgba(var(--gold-soft-rgb),.45);
  border-radius:var(--radius);padding:0.5rem 0.75rem;min-height:44px;
  transition:background var(--dur-1) var(--ease),color var(--dur-1) var(--ease)}
.bv__share[hidden]{display:none}
.bv__share svg{width:14px;height:14px;fill:none;stroke:currentColor}
.bv__share:hover{background:var(--gold-soft);color:var(--midnight)}
/* share-plan.js appends its own .share-note into whichever control it acted on
   and flags it data-shared. The toast is absolutely positioned under the
   header so a two-word confirmation cannot widen the bar and reflow the title
   at 390px — the bar is a flex row and a growing child pushes the ellipsis. */
.bv__share{position:relative}
.bv__share .share-note{position:absolute;top:calc(100% + .35rem);right:0;
  white-space:nowrap;font-size:.7rem;letter-spacing:.08em;text-transform:none;
  color:var(--midnight);background:var(--gold-soft);border-radius:var(--radius);
  padding:.3rem .5rem;opacity:0;pointer-events:none;transition:opacity var(--dur-1) var(--ease)}
.bv__share[data-shared] .share-note{opacity:1}
.bv__x{flex:none;width:44px;height:44px;display:flex;align-items:center;justify-content:center;
  background:none;border:none;color:var(--ivory);font-size:2rem;line-height:1;cursor:pointer;opacity:.85}
.bv__x:hover{opacity:1;color:var(--gold-soft)}
.bv__stage{max-width:min(94vw,1000px);max-height:88dvh;overflow:auto;-webkit-overflow-scrolling:touch;
  touch-action:pinch-zoom;overscroll-behavior:contain;border-radius:2px;box-shadow:0 30px 90px rgba(var(--black-rgb),.6)}
.bv__img{display:block;width:100%;height:auto;background:var(--white);transition:transform var(--dur-2) var(--ease);transform-origin:center top}
.bv__img.is-zoomed{transform:scale(2)}
.bv__nav{position:absolute;top:50%;transform:translateY(-50%);width:52px;height:52px;z-index:var(--z-local-over);
  display:flex;align-items:center;justify-content:center;font-size:2rem;line-height:1;cursor:pointer;
  color:var(--ivory);background:rgba(var(--midnight-rgb),.55);border:1px solid rgba(var(--gold-soft-rgb),.3);border-radius:50%;
  transition:background var(--dur-1) var(--ease),opacity var(--dur-1) var(--ease)}
.bv__nav:hover{background:rgba(var(--gold-soft-rgb),.9);color:var(--midnight)}
.bv__nav:disabled{opacity:.25;cursor:default}
.bv__nav--prev{left:1rem}
.bv__nav--next{right:1rem}
.bv__video{display:none;width:100%;max-height:80dvh;background:var(--video-bg)}
.bv.is-video .bv__stage picture{display:none}
.bv.is-video .bv__video{display:block}
.bv.is-video .bv__stage{overflow:visible;touch-action:auto}
/* ONE EXPAND CONTROL, AND IT IS OURS (Tony, 2026-09-12 — two icons for one
   action on his screenshot).
   TWO LEVERS BECAUSE NO SINGLE ONE WORKS EVERYWHERE, and neither is trusted
   alone: the `controlslist` attribute on the element (set in
   assets/brochure-viewer.haaf864ffeb.js) is what Chromium reads, and it DISABLES the button
   rather than removing it; this shadow-DOM pseudo-element is what WebKit reads,
   including iPhone Safari — which ignores controlslist entirely and is the
   device the defect was reported from. Chrome dropped support for most of these
   pseudo-elements, so on Chromium the greyed-out button is what remains and it
   is recorded here rather than claimed fixed. */
.bv__video::-webkit-media-controls-fullscreen-button{display:none}
.bv__video::-webkit-media-controls-picture-in-picture-button{display:none}

/* PLAN DETAIL: the walkthrough and the plan's availability in one overlay.
   Side by side above 900px, stacked below it — on a phone the video keeps the
   top of the viewport and the community list scrolls under it, because an agent
   on site is reading the list, not watching the tour a second time.
   The panel is hidden unless .is-plandetail is set, so the plain video and
   brochure modes are untouched. */
.bv__avail{display:none}
.bv.is-plandetail{align-items:stretch;justify-content:flex-start;gap:0;padding:4.25rem 1rem 1rem}
.bv.is-plandetail .bv__stage{flex:1 1 58%;max-width:none;align-self:center}
.bv.is-plandetail .bv__nav{display:none}
.bv.is-plandetail .bv__avail{display:block;flex:0 0 clamp(320px,34%,460px);margin-left:1.25rem;
  overflow:auto;-webkit-overflow-scrolling:touch;overscroll-behavior:contain;max-height:100%;
  border-left:1px solid rgba(var(--gold-soft-rgb),.25);padding-left:1.25rem}
.bv__avail-head{margin:0 0 1rem}
.bv__avail-head h4{font-family:'Cormorant Garamond',serif;font-size:1.35rem;font-weight:600;
  color:var(--ivory);margin:0 0 .25rem;line-height:1.15}
.bv__avail-head p,.bv__avail-load,.bv__avail-empty{font-family:'Jost',sans-serif;font-size:.8rem;
  letter-spacing:.06em;color:var(--gold-soft);margin:0}
.bv__mkt{margin:0 0 1.25rem}
.bv__mkt h5{font-family:'Jost',sans-serif;font-size:.7rem;letter-spacing:.18em;text-transform:uppercase;
  font-weight:500;color:var(--gold-soft);margin:0 0 .5rem;padding-bottom:.35rem;
  border-bottom:1px solid rgba(var(--gold-soft-rgb),.2)}
.bv__comm{display:grid;grid-template-columns:1fr auto;gap:.15rem .75rem;padding:.6rem 0;
  border-bottom:1px solid rgba(var(--ivory-rgb),.08);text-decoration:none}
.bv__comm:hover{background:rgba(var(--gold-soft-rgb),.07)}
.bv__comm-name{font-family:'Jost',sans-serif;font-size:.95rem;font-weight:500;color:var(--ivory)}
.bv__55{display:inline-block;margin-left:.4rem;font-size:.6rem;letter-spacing:.1em;font-weight:600;
  color:var(--midnight);background:var(--gold-soft);border-radius:999px;padding:.1rem .4rem;vertical-align:middle}
.bv__comm-n{font-family:'Jost',sans-serif;font-size:.75rem;color:var(--gold-soft);text-align:right}
/* --reduced (#8a2f38) is the sitewide token, but it is a deep burgundy tuned for
   light backgrounds and fails contrast on this near-black overlay. Rendered as
   the gold-on-burgundy pill the overlay already uses for emphasis instead. */
.bv__rt{color:var(--ivory);background:var(--reduced);font-weight:600;
  border-radius:999px;padding:.1rem .45rem;font-size:.68rem;letter-spacing:.04em}
.bv__comm-price{font-family:'Jost',sans-serif;font-size:.85rem;font-weight:500;color:var(--ivory);
  grid-column:1/-1}
.bv__comm-spec,.bv__comm-hoa{font-family:'Jost',sans-serif;font-size:.72rem;letter-spacing:.04em;
  color:rgba(var(--ivory-rgb),.62);grid-column:1/-1}

/* ---------------------------------------------------------------------------
   EXPAND TO FULLSCREEN — phone only, and the "phone only" is not laziness.
   The reported defect is a phone defect (iPhone Safari, Aria modal); the brief
   requires the desktop overlay to render with a zero visual diff, and a new
   control in the media's top-right corner IS a visual diff. Desktop already has
   a fullscreen affordance for the case that matters — the native <video>
   controls carry one. So the button is rendered at <=768px and nowhere else.
   Logged for veto.
   --------------------------------------------------------------------------- */
.bv__expand{display:none}
.bv__fsx{display:none}
@media (max-width:768px){
  /* position:relative ONLY inside this media query, and that scope is
     LOAD-BEARING rather than tidy. A positioned element paints in the
     positioned-descendant layer, ABOVE static siblings — so setting it
     unconditionally lifted .bv__stage's `box-shadow: 0 30px 90px` out from
     under .bv__avail and printed it over the community list. Measured, not
     reasoned about: 16,962 pixels up to 26/255 darker on the DESKTOP overlay,
     with every element's rect unchanged. Geometry could not see it; only the
     pixels could. The desktop overlay is required to be pixel-identical, so
     the declaration lives where the button it exists for lives. */
  .bv__stage{position:relative}
  /* OPAQUE, and it is the gate's rule rather than taste: a control with a glyph
     painted straight onto it, sitting over arbitrary photography, is a backdrop
     carrying text. build/audit_overlay_opacity.py refused the .62 twin of this
     button below; the two are one control in two states, so they match. */
  .bv__expand{display:flex;position:absolute;top:.5rem;right:.5rem;z-index:var(--z-local-control);
    width:44px;height:44px;align-items:center;justify-content:center;cursor:pointer;
    color:var(--ivory);background:var(--midnight);
    border:1px solid rgba(var(--gold-soft-rgb),.35);border-radius:var(--radius);
    transition:background var(--dur-1) var(--ease),color var(--dur-1) var(--ease)}
  .bv__expand svg{width:18px;height:18px}
  .bv__expand:active{background:rgba(var(--gold-soft-rgb),.9);color:var(--midnight)}
  /* THE MEDIA CAP. Scoped to .is-plandetail: that is the overlay with a
     "Where you can sell the <Plan>" section and a community list underneath,
     and therefore the only one where shrinking the media reveals anything. The
     plain brochure viewer has nothing below the page image, so capping it there
     would cost legibility and buy nothing. Logged for veto. */
  /* margin-top:0 undoes the 56px the <=640px .bv__stage rule adds to clear the
     header bar. In plan-detail mode .bv.is-plandetail already reserves 4rem of
     padding for that bar, so the margin was 56px of dead space stacked on top
     of padding that had already done the job — and it was 56px taken from the
     one section this batch exists to make visible. */
  .bv.is-plandetail .bv__stage{max-height:var(--bv-media-cap);overflow:hidden;margin-top:0}
  .bv.is-plandetail .bv__video,
  .bv.is-plandetail .bv__img{max-height:var(--bv-media-cap);height:auto;
    object-fit:contain;background:var(--video-bg)}
}

/* CSS FULLSCREEN FALLBACK — iOS Safari has no Element.requestFullscreen for a
   non-video element, so an expanded brochure page needs one of these.
   IT PROMOTES THE EXISTING .bv__stage RATHER THAN MOVING THE MEDIA ANYWHERE.
   Re-parenting a playing <video> restarts its load on WebKit and loses the play
   position, and cloning an <img> into a second overlay is a second description
   of the same picture. Nothing is moved, so nothing has to be put back.
   z-index is from the LOCAL scale, not the global one: .bv is itself a stacking
   context (it transitions opacity), so a global --z-toast here would be scoped
   inside .bv anyway and would read as a promise the box cannot keep. Above
   .bv__bar (--z-local-over) is what "above the modal" means from in here. */
.bv.is-fs .bv__stage{position:fixed;inset:0;width:100vw;height:100svh;
  max-width:none;max-height:none;margin:0;border-radius:0;box-shadow:none;
  background:var(--video-bg);z-index:var(--z-local-control);
  display:flex;align-items:center;justify-content:center;overflow:hidden}
.bv.is-fs .bv__video,.bv.is-fs .bv__img{width:auto;height:auto;
  max-width:100vw;max-height:100svh;object-fit:contain;background:var(--video-bg)}
.bv.is-fs .bv__expand{display:none}
/* NATIVE fullscreen on the stage (Chrome/Firefox/Android, and iPadOS) needs the
   phone media cap and the expand button lifted off it, or the browser hands the
   element the whole screen and our own max-height clips it back to 42svh. The
   prefixed and unprefixed forms are SEPARATE RULES on purpose: a selector list
   containing a pseudo-class the browser does not know is thrown away whole. */
.bv__stage:fullscreen{max-width:none;max-height:none;margin:0;border-radius:0;
  background:var(--video-bg);display:flex;align-items:center;justify-content:center;overflow:hidden}
.bv__stage:-webkit-full-screen{max-width:none;max-height:none;margin:0;border-radius:0;
  background:var(--video-bg);display:flex;align-items:center;justify-content:center;overflow:hidden}
.bv__stage:fullscreen .bv__img,.bv__stage:fullscreen .bv__video{width:auto;height:auto;
  max-width:100vw;max-height:100svh;object-fit:contain}
.bv__stage:-webkit-full-screen .bv__img,.bv__stage:-webkit-full-screen .bv__video{width:auto;height:auto;
  max-width:100vw;max-height:100svh;object-fit:contain}
.bv__stage:fullscreen .bv__expand{display:none}
.bv__stage:-webkit-full-screen .bv__expand{display:none}

/* OPAQUE — build/audit_overlay_opacity.py aborted the 16:36 production run on
   this rule at alpha .62, and it was right to. The gate keys on a selector's
   LEADING class token, so `.bv.is-fs .bv__fsx` resolves to `.bv`, which is
   classified text-bearing; the sibling controls pass only because they are
   written `.bv__nav{...}` with no `.bv` prefix. The message it printed is about
   the wrong element, but the refusal is correct in kind and the safe default for
   an unclassified translucent layer is FAIL. It is fixed HERE rather than by
   widening the gate: a compliance gate relaxed to accommodate a new control is
   how the next real finding gets waved through. The × is a glyph painted
   straight onto this button, over arbitrary photography — 1.0 is also the more
   legible answer. */
.bv.is-fs .bv__fsx{display:flex;position:fixed;top:.5rem;right:.5rem;
  z-index:calc(var(--z-local-control) + 1);width:44px;height:44px;
  align-items:center;justify-content:center;background:var(--midnight);
  border:1px solid rgba(var(--gold-soft-rgb),.35);border-radius:var(--radius);
  color:var(--ivory);font-size:1.75rem;line-height:1;cursor:pointer}
@media (max-width:900px){
  /* THE TOP PAD IS THE BAR'S MEASURED HEIGHT, NOT A LITERAL. The header is a
     wrapping flex row whose height depends on whether this plan has media
     chips, on the length of the plan name and on the font that actually
     loaded — three things a hardcoded 4rem cannot know, and the failure is the
     bar printing over the top of the video. assets/brochure-viewer.haaf864ffeb.js sets
     --bv-bar-h from the rendered bar on every open and on resize; the literal
     survives only as the fallback for the frame before the first measurement.
     Same argument as _act_ghost in build/generate.py: a height derived from
     typography is a fact nothing will tell you has stopped being true. */
  .bv.is-plandetail{flex-direction:column;align-items:stretch;
    padding:calc(var(--bv-bar-h, 4rem) + .5rem) .75rem .75rem}
  .bv.is-plandetail .bv__stage{flex:0 0 auto;align-self:stretch}
  .bv.is-plandetail .bv__avail{flex:1 1 auto;margin:1rem 0 0;border-left:none;padding-left:0;
    border-top:1px solid rgba(var(--gold-soft-rgb),.25);padding-top:1rem}
}
@media (max-width:640px){
  .bv__stage{max-width:100vw;max-height:calc(100dvh - 132px);margin-top:56px}
  .bv__nav{width:46px;height:46px;top:auto;bottom:1rem;transform:none}
  .bv__nav--prev{left:1.2rem}
  .bv__nav--next{right:1.2rem}
  .bv__title{font-size:1rem}
  .bv__dl span{display:none}
  .bv__dl{padding:0.5rem 0.75rem}
  /* Icon-only at phone width, the same reduction .bv__dl already takes. The
     accessible name is on aria-label and does not go with the text. */
  .bv__sharelabel{display:none}
  .bv__share{padding:0.5rem 0.7rem}
  /* The media chips are a second row under the title rather than a fourth
     control competing with share, download and close for a 390px header —
     and that row REPLACES the count line rather than adding to it. A chip
     reading "Tour" and a caption reading "Tour & availability" are the same
     sentence twice, and the second one costs 20px that comes straight off the
     community list. Measured: header 120px with both, 76px with the count
     dropped, which is the difference between one visible community row and
     two. */
  .bv__variants{order:3;flex:1 0 100%;margin:.25rem 0 0}
  .bv__variant{padding:.35rem .6rem;font-size:.68rem}
  .bv.has-chips .bv__count{display:none}
  .bv__bar{flex-wrap:wrap;gap:.5rem}
  body.bv-open .tgchat-launch,body.bv-open .mobile-callbar,body.bv-open .pbar{display:none!important}
}

/* =============================================================================
   THE SHORTLIST — full-screen wizard. Split layout (imagery left, steps right),
   midnight/ivory/champagne, Cormorant display. Mobile-first: aside collapses.
   ========================================================================== */
.sl{position:fixed;inset:0;z-index:var(--z-modal);display:grid;grid-template-columns:38% 1fr;
  background:var(--ivory);opacity:0;visibility:hidden;transition:opacity var(--dur-2) var(--ease),visibility var(--dur-2)}
.sl.is-open{opacity:1;visibility:visible}
body.sl-open{overflow:hidden}
.sl__aside{position:relative;background:linear-gradient(160deg,var(--midnight),var(--midnight-2));color:var(--ivory);
  display:flex;align-items:center;overflow:hidden}
.sl__aside::after{content:"";position:absolute;inset:0;background:url(/assets/photos/planlib-hayden.h279dd32b5a.jpg) center/cover;
  opacity:.16;mix-blend-mode:luminosity}
.sl__asideInner{position:relative;z-index:var(--z-local-raise);padding:3rem 3rem}
.sl__kicker{font-family:'Jost',sans-serif;font-size:0.625rem;letter-spacing:.24em;text-transform:uppercase;
  color:var(--gold-soft);margin:0 0 0.75rem}
.sl__asideTitle{font-family:'Cormorant Garamond',serif;font-size:2.75rem;font-weight:600;line-height:1.05;
  color:var(--ivory);margin:0 0 2rem}
.sl__dots{display:flex;gap:0.5rem}
.sl__dot{width:26px;height:2px;background:rgba(var(--ivory-rgb),.22);border-radius:2px;transition:background var(--dur-2) var(--ease)}
.sl__dot.is-on{background:var(--gold-soft)}
.sl__dot.is-done{background:rgba(var(--gold-soft-rgb),.55)}
.sl__main{position:relative;overflow-y:auto;-webkit-overflow-scrolling:touch;padding:3rem 3rem 3rem}
/* THE CLOSE CONTROL. Anchored to .sl (which never scrolls), not to .sl__main
   (which does) — see the note in assets/shortlist.h5ceffee45d.js. z-index 200 clears the
   sticky refine bar (2), the mobile sheet scrim (170) and the sheet itself
   (180), so it stays tappable even with the bottom sheet up. It is a solid chip
   rather than a bare glyph because it now sits over whatever the results happen
   to be scrolled to, not over a predictable blank corner. */
.sl__x{position:absolute;z-index:var(--z-local-control);top:.9rem;right:1rem;width:44px;height:44px;
  display:flex;align-items:center;justify-content:center;
  background:var(--white);border:1px solid var(--line);border-radius:99px;
  font-size:1.5rem;line-height:1;color:var(--midnight);cursor:pointer;opacity:1;
  box-shadow:0 4px 14px -6px rgba(var(--midnight-rgb),.42);
  transition:border-color var(--dur-1) var(--ease),color var(--dur-1) var(--ease)}
.sl__x:hover,.sl__x:focus-visible{border-color:var(--gold);color:var(--gold)}
.sl__body{max-width:640px;margin:0 auto}
.sl__stepno{font-family:'Jost',sans-serif;font-size:0.625rem;letter-spacing:.2em;text-transform:uppercase;
  color:var(--muted);margin:0 0 0.5rem}
.sl__q{font-family:'Cormorant Garamond',serif;font-size:2rem;font-weight:600;color:var(--midnight);
  margin:0 0 0.5rem;line-height:1.15}
.sl__sub{font-size:0.875rem;color:var(--muted-aa);font-weight:400;margin:0 0 1.5rem}
.sl__opts{display:grid;gap:0.75rem;margin:1.5rem 0 1.5rem}
.sl__opts--2{grid-template-columns:1fr 1fr}
.sl__opts--3{grid-template-columns:repeat(3,1fr)}
.sl__opts--photo{grid-template-columns:repeat(3,1fr)}
.sl__opt{display:flex;flex-direction:column;align-items:flex-start;gap:0.5rem;min-height:56px;
  padding:1rem 1rem;background:var(--white);border:1px solid var(--line);border-radius:var(--radius);
  cursor:pointer;font:inherit;text-align:left;transition:border-color var(--dur-1) var(--ease),transform var(--dur-1) var(--ease)}
.sl__opt:hover{border-color:var(--gold);transform:translateY(-2px)}
.sl__opt.is-on{border-color:var(--gold);background:rgba(var(--gold-soft-rgb),.10);box-shadow:inset 0 0 0 1px var(--gold)}
.sl__optImg{display:block;width:100%;aspect-ratio:4/3;border-radius:2px;background:var(--cream) center/cover}
.sl__optLabel{font-family:'Jost',sans-serif;font-size:0.875rem;color:var(--midnight)}
/* --- Zero-result state + suggested adjustments ---------------------------
   A dead end is the one thing this quiz is not allowed to be. When the hard
   filters return nothing, this is what the visitor gets instead: the reason,
   stated plainly, and one-tap doors with real counts on them. Styled as
   ACTIONS, not as an error — the gold rule and the tappable rows read as the
   next step, because that is what they are.                                  */
.sl__empty{margin:0 0 1.5rem;padding:1.25rem 1.25rem 1rem;background:var(--cream);
  border:1px solid var(--line);border-left:3px solid var(--gold);border-radius:var(--radius)}
.sl__emptyLine{font-family:'Cormorant Garamond',serif;font-size:1.375rem;line-height:1.3;
  color:var(--midnight);margin:0 0 0.75rem}
.sl__empty .sl__sub{margin:0 0 0.75rem}
.sl__sugs{display:flex;flex-direction:column;gap:0.5rem}
.sl__sug{display:block;width:100%;text-align:left;font-family:'Jost',sans-serif;font-size:0.875rem;
  color:var(--midnight);background:var(--white);border:1px solid var(--line);border-radius:var(--radius);
  padding:0.75rem 1rem;min-height:44px;cursor:pointer;
  transition:border-color var(--dur-1) var(--ease),transform var(--dur-1) var(--ease)}
.sl__sug:hover{border-color:var(--gold);transform:translateY(-1px)}
/* On the dark hero card the same block has to stop being cream-on-cream. */
.hsl .sl__empty{background:rgba(var(--midnight-rgb),.62);border-color:rgba(var(--ivory-rgb),.24)}
.hsl .sl__emptyLine{color:var(--ivory);font-size:1.125rem}
.hsl .sl__sug{color:var(--ivory);background:rgba(var(--ivory-rgb),.06);
  border-color:rgba(var(--ivory-rgb),.24)}
.sl__nav{display:flex;gap:0.75rem;margin-top:0.5rem}
.sl__btn{font-family:'Jost',sans-serif;text-transform:uppercase;letter-spacing:.14em;font-size:0.75rem;
  font-weight:500;color:var(--ivory);background:var(--midnight);border:1px solid var(--midnight);
  border-radius:var(--radius);padding:0.75rem 1.5rem;cursor:pointer;transition:background var(--dur-2) var(--ease)}
.sl__btn:hover{background:var(--gold);border-color:var(--gold);color:var(--midnight)}
.sl__btn--ghost{background:none;color:var(--midnight)}
.sl__btn--full{width:100%;margin-top:0.75rem}
.sl__agentFields{display:grid;gap:1rem;margin:1rem 0}
.sl__agentFields label,.sl__lead label{display:block;font-family:'Jost',sans-serif;font-size:0.75rem;
  letter-spacing:.16em;text-transform:uppercase;color:var(--muted);margin-bottom:1rem}
.sl__agentFields input,.sl__lead input{display:block;width:100%;margin-top:0.5rem;padding:0.75rem 1rem;
  border:1px solid var(--line);border-radius:var(--radius);font-family:'Jost',sans-serif;font-size:16px;
  color:var(--ink);background:var(--white)}
/* results */
.sl__agentNote{font-family:'Jost',sans-serif;font-size:0.75rem;letter-spacing:.08em;color:var(--gold);
  background:rgba(var(--gold-soft-rgb),.12);border-radius:var(--radius);padding:0.5rem 1rem;margin:0 0 1rem}
.sl__cards{display:grid;gap:1rem;margin:1rem 0 2rem}
/* THE THUMBNAIL USED TO STRETCH, AND THAT IS WHAT CUT THE HOUSES IN HALF.
   The image cell was a grid track with no height of its own, so it took the
   height of the BODY beside it — 340-380px of tier badge, specs, price, HOA and
   two buttons — inside a 132px column. `cover` then scaled a 1200x900 exterior
   to cover 360px of height and showed the middle ~27% of its width: a front
   door and half a garage. Every card cropped differently, because every card's
   body is a different height.
   Fix: the image is top-aligned and owns a FIXED 4:3 box, so it is the same
   shape on every card and the photo is never scaled past what it can fill.
   `min-height:0` is load-bearing — grid items default to min-height:auto, which
   would let the old min-height:132px fight the aspect ratio. */
.sl__card{position:relative;display:grid;grid-template-columns:168px 1fr;gap:1rem;align-items:start;
  background:var(--white);padding:1rem 0 1rem 1rem;
  border:1px solid var(--line);border-radius:var(--radius);overflow:hidden;box-shadow:var(--shadow)}
.sl__cardImg{position:relative;aspect-ratio:4/3;min-height:0;border-radius:2px;overflow:hidden;
  background:var(--cream) center 45%/cover no-repeat}
/* THE THUMBNAIL IS AN <img> NOW, NOT A BACKGROUND, AND ONLY SO IT CAN BE LAZY.
   A CSS background cannot carry loading="lazy", and since the results list
   stopped truncating at twelve cards there can be a hundred and fifty of these
   in the document at once. Geometry is unchanged: the box is still the fixed
   4:3 the note above describes and the image still fills it from center 45%,
   so the framing that stopped cutting houses in half is the same framing.
   The REDUCED badge follows the image in source order, so it still paints
   over it without needing a z-index. */
.sl__cardImgEl{position:absolute;inset:0;display:block;width:100%;height:100%;
  object-fit:cover;object-position:center 45%;border-radius:2px}
.sl__rt{position:absolute;top:.5rem;left:.5rem;background:var(--reduced);color:var(--ivory);
  font-family:'Jost',sans-serif;font-size:0.625rem;font-weight:600;letter-spacing:.14em;padding:0.25rem 0.5rem;border-radius:2px}
.sl__cardBody{padding:0 1rem 0 0;min-width:0}
.sl__near{font-family:'Jost',sans-serif;font-size:0.625rem;letter-spacing:.1em;text-transform:uppercase;
  color:var(--gold);margin:0 0 0.25rem}
.sl__cardComm{font-family:'Jost',sans-serif;font-size:0.625rem;letter-spacing:.16em;text-transform:uppercase;
  color:var(--muted-aa);margin:0}
.sl__cardPlan{font-family:'Cormorant Garamond',serif;font-size:1.5rem;font-weight:600;margin:0rem 0 0.25rem;color:var(--midnight)}
.sl__cardSpecs{font-size:0.75rem;color:var(--muted-aa);margin:0;font-variant-numeric:tabular-nums}
.sl__cardPrice{font-family:'Cormorant Garamond',serif;font-size:1.5rem;font-weight:600;color:var(--midnight);margin:0.25rem 0 0}
.sl__cardStage{font-family:'Jost',sans-serif;font-size:0.625rem;letter-spacing:.12em;text-transform:uppercase;color:var(--muted-aa);margin:0rem 0 0}
.sl__why{list-style:none;padding:0;margin:0.5rem 0 0;display:flex;flex-wrap:wrap;gap:0.25rem}
.sl__why li{font-family:'Jost',sans-serif;font-size:0.75rem;color:var(--midnight);background:var(--cream);
  border-radius:99px;padding:0.25rem 0.5rem}
.sl__plan{margin-top:0.75rem;font-family:'Jost',sans-serif;text-transform:uppercase;letter-spacing:.12em;
  font-size:0.75rem;color:var(--midnight);background:none;border:1px solid var(--line);border-radius:var(--radius);
  padding:0.5rem 0.75rem;cursor:pointer}
.sl__plan:hover{border-color:var(--gold);color:var(--gold)}
/* ---- Shortlist: horizontal bands, tier badges, result actions ------------
   Bands are full-width rows so the price spread reads as a spectrum you move
   along, not four boxes to pick between. The count on the right is live. */
.sl__bands{display:flex;flex-direction:column;gap:0.5rem;margin:1rem 0 1.5rem}
.sl__bands--tight{margin:0.5rem 0 1rem}
.sl__band{display:grid;grid-template-columns:1fr auto;align-items:baseline;gap:0.25rem 0.75rem;width:100%;
  text-align:left;background:none;border:1px solid var(--line);border-radius:var(--radius);
  padding:0.75rem 1rem;cursor:pointer;font-family:inherit;
  transition:border-color var(--dur-2) var(--ease),background var(--dur-2) var(--ease),transform var(--dur-2) var(--ease)}
.sl__band:hover{border-color:var(--gold);transform:translateX(2px)}
.sl__band.is-on{border-color:var(--gold);background:rgba(var(--gold-soft-rgb),.1)}
.sl__bandLabel{font-family:'Jost',sans-serif;font-size:1rem;color:var(--midnight);font-weight:400;font-variant-numeric:tabular-nums;letter-spacing:.01em}
.sl__bandSub{grid-column:1;font-size:0.75rem;color:var(--muted-aa);font-weight:400}
.sl__bandN{grid-row:1;grid-column:2;font-family:'Jost',sans-serif;font-size:0.625rem;letter-spacing:.1em;
  text-transform:uppercase;color:var(--muted-aa);white-space:nowrap;font-variant-numeric:tabular-nums}
.sl__label{font-family:'Jost',sans-serif;font-size:0.75rem;letter-spacing:.16em;text-transform:uppercase;
  color:var(--gold);margin:1rem 0 0rem}
.sl__skip{display:inline-block;background:none;border:0;padding:0;margin:-0.5rem 0 0.5rem;cursor:pointer;
  font-family:'Jost',sans-serif;font-size:0.75rem;color:var(--muted-aa);border-bottom:1px solid var(--line)}
.sl__skip:hover{color:var(--gold);border-color:var(--gold)}
.sl__optFrom{display:block;font-size:0.75rem;color:var(--muted-aa);letter-spacing:0;text-transform:none;margin-top:0.25rem;font-variant-numeric:tabular-nums}
.sl__optN{display:inline-block;margin-left:0.5rem;font-size:0.625rem;color:var(--muted-aa);font-variant-numeric:tabular-nums}
.sl__tier{font-family:'Jost',sans-serif;font-size:0.625rem;letter-spacing:.14em;text-transform:uppercase;
  font-weight:500;margin:0 0 0.25rem;display:inline-block;padding:0.25rem 0.5rem;border-radius:99px}
.sl__tier--strong{color:var(--verified-ink);background:rgba(var(--verified-ink-rgb),.1)}
.sl__tier--close{color:var(--warning-ink);background:rgba(var(--gold-soft-rgb),.22)}
.sl__tier--look{color:var(--muted);background:var(--cream)}
.sl__villa{margin-left:0.5rem;color:var(--gold)}
.sl__cardActs{display:flex;flex-wrap:wrap;gap:0.5rem;margin-top:0.75rem}
.sl__cardActs .sl__plan{margin-top:0}
@media(max-width:560px){
  .sl__band{padding:0.75rem 0.75rem}
  .sl__bandLabel{font-size:0.875rem}
}
/* ---- Shortlist: the estimator bridge on each result card ------------------
   Small and champagne, sitting under the price. Deliberately quiet: it is a
   next step, not a second call to action competing with the lead form. */
.sl__cardEst{margin:0.25rem 0 0}
.sl__cardEst a{font-family:'Jost',sans-serif;font-size:0.75rem;letter-spacing:.08em;color:var(--gold);
  text-decoration:none;border-bottom:1px solid rgba(var(--gold-soft-rgb),.5);padding-bottom:1px}
.sl__cardEst a:hover{color:var(--midnight);border-color:var(--midnight)}

/* ---- Shortlist: the refine bar -------------------------------------------
   Results are an adjustable workspace, not a verdict. The bar sticks to the top
   of the results list on desktop; below 640px it becomes a bottom sheet behind
   an "Adjust results" pill so the cards stay visible while you tune them.
   NOTE: the 640px breakpoint is duplicated in isSheet() in assets/shortlist.h5ceffee45d.js.
   Change one and you must change the other. */
/* THE REFINE PANEL OUTRANKS EVERY CARD — and it did not, which is why card
   links and buttons painted straight through it while the results scrolled.
   It sat at --z-local-over (2). So do .home__actions and .home__assets, and a
   tie is broken by DOM order, which the cards win because they come after.
   .hcar__arw sits at --z-local-control (3) and beat it outright. Three separate
   card layers were above a panel that is supposed to float over all of them.

   Two changes, both load-bearing:
     * --z-sticky-sub, which is above the entire per-card scale rather than
       inside it. (From the --z-* scale, never a raw number:
       build/audit_stacking.py fails the build on a bare z-index.)
     * isolation:isolate — the panel becomes ONE stacking context, so nothing
       inside a card can ever reach into it again by raising its own z-index.
   The background must also be genuinely opaque; a translucent panel over
   scrolling cards reads as the same bug even when the order is right. */
/* ONLY THE CONDENSED BAR IS STICKY. The full panel sits in normal flow and
   scrolls away like any other block — so it never overlays a card, and the
   card controls underneath stay reachable (build/audit_shortlist.py asserts
   exactly that, and caught this the moment the panel started winning the
   stacking contest it had previously been losing). What pins is the 48px
   summary bar, which is the whole point of condensing. */
.sl__refine{position:relative;z-index:var(--z-sticky-sub);isolation:isolate;
  background:var(--ivory);
  border:1px solid var(--line);border-radius:var(--radius);
  padding:0.75rem 1rem 1rem;margin:0 0 1rem;
  box-shadow:0 6px 18px -14px rgba(var(--midnight-rgb),.5)}

/* CONDENSED STATE. A full-height control panel pinned over a scrolling list is
   the wrong shape even when it stacks correctly — it eats the results it is
   meant to refine. Full panel at the top of the results; once scrolled it
   collapses to a one-line summary of what is currently applied, and a tap puts
   the full panel back. assets/shortlist.h5ceffee45d.js owns the toggle.

   SCOPED TO >=641px ON PURPOSE. Below that breakpoint .sl__refine is not a
   sticky bar at all — it is the bottom SHEET (position:fixed, parked off-screen
   under transform:translateY(101%) + visibility:hidden until .is-sheetOpen),
   opened by .sl__refinePill. Two different designs for one element.

   Unscoped, these rules collided: `.sl__refine.is-condensed` (0,2,0) outranks
   the media query's `.sl__refine` (0,1,0), so on a phone it dragged position
   back to sticky while the sheet's transform and visibility:hidden stayed put.
   The result was an invisible 49px sticky bar, shoved down 49.49px by a
   transform meant for a sheet, that could never win a hit test — so the cards
   underneath painted straight through it. build/audit_scroll_integrity.py
   caught it at 375x812 and nowhere else, which is exactly the fingerprint of a
   breakpoint collision. is-condensed is a desktop affordance; on mobile the
   pill and the sheet already do this job, so below 641px it must stay inert. */
.sl__refBar{display:none}
@media (min-width:641px){
  .sl__refine.is-condensed{position:sticky;top:0;padding:0;overflow:hidden}
  .sl__refine.is-condensed > *:not(.sl__refBar){display:none}
  .sl__refine.is-condensed .sl__refBar{display:flex;align-items:center;gap:0.6rem;
    width:100%;min-height:48px;padding:0.5rem 0.9rem;cursor:pointer;
    background:transparent;border:0;text-align:left;
    font-family:'Jost',sans-serif;font-size:0.78rem;letter-spacing:.02em;color:var(--ink)}
  .sl__refine.is-condensed:hover .sl__refBarCue{text-decoration:underline}
}
.sl__refBarText{flex:1 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.sl__refBarCue{flex:none;font-size:0.7rem;letter-spacing:.14em;text-transform:uppercase;color:var(--gold-deep,var(--gold))}
.sl__refBar:focus-visible{outline:2px solid var(--gold);outline-offset:-2px}
.sl__refHead{display:flex;align-items:baseline;justify-content:space-between;gap:1rem}
.sl__refTitle{font-family:'Jost',sans-serif;font-size:0.75rem;letter-spacing:.18em;text-transform:uppercase;
  color:var(--gold);margin:0 0 0.5rem}
.sl__refDone{display:none}
.sl__refRow{display:flex;align-items:center;flex-wrap:wrap;gap:0.5rem 0.75rem;margin:0.25rem 0}
.sl__refLabel{font-family:'Jost',sans-serif;font-size:0.75rem;letter-spacing:.14em;text-transform:uppercase;
  color:var(--muted-aa);flex:0 0 5.6rem}
.sl__step{display:flex;align-items:center;gap:0.5rem}
.sl__stepBtn{width:32px;height:32px;flex:0 0 32px;border:1px solid var(--line);border-radius:99px;
  background:var(--white);color:var(--midnight);font-size:1rem;line-height:1;cursor:pointer;
  transition:border-color var(--dur-1) var(--ease),color var(--dur-1) var(--ease)}
.sl__stepBtn:hover:not(:disabled){border-color:var(--gold);color:var(--gold)}
.sl__stepBtn:disabled{opacity:.32;cursor:default}
.sl__stepVal{font-family:'Jost',sans-serif;font-size:0.875rem;color:var(--midnight);
  font-variant-numeric:tabular-nums;min-width:9.2rem;text-align:center}
.sl__chips{display:flex;flex-wrap:wrap;gap:0.5rem}
.sl__chip{font-family:'Jost',sans-serif;font-size:0.75rem;color:var(--midnight);background:var(--white);
  border:1px solid var(--line);border-radius:99px;padding:0.5rem 0.75rem;min-height:32px;cursor:pointer;
  transition:border-color var(--dur-1) var(--ease),background var(--dur-1) var(--ease)}
.sl__chip:hover:not(:disabled){border-color:var(--gold)}
.sl__chip:disabled{opacity:.32;cursor:default}
/* Quiz budget tabs — every surface, payment tab first (2026-07-28). */
.sl__tabs{display:flex;gap:8px;margin:0 0 14px}
.sl__tab{font-family:'Jost',sans-serif;font-size:0.75rem;letter-spacing:.06em;text-transform:uppercase;
  color:var(--midnight);background:var(--white);border:1px solid var(--line);border-radius:99px;
  padding:0.5rem 0.9rem;min-height:32px;cursor:pointer;
  transition:border-color var(--dur-1) var(--ease),background var(--dur-1) var(--ease)}
.sl__tab:hover{border-color:var(--gold)}
.sl__tab.is-on{border-color:var(--gold);background:rgba(var(--gold-soft-rgb),.10);box-shadow:inset 0 0 0 1px var(--gold)}
/* The secondary budget figure on a payment-door result card. */
.sl__cardAlt{font-family:'Jost',sans-serif;font-size:0.85rem;color:var(--muted-aa);margin:0.1rem 0 0}
/* A2 on the quiz card: the terms that qualify the payment above them. Same
   shape as .home__payterms / .home__paydisc so the two surfaces read alike. */
.sl__cardTerms{font-family:'Jost',sans-serif;font-size:0.7rem;line-height:1.55;
  letter-spacing:.01em;color:var(--muted-aa);margin:0.25rem 0 0}
.sl__cardTerms b{font-weight:600;color:var(--ink);white-space:nowrap}
.sl__cardDisc{font-family:'Jost',sans-serif;font-size:0.63rem;line-height:1.5;
  letter-spacing:.01em;color:var(--muted-aa);margin:0.2rem 0 0}
.sl__regz{margin-top:14px}
/* The guided-start line above the payment grid (generate.py fyh template). */
.fyh__quiz{margin:.4rem 0 0;font-family:'Jost',sans-serif;font-size:.9rem;color:var(--muted-aa)}
.fyh__quizBtn{background:none;border:0;padding:0;font:inherit;color:var(--midnight);text-decoration:underline;text-underline-offset:3px;cursor:pointer}
.fyh__quizBtn:hover{color:var(--gold)}
.sl__chip.is-on{border-color:var(--gold);background:rgba(var(--gold-soft-rgb),.14)}
.sl__chipX{margin-left:0.5rem;color:var(--muted-aa);font-size:0.875rem;line-height:1}
.sl__chip--rm:hover .sl__chipX{color:var(--gold)}
.sl__chip--add{border-style:dashed;color:var(--muted-aa)}
.sl__chips--add{margin:0.5rem 0 0 6rem}
.sl__refCount{font-family:'Jost',sans-serif;font-size:0.75rem;letter-spacing:.06em;color:var(--muted-aa);
  margin:0.5rem 0 0;font-variant-numeric:tabular-nums}
.sl__refReset{display:inline-block;background:none;border:0;padding:0;margin:0.25rem 0 0;cursor:pointer;
  font-family:'Jost',sans-serif;font-size:0.75rem;color:var(--muted-aa);border-bottom:1px solid var(--line)}
.sl__refReset:hover{color:var(--gold);border-color:var(--gold)}
/* The re-rank fade. Cards resettle rather than teleport; anyone who asked for
   reduced motion gets the swap instantly (the JS skips the delay entirely). */
.sl__cards{transition:opacity 190ms var(--ease)}
.sl__cards.is-swap{opacity:.15}
@media (prefers-reduced-motion:reduce){
  .sl__cards,.sl__cards.is-swap{transition:none;opacity:1}
}
/* =============================================================================
   SHORTLIST — the 2026-07-28 restructure: chip picks, the location map, the
   clickable card and the collapsed results map.
   ========================================================================== */

/* ---- step 1: the picks, in the filter bar's own chip language -------------
   Deliberately the same shape, weight and count treatment as .flt__pill on
   /homes. A buyer who has used the filter bar already knows what these do, and
   two vocabularies for one idea is exactly the confusion this batch removed. */
.sl__pickRow{margin:0 0 1rem}
.sl__pickRow .sl__label{margin:0 0 0.4rem}
.sl__pills{display:flex;flex-wrap:wrap;gap:0.35rem;min-width:0}
.sl__pill{display:inline-flex;align-items:baseline;gap:0;flex:none;font-family:'Jost',sans-serif;
  font-size:0.75rem;letter-spacing:.04em;padding:0.6rem 0.8rem;min-height:40px;cursor:pointer;
  color:var(--ink);background:transparent;border:1px solid var(--line);border-radius:var(--radius);
  transition:background var(--dur-1) var(--ease),color var(--dur-1) var(--ease),border-color var(--dur-1) var(--ease)}
.sl__pill:hover:not(:disabled){border-color:var(--gold)}
.sl__pill.is-on{background:var(--midnight);color:var(--ivory);border-color:var(--midnight)}
/* Half a selection is neither on nor off, and must not pretend to be either:
   an area chip with 3 of its 9 communities picked reads as outlined-gold. */
.sl__pill.is-some{border-color:var(--gold);box-shadow:inset 0 0 0 1px var(--gold)}
.sl__pill.is-void,.sl__pill:disabled{opacity:.4;cursor:default}
.sl__n{font-style:normal;font-size:0.78em;letter-spacing:.06em;font-variant-numeric:tabular-nums;
  color:var(--muted-aa);margin-left:0.55rem;padding-left:0.55rem;
  border-left:1px solid rgba(var(--gold-soft-rgb),.32)}
.sl__n:empty{display:none}
.sl__pill.is-on .sl__n{color:rgba(var(--gold-soft-rgb),.78);border-left-color:rgba(var(--gold-soft-rgb),.38)}
.sl__pill--clear{border-style:dashed;color:var(--muted-aa)}
.sl__pills--area{margin:0 0 0.75rem}
.sl__pills--comms{margin:0.75rem 0 0}

/* ---- step 1: the bypass line and the "More filters" disclosure -----------
   Step 1 stacked four pill rows, and at 1280x900 that put the Next button and
   the Reg Z block below the fold — the buyer saw a wall of chips with no
   visible way forward. Bedrooms, lot size and home type now sit inside a
   closed <details>, so the screen opens on the budget ceiling, the bypass
   promise, and Next. build/audit_quiz_fold.py asserts the button stays in the
   viewport on load and fails the build if it ever drops out again.

   The disclosure is styled to read as a deliberate door, not as a collapsed
   error state: bordered, full-width, with the marker rotated by hand so the
   affordance survives the marker reset browsers disagree on. */
.sl__bypass{font-family:'Jost',sans-serif;font-size:0.75rem;letter-spacing:.06em;
  color:var(--gold-deep,var(--gold));margin:0 0 0.85rem;display:flex;gap:0.45rem;align-items:baseline}
.sl__bypass::before{content:"\2713";font-size:0.9em;flex:none}
.hsl .sl__bypass{color:var(--gold-soft)}

.sl__more{margin:0 0 1rem;border:1px solid var(--line);border-radius:var(--radius);
  padding:0 0.85rem;overflow:hidden}
.sl__more[open]{padding-bottom:0.35rem}
.sl__moreSum{display:flex;align-items:center;gap:0.6rem;cursor:pointer;list-style:none;
  font-family:'Jost',sans-serif;font-size:0.75rem;letter-spacing:.16em;text-transform:uppercase;
  color:var(--ink);padding:0.85rem 0;min-height:44px;box-sizing:border-box}
.sl__moreSum::-webkit-details-marker{display:none}
.sl__moreSum::after{content:"";flex:none;margin-left:auto;width:0.5rem;height:0.5rem;
  border-right:1.5px solid currentColor;border-bottom:1.5px solid currentColor;
  transform:rotate(45deg) translate(-2px,-2px);
  transition:transform var(--dur-1) var(--ease)}
.sl__more[open] .sl__moreSum::after{transform:rotate(-135deg) translate(-2px,-2px)}
.sl__moreSum:hover{color:var(--gold-deep,var(--gold))}
.sl__moreSum:focus-visible{outline:2px solid var(--gold);outline-offset:2px}
/* The count of applied filters — the only thing that tells a buyer the closed
   panel is doing something. Without it a collapsed disclosure silently holds
   an active bedroom filter and the result count looks wrong for no reason. */
.sl__moreN{font-style:normal;font-size:0.9em;letter-spacing:.06em;font-variant-numeric:tabular-nums;
  color:var(--muted-aa);text-transform:none}
.sl__moreSub{font-family:'Jost',sans-serif;font-size:0.75rem;letter-spacing:.04em;
  color:var(--muted-aa);margin:0 0 0.85rem}
.sl__more .sl__pickRow:last-of-type{margin-bottom:0.6rem}
.hsl .sl__more{border-color:rgba(var(--ivory-rgb),.28)}
.hsl .sl__moreSum{color:var(--ivory)}
.hsl .sl__moreSub,.hsl .sl__moreN{color:rgba(var(--ivory-rgb),.92)}

/* ---- step 2: the location map -------------------------------------------- */
.sl__map{height:clamp(260px,42vh,420px);width:100%;border:1px solid var(--line);
  border-radius:var(--radius);overflow:hidden;background:var(--cream)}
.sl__map--pick{margin:0 0 0.75rem}
.sl__mapOff{font-family:'Jost',sans-serif;font-size:0.75rem;line-height:1.6;color:var(--muted-aa);
  background:var(--cream);border-radius:var(--radius);padding:0.75rem 1rem;margin:0 0 0.5rem}
.sl__selCount{font-family:'Jost',sans-serif;font-size:0.875rem;letter-spacing:.04em;color:var(--midnight);
  font-variant-numeric:tabular-nums;margin:0.25rem 0 1rem}
/* Community pin. Same silhouette as the /map pins so one site has one map. */
.sl-pin{background:none;border:0}
.sl-pin__dot{display:block;width:14px;height:14px;margin:8px;border-radius:50%;
  background:var(--midnight);border:2px solid var(--ivory);
  box-shadow:0 1px 5px rgba(var(--midnight-rgb),.45);
  transition:transform var(--dur-1) var(--ease),background var(--dur-1) var(--ease)}
.sl-pin:hover .sl-pin__dot{transform:scale(1.18)}
.sl-pin.is-on .sl-pin__dot{background:var(--gold);width:18px;height:18px;margin:6px;
  box-shadow:0 0 0 4px rgba(var(--gold-soft-rgb),.35),0 1px 6px rgba(var(--midnight-rgb),.45)}
.sl-pin--home .sl-pin__dot{width:12px;height:12px;margin:7px;background:var(--gold)}
.sl-pin--home.is-hot .sl-pin__dot{background:var(--midnight);
  box-shadow:0 0 0 5px rgba(var(--gold-soft-rgb),.45),0 1px 6px rgba(var(--midnight-rgb),.5)}
.sl__pinNote{font-family:'Jost',sans-serif;font-size:0.6875rem;line-height:1.45;color:var(--muted-aa);
  margin:0.35rem 0 0}

/* ---- Step 2: the viewport legend ----------------------------------------
   ONE RULE FLIPS THE LAYOUT, and that is deliberate. The wrapper is a grid;
   above 760px it is map + a 260px legend column, below it is a single column
   and the legend stacks under the map. No JS branches on width, so an overlay
   left open through an orientation change cannot end up in the wrong layout.
   The legend column is stretched to the map's height by the grid, which is why
   the phone breakpoint is the only place rows are truncated -- on desktop
   there is height to fill, and hiding rows would leave whitespace. */
.sl__pickWrap{display:grid;grid-template-columns:1fr;gap:0.75rem;margin:0 0 0.75rem}
.sl__pickWrap .sl__map--pick{margin:0}
.sl__lg{min-width:0}
.sl__lgHead{font-family:'Jost',sans-serif;text-transform:uppercase;letter-spacing:.14em;
  font-size:0.6875rem;color:var(--muted-aa);margin:0 0 0.4rem;
  font-variant-numeric:tabular-nums}
.sl__lgList{display:flex;flex-direction:column;gap:2px}
.sl__lgRow{display:grid;grid-template-columns:minmax(0,1fr) auto;grid-template-areas:'name n' 'mkt n';
  align-items:baseline;gap:0 0.6rem;width:100%;text-align:left;cursor:pointer;
  background:none;border:1px solid transparent;border-radius:var(--radius);
  padding:0.4rem 0.55rem;min-height:44px;
  transition:background var(--dur-1) var(--ease),border-color var(--dur-1) var(--ease)}
.sl__lgRow:hover{border-color:var(--line);background:var(--cream)}
.sl__lgRow:focus-visible{outline:2px solid var(--gold);outline-offset:1px}
.sl__lgName{grid-area:name;font-family:'Cormorant Garamond',serif;font-size:1rem;line-height:1.2;
  color:var(--midnight);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.sl__lgMkt{grid-area:mkt;font-family:'Jost',sans-serif;font-size:0.625rem;letter-spacing:.1em;
  text-transform:uppercase;color:var(--muted-aa);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.sl__lgN{grid-area:n;font-family:'Jost',sans-serif;font-size:0.8125rem;color:var(--muted-aa);
  font-variant-numeric:tabular-nums;align-self:center}
/* Selected reads the same here as it does on a chip and on a pin -- one state,
   three surfaces, so a buyer never has to learn a second vocabulary. */
.sl__lgRow.is-on{background:var(--midnight);border-color:var(--midnight)}
.sl__lgRow.is-on .sl__lgName{color:var(--ivory)}
.sl__lgRow.is-on .sl__lgN,.sl__lgRow.is-on .sl__lgMkt{color:rgba(var(--gold-soft-rgb),.82)}
/* The map->legend half of the sync: a tapped PIN flashes its row so the eye
   can find it after the scroll, without changing what selection means. */
.sl__lgRow.is-flash{box-shadow:0 0 0 2px var(--gold)}
.sl__lgEmpty{font-family:'Jost',sans-serif;font-size:0.75rem;line-height:1.6;
  color:var(--muted-aa);margin:0;padding:0.4rem 0.55rem}
.sl__lgMore{font-family:'Jost',sans-serif;text-transform:uppercase;letter-spacing:.12em;
  font-size:0.6875rem;color:var(--midnight);background:none;border:0;border-top:1px solid var(--line);
  width:100%;text-align:left;padding:0.6rem 0.55rem 0;margin:0.3rem 0 0;min-height:40px;cursor:pointer}
.sl__lgMore:hover{color:var(--gold)}
@media (min-width:761px){
  .sl__pickWrap{grid-template-columns:minmax(0,1fr) 260px}
  .sl__lg{max-height:clamp(260px,42vh,420px);overflow:auto;
    border:1px solid var(--line);border-radius:var(--radius);padding:0.5rem}
}
/* The §0-Q serif name label, past the cluster break. Non-interactive on
   purpose: it names the pin, it is not a second way to tap it. */
.cm-plabel.leaflet-tooltip{background:rgba(247,243,236,.92);color:var(--midnight);
  border:1px solid var(--gold-soft);border-radius:var(--radius);
  font-family:'Cormorant Garamond',serif;font-weight:600;font-size:0.8125rem;
  letter-spacing:.01em;padding:1px 6px;white-space:nowrap;box-shadow:none;pointer-events:none}
.cm-plabel.leaflet-tooltip::before{display:none}

/* ---- results: the collapsed shortlist map -------------------------------- */
.sl__mapWrap{margin:0 0 1rem}
.sl__mapToggle{font-family:'Jost',sans-serif;text-transform:uppercase;letter-spacing:.14em;
  font-size:0.75rem;color:var(--midnight);background:none;border:1px solid var(--line);
  border-radius:var(--radius);padding:0.6rem 1rem;min-height:40px;cursor:pointer;
  transition:border-color var(--dur-1) var(--ease),color var(--dur-1) var(--ease)}
.sl__mapToggle:hover{border-color:var(--gold);color:var(--gold)}
.sl__mapWrap .sl__map{margin-top:0.75rem}

/* ---- the whole card is a link -------------------------------------------- */
/* The overlay anchor covers the card; every control that has its OWN target is
   raised above it, so VIEW FLOOR PLAN, WATCH VIDEO TOUR and the estimator link
   keep working and never trigger the card link. */
/* Same two layers the home cards already use for exactly this (.home__jump::after
   over .home__actions/.home__assets) — and from the --z-* scale, never raw
   numbers: build/audit_stacking.py fails the build on a bare z-index, which is
   how the old 100/140/170-vs-900/1200/1300 mess started. */
.sl__cardLink{position:absolute;inset:0;z-index:var(--z-local-raise);border-radius:inherit;
  text-indent:-9999px;overflow:hidden;white-space:nowrap}
.sl__cardLink:focus-visible{outline:2px solid var(--gold);outline-offset:-3px}
.sl__card:hover{border-color:var(--gold)}
.sl__cardActs,.sl__cardEst{position:relative;z-index:var(--z-local-over)}
.sl__cardActs .sl__plan{position:relative;z-index:var(--z-local-over)}
/* Flagged by its map pin. A ring, not a colour change — the card keeps reading
   the same, it just says "this one". */
.sl__card.is-flagged{border-color:var(--gold);box-shadow:0 0 0 2px rgba(var(--gold-soft-rgb),.45),var(--shadow)}
.sl__cardHoa{font-family:'Jost',sans-serif;font-size:0.75rem;letter-spacing:.04em;color:var(--muted-aa);
  font-variant-numeric:tabular-nums;margin:0.15rem 0 0}

/* ---- the hero card's dark treatment for all of the above ------------------ */
.hsl .sl__pill{color:var(--ivory);background:rgba(var(--ivory-rgb),.06);
  border-color:rgba(var(--ivory-rgb),.26)}
.hsl .sl__pill:hover:not(:disabled){border-color:var(--gold-soft)}
.hsl .sl__pill.is-on{background:rgba(var(--gold-soft-rgb),.24);border-color:var(--gold-soft);color:var(--ivory)}
.hsl .sl__pill.is-some{border-color:var(--gold-soft);box-shadow:inset 0 0 0 1px var(--gold-soft)}
.hsl .sl__n{color:rgba(var(--ivory-rgb),.78);border-left-color:rgba(var(--ivory-rgb),.28)}
.hsl .sl__pickRow{margin:0 0 0.55rem}
.hsl .sl__pills{gap:0.3rem}
/* The hero panel is a 375px-wide glass card over a video, and the video is
   the thing that pays for the height: build/audit_resolution.py measures the
   <video> box against the 1920x1080 original, so every row this panel grows
   upscales a real asset a little further. Four pick rows fit only because
   they are tighter here than in the overlay. */
.hsl .sl__pill{padding:0.5rem 0.65rem;min-height:38px}
.hsl .sl__selCount{color:var(--ivory)}
@media(max-width:560px){
  .sl__map{height:clamp(220px,38vh,320px)}
}
/* ---- Shortlist results toolbar: summary sentence + Sort (+ Adjust on phones)
   One row directly above the cards. `.sl__toolsText` carries the same string
   the condensed refine bar carries — refineSummary() writes both — so the count
   beside "homes" is the count of the cards under it, always.

   STICKY IS ON THIS ROW ON PURPOSE. Below 641px the Adjust pill used to be a
   sticky direct child of .sl__body; it is now inside this row, so the row takes
   the sticky and the pill goes static. .sl__tools is itself a direct child of
   .sl__body — the SAME containing block the pill had — because a sticky element
   is bounded by its containing block, and a sticky rule that is trapped in a
   short box fails exactly like a rule nobody wrote (CLAUDE.md, 2026-08-20).
   build/audit_shortlist_sort.py G4 asserts it is genuinely pinned. */
.sl__tools{display:flex;flex-wrap:wrap;align-items:center;gap:0.5rem 0.75rem;
  min-width:0;margin:0 0 1rem}
.sl__toolsText{flex:1 1 auto;min-width:0;margin:0;overflow-wrap:anywhere;
  font-family:'Jost',sans-serif;font-size:0.75rem;letter-spacing:.02em;color:var(--muted-aa)}
.sl__sortWrap{flex:0 0 auto;display:flex;align-items:center;gap:0.4rem;margin-left:auto;min-width:0}
.sl__sortLabel{flex:none;font-family:'Jost',sans-serif;font-size:0.7rem;letter-spacing:.14em;
  text-transform:uppercase;color:var(--muted-aa)}
/* max-width, not a fixed width: the longest option is "Est. payment: low to
   high" and at 390px an unconstrained select pushes the row past the viewport.
   min-height:44px is the 2026-08-10 touch-target floor — one control, not
   twenty-six of them (see the .hcar__dot note). */
.sl__sort{min-width:0;max-width:11.5rem;min-height:44px;padding:0.5rem 0.6rem;cursor:pointer;
  font-family:'Jost',sans-serif;font-size:0.75rem;color:var(--midnight);
  background:var(--white);border:1px solid var(--line);border-radius:var(--radius)}
.sl__sort:focus-visible{outline:2px solid var(--gold);outline-offset:2px}
@media (max-width:640px){
  .sl__tools{position:sticky;top:.4rem;z-index:var(--z-local-control);gap:0.4rem 0.5rem;
    padding:0.4rem 0.5rem;border-radius:var(--radius);background:var(--white);
    box-shadow:0 6px 18px -12px rgba(var(--midnight-rgb),.45)}
  /* Summary on its own line, then Sort (left) and ADJUST (right) under it.
     THE SECOND ROW HAS TO FIT, AND AT 390px IT ONLY JUST DOES: 48px of
     .sl__main padding and 16px of this row's own leave 326px for a 149px pill,
     a 32px label and the select. So the select is the part that gives — flex
     with min-width:0, because a select that cannot shrink below its longest
     option ("Est. payment: low to high") wraps the pill onto a third row, which
     is the layout the brief rules out. Measured, not assumed:
     build/audit_shortlist_sort.py G4 asserts Sort and ADJUST share a row. */
  .sl__toolsText{flex:1 1 100%}
  /* A DEFINITE flex-basis, not `auto`. Flexbox decides WRAPPING from each
     item's hypothetical main size before it grows anything, and with basis
     auto the wrap width of .sl__sortWrap is the select's max-content — the
     width of "Est. payment: low to high", ~230px — which pushed the pill onto
     a third row even though the two fit comfortably once the select shrinks.
     8rem is what the row is allowed to ASSUME the control needs; flex-grow
     hands back whatever is left over. */
  .sl__sortWrap{flex:1 1 8rem;margin-left:0;min-width:0}
  .sl__sort{flex:1 1 auto;width:0;min-width:0;max-width:none}
  /* The pill's own sticky retires here — the row it now sits in carries it. */
  .sl__tools .sl__refinePill{position:static;top:auto;margin:0 0 0 auto;flex:none;
    padding:0.75rem 0.7rem;white-space:nowrap}
}
.sl__moreWrap{margin:-0.75rem 0 1.5rem;text-align:center}
/* RESULTS "SHOW MORE LIKE THESE" — .sl__showMore, NOT .sl__more.
   It was .sl__more until 2026-08-01, and then step 1 grew a <details
   class="sl__more"> ("More filters") which shares the document with it: the
   homepage hero card renders step 1 while the overlay renders results, so both
   are mounted at once. Two consequences, both live:
     · this block sat AFTER the disclosure's at line ~2098 and overrode it, so
       the More-filters panel was being painted with the button's uppercase
       0.75rem/.14em type and 0.75rem 1.5rem padding;
     · build/audit_shortlist.py drives '.sl__more' to test 6 -> 12 pagination
       and always got the hero's disclosure instead, which is why it reported
       "'show more' revealed 6 cards, expected 12".
   build/audit_quiz_fold.py legitimately owns '.sl__more' for the disclosure,
   so the BUTTON is the one that moved. */
.sl__showMore{font-family:'Jost',sans-serif;text-transform:uppercase;letter-spacing:.14em;font-size:0.75rem;
  color:var(--midnight);background:none;border:1px solid var(--line);border-radius:var(--radius);
  padding:0.75rem 1.5rem;cursor:pointer;transition:border-color var(--dur-1) var(--ease)}
.sl__showMore:hover{border-color:var(--gold);color:var(--gold)}
.sl__refinePill,.sl__refScrim{display:none}
@media (max-width:640px){
  /* Bottom sheet. The results list stays on screen behind it — the whole point
     is watching the cards re-rank while you adjust. */
  .sl__refinePill{display:block;position:sticky;top:.4rem;z-index:var(--z-local-control);margin:0 auto 1rem;
    font-family:'Jost',sans-serif;text-transform:uppercase;letter-spacing:.14em;font-size:0.75rem;
    color:var(--ivory);background:var(--midnight);border:1px solid var(--midnight);border-radius:99px;
    padding:0.75rem 1.5rem;min-height:44px;cursor:pointer;box-shadow:0 6px 18px -8px rgba(var(--midnight-rgb),.55)}
  .sl__refScrim{display:block;position:fixed;inset:0;z-index:var(--z-local-over);background:rgba(var(--midnight-rgb),.34);
    opacity:0;visibility:hidden;transition:opacity 200ms var(--ease),visibility 200ms}
  .sl__refScrim.is-on{opacity:1;visibility:visible}
  .sl__refine{position:fixed;left:0;right:0;bottom:0;top:auto;z-index:var(--z-local-control);
    max-height:72dvh;overflow-y:auto;-webkit-overflow-scrolling:touch;
    margin:0;border-radius:14px 14px 0 0;border-bottom:0;padding:1rem 1rem calc(1rem + env(safe-area-inset-bottom));
    box-shadow:0 -10px 34px -12px rgba(var(--midnight-rgb),.55);
    transform:translateY(101%);visibility:hidden;
    transition:transform 220ms var(--ease),visibility 220ms}
  .sl__refine.is-sheetOpen{transform:none;visibility:visible}
  .sl__refDone{display:inline-block;background:none;border:0;padding:0.25rem 0rem;margin:0;cursor:pointer;
    font-family:'Jost',sans-serif;text-transform:uppercase;letter-spacing:.14em;font-size:0.75rem;color:var(--gold)}
  .sl__refLabel{flex:0 0 100%}
  .sl__chips--add{margin-left:0}
  .sl__stepVal{min-width:0;flex:1}
  @media (prefers-reduced-motion:reduce){
    .sl__refine,.sl__refScrim{transition:none}
  }
}
.sl__lead{background:var(--cream);border-radius:var(--radius);padding:1.5rem;margin-top:1rem}
.sl__leadTitle{font-family:'Cormorant Garamond',serif;font-size:1.5rem;font-weight:600;margin:0 0 1rem;color:var(--midnight)}
.sl__msg{font-size:0.875rem;color:var(--midnight);margin:0.75rem 0 0}
@media (max-width:900px){
  .sl{grid-template-columns:1fr;grid-template-rows:auto 1fr}
  .sl__aside{min-height:132px}
  .sl__asideInner{padding:1.5rem 1.5rem}
  .sl__asideTitle{font-size:1.5rem;margin-bottom:1rem}
  .sl__main{padding:1.5rem 1.5rem 3rem}
  .sl__opts--photo{grid-template-columns:1fr}
  .sl__optImg{aspect-ratio:16/7}
  .sl__card{grid-template-columns:116px 1fr;gap:0.75rem;padding:0.75rem 0 0.75rem 0.75rem}
  .sl__cardBody{padding:0 0.75rem 0 0}
}

/* =============================================================================
   SITEWIDE FILTERS — one bar, four page types (assets/filters.h705391757f.js).
   Sticky under the header (--nav-h). Midnight/champagne, small-caps labels,
   chips scroll horizontally on phones rather than stacking into a wall.
   ========================================================================== */
/* filters.js hides cards with the `hidden` attribute; grid/flex parents would
   otherwise re-display them, so this has to win. */
[hidden]{display:none!important}

/* THE MOUNT PINS, NOT THE BAR. See assets/filters.h705391757f.js — sticky is bounded by
   its containing block, and `.flt`'s was this mount, 268px tall on a 14,409px
   page, so the bar released after ~44px and never returned. The mount's own
   containing block is the section that also holds the grid, which is as tall as
   the results. */
.flt-mount{position:sticky;top:var(--nav-h);z-index:var(--z-sticky-sub)}
/* AN OPEN PANEL IS AN ACTIVE INTERACTION AND OUTRANKS A PASSIVE FLOATING
   LAUNCHER. +1 puts it over `.tgchat-launch` and `.tgchat-teaser` (both
   --z-chat-launcher) and level with `.tgchat-panel`, which is appended later
   and therefore still wins if the buyer actually opened the chat. The teaser is
   suppressed outright rather than merely out-ranked: it ANIMATES IN over the
   controls, and a bubble sliding across a filter panel mid-tap is a moved
   target even when the tap lands. */
.flt-mount.is-panel-open{z-index:calc(var(--z-chat-launcher) + 1)}
.flt-mount.is-panel-open ~ .tgchat-teaser,
body:has(.flt-mount.is-panel-open) .tgchat-teaser{display:none}
.flt{margin:0 0 1.5rem}
/* COLLAPSED BY DEFAULT AT EVERY WIDTH (Tony, 2026-08-22, from the live page:
   "the expanded filter wall looks broken and buries the cards").
   Measured before the change on /floor-plans at 1280x900: the panel rendered
   EXPANDED by default at 414px, putting the first card at y=1266 — below the
   900px fold. At 390 it was already collapsed, so this was a desktop-only
   default, and the machinery to collapse it already existed; it was scoped to
   a max-width:900px media query.

   THE PANEL STAYS IN FLOW WHEN OPEN, and that is a deliberate reading of
   Tony's own gate: "no element from the filter region intersects the card
   grid's bounding box in EITHER state." An absolutely-positioned dropdown
   would intersect the grid by definition the moment it opened. So expanding
   pushes the grid down instead of covering it — a disclosure that drops down,
   without the overlay. */
.flt__toggle{display:flex;align-items:center;gap:.6rem;text-align:left;
  min-height:44px;width:100%;font-family:'Jost',sans-serif;text-transform:uppercase;
  letter-spacing:.16em;font-size:0.75rem;font-weight:500;padding:1rem 1.5rem;cursor:pointer;
  color:var(--midnight);background:var(--white);border:1px solid var(--midnight);border-radius:var(--radius)}
.flt.is-active .flt__toggle{background:var(--midnight);color:var(--ivory)}
/* Lot-size note: unresolved homes are shown at the END of a filtered list rather
   than hidden, and this says so. Deliberately quiet — it is an explanation, not a
   warning, and it must not compete with the result count beside it. */
/* Shortlist result card: lot size, and the positive match reason. The win line
   is gold because it is the answer to "why is this one here?" — the tradeoff line
   above it is the answer to "what's wrong with it", and the two must not look alike. */
.sl__cardLot{font-family:'Jost',sans-serif;font-size:0.75rem;letter-spacing:.03em;color:var(--muted);margin:0.1rem 0 0}
.sl__win{font-family:'Jost',sans-serif;font-size:0.75rem;letter-spacing:.03em;color:var(--gold);margin:0.15rem 0 0}
.sl__optSub{display:block;font-family:'Jost',sans-serif;font-size:0.625rem;letter-spacing:.06em;color:var(--muted);margin-top:.15rem}
.flt__lotnote{font-family:'Jost',sans-serif;font-size:0.75rem;line-height:1.5;
  letter-spacing:.02em;color:var(--muted);margin:0.35rem 0 0;max-width:62ch}
/* The unresolved tail reads as a distinct group without shouting. */
.home[data-unknown-lot] .home__site,.card[data-unknown-lot] .card__meta{opacity:.85}
/* ---- payment mode: the toggle and the loan-type control ----------------
   The mode box sits ABOVE .flt__inner and outside it, so it is never behind
   the phone-width "Filter & Sort" collapse. Payment-or-price is the question
   the list is answering; the facets are refinements to it, and hiding the
   question behind a control labelled "Filter" is what made the payment door
   invisible on phones the first time.
   .sl__tabs / .sl__tab are the QUIZ's classes, reused verbatim — see the
   comment on the toggle in assets/filters.h705391757f.js. Nothing is redefined here. */
.flt__modes{display:flex;flex-direction:column;gap:var(--sp-3);
  padding:var(--sp-4) var(--sp-5);margin-bottom:var(--sp-3);
  background:var(--white);border:1px solid var(--line);
  border-radius:var(--radius);box-shadow:var(--shadow)}
.flt__modes .sl__tabs{margin:0}
.flt__loan{display:flex;align-items:center;gap:var(--sp-3);flex-wrap:wrap;min-width:0}
.flt__loan[hidden]{display:none}
/* Two lines in one pill: the program, and the down payment it assumes. The
   assumption is half of what the figure means, so it travels with the label
   rather than living in a legend somewhere else. */
.flt__pill--loan{flex-direction:column;align-items:flex-start;gap:1px;line-height:1.25}
.flt__sub{font-style:normal;font-size:0.625rem;letter-spacing:.06em;color:var(--muted-aa)}
.flt__pill--loan.is-on .flt__sub{color:inherit;opacity:.8}
@media (max-width:640px){
  .flt__modes{padding:var(--sp-3) var(--sp-4)}
  .flt__loan{gap:var(--sp-2)}
  .flt__loan .flt__pills{flex-wrap:wrap}
}
.flt__inner{display:none;flex-wrap:wrap;align-items:center;gap:1rem 1.5rem;max-width:100%;
  padding:1rem 1.5rem;background:var(--white);border:1px solid var(--line);
  border-radius:var(--radius);box-shadow:var(--shadow)}
/* An off-mode facet group is hidden, not removed — its pills keep their
   listeners and their cached counts across a switch. */
.flt__group[hidden]{display:none}

/* ===================================================================
   GROUPED FILTER PANEL — docs/specs/4c-filter-panel-reorg.md

   .flt__inner is still the flex row; a SECTION is now what flexes,
   and the groups inside it flex again. Nothing was removed and
   nothing is display:none on desktop — build/audit_filter_sections.py
   asserts the control-id set is identical before and after, so this
   file cannot quietly reduce the panel it was asked to organise.
   =================================================================== */
.flt__sec{display:flex;align-items:baseline;gap:.75rem 1.25rem;min-width:0;
  flex-wrap:wrap}
.flt__sec-b{display:flex;flex-wrap:wrap;align-items:center;gap:.75rem 1.5rem;
  min-width:0}

/* THE KICKER. Same serif-adjacent voice as every other section label on the
   site: Jost, uppercase, wide tracking, gold. It reuses .flt__label's exact
   type ramp on purpose — a second heading style inside one component is how a
   panel stops looking like one component. */
.flt__sec-h{display:inline-flex;align-items:center;gap:.4rem;flex:none;
  background:none;border:0;padding:0;cursor:default;
  font-family:'Jost',sans-serif;text-transform:uppercase;letter-spacing:.2em;
  font-size:.7rem;font-weight:500;color:var(--midnight)}
.flt__sec-label{white-space:nowrap}

/* The hairline that separates one section from the next. A rule rather than a
   band tint: the panel already sits on --white inside a bordered card, and a
   second fill inside it reads as a nested container. */
.flt__sec + .flt__sec{border-top:1px solid var(--line);padding-top:.9rem;
  margin-top:.15rem;width:100%}

/* UTILITY IS SUBORDINATE, AND THAT IS THE ONE PLACE HIERARCHY IS EXPRESSED AS
   WEIGHT RATHER THAN POSITION. Sort, per-page and clear-all are controls, not
   criteria — muted label, and the whole section pushed to its own line. */
.flt__sec--utility{margin-top:.35rem}
.flt__sec--utility .flt__sec-h{color:var(--muted)}
.flt__sec--utility .flt__label{color:var(--muted)}

/* An unmapped facet is a DEFECT and looks like one. The gate blocks on it; if
   one ever reaches a page anyway, it must not blend in. */
.flt__sec--unmapped{outline:1px dashed var(--reduced);outline-offset:4px}

/* PER-SECTION ACTIVE COUNT. Empty on desktop-open sections is fine; on a
   collapsed phone section it is the only evidence a constraint is set. */
.flt__sec-n:empty{display:none}
.flt__sec-n{display:inline-flex;align-items:center;justify-content:center;
  min-width:1.15rem;height:1.15rem;padding:0 .3rem;border-radius:999px;
  background:var(--gold);color:var(--midnight);font-style:normal;
  font-family:'Jost',sans-serif;font-size:.66rem;font-weight:600;
  letter-spacing:0}

/* ---- ACTIVE-FILTER TOKENS (spec 4) --------------------------------------
   A sibling of .flt__toggle, never a child: the toggle is a <button> and a
   removable token is a button, and nesting them makes "removable" true only
   for a mouse. */
.flt__tokens{display:flex;flex-wrap:wrap;gap:.4rem;margin:.55rem 0 0}
.flt__tokens.is-empty{display:none}
.flt__token{display:inline-flex;align-items:center;gap:.4rem;
  padding:.3rem .55rem .3rem .7rem;border:1px solid var(--line);
  border-radius:999px;background:var(--white);cursor:pointer;
  font-family:'Jost',sans-serif;font-size:.75rem;letter-spacing:.04em;
  color:var(--midnight);max-width:100%}
.flt__token-t{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.flt__token-x{font-style:normal;font-size:.95rem;line-height:1;color:var(--muted)}
.flt__token:hover{border-color:var(--gold)}
.flt__token:hover .flt__token-x{color:var(--midnight)}

/* ---- 390 / MOBILE: SECTIONS COLLAPSE (spec 3) ---------------------------
   Desktop keeps every section open and the header inert. Only here does the
   header become an actual disclosure — which is why it is a <button> at every
   width rather than changing tag at a breakpoint and losing its listeners,
   its focus and its aria state on a resize. */
@media (max-width:900px){
  .flt__sec{width:100%;flex-direction:column;align-items:stretch;gap:.5rem}
  .flt__sec-h{cursor:pointer;justify-content:flex-start;width:100%;
    padding:.55rem 0;position:relative}
  .flt__sec-h::after{content:'';position:absolute;right:.15rem;top:50%;
    width:.5rem;height:.5rem;margin-top:-.35rem;
    border-right:1.5px solid var(--muted);border-bottom:1.5px solid var(--muted);
    transform:rotate(45deg);transition:transform var(--dur-1) var(--ease)}
  .flt__sec.is-open .flt__sec-h::after{transform:rotate(-135deg);margin-top:-.1rem}
  /* Collapsed is the DEFAULT at this width for every section except the two
     the spec opens: BUDGET and LOCATION. */
  /* The collapse is now a grid-row animation, not display:none — see
     "ACCORDIONS: 0fr -> 1fr" further down this file. The pair that used to
     live here was deleted rather than left to be overridden: an overridden
     rule still reads as the rule, which is how this repo has twice shipped a
     z-index and a position that could never take effect. */
  .flt__sec + .flt__sec{padding-top:.4rem;margin-top:.4rem}
}
@media (prefers-reduced-motion:reduce){
  .flt__sec-h::after{transition:none}
}
.flt__group{display:flex;align-items:center;gap:0.5rem;min-width:0}
.flt__label{flex:none;font-family:'Jost',sans-serif;text-transform:uppercase;letter-spacing:.16em;
  font-size:0.75rem;font-weight:500;color:var(--gold)}
/* WRAP, DO NOT SPILL. This row was display:flex with no flex-wrap, so it
   defaulted to nowrap and any row too wide for its container ran off the right
   edge instead of going to a second line. It only became visible when the VA
   loan pill was restored on 2026-08-02 and the filter bar grew: the payment
   band pills ended at 1181px inside a 1024px viewport. The mobile rule below
   (max-width:900px) deliberately scrolls horizontally instead, so it restates
   nowrap rather than inheriting this. build/audit_proportions.py asserts it. */
.flt__pills{display:flex;flex-wrap:wrap;gap:0.25rem;min-width:0}
.flt__pill{display:inline-flex;align-items:baseline;gap:0;flex:none;font-family:'Jost',sans-serif;
  font-size:0.75rem;letter-spacing:.04em;padding:0.5rem 0.75rem;cursor:pointer;color:var(--ink);
  background:transparent;border:1px solid var(--line);border-radius:var(--radius);
  transition:background var(--dur-1) var(--ease),color var(--dur-1) var(--ease),border-color var(--dur-1) var(--ease)}
.flt__pill:hover{border-color:var(--gold)}
.flt__pill.is-on{background:var(--midnight);color:var(--ivory);border-color:var(--midnight)}
/* an option that would return nothing is disabled, never hidden — a missing
   chip is more confusing than a greyed one (filters.js sets disabled to match) */
.flt__pill.is-void{opacity:.4}
.flt__pill:disabled{cursor:default}
/* THE COUNT IS METADATA, NOT PART OF THE VALUE. "2.5+ 163" read as one number;
   the hairline divider is what does the real work of separating them. Champagne
   at explicit alpha rather than element opacity, so the digits and the divider
   can brighten independently on the selected midnight background. The count
   spacing is owned here (pill gap is 0), so an empty count collapses cleanly. */
.flt__n{font-style:normal;font-size:0.78em;letter-spacing:.06em;font-variant-numeric:tabular-nums;
  color:rgba(var(--gold-soft-rgb),.55);margin-left:0.55rem;padding-left:0.55rem;
  border-left:1px solid rgba(var(--gold-soft-rgb),.22)}
.flt__n:empty{display:none}
.flt__pill.is-on .flt__n{color:rgba(var(--gold-soft-rgb),.75);border-left-color:rgba(var(--gold-soft-rgb),.35)}
/* Results pagination (2026-07-28). The size selector is chips like every other
   facet; the SHOW MORE button appends below the grid and never moves the page. */
.flt__pager{display:flex;flex-direction:column;align-items:center;gap:.6rem;margin:1.75rem 0 .5rem}
.flt__more{font-family:'Jost',sans-serif;font-size:0.8rem;letter-spacing:.14em;text-transform:uppercase;
  color:var(--midnight);background:transparent;border:1px solid var(--gold);border-radius:var(--radius);
  padding:0.85rem 2.2rem;min-height:44px;cursor:pointer;
  transition:background var(--dur-1) var(--ease),color var(--dur-1) var(--ease)}
.flt__more:hover{background:var(--midnight);color:var(--ivory);border-color:var(--midnight)}
.flt__pagerline{margin:0;font-family:'Jost',sans-serif;font-size:0.8rem;letter-spacing:.06em;color:var(--muted-aa);
  font-variant-numeric:tabular-nums}
@media (max-width:560px){
  .flt__more{width:100%;}
}
.flt__select{font-family:'Jost',sans-serif;font-size:0.75rem;padding:0.5rem 0.75rem;max-width:190px;
  /* a <select> won't shrink past its longest option without this, and that
     intrinsic width was dragging the whole mobile panel past the viewport */
  min-width:0;
  color:var(--ink);background:var(--ivory);border:1px solid var(--line);border-radius:var(--radius);cursor:pointer}
.flt__check{display:inline-flex;align-items:center;gap:0.5rem;font-size:0.75rem;color:var(--ink);cursor:pointer}
.flt__check input{accent-color:var(--gold)}
/* A check whose count is zero reads as unavailable rather than vanishing —
   the same treatment .flt__pill.is-void gets, and the same reasoning: a
   missing control is more confusing than a greyed one, and "No HOA" showing
   0 is INFORMATION (nobody has confirmed a zero yet) rather than an absence. */
.flt__check.is-void{opacity:.45}
.flt__check.is-void input{cursor:not-allowed}

/* =============================================================================
   TYPE-AHEAD FACET — Community and Plan (2026-08-15).

   A filtered, grouped list in place of a 26-option native <select>. The panel
   is absolutely positioned so opening it never reflows the filter bar and
   never shifts the grid beneath — a control that pushes the page down as you
   reach for it is worse than the select it replaced.

   MAX-HEIGHT + SCROLL, not a taller bar: at 390px the panel is capped well
   under the viewport so the input stays visible above it while you scan.
   ========================================================================== */
.flt__ta{position:relative;min-width:0}
.flt__ta-input{font-family:'Jost',sans-serif;font-size:0.75rem;padding:0.5rem 0.75rem;
  width:190px;max-width:100%;min-width:0;border:1px solid var(--line);border-radius:var(--radius);
  background:var(--white);color:var(--ink)}
.flt__ta-input:focus{outline:2px solid var(--gold);outline-offset:1px}
.flt__ta-panel{position:absolute;z-index:var(--z-local-over);top:calc(100% + 4px);left:0;
  width:min(20rem,80vw);max-height:min(60vh,22rem);overflow-y:auto;background:var(--white);
  border:1px solid var(--line);border-radius:var(--radius);box-shadow:var(--shadow);padding:.25rem}
.flt__ta-panel[hidden]{display:none}
.flt__ta-group{font-family:'Jost',sans-serif;text-transform:uppercase;letter-spacing:.14em;
  font-size:0.625rem;color:var(--muted);padding:.5rem .75rem .25rem;margin:0}
.flt__ta-opt{display:flex;align-items:center;justify-content:space-between;gap:.75rem;width:100%;
  /* 44px so the row is a real touch target, which is the whole reason this
     replaced a native wheel on a phone. */
  min-height:44px;padding:.4rem .75rem;font-family:'Jost',sans-serif;font-size:0.8125rem;
  text-align:left;background:none;border:none;border-radius:calc(var(--radius) - 2px);
  color:var(--ink);cursor:pointer}
.flt__ta-opt:hover:not(:disabled),
.flt__ta-opt:focus-visible{background:var(--tint);color:var(--midnight)}
.flt__ta-opt:disabled{opacity:.4;cursor:not-allowed}
.flt__ta-opt--all{font-weight:500;border-bottom:1px solid var(--line);border-radius:0;margin-bottom:.25rem}
.flt__ta-none{padding:.75rem;margin:0;font-size:0.75rem;color:var(--muted)}
@media (max-width:430px){
  /* The bar is already horizontally scrollable at this width; the input must
     not add to that, so it shrinks instead of setting a 190px floor. */
  .flt__ta-input{width:11rem}
  .flt__ta-panel{width:min(17rem,86vw)}
}
.flt__clear{margin-left:auto;font-family:'Jost',sans-serif;text-transform:uppercase;letter-spacing:.14em;
  font-size:0.75rem;color:var(--muted);background:none;border:none;cursor:pointer;
  text-decoration:underline;text-underline-offset:3px}
.flt__clear:hover{color:var(--gold)}
.flt__count{font-family:'Jost',sans-serif;text-transform:uppercase;letter-spacing:.14em;font-size:0.75rem;
  color:var(--muted);margin:0 0 1.5rem;font-variant-numeric:tabular-nums}
.flt__empty{max-width:44ch;margin:2rem auto 3rem;text-align:center}
.flt__empty p{font-family:'Cormorant Garamond',Georgia,serif;font-size:1.5rem;line-height:1.4;
  color:var(--midnight);font-weight:500;text-wrap:balance}

/* The open panel, at desktop: the same flex row it has always been, simply
   revealed. The mobile block below overrides it into a column. */
.flt.is-open .flt__inner{display:flex}
/* THE THREE PARTS OF THE COLLAPSED BAR, now at every width: what is on, how
   many it leaves, the way back in. The summary truncates rather than wraps —
   a two-line pinned bar moves the grid under the finger every time a chip
   changes — and the count and the CTA never shrink. */
.flt__t-sum{flex:1 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.flt__t-num{flex:0 0 auto;font-variant-numeric:tabular-nums;opacity:.75;
  padding-left:.6rem;border-left:1px solid currentColor}
.flt__t-cta{flex:0 0 auto;text-decoration:underline;text-underline-offset:3px}

@media (max-width:900px){
  /* PINNED UNDER THE HEADER, NOT BEHIND IT. top:0 put the collapsed
     filter bar at the very top of the viewport, which on a 87px header
     is entirely behind it. Same token, same reason as the desktop rule. */
  .flt{top:var(--nav-h)}
  .flt__toggle{display:flex;align-items:center;gap:.6rem;text-align:left;min-height:44px}
  /* THREE PARTS: what is on, how many it leaves, the way back in. The summary
     takes the slack and TRUNCATES rather than wrapping — a two-line pinned bar
     moves the grid under the finger every time a chip changes — while the count
     and the ADJUST word are never allowed to shrink, because they are the two
     things a buyer is actually looking for. */
  .flt__t-sum{flex:1 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
  .flt__t-num{flex:0 0 auto;font-variant-numeric:tabular-nums;opacity:.75;
    padding-left:.6rem;border-left:1px solid currentColor}
  .flt__t-cta{flex:0 0 auto;text-decoration:underline;text-underline-offset:3px}
  /* flex-wrap:nowrap IS THE WHOLE MOBILE BUG, AND IT IS AN OMISSION RATHER
     THAN A WRONG VALUE (Tony, iPhone/Safari 390pt, 2026-09-17).

     This line sets `flex-direction:column` and says nothing about wrapping, so
     `.flt__inner` inherits `flex-wrap:wrap` from its desktop rule further up
     this file — where wrap is exactly right, because there the container is a
     ROW and wrapping is how the facets form tidy lines.

     A wrapping COLUMN with a bounded height does not wrap onto a new line. It
     wraps into a NEW COLUMN. The height bound arrived separately and correctly
     (`max-height` immediately below, added so the tail of the panel could not
     be cut by `overflow-x:clip`), and the moment it did, the panel gained a
     second column running off the right of the screen. Measured at 390x844 on
     /floor-plans.html before this change: `.flt__sec--home` laid out at
     left 369 -> right 685, i.e. entirely outside a 390px viewport, which is
     the "left: price/county, right: THE HOME / HOME TYPE / PLAN / LOT SIZE /
     HOA" Tony photographed.

     Two individually-correct rules composing into a defect neither can see —
     the same shape as the 26 carousel dots and the trapped z-index this file
     already records. Neither rule is wrong; the omission between them was.

     Scoped to 900px rather than the 768px the brief names, because 900px is
     where `flex-direction:column` is set and the two must not disagree: a fix
     at 768 would leave 769-900 rendering the second column with nobody
     looking. */
  .flt__inner{display:none;margin-top:0.5rem;flex-direction:column;flex-wrap:nowrap;
    align-items:stretch;gap:1rem;padding:1rem 1rem 1.5rem;box-sizing:border-box;
    width:100%;max-width:100%}
  /* NOTHING INSIDE THE PANEL MAY BE WIDER THAN THE PANEL. Belt to the braces
     above: `min-width:0` is what lets a flex child actually shrink below its
     content's intrinsic width, which is the default that quietly re-creates a
     horizontal spill every time a facet gains a long label. */
  .flt__sec,.flt__sec-b,.flt__sec-i,.flt__group,.flt__pills,.flt__tokens{
    max-width:100%;min-width:0;box-sizing:border-box}
  /* THE OPEN PANEL IS CAPPED AND SCROLLS ITSELF, and this is the defect the
     pinned bar would otherwise have created. `.flt` is sticky under an 87px
     header; expanded at 390x844 with every facet on offer the panel is taller
     than what is left of the viewport, and `overflow-x: clip` on html/body
     means the overflow is CUT, not scrolled. The controls at the bottom —
     Clear, per-page, the last chips — would be painted, laid out, inside the
     document and unreachable, which is exactly the 26 carousel dots of
     2026-08-11 and the covered pills of 2026-08-15 in a third costume.
     Capping it against the viewport and scrolling INSIDE keeps every control
     reachable at any height, and -webkit-overflow-scrolling keeps it usable on
     iOS. overscroll-behavior stops a flick at the end of the list scrolling
     the page underneath and losing the buyer's place, which is the same
     "no jump" promise the toggle makes. */
  .flt.is-open .flt__inner{display:flex;max-height:calc(100vh - var(--nav-h) - 7rem);
    overflow-y:auto;-webkit-overflow-scrolling:touch;overscroll-behavior:contain}
  .flt__group{flex-direction:column;align-items:flex-start;gap:0.5rem;width:100%;min-width:0}
  .flt__group--check{flex-direction:row;align-items:center}
  /* chips scroll sideways instead of wrapping into a tall stack */
  .flt__pills{width:100%;min-width:0;flex-wrap:nowrap;overflow-x:auto;-webkit-overflow-scrolling:touch;
    scrollbar-width:none;padding-bottom:0.25rem}
  .flt__pills::-webkit-scrollbar{display:none}
  .flt__select{max-width:none;width:100%}
  .flt__clear{margin-left:0;align-self:flex-start}
}

/* ---- PHONE (<=768px): CHIPS WRAP, THEY DO NOT RUN OFF THE EDGE ----------
   Tony's ruling, 2026-09-17, and it REVERSES the "chips scroll sideways
   instead of wrapping into a tall stack" decision above for phone widths.
   The old behaviour is left in place from 769px to 900px, where a sideways
   row still fits a hand and the stack it avoids is real.

   WHY IT HAD TO GO ON A PHONE. Measured at 390x844 with every section
   expanded, before this change: FOUR separate `.flt__pills` rows were
   horizontal scrollers inside the panel's own vertical scroller
   (974/316, 511/316, 401/316, 333/316 px of content in a 316px box) and 127
   elements had bounding rects past the right edge — the price chips ran to
   x=901 and x=1011. A chip a buyer cannot see is a filter that does not exist
   to them, and a sideways scroller nested inside a vertical one is the
   jitter Tony reported: the first axis your thumb commits to wins, and on a
   chip row that is usually the wrong one.

   This is the ONLY change that removes scroll surfaces. Wrapping these four
   rows leaves exactly one scroller in the panel — `.flt__inner`'s vertical
   one, which stays deliberately (see its own comment above: uncapped, the
   tail of the panel is CUT by `overflow-x:clip` and its controls become
   unreachable, and build/audit_filter_overlap.py is BLOCKING on a panel that
   escapes its box).

   `overflow-x:visible` is restated rather than left to inherit, because the
   rule above sets `auto` and an unreset `auto` on a wrapping row still
   creates a scroll container with nothing to scroll — a scrollbar that
   appears and does nothing is worse than either state. */
@media (max-width:768px){
  .flt__pills{flex-wrap:wrap;overflow-x:visible;row-gap:0.4rem;
    -webkit-overflow-scrolling:auto;padding-bottom:0}

  /* DONE STAYS ON SCREEN. A STICKY ELEMENT TALLER THAN THE VIEWPORT UNPINS.
     `.flt-mount` is sticky at `top:var(--nav-h)` and that is correct while the
     panel is shut. Open, with every section expanded, the mount measures 917px
     against an 844px viewport — and a sticky box taller than its scrollport
     pins only until its BOTTOM arrives, then travels with the page. Measured
     before this rule, panel open, /homes.html at 390x844:

         scrollY    0     bar top  876   visible false
         scrollY  600     bar top  276   visible TRUE
         scrollY 1200     bar top -324   visible false
         scrollY 2000     bar top -1124  visible false

     So the one control that closes the panel left the screen as soon as the
     buyer read past the middle of it, and the only way back was to scroll UP —
     which is the brief's "reachable without scrolling to the bottom", failing
     in the other direction.

     Pinning the BAR rather than the mount fixes it at any panel height,
     because the bar is 52px and can always pin. It is the same offset token
     the mount uses, so the two cannot disagree about where the header ends.

     THE STICKY "DONE" BAR IS NOT FIXED HERE, AND THE ATTEMPTS ARE RECORDED SO
     THE NEXT READER DOES NOT REPEAT THEM. The brief asks for an apply/close
     affordance reachable without scrolling to the bottom. With the panel OPEN
     and every section expanded the mount is taller than the viewport, so it
     pins only until its bottom arrives and then travels with the page:

         scrollY 1200   mount top -492   bar off screen   (open)
         scrollY 1200   mount top   87   bar PINNED       (closed)

     Two fixes were built, measured and removed:

       1. `position:sticky` on `.flt__toggle`. Computed exactly as written
          (`sticky`, `top:87px`, `z-index:2`, no overflow clipping in the
          ancestor chain) and pinned NOTHING — its containing block `.flt`
          sits inside the already-sticky `.flt-mount`, and sticky nested in
          sticky resolves against a box that is itself being translated.
       2. Capping the mount to `100dvh - var(--nav-h)` and flexing the panel
          inside it. This DID shrink the open mount from 921px to 757px, and
          the bar still did not pin: measured -492 at scrollY 1200, the same
          number as before the cap.

     AND IT IS PRE-EXISTING, not a regression from the wrap fixes above. The
     control run on unmodified styles.css gives the identical -492. So it is
     reported as an open item rather than papered over, because a cap that
     changes a number without changing the behaviour is the "rule that reads
     as the fix" this file already records twice. Fixing it properly means
     changing WHICH element is sticky while the panel is open, and
     build/audit_sticky_pin.py K1/K3 are BLOCKING on that element's behaviour —
     which is a bigger change than a layout pass should make unannounced. */
  /* NO max-height OR padding-bottom HERE, DELIBERATELY. Both were written in
     this block first and both were DEAD: a later rule in this file already
     caps `.flt__inner` with `100dvh - nav - callbar - safe-area` and already
     adds the safe-area padding, and it wins on document order. The measured
     `max-height` stayed at 645px while this block asked for something else —
     a rule that reads as the fix and is inert, which is the exact shape this
     file records for a trapped z-index and a sticky with no containing block.
     The cap that actually applies is tightened where it lives; see it for
     why the number moved. */
}

/* ===================================================================
   HOA SUBGROUP + iOS INTERACTION POLISH (Tony, 2026-08-24)
   =================================================================== */

/* ---- THE HOA SUBGROUP --------------------------------------------------
   HOA left AVAILABILITY for THE HOME and gets its own gold subhead one level
   under the section kicker. It reuses `.flt__label` outright rather than
   inventing a third heading style inside one component — the same reasoning
   that made `.flt__sec-h` borrow `.flt__label`'s type ramp.

   NOT `.flt__sub`. That class was taken in 2026-08-02 by the loan pill's
   0.625rem sub-caption (`.flt__pill--loan .flt__sub`), and a bare `.flt__sub`
   rule sits above it with no scoping at all — so a subgroup wrapper wearing
   that name would have silently inherited caption type and italic reset. Two
   individually-correct rules composing into a defect neither can see is the
   most-logged failure in this repo; this one was caught by reading the file
   before writing the class, which is the only way it ever is. */
.flt__sgrp{display:flex;align-items:center;gap:.5rem;min-width:0;flex-wrap:wrap}
.flt__sgrp-h{white-space:nowrap}

/* THE HIDDEN-COMMUNITY LINE. Deliberately quiet — it is a footnote about what
   the filter refused to guess, not a warning. It takes the full width of the
   subgroup so it always sits UNDER the chips rather than beside them, at
   either breakpoint. */
.flt__sgrp-note{flex:1 0 100%;margin:.15rem 0 0;font-family:'Jost',sans-serif;
  font-size:0.6875rem;line-height:1.5;letter-spacing:.04em;color:var(--muted-aa)}
.flt__sgrp-note[hidden]{display:none}

/* ---- ACCORDIONS: 0fr -> 1fr, NEVER height:auto -------------------------
   `height:auto` is not animatable, and the measured-max-height workaround is a
   number that goes stale the moment a chip rail wraps differently — which
   presents as a section clipping its own last row, and as the end-of-animation
   snap when the measurement and the real height disagree. A grid row animating
   between two <flex> values interpolates to the CONTENT's height, so there is
   no number to keep in step and nothing to jump at the end.

   The desktop rule keeps `.flt__sec-b` as the flex row it has always been; the
   grid exists only where the collapse does. `.flt__sec-i` carries the flexing
   at both widths, so the groups inside are laid out identically either way. */
.flt__sec-i{display:flex;flex-wrap:wrap;align-items:center;gap:.75rem 1.5rem;
  min-width:0;width:100%}

@media (max-width:900px){
  /* REPLACES the display:none/flex pair above. `visibility` is what keeps a
     collapsed section out of the tab order — a 0fr row is 0px tall but its
     contents are still focusable, so a keyboard would walk into a section
     nobody can see. It is transitioned with a step-end delay so the body is
     visible for the whole of the opening animation and hidden only once the
     closing one has finished. */
  /* `align-items:stretch` RESTATED, and it is the whole fix.
     `.flt__sec-b`'s base rule is a flex row carrying `align-items:center`.
     Switching `display` to grid here does NOT reset that, so the grid item was
     CENTRE-aligned instead of stretched — which means it takes its content
     height and overflows a 0px row rather than being sized by it. The row read
     0px and the item read 514px in the same measurement, which is what made
     this look like a clipping bug rather than an alignment one. */
  .flt__sec .flt__sec-b{display:grid;grid-template-rows:0fr;overflow:hidden;
    align-items:stretch;
    visibility:hidden;
    transition:grid-template-rows var(--dur-2) var(--ease),
               visibility 0s linear var(--dur-2)}
  .flt__sec.is-open .flt__sec-b{display:grid;grid-template-rows:1fr;
    align-items:stretch;visibility:visible;
    transition:grid-template-rows var(--dur-2) var(--ease),
               visibility 0s linear 0s}
  /* BOTH OF THESE ARE LOAD-BEARING, AND THE SECOND WAS FOUND BY A GATE.
     `min-height:0` stops the grid item claiming its content height as a
     minimum, without which the 0fr row never actually collapses.

     `overflow:hidden` on the ITEM is what makes its own BOX zero. Clipping it
     from the parent alone is only a paint-level fix: measured on
     /find-your-home.html at 390x844, a collapsed THE HOME showed
     `.flt__sec-b` at 0px with `.flt__sec-i` still 514px tall and its bottom
     257px past its parent's. Invisible, and geometrically sitting on the
     results grid — which is exactly what build/audit_filter_overlap.py exists
     to catch, and it caught it: "DIV.flt__sec-i (static), 96px of overlap".
     A box that is only painted away is still there to every measurement that
     is not a screenshot. */
  .flt__sec-i{min-height:0;overflow:hidden}

  /* AND THE OVERFLOW HAS TO COME BACK ONCE THE ROW HAS FINISHED OPENING, or
     the clip that fixes the collapsed state eats the community type-ahead
     panel — `.flt__ta-panel` is absolutely positioned and deliberately hangs
     below its input, so an ancestor clip removes the dropdown from the one
     control build/audit_filters_390.py is written about.

     It is a THIRD class rather than a rule on `.is-open`, because the flip
     must happen at the END of the animation and not at the start: overflow is
     not animatable, so an `.is-open` rule would un-clip on the first frame and
     the section would spill its full height for the length of the open. JS
     adds `.is-settled` when the row has finished and removes it the instant a
     close begins. */
  .flt__sec.is-open.is-settled .flt__sec-b,
  .flt__sec.is-open.is-settled .flt__sec-i{overflow:visible}

  /* ---- AND AT REST, A CLOSED SECTION IS `display:none` ------------------
     CLIPPING IS NOT ABSENCE, and build/audit_filter_overlap.py is the reason
     that sentence is in this file. Zeroing `.flt__sec-i` fixed the wrapper and
     the gate immediately reported the next layer down — `DIV.flt__group`,
     76px of overlap on four routes — because every DESCENDANT of a zero-height
     clipped box still has a box of its own. There is no depth at which a
     clipping fix reaches the bottom; the boxes are painted away and every
     measurement that is not a screenshot still finds them sitting on the
     results grid, which is what the panel is forbidden to do.

     `display:none` is what the pre-animation panel used, and it is right. It
     is applied at REST ONLY, by a class JS adds when the closing animation has
     finished and removes before an opening one starts — so the collapse still
     animates and a settled closed section still measures as absent. That is
     the same shape as `.is-settled`, one state further along. */
  .flt__sec.is-shut .flt__sec-b{display:none}

  /* ---- CHIP RAILS ------------------------------------------------------
     Momentum, snap and no scrollbar. `proximity`, not `mandatory`: a rail
     whose chips are narrower than the viewport would otherwise refuse to rest
     anywhere except a snap point, and the last chip becomes hard to leave
     showing.

     TOUCH-ACTION IS `pan-x pan-y`, AND THE BRIEF ASKED FOR `pan-x`.
     Deliberate, and reported. touch-action is resolved at the element where
     the gesture STARTS and governs the whole gesture, so `pan-x` alone on a
     rail means a vertical drag beginning on any chip pans nothing at all. At
     390px the chip rails are most of the open panel's surface, so that turns
     "swipe down the filter list" into a dead gesture over the majority of it —
     which is the scroll-axis fight the same brief line asks to avoid, arriving
     through the fix for it. `pan-x pan-y` declares both axes explicitly: the
     rail takes the horizontal, the panel scroller takes the vertical, and
     neither has to guess. */
  .flt__pills{scroll-snap-type:x proximity;touch-action:pan-x pan-y;
    overscroll-behavior-x:contain;scroll-padding-left:.25rem}
  .flt__pill{scroll-snap-align:start}
  .flt__pills::-webkit-scrollbar{display:none;width:0;height:0}

  /* ---- THE PANEL CLEARS THE CALL/TEXT BAR -------------------------------
     The cap was `100vh - nav - 7rem`, which is a guess at the bar's height
     dressed as arithmetic and knows nothing about a notch. `--callbar-h` is
     the bar's real height and `env(safe-area-inset-bottom)` is the home
     indicator underneath it — .mobile-callbar already carries that same
     padding, so the two agree by construction instead of by coincidence.
     100dvh, not 100vh: with Safari's URL bar expanded, 100vh overstates the
     visible viewport by ~60px and the bottom of the panel goes under the bar. */
  /* THIS IS THE CAP THAT ACTUALLY APPLIES between 769px and 900px — it is the
     last `.flt__inner` max-height in the file and wins on document order over
     the 7rem one in the 900px block. Below 769px it is switched off entirely;
     see the block immediately after this one. */
  .flt.is-open .flt__inner{
    max-height:calc(100dvh - var(--nav-h) - var(--callbar-h)
                    - env(safe-area-inset-bottom) - 3.5rem);
    padding-bottom:calc(1.5rem + env(safe-area-inset-bottom));
    scroll-padding-bottom:1rem}
}

/* ---- PHONE (<=768px): THE PANEL IS IN FLOW, THE PAGE IS THE SCROLLER ----
   The last piece of the 2026-09-17 mobile pass, and it had to go AFTER the
   rule above rather than in the <=768px block further up this file — that is
   where it was written first, and the later cap silently overrode it. Reading
   the computed style is what caught it: `max-height` stayed at 645px while an
   earlier block asked for something else. Document order decides between two
   rules of equal specificity, so the override lives next to the thing it
   overrides.

   WHY THE INNER SCROLLER GOES ON A PHONE. Wrapping the chips makes a single
   section taller than the capped box, and build/audit_filter_overlap.py O2 is
   BLOCKING: it compares each panel child's rect against the card grid, and a
   child that is clipped by an ancestor still REPORTS its full rect. Bisected
   on /find-your-home.html at 390x844, which is the tightest route on the site:

       nowrap column fix only        clean, 11 measurements
       + wrapped chips              51px of overlap   BLOCKING red
       + wrapped chips, in flow      clean, 11 measurements

   The 51px was constant while the panel's height moved 705 -> 633, which is
   what ruled height out as the cause and pointed at the clipped child.

   THE 2026-08-20 OBJECTION IS ANSWERED, NOT IGNORED. The cap exists because an
   uncapped panel that is STICKY has its tail cut by `overflow-x: clip` and its
   last controls become unreachable. In flow the panel pushes the grid down
   instead, so the page scrolls and every control is reachable by ordinary
   vertical scroll — which is also the brief's "exactly one scroll surface".
   Verified rather than argued: audit_covered_controls.py and
   audit_sticky_pin.py (K1 pinned, K3 expansion-in-place) both PASS with this
   rule in force. */
@media (max-width:768px){
  .flt.is-open .flt__inner{max-height:none;overflow-y:visible;
    overscroll-behavior:auto}
}

/* ---- THE OPEN PANEL SILENCES THE CHAT LAUNCHER -------------------------
   `.flt-mount.is-panel-open` already raised the panel over `.tgchat-launch`
   (z 300) and hid `.tgchat-teaser`, after elementFromPoint found the launcher
   topmost over a `.flt__pill` at 390x844. Raising the panel fixes the pills it
   covers and leaves the launcher itself sitting over the bottom of the list,
   painted and now unreachable — a control that is neither usable nor gone.
   Hidden outright while the panel is open, restored the moment it closes.

   DRIVEN BY A BODY CLASS, NOT ONLY BY `:has()`. The existing teaser rule uses
   `:has()`, which iOS Safari shipped in 16.4 — so on the exact devices this
   whole pass is about, older ones would keep the launcher over the chips. The
   class is set in the same handler as the raise. */
body.flt-panel-open .tgchat-launch,
body.flt-panel-open .tgchat-teaser,
body:has(.flt-mount.is-panel-open) .tgchat-launch{
  opacity:0;pointer-events:none;visibility:hidden}

/* ---- BODY SCROLL LOCK --------------------------------------------------
   Same shape as `body.tgchat-lock` above, and pointedly NOT `position:fixed`:
   fixed collapses the document offset to 0 and restores it on release, which
   is a jump at both ends of a component whose acceptance test is that it never
   jumps, and it would break `.flt-mount`'s sticky pinning while open.
   `overscroll-behavior:none` on the root is what stops the rubber-band on a
   page that can no longer scroll — the panel scroller's own `contain` already
   stops a flick chaining out of the list. */
html.flt-scroll-lock,body.flt-scroll-lock{overflow:hidden;overscroll-behavior:none}

/* ---- TAP TARGETS, TAP-DELAY AND STICKY HOVER ---------------------------
   `manipulation` drops the 300ms double-tap-zoom wait on every control in the
   panel. The 44px floor is the same one the 2026-08-10 touch-target pass set —
   applied here with `min-height`, never a fixed height, because a chip whose
   label wraps must grow rather than clip. */
@media (max-width:900px){
  .flt__toggle,.flt__sec-h,.flt__pill,.flt__check,.flt__clear,.flt__more,
  .flt__select,.flt__ta-input,.flt__ta-opt,.flt__token,.flt__token-x{
    touch-action:manipulation}
  .flt__pill,.flt__ta-opt,.flt__ta-input{min-height:44px}
  /* THE SELECT NEEDED `appearance:none` BEFORE 44px MEANT ANYTHING.
     Measured at 390x844: both selects rendered 28px tall with computed
     `min-height:18px` and `padding:0px` — while this file sets 0.5rem/0.75rem
     padding on them and the line above asked for 44px. Neither was being
     ignored by cascade; WebKit substitutes the native control's intrinsic
     metrics for a menulist and simply does not honour author padding or
     min-height on one. The author rule was there, correct, and inert — which
     is the failure this repo keeps logging, and the only reason it was found
     is that the probe measured the rendered box instead of reading the rule.
     `appearance:none` hands layout back to CSS; the chevron is redrawn as a
     background so the affordance survives the swap. Mobile only — this is a
     touch-target fix and desktop keeps the native control. */
  .flt__select{-webkit-appearance:none;appearance:none;min-height:44px;
    padding:0.6rem 2.1rem 0.6rem 0.75rem;
    background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6' viewBox='0 0 10 6'%3E%3Cpath d='M1 1l4 4 4-4' fill='none' stroke='%23707070' stroke-width='1.4' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
    background-repeat:no-repeat;background-position:right 0.85rem center}
  .flt__pill{align-items:center}
  .flt__sec-h{min-height:44px}
  .flt__check{min-height:44px;align-items:center}
  .flt__check input{width:20px;height:20px}
  .flt__clear,.flt__more{min-height:44px}
  .flt__token{min-height:36px}
  .flt__token-x{min-width:24px;min-height:24px;display:inline-flex;
    align-items:center;justify-content:center}
}

/* HOVER IS FOR POINTERS. On a touchscreen `:hover` latches after a tap and the
   chip stays gold-bordered until something else is touched, which reads as a
   selected state next to `.is-on`, which actually is one. Every hover rule in
   the panel is re-declared here behind the capability query and the unguarded
   originals are neutralised for touch. */
@media (hover:none){
  .flt__pill:hover{border-color:var(--line)}
  .flt__pill.is-on:hover{border-color:var(--midnight)}
  .flt__token:hover{border-color:var(--line)}
  .flt__token:hover .flt__token-x{color:var(--muted)}
  .flt__clear:hover{color:inherit}
  .flt__more:hover{background:transparent;color:var(--ink);border-color:var(--line)}
  .flt__ta-opt:hover:not(:disabled){background:transparent;color:inherit}
}

/* ---- NO JITTER UNDER THE STICKY BAR ------------------------------------
   A `transform`, `filter`, `perspective` or `will-change` on ANY ancestor
   makes that ancestor the containing block for a fixed child and a new
   stacking context for a sticky one, and on iOS it is also what produces the
   one-frame shudder as the compositor re-promotes the pinned layer on each
   scroll tick. `.flt-mount` is sticky; its ancestors are the section and the
   page wrapper, and the reveal animation on this site sets `transform` on
   sections. The mount's own ancestors are pinned to none while the panel is
   open.

   NOTHING IS PROMOTED HERE, AND THAT IS THE FINDING. Measured at 390x844 with
   the panel open at a scroll depth of 2200px, the list of `.flt-mount`
   ancestors carrying a transform, filter, perspective or will-change is
   EMPTY — the condition this rule would have fixed does not exist, so the
   assertion belongs in the receipt and not in the stylesheet.

   A first draft added `backface-visibility:hidden` as a promotion hint and it
   was REMOVED. It creates a stacking context on the mount; this site has a RED
   stacking gate right now (ADVISORY_REPORT.md, "modal close controls vs
   header/chat"); and adding a speculative stacking context beside a failing
   stacking assertion is how a session hands the next one a defect wearing its
   own fix's clothes. `transform:none` stays, because it is an explicit refusal
   rather than a hint. */
.flt-mount.is-panel-open{transform:none}

/* ---- REDUCED MOTION ----------------------------------------------------
   Collapse every animation this pass introduced to instant. The section body
   still opens and closes — only the interpolation goes. */
@media (prefers-reduced-motion:reduce){
  .flt__sec .flt__sec-b,
  .flt__sec.is-open .flt__sec-b{transition:none}
  .flt__pills{scroll-behavior:auto;scroll-snap-type:none}
  .flt__pill{transition:none}
}

/* --- header phone: always reachable, every page --- */
/* THE PHONE IS A NAV LINK, so it takes the nav link's size, weight and colour
   (2026-08-13). It used to be the one gold item at rest in a row of cream ones,
   which read as an accent nobody had chosen. Gold now means hover here too.
   The icon grows with the type and `align-items:center` keeps it on the same
   optical baseline as the digits — it was 15px against 11.2px text and now sits
   at 16px against 14px, the same ratio. */
.nav__tel{display:inline-flex;align-items:center;gap:.5rem;font-family:'Jost',sans-serif;
  font-size:.875rem;font-weight:400;letter-spacing:.12em;text-transform:uppercase;
  color:var(--ivory);white-space:nowrap;transition:color var(--dur-2) var(--ease)}
.nav__tel svg{width:16px;height:16px;flex:none}
.nav__tel:hover{color:var(--gold-soft)}
@media (max-width:900px){
  /* compact icon left of the hamburger — one tap to call from anywhere */
  /* .65rem, not .4rem: these are two 44px touch targets side by side and 6px
     between them is a mis-tap waiting to happen. It also keeps the header
     collision gate honest at an 8px floor instead of tuning the gate down to
     accommodate a spacing that was too tight anyway. */
  .nav__tel{margin-left:auto;margin-right:.65rem;width:44px;height:44px;justify-content:center;
    border:1px solid rgba(var(--gold-soft-rgb),.4);border-radius:50%}
  .nav__tel svg{width:17px;height:17px}
  .nav__telNum{display:none}
  body.mnav-open .nav__tel{display:none}   /* never competes with the open menu */
}

/* --- legal / privacy page --- */
.legal__body{max-width:760px;margin:0 auto}
.legal__body h3{font-family:'Cormorant Garamond',serif;font-size:1.5rem;font-weight:600;color:var(--midnight);
  margin:2rem 0 0.5rem}
.legal__body h3:first-child{margin-top:0}
.legal__body p{font-size:1rem;line-height:1.75;color:var(--muted);font-weight:300;margin:0 0 0.75rem}
.legal__body strong{color:var(--ink);font-weight:500}
.legal__body a{color:var(--midnight);border-bottom:1px solid var(--gold)}
.legal__contact{margin-top:2rem;padding-top:1.5rem;border-top:1px solid var(--line);font-size:0.875rem}

/* --- SMS consent (verbatim A2P wording; unchecked by default) --- */
.consent,.sl__consent{display:flex;align-items:flex-start;gap:0.5rem;margin:0.25rem 0 1rem;
  font-family:'Jost',sans-serif;font-size:0.75rem;line-height:1.55;font-weight:300;
  color:var(--muted);text-transform:none;letter-spacing:0;cursor:pointer}
.consent input,.sl__consent input{flex:none;width:17px;height:17px;margin-top:0.25rem;accent-color:var(--gold)}
.consent a,.sl__consent a{color:var(--midnight);border-bottom:1px solid var(--gold)}

/* THE CONSENT SENTENCE IS NOT SHOUTED, AND UNTIL 2026-08-26 IT WAS.
   `.consent` has declared `text-transform:none` since it shipped. It never won:
   `.contact__form label` is (0,1,1) and `.consent` is (0,1,0), so every consent
   line living inside a contact form rendered ALL CAPS at 12px — measured on 356
   built pages, most of them buyer home-detail pages, on the one sentence a
   compliance reviewer reads. The stylesheet SAID it was handled, which is
   exactly why nobody looked: a rule sitting in the file reads as proof.

   Specificity, not !important — the declaration was always right, it simply
   lost. The span is named too because text-transform inherits and the label's
   uppercase was reaching it directly. */
.contact__form .consent,.contact__form .consent span,
.contact__form .sl__consent,.contact__form .sl__consent span{
  text-transform:none;letter-spacing:0;font-weight:300}

/* =============================================================================
   ROYAL TREATMENT — global interaction polish.
   Everything here is cross-cutting: focus, selection, scrollbars, pressed
   states, page transitions, image loading. Component-specific polish stays
   with its component.
   ========================================================================== */

/* --- Focus: keep the ring for keyboards, lose the browser blue ----------- */
:focus{outline:none}
:focus-visible{outline:2px solid var(--gold);outline-offset:3px;border-radius:var(--radius)}
/* champagne needs its lighter tone to stay visible on midnight grounds */
.nav :focus-visible,.mnav :focus-visible,.hero :focus-visible,.page-hero :focus-visible,
.footer :focus-visible,.cta-band :focus-visible,.hsl :focus-visible,
.visit :focus-visible,.mobile-callbar :focus-visible{outline-color:var(--gold-soft)}

/* --- Selection ----------------------------------------------------------- */
::selection{background:rgba(var(--gold-rgb),.26);color:var(--midnight)}

/* --- Scrollbar: desktop pointers only; phones keep the native overlay ----- */
@media (hover:hover) and (pointer:fine){
  html{scrollbar-width:thin;scrollbar-color:rgba(var(--gold-rgb),.42) transparent}
  ::-webkit-scrollbar{width:11px;height:11px}
  ::-webkit-scrollbar-track{background:transparent}
  ::-webkit-scrollbar-thumb{background:rgba(var(--gold-rgb),.38);border:3px solid var(--ivory);
    border-radius:var(--radius-pill);background-clip:padding-box}
  ::-webkit-scrollbar-thumb:hover{background:rgba(var(--gold-rgb),.68);background-clip:padding-box}
}

/* --- Pressed state: touch gets no hover, so it needs this ---------------- */
.btn:active,.flt__pill:active,.sl__opt:active,.home__broc:active,.fp-dlall:active,
.flt__clear:active,.sl__btn:active,.visit__submit:active{transform:translateY(1px)}

/* --- Hover: gentle lift on cards, arrow nudge on links ------------------- */
@media (hover:hover) and (pointer:fine){
  .card,.home,.plan{transition:transform var(--dur-2) var(--ease),box-shadow var(--dur-2) var(--ease)}
  .card:hover,.home:hover,.plan:hover{transform:translateY(-3px);box-shadow:var(--shadow-lift)}
  .link-more:hover .arw{transform:translateX(5px)}
}

/* --- Page transition: fade out on leave so navigation never white-flashes.
   The colour has to live on <html>, not <body>, or the gap between documents
   paints white. But once <html> has its own background, <body>'s stops
   propagating to the canvas and becomes a real painted box — which then covers
   every negative-z-index layer on the page (the hero video/stills at z:-2 and
   the hero overlay at z:-1 both vanished behind it). So <body> goes
   transparent and <html> owns the page colour outright. */
html{background:var(--ivory)}
body{background:transparent;transition:opacity var(--dur-1) var(--ease)}
html.is-leaving body{opacity:0}
@media (prefers-reduced-motion:reduce){
  html.is-leaving body{opacity:1}
}

/* --- Images: fade + unblur as they decode, over a warm tint. Never a grey
   box popping in. `is-ready` is set by script.he6fbd5d35b.js on load (or immediately for
   images already in cache). --------------------------------------------- */
@media (prefers-reduced-motion:no-preference){
  img.blurup{opacity:0;filter:blur(14px);transform:scale(1.02);
    transition:opacity var(--dur-3) var(--ease),filter var(--dur-3) var(--ease),
               transform var(--dur-3) var(--ease)}
  img.blurup.is-ready{opacity:1;filter:none;transform:none}
}
.card__media,.home__media,.plan__media,.page-hero__img{background-color:var(--cream)}

/* =============================================================================
   SCHEDULE A VISIT — modal. Replaces every CTA that used to anchor-scroll to
   the footer form. Bottom-sheet on phones, centred panel on desktop.
   ========================================================================== */
.visit{position:fixed;inset:0;z-index:var(--z-modal);display:flex;align-items:center;justify-content:center;
  padding:1.5rem;opacity:0;visibility:hidden;
  transition:opacity var(--dur-2) var(--ease),visibility var(--dur-2)}
.visit.is-open{opacity:1;visibility:visible}
.visit__scrim{position:absolute;inset:0;background:rgba(var(--midnight-rgb),.72);
  backdrop-filter:blur(6px);-webkit-backdrop-filter:blur(6px)}
/* THE PANEL ITSELF NEVER SCROLLS — its BODY does.
   It used to be the scroll container with .visit__x absolutely positioned
   inside it, which meant the close button scrolled away with the form: on a
   short viewport you could scroll the consent text into view and lose the X
   entirely. Same failure .sl fixed by anchoring its close button outside the
   scroll container. Here the panel is a flex column, .visit__form/.visit__done
   take the scrolling, and the X stays pinned to a panel edge that no longer
   moves. `overflow:hidden` also clips content to the border radius, so the
   top edge reads as a clean panel edge instead of a translucent band with the
   page showing through behind it. */
.visit__panel{position:relative;width:100%;max-width:480px;max-height:calc(100dvh - 3rem);
  display:flex;flex-direction:column;overflow:hidden;background:var(--ivory);
  border-radius:var(--radius);box-shadow:var(--shadow-modal);padding:2rem 2rem 2rem;
  transform:translateY(12px) scale(.985);
  transition:transform var(--dur-2) var(--ease)}
/* min-height:0 is what actually lets a flex child scroll — without it the
   child's content sets the floor and the panel grows past max-height. */
.visit__form,.visit__done{overflow-y:auto;-webkit-overflow-scrolling:touch;min-height:0}
.visit.is-open .visit__panel{transform:none}
.visit__x{position:absolute;top:.6rem;right:.8rem;width:44px;height:44px;background:none;border:none;
  font-size:2rem;line-height:1;color:var(--muted);cursor:pointer;transition:color var(--dur-1) var(--ease)}
.visit__x:hover{color:var(--midnight)}
.visit__eyebrow{font-family:'Jost',sans-serif;text-transform:uppercase;letter-spacing:.28em;
  font-size:0.625rem;font-weight:500;color:var(--gold);margin:0}
.visit__title{font-family:'Cormorant Garamond',Georgia,serif;font-size:2rem;font-weight:600;
  line-height:1.12;color:var(--midnight);margin:0.75rem 0 0.5rem;text-wrap:balance}
.visit__sub{font-size:0.875rem;font-weight:300;line-height:1.6;color:var(--muted);margin:0 0 1.5rem}
.visit__field{display:block;margin-bottom:1rem}
.visit__field > span{display:block;font-family:'Jost',sans-serif;font-size:0.75rem;letter-spacing:.16em;
  text-transform:uppercase;color:var(--muted);margin-bottom:0.5rem}
.visit__field input,.visit__field select{width:100%;padding:0.75rem 1rem;font-family:'Jost',sans-serif;
  font-size:16px;color:var(--ink);background:var(--white);border:1px solid var(--line);
  border-radius:var(--radius);transition:border-color var(--dur-1) var(--ease),box-shadow var(--dur-1) var(--ease)}
.visit__field input:focus,.visit__field select:focus{outline:none;border-color:var(--gold);
  box-shadow:0 0 0 3px rgba(var(--gold-rgb),.14)}
/* gentle validation — a champagne note, never a red shout */
.visit__field.is-off input,.visit__field.is-off select{border-color:var(--gold)}
.visit__note{display:block;font-family:'Jost',sans-serif;font-size:0.75rem;letter-spacing:.04em;
  color:var(--gold);margin-top:0.25rem;min-height:1em}
.visit__row{display:grid;grid-template-columns:1fr 1fr;gap:0.75rem}
.visit__consent{display:flex;gap:0.5rem;align-items:flex-start;margin:0.25rem 0 1rem;
  font-size:0.75rem;line-height:1.55;color:var(--muted);cursor:pointer}
.visit__consent input{margin-top:0.25rem;accent-color:var(--gold);flex:none}
.visit__consent a{color:var(--gold);text-decoration:underline;text-underline-offset:2px}
.visit__submit{width:100%;font-family:'Jost',sans-serif;text-transform:uppercase;letter-spacing:.18em;
  font-size:0.75rem;font-weight:500;padding:1rem 1.5rem;cursor:pointer;color:var(--ivory);
  background:var(--midnight);border:1px solid var(--midnight);border-radius:var(--radius);
  transition:background var(--dur-1) var(--ease),color var(--dur-1) var(--ease),opacity var(--dur-1) var(--ease)}
.visit__submit:hover{background:var(--gold);border-color:var(--gold);color:var(--midnight)}
.visit__submit[disabled]{opacity:.6;cursor:default}
.visit__done{text-align:center;padding:1rem 0 0.5rem}
.visit__done p{font-family:'Cormorant Garamond',Georgia,serif;font-size:1.5rem;line-height:1.35;
  color:var(--midnight);font-weight:500;text-wrap:balance}
.visit__fine{font-size:0.75rem;color:var(--muted);margin-top:1rem;line-height:1.5}
body.visit-open{overflow:hidden}

@media (max-width:560px){
  /* bottom sheet */
  .visit{padding:0;align-items:flex-end}
  .visit__panel{max-width:none;border-radius:var(--radius) var(--radius) 0 0;
    max-height:92dvh;padding:2rem 1.5rem 1.5rem;transform:translateY(26px)}
  .visit__row{grid-template-columns:1fr}
}
@media (prefers-reduced-motion:reduce){
  /* every overlay, not just the modal — the audit caught .sl and .mnav missing */
  .visit,.visit__panel,.reveal,.sl,.mnav,.bv{transition-duration:1ms!important}
  .visit__panel{transform:none}
  .mnav{transform:none}
  /* The hero is NOT handled here, deliberately. Hiding .hero__video would hide
     its poster with it, and the poster is the reduced-motion hero — a finished
     2560x1440 still, not a hole. script.he6fbd5d35b.js is what enforces this: it never
     attaches a source under reduced motion, and pauses the video if the
     preference is turned on mid-session. One URL, one request, no rotation. */
}

/* --- Nothing floats over a full-screen surface ----------------------------
   The layer scale already puts every modal (--z-modal) above the chat launcher
   (--z-chat-launcher) and the header (--z-sticky-header), so none of this is
   load-bearing for STACKING any more. It stays for a different reason: a
   launcher that is merely behind a modal is still a tab stop and still reads
   to a screen reader, so it is removed from the page outright while a modal
   owns the screen. */
body.visit-open .tgchat-launch,
body.visit-open .tgchat-panel,
body.visit-open .mobile-callbar,
body.sl-open .tgchat-launch,
body.sl-open .tgchat-panel,
body.sl-open .mobile-callbar,
body.bv-open .tgchat-launch,
body.bv-open .tgchat-panel,
body.bv-open .mobile-callbar,
body.lb-open .tgchat-launch,
body.lb-open .tgchat-panel,
body.lb-open .mobile-callbar{display:none!important}

/* --- The header yields to any modal --------------------------------------
   Stacking alone stops the header PAINTING over a dialog. It does not stop the
   header swallowing a click, receiving focus, or being announced — the nav is
   still a live landmark sitting behind a scrim the user cannot see through.
   While a modal owns the screen the header is inert: no hit-testing, no tab
   stops. `inert` is set on .nav by the modal open/close helpers in JS for AT;
   this is the pointer half, which needs no JS and cannot get out of sync.
   The mobile menu is deliberately absent: it IS header chrome, spawned by the
   nav toggle, so disabling the header would strand its own opener. */
body.visit-open .nav,
body.sl-open .nav,
body.bv-open .nav,
body.lb-open .nav{pointer-events:none}

/* Schedule a Visit inside the mobile menu — styled as a menu row, not a button,
   so it reads as part of the list. Added because hiding the hero's secondary
   CTAs left the visit modal with no reachable trigger on a phone. */
.mnav__visit{display:flex;align-items:center;min-height:48px;padding:.5rem 0;width:100%;
  background:none;border:none;cursor:pointer;text-align:left;
  font-family:'Cormorant Garamond',Georgia,serif;font-size:1.95rem;font-weight:500;
  line-height:1.15;color:var(--ivory);letter-spacing:.01em}
.mnav__visit:active{color:var(--gold-soft)}
@media (max-width:380px){ .mnav__visit{font-size:1.7rem;min-height:46px} }

/* =================================================================
   /map — full area map. Desktop-primary; phones get list-over-map.
   ================================================================= */
.am-wrap{max-width:var(--max);margin:0 auto}
.am-toolbar{display:flex;flex-wrap:wrap;align-items:center;gap:0.5rem;margin:0 0 1rem}
.am-toolbar__label{font-family:'Jost',sans-serif;text-transform:uppercase;letter-spacing:.16em;
  font-size:0.75rem;color:var(--muted);margin-right:0.25rem}
.am-chip{font-family:'Jost',sans-serif;font-size:0.75rem;letter-spacing:.04em;padding:0.5rem 0.75rem;
  border:1px solid var(--line);border-radius:var(--radius-pill);background:transparent;color:var(--ink);
  cursor:pointer;white-space:nowrap;
  transition:background var(--dur-1) var(--ease),color var(--dur-1) var(--ease),border-color var(--dur-1) var(--ease)}
.am-chip:hover{border-color:var(--gold)}
.am-chip.is-on{background:var(--midnight);color:var(--ivory);border-color:var(--midnight)}
.am-chip--rt.is-on{background:var(--reduced);border-color:var(--reduced)}
/* NO HOA — a plain chip. It takes .am-chip.is-on's midnight like Market, Type
   and Lot Size do, and deliberately NOT .am-chip--rt's red: Price Reduced is
   the one chip on this toolbar that marks an event, and giving a second chip
   the same treatment costs the first one its meaning. */
.am-chip__n{font-size:0.625rem;letter-spacing:.1em;color:var(--muted);margin-left:0.35rem}
.am-chip.is-on .am-chip__n{color:var(--ivory);opacity:.75}
.am-clear{background:none;border:0;font-family:'Jost',sans-serif;font-size:0.75rem;letter-spacing:.1em;
  text-transform:uppercase;color:var(--muted);cursor:pointer;text-decoration:underline;padding:0.5rem 0.25rem}
.am-count{font-family:'Jost',sans-serif;font-size:0.625rem;letter-spacing:.14em;text-transform:uppercase;
  color:var(--gold);margin-left:auto}

/* map + list side by side on desktop; stacked (map first, short) on phones */
.am-grid{display:grid;grid-template-columns:1.55fr 1fr;gap:1.5rem;align-items:start}
.am-grid > *{min-width:0}
#area-map{height:min(74dvh,720px);border-radius:var(--radius);border:1px solid var(--line);
  box-shadow:var(--shadow);background:var(--cream)}
.am-side{max-height:min(74dvh,720px);overflow:auto;border:1px solid var(--line);
  border-radius:var(--radius);background:var(--white)}
.am-list{list-style:none;margin:0;padding:0}
.am-row{display:flex;align-items:center;gap:0.75rem;padding:0.75rem 1rem;border-bottom:1px solid var(--line);
  cursor:pointer;transition:background var(--dur-1) var(--ease)}
.am-row:last-child{border-bottom:0}
.am-row:hover{background:var(--ivory)}
.am-row__swatch{width:9px;height:9px;border-radius:50%;flex:0 0 auto}
.am-row__main{display:flex;flex-direction:column;gap:0.25rem;min-width:0;flex:1}
.am-row__name{font-family:'Cormorant Garamond',serif;font-size:1rem;font-weight:600;
  color:var(--midnight);text-decoration:none}
.am-row__name:hover{color:var(--gold)}
.am-row__meta{font-family:'Jost',sans-serif;font-size:0.75rem;letter-spacing:.05em;color:var(--muted)}
.am-row__price{font-family:'Jost',sans-serif;font-size:0.75rem;letter-spacing:.04em;color:var(--ink);
  white-space:nowrap}
.am-row__rt{font-family:'Jost',sans-serif;font-size:0.625rem;letter-spacing:.14em;text-transform:uppercase;
  color:var(--reduced);border:1px solid var(--reduced);border-radius:var(--radius-pill);padding:0rem 0.5rem;
  white-space:nowrap}
/* THE QUIET TAG. Byte-for-byte .am-row__rt's geometry — same family, size,
   tracking, case, pill and padding — with --muted in place of --reduced. It is
   the same object in a lower voice, which is what "same treatment, no colour
   shout" means: a tag that differed in SHAPE would read as a different kind of
   thing, and one that differed in COLOUR would compete with the badge beside
   it for the same glance. */
.am-row__nohoa{font-family:'Jost',sans-serif;font-size:0.625rem;letter-spacing:.14em;text-transform:uppercase;
  color:var(--muted);border:1px solid var(--line);border-radius:var(--radius-pill);padding:0rem 0.5rem;
  white-space:nowrap}

/* ---------------------------------------------------------------------------
   THE AREA-MAP LIST HAD NO PHONE BREAKPOINT — 2026-09-06.

   `.am-row` is `display:flex; align-items:center` at TOP LEVEL and appears in
   no media query at any width. At 390 the row is swatch + name/meta + price +
   REDUCED tag + NO-HOA tag on one line, and it measured:

       .am-row      scrollWidth 476   clientWidth 348   +128 over
       .am-list     scrollWidth 555   clientWidth 348   +207 over
       .am-row__name scrollWidth 54   clientWidth  44   +10  over

   `overflow-x: clip` on html/body means that row CUTS rather than scrolls, so
   the overflow is invisible to a scrollWidth check on the document — the page
   reports no horizontal scroll while "HOMES IN THIS COMMUNITY" is sliced off
   mid-word and the price prints over the county name.

   TWO INDIVIDUALLY-CORRECT RULES, ONE DEFECT NEITHER CAN SEE. The 2026-08-10
   touch-target pass gave `.am-row__name` min-width:44px (correct: it is a tap
   target), and this row has always been horizontal (correct at desktop). The
   composition is the 26-dots defect of 2026-08-11 in a third costume.

   `.pinv__commHead` on the same page already stacks at <=760px. This is the
   same treatment for the list that sits under the map, which is the one Tony
   photographed.

   Wrapping rather than forcing a column: the swatch and name belong on one
   line, and only the trailing price/tags need to fall through. `min-width:0`
   on the flex child is what actually lets the name ellipsize instead of
   forcing its parent wider — without it `flex:1` refuses to shrink below
   content width, which is the whole mechanism behind the +128. */
@media (max-width:760px){
  .am-row{flex-wrap:wrap;align-items:flex-start;gap:.3rem .6rem;padding:.7rem .85rem}
  .am-row__main{flex:1 1 100%;min-width:0}
  .am-row__name{min-width:0;max-width:100%;overflow-wrap:anywhere}
  .am-row__price,.am-row__rt,.am-row__nohoa{flex:0 0 auto}
  .am-list{min-width:0}
}
.am-empty{padding:1.5rem 1rem;font-size:0.875rem;line-height:1.7;color:var(--muted)}

/* layer toggles */
.am-layers{display:flex;flex-wrap:wrap;gap:1rem;margin:1rem 0 0;padding:1rem 0 0;
  border-top:1px solid var(--line)}
.am-layers label{display:inline-flex;align-items:center;gap:0.5rem;font-family:'Jost',sans-serif;
  font-size:0.75rem;letter-spacing:.06em;color:var(--ink);cursor:pointer}
.am-layers input{accent-color:var(--gold)}

/* legend */
.am-legend{display:flex;flex-wrap:wrap;gap:1rem;margin:1rem 0 0;font-family:'Jost',sans-serif;
  font-size:0.625rem;letter-spacing:.06em;color:var(--muted)}
.am-legend span{display:inline-flex;align-items:center;gap:0.5rem}
.am-legend i{width:9px;height:9px;border-radius:50%;display:inline-block}

/* --- markers ------------------------------------------------------------- */
.am-pin{background:none;border:0}
.am-pin__dot{display:block;width:16px;height:16px;border-radius:50%;border:2px solid var(--white);
  box-shadow:0 2px 6px rgba(var(--midnight-rgb),.35);margin:6px}
.am-pin--rt .am-pin__rt{position:absolute;top:2px;right:2px;width:8px;height:8px;border-radius:50%;
  background:var(--reduced);border:1.5px solid var(--white)}
.am-lm{background:none;border:0}
.am-lm__dot{display:block;width:10px;height:10px;border-radius:2px;background:var(--gold);
  border:1.5px solid var(--white);transform:rotate(45deg);box-shadow:0 1px 4px rgba(var(--midnight-rgb),.3)}
.am-ref{background:none;border:0}
.am-ref__dot{display:block;width:10px;height:10px;border-radius:50%;background:transparent;
  border:2px dashed var(--midnight);opacity:.7}

/* --- popup card ---------------------------------------------------------- */
.am-popup .leaflet-popup-content-wrapper{border-radius:var(--radius);padding:0;overflow:hidden;
  box-shadow:0 10px 30px rgba(var(--midnight-rgb),.28)}
.am-popup .leaflet-popup-content{margin:0;width:240px!important}
.am-card__img{display:block;width:100%;height:120px;object-fit:cover}
.am-card__img--ph{background:var(--midnight)}
.am-card__body{padding:0.75rem 1rem 1rem}
.am-card__rt{display:inline-block;font-family:'Jost',sans-serif;font-size:0.625rem;letter-spacing:.14em;
  text-transform:uppercase;color:var(--reduced);border:1px solid var(--reduced);
  border-radius:var(--radius-pill);padding:0rem 0.5rem;margin-bottom:0.25rem}
.am-card__name{font-family:'Cormorant Garamond',serif;font-size:1.25rem;font-weight:600;
  color:var(--midnight);margin:0 0 0.25rem}
.am-card__loc{font-family:'Jost',sans-serif;font-size:0.625rem;letter-spacing:.1em;text-transform:uppercase;
  color:var(--muted);margin:0 0 0.5rem}
.am-card__price{font-family:'Jost',sans-serif;font-size:0.75rem;color:var(--ink);margin:0 0 0rem}
.am-card__count{font-family:'Jost',sans-serif;font-size:0.625rem;color:var(--muted);margin:0 0 0.5rem}
.am-card__link{font-family:'Jost',sans-serif;font-size:0.75rem;letter-spacing:.14em;text-transform:uppercase;
  color:var(--gold);text-decoration:none}
.am-card__link:hover{text-decoration:underline}

/* --- phones: list carries the page, map becomes a short locator band ------ */
@media (max-width:900px){
  .am-grid{grid-template-columns:1fr}
  #area-map{height:44dvh;min-height:300px}
  .am-side{max-height:none;overflow:visible}
  .am-count{margin-left:0;width:100%}
}
@media (max-width:560px){
  #area-map{height:38dvh;min-height:250px}
  .am-toolbar{gap:0.5rem}
  .am-chip{font-size:0.75rem;padding:0.5rem 0.75rem}
}

/* =================================================================
   /discover-ocala — the area story
   ================================================================= */
.dsc-wrap{max-width:var(--max);margin:0 auto;display:grid;gap:4rem}
.dsc{display:grid;grid-template-columns:1fr 1fr;gap:3rem;align-items:center}
.dsc > *{min-width:0}
.dsc:nth-child(even) .dsc__media{order:2}
.dsc__media{position:relative;border-radius:var(--radius);overflow:hidden;box-shadow:var(--shadow);aspect-ratio:4/3;
  background:var(--cream)}
.dsc__media img{width:100%;height:100%;object-fit:cover;display:block}
/* Flagged placeholder — reads as intentional, names the shot required. */
.dsc__media--ph{display:flex;flex-direction:column;align-items:center;justify-content:center;
  gap:1rem;padding:2rem;text-align:center;background:var(--midnight);
  border:1px solid var(--gold-soft);box-shadow:none}
.dsc__phtag{font-family:'Jost',sans-serif;font-size:0.625rem;letter-spacing:.2em;text-transform:uppercase;
  color:var(--gold);border:1px solid var(--gold);border-radius:var(--radius-pill);padding:0.25rem 0.75rem}
.dsc__phnote{font-family:'Jost',sans-serif;font-size:0.75rem;line-height:1.6;
  color:rgba(var(--ivory-rgb),.6);margin:0;max-width:30ch}
.dsc__title{font-family:'Cormorant Garamond',serif;font-size:2rem;font-weight:600;
  color:var(--midnight);line-height:1.15;margin:0.25rem 0 1rem}
.dsc__copy p{font-size:1rem;line-height:1.85;margin:0 0 1rem;color:var(--ink)}
.dsc__copy p:last-child{margin-bottom:0}

/* Location math grid */
.lm{max-width:820px;margin:0 auto}
.lm__grid{border-top:1px solid var(--line)}
.lm__row{display:grid;grid-template-columns:1.1fr .9fr 1.4fr;gap:1rem;align-items:baseline;
  padding:1rem 0.25rem;border-bottom:1px solid var(--line)}
.lm__place{font-family:'Cormorant Garamond',serif;font-size:1.25rem;font-weight:600;color:var(--midnight)}
.lm__time{font-family:'Jost',sans-serif;font-size:0.75rem;letter-spacing:.1em;text-transform:uppercase;
  color:var(--gold)}
.lm__note{font-family:'Jost',sans-serif;font-size:0.875rem;color:var(--muted);line-height:1.6}
.lm__kicker{font-family:'Cormorant Garamond',serif;font-size:1.5rem;font-weight:600;line-height:1.45;
  color:var(--midnight);margin:2rem 0 1rem;text-align:center}

/* Closing bridge */
.dsc-bridge{max-width:680px;margin:0 auto;text-align:center}
.dsc-bridge__title{font-family:'Cormorant Garamond',serif;font-size:2rem;font-weight:600;
  color:var(--midnight);margin:0 0 1rem}
.dsc-bridge p{font-size:1rem;line-height:1.85;color:var(--ink);margin:0 0 1.5rem}
.dsc-bridge__btns{display:flex;gap:0.75rem;justify-content:center;flex-wrap:wrap}
.dsc-bridge__agents{font-size:0.875rem;color:var(--muted);margin-top:2rem}

/* Community-page cross-link band */
.xlink__inner{max-width:var(--max);margin:0 auto;display:flex;align-items:center;
  justify-content:space-between;gap:2rem;flex-wrap:wrap}
.xlink__copy{flex:1 1 340px;min-width:0}
.xlink__title{font-family:'Cormorant Garamond',serif;font-size:1.5rem;font-weight:600;
  color:var(--midnight);margin:0.25rem 0 0.5rem}
.xlink__copy p{font-size:0.875rem;line-height:1.75;color:var(--ink);margin:0;max-width:52ch}
.xlink__btns{display:flex;gap:0.75rem;flex-wrap:wrap}

@media (max-width:900px){
  .dsc{grid-template-columns:1fr;gap:1.5rem}
  .dsc:nth-child(even) .dsc__media{order:0}
  .dsc-wrap{gap:3rem}
  .lm__row{grid-template-columns:1fr auto;row-gap:0.25rem}
  .lm__note{grid-column:1 / -1;font-size:0.75rem}
  .xlink__inner{flex-direction:column;align-items:flex-start}
}
@media (max-width:560px){
  .dsc__title{font-size:1.5rem}
  .lm__kicker{font-size:1.25rem}
  .dsc-bridge__btns .btn{width:100%}
}

/* =================================================================
   /partners — hook-first order: sticky section nav + commission band
   ================================================================= */
.pnav{position:sticky;top:var(--nav-h);z-index:var(--z-sticky-sub);background:rgba(var(--midnight-rgb),.96);backdrop-filter:blur(10px);
  border-bottom:1px solid rgba(var(--gold-soft-rgb),.22)}
.pnav__inner{max-width:var(--max);margin:0 auto;display:flex;align-items:center;gap:1.4rem;
  padding:.75rem 2rem;overflow-x:auto;-webkit-overflow-scrolling:touch;scrollbar-width:none}
.pnav__inner::-webkit-scrollbar{display:none}
.pnav a{font-family:'Jost',sans-serif;font-size:.66rem;letter-spacing:.16em;text-transform:uppercase;
  color:rgba(var(--ivory-rgb),.72);text-decoration:none;white-space:nowrap;
  transition:color var(--dur-2) var(--ease)}
.pnav a:hover{color:var(--gold-soft)}
.pnav__cta{color:var(--midnight)!important;background:var(--gold);padding:.42rem .9rem;
  border-radius:var(--radius-pill);margin-left:auto}
.pnav__cta:hover{background:var(--gold-soft);color:var(--midnight)!important}
.pnav__vcf{border:1px solid rgba(var(--gold-soft-rgb),.45);padding:.4rem .8rem;border-radius:var(--radius-pill)}

.comm__inner{max-width:900px;margin:0 auto;text-align:center}
.comm__title{font-family:'Cormorant Garamond',serif;font-size:2rem;font-weight:600;
  color:var(--midnight);margin:0.25rem 0 0.5rem;line-height:1.1}
.comm__big{font-size:clamp(2rem,4.2vw,3.75rem);color:var(--gold);display:inline-block;line-height:1}
.comm__sub{font-size:1rem;line-height:1.8;color:var(--ink);max-width:60ch;margin:0 auto 2rem}
.comm__ladder{display:flex;align-items:flex-end;justify-content:center;gap:1rem;margin:0 0 2rem}
.comm__step{flex:1 1 0;min-width:0;background:var(--white);border:1px solid var(--line);
  border-radius:var(--radius);padding:1rem 0.75rem;box-shadow:var(--shadow);
  display:flex;flex-direction:column;gap:0.25rem;justify-content:flex-end}
.comm__step--s{padding-top:1rem}
.comm__step--m{padding-top:2rem}
.comm__step--l{padding-top:3rem;border-color:var(--gold);box-shadow:var(--shadow-lift)}
.comm__step b{font-family:'Cormorant Garamond',serif;font-size:2rem;font-weight:600;color:var(--midnight)}
.comm__step--l b{color:var(--gold)}
.comm__step span{font-family:'Jost',sans-serif;font-size:0.75rem;letter-spacing:.1em;
  text-transform:uppercase;color:var(--muted)}
.comm__actions{display:flex;gap:0.75rem;justify-content:center;flex-wrap:wrap;margin-bottom:1.5rem}
.comm__terms{max-width:70ch;margin:0 auto;text-align:left}

@media (max-width:760px){
  /* was top:64px -- a stale literal, 23px behind the 87px header. */
  .pnav{top:var(--nav-h)}
  .pnav__inner{padding:.7rem 1.2rem;gap:1.1rem}
  .pnav__cta{margin-left:0}
  .comm__big{font-size:2.75rem}
  .comm__ladder{gap:0.5rem}
  .comm__step{padding:1rem 0.5rem}
  .comm__step--m,.comm__step--l{padding-top:1.5rem}
  .comm__step b{font-size:1.5rem}
  .comm__step span{font-size:0.75rem;letter-spacing:.06em}
}

/* ===== C4: estimator rate control (merged from build; see docs) ===== */
/* ============================================================
   Payment Estimator — interest-rate control (slider + typed field + promo chip)
   Merge into styles.css near the other .est__ rules (~line 883, right after
   `.est__field input[type=range]{width:100%;accent-color:var(--gold)}`).
   Nothing here overrides an existing selector except that one width:100%,
   which must become flex-shrinkable inside .est__rate-row.
   ============================================================ */

.est__field--rate{gap:0.75rem}

/* Row: slider takes the slack, the number box is intrinsically sized.
   min-width:0 on BOTH is what keeps #estimator from gaining a min-content
   wider than a 375px phone (see the .btn--full note above — same class of bug). */
.est__rate-row{display:flex;align-items:center;gap:0.75rem;min-width:0}
.est__rate-row input[type=range]{flex:1 1 0;min-width:0;width:auto;margin:0}

/* Typed rate box — reads as one control; the wrapper carries the border so the
   trailing % sits inside it. */
.est__rate-num{
  flex:0 0 auto;display:inline-flex;align-items:baseline;gap:0rem;
  padding:0.5rem 0.5rem;border:1px solid var(--line);border-radius:var(--radius);
  background:var(--ivory);
  transition:border-color var(--dur-1) var(--ease),box-shadow var(--dur-1) var(--ease);
}
/* NOTE the [type=text] — it is load-bearing. The existing
   `.est__field input[type=text]` rule (0,2,1) also matches this input and would
   otherwise win on font-size/padding/border/background and draw a second box
   inside the wrapper. Matching its specificity + coming later is what wins. */
.est__rate-num input[type=text]{
  width:3.1rem;padding:0;border:0;border-radius:0;background:transparent;
  font-family:'Cormorant Garamond',serif;font-size:1.25rem;font-weight:600;
  line-height:1.1;color:var(--midnight);text-align:right;
}
.est__rate-num input[type=text]:focus{outline:none}
.est__rate-num b{
  font-family:'Jost',sans-serif;font-size:0.75rem;font-weight:500;
  letter-spacing:.06em;color:var(--gold);
}
.est__rate-num:focus-within{
  border-color:var(--gold);
  box-shadow:0 0 0 2px rgba(var(--gold-rgb),.20);
}

/* Promo reset chip. Hidden by default in the markup; the inline #est-rate-gate
   script un-hides it before first paint only while the promo window is open,
   and estimator.js re-hides it if that gate is missing. */
.est__chip{
  justify-self:start;
  display:inline-flex;align-items:center;gap:0.5rem;
  font-family:'Jost',sans-serif;text-transform:uppercase;letter-spacing:.12em;
  font-size:0.75rem;font-weight:500;line-height:1;
  padding:0.5rem 0.75rem;
  border:1px solid var(--gold);border-radius:var(--radius-pill,999px);
  background:transparent;color:var(--gold);cursor:pointer;
  transition:background var(--dur-1) var(--ease),color var(--dur-1) var(--ease),
             border-color var(--dur-1) var(--ease);
}
.est__chip::before{
  content:"";width:5px;height:5px;border-radius:50%;
  background:currentColor;opacity:.75;flex:0 0 auto;
}
.est__chip:hover{background:var(--gold);color:var(--white,var(--white));border-color:var(--gold)}
.est__chip.is-on{background:var(--midnight);color:var(--gold-soft);border-color:var(--midnight)}
.est__chip[hidden]{display:none}

/* On phones the inputs card is only ~197px of content box. Side by side that
   left the slider 105px for a 3.00–9.00 range (~0.9px per 0.05 step, measured).
   Wrapping gives the slider the full width and parks the typed box above-right. */
@media (max-width:560px){
  .est__rate-row{flex-wrap:wrap}
  .est__rate-row input[type=range]{order:2;flex:1 1 100%}
  .est__rate-num{order:1;margin-left:auto}
}

@media (prefers-reduced-motion:reduce){
  .est__rate-num,.est__chip{transition:none}
}

/* ------------------------------------------------------------------
   OPTIONAL — pre-existing bug, NOT part of the rate-control work.
   Merge it or drop it; nothing above depends on it.

   #est-price is a bare <input type=text> with no width, so it keeps its
   default ~20-character intrinsic width: measured 242px inside a 144px
   content box at a 320px viewport. #estimator's scrollWidth is 331 vs a
   clientWidth of 320 there — same class of clipped-content bug as the
   568px .btn--full one, and it is already live. It is invisible at 375+
   (263px of content box, so 242 fits) and it reproduces with the entire
   new rate field display:none, so it predates this change.
   Scoped to the id on purpose: the obvious `.est__field input[type=text]`
   also matches #est-rate-num and, sitting later in the file at equal
   specificity, stretches the rate box to 189px. Do not "simplify" it.
   ------------------------------------------------------------------ */
#est-price{width:100%}

/* Shortlist: price-step note + the estimator bridge on result cards.
   The link is the ONLY payment-adjacent thing in the quiz — it carries a price
   to the estimator and nothing else. No rate, APR or monthly figure is rendered
   anywhere in the wizard. */
.sl__note{font-family:'Jost',sans-serif;font-size:0.75rem;letter-spacing:.06em;
  color:var(--muted-aa);margin:0.75rem 0 0}
.hsl .sl__note{color:rgba(var(--ivory-rgb),.92)}
.sl__cardEst{margin:0.25rem 0 0.25rem}
.sl__cardEst a{font-family:'Jost',sans-serif;font-size:0.75rem;letter-spacing:.06em;
  color:var(--gold);text-decoration:none;border-bottom:1px solid rgba(var(--gold-rgb),.45);
  padding-bottom:1px;transition:color var(--dur-2) var(--ease),border-color var(--dur-2) var(--ease)}
.sl__cardEst a:hover{color:var(--gold-soft);border-color:var(--gold-soft)}
.hsl .sl__cardEst a{color:var(--gold-soft);border-color:rgba(var(--gold-soft-rgb),.5)}
.sl__cardEst a:focus-visible{outline:2px solid var(--gold);outline-offset:3px}

/* =============================================================================
   /PARTNERS — RESOURCE QUICK-ACCESS BAR + MINI MAP
   Sits directly under the page hero. Deliberately NOT sticky: .pnav already
   covers mid-scroll access, and two stacked sticky bars eats a phone screen.
   Champagne small-caps triggers over midnight dropdown panels.
   NOTE: the 640px sheet breakpoint is duplicated in assets/quickaccess.hf00f06c497.js.
   ========================================================================== */
.qab{background:var(--midnight);border-bottom:1px solid rgba(var(--gold-soft-rgb),.22)}
.qab__inner{max-width:var(--max);margin:0 auto;padding:1.5rem 1.5rem 2rem;
  display:grid;grid-template-columns:1fr 280px;gap:1.5rem 2rem;align-items:start}
.qab__col{grid-column:1}
.qab__eyebrow{font-family:'Jost',sans-serif;font-size:0.625rem;letter-spacing:.24em;
  text-transform:uppercase;color:var(--gold-soft);margin:0 0 0rem}
.qab__grid{display:grid;grid-template-columns:repeat(4,1fr);gap:0.75rem}
/* Grid stretches the ITEM, but the trigger inside it was content-height, so a
   one-line label sat 14px shorter than its wrapping neighbours (measured at
   1440px: "Plan Brochures" 48px vs 62px for the other three). Stretch the
   trigger to the row height so all four share one baseline at every width. */
.qab__item{position:relative;display:flex}
.qab__trigger{display:flex;align-items:center;justify-content:space-between;gap:0.5rem;width:100%;
  height:100%;
  min-height:48px;padding:0.75rem 1rem;cursor:pointer;text-align:left;
  font-family:'Jost',sans-serif;font-size:0.75rem;font-weight:500;letter-spacing:.16em;
  text-transform:uppercase;color:var(--gold-soft);
  background:rgba(var(--ivory-rgb),.04);border:1px solid rgba(var(--gold-soft-rgb),.34);border-radius:var(--radius);
  transition:border-color var(--dur-1) var(--ease),background var(--dur-1) var(--ease),color var(--dur-1) var(--ease)}
.qab__trigger:hover,.qab__trigger:focus-visible{border-color:var(--gold);color:var(--ivory);
  background:rgba(var(--gold-soft-rgb),.12)}
.qab__caret{width:15px;height:15px;flex:0 0 15px;transition:transform var(--dur-1) var(--ease)}
.qab__trigger[aria-expanded="true"]{border-color:var(--gold);color:var(--ivory);background:rgba(var(--gold-soft-rgb),.14)}
.qab__trigger[aria-expanded="true"] .qab__caret{transform:rotate(180deg)}
.qab__panel{position:absolute;z-index:var(--z-dropdown);top:calc(100% + .45rem);left:0;min-width:100%;width:max-content;max-width:min(92vw,520px);
  max-height:min(58dvh,420px);overflow-y:auto;-webkit-overflow-scrolling:touch;
  background:linear-gradient(168deg,var(--midnight),var(--midnight-2));border:1px solid rgba(var(--gold-soft-rgb),.34);
  border-radius:var(--radius);padding:0.75rem;box-shadow:0 22px 46px -20px rgba(var(--black-rgb),.75);
  animation:qabIn 170ms var(--ease)}
@keyframes qabIn{from{opacity:0;transform:translateY(-6px)}to{opacity:1;transform:none}}
.qab__sheetBar{display:flex;align-items:baseline;justify-content:space-between;gap:1rem;
  padding:0rem 0.25rem 0.5rem}
.qab__sheetTitle{font-family:'Jost',sans-serif;font-size:0.75rem;letter-spacing:.2em;text-transform:uppercase;
  color:var(--gold-soft);margin:0}
.qab__sheetClose{display:none}
.qab__list{list-style:none;margin:0;padding:0}
.qab__list--cols{columns:2;column-gap:1rem;min-width:23rem}
/* The two right-hand tiles open leftwards; a 520px panel hung off the
   left edge of the 4th column would run past the viewport. */
.qab__item:nth-child(3) .qab__panel,
.qab__item:nth-child(4) .qab__panel{left:auto;right:0}
.qab__list--cols li{break-inside:avoid}
.qab__link{display:block;width:100%;text-align:left;background:none;border:0;border-radius:4px;
  padding:0.5rem 0.5rem;cursor:pointer;font-family:'Jost',sans-serif;font-size:0.875rem;
  color:rgba(var(--ivory-rgb),.86);text-decoration:none;
  transition:background var(--dur-1) var(--ease),color var(--dur-1) var(--ease)}
.qab__link:hover,.qab__link:focus-visible{background:rgba(var(--gold-soft-rgb),.16);color:var(--ivory)}
.qab__link--zip,.qab__link--anchor{margin-top:0.25rem;color:var(--gold-soft);
  border-top:1px solid rgba(var(--gold-soft-rgb),.24);border-radius:0;padding-top:0.5rem}
/* Community Inventory rows — real links (View / PDF / desktop XLSX), 2026-08-09.
   One row per community; the action targets are 38px+ tall for touch. */
.qab__list--inv{min-width:26rem}
.qab__inv{display:flex;align-items:center;gap:0.5rem;padding:0.3rem 0.25rem;
  border-bottom:1px solid rgba(var(--gold-soft-rgb),.12)}
.qab__invName{flex:1;font-family:'Jost',sans-serif;font-size:0.875rem;
  color:rgba(var(--ivory-rgb),.86)}
.qab__invActs{display:flex;gap:0.375rem}
.qab__invA{display:inline-block;padding:0.4rem 0.65rem;min-height:2.125rem;border-radius:4px;
  font-family:'Jost',sans-serif;font-size:0.75rem;letter-spacing:.06em;text-transform:uppercase;
  color:var(--gold-soft);border:1px solid rgba(var(--gold-soft-rgb),.4);text-decoration:none;
  transition:background var(--dur-1) var(--ease),color var(--dur-1) var(--ease)}
.qab__invA:hover,.qab__invA:focus-visible{background:rgba(var(--gold-soft-rgb),.16);color:var(--ivory)}
.qab__invA--view{background:rgba(var(--gold-soft-rgb),.16);color:var(--ivory)}
@media (max-width:640px){.qab__xlsx{display:none}}
/* Schedule-form inline confirmation (2026-08-10) — replaces the form in place;
   a buyer never navigates away, and never sees a JSON body. */
.al-thanks{border:1px solid rgba(var(--gold-soft-rgb),.45);border-radius:var(--radius);
  padding:1.5rem;margin:0 0 1rem;background:rgba(var(--gold-soft-rgb),.07)}
.al-thanks__msg{font-size:1.05rem;margin:0 0 0.75rem}
.al-thanks__again{background:none;border:0;padding:0;cursor:pointer;
  font-family:'Jost',sans-serif;font-size:0.8125rem;letter-spacing:.06em;
  text-transform:uppercase;color:var(--gold-soft);text-decoration:underline}
.al-thanks__err{border-left:3px solid var(--gold-soft);padding:0.5rem 0.75rem;
  margin:0 0 1rem;font-size:0.9375rem}
.qab__none{margin:0.25rem 0.5rem;font-size:0.875rem;color:rgba(var(--ivory-rgb),.7)}
/* mini map — static SVG, links through to the full Leaflet map */
.qab__map{grid-column:2;display:block;position:relative;border-radius:var(--radius);
  overflow:hidden;border:1px solid rgba(var(--gold-soft-rgb),.45);text-decoration:none;
  transition:border-color var(--dur-2) var(--ease),transform var(--dur-2) var(--ease)}
.qab__map:hover{border-color:var(--gold);transform:translateY(-2px)}
.qab__map img{display:block;width:100%;height:auto;aspect-ratio:4/3;object-fit:cover}
.qab__mapLabel{position:absolute;left:0;right:0;top:0;padding:0.75rem 1rem 1rem;
  font-family:'Jost',sans-serif;font-size:0.75rem;letter-spacing:.16em;text-transform:uppercase;
  color:var(--ivory);background:linear-gradient(to bottom,rgba(var(--midnight-rgb),.92),rgba(var(--midnight-rgb),0))}
.qab__scrim{display:none}
@media (max-width:900px){
  .qab__inner{grid-template-columns:1fr}
  .qab__map{grid-column:1}
}
@media (max-width:640px){
  /* 2x2 tappable grid; each tile opens a bottom sheet with the page behind it. */
  .qab__inner{padding:1rem 1rem 1.5rem;gap:1rem}
  .qab__grid{grid-template-columns:1fr 1fr;gap:0.5rem}
  .qab__trigger{min-height:60px;font-size:0.75rem;letter-spacing:.12em;padding:0.75rem 0.75rem}
  /* width:auto is load-bearing: the desktop panel is width:max-content, which
     beats left:0/right:0 and pushed the sheet (and its Done button) off the
     right edge of a 375px screen. The two-column min-width goes with it. */
  .qab__panel{position:fixed;z-index:var(--z-dropdown);left:0;right:0;bottom:0;top:auto;
    width:auto;min-width:0;max-width:none;max-height:72dvh;border-radius:14px 14px 0 0;border-bottom:0;
    padding:1rem 1rem calc(1rem + env(safe-area-inset-bottom));
    box-shadow:0 -12px 38px -12px rgba(var(--black-rgb),.8);animation:qabSheetIn 200ms var(--ease)}
  @keyframes qabSheetIn{from{transform:translateY(101%)}to{transform:none}}
  .qab__sheetBar{padding:0rem 0 0.75rem;border-bottom:1px solid rgba(var(--gold-soft-rgb),.2);margin-bottom:0.5rem}
  .qab__sheetClose{display:inline-block;background:none;border:0;padding:0.25rem;cursor:pointer;
    font-family:'Jost',sans-serif;font-size:0.75rem;letter-spacing:.14em;text-transform:uppercase;color:var(--gold)}
  .qab__scrim{display:block;position:fixed;inset:0;z-index:calc(var(--z-dropdown) - 1);background:rgba(var(--midnight-rgb),.5)}
  body.qab-sheet{overflow:hidden}
  .qab__list--cols{columns:1;min-width:0}
  .qab__list--inv{min-width:0}
  .qab__inv{padding:0.45rem 0.25rem}
  .qab__link{padding:0.75rem 0.5rem;font-size:0.875rem}
}
@media (prefers-reduced-motion:reduce){
  .qab__panel{animation:none}
}

/* Long-label buttons (e.g. "Download All Inventory Sheets (ZIP)") can't fit
   one line at .75rem on a 375 screen once .14-.2em tracking is added — the
   tracking alone was ~79px. Tighten tracking+padding under 400px so they stay
   one line at the AA-floor size, no wrap, no overflow. */
@media(max-width:400px){
  .btn{letter-spacing:.05em;padding-left:1rem;padding-right:1rem}
}

/* NOTE ON PLACEMENT: this block lives at the END of the file deliberately.
   It was first inserted near the top, where later component rules of equal
   specificity (.flt__select's 12px font, .hcar__dot's padding:0) silently
   overrode it — the fixes were present but inert. An override layer has to
   come last. Do not move it back up. */
/* ===========================================================================
   iOS SAFARI HARDENING
   Added in the iOS polish pass. Every rule here fixes a WebKit-specific
   behaviour that Blink does not share, so none of it is visible on desktop
   Chrome — which is exactly why these went unnoticed.
   =========================================================================== */

/* --- 1. INPUT AUTO-ZOOM ---------------------------------------------------
   Mobile Safari zooms the whole page when a focused form control's font-size is
   under 16px, and does NOT zoom back out on blur. That is the "it opens weird
   and stays zoomed" report. There is no global input font rule in this
   stylesheet, so any control a component does not size explicitly inherits the
   UA default (~13.3px) and triggers it.
   16px is the floor, applied at mobile widths only so the desktop type scale is
   untouched. Sizes ABOVE 16px are left alone (the estimator's 24px price field
   is deliberate). */
@media (max-width:767px){
  input,select,textarea,
  .flt__select,.fp-controls select,.pc-paste-row input,
  .est__pills button,.est__rate-num input[type=text]{
    font-size:16px;
  }
  /* Checkboxes have no text, so the 16px floor is irrelevant to zoom — but a
     17px hit area is under the 44px minimum. Grow the touch target without
     moving the box: padding on the label row does the work. */
  .consent,.sl__consent,.visit__consent{min-height:44px}
  .consent input,.sl__consent input,.visit__consent input{width:22px;height:22px}
  /* Same touch-target floor in the chat panel. NOT folded into the rule above:
     that one sets min-height on the whole label row, and the chat's row lives
     in a fixed-height panel where the composer must stay on screen with the
     keyboard up — so the box grows and the row does not. */
  .tgchat-consent input{width:22px;height:22px}
}

/* --- 2. SAFE AREAS ------------------------------------------------------
   iPhones with a home indicator put ~34px of non-tappable screen below the
   fold. A fixed bottom bar without safe-area padding sits underneath it: the
   bar is visible but its bottom half cannot be tapped. Two sheets already had
   this; every other fixed element did not. */
.mobile-callbar{padding-bottom:env(safe-area-inset-bottom)}
@media (max-width:900px){
  .pbar{padding-bottom:env(safe-area-inset-bottom)}
  /* NOTE: the launcher offset is NOT set here. An earlier version of this block
     set `bottom:calc(1.5rem + env(safe-area-inset-bottom))`, which — because this
     block is the override layer at the end of the file — silently replaced the
     existing `calc(var(--callbar-h) + 12px)` and dropped the launcher from 68px
     to ~58px, on top of a sticky bar that this same block had just grown to
     ~90px. The launcher's clearance is now computed in one place; see
     "STICKY-BAR CLEARANCE" at the end of this file. */
  .mnav{padding-bottom:env(safe-area-inset-bottom);
        padding-left:env(safe-area-inset-left);padding-right:env(safe-area-inset-right)}
  .visit__panel{padding-bottom:calc(1.5rem + env(safe-area-inset-bottom))}
  .sl{padding-bottom:env(safe-area-inset-bottom)}
}

/* --- 3. SCROLL CHAINING -------------------------------------------------
   Without overscroll-behavior:contain, a swipe that reaches the end of a
   modal's scroll area continues into the page behind it — the background moves
   under an open overlay. The JS lock (assets/ios-scrolllock.h7462cf2770.js) stops the body
   scrolling at all; this stops the chain reaching it in the first place, which
   also kills Safari's rubber-band bounce inside the sheet. */
.visit,.visit__panel,.sl,.bv,.mnav,.lightbox,.qab__panel{overscroll-behavior:contain}

/* --- 4. TAP TARGETS ----------------------------------------------------
   Apple's minimum is 44x44pt. The carousel dots were 7px — technically
   clickable, realistically not. The dot stays 7px VISUALLY; the button around
   it grows via padding, so the row looks identical and the target is 44px.
   background-clip:content-box keeps the added padding transparent. */
/* Gated on WIDTH, not (hover:none). Both describe roughly the same population
   here, but width is what the rest of this stylesheet keys on and what can
   actually be verified in a headless harness — a hover-gated rule silently did
   nothing when tested, which is how the 7px dots survived the first pass. */
@media (max-width:767px){
  /* CAROUSEL DOTS — the button is the TAP TARGET, a pseudo-element is the DOT.
     The previous attempt kept the 7px box and grew it with padding +
     background-clip:content-box. That failed on device: `.hcar__dot.is-on`
     sets `background:` as a SHORTHAND, which resets background-clip to
     border-box, and at (0,2,0) it outranks this (0,1,0) rule — so the active
     dot painted its full 45px box gold and scale(1.25) took it to 56px. That
     is the "huge gold circle" over the image.
     Separating the concerns makes it unbreakable: the <button> is a 44px
     transparent hit area with NO background of its own, and ::after draws the
     8px dot. A shorthand on the button can no longer repaint the dot. */
  .hcar__dot{width:44px;height:44px;padding:0;box-sizing:border-box;
             background:none !important;border-radius:0;transform:none !important;
             display:grid;place-items:center}
  .hcar__dot::after{content:"";display:block;width:8px;height:8px;border-radius:50%;
             background:rgba(var(--ivory-rgb),.62);
             transition:background var(--dur-2) var(--ease),transform var(--dur-2) var(--ease)}
  .hcar__dot.is-on::after{background:var(--gold);transform:scale(1.25)}
  .hcar__dots{gap:0;bottom:0;right:0}
  /* ARROWS ARE DESKTOP-ONLY. (hover:none) alone did not gate them — it computed
     to display:block at 390px — so gate on width too, which is deterministic
     and testable. Touch users swipe. */
  .hcar__arw{display:none !important}
  .sl__stepBtn{width:44px;height:44px;flex:0 0 44px}
  .sl__dot{height:4px}
  .qab__sheetClose,.mnav__close,.bv__close{min-width:44px;min-height:44px}
}

/* ===========================================================================
   /partners buyer-style search  +  homepage realtor band
   =========================================================================== */

/* --- mode toggle. Two equal controls; neither is a "more" affordance. ------ */
.pmode{display:flex;gap:.5rem;justify-content:center;margin:0 0 2rem;flex-wrap:wrap}
.pmode__btn{font-family:'Jost',sans-serif;font-size:.8rem;letter-spacing:.12em;
  text-transform:uppercase;padding:.85rem 1.4rem;min-height:44px;cursor:pointer;
  background:transparent;color:var(--ink);border:1px solid var(--line);border-radius:2px;
  transition:background var(--dur-1) var(--ease),color var(--dur-1) var(--ease),
             border-color var(--dur-1) var(--ease)}
.pmode__btn:hover{border-color:var(--gold)}
.pmode__btn.is-on{background:var(--midnight);color:var(--ivory);border-color:var(--midnight)}
.pmode__note{text-align:center;max-width:620px;margin:0 auto 1.5rem}

/* --- community strip: name / from / count, into the BUYER community page --- */
.cstrip{margin-top:3rem;padding-top:2rem;border-top:1px solid var(--line)}
.cstrip__lead{text-align:center;color:var(--muted);font-size:.92rem;margin-bottom:1.25rem}
.cstrip__row{display:grid;grid-template-columns:repeat(auto-fill,minmax(230px,1fr));gap:.6rem}
.cstrip__item{display:grid;grid-template-columns:1fr auto;gap:.1rem .75rem;align-items:baseline;
  padding:.85rem 1rem;min-height:44px;text-decoration:none;color:inherit;
  border:1px solid var(--line);border-radius:2px;background:var(--ivory);
  transition:border-color var(--dur-1) var(--ease),transform var(--dur-1) var(--ease)}
.cstrip__item:hover{border-color:var(--gold);transform:translateY(-1px)}
.cstrip__name{font-weight:500;letter-spacing:.01em}
.cstrip__from{color:var(--gold);font-size:.85rem;white-space:nowrap}
.cstrip__n{grid-column:1/-1;color:var(--muted);font-size:.8rem;letter-spacing:.06em;
  text-transform:uppercase}

/* --- homepage realtor band ------------------------------------------------
   Full-bleed midnight, centred, one CTA. Deliberately NOT a banner: same type
   scale and champagne rule as every other section head, so it reads as part of
   the page rather than an ad dropped into it. */
.rband{max-width:none;background:var(--midnight);color:var(--ivory);
  padding-left:0;padding-right:0;text-align:center}
.rband__inner{max-width:var(--max);margin:0 auto;padding:0 2rem;
  display:flex;flex-direction:column;align-items:center}
.rband__eyebrow{color:var(--gold-soft)}
.rband__rule{display:block;width:54px;height:1px;background:var(--gold);margin:1.25rem auto 1.5rem}
.rband__title{font-size:clamp(1.9rem,4vw,2.7rem);font-weight:500;color:var(--ivory);
  max-width:760px;line-height:1.14}
.rband__value{margin-top:1.1rem;color:rgba(var(--ivory-rgb),.86);font-weight:300;
  font-size:1.02rem;letter-spacing:.02em;max-width:620px}
.rband__cta{margin-top:2rem}



@media (max-width:767px){
  /* 375px: the hero link drops BELOW the CTA rather than beside it, so it can
     never crowd the primary button or push it off-line. */
  .rband__inner{padding:0 1.5rem}
  .cstrip__row{grid-template-columns:1fr}
  .pmode{gap:.4rem}
  .pmode__btn{flex:1 1 auto;padding:.85rem .9rem;font-size:.72rem}
}

/* ===========================================================================
   MARKET LANDING PAGES (/new-homes-*)  +  Discover communities-by-area
   =========================================================================== */

/* Live inventory stat row. Numbers come from the workbook at build, so this is
   the one element on the page that must never look like marketing garnish. */
.mstat{padding-top:2.5rem;padding-bottom:2.5rem}
.mstat__row{display:grid;grid-template-columns:repeat(4,1fr);gap:1px;
  background:rgba(var(--midnight-rgb),.10);border:1px solid rgba(var(--midnight-rgb),.10)}
.mstat__i{background:var(--ivory);padding:1.4rem 1rem;text-align:center}
.mstat__i b{display:block;font-family:'Cormorant Garamond',serif;font-size:clamp(1.8rem,3.4vw,2.5rem);
  font-weight:600;line-height:1;color:var(--midnight)}
.mstat__i span{display:block;margin-top:.45rem;color:var(--muted);font-size:.78rem;
  letter-spacing:.10em;text-transform:uppercase}
.mstat__note{margin-top:1rem;text-align:center;color:var(--muted);font-size:.88rem}

/* FAQ. <details> so it works with no JS and each answer is crawlable text. */
.mfaq{max-width:820px;margin:0 auto}
.mfaq__item{border-bottom:1px solid var(--line);padding:1.1rem 0}
.mfaq__item summary{cursor:pointer;font-weight:500;letter-spacing:.01em;
  list-style:none;display:flex;justify-content:space-between;gap:1rem;min-height:32px;
  align-items:center}
.mfaq__item summary::-webkit-details-marker{display:none}
.mfaq__item summary::after{content:"+";color:var(--gold);font-size:1.3rem;line-height:1;flex:none}
.mfaq__item[open] summary::after{content:"\2013"}
.mfaq__item p{margin-top:.85rem;color:var(--ink);font-weight:300;line-height:1.6}

/* Discover: communities grouped by area */
.dcom__area{margin-bottom:2.5rem}
.dcom__h{font-family:'Jost',sans-serif;font-size:.8rem;letter-spacing:.18em;
  text-transform:uppercase;color:var(--gold);margin-bottom:.9rem}

/* Home detail up-links — the link mesh, styled as a quiet trailing line. */
.hd__up{margin-top:1.6rem;text-align:center;color:var(--muted);font-size:.9rem;
  line-height:1.9}
.hd__up a{color:var(--midnight);border-bottom:1px solid rgba(var(--gold-rgb),.5)}
.hd__up a:hover{color:var(--gold)}

@media (max-width:767px){
  .mstat__row{grid-template-columns:repeat(2,1fr)}
  .mstat__i{padding:1.1rem .75rem}
}

/* Community xlink button row: allow wrapping. A third button landed here (the
   market landing page) and "New homes in the Nature Coast" is wider than a
   375px viewport allows on one line with its siblings. The short labels fix the
   width; this makes the row resilient to the next label that grows. */
@media (max-width:767px){
  .xlink__btns{flex-wrap:wrap}
  .xlink__btns .btn{white-space:normal;text-align:center}
}

/* iOS SLIDE BLEED.
   .hcar__track moves with translate3d. WebKit lets a 3D-transformed child paint
   outside a parent that clips with overflow:hidden + border-radius, so the next
   slide shows along the right edge mid-swipe. Chrome does not reproduce it, so
   this cannot be verified here — it is the standard WebKit mitigation: a mask
   forces a real clip, and isolation keeps the transformed track in the parent's
   own stacking context. Harmless everywhere else. */
.hcar{-webkit-mask-image:-webkit-radial-gradient(white,black);
      mask-image:radial-gradient(white,black);
      isolation:isolate;overflow:hidden}
.hcar__track{will-change:transform;backface-visibility:hidden}
/* Slides must be exactly the container width — no flex-basis rounding drift. */
.hcar__slide{flex:0 0 100%;width:100%;max-width:100%;min-width:100%}

/* ===========================================================================
   NAV "REALTOR PARTNERS" PILL — the agent door.
   Replaces the hero text link.

   THE SHIMMER IS A BACKGROUND, NOT AN ELEMENT.
   First attempt used an absolutely-positioned ::after sweeping from left:-60%
   to left:120%, clipped with overflow:hidden. That failed the header gate on 80
   of 192 route/width combinations: a child parked past the right edge inflates
   the pill's scrollWidth, and the gate reads scrollWidth > clientWidth as
   "label truncated". An animated background-position moves no box at all, so
   the pill's geometry is identical at every frame.

   THE PULSE IS A GLOW, NOT A SCALE.
   The brief asked for "soft glow scale 1.0 -> 1.03 -> 1.0". A transform is
   excluded from layout but NOT from getBoundingClientRect, so a 1.03 scale on a
   ~150px pill grows it ~4.5px and eats the header's 16px minimum gaps — the
   same gate, a different rule. A box-shadow bloom reads as the intended "gold
   catching light" and cannot move a neighbour by a single pixel.

   One sweep per 6s (~1s of travel, 5s still). One arrival bloom, never repeated.
   prefers-reduced-motion gets a plain static gold pill.
   =========================================================================== */
.nav__partner{
  background-image:linear-gradient(100deg,
    rgba(255,250,240,0) 38%,
    rgba(255,250,240,.30) 50%,
    rgba(255,250,240,0) 62%);
  background-repeat:no-repeat;
  background-size:180% 100%;
  background-position:-140% 0;
  animation:pillShimmer 6s ease-in-out 1.6s infinite,
            pillArrive 1200ms var(--ease) 400ms 1 both;
}
@keyframes pillShimmer{
  0%   {background-position:-140% 0}
  18%  {background-position:240% 0}   /* ~1.1s of travel … */
  100% {background-position:240% 0}   /* … then it waits out the cycle */
}
@keyframes pillArrive{
  0%   {box-shadow:0 0 0 0 rgba(var(--gold-soft-rgb),0)}
  40%  {box-shadow:0 0 18px 3px rgba(var(--gold-soft-rgb),.45)}
  100% {box-shadow:0 0 0 0 rgba(var(--gold-soft-rgb),0)}
}
.nav__partner:hover{animation:none}
@media (prefers-reduced-motion:reduce){
  .nav__partner,.nav__partner::before{animation:none!important}
  .nav__partner{background-image:none}
}

/* ===========================================================================
   STICKY-BAR CLEARANCE — the chat launcher must never sit on a sticky bar.
   ONE place computes this, because it has now been got wrong twice: first
   against the Call/Text bar, then against /partners' REGISTER/PROGRAMS/
   INVENTORY quick-nav (whose gold "Text" cell is the "stray gold rectangle"
   showing under the launcher — it is not stray, it is being covered).

   clearance = bar height + safe-area inset + 12px

   The safe-area term is essential: both bars carry
   padding-bottom:env(safe-area-inset-bottom), so on a notched iPhone a 56px bar
   occupies ~90px and a launcher offset that ignores the inset lands on top of it.
   z-index stays on the token scale (--z-chat-launcher) — no ad-hoc values.
   =========================================================================== */
:root{ --pbar-h:56px; }   /* .pbar__btn min-height — keep in sync */

@media (max-width:640px){
  /* default: pages with the Call/Text bar */
  .tgchat-launch{
    bottom:calc(var(--callbar-h) + env(safe-area-inset-bottom) + 12px);
    z-index:var(--z-chat-launcher);
  }
  .tgchat-panel{
    bottom:calc(var(--callbar-h) + env(safe-area-inset-bottom) + 12px);
    max-height:calc(100dvh - var(--callbar-h) - env(safe-area-inset-bottom) - 2.5rem);
  }
  /* /partners hides the Call/Text bar and shows .pbar instead */
  body.partners .tgchat-launch{
    bottom:calc(var(--pbar-h) + env(safe-area-inset-bottom) + 12px);
  }
  body.partners .tgchat-panel{
    bottom:calc(var(--pbar-h) + env(safe-area-inset-bottom) + 12px);
    max-height:calc(100dvh - var(--pbar-h) - env(safe-area-inset-bottom) - 2.5rem);
  }
  /* THE TEASER RIDES THE SAME SAFE-AREA MATH AS THE LAUNCHER. These are the
     authoritative bottom values (the earlier 640px block does not know about
     the inset), so the teaser is re-stated here rather than left to inherit a
     value that stops short of the home indicator. */
  .tgchat-teaser{
    bottom:calc(var(--callbar-h) + env(safe-area-inset-bottom) + 12px + 58px);
  }
  body.partners .tgchat-teaser{
    bottom:calc(var(--pbar-h) + env(safe-area-inset-bottom) + 12px + 58px);
  }
}
/* Between 641 and 900px neither bar is shown, so only the inset matters. */
@media (min-width:641px) and (max-width:900px){
  .tgchat-launch{bottom:calc(1.5rem + env(safe-area-inset-bottom))}
  .tgchat-teaser{bottom:calc(1.5rem + env(safe-area-inset-bottom) + 58px)}
}

/* =============================================================================
   FIND YOUR HOME — payment door (/find-your-home.html)
   Dark behind PAYMENT_SEARCH_ENABLED; the CSS ships either way, which costs
   nothing and means flipping the flag is a one-line change with no restyling.

   NEITHER DOOR IS THE FAVOURED ONE. The two tabs share a width, a weight, a
   type size, a border and a state treatment. There is no "recommended" chip,
   no accent on one, no ordering signal beyond left-to-right. If you are about
   to make one tab heavier, that is a compliance decision, not a design one.

   Every z-index here comes from the --z-* scale (audit_stacking.py enforces it).
   ========================================================================== */
.fyh__tabs{display:flex;gap:0;margin:0 0 2rem;border-bottom:1px solid var(--line)}
.fyh__tab{flex:1 1 0;min-width:0;font-family:'Jost',sans-serif;font-weight:500;
  text-transform:uppercase;letter-spacing:.16em;font-size:0.8125rem;
  padding:1.125rem 1rem;cursor:pointer;color:var(--muted-aa);background:transparent;
  border:0;border-bottom:2px solid transparent;margin-bottom:-1px;
  transition:color var(--dur-2) var(--ease),border-color var(--dur-2) var(--ease)}
.fyh__tab:hover{color:var(--midnight)}
.fyh__tab.is-on{color:var(--midnight);border-bottom-color:var(--gold)}

/* The not-a-lender line. Persistent and legible — never fine print. */
.fyh__standing{max-width:70ch;margin:0 0 2rem;font-size:0.875rem;line-height:1.7;
  color:var(--ink);border-left:2px solid var(--gold);padding-left:1rem}

/* ---- PAYMENT RANGE — two handles over one track --------------------------
   Two stacked native range inputs. The track and fill are painted by the
   wrapper; the inputs themselves are transparent and sit on top so each handle
   stays a real, focusable, keyboard-steppable control. pointer-events are off
   on the input bodies and back ON for the thumbs, so a click between the
   handles does not jump whichever input happens to be on top. */
.fyh__range{background:var(--white);border:1px solid var(--line);
  border-radius:var(--radius);padding:1.4rem 1.5rem;margin:0 0 1.5rem}
.fyh__slider{position:relative;height:2.4rem;margin:.9rem 0 .2rem}
.fyh__track{position:absolute;top:50%;left:0;right:0;height:4px;margin-top:-2px;
  background:var(--line);border-radius:2px}
.fyh__track-fill{position:absolute;top:0;bottom:0;background:var(--gold);border-radius:2px}
.fyh__slider input[type=range]{position:absolute;top:0;left:0;width:100%;height:100%;
  margin:0;background:none;-webkit-appearance:none;appearance:none;pointer-events:none}
.fyh__slider input[type=range]:focus-visible{outline:2px solid var(--gold);outline-offset:2px}
.fyh__slider input[type=range]::-webkit-slider-thumb{-webkit-appearance:none;appearance:none;
  pointer-events:auto;width:22px;height:22px;border-radius:50%;background:var(--white);
  border:2px solid var(--gold);box-shadow:0 1px 3px rgba(0,0,0,.2);cursor:pointer}
.fyh__slider input[type=range]::-moz-range-thumb{pointer-events:auto;width:22px;height:22px;
  border-radius:50%;background:var(--white);border:2px solid var(--gold);
  box-shadow:0 1px 3px rgba(0,0,0,.2);cursor:pointer}
.fyh__slider input[type=range]::-moz-range-track{background:none;border:none}
/* TYPED MIN/MAX beside the payment slider (2026-08-21).
   44px minimum target on both fields — these are the controls a buyer uses on
   a phone, which is the whole reason they exist. The row wraps rather than
   scrolls at 390: a horizontally-scrolling filter row hides its own second
   half, and half of a two-ended range is not a range. */
.fyh__payfields{display:flex;align-items:flex-end;gap:0.5rem;flex-wrap:wrap;margin:.75rem 0 0}
.fyh__payfield{display:block;font-family:'Jost',sans-serif;font-size:0.75rem;letter-spacing:.06em;
  text-transform:uppercase;color:var(--muted-aa)}
.fyh__payfield>span{display:block;margin-bottom:0.25rem}
.fyh__payfield input{width:7rem;min-height:44px;border:1px solid var(--line);border-radius:var(--radius);
  background:var(--white);padding:0 0.75rem;font-family:'Jost',sans-serif;font-size:1rem;
  letter-spacing:.02em;color:var(--ink);text-transform:none}
.fyh__payfield input:focus{outline:2px solid var(--gold);outline-offset:1px}
.fyh__payfield-sep{align-self:center;padding-bottom:0.6rem;color:var(--muted)}
/* A NOTE, NOT A WARNING. No red, no icon, no border — the page absorbed
   something and is saying what it did. Styling it as an error would make an
   ordinary typed number feel like a mistake the visitor made. */
.fyh__paynote{margin:.4rem 0 0;font-family:'Jost',sans-serif;font-size:.8125rem;
  line-height:1.6;color:var(--muted-aa)}
.fyh__count{margin:.4rem 0 0;font-family:'Jost',sans-serif;font-size:.95rem;color:var(--ink)}
.fyh__nearest{margin:.6rem 0 0;font-size:.9rem;color:var(--muted-aa);line-height:1.7}
.fyh__nearest-btn{font-family:'Jost',sans-serif;font-size:.85rem;font-weight:500;
  color:var(--ink);background:var(--white);border:1px solid var(--gold);
  border-radius:var(--radius);padding:.42rem .85rem;cursor:pointer;
  transition:background var(--dur-2) var(--ease)}
.fyh__nearest-btn:hover{background:var(--gold);color:var(--white)}

/* ---- REG Z DISCLOSURE ----------------------------------------------------
   Adjacent to the results, never collapsed, never a footer. A stated monthly
   payment is a triggering term, so this block is part of the payment, not a
   note about it. Do not add display:none, <details>, or a "show more". */
/* The wrapper reg_z_block() emits. Inherits .section's max-width and its
   symmetric 2rem horizontal gutter — that gutter is the whole point, since
   without it the block's own padding overran the content box. Vertical padding
   is dropped so the disclosure sits tight under the payment it qualifies. */
.regz-sec{padding-top:0;padding-bottom:0}
.regz{margin:0 0 1.5rem;padding:1rem 1.15rem;border:1px solid var(--line);
  border-left:3px solid var(--gold);border-radius:var(--radius);background:var(--cream)}
.regz__terms{margin:0;font-family:'Jost',sans-serif;font-size:.85rem;line-height:1.7;color:var(--ink)}
.regz__terms:empty{display:none}
.regz__standing{margin:.5rem 0 0;font-size:.8rem;line-height:1.7;color:var(--muted-aa)}
.regz__eho{margin-left:.35rem}
/* THE NOTE RATE AND THE APR — EQUAL, AND PLAIN.
   1026.24(c) permits a dwelling-secured ad to state a simple annual rate
   beside the APR only "in conjunction with, but not more conspicuously than"
   the annual percentage rate. EQUAL is what the rule asks for; BOLD was never
   part of it. These figures shipped bold on 2026-08-02 and were unbolded the
   same day so the card has one focal point — the payment — rather than three
   competing ones.
   What has not changed, and is the part that actually enforces the rule: both
   figures share ONE class. Everything is inherit, so there is no declaration
   here that can raise the rate above the APR without raising the APR with it.
   Rate louder than APR is not a styling mistake this file can express.
   font-size and color inherit deliberately — this rule is used inside
   .regz__terms, .pay__disc, .home__payterms, .sl__cardTerms and the dark .hsl
   variants, which set different sizes and colours, and the figures must track
   whatever their own container uses. white-space guards the token: "APR" split
   from its number is a different claim (build/audit_token_integrity.py). */
.regz__eq{font-weight:inherit;font-size:inherit;color:inherit;white-space:nowrap}

.fyh__gate{display:grid;gap:1.5rem;margin:0 0 1.5rem}
@media (min-width:900px){.fyh__gate{grid-template-columns:repeat(3,1fr);gap:2rem}}
.fyh__step{background:var(--white);border:1px solid var(--line);border-radius:var(--radius);
  padding:1.5rem;transition:opacity var(--dur-2) var(--ease)}
/* A locked step is dimmed but never hidden: the shape of the gate should be
   visible from the first paint so nobody wonders what else is coming. */
.fyh__step.is-locked{opacity:.45}
.fyh__step.is-locked button,.fyh__step.is-locked input{pointer-events:none}
.fyh__stepn{font-family:'Jost',sans-serif;font-size:0.6875rem;text-transform:uppercase;
  letter-spacing:.18em;color:var(--gold);margin:0 0 0.5rem}
.fyh__steph{font-family:'Cormorant Garamond',serif;font-size:1.375rem;font-weight:600;
  color:var(--midnight);margin:0 0 0.75rem}
.fyh__steph--sub{font-size:1.125rem;margin-top:1.25rem}
.fyh__steph b{font-style:normal;font-variant-numeric:tabular-nums;color:var(--gold)}
.fyh__pills{display:flex;flex-wrap:wrap;gap:0.5rem}
.fyh__pill{display:inline-flex;align-items:center;justify-content:center;
  font-family:'Jost',sans-serif;font-size:0.8125rem;letter-spacing:.04em;
  min-height:44px;padding:0.625rem 1rem;cursor:pointer;color:var(--ink);
  background:transparent;border:1px solid var(--line);border-radius:var(--radius);
  transition:background var(--dur-1) var(--ease),color var(--dur-1) var(--ease),border-color var(--dur-1) var(--ease)}
.fyh__pill:hover{border-color:var(--gold)}
.fyh__pill.is-on{background:var(--midnight);color:var(--ivory);border-color:var(--midnight)}
/* FHA and Conventional are the same size on purpose — see the header comment. */
.fyh__pill--wide{flex:1 1 0;min-width:0}
.fyh__aside{margin:1rem 0 0;font-size:0.8125rem;line-height:1.7;color:var(--muted-aa)}
.fyh__aside a{color:var(--ink);border-bottom:1px solid var(--gold)}
.fyh__field{display:block;margin:1rem 0 0;font-size:0.8125rem;color:var(--muted-aa)}
.fyh__field>span{display:block;margin-bottom:0.375rem}
.fyh__numwrap{display:inline-flex;align-items:center;gap:0.25rem;border:1px solid var(--line);
  border-radius:var(--radius);padding:0 0.75rem;background:var(--white)}
.fyh__numwrap input{width:4.5rem;min-height:44px;border:0;background:transparent;
  font-family:'Jost',sans-serif;font-size:1rem;color:var(--midnight);
  font-variant-numeric:tabular-nums}
.fyh__numwrap input:focus{outline:none}
.fyh__numwrap b{font-weight:400;color:var(--muted-aa)}
.fyh__hint{margin:0.75rem 0 0;font-size:0.8125rem;line-height:1.6;color:var(--muted-aa)}
.fyh__msg{margin:0.5rem 0 0;font-size:0.8125rem;color:var(--midnight);min-height:1.2em}
.fyh__rate-row{display:flex;align-items:center;gap:1rem;margin-top:1rem;flex-wrap:wrap}
.fyh__rate-row input[type=range]{flex:1 1 10rem;min-width:0;min-height:44px;accent-color:var(--gold)}
/* Until the visitor moves it, the slider reads as unset rather than as a value
   we chose for them. There is no default rate in this feature. */
.fyh__rate-row input[type=range]{opacity:.55}
.fyh__rate-row input[type=range].is-set{opacity:1}

.fyh__await{margin:0 0 1.5rem;font-size:0.9375rem;line-height:1.7;color:var(--muted-aa);
  border-top:1px solid var(--line);padding-top:1.5rem}
.fyh__assume{margin:0 0 1.5rem;border-top:1px solid var(--line);padding-top:1.5rem}
.fyh__assume-line{margin:0;font-family:'Jost',sans-serif;font-size:0.875rem;
  letter-spacing:.04em;color:var(--midnight)}
.fyh__assume-prices{margin:0.375rem 0 0;font-size:0.8125rem;color:var(--muted-aa)}
.fyh__incentive{margin:0 0 1.5rem;font-size:0.875rem;line-height:1.7;color:var(--ink)}
.fyh__entry{margin:0 0 1.5rem;font-size:0.9375rem;line-height:1.7}
.fyh__entry a{color:var(--midnight);border-bottom:1px solid var(--gold)}
.fyh__entry-note{display:block;font-size:0.8125rem;color:var(--muted-aa)}

/* ---------------------------------------------------------------------------
   THE PAYMENT DOOR'S TWO PANES — sticky map left, results scrolling right.

   The map used to sit above the grid, which made it useless for its only job:
   you scroll to a card, and by then the map that would tell you where it is
   has left the screen. So it gets its own column and stays put while the list
   moves past it.

   POSITION:STICKY, NOT FIXED. Fixed would take the map out of flow and it
   would hang over the footer and the CTA band at the end of the list. Sticky
   keeps it inside its column: it pins while the column is on screen and
   releases at the bottom of it, which is the behaviour anyone actually wants.
   top:var(--nav-h) clears the sticky site header, matching .flt and .pnav.

   The map column is FIRST in the DOM as well as first visually, so keyboard
   and screen-reader order match what is on screen. The toggle button lives at
   the top of the pane and is display:none above the breakpoint — on desktop
   there is nothing to toggle, the map is simply there.
   --------------------------------------------------------------------------- */
.fyh__two{display:grid;grid-template-columns:minmax(0,7fr) minmax(0,9fr);gap:2rem;align-items:start}
.fyh__listPane{min-width:0}
/* THE STICKY MAP NEVER STUCK, AND THIS IS WHY (§0-Q item 3).
   `.fyh__two` is `align-items:start`, so this grid item shrank to its own
   content height — 857px, exactly the height of `.fyh__mapSticky` inside it.
   A sticky element that FILLS its containing block has nowhere to travel, so
   the pane scrolled away with the results instead of holding position, and
   the market chip row §0-Q item 1 shipped slid under the site header on the
   way past. Measured at 1440x900, y=1400: pane 857, sticky child 857, chips
   at [66..161] against a header bottom edge of 87.

   `align-self:stretch` gives the item the full grid-row height (5807px here)
   without changing `align-items:start` for the list pane beside it, which
   wants to sit at the top. The generator's own comment has described this
   behaviour since the two-column layout was written — "the map pane sticks
   while the results scroll past it independently". It is now true. */
.fyh__mapPane{min-width:0;align-self:stretch}
.fyh__mapSticky{position:sticky;top:var(--nav-h)}
.fyh__map{height:calc(100vh - var(--nav-h) - 5.5rem);min-height:360px;max-height:760px;
  width:100%;border:1px solid var(--line);border-radius:var(--radius);
  background:var(--paper,#f4f2ee);overflow:hidden}
/* Leaflet writes its own z-index stack — it ships 400-700 on its panes — so the
   whole thing is clamped here, under the site header and any modal, or a pin
   renders over the nav.
   THROUGH THE TOKEN, NOT A LITERAL. This used to be a bare numeric literal —
   the same value, but bypassing the --z-* scale, and build/audit_stacking.py
   fails the build on exactly that: a raw number is how the layer drift it was
   written to stop gets reintroduced one innocuous declaration at a time.
   --z-local-raise carries that value, and its stated meaning — "content above
   its own decoration", scoped inside a component — is precisely what these
   panes are doing within .fyh__map's own stacking context.
   (Do not write the offending property/number pair in a comment here: the gate
   scans the stylesheet as text and does not strip comments, so prose naming it
   literally fails the build just as the declaration would.) */
.fyh__map .leaflet-pane,.fyh__map .leaflet-top,.fyh__map .leaflet-bottom{z-index:var(--z-local-raise)}
.fyh__mapNote{margin:0.55rem 0 0;font-family:'Jost',sans-serif;font-size:0.75rem;
  letter-spacing:.04em;line-height:1.6;color:var(--muted-aa);min-height:1.2em}
.fyh__pinNote{margin:0.35rem 0 0;font-size:0.75rem;line-height:1.5;color:var(--muted-aa)}
/* MARKET CHIPS on the find-your-home map pane (HANDOFF §0-Q item 1). Same
   language as .am-chip on /map — Jost, pill, hairline, midnight when active —
   because "styled to the site language" means reusing the language, not
   approximating it.
   FLEX-WRAP IS LOAD-BEARING. `overflow-x: clip` on html and body means a row
   too wide for the viewport is CUT, not scrolled: at 390px five chips do not
   fit on one line, and without wrapping the last two would be painted off the
   left edge and unreachable. That is the 26-dots defect of 2026-08-11 exactly,
   and the reason this row wraps rather than scrolls.
   min-height 44px on the chip meets the 2026-08-10 touch-target rule without
   the fixed 44x44 box that made 26 carousel dots 1,144px wide. */
.fyh__mapChips{display:flex;flex-wrap:wrap;gap:0.4rem;margin:0 0 0.6rem}
.fyh__mapChip{display:inline-flex;align-items:center;gap:0.4rem;min-height:44px;
  font-family:'Jost',sans-serif;font-size:0.75rem;letter-spacing:.04em;
  padding:0.5rem 0.75rem;border:1px solid var(--line);border-radius:var(--radius-pill);
  background:transparent;color:var(--ink);cursor:pointer;white-space:nowrap;
  transition:background var(--dur-1) var(--ease),color var(--dur-1) var(--ease),
             border-color var(--dur-1) var(--ease)}
.fyh__mapChip::before{content:"";width:0.5rem;height:0.5rem;border-radius:50%;
  background:var(--chipDot,var(--gold));flex:0 0 auto}
.fyh__mapChip--all::before{display:none}
.fyh__mapChip:hover{border-color:var(--gold)}
.fyh__mapChip.is-on{background:var(--midnight);color:var(--ivory);border-color:var(--midnight)}
.fyh__mapChipN{font-size:0.625rem;letter-spacing:.1em;color:var(--muted-aa)}
.fyh__mapChip.is-on .fyh__mapChipN{color:var(--ivory)}
.cm-card__link{display:block;color:var(--midnight);border-bottom:1px solid var(--gold)}
.fyh__mapToggle{display:none}

/* The right column is ~9/16 of the content width, so three cards would be
   ~230px each — narrower than the card's own photo aspect wants. Two columns
   inside the pane, dropping to one when the column itself gets tight. */
.fyh__listPane .grid--3{grid-template-columns:repeat(2,1fr);gap:1.5rem}
@media (max-width:1180px){
  .fyh__two{grid-template-columns:minmax(0,6fr) minmax(0,8fr);gap:1.5rem}
  .fyh__listPane .grid--3{grid-template-columns:1fr}
}

/* ---- below the breakpoint: one column, map behind a toggle ----------------
   A sticky map on a phone would eat half the viewport permanently to explain
   a list the visitor can already read. It collapses to a button, closed by
   default, and when opened it is a fixed-height block ABOVE the results
   rather than a sticky one — on a small screen "always visible" and "enough
   room for the cards" cannot both be true, and the cards win. */
@media (max-width:899px){
  .fyh__two{display:block}
  .fyh__mapPane{margin:0 0 1.5rem}
  .fyh__mapSticky{position:static}
  .fyh__mapToggle{display:block;width:100%;min-height:44px;cursor:pointer;
    font-family:'Jost',sans-serif;font-size:0.75rem;letter-spacing:.16em;text-transform:uppercase;
    color:var(--ink);background:transparent;border:1px solid var(--line);
    border-radius:var(--radius);padding:0.75rem 1rem}
  .fyh__mapToggle:hover{border-color:var(--gold)}
  .fyh__mapToggle:focus-visible{outline:2px solid var(--gold);outline-offset:2px}
  /* Closed is the default, and it is the CSS that enforces it — not a hidden
     attribute a script has to remember to set. */
  .fyh__mapSticky{display:none}
  .fyh__two.is-mapOpen .fyh__mapSticky{display:block;margin-top:0.75rem}
  .fyh__map{height:clamp(260px,46vh,420px);max-height:none}
  .fyh__listPane .grid--3{grid-template-columns:repeat(2,1fr)}
}
@media (max-width:640px){
  .fyh__listPane .grid--3{grid-template-columns:1fr}
}
/* Leaflet never loaded — the pane removes itself and the list takes the width
   back rather than leaving a dead column. */
.fyh__two.is-noMap{display:block}

/* ---------------------------------------------------------------------------
   THE PAYMENT BLOCK — figure and disclosure are ONE unit.

   .pay is the visual unit. .pay__figure and .pay__disc are both inside it, they
   are built in the same function call, and break-inside:avoid keeps them on one
   page when printed. The disclosure is 0.8125rem/1.65 at --ink, which is body
   copy — it is deliberately NOT a 10px grey footnote, and it is never inside a
   <details>, a tooltip or a modal. build/audit_payment_search.py asserts all of
   that on the rendered DOM.
   --------------------------------------------------------------------------- */
.pay{margin:0.75rem 0 1.25rem;padding:1rem;background:var(--ivory);
  border:1px solid var(--line);border-radius:var(--radius);break-inside:avoid;
  page-break-inside:avoid}
.pay__head{display:flex;flex-wrap:wrap;align-items:baseline;gap:0.5rem 0.75rem}
.pay__figure{font-family:'Cormorant Garamond',serif;font-size:1.75rem;font-weight:600;
  color:var(--midnight);margin:0;font-variant-numeric:tabular-nums}
.pay__per{font-family:'Jost',sans-serif;font-size:0.75rem;font-weight:400;
  text-transform:uppercase;letter-spacing:.14em;color:var(--muted-aa);margin-left:0.375rem}
.pay__basis{margin:0;font-size:0.8125rem;color:var(--muted-aa)}
.pay__items{margin:0.875rem 0 0;padding:0.875rem 0 0;border-top:1px solid var(--line);
  display:grid;gap:0.25rem}
.pay__item{display:flex;justify-content:space-between;gap:1rem;font-size:0.8125rem;
  color:var(--ink)}
.pay__item dt{margin:0}
.pay__item dd{margin:0;font-variant-numeric:tabular-nums}
.pay__item--total{margin-top:0.375rem;padding-top:0.5rem;border-top:1px solid var(--line);
  font-weight:500;color:var(--midnight)}
.pay__sub{margin:0.75rem 0 0;font-size:0.75rem;line-height:1.65;color:var(--muted-aa)}
.pay__disc{margin:0.75rem 0 0;padding-top:0.75rem;border-top:1px solid var(--line);
  font-size:0.8125rem;line-height:1.65;color:var(--ink)}

@media print{
  .fyh__tabs,.fyh__gate,.fyh__await{display:none}
  .pay,.home{break-inside:avoid;page-break-inside:avoid}
  .pay__disc{color:#000}
}

/* The estimator's standing disclaimer. ABOVE the figure, inside the output
   panel, at readable size — deliberately not the small print at the foot of
   the section, which on a phone sits a screen and a half below the number it
   is supposed to qualify. */
.est__topnote{margin:0 0 1rem;padding-bottom:0.875rem;border-bottom:1px solid var(--line);
  font-size:0.8125rem;line-height:1.6;color:var(--ink)}

/* =============================================================================
   MODEL-HOME CHECK-IN KIOSK (/checkin)
   An iPad on a table, used standing up, by someone who has never seen it before
   and will not be shown how it works. Everything below follows from that:
   60px+ targets, 18px+ type, one action per screen, and no chrome to get lost in.
   Scoped under .kio so it cannot touch the rest of the site.
   ========================================================================== */
.kio-html{height:100%;overflow:hidden;scroll-behavior:auto}
.kio{height:100%;margin:0;overflow:hidden;background:var(--ivory);color:var(--ink);
  font-family:'Jost',sans-serif;
  /* A kiosk has no text selection and no long-press callouts: both only ever
     produce a stuck selection nobody knows how to clear. */
  -webkit-user-select:none;user-select:none;-webkit-touch-callout:none;
  -webkit-tap-highlight-color:transparent}
/* Inputs are the ONE exception — a visitor must be able to select and correct
   what they typed. */
.kio input{-webkit-user-select:text;user-select:text}
.kio__stage{position:fixed;inset:0;display:flex;flex-direction:column;
  padding:env(safe-area-inset-top) env(safe-area-inset-right) 0 env(safe-area-inset-left)}
.kio__screen{flex:1;min-height:0;overflow-y:auto;-webkit-overflow-scrolling:touch;
  display:flex;flex-direction:column;padding:clamp(1.5rem,4vw,3rem) clamp(1.25rem,5vw,4rem) 7rem}
.kio__screen[hidden]{display:none}
/* Welcome and thank-you are single statements: centred, nothing to hunt for. */
.kio__screen--welcome,.kio__screen--thanks{align-items:center;justify-content:center;text-align:center}
.kio__screen--form{max-width:820px;width:100%;margin:0 auto}

.kio__langs{display:flex;gap:0.5rem;margin:0 0 clamp(1rem,3vh,2.5rem)}
.kio__langs--form{align-self:flex-end;margin-bottom:1rem}
.kio__lang{min-height:60px;padding:0 1.5rem;font-family:'Jost',sans-serif;font-size:1rem;
  letter-spacing:.1em;text-transform:uppercase;color:var(--midnight);background:var(--white);
  border:1px solid var(--line);border-radius:var(--radius);cursor:pointer}
.kio__lang.is-on{background:var(--midnight);color:var(--ivory);border-color:var(--midnight)}

.kio__face{width:160px;height:160px;border-radius:50%;object-fit:cover;
  border:1px solid var(--gold);margin:0 0 1.5rem}
.kio__eyebrow{font-family:'Jost',sans-serif;font-size:0.875rem;letter-spacing:.28em;
  text-transform:uppercase;color:var(--gold);margin:0 0 0.75rem}
.kio__title{font-family:'Cormorant Garamond',Georgia,serif;font-weight:600;
  font-size:clamp(2.5rem,7vw,4.5rem);line-height:1.05;color:var(--midnight);margin:0 0 1rem}
.kio__h2{font-family:'Cormorant Garamond',Georgia,serif;font-weight:600;
  font-size:clamp(2rem,5vw,3rem);line-height:1.1;color:var(--midnight);margin:0 0 0.5rem}
.kio__sub{font-size:clamp(1.0625rem,2.2vw,1.375rem);line-height:1.6;color:var(--muted-aa);
  max-width:34ch;margin:0 0 clamp(1.5rem,4vh,2.5rem)}
.kio__screen--form .kio__sub{max-width:48ch}
.kio__sub--tight{margin-bottom:1.75rem}
.kio__host{font-size:0.9375rem;letter-spacing:.06em;color:var(--muted-aa);margin:1.75rem 0 0}
.kio__where{font-size:0.875rem;letter-spacing:.14em;text-transform:uppercase;
  color:var(--gold);margin:0.5rem 0 0}
.kio__where:empty{display:none}

/* 60px is the floor for EVERY control on this page, not a target to aim near.
   The primary action is 72px because it is pressed with a thumb, standing up,
   by somebody holding a phone in the other hand. */
.kio__btn{min-height:72px;padding:0 clamp(2rem,5vw,3.5rem);font-family:'Jost',sans-serif;
  font-size:1.125rem;letter-spacing:.16em;text-transform:uppercase;font-weight:500;
  border-radius:var(--radius);cursor:pointer;border:1px solid var(--midnight);
  color:var(--ivory);background:var(--midnight);transition:background var(--dur-2) var(--ease)}
.kio__btn--go:active{background:var(--gold);border-color:var(--gold);color:var(--midnight)}
.kio__btn--ghost{background:none;color:var(--midnight)}
.kio__actions{display:flex;flex-wrap:wrap;gap:1rem;margin:2rem 0 0}
.kio__actions .kio__btn{flex:1 1 12rem}
.kio__screen--thanks .kio__btn{margin-top:2rem}

.kio__grid{display:grid;grid-template-columns:1fr 1fr;gap:1.25rem 1.5rem;margin:0 0 1.75rem}
.kio__grid--2{margin-top:1.25rem}
.kio__field{display:flex;flex-direction:column;min-width:0}
.kio__label{font-size:0.9375rem;letter-spacing:.12em;text-transform:uppercase;
  color:var(--muted-aa);margin:0 0 0.5rem}
.kio__opt{font-style:normal;text-transform:none;letter-spacing:.02em;color:var(--placeholder)}
.kio__field input{min-height:64px;padding:0 1rem;font-family:'Jost',sans-serif;
  /* 16px+ or iOS zooms the whole locked viewport on focus. 20px because this is
     read at arm's length on a table, not held up to a face. */
  font-size:20px;color:var(--ink);background:var(--white);
  border:1px solid var(--line);border-radius:var(--radius)}
.kio__field input:focus{outline:2px solid var(--gold);outline-offset:2px;border-color:var(--gold)}
.kio__field input.is-invalid{border-color:var(--reduced);background:rgba(var(--reduced-rgb),.04)}

.kio__block{border:0;padding:0;margin:0 0 1.75rem;min-width:0}
.kio__legend{padding:0;font-size:0.9375rem;letter-spacing:.12em;text-transform:uppercase;
  color:var(--muted-aa);margin:0 0 0.75rem}
.kio__hint{font-size:0.9375rem;color:var(--placeholder);margin:-0.35rem 0 0.75rem}
.kio__chips{display:flex;flex-wrap:wrap;gap:0.75rem}
.kio__chip{min-height:60px;padding:0 1.5rem;font-family:'Jost',sans-serif;font-size:1.0625rem;
  color:var(--midnight);background:var(--white);border:1px solid var(--line);
  border-radius:var(--radius);cursor:pointer;transition:background var(--dur-1) var(--ease)}
.kio__chip.is-on{background:var(--midnight);color:var(--ivory);border-color:var(--midnight)}

/* The consent row is a 60px target in its own right — the box alone is a 20px
   tap on a sheet of glass, and a consent nobody could reliably tick is a
   consent record you cannot rely on either. */
.kio__consent{display:flex;align-items:flex-start;gap:1rem;min-height:60px;
  padding:1.25rem;margin:0 0 0.75rem;background:var(--white);
  border:1px solid var(--line);border-radius:var(--radius);cursor:pointer}
.kio__consent input{width:32px;height:32px;flex:0 0 32px;margin:2px 0 0;accent-color:var(--gold)}
.kio__consentText{font-size:1rem;line-height:1.55;color:var(--ink)}
.kio__privacy{margin:0 0 1.5rem}
.kio__privacy a{display:inline-flex;align-items:center;min-height:60px;font-size:1rem;
  color:var(--gold);text-decoration:none;border-bottom:1px solid rgba(var(--gold-soft-rgb),.55)}

.kio__err{display:block;font-size:0.9375rem;color:var(--reduced);margin:0.4rem 0 0;min-height:0}
.kio__err:empty{display:none}
.kio__err--top{margin:0 0 1rem;font-size:1.0625rem}

.kio__id{position:fixed;left:0;right:0;bottom:0;padding:0.75rem clamp(1.25rem,5vw,4rem)
  calc(0.75rem + env(safe-area-inset-bottom));background:var(--cream);
  border-top:1px solid var(--line)}
.kio__id p{margin:0;font-size:0.75rem;line-height:1.5;color:var(--muted-aa);text-align:center}

/* Portrait iPad and anything narrower: one column, and the fields stack. */
@media (max-width:820px){
  .kio__grid{grid-template-columns:1fr}
  .kio__face{width:120px;height:120px}
  .kio__actions .kio__btn{flex:1 1 100%}
}
/* Landscape iPad is short: the welcome screen loses the portrait head so the
   headline and the Start button both stay above the fold. */
@media (orientation:landscape) and (max-height:820px){
  .kio__face{width:96px;height:96px;margin-bottom:1rem}
  .kio__title{font-size:clamp(2rem,5.5vh,3.25rem)}
  .kio__screen{padding-bottom:6rem}
}

/* ================= ESTIMATE PAYMENT MODAL (assets/estimator-modal.js) =======
   Pricing a home is a lookup, not a destination: this opens over the grid the
   buyer is scanning and closes back to the same pixel. The dialog owns its own
   scroll so the page behind never moves — html.estm-open locks the body.
   At <=640px it is a full-screen sheet rather than a shrunken desktop dialog:
   a centred card at 375px leaves ~16px gutters and puts the output panel below
   the fold, which is the one place the payment must not be. */
html.estm-open { overflow: hidden; }
/* --z-modal, no fallback: the stacking gate requires every layer to come from
   the --z-* scale in :root, and a `var(--z-modal, 9000)` fallback IS a raw
   z-index as far as that gate is concerned — it failed the build on it. */
.estm { position: fixed; inset: 0; z-index: var(--z-modal); display: flex;
  align-items: center; justify-content: center; padding: 2rem 1rem; }
.estm[hidden] { display: none; }
.estm__backdrop { position: absolute; inset: 0; background: rgba(var(--midnight-deep-rgb), .62);
  backdrop-filter: blur(2px); }
.estm__dialog { position: relative; width: min(920px, 100%); max-height: calc(100dvh - 4rem);
  display: flex; flex-direction: column; background: var(--ivory);
  border: 1px solid var(--line); border-radius: var(--radius);
  box-shadow: 0 24px 64px rgba(var(--black-rgb), .34); overflow: hidden; }
.estm__head { display: flex; align-items: flex-start; gap: 1rem; padding: 1.25rem 1.5rem;
  background: var(--midnight); color: var(--ivory); }
.estm__eyebrow { margin: 0 0 .35rem; font-family: 'Jost', sans-serif; text-transform: uppercase;
  letter-spacing: .16em; font-size: .625rem; color: var(--gold-soft); }
.estm__title { margin: 0; font-size: 1.375rem; font-weight: 600; line-height: 1.2; }
.estm__meta { margin: .3rem 0 0; font-size: .8125rem; color: rgba(var(--ivory-rgb), .82);
  font-family: 'Jost', sans-serif; letter-spacing: .04em; }
.estm__x { margin-left: auto; flex: none; background: none; border: 0; color: var(--ivory);
  font-size: 1.75rem; line-height: 1; cursor: pointer; padding: 0 .25rem; opacity: .85; }
.estm__x:hover { opacity: 1; color: var(--gold-soft); }
.estm__body { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: 1.5rem; padding: 1.5rem; overflow-y: auto; }
.estm__field { margin-bottom: 1.15rem; }
.estm__lbl { display: block; font-family: 'Jost', sans-serif; text-transform: uppercase;
  letter-spacing: .14em; font-size: .625rem; color: var(--muted-aa); margin-bottom: .5rem; }
.estm__pills { display: flex; flex-wrap: wrap; gap: .4rem; }
.estm__pill { display: flex; flex-direction: column; align-items: center; gap: 2px;
  background: var(--white); border: 1px solid var(--line); border-radius: var(--radius);
  padding: .5rem .7rem; cursor: pointer; font-family: 'Jost', sans-serif; font-size: .8125rem;
  color: var(--midnight); min-height: 44px; justify-content: center; }
.estm__pill .estm__amt { font-size: .625rem; color: var(--muted-aa); letter-spacing: .02em; }
.estm__pill.is-on { background: var(--midnight); border-color: var(--midnight); color: var(--ivory); }
.estm__pill.is-on .estm__amt { color: var(--gold-soft); }
.estm__range { width: 100%; }
.estm__fixed { border-top: 1px solid var(--line); padding-top: 1rem; }
.estm__fixrows { margin: 0; }
.estm__fixrows > div { display: flex; justify-content: space-between; align-items: baseline;
  padding: .3rem 0; border-bottom: 1px solid rgba(var(--gold-rgb), .12); }
.estm__fixrows dt { font-size: .8125rem; color: var(--muted-aa); }
.estm__fixrows dd { margin: 0; font-size: .8125rem; font-weight: 600; color: var(--midnight);
  font-variant-numeric: tabular-nums; }
.estm__fixnote { margin: .6rem 0 0; font-size: .6875rem; line-height: 1.5; color: var(--muted-aa); }
.estm__out { background: var(--midnight); color: var(--ivory); border-radius: var(--radius);
  padding: 1.25rem; display: flex; flex-direction: column; }
.estm__topnote { margin: 0 0 .75rem; font-size: .6875rem; line-height: 1.5;
  color: rgba(var(--ivory-rgb), .78); }
.estm__olbl { margin: .5rem 0 .1rem; font-family: 'Jost', sans-serif; text-transform: uppercase;
  letter-spacing: .14em; font-size: .625rem; color: var(--gold-soft); }
.estm__big { margin: 0; font-family: 'Cormorant Garamond', serif; font-size: 2.25rem;
  font-weight: 600; line-height: 1.1; }
.estm__big--sub { font-size: 1.75rem; }
.estm__detail, .estm__basis { margin: .5rem 0 0; font-size: .75rem; line-height: 1.55;
  color: rgba(var(--ivory-rgb), .82); }
.estm__regz { margin-top: 1rem; background: rgba(var(--midnight-rgb), .45);
  border-color: rgba(var(--ivory-rgb), .24); }
.estm__regz .regz__terms { color: rgba(var(--ivory-rgb), .92); }
.estm__regz .regz__standing { color: rgba(var(--ivory-rgb), .78); }
.estm__cta { display: inline-block; margin-top: 1rem; text-align: center;
  font-family: 'Jost', sans-serif; text-transform: uppercase; letter-spacing: .16em;
  font-size: .75rem; font-weight: 600; color: var(--midnight); background: var(--gold);
  border-radius: var(--radius); padding: .8rem 1rem; }
.estm__cta:hover { background: var(--gold-soft); }
@media (max-width: 860px) {
  .estm__body { grid-template-columns: 1fr; }
}
/* FULL-SCREEN SHEET on phones — not a shrunken dialog. */
@media (max-width: 640px) {
  .estm { padding: 0; align-items: stretch; }
  .estm__dialog { width: 100%; max-width: none; max-height: none; height: 100dvh;
    border: 0; border-radius: 0; }
  .estm__body { padding: 1rem; gap: 1rem; }
  /* --z-local-raise (=1), not a bare 1: the stacking gate rejects any layer
     that is not drawn from the --z-* scale, whatever its numeric value. */
  .estm__head { padding: 1rem; position: sticky; top: 0; z-index: var(--z-local-raise); }
  .estm__big { font-size: 2rem; }
}

/* ============== AGENT ATTRIBUTION BADGE (assets/agent-attrib.js) ============
   Persistent but subtle: this states a relationship, it is not a call to
   action. Sits above the content and clear of the Call/Text bar and the chat
   launcher, both of which own the bottom-right on mobile. Dismissing it hides
   the badge only — the attribution itself is untouched. */
.agentbadge{position:fixed;left:12px;bottom:12px;z-index:var(--z-dropdown);
  display:inline-flex;align-items:center;gap:.5rem;max-width:min(92vw,26rem);
  padding:.5rem .6rem .5rem .85rem;border-radius:40px;
  background:rgba(var(--midnight-rgb),.92);color:var(--ivory);
  border:1px solid rgba(var(--gold-rgb),.45);
  box-shadow:0 6px 20px rgba(var(--black-rgb),.28);
  font-family:'Jost',sans-serif;font-size:.75rem;letter-spacing:.03em}
/* THE RIBBON DOES NOT SWALLOW TAPS MEANT FOR THE PAGE (2026-08-26).

   .agentbadge is position:fixed and nearly full width on a phone, so anything
   that scrolls under it is painted-but-unreachable — the 2026-08-13 dead-card
   defect, arriving from a floating bar instead of a stretched link.
   build/audit_light_cards.py L5 caught it on /a/<slug>.html: the share and save
   controls of cards 7 and 8 resolved to DIV.agentbadge under elementFromPoint.

   IT WAS NEVER VISIBLE TO THAT GATE BEFORE TODAY, and the reason is the bug
   fixed in 4e0d423011: the gate walks the `.html` spelling of the landing URL,
   which did not attribute, so no ribbon was drawn and nothing was covered. The
   pretty URL has drawn this ribbon over buyers' cards since it shipped.

   pointer-events, not z-index or a layout reservation: the ribbon must stay
   ABOVE the page (it is a disclosure, it may not be painted over) and a FIXED
   element cannot avoid passing over content while that content scrolls. What it
   can do is stop intercepting. The two things a buyer actually presses — the
   collapse × and the collapsed pill — take pointer-events back explicitly. */
.agentbadge{pointer-events:none}
.agentbadge__x,.agentbadge__pill{pointer-events:auto}
.agentbadge__txt{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.agentbadge__x{flex:none;background:none;border:0;color:rgba(var(--ivory-rgb),.7);
  font-size:1.1rem;line-height:1;cursor:pointer;padding:0 .15rem;min-width:28px;min-height:28px}
.agentbadge__x:hover{color:var(--gold-soft)}
@media (max-width:640px){
  /* ---- THE BOTTOM STACK, RESERVED RATHER THAN HOPED FOR --------------------
     Four fixed elements share the bottom of a phone screen — the call bar, the
     chat FAB, this badge and June's teaser bubble — and each was positioned by
     a different change on a different day. Each rule was right alone. Composed,
     they collided: measured on production 2026-08-19 at 390px, the badge ran
     10->380 and the FAB 265->374, BOTH with their bottom edge at 832. That is
     6,095px2 of the agent's own name underneath the chat button, and it was
     worst on /a/, where agent-attrib.js zeroes --callbar-h and every offset
     collapses by 56px onto the same line.

     So the stack is now EXPLICIT and bottom-up, each element reserving the one
     below it:

         call bar / sticky CTA   --callbar-h        (0 under /a/)
         chat FAB                + 12px
         this badge              + --fab-h + 10px
         June's teaser           + --badge-h + 10px   (.tg-badge-on only)

     --fab-h is 56px MEASURED, not assumed: the launcher's height comes from its
     own padding and font size, not from its label, so it is a constant. */
  .agentbadge{bottom:calc(var(--callbar-h) + env(safe-area-inset-bottom)
                          + 12px + var(--fab-h) + 10px);
    left:10px;right:10px;max-width:none}
}

/* <=430px: THE DISPLAY NAME AND NOTHING ELSE.
   "Referred by Vanessa Campa \00b7 The LAB Realty Group" is 302px of text in a
   293px box at 390px — the ellipsis was cutting the surname off the one element
   whose entire purpose is to say whose name it is. The prefix is inferable from
   the badge's placement and styling; the brokerage is on the page itself, in the
   hero and in the licensure disclosure. The name is the part that cannot be
   anywhere else. */
@media (max-width:430px){
  .agentbadge__pre,.agentbadge__brk{display:none}
}

/* THE COLLAPSED FORM. The × no longer removes the ribbon — it shrinks it to
   this pill, which reopens on click. A badged page has had every other name
   taken off it, so "dismissed to nothing" would leave the site branded to
   nobody; see the comment in assets/agent-attrib.js. The pill is deliberately
   the size of a touch target and nothing more. */
.agentbadge.is-collapsed{gap:0;padding:0;max-width:none;border-radius:50%}
.agentbadge__pill{width:38px;height:38px;flex:none;border-radius:50%;
  display:grid;place-items:center;cursor:pointer;
  background:none;border:0;color:var(--gold-soft);
  font-family:'Jost',sans-serif;font-size:.75rem;letter-spacing:.04em}
.agentbadge__pill:hover{color:var(--ivory)}
@media (max-width:640px){
  /* Collapsed, it stops spanning the viewport — the whole point is to get out
     of the way, and a full-width bar does not. */
  .agentbadge.is-collapsed{right:auto;width:38px}
}

/* PRE-PAINT PROMO SUPPRESSION.

   stripPromo() in assets/agent-attrib.js REMOVES these surfaces from the DOM,
   and removal — not hiding — remains the guarantee; build/audit_agent_promo.py
   asserts on innerHTML precisely so that a display:none phone number cannot
   pass for a deleted one. Nothing here changes that.

   What this covers is the window BEFORE the removal can happen. stripPromo has
   to wait for DOMContentLoaded because it edits the footer, and the footer is
   the last thing to parse. Measured at 375px over a 400ms / 400kbps link, the
   page paints at 19.0s and the deferred script runs at 24.1s — five seconds in
   which a badged visitor is looking at Tony's sticky "Call or Text" bar, his
   header phone button and his footer column, on the session that exists
   specifically to not show them any of it.

   assets/agent-ribbon-boot.js sets .tg-promo-off synchronously at the top of
   <body>, before the first frame, so these never paint at all. The class is
   only ever set for a REGISTERED agent tag, so an ordinary visit is untouched.
   These five selectors are the ones stripPromo names by class; the inline
   promo links it also removes are left to it, being far less conspicuous than
   a fixed bar across the bottom of the screen. */
html.tg-promo-off .footer__brand,
html.tg-promo-off .footer__social,
html.tg-promo-off #contact,
html.tg-promo-off .mobile-callbar,
html.tg-promo-off .nav__tel{display:none !important}

/* THE FOOTER'S AGENT BLOCK — what replaces Tony's promo column on a badged
   session. Same visual weight as the column it stands in for, so the footer
   grid keeps its three-across rhythm instead of collapsing to two. */
.footer__agent-hd{font-family:'Jost',sans-serif;font-size:.6875rem;
  letter-spacing:.16em;text-transform:uppercase;color:rgba(var(--ivory-rgb),.62);
  margin:0 0 .35rem}
.footer__agent-who{font-family:'Cormorant Garamond',serif;font-size:1.375rem;
  line-height:1.25;margin:0}
.footer__agent-brk{font-family:'Jost',sans-serif;font-size:.8125rem;
  color:rgba(var(--ivory-rgb),.72);letter-spacing:.02em}

/* THE GENERIC CHAT MARK — the badged stand-in for the headshot, in the
   launcher, the teaser, the panel header and every assistant bubble. It wears
   the class the <img> would have worn, so it inherits that slot's size and
   ring; these rules only supply what an <img> got for free. */
.tgchat-mark{display:grid;place-items:center;overflow:hidden;
  background:var(--midnight-2);color:var(--gold-soft)}
.tgchat-mark svg{width:62%;height:62%}
.tgchat-launch__av .tgchat-mark{position:absolute;inset:0}
.tgchat-head__avwrap .tgchat-mark{position:absolute;inset:0;border:none;box-shadow:none}
.tgchat-av--mark{padding:0}

/* THE EXPIRED-RATE NOTICE. Hidden until assets/estimator.h69a796715f.js puts
   .is-rate-expired on #estimator, which it does only when EST_PROMO_ACTIVE is
   false — i.e. the published programme's close-by date has passed in
   America/New_York, evaluated in the browser, so it turns itself on with no
   redeploy. Once the visitor types a rate of their own the estimator adds
   .is-rate-supplied and the notice steps aside: it exists to explain an empty
   field, not to nag someone who has already filled it. */
#estimator .est__expired{display:none}
#estimator.is-rate-expired .est__expired{display:block;margin:.6rem 0 0;
  font-size:.86rem;line-height:1.5;color:var(--ink);opacity:.9;max-width:46ch}
#estimator.is-rate-expired.is-rate-supplied .est__expired{opacity:.62}
/* =============================================================================
   PUBLISHED CORPORATE SCENARIO — the D.R. Horton / DHI Mortgage rate band.

   ONE CLASS FOR BOTH FIGURES, AND THAT IS THE POINT. .promo__num carries the
   rate and the APR identically: same size, same weight, same colour. The flyer
   sets 4.99% at roughly three times the size of 5.843% APR, and the 2026-08-04
   ruling says the rate may never be more conspicuous than the APR — so the
   pair renders at equal weight in the flyer's ORDER. Giving either figure its
   own modifier class is how that rule gets lost, so there is no second class to
   reach for. build/audit_equal_weight.py measures the rendered type and fails
   the build if they diverge.

   The terms live in a <details> for LENGTH, not for prominence: the flyer's
   disclosure paragraph and its footer disclaimer run to ~1,900 characters, and
   a band that pushes the page down by a screen and a half gets removed by
   whoever next redesigns the homepage — at which point the figures stay and the
   terms go. Collapsed, they are in the DOM, in the accessibility tree, and one
   keystroke away. Not collapsed, they are one redesign from being deleted.
   ========================================================================== */
.promo-band{max-width:none;background:var(--cream);padding-left:0;padding-right:0}
/* assets/estimator.h69a796715f.js hides the band while the estimator is modelling VA
   (Tony's ruling, 2026-08-10 — the visible rate context matches the loan type
   being modelled). Stated explicitly rather than left to the UA's `[hidden]`
   rule, which any later `display` on .promo-band would silently outrank. */
.promo-band[hidden]{display:none}
.promo__inner{max-width:var(--max);margin:0 auto;padding:0 2rem;text-align:center}
.promo__eyebrow{color:var(--gold)}
.promo__title{font-size:clamp(1.5rem,3vw,2.1rem);font-weight:500;color:var(--midnight);
  margin:.55rem 0 1.4rem;line-height:1.18}

.promo__figures{display:flex;justify-content:center;align-items:flex-start;
  gap:clamp(1.75rem,6vw,4rem);margin:0 0 1.25rem;flex-wrap:wrap;
  /* SAME WEIGHT AS THE FIGURES, on purpose. audit_equal_weight.py's E3 fails
     any figure heavier than the line it sits in; matching the container here is
     what lets the pair be large without either one being "bolder than its own
     line". Change one of these two numbers and the gate goes red. */
  font-weight:500}
.promo__fig{display:flex;flex-direction:column;align-items:center;gap:.3rem}
/* EQUAL WEIGHT. Do not add a --lead / --hero modifier to either figure. */
.promo__num{font-size:clamp(2.1rem,5.4vw,3.1rem);font-weight:500;line-height:1;
  color:var(--midnight);letter-spacing:-.01em;font-variant-numeric:tabular-nums}
/* Inside .promo__num, not beside it — the compliance probe reads the figure
   span's text to tell a rate from an APR. display:block keeps the visual
   stacking the design wants without splitting the string it has to match. */
.promo__lab{display:block;font-size:.76rem;font-weight:400;letter-spacing:.14em;
  text-transform:uppercase;color:var(--ink);opacity:.72;margin-top:.35rem}

/* THE CAPTION UNDER EACH FIGURE (2026-08-18, Tony's call).
   Two equal giant numbers confuse buyers. The edit everybody reaches for is to
   shrink the APR, and 1026.24(c) forbids precisely that — the rate may be
   stated only "not more conspicuously than" the APR, so the APR's size is
   fixed FROM BELOW by the rate's and cannot be reduced while the rate stands.
   The only remaining degree of freedom is explanation, so each figure gets a
   sentence saying what it is. build/promo_scenario.py carries the words.

   NOTHING ABOVE THIS RULE MOVED. .promo__num, .promo__figures and .promo__fig
   keep their sizing, weight and layout exactly as they were — the caption is
   additive, which is what lets build/audit_equal_weight.py and the new
   build/audit_apr_conspicuousness.py both stay green on the same markup.

   NOT font-weight-bearing and NOT inside .promo__num. Inside the figure span
   it would join the string the compliance probes read as "the figure", and a
   caption is not a figure. It is a SIBLING of .promo__num inside .promo__fig,
   which is a flex column, so it lands under the number it explains and under
   no other.

   opacity .78 over --cream is 5.92:1 — muted to the eye, clear of the 4.5 AA
   floor audit_contrast.py enforces for normal-size text. .72 would read 4.98
   and still pass; .78 is chosen because it leaves room for the palette to move
   without a caption being the first thing that goes red. */
.promo__cap{max-width:26ch;font-family:'Jost',sans-serif;font-size:.72rem;
  font-weight:400;line-height:1.45;letter-spacing:0;text-transform:none;
  color:var(--ink);opacity:.78;text-align:center;overflow-wrap:anywhere}
/* min-width:0 so the columns may actually shrink. Flex items default to
   min-width:auto, which refuses to go below their content's intrinsic width —
   the mechanism that once pushed .promo__figures 48px past a 390px phone and
   is now gated by build/audit_fit_390.py. A caption is the longest thing in
   this block, so the guard belongs here rather than in the media query alone. */
.promo__fig{min-width:0}

.promo__scenario{max-width:640px;margin:0 auto;color:var(--ink);
  font-size:.98rem;line-height:1.6}

.promo__terms{max-width:760px;margin:1.6rem auto 0;text-align:left}
.promo__terms>summary{cursor:pointer;font-size:.86rem;letter-spacing:.03em;
  color:var(--ink);opacity:.85;text-align:center;list-style:none}
.promo__terms>summary::-webkit-details-marker{display:none}
.promo__terms>summary::after{content:" \25be";opacity:.6}
.promo__terms[open]>summary::after{content:" \25b4"}
.promo__legal{margin-top:1rem}
.promo__avail{font-size:.9rem;line-height:1.55;color:var(--ink);margin:0 0 .6rem}
.promo__disc{font-size:.84rem;line-height:1.55;color:var(--ink);opacity:.9;margin:0 0 .8rem}
.promo__fine{font-size:.74rem;line-height:1.5;color:var(--ink);opacity:.78;margin:0 0 .6rem}
.promo__eho{font-weight:500;opacity:.85}

@media(max-width:539px){
  /* The two figures stay SIDE BY SIDE at 375px. Stacking them would put the
     rate above the APR at full width each, which reads as a headline with a
     footnote — the exact hierarchy the ruling forbids. */
  .promo__figures{gap:1.5rem;flex-wrap:nowrap}
  .promo__num{font-size:1.85rem}
  .promo__inner{padding:0 1.25rem}
  /* The pair still does not stack, so each caption takes half the row and the
     two columns must be equal — flex:1 1 0 rather than shrink-to-fit, or the
     longer APR sentence would claim more width than the rate's and reintroduce
     by column width the imbalance the caption exists to remove. */
  .promo__fig{flex:1 1 0}
  .promo__cap{font-size:.66rem;max-width:none}
}

/* --- SHARE A FLOOR PLAN (brief section 3) -------------------------------- */
/* The control sits inside the card it shares, so the card's own data-* are the
   facts it composes from — no lookup, no chance of shipping another plan's
   numbers. */
/* RESTYLED AND RELOCATED 2026-08-11 (Tony's brief).
   WAS: position:absolute at top/right .5rem, border-radius 999px, opaque white
   fill, a ↗ glyph. That is a fat white pill sitting ON the listing photograph,
   in the SAME corner as .badge (styles.css:722, top/right 1rem) — so on every
   under-construction home it fought the UNDER CONSTRUCTION badge for one spot,
   at full opacity, over the photo the card exists to show.

   NOW: in normal flow at the end of the card body (assets/share-plan.js
   mounts it there), in the CONTACT button's family — .btn at styles.css:173
   is the source of every value here: Jost 500, uppercase, .2em tracking,
   border-radius var(--radius), 1px hairline. SECONDARY WEIGHT means the rest
   state is deliberately quiet — --muted-aa text on a --line hairline — and the
   hover is where the gold appears. Nothing is invented; the pill radius and
   the white fill are simply gone. */
/* ---- ICON-ONLY, 2026-08-12 (Tony's flagship brief) ----------------------
   The word "Share" sat next to a heart that has no word; one labelled control
   beside one unlabelled control reads as an oversight. Both are now glyphs at
   the same size, same hairline box, same secondary weight — a pair.
   THE ACCESSIBLE NAME SURVIVES in aria-label (share-plan.js / save.js). An
   icon-only button with no accessible name announces as "button", which is a
   regression wearing a design decision's clothes. */
.share-btn,.save-btn{display:inline-flex;position:relative;
  align-items:center;justify-content:center;
  width:44px;height:44px;min-height:44px;padding:0;
  border:1px solid var(--line);border-radius:var(--radius);
  background:transparent;color:var(--muted-aa);cursor:pointer;
  transition:color var(--dur-2) var(--ease),border-color var(--dur-2) var(--ease)}
.share-btn svg,.save-btn svg{width:18px;height:18px;display:block}
/* The share mark is drawn in strokes, the heart is a filled path — so they are
   coloured by different properties and each has to say which. */
.share-btn svg{fill:none;stroke:currentColor;stroke-width:1.6;
  stroke-linecap:round;stroke-linejoin:round}
.share-btn svg circle{fill:currentColor;stroke:none}
.save-btn svg{fill:none;stroke:currentColor;stroke-width:1.6;stroke-linejoin:round}
.save-btn.is-saved svg{fill:var(--gold);stroke:var(--gold)}
.save-btn.is-saved{color:var(--gold);border-color:var(--gold)}
.share-btn:hover,.save-btn:hover{border-color:var(--gold);color:var(--midnight)}
.save-btn.is-saved:hover{color:var(--gold)}
.share-btn:focus-visible,.save-btn:focus-visible{outline:2px solid var(--gold);outline-offset:2px}
/* The two controls sit together, bottom-right of the text block. */
.home__body>.share-btn,.home__body>.save-btn,
.plan__body>.share-btn,.plan__body>.save-btn{align-self:flex-end}
.card-acts{display:flex;gap:.5rem;align-self:flex-end;margin-top:var(--sp-3)}
/* BOTTOM-RIGHT OF THE TEXT BLOCK. Both card bodies are flex columns
   (.plan__body at styles.css:899, .home__body likewise), so margin-left:auto
   alone would only right-align it in a ROW context. align-self does the
   horizontal placement in a column, and margin-top:auto pins it to the bottom
   so cards of unequal text length still line their Share controls up. */
/* Placement lives with the pair above (.card-acts / align-self). .plan__body
   declares flex:1, so margin-top:auto still pins the row to the bottom of the
   card and a row of plan cards lines its controls up however long each blurb
   runs. */
.home__body>.share-btn,.home__body>.save-btn{margin-top:var(--sp-3)}
/* WAS margin-top:auto, AND THAT WAS THE CARD-ALIGNMENT DEFECT (2026-08-18).
   The comment above is right that margin-top:auto pins a row to the bottom of a
   flex column — when it is the ONLY auto margin in that column. It was not:
   .plan__where carried margin-bottom:auto for the same purpose, so the free
   space split between them and neither block was pinned. .plan__actions now
   owns the single auto margin (see its rule), this row follows it with an
   ordinary gap, and the pair lands on the floor of the card together. */
.plan__body>.share-btn,.plan__body>.save-btn{margin-top:var(--sp-3)}
/* THE DESKTOP CONFIRMATION IS THE FEATURE, not decoration: a copy that gives no
   feedback is indistinguishable from a dead button, and the user's next move is
   to click it again. */
/* SMALL-CAPS, quiet, and it fades rather than vanishing (2026-08-13 brief).
   It is a confirmation, not a notification: no icon, no colour, no movement of
   anything around it — the row must not reflow when it appears, which is why
   it is absolutely positioned out of flow. */
.share-btn[data-shared] .share-note{position:absolute;top:calc(100% + .35rem);right:0;
  white-space:nowrap;background:var(--ink,#2b2f35);color:var(--paper,#fff);
  padding:.3rem .55rem;border-radius:6px;font-family:'Jost',sans-serif;
  font-size:.625rem;font-weight:400;text-transform:uppercase;letter-spacing:.14em;
  pointer-events:none}
.home,.plan-card{position:relative}
@media (prefers-reduced-motion:no-preference){
  .share-btn[data-shared]{transition:border-color .15s ease}
}

/* ==========================================================================
   TOUCH TARGETS — 44px, WCAG 2.5.8 / iOS HIG, 2026-08-10
   ==========================================================================
   Measured at 390px across nine templates on production before writing a line
   of this: 34 distinct control selectors under 44px, headed by the ones a buyer
   uses most — the hamburger at 34x34 on every page, "View This Home" at 22px
   tall on 44 cards, "Share" at 23px on 50, the rate slider at 16px.

   A TRANSPARENT ::after HIT EXPANDER WAS TRIED FIRST AND REMOVED. It grows the
   tappable area without moving the painted box, which is exactly what a card
   layout wants — and it steals from the neighbours. Measured with
   elementFromPoint rather than getBoundingClientRect (the box is what the
   expander deliberately does not change, so a box audit reports it as fixed
   either way), controls NEXT TO an expanded one lost area: .home__broc went
   from 32px of tappable height to 26px, because .share-btn's 44px overlay
   covered the top of it. Every expander is a 44px rectangle laid over whatever
   is within 22px, and on a card that is another control.

   So: real box growth, padding and min-height, nothing overlaid. Cards get
   ~20px taller on a phone. That is the honest cost and it is worth paying.

   .nav__toggle takes min-width/height rather than padding because it already
   uses ::after to print "More" at the mid widths (styles.css:942, :959).

   INLINE TEXT LINKS ARE NOT CONTROLS and are deliberately untouched — a
   breadcrumb, a community name inside a sentence, the footer's phone link.
   WCAG 2.5.8 exempts them, and padding them out would break the line box they
   sit in.
   ========================================================================== */
.nav__toggle{min-width:44px;min-height:44px}
.nav__brand{min-height:44px;display:inline-flex;align-items:center}

/* Card and plan controls. */
/* Both controls are 44x44 by construction above; this stays as the
   floor a future padding change cannot drop below. */
.share-btn,.save-btn{min-height:44px}
/* A FLOOR MAY ONLY RAISE. .tk__link IS NOT IN THIS LIST, 2026-08-10.
   It was, for one deploy, and it cost an advisory failure. .tk__link already
   declares min-height:3.6875rem (59px) at styles.css:1368 — deliberately, so a
   /partners toolkit card whose CTA label wraps to two lines is the same height
   as one that does not. This block sits later in the cascade, so min-height:44px
   OVERRODE 59px: single-line buttons collapsed to 44 while wrapped ones stayed
   at 59, and build/audit_cta_baseline.py reported "CTA buttons have 2 different
   heights [44, 59] — a wrapped label will knock the baseline out" at all six
   widths. The gate was right, and it was the only new advisory failure in the
   deploy.
   The control was never under 44px. Adding it to a 44px floor could only ever
   lower it. Before extending this list, check the selector's existing
   min-height: a floor that can lower is not a floor. */
.link-more,.home__link,.home__pay,.home__broc,.plan__wmore,
.plan__act,.cmp-chip,.chip,.est__chip,.qab__link,.prog__dl,.sl__tab,
.pnav__cta,.pnav__vcf,.flt__pill{
  min-height:44px;display:inline-flex;align-items:center;justify-content:center}
/* The card CTA row is the one place the extra height reads as loose, so the
   two stacked actions share it rather than each taking a full 44. */
.home__actions .home__pay,.home__actions .home__link{padding-block:.55rem}

/* THE `display` DECLARATION MOVED OUT OF HERE, 2026-08-15, AND THAT IS THE
   WHOLE FIX. This rule is part of the 2026-08-10 44px touch-target pass. It
   sits at TOP LEVEL, in no media query, and `display:flex` beat the
   `display:none` on .mobile-callbar's base rule 3,795 lines above it on source
   order alone — so a bar whose name says mobile, and which exists only inside
   `@media (max-width:640px)`, was painted across the bottom 44px of EVERY
   viewport. Measured on production at 1440x900: fixed, z-index 100, full
   width, y 856-900, reading "Call or Text Tony", opaque midnight.

   Two costs, and the second is the one that was reported. It is a visible bar
   on every desktop page. And because it is `position:fixed` and full width, it
   takes the tap for anything underneath it: on /communities/*.html the price
   and bed filter pills sit at y 867-911, so eight pills per community page
   were painted and dead, exactly like the card controls of 2026-08-13.

   This is the 26-dots defect of 2026-08-11 in its purest form — a touch-target
   rule and a visibility rule, written eight days apart in different parts of
   the file, each correct alone. The min-height and the centring are what this
   pass was for and they stay; `display` belongs with the media query that
   decides whether the bar exists at all, and it is now the only place that
   sets it. */
.mobile-callbar{min-height:44px;align-items:center;
  justify-content:center}
.est__pills button{min-height:44px}
/* The rate slider had 16px of hittable height. The track keeps its native
   accent-color rendering; the added height is space around it. */
.est__field input[type=range],
.est__rate-row input[type=range]{min-height:44px}
.est__rate-num{min-height:44px;display:inline-flex;align-items:center}
.est__rate-num input[type=text]{min-height:40px}
/* Disclosure summaries are controls, not captions. */
.promo__terms>summary,.tk__more>summary{min-height:44px;
  display:flex;align-items:center}
.promo__terms>summary{justify-content:center}

/* Chat + badge furniture: small glyph buttons in fixed panels, where growing
   the box costs nothing because the panel is not in document flow. */
.tgchat-x,.tgchat-send,.tgchat-teaser__x,.agentbadge__x,.agentbadge__pill,
.mnav__close{min-width:44px;min-height:44px;display:inline-flex;
  align-items:center;justify-content:center}
.tgchat-input{min-height:44px}
.tgchat-teaser__msg{min-height:44px}

/* Carousel dots sit shoulder to shoulder, so height alone does not help — the
   neighbour bounds the horizontal extent. Spread them instead. */
.hcar__dot{min-width:44px;min-height:44px}

/* ---- THUMB FLOOR, 2026-09-04 -------------------------------------------
   Measured at 390x844 on the live pages: 62 controls under 44x44 on the home
   pages, 83 on /partners. Fixed by PADDING, never by type size, per the brief.

   TWO TECHNIQUES, and the choice is not stylistic:

   * Controls that own their box (chips, pills, buttons) simply get
     min-height:44px. They sit in their own row and 6-10px of growth moves
     nothing else.

   * INLINE links inside prose and headings get padding cancelled by an equal
     negative margin. Padding is inside the border box, so the hit area the
     browser tests — and the box getBoundingClientRect reports — grows to 44px
     while the surrounding layout does not move by a single pixel. This matters
     here specifically: .card__title sits inside .card, and CLAUDE.md 3 makes
     "at least 3 cards fully visible at 390x844" a BLOCKING acceptance. Adding
     15px of real height to 29 card titles would have spent that budget on a
     link whose destination is ALREADY covered by .card__imglink, which is
     position:absolute;inset:0 over the whole photograph. The name is a second
     route to a target that is already the size of the card.
   ------------------------------------------------------------------------ */
.card__title a,
.footer__markets a,.footer__partners a,.footer__credit a,.footer__grid p a,
.footer__social a,.promo__fine a,.crumb a,.pnav__inner a{
  display:inline-block;
  padding-block:calc((44px - 1em) / 2);margin-block:calc((1em - 44px) / 2);
  padding-inline:6px;margin-inline:-6px}
/* Payment-band pills and the amenity filter row: their own boxes, so height
   is the whole fix. */
.sl__pill,.am-chip,.am-clear,.flt__pill{min-height:44px;min-width:44px}
/* .hsl .sl__pill pins min-height:38px at two-class specificity, so a single
   class cannot raise it. Matched here rather than edited there: the 38px is
   that component's own compact rhythm, and this is the floor overriding it. */
.hsl .sl__pill{min-height:44px}
/* A grid cell had collapsed this link to 2px wide. */
.am-row__name{min-width:44px;min-height:44px;display:inline-flex;align-items:center}

/* Leaflet ships 30px zoom controls and its own `.leaflet-touch .leaflet-bar a`
   rule outranks a single class, so this needs the same specificity.
   Map PINS are left alone on purpose: enlarging a marker changes what the map
   says, not how it is tapped. */
.leaflet-touch .leaflet-bar a,
.leaflet-control-zoom a{width:44px;height:44px;line-height:44px}


/* ---- SAVE: FILL AND SETTLE (Tony's flagship brief, 2026-08-12) -----------
   One pulse on tap, ~260ms, then still. It is the only feedback the heart
   gives and it has to read as "that registered", not as decoration.
   ENTIRELY BEHIND prefers-reduced-motion: with reduce set the class is still
   applied and simply animates nothing, so the JS needs no media query and the
   two can never disagree. Transform only — no layout property is touched, so
   this cannot move a neighbour or contribute to CLS. */
@media (prefers-reduced-motion:no-preference){
  @keyframes tg-save-pop{
    0%{transform:scale(1)} 40%{transform:scale(1.28)} 100%{transform:scale(1)}
  }
  .save-btn.is-pop svg{animation:tg-save-pop .26s var(--ease,ease-out)}
}

/* ---- THE SAVED VIEW (assets/save.js) ------------------------------------
   A SEPARATE ATTRIBUTE, NOT display:none. assets/filters.h705391757f.js already owns the
   inline `display` on these cards; a second writer on one property is how a
   card ends up permanently invisible after the two disagree. This composes
   with whatever filters.js is doing instead of fighting it. */
.home[data-saved-hidden]{display:none!important}
.saved-empty{margin:1.5rem 0;padding:1.1rem 0 0;
  border-top:1px solid var(--line);color:var(--muted-aa);
  font-size:.95rem;line-height:1.6;max-width:46ch}
/* The header control. The count is only painted when there is something to
   count — a "0" badge is noise that trains people to ignore the badge. */
.nav__saved{display:inline-flex;align-items:center;gap:.4rem;position:relative;
  min-height:44px;padding:0 .5rem;color:inherit;text-decoration:none}
.nav__saved svg{width:17px;height:17px;fill:none;stroke:currentColor;stroke-width:1.6}
.nav__saved [data-saved-count]{font-family:'Jost',sans-serif;font-size:.6875rem;
  letter-spacing:.06em;line-height:1;min-width:1.15rem;padding:.2rem .3rem;
  border-radius:99px;background:var(--gold);color:var(--midnight);text-align:center}
.nav__saved [data-saved-count][data-empty="1"]{display:none}

/* ==========================================================================
   MOTION LAYER — both sites (Tony's flagship brief §5, 2026-08-12)
   ==========================================================================
   EVERY RULE IS DOUBLY GATED: html.tg-motion is set only by assets/motion.js,
   which exits immediately under prefers-reduced-motion — and the block is
   ALSO wrapped in the media query, so neither layer can regress alone. With
   JS off, html.tg-motion never exists and nothing here applies: no element is
   ever hidden from a reader that cannot run the reveal.
   NO CLS BY CONSTRUCTION: hidden state is opacity+transform only. The box an
   element occupies is identical before and after its reveal. */
@media (prefers-reduced-motion:no-preference){
  html.tg-motion .section__head,
  html.tg-motion .home,
  html.tg-motion .plan,
  html.tg-motion .hd--ident,
  html.tg-motion .al-quick,
  html.tg-motion .al-proof{
    opacity:0;transform:translateY(14px);
    transition:opacity .55s var(--ease),transform .55s var(--ease)}
  html.tg-motion .is-inview{opacity:1;transform:none}
  html.tg-motion .is-inview.no-anim{transition:none}
  /* Gold rules draw in under headings, once, with their heading's reveal.
     scaleX from the left — transform only, no width animation, no layout. */
  html.tg-motion .section__head .section__title::after,
  html.tg-motion .hero__rule{transform-origin:left center}
  html.tg-motion .section__head:not(.is-inview) .section__title::after{transform:scaleX(0)}
  html.tg-motion .section__head.is-inview .section__title::after{
    transform:scaleX(1);transition:transform .7s var(--ease) .15s}

  /* Card hover: the image scales INSIDE its frame; the hairline warms.
     .home/.plan/.card already clip their media (overflow:hidden), so the
     scale cannot escape the frame. No shadow changes — the brief's "no
     shadow explosions" is satisfied by not touching box-shadow at all. */
  .home:hover,.plan:hover{border-color:rgba(var(--gold-soft-rgb),.55)}
  .home .home__media img,.plan img{transition:transform var(--dur-3) var(--ease)}
  .home:hover .home__media img{transform:scale(1.04)}

  /* Buttons: a subtle press. Transform only, 60ms, release springs back. */
  .btn:active,.share-btn:active,.save-btn:active,.al-nav a:active,
  .al-contact__btn:active{transform:scale(.97);transition-duration:.06s}

  /* Chips: hairline glow on hover/focus — a border warm plus a 1px halo of
     the same gold, not a shadow bloom. */
  .al-nav a:hover,.al-nav a:focus-visible,
  .hd__chip:hover{box-shadow:0 0 0 1px rgba(var(--gold-soft-rgb),.35)}
}

/* ---- /a/ VIDEO HERO (flagship §1) ---------------------------------------
   The video fills the same slot .page-hero__img gives a photograph, under the
   SAME overlay — no new gradient, no new type. The subline is the brokerage in
   the small-caps discipline the eyebrow already uses, one step quieter. */
.page-hero--video .page-hero__video{width:100%;height:100%;object-fit:cover;display:block}
.page-hero__sub{margin:.4rem 0 0;font-family:'Jost',sans-serif;font-weight:500;
  text-transform:uppercase;letter-spacing:.22em;font-size:.75rem;
  color:var(--gold-soft,#cbb489);opacity:.95}


/* ===========================================================================
   PLAN ROUTES — /plans/<slug>/ and its three sub-pages (Tony, 2026-08-22)
   ---------------------------------------------------------------------------
   These replace the plan-detail OVERLAY. Everything here is ordinary document
   flow: no fixed positioning, no stacking contexts, no z-index. That is the
   point — the defect being fixed was a 390px viewport filled by a playing
   video with the community list stranded underneath it, and a page cannot
   strand its own content below the fold in the way an overlay can.
   ======================================================================== */

/* The persistent sub-nav. Scrolls WITH the page, deliberately: a sticky bar
   here would re-spend the vertical budget the overlay wasted, and .flt already
   taught this repo (2026-08-20) that a sticky element is bounded by a
   containing block nobody checked.

   IT WAS NAMED .pnav UNTIL 2026-08-22 AND THE COMMENT ABOVE WAS FALSE FOR A
   DAY. /partners has carried a DIFFERENT component called .pnav since the
   initial commit -- dark, sticky, top:var(--nav-h) (see ~line 4064). Two
   unrelated components, one class name, so both blocks composited onto both
   elements: this nav silently became position:sticky (measured in production
   at top:87px, z-index:99, 113px tall at 390 on /plans/coral/availability/),
   and the partners nav inherited this block's light border, 28px margin and
   16px padding, inflating from ~40px to 85px and DOUBLING the distance every
   /partners anchor landed underneath the header.

   A CSS collision raises nothing. The comment sat here reading as proof the
   thing was handled, exactly as the 2026-08-13 dead-card z-index did four
   lines below a comment claiming it worked. Only the browser distinguishes
   painted from working -- getComputedStyle said sticky while this said
   deliberately not. Renamed to .plannav so the two cannot merge again. */
.plannav{display:flex;flex-wrap:wrap;gap:.5rem;margin:0 0 1.75rem;padding:0 0 1rem;
  border-bottom:1px solid rgba(var(--midnight-rgb),.12)}
.plannav__item{font-family:'Jost',sans-serif;font-size:.82rem;letter-spacing:.06em;
  text-transform:uppercase;padding:.55rem .95rem;border-radius:2rem;
  border:1px solid rgba(var(--midnight-rgb),.18);color:var(--midnight);
  text-decoration:none;line-height:1;display:inline-flex;align-items:center;
  /* 44px tall at the default font size — the 2026-08-10 touch-target rule.
     Set with min-height rather than padding alone so a wrapped label cannot
     collapse the target. */
  min-height:44px}
a.plannav__item:hover{background:rgba(var(--midnight-rgb),.06)}
.plannav__item.is-on{background:var(--midnight);color:var(--ivory);border-color:var(--midnight)}
.plannav__back{margin-left:auto}
.plannav__back.is-on{background:none;color:var(--midnight);border-color:transparent;
  font-weight:600}

.al-h{font-family:'Cormorant Garamond',serif;font-size:clamp(1.5rem,3vw,2rem);
  font-weight:600;margin:0 0 .75rem}
.al-headline{font-family:'Jost',sans-serif;color:var(--muted);margin:0 0 1.5rem}
.al-specs-band{margin:0 0 2rem}

/* Hub cards — the three doors out. */
.pcards{display:grid;gap:1rem;grid-template-columns:repeat(auto-fit,minmax(15rem,1fr));
  margin:2rem 0}
.pcard{position:relative;display:block;padding:1.35rem 3rem 1.35rem 1.35rem;
  border:1px solid rgba(var(--midnight-rgb),.15);border-radius:.6rem;
  text-decoration:none;color:inherit;background:var(--ivory);
  transition:border-color var(--dur-1) var(--ease),transform var(--dur-1) var(--ease)}
.pcard:hover{border-color:var(--midnight);transform:translateY(-2px)}
.pcard__t{font-family:'Cormorant Garamond',serif;font-size:1.3rem;font-weight:600;
  margin:0 0 .35rem}
.pcard__d{font-family:'Jost',sans-serif;font-size:.87rem;color:var(--muted);margin:0}
.pcard__go{position:absolute;right:1.35rem;top:50%;transform:translateY(-50%);
  font-size:1.2rem;color:var(--muted)}

/* The video route. The player is the page. */
.pvid{margin:0 0 1.5rem}
.pvid__el{display:block;width:100%;max-width:100%;height:auto;border-radius:.5rem;
  background:#000}
.pvid__next{font-family:'Jost',sans-serif;margin:0 0 2rem}

/* The brochure route: page images, stacked, lazy. */
.pbroc{margin:0 0 2rem}
.pbroc__pg{display:block;width:100%;max-width:100%;height:auto;margin:0 0 1rem;
  border:1px solid rgba(var(--midnight-rgb),.12);border-radius:.4rem}
.pbroc__dl{font-family:'Jost',sans-serif;margin:0}

/* /videos/ — tiles link out, they never play in place. */
.vgrid{display:grid;gap:1.25rem;grid-template-columns:repeat(auto-fit,minmax(16rem,1fr));
  margin:1.5rem 0 2.5rem}
.vtile{position:relative;display:block;text-decoration:none;color:inherit;
  border:1px solid rgba(var(--midnight-rgb),.15);border-radius:.6rem;overflow:hidden;
  background:var(--ivory)}
.vtile:hover{border-color:var(--midnight)}
.vtile__img{display:block;width:100%;height:auto;aspect-ratio:4/3;object-fit:cover}
.vtile__body{display:block;padding:1rem}
.vtile__t{display:block;font-family:'Cormorant Garamond',serif;font-size:1.25rem;
  font-weight:600}
.vtile__d{display:block;font-family:'Jost',sans-serif;font-size:.82rem;color:var(--muted)}
.vtile__play{position:absolute;top:.75rem;right:.75rem;width:44px;height:44px;
  border-radius:50%;background:rgba(var(--midnight-rgb),.85);color:var(--ivory);
  display:flex;align-items:center;justify-content:center}
.vtile__play svg{width:20px;height:20px}

@media (max-width:480px){
  /* At 390 the sub-nav is four items; let the back link drop to its own row
     rather than squeezing the three route pills below the touch target. */
  .plannav__back{margin-left:0;width:100%;justify-content:flex-start}
}

/* ===========================================================================
   /agents — THE LINK FLOW, IN THE FIRST VIEWPORT (Tony, 2026-08-22)
   ---------------------------------------------------------------------------
   "The link agents come for is buried under scroll after scroll." Measured
   before moving it: the only CTA sat 4,014px down at 390x844 — 4.76 viewports,
   past nine explanatory cards.

   .section--tight exists because .section is `padding: 6rem 2rem`, and 96px of
   air above and below is what puts a 250px block out of a 343px budget. The
   hero ends at 501px on a 844px viewport; everything here is sized so the
   whole block clears the fold with room to spare, and
   build/audit_primary_action.py asserts it rather than trusting this comment.

   RESTRAINED ON PURPOSE. This is a realtor-facing surface: no urgency, no
   scarcity, no exclamation. It states what the link does and hands it over.
   ======================================================================== */
.section--tight{padding-top:2rem;padding-bottom:2rem}

.agl{max-width:34rem}
.agl__box{border:1px solid rgba(var(--midnight-rgb),.15);border-radius:.6rem;
  padding:1.35rem 1.35rem 1.15rem;background:var(--ivory)}
.agl__box[hidden]{display:none}
.agl__h{font-family:'Cormorant Garamond',serif;font-size:1.5rem;font-weight:600;
  margin:0 0 .4rem;line-height:1.15}
.agl__p{font-family:'Jost',sans-serif;font-size:.9rem;color:var(--muted);
  margin:0 0 1rem;line-height:1.5}
.agl__who{font-family:'Jost',sans-serif;font-size:.9rem;font-weight:500;
  margin:0 0 .75rem;color:var(--midnight)}
.agl__cta{width:100%;justify-content:center;min-height:48px}
.agl__fine{font-family:'Jost',sans-serif;font-size:.8rem;color:var(--muted);
  margin:.8rem 0 0}

/* The link + copy row. Grid rather than flex so the button keeps its width and
   the field takes the remainder — a copy button that shrinks under a long slug
   is a 30px tap target. */
.agl__row{display:grid;grid-template-columns:1fr auto;gap:.5rem;align-items:stretch}
.agl__url{font-family:'Jost',sans-serif;font-size:.85rem;min-width:0;
  padding:.7rem .8rem;border:1px solid rgba(var(--midnight-rgb),.22);
  border-radius:.4rem;background:var(--cream);color:var(--midnight);
  /* 44px floor, the 2026-08-10 touch-target rule — a readonly field is still
     the thing people tap to select. */
  min-height:44px}
.agl__copy{min-height:44px;min-width:5.5rem;justify-content:center;white-space:nowrap}
.agl__copy.is-done{background:var(--midnight);border-color:var(--midnight)}

@media (max-width:420px){
  /* At 390 the button drops under the field rather than squeezing both. Still
     one tap, still 44px, and the URL stays readable at full width. */
  .agl__row{grid-template-columns:1fr}
}

/* A plan with no library photograph (Tony's ruling, 2026-08-22: a neutral
   placeholder, never a shorter card). Sits in the SAME .plan__media slot at the
   same 4/3, so the grid stays uniform and a real planlib-<slug>.jpg drops in
   with no layout change. Quiet on purpose: the plan name on the card treatment,
   no stock photo and no invented render. */
.plan__media--ph{display:grid;place-items:center;background:var(--cream);
  border-bottom:1px solid rgba(var(--midnight-rgb),.10)}
.plan__ph-name{font-family:'Cormorant Garamond',serif;font-size:1.6rem;
  font-weight:600;color:rgba(var(--midnight-rgb),.42);letter-spacing:.01em;
  padding:0 1rem;text-align:center}

/* =============================================================================
   PARTNERS — COMPACT REGISTER CARD (2026-08-26)

   The register block moved from the BOTTOM of /partners to directly beneath the
   sub-nav, so it is now the first thing under the hero. Two consequences the
   styling has to carry:

   1. IT IS A CARD, NOT A SECTION HEAD. The old block used .section__head, which
      is a centred 620px column with a 4rem gap and a gold rule under the title —
      correct for a section you scroll to, 200px of chrome for a form you land
      on. .preg__title/.preg__sub are the same two strings with the rule and the
      eyebrow gone.

   2. THE CONSENT LINE IS NOT COMPRESSED. .consent keeps its own size and
      leading from the shared rule above; nothing here overrides it, and there is
      no max-height, no clamp, no -webkit-line-clamp anywhere in this block. The
      whole point of the move is that an agent reads it BEFORE submitting, and a
      card that is compact by hiding its disclosure is worse than the layout it
      replaced.

   Field rows reuse .partner-form__row, which is already 1fr 1fr and already
   collapses to one column under 560px — so Name+Phone and Brokerage+Email stack
   on a phone rather than shrinking to unusable widths.
   ========================================================================== */
.section--register{padding-top:calc(var(--sec-y)*.2);padding-bottom:calc(var(--sec-y)*.7)}
.preg{max-width:680px;margin:0 auto;text-align:center}
.preg__title{font-size:clamp(1.35rem,3.4vw,2.1rem);font-weight:500;letter-spacing:.005em;margin:0}
.preg__sub{margin:.4rem auto 0;max-width:52ch;font-family:'Jost',sans-serif;font-weight:300;
  font-size:1rem;line-height:1.45;color:var(--muted-aa)}
/* THE CARD CLEARS THE FIXED BOTTOM BAR, AND THAT IS THE REAL ACCEPTANCE.
   .contact__form is padding:3rem — 48px of inset above the first field, which
   is right for a full-width contact block you scroll to and wrong for a card
   you are supposed to land on. The first measurement said the Name input ended
   at 818px against an 844px viewport and called it a pass; elementFromPoint at
   the input's own centre returned `a.pbar__btn`. .pbar is fixed at the foot of
   /partners and owns 787-844, so the field was inside the viewport and UNDER
   something — painted, not reachable. Rect arithmetic cannot tell those apart;
   only asking the browser what is on top can. Sized so the input ends above
   .pbar's top edge, not above the viewport's. */
.preg__form{margin-top:.85rem;text-align:left;padding:1.25rem}
.preg__form .partner-form__row{gap:.9rem;margin-bottom:.9rem}
.preg__form .btn--full{margin-top:.25rem}
.preg__form .contact__note{margin-top:.6rem}

/* ---- REGISTRATION ANSWERS IN PLACE (Tony 2026-09-26) ---------------------
   .preg__slot is the container the form is swapped OUT of and the confirmation
   swapped IN to. assets/partner-signup.h5695b039d0.js pins its min-height to the form's
   measured height before detaching anything, so the block cannot shrink under
   the agent while they are reading — on a 390x844 phone this form is most of
   the viewport and a collapse is a full-screen jump. The centring below is what
   makes the pinned height look deliberate rather than like a gap. */
.preg__slot{display:flex;flex-direction:column;justify-content:center}
.preg__slot>.preg__form{flex:0 0 auto}

/* THE NO-JS CONFIRMATION, REVEALED BY :target ALONE.
   /api/partner-signup 303s a native POST to /partners#registered. No script is
   involved on that path by definition — it is the path taken when the script
   did not run — so the reveal is a CSS selector and nothing else. The live
   (fetch) copy carries --live and is always shown, because the script inserts
   it only on success. */
.preg__done{display:none;text-align:left;padding:1.25rem 1.4rem;
  border:1px solid var(--gold,#b6975f);border-radius:4px;background:#fff}
.preg__done:target,.preg__done--live{display:block}
.preg__done:focus{outline:2px solid var(--gold,#b6975f);outline-offset:3px}
.preg__done-msg{margin:0;font-family:'Jost',sans-serif;font-weight:300;
  font-size:1rem;line-height:1.55;color:var(--ink)}
/* The cell is a real sms: link on a phone, so it gets a tap target rather than
   just underlined text — CLAUDE.md §3 floors these at 44px. inline-block so the
   padding actually applies to an inline anchor. */
.preg__done-msg a{display:inline-block;min-height:44px;line-height:44px;
  font-weight:500;color:var(--ink);text-decoration:underline}

/* THE FAILURE LINE, WHERE THE AGENT IS ALREADY LOOKING — directly under the
   button they just pressed, values intact, button re-enabled. It is styled as a
   failure: CLAUDE.md records a console where every refusal rendered in the same
   small grey type as a success, and calls naming a failure in the styling of a
   success most of the way to not naming it. */
.preg__err{margin:.6rem 0 0;font-family:'Jost',sans-serif;font-size:.95rem;
  line-height:1.45;color:#a3202c;font-weight:500}
.preg__err[hidden]{display:none}

/* Slim repeat CTA at the foot of the page — one line and a button, NOT a second
   form. See build/generate.py register_repeat for why that distinction matters. */
.preg-again{text-align:center}
.preg-again__line{margin:0 0 1rem;font-family:'Jost',sans-serif;font-weight:300;
  font-size:1rem;line-height:1.55;color:var(--muted-aa)}

/* =============================================================================
   GRID COLLAPSE — see assets/grid-collapse.h25a6bcd132.js.

   .gcol__hidden is applied by SCRIPT and never in the built HTML, so a reader
   with no JS gets the whole grid instead of a truncated one they cannot expand.
   display:none rather than visibility/height, because the whole point is to
   remove the box from layout — a collapsed card that still reserves its height
   has saved nothing.
   ========================================================================== */
.gcol__hidden{display:none!important}
.gcol__act{display:flex;justify-content:center;margin-top:var(--sec-gap)}
.gcol__more{
  display:inline-flex;align-items:center;justify-content:center;
  min-height:44px;padding:.75rem 1.75rem;              /* 44px tap-target floor */
  font-family:'Jost',sans-serif;font-weight:500;text-transform:uppercase;
  letter-spacing:.16em;font-size:.75rem;
  color:var(--midnight);background:transparent;
  border:1px solid var(--gold);border-radius:var(--radius-pill);cursor:pointer;
  transition:background var(--dur-2) var(--ease),color var(--dur-2) var(--ease)}
.gcol__more:hover{background:var(--gold);color:var(--white)}

/* ===== /partners live inventory — THE AGENT PRICE LIST (2026-09-04) =========
   REPLACES the flat-table styling of 2026-09-02. Tony rejected that shape on
   09-02: what he sends agents is a per-community price list, so this styles
   nineteen small tables under their own headers rather than one table of 254
   rows.

   THE TABLE BECOMES CARDS AT 760px, IT DOES NOT SCROLL SIDEWAYS. html/body
   carry overflow-x:clip on this site, so a too-wide row does not become a
   scrollable region — it CUTS, and the cut half is unreachable (the 26-dot
   carousel defect, CLAUDE.md 2026-08-11). TEN columns cannot fit 390px — six
   could not — so below the breakpoint each row is a card and each cell prints
   its own label from the data-label attribute the generator writes.

   THE PHRASE IS "price reduced", EVERYWHERE. The retired promo name (CLAUDE.md
   rule 4) is not spelled anywhere in this section — not in a class, not in a
   comment, not in a string — in this file or in the three that build it, so a
   grep for it over the built page returns nothing rather than returning a
   comment somebody then has to read and clear.
   ========================================================================== */
.pinv__filter{max-width:var(--max);margin:0 auto 1.6rem;border:1px solid var(--line);
  border-radius:var(--radius);background:var(--card,#fff)}
.pinv__summary{display:flex;align-items:center;justify-content:space-between;gap:1rem;
  min-height:48px;padding:.7rem 1rem;cursor:pointer;list-style:none;
  font-family:'Jost',sans-serif;text-transform:uppercase;letter-spacing:.14em;font-size:.78rem}
.pinv__summary::-webkit-details-marker{display:none}
.pinv__summary::after{content:'+';font-size:1.2rem;line-height:1;opacity:.6}
.pinv__filter[open] .pinv__summary::after{content:'\2212'}
.pinv__summaryCount{color:var(--muted-aa);letter-spacing:.08em;text-align:right}
.pinv__panel{padding:.2rem 1rem 1rem;border-top:1px solid var(--line)}
.pinv__panelHead{display:flex;align-items:center;justify-content:space-between;
  gap:1rem;flex-wrap:wrap;margin:.8rem 0}
.pinv__panelNote{margin:0;color:var(--muted-aa);font-size:.92rem;max-width:52ch}
.pinv__clear{min-height:44px;padding:0 1rem;border:1px solid var(--line);
  border-radius:999px;background:none;cursor:pointer;font-size:.9rem}

/* THE PANEL IS A GRID OF FIELDSETS, not a row of controls. Five facets at 390
   in one column, two columns from 560, four from 900 — auto-fit rather than
   breakpoint-per-count so adding a sixth facet needs no CSS. */
.pinv__fields{display:grid;gap:1rem;
  grid-template-columns:repeat(auto-fit,minmax(min(100%,15rem),1fr))}
.pinv__field{border:0;padding:0;margin:0;min-width:0}
.pinv__field legend{padding:0 0 .4rem;font-family:'Jost',sans-serif;
  text-transform:uppercase;letter-spacing:.12em;font-size:.7rem;color:var(--muted-aa)}
.pinv__opts{display:flex;flex-wrap:wrap;gap:.35rem .9rem}
/* Community is the long one — 19 entries. It scrolls inside its own box rather
   than pushing every other facet off a phone screen. */
.pinv__opts--comm{flex-direction:column;flex-wrap:nowrap;gap:.1rem;
  max-height:13rem;overflow:auto;padding-right:.3rem}
.pinv__opt{display:flex;align-items:center;gap:.5rem;min-height:44px;
  font-size:.95rem;cursor:pointer}
.pinv__opt input{width:18px;height:18px;flex:0 0 auto;accent-color:var(--gold)}
.pinv__optN{color:var(--muted-aa);font-weight:400;font-size:.82rem}
.pinv__sel{display:flex;flex-direction:column;gap:.25rem}
.pinv__selLabel{font-size:.82rem;color:var(--muted-aa)}
.pinv__sel select{min-height:44px;padding:0 .6rem;border:1px solid var(--line);
  border-radius:var(--radius);background:var(--card,#fff);font:inherit;font-size:.95rem}
.pinv__planFilter{margin:1.2rem 0 0;padding:1rem 0 0;border-top:1px solid var(--line)}
.pinv__plan{display:flex;flex-wrap:wrap;align-items:baseline;gap:.5rem .8rem;
  padding:.6rem 0;border-top:1px dotted var(--line)}
.pinv__plan:first-child{border-top:0}
.pinv__planName{flex:0 0 8.5rem;font-family:'Jost',sans-serif;text-transform:uppercase;
  letter-spacing:.12em;font-size:.8rem}
.pinv__planComms{display:flex;flex-wrap:wrap;gap:.45rem}
.pinv__chip{display:inline-flex;align-items:center;gap:.4rem;min-height:44px;
  padding:0 .85rem;border:1px solid var(--line);border-radius:999px;background:none;
  cursor:pointer;font-size:.92rem;line-height:1}
.pinv__chip.is-on{border-color:var(--gold,#b6975f);box-shadow:inset 0 0 0 1px var(--gold,#b6975f)}
.pinv__chipN{color:var(--muted-aa);font-size:.82rem}

/* ---- one community ------------------------------------------------------ */
.pinv__comm{max-width:var(--max);margin:0 auto 2rem}
.pinv__comm[hidden]{display:none}
/* THE HEADER IS THE <summary>. list-style:none plus the ::-webkit- rule is what
   removes the native triangle on both engines; the marker is drawn on the right
   instead, where the eye is already going for the counts. min-height is the
   44px tap floor (CLAUDE.md §3) and is load-bearing on a phone, where this row
   is the only way into the community's homes. */
.pinv__commHead{display:flex;flex-wrap:wrap;align-items:baseline;gap:.35rem 1rem;
  margin:0 0 .5rem;padding:.3rem 0 .45rem;border-bottom:1px solid var(--gold);
  min-height:44px;cursor:pointer;list-style:none}
.pinv__commHead::-webkit-details-marker{display:none}
.pinv__commHead::after{content:'+';margin-left:auto;font-size:1.15rem;line-height:1;
  opacity:.55;align-self:center}
.pinv__commDet[open] > .pinv__commHead::after{content:'\2212'}
/* Above the breakpoint every block is open and the marker would be nineteen
   minus signs saying nothing — the disclosure is a phone affordance.
   BOTH STATES, and the second one is the reason this is two selectors: the
   `[open]` rule above carries an attribute selector and out-ranks a bare
   `.pinv__commHead::after`, so hiding only the shut state left every desktop
   block wearing a minus sign. Two individually-correct rules composing into a
   defect neither can see (CLAUDE.md 2026-08-11), caught in the 1440 shot. */
@media (min-width:761px){
  .pinv__commHead::after,
  .pinv__commDet[open] > .pinv__commHead::after{content:none}
}
.pinv__commName{margin:0;font-family:'Cormorant Garamond',serif;font-size:1.45rem;
  font-weight:600;line-height:1.2}
.pinv__commStats{display:flex;flex-wrap:wrap;gap:.3rem .55rem;margin:0}
.pinv__stat{font-family:'Jost',sans-serif;font-size:.72rem;letter-spacing:.1em;
  text-transform:uppercase;color:var(--muted-aa);white-space:nowrap}
.pinv__stat + .pinv__stat::before{content:'\00b7';margin-right:.55rem;opacity:.5}
.pinv__stat--range{color:var(--ink)}
.pinv__stat--reduced{color:var(--reduced)}
.pinv__stat--reduced[hidden]{display:none}
.pinv__stat--age{color:var(--ink);border:1px solid var(--line);border-radius:999px;
  padding:0 .5rem}

.pinv__wrap{max-width:var(--max);margin:0 auto}
.pinv{width:100%;border-collapse:collapse;font-size:1rem;table-layout:auto}
.pinv th{text-align:left;font-family:'Jost',sans-serif;text-transform:uppercase;
  letter-spacing:.12em;font-size:.68rem;color:var(--muted-aa);
  padding:.5rem .55rem;border-bottom:1px solid var(--line);white-space:nowrap}
.pinv td{padding:.6rem .55rem;border-bottom:1px solid var(--line);vertical-align:top}
.pinv__price{font-weight:600;white-space:nowrap}
/* QUIET, AND STRUCK. The strikethrough is the semantic half (an <s> element the
   generator writes, announced as such) and the muted colour is the visual half.
   Neither carries the meaning alone. */
.pinv__was{color:var(--muted-aa);font-size:.9rem;white-space:nowrap}
/* THE TINT IS SUBTLE ON PURPOSE. A discounted home is a fact worth marking, not
   an alarm — and 136 of the 254 rows carry it, so anything louder would paint
   half the page. 4% of the garnet is a wash you notice reading down a column
   and do not notice reading one row. */
.pinv__row--reduced{background:rgba(var(--reduced-rgb),.04)}
.pinv__row--hidden{display:none}
/* SPECIFICITY, NOT REDUNDANCY. The 760px block below turns every row into a
   block (`.pinv tr{display:block}`, one class + one element), which OUT-RANKS a
   bare `.pinv__row--hidden` (one class) — so at 390px the collapse ran, the
   button said "Show all (278)", and all 278 rows rendered anyway: 94,219px of
   page, 111 viewport heights. Desktop was correct the whole time, which is what
   made it invisible. Two individually-correct rules composing into a defect
   neither can see (CLAUDE.md 2026-08-11), caught only by measuring at the
   viewport the rule exists for. This selector carries two classes and beats it
   at every width. */
.pinv tr.pinv__row--hidden{display:none}
.pinv__act{display:flex;justify-content:center;margin:.9rem 0 0}
.pinv__more{min-height:44px;padding:0 1.4rem;border:1px solid var(--line);
  border-radius:999px;background:none;cursor:pointer;font-size:.95rem}
.pinv__empty{max-width:var(--max);margin:0 auto 1.5rem;color:var(--muted-aa);
  font-size:.95rem;line-height:1.7}
.pinv__empty[hidden]{display:none}
.pinv__note{max-width:var(--max);margin:1rem auto 0;color:var(--muted-aa);font-size:.9rem}

/* The jump control areamap.js emits on /partners only — a pin popup and a map
   list row each get one, and it lands on this community's block. Sized to the
   44px floor because it is a primary way into the list on a phone. */
.am-jump{min-height:44px;padding:0 .8rem;border:1px solid var(--line);
  border-radius:999px;background:none;cursor:pointer;
  font-family:'Jost',sans-serif;font-size:.7rem;letter-spacing:.1em;
  text-transform:uppercase;color:var(--ink);white-space:nowrap}
.am-jump:hover{border-color:var(--gold)}

@media (max-width:760px){
  /* ---- THE ROW BECOMES A CARD -------------------------------------------
     Plan + lot on top, price and Was right-aligned beside them, then the
     specs. Ten label/value pairs stacked would be a 300px row; this is a
     two-line card with the specs on one wrapped line under it. */
  .pinv thead{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);
    clip-path:inset(50%);white-space:nowrap}
  .pinv,.pinv tbody{display:block;width:100%}
  /* FIVE COLUMNS, AND EVERY CELL GETS ITS OWN.
     The first version put bed/bath/gar/sq ft/HOA into ONE named area and set
     them `display:inline`, on the theory that they would then flow together on
     a line. They do not: each <td> is still a grid ITEM placed at that area's
     position, so all five painted ON TOP OF EACH OTHER — "BED 3" over "BATH 2"
     over "GAR 2" over "SQ FT 1,672", unreadable, and only visible in a
     screenshot. display:inline changes how a box lays out its own children,
     not where its parent puts it. */
  .pinv tr{display:grid;width:100%;
    grid-template-columns:auto auto auto auto 1fr;
    grid-template-areas:
      'plan plan plan plan price'
      'lot  lot  lot  lot  was'
      'bed  bath gar  sqft hoa'
      'eta  eta  eta  eta  eta';
    align-items:baseline;column-gap:.7rem;row-gap:.12rem;
    padding:.7rem 0;border-bottom:1px solid var(--line)}
  .pinv td{display:block;padding:0;border:0;font-size:.95rem}
  .pinv td:empty{display:none}
  /* The label prefix, for the cells that need one. Plan, lot, price and Was
     are self-evident in position; beds/baths/garage/sqft/eta are not. */
  .pinv td::before{content:attr(data-label) ' ';color:var(--muted-aa);
    font-family:'Jost',sans-serif;text-transform:uppercase;letter-spacing:.08em;
    font-size:.62rem}
  .pinv td[data-label='Floor Plan']{grid-area:plan;font-weight:600;font-size:1.02rem}
  .pinv td[data-label='Floor Plan']::before{content:none}
  .pinv td[data-label='Price']{grid-area:price;text-align:right;font-size:1.02rem}
  .pinv td[data-label='Price']::before{content:none}
  .pinv td[data-label='Lot']{grid-area:lot;color:var(--muted-aa);font-size:.85rem}
  .pinv td[data-label='Was']{grid-area:was;text-align:right;font-size:.85rem}
  .pinv td[data-label='Est. Completion']{grid-area:eta;font-size:.82rem;
    color:var(--muted-aa);margin-top:.1rem}
  /* The specs share one ROW, one grid cell each. */
  .pinv td[data-label='Bed'],
  .pinv td[data-label='Bath'],
  .pinv td[data-label='Gar'],
  .pinv td[data-label='Sq Ft'],
  .pinv td[data-label='HOA/mo']{font-size:.82rem;color:var(--muted-aa);
    white-space:nowrap}
  .pinv td[data-label='Bed']{grid-area:bed}
  .pinv td[data-label='Bath']{grid-area:bath}
  .pinv td[data-label='Gar']{grid-area:gar}
  .pinv td[data-label='Sq Ft']{grid-area:sqft}
  .pinv td[data-label='HOA/mo']{grid-area:hoa;text-align:right}
  /* AN EMPTY SPEC CELL STILL HOLDS ITS COLUMN. `td:empty{display:none}` above
     removes it from the grid, and the four cells after it slide one column
     left — so the six homes with no bed value in the workbook would print
     "BATH 2" under the BED heading's position. Named areas make position
     independent of order, and this keeps the box so the label does not
     reappear on an empty value. */
  .pinv td[data-label='Bed']:empty,
  .pinv td[data-label='Bath']:empty,
  .pinv td[data-label='Gar']:empty,
  .pinv td[data-label='Sq Ft']:empty,
  .pinv td[data-label='HOA/mo']:empty{display:block}
  .pinv td[data-label='Bed']:empty::before,
  .pinv td[data-label='Bath']:empty::before,
  .pinv td[data-label='Gar']:empty::before,
  .pinv td[data-label='Sq Ft']:empty::before,
  .pinv td[data-label='HOA/mo']:empty::before{content:none}
  /* ---- THE HEADER IS THE PAGE AT 390, SO IT IS SIZED LIKE ONE -----------
     MEASURED. With the tables shut, nineteen headers ARE the section, and the
     first version cost 177px each — name and four stats competing for one
     baseline row, wrapping into three lines, plus a 2rem gap sized for a block
     with a table under it. 19 x 177 = 3,305px, which made the rebuilt section
     LONGER than the flat table it replaced (4,398 against 2,551) even with
     every table collapsed.

     Name on its own line, stats on the next, and the gap sized for a header
     rather than for a table. Nothing is removed — every stat still renders. */
  .pinv__comm{margin-bottom:.8rem}
  .pinv__commHead{flex-direction:column;align-items:stretch;gap:.1rem;
    padding:.35rem 0 .4rem;position:relative}
  .pinv__commHead::after{position:absolute;right:0;top:.45rem;margin-left:0}
  .pinv__commName{font-size:1.12rem;padding-right:1.6rem}
  /* NO MIDDOTS ON A PHONE. The separator is an `::before` on every stat after
     the first, which is correct on one line and wrong on three: at 390 the
     stats wrap, and a wrapped line began with a dangling "· 7 PRICE REDUCED".
     A separator that survives the wrap it was never designed for reads as a
     missing word. The gap does the separating instead. */
  .pinv__commStats{gap:.1rem .85rem}
  .pinv__stat{font-size:.68rem}
  .pinv__stat + .pinv__stat::before{content:none;margin-right:0}
  .pinv__planName{flex-basis:100%}
  .pinv__opts--comm{max-height:11rem}
}
/* The live-inventory tile carries its as-of date inside the label, because a
   count with no date on it is a claim (CLAUDE.md §2: every surface showing
   inventory states its data date). */
.partner-strip__stat small{display:block;margin-top:.15rem;font-size:.62rem;
  letter-spacing:.1em;text-transform:none;opacity:.72}

/* ---- /partners only: the map's community list is CAPPED on phones --------
   MEASURED, NOT GUESSED. At 390px `.am-side` renders all 22 communities at
   full length — 3,684px of the map section's 4,722px. That is /map's own
   deliberate mobile design ("mobile renders simplified list-over-map", where
   the list IS the page), and it is the wrong trade on /partners, where the
   list sits above the inventory an agent actually came for.

   THIS IS THE COMPONENT'S OWN DESKTOP BEHAVIOUR, NOT A NEW PATTERN. `.am-side`
   is already `max-height:min(74dvh,720px);overflow:auto` above 760px. Scoped
   to .partners so /map is untouched and keeps the list-first phone layout its
   gates measure. */
@media (max-width:760px){
  body.partners .am-side{max-height:62svh;overflow:auto}
}

/* ====================================================== MYSTERY HOME ========
   Tony's "blur the name, tap to text" pass (2026-09-15), ported from
   ~/rudy-site. One definition, in the shared sheet, because this renders on
   the main tree, on the portal skin and on /a/ — all three load this file, and
   a second copy is a second thing to keep in step.

   THE BLURRED TOKEN IS NOT A BLUR OVER THE NAME. The element contains the
   words "Community name" / "Plan name". There is nothing behind the filter to
   reveal — no devtools trick, no text selection, no copy-paste into a search
   box, nothing for a crawler to index. See build/mystery.py's rule 1.

   IT IS FIXED WIDTH ON PURPOSE. A token that grew with the real name would
   leak the name's LENGTH, and a row of cards would quietly sort itself into
   "the long one" and "the short one". Every token is the same box. `ch` rather
   than `px` so it stays right when the type scale moves. */
.myst{
  display:inline-flex;flex-wrap:wrap;align-items:center;gap:.3rem .4rem;
  min-height:44px;padding:.15rem .3rem;margin:0 0 0 -.3rem;
  max-width:100%;text-align:left;vertical-align:baseline;
  font:inherit;color:inherit;cursor:pointer;
  background:none;border:0;border-radius:var(--radius,2px);
}
.myst:focus-visible{outline:2px solid var(--gold,#b6975f);outline-offset:2px}

/* THE VERTICAL PADDING IS WHAT KEEPS THE BLUR INSIDE ITS OWN BOX. `overflow:
   hidden` truncates the word horizontally — every token is the same 9ch box so
   a row of cards cannot advertise which name is longer — and it clips the
   blur's bleed at the same time. With zero padding-block the top and bottom of
   the blurred glyphs cut flat and it reads as an unfinished smudge rather than
   as a redaction. .12rem is a little over the blur radius. */
.myst__blur{
  display:inline-block;width:9ch;overflow:hidden;white-space:nowrap;
  color:var(--midnight,#14181f);
  background:rgba(var(--gold-soft-rgb,182,151,95),.16);
  border-radius:3px;padding:.12rem .25rem;
  filter:blur(3.4px);opacity:.85;
  user-select:none;-webkit-user-select:none;pointer-events:none;
}

/* THE PILL SAYS WHAT THE CONTROL DOES, IN WORDS, EVERY TIME. An icon on its
   own is a rebus. Purple tint per the addendum — the one place on either site
   that is not gold, so the game reads as a distinct thing rather than as
   another promotional badge. */
.myst__pill{
  display:inline-flex;align-items:center;gap:.25rem;
  font-family:'Jost',sans-serif;font-size:.6875rem;letter-spacing:.06em;
  line-height:1;padding:.3rem .5rem;border-radius:999px;
  background:rgba(109,76,158,.12);color:#5b3f86;white-space:nowrap;
}
.myst:hover .myst__pill{background:rgba(109,76,158,.2)}
.myst__i{flex:0 0 auto}
/* MOBILE SHOWS "tap to solve", DESKTOP SHOWS "Mystery home" (addendum §pill).
   Chosen by POINTER, matching what assets/mystery.js branches on at click
   time, so the label cannot promise a tap on a machine that gets a dialog. */
.myst__pd{display:none}
@media (pointer:fine){
  .myst__pt{display:none}
  .myst__pd{display:inline}
}

/* ONE LINE ABOVE EACH GRID, NEVER PER CARD. */
.myst-rule{
  font-size:.875rem;line-height:1.5;opacity:.75;margin:0 0 1rem;max-width:62ch;
}

/* THE DESKTOP PANEL. One per page, moved and filled by the handler. */
.myst-panel{
  position:fixed;inset:0;z-index:400;display:flex;align-items:center;
  justify-content:center;padding:1.25rem;background:rgba(0,0,0,.55);
}
.myst-panel[hidden]{display:none}
.myst-panel__box{
  background:var(--ivory,#fff);color:var(--ink,#14181f);border-radius:.5rem;
  padding:1.5rem;max-width:30rem;width:100%;box-shadow:0 20px 60px rgba(0,0,0,.35);
}
.myst-panel__h{font-size:1.25rem;margin:0 0 .5rem}
.myst-panel__p{font-size:.9rem;margin:0 0 .75rem;opacity:.8}
/* THE BODY IN A MONO BOX, per the addendum — so the visitor can see exactly
   what they are about to send, character for character. */
.myst-panel__msg{
  font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:.9rem;
  background:rgba(0,0,0,.05);border-radius:.25rem;padding:.75rem;margin:0 0 1rem;
  word-break:break-word;user-select:all;
}
.myst-panel__acts{display:flex;gap:.6rem;flex-wrap:wrap}
.myst-panel__copy,.myst-panel__x{
  font:inherit;font-family:'Jost',sans-serif;font-size:.72rem;
  letter-spacing:.14em;text-transform:uppercase;min-height:44px;
  padding:.6rem 1.1rem;border-radius:var(--radius,2px);cursor:pointer;
}
.myst-panel__copy{background:var(--gold,#b6975f);color:#fff;border:0}
.myst-panel__x{background:none;color:inherit;border:1px solid currentColor}
.myst-panel__copy:focus-visible,.myst-panel__x:focus-visible{
  outline:2px solid var(--gold,#b6975f);outline-offset:2px}

@media (prefers-reduced-motion:reduce){
  .myst,.myst__pill{transition:none}
}
/* The agent decoder's result block (/a/<slug>/solve). */
.al-solve{margin:1.5rem 0 0;padding:1rem 1.25rem;border-left:2px solid var(--gold,#b6975f);
  background:rgba(0,0,0,.02);max-width:36rem}
.al-solve p{margin:.25rem 0;font-size:.98rem}
.al-solve--err{border-left-color:#a33}
.al-solve--wait{opacity:.7}

/* =============================================================================
   AI NATURAL-LANGUAGE SEARCH — the bar and its results (2026-09-12 brief).

   NO NEW TOKENS AND NO NEW CARD RULES. The results grid reuses `.grid.grid--3`
   and the cards ARE `.home`/`.home--light`, so everything below styles the bar,
   the header line and the CTA — a few dozen bytes of layout around components
   that already exist. A second card stylesheet is the drift audit_hero_law.py
   was written to catch, one level down.

   MOBILE-FIRST, LITERALLY: the base rules are the 390px layout (stacked input
   over a full-width button); the widening to a single row is the media query.
   44px minimum on the input and the button, 1rem type in the field so iOS does
   not zoom the page on focus.
   ========================================================================== */
.ais{margin:1.25rem 0 0}
.ais__form{display:block;margin:0 0 1rem}
.ais__label{display:block;font-family:'Jost',sans-serif;font-size:.8rem;
  text-transform:uppercase;letter-spacing:.12em;margin:0 0 .5rem;opacity:.72}
.ais__row{display:flex;flex-direction:column;gap:.6rem}
.ais__input{width:100%;min-height:48px;padding:.75rem 1rem;font-size:1rem;
  font-family:'Jost',sans-serif;color:inherit;background:rgba(var(--white-rgb),.06);
  border:1px solid rgba(var(--gold-soft-rgb),.34);border-radius:var(--radius);
  transition:border-color var(--dur-2) var(--ease)}
.ais__input::placeholder{opacity:.6}
.ais__input:focus-visible{outline:none;border-color:var(--gold,#b6975f)}
.ais__btn{min-height:48px;width:100%}
.ais__results{margin:0}
.ais__head{margin:0 0 .85rem}
.ais__count{margin:0;font-family:'Jost',sans-serif;font-size:1rem;letter-spacing:.02em}
.ais__note,.ais__asof{margin:.3rem 0 0;font-size:.9rem;opacity:.72}
.ais__msg{margin:.5rem 0;font-size:1rem}
.ais__msg--err{color:#a33}
.ais__grid{margin:0}
.ais__more{margin:1rem 0 0;min-height:44px}
.ais__cta{margin:1.25rem 0 0;padding-top:1rem;
  border-top:1px solid rgba(var(--gold-soft-rgb),.22)}
.ais__cta-q{margin:0 0 .6rem;font-size:1rem}
.ais__cta-btn{min-height:44px}
@media (min-width:760px){
  .ais__row{flex-direction:row;align-items:stretch}
  .ais__input{flex:1 1 auto}
  .ais__btn{width:auto;flex:0 0 auto;padding-left:2rem;padding-right:2rem}
}
/* IN THE HERO the bar sits over a photograph, so the field needs a scrim of its
   own rather than the page background it inherits elsewhere. */
.hero .ais__input,.page-hero .ais__input{background:rgba(var(--midnight-deep-rgb),.55);
  color:var(--ivory);backdrop-filter:blur(4px)}
.hero .ais__label,.page-hero .ais__label{color:rgba(var(--ivory-rgb),.82)}
/* The AI bar's own band on a content page. --sec-y is sized for a full section
   with a heading and a grid; a single input row under a hero does not need it,
   and CLAUDE.md §3 caps a page at six viewport heights. */
.section--ais{padding-top:calc(var(--sec-y) * .45);padding-bottom:0}
/* AI search results INSIDE the chat widget. The panel is ~360px wide, so the
   shared `.grid.grid--3` has to collapse to one column here — the cards are the
   build's own and are not restyled; only the track they sit in changes. */
.tgchat-msg--search{max-width:100%;width:100%;background:transparent;padding:0}
.tgchat-msg--search .ais__grid{grid-template-columns:1fr;gap:1rem}
.tgchat-msg--search .ais__count{font-size:.95rem}
.tgchat-msg--search .ais__note,
.tgchat-msg--search .ais__asof{font-size:.84rem}
