/* ═══════════════════════════════════════════════════════════════════
   NURTURING TOUCH
   Direction: Bone & Oxblood, warmed.
   Single page, no framework, no build step, zero photography.

   Editing rules that matter (see README before breaking any of them):
   · Two display faces. Ephesis is the logotype and nothing else; Playfair is
     every other piece of display type, and never below 28px.
   · Oxblood is invisible on ink — always use var(--accent), never a literal.
   · --blush is decorative only. Never text, never a meaningful hairline.
   · The Boundaries section stays undecorated. That plainness is the point.
   ═══════════════════════════════════════════════════════════════════ */

/* ─────────────────────────── 1 · TOKENS ─────────────────────────── */

:root {
  /* Dark by default, and not because the visitor's operating system says so.
     The rooms this describes are low-lit; a bone-white page is the wrong first
     impression, and following prefers-color-scheme meant half of all visitors
     got it anyway. Light is still one tap away and still remembered. */
  color-scheme: dark;

  /* Dark — plum-cast near-black. Never neutral. */
  --bg:             #171013;
  --surface:        #241A20;
  --band-invert:    #241A20;
  --text:           #F3EBE1;
  --text-muted:     #B5A29C;
  --accent:         #E9A7A0;
  --accent-2:       #CE93B4;
  --brass:          #D9A867;
  --blush:          #3A2229;
  --rule:           #3A2C33;
  --rule-strong:    #4E3C44;
  --rule-onsurface: #463640;
  --focus:          #E9A7A0;
  --arc-1: #E9A7A0;
  --arc-2: #CE93B4;
  --arc-3: #D9A867;
  --field-1: rgba(233, 167, 160, .14);
  --field-2: rgba(206, 147, 180, .11);
  --field-3: rgba(217, 168, 103, .08);
  --grain-opacity: .055;
  --grain-blend: screen;

  --font-display: "Playfair Display", "Display Fallback", Georgia, "Times New Roman", serif;
  --font-script:  "Ephesis", "Snell Roundhand", "Brush Script MT", cursive;
  --font-body:    "Instrument Sans", "Helvetica Neue", system-ui, -apple-system, sans-serif;
  --font-mono:    "IBM Plex Mono", ui-monospace, "SF Mono", Menlo, monospace;
  --t-hero:       clamp(4.5rem, 16.5vw, 8rem);
  --t-h2:         clamp(2.125rem, 5.2vw, 4.25rem);
  --t-h3:         clamp(1.75rem, 3vw, 2.5rem);
  --t-quote:      clamp(1.875rem, 4.4vw, 3.5rem);
  --t-price:      clamp(1.75rem, 2.4vw, 2.25rem);
  --t-standfirst: clamp(1.1875rem, 1.9vw, 1.625rem);
  --t-body-lg:    1.1875rem;
  --t-body:       1.0625rem;
  --t-small:      .9375rem;
  --t-eyebrow:    .6875rem;
  --t-mono:       .75rem;
  --t-fine:       .875rem;   /* 14px. Fine print that still has to be read:
                                deposit, notice, payment, credential, legal. */
  --t-wordmark:   1.5rem;
  --s1:4px;  --s2:8px;   --s3:12px;  --s4:16px;  --s5:24px;  --s6:32px;
  --s7:48px; --s8:64px;  --s9:96px;  --s10:128px; --s11:160px; --s12:200px;
  --section-y: clamp(56px, 7vw, 96px);   /* was clamp(88px, 13vw, 176px) — the page ran 14 screens */
  --gutter: clamp(20px, 5vw, 96px);
  --colgap: clamp(16px, 2.2vw, 32px);
  --maxw: 1440px;
  --ease:     cubic-bezier(.16, 1, .3, 1);
  --ease-arc: cubic-bezier(.32, .72, 0, 1);
}

/* Light is opt-in: reachable only through the toggle, never from the OS. */
:root[data-theme="light"] {
  color-scheme: light;

  /* Light — Bone */
  --bg:             #F5F0E8;
  --surface:        #EBE1D4;
  --band-invert:    #1A1114;
  --text:           #1A1114;
  --text-muted:     #6B5A57;
  --accent:         #5E1526;
  --accent-2:       #7A2E52;
  --brass:          #7E5126;
  --blush:          #EBD3CB;
  --rule:           #DCD2C4;
  --rule-strong:    #C7B9A6;
  --rule-onsurface: #D2C6B6;
  --focus:          #5E1526;
  --arc-1: #5E1526;
  --arc-2: #7A2E52;
  --arc-3: #C9A9A0;
  --field-1: rgba(94, 21, 38, .26);
  --field-2: rgba(122, 46, 82, .18);
  --field-3: rgba(235, 211, 203, .12);
  --grain-opacity: .045;
  --grain-blend: multiply;
}

/* Inverted bands rescope the whole token set so nested components inherit
   correctly. Skipping this ships oxblood at 1.4:1 on ink — invisible. */
.band--invert {
  --bg: var(--band-invert);
  --surface: var(--band-invert);
  --text: #F3EBE1;
  --text-muted: #B5A29C;
  --accent: #E9A7A0;
  --accent-2: #CE93B4;
  --brass: #D9A867;
  --blush: #3A2229;
  --rule: rgba(243,235,225,.18);
  --rule-strong: rgba(243,235,225,.32);
  --rule-onsurface: rgba(243,235,225,.18);
  --focus: #E9A7A0;
  --arc-1: #E9A7A0;
  --arc-2: #CE93B4;
  --arc-3: #D9A867;
  background: var(--band-invert);
  color: var(--text);
}

/* ─────────────────────────── 2 · BASE ─────────────────────────── */

*, *::before, *::after { box-sizing: border-box; }

/* overflow-x on html as well as body: the hero arc and the three blurred field
   layers bleed off-canvas by design, and clipping only on body leaves
   documentElement.scrollWidth overhanging. No sticky elements to break. */
html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; overflow-x: clip; }
@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: var(--font-body);
  font-size: var(--t-body);
  line-height: 1.65;
  overflow-x: clip;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

/* height: auto is not optional here. The portrait carries width and height
   attributes so the browser can reserve its space before it loads; max-width
   alone then shrinks the width to fit while the height attribute holds at its
   original value, and the image comes out squeezed. This bit the portrait at
   636x1000 instead of 636x795. svg is kept separate because several inline
   marks are sized deliberately in CSS. */
img { max-width: 100%; height: auto; }
svg { max-width: 100%; }
p { margin: 0 0 var(--s5); text-wrap: pretty; }
p:last-child { margin-bottom: 0; }
h1, h2, h3 { margin: 0; font-weight: 400; text-wrap: balance; }
ul, ol, dl, dd { margin: 0; padding: 0; }
li { list-style: none; }
a { color: inherit; }

.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

.skip-link {
  position: absolute; left: var(--s4); top: -100px; z-index: 9000;
  background: var(--accent); color: var(--bg); padding: var(--s3) var(--s5);
  font: 500 var(--t-mono)/1 var(--font-mono);
  text-transform: uppercase; letter-spacing: .14em; border-radius: 999px;
  transition: top .2s var(--ease);
}
.skip-link:focus { top: var(--s4); }

:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 3px;
  border-radius: 2px;
}

.wrap {
  width: 100%; max-width: var(--maxw);
  margin-inline: auto;
  padding-inline: var(--gutter);
}
.wrap--narrow { max-width: 980px; }

.band { position: relative; padding-block: var(--section-y); }
.band--surface { background: var(--surface); }

/* ─────────────────────────── 3 · TYPE ROLES ─────────────────────────── */

.eyebrow {
  font: 500 var(--t-eyebrow)/1 var(--font-body);
  text-transform: uppercase; letter-spacing: .22em;
  color: var(--brass);
  margin: 0 0 var(--s5);
}

.h2 {
  font-family: var(--font-display);
  font-size: var(--t-h2);
  font-weight: 400;
  line-height: 1.02;
  letter-spacing: -.02em;
  margin-bottom: var(--s6);
}
.band--invert .h2 { font-weight: 500; }

.kicker {
  font: 400 var(--t-mono)/1.4 var(--font-mono);
  text-transform: uppercase; letter-spacing: .14em;
  color: var(--text-muted);
  margin: var(--s2) 0 0;
}

.standfirst {
  font-size: var(--t-standfirst);
  line-height: 1.42;
  color: var(--text-muted);
  max-width: 30ch;
  margin-bottom: var(--s6);
}
.standfirst--sm { max-width: 46ch; font-size: var(--t-body-lg); }

.cap {
  float: left;
  font-family: var(--font-display);
  font-style: italic;
  font-weight: 400;
  font-size: 3.4em;
  line-height: .82;
  color: var(--accent);
  margin: .06em .12em 0 -.02em;
}

.todo {
  color: var(--accent-2);
  font-style: italic;
}

/* ─────────────────────────── 4 · BUTTONS & LINKS ─────────────────────────── */

.btn {
  display: inline-block;
  background: var(--accent);
  color: #1A1114;
  font: 500 .875rem/1 var(--font-mono);
  text-transform: uppercase; letter-spacing: .16em;
  padding: 17px 30px;
  border: 0; border-radius: 999px;
  text-decoration: none; cursor: pointer;
  transition: background .4s var(--ease);
}
.btn:hover { background: var(--accent-2); }
.btn--wide { width: 100%; margin-top: var(--s5); }

/* Outlined sibling of .btn, for when two routes are genuinely equal and one
   filled button beside another would read as a hierarchy that is not there. */
.btn--quiet {
  background: transparent;
  color: var(--accent);
  box-shadow: inset 0 0 0 1px var(--accent);
}
.btn--quiet:hover { background: var(--accent); color: var(--bg); }
/* The button fills with --accent: deep oxblood in light, pale pink in dark, so
   the label flips. Dark is the default, so dark text is the default; light theme
   overrides it, and an inverted band inside light theme overrides it back. */
:root[data-theme="light"] .btn { color: #F3EBE1; }
:root[data-theme="light"] .band--invert .btn { color: #1A1114; }

.link-u {
  text-decoration: none;
  border-bottom: 1px solid var(--accent);
  padding-bottom: 2px;
  transition: border-width .3s var(--ease), padding-bottom .3s var(--ease);
}
.link-u:hover { border-bottom-width: 2px; padding-bottom: 4px; }

/* ─────────────────────────── 5 · ATMOSPHERE ─────────────────────────── */

/* Woven linen grain. The asymmetric baseFrequency (high X, low Y) is the
   whole trick — isotropic noise reads as TV static, not cloth. */
.grain {
  position: fixed; inset: 0; z-index: 9999; pointer-events: none;
  opacity: var(--grain-opacity);
  mix-blend-mode: var(--grain-blend);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='260' height='260'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9 0.035' numOctaves='2' seed='7'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='260' height='260' filter='url(%23n)'/%3E%3C/svg%3E");
}

/* The field gradient. Used exactly three times so it stays an event. */
.field {
  position: absolute;
  width: 140vw; aspect-ratio: 16 / 9;
  pointer-events: none; z-index: 0;
  filter: blur(90px);
  will-change: transform;
  background:
    radial-gradient(ellipse 62% 70% at 32% 40%, var(--field-1) 0%, transparent 62%),
    radial-gradient(ellipse 54% 60% at 66% 62%, var(--field-2) 0%, transparent 66%),
    radial-gradient(ellipse 40% 44% at 50% 78%, var(--field-3) 0%, transparent 70%);
}
.field--cover   { top: -10%; left: -20%; }
.field--reserve { bottom: -30%; left: -20%; }

/* Sized to fade out inside its own row rather than being sliced off by the
   row's clip box — a blurred glow with a straight edge reads as a mistake. */
.field--held {
  aspect-ratio: auto;
  width: 85%; height: 130%;
  top: -15%; left: 4%;
  opacity: .8;
}

@media (prefers-reduced-motion: no-preference) {
  .field { animation: drift 26s ease-in-out infinite alternate; }
  @keyframes drift {
    from { transform: translate3d(0,0,0) scale(1); }
    to   { transform: translate3d(2.5%, -2%, 0) scale(1.05); }
  }
}

/* ─────────────────────────── 6 · NAV ─────────────────────────── */

.nav {
  position: fixed; inset: 0 0 auto 0; z-index: 900;
  display: flex; align-items: center; gap: var(--s5);
  padding: var(--s5) var(--gutter);
  transition: background .4s var(--ease), padding .4s var(--ease), border-color .4s var(--ease);
  border-bottom: 1px solid transparent;
}
.nav.is-stuck {
  background: color-mix(in srgb, var(--bg) 82%, transparent);
  backdrop-filter: blur(12px);
  border-bottom-color: var(--rule);
  padding-block: var(--s3);
}
@media (prefers-reduced-transparency: reduce) {
  .nav.is-stuck { background: var(--bg); backdrop-filter: none; }
}

.brand {
  display: flex; flex-direction: column; align-items: flex-start; gap: 6px;
  text-decoration: none; margin-right: auto;
  padding-block: 9px; /* keeps the tap target at 44px when the wordmark hides */
}
.brand__arc { display: block; width: 26px; height: 26px; }
.brand__arc svg { display: block; width: 100%; height: 100%; }
.brand__word {
  font-family: var(--font-script);
  font-size: var(--t-wordmark);
  font-weight: 400;
  line-height: 1.3;
  margin: 0;
}

.nav__links { display: flex; gap: var(--s6); }
.nav__links a {
  font: 500 var(--t-mono)/1 var(--font-body);
  text-transform: uppercase; letter-spacing: .18em;
  text-decoration: none; color: var(--text-muted);
  padding: 16px 2px; /* 44px tap target */
  transition: color .3s var(--ease);
}
.nav__links a:hover { color: var(--text); }

/* Narrow screens: drop the wordmark to the arc alone and drop "Menu" (the hero
   CTA already points there). Boundaries stays in primary navigation on every
   width — it is the page's trust document and should never be footer-only. */
@media (max-width: 700px) {
  .brand__word { display: none; }
  .nav__links { gap: var(--s5); }
  .nav__links a[href="#menu"] { display: none; }
}

.theme {
  display: grid; place-items: center;
  width: 44px; height: 44px;
  background: none; border: 0; padding: 0; cursor: pointer;
  color: var(--text-muted);
  transition: color .3s var(--ease), transform .5s var(--ease);
}
.theme:hover { color: var(--text); transform: rotate(180deg); }
.theme svg { width: 20px; height: 20px; }

/* Phones only: the page is long and the top nav scrolls away, so the one
   action that matters stays pinned to the bottom edge. app.js hides it while
   the cover (which has its own Reserve link) or the reserve section is on
   screen. Colors come from .btn, so it flips with the theme like every button. */
.cta-pin { display: none; }
@media (max-width: 700px) {
  .cta-pin {
    display: flex; align-items: center; justify-content: center;
    position: fixed; left: var(--s4); right: var(--s4);
    bottom: max(var(--s4), env(safe-area-inset-bottom));
    z-index: 800; min-height: 48px;
    box-shadow: 0 8px 24px rgba(0, 0, 0, .35);
    transition: transform .3s var(--ease), opacity .3s var(--ease);
  }
  .cta-pin.is-hidden { transform: translateY(140%); opacity: 0; pointer-events: none; }
}

/* ─────────────────────────── 7 · COVER ─────────────────────────── */

.cover {
  position: relative;
  min-height: 100svh;
  display: flex; align-items: center;
  padding-block: clamp(120px, 18vh, 200px) var(--s7);
  overflow: clip;
}
.cover__inner { position: relative; z-index: 2; }

.cover__arc {
  position: absolute; z-index: 1;
  width: 74vmin; height: 74vmin;
  right: 0; top: 50%;
  transform: translate(30%, -50%);
  pointer-events: none;
}
.cover__arc svg { width: 100%; height: 100%; display: block; }

@media (prefers-reduced-motion: no-preference) {
  .arc-breathe { animation: breathe 14s ease-in-out infinite alternate; }
  @keyframes breathe {
    from { stroke-width: 1.25; opacity: .85; }
    to   { stroke-width: 1.65; opacity: 1; }
  }
}

/* The hero is the logotype, so it is set in the script rather than the display
   face. Scripts want the opposite of what a Didone wanted: positive tracking, not
   negative, or the connecting strokes collide; generous leading, because the
   ascenders and descenders run long and two lines will otherwise tangle; and no
   faux weight, because Ephesis has exactly one. */
.cover__title {
  font-family: var(--font-script);
  font-size: var(--t-hero);
  font-weight: 400;
  /* 1.4, and it is not arbitrary. Measured: Ephesis overlaps the line below at
     1.05 by 15px, at 1.15 by 9px, at 1.25 by 3px, and only clears at 1.35 --
     the g of "Nurturing" runs straight into the T of "Touch". 1.4 leaves air.
     Any change of script face wants this re-measured, not inherited. */
  line-height: 1.4;
  letter-spacing: .01em;
  margin: 0 0 var(--s7);
  max-width: 12ch;
}
.cover__l1, .cover__l2 { display: block; }
.cover__l2 { padding-left: 1.5ch; }

/* Wide enough for the whole name on one line, which is where a script logotype
   wants to be. It also buys back a great deal of height: stacked at 232px the
   title alone ran 650px tall and pushed the call to action 212px below the fold.
   One line at 208px is both optically larger and 359px shorter. The breakpoint is
   set by width, not taste — "Nurturing Touch" in Ephesis measures 5.52px wide per
   px of font size, so below ~880px it simply will not fit. */
@media (min-width: 880px) {
  :root { --t-hero: clamp(7rem, 14.5vw, 13rem); }
  .cover__l1, .cover__l2 { display: inline; }
  /* No padding here. The newline between the two spans in the markup already
     supplies a word space, and at hero size that is plenty -- adding .18em on
     top of it opened a 0.38em hole and the two words stopped reading as one
     wordmark. */
  .cover__l2 { padding-left: 0; }
}
/* Ephesis has no italic. Left alone, <em> makes the browser synthesize a slant on
   top of a face that is already cursive — a double lean that looks like a mistake.
   The markup keeps its <em> because the emphasis is real; only the slant goes. */
.cover__l2 em { font-style: normal; }

.cover__cta { display: flex; align-items: center; gap: var(--s5); flex-wrap: wrap; }

.folio-inline {
  position: absolute; left: var(--gutter); bottom: var(--s7); z-index: 2;
  display: flex; align-items: center; gap: var(--s3);
  font: 400 var(--t-mono)/1 var(--font-mono);
  text-transform: uppercase; letter-spacing: .24em;
  color: var(--text-muted);
  margin: 0;
}
.folio-rule { display: block; width: 1px; height: 64px; background: var(--rule-strong); }

@media (max-width: 900px) {
  .cover__arc { width: 56vmin; height: 56vmin; top: 8%; transform: translate(38%, 0); opacity: .55; }
  .folio-inline { display: none; }
}

/* ─────────────────────────── 7b · AMBIENT PHOTOGRAPHS ─────────────────────────── */

/* Three photographs, none of them shown as a photograph. Each sits behind a
   band at low opacity, softened, and masked so it fades to the band colour on
   every edge — atmosphere, not illustration. Objects only: flame, wax, a candle
   in cupped hands. No faces, no bodies, nothing a classifier can misread.
   Self-hosted (assets/amb-*.webp); sources and licence in README §"Photographs".
   In light mode they nearly vanish, which is right: bone stays bone. */
.band--photo { position: relative; isolation: isolate; }
.band--photo::before {
  content: ""; position: absolute; inset: -3%; z-index: 0;
  background-size: cover; background-repeat: no-repeat;
  opacity: var(--photo-opacity, .28);
  filter: blur(1.5px) saturate(.85);
  pointer-events: none;
}
.band--photo > .wrap { position: relative; z-index: 1; }
:root[data-theme="light"] .band--photo::before { opacity: .11; filter: blur(2px) saturate(.7); }
@media (prefers-reduced-transparency: reduce) { .band--photo::before { display: none; } }

.manifesto.band--photo::before {
  background-image: url("../assets/amb-flames.webp");
  background-position: center 42%;
  -webkit-mask-image: radial-gradient(ellipse 70% 90% at 50% 50%, #000 20%, transparent 100%);
          mask-image: radial-gradient(ellipse 70% 90% at 50% 50%, #000 20%, transparent 100%);
}
.unfolds.band--photo::before {
  background-image: url("../assets/amb-hands.webp");
  background-position: 12% 40%;
  --photo-opacity: .3;
  -webkit-mask-image: radial-gradient(ellipse 55% 80% at 18% 50%, #000 10%, transparent 100%);
          mask-image: radial-gradient(ellipse 55% 80% at 18% 50%, #000 10%, transparent 100%);
}
.reserve.band--photo::before {
  /* Kept to the right margin, clear of the form column (980px, centred). */
  background-image: url("../assets/amb-wax.webp");
  background-position: 100% 55%;
  --photo-opacity: .3;
  -webkit-mask-image: radial-gradient(ellipse 32% 65% at 96% 50%, #000 10%, transparent 100%);
          mask-image: radial-gradient(ellipse 32% 65% at 96% 50%, #000 10%, transparent 100%);
}
@media (max-width: 700px) {
  /* Narrow screens: centre the subject and let it sit lower behind the text. */
  .unfolds.band--photo::before { background-position: 40% 30%;
    -webkit-mask-image: radial-gradient(ellipse 90% 70% at 50% 35%, #000 10%, transparent 100%);
            mask-image: radial-gradient(ellipse 90% 70% at 50% 35%, #000 10%, transparent 100%); }
  /* Phone: the form fills the width, so the candle drops below it, dimmer. */
  .reserve.band--photo::before { background-position: 60% 100%; --photo-opacity: .2;
    -webkit-mask-image: radial-gradient(ellipse 90% 40% at 55% 88%, #000 10%, transparent 100%);
            mask-image: radial-gradient(ellipse 90% 40% at 55% 88%, #000 10%, transparent 100%); }
}

/* ─────────────────────────── 8 · MANIFESTO ─────────────────────────── */

.manifesto { padding-block: clamp(56px, 9vh, 112px); }
/* The measure lives on the <p>. ch is sized by the font of the element it is
   declared on; on the wrapper it meant 24 body-size characters, about 200px,
   and the quote set as a 983px tower of one or two words per line. */
.pull { margin: 0; }
.pull p {
  max-width: 24ch; margin-inline: auto;
  font-family: var(--font-display);
  font-size: var(--t-quote);
  font-weight: 500;
  line-height: 1.12;
  text-indent: -.4em;
  margin: 0;
}
.pull em { font-style: italic; color: var(--accent); }

/* ─────────────────────────── 9 · MENU ─────────────────────────── */

.menu__head {
  display: grid; gap: var(--s7);
  align-items: end;
  margin-bottom: var(--s7);
}
.menu__headline .h2 { margin-bottom: 0; }


/* ── service rows ── */

.svc {
  position: relative;
  display: grid; gap: var(--s4);
  padding-block: var(--s6);
  border-top: 1px solid var(--rule);
  transition: background .4s var(--ease);
  /* Clips the blurred field, but the clip margin lets the folio numeral hang
     into the left gutter as intended instead of being sliced off. */
  overflow: clip;
  overflow-clip-margin: 120px;
}
.svc::after {
  content: ""; position: absolute; inset: -1px auto auto 0;
  height: 2px; width: 100%; background: var(--accent);
  transform: scaleX(0); transform-origin: left;
  transition: transform .52s var(--ease);
}
.svc:hover::after, .svc:focus-within::after { transform: scaleX(1); }
.svc:hover, .svc:focus-within { background: color-mix(in srgb, var(--blush) 8%, transparent); }

@media (min-width: 900px) {
  .svc {
    grid-template-columns: minmax(0, 5fr) minmax(0, 6fr) minmax(0, 3fr);
    gap: var(--colgap);
    align-items: start;
  }
}

.svc__folio {
  position: absolute; left: 0; top: var(--s6); z-index: 0;
  font-family: var(--font-display);
  font-style: italic;
  font-size: clamp(4rem, 8vw, 9rem);
  line-height: 1;
  margin: 0;
  color: transparent;
  /* The stroke needs its own color: currentColor here would resolve to the
     transparent fill above and the outlined numeral would vanish entirely. */
  -webkit-text-stroke: 1px var(--text);
  opacity: .22;
  pointer-events: none;
  transition: opacity .4s var(--ease), color .4s var(--ease);
}
@media (min-width: 1100px) { .svc__folio { left: -80px; } }
@media (max-width: 1099px) { .svc__folio { display: none; } }
.svc:hover .svc__folio {
  color: var(--accent); -webkit-text-stroke-width: 0; opacity: .28;
}

.svc__id, .svc__copy, .price { position: relative; z-index: 1; }

.svc__glyph { display: block; width: 52px; height: 52px; color: var(--accent); margin-bottom: var(--s4); }
.mark {
  width: 100%; height: 100%; display: block;
  fill: none; stroke: currentColor;
  /* The viewBox is 120 units wide but renders at 52px, so a plain stroke-width
     of 1.25 would scale down to roughly half a screen pixel and disappear.
     non-scaling-stroke pins it to real pixels regardless of the viewBox. */
  stroke-width: 1.5;
  vector-effect: non-scaling-stroke;
  stroke-linecap: round; stroke-linejoin: round;
  opacity: .75;
  transition: opacity .4s var(--ease);
}
.svc:hover .mark { opacity: 1; }

.svc__name {
  font-family: var(--font-display);
  font-size: var(--t-h3);
  font-weight: 400;
  line-height: 1.1;
  letter-spacing: -.01em;
}
.svc__copy p { color: var(--text-muted); margin-bottom: var(--s4); }
.svc__copy p:last-child { margin-bottom: 0; }
.svc__nuance { font-size: var(--t-small); }
.svc__voice { color: var(--accent) !important; font-family: var(--font-display); font-size: 1.75rem; line-height: 1.3; }
.svc__voice em { font-style: italic; }

/* The Held Hour breaks the grid — first, wider, italic, lit, and given room.
   If this row reads like the others, the whole direction fails. */
.svc--held {
  padding-block: 88px;
}
/* Whichever row happens to be first loses its top rule — the section already
   has one. Not hard-coded to a specific service, so reordering the menu never
   leaves a double rule or a missing one. */
.menu .svc:first-of-type { border-top: 0; }
.svc--held .svc__name {
  font-style: italic;
  font-size: clamp(2rem, 3.6vw, 3.25rem);
}
@media (min-width: 900px) {
  .svc--held { grid-template-columns: minmax(0, 5fr) minmax(0, 7fr) minmax(0, 3fr); }
}

/* ── price ── */

/* Every tier on every line. There is no selector: a row of three on narrow
   screens, a right-aligned stack once the price has a column of its own. */
.price { display: grid; grid-template-columns: repeat(3, auto); justify-content: start; gap: var(--s6); }
@media (min-width: 900px) {
  .price { grid-template-columns: 1fr; justify-content: end; justify-items: end; text-align: right; gap: var(--s4); }
}

.price__t dt {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap;
}
.price__t dd { margin: 0; }
.price__lab {
  display: block;
  font: 400 10px/1 var(--font-mono);
  text-transform: uppercase; letter-spacing: .18em;
  color: var(--brass);
  margin-bottom: 6px;
}
.price__fig {
  font-family: var(--font-display);
  font-size: var(--t-price);
  font-variant-numeric: tabular-nums lining-nums;
  color: var(--text);
  line-height: 1;
}
.price__fig--na { font-family: var(--font-body); font-size: 1.125rem; font-style: italic; color: var(--text-muted); }
.cur { font-family: var(--font-body); font-size: .52em; vertical-align: .55em; color: var(--text-muted); margin-right: 1px; }

@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }

/* ── menu foot ── */

.menu__foot { border-top: 1px solid var(--rule); padding-top: var(--s7); }
.menu__voice {
  color: var(--text-muted);
  max-width: 46ch;
  margin-bottom: var(--s6);
}
.menu__creed {
  font-family: var(--font-display);
  font-size: 1.75rem;
  line-height: 1.35;
  text-align: center;
  max-width: 46ch;
  margin: 0 auto var(--s6);
}
.menu__creed em { font-style: italic; }
.menu__terms {
  display: grid; gap: var(--s2);
  margin-bottom: var(--s6);
  font: 400 var(--t-fine)/1.7 var(--font-mono);
  text-transform: uppercase; letter-spacing: .1em;
  color: var(--text-muted);
}
.menu__cta { text-align: center; }

/* ── Enhancements ──
   Hairline list, same grammar as the service rows. Never styled as an upsell:
   no badges, no "popular", no strike-throughs. The default option is listed
   first and costs nothing, which is what keeps this from reading as a ladder. */
.enhance {
  border-top: 1px solid var(--rule);
  padding-top: var(--s6);
  margin-bottom: var(--s6);
}
/* Smaller than a section heading on purpose. This is a footnote to the menu, not
   a fourth thing to choose from, and setting it at full display size was what made
   it read as an upsell. */
.enhance__title {
  font-family: var(--font-display);
  font-size: 1.5rem;
  line-height: 1.2;
  margin-bottom: var(--s3);
}
.enhance__lede {
  color: var(--text-muted);
  max-width: 52ch;
  margin-bottom: var(--s6);
}


/* ─────────────────────────── 10 · UNFOLDS ─────────────────────────── */

/* The rule's offset and the dots' offset are both derived from --tl-rule so
   they cannot drift apart: the dot is 6px, so it centers on the rule at
   (--tl-rule − node padding-top − half the dot). */
.tl { display: grid; gap: var(--s6); position: relative; --tl-rule: 76px; }
@media (min-width: 768px) {
  .tl { grid-template-columns: repeat(4, 1fr); gap: var(--colgap); padding-top: var(--s6); }
  .tl::before {
    content: ""; position: absolute; left: 0; right: 0; top: var(--tl-rule);
    height: 1px; background: var(--rule-onsurface);
  }
}
.tl__node { position: relative; padding-right: var(--s5); }
.tl__t {
  font: 400 var(--t-mono)/1 var(--font-mono);
  text-transform: uppercase; letter-spacing: .18em;
  color: var(--brass);
  margin: 0 0 var(--s4);
}
.tl__dot {
  display: block; width: 6px; height: 6px; border-radius: 50%;
  background: var(--accent); margin-bottom: var(--s5);
}
@media (min-width: 768px) {
  .tl__dot {
    position: absolute; left: 0; margin: 0;
    top: calc(var(--tl-rule) - var(--s8) - 3px);
  }
  .tl__node { padding-top: var(--s6); }
}
.tl__node h3 {
  font-family: var(--font-display);
  font-size: 1.75rem;
  line-height: 1.2;
  margin-bottom: var(--s3);
}
.tl__node p { color: var(--text-muted); font-size: var(--t-small); }
@media (max-width: 767px) {
  .tl { padding-left: var(--s5); border-left: 1px solid var(--rule-onsurface); }
  .tl__dot { position: absolute; left: calc(var(--s5) * -1 - 3px); top: 6px; }
}

/* ─────────────────────────── 11 · BOUNDARIES ─────────────────────────── */
/* Deliberately the plainest block on the page. No field, no arc, no drop cap,
   no muted greys on the statements. That austerity is what makes the warmth
   elsewhere trustworthy. Do not decorate this. */

.bnd__grid { display: grid; gap: var(--s6); margin-bottom: var(--s7); }
@media (min-width: 900px) {
  .bnd__grid { grid-template-columns: minmax(0, 5fr) minmax(0, 7fr); gap: var(--s9); }
}
.bnd__head .h2 { margin-bottom: 0; }

.bnd__list li {
  border-top: 1px solid var(--rule);
  padding-block: var(--s4);
  font-size: 1.125rem;
  color: var(--text);
}

.bnd__pair { display: grid; gap: var(--s6); margin-bottom: var(--s6); }
@media (min-width: 768px) { .bnd__pair { grid-template-columns: 1fr 1fr; gap: var(--s9); } }
.bnd__col ul li {
  border-top: 1px solid var(--rule);
  padding-block: var(--s4);
  font-size: var(--t-body);
  color: var(--text);
}
.bnd__note { color: var(--text-muted); max-width: 62ch; }

/* ─────────────────────────── 12 · PRACTITIONER ─────────────────────────── */

/* The section widens to hold the portrait; the text column keeps its old
   measure so the paragraph does not stretch into an unreadable line length. */
.who .wrap { max-width: 1040px; }
.who__grid { display: grid; gap: var(--s6); align-items: start; }
/* Capped hard, and on mobile too. Left to fill the column it went full-bleed at
   465px wide and dominated a section that is meant to be a paragraph with a face
   next to it, not a portrait with a caption. */
.who__portrait { max-width: 200px; }
@media (min-width: 820px) {
  .who__grid { grid-template-columns: minmax(0, 200px) minmax(0, 1fr); gap: var(--s7); }
}
.who__text { max-width: 62ch; }

/* The one photograph on the site. Held to a portrait ratio so a future
   replacement of different proportions cannot reflow the section, and softened
   at the edge rather than framed -- a hard rectangle on the ink band looks
   pasted on. */
.who__portrait img {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 4 / 5;
  object-fit: cover;
  border-radius: 2px;
  filter: saturate(.92);
  box-shadow: 0 24px 60px rgba(0, 0, 0, .38);
}
.who__body { font-size: var(--t-body-lg); }
.who__sign {
  margin: var(--s5) 0 var(--s4);
  font-family: var(--font-display);
  font-size: 3rem; line-height: 1;
}
.who__sign em { font-style: italic; }
.who__rule { display: block; width: 220px; max-width: 100%; height: 12px; margin-top: var(--s2); }
.who__cred {
  font: 400 var(--t-fine)/1.6 var(--font-mono);
  text-transform: uppercase; letter-spacing: .14em;
  color: var(--text-muted);
}

/* ─────────────────────────── 13 · FAQ ─────────────────────────── */

.acc__item { border-top: 1px solid var(--rule); }
.acc__item:last-child { border-bottom: 1px solid var(--rule); }
.acc__item h3 { margin: 0; }
.acc__item button {
  display: flex; align-items: center; justify-content: space-between; gap: var(--s5);
  width: 100%; padding: var(--s4) 0;
  background: none; border: 0; cursor: pointer; text-align: left;
  font: 500 1.125rem/1.4 var(--font-body);
  color: var(--text);
  transition: color .3s var(--ease);
}
.acc__item button:hover { color: var(--accent); }
.acc__x {
  flex: none;
  font: 400 1.25rem/1 var(--font-mono);
  color: var(--accent);
  transition: transform .3s var(--ease);
}
.acc__item button[aria-expanded="true"] .acc__x { transform: rotate(45deg); }

/* Animating grid-template-rows 0fr→1fr means the panel sizes itself to its
   own content at any width, with no measurement in JS and nothing to re-sync
   on resize. */
.acc__panel {
  display: grid;
  grid-template-rows: 0fr;
  transition: grid-template-rows .4s var(--ease);
}
/* visibility (not just zero height) so a collapsed answer actually leaves the
   accessibility tree — otherwise the button announces "collapsed" while a
   screen reader still reads the text underneath it. The delay lets the
   close animation finish before the content is hidden. */
.acc__panel > div {
  overflow: hidden;
  visibility: hidden;
  transition: visibility 0s linear .4s;
}
.acc__item.is-open .acc__panel { grid-template-rows: 1fr; }
.acc__item.is-open .acc__panel > div { visibility: visible; transition-delay: 0s; }
html:not(.js) .acc__panel > div { visibility: visible; }
.acc__panel > div > p {
  padding-bottom: var(--s6);
  max-width: 62ch;
  color: var(--text-muted);
}
/* No-JS: every answer is open and readable. */
html:not(.js) .acc__panel { grid-template-rows: 1fr; }
html:not(.js) .acc__x { display: none; }

/* ─────────────────────────── 14 · FORM ─────────────────────────── */

.reserve { overflow: clip; }
.reserve .wrap { position: relative; z-index: 1; }

.form { margin-top: var(--s6); max-width: 620px; }
.f { position: relative; margin-bottom: var(--s4); }
.f input, .f textarea, .f select {
  width: 100%;
  background: transparent;
  border: 0; border-bottom: 1px solid var(--rule-strong);
  border-radius: 0;
  padding: 18px 0 10px;
  font: 400 var(--t-body)/1.5 var(--font-body);
  color: var(--text);
  transition: border-color .24s var(--ease);
}
.f textarea { resize: vertical; min-height: 80px; }
.f label {
  position: absolute; left: 0; top: 20px;
  font: 400 var(--t-body)/1.5 var(--font-body);
  color: var(--text-muted);
  pointer-events: none;
  transition: transform .24s var(--ease), font-size .24s var(--ease), color .24s var(--ease);
  transform-origin: left top;
}
.f input:focus + label,
.f textarea:focus + label,
.f input:not(:placeholder-shown) + label,
.f textarea:not(:placeholder-shown) + label,
.f--sel label {
  transform: translateY(-20px);
  font-size: var(--t-eyebrow);
  text-transform: uppercase; letter-spacing: .22em;
  color: var(--brass);
}
.f input:focus, .f textarea:focus, .f select:focus { border-bottom-color: var(--accent); border-bottom-width: 2px; }
.f--sel select { padding-top: 26px; cursor: pointer; }
.f--sel label { top: 14px; }

.form__fine {
  margin-top: var(--s5);
  font: 400 var(--t-fine)/1.7 var(--font-mono);
  color: var(--text-muted);
  max-width: 56ch;
}
.form__fine--noscript { margin-top: var(--s8); }

/* Validation message. role="alert" in the markup, so it is announced on append. */
.form__error:empty { display: none; }
.form__error {
  margin-bottom: var(--s5);
  padding: var(--s3) var(--s4);
  border-left: 2px solid var(--accent);
  background: color-mix(in srgb, var(--accent) 8%, transparent);
  color: var(--text);
  font-size: var(--t-small);
}
.f [aria-invalid="true"] { border-bottom-color: var(--accent); border-bottom-width: 2px; }

.form__done {
  margin-bottom: var(--s7);
  padding: var(--s5) var(--s6);
  border: 1px solid var(--accent);
  border-radius: 4px;
  max-width: 620px;
}
.form__done:focus-visible { outline: 2px solid var(--focus); outline-offset: 3px; }

/* Without JS the form can't open a mail client, and submitting it would send
   the field values to this origin — which the privacy notice says never happens.
   So it is hidden entirely and the <noscript> block takes its place. */
html:not(.js) #enq { display: none; }
.reserve__alt { margin-top: var(--s5); color: var(--text-muted); }

/* ─────────────────────────── 15 · FOOTER ─────────────────────────── */

.foot { padding-block: var(--s7) var(--s6); }
/* Room for the pinned Reserve button on phones, so it never sits on the number. */
@media (max-width: 700px) { .foot { padding-bottom: calc(var(--s6) + 72px); } }
.foot__grid {
  display: grid; gap: var(--s7);
  padding-bottom: var(--s6);
  border-top: 1px solid var(--rule);
  padding-top: var(--s6);
}
@media (min-width: 768px) { .foot__grid { grid-template-columns: repeat(3, 1fr); } }
.brand__arc--foot { width: 26px; height: 26px; margin-bottom: var(--s3); }
.brand__word--foot { margin-bottom: var(--s4); }
.foot__sm {
  font: 400 var(--t-small)/1.9 var(--font-body);
  color: var(--text-muted);
  margin: 0;
}
.foot__sm a { text-decoration: none; border-bottom: 1px solid var(--rule-strong); }
.foot__sm a:hover { border-bottom-color: var(--accent); }
.foot__legal {
  font: 400 var(--t-fine)/1.8 var(--font-mono);
  color: var(--text-muted);
  max-width: 70ch;
  margin: 0;
}

/* ─────────────────────────── 16 · MOTION ─────────────────────────── */

/* The hidden starting state is scoped to html.js on purpose. `.is-in` is only
   ever added by app.js, so without this guard a visitor with JavaScript
   disabled — or a crawler that doesn't execute it — would get a blank page. */
@media (prefers-reduced-motion: no-preference) {
  html.js .reveal { opacity: 0; transform: translateY(14px); }
  html.js .reveal.is-in {
    opacity: 1; transform: none;
    transition: opacity .72s var(--ease), transform .72s var(--ease);
    transition-delay: var(--d, 0ms);
  }
}
@media (prefers-reduced-motion: reduce) {
  .reveal { opacity: 1; transform: none; }
  *, *::before, *::after {
    animation-duration: .001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .001ms !important;
  }
}
