/* ═══════════════════════════════════════════════════════════════════════
   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); }

.menu__panel {
  position: absolute; top: calc(100% + 12px); left: 0;
  min-width: 208px;
  padding: 10px 0;
  background: var(--paper);
  border-radius: 8px;
  box-shadow: 0 18px 44px rgba(20, 16, 12, .22), 0 0 0 1px var(--line);
  /* Clears its sibling button inside the header. The overlays sit at 130+ and
     are a different argument entirely. */
  z-index: 2;
}
.menu__panel[hidden] { display: none; }
.menu__panel ul { list-style: none; margin: 0; padding: 0; }
/* The anchors and the one button read as a single list. Aerial View is a real
   <button> because it opens a player rather than going anywhere — as an href
   it would be a lie, and it would offer a middle-click that leads nowhere. */
.menu__panel a,
.menu__panel button {
  display: block; width: 100%;
  padding: 10px 20px;
  border: 0; background: none;
  font-family: var(--sans-display);
  font-size: 13px; font-weight: 500;
  letter-spacing: .12em; text-transform: uppercase;
  color: var(--accent); text-decoration: none; text-align: left;
  cursor: pointer;
  transition: background .2s ease;
}
.menu__panel a:hover,
.menu__panel button:hover { background: var(--paper-soft); }

/* 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); }

/* 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;
}
/* .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 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;
  /* Content-sized tracks, spread. NOT four equal columns and no longer four
     weighted ones either: "15,580–30,089" is more than four times the ink of
     "110", so an equal track overflowed it — SQ FT went to a second line and
     dropped that one label below the other three — and weighting the tracks
     by hand only moved the guess somewhere else, capping the type at whatever
     the guess happened to allow.

     auto tracks take exactly the width of the value in them, and
     space-between gives the slack to the gaps. So the type can be as large as
     the row can actually hold, the first figure stays on the left margin and
     the last on the right, and nothing has to be re-guessed when a sale
     changes 40 to 39 or moves the size range. */
  grid-template-columns: repeat(4, auto);
  /* space-EVENLY, not space-between. Between put the outer two hard on the
     margins, and because the four values are such different widths the row
     read as weighted to the right — the long range and the elevation filling
     everything from the middle to the right edge while 110 and 40 sat in air.
     Evenly gives the same gap on the outside as between, so the four read as a
     group inset under the rule rather than as a line pushed to the right edge.
     Measured at 1440: 58px outside, 93px between, against 0 and 132. */
  justify-content: space-evenly;
  column-gap: clamp(20px, 2.4vw, 44px);
  row-gap: clamp(28px, 3vw, 44px);
}
/* align-self:start matters here. The item is column-reverse — label written
   first, figure rendered above it — and a grid item stretched to the row's
   height packs that content from the BOTTOM, so an item whose label wraps to
   two lines lifts its figure by a line. Measured at 760 the labels all fit and
   the four figures shared a top; at 700 the tracks narrow enough that
   "HOMESITES IN ALL" and "STILL AVAILABLE" wrap, and those two figures sat
   18px above the other two. Sized to its own content instead, every figure
   starts at the top of the row and the labels hang to whatever depth they
   need — which is the right way round, because the figures are what the eye
   reads across. */
.mile__item {
  display: flex; flex-direction: column-reverse; gap: .5em;
  align-self: start;
}
.mile__fig {
  margin: 0;
  font-family: var(--serif);
  font-size: clamp(34px, 4.6vw, 74px);
  line-height: 1;
  color: var(--accent);
  /* The unit sits on the figure's own line, smaller and lighter, so a range
     and its unit read as one value rather than two lines of type. nowrap
     because a figure broken across two lines is not a figure any more, and
     because one item wrapping is what misaligns the whole row. */
  display: flex; align-items: baseline; gap: .38em;
  flex-wrap: nowrap; white-space: nowrap;
}
.mile__fig small {
  font-family: var(--sans-display);
  font-size: clamp(11px, .85vw, 13px); font-weight: 500;
  letter-spacing: .14em; text-transform: uppercase;
  opacity: .72;
}
/* ── Why the labels are taken out of the width ───────────────────
   space-evenly equalises the gaps between the ITEMS, and an item is as wide as
   its widest child — which for the first two is the LABEL, not the figure.
   "HOMESITES IN ALL" is wider than "110" and "STILL AVAILABLE" than "40", so
   those two boxes carried empty space on their right that the eye reads as
   part of the gap. Measured at 1440, with boxes spaced a uniform 93px apart,
   the gaps between the actual glyphs came out 139, 147 and 92 — the last one
   two thirds of the others, which is the row looking pushed to the right.

   So the label stops contributing width: zero-width, nowrap, and allowed to
   overflow to the right, while align-items:flex-start lets the figure shrink
   to its own glyphs. The item is then exactly as wide as its value, and
   space-evenly has real ink to space. The labels still sit under their
   figures and still start on the same left edge; they simply run into the gap
   rather than reserving it.

   1100px and up, and the number is measured rather than chosen. An
   overflowing label runs into the one beside it once the row is narrow
   enough: swept in 20px steps, the smallest gap between two labels is 28px at
   1200, 13px at 1100, 1px at 1020 and negative from 1000 down. 1100 keeps a
   little air; below it the labels reserve their own width again, which
   re-introduces a little of the unevenness but never a collision — and at
   those sizes the type is small enough that the difference is slight.

   On a phone it must not apply at all: the band is two 1fr columns there and
   a nowrap "AVAILABLE HOMESITE SIZES" would run straight through the column
   beside it. */
@media (min-width: 1100px) {
  .mile__item { align-items: flex-start; }
  .mile__label { width: 0; white-space: nowrap; overflow: visible; }
}

.mile__label {
  font-family: var(--sans-display);
  font-size: 12px; font-weight: 500;
  letter-spacing: .14em; text-transform: uppercase;
  color: color-mix(in srgb, var(--ink) 62%, 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); }
.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. */
.avail {
  position: fixed; inset: 0; z-index: 140;
  background: #0d0c0b;
  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. */
.avail__loading { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; gap: 8px; }
.avail__loading span {
  width: 7px; height: 7px; border-radius: 50%; background: rgba(255,255,255,.55);
  animation: dots 1.1s ease-in-out infinite;
}
.avail__loading span:nth-child(2) { animation-delay: .14s; }
.avail__loading 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); }
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; }
  /* 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, which keep row 3 here as they do at full width. Left at 3 and
     4 the wide plate lands on top of the band and covers it: measured at 390,
     both claimed rows [215..514] and the photograph won. */
  .intro__wide { grid-column: 1 / 13; grid-row: 4; width: auto; }
  .intro__inset {
    grid-column: 6 / 13; grid-row: 5; 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. */
  .mile { grid-template-columns: repeat(2, 1fr); justify-content: stretch; }
  /* Two up, and the range is the one that has to fit 165px. It takes the
     whole row to itself rather than being set at half the size of the
     figures beside it. */
  .mile__item:nth-child(3) { grid-column: 1 / -1; }
  .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__loading span { 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; }
}
