/* ═══════════════════════════════════════════════════════════════════════════
   JJNRE — Shared UI tokens & normalization layer
   Loaded LAST in <head> on every public page AND the IDX listing template
   (listing.html), so it propagates to all generated /homes-for-sale/ pages.
   Scope: spacing, alignment, containment, component sizing ONLY.
   Brand colors (#eb1d25 / #003964) and fonts (Bebas Neue / DM Sans) are
   intentionally NOT redefined here — they remain exactly as each page sets them.
   ═══════════════════════════════════════════════════════════════════════════ */

:root{
  /* ── Spacing scale: 4px (0.25rem) base unit ─────────────────────────────── */
  --sp-1:.25rem;  --sp-2:.5rem;   --sp-3:.75rem;  --sp-4:1rem;
  --sp-5:1.25rem; --sp-6:1.5rem;  --sp-7:1.75rem; --sp-8:2rem;
  --sp-10:2.5rem; --sp-12:3rem;   --sp-14:3.5rem; --sp-16:4rem;
  --sp-20:5rem;   --sp-24:6rem;

  /* ── Layout tokens (single source of truth, static + IDX) ───────────────── */
  --section-y:2.5rem;        /* vertical rhythm between page sections            */
  --gutter:2rem;           /* container side gutter, desktop                   */
  --max-w:1200px;          /* content max-width                                */
  --btn-pad:.8rem 2rem;    /* standard button padding                          */
  --modal-pad:2rem 1.75rem;/* modal internal padding (snapped from 1.8rem)     */
}
@media (max-width:768px){
  :root{ --gutter:1.25rem; }   /* one mobile gutter everywhere                 */
}

/* ── 1. SECTION RHYTHM ─────────────────────────────────────────────────────
   ONE vertical rhythm sitewide, symmetric top and bottom, driven by
   --section-y. Matt's call (Jul 2026): every content section gets the same
   number top and bottom, and that number is 40px.

   Why !important: an audit measured FIFTEEN different padding pairs across the
   site, and most of the outliers come from inline style="padding:..." on the
   section elements themselves — 80/80, 64/48, 64/80, 44/56, 44/64, 40/16,
   0/56, 96/80. An inline style beats any stylesheet selector, so a normal rule
   here silently did nothing and the drift survived. Overriding from the shared
   layer is what keeps this to one file instead of edits inside twenty-four.

   Page heroes are the deliberate exception (see the block directly below):
   they sit under an 84px fixed nav, so their top padding has to carry that
   clearance or the headline tucks under the bar. They are symmetric once the
   nav height is accounted for.

   (The previous comment here claimed sections were "standardized UP to the
   flagship 6rem pattern" while --section-y was set to 2.5rem. The comment was
   describing a value the file no longer had.) */
.section, .section-alt, .section-pad,
.ab-sec, .agent-bio-section, .aph-section, .search-agents-section,
.cc-lead, .svc-lead, .wyl-band, .hub-sec, #agents-grid{
  padding-top:var(--section-y) !important;
  padding-bottom:var(--section-y) !important;
}

/* Same rhythm, one notch of specificity higher (element + class = 0,1,1).
   Several pages declare their own !important padding in a <style> block that
   comes AFTER this file, so at equal specificity theirs won on document order.
   Naming the element instead of raising !important keeps the cascade honest.
   Each of these was a measured outlier:
     .cc-lead              44/64   (calculators, page-level !important)
     .svc-lead             44/56   (services, page-level !important)
     .wyl-band              0/56
     .agent-listings-section 80/80 (agent pages)
     #broker-feature        80/80  (homepage)
     .bg-art-light          56/56  (search, faq) */
section.section, section.section-alt, section.section-pad,
section.ab-sec, section.agent-bio-section, section.aph-section,
section.search-agents-section, section.cc-lead, section.svc-lead,
section.wyl-band, section.hub-sec, section.agent-listings-section,
section.bg-art.bg-art-light, section#agents-grid, section#broker-feature{
  padding-top:var(--section-y) !important;
  padding-bottom:var(--section-y) !important;
}

/* Page hero: nav clearance on top, the standard rhythm below. Symmetric in the
   only sense that matters visually — the gap you SEE above the headline equals
   the gap below it, because the first 84px sits behind the fixed nav. Listed
   after the rules above so it wins for the hero only. */
:root{ --nav-h:84px; }
.page-hero,
section.section-pad.bg-art:first-of-type,
section.rv-wrap:first-of-type{
  padding-top:calc(var(--nav-h) + var(--section-y)) !important;
  padding-bottom:var(--section-y) !important;
}

/* ── 2. CONTAINERS — one max-width and one gutter for both page families ──
   .container (static pages, was 0 2rem → 0 1.25rem mobile)
   .wrap      (IDX template + browse, was 0 1.5rem, no mobile step)
   Both now share identical gutters at every breakpoint.
   NOTE: 404.html has its own narrow .wrap card and does NOT load this file. */
.container, .wrap{
  max-width:var(--max-w);
  margin-left:auto; margin-right:auto;
  padding-left:var(--gutter); padding-right:var(--gutter);
}

/* ── 3. COMPONENT NORMALIZATION ────────────────────────────────────────────
   Buttons: agents.html (.7rem 1.75rem) and areas.html (.75rem 1.75rem)
   drift from the site standard (.8rem 2rem). One size for the type. */
.btn{ padding:var(--btn-pad); }

/* Modals: identical internal padding everywhere, snapped to scale, plus a
   small-viewport guard so tall modals scroll instead of clipping. Overlay
   already provides 1.5rem outer padding and flex centering. */
.contact-modal, .email-modal{
  padding:var(--modal-pad);
  max-height:calc(100vh - 3rem);
  overflow-y:auto;
}

/* ── 4. IDX TEMPLATE VALUES SNAPPED TO THE 4px SCALE ──────────────────────
   (template-level: propagates to every generated listing page) */
.lp-agents{ padding:2.5rem 0 3rem; }      /* was 2.6rem 0 2.8rem (off-scale) */

/* ── 5. OVERFLOW & CONTAINMENT HARDENING ──────────────────────────────────
   index.html already sets overflow-x:hidden; the other pages did not.
   Media elements may never exceed their container. height intentionally NOT
   forced to auto, so fixed-size thumbs/avatars keep their dimensions. */
html, body{ overflow-x:hidden; }
img, svg, video, iframe, embed{ max-width:100%; }
table{ max-width:100%; }

/* Dynamic Supabase-fed text (long addresses, remarks, emails) must wrap
   instead of pushing its container wide. Applied at template level. */
.lp-body, .lp-title, .lp-address, .lp-remarks, .lp-doc, .cp-val{
  overflow-wrap:break-word;
}

/* ═══════════════════════════════════════════════════════════════════════════
   MOBILE OPTIMIZATION LAYER — appended by the mobile pass.
   Additive rules only; loaded by all public pages and the IDX template, so
   every generated listing page inherits these on the next deploy.
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── M1. iOS zoom-on-focus prevention ─────────────────────────────────────
   Inputs under 16px make iOS Safari zoom the page when focused. Several
   inputs run .88–.95rem (14–15px). Forced to 16px on small screens only;
   desktop sizing untouched. */
@media (max-width:768px){
  input, select, textarea{ font-size:16px !important; }
}

/* ── M2. Footer collapse ──────────────────────────────────────────────────
   .footer-grid stopped at 2 columns (set at 1024px) all the way down to
   320px, cramping small phones. Single column below 480px. */
@media (max-width:480px){
  .footer-grid{ grid-template-columns:1fr; }
}

/* ── M3. IDX rec-reel arrows: no bleed on mobile ──────────────────────────
   .lp-rec-arrow.prev/.next sit 10px OUTSIDE the container (left/right:-10px).
   With narrow viewports they clip at the screen edge. Pulled inside the
   container on small screens. Template-level: applies to every listing page. */
@media (max-width:768px){
  .lp-rec-arrow.prev{ left:4px; }
  .lp-rec-arrow.next{ right:4px; }
}

/* ── M4. Touch-target ergonomics (44x44 minimum on touch devices) ─────────
   Applied only for coarse pointers so desktop visuals are unchanged.
   - Carousel/nav circle buttons were 38–40px  → 44px
   - Hamburger was ~34px total                 → padding raised to 44px
   - Modal close was a bare ~26px glyph        → padded to ~44px
   - 6px pagination dots get an invisible expanded hit area (::after),
     visual size unchanged. */
@media (pointer:coarse){
  .area-nav-btn, .fact-nav-btn, .rm-nav-btn{ width:44px; height:44px; }
  .nav-burger{ padding:11px; }
  .contact-modal-close{ padding:.6rem; }
  .area-dot, .rm-dot{ position:relative; }
  .area-dot::after, .rm-dot::after{ content:''; position:absolute; inset:-9px; }
}

/* ═══════════════════════════════════════════════════════════════════════════
   MOBILE PASS 2 — accessibility & width fixes + featured-listings carousel
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── M5. Homepage featured listings: horizontal swipe on mobile ───────────
   Was a vertical grid stack; also its 290px min column overflowed 320px
   screens. Now a swipeable snap carousel; desktop grid unchanged. */
@media (max-width:900px){
  .fl-grid{
    display:flex; overflow-x:auto; scroll-snap-type:x mandatory;
    gap:1rem; padding:0 0 .9rem;
    -webkit-overflow-scrolling:touch; scrollbar-width:thin;
  }
  .fl-card{ flex:0 0 82%; max-width:330px; scroll-snap-align:center; }
  /* Subtitle was white-space:nowrap (inline) and overflowed below ~740px */
  .fl-subtitle{ white-space:normal !important; }
}

/* ── M6. Listing template: dynamic values may wrap on small screens ───────
   These carried white-space:nowrap with no ellipsis; long Supabase values
   (prices, quick stats, income figures) forced horizontal overflow. */
@media (max-width:768px){
  .lp-price, .lp-qs .v, .lp-qs .l,
  .lp-income-card .v, .lp-income-card .l,
  .lp-rec-price{ white-space:normal; }
}

/* ── M7. Listing address is now an <h1> (was a div; page had no h1). ──────
   listing.html has no universal margin reset, so pin the UA h1 defaults. */
h1.lp-addr{ margin:.3rem 0 0; font-size:1.15rem; font-weight:600; line-height:1.3; }
@media (max-width:768px){ h1.lp-addr{ font-size:1.05rem; } }

/* ── M8. Skip link (added to listing.html + browse.html; matches index) ── */
.skip-link{ position:absolute; left:-999px; top:0; z-index:10000; background:#003964;
  color:#fff; padding:10px 18px; border-radius:0 0 6px 0; font-family:inherit;
  font-size:.9rem; text-decoration:none; }
.skip-link:focus{ left:0; }

/* ── M9. MOBILE ONLY: service-page agent marquee forced ~5600px layout width ─
   .astrip-track is width:max-content (64 tiles) inside a grid item with no
   minimum-size guard, so the intro grid, and the whole page, laid out at the
   track's width on phones. Zeroing the grid item's automatic minimum contains
   it. Desktop untouched. */
@media (max-width:768px){
  .astrip-section{ min-width:0; max-width:100%; overflow:hidden; }
}

/* ── Sitewide house signature: trailing period on eyebrow labels ──
   Matches the CRM's UPPERCASE-period style. Labels are already
   text-transform:uppercase per-page; this adds the period via ::after
   so no HTML content changes and no layout risk. */
.label::after,
.county-label::after{ content:"."; }

/* ── Background art system removed (audit Jul 2026) ───────────────────────
   The .bg-art / .bg-art-light glow + inline-SVG pattern system lived here in
   about 60 lines of gradients and data-URI SVG, and a block at the end of this
   file already disabled every one of those rules with display:none !important.
   It was shipped to every visitor on every page and rendered nothing. Removed
   together with that disabling block. The layout-neutral parts (.bg-art
   position/overflow, and .bg-art>* z-index) are kept below because sections
   still carry the class and rely on the stacking context.
   Reversible from git history if the art is ever wanted back. */
.bg-art{position:relative;overflow:hidden;}
.bg-art > *{position:relative;z-index:1;}

/* ── Canonical compact footer (identical sitewide) ───────────────────────
   Overrides per-page footer grids so every page shares one balanced 4-column
   layout: Brand / Services / Explore / Company. Loaded last, so it wins. */
footer .footer-grid{
  display:grid !important;
  grid-template-columns:1.6fr 1fr 1fr 1fr !important;
  gap:2.5rem !important;
  justify-items:center;text-align:center;
  align-items:start;
  margin-bottom:2.5rem !important;
  max-width:var(--max-w);margin-left:auto;margin-right:auto;
}
footer .footer-col ul{list-style:none;padding:0;margin:0;}
footer .footer-col li{margin-bottom:.55rem;}
@media (max-width:900px){
  footer .footer-grid{grid-template-columns:1fr 1fr !important;gap:2rem !important;}
  footer .footer-brand{grid-column:1 / -1;}
}
@media (max-width:560px){
  footer .footer-grid{grid-template-columns:1fr !important;}
}

/* ── Agent bio review carousel (beside the biography) ────────────────────
   When an agent has reviews, the bio section becomes a two-column layout:
   biography on one side, a clickable arrow-navigated review card on the other.
   Injected by the per-page reviews script; if the agent has no reviews the
   layout is untouched. */
.agent-bio-section.has-reviews .container{
  display:grid;grid-template-columns:1.15fr .85fr;gap:3rem;align-items:center;
  max-width:1100px;margin-left:auto;margin-right:auto;text-align:left;
}
.agent-bio-section.has-reviews .bio-text{min-width:0;}
.agent-bio-section.has-reviews .bio-text .label,
.agent-bio-section.has-reviews .bio-text h2,
.agent-bio-section.has-reviews .bio-text .red-rule{text-align:left !important;margin-left:0 !important;margin-right:0 !important;}
.agent-reviews{min-width:0;}
.agent-reviews-head{font-size:.6rem;letter-spacing:.22em;text-transform:uppercase;color:var(--red,#eb1d25);margin-bottom:.9rem;font-weight:600;text-align:left;}
.arev-card{position:relative;background:var(--navy-mid,#002d50);border:1px solid rgba(255,255,255,.1);border-radius:14px;padding:1.7rem 1.6rem;min-height:230px;display:flex;flex-direction:column;}
.arev-stars{color:var(--red,#eb1d25);font-size:.9rem;letter-spacing:.14em;margin-bottom:.8rem;}
.arev-text{font-size:.84rem;line-height:1.7;color:rgba(245,242,237,.82);font-weight:300;margin-bottom:1rem;flex:1;}
.arev-name{font-size:.8rem;font-weight:600;color:#fff;}
.arev-meta{font-size:.66rem;color:rgba(245,242,237,.45);margin-top:.3rem;line-height:1.4;}
.arev-nav{display:flex;align-items:center;justify-content:space-between;margin-top:1.1rem;gap:1rem;}
.arev-dots{display:flex;gap:.35rem;flex-wrap:wrap;}
.arev-dot{width:6px;height:6px;border-radius:50%;background:rgba(255,255,255,.25);transition:background .2s;}
.arev-dot.on{background:var(--red,#eb1d25);}
.arev-btns{display:flex;gap:.5rem;}
.arev-btn{width:38px;height:38px;border-radius:50%;border:1px solid rgba(255,255,255,.2);background:transparent;color:#fff;cursor:pointer;font-size:1rem;line-height:1;display:flex;align-items:center;justify-content:center;transition:border-color .2s,background .2s,transform .2s;}
.arev-btn:hover{border-color:var(--red,#eb1d25);background:rgba(235,29,37,.12);}
.arev-btn:active{transform:scale(.94);}
@media(max-width:820px){
  .agent-bio-section.has-reviews .container{grid-template-columns:1fr;gap:2rem;}
}

/* ══════════════════════════════════════════════════════════════════════════
   Mobile audit (Jul 2026): tap-target sizing at phone widths.
   Verified against live geometry: footer links measured 16px tall, agent
   contact links 17-18px, category chips 29px, market-insight dots 6px, and
   Leaflet zoom controls 30px. Fixes below expand hit areas mostly through
   padding with compensating negative margins so the visual rhythm holds.
   !important is used only where later inline page styles would otherwise
   win the cascade at equal specificity.
   ══════════════════════════════════════════════════════════════════════════ */
@media (max-width:768px){
  /* Footer link lists and phone number: comfortable one-thumb rows */
  .footer-col a{ display:inline-block; padding:.5rem .6rem !important; margin:-.3rem 0; }
  .footer-phone{ display:inline-block; padding:.5rem .75rem !important; }
  /* display:inline-flex is load-bearing: several pages render .social-icon as
     display:inline, where width/height do not apply and this 44px tap-target
     fix was silently inert (measured 22x21 / 18x21 on about, commercial,
     calculators). */
  .social-icon{ width:44px !important; height:44px !important;
    display:inline-flex !important; align-items:center; justify-content:center; }

  /* Fact-strip carousel dots: 6x6 targets on buy, sell, about, management,
     land, services and calculators. Same approach as .rv-dot on the homepage —
     the visible dot is untouched, a centred ::after supplies a real 24x24
     target, and the gap widens so pitch is 6+18=24px and adjacent targets
     touch without overlapping. !important on the gap because each page
     declares .fact-dots{gap:.5rem} inline, after this file, at equal
     specificity. */
  .fact-dots{ gap:18px !important; }
  .fact-dot{ position:relative; }
  .fact-dot::after{ content:''; position:absolute; top:50%; left:50%;
    transform:translate(-50%,-50%); width:24px; height:24px; }

  /* Agent directory cards and agent page hero: phone and email links */
  .agent-card-contacts a, .agent-contact-line a, .aph-contact-val a, .bc-value a{
    display:inline-block; padding:.55rem .45rem !important; margin:-.35rem 0;
  }

  /* Category chips on agent pages and the listings hub */
  .nl-chip{ padding:.7rem 1rem !important; }

  /* Market insights carousel: larger dots plus a deeper invisible hit area */
  .rm-dot{ width:11px !important; height:11px !important; }
  .rm-dot::after{ inset:-13px !important; }
  .rm-town-toggle{ padding:.8rem .9rem !important; }

  /* Leaflet map zoom controls on the listing page */
  .leaflet-control-zoom a{
    width:40px !important; height:40px !important; line-height:40px !important;
  }
}

/* Decorative topo/line motifs stay hidden (the pseudo-element art they sat
   alongside has been deleted above rather than merely disabled). */
.hero-topo,
.bg-art-lines,
.bg-art > .bg-art-lines{ display:none !important; }

/* ═══════════════════════════════════════════════════════════════════════════
   DESKTOP NAVIGATION FIT  (audit Jul 2026)
   The bar carries 9 primary links + the logo + a Phone / e-Mail / Sign-In
   cluster. Measured intrinsic single-line width on the live page:
       gap 2.25rem -> 1512px      gap 1.5rem -> 1416px      gap 1rem -> 1352px
   So no amount of gap tightening fits a 1280px laptop — the item count is the
   constraint, not the spacing.

   Before this block the burger did not appear until 768px, which left every
   width from 769-1500px with no layout that fit: seven of twelve links wrapped
   to two lines, "Contact Us" broke in half and spilled outside its red pill,
   "e-Mail" and "Sign-In" split at their hyphens, and below 1152px the logo was
   pushed off-screen entirely. 1440, 1366 and 1512 are the most common laptop
   widths in the US, so this was the default experience, not an edge case.

   Three states, each verified by width sweep (360-1920px, no wrap, no clip):
     >= 1441px  all nine links, original 2.25rem gap
     1200-1440  gap tightens; Phone / e-Mail collapse into the Sign-In menu's
                neighbourhood (both actions still reachable via Contact Us and
                the drawer), keeping one line
     <= 1199px  burger drawer
   Loaded last, so it wins over each page's own inline nav rules without
   editing thirty files. ══════════════════════════════════════════════════ */
.nav-links a, .nav-info a{ white-space:nowrap; }
.nav-links li{ white-space:nowrap; }

/* #nav is justify-content:space-between, so at exactly the intrinsic width its
   three children (logo / links / info) abut with ZERO clearance — at 1512px the
   logo lockup sat flush against "Search MLS" and the Contact Us pill against
   "Phone". A column-gap guarantees they can never touch at any width. */
#nav{ column-gap:1.5rem; }

/* ── Never let the bar's parts shrink ──────────────────────────────────────
   #nav is a flex row and its children default to flex-shrink:1, so when the
   contents do not fit they SQUASH instead of overflowing. That failure is
   invisible to the obvious check: `scrollWidth > clientWidth` stays false
   because nothing ever overflows. Measured at 1280px while tuning the
   breakpoints below — the logo rendered **1.05px wide** (natural 69.09) with
   the image fully loaded, and every width probe reported "ok".

   flex-shrink:0 converts that silent squash into real overflow, which IS
   detectable, and which the breakpoints below are sized to prevent. Keep this
   rule and the breakpoints together: relaxing one without re-measuring the
   other is what produced the 1.05px logo. */
#nav > *{ flex-shrink:0; }

/* Intrinsic width is 1512px at the original 2.25rem gap — i.e. no slack at all
   on a 1512px MacBook. Tighten from 1600px so there is always breathing room
   rather than a flush fit. */
@media (max-width:1600px){
  #nav .nav-links{ gap:1.25rem !important; }
}

/* ── Breakpoints, measured 2026-08-09 ──────────────────────────────────────
   Measured by forcing each configuration visible and binary-searching the
   narrowest viewport at which the logo is NOT squashed, nothing overflows,
   the link row does not wrap and no two children collide.

   Taken twice: once for the short-lived JJNRE mark, then again from scratch
   when the full Jonathan J. Nelson Real Estate lockup was restored on Matt's
   call. The lockup is 112px wide at 56px tall against the mark's 69px, so
   every number moved and none of them could be adjusted by arithmetic:

                                                  gap 20px   gap 16px
     full bar (links + Phone + e-Mail + Sign-In)    1390       1358
     links + Sign-In only                           1211       1179

   The ladder that follows, each step with real slack:

     >= 1601    gap 36px, full bar          needs ~1518
     1501-1600  gap 20px, full bar          needs  1390
     1371-1500  gap 16px, full bar          needs  1358   (13px spare at 1371)
     1200-1370  gap 16px, links + Sign-In   needs  1179   (21px spare at 1200)
     <= 1199    burger

   Do not re-tune these by eye, and do not scale them when the logo changes —
   re-run the probe. Three separate width probes gave wrong answers on this
   header: see the flex-shrink note above; note that `#nav #navBurger` (two IDs)
   outranks `#nav .nav-burger`, so a measurement override must match the ID
   selector or the burger silently rejoins the row and adds 44px; and note that
   a probe comparing rendered width against a HARDCODED expected height reports
   every size except that one as squashed. */
@media (max-width:1500px){
  #nav .nav-links{ gap:1rem !important; }
}
@media (max-width:1370px){
  /* Phone and e-Mail are the first things to go: both open the same contact
     modal the Contact Us pill opens. Direct children only — the Sign-In
     trigger lives inside .nav-signin and is preserved. */
  #nav .nav-info > a, #nav .nav-info .nav-sep{ display:none !important; }
}
@media (max-width:1199px){
  #nav .nav-links, #nav .nav-info{ display:none !important; }
  #nav .nav-burger, #nav #navBurger{ display:flex !important; align-items:center; justify-content:center; }

  /* Mobile nav drawer: pages already set min-height:44px on these links, but
     anchors are inline by default so min-height never applied. Flex makes the
     existing rule take effect and vertically centers the label.

     This rule used to live in the max-width:768px block above, while the
     burger — and therefore the drawer — appears all the way up to this
     breakpoint. So
     on every tablet in portrait and every small laptop the drawer opened with
     no touch-target minimum at all. Measured on the live site before moving
     it, same page, four widths:

        390px  burger=true   min-height 44px  display flex
        900px  burger=true   min-height auto  display block   <- no target
       1100px  burger=true   min-height auto  display block   <- no target
       1280px  burger=false  min-height auto  display block   (drawer unused)

     768px is a phone breakpoint; 1199px is where this component actually
     stops. The rule belongs with the component, not with the phone. */
  .nav-drawer a{ display:flex; align-items:center; min-height:44px; }
}
/* The burger is 44x37 at its own padding; the tap target needs 44x44. */
#nav .nav-burger, #nav #navBurger{ min-width:44px; min-height:44px; }

/* ── Canonical header: logo mark + active state ────────────────────────────
   Both live here rather than in 41 inline <style> blocks, which is what let
   them drift in the first place. `#nav .nav-logo-img` is specificity (0,1,1)
   and beats every page's `.nav-logo-img` (0,1,0) regardless of source order,
   so the mark is the same size everywhere without editing 41 rules.

   The active state had THREE implementations: `.active` (styled), `.nav-active`
   (styled NOWHERE — so about.html, calculators.html and services.html shipped
   a current-page highlight that rendered nothing), and no marker at all on the
   rest. One rule, matching both the class and the ARIA attribute, so a page
   that sets only `aria-current="page"` still highlights. */
#nav .nav-logo{ display:flex; align-items:center; }
/* The full Jonathan J. Nelson Real Estate lockup, back at its original 56px on
   Matt's call — the shortened JJNRE mark was tried and rejected. That mark file
   (images/brand/jjnre-mark.svg, 22 KB) has been deleted: it was shipped to
   every visitor and referenced by nothing. The last four references were the
   _collections pages, corrected in 473ba1d. Recover it with
   `git show 473ba1d^:images/brand/jjnre-mark.svg > images/brand/jjnre-mark.svg`
   if the shortened mark is ever revisited.

   The usable height is 83px, not 84: #nav is height:84px WITH a 1px bottom
   border under `*{box-sizing:border-box}`, so an EVEN logo height cannot centre
   in it. 56px measures 13.5 above / 13.5 below plus the border, which is as
   close as an even value gets; 55px would be exactly 14/14 but shrinks the
   lockup for no visible gain. Keeping 56 to match what shipped before.

   Changing this height changes the bar's width budget — re-measure the
   breakpoints below with the squash-aware probe before assuming they still
   hold. */
#nav .nav-logo-img{ height:56px; width:auto; display:block; }
#nav .nav-links a{ position:relative; }
#nav .nav-links a.active,
#nav .nav-links a[aria-current="page"]{ color:#fff; }
#nav .nav-links a.active::after,
#nav .nav-links a[aria-current="page"]::after{
  content:''; position:absolute; bottom:-4px; left:0; right:0;
  height:2px; background:#eb1d25; border-radius:1px;
}

/* ── Header link hit targets ───────────────────────────────────────────────
   Measured on the live homepage 2026-08-09: all 15 header links rendered a
   16px-tall hit box. The clickable area was the text line itself — no padding
   — against the 44px minimum. On a trackpad that is a fiddly target; with any
   motor impairment it is a barrier.

   Padding the anchors would have been the obvious fix and the wrong one: it
   grows the boxes, which moves the active underline (anchored to `bottom`) and
   changes the bar's intrinsic width, invalidating every breakpoint measured
   above. A transparent ::before overlay costs nothing in layout — the anchor
   box, the underline and the width budget are all untouched — and the overlay
   is part of the anchor, so clicks land on the link.

   Bounded by each anchor's own left/right, so adjacent links cannot overlap
   each other. ::after paints after ::before, so the active underline stays
   visible. */
#nav .nav-links a, #nav .nav-info a, #nav .nav-signin-trigger{ position:relative; }
#nav .nav-links a::before,
#nav .nav-info a::before,
#nav .nav-signin-trigger::before{
  content:''; position:absolute; left:0; right:0;
  top:50%; transform:translateY(-50%);
  height:44px;
}

/* ── The mobile drawer was clipping its own last five links ────────────────
   Every page opens the drawer with `max-height:520px; overflow:hidden`. The
   content is 872px tall. Measured on the LIVE homepage and about.html at
   390x844 on 2026-08-09: box 520px, scrollHeight 872px, and elementFromPoint
   over "Our Agents", "Contact Us", "Public Access", "Client Portal" and
   "Agent Portal" returned the element behind the drawer — i.e. the primary
   CTA and all three sign-in entry points were unreachable on a phone.

   A fixed pixel cap cannot be right here: the list is 12 items and the
   viewport varies. Cap it to the space actually below the bar and let it
   scroll instead. Selector is `#navDrawer.open` = (1,1,0), which outranks
   every page's `.nav-drawer.open` = (0,2,0) because the ID component is
   compared first — so it wins regardless of source order. That is the point:
   one rule here instead of an edit to 33 inline <style> blocks. */
#navDrawer.open{
  max-height:calc(100dvh - 84px);
  overflow-y:auto;
  overscroll-behavior:contain;
  -webkit-overflow-scrolling:touch;
}

/* Close control injected by js/jj-nav.js — the drawer previously had no visible
   way out other than choosing a link. Positioned clear of the first nav item.

   There used to be a `.nav-drawer{ position:relative; }` here, added only to
   give that absolutely-positioned close button a containing block. It was a
   live bug: every page declares `.nav-drawer{ position:fixed }` in its own
   inline <style>, and on any page where this stylesheet is linked AFTER that
   block — blog.html and home-value.html — equal specificity meant `relative`
   won. The drawer dropped into normal flow, rendered below the fold and
   underneath <main>, and its links became unreachable. Measured on the live
   site 2026-08-09: drawer visible at 390x462 with computed position:relative,
   elementFromPoint over every link returned MAIN, 0 of 13 links clickable.
   `position:fixed` is itself a containing block for absolute children, so the
   close button never needed this rule. Do not reinstate it. */
.nav-drawer .nav-drawer-close{
  position:absolute; top:.75rem; right:1rem;
  width:44px; height:44px; display:flex; align-items:center; justify-content:center;
  background:transparent; border:0; cursor:pointer;
  color:#F5F2ED; font-size:1.35rem; line-height:1; padding:0;
  border-radius:50%; transition:color .2s, background .2s;
}
.nav-drawer .nav-drawer-close:hover,
.nav-drawer .nav-drawer-close:focus-visible{ color:#eb1d25; background:rgba(255,255,255,.07); }

/* ...and hidden, now that the header is uniform. The drawer opens BELOW the
   84px bar, so the burger stays on screen the whole time, animates into an X
   and is already a 44x44 target. The injected control therefore drew a SECOND
   X immediately under the first — visible in the 390px render of every page.
   Rule kept above rather than deleted so this stays one line to reverse.
   Escape, tap-outside-to-close and the focus trap are unaffected: those are
   bound in js/jj-nav.js independently of this button. */
.nav-drawer .nav-drawer-close{ display:none; }

/* ── Reduced motion + print: scroll-reveal must never hide content ─────────
   Every page hides .reveal elements at opacity:0 and only shows them when an
   IntersectionObserver fires. That works for a normal visitor (verified), but
   printing or saving to PDF never scrolls, so every section below the hero
   came out blank — and a JS stall or a reduced-motion preference left content
   permanently invisible with no fallback. Two rules, no layout change. */
@media print{
  .reveal{ opacity:1 !important; transform:none !important; }
}
@media (prefers-reduced-motion:reduce){
  .reveal{ opacity:1 !important; transform:none !important; transition:none !important; }
  *,*::before,*::after{ animation-duration:.001ms !important; animation-iteration-count:1 !important; scroll-behavior:auto !important; }
}

/* ── Remaining sub-44px tap targets on touch devices ───────────────────────
   Measured at 390px on the live site. The existing pointer:coarse block above
   covers .area-dot and .rm-dot; these were missed.
     .fact-dot   6x6   (carousel pagination on the six service pages)
     .jjp-x     26x27  (promo bar close, 22 pages)
     .astrip-caption-link  x16
   The dots keep their 6px visual size — the hit area is expanded with a
   transparent ::after, exactly as .area-dot already does. */
@media (pointer:coarse){
  .fact-dot{ position:relative; }
  .fact-dot::after{ content:''; position:absolute; inset:-13px; }
  .jjp-x{ min-width:44px; min-height:44px; display:inline-flex; align-items:center; justify-content:center; }
  .astrip-caption-link{ display:inline-block; padding:.6rem .4rem; margin:-.5rem 0; }
}

/* ── Anchors inherit their colour ──────────────────────────────────────────
   .jjp-link, .ab-card, .hero-stat, .fl-card and .nav-logo compute to the UA
   default rgb(0,0,238) because nothing sets a colour on the anchor itself.
   Nothing shows it today — every one of them wraps its text in a styled child
   — but any direct text node added to those anchors would render browser-blue
   on navy. Scoped to card/stat wrappers so real body links are untouched. */
.jjp-link, .ab-card, .hero-stat, .fl-card, .nav-logo, .svt-stat-link,
.footer-logo-link, .agent-card, .lst-card, .nl-card, .cl-card, .mi-card, .rv-card{
  color:inherit;
}

/* ═══════════════════════════════════════════════════════════════════════════
   EYEBROW LABEL LEGIBILITY  (audit Jul 2026 — Matt's call)

   The small uppercase kickers above section headings ("WHAT WE DO",
   "THE BROKERAGE", "BUYER QUESTIONS") were set in brand red #eb1d25 on navy.
   Measured against the four navies actually used on the site:

       #eb1d25 on #003964 navy       2.68 : 1
       #eb1d25 on #002d50 navy-mid   3.18 : 1
       #eb1d25 on #001f38 navy-deep  3.78 : 1
       #eb1d25 on #000f1e navy-dk    4.36 : 1

   WCAG AA wants 4.5:1 for text this size (these run 9-10px), so every one of
   them failed — on 23 pages. Getting brand red itself to 4.5:1 on navy means
   going to #f77c80 or lighter, which is a pink and is no longer the brand.

   So the TEXT becomes cream and the red stays where it reads as an accent
   rather than as content: the .red-rule beneath the eyebrow, the underline
   marks, the buttons. Cream #F5F2ED on the darkest navy measures 15.9:1 and on
   the lightest 8.4:1 — comfortably AA, and AAA on most of the site.

   Scoped to the label classes only. Headings, rules, buttons, badges, prices
   and every other use of #eb1d25 are untouched.
   ═══════════════════════════════════════════════════════════════════════════ */
.label,
.hub-eyebrow,
.hub-eyebrow-0,
.ab-eyebrow,
.hero-eyebrow,
.page-hero-eyebrow,
.aph-eyebrow,
.hv-eyebrow,
.county-label,
.rm-county-label,
.fact-card-label,
.cred-label,
.s-label,
.agent-reviews-head{
  color:#F5F2ED !important;
}

/* Footer column headings were red at 65% opacity on #000f1e — 2.30:1, the
   worst pair measured anywhere on the site. Same treatment, held slightly
   back from full cream so the column titles still sit below the links in
   visual weight rather than above them. */
footer .footer-h{
  color:rgba(245,242,237,.86) !important;
}

/* The Sign-In trigger and its caret were red on navy (3.78:1) — this is a
   navigation control, not an accent. */
#nav .nav-signin-trigger,
#nav .nav-signin-trigger .cx{
  color:#F5F2ED !important;
}
#nav .nav-signin-trigger:hover,
#nav .nav-signin-trigger:focus-visible{ color:#eb1d25 !important; }

/* Muted greys that also missed AA on navy, measured on the live site:
     copyright line          rgba(122,136,153,.55)  2.40 : 1
     "Browse all MLS →"      rgba(122,136,153,.75)  3.48 : 1
     breadcrumb links        rgba(245,242,237,.35)  2.93 : 1
     breadcrumb separators   rgba(245,242,237,.20)  1.79 : 1
   Raised to opacities that clear 4.5:1 while staying visibly secondary. */
footer .footer-bottom p,
footer .footer-lic p{ color:rgba(245,242,237,.72) !important; }
.bc-label, .breadcrumb a, .bc a{ color:rgba(245,242,237,.78) !important; }
.breadcrumb span, .bc span{ color:rgba(245,242,237,.55) !important; }

/* ═══════════════════════════════════════════════════════════════════════════
   BRAND RED — AA WITHOUT CHANGING THE BRAND  (audit Jul 2026, Matt's call)

   #eb1d25 against white measures 4.435:1. AA wants 4.5:1 for text under
   18.66px bold / 24px regular. It misses by 1.3% — so white-on-red buttons and
   red-on-white links both failed, on every page.

   --red-aa is #ea1924: contrast 4.504:1, deltaE 0.55 from #eb1d25. deltaE
   below 1.0 is the threshold at which two colours are indistinguishable to the
   human eye even side by side, so this is the same red to look at. It is used
   ONLY where red meets white/cream and a contrast ratio is therefore measured.
   #eb1d25 remains the brand colour and is untouched everywhere else — rules,
   underlines, borders, large display type, hover states.

   NOTE for whoever edits this next: 4.504 clears 4.5 by 0.09%. There is no
   headroom. Do not add opacity, a gradient, or an off-white label on top of
   --red-aa without re-measuring, or it drops back under.
   ═══════════════════════════════════════════════════════════════════════════ */
:root{ --red-aa:#ea1924; }

/* ── Red fills that carry light text ───────────────────────────────────── */
.nav-cta,
.jjp-badge, .jjp-cta,
.btn-red, a.btn.btn-red, button.btn.btn-red,
.cta-band,
.about-badge, .broker-badge,
.mi-cat,
.rv-chip.is-active,
.rm-tab.on,
.blog-filter.active,
.nl-badge.pending,
.cl-card-badge.cl-badge-pending{
  background-color:var(--red-aa) !important;
}

/* ── Red text sitting on white or cream ────────────────────────────────── */
.btn-white, a.btn.btn-white,
.pp-num,
.cc-group-h,
.faq-q-num{
  color:var(--red-aa) !important;
}

/* ═══════════════════════════════════════════════════════════════════════════
   SMALL RED TEXT ON NAVY → CREAM
   Same decision as the eyebrow labels: red stays an accent (rules, fills,
   borders, large display type), and anything small enough to need 4.5:1
   becomes cream. On navy, red measures 2.68-4.36:1 at these sizes; cream
   measures 8.4-15.9:1.

   Large display numerals and the hero wordmark are deliberately NOT in this
   list — at 24px+, or 18.66px+ bold, AA only asks for 3:1, which red already
   clears on the darker navies. The brand's loud red moments are intact.
   ═══════════════════════════════════════════════════════════════════════════ */
.ft,
.arw,
.astrip-caption-link,
.kna-num,
.rv-stars,
.nt-active,
.rm-tab-count,
.rm-town-toggle,
.mi-link,
.rv-agent,
.cp-label,
.ab-more,
.ab-cred-k,
.svt-tab-name,
.step-n,
.diff-item-n,
.ey,
.agent-card-title,
.agent-contact-dot,
.rv-more,
.nl-reset,
.eyebrow,
.cat,
.read,
.svc-card-btn-r{
  color:#F5F2ED !important;
}

/* Hover keeps the red relationship — the accent returns on interaction, where
   a moment of lower contrast on a deliberate pointer action is acceptable. */
.astrip-caption-link:hover, .mi-link:hover, .ab-more:hover,
.rv-more:hover, .nl-reset:hover, .svc-card-btn-r:hover,
.rm-town-toggle:hover{ color:#eb1d25 !important; }

/* ═══════════════════════════════════════════════════════════════════════════
   MUTED TEXT — the second contrast family
   Not red: the grey and low-opacity-cream secondary text. Measured on the live
   site, all below AA. Replacement values chosen as the LIGHTEST step that
   clears 4.5:1 against the worst background it actually appears on, so the
   text stays visibly secondary rather than being promoted to body weight.
     #7a8899 on navy  3.28  ->  #96a5b6  4.72
     #8593a3 on white 3.13  ->  #6b7480  4.74
     cream @ .40-.50 on navy 3.43-4.63  ->  .62 minimum
     white @ .80 on the red fill 3.25   ->  full opacity 4.50
   ═══════════════════════════════════════════════════════════════════════════ */
.svc-pill, .rm-tab, .wyl-census-h, .wyl-cstat-l,
.astrip-name, .srch-astrip-name, .hero-sub{
  color:#96a5b6 !important;
}
.nl-foot, .cl-card-meta{ color:#6b7480 !important; }
.rv-meta, .hub-cnt, .hub-cnt-0, .agent-card-role, .aph-contact-label{
  color:rgba(245,242,237,.72) !important;
}
/* Body copy reversed out of a red band was rgba(255,255,255,.8). */
.cta-band p, .cta-band .p, .about-badge p{ color:#fff !important; }

/* ── Corrections found by re-measuring after the first pass ──────────────── */

/* REGRESSION FIX: .nl-reset sits on a WHITE panel, not on navy, so the cream
   rule above rendered it cream-on-cream — ratio 1.0, completely invisible.
   Red-on-white is the correct treatment here and --red-aa clears 4.5. */
.nl-reset{ color:var(--red-aa) !important; }
.nl-reset:hover{ color:#c41018 !important; }

/* Red links on light panels that the first pass did not reach. */
.areas-more-link, .area-all-link,
a.faq-more, .cc-more, .lp-doc a{ color:var(--red-aa) !important; }

/* Region/segment toggles use a bare .on class on some pages rather than the
   component class the first pass targeted. */
button.on, .seg-btn.on, .toggle-btn.on{ background-color:var(--red-aa) !important; }

/* Drawer and utility links rendered in red on navy at 12.8px. */
.nav-drawer a[href*="auth"], .nav-drawer a[href*="portal"],
.footer-util a, .util-link{ color:#F5F2ED !important; }
.nav-drawer a[href*="auth"]:hover, .nav-drawer a[href*="portal"]:hover{ color:#eb1d25 !important; }

/* ── Second correction pass: red/cream on LIGHT panels ────────────────────
   Re-measuring caught two rules from the pass above landing on light panels
   instead of navy, which is worse than the original defect:
     .mi-link    cream on white  1.12  (invisible)
     .rm-tab.on  grey on the red fill 1.79
   And --red-aa clears 4.5 on white (4.50) but only reaches 4.03 on cream
   (#F5F2ED), because cream is darker than white. Pushing the red far enough to
   clear cream needs #df051d — deltaE 3.5, a visibly different red, which
   defeats the point. So red text on CREAM becomes navy instead: #003964 on
   cream measures 9.71:1 and is already a brand colour. */
.mi-link, .nl-reset, .fresh-tag, .cl-fresh, .lp-fresh{
  color:#003964 !important;
}
.mi-link:hover, .nl-reset:hover{ color:#eb1d25 !important; }

/* A segment toggle in its .on state has a red fill, so its label must be white
   regardless of the muted-grey colour it carries when off. */
.rm-tab.on, button.on, .seg-btn.on, .toggle-btn.on,
.blog-filter.active, .rv-chip.is-active{ color:#fff !important; }

/* ── Final contrast pass: unclassed elements, targeted by context ──────────
   These carry no class of their own, so each is reached through its parent.
   Measured values before the fix are in the comments. */

/* Homepage featured-listings kicker, red on cream — 3.97. Navy on cream is
   10.63, and this sits directly above a navy heading. */
#featured .container > .reveal > span{ color:#003964 !important; }

/* Consent/legal links inside form labels, red on navy — 3.18. */
.fg label span a{ color:#F5F2ED !important; text-decoration:underline; }
.fg label span a:hover{ color:#eb1d25 !important; }

/* Census ZIP readout on the services page, red on navy-mid — 3.18. */
#wylCensusZip{ color:#F5F2ED !important; }

/* In-answer links inside FAQ accordions, red on white — 4.44. */
.faq-a a{ color:var(--red-aa) !important; }

/* Broker card designation line, red on navy-deep — 3.78. */
.broker-designation span{ color:#F5F2ED !important; }

/* Agent card "View Profile →", cream at 50% on navy — 4.22. */
.agent-card-footer a{ color:rgba(245,242,237,.82) !important; }
.agent-card-footer a:hover{ color:#eb1d25 !important; }

/* "Meet the team →" on the search page, red on navy-mid — 3.18; and the
   supporting paragraph, grey #6b7a8d on navy-mid — 3.21. */
.search-agents-inner p{ color:#96a5b6 !important; }
.search-agents-inner p a{ color:#F5F2ED !important; text-decoration:underline; }
.search-agents-inner p a:hover{ color:#eb1d25 !important; }

/* ── Deliberately NOT changed — decorative, not content ───────────────────
   .step-pane-n and .diff-pane-n are oversized ghost numerals drawn at 13-22%
   opacity as background texture behind their panels (51px and 88px). They
   measure ~1:1 by design and are not read as text; WCAG 1.4.3 exempts purely
   decorative content. Likewise .fl-photonav arrows, which sit over listing
   photography and carry their own shadow. Flagged by the automated sweep,
   verified by eye, left alone on purpose. */

/* Hero search submit: the red fill comes from the <button>, the label from the
   <span> inside it, so the fill rule above did not reach it. */
.hero-search button{ background-color:var(--red-aa) !important; }

/* Section kickers on the light cream sections use the same shape as #featured
   but live under different section ids — red on cream measured 3.97. */
/* Scoped to the two CREAM sections by id. An earlier attempt used a generic
   'section.section-pad > .container > .reveal > span' and turned the .label
   eyebrows on the NAVY sections navy-on-navy (1.19:1) — worse than the defect
   it was fixing. Cream sections only. */
#insights .container > .reveal > span,
#featured .container > .reveal > span{ color:#003964 !important; }

/* ── Listing status badges: one palette on every surface ──────────────────
   search.html and listings.html were corrected in place, but the same badge
   is declared independently in index.html (.nl-badge and .fl-badge), on both
   agent pages, and in browse.html — five separate copies of the same
   component, so the homepage still showed the old web-green while the search
   results showed navy. Normalised here so all of them agree.
   navy = available, red = under contract, grey = closed.
   (deal-room, portal and home-value keep #1b8749 — those are success/status
   indicators in app chrome, a different meaning from a listing badge.) */
/* These rules are authoritative, but they are NOT the only copy. index.html,
   browse.html, listings.html, search.html and the 16 agent pages each declare
   the same selectors inline, because the base `.nl-badge` rule sets color:#fff
   and no background — so if this stylesheet ever fails to load, the inline
   declaration is all that stands between a status badge and white text on a
   transparent box over a photograph.

   Those copies had drifted to the old web-green #1b8749 and amber #a96522.
   They never RENDERED (the !important here wins), which made them look like
   dead code — but deleting them would have removed the fallback, so they were
   corrected to these values instead. crm/test/badgecolors.test.mjs asserts the
   inline copies still agree with this file; change a colour here and that test
   tells you which pages to change with it. */
.nl-badge.active, .fl-badge.active, .cl-badge-active,
.lst-badge.active, .lp-status-active{ background:#003964 !important; }
.nl-badge.pending, .fl-badge.pending, .cl-badge-pending,
.lst-badge.pending, .lp-status-pending{ background:var(--red-aa) !important; }
.nl-badge.closed, .fl-badge.closed, .lst-badge.closed{ background:#6b7787 !important; }

/* ── M-24. A visible focus indicator, site-wide ─────────────────────────────

   Measured on the live contact pop-out (about.html, and the same markup on 40
   other pages):

     outline            none
     unfocused border   rgba(245,242,237,.16)  1px
     focused border     #eb1d25                1px
     box-shadow         none

   So the entire keyboard focus indicator was a ONE-PIXEL border changing
   colour. The colour itself is fine — 3.64:1 against the #0e2236 card, past
   the 3:1 floor — but WCAG 2.2 SC 2.4.13 asks for at least 2px of perimeter,
   and a 1px line on a dark card is genuinely hard to find. 49 root pages carry
   an `outline:none` somewhere; 41 carry this exact rule.

   WHY A DOUBLE RING RATHER THAN `outline:3px solid var(--red)`:
   this site puts controls on white cards, on #003964 navy, on #0e2236, and on
   solid #eb1d25 buttons. A single red ring vanishes on the red buttons and a
   single navy one vanishes on the navy nav. White inner + navy outer is
   visible on every one of those surfaces, because whichever half matches the
   background, the other half does not.

   :focus-visible, so this only ever appears for keyboard and assistive-tech
   users — a mouse click still shows nothing, which is why the original rules
   were written this way in the first place.

   !important is not decoration here. css/jjnre-ui.css is a <link> in <head>
   and the rules it overrides live in inline <style> blocks further down the
   document, so at equal specificity the page wins. That is the same reason
   the 95 other !important declarations in this file exist (see M-20). */
a:focus-visible,
button:focus-visible,
input:focus-visible,
select:focus-visible,
textarea:focus-visible,
summary:focus-visible,
[tabindex]:focus-visible,
[role="button"]:focus-visible {
  outline: 2px solid #fff !important;
  outline-offset: 1px !important;
  box-shadow: 0 0 0 5px #003964 !important;
  border-radius: 3px;
}

/* The skip link paints its own high-contrast treatment and sits on the very
   top-left corner, where a 5px navy ring would be clipped. */
.skip-link:focus-visible { box-shadow: none !important; outline: 2px solid #eb1d25 !important; }

/* ---------------------------------------------------------------------------
   Branded checkboxes and radios.

   Only this stylesheet carries them, and it is linked on the public pages
   only — crm.html, portal.html, auth.html, public-access.html and sign.html
   do not link it, so their 38 native controls are untouched.

   Geometry matches the LINE BOX of the native control, not its box. Blink
   paints 13x13 with margin 3px 3px 3px 4px — 19px of vertical space. 13px is
   a small hit target, so this is 14x14 with margin 2.5px 3px 2.5px 4px, which
   is also 19px. Measured on /search with all 166 city checkboxes revealed,
   against the native control as the baseline:

     14x14, margin 3px   3px 3px   4px  ->  159 of 166 rows grew 1px
     14x14, margin 2px   3px 2px   4px  ->  159 of 166 rows shrank 1px
     14x14, margin 2.5px 3px 2.5px 4px  ->    0 of 166 rows moved

   vertical-align made no difference to any of those three; the row height is
   the margin box. Six visible labels is not a sample — the first prototype
   passed on those six at 25x23 and still pushed the FEATURES labels out of
   line. Reveal the collapsed lists before believing a layout measurement.

   Escape hatch: add class="jj-native" to any control that must keep the
   platform appearance.
   --------------------------------------------------------------------------- */
input[type="checkbox"]:not(.jj-native),
input[type="radio"]:not(.jj-native) {
  -webkit-appearance: none;
  appearance: none;
  box-sizing: border-box;
  width: 14px;
  height: 14px;
  min-width: 14px;
  min-height: 14px;
  flex: 0 0 auto;
  margin: 2.5px 3px 2.5px 4px;
  vertical-align: -2px;
  /* #818b98 on #fff is 3.45:1 — clears WCAG 1.4.11 for a UI boundary. */
  border: 1.5px solid #818b98;
  border-radius: 4px;
  background: #fff;
  cursor: pointer;
  display: inline-grid;
  place-content: center;
  padding: 0;
  transition: background-color .13s ease, border-color .13s ease, box-shadow .13s ease;
}
input[type="radio"]:not(.jj-native) { border-radius: 50%; }

input[type="checkbox"]:not(.jj-native):hover:not(:disabled),
input[type="radio"]:not(.jj-native):hover:not(:disabled) { border-color: #003964; }

input[type="checkbox"]:not(.jj-native)::before {
  content: "";
  width: 9px;
  height: 9px;
  transform: scale(0);
  transition: transform .13s ease;
  background: #fff;
  clip-path: polygon(14% 45%, 0 60%, 39% 100%, 100% 22%, 85% 8%, 39% 70%);
}
input[type="radio"]:not(.jj-native)::before {
  content: "";
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: #fff;
  transform: scale(0);
  transition: transform .13s ease;
}

input[type="checkbox"]:not(.jj-native):checked,
input[type="radio"]:not(.jj-native):checked { background: #eb1d25; border-color: #eb1d25; }
input[type="checkbox"]:not(.jj-native):checked::before,
input[type="radio"]:not(.jj-native):checked::before { transform: scale(1); }

input[type="checkbox"]:not(.jj-native):indeterminate { background: #003964; border-color: #003964; }
input[type="checkbox"]:not(.jj-native):indeterminate::before {
  transform: scale(1);
  clip-path: none;
  width: 8px;
  height: 2px;
  border-radius: 1px;
}

/* No :focus-visible rule here on purpose. The site-wide ring above
   (input:focus-visible, white outline + navy shadow, both !important) already
   covers these controls and would win anyway; a second treatment would only be
   dead CSS that reads as if it were live. */

input[type="checkbox"]:not(.jj-native):disabled,
input[type="radio"]:not(.jj-native):disabled {
  opacity: .45;
  cursor: not-allowed;
  background: #f2f5f9;
}
/* :disabled is declared after :checked at the SAME specificity (0,3,1), so on
   its own it takes the fill back off a disabled-but-checked control — and the
   tick is white, so it lands on #f2f5f9 and the box renders EMPTY. A control
   that reads as unchecked when it is checked is the one failure this widget
   must not have; greyed-out is fine, wrong is not. Caught in a rendered
   screenshot of the state, not in the CSS — it looks correct in source.
   Keep the fill and let opacity do the greying. */
input[type="checkbox"]:not(.jj-native):disabled:checked,
input[type="radio"]:not(.jj-native):disabled:checked {
  background: #eb1d25;
  border-color: #eb1d25;
}
input[type="checkbox"]:not(.jj-native):disabled:indeterminate {
  background: #003964;
  border-color: #003964;
}

@media (prefers-reduced-motion: reduce) {
  input[type="checkbox"]:not(.jj-native),
  input[type="radio"]:not(.jj-native),
  input[type="checkbox"]:not(.jj-native)::before,
  input[type="radio"]:not(.jj-native)::before { transition: none; }
}

/* ═══════════════════════════════════════════════════════════════════════════
   FULL-SITE PASS — every sitemap route, not a sample

   The passes above were measured on a subset. This one ran axe over all 171
   sitemap routes at 1440px and 390px (342 scans, 0 failures) and grouped the
   results by the rule responsible rather than by element. It found 20 rules
   the earlier passes had not reached, covering 1,372 elements.

   Nothing here invents a colour. Every value is one this file already
   established: cream #F5F2ED for small red on navy, var(--red-aa) for red on
   white and for red fills carrying white text, #6b7480 for grey on white,
   #96a5b6 for grey on navy, and rgba(245,242,237,.72) as the cream floor.
   #eb1d25 and #003964 are untouched.
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── Red on navy, below the large-text threshold → cream ─────────────────── */

/* "← All Service Areas" on all 123 area pages — 3.77 at 11.52px. */
.area-nav a{ color:#F5F2ED !important; }
.area-nav a:hover{ color:#eb1d25 !important; }

/* "Read more ↓" on every area page — 3.77 at 14.4px. */
summary.bio-toggle{ color:#F5F2ED !important; }
summary.bio-toggle:hover{ color:#eb1d25 !important; }

/* Agent header role line ("Realtor") on all 16 agent pages — 3.77 at 16px. */
.aph-title{ color:#F5F2ED !important; }

/* Calculator group headings — 3.72. These already carried var(--red-aa), but
   --red-aa is the white/cream token: it clears 4.5 on white and only reaches
   3.72 on this navy. Same mistake as the .nl-reset regression above, in the
   other direction. On navy the answer is cream. */
.cc-group-h{ color:#F5F2ED !important; }

/* Blog market-table headers — 3.30 at 10.88px, the lowest red-on-navy left. */
.article-table th, .article-body table th{ color:#F5F2ED !important; }

/* Blog sub-headings that sit on the navy band — 3.77 at 18.88px regular
   (18.66px only relaxes to 3:1 when bold, and these are not). */
.article-body h3{ color:#F5F2ED !important; }

/* Community counter in the hub controls — 4.12 at 11.52px. */
.hub-count b{ color:#F5F2ED !important; }

/* ── Red fills carrying white text → var(--red-aa) ───────────────────────── */
/* White on #eb1d25 measures 4.43; on --red-aa it is 4.50. Both of these are
   fills the earlier list did not name. */
.aph-badge{ background-color:var(--red-aa) !important; }
/* CORRECTION: this was written as a blanket .ac-tabs label rule, which put a
   red fill behind the UNSELECTED tabs too — their label is #7a8899 grey, so
   grey-on-red measured 1.24:1 across 96 area pages, worse than the 4.43 it
   replaced. The fill belongs only on the checked tab, scoped the same way
   build/generate-areas.mjs scopes its own. */
#aclv-town:checked ~ .ac-tabs label[for=aclv-town],
#aclv-zip:checked ~ .ac-tabs label[for=aclv-zip],
#aclv-county:checked ~ .ac-tabs label[for=aclv-county]{
  background-color:var(--red-aa) !important;
}

/* ── Red on white → var(--red-aa) ────────────────────────────────────────── */
/* The sitemap page's section headings measure 4.43 on white — 0.07 short.
   An earlier version of this pass reached them with .bg-art-light h2, but that
   is a generic section utility: it repainted every numbered heading on the
   legal pages red, and put --red-aa on navy (2.63) and on cream (4.03), both
   worse than what it replaced. Left at 4.43 rather than risk a third selector
   for two elements. */

/* ── The grey families, using the replacements this file already chose ───── */

/* Filter labels above every search control — #8593a3 on white, 3.13 at
   9.92px, 320 elements across 16 pages. This file already recorded the fix
   (#8593a3 on white 3.13 -> #6b7480 4.74); these labels were simply never in
   the selector list. */
.nl-field > label, .nl-row label{ color:#6b7480 !important; }

/* Review attribution line on agent pages — cream at .45 on navy, 3.70.
   .72 is the floor this file set for cream on navy. */
.arev-meta{ color:rgba(245,242,237,.72) !important; }

/* "No active listings" panel on 24 area pages, and the blog table footnote —
   #7a8899 on navy, 4.42. */
.area-empty, .article-body .note, .article-table .note{ color:#96a5b6 !important; }

/* ── Left alone on purpose, re-confirmed by this pass ─────────────────────
   .step-pane-n is rgba(235,29,37,.22) at 51.2px with aria-hidden="true" — a
   decorative watermark numeral, exempt under WCAG 1.4.3, and already
   documented above. The full sweep flagged it again; it is still not a defect.
   .hub-sec-live b (16px, weight 900, 3.78) is a display numeral and stays red:
   it is one of the "loud red moments" this file deliberately protects. It sits
   0.72 under 4.5 and clears 3:1 comfortably. */

/* Legal-page body links were browser-default #0000EE with no underline, which
   fails link-in-text-block: colour alone cannot mark a link. --red-aa is
   4.50:1 on the white card, and the underline satisfies 1.4.1 on its own. */
.pp-card a{ color:var(--red-aa) !important; text-decoration:underline !important; }
.pp-card a:hover{ color:#c41018 !important; }

/* Blog "← All Market Insights" — 3.77 at 11.52px, the .area-nav a of the
   market posts. */
.article-nav a{ color:#F5F2ED !important; }
.article-nav a:hover{ color:#eb1d25 !important; }

/* Per-region listing counts on the areas hub — 3.77 at 16px/900. Sixteen
   pixels bold is under WCAG's 18.66px bold threshold, so 4.5 applies and red
   does not reach it. Its twin in the controls bar (.hub-count b) was already
   converted in this pass. */
.hub-sec-live b{ color:#F5F2ED !important; }

/* /sitemap-page.html "Communities We Serve" — red on WHITE at 16.8px, 4.43.
   .sm-communities is unique to that page and its panel is white, so --red-aa
   (4.50 on white) is the right token here. An earlier attempt reached this
   heading with .bg-art-light h2 and hit the legal pages instead; the class is
   scoped deliberately this time. */
.sm-communities h2{ color:var(--red-aa) !important; }
