/* ============================================================================
   BQ Real Estate — Brian Quesada
   The auction catalogue: a lot page for a house, laid over a night film.

   Type      EB Garamond (display + reading) · Archivo Narrow (labels, numerals)
   Ground    the film's own night blue; limestone for the reading surfaces
   Accent    one warm gold, used for labels, rules and the action — nowhere else
   ========================================================================== */

/* ---------- tokens ---------- */
:root {
  /* Sampled from the film: sky, water, and the warm light inside the house. */
  --night-deep : #060B14;
  --night      : #0A1220;
  --night-lift : #101A2B;

  --ink        : #F4F1EA;
  --ink-soft   : rgba(244, 241, 234, .70);
  --ink-faint  : rgba(244, 241, 234, .52);
  --ink-rule   : rgba(244, 241, 234, .18);

  --gold       : #C6AE79;
  --gold-hi    : #E0CFA4;
  --gold-rule  : rgba(198, 174, 121, .42);

  /* The limestone terrace in the film — the light reading surface. */
  --stone      : #EDE8DD;
  --stone-deep : #DED7C7;
  --stone-ink  : #1A2029;
  --stone-soft : rgba(26, 32, 41, .68);
  --stone-rule : rgba(26, 32, 41, .16);

  --display    : "EB Garamond", Georgia, "Times New Roman", serif;
  --label      : "Archivo Narrow", -apple-system, BlinkMacSystemFont, sans-serif;

  /* Corners. Hard edges read as a document; a soft, consistent radius reads as
     a finished product. One scale, applied everywhere — a panel and the button
     inside it must not disagree about how round the world is. */
  --r-card     : 14px;
  --r-act      : 10px;
  --r-input    : 8px;
  --r-pill     : 999px;

  --ease       : cubic-bezier(.22, 1, .36, 1);
  /* Longer tail for content reveals — arrives quickly, settles slowly. */
  --ease-lux   : cubic-bezier(.16, 1, .3, 1);
  --gut        : clamp(22px, 6vw, 68px);
  --measure    : 66ch;
  /* One drawn star, used as a mask so it takes the gold token exactly. */
  --star: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M12 .8l3.3 7.1 7.7.9-5.7 5.3 1.5 7.6L12 18l-6.8 3.7 1.5-7.6L1 8.8l7.7-.9z' fill='%23000'/%3E%3C/svg%3E");
}

/* ---------- reset ---------- */
*, *::before, *::after { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }
@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }
body, h1, h2, h3, h4, p, figure, blockquote, dl, dd, ul, ol { margin: 0; padding: 0; }
ul, ol { list-style: none; }
img, video { max-width: 100%; display: block; }
/* Any image the layout sizes by width keeps its ratio. Without this, adding
   width/height attributes to a tag silently overrides the CSS and stretches it —
   it has now caused two separate defects in this build. */
img[width][height] { height: auto; }
button { font: inherit; color: inherit; background: none; border: 0; cursor: pointer; }
a { color: inherit; }

body {
  background: var(--night-deep);
  color: var(--ink);
  font-family: var(--display);
  font-size: 17px;
  line-height: 1.62;
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}

/* ---------- browser surfaces: these ship with the design, not the browser ---- */
::selection { background: var(--gold); color: var(--night-deep); }
:focus-visible {
  outline: 2px solid var(--gold-hi);
  outline-offset: 3px;
  border-radius: var(--r-act);
}
html { scrollbar-color: var(--gold-rule) var(--night-deep); scrollbar-width: thin; }
::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-track { background: var(--night-deep); }
::-webkit-scrollbar-thumb { background: rgba(198,174,121,.34); border: 3px solid var(--night-deep); border-radius: 99px; }
::-webkit-scrollbar-thumb:hover { background: rgba(198,174,121,.60); }
:root { caret-color: var(--gold); }
a { text-underline-offset: .22em; text-decoration-thickness: 1px; }

/* ============================================================================
   THE FILM — fixed behind everything, scrubbed by scroll
   ========================================================================== */
.film {
  position: fixed;
  top: 0; left: 0; right: 0;
  /* lvh, not inset:0 — same reasoning as the hero runway. A fixed element's
     height tracks the toolbar-dependent viewport, so on every scroll
     direction change the whole background rescaled ~60px while the in-flow
     panels held still: the page read as sections jumping on each reversal.
     Sized to the large viewport once, the film never rescales; when the
     toolbar is visible its bottom edge is simply behind it. */
  height: 100vh;
  height: 100lvh;
  z-index: 0;
  /* Lifts at the very end. The closing frame carries a lot of empty sky above
     the skyline and the page needs somewhere to put the footer; raising the
     whole picture trades that sky for room underneath, and the hem below turns
     the vacated strip into a continuation of the shot rather than a cut edge. */
  transform: translateY(var(--film-lift, 0));
  will-change: transform;
  background: var(--night-deep);
  overflow: hidden;
}
.film__leg {
  position: absolute;
  inset: 0;
  width: 100%; height: 100%;
  object-fit: cover;
  opacity: 0;
  /* No transition: legs are frame-locked to each other, so a crossfade would
     read as a soft cut where the chain is deliberately seamless. */
}
.film__leg.is-live { opacity: 1; }
.film__poster {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: cover;
}
/* One constant grade over the whole film, the way a title sequence carries a
   lower third. It never changes with the section, so it can never step, seam
   or read as a patch: it is simply how the film is lit. Weighted to the base,
   where the type lives, and left almost open across the middle so the skyline
   and the house stay at full strength. */
.film__grade {
  position: fixed; inset: 0; z-index: 1;
  pointer-events: none;
  background:
    linear-gradient(180deg,
      rgba(4, 9, 17, .46) 0%,
      rgba(4, 9, 17, .10) 16%,
      rgba(4, 9, 17, 0) 38%,
      rgba(4, 9, 17, .38) 66%,
      rgba(4, 9, 17, .84) 100%);
}


/* ============================================================================
   GLASS — the one device that makes type legible without hiding the film

   Opacity hides the picture; blur does not. A frosted surface strips the fine
   moving detail that destroys legibility while the film's colour, light and
   motion still read straight through it. Every element that has to sit on the
   moving film is made of this, and nothing on the reading pages is.
   ========================================================================== */
.glass {
  position: relative;
  border-radius: var(--r-card);
  background: rgba(10, 17, 29, .30);
  /* brightness() is the part that matters. Blur alone fails wherever the film
     behind the pane is bright — a lit window, the limestone terrace — because
     white type on a bright backdrop washes out however much it is blurred.
     Darkening the backdrop keeps the picture and wins the contrast. */
  backdrop-filter: blur(14px) saturate(1.08) brightness(.66);
  -webkit-backdrop-filter: blur(14px) saturate(1.08) brightness(.66);
  border: 1px solid rgba(244, 241, 234, .16);
  /* A brighter top edge reads as a lit pane rather than a grey box. */
  border-top-color: rgba(244, 241, 234, .30);
  color: var(--ink);
  text-decoration: none;
}
@supports not (backdrop-filter: blur(4px)) {
  .glass { background: rgba(8, 13, 22, .84); }
}

/* THE ACTION — solid gold, and only ever gold. A frosted outline reads as a
   panel, not a control; the one thing a visitor is asked to do has to look
   like a button from across the room. Sized to its label rather than the
   column, so it never eats the width of the film. */
.act {
  display: inline-flex; align-items: center; justify-content: space-between; gap: 22px;
  max-width: 100%;
  padding: 19px 26px;
  background: var(--gold);
  color: #14181F;
  border: 1px solid var(--gold);
  border-radius: var(--r-act);
  font-family: var(--label);
  font-weight: 600;
  font-size: 12px;
  letter-spacing: .20em;
  text-transform: uppercase;
  text-decoration: none;
  cursor: pointer;
  /* Lifted off the film rather than painted onto it. */
  box-shadow: 0 10px 30px rgba(0, 0, 0, .34);
  transition: background .35s var(--ease), border-color .35s var(--ease),
              transform .35s var(--ease), box-shadow .35s var(--ease);
}
.act b { font-size: 15px; font-weight: 400; }
.act:hover { background: var(--gold-hi); border-color: var(--gold-hi); }
.act:active { transform: translateY(1px); }
.act.is-busy { opacity: .6; pointer-events: none; }

/* The secondary action. It used to be a hairline gold outline on nothing, which
   disappeared into the film the moment the camera moved onto the dark driveway —
   a control you cannot find is not a control. It now gets the same darkened
   glass every other element that sits on the film is made of, so it holds its
   own shape over a lit window or a night lawn alike, and a brighter edge so it
   still reads as the quieter of the two. */
.act--ghost {
  /* Elevation is declared once. The gold action uses its shadow (its border is
     the same colour as its fill); the outlined one uses its border. */
  box-shadow: none;
  background: rgba(10, 17, 29, .34);
  backdrop-filter: blur(14px) saturate(1.06) brightness(.62);
  -webkit-backdrop-filter: blur(14px) saturate(1.06) brightness(.62);
  color: var(--ink);
  border-color: var(--gold-hi);
}
@supports not (backdrop-filter: blur(4px)) {
  .act--ghost { background: rgba(8, 13, 22, .86); }
}
.act--ghost b { color: var(--gold-hi); }
.act--ghost:hover { background: rgba(198, 174, 121, .22); border-color: var(--gold-hi); color: var(--ink); }
/* On a light reading page there is no film to sit on, so the glass comes off
   and it goes back to ink on limestone — gold on limestone is 1.26:1. */
.on-stone .act--ghost {
  background: transparent; backdrop-filter: none; -webkit-backdrop-filter: none;
  color: var(--stone-ink); border-color: var(--stone-ink); box-shadow: none;
}
.on-stone .act--ghost b { color: var(--stone-ink); }
.on-stone .act--ghost:hover { background: var(--stone-ink); color: var(--stone); }
.on-stone .band--ink .act--ghost { color: var(--gold-hi); border-color: var(--gold); }
.on-stone .band--ink .act--ghost b { color: var(--gold-hi); }
.on-stone .band--ink .act--ghost:hover { background: rgba(198,174,121,.16); color: var(--ink); }
.act--wide { display: flex; width: 100%; }

/* A panel: the only place a form is allowed to sit on the film. */
/* Measured before this change, the panels covered 85% of the viewport on
   average across the whole scroll, and 100% of it by the footer — the film ran
   the entire page and was almost never visible. The fix is not a thinner blur
   (blur is what makes the type legible) but less darkening and less panel: the
   backdrop lifts from .54 to .72 so the film's colour, light and motion read
   straight through, and a stronger blur covers the contrast that darkness was
   buying. The panels also lost their eyebrow labels and their duplicated step
   lists, which is where most of the height went. */
.glass--panel {
  width: min(560px, 100%);
  padding: clamp(26px, 4.6vw, 42px);
  background: rgba(9, 15, 26, .20);
  backdrop-filter: blur(22px) saturate(1.06) brightness(.72);
  -webkit-backdrop-filter: blur(22px) saturate(1.06) brightness(.72);
}
@supports not (backdrop-filter: blur(4px)) {
  .glass--panel { background: rgba(8, 13, 22, .90); }
}
.glass--panel .label + .panel__title { margin-top: 14px; }
.panel__title { font-size: clamp(29px, 5.4vw, 44px); line-height: 1.08; text-wrap: balance; }
.panel__prose { margin-top: 20px; color: var(--ink-soft); font-size: 16.5px; }
.panel__prose p { max-width: 46ch; }
.panel__prose p + p { margin-top: 1.05em; }
.panel__more { margin-top: clamp(26px, 4vw, 36px); }

/* A short numbered read: enough of the process to be useful on the homepage,
   with the full guide one click away. Numbers earn their place here because
   the sequence is the information. */
.mini { counter-reset: mini; margin-top: 26px; display: flex; flex-direction: column; }
.mini li {
  counter-increment: mini;
  position: relative;
  padding: 15px 0 15px 44px;
  border-top: 1px solid var(--ink-rule);
  font-size: 16px; line-height: 1.5;
  color: var(--ink-soft);
}
.mini li:last-child { border-bottom: 1px solid var(--ink-rule); }
.mini li::before {
  content: counter(mini, decimal-leading-zero);
  position: absolute; left: 0; top: 17px;
  font-family: var(--label); font-weight: 600; font-size: 10px;
  letter-spacing: .14em; color: var(--gold);
}
.mini b { display: block; font-weight: 400; color: var(--ink); margin-bottom: 3px; }

/* The credential strip: three facts under his name, small, and readable only
   because the glass is under them. */
.glass--strip {
  display: grid; gap: 0;
  width: min(560px, 100%);
  margin-inline: auto;
  padding: 4px 20px;
}
@media (min-width: 620px) { .glass--strip { grid-template-columns: repeat(3, 1fr); padding: 6px 26px; } }
.glass--strip > div { padding: 15px 0; }
@media (min-width: 620px) {
  .glass--strip > div { padding: 18px 20px; border-left: 1px solid rgba(244,241,234,.14); }
  .glass--strip > div:first-child { border-left: 0; padding-left: 0; }
}
@media (max-width: 619px) {
  .glass--strip > div + div { border-top: 1px solid rgba(244,241,234,.14); }
}
.glass--strip dt {
  font-family: var(--label); font-weight: 600; font-size: 8.5px;
  letter-spacing: .22em; text-transform: uppercase; color: var(--gold);
  margin-bottom: 5px;
}
.glass--strip dd { font-size: 14.5px; line-height: 1.35; }

.contact__lots { margin-top: clamp(26px, 4vw, 38px); }
.contact__lots .lot { grid-template-columns: minmax(74px, 96px) 1fr; }
.contact__lots a { color: var(--ink); }
.contact__lots a:hover { color: var(--gold-hi); }

/* ---------- the review rail ----------
   A native scroll-snap row you swipe, not a pair of arrows you hunt for. Taken
   from the Parisi pattern, including both of its hard-won fixes:

   overflow-y MUST be stated. Setting only overflow-x makes computed overflow-y
   "auto" (visible cannot pair with a non-visible value), and the rail becomes a
   vertical scroller that swallows page scroll whenever a gesture starts on it.

   touch-action needs BOTH axes. pan-x alone disables vertical panning for the
   rail and everything inside it, so a finger landing on a review locks the
   page. The rail takes the horizontal, the document keeps the vertical. */
.reviews__stage {
  margin-top: 26px;
  display: grid; grid-auto-flow: column;
  grid-auto-columns: 100%;
  gap: clamp(24px, 4vw, 40px);
  overflow-x: auto; overflow-y: hidden;
  overscroll-behavior-x: contain;
  scroll-snap-type: x mandatory;
  -webkit-overflow-scrolling: touch;
  touch-action: pan-x pan-y;
  scrollbar-width: none;
}
.reviews__stage::-webkit-scrollbar { display: none; }
.review { scroll-snap-align: start; min-width: 0; }
.review blockquote { font-size: 17.5px; line-height: 1.6; color: var(--ink); }
.review blockquote::before { content: "\201C"; }
.review blockquote::after  { content: "\201D"; }
.review figcaption {
  margin-top: 20px;
  font-family: var(--label); font-size: 10px; letter-spacing: .22em;
  text-transform: uppercase; color: var(--gold);
  display: flex; align-items: center; justify-content: space-between; gap: 16px;
}

/* Five drawn stars. An SVG mask rather than a text character, so they carry the
   gold exactly and never fall back to an emoji glyph on some device. */
.stars { display: inline-flex; gap: 3px; flex: none; }
.stars i {
  width: 11px; height: 11px; background: var(--gold);
  -webkit-mask: var(--star) center / contain no-repeat;
  mask: var(--star) center / contain no-repeat;
}
.on-stone .stars i { background: #8A7442; }
.review-card figcaption .stars i { width: 11px; height: 11px; }
.reviews__bar {
  display: flex; align-items: center; gap: 16px;
  margin-top: clamp(24px, 3.5vw, 32px);
  padding-top: 20px;
  border-top: 1px solid var(--ink-rule);
}
/* Dots stand in for the arrows: they say how many there are and where you are,
   without pretending to be the way you move. */
.reviews__dots { display: flex; gap: 7px; }
.reviews__dots i {
  display: block; width: 6px; height: 6px; border-radius: 50%;
  background: rgba(244,241,234,.28);
  transition: background .3s var(--ease);
}
.reviews__dots i.is-at { background: var(--gold); }
.reviews__hint {
  font-family: var(--label); font-size: 9.5px; letter-spacing: .22em;
  text-transform: uppercase; color: var(--ink-faint);
}

.quiet--sm { font-size: 15px; padding-bottom: 5px; }
.reviews__bar .quiet--sm { margin-left: auto; }
/* Read-all sits at the far end, away from the arrows, so the two controls are
   never mistaken for each other. */
.reviews__bar .quiet--sm { margin-left: auto; }
.panel__lede {
  margin-top: 16px; color: var(--ink-soft);
  font-size: 16.5px; line-height: 1.55; max-width: 38ch;
}

/* ============================================================================
   CHROME — mark, hamburger, menu
   ========================================================================== */
.topbar {
  position: fixed;
  top: 0; left: 0; right: 0;
  z-index: 90;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 14px var(--gut);
}
.mark { display: block; }
.mark img { height: 44px; width: auto; }
@media (max-width: 480px) { .mark img { height: 38px; } }

/* Frosted from the first frame, not on a scroll trigger. The mark needs its own
   surface or the film runs straight through the wordmark underneath it. */
.topbar--glass {
  background: rgba(8, 13, 23, .34);
  backdrop-filter: blur(20px) saturate(1.05) brightness(.62);
  -webkit-backdrop-filter: blur(20px) saturate(1.05) brightness(.62);
  border-bottom: 1px solid rgba(244, 241, 234, .12);
}
@supports not (backdrop-filter: blur(4px)) {
  .topbar--glass { background: rgba(8, 13, 23, .88); }
}
/* The bar is the same dark pane on every page, carrying the white mark. It read
   as a grey band across the top of the limestone pages before, and the mark had
   to swap to its dark version to survive there — two versions of his logo in
   circulation, and chrome that changed identity from one page to the next. One
   bar, one mark. */
.on-stone .topbar--glass,
.topbar--glass.is-light {
  /* Nearly solid here on purpose. Over the film a .34 tint plus a darkened
     backdrop reads as deep night; over limestone the same recipe only darkens
     a pale page, which comes out grey. The colour has to be carried rather
     than borrowed. */
  background: rgba(7, 12, 21, .93);
  border-bottom-color: rgba(244, 241, 234, .10);
}
.on-stone .topbar--glass .burger span,
.topbar--glass.is-light .burger span { background: var(--ink); }
@supports not (backdrop-filter: blur(4px)) {
  .on-stone .topbar--glass { background: rgba(8, 13, 23, .88); }
}

.mark--dual { position: relative; display: block; }
.mark--dual img { transition: opacity .45s var(--ease); }
.mark__on-stone { position: absolute; inset: 0; opacity: 0; }
.is-light .mark__on-film  { opacity: 0; }
.is-light .mark__on-stone { opacity: 1; }

/* Over its own overlay — an open menu, or a gate. It keeps its frost rather
   than going transparent: in landscape the menu list is tall enough to scroll,
   and behind a transparent bar those items slide straight over the mark. The
   tint goes dark here whatever the page underneath, because the overlay it is
   sitting on is dark. Declared last so it beats the limestone frost above at
   equal specificity. */
.topbar--glass.is-over,
.on-stone .topbar--glass.is-over {
  background: rgba(8, 13, 23, .58);
  backdrop-filter: blur(20px) saturate(1.05) brightness(.55);
  -webkit-backdrop-filter: blur(20px) saturate(1.05) brightness(.55);
  border-bottom-color: rgba(244, 241, 234, .14);
}
@supports not (backdrop-filter: blur(4px)) {
  .topbar--glass.is-over,
  .on-stone .topbar--glass.is-over { background: rgba(8, 13, 23, .95); }
}
.topbar--glass.is-over .burger span { background: var(--ink); }
.topbar--glass.is-over .mark img { content: url('assets/img/logo-white.png'); }

.topbar__nav { display: none; }
@media (min-width: 1040px) {
  .topbar__nav { display: flex; align-items: center; gap: clamp(20px, 2.4vw, 38px); }
  .topbar__nav a {
    position: relative;
    font-family: var(--label); font-weight: 500;
    font-size: 10.5px; letter-spacing: .22em; text-transform: uppercase;
    color: var(--ink); text-decoration: none;
    padding-bottom: 5px;
  }
  .topbar__nav a::after {
    content: ""; position: absolute; left: 0; right: 0; bottom: 0; height: 1px;
    background: var(--gold);
    transform: scaleX(0); transform-origin: left center;
    transition: transform .35s var(--ease);
  }
  .topbar__nav a:hover::after,
  .topbar__nav a:focus-visible::after,
  .topbar__nav a[aria-current="page"]::after { transform: scaleX(1); }
  .topbar__nav a[aria-current="page"] { color: var(--gold-hi); }
  /* Where the inline nav exists, the burger stands down. The homepage has no
     inline nav — its bar rides the film — so its burger stays. */
  .topbar:has(.topbar__nav) .burger { display: none; }
}

.burger {
  width: 42px; height: 42px;
  display: grid; place-content: center; gap: 7px;
  margin-right: -9px;
}
.burger span {
  display: block;
  width: 26px; height: 1px;
  background: var(--ink);
  transition: transform .4s var(--ease), opacity .25s var(--ease);
}
.burger[aria-expanded="true"] span:first-child { transform: translateY(4px) rotate(45deg); }
.burger[aria-expanded="true"] span:last-child  { transform: translateY(-4px) rotate(-45deg); }

/* Grid with `safe center`: centred when there is room, and falling back to
   start — never clipping — when there is not. A phone in landscape is about
   390px tall, and seven items plus the contact row do not fit; with plain
   centring the top of the list is cut off behind the bar instead of scrolling. */
/* Opening the menu should feel like stopping inside the house, not leaving it.
   At .96 opacity this was a flat slab with a list on it and the film — the whole
   reason the page exists — vanished the moment anyone reached for navigation.
   It is glass now: the house is still there, deeply blurred and darkened, so the
   room you had reached reads behind the names. On the limestone pages there is
   no film and the same rule blurs the page underneath instead, which is the
   same gesture. */
.menu {
  position: fixed; inset: 0; z-index: 80;
  background: rgba(6, 11, 20, .58);
  backdrop-filter: blur(30px) saturate(1.12) brightness(.60);
  -webkit-backdrop-filter: blur(30px) saturate(1.12) brightness(.60);
  /* The names take the middle, the contact strip sits at the foot. Centring the
     whole block left a screen of nothing under it. */
  display: grid;
  grid-template-rows: 1fr auto;
  align-content: stretch;
  gap: clamp(22px, 5vh, 46px);
  /* Clears the bar, so the list can never run underneath the mark and the X. */
  padding: calc(72px + env(safe-area-inset-top)) var(--gut)
           calc(26px + env(safe-area-inset-bottom));
  overflow-y: auto;
  overscroll-behavior: contain;
  visibility: hidden;
  opacity: 0;
  transition: opacity .5s var(--ease), visibility .5s;
}
.menu.is-open { visibility: visible; opacity: 1; }
.menu__list { display: flex; flex-direction: column; gap: 2px; }
.menu__list a {
  font-family: var(--display);
  /* Sized off whichever axis is scarcer. Width alone sends this to 62px on a
     landscape phone, where there is only ~390px of height for seven items. */
  font-size: clamp(24px, min(8.6vw, 6.4vh), 58px);
  line-height: 1.16;
  text-decoration: none;
  color: var(--ink);
  transform: translateY(14px);
  opacity: 0;
  transition: transform .6s var(--ease), opacity .6s var(--ease), color .3s;
}
.menu.is-open .menu__list a { transform: none; opacity: 1; }
.menu.is-open .menu__list a:nth-child(1) { transition-delay: .06s; }
.menu.is-open .menu__list a:nth-child(2) { transition-delay: .12s; }
.menu.is-open .menu__list a:nth-child(3) { transition-delay: .18s; }
.menu.is-open .menu__list a:nth-child(4) { transition-delay: .24s; }
.menu.is-open .menu__list a:nth-child(5) { transition-delay: .30s; }
.menu__list a:hover, .menu__list a[aria-current="page"] { color: var(--gold-hi); }

/* Each name gets a rule that draws itself on approach — the lot rule from the
   reading pages, doing the work an underline would do badly. It also gives the
   list something to do besides sit there. */
@supports not (backdrop-filter: blur(4px)) {
  .menu { background: rgba(6, 11, 20, .97); }
}
.menu__list { align-self: center; }
.menu__list li { position: relative; }
/* position:relative matters here. Without it the rule below is measured
   against the LIST ITEM, which is the full width of the menu, so it drew far
   past the end of the word: it appeared to sit under the text, then keep
   going. Against the link itself, which is inline-block, it is exactly as
   wide as the name. */
.menu__list a { display: inline-block; position: relative; padding-bottom: 4px; }
.menu__list a::after {
  content: ""; position: absolute; left: 0; bottom: 0; height: 1px; width: 100%;
  background: linear-gradient(90deg, var(--gold-hi), rgba(224, 207, 164, 0));
  transform: scaleX(0); transform-origin: 0 50%;
  transition: transform .55s var(--ease);
}
.menu__list a:hover::after,
.menu__list a:focus-visible::after,
.menu__list a[aria-current="page"]::after { transform: scaleX(1); }
/* The page you are on is a state, not a gesture. Drawing it stroke by stroke
   every time the menu opens read as a hesitation before the menu had even
   settled; it is simply already there. Hover and focus keep the draw. */
.menu__list a[aria-current="page"]::after { transition: none; }
@media (prefers-reduced-motion: reduce) {
  .menu__list a::after { transition: none; }
}
.menu__list em { font-style: italic; }
.menu__foot {
  padding-top: clamp(16px, 2.6vh, 22px);
  border-top: 1px solid var(--ink-rule);
  display: flex; flex-wrap: wrap; gap: clamp(6px, 1.2vh, 10px) clamp(16px, 3vw, 30px);
  opacity: 0; transition: opacity .5s var(--ease) .34s;
}
.menu.is-open .menu__foot { opacity: 1; }
.menu__foot a { font-family: var(--label); font-size: clamp(10px, 1.6vh, 12px); letter-spacing: .16em;
                text-transform: uppercase; color: var(--ink-soft); text-decoration: none; }
.menu__foot a:hover { color: var(--gold-hi); }

/* A short, wide viewport — a phone in landscape — splits the list into two
   columns rather than one tall one it cannot show. */
@media (max-height: 520px) and (orientation: landscape) {
  .menu__list {
    display: grid;
    /* Column flow, so each column reads top to bottom like a list rather than
       pairing unrelated items across a row. */
    grid-auto-flow: column;
    grid-template-rows: repeat(4, auto);
    grid-template-columns: 1fr 1fr;
    column-gap: clamp(24px, 5vw, 60px);
    row-gap: 0;
  }
  /* The list takes the top, the contact row is pushed to the base, and the
     type grows into the height that was sitting empty between them. Sized off
     vh here because in landscape height is the scarce axis, not width. */
  /* Two rows: the list takes all the space between the bar and the contact row
     and centres itself inside it, rather than being pinned to the top with a
     void underneath. The items breathe into that space instead of stacking. */
  .menu {
    grid-template-rows: 1fr auto;
    align-content: stretch;
    gap: clamp(10px, 2vh, 18px);
    padding-top: calc(78px + env(safe-area-inset-top));
    padding-bottom: calc(16px + env(safe-area-inset-bottom));
  }
  .menu__list {
    align-self: center;
    row-gap: clamp(2px, 1.4vh, 12px);
  }
  .menu__list a { font-size: clamp(23px, 10vh, 42px); line-height: 1.12; }
  .menu__foot a { font-size: clamp(10px, 2.4vh, 13px); }
}

/* ============================================================================
   TYPE
   ========================================================================== */
.label {
  font-family: var(--label);
  font-weight: 600;
  font-size: 9.5px;
  letter-spacing: .30em;
  text-transform: uppercase;
  color: var(--gold);
}
.display {
  font-family: var(--display);
  font-weight: 400;
  line-height: 1.0;
  letter-spacing: -.012em;
}
h1.display { font-size: clamp(46px, 13vw, 128px); }
h2.display { font-size: clamp(34px, 7.5vw, 74px); line-height: 1.06; }
h3.display { font-size: clamp(23px, 4vw, 32px); line-height: 1.2; }
.display em, .display i { font-style: italic; }

.lede {
  font-size: clamp(18px, 2.3vw, 22px);
  line-height: 1.58;
  max-width: 42ch;
  color: var(--ink);
}
.prose p { max-width: var(--measure); }
.prose p + p { margin-top: 1.15em; }

/* ============================================================================
   THE ACTION — the double-rule plate
   ========================================================================== */
.plate {
  display: block;
  border: 1px solid var(--gold);
  border-radius: var(--r-act);
  padding: 5px;
  text-decoration: none;
  background: rgba(8, 14, 24, .22);
  transition: background .4s var(--ease), border-color .4s var(--ease);
}
.plate > span {
  display: block;
  border: 1px solid var(--gold-rule);
  border-radius: calc(var(--r-act) - 5px);
  padding: 19px 20px;
  text-align: center;
  font-family: var(--label);
  font-weight: 500;
  font-size: 10.5px;
  letter-spacing: .28em;
  text-transform: uppercase;
  color: var(--gold-hi);
  transition: color .4s var(--ease);
}
.plate:hover { background: rgba(198, 174, 121, .12); }
.plate:hover > span { color: var(--ink); }
.plate--solid { background: var(--gold); border-color: var(--gold); }
.plate--solid > span { border-color: rgba(10,16,26,.28); color: var(--night-deep); }
.plate--solid:hover { background: var(--gold-hi); border-color: var(--gold-hi); }
.plate--solid:hover > span { color: var(--night-deep); }
.plate[disabled], .plate.is-busy { opacity: .55; pointer-events: none; }

/* On the limestone pages the plate inverts. */
.on-stone .plate { border-color: var(--stone-ink); background: transparent; }
.on-stone .plate > span { border-color: rgba(26,32,41,.24); color: var(--stone-ink); }
.on-stone .plate:hover { background: var(--stone-ink); }
.on-stone .plate:hover > span { color: var(--stone); border-color: transparent; }

/* A quieter action for secondary moves. */
.quiet {
  display: inline-flex; align-items: center; gap: 12px;
  font-family: var(--display); font-size: 19px;
  text-decoration: none; color: var(--ink);
  padding-bottom: 7px;
  border-bottom: 1px solid var(--gold-rule);
  transition: border-color .35s var(--ease), gap .35s var(--ease);
}
.quiet::after { content: "→"; color: var(--gold); font-size: 15px; }
.quiet:hover { border-bottom-color: var(--gold); gap: 18px; }
.on-stone .quiet { color: var(--stone-ink); border-bottom-color: rgba(26,32,41,.28); }
.on-stone .quiet::after { color: var(--stone-ink); }

/* ============================================================================
   SECTIONS over the film
   ========================================================================== */
.scene {
  position: relative;
  z-index: 10;
  /* Deliberately NOT min-height:100svh. Forcing every section to a full screen
     and then centring it spreads the surplus as padding, so a short panel got
     three times the air of a tall one and the rhythm read as broken. Sections
     size to their content; the padding alone sets the spacing, and it is then
     identical everywhere. */
  scroll-margin-top: 84px;
  display: flex;
  flex-direction: column;
  /* Centred, and with equal padding top and bottom. Left at the top, a short
     panel leaves a screen-deep void beneath it while a tall one sits tight —
     which is exactly why the rhythm read as inconsistent from one section to
     the next. Centring makes the air above and below every panel the same,
     whatever its height. */
  justify-content: center;
  /* The gap between panels is the film's solo time. At the old 13vh the panels
     ran almost shoulder to shoulder — something was on screen for nearly the
     whole scroll and the walk through the house was never actually watchable.
     At 22vh each panel arrives, holds, and leaves, with roughly half a screen
     of clear film between one and the next. */
  padding: clamp(150px, 22vh, 260px) var(--gut) clamp(150px, 22vh, 260px);
}
/* ── panels that have to be somewhere specific ──────────────────────────────
   Three sections are not free to sit wherever the rhythm would put them. The
   film has two passages it does not hold, and these are the panels that cover
   them, so their spacing is set by the footage rather than by the layout.

   About covers the hall's left-hand doorways assembling their black frames,
   Selling takes over from it, and the reel covers the way into the lounge.

   ⚠️ These offsets are tied to the height of the hero. Lengthening the hero
   pushes every panel later in film time and uncovers both passages - which is
   exactly what happened when the hero grew from 300dvh to 375dvh to give the
   name room to climb. If the hero changes again, re-measure the coverage
   across film 10.6-15.4s and 29.6-33.9s before believing it still works. */
/* ── THE SECTION RHYTHM ────────────────────────────────────────────────────
   One gap between panels, everywhere, solved rather than tuned by hand.

   THE FIXED QUANTITY IS THE PAGE'S TOTAL HEIGHT. Every beat of the hero is a
   fraction of the scrollable span, so lengthening or shortening the page moves
   the name, the shutter and the lock frame off the footage they were
   calibrated to. Spacing can therefore be REDISTRIBUTED but never added: the
   hero's first gap, the seven gaps between panels and the tail before the hold
   must always sum to the same number.

   THE ONE PANEL THAT CANNOT MOVE is Looking. It masks the footage's own cut
   into the lounge, so its top has to satisfy T >= y(film 33.6) + vh - h. That
   fixes the sum of everything above it, which in turn fixes what is left for
   the tail. (The About and Selling panels were once described as covering the
   hall's doorways. Measured, they do not and have not for some time: at film
   14.5 the About panel is still off the bottom of the screen and that passage
   is carried entirely by the 2.2x warp. They are free.)

   Solved at the 812px reference: a 216px gap between every pair of panels is
   the most the constraints allow while leaving a real tail. The slack lands in
   the one place it reads as intentional rather than uneven: the stretch of
   open film after the hero releases, before the first panel arrives. Panel to
   panel, the page is now even the whole way down.

   Re-solve all three numbers if a panel's height changes. */
/* Scoped to the film page. These are IDs, and two of them (#work, #contact)
   exist on the reading pages too, where they are ordinary bands with their own
   much tighter rhythm. Unscoped, the About page's reel band inherited the
   film page's 108px and stood out against its 52px neighbours. */
.has-film #about, .has-film #sellers, .has-film #valuation, .has-film #buyers,
.has-film #looking, .has-film #work, .has-film #reviews, .has-film #contact {
  padding-top: clamp(72px, 13.30vh, 150px);
  padding-bottom: clamp(72px, 13.30vh, 150px);
}
.has-film #about { padding-top: clamp(430px, 79.68vh, 880px); }
/* #contact's padding-bottom is owned by the .scene#contact rule further down
   (higher specificity) — do not add one here, it will silently lose. */
/* Each scene decides how much film it lets through. Reading surfaces close it
   down almost completely; moments open it up. */
/* Sections over the film add nothing of their own. The grade above is the only
   thing between the type and the picture, and it is constant, so the film is
   never patched, boxed or blacked out on the way past. */
.scene__inner { width: 100%; max-width: 1180px; margin-inline: auto; }

/* Type over the film carries its own depth as well as the grade. A wide, soft,
   offset shadow is invisible as a shadow but holds the letters when the film
   passes behind them through pale limestone paving — the white-on-white case
   the grade alone cannot solve. */
.scene .display,
.scene .quiet,
.hero__role,
.hint { text-shadow: 0 2px 28px rgba(2, 6, 12, .62), 0 1px 4px rgba(2, 6, 12, .40); }
.glass .display, .glass .quiet, .glass .panel__title { text-shadow: none; }
/* The hero pair has the hardest job on the page: it crosses the lit doorway and
   then the open door, with a chandelier burning behind it. So it is brighter
   than the site's ink, carries a little more weight, and sits on a deeper and
   tighter shadow — the wide soft one alone spreads too far to hold an edge
   against a specular highlight. */
/* Depth on the letters themselves rather than a panel behind them. Three jobs,
   stacked: a tight offset shadow that gives each stroke an edge and lifts it
   off the picture, an unoffset halo that hugs the glyph on every side so it
   keeps its shape against a chandelier or a lit doorway, and a wide soft one
   underneath that separates the whole line from the frame. No box, no plate -
   the film still shows between the letters. */
.hero__sky .display {
  color: #FFFDF8;
  font-weight: 500;
  text-shadow:
    0 1px 2px rgba(0, 0, 0, .95),
    0 2px 6px rgba(0, 0, 0, .92),
    0 0 12px rgba(0, 0, 0, .98),
    0 0 30px rgba(0, 0, 0, .90),
    0 8px 44px rgba(0, 0, 0, .80);
}
.hero__sky .hero__role {
  color: #FFFFFF;
  text-shadow:
    0 1px 2px rgba(0, 0, 0, .98),
    0 1px 5px rgba(0, 0, 0, .95),
    0 0 10px rgba(0, 0, 0, .98),
    0 0 24px rgba(0, 0, 0, .92),
    0 5px 30px rgba(0, 0, 0, .82);
}

/* A single-line scene: one large statement and one quiet link, vertically
   centred and held to the lower half where the grade carries it. */
.line__inner { width: 100%; max-width: 1180px; margin-inline: auto; }
.line__inner .display { max-width: 16ch; text-wrap: balance; }
.line__inner p { margin-top: clamp(26px, 4vw, 40px); }

.contact__number { margin-top: clamp(28px, 4vw, 44px); }
.quiet--big {
  font-size: clamp(30px, 7vw, 54px);
  padding-bottom: 12px;
  border-bottom-width: 1px;
}
.quiet--big::after { font-size: .5em; }
.scene__head { max-width: 30ch; }
.scene__head .label + .display { margin-top: 14px; }
.scene__head .display + .lede  { margin-top: 22px; }

/* The soft ground: a feathered shadow that travels with the type, so a block
   stays readable whatever frame of the film is behind it. */
.grounded { position: relative; }
.grounded::before {
  content: "";
  position: absolute;
  left: 50%; top: 50%;
  width: 165%; height: 240%;
  transform: translate(-50%, -50%);
  background: radial-gradient(60% 50% at 50% 50%,
    rgba(4, 9, 17, .84) 0%, rgba(4, 9, 17, .62) 44%, rgba(4, 9, 17, 0) 78%);
  pointer-events: none;
  z-index: -1;
}

/* ---------- the hero ---------- */
/* Name and place ride high in the open sky, clear of the skyline entirely —
   at 9vh they sat on the buildings, where the city lights ate the letters. */
/* The hero is nearly two screens tall and its contents are pinned to the top of
   it. That extra height is scroll runway: the logo rides out and his name
   arrives while the composition is held in view, and the name then stays through
   the walk up to the front door. Before this the sky scrolled away faster than
   the name faded in, so his name reached full opacity around 550px above the top
   of the screen and was never actually visible on the page. */
/* The runway is lvh, NOT dvh, and the distinction is the whole ballgame on a
   real phone: dvh re-resolves every time the browser toolbar hides or shows,
   so a 500dvh hero breathed by ~300px mid-scroll - every section below it
   jumped under the thumb, and the scroll->film mapping jolted with it (worst
   inside the 4.5x lounge window, where a 60px viewport change reads as a full
   second of film). lvh is the large viewport, resolved once. Emulators show
   dvh and lvh as identical, so this class of bug only ever appears on the
   device - same lesson as the svh hero on jeanius.ai. The pin below keeps
   dvh: it is a visible fill and SHOULD track the real viewport. */
.hero { min-height: 500vh; min-height: 500lvh; display: block; padding: 0; }
.hero__pin {
  position: sticky; top: 0;
  min-height: 100dvh;
  display: flex; flex-direction: column;
  justify-content: flex-start;
  padding: 0 var(--gut) clamp(40px, 6vh, 72px);
}
.hero__sky { text-align: center; padding-top: clamp(84px, 9.5vh, 128px); }
/* Both of his names in one face. He asked for the first to match the second,
   which was the italic; the whole line takes it. */
.hero__sky .display { font-style: italic; }
.hero__sky .display { text-wrap: balance; }
/* Set well above caption size and shadowed, so it holds without glass. It
   breaks to two deliberate lines on a phone rather than wrapping mid-phrase. */
.hero__role {
  margin-top: 16px;
  font-family: var(--label); font-weight: 600;
  font-size: 12.5px; letter-spacing: .26em; text-transform: uppercase;
  /* Not gold here. This line lands on limestone columns and a lit chandelier —
     warm on warm — and the accent disappeared into the house. Gold keeps its
     job everywhere else on the site; over the front door the credential has to
     be readable first. */
  color: var(--ink);
  display: flex; flex-wrap: wrap; justify-content: center; gap: 4px 12px;
}
.hero__role i { font-style: normal; opacity: .55; }
@media (max-width: 560px) {
  .hero__role { flex-direction: column; gap: 7px; font-size: 11.5px; letter-spacing: .30em; }
  .hero__role i { display: none; }
}
.hero__gap  { flex: 1; }

/* The pair is sized to the band it has to cover and nothing more: from the top
   corner of the black door frame on the second floor down to the ceiling under
   the stairs - about 17vh - not the hall ceiling above it. Narrower too, so
   they read as buttons rather than as banners; a centred 350px block still
   spans the doorway on every phone. */
.has-film .hero .act { padding-top: 24px; padding-bottom: 24px; }
.has-film .hero__foot { max-width: 350px; margin-inline: auto; }

/* The bottom of the picture dissolving into the night. It begins where the
   water meets the limestone deck, so the pool and the skyline above it are
   never touched — only the deck, which is the part of the frame the page can
   afford to spend. Rides up with the film, so the join stays at the deck. */
.film__hem {
  /* Starts just under the lit coping at 77% of the frame, so the light on the
     pool edge is the last thing seen and the fall-off below it reads as the
     deck going dark rather than as a panel arriving. */
  position: absolute; left: 0; right: 0; bottom: 0; height: 23%;
  pointer-events: none;
  opacity: var(--hem-o, 0);
  background: linear-gradient(180deg,
    rgba(6, 11, 20, 0) 0%,
    rgba(6, 11, 20, .62) 11%,
    rgba(6, 11, 20, .95) 22%,
    var(--night-deep) 30%);
}

/* Scroll with nothing in it, so the closing shot plays alone. */
.hold { height: 92vh; position: relative; z-index: 10; pointer-events: none; }
@media (max-height: 560px) and (orientation: landscape) { .hold { height: 120vh; } }

/* ---------- the hero handoff ----------
   The logo sits in the sky and rides up out of frame as the camera closes on
   the house, exactly as if it were part of the scene. His name arrives as it
   leaves. Both are driven by the same scroll progress that scrubs the film, so
   the handoff lands on a frame rather than on a timer. The logo is a page
   element rather than part of the footage, so it stays exactly his mark at
   every screen density instead of being redrawn by a video model each frame.
   No-JS and reduced-motion both fall back to everything visible. */
.hero__sky img.hero__logo {
  display: block; width: min(56vw, 300px); max-width: 56vw; height: auto;
  margin: 0 auto 1.2rem;
  opacity: var(--logo-o, 1);
  transform: translateY(var(--logo-y, 0)) scale(var(--logo-s, 1));
  transform-origin: 50% 40%;
  will-change: transform, opacity;
  /* White mark on open sky needs its own depth: a tight dark edge so the
     strokes read, and a wide soft fall so the whole mark sits IN the sky
     rather than printed on it. */
  filter:
    drop-shadow(0 2px 4px rgba(0, 0, 0, .55))
    drop-shadow(0 6px 22px rgba(0, 0, 0, .50))
    drop-shadow(0 14px 48px rgba(2, 6, 12, .38));
}
.js .has-film .hero__sky .display,
.js .has-film .hero__sky .hero__role {
  opacity: var(--name-o, 0);
  transform: translateY(var(--name-y, 14px));
  will-change: transform, opacity;
}
/* The credential dissolves as the name climbs to the head of the door: the
   parked band up there fits one line, and the name is the line that stays. */
.js .has-film .hero__sky .hero__role {
  opacity: calc(var(--name-o, 0) * var(--role-o, 1));
}
/* The mark already owns the sky, so the bar carrying the same mark stays out of
   the way until the sky handoff is finished. It arrives as the logo leaves —
   never two of his marks on screen at once. Hidden means non-interactive too,
   so the burger cannot be tabbed to while it is invisible. */
.js .has-film .topbar {
  opacity: var(--bar-o, 0);
  transform: translateY(calc(-14px * (1 - var(--bar-o, 0))));
  pointer-events: none;
  transition: opacity .18s linear;
}
.js .has-film .topbar.is-revealed { pointer-events: auto; }
@media (prefers-reduced-motion: reduce) {
  .js .has-film .topbar { opacity: 1; transform: none; pointer-events: auto; }
  .hero__logo { opacity: 1; transform: none; }
  .js .has-film .hero__sky .display, .js .has-film .hero__sky .hero__role { opacity: 1; transform: none; }
}

/* Two doors, not one: a seller and a buyer arrive on the same page and each
   needs their own. Sized to their labels and wrapped side by side where there
   is room, stacked where there is not. */
.hero__foot {
  /* Tight pair, tall buttons — on the film page these two are also the shutter
     that covers the second floor while it rebuilds, so the block has to be
     nearly seamless and deep enough to span from the door threshold down to
     the ceiling under the stairs. */
  width: 100%; display: flex; flex-wrap: wrap; gap: 7px; justify-content: center;
  /* Driven by scroll: the two actions climb out of the base of the frame to the
     top of it, where they are the only opaque thing the hero owns and so the
     only thing that can cover the landing while it rebuilds. */
  transform: translateY(var(--foot-y, 0));
  will-change: transform;
}
.hero__foot .act { flex: 0 1 auto; }
@media (max-width: 560px) {
  .hero__foot { flex-direction: column; align-items: stretch; max-width: 340px; margin-inline: auto; }
  .hero__foot .act { justify-content: space-between; }
}

.hint {
  display: flex; align-items: center; justify-content: center; gap: 10px;
  margin-top: 22px;
  font-family: var(--label); font-size: 8.5px; letter-spacing: .26em;
  text-transform: uppercase; color: var(--ink-faint);
}
.hint i {
  display: block; width: 1px; height: 20px;
  background: linear-gradient(180deg, rgba(244,241,234,.6), rgba(244,241,234,0));
}

/* The closing section carries extra depth beneath it: the last leg of the film
   is easing through its weakest stretch here, and the panel staying on screen a
   little longer keeps that out of view. */
/* 3.33vh over the free-stretch half-gap: absorbs the rounding of every
   boundary above so the page's total height — and with it the hero's scroll
   map and the film-end anchor — stays exact. This class+id selector outranks
   the #contact rules in the free-stretch block; the padding-bottom lives
   here and only here. */
.scene#contact { padding-bottom: clamp(90px, 16.75vh, 190px); }   /* the tail: what the solve leaves after the gaps above */

/* ---------- the lot rule: how a catalogue sets a labelled fact ---------- */
.lot {
  display: grid;
  grid-template-columns: minmax(88px, 130px) 1fr;
  gap: 14px 26px;
  align-items: baseline;
  padding: 17px 0;
  border-top: 1px solid var(--ink-rule);
}
.lot:last-of-type { border-bottom: 1px solid var(--ink-rule); }
.lot dt {
  font-family: var(--label); font-weight: 500;
  font-size: 9px; letter-spacing: .20em; text-transform: uppercase;
  color: var(--ink-faint);
}
.lot dd { font-size: 16px; }
.on-stone .lot { border-color: var(--stone-rule); }
.on-stone .lot dt { color: var(--stone-soft); }


/* Jean measured this by eye and he was right: the head's rule had 38px above
   it and 78px below it (52 band + 26 card). The reviews band now opens at the
   head's own rhythm. */
.band:has(> .band__inner > .reviews-grid) { padding-top: clamp(22px, 3vw, 40px); }

/* ---------- the full reviews page ---------- */
.reviews-grid { display: grid; gap: 0; }
/* No opening rule of its own: the page head above already closes with a
   full-width one, and the grid's own would draw a second, shorter line a gap
   below it. Each card still carries the border that separates it from the
   next, so the list reads exactly the same from the first card down. */
/* Column flow, not a row grid. A row grid hands every card in a row the
   tallest card's height, so a long review left its short neighbours sitting
   in craters of empty stone. Columns pack each stack tight; the column-gap is
   exactly the rule's width, so the divider draws where the old per-card
   borders did and the cards' own padding still holds the text off it. */
@media (min-width: 760px) {
  .reviews-grid {
    display: block;
    columns: 2;
    column-gap: 1px;
    column-rule: 1px solid var(--stone-rule);
  }
  .review-card { break-inside: avoid; }
}
@media (min-width: 1100px) { .reviews-grid { columns: 3; } }
.review-card {
  padding: clamp(26px, 3vw, 38px) clamp(20px, 2.4vw, 32px);
  border-bottom: 1px solid var(--stone-rule);
  display: flex; flex-direction: column;
}
.review-card blockquote { font-size: 16.5px; line-height: 1.62; color: var(--stone-soft); }
.review-card blockquote p + p { margin-top: .85em; }
/* The quotes belong to the words, not to the box. Hung on the blockquote they
   sat on their own line once a review ran to more than one paragraph. */
.review-card blockquote p:first-child::before { content: "\201C"; }
.review-card blockquote p:last-child::after  { content: "\201D"; }
.review-card figcaption {
  margin-top: auto; padding-top: 22px;
  font-family: var(--label); font-size: 10px; letter-spacing: .20em;
  text-transform: uppercase; color: #8A7442;
  display: flex; align-items: center; justify-content: space-between; gap: 16px;
}

/* ---------- the reel strip ----------
   Four stills, tall like the clips they come from, so the section shows what it
   is talking about instead of describing it. */
.reel-strip {
  display: grid; grid-template-columns: repeat(4, 1fr);
  gap: clamp(8px, 1.6vw, 14px);
  margin-top: clamp(26px, 4vw, 38px);
}
.reel-strip a { display: block; position: relative; overflow: hidden; }
.reel-strip img {
  /* height:auto is required, not optional. With width/height attributes on the
     tag and no CSS height, the attribute wins and aspect-ratio never applies —
     which rendered each 64px-wide still 854px tall. */
  width: 100%; height: auto; aspect-ratio: 9 / 16; object-fit: cover;
  border: 1px solid var(--ink-rule);
  transition: transform .6s var(--ease-lux), border-color .4s var(--ease);
}
.reel-strip a:hover img { transform: scale(1.04); border-color: var(--gold-rule); }

/* ---------- listings ---------- */
.listings { display: grid; gap: clamp(16px, 2.4vw, 28px); margin-top: clamp(34px, 6vw, 58px); }
@media (min-width: 640px)  { .listings { grid-template-columns: 1fr 1fr; } }
@media (min-width: 1040px) { .listings { grid-template-columns: repeat(4, 1fr); } }
.listing { position: relative; }
.listing video {
  width: 100%;
  aspect-ratio: 9 / 16;
  object-fit: cover;
  background: var(--night);
  border: 1px solid var(--ink-rule);
}
.listing figcaption {
  margin-top: 12px;
  font-family: var(--label); font-size: 10px; letter-spacing: .20em;
  text-transform: uppercase; color: var(--ink-faint);
}
/* On limestone the caption needs limestone ink, not the night tint. */
.on-stone .listing figcaption { color: var(--stone-soft); }
.on-stone .band--ink .listing figcaption { color: var(--ink-faint); }

/* ---------- forms ---------- */
.field { display: block; margin-top: 22px; }
.field span {
  display: block;
  font-family: var(--label); font-weight: 500;
  font-size: 9px; letter-spacing: .22em; text-transform: uppercase;
  color: var(--ink-faint);
  margin-bottom: 9px;
}
.field input, .field textarea {
  width: 100%;
  font-family: var(--display);
  font-size: 17px;
  color: var(--ink);
  background: rgba(244, 241, 234, .05);
  border: 0;
  border-bottom: 1px solid var(--ink-rule);
  /* Soft at the top, flat where the rule lives: the field keeps its underline
     language and still loses the hard corners. */
  border-radius: var(--r-input) var(--r-input) 0 0;
  padding: 12px 10px;
  transition: border-color .35s var(--ease), background .35s var(--ease);
}
.field textarea { resize: vertical; min-height: 104px; line-height: 1.55; }
.field input::placeholder, .field textarea::placeholder { color: rgba(244,241,234,.34); }
.field input:focus, .field textarea:focus {
  outline: none;
  border-bottom-color: var(--gold);
  background: rgba(244, 241, 234, .08);
}
.field input:user-invalid, .field textarea:user-invalid { border-bottom-color: #E8927C; }
.on-stone .field span { color: var(--stone-soft); }
.on-stone .field input, .on-stone .field textarea {
  color: var(--stone-ink); background: rgba(26,32,41,.04); border-bottom-color: var(--stone-rule);
}
.on-stone .field input::placeholder,
.on-stone .field textarea::placeholder { color: rgba(26,32,41,.38); }   /* textarea was missing: its placeholder kept the dark-page off-white and vanished on stone */
.on-stone .field input:focus, .on-stone .field textarea:focus {
  border-bottom-color: var(--stone-ink); background: rgba(26,32,41,.07);
}
.form { max-width: 520px; }
.form .plate, .form .glass--action { margin-top: 30px; }
/* Saying which fields are optional is what makes the rest read as required,
   and it stops someone abandoning a form over a phone number they would rather
   not give. Quiet, lowercase, riding the label rather than competing with it. */
.field span i {
  font-style: normal;
  text-transform: lowercase;
  letter-spacing: .04em;
  opacity: .55;
}

.honeypot { position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden; }

/* The guide gate's follow-up permission. Optional and unchecked — the guide
   opens either way; this only decides what Brian is told he may do. */
.consent {
  display: flex; align-items: flex-start; gap: 10px;
  margin: 18px 0 2px;
  font-size: 14px; line-height: 1.5; color: var(--ink-soft);
  cursor: pointer;
}
.consent input {
  appearance: auto;
  width: 16px; height: 16px; margin-top: 3px; flex: none;
  accent-color: var(--gold);
  cursor: pointer;
}
.on-stone .consent { color: var(--stone-soft); }

.note {
  margin-top: 16px;
  font-family: var(--label);
  font-size: 11.5px; line-height: 1.6; letter-spacing: .04em;
  color: var(--ink-faint);
  max-width: 46ch;
}
.on-stone .note { color: var(--stone-soft); }

.form__done {
  display: none;
  border-top: 1px solid var(--gold-rule);
  border-bottom: 1px solid var(--gold-rule);
  padding: 26px 0;
  margin-top: 26px;
}
.form.is-done .form__done { display: block; }
.form.is-done .form__body { display: none; }
.form__done h3 { font-family: var(--display); font-size: 25px; }
.form__done p  { margin-top: 9px; color: var(--ink-soft); max-width: 42ch; }
.on-stone .form__done p { color: var(--stone-soft); }
.form__error {
  margin-top: 16px; font-family: var(--label); font-size: 12px; letter-spacing: .06em;
  color: #F0A692;
}

/* ============================================================================
   LIMESTONE PAGES — About and the two guides
   ========================================================================== */
.on-stone {
  background: var(--stone);
  color: var(--stone-ink);
}
.on-stone .label { color: #8A7442; }
.on-stone .lede, .on-stone .prose { color: var(--stone-soft); }
.on-stone .prose strong { color: var(--stone-ink); font-weight: 500; }
.on-stone ::selection { background: var(--stone-ink); color: var(--stone); }

.page-head {
  position: relative;
  padding: clamp(104px, 14vh, 150px) var(--gut) clamp(38px, 5vw, 62px);
  border-bottom: 1px solid var(--stone-rule);
}
/* On a page whose next block already opens with a ruled list, the head's own
   border is a third line in a stack of three. */
.page-head--flush { border-bottom: 0; padding-bottom: clamp(24px, 3.5vw, 40px); }
.page-head__inner { max-width: 1180px; margin-inline: auto; }
.page-head .display + .lede { margin-top: 24px; }

/* ---------- the head arrives ----------
   The interior pages animated only on scroll: the reveal system deliberately
   settles anything already visible at load, so the largest type on every page
   — the headline — simply appeared. That is what read as dead. The head is the
   one load moment each page has earned: the words of the title rise in
   sequence, the lede follows, and the head's closing rule draws itself across.
   Everything is gated on .js and flattened by the reduced-motion block. */
/* Masked line-rise, not a fade: the outer span is the mask, the inner span
   starts a full line below it and rises out — the words come off the press.
   The mask carries a little padding pulled back by a negative margin so
   descenders are inside it and never clip at rest. */
.page-head h1 .w {
  display: inline-block;
  overflow: hidden;
  vertical-align: top;
  padding-bottom: .14em;
  margin-bottom: -.14em;
}
.page-head h1 .wi { display: inline-block; }
.js .page-head h1 .wi {
  transform: translateY(118%);
  transition: transform 1.05s cubic-bezier(.16, 1, .3, 1);
}
.js .page-head.is-arrived h1 .wi { transform: none; }
.js .page-head h1 .w:nth-child(2) .wi { transition-delay: .10s; }
.js .page-head h1 .w:nth-child(3) .wi { transition-delay: .20s; }
.js .page-head h1 .w:nth-child(4) .wi { transition-delay: .30s; }
.js .page-head h1 .w:nth-child(5) .wi { transition-delay: .40s; }
.js .page-head h1 .w:nth-child(6) .wi { transition-delay: .50s; }
/* The lede settles out of a blur — focus arriving, not a fade. */
.js .page-head .lede {
  opacity: 0; transform: translateY(12px); filter: blur(7px);
  transition: opacity 1s var(--ease) .5s, transform 1s var(--ease) .5s,
              filter 1s var(--ease) .5s;
}
.js .page-head.is-arrived .lede { opacity: 1; transform: none; filter: none; }
/* The rule cannot animate, so a stone cover retracts off it left to right —
   the line draws itself. On a --flush head there is no rule and the cover
   retracts over nothing. */
.js .page-head::after {
  content: ""; position: absolute; left: 0; right: 0; bottom: -1px; height: 1px;
  background: var(--stone);
  transform-origin: right center;
  transition: transform 1.2s var(--ease) .55s;
}
.js .page-head.is-arrived::after { transform: scaleX(0); }

/* ---------- the hero head ----------
   Four pages open on an image that speaks to the page itself — a brass key for
   sellers, an open front door for buyers, a pen on a letter for reviews, a
   door knocker for contact. Material details, not more rooms: quiet in the
   lower third so the headline sits on shadow, graded to the base exactly the
   way the film carries its type. The image settles from a slow scale as the
   words rise — one authored arrival, not two competing ones. */
.page-head--hero {
  position: relative;
  overflow: hidden;
  color: var(--ink);
  border-bottom: 0;
  min-height: clamp(380px, 54vh, 640px);
  display: flex; align-items: flex-end;
}
@media (max-width: 759px) {
  /* About's head puts its first line right under the bar; the hero heads sat
     theirs at the bottom of the window and read as a different site. On a
     phone the hero text now starts at the same height as every other page,
     and the grade darkens the TOP where the words are instead of the base. */
  .page-head--hero { min-height: clamp(320px, 47vh, 470px); align-items: flex-start; }
  .page-head--hero .page-head__art::after {
    background: linear-gradient(to bottom,
      rgba(6, 11, 20, .78) 0%, rgba(6, 11, 20, .42) 55%, rgba(6, 11, 20, .5) 100%);
  }
}
.page-head--hero .page-head__inner { position: relative; z-index: 1; width: 100%; }
.page-head--hero .lede {
  color: rgba(244, 241, 234, .88);
  text-shadow: 0 1px 4px rgba(6, 11, 20, .9), 0 4px 22px rgba(6, 11, 20, .8) !important;
}
.page-head--hero h1, .page-head--hero .lede, .page-head--hero p, .page-head--hero a {
  text-shadow: 0 1px 3px rgba(6, 11, 20, .6), 0 4px 30px rgba(6, 11, 20, .7);
}
/* EVERYTHING inside a hero head reads light. The contact page's phone and
   email links kept their limestone ink and vanished into the image. */
.page-head--hero .page-head__inner, .page-head--hero .page-head__inner a { color: var(--ink); }
.page-head--hero .page-head__inner a:hover { color: var(--gold-hi); }
.page-head--hero .lede { color: rgba(244, 241, 234, .8); }
/* The h1's words sit inside overflow-hidden masks, and a wide soft shadow
   clips to each word's box — faint rectangles printed themselves behind the
   headline wherever the image ran light. The masked headline keeps only the
   tight shadow; the grade under it does the heavy lifting. */
.page-head--hero h1 {
  text-shadow: none;
  filter: drop-shadow(0 1px 2px rgba(6, 11, 20, .7)) drop-shadow(0 6px 26px rgba(6, 11, 20, .6));
}

/* No stone cover line over an image; the image's own edge is the division. */
.page-head--hero::after { content: none; }
.page-head__art { position: absolute; inset: 0; z-index: 0; }
.page-head__art img,
.page-head__art img[width][height] {
  position: absolute; inset: 0;
  width: 100%; height: 118%;
  aspect-ratio: auto;
  object-fit: cover; display: block;
  will-change: transform;
  /* The heroes are composed subject-right for the left-aligned text. A narrow
     screen shows a half-width slice of a 16:9 frame, and a centred slice
     contains only the quiet left half — the key and the doorbell were cropped
     clean out of their own heroes on a phone. Each image declares where its
     subject sits and the crop follows it; on wide screens the frame fits and
     the value never matters. */
  object-position: var(--hx, 50%) 50%;
}
.page-head__art::after {
  content: ""; position: absolute; inset: 0;
  background: linear-gradient(to top,
    rgba(6, 11, 20, .84) 0%, rgba(6, 11, 20, .46) 48%, rgba(6, 11, 20, .26) 100%);
}
/* Small print over the hero image follows the site's own law — nothing small
   sits on imagery in its dark ink. It goes light, like the lede. */
.page-head--hero .note { color: rgba(244, 241, 234, .62); }
.page-head--hero .note a { color: var(--gold-hi); }

.js .page-head--hero .page-head__art img {
  transform: scale(1.07);
  transition: transform 2.4s var(--ease);
}
.js .page-head--hero.is-arrived .page-head__art img { transform: scale(1); }

.band { padding: clamp(52px, 9vw, 104px) var(--gut); }
.page-head--flush + .band { padding-top: clamp(30px, 4vw, 52px); }
.band__inner { max-width: 1180px; margin-inline: auto; }
.band + .band { border-top: 1px solid var(--stone-rule); }
.band--ink { background: var(--night-deep); color: var(--ink); border-top-color: var(--ink-rule); }
/* An ink band sits inside a limestone page, so every limestone rule above has
   to be undone here or the type and the buttons render dark on dark. */
.on-stone .band--ink .lede,
.on-stone .band--ink .prose { color: var(--ink-soft); }
.on-stone .band--ink .label { color: var(--gold); }
.on-stone .band--ink .lot { border-color: var(--ink-rule); }
.on-stone .band--ink .lot dt { color: var(--ink-faint); }
.on-stone .band--ink .plate { border-color: var(--gold); background: rgba(8,14,24,.22); }
.on-stone .band--ink .plate > span { border-color: var(--gold-rule); color: var(--gold-hi); }
.on-stone .band--ink .plate:hover { background: rgba(198,174,121,.14); }
.on-stone .band--ink .plate:hover > span { color: var(--ink); border-color: transparent; }
.on-stone .band--ink .act { background: var(--gold); color: #14181F; border-color: var(--gold); }
.on-stone .band--ink .act--ghost { background: transparent; color: var(--gold-hi); }
.on-stone .band--ink .quiet { color: var(--ink); border-bottom-color: var(--gold-rule); }
.on-stone .band--ink .quiet::after { color: var(--gold); }

/* ---------- the guide steps: numbered because the sequence IS the information -- */
.step {
  display: grid;
  gap: 10px 40px;
  padding: clamp(30px, 4.5vw, 50px) 0;
  border-top: 1px solid var(--stone-rule);
}
@media (min-width: 860px) { .step { grid-template-columns: 96px 1fr 1fr; align-items: start; } }
.step__n {
  font-family: var(--label); font-weight: 600;
  font-size: 9px; letter-spacing: .22em; text-transform: uppercase;
  color: var(--stone-soft);
  padding-top: 10px;
}
.step__n b {
  display: block;
  margin-top: 4px;
  font-family: var(--display); font-weight: 400;
  font-size: clamp(36px, 5.6vw, 54px); line-height: .86;
  letter-spacing: -.02em;
  color: #8A7442;
}
/* Narrow: the numeral sits on the same baseline as its label rather than
   opening a third row above the heading. */
@media (max-width: 859px) {
  .step__n { display: flex; align-items: baseline; gap: 12px; padding-top: 0; }
  .step__n b { margin-top: 0; font-size: 34px; }
}
.step__body h3 { font-family: var(--display); font-weight: 400; font-size: clamp(24px, 3.2vw, 31px); line-height: 1.16; }
.step__body p  { margin-top: 12px; color: var(--stone-soft); max-width: 46ch; }
.step__points { display: flex; flex-direction: column; gap: 11px; padding-top: 7px; }
.step__points li {
  position: relative;
  padding-left: 24px;
  font-size: 15.5px; line-height: 1.55;
  color: var(--stone-soft);
}
.step__points li::before {
  content: ""; position: absolute; left: 0; top: .72em;
  width: 11px; height: 1px; background: #8A7442;
}

/* ---------- the gate ---------- */
/* ---------- the guide: three groups, divided by a band of scenery ----------
   His guide is eight steps with a heading, a sentence and three bullets each —
   exactly as he wrote it. Run as one unbroken column every block is the same
   height with the same rule above it, and there is no reason for the eye to
   stop. The steps are grouped in threes and each group opens with a full-width
   image band: the spacing tightens inside a group and opens at the band.

   No headings are added. He wrote the guide; the break is a picture and some
   air, not a title somebody else invented.

   The band is short and full width, and the image FILLS it by cropping rather
   than scaling. That is what lets one picture serve both screens: desktop shows
   a wide slice, a phone shows a narrower one, and neither letterboxes. A tall
   portrait beside a headline was the alternative and it left a column of dead
   space on desktop. */

.movement { padding-top: clamp(40px, 6.5vw, 88px); }
.movement:first-of-type { padding-top: clamp(4px, 1vw, 12px); }

/* ---------- the plate: never cropped, revealed ----------
   Four treatments failed before this one, and the last was the worst: the art
   is 16:9 and the sticky side plate displayed it at 3:4 — generating a wide
   frame and then throwing two thirds of it away.

   The rule now is simple and it holds on both screens: THE PICTURE IS NEVER
   CROPPED.

   - Phone: the window IS the picture's own ratio. It fits exactly.
   - Desktop: the window is wide but deliberately shorter than the picture, so
     it never takes the screen — and the picture SLIDES upward inside the
     window as you scroll past, so the whole frame is shown across the pass.
     Scroll-driven reveal is this site's native language: the homepage is a
     scrubbed film. The window shows a moment; scrolling shows the room.

   No rule above or below the plate — the plate is the division, and the step
   after it drops its own top rule so the line is never drawn twice. */

/* A plate carries designed air below it (the margin-bottom clamp further
   down) — right rhythm between steps, wrong before a night band, where it
   printed as a thin strip of stone between the picture and the navy. When a
   plate hands off to an ink band the picture IS the divider and they touch
   (Jean, 2026-08-24, every page). */
.movement__band:has(+ .band--ink),
.movement__band:has(+ .foot) { margin-bottom: 0; }

.movement__band {
  position: relative;
  width: 100vw;
  max-width: 100vw;
  margin-inline: calc(50% - 50vw);
  margin-block: 0 clamp(30px, 4vw, 44px);
  /* Shorter than the picture's own 16:9 on every screen, so the reveal runs on
     the phone as well as the desktop — the window shows a moment, the scroll
     shows the room. Jean approved the desktop reveal and asked for it here. */
  aspect-ratio: 21 / 9;
  overflow: hidden;
  background: var(--stone-deep);
}
.movement__band + .step { border-top: 0; }
/* The plates now stand between sections on every interior page, not only the
   guides. The plate is the division there too. */
.movement__band + .band { border-top: 0; }

/* The picture keeps its natural height — taller than the window at every width
   now — and main.js slides it with scroll. Height auto is safe here for the
   same reason it was dangerous above: here we WANT the intrinsic ratio. */
.movement__band img,
.movement__band img[width][height] {
  position: absolute;
  inset: auto; top: 0; left: 0;
  width: 100%; height: auto;
  aspect-ratio: auto;
  object-fit: cover; display: block;
  will-change: transform;
}
@media (min-width: 1040px) {
  /* Full width, under half the viewport tall. The cap is what keeps it a
     divider rather than a destination — the full-bleed section at the source
     ratio was rejected precisely because it swallowed the screen. */
  .movement__band {
    aspect-ratio: auto;
    height: clamp(280px, 44vh, 520px);
  }
}

/* Until the art exists the slot states what it is, so the layout can be judged
   before a credit is spent. */
.movement__band[data-slot]:not(:has(img)) {
  border: 1px dashed rgba(26, 32, 41, .26);
  background: rgba(26, 32, 41, .035);
}
.movement__band[data-slot]:not(:has(img))::after {
  content: attr(data-slot);
  font-family: var(--label); font-weight: 500;
  font-size: 9.5px; letter-spacing: .18em; text-transform: uppercase;
  color: var(--stone-soft);
  text-align: center; padding: 0 18px; line-height: 1.7;
}

/* On a phone the plate's travel was ~50px across a whole pass — motion you
   could miss. The picture oversizes a quarter, which more than doubles the
   travel; rooms survive the side crop because nothing in them hugs an edge. */
@media (max-width: 759px) {
  .movement__band img,
  .movement__band img[width][height] { width: 126%; left: -13%; max-width: none; }
}

/* Inside a group the steps sit closer than they did on their own. */
.movement .step { padding: clamp(24px, 3.2vw, 36px) 0; }
/* No closing rule under the final step. Every other group ends by handing
   off to a picture with nothing but air between them, and the last group
   does the same — the picture is the close (Jean, 2026-08-24). */

/* The step's bullets arrive as the list they are — one beat apart, after the
   heading has landed. The reduced-motion block above already flattens every
   .rise child with !important, so this inherits that guard for free. */
.js .rise .step__points li {
  opacity: 0; transform: translateY(12px);
  transition: opacity .55s var(--ease), transform .55s var(--ease);
}
.js .rise.is-in .step__points li { opacity: 1; transform: none; }
.js .rise.is-in .step__points li:nth-child(1) { transition-delay: .18s; }
.js .rise.is-in .step__points li:nth-child(2) { transition-delay: .28s; }
.js .rise.is-in .step__points li:nth-child(3) { transition-delay: .38s; }
/* The settle path. Steps already on screen at load take is-shown, not is-in —
   without this rule their bullets stayed at opacity 0 forever, and the hidden
   list read as a block of mystery whitespace under steps 1 and 2. */
.js .rise.is-shown .step__points li { opacity: 1; transform: none; transition: none; }

/* The review cards arrive as the columns they are. */
.js .review-card.rise.is-in { transition-delay: 0s; }
.js .review-card.rise:nth-child(3n+2).is-in { transition-delay: .1s; }
.js .review-card.rise:nth-child(3n+3).is-in { transition-delay: .2s; }

.gate {
  position: fixed; inset: 0; z-index: 70;
  display: grid; place-items: safe center;
  padding: calc(72px + env(safe-area-inset-top)) var(--gut) calc(20px + env(safe-area-inset-bottom));
  overflow-y: auto;
  background: rgba(6, 11, 20, .93);
  backdrop-filter: blur(16px);
  -webkit-backdrop-filter: blur(16px);
}
.gate[hidden] { display: none; }
.gate__card {
  margin-top: clamp(12px, 5vh, 40px);
  width: min(520px, 100%);
  background: var(--night);
  border: 1px solid var(--ink-rule);
  border-radius: var(--r-card);
  padding: clamp(30px, 5vw, 52px);
  max-height: calc(100dvh - 96px); overflow-y: auto;
}
/* A way out. A gate with no exit is a wall, and a visitor who does not want to
   trade an email should still be able to leave without the back button. */
.gate__back {
  display: inline-block;
  margin-bottom: 22px;
  font-family: var(--label); font-size: 10px; letter-spacing: .20em;
  text-transform: uppercase; text-decoration: none;
  color: var(--ink-faint);
  transition: color .3s;
}
.gate__back:hover { color: var(--gold-hi); }
.on-stone .gate__back { color: var(--stone-soft); }
.on-stone .gate__back:hover { color: var(--stone-ink); }

.gate__card h2 { font-family: var(--display); font-size: clamp(28px, 5vw, 40px); line-height: 1.1; }
.gate__card > p { margin-top: 14px; color: var(--ink-soft); max-width: 40ch; }

/* On a limestone page the dialog is limestone too. Without this the card keeps
   the night ground while inheriting the page's dark ink, and the heading lands
   at 1.05:1 — invisible. */
.on-stone .gate__card {
  background: var(--stone);
  border-color: var(--stone-rule);
  color: var(--stone-ink);
}
.on-stone .gate__card > p { color: var(--stone-soft); }
.on-stone .gate__card .form__done { border-color: rgba(26, 32, 41, .28); }

/* ---------- the contact page ----------
   Stacked on a phone: the details you read, then the message you write, a rule
   between them. Wide, the details drop out entirely — every line in that list
   already lives in the footer one scroll below, and restating it above the
   form read as filler (Jean, 2026-08-23). The hero carries the phone number;
   the page is the form. */
.contact-grid { display: grid; gap: clamp(48px, 6vw, 80px); align-items: start; }

.contact-grid > form { position: relative; padding-top: clamp(40px, 5vw, 52px); }
.contact-grid > form::before {
  content: ""; position: absolute; top: 0; left: 0; right: 0;
  height: 1px; background: var(--stone-rule);
}
@media (min-width: 900px) {
  .contact-grid { display: block; }
  .contact-grid > .rise:has(dl) { display: none; }
  .contact-grid > form { max-width: 680px; margin-inline: auto; }
}

/* ---------- about ---------- */
/* The frame holds his proportions and the border; the picture inside is taller
   than the frame and rides the scroll, the same motion the heroes and the guide
   plates carry. The frame keeps the 3:4 of the original crop so nothing about
   how he is framed changes — only that the frame now moves over him. */
.portrait-frame {
  margin: 0;
  position: relative;
  width: 100%;
  max-width: 460px;
  aspect-ratio: 1086 / 1448;
  overflow: hidden;
  border: 1px solid var(--stone-rule);
  background: var(--stone-deep);
}
.portrait,
img.portrait[width][height] {
  position: absolute;
  top: 0; left: 0;
  width: 100%;
  /* 6%, not 16%. The picture is cropped close above his head, so the drift has
     to stay smaller than that headroom — at 116% the crop wandered ~42px each
     way and took the top of his head with it. At 106% the deviation from the
     approved framing is ~16px on a desktop and ~13px on a phone: felt, never
     enough to cut him. */
  height: 108%;             /* the travel; without it the height attribute wins */
  aspect-ratio: auto;
  object-fit: cover;
  display: block;
  border: 0;
  will-change: transform;
}
/* About runs the homepage figure instead of a framed photograph: the cut-out
   standing on the page itself — no frame, no photo background — grounded by
   the same floor shadow the desktop hero gives him. The scroll drift stands
   down on its own: the picture is no taller than the figure, so its travel
   computes to zero and the driver returns before touching him. */
.portrait-frame--cutout {
  border: 0;
  background: transparent;
  overflow: visible;
}
.portrait-frame--cutout .portrait,
.portrait-frame--cutout img.portrait[width][height] {
  position: static;
  height: auto;
  will-change: auto;
}
.about-grid { display: grid; gap: clamp(28px, 4vw, 60px); align-items: start; }
/* Wide, the facts leave the portrait's column and become one ruled strip
   across the full measure, under both the portrait and the bio — the page
   closes on a level line instead of a skinny list hanging off the photo. */
@media (min-width: 900px) {
  .about-grid {
    grid-template-columns: minmax(280px, 400px) 1fr;
    grid-template-areas: "portrait prose" "facts facts";
  }
  .about-grid > .portrait-frame { grid-area: portrait; }
  .about-grid > .prose { grid-area: prose; }
  .about-facts { grid-area: facts; display: grid; grid-template-columns: repeat(4, 1fr); gap: 0 44px; }
  .about-facts .lot {
    grid-template-columns: 1fr;
    gap: 8px;
    align-content: start;
    padding: 20px 0 4px;
  }
  .about-facts .lot:last-of-type { border-bottom: 0; }
}

/* ============================================================================
   FOOTER
   ========================================================================== */
/* The footer is dark on every page, including the limestone ones — so it has
   to declare its own ink. Without this the address, which is plain text rather
   than a link, inherits the limestone page's dark ink onto a dark ground and
   disappears. */
.foot {
  position: relative; z-index: 10;
  background: var(--night-deep);
  color: var(--ink-soft);
  border-top: 1px solid var(--ink-rule);
  padding: clamp(52px, 8vw, 88px) var(--gut) 26px;
}
.on-stone .foot { color: var(--ink-soft); }
.foot li { color: var(--ink-soft); }
/* On the homepage the film runs to the last pixel and the loop closes here, so
   the footer is NOT frosted — frosting it hides the one frame the whole film
   was built to arrive at. Instead it is a gradient: the top is completely clear
   so the final view reads at full strength, and only the base darkens, under
   the text. Same lower-third logic as the film's own grade. */
.foot--glass {
  /* A genuinely clear band at the top — that is the point — then a fast ramp so
     the wordmark and the contact block land on dark rather than on the skyline
     lights, where they were sitting at the edge of legible. */
  /* Clear at the top and solid by the time the wordmark arrives, so the footer
     reads as the night closing over the last shot rather than as a panel parked
     on it. The clear band above no longer has to be deep: the film now finishes
     a footer's height before the page ends, so the closing frame has already
     been seen in full by the time any of this reaches the screen. */
  /* Almost nothing of its own now. The picture's own hem has already carried
     the deck down into the night by the time any of this arrives, so a second
     gradient on top of it only muddied the join. This one just finishes it. */
  background: linear-gradient(180deg,
    rgba(6, 11, 20, 0) 0%,
    rgba(6, 11, 20, .55) 22%,
    rgba(6, 11, 20, .92) 46%,
    var(--night-deep) 72%);
  border-top: 0;
  padding-top: clamp(18px, 2.6vh, 34px);
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
}
/* The text is carrying itself over moving picture now, so it takes the same
   weight and depth the flight's type does.

   This block was corrupt: a comment opened in the middle of the selector list
   below, so the rule that should have set a text-shadow instead applied
   `border-bottom: 1px solid currentColor` to every p, li, a and h4 in the
   footer — a stray underline under each line, three times over, since the
   damaged block was also duplicated twice. */
.foot--glass, .foot--glass li, .foot--glass a { color: var(--ink); }
.foot--glass p,
.foot--glass li,
.foot--glass a,
.foot--glass h4,
.foot--glass .foot__legal {
  text-shadow: 0 2px 22px rgba(2, 6, 12, .70), 0 1px 3px rgba(2, 6, 12, .45);
}
.foot--glass h4 { color: var(--gold); }
.foot--glass .foot__legal { color: rgba(244, 241, 234, .78); }
.foot__inner { max-width: 1180px; margin-inline: auto; }
/* Compact on a phone. Contact and Follow are two short lists; stacking them
   turned the footer into a screen and a half of scrolling after the film's
   closing shot, which is the last thing that should be competing with it. Side
   by side they cost one block instead of three. */
/* Tight on purpose. The whole block has to clear the lit edge of the pool, so
   every line of it is spent deliberately. */
.foot__grid {
  display: grid; gap: 20px 20px;
  grid-template-columns: 1fr auto;
}
/* The wordmark and his name sit side by side across the top of the footer.
   Stacked, they left the whole right half of that row empty while everything
   else crowded into the bottom left, and they cost a block of height the
   footer cannot spare now that it has to clear the pool. */
.foot__mark {
  grid-column: 1 / -1;
  display: flex; align-items: flex-end; justify-content: space-between;
  gap: clamp(12px, 3.4vw, 20px);
}
.foot__mark img { flex: 0 0 auto; }
/* The name block above hangs from the right edge; the Follow column below it
   was an auto-width column with left-aligned text — close to the edge but not
   on it, which read as misplaced rather than placed. It aligns to the same
   edge now. */
.foot__grid > div:last-child { text-align: right; }
.foot__mark p { margin: 0; max-width: none; }
/* Three columns for two items put Follow in the MIDDLE with an empty column
   to its right — "randomly in the middle of the page", as Jean put it. Two
   columns: Contact takes the room, Follow hangs on the right edge. */
@media (min-width: 780px) { .foot__grid { grid-template-columns: 1fr auto; gap: 40px 60px; } }
.foot h4 {
  font-family: var(--label); font-weight: 600; font-size: 8.5px;
  letter-spacing: .24em; text-transform: uppercase; color: var(--ink-faint);
  margin-bottom: 8px;
}
.foot a { color: var(--ink-soft); text-decoration: none; transition: color .3s; }
.foot a:hover { color: var(--gold-hi); }
.foot li + li { margin-top: 4px; }
.foot li { font-size: 14px; line-height: 1.34; }
.foot__mark img { height: 46px; width: auto; margin-bottom: 0; }
/* Right-aligned and sitting on the same line as the foot of the wordmark, so
   the top row reads as one balanced band rather than a logo with a paragraph
   hanging off it and the right half of the footer left empty. */
.foot__mark p { font-size: 13.5px; line-height: 1.34; text-align: right; }
/* The second column goes to the right edge for the same reason, and sits down
   on the baseline of the first: Contact runs to four lines and Follow to three,
   so aligned at the top it left a hole in the bottom right, across from the
   second line of the address. */
.foot__grid > div:last-child { text-align: right; align-self: end; }
/* Credit line. Same size and weight as the copyright beside it, so it reads as
   a footnote rather than an advertisement on someone else's site. */
.foot__by a {
  color: inherit;
  text-decoration: none;
  border-bottom: 1px solid currentColor;
  transition: color .3s var(--ease), border-color .3s var(--ease);
}
.foot__by a:hover { color: var(--gold-hi); border-bottom-color: var(--gold-hi); }

.foot__legal {
  margin-top: clamp(16px, 2.6vw, 26px);
  padding-top: 13px;
  border-top: 1px solid var(--ink-rule);
  display: flex; flex-wrap: wrap; gap: 8px 28px; justify-content: space-between;
  font-family: var(--label); font-size: 10px; letter-spacing: .08em;
  color: var(--ink-faint);
}

/* ============================================================================
   MOTION — one authored moment: content rises as the film settles behind it
   ========================================================================== */
/* Content is visible by default and the reveal is an enhancement. Scoped to
   .js so that without scripting — or if the observer never fires, which is
   exactly what happens when someone lands on a deep anchor — every block is
   simply on screen rather than permanently transparent. */
/* The reveal. Deliberately restricted to transform, opacity and clip-path —
   all compositor properties. Blur and letter-spacing animations look expensive
   in a demo and repaint every frame; over a video that is playing behind them
   on a phone, that is where stutter comes from.

   The heading is unveiled by a wipe rising off its own baseline, the way a
   title card resolves, while the block it sits in lifts a little behind it.
   One long, deep ease, once per block, nothing bouncing. */
.js .rise { opacity: 0; transform: translateY(26px); }
.js .rise.is-in {
  opacity: 1; transform: none;
  transition: opacity 1.05s var(--ease-lux), transform 1.15s var(--ease-lux);
}

/* The heading assembles word by word. Each word is masked by its own line box
   and rises into it, so the sentence builds rather than appearing. Capped at
   twelve steps of delay: past that a long heading finishes noticeably after
   everything under it, which reads as lag rather than choreography. */
.js .rise .display .wd {
  display: inline-block;
  opacity: 0;
  transform: translateY(.42em);
  will-change: transform, opacity;
}
.js .rise.is-in .display .wd {
  opacity: 1;
  transform: none;
  transition: opacity .72s var(--ease-lux), transform .82s var(--ease-lux);
  transition-delay: calc(min(var(--i), 12) * 55ms);
}
/* Headings that never got split still reveal as one block. */
.js .rise .display:not([data-split]) { clip-path: inset(0 0 105% 0); }
.js .rise.is-in .display:not([data-split]) {
  clip-path: inset(0 0 -20% 0);
  transition: clip-path 1.2s var(--ease-lux) .08s;
}

/* Everything under the heading follows it in, in reading order. */
.js .rise .label            { opacity: 0; transform: translateY(10px); }
.js .rise .panel__lede,
.js .rise .panel__prose,
.js .rise .lede,
.js .rise .mini,
.js .rise .contact__number  { opacity: 0; transform: translateY(14px); }
.js .rise .field,
.js .rise .act,
.js .rise .plate,
.js .rise .quiet,
.js .rise .panel__more,
.js .rise .contact__lots,
.js .rise .reviews__stage,
.js .rise .reviews__bar     { opacity: 0; transform: translateY(14px); }

.js .rise.is-in .label,
.js .rise.is-in .panel__lede,
.js .rise.is-in .panel__prose,
.js .rise.is-in .lede,
.js .rise.is-in .mini,
.js .rise.is-in .contact__number,
.js .rise.is-in .field,
.js .rise.is-in .act,
.js .rise.is-in .plate,
.js .rise.is-in .quiet,
.js .rise.is-in .panel__more,
.js .rise.is-in .contact__lots,
.js .rise.is-in .reviews__stage,
.js .rise.is-in .reviews__bar {
  opacity: 1; transform: none;
  transition: opacity .85s var(--ease-lux), transform .85s var(--ease-lux);
}
.js .rise.is-in .label           { transition-delay: .04s; }
.js .rise.is-in .panel__lede,
.js .rise.is-in .panel__prose,
.js .rise.is-in .lede            { transition-delay: .30s; }
.js .rise.is-in .mini,
.js .rise.is-in .contact__lots,
.js .rise.is-in .reviews__stage  { transition-delay: .38s; }
.js .rise.is-in .field           { transition-delay: .38s; }
.js .rise.is-in .field:nth-of-type(2) { transition-delay: .44s; }
.js .rise.is-in .field:nth-of-type(3) { transition-delay: .50s; }
.js .rise.is-in .act,
.js .rise.is-in .plate,
.js .rise.is-in .quiet,
.js .rise.is-in .panel__more,
.js .rise.is-in .contact__number,
.js .rise.is-in .reviews__bar    { transition-delay: .44s; }   /* was .52s: still last in the cascade, no longer late */
/* Anything already on screen is painted outright, with no transition to wait
   on. Browsers pause transitions in backgrounded tabs, and a block that is
   only visible once its transition finishes is a block that can render as
   nothing at all. Motion is for content arriving, never for content present. */
.js .rise.is-shown,
.js .rise.is-shown .display,
.js .rise.is-shown .display .wd,
.js .rise.is-shown .label,
.js .rise.is-shown .panel__lede,
.js .rise.is-shown .panel__prose,
.js .rise.is-shown .lede,
.js .rise.is-shown .mini,
.js .rise.is-shown .contact__number,
.js .rise.is-shown .field,
.js .rise.is-shown .act,
.js .rise.is-shown .plate,
.js .rise.is-shown .quiet,
.js .rise.is-shown .panel__more,
.js .rise.is-shown .contact__lots,
.js .rise.is-shown .reviews__stage,
.js .rise.is-shown .reviews__bar {
  opacity: 1; transform: none; clip-path: none; transition: none;
}
.rise:nth-child(2).is-in { transition-delay: .07s; }
.rise:nth-child(3).is-in { transition-delay: .14s; }

@media (prefers-reduced-motion: reduce) {
  .js .page-head h1 .w, .js .page-head h1 .wi, .js .page-head .lede { opacity: 1 !important; transform: none !important; filter: none !important; transition: none !important; }
  .js .page-head::after { display: none; }
  .js .page-head__art img { transform: none !important; transition: none !important; }
  .js .rise, .js .rise *, .js .rise .display .wd { opacity: 1 !important; transform: none !important;
                           clip-path: none !important; transition: none !important; }
  .menu__list a { opacity: 1; transform: none; }
  .film__leg { display: none; }
  .plate, .quiet, .menu, .topbar::before { transition: none; }
}

.skip {
  position: absolute; left: var(--gut); top: 10px; z-index: 100;
  transform: translateY(-160%);
  /* Hidden for real, not just parked above the page. The translate alone
     stashes the chip in the strip above the document - exactly the strip a
     phone reveals when the visitor pulls down to rubber-band or refresh, so
     the gold chip kept appearing in the top corner on every hard pull. It is
     now invisible until keyboard focus actually summons it. */
  visibility: hidden; opacity: 0;
  background: var(--gold); color: var(--night-deep);
  font-family: var(--label); font-size: 11px; letter-spacing: .18em;
  text-transform: uppercase; padding: 12px 18px; text-decoration: none;
  transition: transform .3s var(--ease);
}
.skip:focus-visible { transform: none; visibility: visible; opacity: 1; }

/* ============================================================================
   LANDSCAPE PHONE — the gate, compacted
   Declared last: these override the base gate rules above, and at equal
   specificity that only works from here.
   ========================================================================== */
@media (max-height: 520px) and (orientation: landscape) {
  .gate__card { padding: clamp(18px, 3vw, 26px); max-height: calc(100dvh - 92px); }
  .gate__card h2 { font-size: clamp(20px, 6.2vh, 27px); line-height: 1.12; }
  .gate__card > p { font-size: 13.5px; margin-top: 8px; }
  .gate__card .field { margin-top: 10px; }
  .gate__card .field span { margin-bottom: 5px; }
  .gate__card .field input { padding: 7px 2px; font-size: 15px; }
  .gate__card .act, .gate__card .plate { margin-top: 14px; }
  .gate__card .note { display: none; }
  .gate__card .form__done h3 { font-size: 20px; }
}
/* Wide enough to put the intro beside the form rather than above it. */
@media (max-height: 520px) and (orientation: landscape) and (min-width: 700px) {
  .gate__card form { display: grid; grid-template-columns: 1fr 1fr; column-gap: 26px; align-items: start; }
  .gate__card .form__body { display: contents; }
}

/* ============================================================================
   LANDSCAPE PHONE — the page, scaled to the height it has
   A secondary orientation, so it gets the same layout at a size that fits
   rather than a second design. Type is driven off vh here because height is
   the scarce axis; left at portrait's width-based sizes, a 110px headline
   tries to live in a 390px screen and everything collides.
   ========================================================================== */
@media (max-height: 560px) and (orientation: landscape) {
  h1.display { font-size: clamp(30px, 13vh, 62px); }
  h2.display { font-size: clamp(24px, 9.5vh, 46px); }
  .panel__title { font-size: clamp(22px, 8.5vh, 38px); }

  /* The hero sets its own vertical rhythm: the name clear of the bar, the role
     given real air beneath it, and the actions held at the base so the house
     breathes in between. It must be exempt from the .scene padding below —
     applying both put 154px of dead space above the name and squeezed the rest
     into what was left, which is what made it look stacked. */
  /* The grade's base ramp is drawn for a tall frame. Compressed into 390px it
     stops being a gradient and becomes a black bar across the bottom of the
     picture, so it is redistributed rather than removed. */
  .film__grade {
    background: linear-gradient(180deg,
      rgba(4, 9, 17, .40) 0%,
      rgba(4, 9, 17, .08) 22%,
      rgba(4, 9, 17, 0) 46%,
      rgba(4, 9, 17, .26) 74%,
      rgba(4, 9, 17, .62) 100%);
  }
  .hero { padding-top: 0; padding-bottom: 0; }
  /* The pinned composition has to fit the height it actually has. On a phone
     held sideways that is around 390px, and the sky mark alone was taking 211px
     of it — the actions ended up 74px below the fold with nothing to say they
     were there. Everything in the hero is sized off vh here, because height is
     the scarce axis in this orientation. */
  .hero__pin { padding-bottom: clamp(14px, 5vh, 26px); }
  .hero__sky { padding-top: clamp(58px, 17vh, 82px); }
  .hero__sky img.hero__logo {
    width: auto; height: clamp(58px, 21vh, 104px); max-width: 46vw;
    margin-bottom: clamp(8px, 2.4vh, 16px);
  }
  .hero__role { margin-top: clamp(8px, 2.6vh, 16px); font-size: clamp(9.5px, 2.6vh, 12px); }
  /* Side by side: stacked, two full-width actions cost 120px of a 390px screen. */
  .hero__foot { gap: 10px; flex-direction: row; flex-wrap: nowrap; max-width: none; }
  .hero__foot .act { flex: 1 1 0; min-width: 0; }
  .hero__foot .act span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .hero .act { padding: 12px 16px; font-size: 10px; letter-spacing: .14em; gap: 10px; }

  /* Sections and panels tighten so a screen shows a whole thought rather than
     two lines of one. The hero is excluded — it owns its own spacing above. */
  .scene:not(.hero) { padding-top: clamp(76px, 18vh, 104px); padding-bottom: clamp(56px, 14vh, 84px); }
  .glass--panel { padding: clamp(20px, 5vh, 30px); }
  .panel__lede, .panel__prose { font-size: 15px; margin-top: 10px; }
  .panel__prose p + p { margin-top: .7em; }
  .panel__more { margin-top: clamp(14px, 3vh, 22px); }
  .field { margin-top: clamp(10px, 2.4vh, 16px); }
  .field span { margin-bottom: 4px; }
  .field input { padding: 8px 2px; }
  .field textarea { min-height: 68px; }
  /* ⚠️ The left gutter is NOT optional: the step number is an absolutely
     positioned ::before at left:0, so zeroing the padding drops it straight
     under the step's own name. That is the overlap Jean photographed on an
     iPhone in landscape. Height is the scarce axis here, so the vertical
     padding tightens and the gutter stays. */
  .mini li { padding: 9px 0 9px 40px; font-size: 14.5px; }
  .review blockquote { font-size: 15.5px; line-height: 1.5; }
  .page-head { padding-top: clamp(80px, 20vh, 116px); padding-bottom: clamp(24px, 6vh, 40px); }
  .band { padding-top: clamp(34px, 8vh, 56px); padding-bottom: clamp(34px, 8vh, 56px); }

  /* Two columns where the width allows it, so a short screen is not one long
     scroll of single-column text. */
  @media (min-width: 720px) {
    .glass--panel { width: min(760px, 100%); }
    .mini { columns: 2; column-gap: 34px; }
    .mini li { break-inside: avoid; }
  }
}

/* ============================================================================
   PHONE BLUR BUDGET — declared last so it wins the cascade.
   On a phone the backdrop blur is the single most expensive thing on screen:
   every scrolled frame re-blurs the moving film behind every pane, and the
   cost scales with the radius. Half the radius keeps the frosted read - the
   brightness() term, which does the actual legibility work, is untouched -
   and returns the GPU time that was coming out of the scrub. Desktop keeps
   the deeper blur; it has the headroom.
   ========================================================================== */
@media (pointer: coarse) {
  .glass--panel {
    backdrop-filter: blur(12px) saturate(1.06) brightness(.72);
    -webkit-backdrop-filter: blur(12px) saturate(1.06) brightness(.72);
  }
  .topbar--glass {
    backdrop-filter: blur(10px) saturate(1.05) brightness(.62);
    -webkit-backdrop-filter: blur(10px) saturate(1.05) brightness(.62);
  }
}

/* ============================================================================
   THE CURTAIN — the page does not scroll until the film can.
   Scroll-locking the document while the take downloads is what makes a cold
   load seamless: the desync between thumb and film can never exist, so there
   is nothing to skip, snap or sweep. The hairline is the only cue — a gold
   line filling along the bottom edge, gone the moment the scroll unlocks.
   ========================================================================== */
/* The lock is a FIXED BODY, not overflow:hidden on the root. iOS half-ignores
   root overflow (the page still pans) while the hidden-overflow root stops
   being a normal scroll container — which kills position:sticky, and the
   hero pin with it: Jean watched the name and buttons simply scroll away.
   A fixed body is the lock Safari actually honours; the page is pinned at
   its own top, and sticky is back to normal the moment the class comes off. */
html.is-holding body {
  position: fixed; top: 0; left: 0; right: 0;
  overflow: hidden;
  overscroll-behavior: none;
}
.film__load {
  position: fixed; left: 0; bottom: 0; z-index: 90;
  height: 2px; width: var(--load, 0%);
  background: var(--gold);
  transition: width .25s linear, opacity .5s var(--ease);
}
.film__load.is-done { width: 100%; opacity: 0; pointer-events: none; }

/* The submit button was landing flush against the last field on every form —
   the honeypot between them is absolutely positioned, so it contributes no
   height, and .field's own margin-top only spaces fields from each other.
   A button reads as part of the input it touches; it needs its own air. */
.form__body .act { margin-top: 30px; }
.form__body .note { margin-top: 16px; }

/* The contact page stacked two rules with nothing between them: the big phone
   link carries its own underline, and the detail list below opened with its
   own top border. Read as a doubled divider. The list keeps every separator
   between its rows and the rule that closes it; only the redundant opening
   one goes. */
.contact-grid .lot:first-of-type,
.contact__lots .lot:first-of-type { border-top: 0; }   /* the homepage panel stacks the same way: the big phone link's underline, then the list's opening rule right below it */

/* ...and the same doubling at the bottom of that list. Stacked one column, the
   detail list closes with its own rule and the form then draws another one
   above itself, a grid gap apart, with nothing between them. The list's
   closing rule is the separator on narrow screens, so the form's is dropped
   there. Side by side at 900px the form's rule becomes the vertical divider
   between the two columns, which is a different job, so it stays. */
@media (max-width: 899px) { .contact-grid > form::before { content: none; } }

/* A closing block that holds nothing but its two actions has no cascade to
   wait for: the .44s delay exists so actions arrive after a heading and lede
   in the same block, and here there is neither. Those buttons now lead their
   own block instead of trailing an empty one. */
.js .rise.acts-lead.is-in .act { transition-delay: .06s; }

/* The word beside the hairline. "Loading" while the take arrives, "Scroll" the
   moment it can follow the thumb, then gone on the first scroll and never
   again. Small, tracked, the same label voice as the rest of the site. */
.film__cue {
  position: fixed; left: 0; right: 0; bottom: 22px; z-index: 91;
  text-align: center; margin: 0;
  font-family: var(--label); font-weight: 500;
  font-size: 10px; letter-spacing: .26em; text-transform: uppercase;
  color: rgba(244, 241, 234, .46);
  transition: opacity .7s var(--ease), color .7s var(--ease);
  pointer-events: none;
}
.film__cue.is-cue { color: var(--gold-hi); animation: cueBreathe 2.4s var(--ease) infinite; }
.film__cue.is-gone { opacity: 0; animation: none; }
@keyframes cueBreathe { 0%, 100% { opacity: .55 } 50% { opacity: 1 } }
@media (prefers-reduced-motion: reduce) { .film__cue.is-cue { animation: none; opacity: 1; } }

/* ============================================================================
   THE DESKTOP PASS (2026-08-23) — the phone layout, given room to stand
   ========================================================================== */
/* The closing block every page ends on: a heading, sometimes a lede, and the
   two actions. On a phone it reads down the left edge like everything else.
   Wide, it is the page's last word, so it takes the center — and the two
   actions sit side by side instead of stacked in a strip of empty stone. */
.close-block { max-width: 620px; }
.close-block .acts-lead { margin-top: 34px; }
.acts-lead { display: grid; gap: 14px; max-width: 420px; }
@media (min-width: 1040px), (orientation: portrait) and (min-width: 768px) {
  .close-block { max-width: 720px; margin-inline: auto; text-align: center; }
  .close-block .lede { margin-inline: auto; }
  .close-block .acts-lead {
    display: flex; justify-content: center;
    gap: 18px; max-width: none; margin-top: 40px;
  }
  .close-block .act { flex: 0 1 340px; }
}

/* A section head that leads a full-width exhibit (the reel strip) centers
   over it wide; the head of a page without hero art does the same. Hero-page
   heads never center — their art carries the subject on the right and the
   left half exists for the words. */
.sec-head { max-width: 640px; }
@media (min-width: 1040px), (orientation: portrait) and (min-width: 768px) {
  .sec-head { max-width: 720px; margin-inline: auto; text-align: center; }
  .sec-head .lede { margin-inline: auto; }
  .page-head--center .page-head__inner { text-align: center; }
  .page-head--center .lede { margin-inline: auto; }
}

/* ============================================================================
   THE LANDSCAPE EXPERIENCE (2026-08-24)
   Portrait keeps the 44.9s film untouched. Landscape — desktop and phones held
   sideways — gets the 6s 16:9 arrival that ends at the open door, then the
   page itself on stone, in the interior pages' language. CSS shows one
   experience, main.js runs one engine, and rotation reloads the page.
   ========================================================================== */
.exp-land { display: none; }

/* ⭐ A TABLET IN PORTRAIT GETS THE WIDE EXPERIENCE, NOT THE PHONE'S.

   The film's choreography is written for a phone's frame: the door beat, the
   panel widths, and the picture that covers the second-floor edge all assume
   tall and narrow. At 3:4 none of it does its job — the cover misses, the
   panels float in a band far wider than they were drawn for, and the title
   card opens a gap you could park in. It is not tunable by width; it would
   need a third beat map. The wide composition already handles a roomy frame,
   so that is what a tablet gets, in either orientation. */
@media (orientation: landscape), (min-width: 768px) {
  .has-film .film,
  .has-film .exp-port,
  .has-film .hold { display: none; }
  .has-film .exp-land { display: block; background: var(--stone); }

  /* The footer's glass gradient exists to let the film's last frame show
     through. There is no film under it here — it closes on solid night. */
  .has-film .foot--glass { background: var(--night-deep); }
}

/* ── the arrival ── */
/* 460vh: the extra stretch holds the title card on stone before release. */
.dhero { position: relative; height: 460vh; background: var(--night-deep); }
.dhero__pin {
  position: sticky; top: 0;
  height: 100vh; height: 100dvh;
  overflow: hidden;
}
.dhero__poster,
.dhero__film {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: cover;
}
/* ⚠️ will-change on a VIDEO is why the arrival froze and the portrait film
   never did — it is the only difference between the two elements.

   It promotes the video to its own compositing layer permanently, and iOS
   then snapshots that layer into a texture and stops refreshing it from the
   decoder. currentTime keeps moving, every seek lands, and the picture is a
   photograph. Jean's readouts show it exactly: scrollY 839 / TIME 5.60, then
   505 / 3.35, 208 / 1.36, 0 / 0.00 — the clock tracking the scroll perfectly
   while all five screenshots show the identical door frame.

   So the hint is worn only while the zoom is actually running. The scrub ends
   at p 0.58 and the zoom starts at 0.56, so by the time this is on, the frame
   is deliberately held anyway and a frozen texture is the correct picture.
   Scrolling back up drops the class, the layer de-promotes, and the film
   paints again. */
/* ON A TOUCH DEVICE the video is the decoder and the canvas is the picture —
   see the long note in main.js. The element stays laid out (the logo is
   positioned against its box and the canvas is sized from it) and stays
   decoding; it simply is not what you look at, because what iOS composites
   for it cannot be trusted. A desktop never had that fault and keeps the
   plain element, so `.canvas-film` gates the swap and no canvas is created
   there at all. */
.canvas-film .dhero__film { opacity: 0; pointer-events: none; }
.dhero__canvas {
  position: absolute; inset: 0;
  display: block; width: 100%; height: 100%;
}
/* Whichever of the two is carrying the zoom gets the hint while it runs. */
.dhero__film.is-zooming,
.dhero__canvas.is-zooming { will-change: transform; }
/* One grade, bottom-weighted: the acts and the cue sit over the walkway. */
.dhero__grade {
  position: absolute; inset: 0; pointer-events: none;
  background: linear-gradient(to top, rgba(6,11,20,.44) 0%, rgba(6,11,20,0) 30%);
}
.dhero__sky {
  /* A point, not a row: the engine sets left/top/width from the film's own
     cover geometry (bay cap: x 777–1118 of 1920). The anchoring translate
     lives HERE, in CSS, so it exists from the very first paint — as a
     JS-written transform it could be seen missing, which rendered the mark
     half its width to the right and sitting on the roof. */
  position: absolute; left: 0; top: 0;
  transform: translate(-50%, -100%) translateY(var(--sky-rise, 0vh));
  pointer-events: none;
  will-change: transform, opacity;
}
.dhero__sky img {
  display: block;
  /* Width is set by the engine: the wordmark line matches the centre bay's
     16.2% of the frame, centred on the bay's own centre (49.9%), so it sits
     between the two piers at every viewport shape (Jean, 2026-08-24). */
  width: 244px; height: auto;
  filter: drop-shadow(0 1px 3px rgba(6,11,20,.9))
          drop-shadow(0 3px 12px rgba(6,11,20,.75))
          drop-shadow(0 12px 48px rgba(6,11,20,.65));
}
.dhero__role {
  margin-top: 12px;
  font-family: var(--label); font-weight: 500;
  font-size: 11px; letter-spacing: .30em; text-transform: uppercase;
  color: var(--stone-ink);
}
.dhero__role i { margin: 0 10px; color: #8A7442; font-style: normal; }
.dhero__acts {
  position: absolute; left: 0; right: 0; bottom: 7vh;
  display: flex; justify-content: center; gap: 18px;
  will-change: opacity;
}
.dhero__acts .act { flex: 0 1 340px; }
.dhero__cue {
  position: absolute; left: 0; right: 0; bottom: 26px;
  text-align: center; margin: 0;
  font-family: var(--label); font-weight: 500;
  font-size: 11px; letter-spacing: .28em; text-transform: uppercase;
  color: var(--gold-hi);
  text-shadow: 0 1px 6px rgba(6, 11, 20, .8);
  animation: cueBreathe 2.4s var(--ease) infinite;
  transition: opacity .7s var(--ease);
}
/* The hairline under the word, drawing the eye down. */
.dhero__cue::after {
  content: "";
  display: block;
  width: 1px; height: 26px;
  margin: 10px auto 0;
  background: linear-gradient(to bottom, var(--gold-hi), transparent);
}
/* No shaped wipe. The doorway is a tall rectangle, so the doorway itself is
   the transition: the held frame scales about the opening's measured centre
   (50.3% 50.9%) until its featureless glow owns the whole viewport, and a
   flat cover in the exact stone token cross-fades over it — the colour walk
   from the footage's golden glow to the token IS that cross-fade. */
.dhero__cover {
  position: absolute; inset: 0;
  background: var(--stone);
  opacity: 0;
  pointer-events: none;
}
/* The identity, landing on the light. The name arrives while the glow takes
   the screen and never leaves; the mark and the two actions join it once the
   screen is stone. It holds as the page's title card until the pin releases. */
.dhero__lockup {
  position: absolute; inset: 0;
  pointer-events: none;
}
.dhero__lockup-grid {
  height: 100%;
  max-width: 1180px; margin-inline: auto;
  padding: 0 var(--gut);
  display: grid; grid-template-columns: 1.05fr 0.95fr;
  align-items: end;
  column-gap: clamp(24px, 4vw, 64px);
}
.dhero__lockup-copy {
  align-self: center;
  text-align: left;
  /* biased down so the column reads against his torso, not his head */
  padding-top: 12vh;
}
.dhero__lockup-mark { width: clamp(132px, 11vw, 184px); height: auto; margin-bottom: clamp(24px, 4.4vh, 42px); }
.dhero__lockup-copy > *, .dhero__lockup-acts { opacity: 0; will-change: transform, opacity; }
/* He stands on the fold, full height, never behind the type. */
.dhero__lockup-figure {
  position: relative;
  align-self: end; justify-self: center;
  height: min(88vh, 760px);
  opacity: 0; will-change: transform, opacity;
}
.dhero__lockup-figure img {
  /* max-width:none is load-bearing: the global `img { max-width: 100% }`
     clamped his width while height:100% forced his height, which squeezed
     him to 82% of his true width (0.613 against a natural 0.750). */
  height: 100%; width: auto; max-width: none; display: block;
  /* ⚠️ NO filter: drop-shadow here. A per-pixel alpha filter on a cut-out is
     expensive and Safari composites it in stages — for a frame it paints the
     image's whole box before the silhouette resolves, which is the flash of
     "the original photo" Jean caught on an iPhone. He is grounded by a shadow
     on the floor beneath him instead, which costs nothing and reads better
     than a halo tracing his outline. */
}
.dhero__lockup-figure::after {
  content: "";
  position: absolute; left: 50%; bottom: -6px;
  width: 78%; height: 26px; transform: translateX(-50%);
  border-radius: 50%;
  background: radial-gradient(closest-side, rgba(26, 32, 41, .20), rgba(26, 32, 41, 0) 78%);
  pointer-events: none;
}
/* His name takes the phone hero's setting: the italic he asked for, the same
   weight, and enough soft ground shadow to lift it off the light. */
.dhero__lockup .display {
  font-size: clamp(42px, 4.3vw, 72px);
  line-height: 1.02;
  font-style: italic;
  font-weight: 500;
  color: var(--stone-ink);
  text-shadow:
    0 1px 2px rgba(237, 232, 221, .9),
    0 2px 10px rgba(120, 104, 74, .38),
    0 10px 40px rgba(120, 104, 74, .30);
}
.dhero__lockup .dhero__role { color: var(--stone-ink); margin-top: 18px; }
/* The actions the phone hero carries, in the phone hero's shape: stacked,
   wide, leading with the gold. */
.dhero__lockup-acts {
  margin-top: clamp(36px, 6vh, 56px);
  display: grid; gap: 14px;
  max-width: 420px;
}
.dhero__lockup-acts .act { width: 100%; box-shadow: 0 10px 30px rgba(26, 32, 41, .18); }
.dhero__lockup-acts .act--ghost {
  background: transparent; color: var(--stone-ink); border-color: var(--stone-ink);
}
.dhero__lockup-acts .act--ghost b { color: var(--stone-ink); }
.dhero__lockup-acts .act--ghost:hover { background: var(--stone-ink); color: var(--ink); }
.dhero__lockup-acts .act--ghost:hover b { color: var(--ink); }

/* ── the page below ── */
.dland-prose { max-width: 62ch; margin: clamp(22px, 3vw, 34px) auto 0; }
.dland-more { text-align: center; margin-top: clamp(22px, 3vw, 32px); }
.dland-guides { display: grid; gap: clamp(40px, 6vw, 72px); }
@media (min-width: 1040px), (orientation: portrait) and (min-width: 768px) { .dland-guides { grid-template-columns: 1fr 1fr; column-gap: clamp(48px, 6vw, 90px); } }
.dland-guides h2 { font-size: clamp(26px, 2.4vw, 38px); line-height: 1.14; }
.dland-guides .lede { margin-top: 16px; font-size: 17.5px; }
.dland-link { margin-top: 24px; }
/* .mini is written for the night panels; on stone its ink goes dark. */
.exp-land .mini li { color: var(--stone-soft); border-color: var(--stone-rule); }
.exp-land .mini li:last-child { border-bottom-color: var(--stone-rule); }
.exp-land .mini b { color: var(--stone-ink); }
.dland-reviews { margin-top: clamp(10px, 2vw, 24px); }
.exp-land .reel-strip { max-width: 1180px; margin-inline: auto; }
.exp-land .reel-strip img { border-color: var(--stone-rule); }

@media (prefers-reduced-motion: reduce) {
  .dhero { height: auto; }
  .dhero__pin { position: relative; }
  .dhero__cue { display: none; }
  .dhero__lockup { display: none; }
}

/* The landscape page is stone, but the body it sits in is the film's night —
   so the stone context is declared here, on the wrapper, not on the body.
   These mirror the on-stone rules the interior pages get from their body
   class, for exactly the pieces this page uses. */
.exp-land { color: var(--stone-ink); }
.exp-land .lede, .exp-land .prose { color: var(--stone-soft); }
.exp-land .quiet { color: var(--stone-ink); border-bottom-color: rgba(26, 32, 41, .28); }
.exp-land .quiet::after { color: var(--stone-ink); }
.exp-land .band--ink .lede { color: var(--ink-soft); }
.exp-land .band--ink .quiet { color: var(--ink); border-bottom-color: var(--gold-rule); }
.exp-land .band--ink .quiet::after { color: var(--gold); }
/* The reviews band opens with its own head here, so the tight opening the
   reviews PAGE uses (where the page head's rule sits right above the grid)
   does not apply. */
.exp-land .band:has(.reviews-grid) { padding-top: clamp(52px, 9vw, 104px); }

/* ── the landscape sections, round two (2026-08-24, Jean's live notes) ── */
/* The decade band carries him: his portrait beside the words, the same frame
   and scroll ride the About page uses. */
.dland-about {
  display: grid; gap: clamp(32px, 5vw, 64px); align-items: center;
  max-width: 1040px; margin-inline: auto;
}
@media (min-width: 1040px), (orientation: portrait) and (min-width: 768px) {
  .dland-about { grid-template-columns: minmax(240px, 300px) 1fr; align-items: stretch; }
  /* His picture and the words stand the same height — the words set it, the
     frame follows, the picture covers. */
  .dland-about .portrait-frame { height: 100%; aspect-ratio: auto; max-width: 300px; }
}
.dland-about h2 { font-size: clamp(30px, 3.2vw, 52px); line-height: 1.1; }

/* Each guide sits beside its own ask: the sellers guide with the valuation,
   the buyers guide with the search — the pairing Jean called for. The second
   pair mirrors so the page does not read as a template repeating. */
.dland-pair { display: grid; gap: clamp(44px, 6vw, 76px); align-items: start; }
@media (min-width: 1040px), (orientation: portrait) and (min-width: 768px) {
  .dland-pair { grid-template-columns: 1fr 1fr; column-gap: clamp(56px, 6.5vw, 110px); }
  .dland-pair--flip .dland-pair__guide { order: 2; }
}
.dland-pair .form { max-width: none; }
.dland-pair__guide h2 { font-size: clamp(26px, 2.4vw, 38px); line-height: 1.14; }
.dland-pair__guide .lede { margin-top: 16px; font-size: 17.5px; }
.dland-pair .panel__title { font-size: clamp(24px, 2.1vw, 34px); }
/* The forms live on stone here, so they take the stone form dress the
   valuation and looking PAGES get from their body class. */
.exp-land .field span { color: var(--stone-soft); }
.exp-land .field input, .exp-land .field textarea {
  color: var(--stone-ink); background: rgba(26,32,41,.04); border-bottom-color: var(--stone-rule);
}
.exp-land .field input::placeholder,
.exp-land .field textarea::placeholder { color: rgba(26,32,41,.38); }
.exp-land .field input:focus, .exp-land .field textarea:focus {
  border-bottom-color: var(--stone-ink); background: rgba(26,32,41,.07);
}
.exp-land .note { color: var(--stone-soft); }
.exp-land .form__done p { color: var(--stone-soft); }
.exp-land .panel__lede { color: var(--stone-soft); }

/* Only three cards, one per column: the column rules carry the separation,
   and without the per-card base rule their mismatched heights stop drawing
   three ragged underlines (Jean, 2026-08-24). */
.dland-reviews .review-card { border-bottom: 0; }

/* The guide labels sit gold over stone, a touch of air under them. */
.dland-guides .label { margin-bottom: 14px; }

/* Over the stone page the glass bar washed out — white links on light ground.
   Revealed, it takes the night dress the interior pages' bar reads as. */
@media (orientation: landscape), (min-width: 768px) {
  .has-film .topbar.is-grounded {
    background: var(--night-deep);
    border-bottom: 1px solid var(--ink-rule);
  }
}


/* The gate offers the way past: the guide is not hostage to the form. The
   skip stores the same unlock, so nobody meets this card twice on the same
   browser (Jean, 2026-08-24). */
.gate__skip { margin-top: 18px; text-align: center; }
.gate__skip a {
  font-family: var(--label); font-weight: 500; font-size: 11px;
  letter-spacing: .18em; text-transform: uppercase;
  color: var(--stone-soft); text-decoration: none;
  border-bottom: 1px solid rgba(26, 32, 41, .24);
  padding-bottom: 3px;
}
.gate__skip a:hover { color: var(--stone-ink); border-bottom-color: var(--stone-ink); }


/* The disclosure every US brokerage page carries. */
.foot__eho { letter-spacing: .06em; }

/* ════════════════════════════════════════════════════════════════════════
   LANDSCAPE ON A PHONE — the hybrid (2026-08-24, Jean's rotation review)
   A phone held sideways gets the landscape composition, but it has 390–430px
   of HEIGHT and sits under 1040px of width. Until now it inherited the
   desktop sizes wholesale and everything collided. These rules make the wide
   layout fit a short viewport; the desktop above 1040 is untouched.
   ════════════════════════════════════════════════════════════════════════ */

/* Section heads and closing blocks centre in the landscape experience at
   EVERY width, not only on a desktop — the heading was left while its own
   paragraph sat centred under it. */
@media (min-width: 1040px), (orientation: portrait) and (min-width: 768px) {
  .exp-land .sec-head,
  .exp-land .close-block { max-width: 720px; margin-inline: auto; text-align: center; }
  .exp-land .sec-head .lede,
  .exp-land .close-block .lede { margin-inline: auto; }
  .exp-land .close-block .acts-lead { margin-inline: auto; }
}
/* Below the desktop, the landscape page speaks the portrait phone's language:
   one column, everything ranged left. Half-centred and half-left was the
   thing that read as broken (Jean, 2026-08-24). */
/* Ranging left is the PHONE-in-landscape treatment, and it was reaching a
   tablet by width alone — which is why an iPad in portrait showed centred
   headings pulled back to the left edge of a very wide band. */
@media (orientation: landscape) and (max-width: 1039px) {
  .exp-land .sec-head,
  .exp-land .close-block,
  .exp-land .dland-prose { text-align: left; margin-inline: 0; }
  .exp-land .dland-more { text-align: left; }
}

@media (orientation: landscape) and (max-width: 1039px) {
  /* Stacked, the guide and its form ran together with nothing between them. */
  .dland-pair > form {
    border-top: 1px solid var(--stone-rule);
    padding-top: clamp(30px, 5vw, 46px);
  }
  .exp-land .band--ink .dland-pair > form { border-top-color: var(--ink-rule); }

  /* Three columns of quotes is unreadable at this height; the phone's own
     portrait page swipes, so this does too. */
  .dland-reviews {
    display: flex; columns: auto; column-rule: none;
    overflow-x: auto; scroll-snap-type: x mandatory;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
  }
  .dland-reviews::-webkit-scrollbar { display: none; }
  /* One review at a time. Showing a slice of the next one meant that by the
     third card a fragment of the second sat on the left, and the eye starts
     there — it read as a mistake rather than an invitation. */
  .dland-reviews .review-card {
    flex: 0 0 100%; scroll-snap-align: center;
    border-right: 0; border-bottom: 0;
    padding-inline: 0;
  }

}
/* Two labels asking to be told apart ("Swipe for more" next to "Read all")
   was worse than none. The dots say WHERE YOU ARE in the row; "Read all"
   says where else you can go. Same split the portrait page uses. */
.dland-reviews-bar {
  display: flex;
  align-items: center; gap: 14px;
  margin-top: 20px;
}
/* Dots alone say "there is more" only to someone already looking for it.
   The word says it outright. */
.dland-reviews-bar::before {
  content: "Swipe";
  font-family: var(--label); font-weight: 500;
  font-size: 9.5px; letter-spacing: .24em; text-transform: uppercase;
  color: var(--stone-soft);
}
.dland-dots { display: flex; gap: 7px; }
.dland-dots i {
  display: block; width: 6px; height: 6px; border-radius: 50%;
  background: rgba(26, 32, 41, .22);
  transition: background .3s var(--ease);
}
.dland-dots i.is-at { background: #8A7442; }
.dland-reviews-bar .quiet--sm { margin-left: auto; }

/* Wide enough for all three reviews to stand side by side: there is nothing
   to swipe and nowhere to be, so the position dots and the invitation both
   go — but the way OUT to the rest of them must not go with them. Hiding the
   whole bar above this width left the desktop page showing three reviews and
   no route to the other eight. */
@media (min-width: 1040px), (orientation: portrait) and (min-width: 768px) {
  .dland-reviews-bar { justify-content: center; }
  .dland-reviews-bar::before,
  .dland-dots { display: none; }
  .dland-reviews-bar .quiet--sm { margin-left: 0; }
}

/* Landscape: the portrait and the words carry equal weight instead of the
   text outweighing the picture. */
@media (orientation: landscape) and (max-width: 1039px) {
  /* The words lead and his portrait answers on the right — reading order runs
     left to right, and it matches the title card, where the copy is on the
     left and he stands on the right. */
  .about-grid {
    grid-template-columns: 1fr 1fr;
    grid-template-areas: "prose portrait" "facts facts";
    align-items: center;
  }
  .about-grid > .portrait-frame { grid-area: portrait; max-width: none; }
  .about-grid > .prose { grid-area: prose; }
  .about-facts { grid-area: facts; }
}

/* The title card has to fit a 390px-tall screen: the mark, his name, the role
   and both actions were sized for a desktop and ran under the bar. */
@media (orientation: landscape) and (max-height: 560px) {
  /* Two fr columns let the words and the man drift to opposite ends of the
     screen with a quarter of the width dead between them. Sized to their own
     content and centred as a pair, they read as one lockup. */
  .dhero__lockup-grid {
    padding-top: 54px;
    grid-template-columns: minmax(0, max-content) minmax(0, max-content);
    justify-content: center;
    column-gap: clamp(18px, 4vw, 40px);
  }
  .dhero__lockup-figure { justify-self: start; }
  .dhero__lockup-copy { padding-top: 0; }
  .dhero__lockup-mark { width: clamp(78px, 20vh, 108px); margin-bottom: clamp(8px, 2vh, 14px); }
  .dhero__lockup .display { font-size: clamp(26px, 9.5vh, 42px); }
  .dhero__lockup .dhero__role { margin-top: 8px; font-size: 9px; letter-spacing: .2em; }
  .dhero__lockup-acts { margin-top: clamp(14px, 3.4vh, 22px); gap: 10px; max-width: 300px; }
  .dhero__lockup-acts .act { padding: 11px 16px; font-size: 9.5px; letter-spacing: .16em; gap: 12px; }
  .dhero__lockup-figure { height: min(72vh, 320px); }
}

/* The guides: a landscape phone takes the narrow step layout, not the
   desktop's three columns — 852 and 932 both sat awkwardly across the old
   860 breakpoint. */
@media (orientation: landscape) and (min-width: 860px) and (max-width: 1039px) {
  .step { grid-template-columns: none; }
}
/* The numeral is set at line-height .86 — tighter than its own glyphs, so its
   ink spills out of its line box and, in a stacked row, lands on the heading
   underneath (Jean saw exactly this on iPhone Safari in landscape). Stacked,
   the numeral gets an honest line box and sits on its label's baseline. */
/* The stacked numeral is the narrow treatment; a tablet has room for the
   proper two-column step and should not inherit a phone's compromise. */
@media (max-width: 767px), (orientation: landscape) and (max-width: 1039px) {
  .step__n { display: flex; align-items: baseline; gap: 12px; padding-top: 0; }
  .step__n b { margin-top: 0; font-size: 34px; line-height: 1; }
  .step { row-gap: 14px; }
}

/* A band that hands off to a plate keeps its air — and takes exactly the
   air an INTERIOR plate gets (the movement's own padding-top), so the last
   step of a guide meets its picture with the same rhythm as step 3 and
   step 6. Zeroing this was wrong: it made the last step and the index's
   sections run straight into the picture. */
.band:has(+ .movement__band) { padding-bottom: clamp(40px, 6.5vw, 88px); }

/* Quieter than the form it sits under — the way past should be available,
   not advertised. */
.gate__skip a { font-size: 10px; letter-spacing: .16em; }


/* ── RULES HAVE RANKS (2026-08-24) ───────────────────────────────────────
   Stacked on a landscape phone the guide summaries printed five full-width
   lines in a row — three between steps, one closing the list, one over the
   form — all the same weight, so nothing read as a boundary and the whole
   block looked boxed in. The rank is now legible:

     · a full-width rule = a boundary between two different things
       (the guide list and the form that follows it)
     · an inset, lighter rule = one item from the next inside a list
     · no rule at all where the list simply ends — the section boundary
       below already does that job
   ------------------------------------------------------------------- */
.exp-land .mini li {
  border-top-color: rgba(26, 32, 41, .10);
  margin-left: 40px;
  padding-left: 0;
}
.exp-land .mini li::before { left: -40px; }
.exp-land .mini li:last-child { border-bottom: 0; }
@media (max-height: 560px) and (orientation: landscape) {
  .exp-land .mini li { margin-left: 40px; padding-left: 0; }
}


/* ── the legal pages ──────────────────────────────────────────────────────
   A policy is a list of separate questions, not one long piece of prose, and
   it was reading as the second: every heading pressed against the paragraph
   above it with the same air between "Cookies and tracking" and the sentence
   before it as between two sentences of the same thought. Nobody reads a page
   like that, they scan it, and scanning needs edges.

   So each part gets real air and a hairline above it — the boundary rule,
   because these are genuinely different things — and the first one has
   neither, because a list does not open with a divider. The measure is held
   at 68 characters, which is where this size stops being comfortable. */
.legal { max-width: 68ch; margin-inline: auto; }
.legal h2 {
  font-size: clamp(21px, 2.4vw, 27px);
  line-height: 1.2;
  letter-spacing: -.01em;
  margin: 0 0 14px;
}
.legal h2 + p { margin-top: 0; }
.legal p { margin: 0 0 15px; }
.legal p:last-child { margin-bottom: 0; }
.legal h2 ~ h2 {
  margin-top: clamp(40px, 5.5vw, 64px);
  padding-top: clamp(26px, 3.6vw, 40px);
  border-top: 1px solid rgba(26, 32, 41, .10);
}
/* Centred on the wide layouts, where a left-ranged column inside a much wider
   band reads as a mistake rather than a choice. */
@media (min-width: 768px) {
  .legal { text-align: left; }
}


/* ── the title card on a tablet held upright ──────────────────────────────
   ⭐ WORDS, THEN HIM, THEN THE ACTIONS — and he stands on stone.

   The side-by-side lockup is drawn for 16:9: he holds one column, the words
   hold the other. In a 3:4 frame it has nowhere to go. Sizing him to fill the
   height made him wider than his column, and because the columns are fr units
   his intrinsic width then ATE the other one — the words collapsed to 299px
   while he took the room, on the one screen whose whole job is the message.

   A tall frame wants a tall composition, and a tall composition has an order:
   the message, the person, the ask. That is also the right order to read in —
   who this is, who he is, what to do — and it puts the actions where a thumb
   already is on a tablet.

   The other half of this is the FLOOR. He was grounded flush to the bottom of
   the hero with the entry-hall photograph starting on the very next pixel, so
   his body appeared to grow out of that room: two images touching read as one
   collage, not as a portrait. Now the actions and real air sit beneath him, so
   he stands on stone and the band that follows is plainly a different picture.

   His height is capped against the VIEWPORT WIDTH as well as his row, because
   his width follows his height and that is where a 3:4 cut-out stops fitting
   between the margins. */
@media (orientation: portrait) and (min-width: 768px) {
  .dhero__lockup-grid {
    grid-template-columns: minmax(0, 1fr);
    /* mark · name · role · him · actions */
    grid-template-rows: auto auto auto minmax(0, 1fr) auto;
    align-items: stretch;
    justify-items: center;
    text-align: center;
    row-gap: 0;
    column-gap: 0;
    /* The bar is fixed and 73px tall, and it is not in the flow — so a bare
       padding-top measures from the top of the SCREEN, not from under the
       menu. The mark was landing 9px below it, near enough to touch. Clearing
       the bar first and then adding the same air that sits under the actions
       makes the card breathe evenly top and bottom. */
    padding-top: calc(76px + clamp(30px, 4.4vh, 60px));
    padding-bottom: clamp(30px, 4.4vh, 60px);
  }
  /* The copy box steps aside so its parts become rows of the grid; the reveal
     still runs on the same children, so the choreography is untouched. */
  .dhero__lockup-copy { display: contents; }

  /* Sized for the room a tablet actually has. The desktop clamp is keyed to
     vw, and 4.3vw of a narrow-but-tall screen is a headline that reads as
     timid: the name came out 256px wide on a 1024px screen, smaller than the
     buttons under it. The hero's headline should be the largest thing on it. */
  .dhero__lockup-mark { width: clamp(148px, 17vw, 190px); margin: 0 0 clamp(16px, 2.2vh, 28px); }
  .dhero__lockup .display { margin: 0; font-size: clamp(48px, 6.6vw, 74px); }
  .dhero__lockup .dhero__role { margin-top: 14px; justify-content: center; }

  .dhero__lockup-figure {
    align-self: end;
    height: min(100%, 74vw);
    margin-top: clamp(20px, 3vh, 44px);
  }

  .dhero__lockup-acts {
    order: 1;                      /* after him, not tucked under the name */
    width: min(460px, 86vw);
    margin: clamp(24px, 3.4vh, 48px) auto 0;
  }
}
