/* ═══════════════════════════════════════════════════════════════════════
   DesertPalisadesLots.com

   Two faces and one colour. Italiana carries every headline and Inter carries
   everything else — the running text, the wordmark, the buttons — and
   #7C7262, sampled off the client's design where it is the headline colour,
   the button fill and the body ink all at once, is the only hue on the page
   that is not a photograph.

   Layout is one 12-column grid used at three scales, so a plate that bleeds
   off the left edge in the intro and one that sits inset in the triptych are
   the same system rather than two ad-hoc arrangements.
   ═══════════════════════════════════════════════════════════════════════ */

/* ── Faces ─────────────────────────────────────────────────────────────
   Two files now, both `swap`: a headline that is invisible for 3s is worse
   than one that arrives in a fallback and settles. The fallback stacks are
   chosen for width, not for looks, so the reflow when the real face lands is
   small. */
@font-face {
  font-family: 'Italiana';
  src: url('../fonts/italiana-latin.woff2') format('woff2');
  /* 400 ONLY, and that is not a shortcut. Italiana ships a single static face
     — no fvar — exactly like the Cal Sans this replaced. Declaring a range it
     does not have would tell the browser this file covers bold, and every
     bold request on the page would silently get these same 400 outlines back.
     Declared honestly, a bold request is visibly synthesised instead, which is
     something you can see and fix. Nothing on the page asks Italiana for bold. */
  font-weight: 400; font-style: normal; font-display: swap;
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
                 U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC,
                 U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

/* Inter now carries EVERYTHING that is not a headline — running text, the
   wordmark, the buttons. Genuinely variable across 100..900, so the 500 the
   wordmark and buttons ask for is a real weight cut rather than a smeared
   synthetic one. */
@font-face {
  font-family: 'Inter';
  src: url('../fonts/inter-latin.woff2') format('woff2');
  font-weight: 100 900; font-style: normal; font-display: swap;
}

/* ── Tokens ───────────────────────────────────────────────────────────── */
:root {
  --accent: #7C7262;
  --ink: #7a7168;          /* sampled from the design's body copy — 4.8:1 on white */
  --ink-strong: #56504a;
  --paper: #ffffff;
  /* The footer's ground is the COMPASS logo's OWN background, sampled from the
     supplied file (median of its border ring — the file is lossy WebP, so the
     flat colour carries a little noise and the mode alone is not reliable).
     The logo ships with its background baked in rather than transparent, so
     anything else here draws a faint rectangle around it. */
  --paper-soft: #eeeeec;
  --line: rgba(124, 114, 98, 0.22);

  --serif: 'Italiana', Georgia, 'Times New Roman', serif;

  /* ── How the headlines are made heavier ─────────────────────────────
     Italiana has ONE weight. There is no 500 or 600 to select, so asking for
     one makes the browser synthesise a bold, and a synthetic bold does two
     things we do not want: it distorts a high-contrast face's letterforms,
     and it CHANGES ADVANCE WIDTHS — which would re-break every headline and
     undo the pull quote's measure.

     A stroke in the text's own colour thickens the same outlines and leaves
     every advance width alone, so nothing re-flows. Expressed in em, so it
     scales with whatever size the headline lands at. Raise or lower this one
     number to make the headlines heavier or lighter.

     A side effect worth knowing: it also rescues Italiana's em dash, which is
     drawn 0.008 em thick and otherwise renders as a gap. */
  --serif-stroke: 0.016em;

  /* Inter for everything that is not a headline. Cal Sans held the wordmark
     and the buttons before this and was dropped at the client's request; the
     token stays because the ROLE is still real — short, uppercase, widely
     tracked text as against running prose — and those call sites ask for
     weight 500 rather than the 400 the body uses, to keep some of the heft
     Cal Sans had. Inter is variable, so that 500 is a real cut. */
  --sans: 'Inter', ui-sans-serif, system-ui, -apple-system, 'Segoe UI', sans-serif;
  --sans-display: var(--sans);

  /* ── Measured off the client's artwork, not chosen ──────────────────
     The design PDF was stacked into one image and its white bands measured.
     Every figure below is that measurement expressed as a fraction of the
     page width, which is how the comp itself scales.

     The page margin is 0.031 of the width — 44px at 1440, where this build
     previously had 72. That single number is most of "too much empty space":
     it widened every gutter and pushed every plate inward.

     The gaps between sections are NOT one rhythm. The design runs 0.032 W
     between a full-bleed plate and the headline under it, and 0.111 W
     between a headline and the pictures under it — nearly four times as
     much. A single --rhythm applied as padding on both sides of a section
     produced ~2x the largest of them everywhere, which is the rest of it. */
  --gutter: clamp(20px, 3.1vw, 58px);
  --maxw: 1680px;

  --gap-xs: clamp(26px, 3.2vw, 62px);    /*  46px @1440 — bleed -> headline   */
  --gap-s:  clamp(32px, 4.1vw, 78px);    /*  59px @1440 — hero -> headline    */
  --gap-m:  clamp(44px, 5.8vw, 110px);   /*  84px @1440 — button -> pictures  */
  --gap-l:  clamp(58px, 7.9vw, 150px);   /* 114px @1440 — section -> section  */
  --gap-xl: clamp(72px, 10.9vw, 205px);  /* 157px @1440 — quote -> bleed      */

  /* Copied verbatim from desert-palisades-sitemap.pages.dev so the two
     properties feel like one hand. Do not "tidy" this curve. */
  --btn-transition: .8s cubic-bezier(.785, .135, .15, .86);
}

/* ── Reset ────────────────────────────────────────────────────────────── */
*, *::before, *::after { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }
body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--sans);
  /* The artwork sets body copy on a 45.5px line at 2560 — ~19px of type at
     1440, where this build had 15. */
  font-size: clamp(16px, 1.32vw, 21px);
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  overflow-x: hidden;
}
img { display: block; max-width: 100%; height: auto; }
figure { margin: 0; }
p { margin: 0 0 1.1em; }
a { color: inherit; }
button { font: inherit; color: inherit; background: none; border: 0; }

/* A visible focus ring everywhere, in the page's own colour rather than the
   browser's blue. */
:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; border-radius: 2px; }

.skip {
  position: fixed; top: 8px; left: 8px; z-index: 200;
  transform: translateY(-160%);
  background: var(--accent); color: #fff;
  padding: 10px 18px; border-radius: 999px; text-decoration: none;
  font-size: 13px; letter-spacing: .08em; text-transform: uppercase;
  transition: transform .25s ease;
}
.skip:focus { transform: translateY(0); }

/* ── The display face ─────────────────────────────────────────────────── */
/* ── One heading size, measured ──────────────────────────────────────
   In the artwork the section headlines set on a 156px line at a 2560px-wide
   page — 88px at 1440, so about 82px of type. This build had 66px. They are
   the h1 of the page and now read like it. The hero, which had been the
   biggest thing here at 78px, measures 52px in the artwork: it is the
   subordinate line, under the logo, and it goes back to that. */
.display {
  font-family: var(--serif);
  font-weight: 400;
  -webkit-text-stroke: var(--serif-stroke) currentColor;
  color: var(--accent);
  line-height: 1.1;
  /* At headline size the default fit is loose and the artwork sets it tight;
     this is measured against the client's design, not a taste call. */
  letter-spacing: -0.012em;
  margin: 0;
}

/* ── Buttons ──────────────────────────────────────────────────────────
   Ported from the sitemap app so the two properties share one button. The
   mechanic: a duplicate of the label sits in an absolutely-positioned mask
   clipped to nothing; on hover the clip opens upward while the resting label
   slides right and its arrow leaves, and the mask's arrow arrives from the
   left a beat later. The two arrows are why there are two copies of the
   label — one set moves out, the other moves in. */
.btn-hover { --btn-transition: .8s cubic-bezier(.785, .135, .15, .86); position: relative; overflow: hidden; cursor: pointer; }
.btn-hover__content { display: flex; align-items: center; justify-content: space-between; width: 100%; position: relative; }
.btn-hover__text { transition: transform var(--btn-transition); }
.btn-hover__arrow { width: .65rem; height: auto; flex-shrink: 0; transition: transform var(--btn-transition); }
.btn-hover__content .btn-hover__arrow { transition-delay: 75ms; }
.btn-hover__arrow--mask {
  position: absolute; right: var(--btn-px); top: 50%;
  transform: translateX(calc(-1 * var(--btn-px) - 100%)) translateY(-50%);
  transition: transform var(--btn-transition); transition-delay: 0ms;
}
.btn-hover__mask {
  position: absolute; inset: 0;
  display: flex; align-items: center; justify-content: space-between;
  background: #000; color: #fff;
  clip-path: inset(100% 0 0 0);
  transition: clip-path var(--btn-transition);
  z-index: 2; pointer-events: none;
}
.btn-hover:hover .btn-hover__mask { clip-path: inset(0 0 0 0); }
.btn-hover:hover .btn-hover__text { transform: translateX(.4rem); }
.btn-hover:hover .btn-hover__content .btn-hover__arrow {
  transform: translateX(calc(100% + var(--btn-px))); transition-delay: 0ms;
}
.btn-hover:hover .btn-hover__arrow--mask { transform: translateX(0) translateY(-50%); transition-delay: 75ms; }

/* The shell. Padding lives on the two layers, never on the button, or the
   mask would inset itself and stop covering the label. */
.btn {
  --btn-px: 2rem;
  padding: 0;
  border-radius: 6px;
  font-family: var(--sans-display);
  /* 500. Inter is variable across 100..900, so this is a real weight cut, not
     a synthetic one — and it is here because Cal Sans was DRAWN heavy and a
     button set in Inter 400 uppercase reads noticeably thinner than the one
     this replaced. */
  font-size: 15px; font-weight: 500;
  letter-spacing: .14em; text-transform: uppercase;
  cursor: pointer;
}
/* The artwork's button measures 218 x 68 at a 1440 page; this was 197 x 53. */
.btn { --btn-px: 2.65rem; }
.btn .btn-hover__content,
.btn .btn-hover__mask { padding: 1.5rem var(--btn-px); gap: 1.6rem; }

/* The design's fill: accent ground, white label; the wipe inverts it. */
.btn--solid { background: var(--accent); color: #fff; }
.btn--solid .btn-hover__mask { background: #fff; color: var(--accent); }

/* On hover the wipe turns this button WHITE, on a white page — it loses its
   edge at exactly the moment the pointer is on it. A ring in the accent gives
   it one back.

   box-shadow rather than outline, deliberately. `:focus-visible` already owns
   `outline` for the whole site, and it is declared ABOVE this rule; a hover
   rule here at the same specificity would win on source order and blank the
   focus ring at the very moment a keyboard user hovered onto the control.
   A shadow spread follows border-radius just as well and leaves focus alone.
   Grown from 0 so it arrives with the wipe rather than snapping in. */
.btn--solid {
  box-shadow: 0 0 0 0 var(--accent);
  transition: box-shadow var(--btn-transition);
}
.btn--solid:hover { box-shadow: 0 0 0 2px var(--accent); }

/* The header's button. Was deliberately small; the client wants it to carry
   more weight, so it is now close to the in-page button's size. */
.btn--pill { --btn-px: 1.95rem; background: var(--accent); color: #fff; font-size: 14.5px; }
.btn--pill .btn-hover__content,
.btn--pill .btn-hover__mask { padding: 1.18rem var(--btn-px); gap: 1.35rem; }
.btn--pill .btn-hover__mask { background: #fff; color: var(--accent); }

/* ── Arriving over the sitemap ──────────────────────────────────────────
   The button opens the overlay WHITE and settles into the brown once the
   sitemap has finished fading in. White is what carries during the arrival:
   the frame behind it is still changing, and a white pill is legible against
   whatever the map happens to be showing under it.

   The snap-then-fade is the point, so the two directions are not symmetrical.
   `transition: none` on the arriving state is what makes it land white
   instantly instead of fading brown -> white first; take the class off and the
   base rule's transition is the one in effect, so it fades white -> brown.
   Reversing those two would give a fade in and a snap out.

   The hover wipe inverts with it. Left alone it would still wipe to a white
   ground under white, and for the second or so this state lasts a pointer
   already near the button would find it blank. */
.btn--pill { transition: background .55s ease, color .55s ease; }
.btn--pill.is-arriving {
  background: #fff; color: var(--accent);
  transition: none;
}
.btn--pill.is-arriving .btn-hover__mask { background: var(--accent); color: #fff; }

/* ── The header button's second state ───────────────────────────────────
   While the overlay is open this same button says "Close ✕". Its width is
   pinned by an inline min-width the script measures from the resting label,
   so swapping to the shorter word does not make the button jump. */
.ic-cross { display: none; }
.btn.is-close .ic-arrow { display: none; }
.btn.is-close .ic-cross { display: block; }
/* The cross is a symbol, not a direction: it should not slide out of the
   button on hover the way the arrow does. */
.btn.is-close .btn-hover__content .btn-hover__arrow,
.btn.is-close:hover .btn-hover__content .btn-hover__arrow { transform: none; }
.btn.is-close .btn-hover__arrow--mask { transform: translateY(-50%); }
.btn.is-close .btn-hover__text,
.btn.is-close:hover .btn-hover__text { transform: none; }

/* On a touch screen there is no hover, so the wipe would never run and the
   arrow would sit half-off the button. Press gives the same gesture. */
@media (hover: none) {
  .btn-hover:active .btn-hover__mask { clip-path: inset(0 0 0 0); }
  .btn-hover:active .btn-hover__arrow--mask { transform: translateX(0) translateY(-50%); }
}

/* ── Header ───────────────────────────────────────────────────────────── */
.nav {
  position: fixed; inset: 0 0 auto; z-index: 90;
  /* Three tracks, with the wordmark in the middle one. The outer two are what
     centre it: the button alone in a two-track grid can only sit at an end. */
  display: grid; grid-template-columns: 1fr auto 1fr; align-items: center;
  padding: clamp(14px, 1.8vw, 26px) var(--gutter);
  padding-top: max(clamp(14px, 1.8vw, 26px), env(safe-area-inset-top));
  color: #fff;
  transition: background .5s ease, color .5s ease, box-shadow .5s ease;
}
/* Centred in the header, on the page and over the sitemap alike — over the
   sitemap it IS this wordmark, so one rule covers both.

   NO text-indent, and that is the opposite of what this rule said the last
   time the wordmark was centred. The old note reasoned that letter-spacing
   pads the right of every character including the last, so a centred tracked
   line must sit visually left by that trailing space, and indented by the
   full tracking to "re-centre" it.

   Measured, that is wrong twice over. Sweeping the indent at 1440 and reading
   the ink's own edges with a Range:

       indent  0      .085em   .17em   .34em
       line 1  0.000   0.625   1.266   2.547     px right of centre
       line 2  0.000   0.516   1.047   2.094

   Zero is exact. Chromium does not carry the trailing letter-space into the
   centred line box, so there is nothing to correct — and an indent under
   text-align:center shifts by HALF itself, so the old .34em pushed the
   wordmark 2.5px off centre rather than onto it. Verified in Chromium only,
   which is the engine to hand; an engine that does include the trailing space
   would want the indent back. */
.wordmark {
  font-family: var(--sans-display);
  grid-column: 2;
  text-decoration: none; text-align: center; line-height: 1.12;
  display: flex; flex-direction: column;
  font-size: clamp(12px, 1.05vw, 15px);
  font-weight: 600; letter-spacing: .34em; text-transform: uppercase;   /* see .btn */
}
.wordmark__l2 { letter-spacing: .28em; }
.wordmark__slash { font-weight: 500; }
.nav > .btn { grid-column: 3; justify-self: end; }

/* ── The menu, top left ────────────────────────────────────
   In the column the centred wordmark left empty, where Marc's portrait was.

   The bars are three <i> elements rather than a background image or an SVG,
   because two of them have to travel into a cross when the panel opens and the
   third has to disappear — three transforms on three boxes, which is not
   something an icon file can do. */
.menu { grid-column: 1; justify-self: start; position: relative; }

.menu__btn {
  width: clamp(38px, 3.1vw, 46px); height: clamp(38px, 3.1vw, 46px);
  display: grid; place-items: center;
  padding: 0; border: 0; background: none; cursor: pointer;
  color: inherit;   /* white over the hero, accent once .nav.is-solid */
}
.menu__bars { display: block; width: 22px; height: 14px; position: relative; }
.menu__bars i {
  position: absolute; left: 0; right: 0; height: 2px;
  background: currentColor; border-radius: 2px;
  transition: transform .32s cubic-bezier(.6,.05,.2,1), opacity .18s ease;
}
.menu__bars i:nth-child(1) { top: 0; }
.menu__bars i:nth-child(2) { top: 6px; }
.menu__bars i:nth-child(3) { top: 12px; }
/* Open: the outer two meet in the middle and cross, the middle one goes. 6px
   is the distance from each end bar to the centre line, so they land on each
   other exactly rather than approximately. */
.menu.is-open .menu__bars i:nth-child(1) { transform: translateY(6px) rotate(45deg); }
.menu.is-open .menu__bars i:nth-child(2) { opacity: 0; }
.menu.is-open .menu__bars i:nth-child(3) { transform: translateY(-6px) rotate(-45deg); }

/* ── The drawer ──────────────────────────────────────────────────────────
   It was a 208px dropdown of thirteen-pixel capitals hanging off the button.
   What replaced it is a full-height panel: six destinations set large enough
   to be aimed at rather than read, the one button somebody came here to press,
   and the agent's own details at the foot — the things a visitor opens a menu
   on a phone to find.

   FROM THE LEFT, because the button that opens it is at the left. A drawer
   that arrives from the opposite side of the screen to the control that
   summoned it is a second thing to look for.

   Everything in here takes its colour from two custom properties rather than
   from the palette directly; `site.js` writes them from one scroll-driven
   number. See `--menu-ground`. */
.menu__scrim {
  position: fixed; inset: 0; z-index: 148;
  background: rgba(24, 20, 16, .46);
  opacity: 0; pointer-events: none;
  transition: opacity .42s ease;
}
.menu.is-open .menu__scrim { opacity: 1; pointer-events: auto; }

/* THE PANEL FLOATS, INSET FROM ALL FOUR EDGES, AND ITS WIDTH IS A SHARE OF
   THE VIEWPORT WITH A FLOOR. Both halves matter, and the second is the one
   that was wrong: a fixed 416px is a sensible drawer at 1440 and a thin strip
   at 1920, because the panel stopped growing while the screen did not. The
   shape measured off the reference is held instead — 400x860 at 1440, 480x1040
   at 1920, 370x824 at 390 — which is a ratio of 0.46 at every one of them.
   `25vw` with a `400px` floor and a `100vw - 2 * gap` ceiling reproduces all
   three to the pixel.

   The inset is why the slide-out needs more than `-100%`: that leaves the
   panel's left edge sitting in the gap, and the 60px shadow stays on screen
   behind it. The extra clears both. */
.menu__panel {
  --menu-gap: 20px;
  position: fixed; z-index: 150;
  top: var(--menu-gap); bottom: var(--menu-gap); left: var(--menu-gap);
  width: min(calc(100vw - var(--menu-gap) * 2), max(400px, 25vw));
  display: flex; flex-direction: column;
  padding: clamp(74px, 8vh, 104px) clamp(26px, 4.4vw, 52px) clamp(26px, 4vh, 44px);
  /* The notch, and the home indicator at the other end. */
  padding-top: max(clamp(74px, 8vh, 104px), calc(env(safe-area-inset-top) + 64px));
  padding-bottom: max(clamp(26px, 4vh, 44px), calc(env(safe-area-inset-bottom) + 20px));
  background: var(--menu-ground, #fff);
  color: var(--menu-ink, #56504a);
  border-radius: clamp(10px, .9vw, 18px);
  box-shadow: 0 0 60px rgba(20, 16, 12, .28);
  /* `auto` and not `contain`. Containment is for a panel over a page that must
     not move; this panel is over a page that is MEANT to move under it, and on
     a phone the panel covers nearly the whole screen, so containing the chain
     leaves a visitor with nowhere to drag. */
  overflow-y: auto; overscroll-behavior: auto;
  transform: translateX(calc(-100% - var(--menu-gap) - 72px));
  /* The transform BELONGS in this list. Without it the panel snapped into
     place, the reflow before `.is-open` had nothing to flush, and the 520ms
     timeout that re-hides it was waiting out an animation that never ran. */
  /* THE GROUND IS NOT IN THIS LIST, and that is deliberate. The script eases
     the crossing itself, so a transition here would ease an already-eased
     value and the panel would lag the page by half a second on every flick of
     the wheel. The colour arrives where the tween puts it, frame by frame. */
  transition: transform .46s cubic-bezier(.22, .61, .36, 1);
}
@media (max-width: 680px) { .menu__panel { --menu-gap: 10px; } }
@media (prefers-reduced-motion: reduce) { .menu__panel { transition: none; } }
/* `hidden` is taken off before the slide and put back after it, so the panel
   is never display:none while it is moving. */
.menu__panel[hidden] { display: none; }
/* The resting state for a browser that never runs the script: open means
   open, with no transform to undo. */
.menu.is-open .menu__panel { transform: translateX(0); }

/* The slot the header button flies to. It draws nothing and catches nothing —
   it exists so that the landing position is written here, next to the padding
   it has to agree with, rather than as a pair of numbers in the script. */
.menu__dock {
  position: absolute; top: clamp(16px, 2.4vh, 28px); right: clamp(18px, 3vw, 30px);
  width: clamp(38px, 3.1vw, 46px); height: clamp(38px, 3.1vw, 46px);
  pointer-events: none;
}
/* OVER THE PANEL, not under it. The button is inside .nav, so the number is
   read against the panel's 150 in the same stacking context. The colour comes
   from the panel's ink because by then the button is sitting ON the panel —
   white over the hero, and it would vanish the moment the ground crossed. */
.menu.is-open .menu__btn {
  position: relative; z-index: 151;
  color: var(--menu-ink, #56504a);
}

.menu__list { list-style: none; margin: 0; padding: 0; }
.menu__list li + li { margin-top: clamp(14px, 2.1vh, 26px); }
/* SENTENCE CASE AND LARGE, which is most of the change. Thirteen-pixel
   tracked capitals are a label; these are destinations, and at this size the
   list is the panel rather than something inside it. */
.menu__list a,
.menu__list button {
  display: inline-block; width: auto;
  padding: 0; border: 0; background: none;
  font-family: var(--sans);
  font-size: clamp(23px, 2.1vw, 30px);
  font-weight: 400; letter-spacing: -.005em; line-height: 1.1;
  text-transform: none;
  color: inherit; text-decoration: none; text-align: left;
  cursor: pointer;
  opacity: .92;
  transition: opacity .22s ease;
}
.menu__list a:hover,
.menu__list button:hover { opacity: .6; }

/* THE BUTTON INVERTS WITH THE PANEL. `.btn--pill` is accent brown with white
   type, which is right everywhere else on the site and is brown on brown once
   the drawer's ground crosses to #463F36 — the one place where the page's own
   button is invisible. It takes the panel's ink as its fill and the panel's
   ground as its type, so it flips with everything else rather than being a
   third colour that has to be kept in step. The hover wipe keeps its own
   reversal by doing the same thing the other way round. */
.menu__cta {
  margin-top: clamp(26px, 4.4vh, 46px); align-self: flex-start;
  background: var(--menu-ink, #56504a); color: var(--menu-ground, #fff);
}
.menu__cta .btn-hover__mask { background: var(--menu-ground, #fff); color: var(--menu-ink, #56504a); }
.menu__cta.is-arriving { background: var(--menu-ground, #fff); color: var(--menu-ink, #56504a); }
.menu__cta.is-arriving .btn-hover__mask { background: var(--menu-ink, #56504a); color: var(--menu-ground, #fff); }

/* ── The foot of the panel ─────────────────────────────────────────────
   Pushed down by the margin rather than pinned to the bottom, so a short
   screen scrolls it into reach instead of overlapping the list. */
.menu__card { margin-top: auto; padding-top: clamp(30px, 5vh, 54px); font-size: 14.5px; line-height: 1.5; }
.menu__card p { margin: 0 0 4px; color: var(--menu-mute, #7a7168); }
.menu__card a { color: inherit; text-decoration: none; }
.menu__card a:hover { text-decoration: underline; text-underline-offset: 3px; }
.menu__name {
  font-family: var(--serif);
  -webkit-text-stroke: var(--serif-stroke) currentColor;
  font-size: clamp(21px, 1.7vw, 25px); line-height: 1.15;
  margin-bottom: 6px !important; color: var(--menu-ink, #56504a) !important;
}
.menu__tel a { font-size: 18px; color: var(--menu-ink, #56504a); }
.menu__where {
  margin-top: 18px !important; padding-top: 16px;
  border-top: 1px solid var(--menu-line, rgba(124, 114, 98, .22));
}
.menu__where span { display: block; color: var(--menu-ink, #56504a); }
.menu__social { display: flex; gap: 16px; margin-top: 20px; }
.menu__social a { color: var(--menu-ink, #56504a); opacity: .75; transition: opacity .2s ease; }
.menu__social a:hover { opacity: 1; }

/* The menu STAYS over the sitemap. It used to go, on the grounds that the page
   behind is inert and locked so every entry would point somewhere unreachable
   — which was true of the markup but is a reason to make the entries work,
   not to take the menu away. site.js now closes the overlay first and acts
   once it has, so Home, About, Architecture and Contact land where they say
   and Aerial Tour opens the player over a page that is no longer covered.

   Two things follow from it being there. The bars need the accent: .nav is
   white over the hero and stays white over the sitemap, and white bars on a
   near-white map are nothing — the same argument as the wordmark's. And the
   header sets pointer-events:none over the overlay so the map underneath can
   be reached, with each control turning them back on for itself. */
.menu { transition: opacity .28s ease; }
body.is-overlay .menu { pointer-events: auto; }
body.is-overlay .menu__btn { color: var(--accent); }

/* Except while two lots are being compared. That panel covers the map and
   carries its own close in the top-left corner, which is where this button
   is; two buttons in one corner, and only one of them belongs to what is on
   screen. site.js mirrors the app's own compare-open onto the body here — the
   same signal its legend uses to step aside. Fades with the .menu transition
   declared above rather than snapping. */
body.is-compare .menu { opacity: 0; pointer-events: none; }

.nav.is-solid {
  background: var(--paper);
  color: var(--accent);
  box-shadow: 0 1px 0 var(--line);
}

/* ── While the sitemap is open ──────────────────────────────────────────
   The header rises above the overlay (140) so its button is reachable, and
   drops its ground and its shadow, so what is left reads as marks floating
   over the map rather than as a second header competing with the sitemap's
   own bar.

   The wordmark STAYS, in the accent brown. It used to be hidden here on the
   grounds that the sitemap carried its own labelling at the top left, so two
   marks would be clutter. Both halves of that are false: the sitemap's corner
   wordmark is anchored RIGHT, and it is force-disabled there
   (`.alure-corner-wordmark { opacity: 0 !important }`). The top-left is empty
   and nothing is doubled — the brand was simply vanishing the moment a
   visitor opened the thing they came to look at.

   Brown, not the white the header carries over the hero: the sitemap renders
   on pale ground, where white would be invisible.

   Nothing about its POSITION is touched — grid-column, alignment and the
   gutter all still come from .wordmark, so it sits in the identical box it
   occupies on the page behind. */
/* The wordmark stays in the sticky header for the whole page. It briefly hid
   itself on scroll here; that was a misreading of a request that was about the
   SITEMAP, and a site's mark leaving its own header is not a thing to do by
   halves.

   Over the SITEMAP it is present when the overlay opens and leaves the moment
   the sitemap is scrolled — which is what was wanted all along, and what a
   cross-origin frame made impossible. The frame is now served from THIS origin
   by functions/[[path]].js, so its own scroll, wheel and touchmove are
   readable and set is-map-engaged; see watchFrameScroll in site.js.

   If that proxy is ever removed the frame goes back to cross-origin,
   contentDocument throws, the class is never set, and the wordmark stays
   visible for the session — the old behaviour, not a broken one. */
body.is-overlay.is-map-engaged .wordmark { opacity: 0; pointer-events: none; }

/* Crossing between the page and the sitemap is a cut. Set by snapWordmark()
   in site.js for one frame either side of the switch, so the mark changes
   state instantly there while keeping its transitions everywhere else — the
   scroll fade over the sitemap, and the white-to-brown ease as the header
   takes its ground. !important because it has to beat the base .wordmark rule
   without dragging this into a specificity argument. */
body.is-snapping .wordmark { transition: none !important; }

body.is-overlay .nav { z-index: 150; background: none; box-shadow: none; }
/* The wordmark names its own colour in EVERY state rather than inheriting the
   header's. It has to, now that it transitions colour itself: .nav animates
   its own colour over .5s on the is-solid toggle, and a child transitioning
   toward a value that is still moving converges on a target that keeps
   receding. Measured at 1200ms after the toggle — long past both .5s — it was
   still at rgb(137,128,113), 90% of the way to the accent and visibly behind
   the rest of the header. With fixed endpoints each transition runs once and
   lands in step.

   Both brown rules resolve to the same colour, so it does not matter which
   wins when a visitor scrolls the page and then opens the sitemap. */
.wordmark                 { color: #fff; }
.nav.is-solid .wordmark,
body.is-overlay .wordmark { color: var(--accent); }

/* ── EXCEPT WHILE THE SITEMAP IS STILL COMING ─────────────────────────
   The rule above is right once the sitemap is on screen — brown on its pale
   ground — and was right during the load too, back when the overlay's own
   ground was #0d0c0b. It is not right now: the overlay opens at the accent
   itself, so for the whole of the load the wordmark was accent on accent,
   which is nothing at all. The mark did not come in late; it was invisible
   until the sitemap painted something behind it.

   White for exactly that window, and the window is the overlay's own loading
   element — removed by waitForSitemap() in site.js when the app says it is
   ready, not when the iframe merely finishes loading. One rule, no state to
   keep in step, because the element it reads is already the one thing on
   screen that means "still waiting".

   `:has()` and not a body class, deliberately: the condition IS the presence
   of that element, and a class would be a second copy of the same fact that
   could disagree with it. A browser without `:has()` falls back to the rule
   above, which is today's behaviour rather than a broken one. */
body.is-overlay:has(.avail__loading) .wordmark { color: #fff; font-weight: 600; }
body.is-overlay:has(.avail__loading) .wordmark__slash { font-weight: 500; }

/* One cut heavier wherever the wordmark is BROWN ON PALE — over the sitemap,
   and in the page's own solid header once you scroll. Not a correction to a
   weight that was wrong: measured, both states were already Inter 500 at 15px
   with an identical 88.719px advance, and the brown one laid down marginally
   MORE ink (105%) than the white.

   What differs is contrast, not weight. Against the hero the white sits at
   |ΔL| 0.988; the brown against the sitemap's near-white sits at 0.551 — 56%
   of the separation — so it reads faint, and faint reads as light. Raising the
   weight puts the ink back that the missing contrast takes away. Rendered at
   3x and measured, the brown sits one cut above the white: at the white's own
   weight it reads lighter, and two cuts above it reads heavier rather than
   equal. So the pair moves together — the white is 600 and the brown 700.

   Both brown states, not just the sitemap's: they are the same condition, and
   splitting them would leave two different browns at two different weights.

   A real cut, not synthesis — Inter is variable across 100..900 here, which is
   also why font-weight can transition rather than snap. */
.nav.is-solid .wordmark,
body.is-overlay .wordmark { font-weight: 700; }

/* The slash is drawn one cut lighter than the letters on purpose, so it moves
   with them. Left at the white slash's own weight against a heavier wordmark
   the gap would double, from one step to two, and the slash would thin out
   just as the letters thickened. */
.nav.is-solid .wordmark__slash,
body.is-overlay .wordmark__slash { font-weight: 600; }

/* The header is a full-width fixed bar. Invisible over the sitemap, it was
   still swallowing every click across the top of the screen — the lots and
   controls up there could not be reached at all. Measured: Playwright
   reported "header intercepts pointer events" on a click inside the frame.

   So while the overlay is open the bar itself stops taking pointer events and
   only the button takes them back. Scoped to the overlay on purpose: when the
   header is a solid bar on the page it SHOULD block what is behind it. */
body.is-overlay .nav { pointer-events: none; }
body.is-overlay .nav > .btn { pointer-events: auto; }

/* A lot's detail panel covers BOTH top corners of the sitemap, so the whole
   header steps aside while one is open and comes back when it shuts. The
   sitemap tells us: it already posts alure-panel-open / alure-panel-close to
   its parent, which is what the script listens for.

   The button goes because the panel itself covers the top right. The wordmark
   goes because of the panel's status pill — measured in the deployed sitemap:

     .detail-status-pill { position: fixed; top: 1.5rem; left: 1.5rem;
                           min-height: 44px; z-index: 1440 }

   which occupies roughly 24–68px down and out from 24px, against a wordmark
   sitting 39–73px down from 45px. They overlap almost exactly. z-index 1440
   would not save it either: that number is scoped to the iframe's own
   stacking context, and our header is painted over the whole frame, so the
   brown letters would land on top of the pill's text rather than behind it.

   Two lines of type in one place is the one arrangement that is worse than
   either alone, so the wordmark yields to the pill and returns with it. */
body.is-panel .nav > .btn,
body.is-panel .wordmark {
  opacity: 0; pointer-events: none; transform: translateY(-8px);
}
.nav > .btn { transition: opacity .28s ease, transform .28s ease; }
.wordmark {
  transition: opacity .28s ease, transform .28s ease,
              color .5s ease, font-weight .5s ease;
}

/* ── Hero ─────────────────────────────────────────────────────────────── */
.hero {
  position: relative;
  /* svh, not vh: on iOS the URL bar makes vh taller than the visible page, so
     the headline sits under the fold on first paint and rises into view as you
     scroll — which reads as a bug. */
  height: 100svh; min-height: 560px;
  /* No flex any more: every child is absolutely positioned, the headline
     included, so there is nothing left for it to lay out. The headline's
     height is set from the hero's, which is what align-items could not do. */
}
/* The clipping lives HERE now, not on .hero. It only ever existed to contain
   the hero photograph's slight over-scale during the entry, which is this
   element's own child — and on .hero it also clipped the reel the instant the
   reel started travelling past the hero's bottom edge, which is the whole
   point of the follow below. Moving it down one level keeps the photograph
   contained and lets the reel leave. */
.hero__media { position: absolute; inset: 0; overflow: hidden; }
.hero__media img,
.hero__media video { width: 100%; height: 100%; object-fit: cover; object-position: 50% 42%; }
.hero__veil {
  position: absolute; inset: 0;
  background:
    linear-gradient(to bottom, rgba(20,16,12,.34) 0%, rgba(20,16,12,0) 26%),
    linear-gradient(to top, rgba(20,16,12,.52) 0%, rgba(20,16,12,0) 46%);
}
.hero__title {
  /* Back at the foot of the hero, on the same clamp the reel sits on, so the
     two share one baseline across the bottom of the frame. Still absolute
     rather than a flex item — the hero has no flow children left to lay out —
     which makes this a plain offset with no translate to undo. */
  position: absolute;
  left: 0; right: 0;
  bottom: clamp(52px, 8vh, 120px);
  margin: 0;
  padding: 0 var(--gutter);
  font-family: var(--serif); font-weight: 400;
  -webkit-text-stroke: var(--serif-stroke) currentColor;
  color: #fff;
  /* The section headlines' size, quoted from .intro__head — the hero is no
     longer the subordinate line it was set as. Both are stated rather than
     shared through a token because .intro__head is one of two call sites; if a
     third appears, make it a token. */
  font-size: clamp(32px, 5.7vw, 100px);
  line-height: 1.2; letter-spacing: -0.012em;
  text-shadow: 0 1px 34px rgba(20, 16, 12, .28);
}
/* Each line is a clipping window with the words inside it, so GSAP can slide
   the words up from behind their own edge rather than fading a whole block. */
/* ── The clipping window headlines rise out of ────────────────────────
   Every .line is a box with its content hidden until it slides up into view.
   At line-height 1.04 that box is barely taller than the em, so any descender
   — the p in "Experienced", the g in "choreograph" — hangs below it and gets
   CUT OFF at rest, not just during the animation.

   Re-measured for Italiana from the font's own metrics: its content box is
   1.178 em against Playfair's 1.333, and its deepest descender sits 0.039 em
   below the line box at line-height 1.10 (Playfair's sat 0.054 em below).
   Still clipped without a fix, and the .22em below covers it about five times
   over — so this padding carries the new face unchanged. Re-run that
   measurement if the headline face changes again; the table is in the README.

   Padding opens the window downward and a matching negative margin takes the
   space back, so the clip box grows without moving a single line. Set here
   rather than inline in the script, so one rule governs the hero and the
   split headlines alike. */
.line {
  display: block;
  overflow: hidden;
  padding-bottom: .22em;
  margin-bottom: -.22em;
}
.line > span { display: block; }

/* ── The aerial reel, inset in the hero ───────────────────────────────
   Bottom-right, on the page's gutter, and now mostly BELOW the fold: it breaks
   the hero's bottom edge with only its top showing, so it reads as something
   continuing past the screen and gives the eye a reason to go down. It no
   longer shares the headline's baseline, because the headline has moved to the
   middle of the hero. */
.reel {
  position: absolute;
  right: var(--gutter);
  margin: 0;
  /* Each term is the original x 1.25 — 378px at a 1440 page, where it was
     302. Kept as a clamp rather than a fixed width so it still tracks the
     viewport the way the rest of the page does.

     Named, because the offset below is DERIVED from it and the two must not
     drift apart. */
  --reel-w: clamp(230px, 26.25vw, 395px);
  width: var(--reel-w);
  aspect-ratio: 16 / 9;
  /* Fully inside the hero again — no part of it under the fold. This is the
     number it had before the peek, and it is not arbitrary: it is the same
     clamp the headline used for its own bottom margin when the headline sat
     at the foot, so the reel still lands on the rhythm the hero was built on. */
  bottom: clamp(52px, 8vh, 120px);
  overflow: hidden;
  background: rgba(20, 16, 12, .45);
  box-shadow: 0 18px 54px rgba(20, 16, 12, .44);
  /* The frame is an OUTLINE, not a border. A border sits inside the
     aspect-ratio box and would eat 12px off the video itself; an outline is
     painted outside the box, so the frame is added to the picture rather than
     taken out of it, and the 16:9 stays exact.

     In the accent rather than white: the tint multiplies the accent INTO the
     footage, which darkens it, so a solid accent frame still sits lighter than
     the picture it holds and the edge stays legible. */
  outline: 9px solid var(--accent);
  outline-offset: 0;
  /* A modern outline follows border-radius, so this one number rounds the
     frame and the picture together. `overflow: hidden` above is what clips the
     video itself to it. A browser old enough to paint a square outline around
     a rounded box degrades to the frame it had before, which is fine. */
  border-radius: 7px;
  /* isolation is load-bearing, not tidiness. .reel is position:absolute with
     z-index:auto, which does NOT open a stacking context, so the tint's
     mix-blend-mode below would blend against whatever is painted under the
     figure — the hero photograph — instead of against the video inside it.
     This confines the blend to this figure's own contents. */
  isolation: isolate;
  /* The right edge, mid-height. The reel grows as it follows the scroll (see
     REEL_GROW in site.js); from the centre it would push past the gutter into
     the right margin, and from a corner it would look like a stretch. Pinned
     to this edge it expands leftward and holds the margin it sits on. */
  transform-origin: 100% 50%;
}
.reel__video { display: block; width: 100%; height: 100%; object-fit: cover; }

/* ── The brown tint, while it is an inset ─────────────────────────────
   The reel is the one thing in the hero that is not the site's own palette —
   78 seconds of bright desert cutting against a page built out of one warm
   grey. Multiplying the accent over it pulls it into that palette while it
   sits small. It comes off in fullscreen, where the footage is the point and
   should be the client's own colour. */
.reel::after {
  content: '';
  position: absolute; inset: 0;
  background: var(--accent);
  mix-blend-mode: multiply;
  opacity: .58;
  pointer-events: none;
}
/* Above the tint: the caption and the controls are chrome, not footage. */
.reel__cap, .reel__controls { z-index: 2; }

.reel__cap {
  position: absolute; left: 0; bottom: 0;
  padding: .5em .85em .55em;
  font-family: var(--sans-display); font-weight: 500;
  font-size: clamp(8px, .72vw, 10px);
  letter-spacing: .22em; text-transform: uppercase; text-indent: .22em;
  color: #fff;
  text-shadow: 0 1px 12px rgba(20, 16, 12, .75);
  pointer-events: none;
}

/* Centred in the picture, the way a player's controls are, rather than tucked
   into a corner. The strip itself takes no pointer events so it is not a dead
   patch over the video; the button takes them back.

   One button now, so `justify-content: center` centres it on both axes on its
   own and the gap between two is gone with the second one. */
.reel__controls {
  position: absolute; inset: 0;
  display: flex; align-items: center; justify-content: center;
  pointer-events: none;
}
.reel__btn {
  pointer-events: auto;
  width: clamp(46px, 4.1vw, 62px); height: clamp(46px, 4.1vw, 62px);
  display: grid; place-items: center;
  border: 0; padding: 0; margin: 0;
  border-radius: 50%;
  /* The page's one colour, rather than a neutral scrim. White on #7C7262 is
     4.7:1, comfortably past the 3:1 WCAG 1.4.11 asks of a control's glyph, so
     the icons stay legible over any frame of the reel. */
  background: var(--accent);
  color: #fff; cursor: pointer;
  /* Out of the way until wanted — but see the exceptions below, which are what
     keep the enlarge control actually reachable. */
  opacity: 0; transition: opacity .25s ease, background .25s ease, transform .25s ease;
}
.reel__btn:hover { background: #6b6255; transform: scale(1.06); }
/* The glyphs are sized here so they track the button, but the markup still
   carries explicit width/height attributes — an inline SVG with only a
   viewBox falls back to 300x150 when the stylesheet is missing or stale, which
   this site has been bitten by once already. See the note in _headers. */
.reel__btn svg { width: 45%; height: 45%; }
.reel:hover .reel__btn,
.reel__btn:focus-visible { opacity: 1; }
/* 1. Stopped — autoplay refused, or reduced motion, or the overlay just shut —
      the picture is a still, and enlarge is the only way to see it move.
   2. A touch screen has no hover at all. */
.reel.is-paused .reel__btn { opacity: 1; }
@media (hover: none) { .reel__btn { opacity: 1; } }
/* 3. And once the visitor has started scrolling. The reel travels and grows
      from here on, so it is the thing moving on screen — the control comes out
      to say it opens. Set from the scroll handler, not by :hover, because the
      point is to reach somebody who has not put a pointer anywhere near it. */
.reel.is-scrolled .reel__btn { opacity: 1; }

/* 54px at 26px. It was briefly cut to 30px at 12px, to clear a supporting
   line that sat centred at the foot of the hero; that line has been dropped,
   so the reason is gone and so is the change. */
.hero__cue {
  position: absolute; left: 50%; bottom: 26px; transform: translateX(-50%);
  width: 1px; height: 54px; background: rgba(255,255,255,.3); overflow: hidden;
}
.hero__cue span { display: block; width: 100%; height: 40%; background: #fff; animation: cue 2.6s cubic-bezier(.7,0,.3,1) infinite; }
@keyframes cue { 0% { transform: translateY(-100%); } 60%, 100% { transform: translateY(250%); } }

/* ── Page grid ────────────────────────────────────────────────────────── */
.intro, .ensemble, .choreo, .quote {
  max-width: var(--maxw);
  margin-inline: auto;
  padding-inline: var(--gutter);
  /* The header is fixed, so anything scrolled to by id — the skip link, a
     shared #intro — would otherwise land underneath it with its first line
     hidden. Measured against the header's own tallest state. */
  scroll-margin-top: clamp(76px, 7vw, 104px);
}

/* ── Intro ────────────────────────────────────────────────────────────── */
.intro {
  padding-top: var(--gap-s);
  display: grid;
  grid-template-columns: repeat(12, 1fr);
  column-gap: clamp(20px, 2.4vw, 44px);
}
.intro__head {
  grid-column: 1 / 9;
  font-size: clamp(32px, 5.7vw, 100px);
  margin-bottom: var(--gap-xs);
}

.intro__side {
  grid-column: 7 / 13; grid-row: 2;
  max-width: 50ch;
  /* CENTRED ON THE ROW, with the drawing beside it. The drawing is the
     taller of the two and sets the row's height, so this is the one that
     moves: top-aligned it sat against the headline with all the slack
     underneath it, which read as a paragraph that had been pushed up rather
     than a pair that belong together. Centring both means neither is
     anchored to an edge, and it stays true if the paragraph ever grows past
     the drawing. */
  align-self: center;
}
/* .intro__cta is gone — the client removed the Availability button from this
   section. The one in the header and the one beside "Homes choreograph…" are
   the two that remain. */

/* ── The site plan ─────────────────────────────────────
   IT TAKES NO NEW ROW. Row 2 was already half empty: the paragraph sits at
   7/13 and the block beside it held nothing but white, measured 642px wide
   and 240px tall at 1440. The drawing goes there, which is why nothing below
   it had to be renumbered — .mile stays on row 3 and the two plates on row 4,
   and the two media queries that reassign those rows are untouched.

   It still lands ABOVE the band, because it is in the row above it. And the
   band gets its breathing room from the plan being taller than the paragraph
   it shares the row with: the row grows to the taller of the two and .mile's
   own margin-top starts from there.

   ── HALF THE BLOCK, CENTRED IN IT. `width: 50%` of columns 1-6 — 304px at
   1440, 393 at 1920 — with `justify-self: center`, so the white falls evenly
   on both sides of it rather than all on one. Hard right put it a single
   gutter from the paragraph and read as crowding the text; hard left put the
   whole gap between the two. Centred is 152px of air each side at 1440.

   Same as the narrow layout below, which has been centred since the band
   went two-up — so this is now one alignment at every width rather than two.

   No panel, no padding, no radius: the drawing IS the element now.

   IT STAYS ON COLUMNS 1-6 EVEN AT HALF WIDTH, and the grid span is doing
   real work there rather than being decoration round a `width`. At the full
   block it was what kept the button off the paragraph: the page grid has
   eleven gaps AND three inflated tracks — .intro__inset's 23vw is wider than
   three 1fr columns give it, so 10-12 grow and 1-9 shrink to pay — which
   puts the band's own halfway point 68px past six columns at 1440 and 95px
   past at 1920, and past the PARAGRAPH's left edge at every width from 1081
   up. `1 / 13` with `width: 50%` is half the band to the pixel, and it was
   tried and reverted: from 1081 to about 1600 it laid the drawing over the
   paragraph, and above that an invisible stretch of BUTTON over it, taking
   its clicks and killing its text selection with nothing on screen to show
   for it. Caught by a box-intersection check, not by looking. Half of six
   columns cannot reach the text from either direction.

   Centred on the row rather than top-aligned, together with the paragraph
   beside it — see .intro__side. Being the taller of the two it does not
   itself move; the rule is here so that whichever is taller, the other sits
   on its middle.

   `height: auto` on the images with the attributes supplying the ratio is
   deliberate — neither the button nor .intro__plan-art has an aspect-ratio
   of its own, so a percentage height would resolve against nothing and
   collapse. */
.intro__plan {
  /* ONE PLACE. The entrance tween has to end on exactly this value, and it
     cannot read it off the map's computed opacity because the resting state
     for the animation sets that to 0. A custom property is not overridden by
     the hidden state, so site.js reads this and the number lives here. */
  --plan-ink: .45;
  /* ── The arrow's size, its gap under the mark, and the lift they cause.
     IN PIXELS, not percentages, and that is what makes the lift expressible
     at all. The disc was `width: 12%` of the art's WIDTH, which cannot be
     reused as a height (a height percentage resolves against the height) and
     so could not be put into the sum below. These two clamps reproduce what
     the percentages measured to within a pixel — 48/13.8 at 1920 against
     48.5/13.9, 37.4/10.4 at 1440 against 37.5/10.8 — and they are the same
     number for both axes, which is the point.

     THE LIFT IS DERIVED. With the mark's centre on the art's centre, the
     group runs from markH/2 above it to gap + size below. Centring the
     group means moving its centre up by half of what hangs below the mark,
     which is (gap + size) / 2 — and the SAME figure for both, because the
     mark and the disc are rigid relative to each other. Hence one transform
     on one layer, rather than two that have to agree. */
  --go-size: clamp(34px, 2.6vw, 48px);
  --go-gap: clamp(9px, .72vw, 14px);
  --go-lift: calc((var(--go-gap) + var(--go-size)) / -2);
  grid-column: 1 / 7; grid-row: 2;
  align-self: center; justify-self: center;
  /* A FLOOR UNDER THE HALF. Six columns is only 456px at 1081, so a straight
     50% put the drawing at 228 and the wordmark at 85x16 — two lines of caps
     in about 6px of cap height, which is a logo you cannot read. 260 holds
     it at 99px of mark, and it is only ever reached below about 1140. */
  width: max(50%, 260px);
  display: block;
  margin: 0; padding: 0;
  border: 0; background: none;
  font: inherit; color: inherit;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
/* The positioning context for the mark, and the thing the hover moves. */
.intro__plan-art {
  position: relative; display: block; width: 100%;
  transition: transform .55s cubic-bezier(.22, .61, .36, 1);
}
/* 45%, and it is load-bearing rather than a mood. See index.html: at full
   strength NO flat ink clears 3:1 over this drawing — it already contains
   every colour you could pick — so the mark reads only because the drawing
   is held at low contrast against the paper, which is the one thing the
   sitemap's own intro is doing with its 7.5%. Worst case over the crop the
   mark covers: 3.40:1 here, 3.02 at 50%. */
.intro__plan-map { display: block; width: 100%; height: auto; opacity: var(--plan-ink); }
/* 46% of the drawing, which is about what the intro gives it. Centred on the
   DRAWING rather than on the button — the two are the same box now that the
   padding is gone, and this stays true if that ever stops being so. */
.intro__plan-mark {
  position: absolute; left: 50%; top: 50%;
  transform: translate(-50%, -50%);
  width: 46%; height: auto;
}
/* ── The cue ───────────────────────────────────────────
   UNDER THE MARK, and the offset is arithmetic rather than a number somebody
   nudged until it looked right. `top` resolves against the art's HEIGHT, and
   everything about the mark is expressed against its WIDTH, so:

     mark height   = 46% of the art's width x 64/333          = 8.84% of W
     art height    = W / (1200/829)                           = W / 1.4475
     half the mark, against H = (8.84% / 2) x 1.4475          = 6.4%

   so 50% + 6.4% puts its top edge on the mark's bottom edge, and the 5%
   after that is the gap. It holds at every size because every term scales.

   Inside .intro__plan-art rather than beside it, so it travels with the
   1.03 hover scale — it is part of the picture moving toward you, not a
   control bolted on top of it. */
/* The layer that rises. inset: 0 makes it the art's own box, so everything
   inside it is positioned exactly as it would be without it.

   pointer-events: none, with the mark put back to auto. The layer covers the
   whole drawing, and left interactive it would be what the page hands back
   when you ask what is painted at a point — which is the check that proves
   the mark is on screen at all. The mark answers for itself; the layer and
   the arrow are not targets. Clicks reach the button regardless: both are
   inside it. */
.intro__plan-lockup {
  position: absolute; inset: 0;
  pointer-events: none;
  transition: transform .5s cubic-bezier(.22, .61, .36, 1);
}
.intro__plan-mark { pointer-events: auto; }
.intro__plan:hover .intro__plan-lockup,
.intro__plan:focus-visible .intro__plan-lockup { transform: translateY(var(--go-lift)); }
/* No pointer means no hover means the arrow is always there, so the lift is
   always there too — otherwise the group would sit low on every phone. */
@media (hover: none) {
  .intro__plan-lockup { transform: translateY(var(--go-lift)); }
}

.intro__plan-go {
  position: absolute; left: 50%; top: calc(50% + 6.4% + var(--go-gap));
  display: grid; place-items: center;
  /* IT IS NOT A TARGET. The whole picture is the button, and an element at
     opacity 0 still answers elementFromPoint — which is how this first showed
     up: the probe that asks the page what is painted on the drawing got the
     arrow back at 360 and 320, where the circle lands on one of its sample
     points. Decoration must not be what the page hands you when you ask what
     is there. */
  pointer-events: none;
  width: var(--go-size); height: var(--go-size);
  border-radius: 50%;
  background: var(--accent); color: #fff;
  opacity: 0;
  transform: translateX(-50%) translateY(7px) scale(.88);
  transition: opacity .34s ease, transform .44s cubic-bezier(.22, .61, .36, 1);
}
.intro__plan-go svg { width: 45%; height: 45%; display: block; }
.intro__plan:hover .intro__plan-go,
.intro__plan:focus-visible .intro__plan-go {
  opacity: 1;
  transform: translateX(-50%) translateY(0) scale(1);
}
/* No pointer, no hover, and a hint that only appears on hover is a hint that
   never appears at all. On touch it is simply there. */
@media (hover: none) {
  .intro__plan-go { opacity: 1; transform: translateX(-50%); }
}

/* The caption under this is gone, so the hover IS the affordance — there is
   no text left to say the picture does something. */
.intro__plan:hover .intro__plan-art,
.intro__plan:focus-visible .intro__plan-art { transform: scale(1.03); }
.intro__plan:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 10px;
  border-radius: 4px;
}

@media (prefers-reduced-motion: reduce) {
  .intro__plan-art, .intro__plan-go, .intro__plan-lockup { transition: none; }
  .intro__plan:hover .intro__plan-art,
  .intro__plan:focus-visible .intro__plan-art { transform: none; }
  /* The cue still appears — it carries meaning. It just does not travel. */
  .intro__plan-go { transform: translateX(-50%); }
  .intro__plan:hover .intro__plan-go,
  .intro__plan:focus-visible .intro__plan-go { transform: translateX(-50%); }
}

/* ── The milestones ────────────────────────────────────
   Its own row between the paragraph and the plates, which is why those two
   moved from row 3 to row 4.

   A <dl>, because that is what this is: four labelled values. The figure is
   the <dd> and the label the <dt>, so a screen reader reads "Still available,
   40" rather than two unrelated numbers — and the pairs are wrapped in divs,
   which is the one place HTML allows a wrapper inside a definition list.

   Set in the display serif at a size between the body and a headline: these
   are meant to be read as facts, not as a headline competing with the one
   above them. The rule above the band is what separates it from the
   paragraph; there is no box, no fill and no card. */
.mile {
  grid-column: 1 / 13; grid-row: 3;
  margin: clamp(38px, 5vw, 76px) 0 0;
  padding-top: clamp(22px, 2.6vw, 38px);
  border-top: 1px solid color-mix(in srgb, var(--accent) 26%, transparent);
  display: grid;
  /* FOUR EQUAL COLUMNS. This was repeat(4, auto) with space-evenly, because
     "15,580-30,089" is more than four times the ink of "110" and an equal
     track could not hold it at the size the figures were set. Equal tracks
     are back and the size came down to pay for them: the widest value is
     measured against the track it has to live in, rather than the row being
     reshaped around it.

     This does NOT fix the sideways scroll the page has at 1920 and up, which
     an earlier draft of this comment claimed. Tested: the band's box stays
     1760px wide with the figures forced to 10px, with the labels at 4px, and
     with both — it is not content-driven at all. #intro's grid is wider than
     its own padding box because .intro__wide and .intro__inset bleed past
     the gutters, and this band spans 1 / 13, so it inherits that width and
     its right edge lands at 1938 in a 1920 viewport. Hiding any one of those
     three returns the page to 1920. That is #intro's layout, not this
     component's, and it is the same 1938 that was there before this refactor.

     No column-gap: the dividers are the separation, and a gap either side of
     a rule reads as a wider, vaguer boundary than the rule itself. */
  /* minmax(0, 1fr), NOT 1fr. A bare 1fr is minmax(auto, 1fr), and `auto` as a
     minimum is the content's min-content width — which for a nowrap figure is
     the whole figure. The tracks could not shrink below their text, so they
     blew past the container and the page scrolled sideways: measured 1938px
     of content in a 1920 viewport, and 2337 in 2200. Zero as the minimum is
     what makes four equal columns actually equal. */
  /* THREE, AND NOT EQUAL. It was four equal columns, and the fourth is what
     made the size range wrap at every width. Merging the two sales figures
     into "40/110" was necessary and not sufficient: measured with the figure
     set nowrap, the range wants between 0.309 and 0.337 of the band
     depending on where the font clamp lands, and an equal third minus its
     own padding gives it LESS — 401px against 426 needed at 1440, 301
     against 329 at 1081. Short by 22 to 28px nearly everywhere, and fitting
     at 1920 by a single pixel.

     So the middle track is 1.25fr: 0.385 of the band against the 0.361 the
     range needs at its hungriest, padding included. The outer two keep
     0.308 each, which is far more than "40/110" or "250-350 FT" ask for.
     Equal tracks were right when four columns held four figures of similar
     weight; three figures of plainly different weight want tracks that say
     so. */
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.25fr) minmax(0, 1fr);
  row-gap: clamp(28px, 3vw, 44px);
}

/* ── The slash ────────────────────────────────────────
   IT INHERITS. Nothing here sets a face, a size, a weight or a colour,
   because the thing it has to match is two columns to its right: the en
   dashes in "15,580-30,089" and "250-350" are Italiana glyphs at the figure
   size, carrying .display's 0.016em text-stroke, and they read as a hairline
   because of it.

   It was the WORDMARK's slash before — Inter at 500, 0.46em, tinted. That
   is the right mark for PAL/SADES at 15px in the header and the wrong one
   here: a sans solidus at a heavy weight beside two hairline serif dashes
   is the one stroke on the row that looks drawn with a different pen.

   The margin is the only thing it keeps, and it is optical. Italiana's
   solidus is cut tight and "40/110" sets without air on either side of it;
   the en dash has the sidebearings of a dash and needs none added. */
.mile__of { margin: 0 .06em; }


/* ---- The divider ------------------------------------------------------
   Drawn ON the item rather than into the gap, so it survives the row
   wrapping on a phone - a rule placed in a gap has to be told which gaps
   exist. The first column never draws one, so the band opens on type rather
   than on a line, and the outer two sit on the band's own margins: the row
   is divided, not boxed. */
.mile__item {
  display: flex; flex-direction: column; gap: .62em;
  /* STRETCHED, so the dividers are one height. align-self: start was needed
     when this was column-reverse — a stretched item packed that content from
     the BOTTOM and lifted the figure of any item whose label wrapped. In
     plain `column` the content sits at the top whatever the item's height,
     so stretching costs nothing and buys rules that all reach the same
     depth: without it the range's two-line block drew a divider twice the
     length of its neighbours'. */
  padding-inline: clamp(14px, 1.7vw, 30px);
  border-left: 1px solid color-mix(in srgb, var(--accent) 18%, transparent);
}
.mile__item:first-child { padding-left: 0; border-left: 0; }
.mile__item:last-child { padding-right: 0; }

/* ---- THE LABEL LEADS NOW ----------------------------------------------
   It sat under the figure, written first in the markup and reversed in flex.
   Source order is already label-then-value - <dt> then <dd> - so plain
   `column` is both the right reading order and the simpler rule, and the
   reversal is gone rather than inverted twice.

   Darker and heavier with it: 62% of --ink at weight 500 was a caption
   hanging off a figure that carried the meaning. Leading the block it has to
   hold its own at 12px, so it takes the full --ink at 600. */
.mile__label {
  font-family: var(--sans-display);
  font-size: clamp(11px, .82vw, 13px); font-weight: 600;
  letter-spacing: .14em; text-transform: uppercase;
  color: var(--ink);
}

.mile__fig {
  margin: 0;
  font-family: var(--serif);
  /* THE SIZE THIS BAND HAD BEFORE THE REFACTOR, restored. Equal columns had
     forced it down to clamp(27px, 2.52vw, 40px) so the size range could hold
     one line in a quarter of the band; it went to 5.6vw/92px when bigger was
     asked for, and this is the original figure — 66px at 1440 — which is
     where it is staying.

     The range still wraps at this size, and that is the price of the equal
     columns rather than of the type: "15,580-30,089" wants about 345px and a
     quarter of the band at 1440 is 312. Wrapping was accepted when the
     figures grew and it is the same wrap here.

     `display: block`, not flex. The unit was a flex item beside the number
     for baseline alignment, which inline text gives for nothing — and a flex
     row cannot break INSIDE the number, so the range would have had to drop
     its unit to a second line while staying too wide itself. In normal flow
     it breaks after the en dash, which is where a range should break, and
     the unit follows the last line.

     line-height is no longer 1: that was set for figures that could only
     ever be one line, and two lines of 66px type at a leading of 1 collide. */
  font-size: clamp(34px, 4.6vw, 74px);
  line-height: 1.06;
  color: var(--accent);
  display: block;
}
.mile__fig small {
  font-family: var(--sans-display);
  /* The unit's original clamp too, drawn against these figures. */
  font-size: clamp(11px, .85vw, 13px); font-weight: 500;
  letter-spacing: .14em; text-transform: uppercase;
  opacity: .72;
  /* 1.6em of the UNIT's own size, which is about .3 of the figure's — the
     gap the flex layout gave it before, when the gap was stated on the
     parent and so measured in figure-ems. Written against the small's own
     font-size it came out at 3.9px and the unit sat on the numeral. */
  margin-left: 1.6em;
}

/* PLACED AFTER the base rules, not before them. `.mile__item` here and
   `.mile__item` above are the same specificity, so source order decides —
   written above, the base padding won and the third stat kept a 17px indent
   and a divider while standing first in its row. */
/* ---- Two up below 1080 ------------------------------------------------
   Four columns stop working before the phone layout starts. Measured, the
   headroom between the widest value and its track: +38px at 1440, +32 at
   1280, +23 at 1100, +7 at 1000, and then NEGATIVE - -14 at 900 and -44 at
   760, where "15,580-30,089" is simply wider than a quarter of the band.
   The old 680px breakpoint left that whole band squeezed. 1080 is the page's
   own next breakpoint down, and it keeps 23px of air at the switch. */
@media (max-width: 1080px) {
  .mile { grid-template-columns: repeat(2, minmax(0, 1fr)); row-gap: 32px; }
  /* The divider follows the COLUMN, not the source order, once the row
     wraps: only the second and fourth items have anything to their left. */
  .mile__item { padding-inline: 0; border-left: 0; }
  .mile__item:nth-child(2n) {
    padding-left: clamp(16px, 2.4vw, 30px);
    border-left: 1px solid color-mix(in srgb, var(--accent) 18%, transparent);
  }
}


/* ── The two plates, measured off the artwork ────────────────────────
   Left:  0.655 W wide, 0.416 W tall (1.57:1), running off the LEFT PAGE EDGE.
   Right: 0.230 W wide, 0.285 W tall — PORTRAIT, 0.81:1 — inset 0.070 W from
          the page's right edge, which is further in than the page margin.
   They are TOP-ALIGNED. This build had the right plate landscape at 4:3 and
   bottom-aligned, which is why the row did not read like the comp.

   Both are sized in vw rather than grid columns because that is how the comp
   is built: their widths are fractions of the page, not of a text grid. */
.intro__wide {
  grid-column: 1 / 10; grid-row: 4;
  margin-left: calc(-1 * var(--gutter));
  margin-top: var(--gap-m);
  width: 65.5vw; max-width: none;
  overflow: hidden;
  aspect-ratio: 1676 / 1066;
}
.intro__inset {
  grid-column: 10 / 13; grid-row: 4;
  justify-self: end;
  align-self: start;              /* top-aligned with the wide plate */
  margin-top: var(--gap-m);
  margin-right: 3.9vw;            /* + the gutter = 0.070 W off the page edge */
  width: 23vw;
  overflow: hidden;
  aspect-ratio: 589 / 729;        /* portrait */
}
.intro__wide img, .intro__inset img { width: 100%; height: 100%; object-fit: cover; }
/* The 38% vertical framing here was there to pull the hero photograph's crop
   off its foreground boulders, back when this plate was that same file. The
   picture is its own now, and at 1823x1009 it is WIDER than this slot, so
   cover crops it horizontally and the vertical figure does nothing at all.
   Centred, which is what its symmetry wants. */
.intro__wide img { object-position: 50% 50%; }

/* ── Pull quote ───────────────────────────────────────────────────────── */
.quote { padding-top: var(--gap-l); padding-bottom: var(--gap-xl); }
.quote blockquote {
  /* The measure that reproduces the artwork's FOUR-line break. Face
     dependent, and NOT in the way you would guess: 26ch in Prata, 31ch in
     Playfair Display, 29ch in Italiana.

     `ch` is the width of the ZERO, which does not track a face's overall set
     width. Italiana sets about 9% NARROWER than Playfair across a line of
     text, but its zero is WIDER — 0.646 em against 0.600 — so inheriting 31ch
     through the swap quietly widened this column from 1153px to 1242px in a
     1352px page, and the quote ran nearly edge to edge. 29ch puts it back to
     1160px. Re-derive this, do not inherit it, if the face changes again. */
  margin: 0 auto; max-width: 29ch;
  text-align: center;
  /* 62px at a 1440 page in the artwork. */
  font-size: clamp(26px, 4.3vw, 76px);
  line-height: 1.12;
}

/* ── Full-bleed plates ────────────────────────────────────────────────── */
.bleed { width: 100%; overflow: hidden; aspect-ratio: 16 / 9; }
.bleed--tall { aspect-ratio: 16 / 8; }
.bleed img { width: 100%; height: 100%; object-fit: cover; }
@media (max-width: 760px) { .bleed, .bleed--tall { aspect-ratio: 4 / 3; } }

/* ── Ensemble ─────────────────────────────────────────────────────────── */
.ensemble { padding-top: var(--gap-xs); padding-bottom: var(--gap-l); }
.ensemble__head {
  font-size: clamp(32px, 5.7vw, 100px);
  max-width: 66vw;
  /* The artwork's own figure here is 0.111 W between this headline and the
     pictures under it — the one genuinely big gap in the comp, and it was set
     to exactly that. Pulled in by about a quarter by request: 8.4vw, which is
     121px at a 1440 page against the comp's 160. The measured value is kept in
     this note so the change reads as a decision rather than as drift. */
  margin-bottom: clamp(44px, 8.4vw, 158px);
}
.triptych {
  display: grid; grid-template-columns: repeat(3, 1fr);
  gap: clamp(10px, 1.3vw, 22px);
}
.triptych figure { overflow: hidden; aspect-ratio: 3 / 4; }
.triptych img { width: 100%; height: 100%; object-fit: cover; }

/* ── Choreograph ──────────────────────────────────────────────────────── */
.choreo {
  padding-bottom: var(--gap-l);
  display: grid;
  grid-template-columns: repeat(12, 1fr);
  column-gap: clamp(20px, 2.4vw, 44px);
  align-items: start;
}
/* Half the grid, not five twelfths: at 5/12 the headline broke as
   "Homes / choreograph / fluently with their / surroundings." and stranded a
   single word on the first line. Measured at 1440. */
/* No top padding: in the artwork the headline and the plate beside it start
   on the same line, and this was adding a second section gap on top of the
   one .ensemble already pays — 171px where the comp has 112. */
.choreo__copy { grid-column: 1 / 7; }
/* Smaller than the other headlines on purpose: this one sits in a half-width
   column, and the artwork's widest line here is 550px at 1440 — a measure
   82px type cannot break in three the way the comp does. */
.choreo__copy h2 { font-size: clamp(28px, 4.4vw, 78px); margin-bottom: var(--gap-xs); }
.choreo__copy p { max-width: 42ch; margin-bottom: var(--gap-m); }
.choreo__copy > div { display: flex; }
/* Runs off the right edge, mirroring the intro's plate. */
.choreo__plate {
  grid-column: 7 / 13;
  margin-right: calc(-1 * var(--gutter));
  overflow: hidden;
  aspect-ratio: 5 / 4;
}
.choreo__plate img { width: 100%; height: 100%; object-fit: cover; }

/* ── Footer ───────────────────────────────────────────────────────────── */
.foot {
  background: var(--paper-soft);
  color: var(--accent);
  padding: clamp(56px, 8vw, 118px) var(--gutter) clamp(34px, 4vw, 54px);
}
.foot__grid {
  max-width: var(--maxw); margin-inline: auto;
  display: grid;
  /* The lead column carries a 62px headline that must break as
     "Local. Experienced. / Trusted." — at 1.25fr it was squeezed to three
     lines regardless of its own max-width, because the grid track, not the
     measure, was the binding constraint. */
  grid-template-columns: minmax(0, 1.9fr) auto minmax(0, .85fr) minmax(0, .8fr);
  gap: clamp(28px, 4vw, 72px);
  /* Stretch, not start: the four columns share a bottom line — the MEET MARC
     button, the last contact line and the Compass address all sit level with
     the bottom of the portrait. Each column is a flex column and the piece
     that belongs at the foot is pushed there with margin-top: auto, so it
     works whatever the column happens to contain. */
  align-items: stretch;
}
.foot__lead { display: flex; flex-direction: column; }
.foot__lead h2 {
  /* Sized to the track it sits in, not to the page. The design's footer
     headline is about 40px at a 1440 viewport; at 4vw it was 58px, which no
     amount of max-width could fit on two lines inside this column. */
  font-size: clamp(30px, 3vw, 50px);
  /* The gap down to the button. This used to be 0, because `margin-top: auto`
     on the button opened the gap by pushing it to the foot of the column —
     the button aligned with the bottom of the portrait rather than sitting
     under the headline. The button now sits under the headline, so the gap
     has to be a real measurement again. */
  margin-bottom: clamp(22px, 2.4vw, 38px);
  /* 21ch held "Local. Experienced. / Trusted." to the artwork's two lines in
     Playfair Display. Italiana sets narrower and it now fits on ONE — the
     measure is doing nothing at this length. Left in place because it is what
     stops a longer headline running the width of the column, but do not read
     it as still reproducing the design's break. */
  max-width: 21ch;
}
/* align-self keeps the button its own width — a flex child would otherwise
   stretch across the whole column. No `margin-top: auto` any more: that was
   what pushed it to the bottom of the column to meet the portrait's lower
   edge, and it now follows the headline instead. */
.foot__lead .btn { --btn-px: 2.6rem; align-self: flex-start; }

/* The community's address, under the button. Set at the same size and rhythm
   as the brokerage address in the other column so the two read as a pair of
   facts rather than one being louder than the other — and the name above it
   is what says which is which. */
.foot__where {
  /* No font-size of its own: it takes the footer's, which is what the card of
     Marc's details beside it is set in. Measured, that is 19.008px against the
     14px the brokerage address uses — this is the community's address and
     reads at the footer's own voice. */
  margin: clamp(30px, 3.4vw, 52px) 0 0;
  line-height: 1.4;
}
/* Underlined, because it goes somewhere — the footer's other links are
   underlined only when they are addresses you can act on, and this is one.
   Offset so the descenders in "Racquet" and "Springs" are not struck. */
.foot__where a {
  color: inherit;
  text-decoration: underline;
  text-decoration-color: color-mix(in srgb, currentColor 38%, transparent);
  text-underline-offset: 4px;
  transition: text-decoration-color .3s ease;
}
.foot__where a:hover { text-decoration-color: currentColor; }
/* The eyebrow keeps the size it had; only the address grew. */
.foot__where span {
  display: block;
  font-family: var(--sans-display);
  font-size: 12px; font-weight: 500;
  letter-spacing: .14em; text-transform: uppercase;
  color: color-mix(in srgb, currentColor 62%, transparent);
  margin-bottom: .5em;
}
/* The portrait IS the line everything else aligns to, so it fills the row
   rather than sitting at its natural height.

   It cannot just be left alone: at 1440 the photo is the tallest column and
   sets the height, but by 1024 the five-line contact block overtakes it and
   the photo ends 19px above everything else. Filling the row makes its bottom
   edge the shared line by construction, at every width.

   The crop is horizontal — a square image covering a slightly taller box
   scales to the height and loses a few pixels off each side, which is jacket,
   not face. The row height is set by five short lines of contact detail, so
   it cannot run away and turn this into a letterbox. */
/* The header is fixed, so an anchor jump would otherwise land a section's top
   edge underneath it. Every target the menu can reach carries the same
   clearance, sized to the tallest the bar ever is. #top is exempt: the hero
   IS the top of the page and there is nothing above it to clear. */
#intro, #architecture { scroll-margin-top: 96px; }
/* Only when the FOOTER is the contact target — in flow, with the fixed header
   over it. Landing on its exact top puts the header across the headline. The
   spacer never wants this: the whole point of landing on it is that the footer
   underneath ends up fully revealed. */
.foot#contact { scroll-margin-top: 96px; }
/* #contact is the spacer at the very end of the document, so a jump to it runs
   out of page and lands on the fully revealed footer. No margin: 96px of
   clearance would aim ABOVE the thing being revealed. */

/* ── The footer is revealed, not scrolled to ──────────────────────────
   The page slides up off it. <main> is the moving sheet — relative, above the
   footer, and opaque, which is the whole trick: the footer is already sitting
   at the bottom of the viewport the entire time, and main is simply covering
   it until it travels away.

   The spacer is the transparent run of document at the end that main does not
   cover, so the footer shows through. Its height is the footer's own, measured
   and set as --foot-h by the script.

   Both the fixed position and the spacer hang off .has-reveal-foot, which the
   script only adds when the footer actually FITS the viewport. Taller than the
   window, a fixed footer would have its head cut off with no way to scroll to
   it — so on a short window it stays in normal flow and simply scrolls up like
   any other section. */
main { position: relative; z-index: 1; background: var(--paper); }
.foot-spacer { display: none; }

body.has-reveal-foot .foot {
  position: fixed; left: 0; right: 0; bottom: 0;
  /* Under main's 1. The overlays and the header sit far above both. */
  z-index: 0;
}
body.has-reveal-foot .foot-spacer { display: block; height: var(--foot-h, 0px); }

/* ── The footer's tint ───────────────────────────────────────────────────
   #7C7262 over the whole panel when the footer is still buried, burning off to
   nothing as it is uncovered. The strength is set in the script — see
   TINT_MAX, which started at 0.25 and was not readable against this footer's
   pale ground. The footer is revealed by main sliding
   up off it, so it arrives already lit; the wash gives it somewhere to arrive
   FROM, which a panel that is simply there has not got.

   ONLY UNDER has-reveal-foot. Without the effect the footer is in normal flow
   and nothing reveals it, so there is no reveal for a tint to track — and the
   overlay is positioned against the footer, which is only a containing block
   while it is fixed. Its opacity is driven from the script; the fallback is 0,
   so with no JavaScript, or under prefers-reduced-motion where the motion
   block returns early, the footer is simply untinted — which is its end
   state anyway. */
.foot__tint { display: none; }
body.has-reveal-foot .foot__tint {
  display: block;
  position: absolute;
  inset: 0;
  z-index: 2;
  pointer-events: none;
  background: #7C7262;
  opacity: var(--foot-tint, 0);
}
.foot__portrait {
  width: clamp(150px, 15vw, 224px);
  align-self: stretch; height: 100%;
  object-fit: cover; object-position: 50% 50%;
}
.foot__card p { margin: 0 0 .85em; }
/* Marc's name is the one piece of display type in this column and reads as
   its heading, so it is set well above the lines under it rather than a step
   above them — measured, 36px against the card's 19px at 1440, where it was
   32px.

   2.5vw is not a taste: the name MUST NOT WRAP, and its column is a fraction
   of the grid, so the type has to track the column rather than the page. The
   card measures 230px at 1440, 189px at 1180, 160px at 1000 and 260px at 1600
   — a steady 0.160 of the viewport — and "Marc Sanders" sets at 6.08 times
   its font size, so the widest that stays on one line is vw x 0.160 / 6.08 =
   2.63vw. 2.5 leaves a little for a wider name or a different cut. At 2.9vw
   it broke to two lines at 1440, 1180 and 1000, and a name split across two
   lines is not a name. */
.foot__name { font-size: clamp(26px, 2.5vw, 40px); margin-bottom: .6em !important; }
.foot__card a { text-decoration: none; }
.foot__card a.u { text-decoration: underline; text-underline-offset: 3px; }
.foot__brand { display: flex; flex-direction: column; }
.foot__social { display: flex; gap: 16px; margin-bottom: clamp(24px, 3vw, 40px); }

.foot__social svg { width: 21px; height: 21px; }
.foot__compass {
  font-size: clamp(22px, 2.3vw, 34px); font-weight: 600;
  letter-spacing: .1em; text-transform: uppercase;
  /* `auto` top: everything from the wordmark down goes to the foot of the
     column. It has to live in THIS shorthand — a separate `margin-top: auto`
     rule above was being reset by the `margin:` here, which is why the address
     was sitting 38px shy of the line. */
  margin: auto 0 .7em;
}
/* The supplied logo, sized to about what the type it replaces occupied. */
.foot__compass img { width: clamp(118px, 12vw, 178px); height: auto; }
.foot__card { display: flex; flex-direction: column; }
/* The four footer columns share a bottom line, and this column is shorter
   than the one setting that line — so the slack has to go somewhere. It was
   going above the LAST line, which put the phone number up with the licence
   number and left the email on its own at the foot.

   The two ways to reach Marc belong together, so the auto margin moves one up:
   it now sits above the phone, and phone and email travel down as a pair. */
.foot__card p:nth-last-child(2) { margin-top: auto; }
.foot__card p:last-child { margin-bottom: 0; }
.foot__addr { font-size: 14px; line-height: 1.5; margin: 0; }

/* The sans, not the headline face. This is the California licensing and
   equal-housing disclosure, and it was set in Italiana at 12.7px — a very
   high-contrast display face at the smallest size on the page, which made the
   one paragraph with a compliance purpose the hardest to read. Inter is drawn
   for this size. */
.foot__legal {
  max-width: var(--maxw); margin: clamp(48px, 7vw, 104px) auto 0;
  font-family: var(--sans);
  font-size: clamp(12px, .88vw, 14px); line-height: 1.55;
}
.foot__marks { display: flex; justify-content: center; margin: clamp(30px, 4vw, 52px) 0 20px; }
.foot__marks img { width: 68px; height: auto; }
.foot__copy, .foot__powered { text-align: center; margin: 0 0 .5em; font-size: 14px; }
.foot__powered a { text-underline-offset: 3px; }

/* ── Meet Marc: slide-out ─────────────────────────────────────────────── */
.marc { position: fixed; inset: 0; z-index: 120; }
.marc[hidden] { display: none; }
.marc__scrim { position: absolute; inset: 0; background: rgba(28, 24, 20, .46); opacity: 0; }
.marc__panel {
  position: absolute; top: 0; right: 0; bottom: 0;
  width: min(620px, 100%);
  background: var(--paper);
  color: var(--ink);
  padding: clamp(28px, 4vw, 64px);
  padding-top: max(clamp(28px, 4vw, 64px), env(safe-area-inset-top));
  overflow-y: auto; overscroll-behavior: contain;
  transform: translateX(100%);
  box-shadow: -24px 0 80px rgba(28, 24, 20, .2);
}
.marc__x {
  position: absolute; top: clamp(18px, 2vw, 30px); right: clamp(18px, 2vw, 30px);
  width: 42px; height: 42px; border-radius: 50%;
  display: grid; place-items: center; cursor: pointer;
  /* A filled brown disc with a white ✕ at rest, by request. It was an
     outline that filled on hover; the hover simply inverts instead, which
     keeps a state change without a second idea. The ring goes accent rather
     than staying a hairline — against a brown fill a pale line reads as a
     seam, and it is what keeps the hovered white disc off the white panel. */
  background: var(--accent);
  color: #fff;
  border: 1px solid var(--accent);
  transition: background .25s ease, color .25s ease;
}
.marc__x:hover { background: #fff; color: var(--accent); }
.marc__x svg { width: 18px; height: 18px; }
.marc__portrait { width: 132px; height: 132px; object-fit: cover; border-radius: 50%; margin-bottom: 28px; }
.marc__title { font-size: clamp(30px, 3.4vw, 46px); margin-bottom: clamp(22px, 3vw, 34px); }
.marc__body {
  max-width: 46ch;
  /* Inherits Inter with the rest of the prose; only the measure differs. */
  font-size: 16.5px; line-height: 1.62;
}
.marc__body em { font-style: italic; }
.marc__body p:last-child { margin-bottom: 0; }

/* No fold: the whole biography is shown and the panel scrolls, which is what
   a panel is for. The Read more control and its chevron are gone. */
.marc__facts {
  margin: clamp(30px, 4vw, 48px) 0 0;
  border-top: 1px solid var(--line);
}
.marc__facts > div {
  display: grid; grid-template-columns: 116px 1fr; gap: 18px;
  padding: 14px 0; border-bottom: 1px solid var(--line);
}
.marc__facts dt {
  font-size: 11px; letter-spacing: .14em; text-transform: uppercase;
  color: var(--accent); padding-top: 3px;
}
.marc__facts dd { margin: 0; font-size: 15px; }
.marc__contact { display: flex; flex-wrap: wrap; gap: 10px 26px; margin-top: clamp(26px, 3vw, 38px); }
.marc__contact a {
  display: inline-flex; align-items: center; gap: 7px;
  color: var(--accent); text-underline-offset: 3px;
}
.marc__contact svg { width: 10px; height: 10px; flex-shrink: 0; opacity: .6; }

/* ── Availability: fullscreen, and nothing else ───────────────────────
   The overlay used to carry its own black bar with a title and an ✕. That is
   two headers and two ways out for one screen. Now the sitemap fills the
   viewport edge to edge and the PAGE'S OWN header button — which sits above
   it — becomes "Close ✕". One control, in the place the pointer already
   knows. */
/* ── ONE COLOUR, FROM THE CLICK TO THE MAP ────────────────────────────
   This was #0d0c0b, and the trip from the home page to the sitemap was three
   colours: the overlay faded to near-black, the sitemap's own document painted
   #e8e8e8 over it, and the map area then arrived at the brand taupe. Measured
   on the running site, in that order: dark at 112ms, #e8e8e8 at 873ms, #7c7262
   at 2098ms.

   The taupe is where it is always going — it is the sitemap's own opening
   ground and this site's accent — so it starts there. The other half of this
   is in assets/css/sitemap-embed.css, which takes the #e8e8e8 out of the
   embedded copy; neither works without the other, because whichever one is
   left still shows for the stretch the other covers. */
.avail {
  position: fixed; inset: 0; z-index: 140;
  background: #7c7262;
  display: flex; flex-direction: column;
  opacity: 0;
}
.avail[hidden] { display: none; }
.avail__frame { flex: 1 1 auto; position: relative; }
.avail__frame iframe { position: absolute; inset: 0; width: 100%; height: 100%; border: 0; }
/* Three dots, because the sitemap is a large application and the overlay is
   otherwise a black rectangle for a second or two. */
/* ── ABOVE THE FRAME, which it was not ─────────────────────────────────────
   The hold and the iframe are both `position: absolute` inside
   `.avail__frame`, both at `z-index: auto`, and the iframe is appended AFTER
   the hold — so source order put the frame on top and the hold was painted
   over within a frame of the overlay opening.

   Everything that looked wrong followed from that. The brown somebody waits on
   is the APP'S own boot cover showing through, not this one. The count that
   "blinked" was this hold being covered, not removed. And a measurement of the
   hold's own box reported it present and correctly sized the whole time,
   because it was — it just was not on screen. Sampled at the centre of the
   overlay, `elementFromPoint` returned the IFRAME.

   One line. The hold now covers the frame for its whole life and dissolves
   into it, which is what the fade below was written for. */
.avail__loading {
  position: absolute; inset: 0; z-index: 2;
  display: grid; place-items: center; padding: 24px;
  transition: opacity .42s ease;
}
/* The handover. See `done()` in site.js for why this is a fade and not a
   removal: both compositions are briefly on screen, and a dissolve is what
   makes that one picture becoming another rather than two at once. */
.avail__loading.is-gone { opacity: 0; pointer-events: none; }
/* ── The hold ──────────────────────────────────────────────────────────────
   What fills the wait. Laid out as the app's own opening is — mark, count,
   plan — so the cross-fade when the real thing arrives is a change of fidelity
   rather than a change of subject. */
.avail__hold {
  position: absolute; inset: 0;
  display: grid; place-items: center;
  animation: holdIn .5s cubic-bezier(.22,.61,.36,1) both;
}
@keyframes holdIn { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: none; } }
.avail__dots { position: relative; display: flex; gap: 8px; }
/* Raised from .55 now that the ground is the accent rather than near-black:
   at .55 over #7c7262 the dots sit at #b4ada3, about a third of the
   separation they had on the dark. */
.avail__dots span {
  width: 7px; height: 7px; border-radius: 50%; background: rgba(255,255,255,.82);
  animation: dots 1.1s ease-in-out infinite;
}
.avail__dots span:nth-child(2) { animation-delay: .14s; }
.avail__dots span:nth-child(3) { animation-delay: .28s; }
@keyframes dots { 0%, 100% { opacity: .25; transform: translateY(0); } 50% { opacity: 1; transform: translateY(-5px); } }

/* ── The reel, enlarged ───────────────────────────────────────────────
   Sibling of .avail and deliberately one z-index below it: the two are never
   open at once, and if a bug ever opened both, the sitemap — the thing the
   client is actually selling from — should win. */
/* The header sits at z-index 90, well under this, but a 93%-opaque scrim is
   not an opaque one: the white wordmark and the Availability pill ghosted
   through it, legibly. Raising the scrim would dull the video, so the header
   steps aside instead — which is what it should do over a full-screen player
   anyway. */
body.is-film .nav { opacity: 0; pointer-events: none; }
.nav { transition: background .5s ease, color .5s ease, box-shadow .5s ease, opacity .3s ease; }

.film {
  position: fixed; inset: 0; z-index: 135;
  display: flex; align-items: center; justify-content: center;
  opacity: 0;
}
.film[hidden] { display: none; }
/* The surround is the accent walked down to about 57% of its value, not a
   neutral black: #463F36 against #7C7262. Warm enough to read as brown beside
   the tinted inset it came from, dark enough to leave the footage the
   brightest thing on the screen. */
.film__scrim { position: absolute; inset: 0; background: rgba(70, 63, 54, .955); }
.film__stage {
  position: relative;
  /* The height cap is expressed as a WIDTH, which is the only way to keep the
     box 16:9. `aspect-ratio` with `max-height` does not do what it reads like:
     max-height caps the height and leaves the width alone, so the box silently
     stops being 16:9. Measured at 1440x420 it was 1324.8 x 344.4 — 3.85:1.

     That was survivable while the stage had no frame: the video letterboxes
     itself inside, and the bars passed for more scrim. With a frame on it the
     brown wraps the BOX, so it was drawing a border around two panels of empty
     bar with the picture stranded in the middle. Deriving the width from the
     same 82vh cap keeps the intent — a wide short window still cannot push the
     native controls off the bottom — and keeps the ratio exact, so the frame
     lands on the picture.

     The old cap's other half, 56.25vw, is gone because it could never bind:
     92vw at 16:9 is 51.75vw tall, always under it. */
  width: min(92vw, 1440px, calc(82vh * 16 / 9));
  aspect-ratio: 16 / 9;
  /* The same frame the inset wears, one pixel heavier, and an OUTLINE for the
     same reason: box-sizing is border-box globally, so a 10px border would be
     taken OUT of the 16:9 stage — the content box would stop being 16:9 and
     the footage would letterbox itself inside its own frame. An outline is
     painted outside the box, so the stage stays exactly 16:9 and the frame is
     added to the picture.

     Room for it is there at every size: the stage is capped at 92vw, leaving
     4vw a side — 15px at a 375px phone, the tightest case — and vertically the
     82vh cap leaves 9vh. Both clear 10px. */
  outline: 10px solid var(--accent);
  outline-offset: 0;
}
/* The video's own letterbox bars, for any frame that is not exactly 16:9.
   Black here would draw hard black edges inside a brown room. */
.film__video { width: 100%; height: 100%; display: block; background: #3b352c; }

.film__x {
  position: absolute; top: clamp(14px, 1.8vw, 26px); right: var(--gutter);
  width: 42px; height: 42px;
  display: grid; place-items: center;
  border: 0; padding: 0;
  background: rgba(255, 255, 255, .1);
  color: #fff; cursor: pointer;
  border-radius: 50%;
  transition: background .25s ease;
}
.film__x:hover { background: rgba(255, 255, 255, .22); }

/* Scrolling the page behind a fullscreen overlay is disorienting, and on iOS
   it scrolls the page instead of the frame. */
/* ── A note on the scroll lock and the framed map ───────────────
   overflow:hidden takes the scrollbar away, and on a browser with classic
   (non-overlay) scrollbars that widens the initial containing block by about
   15px for as long as an overlay is open — so .avail, which is fixed to that
   block, grows, and the framed sitemap is resized twice per visit. That is a
   race with whenever the map inside measures itself, and the reported symptom
   matches: the lot map and the basemap both too far right with only part of
   the map drawn, intermittently, after leaving by the menu and coming back.

   scrollbar-gutter:stable was tried here and REMOVED. It does stop the width
   changing, but this page is full-bleed and the reserved gutter left a 15px
   empty stripe down the right of the hero — measured, the hero ended at 1425
   of 1440, and at 375 of 390 on a phone, which has no scrollbar to reserve
   for in the first place.

   So the fix is on the other side: site.js asks the frame to re-measure when
   the overlay opens, which corrects a stale transform whatever caused it and
   costs nothing when there is nothing wrong. */
body.is-locked { overflow: hidden; }

/* ── Reveal: the resting state ────────────────────────────────────────
   Set in CSS rather than by the script so nothing is visible for a frame
   before GSAP runs. site.js clears it on browsers that will not animate. */
[data-reveal], [data-lines] .line > span, .hero__title .line > span { will-change: transform, opacity; }
html.js [data-reveal] { opacity: 0; transform: translateY(26px); }
/* The site plan's two parts enter separately, so they rest separately — the
   button itself is never hidden, only its contents. Its own opacity would
   have taken the focus ring with it. */
[data-plan-reveal] .intro__plan-map,
[data-plan-reveal] .intro__plan-mark { will-change: transform, opacity; }
html.js [data-plan-reveal] .intro__plan-map,
html.js [data-plan-reveal] .intro__plan-mark { opacity: 0; }
html.js [data-reveal-img] > img { transform: scale(1.14); }
html.js [data-reveal-img] { clip-path: inset(0 0 100% 0); }

/* ── Narrower ─────────────────────────────────────────────────────────── */
@media (max-width: 1080px) {
  .intro__head { grid-column: 1 / 11; }
  .intro__side { grid-column: 1 / 11; grid-row: 2; }
  /* The paragraph takes the whole row here, so the plan cannot share it any
     more and needs one of its own — which pushes EVERY row below it down by
     one. That is the only reason .mile appears in this block at all: it holds
     row 3 at full width and has to be moved by hand here, because a grid row
     number is absolute and nothing shifts to make space.
     It still sits between the paragraph and the band, which is the whole
     point of where it was put. CENTRED here rather than left-aligned, and
     that follows the band: 1080 is also where the figures go two-up, so
     "the first two" stop being the left half of the row and become the whole
     of its first line. Over a full-width pair, centred is the alignment. */
  /* A CAP, not a 50%. Half of a full-width column is the right size at 1080
     and a postage stamp at 390 — the drawing is already only 350px wide
     there, and halving THAT is not what halving it at 1440 meant. 320 is
     half the tablet width and most of the phone's, which is the same
     instruction applied to two different amounts of room. */
  .intro__plan {
    grid-column: 1 / 13; grid-row: 3;
    justify-self: center;
    width: min(100%, 320px);
    margin-top: clamp(30px, 4vw, 52px);
    /* FIXED, because the drawing is. Above this width the disc tracks the
       viewport because the drawing does; here the drawing is capped at 320
       from 1080 all the way down to a phone, so a vw-driven disc just sits on
       its own 34px floor and reads small against it — the percentage it
       replaced measured 39.5. These restore that, and --go-lift follows them
       without being restated. */
    --go-size: 40px;
    --go-gap: 11px;
  }
  .mile { grid-row: 4; }
  /* Below this width the two plates stop sharing a row, so they go back to
     filling the column rather than holding the comp's page fractions — and
     because they stop sharing, each needs a row of its own BELOW the
     milestones. Left on the band's own row the wide plate lands on top of it
     and covers it: measured at 390, both claimed rows [215..514] and the
     photograph won. (These were 4 and 5 against a band on row 3; they are 5
     and 6 now that the plan has taken a row above it.) */
  .intro__wide { grid-column: 1 / 13; grid-row: 5; width: auto; }
  .intro__inset {
    grid-column: 6 / 13; grid-row: 6; width: auto;
    margin-top: clamp(16px, 2vw, 30px);
    margin-right: calc(-1 * var(--gutter));
    aspect-ratio: 4 / 3;
  }
  .choreo__copy { grid-column: 1 / 13; }
  .choreo__plate { grid-column: 1 / 13; margin-top: clamp(36px, 5vw, 60px); }
}

/* The four footer columns still fit at 1024 — the portrait is 154px and the
   two text columns about 160px each — so they hold their shared top and
   bottom line down to here rather than collapsing early. Below this it goes
   to a 2x2, with the lead beside the portrait and the two blocks under them,
   and each pair still shares a bottom line. */
@media (max-width: 900px) {
  .foot__grid { grid-template-columns: 1fr auto; }
  .foot__card, .foot__brand { margin-top: clamp(26px, 4vw, 44px); }
}
@media (max-width: 680px) {
  /* Centred stops being possible before this width and starts being a
     collision. The header is wordmark + button on one line; measured at 390px
     the button alone is 216px of a 390px screen, so once the wordmark is
     centred its right edge and the button's left edge meet exactly — a 0px
     gap, with the wordmark no longer centred anyway (the button's track wins
     the space and drags it 89px off centre).

     So below here it goes back to the gutter, which is where the design put
     it on a phone to begin with. The indent goes with it: it corrects a
     CENTRED line, and on a left-aligned one it would just push the D off the
     margin. */
  .wordmark {
    grid-column: 2; text-align: left;
    align-items: flex-start; text-indent: 0;
  }
  .wordmark__l2 { text-indent: 0; }

  /* … but not over the sitemap. On a wide screen the wordmark can sit in the
     middle of the overlay's header with the legend to its left and Close to its
     right; on a phone those three do not fit, and the wordmark is the one that
     gives, because the other two are controls.

     display:none rather than the opacity this uses on a desktop: at this width
     it is not stepping aside for a moment, it is not part of the overlay at
     all, and leaving an invisible link across the top of a map that is drag-to-
     pan would swallow the drag. It takes the tab stop with it, and Home in the
     menu — which the overlay makes inert anyway — is the route back on the
     page itself. The desktop rules that fade and recolour it are untouched, and
     so is the website header here: this is scoped to body.is-overlay. */
  body.is-overlay .wordmark { display: none; }
  /* Agent, wordmark, button, left to right, with the button taking whatever
     is left and ending at the gutter. The two auto tracks need a gap between
     them that the wide layout got for free from the 1fr either side. */
  .nav { grid-template-columns: auto auto 1fr; column-gap: 12px; }
  .nav > .btn { grid-column: 3; }

  /* Top right on a phone. There is only one free corner down here and the two
     of them take turns: at 390 the headline is 245px of ink on a 390px screen
     and the reel is 224px wide, so whichever owns the foot of the hero pushes
     the other out. The headline is at the foot, so the reel goes up.

     Measured with the reel left at the foot alongside it: the headline's ink
     ran to x 265.3 against a reel starting at x 145.8, straight through it.

     bottom:auto is what takes it out of the base rule's offset; --reel-w still
     drives the width. If the headline ever leaves the foot again, this whole
     block is what comes off. */
  .reel {
    --reel-w: min(57.5vw, 260px);
    top: clamp(74px, 12vh, 108px); bottom: auto;
    outline-width: 7px;
    border-radius: 5px;
  }
  /* 62px buttons inside a 146px-tall frame would be most of the picture. */
  .reel__btn { width: 42px; height: 42px; }
  .reel__cap { display: none; }
  .intro__head, .intro__side, .intro__wide, .intro__inset,
  .choreo__copy, .choreo__plate { grid-column: 1 / 13; }
  /* Four across becomes two by two. Four columns on a 390px screen give each
     figure 78px, which is narrower than "15,580–30,089" can set at any size
     worth reading. */
  /* Back to equal halves down here: two content-sized tracks spread across a
     390px screen leave the pair hanging at opposite edges. */
  /* The two-up grid is set at 1080 now; what is left down here is the one
     thing particular to a phone: the range takes the whole row, so the item
     after it starts a row and must not draw a left rule. */
  /* ── Down here the range takes a row to itself ────────────────────
     Half the band is enough for it from 1080 down to about 600, and then it
     is not: measured, it wraps at 500 and below. So it spans both columns —
     and goes LAST with `order`, which is what keeps the row above it full.
     Left in source order it would sit alone in row one's left cell, elevation
     would start row two, and the layout would have a hole in it.

     The dividers follow the VISUAL order and nth-child does not, so both are
     named here rather than left to the 2n rule above: elevation is second in
     its row and draws one, the range opens a row and must not.

     (These were nth-child(3) and (4) when there were four figures. There are
     three, and the range is the second of them.) */
  .mile__item:nth-child(2) {
    order: 3;
    grid-column: 1 / -1;
    padding-left: 0; border-left: 0;
  }
  .mile__item:nth-child(3) {
    padding-left: clamp(16px, 2.4vw, 30px);
    border-left: 1px solid color-mix(in srgb, var(--accent) 18%, transparent);
  }
}

/* ── One column on the smallest screens ──────────────────────────────────
   NOT A ROUND NUMBER PICKED FOR LOOKS. "250-350 FT" is 146px wide set at the
   34px floor and will not go narrower; half the band less the divider's
   padding gives it 159px at 390 and 152 at 375, and then 144 at 360 — where
   it wraps. The crossing is just under 365, and 360 is one of the commonest
   Android widths, so the break is at 400 to clear it rather than to sit on
   it.

   Below here every figure takes the full band: 320px of usable width against
   the 224 the size range wants and the 146 the elevation wants, with the
   rules gone because a single column has nothing to its left to be divided
   from. Source order comes back too — the `order` above exists only to keep
   a two-column row from having a hole in it. */
@media (max-width: 400px) {
  .mile { grid-template-columns: 1fr; row-gap: clamp(24px, 6vw, 32px); }
  .mile__item,
  .mile__item:nth-child(2),
  .mile__item:nth-child(3) {
    order: 0;
    grid-column: 1;
    padding-left: 0; padding-right: 0;
    border-left: 0;
  }
  .intro__wide { aspect-ratio: 4 / 3; width: auto; }
  /* On a phone the portrait crop is the point of the picture, so it comes
     back — just not at the comp's 23% of a 390px screen. */
  .intro__inset { margin-left: calc(-1 * var(--gutter)); aspect-ratio: 4 / 5; width: auto; }
  .quote blockquote { max-width: 18ch; }
  /* Three portrait crops side by side on a phone are three slivers. */
  .triptych { grid-template-columns: 1fr; }
  .triptych figure { aspect-ratio: 4 / 3; }
  .foot__grid { grid-template-columns: 1fr; }
  .foot__lead, .foot__portrait, .foot__card, .foot__brand { grid-column: 1; }
  .foot__portrait { height: auto; align-self: start; }
  .foot__compass { margin: 0 0 .5em; }
  /* Stacked, no column sets a bottom line for the others to meet, so there is
     no slack to place and the pair needs no separating. */
  .foot__card p:nth-last-child(2) { margin-top: 0; }
  .foot__legal { text-align: left; }
  .marc__facts > div { grid-template-columns: 1fr; gap: 4px; }
}

/* ── Motion off ───────────────────────────────────────────────────────
   Everything the script animates also has a resting state that is simply
   "arrived", so honouring the preference is a matter of not starting. */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  html.js [data-reveal] { opacity: 1; transform: none; }
  html.js [data-reveal-img] { clip-path: none; }
  html.js [data-reveal-img] > img { transform: none; }
  .hero__cue { display: none; }
  .avail__dots span, .avail__hold { animation: none; }
  * { transition-duration: .01ms !important; }
}

/* ══ Mobile-only refinements ═══════════════════════════════════════════════
   A second ≤680 block, after the first and after the motion block, so these
   win on equal specificity without any !important beyond the two places that
   genuinely need it. Kept together because they are one pass over the phone
   layout rather than five unrelated rules. */
@media (max-width: 680px) {

  /* ── The Availability pill has to fit next to the other two ────────
     At its full size it does not. Measured: the pill's natural width is 216px,
     and the header's other content — 20px gutter, 38px menu, 12px gap, 96.5px
     wordmark, 12px gap, 20px gutter — takes 198.5px before it. That is 414.5px
     of header on a 390px screen, and the pill is the end that runs off: its
     right edge measured at x 394.5 on a 360px viewport.

     It cannot shrink its way out of that on its own. The script pins the
     pill's width with an inline min-width, so the width it has when the fonts
     land is a floor it can never go under — which is the point on a desktop,
     where it stops the button moving when the label becomes "Close". The way
     through is to make the natural width smaller BEFORE that measurement is
     taken, so the floor is set low: the padding either side, the gap between
     label and arrow, and the type all come down here, and the pin then records
     whatever that adds up to.

     Which makes the resize re-pin in site.js part of this rule rather than a
     nicety. A window that loads wide and is narrowed — a rotation, or a
     desktop window dragged in — crosses this breakpoint with the wide pin
     still on the element, and the pill would overflow again with no way back.

     Leaves 121.5px at 320, the narrowest phone still in use, which is what the
     sizes below are drawn to rather than to 390. */
  .nav > .btn--pill:not(.is-close) {
    --btn-px: .82rem;
    font-size: 12.5px;
  }
  /* "Choose your lot" is three words where "Availability" was one, and the
     header pill is the one place a wrap must not happen: it would double the
     button's height and shove the ✕ it morphs into off its own centre. The
     label holds one line at every width, and the sizes below are what keep
     that line inside the budget rather than spilling it. */
  .nav > .btn--pill:not(.is-close) .btn-hover__text { white-space: nowrap; }
  .nav > .btn--pill:not(.is-close) .btn-hover__content,
  .nav > .btn--pill:not(.is-close) .btn-hover__mask { padding: .92rem var(--btn-px); gap: .58rem; }
  .nav > .btn--pill:not(.is-close) .btn-hover__arrow { width: .58rem; }

  /* ── Close is a square with a cross ────────────────────────────────
     Over the sitemap the word is redundant — there is one control on screen
     and it is plainly the way out — and at this width a 216px pill is more
     than half the screen. So in the is-close state only, it collapses to a
     rounded square holding the ✕.

     min-width has to be !important: the script pins the pill's width with an
     INLINE style so the label swap does not make it jump, and an inline
     declaration is beaten only by an important one. The Availability state
     keeps its label and its pinned width untouched. */
  .btn.is-close {
    min-width: 0 !important;
    width: 46px; height: 46px;
    padding: 0;
    border-radius: 11px;
  }
  .btn.is-close .btn-hover__content,
  .btn.is-close .btn-hover__mask {
    padding: 0; gap: 0;
    justify-content: center; align-items: center;
    height: 100%;
  }
  .btn.is-close .btn-hover__text { display: none; }
  .btn.is-close .btn-hover__arrow { width: 17px; height: 17px; }

  /* The mask's cross has to be centred SEPARATELY, and forgetting it is what
     put the ✕ off centre on a phone. The button is two stacked copies: the
     resting one, which the flex rules above centre, and the mask, which wipes
     over it. On a desktop the mask is only seen during a hover; on a touch
     screen the tap that opens the overlay leaves :active — and Chromium's
     sticky :hover — on the button, so the mask is what the visitor is left
     looking at. Measured, with the mask open:

         resting cross   cx   0.00   centred
         mask cross      cx -16.69   position:absolute, right: 31.2px

     31.2px is --btn-px, where the arrow's slide-in choreography parks it. On a
     216px pill that is the right-hand end of a label; on a 46px square it is
     16.69px left of the middle. Taking it out of absolute positioning hands it
     to the same flex centring as its twin, and every transform has to go with
     the position — the base rule and the touch :active rule both still carry
     a translateY(-50%), which was centring it against top:50% and would now
     lift it half its own height instead. */
  .btn.is-close .btn-hover__arrow--mask,
  .btn.is-close:hover .btn-hover__arrow--mask,
  .btn-hover.is-close:active .btn-hover__arrow--mask {
    position: static;
    transform: none;
  }

  /* ── The reel sits on the headline ─────────────────────────────────
     Off the top-right corner and down onto the type, just clear of it.

     The offset is derived, not guessed. The headline is TWO lines — they are
     two explicit .line spans and neither wraps at this width — at line-height
     1.2, so its height is exactly 2.4 of its own font size. Add the bottom it
     is held off the foot by, and 20px of air:

         52..120px (the headline's own bottom) + 2.4 x its font size + 20px

     At 390 that is 62.4 + 76.8 + 20 = 159.2px, putting the reel's foot 20px
     above the cap line. top:auto releases the corner the first block pinned
     it to. */
  .reel {
    top: auto;
    bottom: calc(clamp(52px, 8vh, 120px) + 2.4 * clamp(32px, 5.7vw, 100px) + 20px);
  }

  /* ── Every picture goes edge to edge ───────────────────────────────
     A photograph inset inside a 20px gutter on a 390px screen is a postcard
     of itself. Full-bleed via the viewport rather than by cancelling a
     gutter, so it does not matter which ancestor carries the padding: each
     block is centred in the page, so half its own width back from its centre
     lands exactly on the left edge. */
  .intro__wide,
  .intro__inset,
  .triptych,
  .choreo__plate {
    width: 100vw;
    max-width: 100vw;
    margin-left: calc(50% - 50vw);
    margin-right: calc(50% - 50vw);
  }
  /* The inset's own gutter cancel from the first block would now double up. */
  .intro__inset { margin-left: calc(50% - 50vw); }

  /* ── The footer centres ────────────────────────────────────────────
     One column already; this puts everything on its axis rather than leaving
     a left-aligned stack in the middle of the screen. */
  .foot { text-align: center; }
  .foot__grid { justify-items: center; }
  .foot__lead { display: flex; flex-direction: column; align-items: center; }
  .foot__lead h2 { max-width: none; }
  .foot__lead .btn { align-self: center; }
  .foot__legal { text-align: center; }
  .foot__social { justify-content: center; }
  .foot__portrait { margin-inline: auto; }
  /* The portrait and the name belong together — it is his headshot above his
     name, not two items in a list.

     A negative margin-top on a GRID ITEM counts twice: it offsets the item
     within its area AND shortens its margin-box, which shortens the auto row
     it sits in. Swept, the gap measures 34 - 2m, so -5 takes 34px down to 24.
     -24 was the first guess and left 4px, all but touching the crop. */
  .foot__card { margin-top: -5px; }
  /* It arrives here from the lead column (see placeWhere in site.js), so it
     takes its spacing from its new neighbours rather than from the button it
     used to hang under — BOTH sides. The margin it carries on a wide screen
     is a top margin only, which left its last line sitting exactly on the
     Compass mark: measured, the address ended at 5811 and the mark began at
     5811. */
  .foot__brand .foot__where {
    margin-top: clamp(18px, 3vw, 26px);
    margin-bottom: clamp(22px, 3.6vw, 32px);
  }
  .foot__compass img, .foot__marks img { margin-inline: auto; }
}

/* ══ The narrowest phones ═════════════════════════════════════════════
   One more step down, for the 320px class of screen. The ≤680 sizes above fit
   360 with room to spare and 390 comfortably; at 320 the header runs out of
   width again — measured with those sizes in force, the pill's right edge
   landed at x 328.5 on a 320px viewport.

   The arithmetic is unforgiving down here, so all three of the things between
   the pill and the left edge give a little: the header's own side padding, the
   gap between its tracks, and the pill itself. Only the header's padding moves
   — NOT --gutter, which sets the page's margins everywhere and would drag the
   whole column layout in with it for the sake of one row.

   Budget at 320: 320 - 28 padding - 38 menu - 8 - 96.5 wordmark - 8 = 141.5px
   for the pill. */
@media (max-width: 360px) {
  .nav { padding-left: 14px; padding-right: 14px; column-gap: 8px; }
  .nav > .btn--pill:not(.is-close) {
    --btn-px: .62rem;
    font-size: 11.5px;
  }
  .nav > .btn--pill:not(.is-close) .btn-hover__content,
  .nav > .btn--pill:not(.is-close) .btn-hover__mask { padding: .82rem var(--btn-px); gap: .42rem; }
  .nav > .btn--pill:not(.is-close) .btn-hover__arrow { width: .5rem; }
}

/* ── …and one step further, for the 15-character label ─────────────────────
   Everything above was drawn around "Availability". "Choose your lot" is
   three characters longer and carries two word-spaces, and at 320 it no
   longer fits the 141px the header leaves: measured at the ≤360 sizes its
   single-line width is 202.8px, so it wrapped to two lines.

   The tracking is what gives, not the type size. At .34em a 15-character
   label spends about 59px on letter-spacing alone — more than a third of the
   budget on air, and cutting it back buys more room than shrinking the type
   ever could. .1em returns about 38px of that; the rest comes off the type
   and the padding.

   Two passes were needed. .12em/11px measured 151px against the 141px
   budget, and .1em/10.5px measured 149 — the label itself is 115.9px at that
   size, so trimming tracking alone was never going to close a 10px gap
   without taking the type somewhere unreadable.

   So the ARROW goes instead, at this width only. It costs 8px of glyph and
   6.7px of gap, which is the whole shortfall, and it is the one part of the
   button carrying no information: it decorates a control whose label already
   says what it does, and its hover choreography cannot run on a touch screen
   anyway. Measured with it gone: 134px inside a 141px budget, one line, the
   header's 14px gutter intact.

   Held to ≤340 so 360 and 390 — which were measured and screenshotted as
   correct — keep both their tracking and their arrow. */
@media (max-width: 340px) {
  /* The same label in the architecture section's solid button. That one is in
     the flow rather than the header, so it has room to wrap and did: measured
     at 320 the line needs 175px and the button offered 159.6 after its 42.4px
     side padding, its 25.6px gap and the arrow. Trimming the padding is
     enough — the button is 280px wide here and was spending 85 of it on air.
     No nowrap: if a future label really is too long for a 320px screen, two
     lines in a full-width button is a reasonable answer, and only the header
     pill has a morph that two lines would break. */
  .btn--solid .btn-hover__content,
  .btn--solid .btn-hover__mask { padding-left: 1.9rem; padding-right: 1.9rem; column-gap: 1.1rem; }

  .nav > .btn--pill:not(.is-close) {
    --btn-px: .55rem;
    font-size: 10.5px;
    letter-spacing: .1em;
  }
  .nav > .btn--pill:not(.is-close) .btn-hover__content,
  .nav > .btn--pill:not(.is-close) .btn-hover__mask { padding: .8rem var(--btn-px); gap: 0; }
  .nav > .btn--pill:not(.is-close) .btn-hover__arrow { display: none; }
}


/* ══════════════════════════════════════════════════════════════════════════
   LOCATION SECTION + NEIGHBOURHOOD MAP                        (added 2026-10-01)
   ══════════════════════════════════════════════════════════════════════════
   From the handoff bundle, with four changes to fit this site:

   1. THE MAP'S RULES WERE NOT IN THE FILE WE WERE GIVEN. `location-section.css`
      styles `#location` and nothing else; all 43 `#nbhd-map-app` rules lived
      only in `_preview-reference.html`, which the handoff lists as a reference
      rather than as something to install. Following the steps literally would
      have shipped an unstyled map. They are reunited below.

   2. 'Jost' and 'Cormorant Garamond' are gone. Neither is on this site and
      neither came with the bundle — the preview pulled both from Google Fonts,
      which is the one thing this site's type is set up to avoid (see the
      README: Italiana is vendored as a woff2 rather than linked). They now use
      `--sans` and `--serif`, which are self-hosted and already loaded.

   3. The section redefined `--ink`, `--line` and `--muted` — three of this
      site's own tokens — inside `#location`. Scoped, so nothing leaks out, but
      everything INSIDE inherited a different palette from the rest of the
      page. Renamed `--loc-*`, so the shadowing is gone and the intent is
      visible. The values WERE the bundle's; `--loc-ink` was #1A1A1A, markedly
      darker than this site's `--ink: #7a7168`, which is a design decision to
      confirm rather than a bug to fix.

   4. `--gold` is the same `#7C7262` as this site's `--accent`. Kept under a
      local name rather than pointed at the token, so the map's accent can be
      moved without touching the rest of the page.

   `leaflet.css` loads before this, from its own <link> — vendored third-party
   CSS stays its own file so it can be replaced wholesale on an upgrade.
   ══════════════════════════════════════════════════════════════════════════ */

  /* ---- the section itself ---- */
  #location {
    --loc-gold: var(--accent);
    /* Was the bundle's #1A1A1A. The page's own headings and body copy are
       #7C7262 / #7a7168, and a near-black section read as a panel from a
       different site. Pointed AT the token rather than retyped: if the brand
       colour moves, this moves with it. */
    --loc-ink: var(--accent);
    --loc-muted: #8A827A;
    --loc-line: #E4E0D9;
    background: #fff;
    color: var(--loc-ink);
    font-family: var(--sans);
    /* No font-weight. The bundle set the whole section at 300, which made
       every line in it lighter than the same sentence anywhere else on the
       page — body copy here is set against `.intro__side p`, which inherits
       the document's 400. The headline is unaffected: `.display` states its
       own weight. */
    padding: 110px 0 96px;
  }
  /* THE PAGE'S OWN SHELL, not the bundle's. Every other section runs
     max-width:var(--maxw) inside var(--gutter); the bundle shipped 1240/32,
     which at 1440 is 1176px of content against the page's 1351 and at 1920 is
     1176 against 1591. The map and the photos were visibly inset from the
     rest of the page for no reason a reader could see. */
  #location .loc-wrap { max-width: var(--maxw); margin: 0 auto; padding: 0 var(--gutter); }

  #location .dp-eyebrow {
    font-size: 13px;
    letter-spacing: 0.22em;
    text-transform: uppercase;
    color: var(--loc-gold);
    text-align: center;
    margin: 0 0 18px;
  }
  /* Carries `.display` in the markup, which is where the face, the weight,
     the stroke, the colour and the tracking come from — the same declarations
     every other section headline reads. ONLY the two things particular to
     this one are stated here, and the font-size is quoted from .intro__head
     and .ensemble__head, which is the page's section-headline size. Set at
     54px flat, it was the one headline on the page that did not grow with the
     viewport and read a third smaller than its neighbours. */
  #location .loc-title {
    font-size: clamp(32px, 5.7vw, 100px);
    text-align: center;
    margin: 0 0 clamp(34px, 3.6vw, 60px);
  }

  /* ---- photos AND the paragraph, in one grid ---- */
  /* The tall plate holds column one down both rows; the two landscape plates
     take row one; the paragraph takes row two beside the tall one, which is
     the space they were leaving empty. The row sizes are not stated — the
     plates' own aspect ratios set row one and the tall plate's height sets
     the pair, so the text cell is whatever is left, and the copy is centred
     in it rather than hung from its top edge.

     NO BREAKPOINT ON THE PLACEMENT above the phone layout. The text does run
     past the bottom of the tall plate on a narrow laptop — measured -6px at
     1100, -49 at 1000 and -94 at 901, against +12 at 1440 and +17 at 1920 —
     and the first attempt gated the whole arrangement at 1280 because of it.
     That was reverted after looking at the two side by side: a text column a
     little taller than the picture beside it is ordinary, while the fallback
     put the paragraph back underneath everything and left the hole open
     again, which is the thing that was wrong to begin with. */
  #location .loc-grid {
    display: grid;
    grid-template-columns: 1.4fr 1fr 1fr;
    column-gap: 16px;
    row-gap: clamp(24px, 2.4vw, 40px);
    margin-bottom: 58px;
  }
  #location .loc-photo {
    position: relative;
    aspect-ratio: 4 / 3;
    background: #EFECE6;
    overflow: hidden;
  }
  #location .loc-photo--tall {
    grid-column: 1;
    grid-row: 1 / 3;
    aspect-ratio: 3 / 3.2;
    /* A grid item stretches to its row span by default, which would override
       the ratio and re-crop the picture the moment the text grew taller than
       the space beside it. Pinned to the top instead: the plate keeps the
       shape it was cut to, and any slack falls below it. Measured: the ratio
       holds at 0.938 at every width from 901 to 2200. */
    align-self: start;
  }
  #location .loc-photo img { width: 100%; height: 100%; object-fit: cover; display: block; }
  /* Placeholder styling: delete .loc-photo-ph and drop an <img> in instead */
  #location .loc-photo-ph {
    display: flex;
    align-items: center;
    justify-content: center;
    text-align: center;
    padding: 20px;
    font-size: 12px;
    letter-spacing: 0.16em;
    text-transform: uppercase;
    color: #A8A29A;
    border: 1px dashed #D6D1C8;
  }

  /* ---- copy ---- */
  /* SIZE AND LEADING COME FROM `body`, which is clamp(16px, 1.32vw, 21px) on
     1.55. The bundle pinned 19px/1.72: at 1440 that is a rounding away from
     the page's 19.008 and looks identical, and at 1920 the page is at 21px
     while this stayed at 19 — the one paragraph on the site that did not grow
     with the window, set on leading a sixth looser than its neighbours.
     Stating neither is the only way the two cannot drift.

     LEFT, not centred. It sits in a cell the photographs cut for it, with a
     plate squared off above it and another down its left side, so its left
     edge lands on a column the eye is already following; centred, it floated
     in the hole instead of filling it. The section headline stays centred
     over the whole grid — it spans everything and belongs to all of it,
     which is a different job from a paragraph inside one cell. */
  #location .loc-copy {
    grid-column: 2 / 4;
    grid-row: 2;
    /* LAST LINE ON THE PLATE'S BOTTOM EDGE. Centred in the slack it floated a
       few pixels proud of it, and the one hard horizontal in the composition
       — the bottom of the tall photograph — had nothing meeting it. `end`
       puts the baseline of the final line on that line instead.

       Measured: the last line lands exactly on that edge at 1280 and above
       (delta 0 at 1280, 1440, 1600, 1920 and 2200). Below it the paragraph
       is taller than the slack, so it first grows UPWARD — the gap under the
       landscape plates closes from 82px at 1440 to the row-gap's own 26px at
       1100 — and only once that is used up does the grid itself get taller
       than the plate, by 6px at 1100, 49 at 1000 and 94 at 901. `end` cannot
       prevent that: a grid row sizes to its content, so there is no slack
       left to align within. It is the same overhang the centred version had,
       and it reads the same way: an ordinary column a little longer than the
       picture beside it. */
    align-self: end;
    text-align: left;
    color: var(--loc-ink);
  }
  #location .loc-copy p { margin: 0; }

  @media (max-width: 900px) {
    #location { padding: 72px 0 64px; }
    /* `.loc-wrap` is no longer overridden here: it reads var(--gutter), whose
       own clamp floor is 20px, which is what this rule set by hand.
       `.loc-title` is no longer overridden either — a flat 38px here beat the
       clamp and left this the only headline on the page a size apart from its
       neighbours at phone width (38 against the page's 32). */
    /* The 17px that was here is gone with it — body's clamp floor is 16px,
       which is what the rest of the page reads at this width. */
    /* One column, so the cell is the measure — except at the top of this
       band, where the container is 844px and a line would run to about
       ninety characters. */
    #location .loc-copy {
      grid-column: 1 / -1; grid-row: auto;
      max-width: 68ch; margin-bottom: 8px;
    }
    #location .loc-grid {
      grid-template-columns: 1fr 1fr;
      column-gap: 10px;
      row-gap: 18px;
    }
    /* The first slot spans both columns on a phone, but NOT at 16/10 any more.
       That ratio was chosen for a landscape placeholder; the photo now in the
       slot is portrait, cropped to the slot's own 3/3.2, and object-fit:cover
       into 16/10 threw away two thirds of its height — measured at 350x219,
       which cut the subject in half. Square trims about 6% off a 3/3.2 crop
       and is the widest this can go without losing the picture. */
    #location .loc-photo--tall { grid-column: 1 / -1; grid-row: auto; aspect-ratio: 1 / 1; }
  }

/* ── The map, from the preview's own stylesheet ──────────────────────────── */
/* ============================================================
     EDIT ME — brand tokens. Swap these per client.
     ============================================================ */
  #nbhd-map-app {
    /* Black and white palette, for a black Squarespace section. Greys carry
       the states (inactive tab, hover, dividers) since there's no colour to
       work with. Set --accent to a brand colour later if one is introduced. */
    /* Gold on white. --accent is the only colour in the map: active tab,
       community marker, and the selected place in the list. */
    --accent: #7C7262;
    --accent-ink: #FFFFFF;   /* text on the accent */
    --tab-bg: #F4F2EE;       /* inactive tab */
    --tab-hover: #EAE6DF;
    --cream: #FFFFFF;        /* section background */
    --loc-ink: var(--accent);    /* body text — see the note in #location */
    --muted: #8A8580;
    --divider: #E4E0D9;
    --highlight-bg: #FBF7F0;
    /* Inter, like the rest of the site. The bundle shipped Georgia for the
       display face and the system-UI stack for the body, so the map was the
       only thing on the page set in neither of the site's two faces — on a
       Mac it rendered in San Francisco, next to Inter, which reads as a
       widget someone embedded. Both point at --sans: the map has no display
       face of its own, because the page's serif is for section headlines and
       the panel heading is not one. */
    --font-display: var(--sans);
    --font-body: var(--sans);

    all: initial;
    display: block;
    width: 100%;
    margin: 0 auto;
    font-family: var(--font-body);
    color: var(--loc-ink);
    background: var(--cream);
    box-sizing: border-box;
  }
  #nbhd-map-app *, #nbhd-map-app *::before, #nbhd-map-app *::after {
    box-sizing: border-box;
    font-family: inherit;
  }

  /* The fixed header sits over anything scrolled to by script, and the list
     under this map now scrolls back up to it. Measured: the bar is 112px at
     1440 and 77 at phone width; the page's other in-page targets (#intro,
     #architecture, .foot#contact) all use a flat 96px, so this uses the same
     number rather than introducing a second clearance. revealMap() reads the
     value off this element, so moving it here moves both the test for "is it
     already on screen" and the landing position, together. */
  #nbhd-leaflet-map { scroll-margin-top: 96px; }

  #nbhd-map-app .nbhd-map {
    /* Was a flat 550px. The shell above is now the page's, so the pane is
       175px wider at 1440 and 415px wider at 1920 — at a fixed height it
       letterboxed as the window grew. The clamp lands within 3px of the old
       550 at 1440, so the laptop view that was signed off does not move. */
    height: clamp(420px, 38vw, 680px);
    width: 100%;
    background: #F4F2EE;
  }

  /* ---- tabs ---- */
  #nbhd-map-app .nbhd-tabs {
    display: flex;
    flex-wrap: wrap;
  }
  #nbhd-map-app .nbhd-tab {
    flex: 1 1 auto;
    min-width: 110px;
    background: var(--tab-bg);
    color: var(--loc-ink);
    text-align: center;
    padding: 16px 10px;
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 9px;
    font-size: 15px;
    letter-spacing: 0.01em;
    border: none;
    transition: background 0.18s ease;
    user-select: none;
  }
  #nbhd-map-app .nbhd-tabs { border-top: 1px solid var(--divider); }
  #nbhd-map-app .nbhd-tab + .nbhd-tab { border-left: 1px solid var(--divider); }
  #nbhd-map-app .nbhd-tab:hover:not(.is-active) { background: var(--tab-hover); }
  #nbhd-map-app .nbhd-tab.is-active { background: var(--accent); color: var(--accent-ink); }
  #nbhd-map-app .nbhd-tab:focus-visible { outline: 2px solid var(--accent); outline-offset: -4px; }
  #nbhd-map-app .nbhd-tab svg { width: 19px; height: 19px; flex: none; }

  /* ---- list panel ---- */
  #nbhd-map-app .nbhd-list-panel {
    padding: 44px 24px 56px;
    text-align: center;
  }
  /* THE PAGE'S DISPLAY FACE, not the body one. It was bold Inter at 34px,
     deliberately matched to the tabs and the list under it — but it is the
     only heading on the page that was, and beside Italiana everywhere else
     it read as a widget's label rather than as part of the document.

     The size is `.marc__title`'s clamp, quoted rather than invented: that is
     the page's own second-rank heading size, which is what this is. It also
     ANSWERS the enlargement — Italiana sets optically smaller than bold Inter
     at the same measure, so 34px flat would have come out looking smaller
     than what it replaced. 46px at 1440, 30 at phone width against the 26 it
     had there. Weight 400 because Italiana ships one weight and anything
     heavier is the browser smearing it. */
  #nbhd-map-app .nbhd-list-heading {
    font-family: var(--serif);
    font-weight: 400;
    font-size: clamp(30px, 3.4vw, 46px);
    -webkit-text-stroke: var(--serif-stroke) currentColor;
    letter-spacing: -0.012em;
    margin: 0 0 34px;
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 0.3em;
    color: var(--loc-ink);
  }
  /* In em, so the glyph tracks the heading instead of staying at a fixed 30px
     while the type beside it grows from 30 to 46. */
  #nbhd-map-app .nbhd-list-heading svg { width: 0.62em; height: 0.62em; color: var(--accent); }

  /* NO WIDTH CAP. It was 920px, which inside a panel now 1351px wide left
     three 275px columns — and "Melvyn's at the Ingleside Estate" does not fit
     in 275px, so it took two lines and dragged its whole grid row to 86px
     tall. The panel's own padding is the bound; at 1440 the columns come out
     at 402px and at 1920 at 489px, and the longest name on any of the eight
     tabs sits on one line in both. */
  #nbhd-map-app .nbhd-list-grid {
    display: grid;
    /* THE COLUMN COUNT IS NOT FIXED AT THREE. Dropping the old 920px cap gave
       three 402px columns at 1440, which fixed the Restaurants tab — but Arts
       & Culture holds "Children's Discovery Museum of the Desert", and 40
       characters of 19px Inter needs about 440. One name over the line takes
       its whole grid row to two lines with it.

       So the track is a floor, not a count: 440px is the measured width of
       the longest name on any of the eight tabs, and auto-fit lays down as
       many as the panel has room for — three at 1920 (473px each), two at
       1440 (627px). `min(440px, 100%)` because on a phone the floor is wider
       than the panel, and a bare minmax(440px, …) would push the list off the
       side of the screen. */
    grid-template-columns: repeat(auto-fit, minmax(min(440px, 100%), 1fr));
    column-gap: 48px;
    margin: 0 auto;
  }
  #nbhd-map-app .nbhd-list-item {
    padding: 16px 6px;
    border-bottom: 1px solid var(--divider);
    cursor: pointer;
    font-size: 16px;
    line-height: 1.3;
    transition: color 0.15s ease, background 0.15s ease;
    border-radius: 3px;
  }
  #nbhd-map-app .nbhd-list-item:hover { color: var(--accent); background: var(--highlight-bg); }
  #nbhd-map-app .nbhd-list-item:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
  #nbhd-map-app .nbhd-list-item.is-highlighted {
    background: var(--highlight-bg);
    color: var(--accent);
    font-weight: 600;
    box-shadow: inset 2px 0 0 var(--accent);
  }
  #nbhd-map-app .nbhd-empty {
    grid-column: 1 / -1;
    color: var(--loc-muted);
    font-style: italic;
    padding: 8px 0;
  }

  /* Light greyscale map: colour removed, sheet left pale. Applied to the
     tiles alone, so markers, controls and popups keep their own colours.
     Three looks, swap the filter line:
       light (current): grayscale(1) brightness(1.08) contrast(0.86)
       mid grey:        grayscale(1) brightness(0.86) contrast(0.92)
       black:           grayscale(1) invert(1) brightness(0.88) contrast(1.15)
     The lighter the sheet, the more the map reads as a panel sitting on the
     black section rather than dissolving into it. */
  #nbhd-map-app .leaflet-tile {
    filter: grayscale(1) brightness(1.04) contrast(0.9);
  }

  /* Leaflet's controls, popups and attribution ship light; recolour them so
     nothing white-boxes against the black section. */
  #nbhd-map-app .leaflet-control-attribution {
    background: rgba(255,255,255,0.75);
    color: #555;
  }
  #nbhd-map-app .leaflet-control-attribution a { color: #555; }
  #nbhd-map-app .leaflet-bar a {
    background: #fff;
    color: var(--loc-ink);
    border-bottom-color: var(--divider);
  }
  #nbhd-map-app .leaflet-bar a:hover { background: var(--highlight-bg); }

  /* ── THE ZOOM CONTROL — bottom left, side by side, finger-sized ──────────
     Leaflet ships it stacked in the top-left at 26px a side, 30 with the
     touch class, which is a 2010 desktop control: under the 44px every
     platform's own guidance asks for, and sitting over the top-left of a map
     whose subject is in the middle. The CORNER is moved in the script, since
     that is Leaflet's own layout; the SHAPE is here.

     `flex-direction: row` is what makes it left/right. Everything else in
     this block is a consequence of that — Leaflet's bar draws its divider as
     a border-BOTTOM and rounds its first and last child's TOP and BOTTOM
     corners, which are all the wrong axis once the buttons sit side by side.
     The radii are reset to 0 and the pill is cut by the container's own
     overflow instead, so there is one number to change rather than eight. */
  #nbhd-map-app .leaflet-control-zoom {
    --zbtn: clamp(46px, 3.4vw, 58px);
    display: flex;
    flex-direction: row;
    margin: 0 0 18px 18px;
    border-radius: 999px;
    overflow: hidden;
    box-shadow: 0 2px 12px rgba(0, 0, 0, 0.18);
  }
  #nbhd-map-app .leaflet-control-zoom a {
    width: var(--zbtn);
    height: var(--zbtn);
    line-height: var(--zbtn);
    /* The glyphs are Leaflet's "+" and "−" as TEXT, and its own rule sets
       them in bold Lucida Console. The face is already Inter from the reset
       at the top of this block; the weight and size are set here so they are
       not left reading as a monospace control in a sans-serif map. */
    font-size: calc(var(--zbtn) * 0.46);
    font-weight: 400;
    border-bottom: 0;
    border-radius: 0;
  }
  #nbhd-map-app .leaflet-control-zoom a + a {
    border-left: 1px solid var(--divider);
  }
  #nbhd-map-app .leaflet-control-zoom a.leaflet-disabled {
    background: #fff;
    color: var(--loc-line);
  }
  #nbhd-map-app .leaflet-popup-content-wrapper,
  #nbhd-map-app .leaflet-popup-tip {
    background: #fff;
    color: var(--loc-ink);
  }
  #nbhd-map-app .leaflet-popup-close-button { color: var(--loc-muted) !important; }

  /* ---- leaflet marker badges ---- */
  #nbhd-map-app .nbhd-pin {
    display: flex;
    align-items: center;
    justify-content: center;
    border-radius: 50%;
    box-shadow: 0 1px 4px rgba(0,0,0,0.35);
  }
  /* NO RING. Every disc carried a 1.5px white border and the community one a
     solid white version of it. The shadows stay — they are what lifts a disc
     off a pale map sheet — but the rings are gone, so the pins read as marks
     on the sheet rather than as badges stuck over it. */
  #nbhd-map-app .nbhd-pin.nbhd-pin--subject { box-shadow: 0 2px 8px rgba(0,0,0,0.3); }
  #nbhd-map-app .nbhd-pin svg { width: 16px; height: 16px; }
  /* The monogram fills more of its circle than the category glyphs do */
  #nbhd-map-app .nbhd-pin.nbhd-pin--subject svg { width: 34px; height: 34px; }

  /* Desktop sizing for the tab bar and the place list. Kept in its own
     min-width block so the mobile sizes below stay exactly as they were. */
  @media (min-width: 761px) {
    #nbhd-map-app .nbhd-tab {
      font-size: 19px;
      padding: 18px 12px;
      gap: 11px;
    }
    #nbhd-map-app .nbhd-tab svg { width: 24px; height: 24px; }
    #nbhd-map-app .nbhd-list-item {
      font-size: 19px;
      padding: 18px 8px;
    }
  }

  @media (max-width: 760px) {
    #nbhd-map-app .nbhd-list-grid { grid-template-columns: 1fr; }
    /* No font-size here any more — the clamp's floor is 30px, which is this
       heading's phone size now. */
    #nbhd-map-app .nbhd-tab { font-size: 13px; padding: 13px 6px; }
    #nbhd-map-app .nbhd-map { height: 320px; }
    /* TWO BOTTOM PADDINGS WERE STACKING. This panel ends with 56px of its
       own, and #location then adds the page's section rhythm — 64px at this
       width — so the last name in the list sat 120px above the footer where
       the page's own sections leave 58. The section's padding is the one the
       page controls, so the panel's is the one that goes. */
    #nbhd-map-app .nbhd-list-panel { padding-bottom: 0; }
  }

  @media (prefers-reduced-motion: reduce) {
    #nbhd-map-app * { transition: none !important; }
  }
