/* ==========================================================================
   HAILE BOUTIQUE HOTEL — "Lamplight"
   Design system. One file, no framework.

   The house is LIT, not printed. Every earlier version of this site put the
   building on a sheet of cream paper; this one puts it in a dark room after
   sundown and lights it — which is when a 1950s highland house with walnut
   joinery, brass sconces and oil-lamp glass actually looks like itself.

   Materials, unchanged: lime plaster, white boiserie mouldings, walnut,
   brass, travertine — and one loud modern gesture, the burnt-orange lobby
   sofa. What changed is the hour. Night is the ground; paper appears only
   where there is something to READ, the way a lit page looks in a dark room.

   Three lights, never one: brass (the fitting), candle (the flame), ember
   (the sofa). A single acid accent on near-black is the other thing every
   generated page does — this is a warm family of three, on warm walnut-black,
   never grey.
   ========================================================================== */

/* --------------------------------------------------------------------------
   1. Tokens
   -------------------------------------------------------------------------- */
:root {
  /* Night — the ground. Warm walnut-black pulled from the shadow side of the
     joinery, never a neutral grey and never true black: #000 on a screen
     reads as a hole cut in the page, not as an unlit room. */
  --night:     #14100C;
  --night-2:   #1E1811;  /* raised surfaces — cards, panels, the drawer */
  --night-3:   #2A2118;  /* wells, inset fields, form inputs */
  --night-line: rgba(247, 241, 230, .12);
  --night-hair: rgba(247, 241, 230, .07);

  /* Paper — the lit page. Used ONLY for reading chapters, never as the
     default ground; that inversion is the whole design. */
  --paper:      #F7F1E6;
  --paper-2:    #EDE3D3;
  --paper-3:    #E2D6C3;

  /* Ink — for the paper chapters only. Warm blacks, never pure grey.
     --ink-faint is darker than the value this design started from (#8A7A6C):
     it is used at 11px for spec labels, card feature chips and form notes,
     and measured 3.7:1 on paper — under the 4.5:1 floor for text that small.
     At #6E6053 it still reads as the quietest voice on the page and clears
     5.5:1. */
  --ink:       #1E1712;
  --ink-muted: #57493E;
  --ink-faint: #6E6053;
  --walnut:    #5A3A24;

  /* Text on night, contrast-tested against --night (#14100C):
     --lit       16.9:1    --lit-muted  ~11:1    --lit-faint  ~6.4:1 */
  --lit:       #F7F1E6;
  --lit-muted: rgba(247, 241, 230, .74);
  --lit-faint: rgba(247, 241, 230, .52);

  /* Brass — the fitting. On cream this was too low-contrast for copy and was
     restricted to hairlines; on night it clears 7.8:1, so for the first time
     brass can carry small text. The eyebrows are set in it throughout. */
  --brass:      #C9A227;
  /* The paper chapters' brass. Darker than the old #9C7B3F, which measured
     3.5:1 against paper and so could not legally carry an 11px eyebrow —
     the exact job it exists for. #856630 clears 4.7:1. */
  --brass-deep: #856630;
  --brass-line: rgba(201, 162, 39, .38);
  --brass-hair: rgba(201, 162, 39, .20);

  /* Candle — the flame itself. Only ever a light source: sconce pools, the
     lamp, the glow behind a rule. Never a fill, never type. */
  --candle:      #F0C078;
  --candle-glow: rgba(240, 192, 120, .16);

  /* Accent — lacquer and gilt.
     This started as the burnt orange of the lobby sofa, used for everything:
     fills, marks AND coloured words. That could not hold. A red deep enough
     to read as expensive only reaches ~2.4:1 on this ground, so the coloured
     words had to keep climbing towards peach to stay legible — which is how
     an accent meant to say "lacquer" ended up saying "safety orange", and
     why the one full-bleed band of it read as paint rather than material.

     So the job is split. Fills are deep lacquer, carrying paper-coloured
     text on top of them at 7:1. Coloured WORDS are antique gilt, which is
     what a dark room does to gold anyway, and which clears 11:1 — the
     premium reading and the accessible one are finally the same choice
     instead of opposed.

     --lacquer    fills: buttons, tabs, rules          (7.2:1 under paper text)
     --oxblood    the one full-bleed field             (10.9:1 under paper text)
     --gilt       coloured words on night              (11.3:1)
     --gilt-deep  coloured words on paper              (8.3:1)  */
  --lacquer:      #8E2F2B;
  --lacquer-deep: #6E231F;
  --oxblood:      #5E211D;
  --gilt:         #E8C27A;
  --gilt-deep:    #7E2A26;

  /* Alert keeps its own hue. Errors must not be gold — gold is the accent
     here, and a form that fails in the same colour it celebrates in is
     telling the reader nothing. */
  --alert: #E8836A;

  /* The five names the stylesheet and content/presets.json already speak,
     re-pointed at the split above. Kept rather than renamed so a saved
     admin theme keeps resolving and no rule silently loses its colour.
     --ember      fills and marks
     --ember-lit  coloured words on night   → gilt
     --ember-ink  coloured words on paper   → deep lacquer
     --ember-deep fills under paper text
     --ember-wash the faint tinted ground */
  --ember:      var(--lacquer);
  --ember-lit:  var(--gilt);
  --ember-ink:  var(--gilt-deep);
  --ember-deep: var(--lacquer);
  --ember-wash: rgba(142, 47, 43, .12);

  /* Frond — a single green, used once, for the garden */
  --frond: #2F4A32;

  /* Legacy aliases. The page sources and admin-rendered markup still speak
     the old material vocabulary in a few places; these keep that working
     rather than leaving a stale token to resolve as `unset` mid-cascade. */
  --plaster:      var(--paper);
  --plaster-warm: var(--paper-2);
  --travertine:   var(--paper-2);
  --travertine-d: var(--paper-3);

  /* Type */
  --display: "Bodoni Moda", "Didot", "Bodoni MT", Georgia, serif;
  --text: "Instrument Sans", ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
  /* Display weight travels with the display face, because the same number
     does not buy the same presence twice: a Didone at 450 is already dramatic
     at 6rem, an old-style Garamond at 450 goes faint. Each font preset in
     content/presets.json sets its own; this is the fallback. */
  --display-weight: 600;

  --fs-micro: 0.6875rem;
  --fs-small: clamp(0.8125rem, 0.79rem + 0.12vw, 0.875rem);
  --fs-body:  clamp(0.9375rem, 0.90rem + 0.19vw, 1.0625rem);
  --fs-lead:  clamp(1.0625rem, 0.98rem + 0.42vw, 1.3125rem);
  /* Was capped at 1.25rem (20px), which put the fourth-level heading BELOW
     the lead paragraph it outranks (21px) — two roles at near-identical
     sizes, with the junior one larger. A heading and the paragraph under it
     have to be either the same size or clearly different; 20 against 21 is
     the one thing they must not be. */
  --fs-h4:    clamp(1.1875rem, 1.05rem + 0.5vw, 1.625rem);
  --fs-h3:    clamp(1.375rem, 1.20rem + 0.88vw, 1.9375rem);
  --fs-h2:    clamp(2rem, 1.42rem + 2.55vw, 3.75rem);
  --fs-h1:    clamp(2.75rem, 1.62rem + 5.05vw, 6.25rem);

  /* Space — 4px base, named by intent not size */
  --s-1: 0.25rem;  --s-2: 0.5rem;   --s-3: 0.75rem;  --s-4: 1rem;
  --s-5: 1.5rem;   --s-6: 2rem;     --s-7: 2.5rem;   --s-8: 3rem;
  --s-9: 4rem;     --s-10: 5.5rem;  --s-11: 7rem;    --s-12: 9rem;

  --gutter: clamp(1.25rem, 4.5vw, 4rem);
  --measure: 62ch;
  --shell: 78rem;

  /* Section rhythm — one variable, so padding can never drift page to page */
  --band: clamp(4.5rem, 9vw, 8.5rem);

  /* Motion */
  --ease: cubic-bezier(.22, .61, .36, 1);
  --ease-frame: cubic-bezier(.16, 1, .3, 1);
  --t-fast: 180ms;
  --t: 280ms;
  --t-slow: 640ms;

  color-scheme: light;
}

/* --------------------------------------------------------------------------
   2. Reset & base
   -------------------------------------------------------------------------- */
*, *::before, *::after { box-sizing: border-box; }

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  /* On <html>, not <body>: any overflow value other than visible on body
     turns it into its own scroll container, which breaks position:sticky
     for descendants — the .reel's pin included — because sticky elements
     then stick relative to that container instead of the real viewport. */
  overflow-x: hidden;
}

body {
  margin: 0;
  background: var(--night);
  color: var(--lit);
  font-family: var(--text);
  font-size: var(--fs-body);
  /* 1.55, down from 1.7. Instrument Sans has a large x-height, so 1.7 pushed
     past the comfortable 1.45–1.6 band and the paragraphs floated apart
     instead of reading as blocks. */
  line-height: 1.55;
  /* `font-synthesis: none`, not just the -weight longhand: the longhand
     stops faux BOLD but leaves faux ITALIC enabled, and this design leans on
     italics for every accent word. A synthesised italic is the roman
     mechanically slanted — no true a, e or g — which next to a real italic
     looks like a rendering fault. Better the style silently doesn't apply
     than applies as a forgery. */
  font-synthesis: none;
  /* Kerning pairs and ligatures on, explicitly rather than by browser
     default, and contextual alternates with them. */
  font-kerning: normal;
  font-variant-ligatures: common-ligatures contextual;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  touch-action: manipulation;
  -webkit-tap-highlight-color: rgba(142, 47, 43, .16);
}

img, svg, video { display: block; max-width: 100%; }
/* The placeholder behind a not-yet-decoded photograph is now the room it
   hangs in, not a slab of pale stone flashing on a dark page. */
img { height: auto; background: var(--night-3); }

/* The build wraps property photographs in <picture>. `display: contents`
   removes that wrapper from layout so every `.frame__plate img` style below
   keeps applying to a direct child. */
picture { display: contents; }

/* A single filmic grade under every photograph — the difference between "a
   website with pictures on it" and something that reads as shot, not
   rendered. Warmer and a touch deeper than the cream-ground version: on a
   dark page a photograph has to look lit from inside the frame, so the
   shadows are allowed to go down and the whole thing leans to tungsten. */
img { filter: saturate(.94) contrast(1.07) brightness(.98) sepia(.06); }

/* Stand-in photography, tagged by the build. A heavier version of the same
   grade so borrowed images sit inside the house palette instead of fighting
   it. Swap the src for a real photograph and the attribute — and this extra
   weight — disappear. (Filter values don't stack across rules, so this is
   the base grade above, re-composed with more sepia and less saturation.) */
img[data-stock] { filter: sepia(.26) saturate(.78) contrast(1.09) brightness(.94); }

/* Vignette — a soft inward fall-off on every photograph in a frame, plate,
   panel or tile, so the edges recede rather than ending on a hard rectangle.
   An inset shadow on the image itself, not a layered pseudo-element: it can't
   collide with a badge or caption sitting on top of the same figure. */
.frame__plate img,
.plate__figure img,
.diptych__figure img,
.row__figure img,
.gallery-item img,
.pause__img {
  box-shadow: inset 0 0 5.5rem rgba(10, 7, 4, .46), inset 0 0 1.5rem rgba(10, 7, 4, .26);
}

/* Grain — a fixed, page-wide film texture. Static (no animation), so it
   carries no motion-sickness risk and needs no prefers-reduced-motion gate;
   it's felt as material, not seen as movement. `soft-light` rather than
   `overlay`: against a dark ground overlay only ever lifts, which reads as
   dust on the screen instead of grain in the emulsion. */
body::after {
  content: "";
  position: fixed;
  inset: 0;
  z-index: 250;
  opacity: .055;
  mix-blend-mode: soft-light;
  pointer-events: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
  background-size: 140px 140px;
}

/* --------------------------------------------------------------------------
   1b. SIGNATURE — the lamp
   You are walking through the house after dark carrying a light. A single
   warm pool tracks the pointer and lifts whatever it passes over: plaster
   warms, brass catches, a photograph comes up out of its own shadow.

   Mechanically it is one fixed div with two stacked radial gradients — a
   tight candle core and a wide falloff — composited with `screen` so it can
   only ever ADD light. That matters for contrast: a darkening or multiplying
   overlay could push text below its tested ratio depending on where the
   reader's mouse happened to be, whereas this leaves every measured
   foreground/background pair either unchanged or lighter against a dark
   ground. Body copy is never made harder to read by where the lamp is.

   Gated three ways in haile.js before the element is ever un-hidden — fine
   pointer, hover-capable, motion allowed — so touch, keyboard and
   reduced-motion users get a plain unlit page with no trace of it, and the
   .band--paper chapters damp it right down (a lit page in a lit room has
   nothing left to reveal).
   -------------------------------------------------------------------------- */
.lamp {
  position: fixed;
  top: 0;
  left: 0;
  z-index: 1;
  width: 74rem;
  height: 74rem;
  margin: -37rem 0 0 -37rem;
  border-radius: 50%;
  pointer-events: none;
  mix-blend-mode: screen;
  opacity: 0;
  will-change: transform, opacity;
  transition: opacity 900ms var(--ease);
  background:
    radial-gradient(circle closest-side,
      rgba(240, 192, 120, .16) 0%,
      rgba(226, 160, 84, .10) 26%,
      rgba(192, 120, 60, .05) 48%,
      transparent 72%),
    radial-gradient(circle closest-side,
      rgba(255, 226, 175, .19) 0%,
      transparent 26%);
}
.lamp[hidden] { display: none; }
.lamp.is-lit { opacity: 1; }
/* Turned right down over the reading chapters — and off entirely for anyone
   who has asked for less motion or is not driving a mouse. */
body:has(.band--paper:hover) .lamp { opacity: .35; }
@media (prefers-reduced-motion: reduce) { .lamp { display: none !important; } }
@media (hover: none), (pointer: coarse) { .lamp { display: none !important; } }

h1, h2, h3, h4 { margin: 0; font-weight: 400; text-wrap: balance; }
p { margin: 0; text-wrap: pretty; }
ul, ol { margin: 0; padding: 0; list-style: none; }
/* <figure> carries a UA margin of 1em 40px. Every gallery tile is a figure,
   so each one was sitting 80px narrower than its own grid column — invisible
   while the grid was uniform (all tiles inset equally, so it just read as a
   wide gutter), and the reason the mosaic below would not pack: the tiles
   could never fill the cells the grid was sizing for them. */
figure, blockquote { margin: 0; }

a { color: inherit; }

:target { scroll-margin-top: 7rem; }

/* Focus — one ring, everywhere, never removed. Candle rather than ember:
   the ring has to be findable against BOTH grounds this site uses, and ember
   at 3:1 on night is a large-text colour, not a 2px line. Candle clears 9:1
   on night and 3.1:1 on paper, so one token covers both without a swap. */
:focus-visible {
  outline: 2px solid var(--candle);
  outline-offset: 3px;
  border-radius: 1px;
}
.band--paper :focus-visible { outline-color: var(--ember-ink); }
:focus:not(:focus-visible) { outline: none; }

::selection { background: var(--ember); color: var(--paper); }

.skip-link {
  position: absolute;
  left: var(--gutter);
  top: -100%;
  z-index: 200;
  background: var(--ember-deep);
  color: var(--paper);
  padding: var(--s-3) var(--s-5);
  font-size: var(--fs-small);
  text-decoration: none;
  transition: top var(--t-fast) var(--ease);
}
.skip-link:focus { top: var(--s-4); }

.visually-hidden {
  position: absolute; width: 1px; height: 1px;
  margin: -1px; padding: 0; border: 0;
  clip-path: inset(50%); overflow: hidden; white-space: nowrap;
}

/* Honeypot: off-screen (not display:none) so it still occupies layout for
   bots that check computed visibility, but is unreachable and unseen by
   real visitors and screen readers. Field name must stay hp_field to match
   the server-side spam check. */
.hp-field {
  position: absolute;
  left: -9999px;
  top: -9999px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

/* --------------------------------------------------------------------------
   3. Layout
   -------------------------------------------------------------------------- */
.shell {
  width: 100%;
  max-width: var(--shell);
  margin-inline: auto;
  padding-inline: var(--gutter);
}
.shell--wide { max-width: 92rem; perspective: 1800px; }
.shell--narrow { max-width: 52rem; }

/* Bands. Night is the default ground, so a band only ever declares a surface
   when it is stepping AWAY from night — which keeps the page honest about
   how rarely that should happen.

   .band--paper is the lit chapter: it exists where there is something to
   read at length. Everything else stays in the dark and lets the
   photography carry the light. */
.band { padding-block: var(--band); position: relative; }
.band--tight { padding-block: calc(var(--band) * .6); }
.band--night { background: var(--night); }
.band--well  { background: var(--night-2); }
/* The one full-bleed field of colour on the site. Oxblood rather than the
   burnt orange it used to be, and lit from within by a wide off-centre bloom
   instead of being a single flat value — a deep lacquered wall with a lamp
   somewhere in the room, not a printed panel. The hairlines top and bottom
   are the same brass sconce-line the masthead and the frames use, so this
   band joins the house rather than interrupting it. */
.band--ember {
  background: var(--oxblood);
  color: var(--paper);
  border-block: 1px solid var(--brass-hair);
  overflow: hidden;
}
.band--ember::before {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  background:
    radial-gradient(70% 130% at 22% 0%, rgba(240, 192, 120, .17), transparent 62%),
    radial-gradient(60% 120% at 88% 110%, rgba(110, 35, 31, .55), transparent 66%);
}
.band--ember > * { position: relative; }

/* The lit page. A warm inner edge-light along the top and a deep shadow
   below it, so a paper chapter reads as a sheet resting ON the dark room
   rather than a rectangle knocked out of it. */
.band--paper {
  background: var(--paper);
  color: var(--ink);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .6), 0 -30px 60px -40px rgba(0, 0, 0, .8);
}
.band--paper + .band--paper { box-shadow: none; }

/* Legacy band names, kept resolving so nothing renders unstyled if a page or
   an admin-authored block still uses them. */
.band--plaster { background: var(--paper); color: var(--ink); }
.band--travertine { background: var(--night-2); }
.band--ink { background: var(--night); color: var(--lit); }

.stack > * + * { margin-top: var(--flow, var(--s-5)); }

.split {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: clamp(var(--s-7), 5vw, var(--s-10));
  align-items: center;
  /* Establishes the 3D viewing space for any .frame inside — perspective
     has to live on an ancestor to produce real foreshortening; put on the
     tilted element itself it silently renders flat (checked directly with
     a side-by-side isolated render before wiring this up). Harmless on
     every other child: an element with no 3D transform on it looks
     identical regardless of an ancestor's perspective. */
  perspective: 1800px;
}
.split--lead { grid-template-columns: minmax(0, 1.15fr) minmax(0, 1fr); }
.split--trail { grid-template-columns: minmax(0, 1fr) minmax(0, 1.15fr); }

/* A long reading passage beside a single photograph used to leave the image
   stranded at the top of its column while the text ran on for another
   screen — the picture column simply ended, and the page went lopsided for
   as long as it took to finish reading. Pinning the shorter column keeps the
   photograph next to whatever paragraph the reader is actually on, which is
   what a facing plate in a book does. Only ever applied where the copy is
   genuinely longer than the image; on a short pair it would just stick
   pointlessly.

   `align-items: start` is required on the parent: a sticky child cannot
   scroll within a track it has been stretched to fill, which is the usual
   reason this silently does nothing. */
.split--pinned { align-items: start; }
.split--pinned > :first-child {
  position: sticky;
  top: 7.5rem;
}
@media (max-width: 62rem) {
  .split, .split--lead, .split--trail { grid-template-columns: minmax(0, 1fr); }
  .split--pinned > :first-child { position: static; }
}

/* --------------------------------------------------------------------------
   4. Typography components
   -------------------------------------------------------------------------- */

/* Eyebrow — a hairline, then small caps. Never used without meaning.
   Set in brass. On the old cream ground brass was too low-contrast to carry
   any text and was restricted to rules; against night it clears 7.8:1, so
   the fitting's own colour can finally do the labelling. */
.eyebrow {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  font-size: var(--fs-micro);
  font-weight: 500;
  letter-spacing: .26em;
  text-transform: uppercase;
  color: var(--brass);
  margin: 0;
}
.eyebrow::before {
  content: "";
  width: 1.75rem;
  height: 1px;
  background: currentColor;
  flex: none;
  opacity: .75;
}
.eyebrow--center { justify-content: center; }
.eyebrow--bare::before { display: none; }
/* Brass at #C9A227 only clears 2.4:1 on paper — below the floor for text
   this small — so the lit chapters step down to the deep brass instead. */
.band--paper .eyebrow { color: var(--brass-deep); }
.band--ember .eyebrow { color: inherit; opacity: .82; }

/* Display — Bodoni Moda. Weight 450–500, never 300: at 6rem a light Didone
   loses its hairlines entirely. Optical size axis does the thinning instead. */
/* Display — Bodoni Moda at 600.

   Two things had to be right, and for a long time neither was.

   1. OPTICAL SIZE. A face with an `opsz` axis is not one drawing scaled up
      and down — it is redrawn along the axis, so at 76px the browser gets a
      cut with tightened fit and sharper detail, while the same family at
      body size stays open and even. Bodoni Moda carries opsz 6–96, so
      `font-optical-sizing: auto` below is doing real work. An earlier
      version of this file shipped EB Garamond, which has no such axis: every
      headline was a text cut inflated to 76px, and the declaration was
      silently a no-op. That is what made those headlines read soft and wide.

   2. WEIGHT ON A DARK GROUND. A Didone is built out of the difference
      between a hairline and a stem, and at weight 450 those hairlines get
      eaten by a near-black page until the word starts to break up. That was
      read as "Bodoni does not work here" and the face was dropped — wrongly.
      The face was never the problem; 450 was. At 600 the thins hold, the
      contrast stays, and it becomes the most striking of everything tried.

   Requesting the italic across the same 400–700 range as the roman matters
   more here than it looks: the accent words this design sets in gilt are all
   italic, and the preset used to cap italic at 500 while the roman ran to
   600, so every highlighted word was quietly lighter than the sentence
   around it.

   Weight travels with the face — see --display-weight. */
.display {
  font-family: var(--display);
  font-optical-sizing: auto;
  font-weight: var(--display-weight, 450);
  line-height: .96;
  letter-spacing: -.018em;
  color: var(--lit);
}
.display--xl { font-size: var(--fs-h1); }
.display--l  { font-size: var(--fs-h2); line-height: 1.02; letter-spacing: -.014em; }
.display--m  { font-size: var(--fs-h3); line-height: 1.12; letter-spacing: -.008em; }
.display--s  { font-size: var(--fs-h4); line-height: 1.2; letter-spacing: -.004em; }
.display em {
  font-style: italic;
  font-weight: var(--display-weight, 450);
  color: var(--ember-lit);
}
.band--paper .display { color: var(--ink); }
.band--paper .display em { color: var(--ember-ink); }
.band--ember .display { color: inherit; }
.band--ember .display em { color: var(--gilt); font-style: italic; }

/* The foil-stamp gradient treatment lives on .reel__line em only (the
   arrival headline, below) — not here. Applying it to every .display em
   site-wide made it a uniform template effect rather than a considered
   flourish; the entire point of a signature gesture is that it doesn't
   repeat on every heading. One good shine beats a dozen identical ones. */

.lead {
  font-size: var(--fs-lead);
  line-height: 1.6;
  color: var(--lit-muted);
  max-width: 46ch;
}
.band--paper .lead { color: var(--ink-muted); }
.band--ember .lead { color: inherit; opacity: .88; }

.prose { max-width: var(--measure); color: var(--lit-muted); }
.prose > * + * { margin-top: var(--s-4); }
.band--paper .prose { color: var(--ink-muted); }
.band--ember .prose { color: inherit; opacity: .88; }

/* Pull quote — the moulding rule reappears as the quote rule, and on night
   it is lit from the left: the rule carries the candle glow rather than a
   flat bar of colour. */
.stack > .pull { margin-top: var(--s-6); }
.pull {
  font-family: var(--display);
  font-optical-sizing: auto;
  font-size: clamp(1.375rem, 1rem + 1.7vw, 2.375rem);
  font-style: italic;
  font-weight: var(--display-weight, 450);
  line-height: 1.35;
  letter-spacing: -.01em;
  color: var(--candle);
  margin: 0;
  padding-left: clamp(1rem, 2.5vw, 2rem);
  /* Brass, not lacquer: a deep red hairline against a near-black ground is
     effectively invisible, and this rule's whole job is to be seen. */
  border-left: 2px solid var(--brass);
  box-shadow: -2px 0 18px -2px var(--candle-glow);
  max-width: 24ch;
}
.band--paper .pull { color: var(--walnut); border-left-color: var(--lacquer); box-shadow: none; }

/* Data — coordinates, distances, prices. Tabular so columns align. */
.data {
  font-size: var(--fs-small);
  font-variant-numeric: tabular-nums;
  letter-spacing: .04em;
  color: var(--lit-faint);
}
.band--paper .data { color: var(--ink-muted); }
.price {
  font-family: var(--display);
  font-optical-sizing: auto;
  font-weight: 500;
  font-size: 1.35em;
  font-variant-numeric: tabular-nums;
  color: var(--ember-lit);
  letter-spacing: -.01em;
}
.band--paper .price { color: var(--ember-ink); }

/* --------------------------------------------------------------------------
   5. SIGNATURE — the boiserie frame
   A double hairline with mitred corner ticks, drawn side by side on entry,
   exactly the way a moulding is run around a panel. This is the one place
   the site spends its boldness; everything else stays quiet.
   -------------------------------------------------------------------------- */
.frame {
  position: relative;
  padding: clamp(.75rem, 1.6vw, 1.35rem);
  /* A soft, wide, low ambient shadow — always on, not a hover state — so
     every framed photograph sits a few millimetres proud of the page instead
     of lying flat against it, the way a matted print does in a real frame.
     Deeper and blacker than the cream-ground version: on night a light
     shadow is invisible, and the frame needs the separation more, not less. */
  filter: drop-shadow(0 40px 52px rgba(0, 0, 0, .55));
  /* Tilt, on the individual `rotate` property rather than `transform` —
     .frame__rule and .frame__plate img already animate their own
     `transform` for the draw-in reveal, and this has to compose alongside
     that, never replace it. See the matching comment on .plate for the
     specificity bug that taught this lesson. */
  scale: 1;
  rotate: var(--rot-x, 0) var(--rot-y, 0) 0 var(--rot-deg, 0deg);
  transition: scale 420ms var(--ease-frame), rotate 240ms var(--ease-frame);
}
.frame:hover { scale: 1.006; }
.frame__plate { position: relative; }
.frame__plate::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;
  background: radial-gradient(circle at var(--sheen-x, 50%) var(--sheen-y, 50%),
              rgba(255, 255, 255, .3), transparent 45%);
  opacity: 0;
  transition: opacity 400ms var(--ease);
  pointer-events: none;
  mix-blend-mode: soft-light;
}
.frame:hover .frame__plate::after { opacity: 1; }
/* The moulding rules are real brass now, and lit: a hairline plus a faint
   bloom either side of it, so the frame reads as metal catching a lamp
   rather than a 1px border. This is the same gesture as the pull-quote rule
   and the sconce under the masthead — one lighting idea, three placements. */
.frame__rule {
  position: absolute;
  background: linear-gradient(var(--brass), var(--brass));
  box-shadow: 0 0 10px rgba(201, 162, 39, .30), 0 0 3px rgba(240, 192, 120, .38);
  opacity: .82;
  pointer-events: none;
}
.frame__rule--t, .frame__rule--b { left: 0; right: 0; height: 1px; transform: scaleX(0); }
.frame__rule--l, .frame__rule--r { top: 0; bottom: 0; width: 1px; transform: scaleY(0); }
.frame__rule--t { top: 0; transform-origin: left center; }
.frame__rule--r { right: 0; transform-origin: center top; }
.frame__rule--b { bottom: 0; transform-origin: right center; }
.frame__rule--l { left: 0; transform-origin: center bottom; }

.frame.is-drawn .frame__rule { transform: scale(1); }
.frame__rule { transition: transform var(--t-slow) var(--ease-frame); }
.frame.is-drawn .frame__rule--t { transition-delay: 0ms; }
.frame.is-drawn .frame__rule--r { transition-delay: 140ms; }
.frame.is-drawn .frame__rule--b { transition-delay: 280ms; }
.frame.is-drawn .frame__rule--l { transition-delay: 420ms; }

/* Mitre ticks — the short second line that reads as a moulding return */
.frame::before, .frame::after {
  content: "";
  position: absolute;
  width: 1.5rem; height: 1.5rem;
  border: 1px solid var(--brass-line);
  opacity: 0;
  transition: opacity var(--t-slow) var(--ease) 520ms;
  pointer-events: none;
}
.frame::before { top: .375rem; left: .375rem; border-right: 0; border-bottom: 0; }
.frame::after { bottom: .375rem; right: .375rem; border-left: 0; border-top: 0; }
.frame.is-drawn::before, .frame.is-drawn::after { opacity: 1; }

/* The photograph inside a frame un-mattes rather than fading */
.frame__plate { overflow: hidden; }
.frame__plate img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transform: scale(1.06);
  clip-path: inset(4% 4% 4% 4%);
  transition: transform 1.1s var(--ease-frame) 220ms,
              clip-path 1.1s var(--ease-frame) 220ms;
}
.frame.is-drawn .frame__plate img {
  transform: scale(1);
  clip-path: inset(0% 0% 0% 0%);
}

/* --------------------------------------------------------------------------
   6. Controls
   -------------------------------------------------------------------------- */
.btn {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--s-3);
  padding: 1.0625rem 2.25rem;
  font-family: var(--text);
  font-size: var(--fs-micro);
  font-weight: 600;
  letter-spacing: .2em;
  text-transform: uppercase;
  text-decoration: none;
  border: 1px solid transparent;
  cursor: pointer;
  transition: background-color var(--t) var(--ease),
              color var(--t) var(--ease),
              border-color var(--t) var(--ease),
              box-shadow var(--t) var(--ease),
              transform var(--t-fast) var(--ease);
}
.btn:active { transform: translateY(1px); }

/* Shine — a fixed diagonal highlight (not an animated sweep; those read as
   a loading-skeleton cliché) plus a top inner edge-light, so a filled
   button reads as a struck, faceted surface rather than a flat rectangle
   of paint. Layered above the wipe (::before) but below the label text. */
.btn--ember, .btn--ghost {
  background-image: linear-gradient(115deg, rgba(255, 255, 255, .16) 0%, rgba(255, 255, 255, 0) 32%);
}

/* The wipe — a solid layer that fills the button from the left on hover,
   behind the label (z-index:-1 inside the button's own isolated stacking
   context). Replaces a flat background-color crossfade with something that
   reads as a considered gesture rather than a CSS default. Transform-only,
   so it stays smooth under load and is unaffected by the magnetic pull,
   which moves the button itself via a separate inline transform. */
.btn::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  transform: scaleX(0);
  transform-origin: 0 50%;
  transition: transform 520ms var(--ease-frame);
}
.btn:hover::before { transform: scaleX(1); }

/* The filled button is the sofa: the one hot object in a dark room, and it
   throws a little of its own colour onto the page around it. */
.btn--ember {
  background: var(--ember-deep);
  color: var(--paper);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .22),
              0 1px 2px rgba(0, 0, 0, .4),
              0 12px 34px -14px rgba(142, 47, 43, .65);
}
.btn--ember::before { background: var(--ember); }
.btn--ember:hover {
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .3),
              0 14px 40px -12px rgba(142, 47, 43, .85);
}

/* The outline button sits over photography as often as over a flat surface,
   which is where the previous version fell down — a hairline and light text
   on an unpredictable photo is a contrast gamble taken fresh at every scroll
   position. A dark translucent backdrop with a blur behind it makes the
   label's ratio a property of the button instead of a property of whatever
   the picture is doing underneath. */
.btn--ghost {
  color: var(--lit);
  border-color: var(--brass-line);
  background-color: rgba(20, 16, 12, .42);
  backdrop-filter: blur(6px) saturate(1.1);
  -webkit-backdrop-filter: blur(6px) saturate(1.1);
}
.btn--ghost::before { background: var(--brass); }
.btn--ghost:hover { border-color: var(--brass); color: var(--night); }

.band--paper .btn--ghost {
  color: var(--ink);
  background-color: transparent;
  border-color: var(--brass-deep);
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
}
.band--paper .btn--ghost::before { background: var(--ink); }
.band--paper .btn--ghost:hover { color: var(--paper); border-color: var(--ink); }

/* No backdrop blur over a flat ember field — there is nothing behind it to
   blur, and it would spawn a compositing layer for no visual return. */
.band--ember .btn--ghost {
  color: inherit;
  border-color: currentColor;
  background-color: transparent;
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
}
.band--ember .btn--ghost::before { background: var(--paper); }
.band--ember .btn--ghost:hover { color: var(--oxblood); border-color: var(--paper); }
.band--ember .btn--ember { background: var(--night); box-shadow: none; }
.band--ember .btn--ember::before { background: var(--lacquer); }

.btn--block { width: 100%; }

/* Text link — the underline is drawn, not decorated */
.link {
  display: inline-flex;
  align-items: baseline;
  gap: var(--s-2);
  font-size: var(--fs-micro);
  font-weight: 600;
  letter-spacing: .18em;
  text-transform: uppercase;
  color: var(--brass);
  text-decoration: none;
  padding-bottom: .375rem;
  background-image: linear-gradient(currentColor, currentColor);
  background-repeat: no-repeat;
  background-position: 0 100%;
  background-size: 100% 1px;
  transition: background-size var(--t) var(--ease), color var(--t) var(--ease);
}
.link:hover { background-size: 0% 1px; color: var(--candle); }
.link__arrow { transition: transform var(--t) var(--ease); }
.link:hover .link__arrow { transform: translateX(.25rem); }
.band--paper .link { color: var(--ember-ink); }
.band--paper .link:hover { color: var(--ink); }
.band--ember .link { color: inherit; }

/* --------------------------------------------------------------------------
   7. Masthead
   -------------------------------------------------------------------------- */
.masthead {
  position: fixed;
  inset: 0 0 auto;
  z-index: 100;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-5);
  padding: var(--s-5) var(--gutter);
  background: transparent;
  border-bottom: 1px solid transparent;
  transition: padding var(--t) var(--ease),
              background-color var(--t) var(--ease),
              border-color var(--t) var(--ease),
              transform var(--t) var(--ease-frame);
  padding-top: max(var(--s-5), env(safe-area-inset-top));
}
/* Once it lifts off the hero the masthead becomes smoked glass rather than
   frosted white, and grows a brass sconce-line along its lower edge — the
   same lit hairline the frames and the pull quote use. */
.masthead.is-stuck {
  padding-block: var(--s-3);
  background: rgba(20, 16, 12, .78);
  backdrop-filter: blur(16px) saturate(1.3);
  -webkit-backdrop-filter: blur(16px) saturate(1.3);
  border-bottom-color: var(--brass-line);
  box-shadow: 0 1px 16px -2px rgba(201, 162, 39, .18), 0 10px 30px -20px rgba(0, 0, 0, .9);
}
/* Pages that open on a flat surface rather than a photograph need the solid
   state immediately, or the header floats on nothing. */
.masthead--solid {
  background: rgba(20, 16, 12, .94);
  border-bottom-color: var(--brass-line);
  box-shadow: 0 1px 16px -2px rgba(201, 162, 39, .14);
}
/* Steps out of the way on the way down the page, and returns the instant the
   reader reverses — more room for the photography while scrolling, without
   ever losing the way back to navigation. Held off entirely near the top and
   whenever the mobile drawer is open (see haile.js). */
.masthead.is-hidden { transform: translateY(-100%); }

/* A single hairline that fills with the page as it's read — the same ember
   the moulding rules use, doing the same job a bookmark ribbon does. */
.progress {
  position: fixed;
  inset: 0 0 auto 0;
  z-index: 101;
  height: 2px;
  pointer-events: none;
}
.progress__bar {
  height: 100%;
  width: 100%;
  transform: scaleX(0);
  transform-origin: 0 50%;
  background: linear-gradient(90deg, var(--ember-deep), var(--ember));
  transition: transform 100ms linear;
}

/* --------------------------------------------------------------------------
   7b. Cursor caption
   The previous build replaced the system arrow with a dot-and-ring follower
   that grew a "View" bubble over cards and photographs. It is gone: hiding
   the native cursor is a portfolio-site trope rather than anything this
   house would do, it fought the lamp for the reader's attention at exactly
   the same screen position, and two custom pointer treatments is one more
   than a design gets to have. The lamp is the gesture; the arrow stays the
   arrow.

   What survived is the part that carried information rather than mood — the
   small caption naming what a photograph will open into. It now rides just
   below the pointer, in the same brass small-caps as every other label on
   the site, and only over things that genuinely lead somewhere.
   -------------------------------------------------------------------------- */
.caption-cursor {
  position: fixed;
  top: 0;
  left: 0;
  z-index: 300;
  padding: .5rem .875rem;
  font-family: var(--text);
  font-size: .625rem;
  font-weight: 600;
  letter-spacing: .18em;
  text-transform: uppercase;
  white-space: nowrap;
  color: var(--night);
  background: var(--candle);
  pointer-events: none;
  opacity: 0;
  transform: translate(-50%, 1.25rem) scale(.94);
  transition: opacity 240ms var(--ease), transform 240ms var(--ease-frame);
  will-change: transform, opacity;
}
.caption-cursor[hidden] { display: none; }
.caption-cursor.is-shown { opacity: 1; transform: translate(-50%, 1.25rem) scale(1); }
@media (prefers-reduced-motion: reduce) { .caption-cursor { display: none !important; } }
@media (hover: none), (pointer: coarse) { .caption-cursor { display: none !important; } }

/* --------------------------------------------------------------------------
   7c. Folio — a running chapter marker, bottom-left, the way a printed book
   numbers its pages. Reinforces the one idea the whole redesign leans on:
   a stay here is read in chapters, not scrolled as a feed.
   -------------------------------------------------------------------------- */
.folio {
  position: fixed;
  left: var(--gutter);
  bottom: var(--s-5);
  z-index: 90;
  display: flex;
  align-items: baseline;
  gap: .625rem;
  font-family: var(--display);
  font-style: italic;
  font-size: .8rem;
  letter-spacing: .03em;
  color: var(--brass);
  opacity: 0;
  transition: opacity 500ms var(--ease), color 400ms var(--ease);
  pointer-events: none;
}
.folio.is-visible { opacity: .8; }
/* Inverted from the old build: the page is dark by default now, so the
   special case is a section that is LIGHT. */
.folio.is-on-light { color: var(--ink-faint); }
.folio__rule { width: 1rem; height: 1px; background: currentColor; opacity: .6; }
@media (max-width: 54rem) { .folio { display: none; } }

.wordmark {
  display: block;
  text-decoration: none;
  line-height: 1;
  flex: none;
}
/* Before the masthead lands on its smoked-glass panel it is floating over
   whatever the hero photograph happens to be doing behind it — on this house
   that is a sunlit tile roof directly under the wordmark. A soft dark halo
   costs nothing on an already-dark background and keeps the name readable
   over a bright one, without a scrim across the top of the picture. */
.masthead:not(.is-stuck):not(.masthead--solid) .wordmark__name,
.masthead:not(.is-stuck):not(.masthead--solid) .wordmark__place,
.masthead:not(.is-stuck):not(.masthead--solid) .masthead__link {
  text-shadow: 0 1px 14px rgba(10, 7, 4, .85), 0 1px 3px rgba(10, 7, 4, .6);
}
/* Every state of the masthead now sits on something dark — a photograph, or
   smoked glass over the night ground — so header text is light throughout
   and the old set of transparent-state overrides is gone entirely. */
.wordmark__name {
  display: block;
  font-family: var(--display);
  font-optical-sizing: auto;
  font-size: 1.5rem;
  font-weight: 500;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--paper);
  transition: color var(--t) var(--ease);
}
.wordmark__place {
  display: block;
  margin-top: .3rem;
  font-size: 0.5625rem;
  font-weight: 500;
  letter-spacing: .38em;
  text-transform: uppercase;
  color: var(--brass);
  transition: color var(--t) var(--ease);
}

.masthead__nav { display: flex; align-items: center; gap: clamp(1rem, 2.2vw, 2rem); }
.masthead__link {
  font-size: var(--fs-micro);
  font-weight: 500;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: rgba(247, 241, 230, .80);
  text-decoration: none;
  padding-block: .25rem;
  border-bottom: 1px solid transparent;
  transition: color var(--t) var(--ease), border-color var(--t) var(--ease);
}
.masthead__link:hover { color: var(--paper); border-bottom-color: var(--brass); }
.masthead__link[aria-current="page"] { color: var(--candle); border-bottom-color: var(--candle); }

.masthead__cta { padding: .8125rem 1.625rem; }

.burger {
  display: none;
  flex-direction: column;
  justify-content: center;
  gap: 5px;
  width: 2.75rem; height: 2.75rem;
  padding: 0;
  background: none;
  border: 1px solid var(--brass-line);
  cursor: pointer;
}
.burger span {
  display: block;
  width: 1.125rem; height: 1px;
  margin-inline: auto;
  background: var(--paper);
  transition: transform var(--t) var(--ease), opacity var(--t) var(--ease);
}
.burger[aria-expanded="true"] span:nth-child(1) { transform: translateY(6px) rotate(45deg); }
.burger[aria-expanded="true"] span:nth-child(2) { opacity: 0; }
.burger[aria-expanded="true"] span:nth-child(3) { transform: translateY(-6px) rotate(-45deg); }

.drawer {
  position: fixed;
  inset: 0;
  z-index: 99;
  display: flex;
  flex-direction: column;
  justify-content: center;
  /* `safe center`: on a short or landscape viewport, seven links plus a
     button can be taller than the screen. Plain `center` would let the
     overflow clip equally off both edges with no way to scroll to it —
     `safe` falls back to start-alignment once it doesn't fit, so nothing
     ever becomes unreachable. Unsupported browsers keep the plain `center`
     above; overflow-y still makes the difference moot either way. */
  justify-content: safe center;
  overflow-y: auto;
  gap: var(--s-4);
  padding: 6rem var(--gutter) max(var(--s-8), env(safe-area-inset-bottom));
  background: var(--night);
  overscroll-behavior: contain;
  opacity: 0;
  visibility: hidden;
  transition: opacity var(--t) var(--ease), visibility 0s linear var(--t);
}
.drawer.is-open { opacity: 1; visibility: visible; transition-delay: 0s; }
.drawer a {
  font-family: var(--display);
  font-optical-sizing: auto;
  font-size: clamp(1.75rem, 8vw, 2.75rem);
  font-weight: var(--display-weight, 450);
  letter-spacing: -.01em;
  color: var(--paper);
  text-decoration: none;
  padding-block: var(--s-2);
  border-bottom: 1px solid var(--brass-hair);
}
.drawer a[aria-current="page"] { color: var(--candle); }
.drawer .btn { margin-top: var(--s-5); }

@media (max-width: 68rem) {
  .masthead__nav, .masthead__cta { display: none; }
  .burger { display: flex; }
  .masthead { background: rgba(20, 16, 12, .88); backdrop-filter: blur(14px); border-bottom-color: var(--brass-line); }
}

/* --------------------------------------------------------------------------
   8. Reel — the opening sequence.
   The one place the site borrows from screen rather than print: the camera
   holds still while three short ideas replace each other in turn, the way a
   title sequence works rather than a page — full-bleed, nothing boxed in.

   Base rules below are the entire experience with no JavaScript: four ideas
   stacked over one tall photograph, all visible, nothing pinned. The `.js`-
   prefixed rules layer the pin and the scroll-scrubbed crossfade on top of
   that, exactly the way [data-reveal] is enhanced elsewhere — and the
   reduced-motion query un-does the `.js` layer again, back to the plain
   stack, since holding the page still while it scrolls is motion by another
   name. Nothing here is exclusive to the enhanced version. */
/* `overflow-x: clip`, not `hidden`: the hero photograph is held at scale
   1.18 and scrubbed back to 1, and that overscan was extending the document's
   scrollable width by ~64px on a phone. The root's own overflow-x:hidden was
   already stopping anyone actually scrolling sideways, but the overflow
   should be contained where it is created rather than caught at the edge of
   the document. `clip` is the one that does not create a scroll container,
   so the sticky .reel__pin inside still pins against the viewport. */
.reel { position: relative; background: var(--night); overflow-x: clip; }
.reel__pin {
  position: static;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
}
.reel__media { position: absolute; inset: 0; z-index: 0; }
.reel__img { width: 100%; height: 100%; object-fit: cover; }
.reel__scrim {
  position: absolute;
  inset: 0;
  /* Darkest exactly where the transparent masthead sits (top) and where the
     headline/actions sit (bottom); lightest in the middle third, so the
     photograph still gets room to breathe. The nav used to read weakly
     against a bright sky or a pale wall — .5 at the very top wasn't enough.
     The final stop now lands on the page's own ground colour rather than a
     generic brown-black, so the hero doesn't end on a visible seam where it
     meets the first section. */
  background: linear-gradient(180deg,
    rgba(20, 16, 12, .68) 0%, rgba(20, 16, 12, .44) 16%,
    rgba(20, 16, 12, .36) 45%,
    rgba(20, 16, 12, .88) 88%,
    var(--night) 100%);
}
.reel__frame {
  position: static;
  z-index: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--s-5);
  text-align: center;
  padding: clamp(3.5rem, 9vw, 6rem) var(--gutter);
}
.reel .eyebrow { color: var(--brass); }
.reel__line {
  font-family: var(--display);
  font-optical-sizing: auto;
  font-weight: var(--display-weight, 450);
  font-style: italic;
  color: var(--paper);
  text-shadow: 0 2px 40px rgba(0, 0, 0, .5);
  font-size: clamp(2.5rem, 1.5rem + 5vw, 6rem);
  line-height: 1.03;
  letter-spacing: -.015em;
  text-wrap: balance;
  max-width: 16ch;
}
.reel__line em { color: var(--ember); font-style: italic; }
/* Foil stamp — the one place a gradient is allowed, and now a real gilt one:
   old gold into bright gold into an almost-white catch-light and back, raked
   across the letterforms the way light crosses a stamped foil title on a
   book spine. The flat --gilt above is the fallback wherever
   background-clip: text is unavailable, so the word is never invisible. */
@supports (background-clip: text) or (-webkit-background-clip: text) {
  .reel__line em {
    background: linear-gradient(115deg, #A67C2E 0%, #D9AE60 34%, #FFF0CE 52%, #E8C27A 68%, #A67C2E 100%);
    background-clip: text;
    -webkit-background-clip: text;
    -webkit-text-fill-color: transparent;
    color: transparent;
  }
}
.reel__lead { color: rgba(247, 241, 230, .9); font-size: var(--fs-lead); max-width: 34ch; text-shadow: 0 1px 20px rgba(0, 0, 0, .6); }
.reel__data { color: var(--brass); font-size: var(--fs-small); font-variant-numeric: tabular-nums; letter-spacing: .04em; }
.reel__actions { display: flex; gap: var(--s-4); flex-wrap: wrap; justify-content: center; }
.reel__cue { display: none; }

.js .reel { height: 400vh; }
.js .reel__pin {
  position: sticky;
  top: 0;
  height: 100vh;
  overflow: hidden;
}
.js .reel__img { scale: 1.18; }
.js .reel__frame {
  position: absolute;
  inset: 0;
  padding: 0 var(--gutter);
  justify-content: center;
  opacity: 0;
  transform: translateY(22px) scale(.985);
  transition: opacity 640ms var(--ease-frame), transform 640ms var(--ease-frame);
  pointer-events: none;
}
.js .reel__frame.is-active { opacity: 1; transform: none; pointer-events: auto; }

.js .reel__cue {
  display: flex;
  position: absolute;
  z-index: 2;
  left: 50%;
  bottom: var(--s-7);
  transform: translateX(-50%);
  flex-direction: column;
  align-items: center;
  gap: var(--s-3);
  color: var(--brass);
  font-size: var(--fs-micro);
  letter-spacing: .32em;
  text-transform: uppercase;
  opacity: 1;
  transition: opacity 400ms var(--ease);
}
.reel__cue-line { width: 1px; height: 2.75rem; background: linear-gradient(to bottom, currentColor, transparent); }
.js .reel.has-scrolled .reel__cue { opacity: 0; }

@media (prefers-reduced-motion: reduce) {
  .js .reel { height: auto; }
  .js .reel__pin { position: static; height: auto; overflow: visible; }
  .js .reel__img { scale: 1; }
  .js .reel__frame { position: static; opacity: 1; transform: none; pointer-events: auto; }
  .js .reel__cue { display: none; }
}

/* Page header for interior pages. A faint warm bloom sits behind the
   headline — the room's own light source, off-frame — so an interior page
   opens on something lit rather than on a flat black rectangle. */
.pagehead {
  position: relative;
  background: var(--night);
  padding-top: clamp(7.5rem, 13vw, 11rem);
  padding-bottom: var(--band);
  overflow: hidden;
}
.pagehead::before {
  content: "";
  position: absolute;
  top: -30%;
  left: 50%;
  width: min(70rem, 130%);
  aspect-ratio: 1;
  transform: translateX(-50%);
  background: radial-gradient(circle closest-side, rgba(240, 192, 120, .10), transparent 70%);
  pointer-events: none;
}
.pagehead > * { position: relative; }
.pagehead .display { margin-block: var(--s-5) var(--s-5); }

/* Interior page headers used to set the eyebrow, headline and standfirst in
   one narrow left-hand stack, which left the entire right half of the first
   screen empty on every page but the homepage. Setting the standfirst beside
   the headline instead, baseline-aligned to it, fills the measure and reads
   the way a title page actually does.

   Scoped with :has() so it only ever applies to the plain eyebrow/headline/
   standfirst headers — the booking page already runs its own two-column
   split in there, and the room pages carry a backlink and no standfirst. A
   browser without :has() drops the rule entirely and gets the old stacked
   header, which is untidy but never broken. */
@media (min-width: 62rem) {
  .pagehead > .shell:not(.split):has(> .lead) {
    display: grid;
    grid-template-columns: minmax(0, 1.5fr) minmax(0, 1fr);
    column-gap: clamp(2.5rem, 5vw, 5.5rem);
    align-items: end;
  }
  .pagehead > .shell:not(.split):has(> .lead) > .eyebrow { grid-column: 1; }
  .pagehead > .shell:not(.split):has(> .lead) > .display {
    grid-column: 1;
    margin-bottom: 0;
    /* The full-width clamp tops out at 6.25rem, which was set for a headline
       running the whole measure; in a 1.5fr column it breaks a two-line
       headline into four. Capped at 4.25rem rather than 4.75rem because
       Bodoni sets appreciably wider than the faces tried before it, and at
       the larger cap "Three ways to stay." spilled to a third line and left
       the accent word stranded on its own. */
    font-size: clamp(2.5rem, 1.5rem + 3.2vw, 4.25rem);
  }
  .pagehead > .shell:not(.split):has(> .lead) > .lead {
    grid-column: 2;
    grid-row: 2;
    max-width: 38ch;
    padding-bottom: .5rem;
  }
}

/* --------------------------------------------------------------------------
   9. Datum rail — real distances by road from the gate.
   Structure carrying information, not decoration: this is the single most
   asked question by a guest planning a safari.
   -------------------------------------------------------------------------- */
.datum {
  background: var(--night-2);
  color: var(--lit);
  padding-block: var(--s-8);
  border-block: 1px solid var(--brass-hair);
}
.datum__list {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr));
  gap: var(--s-5) var(--s-6);
}
/* Each column is its own grid so the place name, the rule and the figure
   line up across the row no matter how many lines a name wraps to. On a
   phone "Ngorongoro Crater" wraps to two lines and "Serengeti" doesn't,
   which used to leave the big tabular figures sitting at four different
   heights across the rail — the one place on the page where numbers are
   supposed to be directly comparable. */
.datum__item {
  display: grid;
  grid-template-rows: auto 1fr;
  border-top: 1px solid var(--brass-line);
  padding-top: var(--s-3);
}
.datum__place {
  display: block;
  font-size: var(--fs-micro);
  font-weight: 500;
  letter-spacing: .18em;
  text-transform: uppercase;
  color: var(--brass);
}
.datum__dist {
  display: block;
  align-self: end;
  margin-top: var(--s-2);
  font-family: var(--display);
  font-optical-sizing: auto;
  font-size: clamp(1.75rem, 1.3rem + 1.6vw, 2.75rem);
  font-weight: var(--display-weight, 450);
  font-variant-numeric: tabular-nums;
  letter-spacing: -.02em;
  line-height: 1.1;
  color: var(--paper);
}
.datum__dist small {
  font-family: var(--text);
  font-size: 0.6875rem;
  letter-spacing: .16em;
  text-transform: uppercase;
  font-weight: 400;
  color: var(--lit-faint);
  margin-left: .3rem;
}

/* --------------------------------------------------------------------------
   10. Plates — room / experience / gallery cards
   -------------------------------------------------------------------------- */
.plate-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(17rem, 100%), 1fr));
  gap: clamp(var(--s-5), 2.5vw, var(--s-7));
  perspective: 1800px;
}
.plate-grid--two { grid-template-columns: repeat(auto-fit, minmax(min(24rem, 100%), 1fr)); }
/* Four room types, four columns — auto-fit lands on 3 + 1 orphan at this shell
   width, which reads as a mistake rather than a grid. */
@media (min-width: 62rem) { .plate-grid--four { grid-template-columns: repeat(4, minmax(0, 1fr)); } }
/* Same problem, three room types: auto-fit's 24rem minimum only ever fits two
   columns at this shell width, so the third card falls onto its own row —
   full-bleed and oversized next to the pair above it. Force three equal
   columns instead. */
@media (min-width: 62rem) { .plate-grid--three { grid-template-columns: repeat(3, minmax(0, 1fr)); } }

.plate {
  position: relative;
  display: flex;
  flex-direction: column;
  text-decoration: none;
  color: inherit;
  background: var(--night-2);
  border: 1px solid var(--night-line);
  /* Lift, tilt and scroll-reveal are three independent motions living on
     three independent CSS properties (translate / rotate / the transform
     shorthand) on purpose. They used to share `transform`, which meant
     [data-reveal].is-in's `transform: none` silently cancelled the hover
     lift on every card the moment it finished revealing — confirmed with
     a computed-style check, not assumed. Individual transform properties
     compose instead of overwriting one another, so this can't recur. */
  translate: 0 0;
  scale: 1;
  rotate: var(--rot-x, 0) var(--rot-y, 0) 0 var(--rot-deg, 0deg);
  /* A resting shadow, not just a hover one — an unhovered grid of cards
     read as flat paper cut-outs sitting directly on the page rather than
     objects resting a little above it. Hover deepens this; it doesn't
     introduce it from nothing. */
  box-shadow: 0 18px 34px -24px rgba(0, 0, 0, .9);
  transition: border-color var(--t) var(--ease), box-shadow 420ms var(--ease-frame),
              translate 420ms var(--ease-frame), scale 420ms var(--ease-frame),
              rotate 240ms var(--ease-frame);
}
/* Hovering a card doesn't just lift it — it lights it. The brass edge comes
   up and a low warm bloom appears underneath, so picking a room reads as
   turning a lamp on over it rather than as a CSS elevation change. */
.plate:hover {
  border-color: var(--brass-line);
  translate: 0 -7px;
  scale: 1.012;
  box-shadow: 0 34px 56px -28px rgba(0, 0, 0, 1),
              0 0 44px -12px rgba(240, 192, 120, .16);
}
.band--paper .plate {
  background: var(--paper);
  border-color: var(--brass-hair);
  box-shadow: 0 14px 26px -20px rgba(20, 14, 8, .22);
}
.band--paper .plate:hover {
  border-color: var(--brass-line);
  box-shadow: 0 30px 50px -26px rgba(20, 14, 8, .32);
}

/* The 3D tilt — a room card responds to the cursor the way a framed
   photograph tips toward the light when you pick it up. haile.js writes
   --rot-x/--rot-y/--rot-deg as the mouse moves within the card's own
   bounds; touch devices and prefers-reduced-motion never get the listener
   attached in the first place, so the custom properties simply stay at
   their zero fallback and this is a no-op rotation everywhere else. */
.plate__figure::after {
  content: "";
  position: absolute;
  inset: 0;
  background: radial-gradient(circle at var(--sheen-x, 50%) var(--sheen-y, 50%),
              rgba(255, 255, 255, .32), transparent 45%);
  opacity: 0;
  transition: opacity 400ms var(--ease);
  pointer-events: none;
  mix-blend-mode: soft-light;
}
.plate:hover .plate__figure::after { opacity: 1; }

.plate__figure { position: relative; overflow: hidden; aspect-ratio: 4 / 5; }
.plate__figure img {
  width: 100%; height: 100%;
  object-fit: cover;
  transition: transform 900ms var(--ease-frame);
}
.plate:hover .plate__figure img { transform: scale(1.045); }

/* Ember index tab — the room category number, which IS a real category */
.plate__tab {
  position: absolute;
  top: 0; left: 0;
  background: var(--ember-deep);
  color: var(--paper);
  font-size: var(--fs-micro);
  font-weight: 600;
  letter-spacing: .18em;
  text-transform: uppercase;
  padding: .5rem .875rem;
}

.plate__body {
  display: flex;
  flex-direction: column;
  gap: var(--s-2);
  padding: var(--s-5) clamp(var(--s-4), 2vw, var(--s-6)) var(--s-6);
  flex: 1;
  min-width: 0;
}
.plate__name { margin-bottom: var(--s-1); }
.plate__feat {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-2) var(--s-4);
  margin-top: var(--s-3);
  font-size: var(--fs-micro);
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--lit-faint);
}
.band--paper .plate__feat { color: var(--ink-faint); }
.plate__foot {
  margin-top: auto;
  padding-top: var(--s-5);
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--s-4);
  border-top: 1px solid var(--night-line);
}
.band--paper .plate__foot { border-top-color: var(--brass-hair); }

/* Diptych — two full-height panels side by side (dining) */
.diptych { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(22rem, 100%), 1fr)); gap: 1px; background: var(--brass-hair); }
.diptych__panel {
  position: relative;
  background: var(--night-2);
  display: flex;
  flex-direction: column;
}
.band--paper .diptych__panel { background: var(--paper); }
.diptych__figure { overflow: hidden; aspect-ratio: 16 / 11; }
.diptych__figure img { width: 100%; height: 100%; object-fit: cover; transition: transform 1s var(--ease-frame); }
.diptych__panel:hover .diptych__figure img { transform: scale(1.04); }
.diptych__body { padding: clamp(var(--s-6), 3.5vw, var(--s-9)); display: flex; flex-direction: column; gap: var(--s-4); flex: 1; }

/* Alternating full-bleed rows (experiences) */
.row { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); align-items: stretch; }
.row__figure { position: relative; overflow: hidden; min-height: clamp(20rem, 44vw, 34rem); }
.row__figure img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
.row__body {
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: var(--s-4);
  padding: clamp(var(--s-8), 6vw, var(--s-11)) clamp(var(--s-6), 5vw, var(--s-10));
  background: var(--night);
}
.row:nth-child(even) .row__figure { order: 2; }
.row:nth-child(even) .row__body { background: var(--night-2); }
@media (max-width: 62rem) {
  .row { grid-template-columns: minmax(0, 1fr); }
  .row:nth-child(even) .row__figure { order: 0; }
  .row__figure { min-height: 16rem; }
}

/* Pause — a full-bleed, text-only-over-photograph moment with nothing else
   on it: no eyebrow, no button, no card. Every other section on the site
   argues a point; this one just lets a single line sit on a single
   photograph for a moment before the argument resumes. Used sparingly —
   once or twice a page, at most — or it stops being a pause. */
.pause {
  position: relative;
  min-height: 74vh;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
}
.pause__img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
/* The pause bleeds into the night ground at both ends rather than starting
   and stopping on a hard edge — the photograph surfaces out of the dark and
   sinks back into it. */
.pause__scrim {
  position: absolute;
  inset: 0;
  background: linear-gradient(180deg,
    var(--night) 0%, rgba(20, 16, 12, .62) 22%,
    rgba(20, 16, 12, .55) 55%,
    rgba(20, 16, 12, .82) 88%, var(--night) 100%);
}
.pause__quote {
  position: relative;
  z-index: 1;
  max-width: 21ch;
  margin: 0 auto;
  padding-inline: var(--gutter);
  text-align: center;
  font-family: var(--display);
  font-optical-sizing: auto;
  font-style: italic;
  font-weight: var(--display-weight, 450);
  font-size: clamp(1.9rem, 1.1rem + 3.9vw, 4.25rem);
  line-height: 1.2;
  letter-spacing: -.012em;
  color: var(--paper);
  text-wrap: balance;
  text-shadow: 0 2px 50px rgba(0, 0, 0, .6);
}
.pause__quote em { color: var(--ember-lit); font-style: italic; }

/* --------------------------------------------------------------------------
   Gallery — a hang, not a contact sheet.
   The previous version was a uniform grid of identical 4:5 crops, which is
   what a photographer's proof sheet looks like: every frame the same size
   because none has been chosen yet. A gallery is the opposite — someone has
   decided which pictures are worth the wall, and said so by hanging them
   bigger. The rhythm below is fixed and repeating (a 6-tile phrase), so it
   reads as a considered hang rather than a random masonry scatter, and it
   survives filtering: :nth-of-type counts DOM order, so hiding a category
   can't reshuffle which tiles are large.
   -------------------------------------------------------------------------- */
/* Square base cells set the implicit row height; a feature plate then spans
   exactly two columns AND two of those rows, so it comes out square at
   double scale and every row still lines up. `dense` backfills the gap left
   whenever a feature can't start where auto-placement reached, which is the
   part a naive span-based mosaic gets wrong — it leaves holes in the wall. */
.gallery-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  grid-auto-flow: row dense;
  gap: var(--s-3);
}
@media (min-width: 48rem) {
  .gallery-grid { grid-template-columns: repeat(4, 1fr); gap: var(--s-4); }
}

.gallery-item {
  position: relative;
  overflow: hidden;
  aspect-ratio: 1;
  background: var(--night-3);
  border: 1px solid var(--night-line);
  transition: border-color var(--t) var(--ease), box-shadow 420ms var(--ease-frame);
}
/* One plate in every seven is hung at double size. :nth-of-type counts DOM
   order rather than rendered order, so filtering the wall down to a single
   category can never reshuffle which pictures are the big ones. */
/* The feature keeps a square ratio rather than being left on `auto`: with no
   ratio its height came from whatever the photograph's own proportions
   happened to be, and grid then spread that over the two rows it spans —
   pushing those rows taller than the square tiles beside it and opening a
   gap under every one of them. Asking for width == height makes each spanned
   row exactly one cell tall, which is what the neighbours are already. */
.gallery-item:nth-of-type(7n + 1) {
  grid-column: span 2;
  grid-row: span 2;
  aspect-ratio: 1;
}
.gallery-item:hover {
  border-color: var(--brass-line);
  box-shadow: 0 0 40px -10px rgba(240, 192, 120, .2);
}
.gallery-item img { width: 100%; height: 100%; object-fit: cover; transition: transform 800ms var(--ease-frame); }
.gallery-item:hover img { transform: scale(1.05); }
.gallery-item figcaption {
  position: absolute;
  inset: auto 0 0 0;
  padding: var(--s-7) var(--s-4) var(--s-3);
  background: linear-gradient(to top, rgba(10, 7, 4, .88), transparent);
  color: var(--paper);
  font-size: var(--fs-micro);
  letter-spacing: .16em;
  text-transform: uppercase;
}
.gallery-item.is-hidden { display: none; }

.filters { display: flex; flex-wrap: wrap; gap: var(--s-3); }
.filter {
  font-family: var(--text);
  font-size: var(--fs-micro);
  font-weight: 500;
  letter-spacing: .16em;
  text-transform: uppercase;
  padding: .625rem 1.125rem;
  background: transparent;
  color: var(--lit-muted);
  border: 1px solid var(--night-line);
  cursor: pointer;
  transition: background-color var(--t) var(--ease), color var(--t) var(--ease), border-color var(--t) var(--ease);
}
.filter:hover { border-color: var(--brass); color: var(--candle); }
.filter[aria-pressed="true"] { background: var(--brass); color: var(--night); border-color: var(--brass); }

/* Room detail — copy column plus a reserve panel that follows the reader */
.room-layout {
  display: grid;
  grid-template-columns: minmax(0, 1.75fr) minmax(0, 1fr);
  gap: clamp(var(--s-7), 5vw, var(--s-10));
  align-items: start;
}
.room-aside { position: sticky; top: 6.5rem; }
@media (max-width: 62rem) {
  .room-layout { grid-template-columns: minmax(0, 1fr); }
  .room-aside { position: static; }
}

.room-plate, .plate-wide { aspect-ratio: 21 / 9; }
@media (max-width: 48rem) {
  .room-plate, .plate-wide { aspect-ratio: 4 / 3; }
}

.backlink {
  display: inline-flex;
  align-items: center;
  gap: var(--s-2);
  font-size: var(--fs-micro);
  font-weight: 500;
  letter-spacing: .2em;
  text-transform: uppercase;
  color: var(--brass);
  text-decoration: none;
  margin-bottom: var(--s-5);
  transition: color var(--t) var(--ease);
}
.backlink:hover { color: var(--candle); }
.band--paper .backlink { color: var(--ink-muted); }
.band--paper .backlink:hover { color: var(--ember-ink); }
.backlink span { transition: transform var(--t) var(--ease); }
.backlink:hover span { transform: translateX(-.25rem); }

/* --------------------------------------------------------------------------
   11. Ledger — spa menu, room features, contact facts.
   A ruled list. The rule is the same hairline as the boiserie.
   -------------------------------------------------------------------------- */
.ledger { display: grid; gap: 0; }
.ledger__row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: var(--s-2) var(--s-5);
  align-items: baseline;
  padding-block: var(--s-4);
  border-top: 1px solid var(--night-line);
}
.ledger__row:last-child { border-bottom: 1px solid var(--night-line); }
.ledger__name { font-weight: 600; font-size: var(--fs-small); letter-spacing: .06em; color: var(--lit); }
.ledger__meta { font-size: var(--fs-micro); letter-spacing: .16em; text-transform: uppercase; color: var(--brass); font-variant-numeric: tabular-nums; }
.ledger__desc { grid-column: 1 / -1; font-size: var(--fs-small); color: var(--lit-muted); max-width: 56ch; }
.band--paper .ledger__row { border-top-color: var(--brass-hair); }
.band--paper .ledger__row:last-child { border-bottom-color: var(--brass-hair); }
.band--paper .ledger__name { color: var(--ink); }
.band--paper .ledger__meta { color: var(--ember-ink); }
.band--paper .ledger__desc { color: var(--ink-muted); }

.spec { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(11rem, 100%), 1fr)); gap: var(--s-5) var(--s-7); }
.spec__item { border-top: 1px solid var(--brass-line); padding-top: var(--s-3); }
.spec__label { display: block; font-size: var(--fs-micro); letter-spacing: .2em; text-transform: uppercase; color: var(--brass); }
.spec__value { display: block; margin-top: var(--s-1); font-size: var(--fs-small); font-weight: 600; color: var(--lit); }
.band--paper .spec__item { border-top-color: var(--brass-hair); }
.band--paper .spec__label { color: var(--ink-faint); }
.band--paper .spec__value { color: var(--ink); }

/* --------------------------------------------------------------------------
   12. Forms
   -------------------------------------------------------------------------- */
/* The booking panel is the one piece of furniture in the room a guest
   actually has to use, so it is the most-lit object on the page: a raised
   surface with a brass edge and a warm bloom under it. */
.panel {
  background: var(--night-2);
  border: 1px solid var(--brass-line);
  padding: clamp(var(--s-6), 3vw, var(--s-8));
  box-shadow: inset 0 1px 0 rgba(240, 192, 120, .14),
              0 44px 70px -40px rgba(0, 0, 0, 1),
              0 0 60px -24px rgba(240, 192, 120, .12);
}
.band--paper .panel {
  background: var(--paper-2);
  border-color: var(--brass-hair);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .6), 0 44px 64px -40px rgba(20, 14, 8, .22);
}

.field { display: flex; flex-direction: column; gap: var(--s-2); margin-bottom: var(--s-5); min-width: 0; }
.field__label {
  font-size: var(--fs-micro);
  font-weight: 500;
  letter-spacing: .2em;
  text-transform: uppercase;
  color: var(--brass);
}
.band--paper .field__label { color: var(--ink-muted); }

/* Inputs are wells cut into the panel, not raised tiles on top of it —
   darker than their surround, which is how a recessed field reads under a
   light and keeps the typed value the brightest thing in the control. */
.field input, .field select, .field textarea {
  width: 100%;
  font-family: var(--text);
  font-size: var(--fs-small);
  color: var(--lit);
  background: var(--night-3);
  border: 1px solid var(--night-line);
  padding: .875rem 1rem;
  transition: border-color var(--t) var(--ease), box-shadow var(--t) var(--ease);
}
.field input::placeholder, .field textarea::placeholder { color: var(--lit-faint); }
/* The native date/time picker indicator is drawn in the UA's own dark-on-
   light glyph, which disappears against a dark well — invert it back. */
.field input::-webkit-calendar-picker-indicator { filter: invert(1) sepia(.3) saturate(2) hue-rotate(2deg); opacity: .7; cursor: pointer; }
.field input::-webkit-calendar-picker-indicator:hover { opacity: 1; }

.band--paper .field input,
.band--paper .field select,
.band--paper .field textarea {
  color: var(--ink);
  background: var(--paper);
  border-color: var(--brass-line);
}
.band--paper .field input::placeholder,
.band--paper .field textarea::placeholder { color: var(--ink-faint); }
.band--paper .field input::-webkit-calendar-picker-indicator { filter: none; }

.field input:hover, .field select:hover, .field textarea:hover { border-color: var(--brass-line); }
.field input:focus-visible, .field select:focus-visible, .field textarea:focus-visible {
  border-color: var(--candle);
  outline-offset: 1px;
}
.field textarea { resize: vertical; min-height: 7rem; }
.field select {
  appearance: none;
  background-color: var(--night-3);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 8' fill='none' stroke='%23C9A227' stroke-width='1.2'%3E%3Cpath d='M1 1.5 6 6.5 11 1.5'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 1rem center;
  background-size: .7rem;
  padding-right: 2.5rem;
}
/* A <select>'s dropdown list is painted by the OS, which picks its colours
   from the control's own — without this the open menu renders as light text
   on a light popup on some platforms. */
.field select option { background: var(--night-2); color: var(--lit); }
.band--paper .field select {
  background-color: var(--paper);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 8' fill='none' stroke='%235A3A24' stroke-width='1.2'%3E%3Cpath d='M1 1.5 6 6.5 11 1.5'/%3E%3C/svg%3E");
}
.band--paper .field select option { background: var(--paper); color: var(--ink); }

.field__error { font-size: var(--fs-micro); letter-spacing: .06em; color: var(--alert); font-weight: 600; }
.band--paper .field__error { color: var(--gilt-deep); }
.field:has(.field__error:not(:empty)) input,
.field:has(.field__error:not(:empty)) select { border-color: var(--alert); }

.field-row { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(9rem, 100%), 1fr)); gap: var(--s-4); }

.form-note { font-size: var(--fs-micro); letter-spacing: .08em; color: var(--lit-faint); text-align: center; margin-top: var(--s-3); }
.form-status { font-size: var(--fs-small); color: var(--candle); font-weight: 600; margin-top: var(--s-4); min-height: 1.5rem; }
.form-status:empty { margin-top: 0; min-height: 0; }
.form-status--error { color: var(--alert); text-decoration: underline; text-decoration-color: var(--alert); text-underline-offset: .2em; }
.band--paper .form-note { color: var(--ink-faint); }
.band--paper .form-status { color: var(--gilt-deep); }
.band--paper .form-status--error { color: var(--gilt-deep); text-decoration-color: var(--lacquer); }

/* --------------------------------------------------------------------------
   13. Colophon
   -------------------------------------------------------------------------- */
/* The deepest room in the house — a step darker than the page, with a brass
   sconce-line where it meets what came before. */
.colophon {
  background: #100D09;
  color: var(--lit);
  padding-block: var(--s-10) var(--s-6);
  border-top: 1px solid var(--brass-hair);
}
.colophon__grid {
  display: grid;
  grid-template-columns: minmax(0, 1.6fr) repeat(auto-fit, minmax(9rem, 1fr));
  gap: var(--s-8) var(--s-6);
}
.colophon h2 { font-size: var(--fs-micro); font-weight: 600; letter-spacing: .24em; text-transform: uppercase; color: var(--brass); margin-bottom: var(--s-4); }
.colophon li + li { margin-top: var(--s-3); }
.colophon a { font-size: var(--fs-small); color: var(--lit-muted); text-decoration: none; transition: color var(--t) var(--ease); }
.colophon a:hover { color: var(--candle); }
.colophon address { font-style: normal; font-size: var(--fs-small); line-height: 2; color: var(--lit-muted); }
.colophon .wordmark__name { color: var(--paper); }
.colophon .wordmark__place { color: var(--brass); }

.social { display: flex; gap: var(--s-3); margin-top: var(--s-5); }
.social a {
  display: grid;
  place-items: center;
  width: 2.5rem; height: 2.5rem;
  border: 1px solid var(--night-line);
  transition: border-color var(--t) var(--ease), background-color var(--t) var(--ease), color var(--t) var(--ease);
}
.social a:hover { border-color: var(--brass); background: transparent; color: var(--candle); }
.social svg { width: 1.0625rem; height: 1.0625rem; fill: currentColor; }

.colophon__base {
  margin-top: var(--s-9);
  padding-top: var(--s-5);
  border-top: 1px solid var(--night-line);
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-3) var(--s-6);
  align-items: center;
  justify-content: space-between;
  font-size: var(--fs-micro);
  letter-spacing: .1em;
  color: var(--lit-faint);
}
.colophon__base nav { display: flex; flex-wrap: wrap; gap: var(--s-5); }
.colophon__base a { font-size: var(--fs-micro); letter-spacing: .1em; color: var(--lit-faint); }

/* --------------------------------------------------------------------------
   14. Scroll reveal
   -------------------------------------------------------------------------- */
/* Gated on the `js` class set by the inline snippet in <head>. Content is never
   hidden by a stylesheet alone — if the script fails, everything is visible.
   Unhurried on purpose — a quick fade reads as a loading spinner, not as
   something arriving deliberately. The slight scale gives each block a hint
   of weight settling into place, rather than a flat slide. */
.js [data-reveal] {
  opacity: 0;
  transform: translateY(26px) scale(.985);
  transition: opacity 900ms var(--ease-frame), transform 900ms var(--ease-frame);
}
.js [data-reveal].is-in { opacity: 1; transform: none; }
/* Hand-placed pairs (a photograph beside its copy) use these. Grids of three
   or more — room cards, gallery tiles — are staggered automatically in
   haile.js instead, one small step per item, so the count never needs to
   match a fixed set of delay classes. */
[data-reveal][data-reveal-delay="1"].is-in { transition-delay: 100ms; }
[data-reveal][data-reveal-delay="2"].is-in { transition-delay: 200ms; }
[data-reveal][data-reveal-delay="3"].is-in { transition-delay: 300ms; }

/* --------------------------------------------------------------------------
   15. Reduced motion — one switch, everything obeys
   -------------------------------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: .001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .001ms !important;
    transition-delay: 0ms !important;
    scroll-behavior: auto !important;
  }
  .js [data-reveal] { opacity: 1; transform: none; }
  .frame__rule { transform: scale(1) !important; }
  .frame::before, .frame::after { opacity: 1; }
  .frame__plate img { transform: none; clip-path: none; }
  /* Belt and braces: neither the lamp nor the caption is ever initialised by
     JS under a reduced-motion preference, but this guarantees they're gone
     even if that check is ever bypassed — a pointer-following element is
     exactly the kind of continuous motion this preference exists to remove. */
  .lamp, .caption-cursor { display: none !important; }
}

/* Coarse pointers (touch) never get the lamp, the caption or the magnetic
   pull — all three are mouse-hover concepts a touchscreen has no equivalent
   gesture for. */
@media (hover: none), (pointer: coarse) {
  .lamp, .caption-cursor { display: none !important; }
}

/* --------------------------------------------------------------------------
   16. Print
   -------------------------------------------------------------------------- */
/* Paper is not backlit. Everything the screen design does with light has to
   invert wholesale here, or a guest printing a booking confirmation empties
   a cartridge onto a black page. */
@media print {
  .masthead, .drawer, .burger, .datum, .social, .lamp, .caption-cursor, .folio, .progress { display: none !important; }
  body { background: #fff; color: #000; }
  body::after { display: none; }
  .band--night, .band--well, .band--paper, .band--ember, .colophon, .pagehead, .panel, .plate, .row__body, .diptych__panel {
    background: #fff !important;
    color: #000 !important;
    box-shadow: none !important;
  }
  .display, .lead, .prose, .data, .price, .eyebrow, .ledger__name, .ledger__desc, .spec__value, .spec__label { color: #000 !important; }
  .js [data-reveal] { opacity: 1; transform: none; }
  a[href^="http"]::after { content: " (" attr(href) ")"; font-size: .75em; }
}

/* --------------------------------------------------------------------------
   17. Page transitions
   Cross-document View Transitions: the browser itself cross-fades one full
   page into the next on every navigation, including back/forward — no
   router, no JavaScript, nothing that can throw. A browser that has never
   heard of @view-transition just does the plain navigation it always did;
   an unknown at-rule, an unknown pseudo-element and an unknown property
   value are each dropped on their own without disturbing anything around
   them, which is the whole reason this is safe to ship with zero feature
   detection. Every page opts in via this one shared stylesheet, so the
   pair on either side of any internal link always agrees to transition.

   The wordmark carries a view-transition-name across every page, so it
   reads as one fixed anchor the rest of the page dissolves around instead
   of fading out and back in on every click — the one piece of visual
   continuity a full-page reload can't otherwise offer. */
@view-transition {
  navigation: auto;
}

::view-transition-old(root),
::view-transition-new(root) {
  animation-duration: 420ms;
  animation-timing-function: var(--ease-frame);
}

.wordmark { view-transition-name: wordmark; }

@media (prefers-reduced-motion: reduce) {
  ::view-transition-group(*),
  ::view-transition-old(*),
  ::view-transition-new(*) {
    animation: none !important;
  }
}
