/* =============================================================================
   Jennifer Realty — Bradenton, Florida
   One stylesheet, no preprocessor, no utility framework.

   Everything visual reads from the tokens in :root. Rebranding this site means
   changing values there and nowhere else. Each color carries the contrast ratio
   it satisfies, because that comment is the only thing that keeps a palette
   honest once someone starts nudging values.
   ========================================================================== */

:root {
  /* ---- neutrals, taken from the design reference ---------------------- */
  --bone:    #efeeec;  /* page ground */
  --bone-2:  #e5e3e0;  /* deeper stone, alternating bands */
  --paper:   #ffffff;  /* cards, panels, form fields */
  --ink:     #242424;  /* body text          — 12.8:1 on bone */
  --ink-2:   #575756;  /* secondary text     — 6.0:1 on bone */
  --muted:   #646260;  /* quiet text         — 5.1:1 on bone (AA) */
  --line:    #dcdad7;  /* hairlines */
  --sand:    #817872;  /* warm taupe: decorative, large text on dark (3.6:1) */
  --sand-l:  #bcbcba;  /* light warm grey */
  --deep:    #1f2422;  /* dark section panels */
  --deep-2:  #2b312e;  /* raised surface on dark */

  /* ---- the single accent ---------------------------------------------- */
  /* One color, used in one region. It never appears on nav or buttons. */
  --gulf:    #6f9ea3;  /* on dark grounds    — 5.3:1 on deep */
  --gulf-d:  #2c5f66;  /* links, small text on light — 6.0:1 on bone */

  /* ---- type ------------------------------------------------------------ */
  --display: "Cormorant Garamond", Georgia, "Times New Roman", serif;
  --label:   "Cinzel", Georgia, serif;
  --ui:      "Montserrat", system-ui, -apple-system, "Segoe UI", sans-serif;
  /* The one script face, loaded on index.html only and used for exactly one
     word. Italianno is a fine chancery hand: light even strokes and restrained
     flourishes, which is why it sits quietly next to Cinzel and Cormorant
     where a high contrast copperplate did not. Its strokes are genuinely thin,
     so it only works large. Never set it below display size. */
  --script:  "Italianno", "Apple Chancery", cursive;

  /* ---- shadows: diffuse only, always a large blur and negative spread --- */
  --shadow-sm: 0 2px 10px rgba(31, 36, 34, .05);
  --shadow-md: 0 18px 50px -22px rgba(31, 36, 34, .30);
  --shadow-lg: 0 40px 90px -40px rgba(31, 36, 34, .42);

  /* ---- geometry -------------------------------------------------------- */
  --wrap: 1180px;
  --ease: cubic-bezier(.22, .61, .36, 1);
  --radius: 3px;
}

/* A custom property only animates if the browser knows its type. Without this
   registration --pz would jump between values instead of tweening, and the
   hover scale on card imagery would snap. */
@property --pz {
  syntax: '<number>';
  inherits: false;
  initial-value: 1;
}

/* ============================================================ base ======= */

*, *::before, *::after { box-sizing: border-box; }

html { scroll-behavior: smooth; }
@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }

/* The nav is fixed, so an anchor target would otherwise come to rest
   underneath it. Nav height plus a little air. This matters more since About
   became a section on the home page rather than a page of its own: every nav
   and footer About link on the site is now a jump to #about. */
html { scroll-padding-top: 92px; }

body {
  margin: 0;
  background: var(--bone);
  color: var(--ink);
  font-family: var(--ui);
  font-size: 1rem;
  font-weight: 400;
  line-height: 1.7;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  overflow-x: hidden;
}
body.no-scroll { overflow: hidden; }

img { max-width: 100%; height: auto; display: block; }

h1, h2, h3, h4 {
  font-family: var(--display);
  font-weight: 500;
  line-height: 1.1;
  margin: 0;
  letter-spacing: .002em;
}

p { margin: 0 0 1.1em; }
p:last-child { margin-bottom: 0; }

a { color: var(--gulf-d); text-decoration-thickness: 1px; text-underline-offset: 3px; }
a:hover { color: var(--ink); }

/* Skip link. Present on every page, first focusable element in the document. */
.skip-link {
  position: absolute; left: 0; top: 0; z-index: 999;
  transform: translateY(-120%);
  background: var(--ink); color: var(--bone);
  padding: .8rem 1.4rem;
  font-family: var(--ui); font-size: .8rem; font-weight: 600;
  letter-spacing: .1em; text-transform: uppercase; text-decoration: none;
  transition: transform .2s var(--ease);
}
.skip-link:focus { transform: translateY(0); color: var(--bone); }

/* Global focus ring. Retuned per background further down. */
:where(a, button, input, textarea, select, summary, [tabindex]):focus-visible {
  outline: 3px solid var(--gulf-d);
  outline-offset: 2px;
  border-radius: var(--radius);
}

/* ============================================================ layout ===== */

.wrap { width: min(100% - 3rem, var(--wrap)); margin-inline: auto; }

.section { padding: clamp(4.5rem, 9vw, 8rem) 0; }
.section--tight { padding: clamp(3rem, 6vw, 5rem) 0; }
.section--bone2 { background: var(--bone-2); }
.section--dark { background: var(--deep); color: var(--bone); }
.section--dark h1, .section--dark h2, .section--dark h3 { color: var(--bone); }

/* The all-caps letterspaced label. Cinzel is a Roman inscriptional face and
   this is the one job it is actually designed for. */
.eyebrow {
  font-family: var(--label);
  font-size: .68rem;
  font-weight: 600;
  letter-spacing: .3em;
  text-transform: uppercase;
  color: var(--gulf-d);
  margin: 0 0 1.1rem;
}
.section--dark .eyebrow,
.hero .eyebrow { color: var(--gulf); }

.section__title {
  font-size: clamp(1.9rem, 4.2vw, 3.2rem);
  max-width: 22ch;
}
.section__lede {
  font-size: 1.03rem;
  color: var(--ink-2);
  max-width: 58ch;
  margin-top: 1.2rem;
}
.section--dark .section__lede { color: var(--sand-l); }

.section__head { margin-bottom: clamp(2.5rem, 5vw, 4rem); }
.section__head--center { text-align: center; }
.section__head--center .section__title,
.section__head--center .section__lede { margin-inline: auto; }

/* ============================================================ buttons ==== */

.btn {
  display: inline-flex; align-items: center; justify-content: center;
  gap: .5rem;
  min-height: 46px;                 /* 44px touch target floor, plus a hair */
  padding: .85rem 1.9rem;
  border: 1px solid transparent;
  border-radius: var(--radius);
  font-family: var(--ui);
  font-size: .78rem;
  font-weight: 600;
  letter-spacing: .13em;
  text-transform: uppercase;
  text-decoration: none;
  cursor: pointer;
  transition: transform .3s var(--ease), background .3s var(--ease),
              color .3s var(--ease), border-color .3s var(--ease),
              box-shadow .3s var(--ease);
}
.btn:hover { transform: translateY(-3px); }
.btn:active { transform: translateY(-1px); }

.btn--solid {
  background: var(--ink);
  color: var(--bone);
  border-color: var(--ink);
}
.btn--solid:hover { background: var(--deep); color: var(--bone); box-shadow: var(--shadow-md); }

.btn--ghost {
  background: transparent;
  color: var(--ink);
  border-color: var(--ink);
}
.btn--ghost:hover { background: var(--ink); color: var(--bone); }

/* On dark grounds the ghost button inverts. */
.section--dark .btn--ghost,
.hero .btn--ghost { color: var(--bone); border-color: rgba(239, 238, 236, .55); }
.section--dark .btn--ghost:hover,
.hero .btn--ghost:hover { background: var(--bone); color: var(--ink); border-color: var(--bone); }

.section--dark .btn--solid,
.hero .btn--solid { background: var(--bone); color: var(--ink); border-color: var(--bone); }
.section--dark .btn--solid:hover,
.hero .btn--solid:hover { background: var(--paper); color: var(--ink); }

.btn[disabled] { opacity: .5; cursor: not-allowed; transform: none; }

/* ============================================================ nav ======== */

.nav {
  position: fixed; inset: 0 0 auto 0; z-index: 100;
  transition: background .45s var(--ease), box-shadow .45s var(--ease),
              border-color .45s var(--ease);
  background: var(--bone);
  border-bottom: 1px solid var(--line);
}
.nav.is-scrolled { box-shadow: var(--shadow-sm); }

/* Over a dark hero: transparent shell, light text. */
.nav.is-top {
  background: transparent;
  border-bottom-color: transparent;
  box-shadow: none;
}

.nav__inner {
  width: min(100% - 3rem, 1340px);
  margin-inline: auto;
  min-height: 76px;
  display: flex; align-items: center; gap: 1.5rem;
}

.nav__brand {
  display: flex; align-items: center; gap: .85rem;
  text-decoration: none; color: inherit;
  margin-right: auto;
}

/* The J and B monogram. Loaded as a background rather than inline markup so
   the outlines are fetched and cached once instead of being repeated in all 27
   pages, and as a background rather than an image element so the state swap
   below is a one line CSS change. Two files rather than a mask, because a mask
   would flatten the gulf rule to a single color and that hairline is the whole
   character of the mark. Aspect ratio is fixed to the artwork: 1.2 to 1. */
.nav__brand-mark {
  flex: none;
  width: 36px; height: 30px;
  background: url("../assets/logo-mark.svg") center / contain no-repeat;
}
.nav.is-top .nav__brand-mark { background-image: url("../assets/logo-mark-light.svg"); }
/* Narrow phones: the wordmark keeps the room and the monogram steps aside. */
@media (max-width: 400px) { .nav__brand-mark { display: none; } }

/* Both lines hold to one line. The monogram takes real width out of a nav that
   was already tight at 1280, and a wrapped wordmark next to a mark reads as a
   broken logo rather than as two lines of type. */
.nav__brand-name {
  display: block;
  font-family: var(--label);
  font-size: 1.02rem;
  font-weight: 600;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--ink);
  line-height: 1.2;
  white-space: nowrap;
}
.nav__brand-sub {
  display: block;
  font-family: var(--ui);
  font-size: .58rem;
  font-weight: 500;
  letter-spacing: .19em;
  text-transform: uppercase;
  color: var(--muted);
  margin-top: .25rem;
  white-space: nowrap;
}
.nav.is-top .nav__brand-name { color: var(--bone); }
.nav.is-top .nav__brand-sub { color: rgba(239, 238, 236, .82); }

.nav__links { display: flex; align-items: center; gap: 1.9rem; }
/* The monogram costs the brand block about 50px, which is enough to push the
   brokerage line onto a second row on a 1280 laptop. Buy it back from the link
   gap instead: the firm name has to sit with the phone number under
   61J2-10.025, and a wrapped one reads as detached from it. Desktop nav only,
   since below 1040 the links become the stacked panel. */
@media (min-width: 1281px) and (max-width: 1560px) {
  .nav__links { gap: 1.35rem; }
}
/* The language pill costs the bar roughly another 130px, on top of what the
   monogram already took. Below this the Spanish labels, which are the longest
   set the nav has to carry, need the gap tightened further still. Measured
   against the Spanish nav, which is the binding case: PROPIEDADES, COMPRAR,
   VENDER, ZONAS, MUDARSE, SOBRE MI, CONTACTO. */
@media (min-width: 1281px) and (max-width: 1340px) {
  .nav__links { gap: .95rem; }
  .nav__links a { font-size: .7rem; letter-spacing: .1em; }
  .nav__lang { padding: .42rem .62rem; }
  .nav__lang span { letter-spacing: .08em; }
}
.nav__links a {
  font-size: .74rem;
  font-weight: 500;
  letter-spacing: .13em;
  text-transform: uppercase;
  text-decoration: none;
  color: var(--ink-2);
  position: relative;
  padding: .5rem 0;
  transition: color .3s var(--ease);
  /* A nav label is a label. Spanish runs about a fifth longer than English,
     and SOBRE MI was breaking across two lines inside its own link, which
     dragged the whole bar to two rows. */
  white-space: nowrap;
}
.nav__links a::after {
  content: ""; position: absolute; left: 0; bottom: 0;
  width: 100%; height: 1px;
  background: currentColor;
  transform: scaleX(0); transform-origin: right;
  transition: transform .4s var(--ease);
}
.nav__links a:hover { color: var(--ink); }
.nav__links a:hover::after { transform: scaleX(1); transform-origin: left; }
.nav.is-top .nav__links a { color: rgba(239, 238, 236, .9); }
.nav.is-top .nav__links a:hover { color: var(--bone); }

/* The phone CTA. Florida rule 61J2-10.025 requires the brokerage name to sit
   adjacent to any point of contact, which is why this is a two-line block and
   not a bare phone number. */
.nav__cta {
  display: flex; flex-direction: column; align-items: flex-end;
  text-decoration: none; line-height: 1.25;
  padding: .4rem 0;
}
.nav__cta-num {
  font-family: var(--ui);
  font-size: .84rem; font-weight: 600; letter-spacing: .04em;
  color: var(--ink);
  transition: color .3s var(--ease);
}
.nav__cta-firm {
  font-size: .55rem; font-weight: 500; letter-spacing: .16em;
  text-transform: uppercase; color: var(--muted);
  /* Never wraps. 61J2-10.025 wants the brokerage name adjacent to the point
     of contact, and a firm name broken across two lines under a phone number
     reads as detached from it rather than attached. Same reasoning, and the
     same rule, as .nav__brand-sub above. */
  white-space: nowrap;
}
.nav__cta:hover .nav__cta-num { color: var(--gulf-d); }
.nav.is-top .nav__cta-num { color: var(--bone); }
.nav.is-top .nav__cta-firm { color: rgba(239, 238, 236, .78); }
.nav.is-top .nav__cta:hover .nav__cta-num { color: var(--gulf); }

.nav__burger {
  display: none;
  width: 46px; height: 46px;
  background: none; border: 0; cursor: pointer;
  padding: 11px 9px;
  flex-direction: column; justify-content: space-between;
}
.nav__burger span {
  display: block; height: 1.5px; width: 100%;
  background: var(--ink);
  transition: transform .35s var(--ease), opacity .25s var(--ease),
              background .35s var(--ease);
}
.nav.is-top .nav__burger span { background: var(--bone); }
.nav.is-open .nav__burger span:nth-child(1) { transform: translateY(10px) rotate(45deg); }
.nav.is-open .nav__burger span:nth-child(2) { opacity: 0; }
.nav.is-open .nav__burger span:nth-child(3) { transform: translateY(-10px) rotate(-45deg); }

/* The bar collapses into the burger here.

   This was 1040px until 2026-08-19, when the language pill was added. The
   pill costs the bar about 130px, and below roughly 1280 that was enough to
   push the phone block clean off the right edge: on English at 1100px the
   number and the brokerage line disappeared entirely, and on Spanish, whose
   seven labels are the longest set the nav carries, the links themselves
   collided and the block was clipped mid word at 1210.

   1280 was measured, not guessed. Spanish renders cleanly at 1280 and breaks
   by 1210, so the seam sits at the top of that band. Spanish is the binding
   case at every width, which is why the number is set against it and not
   against the shorter English labels.

   Losing the phone number off the edge is not a cosmetic problem. 61J2-10.025
   wants the brokerage name adjacent to the point of contact, and neither is
   adjacent to anything once it is outside the viewport. The burger keeps both
   reachable, so the seam moves rather than the compliance. */
@media (max-width: 1280px) {
  .nav__burger { display: flex; }
  .nav__cta { display: none; }
  .nav__links {
    position: fixed; inset: 76px 0 auto 0;
    flex-direction: column; align-items: stretch;
    gap: 0;
    background: var(--bone);
    border-bottom: 1px solid var(--line);
    padding: .5rem 1.5rem 1.5rem;
    transform: translateY(-130%);
    transition: transform .5s var(--ease);
    box-shadow: var(--shadow-md);
    max-height: calc(100dvh - 76px);
    overflow-y: auto;
  }
  .nav.is-open .nav__links { transform: translateY(0); }
  .nav.is-open { background: var(--bone); border-bottom-color: var(--line); }
  .nav.is-open .nav__brand-name { color: var(--ink); }
  .nav.is-open .nav__brand-sub { color: var(--muted); }
  .nav.is-open .nav__burger span { background: var(--ink); }
  /* The open menu drops a bone panel behind a header that may still be flagged
     is-top. Same specificity as the is-top rule but later in the file, so the
     dark mark wins back and does not vanish into the light panel. */
  .nav.is-open .nav__brand-mark { background-image: url("../assets/logo-mark.svg"); }
  .nav__links a {
    color: var(--ink-2);
    padding: 1rem 0;
    border-bottom: 1px solid var(--line);
    font-size: .82rem;
  }
  .nav__links a:last-child { border-bottom: 0; }
  .nav.is-top .nav__links a { color: var(--ink-2); }
  .nav__links a::after { display: none; }
}

/* ============================================================ hero ======= */

/* The scrub hero is a tall scroll track. .hero__sticky pins inside it while
   the frame sequence plays. On narrow screens the whole mechanism is replaced
   by a plain looping video, because iOS Safari cannot scrub reliably. */
.hero {
  position: relative;
  background: var(--deep);
  color: var(--bone);
}
/* Total scroll distance for the scrubbed hero, and the one dial that sets how
   fast the footage plays. 100vh of this is pinned, so the scrub span is this
   height minus 100vh, and 145 frames are spread across it. Lower is faster.

     320vh  220vh span, 1.52vh per frame   the original, felt slow
     240vh  140vh span, 0.97vh per frame   current
     200vh  100vh span, 0.69vh per frame   brisk
     180vh   80vh span, 0.55vh per frame   about the floor, frames start to
                                           skip on a trackpad flick

   --hero-exit-vh is how much scrolling the closing dissolve gets, in vh. It
   lives here rather than in the JS because it only means anything relative to
   the height above: js/hero-scrub.js and js/main.js both read it and convert
   it into a progress fraction against whatever the span currently is. Change
   the height on its own and the dissolve keeps its physical length. */
.hero--scrub {
  height: 240vh;
  --hero-exit-vh: 4;
}

/* js/hero-scrub.js swaps to this on phones and under reduced motion: the tall
   scroll track collapses and the hero becomes an ordinary full height section
   playing the looping video. */
.hero--static { height: 100dvh; min-height: 560px; }
.hero--static .hero__sticky { position: relative; }

.hero__sticky {
  position: sticky; top: 0;
  height: 100dvh;
  min-height: 560px;
  overflow: hidden;
  display: grid;
  place-items: center;
  /* The nav is fixed and 76px tall. Without this the centered content block
     can ride up under the logo on shorter viewports; interior .page-hero
     carries the same allowance for the same reason. */
  padding-top: 84px;
}

.hero__media { position: absolute; inset: 0; }
.hero__canvas,
.hero__video {
  width: 100%; height: 100%;
  object-fit: cover;
  display: block;
}
.hero__video { position: absolute; inset: 0; }

/* Scrim. Two layers: a bottom-weighted gradient so the headline always has
   ground to sit on, and a flat wash to hold contrast when the footage goes
   bright (this clip opens on a pale sunset sky and white sand). */
.hero__veil {
  position: absolute; inset: 0;
  background:
    linear-gradient(180deg, rgba(31,36,34,.52) 0%, rgba(31,36,34,.18) 32%,
                            rgba(31,36,34,.42) 68%, rgba(31,36,34,.72) 100%),
    linear-gradient(90deg, rgba(31,36,34,.34) 0%, rgba(31,36,34,0) 62%);
}

/* Scroll-driven exit. js/hero-scrub.js ramps --scrub-exit 0 to 1 over the last
   stretch so the hero dissolves into the page instead of hard cutting. */
.hero__fade {
  position: absolute; inset: 0;
  background: var(--bone);
  opacity: var(--scrub-exit, 0);
  pointer-events: none;
}

.hero__content {
  position: relative;
  z-index: 2;
  width: min(100% - 3rem, var(--wrap));
  margin-inline: auto;
  text-align: left;
  opacity: calc(1 - var(--scrub-exit, 0) * 1.35);
}

.hero__title {
  font-size: clamp(2.6rem, 7vw, 5.4rem);
  line-height: 1.02;
  max-width: 15ch;
  margin-bottom: 1.6rem;
}
/* Each sentence is its own beat and gets its own line. Block is load bearing
   twice over here: it stops the italic sentence starting mid line and leaving
   an orphaned word behind it, and translateY in .reveal does nothing at all on
   an inline element, so until this the staggered rise was silently not
   happening and the two lines only faded. */
.hero__title span { display: block; }
.hero__title em {
  font-style: italic;
  color: var(--gulf);
}
.hero__lede {
  font-size: clamp(1rem, 1.5vw, 1.12rem);
  max-width: 46ch;
  color: rgba(239, 238, 236, .9);
  margin-bottom: 2.3rem;
}
.hero__actions { display: flex; flex-wrap: wrap; gap: .9rem; }

/* Scroll cue. A line that travels down its own track, borrowed in spirit from
   the reference's arrow. */
.hero__scroll {
  position: absolute; left: 50%; bottom: 2.2rem;
  transform: translateX(-50%);
  z-index: 2;
  width: 1px; height: 62px;
  background: rgba(239, 238, 236, .3);
  overflow: hidden;
  opacity: calc(1 - var(--scrub-exit, 0) * 2);
}
.hero__scroll::after {
  content: ""; position: absolute; inset: 0;
  background: var(--bone);
  animation: scrollcue 2.4s var(--ease) infinite;
}
@keyframes scrollcue {
  0%   { transform: translateY(-100%); }
  55%  { transform: translateY(0); }
  100% { transform: translateY(100%); }
}

/* Page heroes on interior pages. Photo, not video. */
.page-hero {
  position: relative;
  min-height: clamp(380px, 52vh, 560px);
  display: grid; place-items: center;
  background: var(--deep);
  color: var(--bone);
  overflow: hidden;
  padding-top: 76px;
}
/* Oversized on purpose. js/reveal.js drifts this against the scroll, so it
   needs bleed above and below or the translate would expose the edges. */
.page-hero__img {
  position: absolute; left: 0; top: -60px;
  width: 100%; height: calc(100% + 120px);
  object-fit: cover;
}
.page-hero__veil {
  position: absolute; inset: 0;
  background: linear-gradient(180deg, rgba(31,36,34,.62), rgba(31,36,34,.44) 45%, rgba(31,36,34,.7));
}
.page-hero__content {
  position: relative; z-index: 2;
  width: min(100% - 3rem, var(--wrap));
  margin-inline: auto;
  text-align: center;
}
/* Photo credit, bottom left of the hero.
   Every neighborhood hero is a freely licensed photograph, and CC BY and
   CC BY-SA both require attribution to be visible with the work. Small and
   quiet is fine; absent is a license violation. It sits on the gutter line
   rather than hard against the edge, and it does not drift with the image,
   so it stays legible while the hero parallaxes. */
.page-hero__credit {
  position: absolute; z-index: 2;
  left: max(1.5rem, calc((100% - var(--wrap)) / 2));
  right: max(1.5rem, calc((100% - var(--wrap)) / 2));
  bottom: .75rem;
  margin: 0;
  font-family: var(--ui);
  font-size: .6rem;
  font-weight: 400;
  letter-spacing: .1em;
  text-transform: uppercase;
  line-height: 1.5;
  color: rgba(239, 238, 236, .72);
  /* The veil cannot promise a dark pixel under this corner: some heroes are a
     white picket fence or a sand beach right where the credit sits. The shadow
     is what keeps required attribution readable on every one of them. */
  text-shadow: 0 1px 3px rgba(15, 18, 17, .75);
  text-align: left;
  text-wrap: balance;
}
.page-hero__credit a {
  color: inherit;
  text-decoration: none;
  border-bottom: 1px solid rgba(239, 238, 236, .22);
  transition: color .25s var(--ease), border-color .25s var(--ease);
}
.page-hero__credit a:hover,
.page-hero__credit a:focus-visible {
  color: rgba(239, 238, 236, .92);
  border-bottom-color: rgba(239, 238, 236, .55);
}
@media (max-width: 640px) {
  .page-hero__credit { font-size: .55rem; letter-spacing: .06em; }
}

.page-hero__title {
  font-size: clamp(2.1rem, 5.2vw, 3.9rem);
  max-width: 20ch; margin-inline: auto;
}
.page-hero__lede {
  margin: 1.3rem auto 0;
  max-width: 54ch;
  color: rgba(239, 238, 236, .9);
}
.page-hero .eyebrow { color: var(--gulf); }

/* Focus rings need to invert wherever the ground is dark. */
.hero :focus-visible,
.page-hero :focus-visible,
.nav.is-top :focus-visible,
.section--dark :focus-visible,
.footer :focus-visible { outline-color: var(--bone); }

/* ================================================= scroll animation ====== */

/* The whole vocabulary runs off one IntersectionObserver and one inline --d
   custom property for stagger. Adding a motion means adding a class here, not
   touching JavaScript. */

/* Entrances replay by default: an element animates out as it leaves the
   viewport and back in on return, so the page is alive scrolling either
   direction. Add .reveal--once to anything that should settle permanently. */
.reveal {
  opacity: 0;
  transform: translateY(26px);
  transition: opacity .9s var(--ease) var(--d, 0s),
              transform .9s var(--ease) var(--d, 0s);
  will-change: opacity, transform;
}
.reveal.is-in { opacity: 1; transform: none; }

/* Retained as a no-op so older markup keeps working. Repeat is now the
   default, and .reveal--once is the opt out. */
.reveal--repeat {}

/* Photography wipes rather than slides. */
.reveal--mask {
  opacity: 1;
  transform: none;
  clip-path: inset(0 0 100% 0);
  transition: clip-path 1.15s var(--ease) var(--d, 0s);
}
.reveal--mask.is-in { clip-path: inset(0 0 0 0); }

/* Cards settle in rather than rise. */
.reveal--scale {
  opacity: 0;
  transform: scale(1.04);
  transition: opacity .85s var(--ease) var(--d, 0s),
              transform .85s var(--ease) var(--d, 0s);
}
.reveal--scale.is-in { opacity: 1; transform: none; }

/* Headlines reveal line by line. Each line needs its own span inside an
   overflow clip, which the markup provides as .lines__line > span. */
.reveal--lines .lines__line { display: block; overflow: hidden; }
.reveal--lines .lines__line > span {
  display: block;
  transform: translateY(102%);
  transition: transform 1s var(--ease);
}
.reveal--lines.is-in .lines__line > span { transform: none; }
.reveal--lines .lines__line:nth-child(2) > span { transition-delay: .09s; }
.reveal--lines .lines__line:nth-child(3) > span { transition-delay: .18s; }

/* A hairline that draws itself across. */
.reveal--rule {
  transform-origin: left;
  transform: scaleX(0);
  opacity: 1;
  transition: transform 1.1s var(--ease) var(--d, 0s);
}
.reveal--rule.is-in { transform: scaleX(1); }

/* ---- parallax ---------------------------------------------------------- */

/* js/reveal.js tags these with .px and writes --py in pixels as the element
   crosses the viewport. Everything that also scales on hover composes through
   --pz rather than declaring its own transform, so the two never fight. */
.px {
  will-change: transform;
  transform: translate3d(0, var(--py, 0px), 0) scale(var(--pz, 1));
}

/* ---- scroll progress --------------------------------------------------- */

/* A hairline tracking read position. Cheap, and it keeps something moving
   even through a stretch of pure text. */
.scroll-progress {
  position: fixed;
  inset: 0 0 auto 0;
  z-index: 200;
  height: 2px;
  background: var(--gulf-d);
  transform: scaleX(0);
  transform-origin: left;
  pointer-events: none;
}

@media (prefers-reduced-motion: reduce) {
  .reveal,
  .reveal--mask,
  .reveal--scale,
  .reveal--rule { opacity: 1; transform: none; clip-path: none; transition: none; }
  .reveal--lines .lines__line > span { transform: none; transition: none; }
  .px { transform: none; }
  .scroll-progress { display: none; }
  .hero__scroll::after { animation: none; }
}

/* Failsafe. Entrances start hidden and are revealed by js/reveal.js, so
   without scripting every one of them would stay invisible, including
   Jennifer's photograph. This renders them in their resting state instead.
   Cheaper and flash free compared with tagging the html element from JS, and
   it needs no inline script, which the CSP in netlify.toml forbids. */
@media (scripting: none) {
  .reveal,
  .reveal--mask,
  .reveal--scale,
  .reveal--rule { opacity: 1; transform: none; clip-path: none; transition: none; }
  .reveal--lines .lines__line > span { transform: none; }
  .scroll-progress { display: none; }
}

/* ====================================================== ways to work ===== */

/* Modeled on the "Ways You Can Work With Us" band on schranerrealty.com.

   The move that makes it work is the seam: a photograph runs across the top,
   the section ground underneath is solid dark, and the cards straddle the
   boundary. Their images sit on the photo and their bodies land on the dark,
   which ties the two halves together instead of stacking them.

   The reference does this with Wix BackgroundParallax. Here .workwith__img is
   oversized and registered in PARALLAX_TARGETS, so the house drifts against
   the scroll the same way. */
/* This band runs shorter than a standard .section. It was the tallest thing on
   the page after the hero and most of that was air, so the padding, the gap
   under the heading, the card photos and the card bodies are all tightened
   here. Nothing outside .workwith changes.

   Every one of these numbers moves the seam, so the 58% above became 62%.
   Change any of them and that percentage has to be checked again. */
.workwith {
  position: relative;
  background: var(--deep);
  color: var(--bone);
  overflow: hidden;
  padding: clamp(3rem, 5.5vw, 4.75rem) 0;
}
.workwith .section__head { margin-bottom: clamp(1.8rem, 3vw, 2.5rem); }
.workwith__inner { position: relative; z-index: 1; }

/* Where the photo stops. The cards are pulled up so they cross it. */
.workwith__bg {
  position: absolute;
  inset: 0 0 auto 0;
  height: 62%;
  overflow: hidden;
}
/* Headroom for the drift, and the zoom comes free with it.

   The image is 1000px taller than its frame, centred with a negative margin, so
   it can travel 500px either way before an edge shows. js/reveal.js drives it
   at range 920, which is +/-460.

   Making the box taller is also what zooms the picture: object-fit cover has to
   scale the image up to cover the extra height, which crops the sides and
   enlarges the house. That is deliberate. Travel and zoom are the same dial
   here, and a bigger source file (2600px wide, full frame) keeps it sharp.

   Travel must stay under the oversize or the translate slides the image off its
   own frame. Change one number and change the other. */
.workwith__img {
  width: 100%;
  height: calc(100% + 1000px);
  margin-top: -500px;
  object-fit: cover;
  object-position: center 45%;
}
/* Scrim. Darkest at the bottom so the seam into the solid ground is a fade
   rather than a hard edge, and heavy enough at the top for white type. */
.workwith__bg::after {
  content: "";
  position: absolute; inset: 0;
  background: linear-gradient(
    180deg,
    rgba(31, 36, 34, .58) 0%,
    rgba(31, 36, 34, .34) 38%,
    rgba(31, 36, 34, .78) 82%,
    var(--deep) 100%
  );
}

.workwith .section__title { color: var(--bone); }
.workwith .section__lede  { color: rgba(239, 238, 236, .88); }
.workwith .eyebrow        { color: var(--gulf); }

/* Cards follow the reference layout: a solid panel under a photo band, with a
   large white serif question and small centred body copy beneath it. The
   reference's warm taupe is not this site's color, so the panel is --gulf-d,
   the deep half of the single accent.

   This stays inside the one accent, one region rule: the band already carries
   gulf on its eyebrows, so the accent is not gaining a new region here, and it
   still never touches nav or a button.

   --gulf-d against white measures 7.2:1, so unlike the taupe it had before
   (4.65:1, a hair over the floor) the body copy has real headroom. At 92%
   white the copy still measures 6.3:1. Against the --deep ground the panel
   separates 2.2:1, which the photo band and the card edge carry the rest of. */
.workwith .path {
  background: var(--gulf-d);
  border-color: transparent;
  border-radius: 0;
  text-align: center;
  box-shadow: var(--shadow-lg);
}
.workwith .path:hover { border-color: transparent; }
/* The reference leaves a lot of air below the copy. Kept, but roughly half of
   what it was: at 5.5rem it was the single biggest block of empty space in the
   band. */
.workwith .path__body {
  padding: clamp(1.5rem, 2.4vw, 2rem) clamp(1.4rem, 2.4vw, 2.2rem)
           clamp(1.8rem, 3vw, 2.6rem);
}
.workwith .path__title {
  color: var(--paper);
  font-size: clamp(1.45rem, 2.1vw, 1.85rem);
  font-weight: 600;
  line-height: 1.14;
  margin-bottom: 1rem;
  max-width: 15ch;
  margin-inline: auto;
}
.workwith .path__text {
  color: rgba(255, 255, 255, .92);
  font-size: .89rem;
  line-height: 1.72;
  max-width: 34ch;
  margin-inline: auto;
  flex: 0 1 auto;   /* the reference leaves air below the copy, not stretched */
}
.workwith .path__num { display: none; }
.workwith .path__link {
  color: var(--paper);
  margin-top: 1.1rem;
  justify-content: center;
}
/* 3/2 rather than 4/3. A third of the band's height was card photograph, and
   the wider crop suits these three images anyway. */
.workwith .path__media { aspect-ratio: 3 / 2; }

@media (max-width: 860px) {
  /* Less card height to cross, so the photo band shrinks with it. Approximate
     by nature: one percentage has to cover everything from a phone, where the
     cards are in a single tall column, up to the two column layout just under
     this breakpoint, and the seam sits at a very different fraction of the
     section in each. */
  .workwith__bg { height: 48%; }
}

/* ============================================================ pathways === */

/* The three card Start Here row. Buy, Sell, Relocate. */
.paths__grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  gap: 1.5rem;
}
.path {
  position: relative;
  display: flex; flex-direction: column;
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  overflow: hidden;
  text-decoration: none;
  color: inherit;
  transition: transform .5s var(--ease), box-shadow .5s var(--ease),
              border-color .5s var(--ease);
}
.path:hover {
  transform: translateY(-6px);
  box-shadow: var(--shadow-md);
  border-color: var(--sand-l);
  color: inherit;
}
.path__media { aspect-ratio: 4 / 3; overflow: hidden; background: var(--bone-2); }
/* Slightly oversized so the parallax drift never shows an edge. Hover scale
   goes through --pz so it composes with the drift rather than replacing it. */
.path__media img {
  width: 100%; height: calc(100% + 34px);
  margin-top: -17px;
  object-fit: cover;
  --pz: 1;
  transition: --pz 1.1s var(--ease), transform 1.1s var(--ease);
}
.path:hover .path__media img { --pz: 1.05; }
.path__body { padding: 1.9rem 1.7rem 2.1rem; display: flex; flex-direction: column; flex: 1; }
.path__title { font-size: 1.65rem; margin-bottom: .7rem; }
.path__text { font-size: .93rem; color: var(--ink-2); flex: 1; }
.path__link {
  margin-top: 1.4rem;
  font-size: .72rem; font-weight: 600; letter-spacing: .14em;
  text-transform: uppercase; color: var(--ink);
  display: inline-flex; align-items: center; gap: .5rem;
}
/* The line stretches with scaleX rather than width so hover never reflows:
   the box is 40px from the start and only the paint scales. */
.path__link::after {
  content: ""; width: 40px; height: 1px; background: currentColor;
  transform: scaleX(.65); transform-origin: left center;
  transition: transform .4s var(--ease);
}
.path:hover .path__link::after { transform: scaleX(1); }

/* ============================================================ stats ====== */

/* ============================================================ about ====== */

/* ---- pointer orb ------------------------------------------------------- */

/* A soft light that drifts after the cursor across the About band. It is the
   one place the gulf accent appears as atmosphere rather than as a line or a
   word, which keeps it inside the one accent, one region rule in DESIGN.md.

   Deliberately slow and low contrast. This band carries her photograph and her
   bio, and anything brighter competes with both. */
#about { position: relative; overflow: hidden; }
#about > .wrap { position: relative; z-index: 1; }

.orb {
  position: absolute;
  inset: 0;
  pointer-events: none;
  opacity: 0;
  transition: opacity .9s var(--ease);
  background: radial-gradient(
    460px circle at var(--ox, 50%) var(--oy, 45%),
    rgba(111, 158, 163, .18),
    rgba(111, 158, 163, .07) 36%,
    rgba(111, 158, 163, 0) 65%
  );
}
.orb.is-lit { opacity: 1; }

/* Never on touch, where there is no cursor to trail and it would just sit
   wherever the last tap landed. */
@media (hover: none), (prefers-reduced-motion: reduce) {
  .orb { display: none; }
}

.about__grid {
  display: grid;
  grid-template-columns: minmax(0, 5fr) minmax(0, 7fr);
  gap: clamp(2.5rem, 6vw, 5rem);
  align-items: center;
}
@media (max-width: 860px) { .about__grid { grid-template-columns: 1fr; } }

/* Framed portrait. A hairline rectangle sits 16px outside the photo on every
   side, like the mat line on a mounted print. Symmetric on purpose: this rule
   is used in the left column on the home page and the right column on About,
   and an offset frame would lean into the text on one of them.

   No overflow:hidden here any more, or the frame would be clipped by its own
   container. The radius moved onto the image instead, which is possible only
   because the file is now cut to the container ratio exactly. */
.about__photo {
  position: relative;
  aspect-ratio: 4 / 5;
}
.about__photo::before {
  content: "";
  position: absolute;
  inset: -16px;
  border: 1px solid var(--sand);
  border-radius: calc(var(--radius) + 2px);
  pointer-events: none;
}
/* The frame reads as decoration on light grounds and needs a touch more
   presence against the dark band. */
.section--dark .about__photo::before { border-color: rgba(129, 120, 114, .85); }

@media (max-width: 560px) {
  /* Less room to breathe, so the frame tucks in closer. */
  .about__photo::before { inset: -10px; }
}
/* Exact fit, no oversize. The file is cut to the same ratio as the box, so
   cover crops nothing and the whole photograph is visible. It used to be 42px
   taller to give the parallax something to travel through, which quietly ate
   the top and bottom of the frame. The drift is not worth losing her hands and
   the top of the artwork, so this one does not parallax. */
.about__photo img {
  position: relative;   /* above the frame line */
  width: 100%; height: 100%;
  object-fit: cover;
  object-position: center;
  border-radius: var(--radius);
  background: var(--bone-2);
  box-shadow: var(--shadow-md);
}
.about__quote {
  font-family: var(--display);
  font-size: clamp(1.5rem, 2.6vw, 2.05rem);
  font-style: italic;
  line-height: 1.35;
  color: var(--ink);
  margin: 0 0 1.6rem;
}
.section--dark .about__quote { color: var(--bone); }
/* "Meet Jennifer" in the About band. Two faces on one line, so the join is the
   whole job. Cormorant at 600 gives Meet its weight, and the script carries
   the name. */
.meet {
  font-size: clamp(2.5rem, 6.2vw, 4.8rem);
  font-weight: 600;
  line-height: 1.04;
  max-width: none;
}
.meet em {
  font-family: var(--script);
  font-style: normal;
  font-weight: 400;
  color: var(--gulf);
  /* Italianno's capitals and x height sit very small inside their em box, much
     smaller than Cormorant's, so at a matched font-size the name would read as
     roughly half the word beside it. 1.58 is what it takes to look like one
     line of type rather than two sizes, and the nudge puts it back on the same
     baseline afterwards. This number belongs to this typeface. Change the font
     and it has to be measured again. */
  font-size: 1.58em;
  line-height: .9;
  display: inline-block;
  transform: translateY(.08em);
}
/* The descenders on a copperplate are long, so a tight line height clips the
   name against whatever follows it. */
.meet + * { margin-top: 1.4rem; }

.about__sig {
  font-family: var(--label);
  font-size: .7rem; letter-spacing: .24em; text-transform: uppercase;
  color: var(--muted);
  margin-top: 1.8rem;
}
.section--dark .about__sig { color: var(--sand); }

/* The long biography, folded in from the separate about page it used to live
   on. It sits below the portrait grid at full width, because a long read
   stacked into the right hand column would run twice the height of the
   photograph beside it. */
.about__story {
  position: relative;
  /* Own stacking context, so the photograph behind the copy can sit at z-index
     -1 and land above the section's own dark ground rather than behind it. */
  isolation: isolate;
  margin-top: clamp(3rem, 6vw, 4.5rem);
  /* Generous, and the padding is what sizes the photograph. The band is the
     picture's frame, so growing it does two things at once: the photo covers
     more of the section, and cover has to scale the source up to fill the
     taller box, which enlarges the room inside it. */
  padding-top: clamp(4.5rem, 9vw, 8rem);
  padding-bottom: clamp(4.5rem, 9vw, 8rem);
  /* No hairline any more. The photograph's own hard top edge is the divider,
     and a rule sitting on it read as a mistake. */
}

/* Interior photograph behind the long read. Full bleed out of .wrap, the same
   left 50% and negative margin trick a contained column always needs, because
   the copy stays on the 1180px measure while the picture runs edge to edge.
   Parallaxed like the Ways To Work band: .story__img is oversized and
   registered in PARALLAX_TARGETS. */
.story__bg {
  position: absolute;
  top: 0; bottom: 0;
  left: 50%;
  width: 100vw;
  margin-left: -50vw;
  overflow: hidden;
  z-index: -1;
}
/* 1200px of headroom, deliberately hung off centre. -800px rather than the
   -600px that would centre it, so the frame rests low in the photograph, on
   the seating and the ottoman, instead of on the middle of the room. That is
   the only way to choose which part of the picture you see: the box is taller
   than the source's aspect, so cover crops the width and object-position has
   no vertical travel to work with.

   Hanging it low leaves 800px of room to move down and 400px to move up, and
   it is the 400 that constrains reveal.js. It runs range 600, so 300px each
   way, which keeps 100px in hand before an edge of the photograph could show.
   That 400 holds whatever height the band ends up, since both this margin and
   the oversize are fixed. The two numbers move together: raising the oversize
   without moving this margin down by the same amount buys travel in the wrong
   direction, since it is upward room that is short.

   1200 is near the ceiling, and the ceiling is the source. At roughly 610px of
   band the image has to fill a box about 1810px tall, and the file is 1801px.
   That is native size, near enough. Grow either this or the band's padding
   much further and the photograph starts being stretched to fit, which the
   .66 scrim is too light to hide. Refetch a taller source instead. */
.story__img {
  width: 100%;
  height: calc(100% + 1200px);
  margin-top: -800px;
  object-fit: cover;
  object-position: center;
}
/* Scrim. Flat, and it stops hard at both edges rather than fading into the
   section, because the band is meant to arrive as a photograph and not as a
   gradient. The room should be plainly readable through it.

   .66 is the floor, and it is not a taste number. Against a pure white pixel
   it blends to about rgb(107,107,107), which carries the --bone copy at
   4.63:1, just over the 4.5:1 AA floor for body text. That is the worst case,
   so the copy stays legible no matter which part of the room drifts behind it.
   Going lighter means either shrinking the picture's role or putting the text
   on its own panel. Do not just lower this number. */
.story__bg::after {
  content: "";
  position: absolute; inset: 0;
  background: rgba(31, 36, 34, .66);
}
/* Held narrow so the quotation breaks the column rhythm instead of stretching
   into a headline. */
.about__story-quote { max-width: 24ch; margin-bottom: clamp(2rem, 4vw, 2.8rem); }

.about__cols {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: clamp(1.4rem, 4vw, 3.5rem);
  align-items: start;
}
@media (max-width: 860px) { .about__cols { grid-template-columns: 1fr; } }
.about__cols p { max-width: 62ch; }
/* Full --bone rather than the dimmed --sand-l used elsewhere on dark grounds.
   These paragraphs sit on a photograph now, and --sand-l does not survive a
   .66 scrim over a bright part of the room. See .story__bg::after. */
.section--dark .about__cols p { color: var(--bone); }

.about__cred {
  margin-top: clamp(3rem, 6vw, 4.5rem);
  padding-top: clamp(2.6rem, 5vw, 3.6rem);
  border-top: 1px solid rgba(239, 238, 236, .16);
}
.about__cred .section__title { font-size: clamp(1.6rem, 3vw, 2.3rem); }

/* ============================================================ guide ====== */

/* Gated relocation guide. Form on one side, cover on the other. */
.guide__grid {
  display: grid;
  grid-template-columns: minmax(0, 6fr) minmax(0, 5fr);
  gap: clamp(2.5rem, 6vw, 4.5rem);
  align-items: center;
}
@media (max-width: 860px) { .guide__grid { grid-template-columns: 1fr; } }

.guide__list { list-style: none; padding: 0; margin: 1.8rem 0 0; }
.guide__list li {
  position: relative;
  padding-left: 1.8rem;
  margin-bottom: .8rem;
  font-size: .95rem;
  color: var(--ink-2);
}
.section--dark .guide__list li { color: var(--sand-l); }
.guide__list li::before {
  content: ""; position: absolute; left: 0; top: .62em;
  width: 9px; height: 1px; background: var(--gulf-d);
}
.section--dark .guide__list li::before { background: var(--gulf); }

.guide__card {
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: clamp(1.8rem, 3.5vw, 2.6rem);
  box-shadow: var(--shadow-md);
}
.section--dark .guide__card { border-color: transparent; }
.guide__card-title { font-size: 1.5rem; margin-bottom: .6rem; color: var(--ink); }
.guide__card-sub { font-size: .88rem; color: var(--muted); margin-bottom: 1.6rem; }

/* ============================================================ forms ====== */

/* Honeypot. Off screen rather than display:none, because some bots skip
   hidden inputs but happily fill positioned ones. */
.hp-field {
  position: absolute !important;
  left: -9999px !important;
  width: 1px; height: 1px;
  overflow: hidden;
}

.field, .vfield {
  display: flex; flex-direction: column; gap: .45rem;
  margin-bottom: 1.05rem;
}
.field > span, .vfield > span {
  font-size: .68rem; font-weight: 600;
  letter-spacing: .16em; text-transform: uppercase;
  color: var(--ink-2);
}
.field > span em, .vfield > span em {
  font-style: normal; text-transform: none; letter-spacing: .02em;
  color: var(--muted); font-weight: 400;
}

input[type="text"], input[type="email"], input[type="tel"],
input[type="number"], input[type="search"], select, textarea {
  width: 100%;
  min-height: 46px;
  padding: .75rem .9rem;
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  font-family: var(--ui);
  font-size: .95rem;
  color: var(--ink);
  transition: border-color .3s var(--ease), box-shadow .3s var(--ease);
}
textarea { min-height: 110px; resize: vertical; line-height: 1.6; }
input::placeholder, textarea::placeholder { color: #8d8b88; }

input:hover, select:hover, textarea:hover { border-color: var(--sand-l); }
input:focus, select:focus, textarea:focus {
  border-color: var(--gulf-d);
  box-shadow: 0 0 0 3px rgba(44, 95, 102, .12);
}

/* Inside forms the focus ring sits tighter, since the field already has a
   visible border state to pair with. */
.contact__form :focus-visible,
.vform :focus-visible,
.guide__card :focus-visible { outline-color: var(--ink); outline-offset: 1px; }

.vfield.is-invalid input,
.vfield.is-invalid select,
.vfield.is-invalid textarea,
.field.is-invalid input,
.field.is-invalid textarea {
  border-color: #a33a2a;
  box-shadow: 0 0 0 3px rgba(163, 58, 42, .1);
}
.vfield__err, .field__err {
  font-size: .78rem; color: #8f2f21; display: none;
}
.vfield.is-invalid .vfield__err,
.field.is-invalid .field__err { display: block; }

.form__row {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 0 1rem;
}

/* ---- contact form ---------------------------------------------------- */
.contact__grid {
  display: grid;
  grid-template-columns: minmax(0, 5fr) minmax(0, 6fr);
  gap: clamp(2.5rem, 6vw, 5rem);
}
@media (max-width: 860px) { .contact__grid { grid-template-columns: 1fr; } }

.contact__form {
  display: flex; flex-direction: column;
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: clamp(1.8rem, 3.5vw, 2.6rem);
  box-shadow: var(--shadow-md);
}
.section--dark .contact__form { border-color: transparent; }

.form__done {
  text-align: center;
  padding: 2.5rem 1rem;
}
.form__done-mark {
  display: grid; place-items: center;
  width: 52px; height: 52px; margin: 0 auto 1.3rem;
  border-radius: 50%;
  background: var(--gulf-d); color: var(--paper);
  font-size: 1.3rem;
}
.form__done h3 { font-size: 1.6rem; margin-bottom: .7rem; color: var(--ink); }
.form__done p { color: var(--ink-2); font-size: .95rem; }
.form__error {
  margin-top: 1rem;
  padding: .9rem 1.1rem;
  background: #fbf0ee;
  border-left: 2px solid #a33a2a;
  font-size: .88rem;
  color: #6d2419;
}
.form__error a { color: #6d2419; }
.form__note {
  margin-top: 1.1rem;
  font-size: .76rem;
  color: var(--muted);
  text-align: center;
}

/* ---- multi step wizard ------------------------------------------------ */
.vform {
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: clamp(1.8rem, 3.5vw, 2.6rem);
  box-shadow: var(--shadow-md);
}
.section--dark .vform { border-color: transparent; }

.vform__head { margin-bottom: 1.8rem; }
.vform__bar {
  display: block; height: 2px;
  background: var(--line);
  border-radius: 2px; overflow: hidden;
}
/* Full width fill scaled down with scaleX rather than an animated width, so
   advancing a step repaints without reflow. js/main.js sets the scale; the
   .33 here is only the pre-init state for a three step form. */
.vform__bar i {
  display: block; height: 100%; width: 100%;
  background: var(--gulf-d);
  transform: scaleX(.33); transform-origin: left center;
  transition: transform .55s var(--ease);
}
.vform__stepmeta {
  display: flex; justify-content: space-between; align-items: baseline;
  gap: 1rem; margin: .9rem 0 0;
  font-size: .68rem; font-weight: 600;
  letter-spacing: .16em; text-transform: uppercase;
}
.vform__stepnum { color: var(--muted); }
.vform__steptitle { color: var(--ink); font-weight: 600; }

.vform__step { display: none; animation: stepin .5s var(--ease); }
.vform__step.is-active { display: block; }
@keyframes stepin {
  from { opacity: 0; transform: translateX(14px); }
  to   { opacity: 1; transform: none; }
}
@media (prefers-reduced-motion: reduce) {
  .vform__step { animation: none; }
}

.vform__row {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
  gap: 0 1rem;
}
.vfield--sm { min-width: 0; }

.vform__nav {
  display: flex; gap: .8rem; align-items: center;
  margin-top: 1.6rem;
}
.vform__nav .btn { flex: 1; }
.vform__note {
  margin-top: 1.1rem; text-align: center;
  font-size: .76rem; color: var(--muted);
}

/* ============================================================ listings === */

.listings__head { margin-bottom: 2rem; }

.listings__controls { margin-bottom: 2rem; }
.listings__tabs { display: flex; flex-wrap: wrap; gap: .6rem; }
.tab {
  display: inline-flex; align-items: center; gap: .55rem;
  min-height: 44px;
  padding: .6rem 1.2rem;
  background: transparent;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  font-family: var(--ui);
  font-size: .72rem; font-weight: 600;
  letter-spacing: .12em; text-transform: uppercase;
  color: var(--ink-2);
  cursor: pointer;
  transition: background .3s var(--ease), color .3s var(--ease),
              border-color .3s var(--ease);
}
.tab:hover { border-color: var(--sand); color: var(--ink); }
.tab.is-active { background: var(--ink); border-color: var(--ink); color: var(--bone); }
.tab__count {
  font-size: .64rem; opacity: .7;
  font-variant-numeric: tabular-nums;
}

/* A gutter on each side holds the arrows. The viewport is inset by the same
   amount, so an arrow sits beside the cards and never over one. */
.listings__carousel {
  position: relative;
  --gutter: 64px;
  padding-inline: var(--gutter);
}
@media (max-width: 1080px) { .listings__carousel { --gutter: 52px; } }
@media (max-width: 680px)  { .listings__carousel { --gutter: 0px; } }
.listings__viewport {
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  scrollbar-width: none;
  -ms-overflow-style: none;
}
.listings__viewport::-webkit-scrollbar { display: none; }

.listings__grid {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: calc((100% - 3rem) / 3);
  grid-template-rows: 1fr;
  gap: 1.5rem;
  padding-bottom: .5rem;
}
.listings__grid--six { grid-template-rows: 1fr 1fr; grid-auto-flow: column; }
@media (max-width: 1024px) { .listings__grid { grid-auto-columns: calc((100% - 1.5rem) / 2); } }
@media (max-width: 680px)  { .listings__grid { grid-auto-columns: 86%; } }

.listings__nav {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  z-index: 3;
  width: 46px; height: 46px;
  display: grid; place-items: center;
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: 50%;
  font-size: 1.4rem; line-height: 1; color: var(--ink);
  cursor: pointer;
  box-shadow: var(--shadow-sm);
  transition: background .3s var(--ease), border-color .3s var(--ease),
              color .3s var(--ease), opacity .3s var(--ease);
}
/* Centred in the gutter rather than hard against the cards. */
.listings__nav--prev { left: calc((var(--gutter) - 46px) / 2); }
.listings__nav--next { right: calc((var(--gutter) - 46px) / 2); }
.listings__nav:hover:not([disabled]) {
  background: var(--ink);
  border-color: var(--ink);
  color: var(--bone);
}
/* Dimmed rather than hidden at the ends of the track, so the arrow does not
   pop out of the layout as you reach either edge. */
.listings__nav[disabled] { opacity: .28; cursor: not-allowed; }

/* No room for a gutter on phones, and the grid swipes anyway. */
@media (max-width: 680px) { .listings__nav { display: none; } }

.card {
  display: flex; flex-direction: column;
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  overflow: hidden;
  text-decoration: none;
  color: inherit;
  scroll-snap-align: start;
  transition: transform .5s var(--ease), box-shadow .5s var(--ease),
              border-color .5s var(--ease);
}
.card:hover {
  transform: translateY(-5px);
  box-shadow: var(--shadow-md);
  border-color: var(--sand-l);
  color: inherit;
}
.card__media {
  position: relative;
  aspect-ratio: 3 / 2;
  background: var(--bone-2);
  overflow: hidden;
}
.card__media img {
  width: 100%; height: calc(100% + 26px);
  margin-top: -13px;
  object-fit: cover;
  --pz: 1;
  transition: --pz 1.1s var(--ease), transform 1.1s var(--ease);
}
.card:hover .card__media img { --pz: 1.05; }
.card__tag, .modal__tag {
  position: absolute; top: .9rem; left: .9rem;
  padding: .35rem .8rem;
  background: rgba(31, 36, 34, .86);
  color: var(--bone);
  font-size: .6rem; font-weight: 600;
  letter-spacing: .16em; text-transform: uppercase;
  border-radius: 2px;
}
.card__price, .modal__price {
  position: absolute; bottom: .9rem; left: .9rem;
  padding: .4rem .9rem;
  background: var(--paper);
  color: var(--ink);
  font-size: .88rem; font-weight: 600;
  letter-spacing: .02em;
  border-radius: 2px;
}
.card__body { padding: 1.3rem 1.3rem 1.1rem; display: flex; flex-direction: column; flex: 1; }
.card__addr {
  font-family: var(--display);
  font-size: 1.28rem; line-height: 1.25;
  margin-bottom: .25rem;
}
.card__city { font-size: .8rem; color: var(--muted); }
.card__facts {
  display: flex; flex-wrap: wrap; gap: 1.1rem;
  margin-top: 1rem; padding-top: .9rem;
  border-top: 1px solid var(--line);
  font-size: .78rem; color: var(--ink-2);
}
.card__fact b, .modal__fact b {
  font-weight: 600; color: var(--ink);
  font-variant-numeric: tabular-nums;
}
.card__foot {
  display: flex; justify-content: space-between; align-items: center;
  gap: 1rem; margin-top: auto; padding-top: 1rem;
  font-size: .7rem; letter-spacing: .1em; text-transform: uppercase;
}
.card__dist { color: var(--muted); }
.card__link { color: var(--ink); font-weight: 600; }

.listings__empty {
  grid-column: 1 / -1;
  padding: 3rem 1rem; text-align: center;
  color: var(--muted); font-size: .95rem;
}

/* Handoff to her brokerage MLS search. Shown in place of the carousel whenever
   she has no listings of her own, which is the normal state. It sits inside
   .listings__grid, so it has to break out of the horizontal auto columns. */
.handoff {
  grid-column: 1 / -1;
  width: 100%;
  background: var(--deep);
  color: var(--bone);
  border-radius: var(--radius);
  padding: clamp(2.5rem, 6vw, 4.5rem) clamp(1.5rem, 5vw, 4rem);
  text-align: center;
  box-shadow: var(--shadow-md);
}
.handoff__kicker {
  font-family: var(--label);
  font-size: .66rem; font-weight: 600;
  letter-spacing: .3em; text-transform: uppercase;
  color: var(--gulf);
  margin-bottom: 1.1rem;
}
.handoff__title {
  font-size: clamp(1.7rem, 3.6vw, 2.6rem);
  color: var(--bone);
  max-width: 20ch;
  margin: 0 auto 1.2rem;
}
.handoff__text {
  max-width: 56ch;
  margin: 0 auto 2rem;
  color: var(--sand-l);
  font-size: .98rem;
}
.handoff__note {
  margin-top: 1.2rem;
  font-size: .72rem;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--sand);
}
.listings__meta {
  margin-top: 1.8rem;
  font-size: .74rem; color: var(--muted); line-height: 1.7;
}

/* ============================================================ modal ====== */

.modal { position: fixed; inset: 0; z-index: 200; display: none; }
.modal.is-open { display: block; }
.modal__backdrop {
  position: absolute; inset: 0;
  background: rgba(31, 36, 34, .78);
  backdrop-filter: blur(3px);
}
.modal__dialog {
  position: relative;
  width: min(100% - 2rem, 1020px);
  max-height: 90dvh;
  margin: 5dvh auto 0;
  background: var(--paper);
  border-radius: var(--radius);
  overflow: hidden;
  display: grid;
  grid-template-columns: minmax(0, 7fr) minmax(0, 5fr);
  box-shadow: var(--shadow-lg);
}
@media (max-width: 860px) {
  .modal__dialog { grid-template-columns: 1fr; grid-template-rows: auto minmax(0, 1fr); }
}
.modal__close {
  position: absolute; top: .7rem; right: .7rem; z-index: 5;
  width: 44px; height: 44px;
  display: grid; place-items: center;
  background: rgba(255, 255, 255, .92);
  border: 0; border-radius: 50%;
  font-size: 1.5rem; line-height: 1; color: var(--ink);
  cursor: pointer;
}
.modal__media { background: var(--deep); display: flex; flex-direction: column; min-width: 0; }
.modal__stage { position: relative; flex: 1; min-height: 260px; background: var(--deep); }
.modal__stage img { width: 100%; height: 100%; object-fit: cover; cursor: zoom-in; }
.modal__nav {
  position: absolute; top: 50%; transform: translateY(-50%);
  width: 44px; height: 44px;
  display: grid; place-items: center;
  background: rgba(255, 255, 255, .9);
  border: 0; border-radius: 50%;
  font-size: 1.4rem; color: var(--ink);
  cursor: pointer;
}
.modal__nav--prev { left: .7rem; }
.modal__nav--next { right: .7rem; }
.modal__counter {
  position: absolute; bottom: .8rem; right: .8rem;
  padding: .3rem .7rem;
  background: rgba(31, 36, 34, .78);
  color: var(--bone);
  font-size: .7rem; border-radius: 2px;
  font-variant-numeric: tabular-nums;
}
.modal__thumbs {
  display: flex; gap: .4rem;
  padding: .6rem;
  overflow-x: auto;
  background: var(--deep-2);
}
.modal__thumb {
  flex: 0 0 72px; height: 52px;
  padding: 0; border: 2px solid transparent; border-radius: 2px;
  background: none; cursor: pointer; overflow: hidden;
}
.modal__thumb img { width: 100%; height: 100%; object-fit: cover; }
.modal__thumb.is-active { border-color: var(--gulf); }
.modal__body { padding: 2rem 1.8rem; overflow-y: auto; }
.modal__addr { font-size: 1.7rem; margin-bottom: .3rem; }
.modal__city { font-size: .85rem; color: var(--muted); margin-bottom: 1.2rem; }
.modal__facts {
  display: flex; flex-wrap: wrap; gap: 1.2rem;
  padding: .9rem 0; margin-bottom: 1.2rem;
  border-top: 1px solid var(--line); border-bottom: 1px solid var(--line);
  font-size: .82rem; color: var(--ink-2);
}
.modal__desc { font-size: .92rem; color: var(--ink-2); }
.modal__nudge { font-size: .88rem; }
.modal__foot {
  display: flex; flex-wrap: wrap; gap: .8rem; align-items: center;
  margin-top: 1.6rem;
}
.modal__source { font-size: .74rem; letter-spacing: .1em; text-transform: uppercase; }
.modal__disclaimer {
  margin-top: 1.4rem; padding-top: 1.1rem;
  border-top: 1px solid var(--line);
  font-size: .7rem; color: var(--muted); line-height: 1.65;
}

/* ============================================================ faq ======== */

.faq { max-width: 780px; }
.faq__item {
  border-bottom: 1px solid var(--line);
}
.faq__item summary {
  display: flex; justify-content: space-between; align-items: center; gap: 1.5rem;
  padding: 1.4rem 0;
  cursor: pointer;
  list-style: none;
  font-family: var(--display);
  font-size: 1.25rem;
  color: var(--ink);
}
.faq__item summary::-webkit-details-marker { display: none; }
.faq__item summary::after {
  content: ""; flex: 0 0 12px; height: 12px;
  background: currentColor;
  clip-path: polygon(45% 0, 55% 0, 55% 45%, 100% 45%, 100% 55%, 55% 55%, 55% 100%, 45% 100%, 45% 55%, 0 55%, 0 45%, 45% 45%);
  transition: transform .35s var(--ease);
}
.faq__item[open] summary::after { transform: rotate(135deg); }
.faq__answer { padding: 0 0 1.5rem; color: var(--ink-2); font-size: .95rem; max-width: 68ch; }

/* ============================================================ journey ==== */

/* Three numbered stages side by side, each ending in its own call to action.
   Follows the buying page at schranerrealty.com: the number is the visual
   anchor, oversized and in the accent, with the heading under it rather than
   beside it. Buttons sit on a shared baseline via margin-top auto, so a longer
   paragraph in one column does not leave its button floating. */
.journey { padding: clamp(4.5rem, 9vw, 8rem) 0 0; }

/* Full width colour band. Alternates dark, light, dark down the page. */
.journey__band { padding: clamp(3.5rem, 6vw, 5.5rem) 0; }
.journey__band--dark { background: var(--deep); color: var(--bone); }
.journey__band--dark .journey__title { color: var(--bone); }
.journey__band--dark .journey__text { color: var(--sand-l); }
.journey__band--dark .journey__num { color: var(--gulf); }

.journey__row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: clamp(2rem, 5vw, 4.5rem);
  align-items: center;
}
/* Photo on the left, copy on the right. Source order stays copy first so the
   reading order is right for a screen reader on every band. */
.journey__row--flip .journey__copy  { order: 2; }
.journey__row--flip .journey__figure { order: 1; }

/* The photograph breaks out past the band, which is the whole trick: the
   negative margin is larger than the band padding, so it overhangs into the
   sections above and below. Nothing in this component may set overflow
   hidden or the overhang is clipped away. */
.journey__figure {
  margin: clamp(-5rem, -8vw, -7rem) 0;
  padding: 10px;
  background: var(--paper);
  box-shadow: var(--shadow-lg);
  position: relative;
  z-index: 1;
}
.journey__figure img { width: 100%; height: auto; display: block; }

@media (max-width: 780px) {
  /* Stacked, so the overhang has nothing to overhang into. */
  .journey__row { grid-template-columns: 1fr; }
  .journey__row--flip .journey__copy,
  .journey__row--flip .journey__figure { order: 0; }
  .journey__figure { margin: 0; max-width: 26rem; }
}
.journey__num {
  font-family: var(--display);
  font-size: clamp(2.6rem, 5vw, 3.6rem);
  line-height: 1;
  color: var(--gulf-d);
  margin: 0 0 .6rem;
}
.journey__title {
  font-size: clamp(1.7rem, 3vw, 2.2rem);
  margin-bottom: 1rem;
}
.journey__text {
  color: var(--ink-2);
  font-size: .96rem;
}
/* Solid taupe, as on the reference, rather than the ghost outline used
   elsewhere on the site. Taupe rather than the accent because this is a
   button, and the accent never appears on one. #7b736d reads on both the dark
   and the light bands without needing a variant for each. */
.journey__cta {
  margin-top: 2rem;
  align-self: flex-start;
  background: #7b736d;
  border-color: #7b736d;
  color: var(--paper);
}
.journey__cta:hover {
  background: var(--ink);
  border-color: var(--ink);
  color: var(--paper);
}
.journey__band--dark .journey__cta:hover {
  background: var(--bone);
  border-color: var(--bone);
  color: var(--ink);
}

.section--dark .journey__step { border-top-color: rgba(239, 238, 236, .18); }
.section--dark .journey__num { color: var(--gulf); }
.section--dark .journey__text { color: var(--sand-l); }

/* ============================================================ process ==== */

.steps { counter-reset: step; display: grid; gap: 2.2rem; }
.step-item {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 1.5rem;
  align-items: start;
}
.step-item__num {
  counter-increment: step;
  font-family: var(--label);
  font-size: .74rem; font-weight: 600; letter-spacing: .16em;
  color: var(--gulf-d);
  padding-top: .35rem;
  min-width: 2.4rem;
}
.step-item__num::before { content: "0" counter(step); }
.section--dark .step-item__num { color: var(--gulf); }
.step-item__title { font-size: 1.4rem; margin-bottom: .5rem; }
.step-item__text { font-size: .95rem; color: var(--ink-2); }
.section--dark .step-item__text { color: var(--sand-l); }

/* ============================================================ calculator = */

.mcalc {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: clamp(1.8rem, 4vw, 3rem);
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: clamp(1.8rem, 3.5vw, 2.6rem);
}
@media (max-width: 720px) { .mcalc { grid-template-columns: 1fr; } }
.mcalc__out {
  background: var(--deep);
  color: var(--bone);
  border-radius: var(--radius);
  padding: 1.8rem 1.6rem;
  align-self: start;
}
.mcalc__total {
  font-family: var(--display);
  font-size: clamp(2.2rem, 5vw, 3rem);
  line-height: 1;
  color: var(--gulf);
}
.mcalc__total-label {
  font-size: .64rem; letter-spacing: .2em; text-transform: uppercase;
  color: var(--sand-l);
  margin-bottom: .7rem;
}
.mcalc__rows { margin-top: 1.6rem; border-top: 1px solid rgba(239,238,236,.16); }
.mcalc__row {
  display: flex; justify-content: space-between; gap: 1rem;
  padding: .65rem 0;
  font-size: .85rem;
  border-bottom: 1px solid rgba(239,238,236,.1);
}
.mcalc__row span { color: var(--sand-l); }
.mcalc__row b { font-weight: 600; font-variant-numeric: tabular-nums; }
.mcalc__note {
  margin-top: 1.2rem;
  font-size: .7rem; color: var(--sand);
  line-height: 1.65;
}

/* ============================================================ footer ===== */

.footer {
  background: var(--deep);
  color: var(--sand-l);
  padding: clamp(3.5rem, 7vw, 5.5rem) 0 2.5rem;
  font-size: .88rem;
}
.footer a { color: var(--sand-l); text-decoration: none; }
.footer a:hover { color: var(--bone); text-decoration: underline; }

.footer__top {
  display: grid;
  grid-template-columns: minmax(0, 4fr) repeat(2, minmax(0, 2fr)) minmax(0, 3fr);
  gap: 2.5rem;
  padding-bottom: 3rem;
  border-bottom: 1px solid rgba(239, 238, 236, .14);
}
@media (max-width: 900px) { .footer__top { grid-template-columns: 1fr 1fr; } }
@media (max-width: 560px) { .footer__top { grid-template-columns: 1fr; } }

.footer__brand-name {
  font-family: var(--label);
  font-size: 1.05rem; font-weight: 600;
  letter-spacing: .16em; text-transform: uppercase;
  color: var(--bone);
}
/* The full lockup, reversed, as the footer signature. Width capped rather than
   set, so it never outgrows a narrow column. The alt text carries the name, so
   nothing is lost to a screen reader or to a crawler by it being artwork. */
.footer__logo { width: 100%; max-width: 226px; height: auto; }
.footer__tagline { margin-top: .9rem; max-width: 32ch; color: var(--sand-l); }

.footer__title {
  font-family: var(--ui);
  font-size: .64rem; font-weight: 600;
  letter-spacing: .2em; text-transform: uppercase;
  color: var(--bone);
  margin: 0 0 1.1rem;
}
.footer__list { list-style: none; margin: 0; padding: 0; }
.footer__list li { margin-bottom: .6rem; }

/* Point of contact block. Florida rule 61J2-10.025 requires the brokerage's
   licensed name adjacent to, above, or below every point of contact. The
   brokerage line is part of this block for that reason, not for decoration.
   Do not separate them. */
.footer__contact address { font-style: normal; line-height: 1.8; }
.footer__firm {
  display: block;
  margin-top: .7rem;
  padding-top: .7rem;
  border-top: 1px solid rgba(239, 238, 236, .14);
  font-size: .78rem;
  color: var(--sand);
}
.footer__firm b { color: var(--sand-l); font-weight: 600; }

.footer__bottom { padding-top: 2rem; }
.footer__legal {
  font-size: .74rem;
  line-height: 1.75;
  color: var(--sand);
  max-width: 90ch;
  margin-bottom: 1rem;
}

/* Equal Housing Opportunity mark. Inline SVG as a CSS mask so there is no
   image file to go missing. */
.footer__eho { display: flex; align-items: flex-start; gap: .7rem; }
.eho-mark {
  flex: 0 0 22px;
  width: 22px; height: 22px;
  margin-top: .1rem;
  background: var(--sand-l);
  -webkit-mask: var(--eho) center / contain no-repeat;
          mask: var(--eho) center / contain no-repeat;
}
:root {
  --eho: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 64 64'%3E%3Cpath d='M32 6 4 26h8v30h40V26h8L32 6zm0 8.6L44 24v26H20V24l12-9.4z'/%3E%3Cpath d='M24 30h16v3H24zm0 6h16v3H24zm0 6h16v3H24z'/%3E%3C/svg%3E");
}

.footer__social { display: flex; gap: 1.2rem; margin-top: 1.2rem; }
.footer__social a {
  font-size: .7rem; letter-spacing: .12em; text-transform: uppercase;
}

/* ============================================================ misc ======= */

.prose { max-width: 68ch; }
.prose h2 { font-size: clamp(1.6rem, 3vw, 2.2rem); margin: 2.6rem 0 1rem; }
.prose h3 { font-size: 1.3rem; margin: 2rem 0 .7rem; }
.prose ul, .prose ol { padding-left: 1.3rem; color: var(--ink-2); }
.prose li { margin-bottom: .5rem; }

.lede-lg {
  font-family: var(--display);
  font-size: clamp(1.35rem, 2.4vw, 1.8rem);
  line-height: 1.45;
  color: var(--ink);
  max-width: 44ch;
}

/* ======================================================= contrast block == */

/* The opening statement on the neighborhoods hub. Two places set against each
   other with the distance between them on the rule that divides them, and the
   consequence underneath. It is the same words the paragraph had, given the
   shape of the argument they are making.

   Both new blocks here take the accent on an italic Cormorant phrase, which is
   .hero__title em's idiom. On this ground it has to be --gulf-d and not
   --gulf: the light one is 5.3:1 on the dark panels and nowhere near that on
   bone. */
.contrast { max-width: 1000px; margin-inline: auto; text-align: center; }
.contrast .eyebrow { margin-bottom: 0; }

.contrast__pair {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  gap: clamp(1.2rem, 4vw, 3.5rem);
  margin-top: clamp(1.6rem, 4vw, 2.6rem);
}
.contrast__place {
  font-family: var(--display);
  font-size: clamp(1.9rem, 4.5vw, 3.2rem);
  line-height: 1.05;
  margin: 0 0 .8rem;
}
.contrast__text {
  font-size: .95rem;
  color: var(--ink-2);
  max-width: 28ch;
  margin: 0 auto;
}

/* The divider carries the one fact both halves share. Hairlines above and
   below on desktop so it reads as a seam rather than as a third column. */
.contrast__vs {
  display: grid;
  justify-items: center;
  gap: .9rem;
  margin: 0;
  font-family: var(--display);
  font-style: italic;
  font-size: 1.05rem;
  line-height: 1.2;
  color: var(--gulf-d);
  white-space: nowrap;
}
.contrast__vs::before,
.contrast__vs::after {
  content: "";
  width: 1px;
  height: clamp(1.6rem, 5vw, 3.4rem);
  background: var(--line);
}

@media (max-width: 760px) {
  .contrast__pair { grid-template-columns: 1fr; }
  /* Stacked, so the seam turns and runs across instead of down. */
  .contrast__vs::before,
  .contrast__vs::after { width: clamp(2.5rem, 22vw, 6rem); height: 1px; }
}

.contrast__punch {
  font-family: var(--display);
  font-size: clamp(1.45rem, 3vw, 2.25rem);
  line-height: 1.32;
  max-width: 30ch;
  margin: clamp(2.2rem, 5vw, 3.6rem) auto 0;
  padding-top: clamp(1.8rem, 4vw, 3rem);
  border-top: 1px solid var(--line);
}
.contrast__punch em { font-style: italic; color: var(--gulf-d); }

/* ========================================================= page opener === */

/* An editorial opener: the claim at display size on the left, the turn it
   takes in a narrow column on the right, divided by a hairline. Asymmetric on
   purpose, so it does not read as the same device as .contrast on the
   neighborhoods hub, which is a symmetric two up comparison. */
.opener { max-width: 1000px; }
.opener .eyebrow { margin-bottom: clamp(1.2rem, 3vw, 1.8rem); }

.opener__grid {
  display: grid;
  grid-template-columns: minmax(0, 1.4fr) minmax(0, 1fr);
  gap: clamp(1.6rem, 4.5vw, 3.5rem);
  align-items: start;
}
.opener__claim {
  font-family: var(--display);
  font-size: clamp(1.7rem, 3.8vw, 2.9rem);
  line-height: 1.2;
  color: var(--ink);
  margin: 0;
}
.opener__claim em { font-style: italic; color: var(--gulf-d); }

.opener__aside {
  font-size: .98rem;
  line-height: 1.75;
  color: var(--ink-2);
  margin: 0;
  padding-left: clamp(1.6rem, 4.5vw, 3.5rem);
  border-left: 1px solid var(--line);
  /* Hangs off the first line of the claim rather than its box, so the two
     columns look aligned to the eye rather than to the layout. */
  margin-top: .45rem;
}

@media (max-width: 760px) {
  .opener__grid { grid-template-columns: 1fr; }
  .opener__aside {
    padding-left: 0;
    border-left: 0;
    padding-top: 1.4rem;
    border-top: 1px solid var(--line);
    max-width: 52ch;
  }
}

.disclaimer {
  font-size: .74rem;
  color: var(--muted);
  line-height: 1.7;
  max-width: 90ch;
  padding: 1.2rem 0 0;
  border-top: 1px solid var(--line);
  margin-top: 2.5rem;
}

/* Print: nobody prints a real estate site, but if they print a listing page
   they should get the address and the phone number without the furniture. */
@media print {
  .nav, .hero, .footer__social, .listings__nav, .modal { display: none !important; }
  body { background: #fff; color: #000; }
  .reveal { opacity: 1 !important; transform: none !important; }
}

/* ------------------------------------------------------------------ toc ---
   "In this guide" jump row on guide pages, generated by build-guides.js.
   Links use the dark accent, the one place the accent family appears on a
   light ground, consistent with links elsewhere. */
.toc {
  border: 1px solid var(--line);
  background: var(--bone-2);
  border-radius: var(--radius);
  padding: 1.3rem 1.5rem;
  margin: 2rem 0 2.6rem;
}
.toc__kicker {
  font-family: var(--label);
  font-size: .72rem;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--muted);
  margin: 0 0 .8rem;
}
.toc__list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  gap: .5rem 1.4rem;
}
.toc__link {
  font-family: var(--ui);
  font-size: .85rem;
  color: var(--gulf-d);
  text-decoration: none;
  border-bottom: 1px solid var(--line);
  padding-bottom: .15rem;
}
.toc__link:hover { border-bottom-color: var(--gulf-d); }

/* ------------------------------------------------------------- landing ---
   The g/ pages Jennifer sends in a DM. Slim chrome: brand only, no nav. */
.lp__head {
  padding: 1.4rem 1.5rem;
  display: flex;
  justify-content: center;
  background: var(--deep);
  border-bottom: 1px solid rgba(255, 255, 255, .08);
}
.lp__head .nav__brand-name,
.lp__head .nav__brand-sub { color: var(--bone); }

/* ========================================================= language ======
   The English and Spanish sides of the site are separate static pages, so
   this is a plain link, not a toggle that swaps text in place. It always
   names the language you would GET, written in that language: a visitor who
   reads little English still recognises the word Espanol.

   No flags anywhere. A flag names a country, not a language, and any single
   flag excludes most of the people this is for.

   It lives in .nav__inner beside the phone block rather than inside
   .nav__links, and that placement is the whole point: below 1040 the links
   collapse into the burger panel and .nav__cta is hidden entirely, so a
   switch inside either one would be invisible on a phone. This stays in the
   bar at every width.
   ========================================================================= */
.nav__lang {
  flex: none;
  display: inline-flex; align-items: center; gap: .42rem;
  padding: .46rem .78rem;
  border: 1px solid var(--line);
  border-radius: 999px;
  font-family: var(--ui);
  font-size: .68rem; font-weight: 600;
  letter-spacing: .12em; text-transform: uppercase;
  color: var(--ink-2);
  text-decoration: none;
  white-space: nowrap;
  transition: color .3s var(--ease), border-color .3s var(--ease),
              background .3s var(--ease);
}
.nav__lang svg { width: 14px; height: 14px; flex: none; }
.nav__lang:hover {
  color: var(--paper);
  background: var(--deep);
  border-color: var(--deep);
}
/* Over the dark hero the nav shell is transparent, so the pill has to carry
   its own contrast the way .nav__cta-num does. */
.nav.is-top .nav__lang {
  color: var(--bone);
  border-color: rgba(239, 238, 236, .45);
}
.nav.is-top .nav__lang:hover {
  color: var(--deep);
  background: var(--bone);
  border-color: var(--bone);
}
/* The burger panel drops a bone ground behind a header that may still be
   flagged is-top. Same reasoning as .nav.is-open .nav__brand-mark above. */
.nav.is-open .nav__lang { color: var(--ink-2); border-color: var(--line); }

@media (max-width: 1280px) {
  .nav__inner { gap: .75rem; }
  .nav__lang { padding: .42rem .66rem; font-size: .64rem; }
}
/* Narrow phones: the globe carries it and the word steps aside, the same
   trade the brand monogram makes at this width. aria-label on the link is
   what keeps it announced once the text is gone. */
@media (max-width: 380px) {
  .nav__lang span { display: none; }
  .nav__lang { padding: .42rem .5rem; }
}

/* ---------------------------------------------------------- language bar --
   Shown only when the browser asks for Spanish and the visitor is on an
   English page, and only until they answer it once. It offers, it never
   redirects: a forced redirect traps bilingual readers and Google advises
   against it.

   It is fixed above the nav rather than in the document flow, so the nav and
   the burger panel are pushed down by --langbar-h instead of being covered.
   ------------------------------------------------------------------------ */
body { --langbar-h: 0px; }
body.has-langbar { --langbar-h: 46px; }
.nav { top: var(--langbar-h); }
/* The anchor offset has to grow with the bar too, or a jump to #contact comes
   to rest under a header that is now 46px lower. 92px is the value authored
   at the top of this file. */
body.has-langbar { scroll-padding-top: calc(92px + var(--langbar-h)); }

.langbar {
  position: fixed; inset: 0 0 auto 0; z-index: 110;
  min-height: 46px;
  display: flex; align-items: center; justify-content: center;
  gap: 1rem;
  flex-wrap: wrap;
  padding: .5rem 1.1rem;
  background: var(--deep);
  color: var(--bone);
  font-family: var(--ui);
  font-size: .82rem;
}
.langbar__text { margin: 0; }
.langbar__go {
  display: inline-block;
  padding: .32rem .85rem;
  border: 1px solid var(--gulf);
  border-radius: 999px;
  color: var(--bone);
  text-decoration: none;
  font-size: .72rem; font-weight: 600;
  letter-spacing: .1em; text-transform: uppercase;
  transition: background .3s var(--ease), color .3s var(--ease);
}
.langbar__go:hover { background: var(--gulf); color: var(--deep); }
.langbar__close {
  position: absolute; right: .5rem; top: 50%; transform: translateY(-50%);
  width: 34px; height: 34px;
  background: none; border: 0; cursor: pointer;
  color: rgba(239, 238, 236, .7);
  font-size: 1.3rem; line-height: 1;
}
.langbar__close:hover { color: var(--bone); }

@media (max-width: 1280px) {
  body.has-langbar { --langbar-h: 54px; }
  /* The burger panel is pinned under the header, so it has to clear the bar
     as well. 76px is .nav__inner min-height, authored in the nav block. */
  .nav__links {
    inset: calc(76px + var(--langbar-h)) 0 auto 0;
    max-height: calc(100dvh - 76px - var(--langbar-h));
  }
  .langbar { font-size: .76rem; padding-right: 2.4rem; }
}

/* ------------------------------------------------------------ hablo -------
   "Hablo espanol" next to her photograph and beside the contact details.
   This is the part the language switch cannot do on its own: a switch proves
   the PAGE is translated, and what a Spanish speaking client is actually
   deciding is whether the AGENT can take them through an inspection and a
   closing in Spanish. Jennifer can, so the site says so, in Spanish, where
   her face is.
   -------------------------------------------------------------------------- */
.hablo {
  display: flex; align-items: flex-start; gap: .8rem;
  margin: 1.6rem 0 0;
  padding: .95rem 1.15rem;
  border-left: 2px solid var(--gulf-d);
  background: rgba(111, 158, 163, .09);
  border-radius: 0 var(--radius) var(--radius) 0;
  font-family: var(--ui);
  font-size: .92rem;
  line-height: 1.6;
  color: var(--ink-2);
}
.hablo svg { width: 18px; height: 18px; flex: none; margin-top: .18rem; color: var(--gulf-d); }
.hablo b { color: var(--ink); font-weight: 600; }
.hablo a { color: var(--gulf-d); text-decoration: none; border-bottom: 1px solid var(--line); }
.hablo a:hover { border-bottom-color: var(--gulf-d); }

/* On the dark About band the tinted ground and the ink colors both invert. */
.section--dark .hablo {
  background: rgba(111, 158, 163, .13);
  border-left-color: var(--gulf);
  color: rgba(239, 238, 236, .86);
}
.section--dark .hablo svg { color: var(--gulf); }
.section--dark .hablo b { color: var(--bone); }
.section--dark .hablo a { color: var(--gulf); border-bottom-color: rgba(111, 158, 163, .45); }
.section--dark .hablo a:hover { border-bottom-color: var(--gulf); }
