/* ==========================================================================
   jacquelinewongrealestate.com — production stylesheet
   Mobile-first. The design mockups in ../design are desktop-only
   (min-width:1180px); this is the responsive implementation of them.
   Palette and type carry over unchanged — see ../design/README.md for the
   two-gold rule, which still applies: champagne on dark, bronze on light.
   ========================================================================== */

:root{
  --ink:#1E1E1C; --ink-soft:#43413C;
  --paper:#F5F2EA; --card:#FFFFFF;
  --bronze:#8A6318; --champagne:#E3C46A;
  --stone:#6B6862; --line:#E4DECF; --line-soft:#EFEAE0;
  /* Motion scale. Everything else here comes off a scale — type, space,
     colour — and motion was the one system that did not. Durations follow
     the usual bands: micro-feedback, hover, state change, reveal, entrance.
     --ease-out is the decelerate curve already used across the site. */
  --dur-1:120ms; --dur-2:200ms; --dur-3:340ms; --dur-4:520ms; --dur-5:720ms;
  --ease-out:cubic-bezier(.2,.8,.3,1);
  --ease-press:cubic-bezier(.4,0,.6,1);
  --display:"Jost","Noto Sans TC",Futura,-apple-system,Helvetica,Arial,sans-serif;
  --ui:"Archivo","Noto Sans TC",-apple-system,"Segoe UI",Helvetica,Arial,sans-serif;
  --cjk:"Noto Sans TC","Jost",-apple-system,"PingFang TC","Microsoft JhengHei",sans-serif;
  --pad:clamp(18px,5vw,44px);
  --maxw:1200px;

  /* Type scale. Nine steps, down from twenty-one ad-hoc sizes. Two jobs:
     numbers (price, beds, sqft, years) carry --display + tabular figures;
     names and prose stay in --ui. Anything not on this scale is a bug. */
  --t-display:clamp(36px,9.5vw,68px);
  --t-3xl:clamp(30px,8vw,42px);
  --t-2xl:clamp(23px,4.2vw,30px);
  --t-xl:25px;
  --t-lg:18px;
  --t-md:16px;
  --t-sm:14px;
  --t-xs:12.5px;
  --t-2xs:11px;

  /* Spacing, 4px based. --sec-y is the vertical rhythm between sections. */
  --s1:4px; --s2:8px; --s3:12px; --s4:16px; --s5:24px; --s6:32px; --s7:48px;
  --sec-y:clamp(44px,7vw,84px);

  /* The metallic gradient, kept in one place. Jacqueline chose this over the
     flat champagne — client call, 2026-08-26. #22221C on the darkest stop is
     4.94:1, so it still passes AA; do not lighten the first stop past
     roughly #C08F30 or the label stops being readable at the left edge. */
  --gold:linear-gradient(100deg,#B8862C,#E3C46A 55%,#F7EDC4);
  --on-gold:#22221C;
}
*{box-sizing:border-box}
html{-webkit-text-size-adjust:100%}
body{margin:0;background:var(--paper);color:var(--ink);
  font-family:var(--ui);font-size:var(--t-md);line-height:1.65;
  -webkit-font-smoothing:antialiased}
img{max-width:100%;display:block}
a{color:inherit;text-decoration:none}
/* The focus ring obeys the two-gold rule like everything else: bronze reads
   on the paper ground (4.84:1) but only scrapes 3.08:1 on charcoal, where
   champagne gets 9.84:1. Same reasoning as the nav mark and the price pins. */
:focus-visible{outline:2px solid var(--bronze);outline-offset:3px}
.hero :focus-visible,.band :focus-visible,.agent :focus-visible,
.contactbar :focus-visible,.sitefoot :focus-visible,
.nb-hero :focus-visible,.nbcard:focus-visible{outline-color:var(--champagne)}
.wrap{max-width:var(--maxw);margin:0 auto;padding-left:var(--pad);padding-right:var(--pad)}
.sr{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap}

/* ---------- nav ---------- */
.nav{background:var(--card);border-bottom:1px solid var(--line);position:sticky;top:0;z-index:50}
.nav-in{display:flex;align-items:center;justify-content:space-between;gap:16px;
  padding-top:14px;padding-bottom:14px}
.brand{display:flex;align-items:center;gap:10px;min-width:0}
.mark{width:30px;height:30px;border-radius:3px;background:var(--ink);color:var(--champagne);
  display:grid;place-items:center;font-family:var(--display);font-weight:600;
  font-size:var(--t-xs);
  letter-spacing:.04em;flex:none}
.wordmark{font-family:var(--display);font-weight:500;font-size:var(--t-xs);letter-spacing:.18em;
  text-transform:uppercase;line-height:1.25;min-width:0}
.wordmark small{display:block;font-size:var(--t-2xs);letter-spacing:.16em;color:var(--stone);
  font-weight:400;margin-top:2px;text-transform:uppercase}
.nav-cta{background:var(--ink);color:#fff;border-radius:999px;padding:10px 18px;
  font-size:var(--t-sm);
  font-weight:500;white-space:nowrap;font-variant-numeric:tabular-nums;flex:none}
.menu{display:flex;gap:var(--s5);font-size:var(--t-sm);overflow-x:auto;scrollbar-width:none;
  border-top:1px solid var(--line-soft);padding:11px 0;-webkit-overflow-scrolling:touch}
.menu::-webkit-scrollbar{display:none}
/* The visible box stays tight so the active underline hugs the text, but the
   hit area is expanded to clear the 24px minimum for pointer targets — these
   links measured 23-27px tall, and most of this site's traffic is a phone. */
.menu a{white-space:nowrap;padding-bottom:2px;color:var(--ink);position:relative}
.menu a::before{content:"";position:absolute;inset:-10px -7px}
.menu a[aria-current]{border-bottom:1.5px solid var(--bronze);color:var(--bronze)}
/* ---------- language control ----------
   Pinned in the top row beside the phone number, never in the scrolling
   menu. Sized as a control, not a nav link: a boxed pill so it reads as a
   switch. Champagne would fail contrast on this paper ground, so the
   active-state colour is bronze — the two-gold rule again. */
.nav-right{display:flex;align-items:center;gap:var(--s3);flex:none}
.nav-lang{font-family:var(--cjk);font-size:var(--t-xs);letter-spacing:.1em;
  padding:7px 13px;border:1px solid var(--line);border-radius:999px;
  color:var(--ink-soft);white-space:nowrap;line-height:1;
  transition:border-color var(--dur-2) var(--ease-out),color var(--dur-2) var(--ease-out)}
.nav-lang:hover{border-color:var(--bronze);color:var(--bronze)}
/* Under 560px the top row carries the mark, the name, the language pill and
   the phone number. The descriptor line is the one thing there that is not
   a control, and it is repeated in the footer, so it goes rather than
   wrapping the name onto four lines and making the sticky nav 153px tall. */
@media (max-width:559px){
  .wordmark small{display:none}
  .wordmark{font-size:var(--t-2xs);letter-spacing:.13em;line-height:1.3}
  .brand{gap:var(--s2)}
  .nav-cta{padding:9px 14px;font-size:var(--t-xs)}
  .nav-lang{padding:6px 11px}
  .nav-right{gap:var(--s2)}
  .nav-in{padding-top:var(--s3);padding-bottom:var(--s3)}
}
@media (min-width:900px){
  .nav-in{padding-top:18px;padding-bottom:18px}
  .wordmark{font-size:var(--t-sm);letter-spacing:.20em}
  .menu{border-top:0;padding:0;gap:26px;overflow:visible;order:2}
  .nav-in{position:relative}
  .nav-wrap{display:flex;align-items:center;gap:28px;justify-content:space-between}
}

/* ---------- hero: full-bleed image, text overlaid ----------
   The skyline is bright, so legibility comes from a two-axis scrim —
   darkest bottom-left where the type sits, lighter top-right so the
   photograph still reads. Champagne on dark, per the two-gold rule. */
.hero{
  position:relative;
  min-height:clamp(460px,80svh,700px);
  display:flex;flex-direction:column;justify-content:flex-end;
  background-size:cover;background-position:50% 52%;
  background-color:var(--ink);
  color:var(--paper);
  isolation:isolate;
}
.hero::after{
  content:"";position:absolute;inset:0;z-index:-1;
  background:
    linear-gradient(180deg,rgba(18,18,16,.34) 0%,rgba(18,18,16,.10) 34%,rgba(18,18,16,.82) 100%),
    linear-gradient(90deg,rgba(18,18,16,.60) 0%,rgba(18,18,16,.14) 62%,rgba(18,18,16,0) 100%);
}
.hero-inner{width:100%;padding-top:clamp(56px,14vh,110px);padding-bottom:clamp(22px,4vw,34px)}
.hero h1{
  font-family:var(--display);font-weight:300;text-transform:uppercase;margin:0;
  font-size:var(--t-display);line-height:.98;letter-spacing:-.012em;
  text-wrap:balance;text-shadow:0 2px 18px rgba(0,0,0,.42);
}
.hero h1 b{font-weight:600}
.hero .sub{
  margin:var(--s5) 0 0;max-width:44ch;text-wrap:pretty;
  font-size:var(--t-md);color:rgba(245,242,234,.9);
  text-shadow:0 1px 10px rgba(0,0,0,.5);
}
/* Scoped to .hero deliberately. This shares .cb-btn's markup, and .cb-btn is
   defined further down the file — at equal specificity that wins on source
   order and stretches the button to full width. The old stylesheet worked
   around it with a second copy of this rule at the very bottom; scoping is
   the fix that survives someone reordering the file. */
.hero .hero-cta{margin-top:var(--s6);display:inline-flex;width:auto;
  align-self:flex-start}
@media (max-width:599px){.hero .hero-cta{display:flex;width:100%}}

/* stats sit on the hero itself rather than in a separate block */
.hero-stats{
  width:100%;
  display:grid;grid-template-columns:repeat(3,auto);gap:clamp(20px,7vw,52px);
  justify-content:start;
  padding-top:clamp(16px,3vw,20px);padding-bottom:clamp(20px,4vw,30px);
  border-top:1px solid rgba(245,242,234,.20);
}
.hero-stats b{
  display:block;font-family:var(--display);font-size:var(--t-xl);
  font-weight:500;color:var(--champagne);font-variant-numeric:tabular-nums;
  letter-spacing:-.015em;line-height:1.1;
  text-shadow:0 1px 8px rgba(0,0,0,.45);
}
.hero-stats span{
  font-size:var(--t-2xs);letter-spacing:.14em;text-transform:uppercase;
  color:rgba(245,242,234,.78);margin-top:var(--s1);display:block;
  text-wrap:balance;
}

/* kept for when the MLS feed is live and the search field returns */
.search{display:flex;border:1px solid var(--ink);border-radius:2px;overflow:hidden;
  background:var(--card);margin-top:20px}
.search .f{flex:1;padding:14px 16px;color:var(--stone);font-size:var(--t-sm);min-width:0;
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.search .go{width:54px;background:var(--ink);color:var(--champagne);display:grid;
  place-items:center;font-size:var(--t-lg);flex:none}

/* ---------- sections & cards ---------- */
.sec{padding-top:var(--sec-y);padding-bottom:var(--sec-y)}
.sec-head{display:flex;align-items:baseline;justify-content:space-between;gap:16px;
  margin-bottom:var(--s5);flex-wrap:wrap}
.sec-head h2{font-family:var(--display);font-weight:400;font-size:var(--t-2xl);
  margin:0;letter-spacing:-.01em}
.sec-head a{font-size:var(--t-xs);color:var(--bronze);white-space:nowrap;
  padding:6px 0}
.cards{display:grid;gap:var(--s5);grid-template-columns:1fr}
@media (min-width:600px){.cards{grid-template-columns:repeat(2,1fr)}}
@media (min-width:960px){.cards{grid-template-columns:repeat(3,1fr)}}
.card{background:var(--card);border:1px solid var(--line);border-radius:4px;overflow:hidden;
  display:flex;flex-direction:column;transition:transform var(--dur-2) var(--ease-out),box-shadow var(--dur-2) var(--ease-out)}
.card:hover{transform:translateY(-2px);box-shadow:0 12px 26px rgba(0,0,0,.10)}
.card .ph{aspect-ratio:4/3;background:linear-gradient(150deg,rgba(184,134,44,.16),rgba(30,30,28,.10));
  position:relative;overflow:hidden}
.card .ph img{width:100%;height:100%;object-fit:cover}
.card .ph .none{position:absolute;inset:0;display:grid;place-items:center;
  font-size:var(--t-2xs);letter-spacing:.2em;text-transform:uppercase;color:var(--stone)}
.tag-video{display:inline-flex;align-items:center;gap:5px}
.tag-video svg{width:14px;height:14px;color:#FF0033}
.tag{position:absolute;top:var(--s3);left:var(--s3);background:rgba(30,30,28,.92);
  color:var(--champagne);font-size:var(--t-2xs);letter-spacing:.14em;text-transform:uppercase;
  padding:5px 10px;border-radius:2px}
.card .b{padding:var(--s5) var(--s5) var(--s5);display:flex;flex-direction:column;
  gap:var(--s1);flex:1}
.price{font-family:var(--display);font-size:var(--t-xl);font-weight:500;
  font-variant-numeric:tabular-nums;letter-spacing:-.015em;line-height:1.1}
.addr{font-size:var(--t-sm)}
.where{font-size:var(--t-xs);color:var(--stone)}
.specs{font-size:var(--t-xs);color:var(--stone);margin-top:var(--s3);padding-top:var(--s3);
  border-top:1px solid var(--line);font-variant-numeric:tabular-nums}
.mls{font-size:var(--t-2xs);letter-spacing:.12em;text-transform:uppercase;
  color:var(--stone);margin-top:var(--s2)}

/* ---------- property detail ---------- */
.crumb{font-size:var(--t-xs);color:var(--stone);padding-top:var(--s4)}
.crumb a{color:var(--bronze)}
.gal{display:grid;gap:var(--s2);grid-template-columns:1fr;margin-top:var(--s4)}
.gal a{display:block;overflow:hidden;border-radius:2px;background:var(--line-soft)}
.gal img{width:100%;height:100%;object-fit:cover;aspect-ratio:4/3}
.gal .lead img{aspect-ratio:16/10}
@media (min-width:760px){
  .gal{grid-template-columns:1.62fr 1fr 1fr;grid-auto-rows:1fr}
  .gal .lead{grid-row:span 2;grid-column:1}
  .gal .lead img{aspect-ratio:auto;height:100%}
  .gal a:nth-child(n+6){display:none}
}
.more-shot{position:relative}
.more-shot span{position:absolute;inset:0;display:grid;place-items:center;
  background:rgba(30,30,28,.7);color:var(--paper);font-size:var(--t-sm);letter-spacing:.04em}
.dbody{display:grid;gap:var(--s7);grid-template-columns:1fr;margin-top:var(--s6)}
@media (min-width:900px){.dbody{grid-template-columns:1.5fr .85fr;gap:44px}}
.dhead{display:flex;align-items:flex-start;justify-content:space-between;gap:16px;flex-wrap:wrap}
/* The address is what the page is about, so it is the h1 and it is set as a
   heading. The price still dominates as the number — that is how listings
   are scanned — but the subject is no longer smaller than its own metadata. */
h1.dloc{font-family:var(--display);font-weight:400;font-size:var(--t-2xl);
  margin:0;line-height:1.2;letter-spacing:-.01em;
  display:flex;flex-wrap:wrap;align-items:baseline;gap:var(--s2) var(--s4)}
h1.dloc .dwhere{font-family:var(--ui);font-weight:400;font-size:var(--t-sm);
  color:var(--stone);letter-spacing:0}
/* A street address is Latin in both languages, so it keeps --display on the
   Chinese pages too; only the city/area line beside it takes the CJK face. */
.zh h1.dloc .dwhere{font-family:var(--cjk);letter-spacing:.02em}

/* section-head furniture that used to be written as inline styles */
.sec-count{font-size:var(--t-xs);color:var(--stone);font-variant-numeric:tabular-nums}
.sec-lede{margin:0 0 var(--s2);color:var(--stone);font-size:var(--t-sm);max-width:64ch}
.sec-note{margin-top:var(--s6);font-size:var(--t-xs);color:var(--stone);max-width:70ch}

/* .sec-head carries an h1 on the listings and neighbourhood indexes and an
   h2 everywhere else; both take the same treatment. */
.sec-head h1{font-family:var(--display);font-weight:400;font-size:var(--t-2xl);
  margin:0;letter-spacing:-.01em}
.zh .sec-head h1{font-family:var(--cjk);font-weight:500;letter-spacing:.03em}
.dprice{font-family:var(--display);font-size:var(--t-3xl);font-weight:500;
  margin:var(--s3) 0 0;font-variant-numeric:tabular-nums;letter-spacing:-.02em;
  line-height:1.05}
.dsub{font-size:var(--t-xs);color:var(--stone);margin-top:var(--s2)}
/* The strip carries however many facts a listing actually has. The retail
   unit has only a floor area, and a lone cell in a four-column grid left a
   wide band of the container's own background showing — which reads as a
   broken layout rather than a short list. Fewer facts, narrower strip. */
.facts{display:grid;grid-template-columns:repeat(2,1fr);gap:1px;background:var(--line);
  border:1px solid var(--line);margin-top:var(--s6)}
@media (min-width:560px){
  .facts{grid-template-columns:repeat(4,1fr)}
  .facts-1{grid-template-columns:minmax(160px,240px);width:max-content;max-width:100%}
  .facts-2{grid-template-columns:repeat(2,minmax(160px,220px));width:max-content;max-width:100%}
  .facts-3{grid-template-columns:repeat(3,1fr)}
}
@media (max-width:559px){
  .facts-1{grid-template-columns:1fr}
}
.fact{background:var(--paper);padding:var(--s4) var(--s5)}
.fact b{display:block;font-family:var(--display);font-size:var(--t-lg);font-weight:500;
  font-variant-numeric:tabular-nums;letter-spacing:-.01em}
.fact span{font-size:var(--t-2xs);letter-spacing:.14em;text-transform:uppercase;
  color:var(--stone)}
.note-box{margin-top:var(--s6);padding:var(--s5);background:var(--card);
  border:1px solid var(--line);border-left:2px solid var(--bronze);border-radius:2px}
.note-box h3{font-family:var(--display);font-weight:500;font-size:var(--t-lg);
  margin:0 0 var(--s2)}
.note-box p{margin:0;font-size:var(--t-sm);color:var(--ink-soft);line-height:1.65}

/* ---------- video row (property pages) ----------
   Only rendered for listings that actually have a film. Text rather than the
   YouTube thumbnail: the thumbnails carry marketing overlays, and one of them
   states a floor area that differs from listings.json — two different numbers
   on one page is not something to publish in a property advertisement. */
.vlink{display:flex;align-items:center;gap:var(--s4);margin-top:var(--s6);
  padding:var(--s4) var(--s5);background:var(--ink);color:var(--paper);
  border-radius:4px;transition:transform var(--dur-2) var(--ease-out),box-shadow var(--dur-2) var(--ease-out)}
.vlink:hover{transform:translateY(-2px);box-shadow:0 12px 26px rgba(0,0,0,.18)}
.vl-play{flex:none;display:grid;place-items:center;width:38px;height:38px}
.vl-play svg{width:34px;height:34px;color:#FF0033}
.vl-txt{flex:1;min-width:0;font-size:var(--t-sm);line-height:1.35}
.vl-txt b{font-family:var(--display);font-size:var(--t-lg);font-weight:500}
.vl-len{margin-left:var(--s2);font-size:var(--t-2xs);letter-spacing:.1em;
  color:var(--champagne);font-variant-numeric:tabular-nums}
.vl-sub{display:block;margin-top:2px;font-size:var(--t-xs);
  color:rgba(245,242,234,.72)}
.vl-go{flex:none;font-size:var(--t-xs);color:var(--champagne);white-space:nowrap}
@media (max-width:559px){
  .vlink{flex-wrap:wrap;gap:var(--s3)}
  .vl-go{width:100%;padding-left:calc(38px + var(--s3))}
}
.zh .vl-txt b{font-family:var(--cjk);font-weight:500}

/* Property pages are the only ones whose <main> holds a bare .wrap rather
   than a .sec, so they were the only ones with no space between the last
   panel and the charcoal contact bar — the note box butted straight into it.
   Matching --sec-y keeps the rhythm the same as every other page.
   Tagged explicitly rather than selected as `main > div.wrap`: that is unique
   today and would silently break the first time another page uses one. */
.propbody{padding-bottom:var(--sec-y)}

/* ---------- agent card ---------- */
.agent{background:var(--ink);color:var(--paper);padding:var(--s6) var(--s5);border-radius:4px;
  position:sticky;top:96px}
@media (max-width:899px){.agent{position:static}}
.agent .row{display:flex;gap:13px;align-items:center}
.agent img{width:72px;height:72px;border-radius:50%;object-fit:cover;
  box-shadow:0 0 0 1px rgba(227,196,106,.5)}
.agent .nm{font-family:var(--display);font-size:var(--t-lg);font-weight:500;
  line-height:1.2}
.agent .cjk{font-family:var(--cjk);font-size:var(--t-xs);color:var(--champagne);
  letter-spacing:.16em}
.agent .rl{font-size:var(--t-2xs);letter-spacing:.14em;text-transform:uppercase;
  color:rgba(242,239,230,.7);margin-top:3px}
.btn-gold,.btn-ghost{display:flex;align-items:center;justify-content:center;gap:9px;
  border-radius:999px;padding:14px 20px;font-size:var(--t-sm);font-weight:600;
  margin-top:var(--s3);
  font-variant-numeric:tabular-nums;transition:transform var(--dur-2) var(--ease-out),box-shadow var(--dur-2) var(--ease-out)}
.btn-gold{background:var(--gold);color:var(--on-gold)}
.btn-ghost{border:1px solid rgba(242,239,230,.28);color:var(--paper);font-weight:500}
.btn-gold:hover,.btn-ghost:hover{transform:translateY(-2px)}
.btn-gold svg,.btn-ghost svg{width:15px;height:15px;flex:none}
.agent .fine{font-size:var(--t-2xs);color:rgba(242,239,230,.62);margin:var(--s3) 0 0;
  line-height:1.55}

/* ---------- the bilingual name lockup ----------
   Her practice is bilingual and that is the differentiator in this market,
   so the two names are set as one unit wherever the name appears — nav
   aside, where the wordmark is a logotype. Champagne on dark grounds, bronze
   on light, per the two-gold rule. */
.zh-name{font-family:var(--cjk);font-weight:400;font-size:var(--t-xs);
  letter-spacing:.18em;color:var(--bronze);margin-left:var(--s3);
  white-space:nowrap;vertical-align:.06em}
.agent .zh-name,.cb-who .zh-name,.ab-name .zh-name{color:var(--champagne)}

/* ---------- editorial pages ---------- */
.band{background:var(--ink);color:var(--paper);
  padding-top:var(--sec-y);padding-bottom:var(--sec-y)}
.band .eb{font-size:var(--t-2xs);letter-spacing:.24em;text-transform:uppercase;
  color:var(--champagne);margin:0 0 var(--s3)}
.band h1{font-family:var(--display);font-weight:300;text-transform:uppercase;
  font-size:clamp(30px,8vw,52px);line-height:1;margin:0;text-wrap:balance;
  letter-spacing:-.012em}
.band h1 b{font-weight:600}
.band .lede{margin:var(--s4) 0 0;max-width:54ch;color:rgba(245,242,234,.74);
  font-size:var(--t-md)}
@media (min-width:900px){.band-split{display:grid;grid-template-columns:1.15fr .85fr;
  gap:52px;align-items:end}}
.eval{margin-top:var(--s6)}
.eval label{display:block;font-size:var(--t-2xs);letter-spacing:.18em;
  text-transform:uppercase;color:rgba(245,242,234,.68);margin-bottom:var(--s2)}
.eval .fine{margin:var(--s3) 0 0;font-size:var(--t-2xs);color:rgba(245,242,234,.62)}
.eval .fine a{color:var(--champagne)}
.steps{display:grid;grid-template-columns:1fr;gap:1px;background:var(--line);
  border-top:1px solid var(--line);border-bottom:1px solid var(--line)}
@media (min-width:640px){.steps{grid-template-columns:repeat(2,1fr)}}
@media (min-width:1000px){.steps{grid-template-columns:repeat(4,1fr)}
  .steps.five{grid-template-columns:repeat(5,1fr)}}
.step{background:var(--paper);padding:var(--s6) var(--s5)}
.step .n{font-family:var(--display);font-size:var(--t-2xs);font-weight:600;letter-spacing:.14em;
  color:var(--bronze);font-variant-numeric:tabular-nums}
.step h3{font-family:var(--display);font-weight:500;font-size:var(--t-lg);
  margin:var(--s3) 0 var(--s2)}
.step p{margin:0;font-size:var(--t-sm);line-height:1.65;color:var(--stone)}
.fgrid{display:grid;grid-template-columns:1fr;gap:1px;background:var(--line);
  border:1px solid var(--line);margin-top:var(--s5)}
@media (min-width:600px){.fgrid{grid-template-columns:repeat(2,1fr)}}
@media (min-width:960px){.fgrid{grid-template-columns:repeat(3,1fr)}}
.f{background:var(--card);padding:var(--s6) var(--s5)}
.f h3{font-family:var(--display);font-weight:500;font-size:var(--t-lg);
  margin:0 0 var(--s2)}
.f p{margin:0;font-size:var(--t-sm);line-height:1.65;color:var(--stone)}
.prose h2{font-family:var(--display);font-weight:500;font-size:var(--t-2xl);
  margin:var(--s6) 0 var(--s2);letter-spacing:-.01em}
.prose p{margin:0 0 var(--s3);font-size:var(--t-md);color:var(--ink-soft);
  max-width:64ch;line-height:1.7}
.nb-hero{aspect-ratio:21/9;background-size:cover;background-position:50% 58%;position:relative;
  display:flex;align-items:flex-end;min-height:200px}
.nb-hero::after{content:"";position:absolute;inset:0;
  background:linear-gradient(180deg,rgba(30,30,28,.10),rgba(30,30,28,.74))}
.nb-hero .in{position:relative;z-index:1;color:var(--paper);width:100%;
  padding-bottom:clamp(16px,3vw,28px)}
.stats{display:grid;grid-template-columns:repeat(2,1fr);gap:1px;background:var(--line);
  border-bottom:1px solid var(--line)}
@media (min-width:760px){.stats{grid-template-columns:repeat(4,1fr)}}
.stat{background:var(--card);padding:var(--s5) var(--pad)}
.stat b{display:block;font-family:var(--display);font-size:var(--t-xl);font-weight:500;
  font-variant-numeric:tabular-nums;letter-spacing:-.015em}
.stat span{font-size:var(--t-2xs);letter-spacing:.14em;text-transform:uppercase;
  color:var(--stone)}

/* ---------- neighbourhood detail ----------
   These were written as inline styles, which put six more type sizes outside
   the scale — including a display clamp that existed nowhere else. */
.nb-title{font-family:var(--display);font-weight:300;text-transform:uppercase;
  font-size:clamp(26px,7vw,50px);line-height:1;margin:0;letter-spacing:-.012em}
.zh .nb-title{font-family:var(--cjk);text-transform:none;font-weight:500;
  line-height:1.22;letter-spacing:.02em}
.nb-hero .eb{color:var(--champagne);font-size:var(--t-2xs);letter-spacing:.24em;
  text-transform:uppercase;margin:0 0 var(--s2)}
.crumb-tight{padding-top:0;margin:0 0 var(--s2)}
.area-link{margin-top:var(--s3);font-size:var(--t-xs);color:var(--bronze)}
.area-link a{color:var(--bronze);display:inline-block;padding:5px 0}
.nbcard .area-link{color:var(--champagne);margin-top:var(--s3)}

/* ---------- agent band ----------
   The homepage introduction, and the only place her portrait is shown large.
   Dark on purpose: the photograph was shot on a near-black ground, so against
   --ink it has no visible edge and she reads as standing in the page rather
   than sitting in a box. Champagne accents, per the two-gold rule. */
.agentband{background:var(--ink);color:var(--paper);
  padding-top:var(--sec-y);padding-bottom:var(--sec-y)}
.ab-in{display:grid;gap:var(--s6);grid-template-columns:1fr;align-items:center}
@media (min-width:700px){
  .ab-in{grid-template-columns:250px 1fr;gap:var(--s7);align-items:center}
}
/* Square, capped at 250. The source is 500 so it stays sharp on a 2x screen;
   the crop is baked into the file because object-position is a no-op when the
   image and its box are both square. */
.ab-photo{margin:0;max-width:250px}
.ab-photo img{width:100%;height:auto;display:block;border-radius:3px}
.ab-eb{font-size:var(--t-2xs);letter-spacing:.24em;text-transform:uppercase;
  color:var(--champagne);margin:0 0 var(--s3)}
.ab-body h2{font-family:var(--display);font-weight:300;text-transform:uppercase;
  font-size:var(--t-3xl);line-height:1.02;margin:0;letter-spacing:-.012em}
.ab-body h2 b{font-weight:600}
.ab-body p{margin:var(--s4) 0 0;font-size:var(--t-md);max-width:54ch;
  color:rgba(245,242,234,.82);line-height:1.7}
.ab-name{margin-top:var(--s5);font-family:var(--display);font-size:var(--t-lg);
  font-weight:500;line-height:1.2}
.ab-role{font-size:var(--t-2xs);letter-spacing:.14em;text-transform:uppercase;
  color:rgba(242,239,230,.66);margin-top:3px}
.ab-acts{display:grid;grid-template-columns:1fr;gap:var(--s3);
  margin-top:var(--s5);max-width:440px}
@media (min-width:520px){.ab-acts{grid-template-columns:1fr 1fr}}

/* ---------- video panel (selling) ----------
   A light panel rather than another dark band: the contact bar and footer
   below it are both charcoal, and a third dark block would run them together.
   The framed play mark carries the idea instead — champagne on ink, per the
   two-gold rule, and no fake thumbnails standing in for real videos. */
.ytpanel{display:grid;grid-template-columns:1fr;gap:var(--s6);
  background:var(--card);border:1px solid var(--line);
  border-radius:4px;padding:var(--s6) var(--s5);align-items:center}
@media (min-width:820px){
  .ytpanel{grid-template-columns:1fr minmax(0,300px);gap:var(--s7);
    padding:var(--s7)}
}
.yt-eb{font-size:var(--t-2xs);letter-spacing:.24em;text-transform:uppercase;
  color:var(--bronze);margin:0 0 var(--s3)}
.yt-body h2{font-family:var(--display);font-weight:300;text-transform:uppercase;
  font-size:var(--t-3xl);line-height:1.02;margin:0;letter-spacing:-.012em}
.yt-body h2 b{font-weight:600}
.yt-body p{margin:var(--s4) 0 0;font-size:var(--t-md);max-width:56ch;
  color:var(--ink-soft);line-height:1.7}
.zh .yt-body h2{font-family:var(--cjk);text-transform:none;font-weight:500;
  line-height:1.25;letter-spacing:.02em}

/* the CTA is dark-on-light here, so it takes the ink pill rather than the
   champagne one, which would fail contrast on this ground */
.yt-cta{display:inline-flex;align-items:center;gap:var(--s2);
  margin-top:var(--s5);background:var(--ink);color:var(--paper);
  border-radius:999px;padding:13px 22px;font-size:var(--t-sm);font-weight:500;
  transition:transform var(--dur-2) var(--ease-out)}
.yt-cta svg{width:17px;height:17px;flex:none;color:#FF0033}
.yt-cta:hover{transform:translateY(-2px)}

/* Decoration only, and on a phone it lands below the CTA where it adds a
   16:9 block of nothing. It is aria-hidden, so dropping it costs no meaning. */
@media (max-width:819px){.yt-frame{display:none}}
.yt-frame{position:relative;display:grid;place-items:center;
  background:var(--ink);border-radius:3px;aspect-ratio:16/9;
  box-shadow:inset 0 0 0 1px rgba(227,196,106,.18)}
.yt-frame svg{width:clamp(44px,9vw,60px);height:auto;color:var(--champagne)}

/* ---------- contact bar ---------- */
.contactbar{background:var(--ink);color:var(--paper);
  padding-top:var(--s6);padding-bottom:var(--s6)}
/* Three children: who / buttons / WeChat. The three-column row used to start
   at 1000px, so every laptop narrower than that stacked them and dropped the
   QR onto a line of its own, making the dark block at the foot of the page
   366px tall. From 620px the QR sits beside the buttons; from 880px all three
   share one row. */
.cb-in{display:grid;gap:var(--s5);grid-template-columns:1fr;align-items:center}
@media (min-width:620px){
  .cb-in{grid-template-columns:1fr auto;column-gap:var(--s6)}
  .cb-who{grid-column:1 / -1}
}
/* Three columns need roughly 1060px: the name block wants ~280, the two
   buttons ~490 and the WeChat block ~210, plus gaps. At 880 they fitted only
   by wrapping her name onto five lines and clipping the WeChat ID. */
@media (min-width:1060px){
  .cb-in{grid-template-columns:1fr auto auto;gap:var(--s6)}
  .cb-who{grid-column:auto}
}
.cb-who{display:flex;align-items:center;gap:13px}
.cb-who img{width:64px;height:64px;border-radius:50%;object-fit:cover;
  box-shadow:0 0 0 1px rgba(227,196,106,.45);flex:none}
.cb-who .nm{font-family:var(--display);font-size:var(--t-lg);font-weight:500;
  line-height:1.2}
.cb-who .rl{font-size:var(--t-2xs);letter-spacing:.14em;text-transform:uppercase;
  color:rgba(242,239,230,.68);margin-top:3px}
.cb-acts{display:grid;grid-template-columns:1fr;gap:9px}
@media (min-width:520px){.cb-acts{grid-template-columns:1fr 1fr}}
.cb-btn{display:flex;align-items:center;justify-content:center;gap:9px;border-radius:999px;
  padding:13px 20px;font-size:var(--t-sm);font-weight:500;white-space:nowrap;
  font-variant-numeric:tabular-nums;transition:transform var(--dur-2) var(--ease-out),border-color var(--dur-2) var(--ease-out)}
.cb-btn svg{width:15px;height:15px;flex:none}
.cb-btn.gold{background:var(--gold);color:var(--on-gold)}
.cb-btn.ghost{border:1px solid rgba(242,239,230,.3);color:var(--paper)}
.cb-btn:hover{transform:translateY(-2px)}
.cb-wechat{display:flex;align-items:center;gap:var(--s3);padding-top:var(--s4);
  border-top:1px solid rgba(242,239,230,.16)}
@media (min-width:620px){.cb-wechat{padding-top:0;border-top:0}}
@media (min-width:1060px){.cb-wechat{padding-left:var(--s5);
  border-left:1px solid rgba(242,239,230,.16)}}
/* 88px box, 80px of QR. The code is ~40 modules across, so this is 2.0 CSS px
   per module — below that it stops scanning reliably from a 1x monitor, which
   is the case that matters because people scan a desktop screen with a phone.
   The footer height came from the grid restructure above, not from shrinking
   this. */
.cb-qr{width:88px;height:88px;border-radius:4px;background:#fff;padding:4px;flex:none}
.cb-qr img{width:100%;height:100%;image-rendering:crisp-edges}
.cb-wechat .lab{font-size:var(--t-xs);line-height:1.55;min-width:0}
.cb-wechat{flex:none}
.cb-wechat .lab b{display:block;font-family:var(--display);font-size:var(--t-lg);
  font-weight:500;line-height:1.2}
.cb-wechat .lab .cjk{font-family:var(--cjk);color:var(--champagne);font-size:var(--t-xs);
  letter-spacing:.14em}
.cb-wechat .wid{display:block;margin-top:var(--s2);font-size:var(--t-xs);color:var(--paper);
  user-select:all;cursor:text;font-variant-numeric:tabular-nums}
.cb-wechat .scan{display:block;margin-top:3px;font-size:var(--t-2xs);
  color:rgba(242,239,230,.66)}

/* ---------- footer ---------- */
.sitefoot{background:var(--ink);color:var(--paper);padding-bottom:var(--s5)}
/* Two children now — links and social. The brand block was removed because
   the contact bar immediately above already carries her photo, name and role,
   and the page was showing the same name twice in a row. */
.sf-top{display:grid;gap:var(--s4);grid-template-columns:1fr;padding:var(--s5) 0;
  border-top:1px solid rgba(242,239,230,.14);align-items:center}
@media (min-width:520px){
  .sf-top{grid-template-columns:1fr auto;column-gap:var(--s6)}
}
.sf-links{display:flex;gap:var(--s5);font-size:var(--t-sm);color:rgba(242,239,230,.78);
  flex-wrap:wrap}
.sf-links a{padding:6px 0}
.sf-links a:hover{color:var(--champagne)}
.sf-social{display:flex;gap:9px}
.sf-social a{width:36px;height:36px;border-radius:50%;display:grid;place-items:center;
  border:1px solid rgba(242,239,230,.22);color:rgba(242,239,230,.72);
  transition:color var(--dur-2) var(--ease-out),border-color var(--dur-2) var(--ease-out),transform var(--dur-2) var(--ease-out)}
.sf-social a svg{width:16px;height:16px}
.sf-social a:hover{color:var(--champagne);border-color:rgba(227,196,106,.55);
  transform:translateY(-2px)}
/* The MLS disclaimer is a board requirement, so it has to be readable —
   it was 2.69:1 at 10.5px, which is not. */
.sf-legal{padding-top:var(--s4);border-top:1px solid rgba(242,239,230,.12);
  color:rgba(242,239,230,.62);font-size:var(--t-2xs);line-height:1.6}
.sf-legal p{margin:0 0 6px;max-width:104ch}
.sf-legal p:last-child{margin-bottom:0}

/* ---------- 中文 ----------
   Traditional Chinese needs more room than Latin at the same nominal size:
   a glyph like 繁 or 靈 carries far more strokes into the same em box than
   any Latin letter, so at 14px the strokes start to merge. The whole scale
   is redefined here rather than patching selectors one at a time — .zh sits
   on <body>, so every token below cascades to the entire Chinese mirror and
   the two languages stay in step by construction.

   The measured problem was the small end: 27 elements of running text at
   14px and 9 labels at 11px. Display sizes are left alone — CJK glyphs fill
   the em box, so the 52px Chinese headline already reads as large as the
   68px English one. */
.zh{
  --t-xl:26px;
  --t-lg:19px;
  --t-md:17px;
  --t-sm:15.5px;
  --t-xs:14px;
  --t-2xs:12.5px;
  font-family:var(--cjk);line-height:1.85;
}
.zh .hero h1{font-family:var(--cjk);text-transform:none;font-weight:500;
  font-size:clamp(30px,8vw,52px);line-height:1.22;letter-spacing:.02em}
.zh .band h1{font-family:var(--cjk);text-transform:none;font-weight:500;line-height:1.3}
.zh .ab-body h2{font-family:var(--cjk);text-transform:none;font-weight:500;
  line-height:1.25;letter-spacing:.02em}
.zh .sec-head h2,.zh .prose h2{font-family:var(--cjk);font-weight:500;letter-spacing:.03em}
.zh .price,.zh .fact b,.zh .dprice{font-family:var(--display)}
.zh .nav-lang{font-family:var(--ui);letter-spacing:.12em}
/* The lockup stays on the Chinese pages too. Hiding it there left her name
   rendered only in English on a Chinese page — the opposite of the point. */
.zh .zh-name{color:var(--bronze)}
.zh .agent .zh-name,.zh .cb-who .zh-name,.zh .ab-name .zh-name{color:var(--champagne)}
/* The wordmark is a logotype — the same Latin lockup in both languages — so
   it opts back out of the Chinese scale rather than growing and wrapping
   "JACQUELINE WONG" onto two lines. Re-pinning the tokens on the element
   itself keeps this to one rule. */
.zh .wordmark{--t-2xs:11px;--t-xs:12.5px;--t-sm:14px;letter-spacing:.1em}
.zh .wordmark small{letter-spacing:.08em;text-transform:none}
.zh .hero-stats span{letter-spacing:.08em;text-transform:none;font-size:var(--t-2xs)}

@media (prefers-reduced-motion:reduce){
  *{animation:none!important;transition:none!important}
  .card:hover,.cb-btn:hover,.btn-gold:hover,.btn-ghost:hover,.sf-social a:hover{transform:none}
}

/* On a phone, six stacked photos push the price below several screens of
   scrolling. Cap the gallery and state the real count instead. */
@media (max-width:759px){
  .gal a:nth-child(n+3){display:none}
  .gal{margin-bottom:4px}
}
.gal-count{font-size:var(--t-xs);color:var(--stone);margin:var(--s2) 0 0;
  font-variant-numeric:tabular-nums}
@media (min-width:760px){.gal-count{display:none}}

/* Property pages carry an agent card already; their contact strip is
   WeChat-only, so centre it rather than leaving it stranded left. */
.contactbar.compact .cb-in{grid-template-columns:1fr}
.contactbar.compact .cb-wechat{border:0;padding:0;justify-content:flex-start}
@media (min-width:1000px){.contactbar.compact .cb-wechat{padding-left:0;border-left:0}}

/* An area with no photography of its own gets a plain band rather than a
   borrowed image that would misrepresent it. */
.nb-hero.nb-plain{background:var(--ink);aspect-ratio:auto;min-height:0;padding:26px 0 4px}
.nb-hero.nb-plain::after{display:none}

/* .f is used as both a static cell and a link card */
a.f{display:block;transition:background-color var(--dur-2) var(--ease-out)}
a.f:hover{background:var(--paper)}
a.f:hover h3{color:var(--bronze)}

/* ---------- neighbourhood index cards ----------
   Image fills the whole button, text sits on top. The scrim is a gradient
   rather than a flat tint so the photograph still reads at the top while
   the type stays legible at the bottom. */
.nbgrid{display:grid;gap:var(--s5);grid-template-columns:1fr;margin-top:var(--s5)}
@media (min-width:620px){.nbgrid{grid-template-columns:repeat(2,1fr)}}
@media (min-width:1000px){.nbgrid{grid-template-columns:repeat(3,1fr)}}

.nbcard{
  position:relative;display:flex;align-items:flex-end;
  min-height:210px;aspect-ratio:16/10;
  border-radius:4px;overflow:hidden;isolation:isolate;
  background:var(--ink);color:var(--paper);
  transition:transform var(--dur-3) var(--ease-out),box-shadow var(--dur-3) var(--ease-out);
}
@media (min-width:620px){.nbcard{min-height:230px}}
.nbcard img{
  position:absolute;inset:0;width:100%;height:100%;object-fit:cover;
  z-index:-2;transition:transform var(--dur-4) var(--ease-out);
}
.nbcard::after{
  content:"";position:absolute;inset:0;z-index:-1;
  background:linear-gradient(180deg,rgba(20,20,18,.06) 0%,rgba(20,20,18,.38) 46%,rgba(18,18,16,.88) 100%);
}
/* an area with no photography of its own gets a treated ground, not a
   borrowed image */
.nbcard.noimg::after{
  background:linear-gradient(150deg,rgba(184,134,44,.26),rgba(20,20,18,.92) 72%);
}
.nbcard .in{position:relative;padding:var(--s5);width:100%}
.nbcard h3{
  font-family:var(--display);font-weight:500;font-size:var(--t-lg);margin:0 0 var(--s1);
  letter-spacing:0;text-shadow:0 1px 3px rgba(0,0,0,.5);
}
.nbcard p{
  margin:0;font-size:var(--t-sm);line-height:1.55;color:rgba(245,242,234,.9);
  text-shadow:0 1px 3px rgba(0,0,0,.5);max-width:40ch;
}
.nbcard .meta{
  display:block;margin-top:var(--s3);font-size:var(--t-2xs);letter-spacing:.14em;
  text-transform:uppercase;color:var(--champagne);
}
.nbcard:hover{transform:translateY(-3px);box-shadow:0 16px 34px rgba(0,0,0,.30)}
.nbcard:hover img{transform:scale(1.045)}
.nbcard:focus-visible{outline:2px solid var(--champagne);outline-offset:3px}
@media (prefers-reduced-motion:reduce){
  .nbcard,.nbcard img{transition:none}
  .nbcard:hover{transform:none}
  .nbcard:hover img{transform:none}
}

/* hero CTA standing in for the search field until the MLS feed is live.
   Defined once — an earlier duplicate at the foot of this file was silently
   overriding the margin set beside the rest of the hero rules. */

/* The About page reuses her portrait inside the dark band, for the same reason
   the homepage agent band is dark: the photograph was shot on a near-black
   ground, so on --ink it has no visible edge. On a light section it would sit
   in a hard rectangle. Keep it on --ink or regenerate the crop. */
.ab-photo-page{max-width:250px;margin:var(--s5) 0 0}
@media (min-width:900px){.ab-photo-page{justify-self:end;align-self:end;margin:0}}

/* ---------- timeline (About) ----------
   Cursor-driven, but CSS only — this site ships no behavioural JavaScript and
   this did not warrant being the first. `:has(~ …:hover)` selects the items
   BEFORE the hovered one, which is what fills the rail left-to-right; the
   general sibling combinator alone can only reach forwards.

   Nothing is hidden behind the interaction. Every entry stays fully readable
   at rest and the cursor only shifts emphasis — content on a real estate site
   should never depend on a pointer to be legible. */
.tl{display:grid;grid-template-columns:1fr;gap:1px;background:var(--line);
  border-top:1px solid var(--line);border-bottom:1px solid var(--line)}
@media (min-width:760px){.tl{grid-template-columns:repeat(3,1fr)}}
.tl-item{background:var(--paper);padding:var(--s6) var(--s5);
  transition:background-color var(--dur-3) var(--ease-out)}
.tl-track{position:relative;height:2px;background:var(--line);margin:0 0 var(--s5)}
.tl-seg{position:absolute;inset:0;transform:scaleX(0);transform-origin:left;
  background:linear-gradient(90deg,var(--bronze),var(--champagne));
  transition:transform var(--dur-4) var(--ease-out)}
.tl-dot{position:absolute;left:0;top:50%;width:9px;height:9px;border-radius:50%;
  transform:translate(-1px,-50%);background:var(--paper);
  box-shadow:0 0 0 1.5px var(--line);
  transition:background-color var(--dur-3) var(--ease-out),box-shadow var(--dur-3) var(--ease-out)}
.tl-item .n{font-family:var(--display);font-size:var(--t-2xs);font-weight:600;
  letter-spacing:.14em;color:var(--bronze);font-variant-numeric:tabular-nums}
.tl-item h3{font-family:var(--display);font-weight:500;font-size:var(--t-lg);
  margin:var(--s3) 0 var(--s2);transition:color var(--dur-3) var(--ease-out)}
.tl-item p{margin:0;font-size:var(--t-sm);line-height:1.65;color:var(--stone)}

.tl-item:hover,.tl-item:focus-within{background:var(--card)}
.tl-item:hover h3,.tl-item:focus-within h3{color:var(--bronze)}
.tl-item:hover .tl-seg,.tl-item:focus-within .tl-seg,
.tl-item:has(~ .tl-item:hover) .tl-seg,
.tl-item:has(~ .tl-item:focus-within) .tl-seg{transform:scaleX(1)}
.tl-item:hover .tl-dot,.tl-item:focus-within .tl-dot,
.tl-item:has(~ .tl-item:hover) .tl-dot,
.tl-item:has(~ .tl-item:focus-within) .tl-dot{
  background:var(--bronze);box-shadow:0 0 0 1.5px var(--bronze)}

/* Touch has no cursor to trigger it, and older engines lack :has() — both get
   the completed timeline rather than a permanently empty rail. */
@media (hover:none){
  .tl-seg{transform:scaleX(1)}
  .tl-dot{background:var(--bronze);box-shadow:0 0 0 1.5px var(--bronze)}
}
@supports not selector(:has(*)){
  .tl-seg{transform:scaleX(1)}
  .tl-dot{background:var(--bronze);box-shadow:0 0 0 1.5px var(--bronze)}
}
@media (prefers-reduced-motion:reduce){
  .tl-item,.tl-item h3,.tl-seg,.tl-dot{transition:none}
}

/* ---------- credentials strip (About) ----------
   A staggered reveal on load, deliberately NOT scroll-driven.

   Scroll-driven was tried first and measured: the strip sits ~830px down a
   ~964px viewport, so it is already at the fold. Its entry phase is over
   almost immediately — the reveal was imperceptible, and with the timeline
   inactive the animation never applied at all. `animation-timeline: view()`
   is the right tool for content further down the page, not for this.

   A document timeline always advances, so there is no state in which these
   stay hidden. The hidden state lives only in the keyframes; reduced-motion
   skips the animation entirely and the credentials just appear.

   Only opacity and transform are animated. letter-spacing was tried and
   dropped: it relayouts every frame. */
.stat b{position:relative;margin-bottom:var(--s3)}
.stat b::after{content:"";position:absolute;left:0;bottom:calc(var(--s3) * -0.6);
  width:34px;height:2px;transform-origin:left;
  background:linear-gradient(90deg,var(--bronze),var(--champagne))}

@media (prefers-reduced-motion: no-preference) {
  .stat{animation:statRise var(--dur-5) var(--ease-out) both}
  .stat b::after{animation:statRule var(--dur-4) var(--ease-out) both}
  .stat:nth-child(1){animation-delay:.08s} .stat:nth-child(1) b::after{animation-delay:.30s}
  .stat:nth-child(2){animation-delay:.20s} .stat:nth-child(2) b::after{animation-delay:.42s}
  .stat:nth-child(3){animation-delay:.32s} .stat:nth-child(3) b::after{animation-delay:.54s}
  .stat:nth-child(4){animation-delay:.44s} .stat:nth-child(4) b::after{animation-delay:.66s}
}

@keyframes statRise{
  from{opacity:0;transform:translateY(18px)}
  to  {opacity:1;transform:none}
}
@keyframes statRule{from{transform:scaleX(0)}to{transform:scaleX(1)}}

/* ---------- hero video ----------
   Sits behind the existing scrim (.hero::after is z-index -1, this is -2).
   No JavaScript: autoplay+muted+loop+playsinline is enough, and muted is what
   makes autoplay permitted at all.

   The <source> carries media="(min-width:900px)", so phones match no source
   and download nothing — they keep the poster. That matters more than usual
   here: most of this site's traffic is a phone on cellular, and the clip is
   3.1 MB. preload="none" keeps it off the critical path on desktop too.

   poster is the video's own first frame, so the still-to-motion handoff has
   no visible jump. It is the same file as the CSS background, which is the
   fallback if the video element fails entirely. */
.hero-video{
  position:absolute;inset:0;z-index:-2;
  width:100%;height:100%;object-fit:cover;
  pointer-events:none;
}
/* Someone who has asked for less motion should not be given an autoplaying
   video. Hiding it leaves the poster, which is the same frame. */
@media (prefers-reduced-motion:reduce){.hero-video{display:none}}

/* ---------- press feedback ----------
   Until now every interactive surface had :hover and none had :active. On a
   phone there is no hover at all, so a tap produced no confirmation that
   anything had been registered — and most of this site's traffic is a phone.

   Transform only, so it composites. Duration is the shortest step on the
   scale: press feedback that outlasts the press reads as lag. */
.cb-btn:active,.btn-gold:active,.btn-ghost:active,
.hero-cta:active,.nav-cta:active,.yt-cta:active{
  transform:translateY(0) scale(.985);
  transition-duration:var(--dur-1);
  transition-timing-function:var(--ease-press);
}
.card:active,.nbcard:active,.vlink:active{
  transform:translateY(-1px) scale(.995);
  transition-duration:var(--dur-1);
  transition-timing-function:var(--ease-press);
}
.menu a:active,.sf-links a:active,.nav-lang:active,.sf-social a:active{
  opacity:.62;
  transition-duration:var(--dur-1);
}
/* iOS paints its own grey flash on tap. That was the only press feedback the
   site had; now that there is a real one, the two would fight. */
a,button{-webkit-tap-highlight-color:transparent}

/* Reduced motion still needs to confirm a tap — it just should not move.
   Swap the scale for a brightness shift rather than removing the feedback. */
@media (prefers-reduced-motion:reduce){
  .cb-btn:active,.btn-gold:active,.btn-ghost:active,.hero-cta:active,
  .nav-cta:active,.yt-cta:active,.card:active,.nbcard:active,.vlink:active{
    transform:none;filter:brightness(.94)
  }
}

/* ---------- photo lightbox ----------
   Driven by :target, so there is no JavaScript. Two consequences worth
   knowing, one of them a trade-off.

   Each photo is a real URL, so a specific shot can be linked or shared —
   useful when she is sending a buyer to one room.

   But every photo also pushes a history entry, and without JS there is no
   history.replaceState to avoid it. Verified: after paging 1 → 2 → 3, Back
   steps back through the photos rather than closing. Back does exit
   eventually, and closing outright is one tap on the ✕ or anywhere off the
   photo. If that ever becomes a complaint, the fix is JS, and it should be
   weighed against this being the only page on the site that would need any.

   Closed state uses visibility, not just opacity — opacity alone would leave
   the close and next links in the tab order of a panel nobody can see. */
.lb{
  position:fixed;inset:0;z-index:90;
  display:flex;align-items:center;justify-content:center;
  padding:clamp(12px,4vw,48px);
  background:rgba(18,18,16,.94);
  -webkit-backdrop-filter:blur(6px);backdrop-filter:blur(6px);
  overflow:auto;overscroll-behavior:contain;
  opacity:0;visibility:hidden;
  transition:opacity var(--dur-3) var(--ease-out),visibility 0s linear var(--dur-3);
}
.lb:target{opacity:1;visibility:visible;transition-delay:0s}

/* margin:auto rather than place-items:center — a centred flex item that is
   taller than the viewport overflows off the TOP and cannot be scrolled to.
   This keeps tall photos reachable. */
.lb-fig{margin:auto;position:relative;z-index:1;max-width:min(1100px,94vw)}
.lb-fig img{width:100%;height:auto;display:block;border-radius:3px;
  box-shadow:0 30px 80px rgba(0,0,0,.55)}

/* Click anywhere off the photo to close. */
.lb-scrim{position:absolute;inset:0;z-index:0;cursor:zoom-out}

.lb-close,.lb-nav{
  position:fixed;z-index:2;
  display:grid;place-items:center;
  width:44px;height:44px;border-radius:50%;
  color:var(--paper);background:rgba(245,242,234,.10);
  border:1px solid rgba(245,242,234,.22);
  font-size:var(--t-lg);line-height:1;text-decoration:none;
  transition:background-color var(--dur-2) var(--ease-out),
             border-color var(--dur-2) var(--ease-out),
             transform var(--dur-2) var(--ease-out);
}
.lb-close{top:clamp(12px,3vw,26px);right:clamp(12px,3vw,26px)}
.lb-prev{left:clamp(12px,3vw,26px);top:50%;transform:translateY(-50%)}
.lb-next{right:clamp(12px,3vw,26px);top:50%;transform:translateY(-50%)}
.lb-close:hover,.lb-nav:hover{background:rgba(227,196,106,.18);
  border-color:rgba(227,196,106,.55)}
.lb-close:active,.lb-nav:active{background:rgba(227,196,106,.28)}
.lb-prev:hover,.lb-next:hover{transform:translateY(-50%) scale(1.06)}
.lb-prev:active,.lb-next:active{transform:translateY(-50%) scale(.96)}
.lb-close:focus-visible,.lb-nav:focus-visible{
  outline:2px solid var(--champagne);outline-offset:3px}

.lb-count{position:fixed;left:50%;transform:translateX(-50%);
  bottom:clamp(12px,3vw,26px);margin:0;z-index:2;
  font-size:var(--t-2xs);letter-spacing:.16em;
  color:rgba(245,242,234,.62);font-variant-numeric:tabular-nums}

/* The gallery thumbnails now open a panel rather than leaving the site. */
.gal a{cursor:zoom-in}

@media (prefers-reduced-motion:reduce){
  .lb{transition:none}
  .lb-prev:hover,.lb-next:hover,.lb-prev:active,.lb-next:active{
    transform:translateY(-50%)}
}
