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

:root {
  --cream:  #F5F2ED;   /* page background, carried over from the live site */
  --brown:  #583B1F;   /* body + brand text */
  --olive:  #666439;   /* display headings */
  --line:   rgba(88, 59, 31, .16);
  --muted:  rgba(88, 59, 31, .62);

  --display: "Marcellus", Georgia, "Times New Roman", serif;
  --body:    "PT Serif", Georgia, "Times New Roman", serif;

  /* one definition of the frosted glass, shared by the hero panel and the scrolled
     header so the two can never drift apart */
  --frost-bg:   rgba(245, 242, 237, .55);
  --frost-blur: blur(22px) saturate(135%);
  --frost-line: rgba(88, 59, 31, .10);

  /* ---- golden-ratio spacing scale (phi = 1.618); each step is phi x the last ---- */
  --s0: 0.382rem;   /* phi^-2 */
  --s1: 0.618rem;   /* phi^-1 */
  --s2: 1rem;       /* base   */
  --s3: 1.618rem;   /* phi    */
  --s4: 2.618rem;   /* phi^2  */
  --s5: 4.236rem;   /* phi^3  */
  --s6: 6.854rem;   /* phi^4  */
  --s7: 11.089rem;  /* phi^5  */

  /* Page gutter: 7vw governs from roughly 370px upward (~101px at 1440), while the
     clamp floor only takes over on narrow phones -- keeping the tighter mobile
     margin that reads better on a small screen. */
  /* The env() terms are 0 everywhere except a notched phone in landscape, where
     viewport-fit=cover lets the page run under the sensor housing and the gutter
     has to clear it. Symmetric on purpose, so .bleed still cancels it exactly. */
  --gut:   max(clamp(var(--s3), 7vw, var(--s7)),
               env(safe-area-inset-left, 0px), env(safe-area-inset-right, 0px));
  --stack: clamp(var(--s5), 9vw, var(--s7));

  /* Header padding in a phi:1 ratio at both ends of the range: s3/s2 on mobile,
     s4/s3 on desktop, each pair being 1.618:1. */
  --head-pt: clamp(var(--s3), 3vw,    var(--s4));
  --head-pb: clamp(var(--s2), 1.85vw, var(--s3));
  --header-h: calc(var(--head-pt) + var(--head-pb) + 1.75rem);
}

html { scroll-behavior: smooth; }
body {
  margin: 0;
  background: var(--cream);
  color: var(--brown);
  font: 400 clamp(1rem, .96rem + .2vw, 1.0625rem)/1.75 var(--body);
  -webkit-font-smoothing: antialiased;
  /* clip, not hidden: hidden makes the body a scroll container, which iOS Safari
     handles badly around its bars (the page must stay the document's own scroll).
     clip stops sideways overflow without that. */
  overflow-x: clip;
}

img { display: block; max-width: 100%; }
a { color: inherit; }

.wrap { padding-inline: var(--gut); }

/* ---------- header ---------- */
header {
  position: fixed;
  inset: 0 0 auto 0;
  z-index: 50;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s3);
  padding: var(--head-pt) var(--gut) var(--head-pb);
  background: transparent;
  transition: background .45s ease, box-shadow .45s ease, backdrop-filter .45s ease;
}
header.scrolled {
  /* same frosted glass as the hero panel, not an opaque bar */
  background: var(--frost-bg);
  -webkit-backdrop-filter: var(--frost-blur);
  backdrop-filter: var(--frost-blur);
  box-shadow: 0 1px 0 var(--frost-line);
}
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  header.scrolled { background: rgba(245, 242, 237, .92); }
}
header .brand img { width: clamp(8.71rem, 14.16vw, 11.42rem); height: auto; }   /* +2% at every stop, 2026-10-15 */
/* phones carry a smaller wordmark */
@media (max-width: 47.99rem) {
  header .brand img {
    width: 10.24rem;            /* +2%, 2026-10-15 */
    /* The wordmark's ampersand hangs below the lettering, so the box's centre is
       not the letters' centre: measured from logo.png, the body of the lettering
       is centred 9.45% of the image height above the box centre. Shifting by that
       lines the letters up with the menu button. (9.35% for the 1326px artwork;
       re-measured for the 2652px one, 2026-10-15.) */
    transform: translateY(9.45%);
  }
}
header nav {
  display: flex;
  gap: clamp(var(--s2), 2.6vw, var(--s4));
  font-family: var(--display);
  font-size: clamp(.88rem, .814rem + .275vw, 1.045rem);
  letter-spacing: .02em;
}
header nav a { text-decoration: none; padding-block: var(--s0); position: relative;
  line-height: calc(1em + 4px); }
header nav a::after {
  content: ""; position: absolute; left: 0; right: 0; bottom: 0;
  height: 1px; background: rgba(88, 59, 31, .55); transform: scaleX(0);
  transform-origin: left; transition: transform .35s ease;
}
header nav a:hover::after, header nav a:focus-visible::after { transform: scaleX(1); }

/* ---------- type ---------- */
h1, h2, h3 {
  font-family: var(--display);
  font-weight: 400;
  color: var(--olive);
  letter-spacing: -.02em;
  line-height: calc(1.08em + 4px);
  margin: 0;
  text-wrap: balance;
}
h1 { font-size: clamp(2.3rem, 1.35rem + 4.2vw, 4.1rem); }
h2 { font-size: clamp(1.9rem, 1.25rem + 2.9vw, 3.1rem); }
h3 { font-size: clamp(1.15rem, 1rem + .7vw, 1.5rem); color: var(--brown); }
p  { margin: 0 0 1.15em; line-height: calc(1.75em + 2px); }
p:last-child { margin-bottom: 0; }

.eyebrow {
  font-family: var(--display);
  font-size: .76rem;
  letter-spacing: .26em;
  text-transform: uppercase;
  color: var(--muted);
  margin: 0 0 var(--s3);
}

section { scroll-margin-top: var(--header-h); }
.section-pad { padding-block: var(--stack); }

/* ---------- home: full-screen gallery ----------
   The landscape renderings fill the screen and cross-fade on their own; the
   header, the headline and the intro sit over them in white. Two soft scrims --
   one under the header, one under the copy -- keep the white type legible on the
   brighter frames without dimming the photographs as a whole. */
.hero-gallery {
  position: relative;
  height: 100svh;
  min-height: 32rem;
  overflow: hidden;
  background: #2a2119;              /* behind the first frame while it loads */
  color: #fff;
}
.hero-gallery .slides { position: absolute; inset: 0; }
/* the gallery's contents, as one layer that stays put while the page slides over it */
.hero-stage { position: absolute; inset: 0; }
.slide {
  position: absolute;
  inset: 0;
  margin: 0;
  opacity: 0;
  transition: opacity 1.4s ease;
}
.slide.is-active { opacity: 1; }
.slide img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* a slow settle while each frame is up: starts a touch close, eases back to the
     composed view. Transform on its own layer, so it costs nothing per frame. */
  transform: scale(1.06);
  transition: transform 7.5s cubic-bezier(.25, .1, .25, 1);
}
.slide.is-active img { transform: scale(1); }

.hero-stage::before,
.hero-stage::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  z-index: 1;
  pointer-events: none;
}
.hero-stage::before {
  top: 0;
  height: 11rem;
  background: linear-gradient(rgba(20, 14, 9, .42), rgba(20, 14, 9, 0));
}
.hero-stage::after {
  bottom: 0;
  height: 62%;
  background: linear-gradient(rgba(20, 14, 9, 0), rgba(20, 14, 9, .30) 45%, rgba(20, 14, 9, .62));
}

.hero-overlay {
  position: absolute;
  inset: auto 0 0 0;
  z-index: 2;
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--s3) var(--s5);
  padding-bottom: clamp(var(--s4), 6vh, var(--s6));
}
.hero-copy { max-width: 46rem; }
.hero-copy h1 {
  color: #fff;
  font-size: clamp(2.3rem, 1.35rem + 4.2vw, 4.6rem);
  letter-spacing: -.015em;
  margin-bottom: var(--s3);
}
.hero-copy .intro {
  color: rgba(255, 255, 255, .88);
  max-width: 32rem;
  font-size: 1.0625rem;
}

.gallery-meta {
  display: flex;
  align-items: center;
  gap: var(--s3);
}
/* ---- slide markers: hairline dashes (2026-10-23) ----
   A quiet row of fine white dashes, after the numbered clock discs. At rest each
   dash is a short, soft-white line; the current one lengthens a touch and a solid
   white fill draws itself across it, left to right, over exactly the time that
   frame is shown -- the same clock as before, kept discreet. */
.dots { display: flex; align-items: center; gap: .9rem; height: 2.1rem; }

.marker {
  position: relative;
  display: grid;
  place-items: center;
  padding: 0;
  border: 0;
  background: none;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
/* a generous touch target around a small mark */
.marker::before { content: ""; position: absolute; inset: -.7rem -.4rem; }

.marker .ring {
  --d: 1.3rem;
  position: relative;
  display: block;
  width: var(--d);
  height: 2px;
  border-radius: 1px;
  background: rgba(255, 255, 255, .38);
  transition: width .6s cubic-bezier(.22, .61, .36, 1),
              background-color .4s ease;
}
.marker:hover .ring { background-color: rgba(255, 255, 255, .62); }

/* the fill: solid white, drawn across the lengthened dash */
.marker .ring::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  background: #fff;
  opacity: 0;
  transform: scaleX(0);
  transform-origin: left center;
  transition: opacity .3s ease, transform .45s cubic-bezier(.22, .61, .36, 1);
}

/* current */
.marker[aria-current="true"] .ring { --d: 2.4rem; }
.marker[aria-current="true"] .ring::after { opacity: 1; transform: scaleX(1); }
.marker[aria-current="true"].run .ring::after {
  transition: opacity .3s ease;
  animation: line-fill var(--interval, 6s) linear forwards;
}
@keyframes line-fill { from { transform: scaleX(0); } to { transform: scaleX(1); } }

.marker:focus-visible { outline: none; }
.marker:focus-visible .ring { outline: 1px solid #fff; outline-offset: 4px; }

/* Phones: the gallery runs edge to edge, under Safari's own bars.
   - Top: Safari only draws page pixels behind the status bar / Dynamic Island
     once the page is scrolled -- at scroll position 0 it paints that strip itself.
     So the gallery starts --runway above the first screen, and site.js opens the
     page scrolled down by exactly that much: the first screen looks the same, but
     the strip behind the clock is now photograph.
   - Bottom: 100lvh (the largest viewport) instead of 100svh carries the photograph
     down behind the floating toolbar; the copy is lifted by the difference so it
     still clears the toolbar. */
@media (max-width: 47.99rem) {
  /* --runway: the photograph above the first screen; the page opens scrolled down
     by exactly this much, so it sits behind the status bar (site.js reads it, so
     it must stay a plain length).
     --past-bottom: the photograph below the first screen. Opening the page
     --runway down raises the gallery's end by the same amount, and Safari's
     floating toolbar sits over the bottom of the screen, so the photograph must
     run on past the screen's bottom edge by more than both, or the next section
     shows under the toolbar. */
  .hero-gallery {
    --runway: 72px;
    --past-bottom: 110px;
    height: calc(100lvh + var(--runway) + var(--past-bottom));
  }
  .hero-stage::before { height: calc(11rem + var(--runway)); }
  /* no rubber-band past the top (or bottom) of the home page on phones: pulling
     down stops at the top of the band above the opening position, and site.js
     glides back to the opening position from there */
  html:has(body.has-gallery) { overscroll-behavior-y: none; }
  /* The copy and the markers sit just above the first screen's bottom edge. With
     the page opened --runway down, that edge (100lvh) is above Safari's floating
     address bar, so only a small gap is needed. (100lvh - 100svh) adds the
     toolbar's height wherever the viewport units report one. */
  /* the copy and the markers belong to the first screen, so they sit
     --past-bottom up from the gallery's own bottom edge */
  .hero-overlay {
    bottom: var(--past-bottom);
    flex-direction: column;
    align-items: flex-start;
    padding-bottom: calc(.5rem + (100lvh - 100svh));
  }
  .gallery-meta { align-self: center; }
  .hero-copy h1 { font-size: clamp(1.75rem, 9.1vw, 2.3rem); }
}

@media (prefers-reduced-motion: reduce) {
  .slide, .slide img { transition: none; }
  .slide img { transform: none; }
  .marker .ring::after { animation: none !important; }
  .marker .ring { transition: none; }
}

/* the header over the gallery: white; it returns to the site-wide olive bar once
   the gallery has scrolled away. Nav in sentence case with a touch of tracking
   (all caps were tried and dropped). */
body.has-gallery header nav {
  font-size: clamp(.72rem, .68rem + .15vw, .8rem);
  letter-spacing: .03em;
}
body.has-gallery header:not(.scrolled) nav a { color: #fff; }
body.has-gallery header:not(.scrolled) nav a::after { background: rgba(255, 255, 255, .8); }
body.has-gallery header:not(.scrolled) .brand img { filter: brightness(0) invert(1); }
body.has-gallery header:not(.scrolled) .menu-toggle { color: #fff; }
/* the rubber-band strip above the page and the status-bar strip at rest show the
   root colour, so on the home page it matches the gallery, not the cream */
/* The page colour behind the home page. iOS fades the top of the screen, just
   under the clock, toward this colour -- dark, it disappears into the
   photograph; white, it read as a white haze over it. While the menu sheet is
   open it switches to the sheet's cream, so anything Safari fills from it matches
   the sheet. */
html:has(body.has-gallery),
body.has-gallery { background-color: #2a2119; }
html:has(body.has-gallery.menu-open),
body.has-gallery.menu-open { background-color: var(--cream); }
/* (not the portfolio's fixed "back to the house" button, which floats over the page) */
body.has-gallery main > :not(.hero-gallery):not(.to-house),
body.has-gallery footer { background-color: var(--cream); }

/* ---------- media / parallax ---------- */
.media {
  position: relative;
  overflow: hidden;
  background: #e7e1d8;
}
.media.full { height: clamp(26rem, 78svh, 54rem); }

/* Only 115% of the frame height (pulled up 7.5%), so object-fit: cover barely has
   to scale the image up -- the composition stays close to the original crop. That
   caps parallax travel at +/-7.5% of the frame, which is the deliberate trade:
   less movement in exchange for almost no zoom. Keep JS travel under 0.075. */
.media img {
  position: absolute;
  top: -7.5%;
  left: 0;
  width: 100%;
  height: 115%;
  object-fit: cover;
  will-change: transform;
  transform: translate3d(0, 0, 0);
}
.bleed { margin-inline: calc(var(--gut) * -1); }

figure { margin: 0; }
figcaption {
  margin-top: var(--s2);
  font-family: var(--display);
  font-size: .8rem;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--muted);
}

.pair { display: grid; gap: clamp(var(--s2), 3vw, var(--s4)); grid-template-columns: 1fr; }
@media (min-width: 48rem) { .pair { grid-template-columns: 1fr 1fr; } }
.pair .media { aspect-ratio: 4 / 3; height: auto; }
.single-ratio { aspect-ratio: 16 / 9; height: auto; }
.media.portrait { aspect-ratio: 2 / 3; height: auto; }

.gallery > * + * { margin-top: clamp(var(--s4), 6vh, var(--s6)); }

/* ---------- content blocks ---------- */
.split { display: grid; gap: clamp(var(--s3), 5vw, var(--s5)); grid-template-columns: 1fr; }
@media (min-width: 56rem) {
  .split { grid-template-columns: 1fr 1fr; }
  .split.offset > :first-child { padding-right: clamp(0rem, 4vw, var(--s4)); }
}

.band { display: grid; gap: clamp(var(--s3), 5vw, var(--s5)); grid-template-columns: 1fr; align-items: center; }
@media (min-width: 56rem) {
  /* 1 : 1.618 -- image column to copy column */
  .band { grid-template-columns: minmax(0, 1fr) minmax(0, 1.618fr); }
  .band > .copy { padding-left: clamp(0rem, 3vw, var(--s4)); }
}
.band .copy h2 { margin-bottom: var(--s3); }

.rule { height: 1px; background: var(--line); border: 0; margin: 0; }

.pagehead { padding-block: calc(var(--header-h) + var(--stack)) 0; }
.pagehead .lead { max-width: 38rem; margin-top: var(--s3); }

/* ---------- contact ---------- */
.cta {
  display: inline-flex;
  align-items: center;
  gap: var(--s1);
  font-family: var(--display);
  font-size: .92rem;
  letter-spacing: .12em;
  text-transform: uppercase;
  text-decoration: none;
  color: var(--cream);
  background: var(--brown);
  padding: var(--s2) var(--s4);
  border-radius: 999px;
  transition: background .3s ease, transform .3s ease;
}
.cta:hover, .cta:focus-visible { background: var(--olive); transform: translateY(-2px); }

/* ---------- footer ---------- */
footer {
  padding: var(--s5) var(--gut) var(--s4);
  border-top: 1px solid var(--line);
}
.footer-row {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--s3);
  align-items: center;
  justify-items: center;
  text-align: center;
}
.footer-meta { display: grid; gap: var(--s3); justify-items: center; }

@media (min-width: 48rem) {
  /* equal 1fr side columns so the copyright lands on the true page centre,
     not merely centred within whatever space is left over */
  .footer-row {
    grid-template-columns: 1fr auto 1fr;
    gap: var(--s4);
    text-align: left;
  }
  .footer-brand { justify-self: start; }
  .colophon     { justify-self: center; }
  .footer-meta  { justify-self: end; grid-auto-flow: column; gap: var(--s5); justify-items: start; }
}
footer img { width: 9.69rem; height: auto; }   /* +2%, 2026-10-15 */
footer h4 {
  font-family: var(--display); font-weight: 400; color: var(--brown);
  font-size: .78rem; letter-spacing: .2em; text-transform: uppercase; margin: 0 0 var(--s1);
}
footer a { text-decoration: none; border-bottom: 1px solid var(--line); }
footer a:hover { border-color: currentColor; }

/* sits inside the footer row, on the same line as the logo / Location / Follow */
.colophon {
  margin: 0;
  text-align: center;
  font-size: .75rem;
  letter-spacing: .08em;
  color: var(--muted);
}
@media (min-width: 48rem) { .colophon { justify-self: center; } }

/* ---------- reveal ---------- */
.reveal { opacity: 0; transform: translateY(1.5rem); transition: opacity .9s ease, transform .9s cubic-bezier(.22,.61,.36,1); }
.reveal.in { opacity: 1; transform: none; }
.reveal.d1 { transition-delay: .1s; }
.reveal.d2 { transition-delay: .2s; }

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  .reveal { opacity: 1; transform: none; transition: none; }
  /* no parallax: sit the image flush in its frame instead of offset by the travel margin */
  .media img { position: static; height: 100%; transform: none !important; }
  .cta { transition: none; }
}

:focus-visible { outline: 2px solid var(--olive); outline-offset: 3px; }
.skip {
  position: absolute; left: -9999px; top: 0; z-index: 100;
  background: var(--brown); color: var(--cream); padding: var(--s1) var(--s3);
}
.skip:focus { left: var(--gut); top: var(--s1); }

/* ---------------- mobile menu ---------------- */

.menu-toggle {
  display: none;              /* desktop: the nav links are shown instead */
  position: relative;
  z-index: 60;
  width: 2.75rem;
  height: 2.75rem;
  margin-right: -0.6rem;      /* optically align the bars with the page gutter */
  padding: 0;
  border: 0;
  background: none;
  color: var(--brown);
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}

/* Three bars, each centred on the button so every open/closed position is just a
   transform from the same origin -- which is what makes the morph interpolate
   cleanly instead of jumping. */
.menu-toggle .bar {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 24px;
  height: 1.5px;
  margin: -0.75px 0 0 -12px;
  background: currentColor;
  border-radius: 1px;
  transform-origin: 50% 50%;
  transition: transform .45s cubic-bezier(.65, 0, .35, 1);
}

/* closed: evenly stacked */
.menu-toggle .bar-1 { transform: translateY(-7px); }
.menu-toggle .bar-2 { transform: translateY(0); }
.menu-toggle .bar-3 { transform: translateY(7px); }

/* open: the top two bars swing into a roof peak, the third drops to be the floor,
   so the three strokes read as a house */
/* Roof spans the full 24px bar width: each slope runs 12px across and 12px up, so
   its length is 12*sqrt(2) = 17px, i.e. scaleX 17/24 = .707 at 45deg. The floor
   keeps its full width and drops clear of the eaves, leaving the gap that reads
   as walls. */
.menu-toggle[aria-expanded="true"] .bar-1 {
  transform: translate(-6px, -6px) rotate(-45deg) scaleX(.707);
}
.menu-toggle[aria-expanded="true"] .bar-2 {
  transform: translate(6px, -6px) rotate(45deg) scaleX(.707);
}
/* floor sits a clear 10px below the eaves and is slightly narrower than the roof
   footprint -- the gap is what implies walls, otherwise the glyph reads as an
   arrow above a rule rather than a house */
.menu-toggle[aria-expanded="true"] .bar-3 {
  transform: translate(0, 10px) scaleX(.85);
}

@media (max-width: 47.99rem) {
  .menu-toggle { display: block; }

  /* the brand and the button stay above the overlay so the header reads normally
     while the menu is open */
  header .brand { position: relative; z-index: 3; }

  /* full-screen frosted overlay, links centred */
  header nav {
    position: fixed;
    inset: 0;
    z-index: 1;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    gap: clamp(var(--s3), 5vh, var(--s5));
    padding: var(--gut);
    background: var(--frost-bg);
    -webkit-backdrop-filter: var(--frost-blur);
    backdrop-filter: var(--frost-blur);
    opacity: 0;
    visibility: hidden;
    pointer-events: none;
    transition: opacity .4s ease, visibility 0s linear .4s;
  }
  header nav.open {
    opacity: 1;
    visibility: visible;
    pointer-events: auto;
    transition: opacity .4s ease, visibility 0s linear 0s;
  }

  header nav a {
    font-size: clamp(1.75rem, 9vw, 2.75rem);
    letter-spacing: .01em;
    padding-block: var(--s1);
    /* each link eases up slightly behind the fade; staggered for a bit of life */
    opacity: 0;
    transform: translateY(.75rem);
    transition: opacity .45s ease, transform .45s cubic-bezier(.22, .61, .36, 1);
  }
  header nav.open a { opacity: 1; transform: none; }
  header nav.open a:nth-child(1) { transition-delay: .08s; }
  header nav.open a:nth-child(2) { transition-delay: .14s; }
  header nav.open a:nth-child(3) { transition-delay: .20s; }

  /* the underline hover affordance is pointless on touch and looks odd this large */
  header nav a::after { content: none; }

  @supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
    header nav { background: rgba(245, 242, 237, .97); }
  }
}

@media (prefers-reduced-motion: reduce) {
  .menu-toggle .bar { transition: none; }
  header nav { transition: none; }
}

/* page is locked while the full-screen menu is open, so the content behind it
   cannot scroll away under the overlay */
body.menu-open { overflow: hidden; }
/* Phones: NOT overflow: hidden -- on iOS that makes Safari give up its full-screen
   layout and paint its bars in the page colour (the bands above and below the
   open menu). The sheet covers the whole screen, so blocking touch on the sheet
   itself is enough to keep the page behind it still. */
@media (max-width: 47.99rem) {
  body.menu-open { overflow: visible; }
  header nav.open { touch-action: none; overscroll-behavior: contain; }
}

@media (prefers-reduced-motion: reduce) {
  header nav a { transition: none; opacity: 1; transform: none; }
}

/* ---------- overscroll canvas ----------
   iOS (and others) paint the rubber-band area past the top and bottom of the page
   with the ROOT element's background, not the body's. body was cream but html was
   not, so overscrolling exposed white. Setting it on html makes that space match
   the site. Note: only a colour can appear there -- it is outside the document, so
   no amount of extending content upward will fill it. */
html { background-color: var(--cream); }



/* ---------- mobile scroll performance ----------
   Three expensive things were stacked here. In order of cost:

   1. `background-attachment: fixed` makes the browser repaint the element against
      a viewport-anchored image every single frame, and iOS ignores it outright --
      which also made the fill collapse to a near-flat patch there, because `cover`
      was then sizing against the tiny element box instead of the viewport. Pinning
      the size to the viewport fixes both the jank and the flat fill.
   2. A 22px backdrop blur across a full-width panel is heavy on phone GPUs, and it
      has to be recomputed on every frame the panel slides.
   3. `will-change: transform` on every gallery image promotes dozens of permanent
      compositor layers, which exhausts GPU memory on phones. translate3d already
      promotes the few that are actually moving. */
@media (max-width: 47.99rem) {
  :root { --frost-blur: blur(12px) saturate(130%); }

  .media img { will-change: auto; }
}

/* ---------- iOS Safari: let the page run under the status bar ----------
   Safari 26+ decides what to paint behind the status bar / Dynamic Island by
   hit-testing a point 4px below the top of the viewport, at its horizontal centre
   (WebKit: LocalFrameView::fixedContainerEdges). If that point lands on a
   full-width fixed or sticky element, Safari covers the whole status-bar strip
   with ONE SOLID COLOUR -- the element's background, or, when the element has a
   backdrop-filter, the page background. That was the hard cream band: the header
   sat at top: 0, so the photographs could never scroll up underneath the clock.

   If the point lands on nothing fixed, there is no colour strip at all and the
   page simply scrolls on under the status bar. So on phones the header floats
   clear of that point, as a detached glass bar. Two things must stay true:

     1. the header's box starts BELOW 4px (--head-float), and nothing inside it
        reaches above that;
     2. the glass lives on ::before, not on the fixed element itself.

   (2) also fixes a second bug: backdrop-filter on the header made it the
   containing block for the position: fixed menu overlay, so opening the menu
   after scrolling confined the "full-screen" overlay to the header's own box.

   The logo and the menu button do not move: the padding gives back exactly what
   the offsets take, in both states, so nothing shifts when the bar appears. */
@media (max-width: 47.99rem) {
  /* 6px: as high as the bar can sit while still clearing Safari's 4px test point.
     Tight vertical padding -- the 44px menu button sets the bar's height. */
  :root { --head-float: 6px; --head-side: calc(var(--gut) / 2); }

  /* Full-width band. The header's own box starts --head-float down so that, at
     rest, nothing of it covers Safari's test point 4px from the top (the photo
     then shows behind the clock); the bar itself reaches up to the very top
     of the screen through its negative top. */
  header {
    inset: var(--head-float) 0 auto 0;
    padding: .375rem var(--gut) .375rem;
    border-radius: 0;
  }

  /* nothing on the fixed element itself */
  header.scrolled {
    background: transparent;
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
    box-shadow: none;
  }

  header::before {
    content: "";
    position: absolute;
    inset: 0;
    z-index: -1;                /* above the header's own box, below its children */
    border-radius: inherit;
    pointer-events: none;
    background: var(--frost-bg);
    -webkit-backdrop-filter: var(--frost-blur);
    backdrop-filter: var(--frost-blur);
    box-shadow: 0 0 0 1px var(--frost-line), 0 .5rem 1.5rem rgba(88, 59, 31, .10);
    opacity: 0;
    transition: opacity .45s ease;
  }
  header.scrolled::before { opacity: 1; }
  /* the full-screen menu is its own sheet of glass; a second pane behind it
     would show through as a ghost rectangle */
  body.menu-open header::before { opacity: 0; }

  @supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
    header.scrolled { background: transparent; }
    header::before { background: rgba(245, 242, 237, .92); }
  }

}

@media (max-width: 47.99rem) and (prefers-reduced-motion: reduce) {
  header::before { transition: none; }
}

/* ---------- scroll-linked motion without script ----------
   On iOS the page is scrolled by the compositor and the script is only told
   afterwards, so a transform written from a scroll handler is always a frame
   behind the content around it. At 1x that is invisible; on the hero panel, which
   travels at nearly twice the scroll speed, it reads as stutter. Scroll-driven
   animations hand the same motion to the browser, which keys it to the scroll
   position directly. site.js stands down wherever these are
   supported, and keeps its scripted transforms where they are not.

   The numbers mirror site.js: the panel adds 0.9x to its own scroll, and the
   images drift by 0.055 / 0.07 of their frame -- written here as a share of the
   image's own height, which is 115% of the frame (0.055 / 1.15, 0.07 / 1.15). */
@keyframes frame-drift {
  from { transform: translate3d(0, -4.78%, 0); }
  to   { transform: translate3d(0,  4.78%, 0); }
}
@keyframes frame-drift-strong {
  from { transform: translate3d(0, -6.09%, 0); }
  to   { transform: translate3d(0,  6.09%, 0); }
}

/* every width: desktop moved onto the same path once it proved smoother on phones */
@supports (animation-timeline: scroll()) {
  /* the frame is the subject, not the image: .media clips its overflow, which
     would otherwise make it the image's own (non-scrolling) scroll container */
  .media[data-parallax] { view-timeline: --frame block; }
  .media[data-parallax] img {
    animation: frame-drift linear both;
    animation-timeline: --frame;
  }
  .media[data-parallax="strong"] img { animation-name: frame-drift-strong; }
  .media[data-parallax-reverse] img { animation-direction: reverse; }
}

@media (prefers-reduced-motion: reduce) {
  .media img { animation: none !important; }
}

/* ---------- detached bar: solid olive, solid white marks (2026-10-23) ----------
   Once the header detaches it becomes a solid bar in the brand's olive, and the
   wordmark, the nav and the menu button sit on it in solid white -- the same
   white they carry over the gallery photographs. (History: the bar began as a
   white knockout -- the marks were windows onto the page via mix-blend-mode:
   lighten; 2026-10-15 brought solid brown marks on the white bar; the bar itself
   turned olive 2026-10-23.)
   The phone menu overlay lives inside the header, so blending stays off while it
   is open. */
header.scrolled { mix-blend-mode: normal; }
body.menu-open header { mix-blend-mode: normal; }

@media (min-width: 48rem) {
  header.scrolled {
    background: var(--olive);
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
    box-shadow: none;
  }
}
@media (max-width: 47.99rem) {
  header::before {
    background: var(--olive);
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
    box-shadow: none;
  }
}

header.scrolled nav a {
  color: #fff;
  background-image: none;
}
header.scrolled nav a::after { background: #fff; }
header.scrolled .menu-toggle { color: #fff; }
header.scrolled a:focus-visible { outline-color: #fff; }

/* the wordmark is a brown PNG: brightness(0) invert(1) turns it white */
header.scrolled .brand img { filter: brightness(0) invert(1); }

/* phone overlay links stay plain ink, and the open-menu icon too */
@media (max-width: 47.99rem) {
  header.scrolled nav a,
  body.has-gallery header:not(.scrolled) nav a { color: var(--brown); }
  body.menu-open header .menu-toggle,
  body.has-gallery.menu-open header:not(.scrolled) .menu-toggle { color: var(--brown); }
  /* the white wordmark would vanish on the cream menu sheet */
  body.has-gallery.menu-open header .brand img { filter: none; }
}

/* ---------- iOS: the status-bar strip on first open ----------
   On first open, before the visitor has scrolled, Safari paints the strip behind
   the clock and Dynamic Island itself -- system white -- unless an element fixed to
   the top edge gives it a colour (WebKit takes that element's background-color
   for the strip). This is that element: full width, 16px (Safari ignores anything
   10px or thinner), and stacked BEHIND the page so nobody ever sees it -- Safari
   reads its colour from the style, not from what is on screen. site.js sets the
   colour from the top edge of the opening photograph, and removes the element
   once the visitor scrolls, at which point Safari shows the page itself there.
   It must stay out of the header: a fixed element at the top edge that is still
   present while scrolling would put a solid band back over the photographs. */
.status-tint { display: none; }
@media (max-width: 47.99rem) {
  body.has-gallery .status-tint {
    display: block;
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    height: 16px;
    z-index: -1;
    pointer-events: none;
    /* white: if Safari ever paints its strip from this, it should be white, not a
       colour sampled from the photograph */
    background-color: #fff;
  }
  body.has-gallery.scrolled-by-hand .status-tint { display: none; }
}

/* ---------- phone: hero intro leading, and the menu as a full-screen sheet ---------- */
@media (max-width: 47.99rem) {
  /* the intro as an editorial caption under the headline rather than a block of
     body copy on the photograph: a short hairline, smaller and lighter type, a
     narrower measure */
  .hero-copy h1 { margin-bottom: var(--s2); }
  .hero-copy .intro {
    position: relative;
    /* right edge = the end of "perspective." in the headline, measured by site.js */
    max-width: var(--intro-w, 36ch);
    padding-top: var(--s2);
    font-size: .9rem;
    line-height: 1.5;
    letter-spacing: .01em;
    color: rgba(255, 255, 255, .82);
    text-wrap: pretty;          /* no single word stranded on the last line */
  }
  .hero-copy .intro::before {
    content: "";
    position: absolute;
    top: 0;
    left: 0;
    width: 2.5rem;
    height: 1px;
    background: rgba(255, 255, 255, .7);
  }

  /* The menu sheet must reach under Safari's status bar and toolbar too. Safari
     lays fixed elements out between its bars and fills the bars itself, taking
     the colour from a full-screen fixed element at the edge -- but only from its
     background-color, and not at all if it has a backdrop-filter (the bars then
     fall back to the page colour: the dark band). So on phones the sheet is solid
     cream, no glass, and Safari paints both bars in the same cream. */
  header nav {
    background: var(--cream);
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
  }
  /* the menu sheet's own tracking */
  body.has-gallery header nav { text-transform: none; letter-spacing: .01em; }

  /* On the home page, before the first scroll, the status-tint strip already
     holds the top bar's colour, and Safari keeps an existing colour when a
     full-screen layer opens over it. So while the menu is open the strip turns
     cream and comes to the front (cream on the cream sheet -- invisible), which
     makes it the element Safari reads at the top edge. */
  body.has-gallery.menu-open .status-tint {
    display: block !important;
    z-index: 70;
    background-color: var(--cream);
  }
}

/* ---------- the detached bar's on-bar marks layer (phones) ----------
   .header-tint is a copy of the header made by site.js, laid exactly over it and
   clipped to the part of the header the bar has reached. It carries the marks
   shown on the olive bar -- drawn solid white by "solid white on the bar" below --
   while the real header's own marks stay hidden (still there to tap).
   (Its faint-brown tint, the knocked-out bar's legibility aid, and its drop
   shadow are gone: the bar is solid olive now, and phones take no bar shadow.) */
.header-tint { display: none; }
@media (max-width: 47.99rem) {
  body.header-detached:not(.menu-open) .header-tint {
    display: flex;
    z-index: 51;
    pointer-events: none;
    mix-blend-mode: normal;
  }
  .header-tint nav { display: none !important; }
}

/* ---------- desktop nav set at the wordmark's size ----------
   Same cap height as the capitals in the logo: in logo.png the capitals are
   141/1326 of the image's width, and Marcellus capitals are .70 of the font
   size, so the nav's font size is .1519 x the logo's width -- written against
   the logo's own clamp so the two scale together -- then taken down 10%, then
   up 3% (.1408 x) at Manuel's call, then down 2% (.138 x). */
@media (min-width: 48rem) {
  header nav,
  body.has-gallery header nav {
    font-size: clamp(1.068rem, 1.738vw, 1.402rem);
    /* the logo's own tracking: the wordmark is Marcellus at its default spacing (measured 2026-10-24;
       Manuel: "match the menu text char spacing to the same as in the patina and arch logo"). It was
       +3px (.13em) before. */
    letter-spacing: 0;
  }
}

/* ---------- phone menu sheet: quieter, more classic ----------
   Smaller, lighter ink, a little letter-spacing, and a short hairline between the
   options -- a menu card rather than three headlines. */
@media (max-width: 47.99rem) {
  header nav { --menu-gap: 2.1rem; gap: var(--menu-gap); }
  header nav a,
  body.has-gallery header nav a,
  body.has-gallery header:not(.scrolled) nav a,
  header.scrolled nav a {
    font-size: 1.3rem;
    letter-spacing: .07em;
    line-height: 1.2;
    color: rgba(88, 59, 31, .86);
    padding-block: .25rem;
  }
  header nav a + a::before {
    content: "";
    position: absolute;
    left: 50%;
    top: calc(var(--menu-gap) / -2);
    width: 1.25rem;
    height: 1px;
    background: rgba(88, 59, 31, .28);
    transform: translateX(-50%);
  }
}

/* ---------- footer on phones: Location and Follow side by side ---------- */
@media (max-width: 47.99rem) {
  .footer-meta { grid-auto-flow: column; gap: var(--s5); }
}

/* ---------- home: the sections below the gallery in the hero's language ----------
   The same editorial pattern as the hero copy: the headline, a short hairline,
   then the text set a little smaller and lighter on a comfortable measure; the
   call to action as a fine outlined button in small tracked capitals, like the
   nav. */
body.has-gallery .band .copy h2,
body.has-gallery #contact h2 { margin-bottom: var(--s2); }

body.has-gallery .band .copy p,
body.has-gallery #contact .split > div:last-child > p:first-child {
  position: relative;
  max-width: 34rem;
  padding-top: var(--s3);
  font-size: .98rem;
  line-height: 1.65;
  letter-spacing: .005em;
  color: rgba(88, 59, 31, .88);
  text-wrap: pretty;
}
body.has-gallery .band .copy p::before,
body.has-gallery #contact .split > div:last-child > p:first-child::before {
  content: "";
  position: absolute;
  top: 0;
  left: 0;
  width: 2.5rem;
  height: 1px;
  background: rgba(88, 59, 31, .45);
}
/* on a wide screen the contact copy sits beside its headline, so it has no
   headline directly above it to hang a hairline from -- align its top with the
   headline's cap instead of adding space */
@media (min-width: 56rem) {
  body.has-gallery #contact .split > div:last-child > p:first-child { padding-top: var(--s3); }
}

body.has-gallery .cta {
  background: transparent;
  color: var(--brown);
  border: 1px solid rgba(88, 59, 31, .55);
  border-radius: 0;
  padding: .9rem 1.6rem;
  font-size: .74rem;
  letter-spacing: .2em;
}
body.has-gallery .cta:hover,
body.has-gallery .cta:focus-visible {
  background: var(--brown);
  color: var(--cream);
  border-color: var(--brown);
  transform: none;
}
/* phones: the contact headline and its copy stack, so close the column gap -- the
   hairline should sit as close under "Contact Us" as it does under the headline
   above */
@media (max-width: 55.99rem) {
  body.has-gallery #contact .split { gap: 0; }
}

/* ---------- home: pinned gallery, page slides up, bar slides down ----------
   The gallery stays put (sticky) while the rest of the page slides up over it.
   The olive bar comes down from the top of the header at the same time and is
   fully down exactly when the page's top edge reaches the bar's bottom edge
   (site.js sets --bar-start / --bar-end to that scroll range). From there the
   page scrolls on under the bar as normal.

   The bar GROWS down from its own top edge rather than moving down from off
   screen: on phones Safari reads any full-width fixed box covering a point 4px
   below the top of the screen and would paint a solid band behind the clock
   while the bar passed through it. Growing, it never reaches above the header.

   Over the photograph the wordmark, nav and menu button are white (.header-white,
   a copy of the header made by site.js); where the bar has already come down they
   are the bar's own white marks. Both copies are clipped at the bar's
   moving edge by animations on the same timeline, so the two meet exactly there
   instead of the white marks vanishing into the bar. */
body.has-gallery .hero-gallery { position: sticky; top: 0; }
body.has-gallery main > :not(.hero-gallery):not(.to-house),
body.has-gallery footer { position: relative; z-index: 1; }
@media (max-width: 47.99rem) {
  /* the page opens --runway down, so the gallery sticks with that much above the
     screen -- exactly where it already sits on arrival */
  body.has-gallery .hero-gallery { top: calc(-1 * var(--runway)); }
}

.header-white,
.header-shadow { display: none; }

@supports (animation-timeline: scroll()) {
  /* the real bar */
  body.pin-mode header {
    background: transparent !important;
    -webkit-backdrop-filter: none !important;
    backdrop-filter: none !important;
    box-shadow: none !important;
  }
  body.pin-mode header::before {
    content: "";
    position: absolute;
    inset: 0 0 auto 0;
    height: 100%;
    z-index: -1;
    border-radius: inherit;
    background: var(--olive);
    pointer-events: none;
    opacity: 1 !important;
    transition: none;
    box-shadow: none;
    animation: bar-grow linear both;
    animation-timeline: scroll(root);
    animation-range: var(--bar-start, 0px) var(--bar-end, 600px);
  }
  @keyframes bar-grow { from { height: 0; } to { height: 100%; } }

  /* the shadow layer: the same growing box, drawing only an outer shadow */
  body.pin-mode:not(.menu-open) .header-shadow {
    display: flex;
    z-index: 49;
    pointer-events: none;
    mix-blend-mode: normal;
  }
  body.pin-mode .header-shadow > * { visibility: hidden; }
  body.pin-mode .header-shadow::before {
    content: "";
    position: absolute;
    inset: 0 0 auto 0;
    height: 100%;
    border-radius: inherit;
    background: none !important;
    -webkit-backdrop-filter: none !important;
    backdrop-filter: none !important;
    opacity: 1 !important;
    transition: none;
    box-shadow: 0 .6rem 1.6rem rgba(42, 30, 18, .16),
                0 .1rem .35rem rgba(42, 30, 18, .12) !important;
    animation: bar-grow linear both;
    animation-timeline: scroll(root);
    animation-range: var(--bar-start, 0px) var(--bar-end, 600px);
  }

  /* white marks over the photograph: visible below the bar's edge */
  body.pin-mode:not(.menu-open) .header-white {
    display: flex;
    z-index: 52;
    pointer-events: none;
    mix-blend-mode: normal;
    animation: below-bar linear both;
    animation-timeline: scroll(root);
    animation-range: var(--bar-start, 0px) var(--bar-end, 600px);
  }
  @keyframes below-bar { from { clip-path: inset(0 0 0 0); } to { clip-path: inset(100% 0 0 0); } }
  body.pin-mode .header-white::before { display: none !important; }
  body.pin-mode .header-white .brand { background: none; }
  body.pin-mode .header-white .brand img { filter: brightness(0) invert(1) !important; opacity: 1; visibility: visible; }
  body.pin-mode .header-white nav a { color: #fff !important; background: none; }
  body.pin-mode .header-white .menu-toggle { color: #fff !important; }

  /* the faint brown tint (phones): visible above the bar's edge, i.e. on the bar */
  body.pin-mode .header-tint {
    animation: on-bar linear both;
    animation-timeline: scroll(root);
    animation-range: var(--bar-start, 0px) var(--bar-end, 600px);
  }
  @keyframes on-bar { from { clip-path: inset(0 0 100% 0); } to { clip-path: inset(0 0 0 0); } }
  body.pin-mode .header-tint::before { box-shadow: none !important; }

  /* the gallery's frames recede a touch while the page slides over them: a
     slight push-in, tied to the scroll itself, that eases back out as the page
     returns to the home screen. Transform only -- it lives on the compositor. */
  body.pin-mode .hero-gallery .slides {
    animation: gallery-lens linear both;
    animation-timeline: scroll(root);
    animation-range: var(--bar-start, 0px) var(--bar-end, 600px);
  }
  @keyframes gallery-lens { from { transform: scale(1); } to { transform: scale(1.04); } }
}

@media (max-width: 47.99rem) {
  body.pin-mode .header-white nav { display: none !important; }
}

/* ---------- phones: the olive bar comes down from the very top of the screen ----------
   A fixed bar cannot do that: Safari lays fixed boxes out below its status bar,
   and any full-width fixed box that reaches the top 4px makes Safari paint its
   own strip behind the clock -- undoing the photograph there. So the bar is
   built from two parts that meet edge to edge:
     .hero-bar  olive, part of the pinned gallery -- page content, which IS drawn
                behind the clock. It grows from the gallery's top (--runway above
                the screen) down to the header's bottom.
     header::before  the fixed bar, which only starts once that edge reaches the
                header's own top (--head-float down), and then grows with it.
   Over the whole slide the olive edge travels 72 + 62 = 134px: it reaches the
   header's top after 78px (58.21% of the way), so the header's bar and the
   white/on-bar mark clips hold until then. */
.hero-bar { display: none; }
@supports (animation-timeline: scroll()) {
  @media (max-width: 47.99rem) {
    body.pin-mode .hero-bar {
      display: block;
      position: absolute;
      top: 0;
      left: 0;
      right: 0;
      z-index: 3;
      background: var(--olive);
      pointer-events: none;
      animation: hero-bar-grow linear both;
      animation-timeline: scroll(root);
      animation-range: var(--bar-start, 0px) var(--bar-end, 600px);
    }
    @keyframes hero-bar-grow {
      from { height: 0; }
      to   { height: calc(var(--runway) + var(--head-float) + .75rem + 44px); }
    }

    @keyframes bar-grow-phone {
      0%, 58.21% { height: 0; }
      100%       { height: 100%; }
    }
    @keyframes below-bar-phone {
      0%, 58.21% { clip-path: inset(0 0 0 0); }
      100%       { clip-path: inset(100% 0 0 0); }
    }
    @keyframes on-bar-phone {
      0%, 58.21% { clip-path: inset(0 0 100% 0); }
      100%       { clip-path: inset(0 0 0 0); }
    }
    body.pin-mode header::before,
    body.pin-mode .header-shadow::before { animation-name: bar-grow-phone; }
    body.pin-mode:not(.menu-open) .header-white { animation-name: below-bar-phone; }
    body.pin-mode .header-tint { animation-name: on-bar-phone; }
  }
}

/* ---------- phones: once the bar is down, it docks at the top ----------
   While it comes down, the olive is page content (.top-cap below), so it can reach
   up behind the clock. Once it is fully down (body.header-docked, site.js) an olive
   .dock-bar appears behind the header, from the top edge to the bar's bottom:
   Safari's test point 4px from the top lands on it, and Safari paints the strip
   behind the clock in that olive -- above the page, so nothing scrolling under it
   can show through. See "nothing Safari can latch onto" at the end of this file
   for why it is a separate element and not the header itself. */
/* ---------- the home screen settle band ----------
   site.js owns the handoff between native momentum and the final screen settle.
   --runway is 0 on desktop and 72px on phones. */
body.has-gallery { --runway: 0px; }

/* ---------- phones: wordmark and menu button in solid white on the bar ----------
   On the bar the marks are no longer see-through: the bar's copy (.header-tint,
   clipped to the part of the header the bar has reached) draws them fully opaque
   in white over the bar's olive. The wordmark is an image, so it is masked: white
   fill, the logo's own shape as the stencil. */
@media (max-width: 47.99rem) {
  body .header-tint .brand,
  body .header-tint .menu-toggle { opacity: 1 !important; }
  body .header-tint .brand {
    display: block;
    background: #fff !important;
    /* ?v= because the edge caches /assets/logo.png and this URL is not stamped by the build */
    -webkit-mask: url('/assets/logo.png?v=20261015') left center / contain no-repeat;
    mask: url('/assets/logo.png?v=20261015') left center / contain no-repeat;
    /* the same optical nudge the real wordmark has (on its img, which is hidden here) */
    transform: translateY(9.45%);
  }
  body .header-tint .brand img { visibility: hidden !important; }
  body .header-tint .menu-toggle { color: #fff !important; }
}
/* the bar's own (knocked-out) marks are fully covered by the white on-bar copy
   above the bar's edge and the white over-photo copy below it, and showed through
   only as a dark halo at the letter edges -- so on phones they are hidden (still
   there to tap) except with the menu open, when they are the marks on the sheet */
@media (max-width: 47.99rem) {
  body.pin-mode:not(.menu-open) #site-header .brand img,
  body.pin-mode:not(.menu-open) #site-header .menu-toggle .bar { opacity: 0; }
}

/* (.top-cap, a sticky full-screen layer, was removed 2026-10-14 -- see "pinned without sticky"
   at the end of this file. The bar now comes down as .hero-bar inside the pinned stage.) */
@media (max-width: 47.99rem) {
  body.has-gallery { --runway: 72px; }
}
/* once the panel is up behind the clock, the page colour there is the bar's olive
   too, so iOS's fade at the very top matches the panel instead of greying it */
html:has(body.has-gallery.cap-on),
body.has-gallery.cap-on { background-color: var(--olive); }
html:has(body.has-gallery.menu-open),
body.has-gallery.menu-open { background-color: var(--cream); }

/* ---------- phones: nothing see-through in the header, and a menu that is gone
   when closed ----------
   - No knockout on phones: the header is never blended with the page. Its marks
     are drawn solid by the olive copy (on the bar) and the white copy (over the
     photograph); desktop keeps its knocked-out nav and wordmark.
   - The menu sheet is removed outright when closed rather than faded out. While it
     faded, Safari still counted it as a full-screen layer -- inside the header,
     so it attributed it to the header and could keep a colour for its bars from
     that moment (the brown bands after closing). It fades IN with an animation. */
@media (max-width: 47.99rem) {
  header,
  header.scrolled { mix-blend-mode: normal !important; }

  header nav:not(.open) { display: none !important; }
  header nav.open { animation: menu-sheet-in .4s ease both; }
  header nav.open a { animation: menu-link-in .45s cubic-bezier(.22, .61, .36, 1) both; }
  header nav.open a:nth-child(1) { animation-delay: .08s; }
  header nav.open a:nth-child(2) { animation-delay: .14s; }
  header nav.open a:nth-child(3) { animation-delay: .20s; }

  header nav a,
  body.has-gallery header nav a,
  body.has-gallery header:not(.scrolled) nav a,
  header.scrolled nav a { color: var(--brown) !important; }
}
@keyframes menu-sheet-in { from { opacity: 0; } to { opacity: 1; } }
@keyframes menu-link-in {
  from { opacity: 0; transform: translateY(.75rem); }
  to   { opacity: 1; transform: none; }
}
@media (prefers-reduced-motion: reduce) {
  header nav.open, header nav.open a { animation: none !important; }
}

/* ---------- phones: nothing Safari can latch onto as a bar, except the dock bar ----------
   Safari only re-reads what sits at the top/bottom edge when a fixed element is added or removed
   or its toolbar changes size -- and when a re-read finds nothing, it KEEPS the last colour for
   as long as the element it found it on is still there; at the top edge, for ever if that element
   is a <header>. So once the site header was picked up, the bar never went away until a reload.
   (WebKit: Page::updateFixedContainerEdges, LocalFrameView::fixedContainerEdges.)

   1. The header's own box is inset by the page gutter, which keeps it under the 90% of the screen
      width Safari requires of a bar. The olive bar is ::before, bled back out to the screen edges
      -- an absolutely positioned pseudo-element, which Safari never counts. The wordmark and the
      menu button sit exactly where they did (the inset replaces the side padding).
   2. The only element meant to colour the strip behind the clock is .dock-bar (made by site.js):
      olive, full width, down to the bar's bottom edge, and present only while the bar is docked.
      Removing it is what makes Safari drop the olive again. It must appear fully opaque (no fade
      in): Safari reads it once, when it is added, and skips anything transparent at that moment. */
.dock-bar { display: none; }
@media (max-width: 47.99rem) {
  header {
    left: var(--gut);
    right: var(--gut);
    padding-left: 0;
    padding-right: 0;
  }
  header::before,
  body.pin-mode header::before,
  body.pin-mode .header-shadow::before {
    left: calc(-1 * var(--gut));
    right: calc(-1 * var(--gut));
  }
  body.header-docked:not(.menu-open) .dock-bar {
    display: block;
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    /* = the header's bottom edge: float + padding + the 44px menu button */
    height: calc(var(--head-float) + .75rem + 2.75rem);
    background: var(--olive);
    z-index: 49;                /* under the header and its copies, over the page */
    pointer-events: none;
  }
}

/* ---------- home: pinned without sticky ----------
   The gallery used to stay put with position: sticky. A sticky (or fixed) box that spans the
   screen is exactly what Safari inspects when it decides what to paint behind its status bar and
   toolbar, and it keeps whatever it found while that box is still on the page -- the brown bars
   (#2a2119 is the gallery's own background colour) that appeared on the first scroll and stayed.
   So the gallery is an ordinary box now, and its contents (.hero-stage) are held still by a
   scroll-driven transform instead: over the scroll range from where the page opens to one gallery
   height further, the stage moves down by exactly as much as the page scrolls up (0 -> 100% of
   its own height), i.e. it stays put on screen. overflow: clip on the gallery trims it to the
   gallery's own box, which is exactly the part the page has not yet slid over -- and stops the
   moved stage from making the page any longer. site.js sets --pin-start / --pin-end. */
@supports (animation-timeline: scroll()) {
  body.pin-mode .hero-gallery {
    position: relative;
    top: auto;
    overflow: clip;
  }
  body.pin-mode .hero-stage {
    animation: stage-pin linear both;
    animation-timeline: scroll(root);
    animation-range: var(--pin-start, 0px) var(--pin-end, 1000px);
  }
  @keyframes stage-pin {
    from { transform: translate3d(0, 0, 0); }
    to   { transform: translate3d(0, 100%, 0); }
  }
}

/* ---------- phones: no drop shadow under the bar ----------
   Under a full-width bar the shadow only made the header read as a separate white band. */
@media (max-width: 47.99rem) {
  body .header-shadow { display: none !important; }
  body .header-tint::before { box-shadow: none !important; }
}

/* ---------- home gallery: a lighter, clearer tint (2026-10-15) ----------
   The old scrims laid a warm brown veil over the whole lower 62% of every rendering (up to
   .62) and the top 11rem (.42): measured on the Lower Level frame it cut the bottom third's
   contrast by 43% -- the haze on the darker rooms. Now:
   - neutral black instead of brown, so the photographs keep their own colour;
   - tint only at the two ends where the type is (see the revision note below);
   - legibility carried by a close, soft shadow on the white type and marks themselves, which
     darkens only the few pixels around each letter instead of the picture. */
/* Revised the same night at Manuel's suggestion: a soft gradient at each END of the frame, a
   touch darker, so it is only darker where the logo/nav (top) and the type (bottom) live. Each
   ramp is eased -- many stops along a smooth curve -- so there is no visible edge where it starts,
   and the middle of every photograph is left untouched. */
.hero-stage::before {
  height: 11rem;
  /* end values unchanged; the fall-off is steeper (Manuel: "could be lighter in the middle") */
  background: linear-gradient(rgba(0, 0, 0, .32) 0%, rgba(0, 0, 0, .20) 16%, rgba(0, 0, 0, .10) 36%,
              rgba(0, 0, 0, .04) 58%, rgba(0, 0, 0, .01) 80%, rgba(0, 0, 0, 0) 100%);
}
.hero-stage::after {
  height: 58%;
  background: linear-gradient(rgba(0, 0, 0, 0) 0%, rgba(0, 0, 0, .01) 20%, rgba(0, 0, 0, .05) 38%,
              rgba(0, 0, 0, .13) 55%, rgba(0, 0, 0, .26) 72%, rgba(0, 0, 0, .40) 88%, rgba(0, 0, 0, .48) 100%);
}
@media (max-width: 47.99rem) {
  .hero-stage::before { height: calc(8rem + var(--runway)); }
  /* on a phone the type is a tall block from about mid-screen down, so the bottom "end" is
     taller and its ramp starts earlier, reaching the headline */
  .hero-stage::after {
    height: 62%;
    background: linear-gradient(rgba(0, 0, 0, 0) 0%, rgba(0, 0, 0, .03) 10%, rgba(0, 0, 0, .08) 20%,
                rgba(0, 0, 0, .16) 40%, rgba(0, 0, 0, .28) 60%, rgba(0, 0, 0, .41) 80%, rgba(0, 0, 0, .50) 100%);
  }
}
.hero-copy h1 {
  text-shadow: 0 1px 2px rgba(0, 0, 0, .28), 0 2px 18px rgba(0, 0, 0, .30);
}
.hero-copy .intro {
  color: rgba(255, 255, 255, .96);
  text-shadow: 0 1px 2px rgba(0, 0, 0, .35), 0 1px 12px rgba(0, 0, 0, .30);
}
.hero-copy .intro::before { box-shadow: 0 1px 2px rgba(0, 0, 0, .3); }
.marker .ring { filter: drop-shadow(0 1px 2px rgba(0, 0, 0, .35)); }
/* the white wordmark, nav and menu button over the photograph */
body.pin-mode .header-white .brand img {
  filter: brightness(0) invert(1) drop-shadow(0 1px 3px rgba(0, 0, 0, .40)) !important;
}
body.pin-mode .header-white nav a { text-shadow: 0 1px 3px rgba(0, 0, 0, .40); }
body.pin-mode .header-white .menu-toggle { filter: drop-shadow(0 1px 2px rgba(0, 0, 0, .45)); }
body.has-gallery:not(.pin-mode) header:not(.scrolled) .brand img {
  filter: brightness(0) invert(1) drop-shadow(0 1px 3px rgba(0, 0, 0, .40));
}
body.has-gallery:not(.pin-mode) header:not(.scrolled) nav a { text-shadow: 0 1px 3px rgba(0, 0, 0, .40); }

/* ---------- desktop nav hover line (2026-10-21) ----------
   Manuel, after the run of drawn flowers: "a simple basic elegant selection animation". A 1px line
   in the word's own ink draws out from the centre of the word, beneath it, and draws back in to the
   centre on leaving -- white over the photographs and on the olive bar, brown on cream. .bloom is set by
   site.js on the same item in every copy of the header (the copies take no pointer). (Its right
   edge used to be pulled in by the nav's .13em letter-spacing, which trailed the last letter; the
   nav has no extra tracking now, so the line runs the word's full width.) */
@media (min-width: 48rem) {
  header nav a::after {
    content: "";
    left: 0;
    right: 0;
    bottom: .1em;
    height: 1px;
    background: currentColor !important;
    transform: scaleX(0);
    transform-origin: 50% 50%;
    transition: transform .3s cubic-bezier(.4, 0, .2, 1);
  }
  header nav a.bloom::after,
  header nav a:focus-visible::after {
    transform: scaleX(1);
    transition: transform .5s cubic-bezier(.22, .61, .36, 1);
  }
  /* over the photograph the white line gets the same soft shadow as the white type */
  body.pin-mode .header-white nav a::after { box-shadow: 0 1px 3px rgba(0, 0, 0, .35); }
}
@media (prefers-reduced-motion: reduce) {
  header nav a::after { transition: none !important; }
}

/* The supplied overlapping mark replaces the text ghost in the conversation panel. */
:is(body.has-gallery, body.inner-page) #contact.contact-section::before { content: none; }
.contact-watermark {
  position: absolute;
  right: clamp(-5rem, -4vw, -1.5rem);
  bottom: clamp(-6rem, -5vw, -2rem);
  z-index: 0;
  width: clamp(17rem, 34vw, 35rem);
  height: auto;
  opacity: .12;
  pointer-events: none;
  user-select: none;
}
@media (max-width: 55.99rem) {
  .contact-watermark {
    right: -3rem;
    bottom: -2rem;
    width: 18rem;
  }
}

/* ---------- home: the editorial second act ----------
   After the gallery, the page changes register instead of repeating the hero's
   two-column rhythm: a numbered rail, a large statement, and a framed image form
   one architectural composition. The olive conversation panel then gives the
   page a deliberate final room rather than a generic contact row. */
body.has-gallery .approach-section {
  padding-block: clamp(6rem, 14vw, 13rem) clamp(7rem, 16vw, 15rem);
}
.approach-layout,
.ed-layout {
  display: grid;
  grid-template-columns: minmax(4.5rem, .48fr) minmax(0, 1.12fr) minmax(15rem, .72fr);
  gap: clamp(var(--s3), 5vw, var(--s6));
  align-items: end;
}
.approach-rail,
.rail {
  align-self: start;
  display: grid;
  gap: var(--s3);
  border-top: 1px solid var(--line);
  padding-top: var(--s2);
}
.approach-index,
.rail-index {
  color: var(--olive);
  font-family: var(--display);
  font-size: clamp(4.25rem, 8vw, 8rem);
  letter-spacing: -.08em;
  line-height: .78;
}
.approach-label,
.approach-kicker,
.contact-kicker,
.rail-label,
.kicker {
  color: var(--muted);
  font-family: var(--display);
  font-size: .68rem;
  letter-spacing: .22em;
  line-height: 1.4;
  text-transform: uppercase;
}
.approach-copy,
.ed-copy {
  align-self: end;
  padding-bottom: clamp(0rem, 4vw, var(--s5));
}
.approach-kicker { margin: 0 0 var(--s3); }
.approach-copy h2,
.ed-copy h2 {
  max-width: 10ch;
  margin-bottom: var(--s3);
  color: var(--olive);
  font-size: clamp(2.8rem, 5.2vw, 5.8rem);
  letter-spacing: -.055em;
  line-height: .94;
}
.approach-copy > p:not(.approach-kicker),
.ed-copy > p:not(.kicker) {
  max-width: 30rem;
  margin: 0 0 var(--s4);
  color: rgba(88, 59, 31, .84);
  font-size: clamp(.98rem, .9rem + .18vw, 1.08rem);
  line-height: 1.72;
}
.approach-link,
.text-link {
  display: inline-flex;
  align-items: center;
  gap: var(--s1);
  border-bottom: 1px solid rgba(88, 59, 31, .5);
  color: var(--brown);
  font-family: var(--display);
  font-size: .74rem;
  letter-spacing: .18em;
  padding-bottom: .55rem;
  text-decoration: none;
  text-transform: uppercase;
  transition: color .25s ease, border-color .25s ease, gap .25s ease;
}
.approach-link:hover,
.approach-link:focus-visible,
.text-link:hover,
.text-link:focus-visible {
  gap: var(--s2);
  border-color: var(--olive);
  color: var(--olive);
}
.approach-visual-wrap,
.frame-card {
  position: relative;
  width: 100%;
  max-width: 25rem;
  justify-self: end;
}
.approach-visual-wrap::before,
.frame-card::before {
  content: "";
  position: absolute;
  inset: 1.15rem -1.15rem -1.15rem 1.15rem;
  border: 1px solid rgba(102, 100, 57, .6);
  pointer-events: none;
}
.approach-visual-wrap .media,
.frame-card .media {
  position: relative;
  z-index: 1;
}
.approach-caption,
.frame-caption {
  position: relative;
  z-index: 2;
  display: flex;
  justify-content: space-between;
  gap: var(--s2);
  margin-top: var(--s2);
  border-top: 1px solid var(--line);
  padding-top: .7rem;
  color: var(--muted);
  font-family: var(--display);
  font-size: .65rem;
  letter-spacing: .14em;
  text-transform: uppercase;
}

:is(body.has-gallery, body.inner-page) #contact.contact-section {
  position: relative;
  overflow: hidden;
  margin: 0;
  background: var(--olive);
  color: var(--cream);
}
:is(body.has-gallery, body.inner-page) #contact.contact-section::before {
  content: "P&A";
  position: absolute;
  right: -.04em;
  bottom: -.22em;
  color: rgba(245, 242, 237, .07);
  font-family: var(--display);
  font-size: clamp(13rem, 32vw, 38rem);
  letter-spacing: -.12em;
  line-height: .7;
  pointer-events: none;
  white-space: nowrap;
}
.contact-inner {
  position: relative;
  z-index: 1;
  max-width: 100rem;
  margin-inline: auto;
  padding: clamp(4.5rem, 10vw, 9rem) var(--gut) clamp(5rem, 11vw, 10rem);
}
.contact-top {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--s3);
  border-bottom: 1px solid rgba(245, 242, 237, .28);
  padding-bottom: var(--s2);
}
.contact-kicker { margin: 0; color: rgba(245, 242, 237, .72); }
.contact-mark {
  color: rgba(245, 242, 237, .78);
  font-family: var(--display);
  font-size: .8rem;
  letter-spacing: .18em;
}
.contact-grid {
  display: grid;
  grid-template-columns: minmax(0, 1.35fr) minmax(15rem, .65fr);
  gap: clamp(var(--s4), 8vw, var(--s7));
  align-items: end;
  padding-top: clamp(4rem, 8vw, 8rem);
}
:is(body.has-gallery, body.inner-page) #contact.contact-section h2 {
  max-width: 9ch;
  margin: 0;
  color: var(--cream);
  font-size: clamp(3.15rem, 7.2vw, 8rem);
  letter-spacing: -.065em;
  line-height: .88;
}
.contact-copy { max-width: 25rem; }
.contact-copy p {
  margin: 0 0 var(--s4);
  color: rgba(245, 242, 237, .8);
  font-size: clamp(1rem, .92rem + .2vw, 1.12rem);
  line-height: 1.7;
}
:is(body.has-gallery, body.inner-page) #contact.contact-section .cta {
  display: inline-flex;
  align-items: center;
  gap: var(--s1);
  border: 1px solid rgba(245, 242, 237, .62);
  border-radius: 0;
  background: transparent;
  color: var(--cream);
  padding: .95rem 1.25rem;
  font-size: .72rem;
  letter-spacing: .18em;
}
:is(body.has-gallery, body.inner-page) #contact.contact-section .cta:hover,
:is(body.has-gallery, body.inner-page) #contact.contact-section .cta:focus-visible {
  border-color: var(--cream);
  background: var(--cream);
  color: var(--olive);
  transform: none;
}

/* ---------- contact form (2026-10-24) ----------
   Enquiries to Carmen, in the panel's own vocabulary: the kicker's tracked capitals
   as labels over hairline fields, cream on olive, and the panel's outlined button
   to send. Instagram stays as a quieter line beneath. */
.contact-form {
  display: grid;
  gap: var(--s3);
  margin: 0 0 var(--s4);
}
.contact-form .field-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: var(--s3);
}
.contact-form .field { display: grid; gap: .3rem; }
.contact-form .field > span {
  color: rgba(245, 242, 237, .72);
  font-family: var(--display);
  font-size: .68rem;
  letter-spacing: .18em;
  text-transform: uppercase;
}
.contact-form input,
.contact-form textarea {
  width: 100%;
  margin: 0;
  border: 0;
  border-bottom: 1px solid rgba(245, 242, 237, .45);
  border-radius: 0;
  background: transparent;
  color: var(--cream);
  padding: .35rem 0 .5rem;
  font: inherit;
  font-size: 1rem;                 /* 16px: iOS Safari doesn't zoom the page into the field */
  line-height: 1.5;
  transition: border-color .25s ease, box-shadow .25s ease;
}
.contact-form textarea { min-height: 6.5rem; resize: vertical; }
.contact-form input:focus,
.contact-form textarea:focus {
  outline: none;
  border-bottom-color: var(--cream);
  box-shadow: 0 1px 0 var(--cream);
}
/* browser autofill paints its own pale field -- keep it olive with cream type */
.contact-form input:-webkit-autofill,
.contact-form input:-webkit-autofill:focus {
  -webkit-text-fill-color: var(--cream);
  caret-color: var(--cream);
  box-shadow: inset 0 0 0 10rem var(--olive);
}
.contact-form .hp { display: none; }
:is(body.has-gallery, body.inner-page) #contact.contact-section .contact-form .cta {
  justify-self: start;
  margin-top: var(--s1);
  font-family: var(--display);
  line-height: inherit;            /* same height as the Instagram button beside it (a <button> doesn't inherit by default) */
  cursor: pointer;
}
:is(body.has-gallery, body.inner-page) #contact.contact-section .contact-form .cta:disabled { opacity: .6; cursor: progress; }
.contact-copy .form-status {
  margin: 0;
  color: var(--cream);
  font-size: .98rem;
  line-height: 1.6;
}
.contact-copy .form-status:empty { display: none; }
/* (the id: the panel's button rule above carries one, and would keep Send showing) */
#contact.contact-section .contact-form.is-sent > :not(.form-status) { display: none; }
.contact-form.is-sent .form-status { font-family: var(--display); font-size: 1.3rem; }
.contact-copy .contact-alt {
  margin: 0;
  font-size: .95rem;
}
.contact-copy :is(.contact-alt, .form-status) a {
  color: var(--cream);
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: .2em;
}
@media (max-width: 25.99rem) {
  .contact-form .field-row { grid-template-columns: minmax(0, 1fr); }
}

@media (max-width: 55.99rem) {
  body.has-gallery .approach-section {
    padding-block: calc(5.5rem + var(--header-h)) 8rem;
  }
  .approach-layout,
  .ed-layout {
    grid-template-columns: 1fr;
    gap: var(--s5);
  }
  .approach-rail,
  .rail {
    display: flex;
    align-items: baseline;
    gap: var(--s2);
    padding-top: .85rem;
  }
  .approach-index, .rail-index { font-size: 4.8rem; }
  .approach-label, .rail-label { padding-bottom: .35rem; }
  .approach-copy, .ed-copy { padding-bottom: 0; }
  .approach-copy h2, .ed-copy h2 { max-width: 11ch; font-size: clamp(2.75rem, 11vw, 4.6rem); }
  .approach-visual-wrap {
    width: min(80vw, 24rem);
    margin-top: var(--s2);
  }
  .contact-inner { padding-block: 4.5rem 5.5rem; }
  .contact-grid {
    grid-template-columns: 1fr;
    gap: var(--s5);
    padding-top: 4rem;
  }
  :is(body.has-gallery, body.inner-page) #contact.contact-section h2 { max-width: 11ch; font-size: clamp(3rem, 13vw, 5rem); }
  .contact-copy { max-width: 28rem; }
}

/* Final watermark override: the supplied letterform is the only lower-panel mark. */
:is(body.has-gallery, body.inner-page) #contact.contact-section::before {
  content: none !important;
  display: none !important;
}

/* ---------- desktop/iPad: solid white marks on the olive bar ----------
   Manuel: "I don't want the top popout bar stuff to be clear anymore" (2026-10-15)
   -- the marks are solid, not windows onto the page; and since 2026-10-23 the bar
   is olive and they are solid white, as over the gallery. On the home page the
   real header's marks would sit underneath the white copy over the photograph and
   fringe its edges, so there the solid marks come from the .header-tint copy,
   clipped to the part of the header the bar has reached (its on-bar animation),
   and the real header's own marks are hidden -- still there to click and tab to. */
@media (min-width: 48rem) {
  header.scrolled { mix-blend-mode: normal; }
  header.scrolled nav a { color: #fff; }
  header.scrolled .brand img { filter: brightness(0) invert(1); }
  body.pin-mode:not(.menu-open) .header-tint {
    display: flex;
    z-index: 51;
    pointer-events: none;
    mix-blend-mode: normal;
  }
  body.pin-mode .header-tint .brand img { filter: brightness(0) invert(1) !important; opacity: 1; visibility: visible; }
  body.pin-mode .header-tint nav a { color: #fff !important; text-shadow: none !important; }
  body.pin-mode:not(.menu-open) #site-header .brand,
  body.pin-mode:not(.menu-open) #site-header nav a { opacity: 0; }
}

/* ---------- link arrows (2026-10-21) ----------
   The links' "north-east" arrow is drawn, not typed: U+2197 has an emoji form, and with no glyph in
   Marcellus, Apple's browsers fell back to the emoji font and showed a blue tile with a white arrow.
   A 12-unit stroke in currentColor follows the link's colour and hover in every browser. */
.arrow {
  flex: none;
  width: .78em;
  height: .78em;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.15;
  stroke-linecap: square;
  overflow: visible;
}

/* ---------- home: native two-screen snap on touch screens (2026-10-21) ----------
   The home screen and the page after the gallery are two snap points, and the scroll's own
   physics decides: let go short of halfway and the page slides back home, past it (or with any
   flick) and it slides on -- one continuous motion that bends the fling itself, so nothing waits
   for momentum to end and then starts a second, scripted glide (which is what read as slow,
   clunky and jittery on phones). Only on touch screens: there the platform's scroller does it;
   mice and trackpads keep the scripted settle in site.js (WebKit's wheel snapping pulls the page
   back to the top of a tall snap area, which would trap a mouse).
     .snap-home  the opening position (--runway: 72px on phones, 0 on tablets), outside the
                 pinned stage so it never moves.
     .snap-rest  one snap area from the gallery's end (--snap-next, set by site.js) to the very
                 end of the page. It is taller than the screen, so inside it scrolling is free;
                 it just has to reach the bottom, or the footer would be pulled back up. */
.snap-home, .snap-rest { display: none; }
@media (hover: none) and (pointer: coarse) {
  html:has(body.pin-mode) { scroll-snap-type: y mandatory; }
  body.pin-mode { position: relative; }      /* .snap-rest's bottom: 0 = the page's end */
  body.pin-mode .snap-home,
  body.pin-mode .snap-rest {
    display: block;
    position: absolute;
    left: 0;
    width: 1px;
    pointer-events: none;
    scroll-snap-align: start;
  }
  body.pin-mode .snap-home { top: var(--runway); height: 1px; }
  body.pin-mode .snap-rest { top: var(--snap-next, 100vh); bottom: 0; }
}

/* ---------- big screens: the contact headline keeps its three lines (2026-10-21) ----------
   .contact-inner was capped at 100rem while its gutters and gap kept growing, so past ~2000px
   the headline's column got NARROWER as the screen got wider (795px at 1920, 721px at 2560) and
   "Made for living." (781px at its 8rem cap) broke onto a fourth line. Its content now never goes
   below 84rem; below ~1900px the 100rem cap still governs, exactly as before. */
.contact-inner { max-width: max(100rem, calc(84rem + 2 * var(--gut))); }

/* ---------- short landscape screens: a phone held sideways (2026-10-21) ----------
   The section headings are sized from the screen's width, which on a phone on its side means
   ~75px type on a ~340px-tall screen: "Made for living." ran off the bottom and the space above
   "01" took most of the first screen. Here they follow the height instead. Portrait phones,
   tablets and desktops never match this query. */
@media (orientation: landscape) and (max-height: 32rem) and (max-width: 60rem) {
  body.has-gallery .approach-section { padding-block: calc(2.5rem + var(--header-h)) 5rem; }
  .approach-index, .rail-index { font-size: min(4.8rem, 18svh); }
  .approach-copy h2, .ed-copy h2 { font-size: clamp(2.2rem, 13svh, 4.6rem); }
  :is(body.has-gallery, body.inner-page) #contact.contact-section h2 { font-size: clamp(2.4rem, 14svh, 5rem); }
  .contact-inner { padding-block: 3rem 4rem; }
  .contact-grid { padding-top: 2.5rem; }
}
/* Sideways iPhones report 852-932px, i.e. the tablet layout, whose gallery floor of 32rem is
   taller than the ~400px screen -- the copy anchored to its bottom sat below the first screen.
   The gallery is the screen here, and the headline follows the height. (Narrower sideways
   phones keep the phone layout and its tuned caption, untouched.) */
@media (orientation: landscape) and (max-height: 32rem) and (min-width: 48rem) and (max-width: 60rem) {
  .hero-gallery { min-height: 0; }
  .hero-copy h1 { font-size: clamp(1.9rem, 11svh, 4.6rem); }
  .hero-copy .intro { font-size: .95rem; line-height: 1.55; max-width: 36rem; }
}

/* ======================================================================
   Editorial sections, shared by every page (2026-10-21)
   One vocabulary, taken from the home page's "second act" so the three pages read as one:
     .rail / .rail-index / .rail-label  the numbered rail (aliases of .approach-rail etc.)
     .kicker                            small letter-spaced line above a heading
     .ed-layout / .ed-copy              the rail | copy | picture grid and its type
     .frame-card / .frame-caption       a picture with the offset hairline frame and a caption row
     .text-link                         the underlined small-caps link with its drawn arrow
     #contact.contact-section           the olive conversation panel (home + inner pages)
   ====================================================================== */
.kicker { margin: 0 0 var(--s3); }
.ed-section { padding-block: clamp(5rem, 10vw, 10rem); }
.ed-section + .ed-section { padding-top: 0; }
.frame-card { max-width: none; justify-self: auto; }
.frame-card .media { aspect-ratio: 4 / 5; height: auto; }
.frame-card.landscape .media { aspect-ratio: 5 / 4; }
.frame-card.wide .media { aspect-ratio: 3 / 2; }

/* picture first, copy last: the same three columns, mirrored */
.ed-layout.flip { grid-template-columns: minmax(4.5rem, .48fr) minmax(0, 1.2fr) minmax(16rem, .8fr); }
.ed-layout.flip > .frame-card { order: 2; align-self: end; margin-right: 1.15rem; }
.ed-layout.flip > .ed-copy { order: 3; }
.ed-copy .text-link { margin-top: var(--s1); }

/* ---------- home 03: room by room ---------- */
.rooms-head {
  display: grid;
  grid-template-columns: minmax(4.5rem, .48fr) minmax(0, 1.84fr);
  gap: clamp(var(--s3), 5vw, var(--s6));
  align-items: end;
  margin-bottom: clamp(var(--s4), 6vw, var(--s6));
}
.rooms-head .ed-copy {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(14rem, .7fr);
  gap: var(--s5);
  align-items: end;
  padding-bottom: 0;
}
.rooms-head .ed-copy h2 { margin-bottom: 0; }
.rooms-head .ed-copy > p:not(.kicker) { margin: 0; }
.rooms-track {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: clamp(var(--s4), 3.4vw, var(--s5));
  padding-right: 1.15rem;                       /* room for the last frame's offset */
}
.room-card { display: block; color: inherit; text-decoration: none; }
.room-card .media img { transition: scale 1.1s cubic-bezier(.22, .61, .36, 1); }
.room-card:hover .media img,
.room-card:focus-visible .media img { scale: 1.035; }
.room-card .frame-caption { color: var(--brown); }
.room-card .frame-caption span:last-child { color: var(--muted); }
.room-card p {
  margin: var(--s2) 0 0;
  max-width: 24ch;
  color: rgba(88, 59, 31, .78);
  font-size: .92rem;
  line-height: 1.55;
}
.rooms-more { margin-top: clamp(var(--s4), 5vw, var(--s5)); }

/* ---------- home 04: materials, old-world soul | modern perspective ---------- */
.materials-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: clamp(var(--s4), 6vw, var(--s6));
  margin-top: clamp(var(--s4), 6vw, var(--s6));
  padding-right: 1.15rem;
}
.material h3 {
  margin: var(--s4) 0 var(--s2);
  color: var(--olive);
  font-family: var(--display);
  font-size: clamp(1.5rem, 1.1rem + 1.2vw, 2.2rem);
  letter-spacing: -.03em;
  line-height: 1.05;
}
.facts { list-style: none; margin: 0; padding: 0; max-width: 32rem; }
.facts li {
  border-top: 1px solid var(--line);
  padding-block: .8rem;
  color: rgba(88, 59, 31, .84);
  font-size: .98rem;
  line-height: 1.55;
}
.facts li:last-child { border-bottom: 1px solid var(--line); }

/* ---------- inner pages: the opening ---------- */
.page-intro { padding-block: calc(var(--header-h) + clamp(4rem, 9vw, 9rem)) clamp(4rem, 8vw, 8rem); }
.page-intro h1 {
  max-width: 12ch;
  color: var(--olive);
  font-size: clamp(3.6rem, 10vw, 10rem);
  letter-spacing: -.065em;
  line-height: .86;
}
.page-lead {
  max-width: 34rem;
  margin: clamp(var(--s3), 3vw, var(--s4)) 0 0;
  color: rgba(88, 59, 31, .86);
  font-size: clamp(1.08rem, .98rem + .45vw, 1.4rem);
  line-height: 1.6;
}
.page-meta {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s2) var(--s5);
  margin: clamp(var(--s4), 5vw, var(--s5)) 0 0;
  border-top: 1px solid var(--line);
  padding: var(--s2) 0 0;
  list-style: none;
}
.page-meta li { margin: 0; }

/* ---------- portfolio: the opening frame (2026-10-23) ----------
   The portfolio opens like the home page -- a full-screen frame (the front of the
   house) with the header, the title and the lead over it in white -- but held on
   that one frame: no cross-fade, no markers. The markup reuses the home hero
   (.hero-gallery / .hero-stage / .hero-overlay), so the pinned slide-over, the
   olive bar and the white header marks all come from the shared rules above; only
   what differs lives here. (.page-intro/.page-lead/.page-meta above remain for
   the about page.) */

/* "Marion" is one short word, so it carries a larger size than the home headline */
body.portfolio-page .hero-copy h1 {
  font-size: clamp(3rem, 1.9rem + 5vw, 6.4rem);
  letter-spacing: -.02em;
}
@media (max-width: 47.99rem) {
  body.portfolio-page .hero-copy h1 { font-size: clamp(2.8rem, 14vw, 4rem); }
}
/* a phone held sideways: follow the screen's height, like the home headline there */
@media (orientation: landscape) and (max-height: 32rem) and (min-width: 48rem) and (max-width: 60rem) {
  body.portfolio-page .hero-copy h1 { font-size: clamp(2.2rem, 12svh, 3.8rem); }
}

/* the kicker above the title, in the hero's white */
.hero-copy .kicker {
  margin-bottom: var(--s2);
  color: rgba(255, 255, 255, .8);
  text-shadow: 0 1px 2px rgba(0, 0, 0, .3);
}

/* phones: site.js measures the home intro to the width of the headline's last
   line; "Marion" is a single short word, which would squeeze the caption to its
   width -- let it take the comfortable measure instead */
@media (max-width: 47.99rem) {
  body.portfolio-page .hero-copy .intro { max-width: 34rem; }
}

/* the facts, bottom right where the home page keeps its markers */
.hero-meta {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s1) var(--s4);
  margin: 0;
  padding: var(--s2) 0 0;
  border-top: 1px solid rgba(255, 255, 255, .38);
  list-style: none;
  color: rgba(255, 255, 255, .86);
  font-family: var(--display);
  font-size: .72rem;
  letter-spacing: .2em;
  text-transform: uppercase;
  text-shadow: 0 1px 3px rgba(0, 0, 0, .35);
}
.hero-meta li { margin: 0; }

/* the tint (2026-10-24, Manuel: "= to tint on homepage hero"). The home gradients
   alone did less here: the front of the house is bright -- white trim behind
   "Marion", sky behind the nav -- where the home frames are dark interiors. These
   are tuned so the brightest tenth of the photo behind the type measures what it
   does behind the home headline (white type ~2.9:1 there on desktop, ~2.5:1 on a
   phone). Same eased ramps; on desktop part of the darkening is a soft pool behind
   the type at bottom left, so the rest of the house stays clear. */
body.portfolio-page .hero-stage::before {
  background: linear-gradient(rgba(0, 0, 0, .42) 0%, rgba(0, 0, 0, .30) 18%, rgba(0, 0, 0, .17) 38%,
              rgba(0, 0, 0, .07) 60%, rgba(0, 0, 0, .02) 82%, rgba(0, 0, 0, 0) 100%);
}
body.portfolio-page .hero-stage::after {
  height: 70%;
  background:
    radial-gradient(ellipse 62% 58% at 4% 78%, rgba(0, 0, 0, .26) 0%, rgba(0, 0, 0, .21) 25%,
                    rgba(0, 0, 0, .12) 50%, rgba(0, 0, 0, .04) 75%, rgba(0, 0, 0, 0) 100%),
    linear-gradient(rgba(0, 0, 0, 0) 0%, rgba(0, 0, 0, .02) 15%, rgba(0, 0, 0, .06) 30%, rgba(0, 0, 0, .14) 45%,
                    rgba(0, 0, 0, .24) 60%, rgba(0, 0, 0, .33) 75%, rgba(0, 0, 0, .41) 90%, rgba(0, 0, 0, .46) 100%);
}
@media (max-width: 47.99rem) {
  /* the phone's taller bottom end, as on the home page, just stronger */
  body.portfolio-page .hero-stage::after {
    height: 62%;
    background: linear-gradient(rgba(0, 0, 0, 0) 0%, rgba(0, 0, 0, .04) 10%, rgba(0, 0, 0, .12) 20%,
                rgba(0, 0, 0, .26) 35%, rgba(0, 0, 0, .38) 50%, rgba(0, 0, 0, .46) 65%, rgba(0, 0, 0, .52) 80%,
                rgba(0, 0, 0, .56) 100%);
  }
}

/* after the full-screen frame, the house map opens with the same air the page
   intro used to give it */
body.portfolio-page .house-map { padding-top: clamp(3.5rem, 7vw, 7rem); }

/* ---------- portfolio: the index of rooms ---------- */
.toc { padding-bottom: clamp(4rem, 8vw, 8rem); }
.toc ol {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(16rem, 1fr));
  column-gap: clamp(var(--s3), 4vw, var(--s5));
  margin: 0;
  padding: 0;
  list-style: none;
  border-top: 1px solid var(--line);
}
.toc a {
  display: flex;
  gap: var(--s2);
  align-items: baseline;
  border-bottom: 1px solid var(--line);
  padding-block: .7rem;
  color: var(--brown);
  font-family: var(--display);
  font-size: .95rem;
  letter-spacing: .01em;
  text-decoration: none;
  transition: color .25s ease, padding-left .25s ease;
}
.toc a span:first-child { min-width: 1.6rem; color: var(--muted); font-size: .72rem; letter-spacing: .14em; }
.toc a:hover, .toc a:focus-visible { color: var(--olive); padding-left: .35rem; }

/* ---------- portfolio: a chapter ---------- */
.chapter { scroll-margin-top: var(--header-h); }
.chapter-grid {
  display: grid;
  grid-template-columns: minmax(4.5rem, .42fr) minmax(0, 1fr) minmax(0, .95fr);
  grid-template-areas: "rail text card";
  gap: clamp(var(--s3), 5vw, var(--s6));
  align-items: start;
  padding-block: clamp(4.5rem, 9vw, 9rem);
}
.chapter.flip .chapter-grid {
  grid-template-columns: minmax(4.5rem, .42fr) minmax(0, .95fr) minmax(0, 1fr);
  grid-template-areas: "rail card text";
}
.chapter-grid > .rail { grid-area: rail; }
.chapter-text { grid-area: text; }
.chapter-cards {
  grid-area: card;
  display: grid;
  gap: clamp(var(--s4), 4vw, var(--s5));
  margin-right: 1.15rem;                        /* the frame's offset stays inside the grid */
  position: sticky;
  top: calc(var(--header-h) + var(--s4));
}
.chapter-cards .frame-card + .frame-card { width: 72%; justify-self: end; }
.chapter-text h2,
.chapter-head h2 {
  max-width: 13ch;
  margin-bottom: clamp(var(--s3), 3vw, var(--s4));
  color: var(--olive);
  font-size: clamp(2.3rem, 3.9vw, 4.2rem);
  letter-spacing: -.05em;
  line-height: .96;
}
.chapter-copy p {
  max-width: 34rem;
  color: rgba(88, 59, 31, .84);
  font-size: clamp(.98rem, .92rem + .16vw, 1.06rem);
  line-height: 1.75;
}
.chapter-tagline {
  max-width: 28ch;
  margin: clamp(var(--s4), 4vw, var(--s5)) 0 0;
  border-top: 1px solid rgba(102, 100, 57, .6);
  padding-top: var(--s2);
  color: var(--olive);
  font-family: var(--display);
  font-size: clamp(1.1rem, .98rem + .45vw, 1.45rem);
  letter-spacing: -.01em;
  line-height: 1.32;
}
/* key rooms open full-bleed, the heading and the copy side by side beneath */
.chapter-bleed .media.full { height: clamp(28rem, 86svh, 60rem); }
.chapter-full .chapter-grid {
  grid-template-columns: minmax(4.5rem, .42fr) minmax(0, 1fr) minmax(0, 1fr);
  grid-template-areas: "rail head copy";
}
.chapter-head { grid-area: head; }
.chapter-full .chapter-copy { grid-area: copy; }
.chapter-head .chapter-tagline { margin-top: 0; }
.chapter + .chapter:not(.chapter-full) .chapter-grid { border-top: 1px solid var(--line); }

/* ---------- portfolio: also at Marion ---------- */
.also-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: clamp(var(--s4), 5vw, var(--s6));
  padding-right: 1.15rem;
}

/* ---------- tablets and phones ---------- */
@media (max-width: 55.99rem) {
  .ed-layout.flip { grid-template-columns: 1fr; }
  .ed-layout.flip > .frame-card { order: 3; width: auto; max-width: 40rem; }
  .ed-layout.flip > .ed-copy { order: 2; }
  .rooms-head { grid-template-columns: 1fr; gap: var(--s4); }
  .rooms-head .ed-copy { grid-template-columns: 1fr; gap: var(--s3); }
  .rooms-track { grid-template-columns: repeat(2, minmax(0, 1fr)); row-gap: var(--s5); }
  .materials-grid { grid-template-columns: 1fr; }
  .chapter-grid,
  .chapter.flip .chapter-grid,
  .chapter-full .chapter-grid { grid-template-columns: 1fr; grid-template-areas: none; gap: var(--s4); }
  .chapter-grid > *,
  .chapter-grid > .rail,
  .chapter-full .chapter-copy { grid-area: auto; }
  .chapter-cards { position: static; max-width: 40rem; }
  .chapter-text h2, .chapter-head h2 { max-width: 14ch; font-size: clamp(2.3rem, 9vw, 3.6rem); }
}
/* phones: the room cards become a row to swipe through, running to the screen's edges */
@media (max-width: 47.99rem) {
  .rooms-track {
    grid-template-columns: none;
    grid-auto-flow: column;
    grid-auto-columns: min(74vw, 20rem);
    gap: var(--s4);
    margin-inline: calc(var(--gut) * -1);
    padding: 0 calc(var(--gut) + 1.15rem) 1.6rem var(--gut);
    overflow-x: auto;
    overscroll-behavior-x: contain;
    scroll-snap-type: x mandatory;
    scroll-padding-inline: var(--gut);
    scrollbar-width: none;
  }
  .rooms-track::-webkit-scrollbar { display: none; }
  .room-card { scroll-snap-align: start; }
  .also-grid { grid-template-columns: 1fr; }
  .page-intro h1 { font-size: clamp(3.4rem, 17vw, 5.4rem); }
  .toc ol { grid-template-columns: 1fr; }
}
@media (prefers-reduced-motion: reduce) {
  .room-card .media img { transition: none; }
}

/* about: the founder's heading beside the story, not above it */
.page-intro + .ed-section { padding-top: 0; }
.ed-layout.founder-layout { grid-template-columns: minmax(4.5rem, .48fr) minmax(0, .82fr) minmax(0, 1.18fr); align-items: start; }
.founder-layout .ed-copy { align-self: start; padding-bottom: 0; }
.founder-copy p { max-width: 36rem; color: rgba(88, 59, 31, .84); }
@media (max-width: 55.99rem) {
  .ed-layout.founder-layout { grid-template-columns: 1fr; row-gap: var(--s4); }
}

/* ---------- framed cards: the frame outlines the picture only (2026-10-21) ----------
   The offset hairline used to be drawn around the whole card -- picture AND caption -- so on a
   card with a caption or a line of text its left edge ran straight through the words. The frame
   and the picture now share the first cell of a one-column grid; the frame is shifted down and
   right by its offset, and the caption and text sit in the rows beneath, clear of it. */
.approach-visual-wrap,
.frame-card {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  align-content: start;
}
.approach-visual-wrap::before,
.frame-card::before {
  position: relative;
  inset: auto;
  grid-area: 1 / 1;
  z-index: 0;
  transform: translate(1.15rem, 1.15rem);
}
.approach-visual-wrap > .media,
.frame-card > .media { grid-area: 1 / 1; z-index: 1; }
.approach-caption,
.frame-caption { margin-top: calc(1.15rem + var(--s2)); }

/* consecutive paragraphs in a section's copy sit at body spacing, not the section's gap */
.ed-copy > p:not(.kicker) + p:not(.kicker) { margin-top: calc(1.15em - var(--s4)); }
.ed-layout.flip > .frame-card { align-self: center; }

/* ---------- portfolio: the house map's heading (2026-10-21) ----------
   Manuel: instead of a listed table of contents, "a graphic of the house where you can select the
   areas of the house to enter" -- now the three-dimensional explorer (see "the house explorer"
   below). The first, flat section drawing is kept in _patinaandarch-checkpoints/2026-10-21-house-section-v1. */
.house-map { padding-bottom: clamp(4rem, 8vw, 8rem); scroll-margin-top: var(--header-h); }
.house-map-head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: var(--s3);
  border-top: 1px solid var(--line);
  padding-top: var(--s2);
  margin-bottom: clamp(var(--s3), 3vw, var(--s4));
}
.house-map-head .kicker { margin: 0; }
.house-hint {
  margin: 0;
  color: var(--muted);
  font-family: var(--display);
  font-size: .7rem;
  letter-spacing: .16em;
  line-height: 1.5;
  text-transform: uppercase;
}
.hint-touch { display: none; }
@media (hover: none) {
  .hint-fine { display: none; }
  .hint-touch { display: inline; }
}
@media (max-width: 47.99rem) {
  .house-map-head { flex-direction: column; gap: var(--s1); }
}

/* back to the house: a small round button while you are reading the chapters */
.to-house {
  position: fixed;
  right: max(var(--s3), env(safe-area-inset-right, 0px));
  bottom: calc(var(--s3) + env(safe-area-inset-bottom, 0px));
  z-index: 40;
  display: grid;
  place-items: center;
  width: 3rem;
  height: 3rem;
  border: 1px solid rgba(102, 100, 57, .55);
  border-radius: 50%;
  background: rgba(245, 242, 237, .94);
  color: var(--olive);
  box-shadow: 0 6px 22px rgba(40, 30, 20, .12);
  opacity: 0;
  transform: translateY(.6rem);
  pointer-events: none;
  transition: opacity .35s ease, transform .35s ease, background-color .25s ease, color .25s ease;
}
.to-house svg { width: 1.3rem; height: 1.3rem; fill: none; stroke: currentColor; stroke-width: 1.4; stroke-linecap: round; stroke-linejoin: round; }
.to-house.show { opacity: 1; transform: none; pointer-events: auto; }
.to-house:hover,
.to-house:focus-visible { background: var(--olive); color: var(--cream); }
.also-grid .frame-card { scroll-margin-top: calc(var(--header-h) + var(--s4)); }
@media (prefers-reduced-motion: reduce) {
  .to-house { transition: none; }
}

/* ---------- portfolio: the house explorer, in three dimensions (2026-10-21) ----------
   Manuel: "a more 3d view, like an upper front corner pan, and kind of translucent so that you can
   select around the rooms, combined with a way to rotate or move views so that everything is
   visible". Drawn by site.js from the model build-house-map.py writes into the page (in feet, to
   scale). The palette is the site's: cream floors, olive hairlines, see-through olive walls. */
.house-explorer {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(16rem, 21rem);
  gap: clamp(var(--s4), 4vw, var(--s6));
  align-items: start;
}
.hx-stage { position: relative; }
.hx-3d {
  display: block;
  width: 100%;
  aspect-ratio: 10 / 7.4;
  overflow: hidden;
  touch-action: pan-y;               /* a sideways drag turns the house, an upward one still scrolls */
  user-select: none;
  -webkit-user-select: none;
  cursor: grab;
}
.is-turning .hx-3d { cursor: grabbing; }
.hx-3d text { font-family: var(--display); }
.hx-3d polygon, .hx-3d polyline { stroke-linejoin: round; }
.hx-a { cursor: pointer; }
.hx-a:focus { outline: none; }

/* the whole house: see-through walls and roof, the floors inside */
.w-ground { fill: rgba(102, 100, 57, .045); }
.w-slab { fill: rgba(245, 242, 237, .5); stroke: rgba(102, 100, 57, .6); stroke-width: .8; transition: fill .3s ease; }
.w-room { fill: none; stroke: rgba(102, 100, 57, .42); stroke-width: .65; }
.w-shell { fill: rgba(102, 100, 57, .04); stroke: rgba(102, 100, 57, .62); stroke-width: .85; cursor: pointer; transition: fill .3s ease; }
.w-roof { fill: rgba(102, 100, 57, .08); stroke: var(--olive); stroke-width: 1; pointer-events: none; }
.w-shell.is-lit { fill: rgba(102, 100, 57, .15); }
.w-slab.is-lit { fill: rgba(222, 217, 196, .92); }
.w-room.is-lit { stroke: rgba(102, 100, 57, .75); }

/* outside: stoop, portico, rear steps, patio, pergola */
.x-solid { fill: #e9e4d7; stroke: rgba(102, 100, 57, .85); stroke-width: .75; }
.x-solid.top { fill: #f4f1e9; }
.x-pave { fill: rgba(88, 59, 31, .07); stroke: rgba(102, 100, 57, .56); stroke-width: .6; }
.x-pave.top { fill: rgba(88, 59, 31, .09); }
.x-roof { fill: #e4dfcf; stroke: var(--olive); stroke-width: .9; }
.x-mass { fill: #e9e4d8; stroke: rgba(102, 100, 57, .82); stroke-width: .85; }
.x-mass.top { fill: #f2eee5; }
.x-rail { fill: none; stroke: rgba(102, 100, 57, .9); stroke-width: 1; }
.x-rafter { fill: none; stroke: rgba(102, 100, 57, .74); stroke-width: .75; }
.x-solid.is-on, .x-pave.is-on, .x-roof.is-on, .x-mass.is-on { fill: #dcd7c0; }

/* one floor: a cutaway -- floors cream, walls see-through olive, cut at waist height */
.v-ghost { fill: none; stroke: rgba(102, 100, 57, .26); stroke-width: .7; stroke-dasharray: 3 3; }
.v-ghost-room { fill: none; stroke: rgba(102, 100, 57, .16); stroke-width: .6; }
.f-slab { fill: #e7e2d4; stroke: rgba(102, 100, 57, .72); stroke-width: .8; }
.f-slab-edge { fill: #d8d2c1; stroke: rgba(102, 100, 57, .72); stroke-width: .6; }
.f-outer { fill: rgba(102, 100, 57, .1); stroke: rgba(102, 100, 57, .8); stroke-width: .8; pointer-events: none; }
.f-floor { fill: #faf8f3; cursor: pointer; transition: fill .3s ease; }
.f-floor.is-on { fill: #e3dfca; }
.f-hall { fill: #f1eee6; }
.f-plain { fill: #f3f0e9; }
.f-wall { fill: rgba(102, 100, 57, .07); stroke: rgba(102, 100, 57, .78); stroke-width: .8; pointer-events: none; transition: fill .3s ease; }
.f-wall.is-on { fill: rgba(102, 100, 57, .17); stroke: var(--olive); }
.f-stair { fill: rgba(102, 100, 57, .2); stroke: rgba(102, 100, 57, .82); stroke-width: .75; pointer-events: none; }
.f-stringer { fill: rgba(102, 100, 57, .12); stroke: rgba(102, 100, 57, .68); stroke-width: .6; pointer-events: none; }
.f-tread { fill: none; stroke: rgba(102, 100, 57, .66); stroke-width: .65; pointer-events: none; }
.f-well { fill: rgba(102, 100, 57, .07); stroke: rgba(102, 100, 57, .62); stroke-width: .7; stroke-dasharray: 3 2; }
.f-fix { fill: rgba(88, 59, 31, .07); stroke: rgba(88, 59, 31, .5); stroke-width: .65; pointer-events: none; }
.f-fix.top { fill: rgba(88, 59, 31, .14); }
.f-range, .f-range.top { fill: rgba(34, 30, 26, .4); stroke: rgba(20, 18, 16, .6); }
.f-stone.top { fill: rgba(88, 59, 31, .3); }
.f-glass, .f-glass.top { fill: rgba(150, 170, 170, .22); stroke: rgba(55, 64, 64, .62); }
.f-tub, .f-tub.top { fill: rgba(255, 255, 255, .8); }

/* the grounds */
.g-street { fill: rgba(88, 59, 31, .06); }
.g-lot { fill: rgba(102, 100, 57, .07); stroke: rgba(102, 100, 57, .52); stroke-width: .7; stroke-dasharray: 4 3; }
.g-yard { fill: rgba(102, 100, 57, 0); cursor: pointer; transition: fill .3s ease; }
.g-yard.is-on { fill: rgba(102, 100, 57, .14); }
.g-pave { fill: rgba(88, 59, 31, .1); }
.g-mass { fill: #ebe6da; stroke: rgba(102, 100, 57, .82); stroke-width: .75; cursor: pointer; transition: fill .3s ease; }
.g-mass.top { fill: #f2eee5; }
.g-roof { fill: #e1dbca; stroke: var(--olive); stroke-width: .9; cursor: pointer; transition: fill .3s ease; }
.g-roof.end { fill: #d9d3c0; }
.g-mass.is-lit, .g-roof.is-lit, .g-roof.is-on { fill: #d8d2bb; }

/* labels */
.lb { pointer-events: none; }
.lb-n { fill: var(--olive); font-size: 15px; letter-spacing: -.02em; text-anchor: middle; }
.lb-name { fill: rgba(88, 59, 31, .82); font-size: 9.5px; letter-spacing: .1em; text-anchor: middle; text-transform: uppercase; }
.hx-3d[data-mode="main"] .lb:not(.lb-big):not(.lb-plain):not(.lb-pin) .lb-name,
.hx-3d[data-mode="second"] .lb:not(.lb-big):not(.lb-plain):not(.lb-pin) .lb-name,
.hx-3d[data-mode="lower"] .lb:not(.lb-big):not(.lb-plain):not(.lb-pin) .lb-name { display: none; }
.lb-plain .lb-name { fill: rgba(88, 59, 31, .42); }
.lb-level .lb-name { fill: var(--muted); font-size: 10px; text-anchor: end; transition: fill .3s ease; }
.lb-level.is-lit .lb-name { fill: var(--olive); }
.lb-enter .lb-name { fill: var(--olive); }
.lb.is-on .lb-n, .lb.is-on .lb-name { fill: var(--olive); }
.lb-pin { pointer-events: auto; cursor: pointer; }
.lb-pin circle { fill: var(--cream); stroke: var(--olive); stroke-width: 1; transition: fill .3s ease; }
.lb-pin .lb-n { font-size: 9.5px; }
.lb-pin.is-on circle { fill: var(--olive); }
.lb-pin.is-on .lb-n { fill: var(--cream); }

/* turning */
.hx-turn { position: absolute; left: 0; bottom: 0; display: flex; align-items: center; gap: var(--s1); }
.hx-rot {
  display: grid;
  place-items: center;
  width: 2.5rem;
  height: 2.5rem;
  border: 1px solid rgba(102, 100, 57, .5);
  border-radius: 50%;
  background: rgba(245, 242, 237, .92);
  color: var(--olive);
  cursor: pointer;
  transition: background-color .25s ease, color .25s ease;
}
.hx-rot svg { width: 1.1rem; height: 1.1rem; fill: none; stroke: currentColor; stroke-width: 1.5; stroke-linecap: round; stroke-linejoin: round; }
.hx-rot:hover, .hx-rot:focus-visible { background: var(--olive); color: var(--cream); }
.hx-turn-hint { margin-left: var(--s1); color: var(--muted); font-family: var(--display); font-size: .64rem; letter-spacing: .16em; text-transform: uppercase; }

/* beside the drawing: the views, the key to the floor, the card */
.hx-side { display: grid; gap: var(--s4); align-content: start; }
.hx-tabs { display: grid; border-top: 1px solid var(--line); }
.hx-tab {
  display: flex;
  justify-content: space-between;
  align-items: center;
  border: 0;
  border-bottom: 1px solid var(--line);
  background: none;
  padding: .72rem 0;
  color: var(--brown);
  font-family: var(--display);
  font-size: .95rem;
  text-align: left;
  cursor: pointer;
  transition: color .25s ease, padding-left .25s ease;
}
.hx-tab::after { content: ""; width: .5rem; height: .5rem; border: 1px solid rgba(102, 100, 57, .5); border-radius: 50%; transition: background-color .25s ease; }
.hx-tab[aria-selected="true"] { color: var(--olive); padding-left: .35rem; }
.hx-tab[aria-selected="true"]::after { background: var(--olive); border-color: var(--olive); }
.hx-tab:hover, .hx-tab:focus-visible { color: var(--olive); }
.hx-key-hint { margin: 0; color: var(--muted); font-size: .92rem; line-height: 1.55; }
.hx-legend { margin: 0; padding: 0; list-style: none; columns: 2; column-gap: var(--s3); }
.hx-legend li { break-inside: avoid; }
.hx-key {
  display: flex;
  gap: .55rem;
  align-items: baseline;
  padding: .3rem 0;
  color: var(--brown);
  font-family: var(--display);
  font-size: .84rem;
  line-height: 1.3;
  text-decoration: none;
  transition: color .2s ease;
}
.hx-key span:first-child { min-width: 1.35rem; color: var(--muted); font-size: .66rem; letter-spacing: .12em; }
.hx-key:hover, .hx-key:focus-visible, .hx-key.is-on { color: var(--olive); }
.hx-preview .media { aspect-ratio: 4 / 3; }
.hx-preview .hp-title { margin: var(--s3) 0 .25rem; color: var(--olive); font-family: var(--display); font-size: 1.35rem; letter-spacing: -.02em; line-height: 1.15; }
.hx-preview .hp-tag { margin: 0; color: rgba(88, 59, 31, .8); font-size: .92rem; line-height: 1.5; }
.hx-caption { margin: 0; color: var(--muted); font-family: var(--display); font-size: .6rem; letter-spacing: .18em; text-transform: uppercase; }

@media (max-width: 63.99rem) {
  .house-explorer { grid-template-columns: 1fr; }
  .hx-tabs { display: flex; gap: var(--s3); border-top: 0; overflow-x: auto; scrollbar-width: none; }
  .hx-tabs::-webkit-scrollbar { display: none; }
  .hx-tab { flex: none; border-bottom: 1px solid transparent; padding: .5rem 0; }
  .hx-tab::after { display: none; }
  .hx-tab[aria-selected="true"] { border-bottom-color: var(--olive); padding-left: 0; }
  .hx-preview { display: none; }
}
@media (max-width: 47.99rem) {
  .hx-3d { width: calc(100% + 2 * var(--gut)); margin-inline: calc(var(--gut) * -1); aspect-ratio: 1 / 1.08; }
  .hx-3d .lb:not(.lb-level):not(.lb-enter):not(.lb-plain) .lb-name { display: none; }
  .hx-turn { bottom: .25rem; }
}
@media (prefers-reduced-motion: reduce) {
  .w-slab, .w-shell, .f-floor, .f-wall, .g-mass, .g-roof, .lb-pin circle { transition: none; }
}
/* lit faces carry their tone inline (site.js), so the chosen states must win over it */
.w-ground { fill: rgba(232, 227, 214, .82); stroke: rgba(102, 100, 57, .45); stroke-width: .7; }
.w-shell.is-lit { fill: rgba(102, 100, 57, .16) !important; fill-opacity: 1 !important; }
.f-wall.is-on { fill: rgba(102, 100, 57, .18) !important; fill-opacity: 1 !important; }
.x-solid.is-on, .x-pave.is-on, .x-roof.is-on, .x-mass.is-on,
.g-mass.is-lit, .g-roof.is-lit, .g-roof.is-on { fill: #d6d0b8 !important; }
.f-break { fill: none; stroke: rgba(102, 100, 57, .85); stroke-width: .9; }
.lb-leader { stroke: rgba(102, 100, 57, .45); stroke-width: .7; stroke-dasharray: 2 2; }
.lb-level.is-lit .lb-leader { stroke: var(--olive); stroke-dasharray: none; }
.hx-3d.is-narrow .lb-level { display: none; }
/* v4, from the plans: windows and doors, the stair landing, the parkway */
.w-win { fill: rgba(64, 76, 78, .18); stroke: rgba(102, 100, 57, .62); stroke-width: .6; }
.g-win { fill: #b5b7aa; stroke: rgba(102, 100, 57, .75); stroke-width: .6; }
.g-door { fill: #a09883; stroke: rgba(102, 100, 57, .8); stroke-width: .6; }
.f-win { fill: rgba(150, 170, 170, .28); stroke: rgba(55, 64, 64, .58); stroke-width: .6; pointer-events: none; }
.x-door { fill: #d6d0bd; stroke: rgba(102, 100, 57, .75); stroke-width: .6; }
.f-landing { fill: rgba(102, 100, 57, .11); stroke: rgba(102, 100, 57, .68); stroke-width: .6; pointer-events: none; }
.f-landing.top { fill: rgba(102, 100, 57, .2); }
.g-park { fill: rgba(102, 100, 57, .1); }
.hx-under { pointer-events: none; }
/* v6: the smaller parts -- railings, trim, gutters, the lantern, grilles -- and the planting */
.x-trim { fill: #f6f3ec; stroke: rgba(102, 100, 57, .72); stroke-width: .5; }
.x-gutter { fill: #f1ede4; stroke: rgba(102, 100, 57, .7); stroke-width: .5; pointer-events: none; }
.x-metal { fill: #c4c5be; stroke: rgba(70, 72, 66, .7); stroke-width: .5; }
.x-lamp { fill: #3a342d; stroke: rgba(30, 26, 22, .85); stroke-width: .5; }
.x-trunk { fill: #7a664d; stroke: rgba(60, 48, 35, .7); stroke-width: .5; pointer-events: none; }
.x-rail.low { stroke-width: .7; }
.x-baluster { fill: none; stroke: rgba(102, 100, 57, .62); stroke-width: .45; pointer-events: none; }
.w-bar { fill: none; stroke: rgba(102, 100, 57, .45); stroke-width: .45; pointer-events: none; }
.g-bar { fill: none; stroke: #f4f0e6; stroke-width: .7; pointer-events: none; }
.p-boxwood { fill: url(#hx-g-box); stroke: rgba(62, 66, 36, .6); stroke-width: .5; }
.p-hydrangea { fill: url(#hx-g-hyd); stroke: rgba(102, 100, 57, .62); stroke-width: .5; }
.p-lavender { fill: url(#hx-g-lav); stroke: rgba(84, 72, 94, .6); stroke-width: .45; }
.p-fern { fill: url(#hx-g-fern); stroke: rgba(70, 76, 40, .6); stroke-width: .45; }
.p-vine { fill: url(#hx-g-vine); stroke: rgba(62, 68, 36, .55); stroke-width: .4; }
.p-tree { fill: url(#hx-g-tree); fill-opacity: .8; stroke: rgba(70, 76, 40, .45); stroke-width: .7; }
.p-tree.p-small { fill: url(#hx-g-small); stroke: rgba(70, 76, 40, .6); stroke-width: .5; }
.p-boxwood, .p-hydrangea, .p-lavender, .p-fern, .p-vine, .p-tree { pointer-events: none; }
/* the whole house: the ground never takes the pointer, so the lower level beneath it can be lit and chosen, and it
   thins while the lower level is lit; the floors' names choose their floors too */
.w-ground { pointer-events: none; transition: fill .3s ease; }
.hx-3d[data-lit="lower"] .w-ground { fill: rgba(232, 227, 214, .42); }
.lb-level { pointer-events: auto; cursor: pointer; }
.x-pipe { fill: none; stroke: #f8f6f0; stroke-width: 1.3; stroke-linecap: round; stroke-linejoin: round; pointer-events: none; }
.x-pipe.edge { stroke: rgba(102, 100, 57, .75); stroke-width: 2.4; }
.w-hit { fill: none; stroke: none; pointer-events: all; cursor: pointer; }
/* v7: the house is painted on a canvas laid under the drawing (site.js); the drawing's faces stay above it unseen, for
   the pointer and the links, and its labels show. While it turns the faces are not even laid out, and nothing fades. */
.hx-3d { position: relative; }
.hx-canvas { position: absolute; display: block; pointer-events: none; }
.hx-3d.has-sheet .hx-scene { opacity: 0; }
.hx-3d.has-sheet.is-moving .hx-scene { display: none; }   /* out of layout and hit-testing altogether while it turns */
.hx-3d.no-fade * { transition: none !important; }
/* v8: the porches' brick, where the drawing is shown instead of the canvas (light tones, as .x-solid) */
.x-brick { fill: #e9e4d7; stroke: rgba(102, 100, 57, .85); stroke-width: .6; }
.x-brick.is-on { fill: #dcd7c0 !important; }
/* the roofs a step darker than the walls (2026-10-24), the drawing's hover to match */
.x-roof.is-on, .g-roof.is-lit, .g-roof.is-on { fill: #988d70 !important; }
/* the floors' names answer the pointer between and around their letters too, not only on the strokes of the glyphs */
.lb-level .lb-name { stroke: transparent; stroke-width: 14px; pointer-events: all; }
.lb-level .lb-leader { pointer-events: none; }   /* the leader is drawing only; the name is what takes the pointer */

/* ---------- the bar comes down from the top, on every page (2026-10-23) ----------
   Manuel: the home page's bar, which comes in from the top, everywhere. Where the home page's scroll-linked bar does
   not run (a page without the photo opening, or a browser without scroll-driven animation) the detached bar is the
   header's ::before, uncovered from the top down as the header detaches and drawn back up as it returns; the marks turn
   white just as the bar's edge passes them, and back just as it leaves them. The wordmark is olive on cream (logo.png
   is drawn in the bar's own olive) and white on the bar. */
@media (min-width: 48rem) {
  body:not(.pin-mode) header.scrolled { background: transparent; box-shadow: none; }
  body:not(.pin-mode) header::before {
    content: "";
    position: absolute;
    inset: 0;
    z-index: -1;
    pointer-events: none;
    background: var(--olive);
  }
  body:not(.pin-mode) header nav a,
  body:not(.pin-mode) header.scrolled nav a { transition: color 0s linear .06s; }
}
body:not(.pin-mode) header::before {
  opacity: 1;
  clip-path: inset(0 0 100% 0);
  transition: clip-path .5s cubic-bezier(.2, .65, .25, 1);
}
body:not(.pin-mode) header.scrolled::before { clip-path: inset(0 0 0 0); }
body:not(.pin-mode).menu-open header::before { opacity: 0; }
body:not(.pin-mode) header .brand img,
body:not(.pin-mode) header .menu-toggle { transition: filter 0s linear .06s, color 0s linear .06s; }
@media (prefers-reduced-motion: reduce) {
  body:not(.pin-mode) header::before,
  body:not(.pin-mode) header .brand img,
  body:not(.pin-mode) header .menu-toggle,
  body:not(.pin-mode) header nav a { transition: none; }
}

/* ---------- home: the firm (2026-10-24) ----------
   02 What we do reuses the room cards (three of them, not links); 03 How we work is the flipped
   rail | picture | copy layout with a row of four steps beneath; 04 The founder is the plain
   ed-layout. Pictures are filler (firm-* images) until the firm's own photography exists. */
.services-track { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.service-card p { max-width: 34ch; }
/* the service cards aren't links, so no hover zoom suggesting they are */
.service-card:hover .media img { scale: 1; }

.steps {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: clamp(var(--s3), 3.4vw, var(--s5));
  margin: clamp(var(--s5), 7vw, var(--s6)) 0 0;
  padding: 0;
  list-style: none;
}
.steps li { border-top: 1px solid var(--line); padding-top: var(--s2); }
.step-n {
  display: block;
  color: var(--olive);
  font-family: var(--display);
  font-size: clamp(2rem, 1.4rem + 1.6vw, 3rem);
  letter-spacing: -.06em;
  line-height: 1;
}
.steps h3 {
  margin: var(--s3) 0 var(--s1);
  color: var(--olive);
  font-family: var(--display);
  font-size: clamp(1.3rem, 1rem + .8vw, 1.7rem);
  letter-spacing: -.02em;
  line-height: 1.1;
}
.steps p,
.service-card p,
.founder-copy p {
  text-wrap: pretty;                 /* no one-word last lines */
}
.steps p {
  margin: 0;
  max-width: 28ch;
  color: rgba(88, 59, 31, .8);
  font-size: .95rem;
  line-height: 1.6;
}
@media (max-width: 55.99rem) {
  .steps { grid-template-columns: repeat(2, minmax(0, 1fr)); row-gap: var(--s5); }
}
/* phones: the service cards swipe like the room cards did (this block comes after the rooms-track
   phone rule, so it has to hand the columns back) and the steps stack */
@media (max-width: 47.99rem) {
  .services-track { grid-template-columns: none; }
  .steps { grid-template-columns: 1fr; }
}

/* ======================================================================
   Frames and notches (2026-10-24)
   Manuel: "evaluate the best spacing and notching on all the pages so that the page lands where it
   should when you stop scrolling, and so that content effectively uses the space in every frame".
   Every section after the opening is a FRAME: it lands with its top at the bar's bottom edge (or, the
   one after a photo opening, where the opening's settle already puts it), and on desktop it is at
   least that one screen tall, its content centred in it, its pictures sized so that it fits. Each
   section's own padding is its breathing room under the bar, so sections no longer share one gap
   (the old `.ed-section + .ed-section { padding-top: 0 }`). The notches themselves: touch screens
   snap natively (below), mice and trackpads through the settle in site.js.
   ====================================================================== */
:root {
  /* --bar-h is the bar's measured height (site.js); --header-h, its design value, runs ~10px short */
  --frame-h: calc(100svh - var(--bar-h, var(--header-h)));   /* a screen, less the bar */
  --frame-pad: clamp(2.5rem, 8svh, 7rem);                    /* a frame's room above and below its content */
}
/* an anchor lands where the notch is: under the bar's real height */
section, .chapter, .house-map { scroll-margin-top: var(--bar-h, var(--header-h)); }
/* 02 what we do: "See our work" sits under the heading row's note (it used to follow the cards) */
.head-note > p { margin: 0; }
.head-note .text-link { margin-top: var(--s3); }

/* desktop: one section, one frame */
@media (min-width: 56rem) and (min-height: 34rem) {
  .ed-section,
  .ed-section + .ed-section,
  .page-intro + .ed-section,
  body.has-gallery .approach-section {
    box-sizing: border-box;
    min-height: var(--frame-h);
    padding-block: var(--frame-pad);
    display: flex;
    flex-direction: column;
    justify-content: center;
  }
  /* after the photo opening the page settles with the bar over this section's top */
  body.has-gallery .approach-section { min-height: 100svh; padding-top: calc(var(--bar-h, var(--header-h)) + var(--frame-pad)); }
  /* the about page's opening: the title on the first screen on its own, set low like the photo openings' */
  .page-intro {
    box-sizing: border-box;
    min-height: 100svh;
    display: flex;
    flex-direction: column;
    justify-content: flex-end;
    padding-block: calc(var(--bar-h, var(--header-h)) + var(--frame-pad)) calc(var(--frame-pad) * 1.25);
  }
  /* the content a frame can hold: the screen less the bar and the frame's own padding */
  :root { --frame-in: calc(var(--frame-h) - 2 * var(--frame-pad)); }
  /* 01 approach (home and about): the tall picture is as tall as the frame allows (its caption under it) */
  .approach-visual-wrap { max-width: min(25rem, calc((var(--frame-in) - 4.5rem) * 2 / 3)); }
  /* 02 what we do: the heading row tighter (its heading on two lines, its note beside it), the cards'
     lines longer, the three pictures as tall as what is left (the heading row ~13.5rem, the captions
     and their lines ~10.5rem) */
  .services-section .rooms-head { margin-bottom: clamp(var(--s3), 4.5svh, var(--s5)); }
  .services-section .rooms-head .ed-copy { grid-template-columns: minmax(0, 1.3fr) minmax(16rem, 1fr); }
  .service-card p { max-width: 48ch; }
  /* (width: 100% -- a grid item with an aspect ratio otherwise takes its width from the capped height) */
  .services-track .media { width: 100%; max-height: calc(var(--frame-in) - 25.5rem); min-height: 11rem; }
  /* 03 how we work: the copy column wider so the heading takes two lines, the picture and the four
     steps sharing what is left */
  .process-section .ed-layout.flip { grid-template-columns: minmax(4.5rem, .48fr) minmax(0, 1.05fr) minmax(18rem, .95fr); }
  .process-section .ed-copy h2 { max-width: 12ch; }
  .process-section .frame-card.wide .media { width: 100%; max-height: calc(var(--frame-in) - 20rem); min-height: 13rem; }
  .process-section .steps { margin-top: clamp(var(--s4), 5svh, var(--s6)); }
  .steps p { max-width: 36ch; }
  /* the conversation panel: one frame too, the headline and the form level at their feet */
  #contact.contact-section .contact-inner {
    box-sizing: border-box;
    min-height: var(--frame-h);
    display: flex;
    flex-direction: column;
    justify-content: center;
    padding-block: var(--frame-pad);
  }
  .contact-grid { padding-top: clamp(var(--s3), 4.5svh, 5rem); }
  :is(body.has-gallery, body.inner-page) #contact.contact-section h2 { font-size: clamp(3.15rem, min(7.2vw, 15svh), 8rem); }
  /* the form's column is the panel's tall one: its gaps at body spacing, the message box shorter */
  .contact-copy > p { margin-bottom: var(--s3); }
  .contact-copy .contact-form { margin-bottom: var(--s3); }
  .contact-form textarea { height: clamp(4.5rem, 9svh, 7rem); min-height: 0; }
  /* the portfolio: the explorer lands under the bar after the photo opening, and every chapter keeps the
     same room above and below as the frames (it had ~9rem, which pushed most of them past a screen) */
  body.portfolio-page .house-map { padding-block: calc(var(--bar-h, var(--header-h)) + var(--frame-pad) * .75) calc(var(--frame-pad) * .5); }
  /* the explorer as tall as the frame leaves (it fits the house to whatever box it is given), its side column tighter */
  .house-map-head { margin-bottom: var(--s3); }
  .hx-3d { max-height: calc(var(--frame-in) - 4rem); }
  .hx-side { gap: var(--s3); }
  .hx-preview .media { width: 100%; max-height: 22svh; }
  .chapter-grid { padding-block: var(--frame-pad); }
  /* a short chapter is still a whole frame: no next chapter peeking in under it */
  .chapter:not(.chapter-full), .chapter.no-bleed { box-sizing: border-box; min-height: var(--frame-h); display: flex; flex-direction: column; justify-content: center; }
}

/* laptops (up to ~960px tall): a little less room around each frame, and the section headings sized
   from the screen's height as well as its width, so the frames still hold their content */
@media (min-width: 56rem) and (min-height: 34rem) and (max-height: 60rem) {
  :root { --frame-pad: clamp(2.5rem, 7svh, 7rem); }
  .approach-copy h2, .ed-copy h2 { font-size: clamp(2.8rem, min(5.2vw, 8svh), 5.8rem); }
}

/* the founder's picture, under the heading in the about page's founder section */
.founder-card { max-width: 26rem; justify-self: start; margin-top: var(--s4); }
@media (min-width: 56rem) and (min-height: 34rem) {
  .founder-card .media { width: 100%; max-height: calc(var(--frame-in) - 15rem); min-height: 12rem; }
}
@media (max-width: 55.99rem) {
  .founder-card { width: min(80vw, 24rem); margin-top: var(--s3); }
}

/* phones and tablets held upright: the sections stack, so each one only needs its own room under
   the bar when it lands (it used to start flush under the previous section's padding) */
@media (max-width: 55.99rem) {
  .ed-section,
  .ed-section + .ed-section,
  .page-intro + .ed-section { padding-block: clamp(3.5rem, 8svh, 5rem); }
  body.has-gallery .approach-section { padding-bottom: clamp(3.5rem, 8svh, 5rem); }
  /* the about page opens on its title alone, set low, as the other pages open on their photographs */
  .page-intro {
    box-sizing: border-box;
    min-height: 100svh;
    display: flex;
    flex-direction: column;
    justify-content: flex-end;
    padding-block: calc(var(--bar-h, var(--header-h)) + 3rem) clamp(3rem, 9svh, 5.5rem);
  }
}

/* ---------- the notches on touch screens: native, mandatory ----------
   Replaces the one tall `.snap-rest` area of 2026-10-21 with one area per section: each section
   is a snap area aligned at its start (its scroll-margin = the bar), and the footer one aligned at
   its end, so the page's end is always reachable. A section taller than the screen scrolls freely
   inside (an area larger than the snapport: CSS Scroll Snap 1, 6.2); where two sections meet, a
   rest lands on the next one's start or on the last one's end, never with the seam mid-screen.
   The home screen keeps its own snap point (.snap-home) and the section after a photo opening
   lands where the opening's second point always was (its top, no margin). */
@media (hover: none) and (pointer: coarse) {
  /* only where the top of the page is a snap point too: a page with no photo opening, or one whose
     opening runs pinned (.snap-home) -- without it, mandatory snapping would never let the opening rest */
  html:has(body.pin-mode), html:not(:has(.hero-gallery)) { scroll-snap-type: y mandatory; }
  main > :is(section, article, nav):not(.hero-gallery) { scroll-snap-align: start; scroll-margin-top: var(--bar-h, var(--header-h)); }
  body.pin-mode main > .hero-gallery + * { scroll-margin-top: 0; }
  body > footer { scroll-snap-align: end; }
  body.pin-mode .snap-rest { display: none; }
}
