/* Hillcroft Villa — one stylesheet.

   Skin: bold editorial, set as a poster. The page is a sheet of warm paper and
   everything on it is set, not boxed — hairline rules do the dividing, corners
   are square, and the hierarchy is carried by type at full contrast rather than
   by shadows and rounded cards. Headlines are Archivo run down its width axis to
   62% — the bottom of it — and up its weight axis to 800: condensed poster
   capitals, sized to the viewport. Labels, navigation and buttons are the same family at normal
   width in small tracked capitals, so the whole page is one typeface at two
   extremes of itself.

   The page alternates ground. Paper is the default; `What's here', `Guests' and
   the closing run full-bleed dark, so the page reads as a sequence of spreads
   rather than one continuous scroll. Each section's number and name pin under
   the masthead for the length of that section — a running head, `position:
   sticky', no script and no per-frame work.

   Colour is the logo's green (#00443f) and nothing else. The earlier skin
   lightened that green into mints and had to walk its hue to stop it reading as
   cyan; this one uses it at full strength on paper and only lifts it on the
   dark grounds, where --green-lit holds hue ~155deg for the same reason.

   Motion: an orchestrated entrance for the hero on load, a single fade-and-rise
   for everything else the first time it comes into view, and a measured
   expansion on the questions. Nothing but the sticky running heads responds to
   scroll, and sticky is free — a transition that runs once and stops is
   smoother to watch than an animation the compositor re-evaluates on every
   scrolled frame. */

/* ============================================================
   Tokens
   ============================================================ */
:root {
  --paper:      #f3efe6;   /* the sheet */
  --paper-warm: #e9e3d5;   /* the one tone below it, for inset blocks */
  --card:       #fbf9f4;   /* raised paper: the hero panel */

  --ink:       #131c19;
  --text:      #3a4642;
  --text-soft: #55625d;    /* clears 4.5:1 on the paper and on the warm tone */

  --green-ink: #00443f;    /* the logo, exactly — rules, capitals, fills */
  --green:     #0a5f4c;    /* links, and the accent inside a headline */
  --on-green:  #f3efe6;    /* paper set on the green: 10.4:1 */

  /* The dark ground. */
  --dark:         #101917;
  --on-dark:      #ece7db;
  --on-dark-soft: #9aa8a1;  /* 6.4:1 on --dark */
  --green-lit:    #7ccba6;  /* the accent, lifted for the dark ground */

  /* Rules do the dividing here. Two weights on paper, two on dark. */
  --rule:      rgba(19, 28, 25, .16);
  --rule-firm: rgba(19, 28, 25, .46);
  --rule-dark: rgba(236, 231, 219, .18);
  --rule-dark-firm: rgba(236, 231, 219, .4);

  --sans: "Archivo", system-ui, -apple-system, "Segoe UI", sans-serif;

  /* Small tracked capitals — the page's second voice, used for every label,
     eyebrow, button and caption. */
  --caps: 600 11px/1 var(--sans);
  --track: .2em;

  --page: 1280px;
  --gutter: clamp(18px, 4vw, 56px);
  --band: clamp(64px, 8vw, 132px);
  --head: 67px;            /* the masthead's height, for the sticky running heads */
  --r: 2px;                /* the only radius on the page, on controls */
  --ease: cubic-bezier(.22, .61, .36, 1);
  --dur: .3s;
}

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

/* The overlays and filtered-out tiles switch on the `hidden` attribute, and any
   `display` value would quietly outrank it. */
[hidden] { display: none !important; }

html { -webkit-text-size-adjust: 100%; }
@media (prefers-reduced-motion: no-preference) { html { scroll-behavior: smooth; } }

body {
  margin: 0;
  font-family: var(--sans);
  font-size: 17px;
  line-height: 1.62;
  color: var(--text);
  background: var(--paper);
  -webkit-font-smoothing: antialiased;
}

/* Poster capitals. The width axis does most of the work: at 62% the headline
   holds far more characters per line, which is what lets it be set this large
   without breaking into six lines. */
h1, h2 {
  margin: 0;
  color: var(--ink);
  font-weight: 800;
  font-stretch: 62%;
  text-transform: uppercase;
  letter-spacing: -.002em;
  line-height: .86;
  text-wrap: balance;
}
h2 { font-size: clamp(40px, 7.8vw, 100px); }

h3, h4 {
  margin: 0;
  color: var(--ink);
  font-weight: 600;
  font-stretch: 92%;
  letter-spacing: -.012em;
  line-height: 1.22;
  text-wrap: balance;
}
h3 { font-size: clamp(19px, 1.6vw, 23px); }

figure { margin: 0; }
p { margin: 0 0 1em; }
p:last-child { margin-bottom: 0; }
a { color: inherit; }
img { max-width: 100%; display: block; }

:focus-visible { outline: 2px solid var(--green-ink); outline-offset: 3px; }
.defs { position: absolute; width: 0; height: 0; overflow: hidden; }

.skip {
  position: absolute;
  left: 16px; top: -70px;
  z-index: 100;
  padding: 13px 22px;
  border-radius: var(--r);
  background: var(--ink);
  color: var(--paper);
  font: var(--caps);
  letter-spacing: var(--track);
  text-transform: uppercase;
  text-decoration: none;
  transition: top var(--dur) var(--ease);
}
.skip:focus { top: 16px; }

/* ============================================================
   Editorial primitives — capitals, rules, the running head
   ============================================================ */
/* Every section opens on the same line: a number, its name, and a rule out to
   the edge of the measure. It is the page's one organising device, and it
   replaces the tinted band each section used to sit in. It is also sticky, so
   it stays under the masthead for as long as its section is on screen — the
   section is pinned and named the whole way down. */
.kicker {
  position: sticky;
  top: var(--head);
  z-index: 20;
  display: flex;
  align-items: center;
  gap: 14px;
  margin: 0 0 clamp(22px, 2.6vw, 38px);
  padding: 14px 0;
  background: var(--paper);
  font: var(--caps);
  letter-spacing: var(--track);
  text-transform: uppercase;
  color: var(--green-ink);
}
.kicker b { font-weight: 700; font-variant-numeric: tabular-nums; }
.kicker span { color: var(--ink); }
.kicker::after { content: ""; flex: 1 1 auto; height: 1px; background: var(--rule); }


/* The hero dateline: place, set as a label with the mark's green dot. */
.badge {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  margin: 0;
  font: var(--caps);
  letter-spacing: var(--track);
  text-transform: uppercase;
  color: var(--green-ink);
}
.badge .dot { width: 6px; height: 6px; border-radius: 50%; background: currentColor; }

/* Raised paper. A hairline and a half-tone of warmth, nothing else — no
   shadow, no radius. */
.card { background: var(--card); border: 1px solid var(--rule); }


/* ============================================================
   Buttons — set in capitals, filled or ruled, never soft
   ============================================================ */
.button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  padding: 15px 26px;
  border: 1px solid transparent;
  border-radius: var(--r);
  font: var(--caps);
  font-size: 12px;
  letter-spacing: .16em;
  text-transform: uppercase;
  text-decoration: none;
  cursor: pointer;
  transition: background var(--dur) var(--ease), color var(--dur) var(--ease),
              border-color var(--dur) var(--ease);
}
.button-big { padding: 19px 34px; font-size: 12.5px; }

.button-solid { background: var(--green-ink); color: var(--on-green); }
.button-solid:hover { background: var(--ink); }

.button-line { background: transparent; color: var(--ink); border-color: var(--rule-firm); }
.button-line:hover { background: var(--ink); color: var(--paper); border-color: var(--ink); }

/* A link that behaves like a button: the rule under it is the whole treatment. */
.button-quiet {
  padding: 5px 0;
  border: 0;
  border-bottom: 1px solid currentColor;
  border-radius: 0;
  background: none;
  color: var(--green-ink);
}
.button-quiet:hover { color: var(--ink); background: none; }

/* ============================================================
   Masthead
   ============================================================ */
/* A continuous bar with a rule for its lower edge and the mark's green banded
   across the very top of the sheet — the page's spine. Only the contents are
   held to the measure, so the bar lines up with every section below it. */
.masthead {
  position: sticky;
  top: 0;
  z-index: 50;
  border-top: 3px solid var(--green-ink);
  border-bottom: 1px solid var(--rule);
  background: rgba(243, 239, 230, .88);
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
  transition: background var(--dur) var(--ease), border-color var(--dur) var(--ease);
}
.masthead-inner {
  display: flex;
  align-items: center;
  gap: 24px;
  max-width: var(--page);
  margin-inline: auto;
  padding: 12px var(--gutter);
}
.masthead.is-landed { background: rgba(243, 239, 230, .97); border-bottom-color: var(--rule-firm); }

.mark {
  display: flex;
  align-items: center;
  gap: 11px;
  margin-right: auto;
  font-size: 19px;
  font-weight: 800;
  font-stretch: 80%;
  text-transform: uppercase;
  letter-spacing: .01em;
  color: var(--ink);
  text-decoration: none;
}
.mark img { width: 36px; height: 36px; object-fit: contain; }

.nav { display: flex; gap: clamp(14px, 1.8vw, 26px); }
.nav a {
  padding: 6px 0;
  border-bottom: 2px solid transparent;
  font: var(--caps);
  letter-spacing: .15em;
  text-transform: uppercase;
  color: var(--text);
  text-decoration: none;
  transition: color var(--dur) var(--ease), border-color var(--dur) var(--ease);
}
.nav a:hover { color: var(--ink); border-bottom-color: var(--rule-firm); }
.nav a[aria-current] { color: var(--green-ink); border-bottom-color: var(--green-ink); }

.masthead-end { display: flex; align-items: center; gap: 14px; }
.masthead .button-solid { padding: 12px 20px; font-size: 11px; }

.menu-button {
  display: none;
  flex-direction: column;
  justify-content: center;
  gap: 5px;
  width: 42px; height: 42px;
  padding: 0;
  border: 1px solid var(--rule-firm);
  border-radius: var(--r);
  background: transparent;
  color: var(--ink);
  cursor: pointer;
}
.menu-button span {
  display: block;
  width: 18px; height: 1.6px;
  margin-inline: auto;
  background: currentColor;
  transition: transform var(--dur) var(--ease);
}
.menu-button[aria-expanded="true"] span:first-child { transform: translateY(3.3px) rotate(45deg); }
.menu-button[aria-expanded="true"] span:last-child { transform: translateY(-3.3px) rotate(-45deg); }

/* The phone menu is a contents page: numbered entries, one rule apiece. */
.menu {
  position: fixed;
  inset: 0;
  z-index: 45;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 38px;
  padding: 92px var(--gutter) 36px;
  background: var(--paper);
}
.menu nav { display: flex; flex-direction: column; counter-reset: menu; }
.menu nav a {
  display: flex;
  align-items: baseline;
  gap: 16px;
  padding: 15px 0;
  border-top: 1px solid var(--rule);
  font-size: clamp(30px, 9vw, 46px);
  font-weight: 800;
  font-stretch: 74%;
  text-transform: uppercase;
  letter-spacing: -.005em;
  line-height: 1;
  color: var(--ink);
  text-decoration: none;
}
.menu nav a:last-child { border-bottom: 1px solid var(--rule); }
.menu nav a::before {
  counter-increment: menu;
  content: counter(menu, decimal-leading-zero);
  font: var(--caps);
  letter-spacing: var(--track);
  color: var(--green-ink);
}
.menu-foot { display: flex; flex-direction: column; gap: 20px; align-items: flex-start; }
.menu-phone {
  font-size: 25px;
  font-weight: 700;
  font-stretch: 86%;
  letter-spacing: -.02em;
  color: var(--green-ink);
  text-decoration: none;
}
body.menu-open { overflow: hidden; }

/* ============================================================
   Shared section frame
   ============================================================ */
.band {
  max-width: var(--page);
  margin-inline: auto;
  padding: clamp(30px, 3.4vw, 48px) var(--gutter) var(--band);
}

/* The content column, stated once. A section that paints its own ground runs
   the full width of the sheet and uses this to hold its contents to the
   measure, so the ground is edge to edge but the type still lines up with
   every section above it. No viewport units: `100vw` includes the scrollbar
   and would widen the document, which is exactly what the audit looks for.
   It has to come after `.band` or the shorthand there would outrank it. */
.bleed {
  max-width: none;
  margin-inline: 0;
  padding-inline: max(var(--gutter), calc(50% - var(--page) / 2 + var(--gutter)));
}

.band-head { margin-bottom: clamp(28px, 3.4vw, 48px); }
/* A headline gets a measure of its own, in `ch` — which is relative to the
   headline's own size, so the block scales with it. Left to run the full width
   of the section these filled barely half a line each and read as type that had
   failed to reach the margin rather than as a composition; held to 16ch they
   set at 82-99% of their box and the space beside them becomes deliberate.
   `.measure` and `.closing-body` already do this at 12ch and 13ch. */
.band-head h2 { max-width: 16ch; }
.band-head .lead { margin-top: 18px; max-width: 56ch; }
/* The gallery's head carries a control alongside it. The headline still gets
   the whole measure — it is set at the same poster size as every other h2, and
   boxing it into the half-width column beside the filters broke it over four
   lines and stranded the rest of the row. So the wrapper steps out of the way
   with `display: contents` and the three parts are placed directly: headline
   across the top, standfirst and filters on the line beneath it. */
.band-head-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: end;
  column-gap: clamp(24px, 3vw, 48px);
}
.band-head-row > div:not(.filters) { display: contents; }
.band-head-row h2 { grid-column: 1; }
.band-head-row .lead { grid-column: 1; margin-bottom: 0; }
.band-head-row .filters { grid-column: 2; grid-row: 2; }

/* The standfirst. Wider than the body and a shade lighter — the one size
   between the headline and the text. */
.lead {
  font-size: clamp(18px, 1.5vw, 22px);
  font-weight: 400;
  line-height: 1.5;
  letter-spacing: -.012em;
  color: var(--text);
}

/* ------------------------------------------------------------
   The dark grounds
   `What's here', `Guests' and the closing run edge to edge on near-black, so
   the page reads as a sequence of spreads. Everything inside inverts.
   ------------------------------------------------------------ */
.is-dark {
  background: var(--dark);
  color: var(--on-dark-soft);
  padding-block: clamp(46px, 5.4vw, 88px) var(--band);
}
.is-dark h2, .is-dark h3, .is-dark h4 { color: var(--on-dark); }
.is-dark .lead { color: var(--on-dark-soft); }
.is-dark .kicker { background: var(--dark); color: var(--green-lit); }
.is-dark .kicker span { color: var(--on-dark); }
.is-dark .kicker::after { background: var(--rule-dark); }
.is-dark .button-line { color: var(--on-dark); border-color: var(--rule-dark-firm); }
.is-dark .button-line:hover { background: var(--on-dark); color: var(--dark); border-color: var(--on-dark); }
.is-dark .button-solid { background: var(--green-lit); color: var(--dark); }
.is-dark .button-solid:hover { background: var(--on-dark); }

/* ============================================================
   Hero
   The headline is a poster: condensed capitals at the full width of the
   measure, with the second line carried on the width axis instead of a
   different face. The photograph below it runs to both edges of the sheet, and
   the booking panel overlaps its lower-left corner — the grid breaks once, on
   purpose, so the page opens on a composition rather than a row of boxes.
   ============================================================ */
.hero { padding: 0 0 clamp(70px, 8vw, 130px); }

.hero-head {
  max-width: var(--page);
  margin-inline: auto;
  padding: clamp(26px, 4vw, 54px) var(--gutter) clamp(26px, 3.4vw, 44px);
}
.hero-head > .badge { margin-bottom: clamp(18px, 2.2vw, 28px); }

.hero-title {
  margin: 0;
  font-size: clamp(44px, 11.8vw, 168px);
  font-stretch: 62%;
  letter-spacing: -.004em;
  line-height: .86;
  /* The line break is authored with <br>; balancing on top of it makes Chrome
     leave a gap before the trailing punctuation. */
  text-wrap: initial;
}
/* The second line is the same size and width as the first — two full-measure
   lines stacked — and takes the green. */
.hero-title em { font-style: normal; color: var(--green); }

.hero-stage { position: relative; }

.card-photo { position: relative; overflow: hidden; padding: 0; }
.card-photo img { width: 100%; height: 100%; object-fit: cover; }
.hero-shot { border-inline: 0; aspect-ratio: 21 / 9; }

.hero-panel {
  position: relative;
  /* Above the scrim, below the masthead and the WhatsApp button, so it is the
     one thing still lit when the scrim is up — and so it can never ride over
     the sticky masthead on its way past. */
  z-index: 31;
  padding: clamp(24px, 2.6vw, 36px);
  /* The scrim follows this element's progress through the viewport. */
  view-timeline-name: --panel;
}
.hero-lead { max-width: 40ch; font-size: clamp(16px, 1.2vw, 18px); color: var(--text); }
.hero-actions { display: flex; gap: 10px; flex-wrap: wrap; margin-top: 24px; }

/* The dateline figures: tracked capitals over condensed numerals, divided by
   rules rather than boxed. */
.hero-facts {
  display: flex;
  gap: clamp(18px, 2.4vw, 34px);
  margin: 26px 0 0;
  padding-top: 20px;
  border-top: 1px solid var(--rule);
}
.hero-facts > div + div { padding-left: clamp(18px, 2.4vw, 34px); border-left: 1px solid var(--rule); }
.hero-facts dt {
  font: var(--caps);
  font-size: 10px;
  letter-spacing: var(--track);
  text-transform: uppercase;
  color: var(--text-soft);
}
.hero-facts dd {
  margin: 8px 0 0;
  font-size: clamp(24px, 2.2vw, 34px);
  font-weight: 800;
  font-stretch: 76%;
  letter-spacing: -.01em;
  line-height: 1;
  color: var(--ink);
}
.hero-facts dd span { font-size: .58em; font-weight: 600; color: var(--text-soft); }

@media (min-width: 941px) {
  .hero-stage { padding-bottom: clamp(58px, 6vw, 92px); }
  .hero-panel {
    position: absolute;
    bottom: 0;
    /* Held to the content column rather than to the photograph, so its left
       edge lines up with the headline above and every section below. */
    left: max(var(--gutter), calc(50% - var(--page) / 2 + var(--gutter)));
    width: min(520px, 42%);
  }
}
@media (max-width: 940px) {
  .hero-shot { aspect-ratio: 4 / 3; }
  .hero-panel { margin: var(--gutter) var(--gutter) 0; }
}

/* ============================================================
   The villa
   Headline left, standfirst and body right — a spread, not a stack.
   ============================================================ */
.measure { max-width: none; }
.measure h2 { max-width: 12ch; margin-bottom: clamp(20px, 2.6vw, 32px); }
.measure .lead { margin-bottom: 1.1em; }
.measure p:not(.lead) { max-width: 62ch; }

/* The opening capital. Condensed, green, and dropped three lines — the one
   ornament on the page. */
.measure .lead + p::first-letter {
  float: left;
  margin: .02em .1em -.06em 0;
  font-size: 4.4em;
  font-weight: 800;
  font-stretch: 66%;
  line-height: .72;
  color: var(--green-ink);
}

@media (min-width: 941px) {
  .measure {
    display: grid;
    grid-template-columns: 1.02fr 1fr;
    column-gap: clamp(34px, 5vw, 88px);
    align-items: start;
  }
  .measure > h2 { grid-column: 1; grid-row: 1 / span 2; margin-bottom: 0; }
  .measure > .lead { grid-column: 2; grid-row: 1; }
  .measure > p:not(.lead) { grid-column: 2; grid-row: 2; }
}

/* `The villa' is the one section with a full-bleed child, so it is laid out on
   a named-line grid instead of a centred max-width box: every child sits in the
   `measure' column, and the plate opts into `full'. The measure track resolves
   to exactly the same column the other sections get, and because this is grid
   track maths rather than `100vw' it never counts the scrollbar and so can
   never widen the document. */
#about {
  max-width: none;
  margin-inline: 0;
  padding-inline: 0;
  display: grid;
  grid-template-columns:
    [full-start] minmax(var(--gutter), 1fr)
    [measure-start] minmax(0, calc(var(--page) - var(--gutter) * 2)) [measure-end]
    minmax(var(--gutter), 1fr) [full-end];
}
#about > * { grid-column: measure; }
#about > .plate-open { grid-column: full; }
/* The running head has to run full width here too. It is sticky, so the plate
   scrolls underneath it, and a paper background that stopped at the measure
   would cut a strip across the photograph. Its contents stay on the measure. */
#about > .kicker {
  grid-column: full;
  padding-inline: max(var(--gutter), calc(50% - var(--page) / 2 + var(--gutter)));
}

/* Photographs on paper: no radius, no shadow, and the caption set below the
   frame in tracked capitals the way a plate is captioned, rather than floating
   on the image as a pill. */
.plate { margin: clamp(32px, 4vw, 60px) 0 0; }
/* The full-bleed plate's frame runs to both edges, but its caption is still a
   caption and belongs on the measure. */
.plate-open img { aspect-ratio: 21 / 9; border-inline: 0; }
.plate-open figcaption { margin-inline: max(var(--gutter), calc(50% - var(--page) / 2 + var(--gutter))); }
.plate img {
  width: 100%; height: auto;
  aspect-ratio: 16 / 9;
  object-fit: cover;
  border: 1px solid var(--rule);
}
.plate-inset { margin-top: 0; }
.plate-inset img { aspect-ratio: 4 / 3; }
.plate figcaption {
  margin-top: 12px;
  padding-top: 10px;
  border-top: 1px solid var(--rule);
  font: var(--caps);
  font-size: 10.5px;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--text-soft);
}

/* The figures. Four columns divided by rules, numerals condensed and large. */
.facts {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  margin: clamp(34px, 4vw, 60px) 0 0;
  border-top: 1px solid var(--rule-firm);
}
.facts > div { padding: clamp(20px, 2vw, 28px) clamp(16px, 2vw, 26px) 0 0; }
.facts > div + div { padding-left: clamp(16px, 2vw, 26px); border-left: 1px solid var(--rule); }
.facts dt {
  font: var(--caps);
  font-size: 10px;
  letter-spacing: var(--track);
  text-transform: uppercase;
  color: var(--text-soft);
}
.facts dd {
  margin: 10px 0 0;
  font-size: clamp(26px, 2.8vw, 42px);
  font-weight: 800;
  font-stretch: 74%;
  text-transform: uppercase;
  letter-spacing: -.01em;
  line-height: 1;
  color: var(--ink);
}

/* ============================================================
   What's here — a numbered index on the dark ground
   ============================================================ */
.bento {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  column-gap: clamp(24px, 3vw, 52px);
  row-gap: clamp(30px, 3.4vw, 50px);
  margin: 0;
  padding: 0;
  list-style: none;
  counter-reset: bento;
}
.bento li {
  padding-top: 20px;
  border-top: 1px solid var(--rule-dark-firm);
  transition: border-color var(--dur) var(--ease);
}
.bento li::before {
  counter-increment: bento;
  content: counter(bento, decimal-leading-zero);
  display: block;
  margin-bottom: 16px;
  font: var(--caps);
  letter-spacing: var(--track);
  color: var(--green-lit);
}
.bento h3 { margin-bottom: 10px; }
.bento p { margin: 0; font-size: 16px; color: var(--on-dark-soft); }
.bento li:hover { border-top-color: var(--green-lit); }

/* ============================================================
   Photographs
   ============================================================ */
.filters { display: flex; gap: 8px; flex-wrap: wrap; }
.filters button {
  padding: 11px 18px;
  border: 1px solid var(--rule-firm);
  border-radius: var(--r);
  background: transparent;
  color: var(--text);
  font: var(--caps);
  letter-spacing: .14em;
  text-transform: uppercase;
  cursor: pointer;
  transition: background var(--dur) var(--ease), color var(--dur) var(--ease),
              border-color var(--dur) var(--ease);
}
.filters button:hover { border-color: var(--ink); color: var(--ink); }
.filters button.is-on { background: var(--green-ink); border-color: var(--green-ink); color: var(--on-green); }

.shots {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  grid-auto-rows: 200px;
  grid-auto-flow: row dense;
  gap: clamp(8px, 1vw, 14px);
}
.tile {
  position: relative;
  grid-column: span 1;
  grid-row: span 1;
  margin: 0;
  overflow: hidden;
  background: var(--paper-warm);
  cursor: zoom-in;
}
.tile img {
  width: 100%; height: 100%;
  object-fit: cover;
  transition: transform .9s var(--ease);
}
.tile:hover img { transform: scale(1.05); }
.tile-wide { grid-column: span 2; }
.tile-tall { grid-row: span 2; }
/* The caption belongs to the photograph, so it is set on it rather than beside
   it: paper capitals along the foot of the frame, over a scrim that fades up out
   of the image. The previous version was an opaque slip of paper pasted on the
   corner, which on a phone — where every caption is showing at once — read as a
   sheet of stickers rather than as a gallery. The scrim is the same device the
   closing slab uses, and it is what keeps the capitals legible whatever the
   photograph is doing underneath them. */
.tile figcaption {
  position: absolute;
  left: 0; right: 0; bottom: 0;
  padding: 34px 13px 12px;
  background: linear-gradient(to top,
    rgba(9, 18, 15, .88) 0%,
    rgba(9, 18, 15, .74) 38%,
    rgba(9, 18, 15, .34) 72%,
    rgba(9, 18, 15, 0) 100%);
  font: var(--caps);
  font-size: 9.5px;
  line-height: 1.5;
  letter-spacing: .13em;
  text-transform: uppercase;
  color: var(--paper);
  opacity: 0;
  transform: translateY(18px);
  transition: opacity var(--dur) var(--ease), transform var(--dur) var(--ease);
}
.tile:hover figcaption, .tile:focus-visible figcaption { opacity: 1; transform: none; }
/* Nothing to hover on a touch screen, so every caption is simply present. */
@media (hover: none) { .tile figcaption { opacity: 1; transform: none; } }

/* ============================================================
   Guests — two rows of cards, running opposite ways
   The quotes travel rather than sit: the top row drifts left, the lower row
   right, both fading out at the edges of the measure so they read as a strip
   that continues past the page rather than a list that stops.

   This is the one thing here that loops. It animates `transform` only and is
   duplicated in the track so the loop is seamless, which keeps it on the
   compositor — but it never stops on its own, so it carries the obligations
   that come with moving content: it pauses on hover, it pauses on the button
   under it, and `prefers-reduced-motion` switches it off entirely and hands the
   rows back to the browser as ordinary scrollers.

   hillcroft.js clones the cards and adds `is-live`; without script there is no
   animation and the rows are plain horizontal scrollers, which is also what a
   visitor who has asked for less motion gets.
   ============================================================ */
.marquee { display: flex; flex-direction: column; gap: clamp(14px, 1.6vw, 22px); }

.marquee-row {
  overflow: hidden;
  /* The strip fades out rather than being cut off at the measure. */
  -webkit-mask-image: linear-gradient(90deg, transparent 0, #000 7%, #000 93%, transparent 100%);
  mask-image: linear-gradient(90deg, transparent 0, #000 7%, #000 93%, transparent 100%);
}
/* Without the script this is the whole interaction: a scrollable row. */
.marquee:not(.is-live) .marquee-row { overflow-x: auto; scrollbar-width: none; }
.marquee:not(.is-live) .marquee-row::-webkit-scrollbar { display: none; }

.marquee-track {
  display: flex;
  gap: clamp(14px, 1.6vw, 22px);
  width: max-content;
}

@media (prefers-reduced-motion: no-preference) {
  .marquee.is-live .marquee-track {
    animation: marquee 74s linear infinite;
    will-change: transform;
  }
  .marquee.is-live .marquee-row-back .marquee-track {
    animation-duration: 88s;
    animation-direction: reverse;
  }
  /* Hovering a row holds that row; the button holds both. */
  .marquee.is-live .marquee-row:hover .marquee-track,
  .marquee.is-live.is-paused .marquee-track { animation-play-state: paused; }
}
/* The track carries two copies of every card, so half a turn is a whole loop. */
@keyframes marquee {
  from { transform: translate3d(0, 0, 0); }
  to { transform: translate3d(-50%, 0, 0); }
}

.quote-card {
  flex: none;
  width: clamp(258px, 25vw, 330px);
  margin: 0;
  padding: clamp(20px, 1.9vw, 26px);
  border: 1px solid var(--rule-dark);
  background: rgba(236, 231, 219, .04);
}
.quote-card blockquote { margin: 0 0 20px; }
.quote-card p {
  margin: 0;
  font-size: 14.5px;
  line-height: 1.55;
  color: var(--on-dark-soft);
}
.quote-card figcaption {
  display: flex;
  align-items: center;
  gap: 11px;
  padding-top: 16px;
  border-top: 1px solid var(--rule-dark);
}
/* Initials, not a portrait. The photographs that used to sit here were stock
   images of people who did not write these reviews. */
.quote-card .mono {
  flex: none;
  display: grid;
  place-items: center;
  width: 34px; height: 34px;
  border-radius: 50%;
  background: var(--green-lit);
  color: var(--dark);
  font: var(--caps);
  font-size: 11px;
  letter-spacing: .02em;
}
.quote-card .who {
  display: flex;
  flex-direction: column;
  gap: 5px;
  min-width: 0;
  font: var(--caps);
  font-size: 9.5px;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--on-dark-soft);
}
.quote-card .who b { font-size: 11px; font-weight: 700; color: var(--on-dark); }

.quotes-foot {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 24px;
  flex-wrap: wrap;
  margin: clamp(30px, 3.4vw, 50px) 0 0;
  padding-top: 22px;
  border-top: 1px solid var(--rule-dark);
}

.rating {
  display: flex;
  align-items: baseline;
  gap: 14px;
  margin: 0;
  font: var(--caps);
  font-size: 11.5px;
  letter-spacing: var(--track);
  text-transform: uppercase;
  color: var(--on-dark-soft);
}
.rating b {
  font-size: clamp(34px, 3.4vw, 50px);
  font-weight: 800;
  font-stretch: 74%;
  letter-spacing: -.01em;
  line-height: 1;
  color: var(--green-lit);
}

/* Moving content that starts on its own needs a way to stop it that does not
   depend on having a pointer. */
.marquee-toggle {
  padding: 11px 20px;
  border: 1px solid var(--rule-dark-firm);
  border-radius: var(--r);
  background: transparent;
  color: var(--on-dark);
  font: var(--caps);
  letter-spacing: .16em;
  text-transform: uppercase;
  cursor: pointer;
  transition: background var(--dur) var(--ease), color var(--dur) var(--ease);
}
.marquee-toggle:hover { background: var(--on-dark); color: var(--dark); }

/* ============================================================
   Nuwara Eliya
   ============================================================ */
.location-grid {
  display: grid;
  grid-template-columns: 1.15fr .85fr;
  gap: clamp(28px, 3.4vw, 64px);
  align-items: start;
}

/* An index, not a list of cards: the distance sits in its own column in
   tabular numerals, and a rule separates every entry. */
.nearby { margin: 0; padding: 0; list-style: none; border-top: 1px solid var(--rule-firm); }
.nearby li {
  display: grid;
  grid-template-columns: 4.4em 1fr;
  gap: clamp(14px, 2vw, 28px);
  padding: 22px 0;
  border-bottom: 1px solid var(--rule);
}
.nearby li > span {
  font: var(--caps);
  font-size: 11px;
  letter-spacing: .12em;
  text-transform: uppercase;
  font-variant-numeric: tabular-nums;
  color: var(--green-ink);
  padding-top: .34em;
}
.nearby h3 { margin-bottom: 5px; }
.nearby p { margin: 0; font-size: 15.5px; color: var(--text-soft); }

.aside { display: flex; flex-direction: column; gap: clamp(24px, 3vw, 40px); }

.getting-here { padding-top: 22px; border-top: 1px solid var(--rule-firm); }
.getting-here h3 { margin-bottom: 14px; }
.getting-here ul { margin: 0 0 16px; padding: 0; list-style: none; }
.getting-here li {
  display: grid;
  grid-template-columns: 1fr auto auto;
  gap: 16px;
  padding: 13px 0;
  border-top: 1px solid var(--rule);
  font-size: 14px;
  font-variant-numeric: tabular-nums;
  color: var(--text-soft);
}
.getting-here li span:first-child {
  font: var(--caps);
  font-size: 11px;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--ink);
}
.getting-here p { margin: 0; font-size: 14.5px; color: var(--text-soft); }

/* The address block, set like a colophon: a heavy rule above it and the number
   at poster scale. */
.contact {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: 16px clamp(20px, 3vw, 52px);
  align-items: center;
  margin-top: clamp(40px, 4.6vw, 72px);
  padding-top: clamp(26px, 3vw, 40px);
  border-top: 2px solid var(--ink);
}
.contact > .contact-address { grid-column: 1; grid-row: 1; }
.contact > .contact-phone { grid-column: 1; grid-row: 2; }
.contact > .contact-actions { grid-column: 2; grid-row: 1 / span 2; align-self: center; }
.contact-address {
  margin-bottom: 0;
  font: var(--caps);
  font-size: 11px;
  line-height: 1.9;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--text-soft);
}
.contact-phone {
  display: inline-block;
  font-size: clamp(30px, 4vw, 56px);
  font-weight: 800;
  font-stretch: 74%;
  letter-spacing: -.015em;
  line-height: 1;
  color: var(--green-ink);
  text-decoration: none;
  transition: color var(--dur) var(--ease);
}
.contact-phone:hover { color: var(--ink); }
.contact-actions { display: flex; gap: 10px; flex-wrap: wrap; }

/* ============================================================
   Questions
   ============================================================ */
.questions { border-top: 1px solid var(--rule-firm); }
.questions details { border-bottom: 1px solid var(--rule); }
.questions summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 24px;
  padding: 26px 0;
  list-style: none;
  cursor: pointer;
  font-size: clamp(19px, 1.9vw, 26px);
  font-weight: 700;
  font-stretch: 84%;
  letter-spacing: -.014em;
  line-height: 1.2;
  color: var(--ink);
  transition: color var(--dur) var(--ease);
}
.questions summary::-webkit-details-marker { display: none; }
.questions summary:hover { color: var(--green-ink); }
/* The marker is a real drawn plus — now bare, with no tinted disc behind it.
   It used to be two gradient bars over a circle, with an inset box-shadow that
   painted straight over them. */
.questions summary::after {
  content: "";
  flex: none;
  width: 22px; height: 22px;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%2300443f' stroke-width='1.8' stroke-linecap='round'%3E%3Cpath d='M12 4v16M4 12h16'/%3E%3C/svg%3E");
  background-size: 22px 22px;
  background-position: center;
  background-repeat: no-repeat;
  transition: transform .45s var(--ease);
}
.questions details[open] summary::after { transform: rotate(135deg); }

/* hillcroft.js animates this open and closed; it must clip while it moves. */
.questions details > div { overflow: hidden; }
.questions details > div { padding-bottom: 26px; }
.questions details p { max-width: 68ch; margin: 0; font-size: 16px; color: var(--text-soft); }

/* ============================================================
   Closing — the last spread, full bleed over the photograph
   ============================================================ */
#book { padding: 0; }
.closing {
  position: relative;
  overflow: hidden;
  border: 0;
  padding-block: clamp(70px, 9vw, 150px);
  color: var(--on-dark);
  isolation: isolate;
}
.closing-photo { position: absolute; inset: 0; z-index: -1; }
.closing-photo img { width: 100%; height: 100%; object-fit: cover; }
.closing-photo::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(100deg, rgba(10, 20, 17, .95) 4%, rgba(10, 20, 17, .82) 52%, rgba(10, 20, 17, .55) 100%);
}
.closing-body { max-width: 54ch; }
.closing-body h2 { max-width: 13ch; color: var(--on-dark); margin-bottom: 22px; }
.closing-body p {
  max-width: 46ch;
  font-size: clamp(17px, 1.4vw, 20px);
  line-height: 1.5;
  /* A shade brighter than --on-dark-soft: this is the one block of text on the
     page that sits over a photograph rather than a flat ground. */
  color: #c9d2cc;
  margin-bottom: 34px;
}
.closing-actions { display: flex; align-items: center; gap: 16px; flex-wrap: wrap; }
.closing .button-solid { background: var(--green-lit); color: var(--dark); }
.closing .button-solid:hover { background: var(--on-dark); }
.closing .button-line { color: var(--on-dark); border-color: var(--rule-dark-firm); }
.closing .button-line:hover { background: var(--on-dark); color: var(--dark); border-color: var(--on-dark); }
/* The quiet link's green is for the paper; over the photograph it has to be
   white or it disappears. */
.closing .button-quiet { color: var(--on-dark); }
.closing .button-quiet:hover { color: var(--green-lit); }

/* ============================================================
   Footer
   ============================================================ */
/* The footer answers the masthead: a continuous band across the sheet under a
   heavy rule, closed by the same green the masthead opens on. */
.footer { border-top: 2px solid var(--ink); background: var(--paper); }
.footer-top {
  display: grid;
  grid-template-columns: 1.35fr 1.1fr .8fr;
  align-items: start;
  gap: clamp(26px, 3vw, 48px);
  max-width: var(--page);
  margin-inline: auto;
  padding: clamp(40px, 4.6vw, 68px) var(--gutter) clamp(30px, 3.4vw, 44px);
}
.footer .mark { font-size: clamp(24px, 2.6vw, 36px); margin-right: 0; }
.footer .mark img { width: 48px; height: 48px; }
.footer address {
  font-style: normal;
  font: var(--caps);
  font-size: 11px;
  line-height: 2.1;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--text-soft);
}
.footer address a { color: var(--green-ink); font-weight: 700; text-decoration: none; }
.footer address a:hover { text-decoration: underline; }
.footer nav { display: flex; flex-direction: column; gap: 11px; align-items: flex-start; }
.footer nav a {
  font: var(--caps);
  font-size: 11px;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--text);
  text-decoration: none;
  transition: color var(--dur) var(--ease);
}
.footer nav a:hover { color: var(--green-ink); }
.footer-base {
  max-width: var(--page);
  margin: 0 auto;
  padding: 18px var(--gutter) 26px;
  border-top: 1px solid var(--rule);
  font: var(--caps);
  font-size: 10.5px;
  letter-spacing: var(--track);
  text-transform: uppercase;
  color: var(--text-soft);
}

/* ============================================================
   WhatsApp
   ============================================================ */
.whatsapp {
  position: fixed;
  right: 20px; bottom: 20px;
  z-index: 40;
  display: grid;
  place-items: center;
  width: 54px; height: 54px;
  border-radius: 50%;
  background: var(--green-ink);
  color: var(--on-green);
  transition: transform var(--dur) var(--ease), background var(--dur) var(--ease);
}
.whatsapp:hover { transform: translateY(-3px); background: var(--ink); }

/* ============================================================
   Viewer
   ============================================================ */
.viewer {
  position: fixed;
  inset: 0;
  z-index: 60;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 12px;
  padding: clamp(16px, 4vw, 44px);
  background: rgba(10, 18, 16, .96);
  backdrop-filter: blur(8px);
  overscroll-behavior: contain;
}
.viewer figure { margin: 0; max-width: min(1120px, 100%); }
.viewer img { max-height: 78svh; width: auto; margin-inline: auto; }
.viewer figcaption {
  margin-top: 18px;
  text-align: center;
  font: var(--caps);
  font-size: 10.5px;
  letter-spacing: var(--track);
  text-transform: uppercase;
  color: var(--on-dark-soft);
}
.viewer-close, .viewer-step {
  flex: none;
  display: grid;
  place-items: center;
  width: 48px; height: 48px;
  border: 1px solid var(--rule-dark-firm);
  border-radius: var(--r);
  background: transparent;
  color: var(--on-dark);
  font-size: 22px;
  line-height: 1;
  cursor: pointer;
  transition: background var(--dur) var(--ease), color var(--dur) var(--ease);
}
.viewer-close:hover, .viewer-step:hover { background: var(--on-dark); color: var(--dark); }
.viewer-close { position: absolute; top: clamp(16px, 3vw, 30px); right: clamp(16px, 3vw, 30px); }

/* ============================================================
   Motion — one arrival, one drift
   ============================================================ */
@media (prefers-reduced-motion: no-preference) {
  .hero-head > .badge, .hero-shot, .hero-panel {
    animation: arrive .8s var(--ease) both;
  }
  .hero-head > .badge { animation-delay: .04s; }
  .hero-shot  { animation-delay: .34s; }
  .hero-panel { animation-delay: .52s; }
}
@keyframes arrive {
  from { opacity: 0; transform: translate3d(0, 16px, 0); }
  to { opacity: 1; transform: none; }
}

/* Entrances. One pass per element, driven by an IntersectionObserver in
   hillcroft.js rather than tied to scroll position: a transition that runs once
   and stops is both smoother to watch and far cheaper than an animation the
   compositor has to re-evaluate on every scrolled frame. The class is only ever
   added by script, so nothing is hidden if the script does not run. */
@media (prefers-reduced-motion: no-preference) {
  .reveal {
    opacity: 0;
    transform: translate3d(0, 22px, 0);
    transition: opacity .72s var(--ease), transform .72s var(--ease);
    transition-delay: var(--reveal-delay, 0ms);
  }
  .reveal-photo { transform: translate3d(0, 22px, 0) scale(.985); }
  .reveal.is-in { opacity: 1; transform: none; }
}

/* ============================================================
   Responsive
   ============================================================ */
@media (max-width: 1100px) {
  .shots { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .location-grid { grid-template-columns: 1fr; }
  .contact { grid-template-columns: 1fr; align-items: start; }
  .bento { grid-template-columns: repeat(2, 1fr); }
}

@media (max-width: 1023px) {
  .nav { display: none; }
  .menu-button { display: flex; }
  .masthead .button-solid { display: none; }
  .footer-top { grid-template-columns: 1fr 1fr; }
}

@media (max-width: 760px) {
  body { font-size: 16px; }
  /* The running heads stack up under a masthead that is itself shorter here,
     and at this width they cost more room than they earn. */
  .kicker { position: static; padding-top: 0; }
  .facts { grid-template-columns: 1fr 1fr; }
  .facts > div { padding-right: clamp(14px, 4vw, 22px); }
  .facts > div:nth-child(3), .facts > div:nth-child(4) { padding-top: 22px; border-top: 1px solid var(--rule); }
  .facts > div:nth-child(3) { padding-left: 0; border-left: 0; }
  .hero-facts { display: grid; grid-template-columns: 1fr 1fr; gap: 20px 22px; }
  .hero-facts > div + div { padding-left: 0; border-left: 0; }
  .hero-facts > div:nth-child(even) { padding-left: 22px; border-left: 1px solid var(--rule); }
  .bento { grid-template-columns: 1fr; }
  .shots { grid-template-columns: repeat(2, minmax(0, 1fr)); grid-auto-rows: 172px; }
  .tile-wide { grid-column: span 2; }
  .tile-tall { grid-row: span 2; }
  .nearby li { grid-template-columns: 1fr; gap: 6px; }
  /* The headline keeps its own measure here — the type scales with the column,
     so 16ch holds the same proportion at every width — but the filters need the
     full width and drop below the standfirst. */
  .band-head-row { grid-template-columns: minmax(0, 1fr); }
  .band-head-row .filters { grid-column: 1; grid-row: auto; margin-top: 22px; }
  /* 21:9 is a letterbox slot on a phone. */
  .plate-open img { aspect-ratio: 4 / 3; }
  /* The diagonal scrim clears the headline at desktop because the text only
     occupies the left third. Here the copy runs the full width of the slab and
     crosses the bright end of it, so the scrim goes flat and heavy instead. */
  .closing-photo::after { background: linear-gradient(180deg, rgba(10, 20, 17, .88) 30%, rgba(10, 20, 17, .7) 100%); }
  .nearby li > span { padding-top: 0; }
  .footer-top { grid-template-columns: 1fr; }
  .viewer-step { display: none; }
}

/* ============================================================
   Touch targets
   Where the pointer is a finger, every standalone control reaches 44px. This is
   hit area only: no type changes size, and on a desktop nothing moves at all.
   The footer's navigation was the real failure — 11px tall, under the 24px floor
   WCAG 2.2 asks of any target that is not inline in a sentence. The controls in
   the masthead grow with a pseudo-element rather than padding, so the bar itself
   keeps its height and the sticky running heads still tuck under it.
   ============================================================ */
@media (pointer: coarse) {
  :root { --head: 72px; }

  /* Buttons sit at exactly 44px on their own padding, which fractional font
     metrics can tip just under. A few px of margin settles it. The quiet button
     is excluded — its padding would push its underline off the words — and the
     masthead's is re-asserted so the bar keeps its height. */
  .button:not(.button-quiet), .marquee-toggle { padding-block: 17px; }
  .masthead .button-solid { padding-block: 12px; }

  .footer nav { gap: 0; }
  .footer nav a { padding-block: 17px; }
  .footer address a { display: inline-block; padding-block: 12px; }
  .filters button { padding-block: 16px; }
  .skip { padding-block: 17px; }
  .menu-button { width: 44px; height: 44px; }

  .mark, .nav a, .masthead .button-solid,
  .button-quiet, .contact-phone { position: relative; }
  .mark::after, .masthead .button-solid::after {
    content: "";
    position: absolute;
    inset: -5px -4px;
  }
  /* Both of these are a phone number to tap, and both carry a rule right under
     the text — padding would push that rule away from the words, so the target
     grows behind them instead. */
  .button-quiet::after { content: ""; position: absolute; inset: -14px -6px; }
  .contact-phone::after { content: ""; position: absolute; inset: -10px -4px; }
  /* The nav bar shows from 1024px up, which on a tablet is still a finger. The
     overlay grows the target without moving the underline beneath the label. */
  .nav a::after { content: ""; position: absolute; inset: -12px -5px; }
}

/* At the narrowest widths a two-line label would push its figure out of line
   with the one-line label beside it. */
@media (max-width: 400px) {
  .hero-facts dt { min-height: 2.1em; }
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .001ms !important;
  }
}

/* ============================================================
   Text motion
   Headings are split into words by hillcroft.js and each word rises out of a
   clip. The clip needs a little room below the baseline or descenders get
   shaved, and a little to the right or the condensed capitals' overhang does;
   the negative margins give both back so the line box is unchanged.
   ============================================================ */
.word {
  display: inline-block;
  overflow: hidden;
  vertical-align: bottom;
  padding-bottom: .14em;
  margin-bottom: -.14em;
  padding-right: .05em;
  margin-right: -.05em;
}
@media (prefers-reduced-motion: no-preference) {
  .word > span {
    display: inline-block;
    transform: translate3d(0, 110%, 0);
    transition: transform .82s var(--ease);
    transition-delay: var(--d, 0ms);
  }
  .is-in .word > span { transform: none; }
}


/* ============================================================
   The booking panel, held for a moment
   As the panel reaches the middle of the viewport the rest of the page dims
   behind it and it reads as a modal; scroll on and the page comes back. The
   scrim is `position: fixed`, so it has no view progress of its own — it
   borrows the panel's through a named timeline, which is what `timeline-scope`
   on the root is for.

   It animates `opacity` only, is `pointer-events: none` so it can never take a
   click or block the buttons underneath, and is `aria-hidden`. Behind @supports
   and a reduced-motion guard: with neither, the scrim simply never paints.

   The trade-off worth knowing: while the scrim is up, the text behind it is
   dimmed and momentarily below its contrast ratio. That is the effect working
   as asked, but it is why the scrim stops at 72% and why it clears completely
   within half a screen of scrolling either side of centre.
   ============================================================ */
:root { timeline-scope: --panel; }

.spotlight {
  position: fixed;
  inset: 0;
  z-index: 30;
  pointer-events: none;
  background: rgba(9, 18, 15, .72);
  opacity: 0;
}
@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    .spotlight {
      animation-name: spotlight;
      animation-duration: auto;
      animation-timing-function: linear;
      animation-fill-mode: both;
      animation-timeline: --panel;
      /* Symmetric about `cover 50%`, which is the panel dead-centre in the
         viewport. It starts at 34% because at the top of the page the panel is
         already up to 23% through its cover range on a phone — a wider range
         would have the page loading with the scrim faintly on. */
      animation-range: cover 34% cover 66%;
    }
  }
}
/* Dark only around the middle of the pass, with a short hold at full strength
   so it registers as a state rather than a flicker. */
@keyframes spotlight {
  0%, 100% { opacity: 0; }
  42%, 58% { opacity: 1; }
}

/* ============================================================
   The plates open out
   Both photographs start as a centred inset picture and grow into their frame as
   they are scrolled through — the valley plate out to the full width of the
   viewport, the tea-path plate to the width of the aside column it sits in. This
   is the one scroll-linked effect on the page, and it is written as a CSS view-timeline rather than a
   scroll listener: Chrome runs it on the compositor, so it costs nothing per
   frame, which is the whole reason the earlier scroll-driven version of this
   page was torn out. It only ever animates `transform`, so it changes no layout
   and cannot widen the document however far it scales.

   It scales rather than wiping. An earlier revision of this page animated
   `clip-path` on the large photographs, which reads well but is the one property
   here the compositor will not reliably take on its own; on a page that is mostly
   photography it more than doubled the janky-frame count.

   Everything is behind @supports, and hillcroft.js leaves both plates out of the
   entrance groups: where there is no view-timeline, or the visitor has asked for
   less motion, the photographs are simply there at full size.
   ============================================================ */
@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    .plate-open img, .plate-inset img {
      transform-origin: 50% 50%;
      animation-name: open-out;
      /* Longhand, and `auto`: the `animation` shorthand would reset duration to
         0s, which a view-timeline cannot stretch over its range. */
      animation-duration: auto;
      animation-timing-function: linear;
      animation-fill-mode: both;
      animation-timeline: view();
      animation-range: cover 12% cover 48%;
    }
    /* The tea-path plate opens inside the aside column rather than out to the
       edges of the sheet, so it starts much nearer its final size: .68 in a
       column that narrow would read as a stamp adrift in an empty frame. */
    .plate-inset img { animation-name: open-inset; }
  }
}
@keyframes open-out {
  from { transform: scale(.68); }
  to { transform: scale(1); }
}
@keyframes open-inset {
  from { transform: scale(.76); }
  to { transform: scale(1); }
}

/* Numbers count up once, in place. The element keeps its final value in the
   markup, so it is correct before the script runs and under reduced motion. */
.counting { font-variant-numeric: tabular-nums; }
