/* ==========================================================================
   Thomas Chang website styles.

   The design in one paragraph: a fixed photograph (the "stage") sits behind the
   whole page. The words scroll through it on soft veils with no visible edge.
   Between passages the photograph shows at full strength. Change colours, type
   and spacing in the :root block below. Everything else reads from those values.

   Two tones decide how words look over a photo:
     dusk: white words on a dark veil
     mist: dark words on a pale veil
   A photo's own veil colour is set in content/photos.json, taken from its sky or
   water, so a veil reads as more of the photograph and not as a tint on top.
   ========================================================================== */

@font-face { font-family: "Jost"; src: url("/assets/fonts/jost-var.woff2") format("woff2"); font-weight: 100 900; font-style: normal; font-display: swap; }
@font-face { font-family: "Source Serif 4"; src: url("/assets/fonts/source-serif-4-var.woff2") format("woff2"); font-weight: 200 900; font-style: normal; font-display: swap; }
@font-face { font-family: "Source Serif 4"; src: url("/assets/fonts/source-serif-4-italic-var.woff2") format("woff2"); font-weight: 200 900; font-style: italic; font-display: swap; }

:root {
  --ink: #0f1e2b;
  --paper: #fbf5ea;
  --cream: #f8ecd9;
  --error: #a32020;

  --font-head: "Jost", "Avenir Next", "Century Gothic", "Helvetica Neue", Arial, sans-serif;
  --font-body: "Source Serif 4", Georgia, "Times New Roman", serif;

  --container: 1240px;
  --gutter: max(32px, env(safe-area-inset-left), env(safe-area-inset-right));
  --measure: 37rem;                       /* the reading column, about 66 characters wide */
  --small: 1.125rem;                      /* notes, captions and tables: the smallest reading size */
  --feather: clamp(130px, 21vh, 230px);   /* how far a veil takes to fade out, top and bottom */
  --banner-pad: clamp(48px, 9vh, 96px);   /* the space under the words of a page banner */
  --cover-bottom: 0px;                    /* how much of the screen the phone bar covers */
  /* The open stretch of photograph between passages. With the two veil fades added, it
     spans exactly the part of the screen the header and phone bar leave free, so
     the stage is empty for a moment and no taller than that. */
  --open: calc(100vh - var(--bar-solid) - var(--cover-bottom) - 2 * var(--feather));
  --header: 92px;                         /* the space the header takes up at the top of the page */
  --bar-solid: 62px;                      /* how far the header's veil stays almost opaque, behind its words */
  --bar-fade: 104px;                      /* where it has fallen away to nothing */
  --bar-change: 240ms;                    /* how long the header takes to change tone */
  --action-bar: 56px;                     /* the phone action bar, before the iPhone home bar */
  --ease: .25s ease;
  --fade: 1s;                             /* the backdrop dissolve. DISSOLVE_MS in stage.js matches it */
  --fade-curve: ease-in-out;              /* stage.js sets both of these for each change of photo */
}

/* White words on a dark veil. */
.tone-dusk {
  --fg: #fff;
  --fg-soft: #eff3f6;
  --fg-faint: #d5dee5;
  --accent: #f2d2ab;
  --rule: rgb(255 255 255 / .28);
  --btn-line: rgb(255 255 255 / .75);
  --veil: 10 23 33;
  --veil-alpha: .66;
  --btn-bg: var(--cream);
  --btn-fg: var(--ink);
  --btn-hover: #fff;
  color: var(--fg-soft);
}

/* Dark words on a pale veil. */
.tone-mist {
  --fg: var(--ink);
  --fg-soft: #1f2d38;
  --fg-faint: #36444f;
  --accent: #532410;
  --rule: rgb(15 30 43 / .26);
  --btn-line: rgb(15 30 43 / .75);
  --veil: 251 245 234;
  --veil-alpha: .76;
  --btn-bg: var(--ink);
  --btn-fg: #fff;
  --btn-hover: #27404f;
  color: var(--fg-soft);
}

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

/* A button or panel marked hidden must stay hidden, whatever its own display rule says. */
[hidden] { display: none !important; }
/* The skip link moves focus here, and a page-sized outline helps no one. */
main:focus { outline: none; }
/* The header floats on the photograph, so the page slides up underneath it. */
main { margin-top: calc(-1 * var(--header)); }

/* <picture> only chooses the file. The <img> inside it does the layout. */
picture { display: contents; }

html {
  background: var(--ink);
  -webkit-text-size-adjust: 100%; text-size-adjust: 100%;
}
/* When the page moves to a link or a focused box, this leaves room for the floating header.
   It sits on the things in the page and not on <html>, because there it would also count
   for the header's own links, and the browser would scroll away to "uncover" them. */
main :is(a, button, input, textarea, [id]), .site-footer a { scroll-margin-top: calc(var(--header) + 12px); }
/* In-page links glide. Not while a page opens, or a deep link or the back button
   would slide down through every photo on the way. */
@media (prefers-reduced-motion: no-preference) { html.has-stage:not(.is-arriving) { scroll-behavior: smooth; } }
body {
  margin: 0;
  color: var(--ink);
  font-family: var(--font-body);
  font-size: 1.1875rem;
  line-height: 1.68;
  font-optical-sizing: auto;
  font-variant-numeric: lining-nums;
  overflow-wrap: break-word;
  -webkit-font-smoothing: antialiased;
}
img, svg { max-width: 100%; height: auto; display: block; }
h1, h2, h3, h4 { margin: 0; color: var(--fg, var(--ink)); }
p, ul, ol, table, blockquote, figure { margin: 0; }
ul[class], ol[class] { list-style: none; padding: 0; }
p + p { margin-top: 1em; }
a { color: inherit; text-decoration-color: var(--accent); text-decoration-thickness: 1px; text-underline-offset: .2em; }
a:hover { text-decoration-thickness: 2px; }
/* A phone number stays in one piece. */
a[href^="tel:"] { white-space: nowrap; }
button, input, select, textarea { font: inherit; color: inherit; }
strong { font-weight: 600; color: var(--fg, var(--ink)); }

/* Keyboard focus. A white ring with a dark edge shows up on any photo. */
:focus { outline: none; }
:focus-visible { outline: 3px solid #fff; outline-offset: 3px; box-shadow: 0 0 0 3px var(--ink); }
/* Headings that receive focus from a script should not show a ring. */
[tabindex="-1"]:focus, [tabindex="-1"]:focus-visible { outline: none; box-shadow: none; }

.skip {
  position: absolute; left: 16px; top: -80px; z-index: 100;
  background: #fff; color: var(--ink); padding: 12px 20px; font-family: var(--font-head); font-weight: 500;
}
.skip:focus { top: 12px; }

/* Type scale. Source Serif 4 at a light weight becomes an elegant display face. */
h1 {
  font-family: var(--font-body); font-weight: 300;
  font-size: clamp(2.875rem, 6.2vw, 6rem);
  line-height: 1.04; letter-spacing: -.012em;
}
h2 {
  font-family: var(--font-body); font-weight: 300;
  font-size: clamp(2.125rem, 3.7vw, 3.375rem);
  line-height: 1.1; letter-spacing: -.008em;
}
/* Keeps a large heading from ending on one lonely word. */
h1, h2 { text-wrap: balance; }
h3 { font-family: var(--font-head); font-weight: 500; font-size: 1.3125rem; line-height: 1.3; letter-spacing: .005em; }
h4 { font-family: var(--font-head); font-weight: 500; font-size: 1.125rem; line-height: 1.35; letter-spacing: .01em; }
.lead { font-size: clamp(1.25rem, 1.62vw, 1.4375rem); line-height: 1.52; color: var(--fg); }
.small { font-size: var(--small); line-height: 1.55; }

.eyebrow {
  display: flex; align-items: center; gap: 14px;
  margin: 0 0 18px;
  font-family: var(--font-head); font-size: .8125rem; font-weight: 500;
  letter-spacing: .18em; line-height: 1.3; text-transform: uppercase; color: var(--fg); overflow-wrap: anywhere;
}
.eyebrow::before { content: ""; width: 28px; height: 1px; background: var(--accent); flex: none; }

.section__head { margin-bottom: clamp(26px, 3.4vw, 40px); }
.section__head p:not(.eyebrow) { margin-top: 16px; }
.flow { margin-top: 20px; }

/* Buttons */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 12px;
  min-height: 54px; padding: 14px 30px;
  font-family: var(--font-head); font-size: .875rem; font-weight: 500;
  letter-spacing: .1em; line-height: 1.2; text-align: center; text-decoration: none; text-transform: uppercase;
  border: 1px solid var(--btn-bg); border-radius: 0; background: var(--btn-bg); color: var(--btn-fg); cursor: pointer;
  transition: background var(--ease), color var(--ease), border-color var(--ease);
}
.btn:hover { background: var(--btn-hover); border-color: var(--btn-hover); }
/* The quieter action: a clear thin line around a light fill. */
.btn--outline { background: rgb(var(--veil) / .5); color: var(--fg); border-color: var(--btn-line); }
.btn--outline:hover { background: rgb(var(--veil) / .75); color: var(--fg); border-color: var(--fg); }
.btn-row { display: flex; flex-wrap: wrap; gap: 14px; margin-top: 28px; }
.link-arrow {
  display: inline-flex; align-items: center; gap: 12px; min-height: 44px; margin-top: 20px;
  font-family: var(--font-head); font-size: .875rem; font-weight: 500; letter-spacing: .1em; text-transform: uppercase; text-decoration: none; color: var(--fg);
}
.link-arrow::after { content: ""; width: 54px; height: 1px; background: currentColor; transform: scaleX(.63); transform-origin: left; transition: transform var(--ease); }
.link-arrow:hover::after { transform: none; }
/* Text messages only make sense on a phone. */
.only-phone { display: none; }

/* --------------------------------------------------------------------------
   The stage: the fixed photograph behind the page
   -------------------------------------------------------------------------- */

/* "lvh" means the height of the screen with the iPhone browser bar tucked away.
   That height never changes, so the photo does not jump as the bar grows and shrinks. */
.stage { position: fixed; top: 0; left: 0; width: 100%; height: 100vh; height: 100lvh; z-index: -1; overflow: hidden; }
.stage__photo { position: absolute; inset: 0; }
.has-stage .stage__photo { opacity: 0; transition: opacity var(--fade) var(--fade-curve); will-change: opacity; }
.has-stage .stage__photo.is-shown { opacity: 1; }
/* A photo that arrives late, with its colour already up, fades in over that colour. */
.has-stage .stage__photo img { opacity: 0; transition: opacity .3s ease; }
.has-stage .stage__photo.is-ready img { opacity: 1; }
/* While a page opens on a deep link or a reload, nothing dissolves or fades. */
.is-arriving { --fade: 0s; --bar-change: 0s; }
.is-arriving .stage__photo img { transition: none; }
.stage__photo img { width: 100%; height: 100%; object-fit: cover; object-position: var(--focus, 50% 50%); }
/* The frame at the top of a long read on a phone. See the phone rules below. */
.head-rail { display: none; }
/* A graduated filter, like the glass one a photographer holds over a bright sky. It
   belongs to the photo and never moves, so it reads as a deeper sky and not as a veil.
   A photo asks for it with "shade" in content/photos.json. */
.stage__photo--shaded::after {
  content: ""; position: absolute; inset: 0; pointer-events: none;
  background: linear-gradient(to bottom,
    rgb(var(--shade-colour) / var(--shade)) 0,
    rgb(var(--shade-colour) / calc(var(--shade) * .86)) 14%,
    rgb(var(--shade-colour) / calc(var(--shade) * .55)) 28%,
    rgb(var(--shade-colour) / calc(var(--shade) * .24)) 40%,
    rgb(var(--shade-colour) / 0) 54%);
}

/* --------------------------------------------------------------------------
   Scenes and passages
   -------------------------------------------------------------------------- */

.scene { position: relative; isolation: isolate; color: var(--fg-soft); }
/* The open stretch before a scene's words. The photo changes when the marker in it
   reaches the middle of the screen. At that moment the last words have just gone under
   the header and the next ones have not yet risen from the bottom, so the new photo
   arrives on an empty stage. The marker sits a little off centre, because the header
   and the phone bar cover different amounts of the screen. */
.scene__open { position: relative; height: calc(var(--open) + var(--turn-room, 0px)); }
.scene__cue { position: absolute; top: 0; width: 1px; height: 1px; pointer-events: none; }
.scene__open .scene__cue { top: calc(50% + (var(--cover-bottom) - var(--bar-solid) - var(--turn-room, 0px)) / 2); }
/* A page banner has less padding under its words than a passage has. After a photo of
   homes the open stretch makes up the difference, so it still spans one clear screen. */
.has-stage .scene--first[data-apart] + .scene > .scene__open { height: calc(var(--open) + var(--feather) - var(--banner-pad) + var(--turn-room, 0px)); }

/* A quiet place name, set like a dateline above a passage. */
.scene__place { margin: 0 0 14px; font-family: var(--font-body); font-style: italic; font-weight: 300; font-size: 1.625rem; line-height: 1.2; letter-spacing: .005em; color: var(--fg); }

/* A place name belongs to its photo. It fades away at once when the photo starts to
   leave, and comes in only when its own photo is fully showing, so it never sits on a
   photo of somewhere else. stage.js adds the classes, so on the plain page, where
   every name scrolls with its own photo, each one simply stays visible. */
.has-stage .scene__place { opacity: 0; transition: opacity .15s ease; }
.has-stage .scene.is-current .scene__place { opacity: 1; transition: opacity .5s ease var(--fade); }

/* Words on a soft veil. The veil scrolls with the words. The vertical fade is
   painted into the background, and a mask adds the sideways fade on wide screens.
   The percentages trace a gentle S-curve, because a straight fade shows its start
   and end as a faint line. */
.passage { position: relative; isolation: isolate; padding-block: var(--feather); }
.passage::before {
  content: ""; position: absolute; inset: 0; z-index: -1; pointer-events: none;
  --top-fade: calc(var(--feather) - var(--veil-late, 0px));
  --end-fade: calc(var(--feather) - var(--veil-early, 0px));
  background: linear-gradient(to bottom,
    rgb(var(--veil) / 0) var(--veil-late, 0px),
    rgb(var(--veil) / calc(var(--veil-alpha) * .1)) calc(var(--veil-late, 0px) + var(--top-fade) * .2),
    rgb(var(--veil) / calc(var(--veil-alpha) * .35)) calc(var(--veil-late, 0px) + var(--top-fade) * .4),
    rgb(var(--veil) / calc(var(--veil-alpha) * .65)) calc(var(--veil-late, 0px) + var(--top-fade) * .6),
    rgb(var(--veil) / calc(var(--veil-alpha) * .9)) calc(var(--veil-late, 0px) + var(--top-fade) * .8),
    rgb(var(--veil) / var(--veil-alpha)) var(--feather),
    rgb(var(--veil) / var(--veil-alpha)) calc(100% - var(--feather)),
    rgb(var(--veil) / calc(var(--veil-alpha) * .9)) calc(100% - var(--feather) + var(--end-fade) * .2),
    rgb(var(--veil) / calc(var(--veil-alpha) * .65)) calc(100% - var(--feather) + var(--end-fade) * .4),
    rgb(var(--veil) / calc(var(--veil-alpha) * .35)) calc(100% - var(--feather) + var(--end-fade) * .6),
    rgb(var(--veil) / calc(var(--veil-alpha) * .1)) calc(100% - var(--feather) + var(--end-fade) * .8),
    rgb(var(--veil) / 0) calc(100% - var(--veil-early, 0px)));
}
.passage__inner { width: min(100% - var(--gutter) * 2, var(--container)); margin-inline: auto; }
.passage__inner > * { max-width: var(--measure); }
/* Every line starts from one left edge, however wide it is. */
.passage--right .passage__inner > * { margin-left: max(0px, 100% - var(--measure)); }

/* Where white words give way to dark ones, or dark to white, a veil would show on the
   other photo as a strip of fog or of shadow along the edge of the screen, in the moment
   before the photos change. So at such a turn the two veils meet the photo later and leave
   it sooner, and the open stretch grows by the length of their fades. Then neither veil
   is on screen when the photo changes, and on its own photo a shorter fade has no edge
   to show. blocks.mjs marks each turn. */
.scene--turn { --turn-room: calc(var(--feather) * .45); }
.scene--turn > .passage { --veil-late: calc(var(--feather) * .55); }
.scene:has(+ .scene--turn) > .passage:last-child,
.reading:has(+ .scene--turn) > .scene:last-child > .passage { --veil-early: calc(var(--feather) * .55); }

/* The sideways fade. --clear is where the words end, and the veil is gone by
   --clear plus --drift, so the far side of the screen stays pure photograph. --start
   is where the words begin. It only matters on a very wide screen, where the veil fades
   on that side too, so it stays a soft pool around the words and never grows into a band.
   It starts 140px out from the words, so on a laptop it reaches the edge of the screen.
   A "band" passage has no fade beside its words. It crosses the width of the page,
   which keeps a bright horizon even instead of cutting it in two. */
@media (min-width: 901px) {
  .passage, .toc-rail {
    --start: calc((100% - var(--container)) / 2 - 140px);
    --clear: calc(max(var(--gutter), (100% - var(--container)) / 2) + var(--measure) + 44px);
    --drift: min(21vw, 620px);
  }
  .passage::before, .passage--banner .passage__inner::before, .desk::before, .toc-rail {
    --fade: transparent calc(var(--start) - var(--drift)),
      rgb(0 0 0 / .1) calc(var(--start) - var(--drift) * .8),
      rgb(0 0 0 / .35) calc(var(--start) - var(--drift) * .6),
      rgb(0 0 0 / .65) calc(var(--start) - var(--drift) * .4),
      rgb(0 0 0 / .9) calc(var(--start) - var(--drift) * .2),
      #000 var(--start),
      #000 var(--clear),
      rgb(0 0 0 / .9) calc(var(--clear) + var(--drift) * .2),
      rgb(0 0 0 / .65) calc(var(--clear) + var(--drift) * .4),
      rgb(0 0 0 / .35) calc(var(--clear) + var(--drift) * .6),
      rgb(0 0 0 / .1) calc(var(--clear) + var(--drift) * .8),
      transparent calc(var(--clear) + var(--drift));
    -webkit-mask-image: linear-gradient(to right, var(--fade));
    mask-image: linear-gradient(to right, var(--fade));
  }
  .passage--right::before, .passage--right .desk::before {
    -webkit-mask-image: linear-gradient(to left, var(--fade));
    mask-image: linear-gradient(to left, var(--fade));
  }
  .passage--band, .passage--wide { --clear: calc(100% - (100% - var(--container)) / 2 + 140px); }
  /* A faint veil over a calm sky can stop close to the words. */
  .passage--near { --measure: 30rem; --drift: 200px; }
  .passage--near .btn-row { max-width: none; }
  .passage--band .passage__inner > * { max-width: none; }
  .passage--band .section__head { max-width: 40rem; }
  .passage--band .flow, .passage--band .rows, .passage--band .ledger { max-width: var(--measure); }
}

/* The side of a near veil only stays out of sight on a wide window, where the photo's
   calm sky reaches past the words. On a narrower one, or an upright one, the photo's subject
   stands behind them, so the veil crosses the whole window, as it does on a phone. */
@media (min-width: 901px) and (max-width: 1299px), (min-width: 901px) and (max-aspect-ratio: 5/4) {
  .passage--near::before { -webkit-mask-image: none; mask-image: none; }
  .passage--near { --veil-alpha: .9; }
}

/* On a very wide screen the page sits in the middle of the photo, so words that had
   open sky on a laptop can land on whatever stands there. */
@media (min-width: 1500px) {
  .passage--near { --drift: 340px; }
  /* The closing words land on the bright reflection under the pier. */
  .scene .passage--close { --veil-alpha: .66; }
}

/* Text beside a photo crosses the whole screen, so its veil does too. */
.passage--wide .passage__inner > * { max-width: none; }
.passage--table { --measure: 54rem; }

/* Home hero: every word sits in the calm sky of the photograph, with no veil. */
.passage--hero { min-height: 100vh; min-height: 100svh; padding-block: calc(var(--header) + clamp(0px, .8vh, 10px)) 0; }
.passage--hero::before { display: none; }
.passage--hero .passage__inner > * { max-width: none; }
.passage--hero h1 { font-size: clamp(2.875rem, 5.4vw, 5.625rem); }
.passage--hero .lead { max-width: 34em; margin-top: clamp(14px, 2vh, 22px); font-size: clamp(1.125rem, 1.45vw, 1.3125rem); }
.passage--hero .btn-row { margin-top: clamp(16px, 2.6vh, 26px); align-items: center; }
/* The first photo's place name sits in the sky, away from the words. */
.passage--hero .passage__inner { position: relative; }
.passage--hero .scene__place { position: absolute; right: 0; top: 4px; margin: 0; }
/* The main action is a filled button. The call is a quiet text link beside it. */
.passage--hero .btn--outline {
  min-height: 44px; padding: 8px 22px; border-color: transparent; text-decoration: underline; text-decoration-color: var(--accent); text-underline-offset: .35em;
  /* A soft glow with no edge, so the words stay clear if distant palms sit behind them. */
  background: radial-gradient(ellipse closest-side, rgb(var(--veil) / .85) 64%, rgb(var(--veil) / 0) 100%);
}
.passage--hero .btn--outline:hover { border-color: transparent; text-decoration-thickness: 2px; }

/* Interior page banner: a nearly full screen of photograph. The words sit low, and
   the veil rises from just above them, however many lines they take. So on a short
   screen it reaches the first word, and on a tall one the top of the picture stays at
   full strength. The veil hangs on the words here, and reaches out to the screen edges. */
.passage--banner { min-height: 92vh; min-height: 92svh; display: flex; flex-direction: column; justify-content: flex-end; padding-top: 0; padding-bottom: var(--banner-pad); overflow-x: clip; --measure: 44rem; --rise: calc(var(--feather) * 1.5); }
.passage--banner::before { display: none; }
.passage--banner .passage__inner { position: relative; }
.passage--banner .passage__inner::before {
  content: ""; position: absolute; z-index: -1; pointer-events: none;
  top: calc(var(--rise) * -1 - 14px); bottom: calc(var(--banner-pad) * -1); left: calc(50% - 50vw); right: calc(50% - 50vw);
  background: linear-gradient(to bottom,
    rgb(var(--veil) / 0) 0,
    rgb(var(--veil) / calc(var(--veil-alpha) * .1)) calc(var(--rise) * .2),
    rgb(var(--veil) / calc(var(--veil-alpha) * .35)) calc(var(--rise) * .4),
    rgb(var(--veil) / calc(var(--veil-alpha) * .65)) calc(var(--rise) * .6),
    rgb(var(--veil) / calc(var(--veil-alpha) * .9)) calc(var(--rise) * .8),
    rgb(var(--veil) / var(--veil-alpha)) var(--rise),
    rgb(var(--veil) / var(--veil-alpha)) calc(100% - var(--banner-pad)),
    rgb(var(--veil) / calc(var(--veil-alpha) * .65)) calc(100% - var(--banner-pad) * .6),
    rgb(var(--veil) / calc(var(--veil-alpha) * .35)) calc(100% - var(--banner-pad) * .4),
    rgb(var(--veil) / calc(var(--veil-alpha) * .1)) calc(100% - var(--banner-pad) * .2),
    rgb(var(--veil) / 0) 100%);
}
.passage--banner h1 { font-size: clamp(2.625rem, 5.2vw, 5rem); max-width: 13em; }
.passage--banner .lead { margin-top: 18px; max-width: 34em; }
.crumbs { margin: 0 0 20px; padding: 0; list-style: none; display: flex; flex-wrap: wrap; gap: 8px; font-family: var(--font-head); font-size: .8125rem; letter-spacing: .08em; color: var(--fg-soft); }
.crumbs li + li::before { content: "/"; margin-right: 8px; opacity: .7; }
/* Padding on a link inside a line makes it easier to tap and moves nothing. */
.crumbs a { padding: 13px 4px; margin-inline: -4px; color: var(--fg); }

.passage--high { justify-content: flex-start; padding-top: calc(var(--header) + clamp(10px, 4vh, 44px)); --measure: 37rem; }
.passage--high .passage__inner::before { display: none; }
/* As on the home page, a place name stays out of the way, up in the sky. */
@media (min-width: 701px) {
  .passage--high .scene__place { position: absolute; right: 0; top: 4px; margin: 0; }
}
/* "haze" in a content file: on an upright or a short screen something in the photo leans
   in behind the words and no crop clears it, so a veil in the colour of the sky comes
   back, only behind the words, and falls away below them. */
@media (max-width: 900px), (max-height: 760px) {
  .passage--haze .passage__inner::before {
    display: block; top: calc(var(--header) * -1 - 44px); bottom: calc(var(--feather) * -1);
    background: linear-gradient(to bottom,
      rgb(var(--veil) / var(--veil-alpha)) calc(100% - var(--feather)),
      rgb(var(--veil) / calc(var(--veil-alpha) * .9)) calc(100% - var(--feather) * .8),
      rgb(var(--veil) / calc(var(--veil-alpha) * .65)) calc(100% - var(--feather) * .6),
      rgb(var(--veil) / calc(var(--veil-alpha) * .35)) calc(100% - var(--feather) * .4),
      rgb(var(--veil) / calc(var(--veil-alpha) * .1)) calc(100% - var(--feather) * .2),
      rgb(var(--veil) / 0) 100%);
  }
  /* On a page with a form, "haze" stays behind the words and falls away as the form
     starts. Its own veil takes over from there, so the two never add up to a wall. */
  .passage--desk.passage--haze .passage__inner::before { bottom: auto; height: calc(var(--header) + 44px + 250px + var(--feather)); }
}
/* On a wide screen a form stands clear of the photo's subject, so it needs no haze. */
@media (min-width: 901px) {
  .passage--desk.passage--haze .passage__inner::before { display: none; }
}

/* A page that gets straight to work. Its form or first question comes right under the
   words, so it is on the first screen, and the banner keeps only the sky it needs. The
   veil rises from behind the last words, reaches full strength where the form starts,
   and falls away below it. Beside the form on a wide screen, the photo stays open. */
.passage--desk { min-height: 0; padding-bottom: var(--feather); --rise: clamp(96px, 17vh, 170px); }
.passage--desk .passage__inner > .desk { position: relative; max-width: none; margin: clamp(14px, 2.6vh, 28px) 0 0; }
.desk > * { max-width: var(--measure); }
.passage--right .desk > *, .passage--right .desk .note { margin-left: max(0px, 100% - var(--measure)); }
.desk .note { max-width: var(--measure); }
.desk::before {
  content: ""; position: absolute; z-index: -1; pointer-events: none;
  top: calc(var(--rise) * -1); bottom: calc(var(--feather) * -1); left: calc(50% - 50vw); right: calc(50% - 50vw);
  background: linear-gradient(to bottom,
    rgb(var(--veil) / 0) 0,
    rgb(var(--veil) / calc(var(--veil-alpha) * .1)) calc(var(--rise) * .2),
    rgb(var(--veil) / calc(var(--veil-alpha) * .35)) calc(var(--rise) * .4),
    rgb(var(--veil) / calc(var(--veil-alpha) * .65)) calc(var(--rise) * .6),
    rgb(var(--veil) / calc(var(--veil-alpha) * .9)) calc(var(--rise) * .8),
    rgb(var(--veil) / var(--veil-alpha)) var(--rise),
    rgb(var(--veil) / var(--veil-alpha)) calc(100% - var(--feather)),
    rgb(var(--veil) / calc(var(--veil-alpha) * .9)) calc(100% - var(--feather) * .8),
    rgb(var(--veil) / calc(var(--veil-alpha) * .65)) calc(100% - var(--feather) * .6),
    rgb(var(--veil) / calc(var(--veil-alpha) * .35)) calc(100% - var(--feather) * .4),
    rgb(var(--veil) / calc(var(--veil-alpha) * .1)) calc(100% - var(--feather) * .2),
    rgb(var(--veil) / 0) 100%);
}

/* A long read: the photo melts into paper, and the paper simply carries on into
   the article below with no join. The paper is solid behind the words, so nothing from
   the photo shows through them, and warm on every page, whatever the photo. The photo
   stays open beside it on a wide screen. */
.passage--paper, .passage--joined, .passage--reading { --measure: 35rem; --veil-alpha: 1; }
.tone-mist .passage--paper, .tone-mist .passage--joined, .tone-mist .passage--reading { --veil: 251 245 234; }
.passage--paper { padding-bottom: 0; --rise: calc(var(--feather) * 2.2); }
.passage--paper .passage__inner::before {
  bottom: 0;
  background: linear-gradient(to bottom,
    rgb(var(--veil) / 0) 0,
    rgb(var(--veil) / calc(var(--veil-alpha) * .1)) calc(var(--rise) * .2),
    rgb(var(--veil) / calc(var(--veil-alpha) * .35)) calc(var(--rise) * .4),
    rgb(var(--veil) / calc(var(--veil-alpha) * .65)) calc(var(--rise) * .6),
    rgb(var(--veil) / calc(var(--veil-alpha) * .9)) calc(var(--rise) * .8),
    rgb(var(--veil) / var(--veil-alpha)) var(--rise));
}
.passage--paper h1 { max-width: var(--measure); font-size: clamp(2.5rem, 4.1vw, 3.625rem); }
/* The words stay on the solid paper, and never run out onto the fading edge. */
.passage--paper .lead { max-width: var(--measure); }
.passage--paper .crumbs { color: var(--fg-soft); }
.passage--joined { padding-top: clamp(28px, 4vh, 44px); }
.passage--joined::before {
  background: linear-gradient(to bottom,
    rgb(var(--veil) / var(--veil-alpha)) 0,
    rgb(var(--veil) / var(--veil-alpha)) calc(100% - var(--feather)),
    rgb(var(--veil) / calc(var(--veil-alpha) * .9)) calc(100% - var(--feather) + var(--end-fade) * .2),
    rgb(var(--veil) / calc(var(--veil-alpha) * .65)) calc(100% - var(--feather) + var(--end-fade) * .4),
    rgb(var(--veil) / calc(var(--veil-alpha) * .35)) calc(100% - var(--feather) + var(--end-fade) * .6),
    rgb(var(--veil) / calc(var(--veil-alpha) * .1)) calc(100% - var(--feather) + var(--end-fade) * .8),
    rgb(var(--veil) / 0) calc(100% - var(--veil-early, 0px)));
}

/* A photograph set into the page. The fine inner line gives it the look of a framed print. */
.frame { position: relative; }
.frame img { width: 100%; outline: 1px solid rgb(255 255 255 / .55); outline-offset: -12px; }
.frame figcaption { margin-top: 16px; font-size: var(--small); line-height: 1.5; color: var(--fg-soft); }

/* Text beside a photo, and the home portrait beside its questions. */
.split, .with-portrait { display: grid; grid-template-columns: minmax(0, 37rem) minmax(0, 27rem); justify-content: space-between; gap: clamp(32px, 6vw, 88px); align-items: start; }
.with-portrait .frame { position: sticky; top: calc(var(--header) + 20px); }

/* Three short ideas: read, eye, trust */
.trio > * { padding-block: 20px; border-top: 1px solid var(--rule); }
.trio h3 { margin-bottom: 6px; }
.trio__tag { display: block; margin-bottom: 8px; font-family: var(--font-head); font-size: .8125rem; font-weight: 500; letter-spacing: .16em; text-transform: uppercase; color: var(--accent); }

/* Question and answer rows */
.rows > div { padding-block: 22px; border-top: 1px solid var(--rule); }
.rows h3 { margin-bottom: 8px; }

/* The ledger: real numbers in a real table */
.ledger { width: 100%; border-collapse: collapse; border-top: 1px solid var(--fg); font-variant-numeric: lining-nums tabular-nums; }
.ledger caption { caption-side: bottom; padding-top: 14px; text-align: left; font-size: var(--small); line-height: 1.45; color: var(--fg-soft); }
.ledger th { padding: 14px 20px 14px 0; border-bottom: 1px solid var(--rule); text-align: left; vertical-align: bottom; font-family: var(--font-head); font-size: .8125rem; font-weight: 500; letter-spacing: .12em; text-transform: uppercase; color: var(--fg); }
.ledger td { padding: 18px 20px 18px 0; border-bottom: 1px solid var(--rule); vertical-align: top; font-size: var(--small); line-height: 1.45; }
.ledger td.num { font-family: var(--font-body); font-weight: 300; font-size: 1.75rem; line-height: 1.1; color: var(--fg); white-space: nowrap; }

/* A plain statement */
.note { padding-left: 24px; border-left: 1px solid var(--accent); }

/* Area tiles: the place name is the link, and the photo is the place */
.passage--tile { --measure: 34rem; }
.tile { display: block; color: var(--fg); text-decoration: none; }
.tile h3 { font-family: var(--font-body); font-weight: 300; font-size: clamp(2.5rem, 5vw, 4.5rem); line-height: 1.05; letter-spacing: -.01em; }
.tile p { margin-top: 12px; color: var(--fg-soft); }
.tile::after { content: ""; display: block; width: 88px; height: 1px; margin-top: 22px; background: currentColor; transform: scaleX(.5); transform-origin: left; transition: transform var(--ease); }
.tile:hover::after, .tile:focus-visible::after { transform: none; }

/* Numbered steps */
.steps { counter-reset: step; }
.steps > li { position: relative; counter-increment: step; padding: 20px 0 20px 68px; border-top: 1px solid var(--rule); }
.steps > li::before { content: counter(step); position: absolute; left: 0; top: 10px; font-family: var(--font-body); font-weight: 300; font-size: 2.5rem; line-height: 1.2; color: var(--accent); font-variant-numeric: lining-nums; }
.steps h3 { margin-bottom: 6px; }
@media (min-width: 991px) {
  /* In a full-width band the steps run side by side. */
  .passage--band .steps { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: clamp(24px, 3vw, 44px); }
  .passage--band .steps > li { padding: 16px 0 0; }
  .passage--band .steps > li::before { position: static; display: block; margin-bottom: 6px; }
}

/* Compact link list and cards */
.cards > a { display: block; padding-block: 22px; border-top: 1px solid var(--rule); text-decoration: none; color: inherit; }
.cards h3 { margin-bottom: 6px; }
.cards > a:hover h3 { text-decoration: underline; text-decoration-color: var(--accent); text-underline-offset: .2em; }

/* The last scene of a page */
.passage--close { --veil-alpha: .62; }
.passage--close h2 { font-size: clamp(2.5rem, 5.2vw, 5rem); line-height: 1.05; }
.passage--close p:not(.eyebrow) { margin-top: 14px; color: var(--fg); }

/* Long reads. "clip" stops the soft pool behind the headings list from widening
   the page, and unlike "hidden" it still lets that list stick in place. */
.reading { position: relative; overflow-x: clip; }
/* Until there is room beside the column, the headings list comes first, on the same paper. */
.toc-rail { --measure: 35rem; padding: 8px max(var(--gutter), calc((100% - var(--container)) / 2)) 28px; background: rgb(var(--veil)); }
.toc-rail .toc { max-width: 35rem; }
/* A pause in a long read: the mist parts and the photograph shows through. */
.scene__open--pause { height: 34vh; }
.passage--plain { --veil-alpha: .92; }
/* A form or a plan should not compete with the photograph behind it. */
.scene .passage--form { --veil-alpha: .94; }
/* A link to a section lands on its words, not on the top of its fading veil. */
.passage[id] { scroll-margin-top: calc(var(--header) + 24px - var(--feather)); }
.prose > * + * { margin-top: 1em; }
.prose h2 { margin-top: 2em; font-size: clamp(1.75rem, 2.5vw, 2.25rem); }
.prose h2:first-child { margin-top: 0; }
.prose h3 { margin-top: 1.8em; }
.prose ul, .prose ol { padding-left: 1.25em; }
.prose li + li { margin-top: .5em; }
.prose blockquote { padding-left: 20px; border-left: 1px solid var(--accent); font-style: italic; }
.prose table { width: 100%; border-collapse: collapse; border-top: 1px solid var(--fg); font-size: var(--small); line-height: 1.45; }
.prose th { padding: 12px 16px 12px 0; border-bottom: 1px solid var(--rule); text-align: left; font-family: var(--font-head); font-size: .8125rem; font-weight: 500; letter-spacing: .1em; text-transform: uppercase; color: var(--fg); vertical-align: bottom; }
.prose td { padding: 12px 16px 12px 0; border-bottom: 1px solid var(--rule); vertical-align: top; }
/* Padding on a link inside a line makes it easier to tap and moves nothing. */
.prose td a { padding-block: 10px; }
.callout { padding-left: 24px; border-left: 1px solid var(--accent); }
.callout > * + * { margin-top: .6em; }
.callout__title { font-family: var(--font-head); font-size: .8125rem; font-weight: 500; letter-spacing: .14em; text-transform: uppercase; color: var(--fg); }
.toc { font-size: var(--small); line-height: 1.4; }
.toc__title { margin-bottom: 10px; font-family: var(--font-head); font-size: .8125rem; font-weight: 500; letter-spacing: .14em; text-transform: uppercase; color: var(--fg); }
.toc ul { margin: 0; padding: 0; list-style: none; }
.toc li { padding-block: 7px; border-top: 1px solid var(--rule); }
.toc a { padding-block: 11px; text-decoration: none; color: var(--fg); }
.toc a:hover { text-decoration: underline; text-decoration-color: var(--accent); }
.sources { margin-top: 56px; padding-top: 18px; border-top: 1px solid var(--rule); font-size: var(--small); line-height: 1.5; color: var(--fg-soft); }
.sources h2 { margin: 0 0 10px; font-family: var(--font-head); font-weight: 500; font-size: .875rem; letter-spacing: .14em; text-transform: uppercase; }
.sources ul { padding-left: 1.1em; }
.sources li + li { margin-top: .35em; }
.checked { margin-top: 14px; font-size: var(--small); line-height: 1.45; color: var(--fg-soft); }

/* Beside a pale photo the list floats next to the column on its own patch of mist, and
   stays with the reader through a pause. Beside a dark photo a pale patch would glow like
   a box, so there the list keeps its place on the paper, above the words. */
@media (min-width: 1200px) {
  /* The rail takes up no height. The track sets where it rides: it starts below the first
     screen and stops a screen before the end of the article, so the list leaves before
     the last photo arrives. 1200px is where the paper's soft edge has fully ended
     before the list begins. */
  .toc-track--float { position: absolute; left: 0; right: 0; top: 150px; bottom: 100vh; z-index: 2; pointer-events: none; }
  .toc-track--float .toc-rail { position: sticky; top: calc(var(--header) + 24px); height: 0; width: min(100% - var(--gutter) * 2, var(--container)); margin-inline: auto; padding: 0; background: none; display: flex; justify-content: flex-end; -webkit-mask-image: none; mask-image: none; }
  .toc-track--float .toc { position: relative; isolation: isolate; width: 17rem; height: max-content; margin: 0; pointer-events: auto; }
  /* A patch of mist behind the list. Two masks fade it on all four sides over 130px, so
     it stays solid behind every word and has a long soft edge, with no oval shape to
     catch the eye. The box reaches out by the same 130px, which keeps the words clear. */
  .toc-track--float .toc::before {
    content: ""; position: absolute; z-index: -1; inset: -130px; background: rgb(var(--veil) / .95);
    --side: transparent 0, rgb(0 0 0 / .1) 26px, rgb(0 0 0 / .35) 52px, rgb(0 0 0 / .65) 78px, rgb(0 0 0 / .9) 104px, #000 130px, #000 calc(100% - 130px), rgb(0 0 0 / .9) calc(100% - 104px), rgb(0 0 0 / .65) calc(100% - 78px), rgb(0 0 0 / .35) calc(100% - 52px), rgb(0 0 0 / .1) calc(100% - 26px), transparent 100%;
    -webkit-mask-image: linear-gradient(to right, var(--side)), linear-gradient(to bottom, var(--side));
    mask-image: linear-gradient(to right, var(--side)), linear-gradient(to bottom, var(--side));
    -webkit-mask-composite: source-in;
    mask-composite: intersect;
  }
}

/* The film. Until someone presses play it is only a clear button and its title on the
   veil, so nothing on the photo can look like a video that failed to load. Pressing play
   puts the player in a dark 16:9 frame. */
.passage--film { --measure: 46rem; }
.video.is-playing { position: relative; aspect-ratio: 16 / 9; background: #0a1721; }
.video iframe { position: absolute; inset: 0; width: 100%; height: 100%; border: 0; }
.video__play {
  display: inline-flex; align-items: center; gap: 18px; min-height: 72px;
  color: var(--fg); text-decoration: none; font-family: var(--font-head); font-size: 1.125rem; font-weight: 500; letter-spacing: .04em; line-height: 1.35;
}
.video__icon { position: relative; flex: none; width: 72px; aspect-ratio: 1; border-radius: 50%; background: var(--cream); transition: transform var(--ease); }
/* The triangle sits a little right of centre, so it looks centred in the circle. */
.video__icon::after { content: ""; position: absolute; left: 54%; top: 50%; transform: translate(-50%, -50%); border-style: solid; border-width: 11px 0 11px 18px; border-color: transparent transparent transparent var(--ink); }
.video__play:hover .video__icon { transform: scale(1.06); }

/* Forms: contact and planner share these */
.field { margin-top: 24px; }
.field label, .field legend, .field .label {
  display: block; margin-bottom: 6px; padding: 0;
  font-family: var(--font-head); font-size: 1rem; font-weight: 500; letter-spacing: .06em; color: var(--fg);
}
.field .hint { display: block; margin-top: 2px; font-family: var(--font-body); font-size: var(--small); letter-spacing: 0; font-weight: 400; color: var(--fg-faint); }
.field input[type="text"], .field textarea {
  display: block; width: 100%; min-height: 54px; padding: 12px 14px; border: 1px solid #6f7a83; border-radius: 0; background: #fff; color: var(--ink); font-size: 1.0625rem; line-height: 1.4;
}
.field textarea { min-height: 120px; resize: vertical; }
/* A white box needs only a dark edge to show focus, not the double ring used over photos. */
.field input:focus-visible, .field textarea:focus-visible { outline: 2px solid var(--ink); outline-offset: 0; box-shadow: none; }
.field [aria-invalid="true"] { border-color: var(--error); border-width: 2px; }
.field__error { display: block; margin-top: 6px; color: #741414; font-size: var(--small); line-height: 1.4; }
.form-note { margin-top: 18px; font-size: var(--small); line-height: 1.5; color: var(--fg-soft); }
.form-summary { margin-bottom: 20px; padding: 16px 18px; border-left: 3px solid var(--error); background: #fbf1f0; color: #6d1616; font-size: var(--small); line-height: 1.5; }
.preview-note { margin-bottom: 24px; padding: 16px 18px; border-left: 1px solid var(--accent); font-size: var(--small); line-height: 1.5; color: var(--fg); }
.hp { position: absolute; left: -10000px; top: auto; width: 1px; height: 1px; overflow: hidden; }
/* The spam check takes no room unless it needs a click from the visitor. */
#turnstile { margin-top: 24px; }
/* Each label sits above its value, so a long email address can never break. */
.contact-facts { display: flex; flex-wrap: wrap; gap: 8px clamp(36px, 5vw, 64px); }
.contact-facts dt { font-family: var(--font-head); font-size: .8125rem; font-weight: 500; letter-spacing: .14em; text-transform: uppercase; color: var(--fg); }
.contact-facts dd { margin: 2px 0 0; }
.contact-direct a { display: inline-flex; align-items: center; min-height: 44px; font-size: 1.25rem; white-space: nowrap; color: var(--fg); }
.contact-direct { margin-top: 16px; }
.contact-more { margin-top: 32px; padding-top: 24px; border-top: 1px solid var(--rule); }
.contact-more .contact-facts { margin-top: 20px; }

/* Planner */
.planner__progress { margin: 0 0 24px; font-family: var(--font-head); font-size: var(--small); letter-spacing: .08em; color: var(--fg-faint); }
.planner fieldset { margin: 0; padding: 0; border: 0; }
.planner legend { padding: 0; font-family: var(--font-head); font-size: clamp(1.375rem, 2.4vw, 1.875rem); font-weight: 500; line-height: 1.25; letter-spacing: .01em; color: var(--fg); }
.planner .help { margin-top: 8px; color: var(--fg-faint); font-size: var(--small); }
.choices { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px; margin-top: 24px; }
.choice { position: relative; display: block; }
.choice input { position: absolute; opacity: 0; width: 100%; height: 100%; margin: 0; cursor: pointer; }
.choice span { display: flex; align-items: center; min-height: 72px; padding: 14px 18px; border: 1px solid #6f7a83; background: #fff; font-family: var(--font-head); font-size: 1.0625rem; font-weight: 500; line-height: 1.3; color: var(--ink); transition: background var(--ease), color var(--ease); }
.choice small { display: block; margin-top: 2px; font-family: var(--font-body); font-size: var(--small); font-weight: 400; color: #4a5660; }
.choice span > div { width: 100%; }
.choice input:hover + span { background: var(--cream); }
.choice input:checked + span { background: var(--ink); border-color: var(--ink); color: #fff; }
.choice input:checked + span small { color: #d5dee5; }
.choice input:focus-visible + span { outline: 3px solid var(--ink); outline-offset: 2px; }
.planner__nav { display: flex; flex-wrap: wrap; align-items: center; gap: 14px; margin-top: 32px; }
.plan__head { padding-bottom: 20px; border-bottom: 1px solid var(--fg); }
.plan__head h2 { font-size: clamp(1.75rem, 2.6vw, 2.375rem); }
.plan__meta { margin-top: 10px; color: var(--fg-faint); font-size: var(--small); }
.plan__stage { margin-top: 32px; }
.plan__stage h3 { padding-bottom: 8px; border-bottom: 1px solid var(--rule); }
.plan__stage ol { margin: 0; padding: 0; list-style: none; }
.plan__stage li { display: grid; grid-template-columns: 26px minmax(0, 1fr); gap: 12px; padding-block: 14px; border-bottom: 1px solid var(--rule); }
.plan__stage li::before { content: ""; width: 18px; height: 18px; margin-top: 6px; border: 1px solid var(--fg); }
.plan__links { margin-top: 32px; }
.plan__links ul { margin: 10px 0 0; padding: 0; list-style: none; }
.plan__links li { border-bottom: 1px solid var(--rule); }
.plan__links a { display: flex; align-items: center; min-height: 48px; }
.plan__disclaimer { margin-top: 28px; font-size: var(--small); line-height: 1.5; color: var(--fg-faint); }
.plan__actions { display: flex; flex-wrap: wrap; gap: 14px; margin-top: 28px; }
.print-only { display: none; }

/* --------------------------------------------------------------------------
   Header. It floats on the photograph and takes its colour from the scene.
   -------------------------------------------------------------------------- */

:root { --bar-fg: #fff; --btn-bar-bg: var(--cream); --btn-bar-fg: var(--ink); }
html[data-bar="mist"] { --bar-fg: var(--ink); --btn-bar-bg: var(--ink); --btn-bar-fg: #fff; }

/* Two soft veils sit behind the header, a dark one and a pale one above it. Each is
   almost opaque behind the header's words, so words passing underneath stay out of
   the way, then falls away quickly with no lower edge.

   A slow blend from white words to dark words passes through a grey that reads on
   nothing. So the header changes tone as one piece. The words flip at once, and in that
   same moment the pale veil jumps past the middle shades, where neither white nor
   dark words would pass, and then settles. The two curves below do this: going pale
   the veil starts at .85 of the way, and going dark it starts .9 of the way back. A
   browser too old to read them changes the veil in one step instead. */
.site-header { position: sticky; top: 0; z-index: 50; height: var(--header); color: var(--bar-fg); --accent: var(--bar-fg); }
.site-header::before, .site-header::after {
  content: ""; position: absolute; left: 0; right: 0; top: 0; height: var(--bar-fade); z-index: -1; pointer-events: none;
  --fall: calc(var(--bar-fade) - var(--bar-solid));
  background: linear-gradient(to bottom,
    rgb(var(--veil-now) / .995) 0,
    rgb(var(--veil-now) / .985) var(--bar-solid),
    rgb(var(--veil-now) / .86) calc(var(--bar-solid) + var(--fall) * .2),
    rgb(var(--veil-now) / .6) calc(var(--bar-solid) + var(--fall) * .4),
    rgb(var(--veil-now) / .32) calc(var(--bar-solid) + var(--fall) * .6),
    rgb(var(--veil-now) / .1) calc(var(--bar-solid) + var(--fall) * .8),
    rgb(var(--veil-now) / 0) 100%);
}
.site-header::before { --veil-now: var(--bar-dusk, 10 23 33); }
.site-header::after { --veil-now: var(--bar-mist, 251 245 234); opacity: 0; transition: opacity var(--bar-change) steps(1, start); transition-timing-function: linear(.9, 1); }
html[data-bar="mist"] .site-header::after { opacity: 1; transition-timing-function: linear(.85, 1); }
/* The dark veil steps aside once the pale one has fully covered it. */
html[data-bar="mist"] .site-header::before { opacity: 0; transition: opacity 0s linear var(--bar-change); }

.bar { display: flex; align-items: center; justify-content: space-between; gap: 24px; height: calc(var(--header) - 24px); padding-inline: max(clamp(20px, 3vw, 44px), env(safe-area-inset-left), env(safe-area-inset-right)); }
.wordmark { display: flex; flex-direction: column; justify-content: center; min-height: 44px; color: inherit; text-decoration: none; font-family: var(--font-head); line-height: 1.15; flex: none; }
.wordmark__line { display: flex; align-items: center; gap: 14px; }
.wordmark strong { display: block; color: inherit; font-size: 1.0625rem; font-weight: 500; letter-spacing: .18em; text-transform: uppercase; white-space: nowrap; }
.wordmark__brand { padding-left: 14px; border-left: 1px solid currentColor; }
.wordmark small { display: block; margin-top: 3px; color: inherit; font-size: .75rem; letter-spacing: .14em; text-transform: uppercase; }
.nav ul { display: flex; align-items: center; gap: clamp(14px, 1.7vw, 28px); margin: 0; padding: 0; list-style: none; }
.nav a {
  display: block; padding-block: 11px; color: inherit; text-decoration: none; font-family: var(--font-head);
  font-size: .8125rem; font-weight: 500; letter-spacing: .1em; text-transform: uppercase; border-bottom: 1px solid transparent;
}
.nav a:hover, .nav a[aria-current="page"] { border-bottom-color: currentColor; }
.bar__right { display: flex; align-items: center; gap: 20px; flex: none; }
.bar__tel { display: inline-flex; align-items: center; justify-content: center; min-height: 44px; color: inherit; text-decoration: none; font-family: var(--font-head); font-size: .875rem; letter-spacing: .08em; white-space: nowrap; }
.bar__tel svg { display: none; width: 20px; height: 20px; }
.btn--bar { min-height: 44px; padding: 10px 20px; font-size: .8125rem; background: var(--btn-bar-bg); color: var(--btn-bar-fg); border-color: var(--btn-bar-bg); white-space: nowrap; transition: none; }
.btn--bar:hover { background: var(--btn-bar-bg); opacity: .88; }

/* The phone menu. Hidden on wide screens. */
.menu { display: none; }
.menu > summary {
  display: flex; align-items: center; gap: 10px; min-height: 44px; padding: 8px 2px; cursor: pointer; list-style: none;
  font-family: var(--font-head); font-size: .8125rem; font-weight: 500; letter-spacing: .12em; text-transform: uppercase;
}
.menu > summary::-webkit-details-marker { display: none; }
.menu svg { width: 20px; height: 20px; flex: none; }

/* The header stays on one line at every width. It tightens first, then the phone
   number becomes a phone button, and only then do the main links move into Menu. */
@media (max-width: 1360px) {
  .bar, .bar__right { gap: 16px; }
  .wordmark strong { font-size: .9375rem; letter-spacing: .15em; }
  .wordmark__line { gap: 12px; }
  .wordmark__brand { padding-left: 12px; }
  .nav ul { gap: clamp(12px, 1.5vw, 20px); }
  .nav a { letter-spacing: .08em; }
}
@media (max-width: 1190px) {
  .bar__tel { min-width: 44px; }
  .bar__tel svg { display: block; }
  .bar__tel span { display: none; }
  .wordmark strong { font-size: .875rem; letter-spacing: .13em; }
}
@media (max-width: 1080px) {
  .bar__tel { min-width: 0; }
  .bar__tel svg { display: none; }
  .bar__tel span { display: inline; }
  .nav--wide { display: none; }
  .menu { display: block; }
  .bar { gap: 20px; }
  .bar__right { margin-left: auto; }
  .menu__panel {
    --accent: #f2d2ab;
    position: fixed; inset: 0; z-index: 60; overflow-y: auto; background: var(--ink); color: #fff;
    padding: 20px var(--gutter) 40px;
  }
  .menu__close { display: flex; justify-content: flex-end; }
  .menu__close button {
    display: inline-flex; align-items: center; min-height: 48px; padding: 8px 14px; background: none; border: 1px solid rgb(255 255 255 / .28); color: #fff; cursor: pointer;
    font-family: var(--font-head); font-size: .8125rem; font-weight: 500; letter-spacing: .12em; text-transform: uppercase;
  }
  .menu__panel ul { margin: 16px 0 0; padding: 0; list-style: none; }
  .menu__panel li + li { border-top: 1px solid rgb(255 255 255 / .28); }
  .menu__panel a { display: block; padding: 16px 0; color: #fff; text-decoration: none; font-family: var(--font-head); font-size: 1.25rem; font-weight: 500; letter-spacing: .1em; text-transform: uppercase; }
  .menu__panel a[aria-current="page"] { color: var(--accent); }
  .menu__contact { margin-top: 28px; color: #d5dee5; }
  .menu__contact a { padding: 8px 0; font-family: var(--font-body); font-size: 1.125rem; letter-spacing: 0; text-transform: none; text-decoration: underline; text-decoration-color: var(--accent); }
}

/* With scripting off the Close button can't work, so it steps aside, and the menu starts
   below the header, which keeps the Menu button on screen to close it. */
@media (scripting: none) and (max-width: 1080px) {
  .menu__close { display: none; }
  .menu__panel { top: var(--header); }
}

/* The phone action bar: one tap to reach Thomas from anywhere. */
.action-bar { display: none; }

/* --------------------------------------------------------------------------
   Footer. It settles onto the last photograph as night falls.
   -------------------------------------------------------------------------- */

.site-footer { position: relative; isolation: isolate; padding-block: clamp(150px, 24vh, 240px) 40px; font-size: var(--small); line-height: 1.55; }
.site-footer::before {
  content: ""; position: absolute; inset: 0; z-index: -1; pointer-events: none;
  background: linear-gradient(to bottom, rgb(var(--veil) / 0) 0, rgb(var(--veil) / .3) 60px, rgb(var(--veil) / .72) 130px, rgb(var(--veil) / .9) 190px, rgb(var(--veil) / .96) 100%);
}
.site-footer > .container { width: min(100% - var(--gutter) * 2, var(--container)); margin-inline: auto; }
.site-footer__grid { display: grid; grid-template-columns: minmax(0, 4fr) minmax(0, 4fr) minmax(0, 4fr); gap: 32px clamp(24px, 4vw, 56px); }
.site-footer h2 { margin-bottom: 12px; font-family: var(--font-head); font-weight: 500; font-size: .875rem; letter-spacing: .14em; text-transform: uppercase; }
.site-footer ul { margin: 0; padding: 0; list-style: none; }
.site-footer__nav { columns: 2; column-gap: 24px; }
.site-footer__nav li { break-inside: avoid; }
.site-footer__nav a, .site-footer__contact a, .site-footer__row a { display: inline-flex; align-items: center; min-height: 44px; min-width: 44px; }
.site-footer__address { padding-block: 6px; line-height: 1.5; }
.site-footer__legal { margin-top: 40px; padding-top: 24px; border-top: 1px solid var(--rule); line-height: 1.55; color: var(--fg-faint); }
.site-footer__legal p + p { margin-top: .7em; }
/* Padding on a link inside a line makes it easier to tap and moves nothing. */
.site-footer__legal p a { padding-block: 13px; }
.site-footer__row { display: flex; flex-wrap: wrap; align-items: center; gap: 10px 24px; margin-top: 18px; }
.eho { display: inline-flex; align-items: center; gap: 8px; }

/* A tall screen crops a photo from the sides, so it uses the photo's phone crop point. */
@media (max-aspect-ratio: 1/1) {
  .stage__photo img { object-position: var(--focus-phone, 50% 50%); }
}

/* Up to this width, and on any upright window, the words cross the busy middle of the photo,
   so the veil runs a little deeper for comfortable reading. The open gaps keep the photo
   at full strength. */
@media (max-width: 900px), (max-aspect-ratio: 1/1) {
  /* A photo can ask for a lighter veil here with "phoneVeilStrength", if its own colour
     can carry white words. */
  .tone-dusk .passage { --veil-alpha: var(--veil-phone, .76); }
  .tone-mist .passage { --veil-alpha: .9; }
  .scene .passage--close { --veil-alpha: .62; }
  .scene .passage--paper, .scene .passage--joined, .scene .passage--reading { --veil-alpha: 1; }
}
@media (max-width: 900px) {
  /* The paper crosses the whole photo here, so it rises in a short fade, and the frame
     above it stays in full colour for as long as it can. */
  .passage--paper { --rise: var(--feather); }
  /* The veil crosses the whole photo here, so words on the right gain nothing. They start
     at the same left edge as the header. */
  .passage--right .passage__inner > *, .passage--right .desk > *, .passage--right .desk .note { margin-left: 0; }
  /* Here a form crosses the full width of the photo, so a thinner veil lets the photo
     still set the scene. The boxes are solid white and need no help. */
  .scene .passage--desk { --veil-alpha: .76; }
  /* A finished plan is a long read, so it goes back to clean paper. */
  .scene .passage--desk:has(.plan:not([hidden])) { --veil-alpha: .94; }
}

/* Tablet */
@media (max-width: 990px) {
  .split, .with-portrait { grid-template-columns: minmax(0, 1fr); }
  /* Stacked columns would otherwise run the full width, which is too long to read. */
  .split > div, .with-portrait > div, .passage--band .steps { max-width: var(--measure); }
  .with-portrait .frame { position: static; order: -1; max-width: 27rem; }
  .split .frame { max-width: 27rem; }
  .site-footer__grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* Phone */
@media (max-width: 700px) {
  :root { --gutter: max(18px, env(safe-area-inset-left), env(safe-area-inset-right)); --header: 72px; --bar-solid: 52px; --bar-fade: 90px; --feather: 150px; --cover-bottom: var(--action-bar); --small: 1.0625rem; }
  /* The call sits across the dark band of rooftops here. Smoked glass lets the photo's
     own colours through and quiets the busy detail behind the words, so it belongs to
     the picture and ranks under the solid button above it. The tint alone keeps the
     words clear where a browser has no blur. */
  .passage--hero .btn--outline, .passage--hero .btn--outline:hover {
    min-height: 48px; color: #fff; text-decoration: none; border-color: rgb(255 255 255 / .75);
    background: rgb(15 30 43 / .6); -webkit-backdrop-filter: blur(6px); backdrop-filter: blur(6px);
  }
  /* The sky is full of words on a phone, and a place name lower down would still
     be on screen after the backdrop has moved on, so it stays off this first screen. */
  .passage--hero .scene__place { display: none; }
  /* A tall screen opens on the photo, like a magazine feature. The words always start
     at the same height, so the veil never climbs over the subject. A long lead simply
     runs on past the first screen. */
  .passage--banner:not(.passage--high, .passage--desk) { justify-content: flex-start; min-height: 0; padding-top: 56vh; padding-top: 56svh; --rise: var(--feather); }
  /* A long read opens on the top of the screen, with the paper rising under it. So a copy of
     the photo fills only that part, which frames its subject there and not behind the
     paper. A photo's "phoneFocusY" sets which part of it stays in view. It stays still while
     the paper covers it, then the rail ends and it leaves with the page, out of sight. Every
     pause further down shows the stage's full-height photo. The rail's height is what
     sets that moment: the paper is solid across the screen well before it ends. */
  .head-rail { display: block; position: absolute; top: 0; left: 0; width: 100%; height: 120vh; height: 120lvh; z-index: -1; pointer-events: none; }
  .head-rail .stage__photo { position: sticky; inset: 0 auto auto 0; width: 100%; height: 64vh; height: 64lvh; }
  body { font-size: 1.125rem; line-height: 1.62; }
  .only-phone { display: inline-flex; }
  .bar { height: calc(var(--header) - 18px); gap: 12px; padding-inline: var(--gutter); }
  .wordmark strong { letter-spacing: .16em; }
  .wordmark__line { gap: 10px; }
  .wordmark__brand { padding-left: 10px; }
  .wordmark small { font-size: .6875rem; margin-top: 2px; }
  .bar__right { display: none; }
  .btn { width: 100%; }
  .btn-row { flex-direction: column; }
  /* The closing band: Call runs the full width, with Text and the note side by side
     under it. Each takes the width its own words need. */
  .btn-row--reach { flex-direction: row; gap: 10px; }
  .btn-row--reach .btn { width: auto; flex: 1 1 auto; padding-inline: 10px; }
  .btn-row--reach .btn:first-child { flex-basis: 100%; }
  .choices { grid-template-columns: minmax(0, 1fr); }
  .site-footer__grid { grid-template-columns: minmax(0, 1fr); }
  .site-footer__nav { columns: 2; }
  /* Room under the footer, so the action bar never covers the end of the page. */
  .site-footer { padding-bottom: calc(40px + var(--action-bar) + env(safe-area-inset-bottom)); }
  html { scroll-padding-bottom: calc(var(--action-bar) + 16px); }

  .action-bar {
    position: fixed; left: 0; right: 0; bottom: 0; z-index: 45; display: grid; grid-template-columns: repeat(3, 1fr);
    padding-inline: env(safe-area-inset-left) env(safe-area-inset-right);
    background: #0a1721; color: #fff;
  }
  .action-bar a {
    display: flex; align-items: center; justify-content: center; gap: 8px; min-height: calc(var(--action-bar) + env(safe-area-inset-bottom)); padding-bottom: env(safe-area-inset-bottom);
    color: #fff; text-decoration: none; font-family: var(--font-head); font-size: .8125rem; font-weight: 500; letter-spacing: .12em; text-transform: uppercase;
  }
  .action-bar a + a { box-shadow: -1px 0 0 rgb(255 255 255 / .16); }
  .action-bar svg { width: 18px; height: 18px; flex: none; }
  /* Call is the filled cell, so one action leads. */
  .action-bar .action-bar__lead { background: var(--cream); color: var(--ink); }
  /* The ring sits inside the cell, so it stays on the bar whatever the photo behind. */
  .action-bar a:focus-visible { outline: 3px solid #fff; outline-offset: -6px; box-shadow: none; }
  .action-bar .action-bar__lead:focus-visible { outline-color: var(--ink); }
  /* The bar steps aside while the menu or the note panel is open. */
  html:has(.menu[open]) .action-bar, html.menu-open .action-bar, html.talk-open .action-bar { display: none; }

  /* The ledger becomes labelled rows so nothing scrolls sideways */
  .ledger, .ledger tbody, .ledger tr, .ledger td { display: block; width: 100%; }
  .ledger { display: flex; flex-direction: column; }
  .ledger caption { display: block; order: 2; width: 100%; }
  .ledger thead { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0, 0, 0, 0); }
  .ledger tr { padding-block: 14px; border-bottom: 1px solid var(--rule); }
  .ledger td { padding: 3px 0; border: 0; }
  .ledger td::before { content: attr(data-label); display: block; font-family: var(--font-head); font-size: .8125rem; font-weight: 500; letter-spacing: .12em; text-transform: uppercase; color: var(--fg-soft); }
  .ledger td.num { white-space: normal; }
  .prose table, .prose thead, .prose tbody, .prose tr, .prose th, .prose td { display: block; }
  .prose thead { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0, 0, 0, 0); }
  .prose tr { padding-block: 10px; border-bottom: 1px solid var(--rule); }
  .prose td { padding: 2px 0; border: 0; }
  .prose td::before { content: attr(data-label); display: block; font-family: var(--font-head); font-size: .8125rem; font-weight: 500; letter-spacing: .12em; text-transform: uppercase; color: var(--fg-soft); }
}

/* On a short phone the horizon sits higher, right behind the lines above a form, so
   the veil starts higher too and those lines stay readable on the rooftops. */
@media (max-width: 900px) and (max-height: 760px) {
  .passage--desk { --rise: 260px; }
}

/* Below this width, Text and the note only fit side by side without the number, and
   with the number gone the two take equal widths. */
@media (max-width: 359px) {
  .btn__number { display: none; }
  .btn-row--reach .btn:not(:first-child) { flex-basis: 0; }
}

/* A small phone. The wordmark tightens so Menu stays on screen at 320 wide. */
@media (max-width: 379px) {
  .wordmark strong { font-size: .8125rem; letter-spacing: .09em; }
  .wordmark__line { gap: 8px; }
  .wordmark__brand { padding-left: 8px; }
  .bar { gap: 8px; }
  .menu > summary { gap: 6px; }
  .passage--hero h1 { font-size: 2.625rem; }
}

/* The narrowest phones. The words run down past the horizon and no crop can lift it any
   further, so a haze in the colour of the sky rises behind the last lines. */
@media (max-width: 359px) {
  .passage--hero::before {
    display: block;
    background: linear-gradient(to bottom, rgb(var(--veil) / 0) 170px, rgb(var(--veil) / .3) 220px, rgb(var(--veil) / .88) 280px, rgb(var(--veil) / .88) 385px, rgb(var(--veil) / 0) 450px);
  }
}

/* A phone held sideways. The hero's words reach below the horizon here, so they get a
   soft veil from the side, and the header gives some height back to the photo. */
@media (max-height: 560px) and (min-aspect-ratio: 1/1) {
  :root { --header: 72px; --bar-solid: 52px; --bar-fade: 90px; }
  .bar { height: calc(var(--header) - 18px); }
  .passage--hero::before {
    display: block; -webkit-mask-image: none; mask-image: none;
    background: linear-gradient(to right, rgb(var(--veil) / .92) 0, rgb(var(--veil) / .92) 58%, rgb(var(--veil) / .6) 72%, rgb(var(--veil) / .2) 86%, rgb(var(--veil) / 0) 100%);
  }
}

/* --------------------------------------------------------------------------
   The quick note panel. A sheet of the site's own paper laid on the page: along the
   right edge on a computer, and rising from the bottom on a phone. It is a native
   <dialog>, and src/contact.js opens it. It fades and slides only, and the two
   "allow-discrete" lines keep it on screen until its fade out has finished.
   -------------------------------------------------------------------------- */

/* A browser too old to know <dialog> leaves the closed panel in place, so it must not block taps. */
.talk:not([open]) { visibility: hidden; pointer-events: none; }
.talk {
  --slide: translateX(32px);
  position: fixed; inset: 0 0 0 auto; width: min(100%, 35rem); height: 100vh; height: 100dvh; max-width: none; max-height: none;
  margin: 0; padding: 0; border: 0; overflow-y: auto; overscroll-behavior: contain;
  background: var(--paper); color: var(--fg-soft);
  box-shadow: -24px 0 80px rgb(10 23 33 / .32);
  opacity: 0; transform: var(--slide);
  transition: opacity .32s ease, transform .32s ease, visibility .32s, overlay .32s allow-discrete, display .32s allow-discrete;
}
.talk[open] { opacity: 1; transform: none; }
@starting-style { .talk[open] { opacity: 0; transform: var(--slide); } }
.talk::backdrop { background: rgb(10 23 33 / .52); opacity: 0; transition: opacity .32s ease, overlay .32s allow-discrete, display .32s allow-discrete; }
.talk[open]::backdrop { opacity: 1; }
@starting-style { .talk[open]::backdrop { opacity: 0; } }
/* While it is open the page behind stays still. Hiding a computer's scrollbar would widen
   the page and refit the photo, so its space stays reserved. */
html.talk-open { overflow: hidden; scrollbar-gutter: stable; }

/* The sheet fills the panel, so a press on its blank paper is a press on the sheet, and
   only a press on the dark area outside lands on the panel itself. */
.talk__sheet { min-height: 100%; padding: clamp(24px, 5vh, 52px) clamp(24px, 4vw, 48px) max(32px, env(safe-area-inset-bottom)); }
.talk h2 { font-size: clamp(2.125rem, 3vw, 2.75rem); padding-right: 44px; }
.talk .lead { margin-top: 14px; }
/* The close button stays in reach while a long form scrolls under it. */
.talk__close {
  position: sticky; top: 4px; z-index: 1; display: flex; align-items: center; justify-content: center; width: 44px; height: 44px; margin: -8px -10px -36px auto; padding: 0;
  border: 0; background: rgb(var(--veil) / .9); color: var(--fg); cursor: pointer;
}
.talk__close svg { width: 22px; height: 22px; }
.talk__lines { margin-top: 12px; font-size: var(--small); line-height: 1.5; color: var(--fg); }
.talk__lines p + p { margin-top: 2px; }
/* The number is one tap away, so its tap area reaches past the line of text. */
.talk__lines a { padding-block: 12px; }
.talk .preview-note, .talk .form-summary { margin: 18px 0 0; }
.talk .field { margin-top: 18px; }
.talk textarea { min-height: 96px; }
.talk .btn { width: 100%; }
.talk .form-note { margin-top: 14px; }
.talk #turnstile { margin-top: 20px; }
#talk-sent > * + * { margin-top: 16px; }

@media (max-width: 700px) {
  .talk {
    --slide: translateY(48px);
    inset: auto 0 0 0; width: 100%; height: auto; max-height: calc(100vh - 28px); max-height: calc(100dvh - 28px);
    box-shadow: 0 -18px 60px rgb(10 23 33 / .32);
  }
}

/* --------------------------------------------------------------------------
   The plain page: the safe default, for when stage.js cannot run. Each scene's photo
   sits in the page and stays in place behind its words, and the next one slides up
   over it on a soft fade. Nothing changes on its own, and nothing is hidden.
   stage.js adds "has-stage" to <html>, which hides these and turns on the fixed stage.
   -------------------------------------------------------------------------- */

.has-stage .scene .stage__photo { display: none; }

:root:not(.has-stage) .scene .stage__photo { position: sticky; top: 0; height: 100vh; height: 100lvh; margin-bottom: -100vh; z-index: -1; pointer-events: none; }
@supports (height: 100lvh) { :root:not(.has-stage) .scene .stage__photo { margin-bottom: -100lvh; } }

/* With no script to follow the scenes, the header keeps the first photo's tone and
   stays with that photo, so its words never pass over a photo that does not suit them. */
:root:not(.has-stage) .site-header { position: relative; }

/* Words can sit on the photo before or after their own here, so the veils run deeper. */
:root:not(.has-stage) .passage:not(.passage--paper, .passage--joined, .passage--reading) { --veil-alpha: .86 !important; }

/* Each scene runs on underneath the next one, so the old photo stays behind the new
   one as it slides up. --hold starts the new photo halfway through the open stretch,
   as on the live stage, so the words above have left the screen before it appears.
   The stage behind everything holds the first photo, for the first scene and for
   sections with no photo of their own, such as a long read or a form. */
:root:not(.has-stage) .scene { --hold: 0px; --soft: 34vh; padding-bottom: 65vh; }
:root:not(.has-stage) .scene:has(> .scene__open) { --hold: calc(var(--open) * .5); }
:root:not(.has-stage) .scene + .scene { display: flow-root; margin-top: -65vh; }
:root:not(.has-stage) .reading + .scene { display: flow-root; }
/* A photo of homes waits until the words above have fully left the screen. The photo
   after it starts right below the last words, and the open stretch runs a little longer,
   so it has fully covered the photo of homes before its own words rise. */
:root:not(.has-stage) .scene[data-apart] { --hold: calc(var(--open) + var(--feather) + var(--bar-solid)); }
/* Its own words wait until its soft top edge has gone by. Otherwise a place name would ride
   up the screen right on that edge, on the photo before it. */
:root:not(.has-stage) .scene[data-apart] > .scene__open { height: calc(var(--open) + var(--bar-solid) + var(--soft)); }
/* The photo after it, unless that is a photo of homes too, which simply waits its turn. */
:root:not(.has-stage) .scene[data-apart] + .scene:not([data-apart]) { --soft: 12vh; --lead-in: var(--feather); --hold: calc(var(--lead-in) * -1); }
:root:not(.has-stage) .scene--first[data-apart] + .scene:not([data-apart]) { --lead-in: var(--banner-pad); }
:root:not(.has-stage) .scene[data-apart] + .scene:not([data-apart]) > .scene__open { height: calc(var(--open) + var(--soft) + var(--feather) - var(--lead-in)); }
/* A page banner runs straight into the long read below it. */
:root:not(.has-stage) .scene:has(+ .reading) { padding-bottom: 0; }
/* The footer rises over the last photo and turns solid before that photo ends, so
   the photo's lower edge never shows. */
:root:not(.has-stage) .scene:last-child { padding-bottom: 36vh; }
:root:not(.has-stage) .site-footer { margin-top: -36vh; }
:root:not(.has-stage) .site-footer::before { background: linear-gradient(to bottom, rgb(var(--veil) / 0) 0, rgb(var(--veil) / .3) 60px, rgb(var(--veil) / .72) 130px, rgb(var(--veil) / .94) 190px, rgb(var(--veil) / 1) 280px); }

/* Each photo is a little taller than the screen, and its soft top edge pokes out
   above it once pinned. So the fade shows only while the photo is sliding up. */
:root:not(.has-stage) .scene + .scene .stage__photo, :root:not(.has-stage) .reading + .scene .stage__photo {
  top: calc(var(--soft) * -1); height: calc(100vh + var(--soft)); height: calc(100lvh + var(--soft));
  margin-top: var(--hold); margin-bottom: calc(-100vh - var(--soft) - var(--hold));
  -webkit-mask-image: linear-gradient(to bottom, transparent 0, #000 var(--soft));
  mask-image: linear-gradient(to bottom, transparent 0, #000 var(--soft));
}
@supports (height: 100lvh) {
  :root:not(.has-stage) .scene + .scene .stage__photo, :root:not(.has-stage) .reading + .scene .stage__photo { margin-bottom: calc(-100lvh - var(--soft) - var(--hold)); }
}

/* Reduce Motion: the same fixed stage, with a fade so short that nothing seems to move. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after, ::backdrop { transition: none !important; animation: none !important; }
  .has-stage .stage__photo { transition: opacity var(--fade) linear !important; }
  /* A place name still waits for its photo. It simply appears, with no fade of its own. */
  .has-stage .scene.is-current .scene__place { transition: opacity 0s linear var(--fade) !important; }
}

/* --------------------------------------------------------------------------
   Print: black words on white paper with no photos. The planner still prints
   as a clean one or two page plan.
   -------------------------------------------------------------------------- */
@media print {
  :root { --small: 10pt; }
  /* The page colour comes from the first photo and sits on <html> itself, so this has to insist. */
  html, body { background: #fff !important; }
  main { margin-top: 0; }
  body { font-size: 11pt; line-height: 1.38; color: #000; }
  /* The panel's slide would otherwise keep it on the page for a moment after this hides it. */
  .talk { transition: none !important; }
  .skip, .site-header, .site-footer, .action-bar, .talk, .stage, .head-rail, .scene__open, .scene__place, .scene .stage__photo, .planner, .plan__actions, .passage--close, .only-phone { display: none !important; }
  .scene, .scene + .scene { margin: 0; padding: 0; color: #000; isolation: auto; }
  .passage, .passage--banner:not(.passage--high, .passage--desk) { padding: 0; min-height: 0; display: block; }
  .passage::before, .toc-track--float .toc::before, .desk::before { display: none; }
  .passage__inner { width: 100%; display: block; }
  .passage__inner > *, .desk > *, .passage--right .passage__inner > *, .passage--right .desk > * { max-width: none; margin-left: 0; }
  .scene, .scene *, .reading, .reading * { --fg: #000; --fg-soft: #000; --fg-faint: #000; --accent: #000; --rule: #999; color: #000 !important; background: none; }
  .toc-track, .toc-track--float { position: static; }
  .toc-rail, .toc-track--float .toc-rail { position: static; display: block; height: auto; width: 100%; padding: 0 0 12pt; background: none; }
  .toc-rail .toc, .toc-track--float .toc { position: static; width: auto; max-width: none; }
  /* No photos on paper, but a caption's words still print. */
  .frame img, .video { display: none; }
  .frame { position: static; max-width: none; }
  .eyebrow::before, .passage--banner .passage__inner::before { display: none; }
  .btn, .link-arrow { display: none; }
  /* The planner's own banner is not part of the plan. */
  .passage__inner:has(.plan:not([hidden])) > :not(.desk) { display: none; }
  .plan { width: 100%; max-width: none; }
  .print-only { display: block; }
  .print-head { display: flex; justify-content: space-between; gap: 24px; padding-bottom: 8px; margin-bottom: 14px; border-bottom: 1.5pt solid #000; font-family: var(--font-head); font-size: 10pt; }
  .plan__head { border-bottom-width: 1pt; }
  .plan__stage { break-inside: avoid; margin-top: 11pt; }
  .plan__stage li { padding-block: 3.5pt; break-inside: avoid; }
  .plan__links { break-inside: avoid; margin-top: 11pt; }
  .plan__links a { display: inline; min-height: 0; }
  .plan__links li { padding-block: 3.5pt; }
  a { color: #000; text-decoration: none; }
  .plan__links a::after { content: " (" attr(data-print-url) ")"; font-size: 9pt; }
  .print-legal { margin-top: 10pt; font-size: 10pt; line-height: 1.3; }
}
