/* ===========================================================================
 * FONTS - self-hosted, not Google's CDN
 *
 * Nine pages used to load these from fonts.googleapis.com, which handed every
 * visitor's IP address to Google before they had consented to anything. A
 * Munich court ruled that a GDPR breach in 2022 and a wave of warning letters
 * followed in Germany specifically, and German tourists are a named audience
 * here. Both families are OFL-licensed, so self-hosting is expressly permitted.
 *
 * latin-ext is not optional: Polish needs it. Without it the PL translation
 * renders those accented characters from a fallback font.
 *
 * Both are VARIABLE fonts, so one file covers every weight - `font-weight`
 * below is a range, not a value. Six files, 249 KB, instead of fourteen cuts.
 *
 * `font-display: swap` matches what Google served: text paints immediately in
 * the fallback and re-renders when the file lands, rather than sitting blank.
 *
 * The unicode-range lines are what make the split pay: a visitor reading
 * English downloads latin only and never fetches latin-ext at all.
 *
 * To update: re-fetch from the css2 API with a modern browser User-Agent, keep
 * the latin and latin-ext blocks and drop the rest - cyrillic, greek and
 * vietnamese are all served by default and none of them are needed.
 * =========================================================================== */


@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url('../fonts/inter-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url('../fonts/inter-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

@font-face {
  font-family: 'Playfair Display';
  font-style: normal;
  font-weight: 400 900;
  font-display: swap;
  src: url('../fonts/playfair-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: 'Playfair Display';
  font-style: normal;
  font-weight: 400 900;
  font-display: swap;
  src: url('../fonts/playfair-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

@font-face {
  font-family: 'Playfair Display';
  font-style: italic;
  font-weight: 400 900;
  font-display: swap;
  src: url('../fonts/playfair-italic-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: 'Playfair Display';
  font-style: italic;
  font-weight: 400 900;
  font-display: swap;
  src: url('../fonts/playfair-italic-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

/* ═══════════════════════════════════════════════════════════
   TRANCE RITUAL — Editorial Luxury Experience
   ═══════════════════════════════════════════════════════════ */

:root {
  --bg: #0F0E0C;
  --bg-elevated: #1C1814;
  --bg-card: #161310;
  /* Subtle cool bluish dark (matches footer #100e16 / scrolled nav tint) */
  --bg-cool: #100e16;
  --bg-cool-mid: rgb(16, 15, 24);
  --bg-cool-soft: rgb(12, 12, 19);
  --gold: #D4AF87;
  --gold-light: #E8D5B5;
  --gold-dim: rgba(var(--line-rgb), 0.35);
  /* Bronze is a DECORATIVE token only — SVG strokes, hairlines, ornament.
     Never `color:` on text. Measured 2026-08-11: bronze on this background is
     4.13:1, and normal-size text needs 4.5:1, so every text use of it failed.
     All sixteen were moved to clay (5.31:1). Text gold is `--gold` (9.45:1). */
  --bronze: #8C6F4E;
  --sage: #6E7F63;
  --cream: #EDE4D5;
  --cream-muted: rgba(var(--ink-rgb), 0.55);
  --cream-soft: rgba(var(--ink-rgb), 0.82);

  /* ---- Channel tokens ----------------------------------------------------
     Bare "r, g, b" triplets so tinted values can be written as
     rgba(var(--x-rgb), a) instead of hand-typed literals. Seeded with the
     exact channels they replace, so this layer is a no-op for the dark theme.

     The gold split matters: --gold-rgb is gold as *ink* (color/fill/stroke),
     --line-rgb is gold as a *drawn line or wash* (borders, hairlines,
     gradients, glows). They are the same colour at night. On a light ground
     they must diverge — #D4AF87 is only ~2.2:1 on cream, unreadable as text,
     but still the correct hairline. See css/light.css. */
  --gold-rgb: 212, 175, 135;
  --line-rgb: 212, 175, 135;
  --gold-light-rgb: 232, 213, 181;
  --bronze-rgb: 140, 111, 78;
  --clay-rgb: 177, 123, 67;        /* #B17B43 — the fourth gold, previously untokenised */
  --ink-rgb: 237, 228, 213;        /* --cream */
  --hilite-rgb: 255, 255, 255;
  --shadow-rgb: 0, 0, 0;
  --bg-rgb: 15, 14, 12;
  --bg-elevated-rgb: 28, 24, 20;
  --bg-cool-rgb: 16, 14, 22;
  --bg-cool-mid-rgb: 16, 15, 24;
  --bg-cool-soft-rgb: 12, 12, 19;
  --bg-cool-card-rgb: 22, 20, 32;
  --serif: 'Playfair Display', Georgia, 'Times New Roman', serif;
  --sans: 'Inter', system-ui, sans-serif;
  --ease-out: cubic-bezier(0.16, 1, 0.3, 1);
  --ease-slow: cubic-bezier(0.4, 0, 0.2, 1);
  --header-h: 88px;
}

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

html {
  scroll-behavior: smooth;
  font-size: 16px;
  /*
   * Always reserve the vertical scrollbar gutter so it doesn’t pop in
   * mid-intro and shove the hero image sideways (classic load jump).
   */
  overflow-y: scroll;
  scrollbar-gutter: stable;
}

/* Lenis smooth scroll (v2 item 3) — avoid fighting CSS scroll-behavior */
html.has-smooth-scroll {
  scroll-behavior: auto;
}

html.lenis,
html.lenis body {
  height: auto;
}

html.lenis.lenis-smooth {
  scroll-behavior: auto !important;
}

html.lenis.lenis-smooth [data-lenis-prevent] {
  overscroll-behavior: contain;
}

html.lenis.lenis-stopped {
  overflow: hidden;
}

body {
  font-family: var(--sans);
  font-weight: 300;
  background: var(--bg);
  color: var(--cream);
  line-height: 1.75;
  overflow-x: hidden;
  -webkit-font-smoothing: antialiased;
}

/* Custom cursor — hide system pointer only when JS enables it */
html.has-custom-cursor,
html.has-custom-cursor * {
  cursor: none !important;
}

.cursor-root {
  position: fixed;
  inset: 0;
  z-index: 100000;
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.35s var(--ease-out);
}

.cursor-root.is-visible {
  opacity: 1;
}

.cursor-dot,
.cursor-ring {
  position: fixed;
  top: 0;
  left: 0;
  border-radius: 50%;
  pointer-events: none;
  will-change: transform;
  /* JS sets translate3d; -50% centres on the point */
  margin: 0;
}

.cursor-dot {
  width: 6px;
  height: 6px;
  margin: -3px 0 0 -3px;
  background: var(--gold-light);
  box-shadow: 0 0 10px rgba(var(--gold-light-rgb), 0.45);
  transition:
    width 0.45s var(--ease-out),
    height 0.45s var(--ease-out),
    margin 0.45s var(--ease-out),
    background 0.45s var(--ease-out),
    opacity 0.35s ease;
}

.cursor-ring {
  width: 28px;
  height: 28px;
  margin: -14px 0 0 -14px;
  border: 1px solid rgba(var(--line-rgb), 0.55);
  background: rgba(var(--line-rgb), 0.04);
  box-shadow:
    0 0 0 1px rgba(var(--bg-rgb), 0.15),
    0 0 18px rgba(var(--clay-rgb), 0.12);
  transition:
    width 0.5s cubic-bezier(0.22, 1, 0.36, 1),
    height 0.5s cubic-bezier(0.22, 1, 0.36, 1),
    margin 0.5s cubic-bezier(0.22, 1, 0.36, 1),
    border-color 0.45s ease,
    background 0.45s ease,
    opacity 0.35s ease;
  mix-blend-mode: screen;
}

/* Over links / buttons — soft expand */
.cursor-root.is-hover .cursor-dot {
  width: 4px;
  height: 4px;
  margin: -2px 0 0 -2px;
  opacity: 0.85;
}

.cursor-root.is-hover .cursor-ring {
  width: 44px;
  height: 44px;
  margin: -22px 0 0 -22px;
  border-color: rgba(var(--gold-light-rgb), 0.75);
  background: rgba(var(--line-rgb), 0.08);
  box-shadow:
    0 0 0 1px rgba(var(--bg-rgb), 0.1),
    0 0 28px rgba(var(--line-rgb), 0.18);
}

/* Click press */
.cursor-root.is-down .cursor-ring {
  width: 22px;
  height: 22px;
  margin: -11px 0 0 -11px;
  border-color: rgba(var(--gold-light-rgb), 0.9);
}

.cursor-root.is-down .cursor-dot {
  opacity: 0.5;
}

@media (prefers-reduced-motion: reduce) {
  html.has-custom-cursor,
  html.has-custom-cursor * {
    cursor: auto !important;
  }

  .cursor-root {
    display: none !important;
  }
}

/*
 * Block scroll during intro without removing the html scrollbar track
 * (overflow hidden only on body; gutter stays on html).
 */
body.is-loading,
body.is-intro {
  overflow: hidden;
}

::selection {
  background: var(--gold);
  color: var(--bg);
}

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

/* `display: block` above is an author declaration, so it beats the browser's own
   [hidden] { display: none } no matter what the specificity says — which meant
   the `hidden` attribute did nothing to an image anywhere on this site. The
   thumbnails on the treatment cards are marked hidden because they exist only to
   be read by the accordion preview builder in main.js; they were rendering full
   size inside the cards instead. Restores what the attribute is supposed to do
   without needing a class on every image that uses it. */
img[hidden] { display: none; }

a {
  color: inherit;
  text-decoration: none;
  transition: color 0.5s var(--ease-out);
}

/* ═══ Decorative accents (hand, paint, mandala) ═══ */
.deco {
  pointer-events: none;
  user-select: none;
  display: block;
}

/* Organic life (v2 item 5): slow spin + soft opacity breathe (no transform conflict) */
.deco-mandala {
  position: absolute;
  opacity: 0.42;
  animation:
    mandalaSpin 140s linear infinite,
    mandalaBreathe 9s ease-in-out infinite;
  will-change: transform, opacity;
}

.deco-mandala--sm { width: 140px; }
.deco-mandala--md { width: 220px; }
.deco-mandala--lg { width: 320px; }

@keyframes mandalaSpin {
  from { transform: rotate(0deg); }
  to { transform: rotate(360deg); }
}

@keyframes mandalaBreathe {
  0%,
  100% {
    opacity: 0.36;
  }
  50% {
    opacity: 0.5;
  }
}

/* ═══ Paint stroke — one colour per page ═══
 *
 * The stroke is a single PNG recoloured by filter, so a new colour costs
 * nothing: no second image, no export. What was not free was consistency. The
 * colour used to live on each <img> as a modifier class, which meant a page
 * carrying four strokes had four chances to disagree with itself — and six of
 * the nine pages did. back-pain-massage.html ran bronze, gold, gold, violet
 * down a single page.
 *
 * So the colour now hangs off `data-page` on <body>: one declaration per page,
 * every stroke on that page inherits it, and a section added later cannot
 * introduce a mismatch because there is nothing left to set at the element.
 * Changing a page's colour is one line here.
 *
 * Note the strokes are nearly all `--hero`, and paintBreathe animates opacity
 * on those — so --paint-opacity only really bites on the static ones.
 */
.deco-paint {
  display: block;
  height: 10px;
  width: auto;
  max-width: 200px;
  filter: var(--paint-filter);
  opacity: var(--paint-opacity);
  margin: 1.25rem 0;
}

/* Gold is the brand stroke and the fallback: a page with no data-page, or a
   typo in one, lands here rather than losing its colour entirely. */
body {
  --paint-filter: sepia(0.8) saturate(2.5) hue-rotate(5deg) brightness(1.1);
  --paint-opacity: 0.7;
}

/* Treatments — violet, the accent already carrying the deeper work */
body[data-page="services"] {
  --paint-filter: sepia(0.5) saturate(1.6) hue-rotate(240deg) brightness(0.95);
  --paint-opacity: 0.65;
}

/* The practitioner — bronze, the warmest and most grounded of the set */
body[data-page="about"] {
  --paint-filter: sepia(1) saturate(1.8) hue-rotate(-15deg) brightness(0.85);
  --paint-opacity: 0.65;
}

/* Pain work — teal. Cool on purpose, the same signal the grey studio backdrop
   sends in the Reset photographs: a different category from the candle-lit
   treatments, stated in colour rather than in a claim. */
body[data-page="pain"] {
  --paint-filter: sepia(0.5) saturate(1.4) hue-rotate(135deg) brightness(0.9);
  --paint-opacity: 0.63;
}

/* Couples — rose, warm and the only pink on the site */
body[data-page="couples"] {
  --paint-filter: sepia(0.65) saturate(1.9) hue-rotate(-40deg) brightness(1.05);
  --paint-opacity: 0.66;
}

body[data-page="gallery"] {
  --paint-filter: sepia(0.45) saturate(1.35) hue-rotate(68deg) brightness(0.88);
  --paint-opacity: 0.62;
}

body[data-page="contact"] {
  --paint-filter: sepia(0.2) saturate(0.85) hue-rotate(12deg) brightness(1.18);
  --paint-opacity: 0.68;
}

/* The two utility pages are deliberately the quiet end of the palette — they
   are not part of the journey and should not compete with it. */
body[data-page="privacy"] {
  --paint-filter: sepia(0.45) saturate(1.5) hue-rotate(205deg) brightness(0.9);
  --paint-opacity: 0.6;
}

body[data-page="error"] {
  --paint-filter: sepia(0.3) saturate(0.4) hue-rotate(180deg) brightness(0.85);
  --paint-opacity: 0.55;
}

.deco-paint--center {
  margin-left: auto;
  margin-right: auto;
}

.deco-paint--hero {
  height: 30px;
  max-width: 600px;
  margin: 1.5rem auto 2rem;
  transform-origin: center center;
  animation: paintBreathe 8s ease-in-out infinite;
}

/*
 * One survivor on the home page, 2026-08-11. It carried four of these as
 * heading dividers at 86px each - 30px of art and 56px of margin - and Greg's
 * objection was the right one: repeated under every title it reads as
 * furniture, and it was pushing the treatments and the reviews down the page
 * for nothing.
 *
 * What is left is a single instance under the session film, where the copy
 * column is the taller of the two and the space already existed. The dividers
 * under "Where touch becomes transcendence", "Signature Experiences" and
 * "Moments of Deep Trance" are gone. A fifth position was tried under the
 * Explore Treatments button and rejected on screen - the button already closes
 * that section, and a mark under it read as a stray stroke.
 *
 * Its margin lives further down, next to `.heading-accent .deco-paint--hero`,
 * which is two classes deep and would otherwise win here.
 */

.deco-paint--footer {
  height: 20px;
  max-width: 420px;
  margin: 0 auto;
  transform-origin: center center;
  animation: paintBreathe 9.5s ease-in-out infinite 0.6s;
}

@keyframes paintBreathe {
  0%,
  100% {
    opacity: 0.62;
    transform: scaleX(1) scaleY(1);
  }
  50% {
    opacity: 0.88;
    transform: scaleX(1.035) scaleY(1.08);
  }
}

.deco-hand {
  width: 32px;
  height: auto;
  opacity: 0.8;
  flex-shrink: 0;
  transition: transform 0.7s cubic-bezier(0.16, 1, 0.3, 1), opacity 0.7s ease;
}

.deco-hand--accent {
  width: 44px;
  opacity: 0.7;
}

.menu-item:hover .deco-hand,
.treatment-book:hover .deco-hand,
.btn-with-hand:hover .deco-hand {
  transform: scale(1.15);
  opacity: 1;
}

.heading-accent {
  margin: 1.25rem 0 2rem;
}

.heading-accent--center {
  display: flex;
  justify-content: center;
}

.heading-accent .deco-paint {
  margin: 0;
}

.heading-accent .deco-paint--hero {
  margin: 1.5rem auto 2rem;
}

/*
 * Must be a descendant selector, not a plain `.deco-paint--under-video`. The
 * rule directly above is two classes deep, so a single-class variant loses to
 * it however late it appears in the file - which is what happened on the first
 * attempt here, and the computed margin stayed at the hero 24/32px.
 * Specificity, not source order.
 */
.heading-accent .deco-paint--under-video {
  margin: 1.75rem auto 0;
}

.pullquote .deco-paint {
  margin: 1.5rem auto 0;
}

.btn-with-hand {
  display: inline-flex;
  align-items: center;
  gap: 0.75rem;
}

.btn-with-hand .deco-hand {
  width: 28px;
}

.btn-with-hand.btn-arrow::before {
  display: none;
}

.treatment-book {
  display: inline-flex;
  align-items: center;
  gap: 0.6rem;
  margin-top: 1.5rem;
  font-size: 0.68rem;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--gold);
  transition: color 0.4s;
}

.treatment-book:hover {
  color: var(--cream);
}

/* Hero mandala reveal layer (services): a full-hero, pointer-transparent
   overlay whose opacity fades in as the first step of the top-to-bottom
   cascade. The inner mandala keeps its own spin + breathe (transform +
   opacity), so the fade lives on this wrapper to avoid fighting them.
   Fade only — no slide — so the centered mandala simply blooms in. */
.hero-mandala {
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
}

.hero-mandala.reveal { opacity: 0; transform: none; }
.hero-mandala.reveal.in-view { opacity: 1; transform: none; }

/* Mandala centered behind content */
.deco-mandala--behind {
  top: 50%;
  left: 50%;
  margin-top: -160px;
  margin-left: -160px;
  z-index: 0;
}

/*
 * Scroll-tied décor plane (v2 item 8)
 * Outer rotates a few degrees with scroll; inner mandala still spins slowly.
 */
.mandala-scroll-plane {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 320px;
  height: 320px;
  margin-top: -160px;
  margin-left: -160px;
  z-index: 0;
  pointer-events: none;
  will-change: transform;
  /* transform set by JS — do not animate here */
}

/*
 * Positioning context for the mandala, so it centres on the quote rather than
 * on the section. Added 2026-08-11 for about.html: `.pullquote` is
 * position:relative and its padding is lopsided (48px top, 112px bottom), so a
 * plane at top:50% of the section landed low - visibly below the text, and it
 * pushed the attribution onto the brightest part of the pattern.
 *
 * The blockquote alone is the stage; the cite sits outside it so it cannot drag
 * the centre down. For a three-line quote the middle of the blockquote is the
 * middle of line two, which is what Greg asked for - and it stays true at every
 * breakpoint and any line count, instead of an offset that needs retuning.
 *
 * index.html is deliberately untouched: its quote runs eight lines and the
 * mandala already falls where it should.
 */
.pullquote-stage {
  position: relative;
}

/* The plane is z-index 0 and comes first in the DOM; this keeps the words above
   it explicitly rather than by paint order. */
.pullquote-stage blockquote {
  position: relative;
  z-index: 1;
}

.mandala-scroll-plane .deco-mandala {
  position: relative;
  top: auto;
  left: auto;
  margin: 0;
  width: 100%;
  height: auto;
  max-width: none;
}

/*
 * Renamed from --home 2026-08-11, when about.html took the same treatment.
 * It never had anything to do with the home page - it is the opacity for a
 * mandala sitting behind a pullquote, and the old name would have been actively
 * misleading on the second page that used it.
 *
 * The pattern this belongs to: `.mandala-scroll-plane` + `data-scroll-rotate`
 * wrapping `mandala.png`. That combination now means "pullquote moment" across
 * the site, which is why about.html uses `mandala.png` here rather than the
 * `mandala1.png` already in its own hero - same role, same artwork; different
 * role, different artwork.
 */
.deco-mandala--quote {
  opacity: 0.38;
}

@media (max-width: 768px) {
  .mandala-scroll-plane {
    width: 200px;
    height: 200px;
    margin-top: -100px;
    margin-left: -100px;
  }
}

@media (prefers-reduced-motion: reduce) {
  .mandala-scroll-plane {
    will-change: auto;
    transform: none !important;
  }
}

.deco-mandala--warm {
  filter: sepia(0.35) saturate(1.5) hue-rotate(-8deg) brightness(1.08);
  opacity: 0.4;
}

.deco-mandala--cool {
  filter: saturate(1.7) hue-rotate(30deg) brightness(0.92);
  opacity: 0.38;
}

.pullquote {
  position: relative;
  overflow: hidden;
}

.pullquote .deco-mandala {
  z-index: 0;
}

.pullquote blockquote,
.pullquote cite {
  position: relative;
  z-index: 1;
}

.page-hero,
.section--mandala {
  position: relative;
  overflow: hidden;
}

.page-hero .container,
.section--mandala .container {
  position: relative;
  z-index: 1;
}

.has-deco {
  position: relative;
}

@media (prefers-reduced-motion: reduce) {
  .deco-mandala,
  .deco-mandala--gallery-bg,
  .deco-paint--hero,
  .deco-paint--footer,
  .editorial-deco-img,
  .ornament span {
    animation: none;
  }

  .deco-hand {
    transition: none;
  }

  .menu-item:hover .deco-hand,
  .treatment-book:hover .deco-hand,
  .btn-with-hand:hover .deco-hand {
    transform: none;
  }

  .treatment-visual-media img {
    transition: none;
  }

  .treatment-visual:hover .treatment-visual-media img {
    transform: none;
  }

  .gallery-cell img {
    transition: none;
    filter: brightness(0.85) saturate(0.9);
  }

  .gallery-cell:hover img {
    transform: none;
    filter: brightness(1) saturate(1);
  }
}

@media (max-width: 768px) {
  .deco-mandala--lg,
  .deco-mandala--behind {
    width: 200px;
    margin-top: -100px;
    margin-left: -100px;
  }

  .deco-mandala--md { width: 120px; }

  .deco-hand { width: 28px; }
  .deco-hand--accent { width: 36px; }
}

/*
 * Static film-grain overlay (noise) — sits above most of the page.
 * JS clips it above the footer curtain so the address footer stays plain black
 * (no grain). Privacy bar keeps its own Silk diagonal only.
 */
.grain {
  pointer-events: none;
  position: fixed;
  inset: 0;
  z-index: 9999;
  opacity: 0.03;
  background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
  background-repeat: repeat;
}

/* Preloader — solid curtain over an already-painted hero; fades out as one calm crossfade */
.preloader {
  position: fixed;
  inset: 0;
  z-index: 10000;
  background: var(--bg);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 2rem;
  transition:
    opacity 2.8s cubic-bezier(0.4, 0, 0.2, 1),
    visibility 2.8s cubic-bezier(0.4, 0, 0.2, 1);
}

.preloader.done {
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
}

/* Fast-intro pages (opt-in via body[data-fast-intro]): lift the curtain quickly
   instead of the slow 2.8s crossfade, so content isn't held back behind it. */
body[data-fast-intro] .preloader {
  transition:
    opacity 0.7s cubic-bezier(0.4, 0, 0.2, 1),
    visibility 0.7s cubic-bezier(0.4, 0, 0.2, 1);
}

/*
 * Inner pages: the sigil arrives already whole, it does not draw itself.
 *
 * Greg's call, 2026-09-18. The drawing is 2.1s of ceremony and it is right
 * exactly once, on the home page, where the mark is the first thing anyone
 * sees. Arriving at Contact for the third time it is a wait. The obvious fix —
 * running the same draw faster — is the wrong one on a page selling
 * relaxation: a compressed stroke animation reads hurried, and the site should
 * never look like it is rushing. So nothing moves quickly here; there is
 * simply less to watch. Mark and name fade up over ~0.3s, hold, and the
 * curtain crossfades out in 0.7s (hold is 900ms, in initSiteIntro).
 *
 * The contrast is the point and it only works while the home page keeps the
 * full version — do not "simplify" this by giving every page the same intro.
 */
body[data-fast-intro] .preloader-sigil {
  /* Whole from the first frame: no dash offset to travel, so `sigilDraw` has
     nothing left to animate and the marks simply appear with the fade below.
     The breath starts once the fade has landed. */
  animation: preloaderIn 0.3s var(--ease-out) forwards,
             sigilBreath var(--breath) ease-in-out 0.4s infinite;
  opacity: 0;
}

body[data-fast-intro] .preloader-sigil .sig {
  animation: none;
  stroke-dashoffset: 0;
}

body[data-fast-intro] .preloader-line { animation: preloaderLine 0.4s var(--ease-out) 0.1s forwards; }
body[data-fast-intro] .preloader-logo { animation: preloaderIn 0.3s var(--ease-out) 0.1s forwards; }
body[data-fast-intro] .preloader-text { animation: preloaderIn 0.3s var(--ease-out) 0.2s forwards; }

.preloader-logo {
  height: 72px;
  opacity: 0;
  animation: preloaderIn 1.4s var(--ease-out) 0.25s forwards;
}

.preloader-line {
  width: 0;
  height: 1px;
  background: linear-gradient(90deg, transparent, var(--gold), transparent);
  animation: preloaderLine 1.5s var(--ease-out) 0.7s forwards;
}

.preloader-text {
  font-family: var(--serif);
  font-size: 0.7rem;
  letter-spacing: 0.45em;
  text-transform: uppercase;
  color: rgb(var(--clay-rgb));
  opacity: 0;
  animation: preloaderIn 1.2s var(--ease-out) 1.1s forwards;
}

@keyframes preloaderIn {
  to { opacity: 1; }
}

/*
 * The positioning line under the name, home page only.
 *
 * Deliberately quieter than the name above it and in the sans face, not the
 * serif: the two are doing different jobs, and setting both in Playfair at the
 * same tracking would read as one broken heading rather than a name and a
 * description.
 *
 * It arrives 0.5s after the name — late enough that the eye reads "Trance
 * Ritual" first and the line answers the question the name raises, rather than
 * both landing at once and competing.
 */
.preloader-tagline {
  /* Negative, because .preloader is a column flex with `gap: 2rem` and that gap
     is right for separating the sigil, the line and the name — but not for a
     descriptor that belongs to the name directly above it. This pulls the pair
     back together without changing the spacing of anything else. */
  margin-top: -1.15rem;
  font-family: var(--sans);
  font-size: 0.6rem;
  font-weight: 300;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  /* Clay at full strength, not bronze. Greg could not read this outdoors, and
     raising the opacity alone could not have fixed it: bronze on this background
     tops out at 4.13:1 even at 100%, below the 4.5:1 needed for small text. Clay
     is the next warm token up and reaches 5.31:1, which is legible in sunlight
     without introducing a colour the site does not already use. */
  color: rgb(var(--clay-rgb));
  text-align: center;
  /* Three words shorter than the first version, so it now holds one line
     everywhere including a 320px phone — which is why the width cap and the
     phone override this rule used to need are both gone. */
  padding: 0 1.5rem;
  line-height: 1.9;
  opacity: 0;
  animation: preloaderIn 1.2s var(--ease-out) 1.6s forwards;
}

@keyframes preloaderLine {
  to { width: 120px; }
}

/* Typography */
.serif { font-family: var(--serif); }
.italic { font-style: italic; }

h1, h2, h3, h4 {
  font-family: var(--serif);
  font-weight: 400;
  line-height: 1.1;
  letter-spacing: -0.01em;
}

.eyebrow {
  font-size: 0.65rem;
  font-weight: 500;
  letter-spacing: 0.38em;
  text-transform: uppercase;
  color: var(--gold);
}

.display-xl {
  font-size: clamp(3.5rem, 11vw, 8.5rem);
  line-height: 0.95;
  letter-spacing: -0.02em;
}

.display-lg {
  font-size: clamp(2.4rem, 5.5vw, 4.5rem);
  line-height: 1.05;
}

.display-md {
  font-size: clamp(1.8rem, 3.5vw, 3rem);
  line-height: 1.15;
}

.lead {
  font-size: clamp(1.05rem, 1.8vw, 1.25rem);
  line-height: 1.85;
  color: var(--cream-soft);
  font-weight: 300;
}

.body-text {
  color: var(--cream-soft);
  font-weight: 300;
}

/* Ornament */
.ornament {
  display: flex;
  align-items: center;
  gap: 1.5rem;
  margin: 2rem 0;
}

.ornament::before,
.ornament::after {
  content: '';
  flex: 1;
  height: 1px;
  background: linear-gradient(90deg, transparent, var(--gold-dim), transparent);
}

.ornament span {
  font-size: 0.5rem;
  color: rgb(var(--clay-rgb));
  letter-spacing: 0.3em;
  display: inline-block;
  animation: diamondPulse 5.5s ease-in-out infinite;
}

@keyframes diamondPulse {
  0%,
  100% {
    opacity: 0.75;
    transform: scale(1);
  }
  50% {
    opacity: 1;
    transform: scale(1.12);
  }
}

/* Layout */
.container {
  width: 100%;
  max-width: 1320px;
  margin: 0 auto;
  padding: 0 clamp(1.5rem, 4vw, 4rem);
}

.container-narrow {
  max-width: 780px;
}

.section {
  /* Preferred 1vw → effectively ~min 5rem on real screens (tighter section gaps) */
  padding: clamp(5rem, 1vw, 10rem) 0;
  position: relative;
}

.section--before-cta {
  padding-bottom: clamp(2.5rem, 6vw, 4rem);
}

.section--cta {
  padding-top: clamp(2rem, 5vw, 3rem);
  padding-bottom: clamp(1.25rem, 3vw, 2rem);
}

/* Header — hidden during intro, then soft fade over the hero (no hard black bar) */
.header {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 1000;
  opacity: 0;
  transform: translate3d(0, -10px, 0);
  pointer-events: none;
  transition:
    opacity 2.4s var(--ease-out) 0.55s,
    transform 2.4s var(--ease-out) 0.55s,
    background 0.6s var(--ease-out),
    border-color 0.6s,
    padding 0.6s;
  border-bottom: 1px solid transparent;
  background: transparent;
}

.header.is-revealed {
  opacity: 1;
  transform: translate3d(0, 0, 0);
  pointer-events: auto;
}

/*
 * Scrolled nav: clean deep glass + soft mouse spotlight (--spot-x / --spot-y).
 * No hatch pattern. Transparent until .scrolled.
 */
.header.scrolled {
  background-color: transparent;
  background-image:
    radial-gradient(
      480px circle at var(--spot-x, 50%) var(--spot-y, 50%),
      rgba(var(--gold-light-rgb), 0.07) 0%,
      rgba(var(--line-rgb), 0.035) 32%,
      transparent 62%
    ),
    linear-gradient(
      180deg,
      rgba(var(--bg-rgb), 0.94) 0%,
      rgb(var(--bg-cool-soft-rgb)) 100%
    );
  backdrop-filter: blur(18px);
  -webkit-backdrop-filter: blur(18px);
  border-bottom-color: rgba(var(--line-rgb), 0.1);
  transition:
    opacity 2.4s var(--ease-out) 0.55s,
    transform 2.4s var(--ease-out) 0.55s,
    background-color 0.6s var(--ease-out),
    border-color 0.6s,
    padding 0.6s;
}

.header-inner {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  height: var(--header-h);
  padding: 0 clamp(1.5rem, 4vw, 4rem);
  max-width: 1440px;
  margin: 0 auto;
}

.logo-link {
  display: flex;
  align-items: center;
  gap: 1rem;
  justify-self: start;
}

.logo-link--split {
  gap: 0.7rem;
}

.logo-img {
  height: 44px;
  width: auto;
  filter: brightness(1.05);
  transition: opacity 0.5s;
}

.logo-text {
  font-family: var(--serif);
  font-size: 1.05rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--cream);
}

.nav {
  display: flex;
  gap: clamp(1.5rem, 3vw, 2.75rem);
  justify-self: center;
}

.nav a {
  font-size: 0.68rem;
  font-weight: 400;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--cream-muted);
  position: relative;
  padding: 0.25rem 0;
}

.nav a::after {
  content: '';
  position: absolute;
  bottom: 0;
  left: 50%;
  width: 0;
  height: 1px;
  background: var(--gold);
  transition: width 0.5s var(--ease-out), left 0.5s var(--ease-out);
}

.nav a:hover,
.nav a.active {
  color: var(--cream);
}

.nav a:hover::after,
.nav a.active::after {
  width: 100%;
  left: 0;
}

/* ═══ Services submenu ═══
 *
 * The two SEO pages live under Services, which is their real parent. The nav
 * is on every page, so this gives each of them a site-wide internal link
 * instead of the single one they had — which is the point of an SEO page.
 *
 * No JavaScript. Hover opens it, and :focus-within keeps it open for keyboard
 * users, so tabbing through the children works without a script. "Services"
 * itself stays a link to the treatments page: the parent is never sacrificed
 * to become a menu button.
 */
.nav-item--has-sub {
  position: relative;
  display: flex;
  align-items: center;
}

.nav-sub {
  position: absolute;
  top: 100%;
  left: 50%;
  transform: translateX(-50%) translateY(6px);
  display: flex;
  flex-direction: column;
  gap: 0.9rem;
  min-width: 220px;
  padding: 1.15rem 1.4rem;
  background: linear-gradient(
    180deg,
    rgba(var(--bg-rgb), 0.97) 0%,
    rgb(var(--bg-cool-soft-rgb)) 100%
  );
  border: 1px solid rgba(var(--line-rgb), 0.16);
  box-shadow: 0 24px 48px rgba(var(--shadow-rgb), 0.5);
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transition: opacity 0.35s var(--ease-out), transform 0.35s var(--ease-out), visibility 0.35s;
  z-index: 1001;
}

/* No hover bridge needed: at rest the panel sits flush at top:100%, so the
   pointer never crosses a gap. An earlier bridge element reached up over the
   trigger and made the bottom of the Services link unclickable. */
.nav-item--has-sub:hover .nav-sub,
.nav-item--has-sub:focus-within .nav-sub {
  opacity: 1;
  visibility: visible;
  pointer-events: auto;
  transform: translateX(-50%) translateY(0);
}

.nav-sub a {
  white-space: nowrap;
  text-align: center;
  font-size: 0.62rem;
}

.header-right {
  display: flex;
  align-items: center;
  gap: 1rem;
  justify-self: end;
}

.header-contact-block {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.35rem;
  width: max-content;
}

.header-contact-block .lang-flags {
  justify-content: center;
}

.lang-flags {
  display: flex;
  align-items: center;
  gap: 0.3rem;
  flex-wrap: nowrap;
}

/* Nav flag row doubles as a click-to-call link (same as the phone number) */
.lang-flags--call {
  text-decoration: none;
  cursor: pointer;
}

.lang-flags--call .lang-flag { cursor: pointer; }

.lang-flag {
  display: inline-flex;
  flex-shrink: 0;
  opacity: 0.75;
  transition: opacity 0.4s var(--ease-out), transform 0.4s var(--ease-out);
  cursor: default;
}

.lang-flag:hover {
  opacity: 1;
  transform: scale(1.1);
}

.lang-flag img {
  width: 16px;
  height: 16px;
  display: block;
}

.header-phone {
  font-size: 0.84rem;
  letter-spacing: 0.07em;
  color: var(--cream-muted);
  line-height: 1;
  transition: color 0.4s;
  white-space: nowrap;
  text-align: center;
  width: 100%;
}

.header-phone:hover { color: var(--gold); }

.btn-wa {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  width: 34px;
  height: 34px;
  padding: 0;
  border: 1px solid rgba(var(--line-rgb), 0.2);
  border-radius: 50%;
  color: var(--cream-muted);
  transition: all 0.4s var(--ease-out);
}

.btn-wa:hover {
  background: #25D366;
  border-color: #25D366;
  color: #fff;
}

.btn-wa svg {
  width: 17px;
  height: 17px;
  fill: currentColor;
}

.btn-wa--green,
.contact-detail a.btn-wa--green {
  width: 26px;
  height: 26px;
  background: #3aa860;
  border-color: #3aa860;
  color: #fff;
}

.btn-wa--green svg {
  width: 13px;
  height: 13px;
}

.btn-wa--green:hover,
.contact-detail a.btn-wa--green:hover {
  background: #3aa860;
  border-color: #3aa860;
  color: #fff;
}

.menu-toggle {
  display: none;
  background: none;
  border: none;
  cursor: pointer;
  padding: 0.5rem;
}

.menu-toggle span {
  display: block;
  width: 22px;
  height: 1px;
  background: var(--gold);
  margin: 6px 0;
  transition: 0.4s var(--ease-out);
}

/* Buttons */
.btn {
  display: inline-flex;
  align-items: center;
  gap: 1rem;
  font-size: 0.68rem;
  font-weight: 500;
  letter-spacing: 0.28em;
  text-transform: uppercase;
  color: var(--gold);
  padding: 1.1rem 0;
  border: none;
  background: none;
  cursor: pointer;
  position: relative;
  transition: color 0.4s;
}

.btn::after {
  content: '';
  position: absolute;
  bottom: 0;
  left: 0;
  width: 100%;
  height: 1px;
  background: var(--gold);
  transform: scaleX(0.3);
  transform-origin: left;
  transition: transform 0.6s var(--ease-out);
}

.btn:hover::after { transform: scaleX(1); }
.btn:hover { color: var(--cream); }

.btn-solid {
  position: relative;
  padding: 1.15rem 2.8rem;
  border: 1px solid rgba(var(--gold-rgb), 0.55);
  letter-spacing: 0.22em;
  transition: background 0.5s var(--ease-out), color 0.5s, border-color 0.5s;
}

.btn-solid::after { display: none; }

/*
 * Travelling border light, after the Aceternity hover-border-gradient. That one
 * is React, Tailwind and Framer Motion, so none of it transfers to a static
 * site — but the effect is only a conic gradient rotating behind the element
 * with everything except a 1px ring masked away, which plain CSS does.
 *
 * `mask-composite: exclude` is what cuts the middle out: two identical masks,
 * one clipped to the content box, and the difference between them is the ring.
 * That keeps the button transparent, which matters because several of these sit
 * over photographs and a filled centre would punch a dark rectangle into one.
 *
 * The rotation animates a custom property, so it needs @property to know the
 * value is an angle. Where that is unsupported the gradient simply sits still —
 * a lit border rather than a travelling one, which is a fine place to land.
 *
 * Hover only. Nothing on this site animates on a loop except the single pulse
 * on the back-pain link, and the reason holds here too: constant motion stops
 * being a signal and starts being decoration.
 */
@property --btn-angle {
  syntax: '<angle>';
  initial-value: 0deg;
  inherits: false;
}

.btn-solid:not(.btn-arrow)::before {
  content: '';
  position: absolute;
  /* Sits ON the border, not outside it. At inset: -1px the ring lands next to
     the button's own border and reads as a second, doubled line. */
  inset: 0;
  padding: 1px;
  pointer-events: none;
  background: conic-gradient(
    from var(--btn-angle),
    rgba(var(--gold-rgb), 0.18) 0deg 190deg,
    rgba(var(--gold-rgb), 0.7) 275deg,
    #F3E4CC 320deg,
    rgba(var(--gold-rgb), 0.7) 350deg,
    rgba(var(--gold-rgb), 0.18) 360deg
  );
  /* `linear-gradient(#000 0 0)` is the shorthand normally quoted for this, and
     it does not work here: both stops land at 0px and the mask computes to a
     zero-length gradient, which hides the whole pseudo-element rather than
     cutting a ring out of it. Two explicit colour stops paint solid. */
  -webkit-mask: linear-gradient(#000, #000) content-box, linear-gradient(#000, #000);
          mask: linear-gradient(#000, #000) content-box, linear-gradient(#000, #000);
  -webkit-mask-composite: xor;
          mask-composite: exclude;
  opacity: 0;
  transition: opacity 0.35s var(--ease-out);
}

@keyframes btn-border-travel {
  to { --btn-angle: 360deg; }
}

.btn-solid:hover:not(.btn-arrow)::before,
.btn-solid:focus-visible:not(.btn-arrow)::before {
  opacity: 1;
  animation: btn-border-travel 1.4s linear infinite;
}

/*
 * The fill drops from solid gold to a wash. A filled button hides its own
 * border, so the two effects cannot both be at full strength — and the wash is
 * what lets the travelling light read. The text lifting to cream keeps the
 * hover as legible a state change as the fill was.
 */
.btn-solid:hover,
.btn-solid:focus-visible {
  background: rgba(var(--gold-rgb), 0.12);
  /* The travelling ring takes over the border while hovered, so the static one
     goes transparent — otherwise the bright arc has a solid line under it and
     the movement barely reads. */
  border-color: transparent;
  color: var(--cream);
}

@media (prefers-reduced-motion: reduce) {
  .btn-solid:hover:not(.btn-arrow)::before,
  .btn-solid:focus-visible:not(.btn-arrow)::before { animation: none; }
}

.btn-arrow::before {
  content: '→';
  font-size: 1rem;
  transition: transform 0.4s var(--ease-out);
}

.btn:hover.btn-arrow::before { transform: translateX(4px); }

/*
 * A slow gold halo that expands and fades, for the rare link that leaves the
 * page the reader is on. Two decisions worth keeping:
 *
 * It animates box-shadow, not transform. A button that scales on a loop reads
 * as an advert, and this site is selling calm — the halo draws the eye without
 * anything actually moving.
 *
 * 4.5s is breath tempo, the same rhythm the atmosphere glows drift at. On a
 * site called after a pulse that is the one animation that can justify itself,
 * which is also why it should stay rare: a second one on the same page would
 * turn a signal into decoration.
 *
 * ::after and ::before are both already taken on .btn (the underline sweep and
 * the arrow), which is why this had to be a box-shadow rather than a ring
 * element.
 */
@keyframes btn-pulse-halo {
  0%   { box-shadow: 0 0 0 0 rgba(var(--gold-rgb), 0.32); }
  70%  { box-shadow: 0 0 0 14px rgba(var(--gold-rgb), 0); }
  100% { box-shadow: 0 0 0 0 rgba(var(--gold-rgb), 0); }
}

.btn-pulse {
  animation: btn-pulse-halo 4.5s var(--ease-out) infinite;
}

/* Stops on hover: once the pointer is on it the reader has been reached, and
   the halo competes with the fill transition underneath it. */
.btn-pulse:hover {
  animation: none;
}

@media (prefers-reduced-motion: reduce) {
  .btn-pulse { animation: none; }
}

/* ═══ HERO ═══ */
/* Full-bleed under transparent header — no black strip above the image */
.hero {
  position: relative;
  min-height: 100vh;
  min-height: 100dvh;
  display: flex;
  align-items: flex-end;
  padding-bottom: clamp(4rem, 10vh, 8rem);
  margin-top: 0;
  overflow: hidden;
}

.hero-slider {
  position: absolute;
  inset: -8% 0;
  height: 116%;
  will-change: transform;
  /*
   * Image is painted under the preloader from the start so the curtain
   * lift = true crossfade (logo dissolves as the photo emerges).
   */
  opacity: 1;
}

.hero-slide {
  position: absolute;
  inset: 0;
  background-size: cover;
  background-position: center;
  opacity: 0;
  z-index: 0;
  transition: opacity 2.6s ease-in-out;
  transform: scale(1.03);
  will-change: opacity, transform;
}

/* Ken-burns only after media has entered (and only while active) */
.hero-slide.active {
  opacity: 1;
  z-index: 1;
}

.hero.is-media-in .hero-slide.active:not(.is-exiting) {
  animation: heroKen 7s linear forwards;
}

.hero-slide.is-exiting {
  z-index: 0;
  opacity: 0;
  animation: none;
  /* transform is frozen inline at the last ken-burns scale */
}

@keyframes heroKen {
  from { transform: scale(1.03); }
  to { transform: scale(1.09); }
}

.hero-dots {
  position: absolute;
  bottom: clamp(3rem, 8vh, 6rem);
  left: 50%;
  transform: translateX(-50%);
  z-index: 3;
  display: flex;
  align-items: center;
  gap: 0.75rem;
}

/*
 * Visual bar stays 2px thin; the button itself is taller/wider so the
 * click target is easy without changing the look.
 */
.hero-dot {
  position: relative;
  width: 32px;
  height: 44px;
  padding: 0;
  border: none;
  background: transparent;
  cursor: pointer;
  transition: width 0.5s var(--ease-out);
}

.hero-dot::after {
  content: '';
  position: absolute;
  left: 0;
  right: 0;
  top: 50%;
  height: 2px;
  margin-top: -1px;
  background: rgba(var(--ink-rgb), 0.25);
  transition: background 0.5s var(--ease-out);
  pointer-events: none;
}

.hero-dot:hover::after {
  background: rgba(var(--line-rgb), 0.6);
}

.hero-dot.active {
  width: 48px;
}

.hero-dot.active::after {
  background: var(--gold);
}

.hero-vignette {
  position: absolute;
  inset: 0;
  background:
    linear-gradient(to top, var(--bg) 0%, transparent 35%),
    linear-gradient(to right, rgba(var(--bg-rgb), 0.5) 0%, transparent 50%),
    radial-gradient(ellipse at center, transparent 40%, rgba(var(--bg-rgb), 0.4) 100%);
  opacity: 1;
}

.hero-content {
  position: relative;
  z-index: 2;
  width: 100%;
  padding: 0 clamp(1.5rem, 4vw, 4rem);
  max-width: 1440px;
  margin: 0 auto;
}

.hero-eyebrow {
  margin-bottom: 1.5rem;
}

.hero-title {
  margin-bottom: 2rem;
}

.hero-title .line {
  display: block;
  overflow: hidden;
}

.hero-title .word {
  display: inline-block;
  font-style: italic;
  color: var(--gold-light);
}

.hero-lead {
  max-width: 520px;
  margin-bottom: 3rem;
}

.hero-scroll {
  position: absolute;
  right: clamp(1.5rem, 4vw, 4rem);
  bottom: clamp(3rem, 8vh, 6rem);
  z-index: 2;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 1rem;
}

.hero-scroll span {
  font-size: 0.55rem;
  letter-spacing: 0.3em;
  text-transform: uppercase;
  /* Clay, not bronze: bronze reaches only 4.13:1 here and small text needs 4.5.
     Same swap as the preloader line. Clay at full strength measures 5.31:1. */
  color: rgb(var(--clay-rgb));
  writing-mode: vertical-rl;
}

.hero-scroll .line-anim {
  width: 1px;
  height: 60px;
  background: linear-gradient(to bottom, var(--gold), transparent);
  opacity: 0.5;
  transform-origin: top center;
}

/* Hero entrance + polish (v2 item 4) */
.hero-anim {
  opacity: 0;
  transform: translate3d(0, 1.75rem, 0);
  transition:
    opacity 1.05s var(--ease-out),
    transform 1.15s var(--ease-out);
  transition-delay: 0s;
}

.hero-title .line.hero-anim {
  overflow: hidden;
}

/* Text/UI arrive mid–crossfade — slightly longer, calmer stagger */
.hero.is-ready .hero-eyebrow.hero-anim {
  opacity: 1;
  transform: none;
  transition-delay: 0.15s;
}

.hero.is-ready .hero-title .line.hero-anim:nth-child(1) {
  opacity: 1;
  transform: none;
  transition-delay: 0.35s;
}

.hero.is-ready .hero-title .line.hero-anim:nth-child(2) {
  opacity: 1;
  transform: none;
  transition-delay: 0.55s;
}

.hero.is-ready .hero-lead.hero-anim {
  opacity: 1;
  transform: none;
  transition-delay: 0.75s;
}

.hero.is-ready .hero-cta.hero-anim {
  opacity: 1;
  transform: none;
  transition-delay: 0.95s;
}

.hero-dots.hero-anim {
  /* keep horizontal centering while animating in */
  transform: translate(-50%, 1.75rem);
}

.hero.is-ready .hero-dots.hero-anim {
  opacity: 1;
  transform: translate(-50%, 0);
  transition-delay: 1.15s;
}

.hero.is-ready .hero-scroll.hero-anim {
  opacity: 1;
  transform: none;
  transition-delay: 1.35s;
}

.hero.is-ready .hero-scroll .line-anim {
  animation: heroScrollLine 2.4s var(--ease-out) 1.6s infinite;
}

@keyframes heroScrollLine {
  0% {
    transform: scaleY(0.35);
    opacity: 0.25;
  }
  45% {
    transform: scaleY(1);
    opacity: 0.65;
  }
  100% {
    transform: scaleY(0.35);
    opacity: 0.25;
  }
}

@media (prefers-reduced-motion: reduce) {
  .preloader {
    transition: opacity 0.4s ease, visibility 0.4s;
  }

  .header {
    opacity: 1;
    transform: none;
    pointer-events: auto;
    transition: background 0.3s ease, border-color 0.3s;
  }

  .hero-slider {
    inset: 0;
    height: 100%;
    will-change: auto;
    transform: none !important;
    opacity: 1;
  }

  .hero-vignette {
    opacity: 1;
  }

  .hero-slide,
  .hero-slide.active,
  .hero-slide.is-exiting {
    transform: none !important;
    animation: none;
  }

  .hero-anim {
    opacity: 1;
    transform: none;
    transition: none;
  }

  .hero-dots.hero-anim,
  .hero.is-ready .hero-dots.hero-anim {
    transform: translateX(-50%);
  }

  .hero.is-ready .hero-scroll .line-anim {
    animation: none;
  }
}

/* ═══ EDITORIAL INTRO ═══ */
.editorial {
  display: grid;
  grid-template-columns: minmax(160px, 0.4fr) 1fr;
  gap: clamp(2rem, 5vw, 5rem);
  align-items: stretch;
}

/*
 * min-height reduced 2026-08-11, from clamp(340px, 38vw, 540px).
 *
 * The old floor resolved to 540px at 1440, and `.editorial` uses
 * `align-items: stretch`, so the decorative column was setting the height of
 * the whole section - the text column needs 397px and was being stretched to
 * match, leaving 143px of empty page below the Explore Treatments button and
 * pushing The Practitioner down for nothing. The swirl itself only renders
 * 333px tall, so even the decoration never wanted the extra space.
 *
 * Now the content sets the height and the decoration fits inside it. Kept above
 * the image's own rendered height so the swirl is never the thing being
 * squeezed; below the text column so it never dictates again.
 */
.editorial-deco {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: clamp(260px, 24vw, 360px);
}

.editorial-deco-img {
  position: relative;
  z-index: 1;
  width: 115%;
  max-width: 420px;
  max-height: clamp(340px, 40vw, 560px);
  margin: 0 auto;
  object-fit: contain;
  /* Soft float + 1–2° drift — “alive in the wind” */
  animation: ornamentDrift 11s ease-in-out infinite;
  will-change: transform;
  filter: grayscale(1) sepia(1) saturate(0.45) hue-rotate(8deg) brightness(0.82) contrast(0.95);
  -webkit-mask-image: linear-gradient(
    168deg,
    rgba(0, 0, 0, 0.38) 0%,
    rgba(0, 0, 0, 0.22) 40%,
    rgba(0, 0, 0, 0.1) 72%,
    rgba(0, 0, 0, 0.03) 100%
  );
  mask-image: linear-gradient(
    168deg,
    rgba(0, 0, 0, 0.38) 0%,
    rgba(0, 0, 0, 0.22) 40%,
    rgba(0, 0, 0, 0.1) 72%,
    rgba(0, 0, 0, 0.03) 100%
  );
}

/* Logo used as the editorial watermark: bigger, monochromatic-gold,
   and faded evenly from the centre (it's a symmetric emblem, not the
   asymmetric filigree the diagonal fade was tuned for). */
.editorial-deco-img--logo {
  width: 100%;
  max-width: 520px;
  max-height: clamp(360px, 44vw, 600px);
  /* Single knob for tuning visibility in dev tools — try 0.3–0.7.
     This stacks on top of the mask fade + filter below. */
  opacity: 0.4;
  filter: grayscale(1) sepia(1) saturate(0.5) hue-rotate(6deg) brightness(0.86) contrast(0.92);
  -webkit-mask-image: radial-gradient(
    ellipse at center,
    rgba(0, 0, 0, 0.42) 0%,
    rgba(0, 0, 0, 0.3) 45%,
    rgba(0, 0, 0, 0.12) 75%,
    rgba(0, 0, 0, 0) 100%
  );
  mask-image: radial-gradient(
    ellipse at center,
    rgba(0, 0, 0, 0.42) 0%,
    rgba(0, 0, 0, 0.3) 45%,
    rgba(0, 0, 0, 0.12) 75%,
    rgba(0, 0, 0, 0) 100%
  );
}

@keyframes ornamentDrift {
  0%,
  100% {
    transform: translate3d(0, 0, 0) rotate(0deg);
  }
  40% {
    transform: translate3d(0, -10px, 0) rotate(1.4deg);
  }
  70% {
    transform: translate3d(0, -4px, 0) rotate(-0.8deg);
  }
}

.editorial-body h2 {
  margin-bottom: 2rem;
}

.editorial-body p + p {
  margin-top: 1.5rem;
}

/* Pull quote */
.pullquote {
  padding: clamp(4rem, 8vw, 7rem) 0;
  text-align: center;
  position: relative;
}

.pullquote blockquote {
  font-family: var(--serif);
  font-size: clamp(1.6rem, 3.5vw, 2.8rem);
  font-style: italic;
  font-weight: 400;
  line-height: 1.45;
  color: var(--cream);
  max-width: 900px;
  margin: 0 auto;
}

/*
 * Longer quotes step down a size. The display setting is tuned for the ~90
 * characters on the home page; at nearly twice that it fills the screen and
 * stops reading as a quote. Slightly tighter leading too, since more lines
 * need less air between them, not more.
 */
.pullquote--long blockquote {
  font-size: clamp(1.35rem, 2.4vw, 2rem);
  line-height: 1.5;
  max-width: 820px;
}

/*
 * The mandala is centred on the section, so at four lines the attribution
 * landed on the brightest part of the pattern and became unreadable. Moving
 * the cite to the right edge clears it by construction — the mandala is 320px
 * wide about the centre, the container is far wider — rather than by nudging
 * offsets that would need retuning at every breakpoint.
 */
.pullquote--long cite {
  text-align: right;
  margin-top: 2.25rem;
}

/*
 * A whole review rather than a line. At ~450 characters this is five times the
 * home-page quote it replaced, so --long is not a small enough step - the
 * display size that makes one sentence land turns five into a wall of italic.
 *
 * Centring also stops working past three or four lines: the eye has to hunt for
 * a new start position on every line, and that cost lands hardest on the ES /
 * DE / PL readers this site is built for. So it is left-aligned and held to a
 * reading measure, with the block itself still centred in the section.
 */
.pullquote--review blockquote {
  font-size: clamp(1.1rem, 1.7vw, 1.45rem);
  line-height: 1.7;
  text-align: left;
}

.pullquote--review cite {
  display: block;
  margin: 2rem auto 0;
  text-align: right;
}

/*
 * Binding the review to The Practitioner, 2026-08-11 — Greg's read, and it is
 * right: the quote is about the practitioner, so it should not read as its own
 * floating section between two others.
 *
 * The gaps were doing the opposite. `.section` carries 80px top and bottom
 * (the clamp never resolves above its 5rem floor at any real viewport), and
 * `.pullquote` carries 112px, so the quote sat 192px below the heading it
 * belongs to and only 160px below the section it does not. Proximity is the
 * strongest grouping signal there is, and it was pointing the wrong way.
 *
 * Now: 32px above the quote, 88px between intro and practitioner. The quote
 * reads as the closing line of the practitioner block, which is what it is.
 * Scoped to these three sections — every other `.section` on the site keeps
 * its rhythm.
 */
/*
 * Named `--home-intro`, not `--intro`. `.section--intro` already exists further
 * down this file for back-pain-massage.html and couples-massage.html, and it
 * carries its own padding plus a `.section--intro .lead { max-width: 890px }`.
 * Reusing the name silently restyled the home page. Caught by measuring the
 * computed padding rather than trusting the rule that was written.
 */
.section--home-intro {
  padding-bottom: clamp(1.5rem, 3vw, 2.5rem);
}

.section--practitioner {
  padding-top: clamp(2rem, 4vw, 3rem);
  padding-bottom: 0;
}

.pullquote--review {
  padding-top: clamp(1.25rem, 2.5vw, 2rem);
  padding-bottom: clamp(4rem, 6vw, 5.5rem);
}

/*
 * No max-width here on purpose. `.container-narrow` caps at 780px and
 * `.container` takes clamp(1.5rem, 4vw, 4rem) off each side, so the measure is
 * already 666px at 1440 and never exceeds ~700px at any viewport - about 64
 * characters a line, which is where prose reads best anyway. A max-width on the
 * blockquote would be dead code that looks load-bearing.
 *
 * Worth knowing if this is ever retuned: the measure is widest around a 1000px
 * viewport and gets *narrower* on a large monitor, because the padding keeps
 * growing until it caps while the 780px ceiling never moves.
 */

/* ═══ Quote over a parallax band ═══
 *
 * Same words, different ground: an anatomical plate instead of the mandala, so
 * the tower metaphor and the body's own architecture say the same thing at
 * once. The image carries the attention, so the type steps down rather than up
 * — smaller than the pullquote and in gold rather than cream, which separates
 * it from every other body of text on the site without introducing a colour
 * the palette does not already own.
 */
.band--quote {
  min-height: min(62vh, 560px);
}

/* Darker than the standard band: this one sits under text that has to be read
   at length, not a three-word heading */
.band--quote .band-overlay {
  background:
    radial-gradient(ellipse 80% 70% at 50% 50%, rgba(var(--bg-rgb), 0.72) 0%, rgba(var(--bg-rgb), 0.88) 100%);
}

.band--quote .band-content-inner {
  margin: 0 auto;
  text-align: center;
}

.band--quote blockquote {
  font-family: var(--serif);
  font-size: clamp(1.1rem, 1.9vw, 1.6rem);
  font-style: italic;
  font-weight: 400;
  line-height: 1.55;
  color: var(--gold-light);
  max-width: 760px;
  margin: 0 auto;
}

/* Centred under the quote. It was aligned to the quote's own right edge, but
   this quote wraps to three lines and the last line ends short — so the right
   edge the cite hung from was an invisible one, and it drifted with every
   resize. Centre is the only anchor that does not move. */
.band--quote cite {
  display: block;
  max-width: 760px;
  margin: 1.5rem auto 0;
  text-align: center;
  font-family: var(--sans);
  font-size: 0.62rem;
  font-style: normal;
  letter-spacing: 0.3em;
  text-transform: uppercase;
  color: var(--gold);
}

.pullquote cite {
  display: block;
  margin-top: 2rem;
  font-family: var(--sans);
  font-size: 0.65rem;
  font-style: normal;
  letter-spacing: 0.3em;
  text-transform: uppercase;
  color: rgb(var(--clay-rgb));
}

.section--before-quote {
  padding-bottom: clamp(1.5rem, 3vw, 2.5rem);
}

.pullquote--framed {
  padding: clamp(1rem, 2vw, 1.75rem) 0 clamp(2rem, 4vw, 3rem);
}

.pullquote-frame {
  position: relative;
  padding: clamp(1.35rem, 2.2vw, 1.75rem) clamp(1.25rem, 2.5vw, 1.75rem);
  border: 1px solid rgba(var(--line-rgb), 0.22);
  /* Cool bluish dark — same principle as home cards / footer */
  background:
    linear-gradient(
      165deg,
      rgba(var(--line-rgb), 0.07) 0%,
      rgba(var(--bg-cool-card-rgb), 0.55) 45%,
      rgba(var(--bg-cool-rgb), 0.72) 100%
    );
}

.pullquote-frame-inner {
  display: flex;
  flex-direction: column;
  align-items: center;
}

.pullquote-frame::before,
.pullquote-frame::after {
  content: '';
  position: absolute;
  width: 28px;
  height: 28px;
  border-color: var(--gold);
  border-style: solid;
  opacity: 0.45;
}

.pullquote-frame::before {
  top: -1px;
  left: -1px;
  border-width: 1px 0 0 1px;
}

.pullquote-frame::after {
  bottom: -1px;
  right: -1px;
  border-width: 0 1px 1px 0;
}

.pullquote--framed .pullquote-frame blockquote {
  margin: 0;
  font-size: clamp(1.05rem, 1.6vw, 1.35rem);
  line-height: 1.65;
  max-width: 36em;
}

.pullquote-frame .ornament {
  justify-content: center;
  width: 100%;
  margin: 0 0 0.85rem;
}

/* Full bleed band */
.band {
  position: relative;
  min-height: 70vh;
  display: flex;
  align-items: center;
  overflow: hidden;
}

.band-media {
  position: absolute;
  inset: 0;
}

.band-media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.band-overlay {
  position: absolute;
  inset: 0;
  background: rgba(var(--bg-rgb), 0.65);
}

.band-content {
  position: relative;
  z-index: 2;
  width: 100%;
  padding: clamp(4rem, 10vw, 8rem) clamp(1.5rem, 4vw, 4rem);
  max-width: 1440px;
  margin: 0 auto;
}

/* Multi-plane parallax (v2 item 2) — taller media so translate never shows edges */
.band--parallax .band-media {
  inset: auto;
  top: -18%;
  left: 0;
  right: 0;
  height: 136%;
  will-change: transform;
}

.band--parallax .band-media img {
  transform: scale(1.06);
  transform-origin: center center;
}

.band--parallax .band-content {
  will-change: transform;
}

.band--parallax .band-content-inner {
  max-width: 100%;
}

/*
 * Softer / slower CTA-style band (About “Ready to let go”).
 * Taller window + extra media bleed so a small drift still feels smooth.
 */
.band.band--parallax-soft {
  min-height: min(78vh, 720px);
}

.band--parallax-soft .band-media {
  top: -22%;
  height: 144%;
}

.band--parallax-soft .band-media img {
  transform: scale(1.08);
}

.band--parallax-soft .band-overlay {
  /* Slightly cooler veil so it sits with the bluish dark language */
  background: rgba(var(--bg-cool-soft-rgb), 0.62);
}

@media (prefers-reduced-motion: reduce) {
  .band--parallax .band-media {
    top: 0;
    height: 100%;
    will-change: auto;
    transform: none !important;
  }

  .band--parallax .band-media img {
    transform: none;
  }

  .band--parallax .band-content {
    will-change: auto;
    transform: none !important;
  }
}

/* ═══ FEATURED TREATMENTS (homepage) ═══ */
.section-treatments {
  background: linear-gradient(
    180deg,
    var(--bg) 0%,
    var(--bg-elevated) 10%,
    var(--bg-elevated) 90%,
    var(--bg) 100%
  );
  padding: clamp(5rem, 10vw, 8rem) 0;
}

.treatments-intro {
  text-align: center;
  max-width: 680px;
  margin: 0 auto clamp(3rem, 6vw, 5rem);
}

.treatments-lead {
  margin-top: 1.5rem;
  color: var(--cream-muted);
}

.featured-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: clamp(1.25rem, 2.5vw, 2rem);
  perspective: 1200px;
}

.featured-card {
  position: relative;
  display: flex;
  flex-direction: column;
  border: 1px solid rgba(var(--line-rgb), 0.14);
  /* Cool bluish dark body — slightly distinct from page --bg */
  background: var(--bg-cool);
  overflow: hidden;
  transform-style: preserve-3d;
  transition:
    border-color 0.5s var(--ease-out),
    transform 0.55s var(--ease-out),
    box-shadow 0.55s var(--ease-out);
}

.featured-card:hover {
  border-color: rgba(var(--line-rgb), 0.38);
  transform: translateY(-6px);
  /* Amber edge glow on top of the existing lift */
  box-shadow:
    0 0 0 1px rgba(var(--line-rgb), 0.1),
    0 0 10px rgba(var(--line-rgb), 0.14),
    0 0 18px rgba(var(--clay-rgb), 0.08);
}

/* Magnetic tilt + sheen (v2 item 6) — desktop pointer only */
.featured-card-sheen {
  position: absolute;
  inset: 0;
  z-index: 4;
  pointer-events: none;
  opacity: 0;
  mix-blend-mode: soft-light;
  /* Opacity is driven by JS lerp; no snappy CSS race */
  transition: none;
}

@media (hover: hover) and (pointer: fine) {
  .featured-card.has-tilt {
    will-change: transform;
    /* Leave transform entirely to the calm JS lerp (no CSS transform transition) */
    transition:
      border-color 0.7s var(--ease-out),
      box-shadow 0.85s var(--ease-out);
  }

  .featured-card.has-tilt:hover,
  .featured-card.has-tilt.is-tilting {
    /* Do NOT set transform here — JS owns tilt. Elevate shadow + amber glow. */
    box-shadow:
      0 18px 40px rgba(var(--shadow-rgb), 0.35),
      0 0 0 1px rgba(var(--line-rgb), 0.1),
      0 0 10px rgba(var(--line-rgb), 0.14),
      0 0 18px rgba(var(--clay-rgb), 0.08);
  }

  /* While tilting, kill any leftover transform transitions (e.g. from reveal) */
  .featured-card.has-tilt.is-tilting {
    transition:
      border-color 0.7s var(--ease-out),
      box-shadow 0.85s var(--ease-out) !important;
  }
}

@media (prefers-reduced-motion: reduce) {
  .featured-card-sheen {
    display: none;
  }

  .featured-card.has-tilt:hover {
    transform: translateY(-6px);
  }
}

.featured-card-media {
  position: relative;
  overflow: hidden;
  box-shadow:
    inset 0 0 0 1px rgba(var(--clay-rgb), 0.35),
    0 10px 24px rgba(var(--shadow-rgb), 0.28);
}

.featured-card-media img {
  width: 100%;
  aspect-ratio: 4 / 5;
  object-fit: cover;
  filter: brightness(0.86);
  transition: transform 0.9s var(--ease-out), filter 0.5s var(--ease-out);
}

.featured-card:hover .featured-card-media img {
  transform: scale(1.05);
  filter: brightness(1);
}

.featured-card-media::after {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(180deg, transparent 45%, rgba(var(--bg-cool-rgb), 0.58) 100%);
  pointer-events: none;
}

.featured-card-num {
  position: absolute;
  top: 1.25rem;
  left: 1.25rem;
  font-size: clamp(2rem, 4vw, 2.75rem);
  font-style: italic;
  color: rgba(var(--gold-rgb), 0.35);
  line-height: 1;
  z-index: 1;
}

.featured-card-body {
  padding: clamp(1.5rem, 3vw, 2rem);
  flex: 1;
  display: flex;
  flex-direction: column;
}

.featured-card-body .eyebrow {
  margin-bottom: 0.75rem;
  /* The eyebrow is a <p> too, so it also picks up the flex:1 below and gets
     stretched by whatever space the description leaves over — which pushed the
     title down by a different amount on every card. Only the description should
     absorb the slack. */
  flex: none;
}

.featured-card-body h3 {
  font-size: clamp(1.35rem, 2.2vw, 1.75rem);
  color: var(--gold-light);
  margin-bottom: 0.85rem;
  transition: color 0.4s var(--ease-out);
}

.featured-card:hover .featured-card-body h3 {
  color: var(--gold);
}

.featured-card-body p {
  font-size: 0.9rem;
  color: var(--cream-muted);
  line-height: 1.7;
  flex: 1;
  margin-bottom: 1.25rem;
}

.featured-card-price {
  font-family: var(--sans);
  font-weight: 300;
  font-size: 0.95rem;
  letter-spacing: 0.05em;
  color: var(--gold);
  padding-top: 1rem;
  border-top: 1px solid rgba(var(--line-rgb), 0.14);
}

/* ═══ LUXURY MENU (legacy list style) ═══ */
.menu-list {
  border-top: 1px solid rgba(var(--line-rgb), 0.12);
}

.menu-item {
  display: grid;
  grid-template-columns: 4rem 1fr auto;
  gap: 2rem;
  align-items: center;
  padding: 2.5rem 0;
  border-bottom: 1px solid rgba(var(--line-rgb), 0.12);
  transition: padding-left 0.6s var(--ease-out);
  position: relative;
}

.menu-item::before {
  content: '';
  position: absolute;
  left: 0;
  top: 0;
  bottom: 0;
  width: 0;
  background: linear-gradient(90deg, rgba(var(--line-rgb), 0.04), transparent);
  transition: width 0.6s var(--ease-out);
}

.menu-item:hover::before { width: 100%; }
.menu-item:hover { padding-left: 1rem; }

.menu-num {
  font-family: var(--serif);
  font-size: 0.85rem;
  color: rgb(var(--clay-rgb));
  letter-spacing: 0.1em;
}

.menu-info h3 {
  font-size: clamp(1.3rem, 2.5vw, 1.8rem);
  margin-bottom: 0.5rem;
  color: var(--cream);
  transition: color 0.4s;
}

.menu-item:hover .menu-info h3 { color: var(--gold); }

.menu-info p {
  font-size: 0.9rem;
  color: var(--cream-muted);
  max-width: 520px;
}

.menu-price {
  font-family: var(--serif);
  font-size: 1rem;
  color: var(--gold);
  white-space: nowrap;
  text-align: right;
}

/* Image frame */
.frame {
  position: relative;
}

.frame img {
  width: 100%;
  aspect-ratio: 3/4;
  object-fit: cover;
}

.frame.frame--fit {
  display: flex;
  justify-content: center;
  align-items: center;
  width: fit-content;
  max-width: 100%;
  margin: 0 auto;
}

.frame.frame--fit img {
  width: auto;
  max-width: 100%;
  height: auto;
  max-height: clamp(420px, 55vh, 580px);
  aspect-ratio: auto;
  object-fit: contain;
}

.frame::after {
  content: '';
  position: absolute;
  inset: 12px;
  border: 1px solid rgba(var(--line-rgb), 0.2);
  pointer-events: none;
}

.frame-offset {
  margin-top: 3rem;
  margin-left: -2rem;
}

/* Split section */
.split {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: clamp(3rem, 6vw, 6rem);
  align-items: center;
}

.split-reverse .split-image { order: 2; }
.split-reverse .split-text { order: 1; }

/*
 * Split text had no internal spacing at all — `.body-text` carries no bottom
 * margin and nothing else supplied one, so eyebrow, heading and every
 * paragraph sat flush against the next and the block read as one dense mass.
 * Measured at 0px between every child, on this page and on about.html, so it
 * is fixed here rather than per page.
 */
.split-text > * + * { margin-top: 1.35rem; }
.split-text > .eyebrow + h2 { margin-top: 0.75rem; }
.split-text > h2 + p { margin-top: 1.6rem; }

/* ═══ ABOUT — the three facets ═══
 *
 * Two wide landscapes stacked against one tall vertical, which spans both
 * rows. The irregularity is the genuine difference in orientation rather than
 * a width trick, so nothing is cropped away from the shape it was shot in —
 * the Buddha and the instrument spread both need their width. Each tile
 * carries its own caption: these are proof beside a claim, not a gallery.
 */
.section--facets {
  padding-top: clamp(2rem, 4vw, 4rem);
  /* Was the inherited 80px. The quote below is the closing thought of this
     section - Sound, in particular, is the paragraph it answers - so it should
     not sit a full section-break away. 2026-08-11. */
  padding-bottom: clamp(1.5rem, 3vw, 2.5rem);
}

/*
 * Written as an adjacent sibling rather than a new class: `.pullquote` is
 * shared with index.html, and this must not reach it. The selector is two
 * classes deep so it also wins against the base `.pullquote` padding without
 * depending on source order - the trap that cost time twice today.
 *
 * 192px to 88px, which matches the rhythm the home page now uses between its
 * intro and The Practitioner.
 */
.section--facets + .pullquote {
  padding-top: clamp(2rem, 4vw, 3rem);
}

.facets-intro {
  max-width: 640px;
  margin-bottom: clamp(2.5rem, 5vw, 4rem);
}

/*
 * Three across, and deliberately small — these are miniatures that carry a
 * caption, not feature images. The narrow middle column lets the portrait sit
 * between the two landscapes without towering over them: the vertical is given
 * less width precisely because it has more height, so the three finish at
 * comparable weight. The staggered top margins are what stop it reading as a
 * tidy row of thumbnails.
 */
.facets {
  display: grid;
  grid-template-columns: minmax(0, 1.15fr) minmax(0, 0.7fr) minmax(0, 1.15fr);
  gap: clamp(1.5rem, 3vw, 2.75rem);
  align-items: start;
}

.facet {
  display: flex;
  flex-direction: column;
  margin: 0;
}

.facet--1 { margin-top: clamp(1.5rem, 3vw, 3rem); }
.facet--2 { margin-top: 0; }
.facet--3 { margin-top: clamp(3rem, 6vw, 6rem); }

.facet--wide .treatment-visual-media img { aspect-ratio: 16 / 9; }

/* Its own shape, uncropped — the width column is doing the balancing */
.facet--tall .treatment-visual-media img {
  aspect-ratio: 1500 / 2600;
}

.facet-caption {
  padding-top: clamp(0.9rem, 1.6vw, 1.25rem);
}

.facet-caption .eyebrow { margin-bottom: 0.6rem; }

.facet-caption p:last-child {
  color: var(--cream-soft);
  font-weight: 300;
  font-size: 0.9rem;
  line-height: 1.7;
}

/* ---- About coda: the pair below the closing parallax band ----
 *
 * Reuses .facet and the frame-depth visual from the trio above, so the frames,
 * the gilt line and the hover lift are the same object and stay the same when
 * that block changes. Only the geometry is local.
 *
 * The two frames finish level. Both photographs are 16:9, so at equal widths
 * they would already match — but the columns are deliberately uneven (a matched
 * pair of still-lifes from one room reads as filler), and an uneven width at a
 * fixed ratio means an uneven height. The left frame is given a wider ratio
 * instead of a crop: 16/9 scaled by the column ratio. Because both columns are
 * fr units out of the same leftover space, their widths stay in 1.15 : 0.85
 * whatever the gap or viewport does, so the two heights stay equal without a
 * stretch hack. Change one number below and the other follows.
 */
.about-coda {
  --coda-l: 1.15;
  --coda-r: 0.85;
  display: grid;
  /* Written out rather than calc()'d from the vars above: `fr` is not a valid
     calc() operand, and the invalid declaration silently dropped the whole
     grid, stacking the two frames. The vars still drive the aspect ratio below
     — keep the two in step by hand. */
  grid-template-columns: 1.15fr 0.85fr;
  /* Same 1rem as the gallery grid and the coda on gallery.html. A wider gap
     takes width off both frames and Greg saw it immediately. */
  gap: 1rem;
  margin-top: 1rem;
}

.about-coda .treatment-visual-media img {
  /* Wider column, wider ratio — same height as the 16/9 frame beside it. */
  aspect-ratio: calc((16 / 9) * (var(--coda-l) / var(--coda-r)));
}

.about-coda .facet:last-child .treatment-visual-media img {
  aspect-ratio: 16 / 9;
}

@media (max-width: 860px) {
  .about-coda {
    grid-template-columns: 1fr;
  }

  /* Stacked, there is no neighbour to match a height to — both take their own
     shape back. */
  .about-coda .treatment-visual-media img {
    aspect-ratio: 16 / 9;
  }
}

/* Two up, then one: the stagger only reads while there is a neighbour to
   stagger against, so it comes off entirely in the single column. */
@media (max-width: 1000px) {
  .facets { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }
  /* The portrait keeps its narrower share here too, or at half the row it
     towers over the landscape beside it */
  .facet--2 { max-width: 62%; }
  .facet--3 { grid-column: 1 / -1; max-width: 60%; }
}

@media (max-width: 640px) {
  .facets { grid-template-columns: 1fr; }

  .facet--1,
  .facet--2,
  .facet--3 {
    grid-column: 1;
    margin-top: 0;
    max-width: 100%;
  }
}

/* ═══ WHAT TO EXPECT (v2 item 10 — video placeholder + journey steps) ═══ */
.section-expect {
  background: linear-gradient(
    180deg,
    var(--bg) 0%,
    rgba(18, 16, 14, 0.55) 40%,
    var(--bg) 100%
  );
}

/*
 * Closing the treatments/expect gap, 2026-08-11 - Greg's ask.
 *
 * It was 208px, not the 160px the rest of the page runs: `.section-treatments`
 * sets its own clamp(5rem, 10vw, 8rem) = 128px, and `.section-expect` inherits
 * the standard 80px on top of it. Now 72 + 64 = 136px.
 *
 * These rules must sit after the two above them, not with the other spacing
 * work earlier in the file - same single-class specificity, so source order is
 * the only thing deciding it, and `.section-treatments` uses the `padding`
 * shorthand which would otherwise win.
 *
 * Not collapsed further on purpose: both sections carry their own background
 * gradient fading to --bg at each edge, and with too little between them the
 * two fades meet and read as a seam rather than two rooms.
 */
.section-treatments {
  padding-bottom: clamp(3rem, 6vw, 4.5rem);
}

.section-expect {
  padding-top: clamp(2.5rem, 5vw, 4rem);
}

.expect-grid {
  display: grid;
  grid-template-columns: minmax(0, 1.08fr) minmax(0, 0.92fr);
  gap: clamp(2.5rem, 5vw, 4.5rem);
  align-items: center;
}

.expect-media {
  min-width: 0;
}

.expect-video {
  position: relative;
  aspect-ratio: 16 / 9;
  overflow: hidden;
  border: 1px solid rgba(var(--line-rgb), 0.28);
  background: rgba(12, 11, 10, 0.9);
  box-shadow:
    0 0 0 1px rgba(var(--shadow-rgb), 0.35),
    0 24px 48px rgba(var(--shadow-rgb), 0.35);
}

.expect-video-bg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center;
  /* The frame was showing the photograph at roughly a quarter of its real
     luminance: 0.55 brightness under a veil that reached 0.55 black at the
     bottom. Raised to land near half — 0.75 × (1 − 0.33) ≈ 0.50 at the darkest
     point, ≈ 0.64 at the top. Saturation kept where it was; the frame should
     stay quieter than the photographs around it. */
  filter: saturate(0.75) brightness(0.75);
  transform: scale(1.02);
}

.expect-video-veil {
  position: absolute;
  inset: 0;
  background:
    radial-gradient(ellipse 70% 60% at 50% 45%, rgba(var(--bg-elevated-rgb), 0.15) 0%, transparent 65%),
    linear-gradient(180deg, rgba(10, 9, 8, 0.15) 0%, rgba(10, 9, 8, 0.33) 100%);
  pointer-events: none;
}

.expect-video-ui {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.65rem;
  z-index: 1;
  text-align: center;
  padding: 1.5rem;
}

.expect-play {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 4rem;
  height: 4rem;
  border-radius: 50%;
  border: 1px solid rgba(var(--line-rgb), 0.45);
  color: var(--gold-light);
  background: rgba(var(--bg-rgb), 0.45);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  box-shadow: 0 8px 28px rgba(var(--shadow-rgb), 0.35);
  transition:
    border-color 0.45s var(--ease-out),
    transform 0.45s var(--ease-out),
    background 0.45s var(--ease-out);
}

.expect-play svg {
  margin-left: 0.15rem; /* optical centre of play triangle */
}

@media (hover: hover) and (pointer: fine) {
  .expect-video:hover .expect-play {
    border-color: rgba(var(--line-rgb), 0.7);
    background: rgba(var(--bg-elevated-rgb), 0.55);
    transform: scale(1.04);
  }
}

.expect-video-label {
  font-size: 0.72rem;
  font-weight: 500;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--gold-light);
  margin-top: 0.35rem;
}

.expect-video-soon {
  font-family: var(--serif);
  font-style: italic;
  font-size: 1.05rem;
  color: var(--cream-soft);
  opacity: 0.9;
}

.expect-video-caption {
  margin: 0.85rem 0 0;
  font-size: 0.72rem;
  letter-spacing: 0.06em;
  color: rgb(var(--clay-rgb));
  text-align: center;
}

.expect-copy {
  min-width: 0;
}

.expect-copy .heading-accent {
  margin: 0.35rem 0 0.75rem;
}

.expect-lead {
  margin: 0 0 clamp(1.75rem, 3vw, 2.25rem);
  color: var(--cream-muted);
  max-width: 34rem;
}

.expect-steps {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0;
}

.expect-step {
  display: grid;
  grid-template-columns: 2.75rem minmax(0, 1fr);
  gap: 0.85rem 1rem;
  padding: 1.1rem 0;
  border-top: 1px solid rgba(var(--line-rgb), 0.12);
}

.expect-step:last-child {
  border-bottom: 1px solid rgba(var(--line-rgb), 0.12);
}

.expect-step-num {
  font-size: 1.15rem;
  font-weight: 400;
  color: var(--gold);
  opacity: 0.7;
  line-height: 1.3;
  padding-top: 0.1rem;
}

.expect-step-title {
  font-family: var(--serif);
  font-size: clamp(1.15rem, 1.6vw, 1.3rem);
  font-weight: 400;
  color: var(--cream-soft);
  margin: 0 0 0.35rem;
  letter-spacing: 0.02em;
}

.expect-step-body p {
  margin: 0;
  font-size: 0.95rem;
  line-height: 1.65;
  color: var(--cream-muted);
}

@media (max-width: 900px) {
  .expect-grid {
    grid-template-columns: 1fr;
    gap: 2rem;
  }

  .expect-media {
    max-width: 640px;
    margin: 0 auto;
    width: 100%;
  }
}

@media (prefers-reduced-motion: reduce) {
  .expect-play {
    transition: none;
  }

  .expect-video:hover .expect-play {
    transform: none;
  }
}

/* ═══ TESTIMONIALS (v2 item 9 — sample copy, no guest photos) ═══ */
.section-testimonials {
  background: linear-gradient(
    180deg,
    var(--bg) 0%,
    var(--bg-elevated) 12%,
    var(--bg-elevated) 88%,
    var(--bg) 100%
  );
}

.testimonials-intro {
  text-align: center;
  max-width: 640px;
  margin: 0 auto clamp(2.5rem, 5vw, 3.75rem);
}

.testimonials-lead {
  margin-top: 0.5rem;
  color: var(--cream-muted);
}

/* Temporary red reminders — remove when real Google copy is in */
.placeholder-note {
  color: #e23d3d !important;
  font-weight: 500;
}

.testimonials-footnote .placeholder-note {
  font-size: inherit;
  letter-spacing: inherit;
}

/* Horizontal strip — transform slide (wheel/arrows); no overflow scroll fight with Lenis */
.testimonials-carousel {
  position: relative;
  /* Wheel / trackpad over the strip must not chain-scroll the page */
  overscroll-behavior: contain;
}

.testimonials-viewport {
  position: relative;
  overflow: hidden;
  margin: 0 calc(-1 * clamp(0.5rem, 2vw, 1rem));
  padding: 0 clamp(0.5rem, 2vw, 1rem) 0.35rem; /* room for 3% focus-slot scale */
  touch-action: none; /* JS owns horizontal drag/swipe; no page pan bleed */
}

.testimonials-track {
  display: flex;
  gap: clamp(1rem, 2vw, 1.5rem);
  width: max-content;
  max-width: none;
  padding: 0.5rem 0 1.15rem;
  cursor: grab;
  will-change: transform;
  transform: translate3d(0, 0, 0);
  transition: transform 0.72s cubic-bezier(0.33, 0, 0.2, 1);
  -webkit-user-select: none;
  user-select: none;
}

.testimonials-track.is-dragging {
  cursor: grabbing;
}

.testimonials-track.is-wheel-sliding {
  transition: none;
}

.testimonials-track.is-snapping {
  /* Magnetic ease-in / ease-out between cards */
  transition: transform 0.72s cubic-bezier(0.33, 0, 0.2, 1);
}

@media (prefers-reduced-motion: reduce) {
  .testimonials-track,
  .testimonials-track.is-snapping {
    transition: none;
  }

  .testimonial-card,
  .testimonial-card.is-hot {
    transform: none !important;
    transition: none;
  }
}

.testimonials-track:focus-visible {
  outline: 1px solid rgba(var(--line-rgb), 0.45);
  outline-offset: 4px;
}

.testimonial-card {
  display: flex;
  flex-direction: column;
  flex: 0 0 min(340px, 82vw);
  width: min(340px, 82vw);
  max-width: 380px;
  min-height: 100%;
  padding: clamp(1.75rem, 3vw, 2.25rem);
  border: 1px solid rgba(var(--line-rgb), 0.14);
  /* Same cool bluish dark as treatments / CTA */
  background:
    linear-gradient(
      165deg,
      rgba(var(--bg-cool-card-rgb), 0.72) 0%,
      rgba(var(--bg-cool-rgb), 0.55) 55%,
      rgba(var(--bg-cool-soft-rgb), 0.5) 100%
    );
  position: relative;
  transform: scale(1);
  transform-origin: center center;
  transition:
    transform 0.45s cubic-bezier(0.33, 0, 0.2, 1),
    border-color 0.45s var(--ease-out),
    box-shadow 0.5s var(--ease-out);
}

/*
 * Scale the card currently under the pointer (.is-hot from JS hit-test).
 * CSS :hover alone sticks on the old card when the strip slides under a still mouse.
 */
.testimonial-card.is-hot {
  transform: scale(1.03);
  border-color: rgba(var(--line-rgb), 0.4);
  box-shadow: 0 16px 36px rgba(var(--shadow-rgb), 0.28);
  z-index: 2;
}

.testimonials-track.is-dragging .testimonial-card.is-hot {
  transform: scale(1);
  border-color: rgba(var(--line-rgb), 0.14);
  box-shadow: none;
}

.testimonial-card.is-clone {
  /* visual only for seamless loop */
}

/* Edge fades — show that more quotes sit off-screen */
.testimonials-fade {
  position: absolute;
  top: 0;
  bottom: 0.75rem;
  width: clamp(2rem, 6vw, 4rem);
  pointer-events: none;
  z-index: 2;
  opacity: 0;
  transition: opacity 0.4s var(--ease-out);
}

.testimonials-fade--left {
  left: 0;
  background: linear-gradient(90deg, var(--bg-elevated) 0%, transparent 100%);
}

.testimonials-fade--right {
  right: 0;
  background: linear-gradient(270deg, var(--bg-elevated) 0%, transparent 100%);
}

.testimonials-carousel.can-scroll-left .testimonials-fade--left,
.testimonials-carousel.can-scroll-right .testimonials-fade--right {
  opacity: 1;
}

.testimonials-controls {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 1rem;
  margin-top: 0.75rem;
}

.testimonials-arrow {
  width: 48px;
  height: 48px;
  border: 1px solid rgba(var(--line-rgb), 0.28);
  background: rgba(var(--bg-rgb), 0.45);
  color: var(--gold);
  font-size: 1.15rem;
  line-height: 1;
  cursor: pointer;
  transition:
    border-color 0.4s var(--ease-out),
    color 0.4s var(--ease-out),
    background 0.4s var(--ease-out),
    opacity 0.4s;
}

.testimonials-arrow:hover:not(:disabled) {
  border-color: rgba(var(--line-rgb), 0.55);
  color: var(--gold-light);
  background: rgba(var(--bg-elevated-rgb), 0.7);
}

.testimonials-arrow:disabled {
  opacity: 0.28;
  cursor: default;
}

.testimonial-icon {
  width: 2.25rem;
  height: 2.25rem;
  color: var(--gold);
  opacity: 0.55;
  margin-bottom: 1rem;
}

.testimonial-icon svg {
  width: 100%;
  height: 100%;
  display: block;
}

.testimonial-stars {
  font-size: 0.75rem;
  letter-spacing: 0.2em;
  color: var(--gold);
  margin-bottom: 1.15rem;
  line-height: 1;
}

.testimonial-quote {
  flex: 1;
  margin: 0 0 1.75rem;
}

.testimonial-quote p {
  font-family: var(--serif);
  font-size: clamp(1.05rem, 1.6vw, 1.2rem);
  font-style: italic;
  font-weight: 400;
  line-height: 1.65;
  color: var(--cream-soft);
  margin: 0;
}

.testimonial-meta {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
  padding-top: 1.25rem;
  border-top: 1px solid rgba(var(--line-rgb), 0.12);
}

.testimonial-name {
  font-size: 0.78rem;
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--gold-light);
}

.testimonial-place {
  font-size: 0.8rem;
  color: var(--cream-muted);
  letter-spacing: 0.02em;
}

.testimonials-footnote {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.65rem;
  margin-top: clamp(2rem, 4vw, 2.75rem);
  font-size: 0.72rem;
  letter-spacing: 0.06em;
  color: rgb(var(--clay-rgb));
  text-align: center;
  max-width: 36rem;
  margin-left: auto;
  margin-right: auto;
  line-height: 1.5;
}

.testimonials-footnote-icon {
  flex-shrink: 0;
  color: rgb(var(--clay-rgb));
  opacity: 0.85;
  display: flex;
}

/* The two listing links have to *look* clickable — the global `a` rule strips
   every underline site-wide, and against bronze footnote text a gold word alone
   is not a strong enough signal that it leads somewhere. Gold + underline is the
   pattern already used for the cookie notice and the map fallback, so this
   introduces nothing new. Underline sits away from the baseline and starts
   half-strength, then goes solid on hover, which keeps it quiet at rest without
   ever being invisible. No italic: the footnote is small and letter-spaced, and
   slanting it would cost more legibility than it buys in signal. */
.testimonials-footnote a {
  color: var(--gold);
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 0.25em;
  text-decoration-color: rgba(var(--gold-rgb), 0.5);
  transition: color 0.25s ease, text-decoration-color 0.25s ease;
}

.testimonials-footnote a:hover,
.testimonials-footnote a:focus-visible {
  color: var(--gold-light);
  text-decoration-color: currentColor;
}

/* CTA block — item 11: amber edge glow (no drop shadow on dark bg) */
.cta-block {
  text-align: center;
  padding: clamp(3.25rem, 6.5vw, 5.25rem) clamp(2rem, 5vw, 4rem);
  border: 1px solid rgba(var(--line-rgb), 0.22);
  position: relative;
  margin: 0 clamp(1.5rem, 4vw, 4rem);
  max-width: 900px;
  margin-left: auto;
  margin-right: auto;
  transform: scale(1);
  transform-origin: center center;
  /* overflow stays visible so corner brackets + glow aren’t clipped */
  /* Cool bluish dark — same principle as footer / scrolled nav */
  background:
    radial-gradient(ellipse 90% 70% at 50% 45%, rgba(var(--bg-cool-card-rgb), 0.32) 0%, transparent 65%),
    linear-gradient(180deg, rgba(18, 17, 26, 0.72) 0%, rgba(var(--bg-cool-soft-rgb), 0.58) 100%);
  /* Idle: no glow. Leave = ease-out, no delay. Enter delay set on :hover. */
  box-shadow: none;
  transition:
    border-color 0.55s var(--ease-out),
    box-shadow 0.55s cubic-bezier(0.22, 1, 0.36, 1),
    transform 0.55s cubic-bezier(0.33, 0, 0.2, 1);
}

@media (hover: hover) and (pointer: fine) {
  .cta-block:hover {
    border-color: rgba(var(--line-rgb), 0.38);
    /* Glow reveals after a short beat, soft ease-out; scale slightly from center */
    transform: scale(1.01);
    box-shadow:
      0 0 0 1px rgba(var(--line-rgb), 0.1),
      0 0 10px rgba(var(--line-rgb), 0.14),
      0 0 18px rgba(var(--clay-rgb), 0.08);
    transition:
      border-color 0.65s var(--ease-out) 0.08s,
      box-shadow 0.85s cubic-bezier(0.22, 1, 0.36, 1) 0.14s,
      transform 0.6s cubic-bezier(0.33, 0, 0.2, 1) 0.06s;
  }

  .cta-block:hover::before,
  .cta-block:hover::after {
    opacity: 0.9;
  }
}

/*
 * Concentric trance rings (liked pattern from footer trials).
 * Clip wrapper stays fixed; inner layer parallaxes slightly slower than scroll.
 */
.cta-block-rings-clip {
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  overflow: hidden;
  mask-image: radial-gradient(ellipse 75% 70% at 50% 50%, #000 20%, transparent 75%);
  -webkit-mask-image: radial-gradient(ellipse 75% 70% at 50% 50%, #000 20%, transparent 75%);
}

.cta-block-rings {
  position: absolute;
  /* Extra room so slow parallax never shows empty edges */
  inset: -18% -10%;
  pointer-events: none;
  will-change: transform;
  background:
    repeating-radial-gradient(
      circle at 50% 48%,
      transparent 0,
      transparent 42px,
      rgba(var(--line-rgb), 0.055) 42px,
      rgba(var(--line-rgb), 0.055) 43px,
      transparent 44px,
      transparent 84px
    ),
    repeating-radial-gradient(
      circle at 50% 48%,
      transparent 0,
      transparent 62px,
      rgba(var(--clay-rgb), 0.035) 62px,
      rgba(var(--clay-rgb), 0.035) 63px,
      transparent 64px,
      transparent 124px
    );
  opacity: 0.9;
}

@media (prefers-reduced-motion: reduce) {
  .cta-block-rings {
    will-change: auto;
    transform: none !important;
    inset: 0;
  }
}

/* Gold corner brackets — top-left & bottom-right (restored, above rings) */
.cta-block::before,
.cta-block::after {
  content: '';
  position: absolute;
  width: 48px;
  height: 48px;
  border-color: var(--gold);
  border-style: solid;
  opacity: 0.65;
  z-index: 3;
  pointer-events: none;
  transition: opacity 0.55s var(--ease-out);
}

.cta-block::before {
  top: -1px;
  left: -1px;
  border-width: 2px 0 0 2px;
}

.cta-block::after {
  bottom: -1px;
  right: -1px;
  border-width: 0 2px 2px 0;
}

.cta-block > *:not(.cta-block-rings-clip) {
  position: relative;
  z-index: 1;
}

.cta-block h2 {
  margin-bottom: 1.25rem;
}

.cta-block .cta-lead {
  color: var(--cream-muted);
  margin: 0 auto 0.85rem;
  max-width: 34rem;
  line-height: 1.7;
}

.cta-block .cta-note {
  margin: 0 auto 2.25rem;
  max-width: 28rem;
  font-size: 0.78rem;
  letter-spacing: 0.04em;
  line-height: 1.55;
  color: rgb(var(--clay-rgb));
}

.cta-actions {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 1.35rem;
}

.cta-actions .btn-solid {
  /* Slightly more presence as the final door on the page */
  padding: 1.2rem 2.9rem;
}

.cta-secondary {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 0.55rem 0.75rem;
}

.cta-secondary-sep {
  color: rgba(var(--gold-rgb), 0.35);
  font-size: 0.85rem;
  line-height: 1;
}

.cta-link {
  display: inline-flex;
  align-items: center;
  gap: 0.45rem;
  font-size: 0.72rem;
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--gold);
  opacity: 0.88;
  transition:
    color 0.4s var(--ease-out),
    opacity 0.4s var(--ease-out);
}

.cta-link svg {
  flex-shrink: 0;
  opacity: 0.9;
}

.cta-link:hover {
  color: var(--gold-light);
  opacity: 1;
}

.cta-link--wa {
  letter-spacing: 0.16em;
}

@media (prefers-reduced-motion: reduce) {
  .cta-block {
    transition: border-color 0.4s var(--ease-out), box-shadow 0.4s var(--ease-out);
    transform: none !important;
  }

  .cta-block:hover {
    transform: none !important;
  }
}

/* ═══ 404 ERROR PAGE ═══ */
.error-page {
  text-align: center;
  padding: calc(var(--header-h) + clamp(3rem, 7vw, 5.5rem)) 0 clamp(4rem, 8vw, 6rem);
  min-height: calc(100vh - var(--header-h) - 140px);
  display: flex;
  align-items: center;
}

.error-panel {
  max-width: 40rem;
  margin: 0 auto;
  padding: clamp(2.5rem, 5vw, 3.75rem) clamp(1.5rem, 4vw, 2.5rem);
  border: 1px solid rgba(var(--line-rgb), 0.2);
  background:
    radial-gradient(ellipse 90% 70% at 50% 30%, rgba(var(--bg-elevated-rgb), 0.35) 0%, transparent 65%),
    linear-gradient(180deg, rgba(var(--bg-cool-card-rgb), 0.45) 0%, rgba(var(--bg-cool-rgb), 0.55) 100%);
  box-shadow:
    0 0 0 1px rgba(var(--shadow-rgb), 0.2),
    0 24px 48px rgba(var(--shadow-rgb), 0.25);
}

.error-code {
  font-size: clamp(5rem, 14vw, 8.5rem);
  font-style: italic;
  font-weight: 400;
  line-height: 1;
  color: rgba(var(--gold-rgb), 0.22);
  margin-bottom: 0.35rem;
  letter-spacing: 0.04em;
}

.error-lead {
  max-width: 480px;
  margin: 0 auto;
  color: var(--cream-muted);
}

.error-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 1rem 1.5rem;
  justify-content: center;
  margin-top: 2.5rem;
}

.error-page .heading-accent .deco-paint--hero {
  margin: 0.85rem auto 1.25rem;
}

/* ═══ INNER PAGE HERO ═══ */
.page-hero {
  padding: calc(var(--header-h) + clamp(5rem, 10vw, 8rem)) 0 clamp(3rem, 6vw, 5rem);
  text-align: center;
  background: linear-gradient(180deg, var(--bg-elevated) 0%, var(--bg) 100%);
}

.page-hero h1 {
  margin-top: 1rem;
}

.page-hero-title {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.85rem;
}

.page-hero-title-main {
  display: block;
}

.page-hero-title-sub {
  display: block;
  font-family: var(--sans);
  font-size: 0.68rem;
  font-weight: 400;
  letter-spacing: 0.24em;
  text-transform: uppercase;
  color: rgb(var(--clay-rgb));
}

.page-hero--about {
  padding-bottom: clamp(1rem, 2vw, 1.75rem);
}

/* Still live: back-pain-massage.html and couples-massage.html both keep their
   hero paint. about.html's moved into the body column - see below. */
.page-hero--about .heading-accent .deco-paint--hero {
  margin: 0.75rem auto 0;
}

/*
 * about.html only: the paint that used to sit in the hero now closes the
 * split-text column under "...truly help people restore balance".
 *
 * Written three selectors deep on purpose. `.heading-accent .deco-paint--hero`
 * is two deep and would otherwise win regardless of where this sits in the
 * file - the specificity trap that cost time twice today.
 *
 * Top margin carries the separation from the last paragraph; no bottom margin,
 * because the section below it supplies its own.
 */
.split-text .heading-accent .deco-paint--about-body {
  margin: 2rem auto 0;
}

.page-hero--gallery {
  padding-bottom: clamp(2.5rem, 5vw, 3.5rem);
}

.page-hero--gallery .ornament {
  margin: 1.25rem 0;
}

.page-hero--gallery .heading-accent {
  margin: 0.5rem 0 0;
}

.page-hero--gallery .heading-accent .deco-paint--hero {
  margin: 0.75rem auto 0;
}

/* ═══ TREATMENTS (services page) ═══ */
.treatment {
  display: grid;
  grid-template-columns: 1.1fr 1fr;
  gap: clamp(2rem, 5vw, 5rem);
  padding: clamp(3rem, 6vw, 5rem) 0;
  border-bottom: 1px solid rgba(var(--line-rgb), 0.1);
  align-items: center;
}

.treatment:nth-child(even) {
  grid-template-columns: 1fr 1.1fr;
}

.treatment:nth-child(even) .treatment-visual { order: 2; }
.treatment:nth-child(even) .treatment-copy { order: 1; }

.treatment-visual {
  position: relative;
  display: block;
  width: 100%;
}

.treatment-visual-media {
  position: relative;
  overflow: hidden;
  border: 1px solid rgb(var(--clay-rgb));
  /* Soft object-in-space depth (base for all treatment visuals) */
  box-shadow:
    0 1px 0 rgba(var(--gold-light-rgb), 0.12),
    0 18px 36px rgba(var(--shadow-rgb), 0.38),
    0 4px 12px rgba(var(--shadow-rgb), 0.28);
}

.treatment-visual-media img {
  display: block;
  width: 100%;
  aspect-ratio: 5/4;
  object-fit: cover;
  transform-origin: center center;
  transition: transform 0.65s ease-in-out;
}

.treatment-visual:hover .treatment-visual-media img {
  transform: scale(1.03);
}

/* ═══ Cutout visual — a figure with no frame ═══
 *
 * The PNG has its own alpha, so it needs none of the framing the photographs
 * get: no border, no gilt line, no box. It stands directly on the page
 * background. That is the whole reason to keep a cutout rather than crop it
 * into a rectangle — it breaks the rhythm of framed photos exactly once, in
 * the one row that is a programme rather than a treatment. A soft radial
 * ground beneath stops it floating.
 */
.treatment--figure {
  grid-template-columns: 1fr 0.85fr;
}

.treatment--figure .treatment-copy { order: 1; }
.treatment--figure .treatment-visual { order: 2; }

/*
 * The tile picture carrier. It exists only so the collapsed thumbnail can show
 * something other than the cutout, and it must not render: left visible it
 * would both appear in the row and add a third item to this two-column grid.
 * `display: none` is safe here — the tile builder only reads its `src`
 * attribute and builds a fresh <img> from it, so the element never needs to
 * lay out or load. Scoped to this row so no card thumbnail is affected.
 */
.treatment--figure > .treatment-mini-thumb { display: none; }

.treatment-visual--cutout {
  position: relative;
  display: flex;
  justify-content: center;
  align-items: flex-end;
}

/*
 * The site's standard ground mark is a white (`--hilite-rgb`) reflection strip
 * running the full column width. Under a rectangular photo that reads as a
 * gallery plinth; under a standing figure it was a bright streak wider than
 * the body. Replaced with what a figure actually casts: a soft dark ellipse
 * at the feet, in shadow rather than highlight, blurred so it has no edge.
 * Specificity has to beat the plain `.treatment-visual::after` further down
 * the file, hence the doubled class.
 */
.treatment-visual.treatment-visual--cutout::after {
  content: '';
  display: block;
  position: absolute;
  left: 50%;
  bottom: -1.2rem;
  transform: translateX(-50%);
  width: 46%;
  height: clamp(34px, 4vw, 54px);
  background: radial-gradient(
    ellipse at 50% 50%,
    rgba(var(--shadow-rgb), 0.62) 0%,
    rgba(var(--shadow-rgb), 0.34) 45%,
    transparent 72%
  );
  filter: blur(7px);
  pointer-events: none;
  z-index: 0;
}

.treatment-visual--cutout img {
  display: block;
  position: relative;
  z-index: 1;
  width: auto;
  max-width: 100%;
  height: auto;
  max-height: clamp(400px, 54vh, 580px);
  /* Same grade as every photograph on the site */
  filter: saturate(0.62) contrast(1.06) brightness(0.78) sepia(0.16) hue-rotate(-8deg);
}

/* ═══ Shot shapes ═══
 *
 * Deliberate frame ratios, so the layout states what each photograph has to be
 * before it is taken. Everything currently in the folder is 16:9 — a video
 * ratio — so today these crop the placeholders hard. That is the point: the
 * crop is the brief. Chosen by subject, not for variety: two people side by
 * side is inherently wide, one client between two therapists is inherently
 * tall. Documented in SHOT-LIST.md.
 */
.shot--wide .treatment-visual-media img { aspect-ratio: 16 / 9; }
.shot--landscape .treatment-visual-media img { aspect-ratio: 3 / 2; }
.shot--portrait .treatment-visual-media img { aspect-ratio: 4 / 5; }

/* The portrait slot must not tower over its copy in a two-column row */
.treatment .shot--portrait { max-width: 420px; margin-inline: auto; }

@media (max-width: 860px) {
  .treatment .shot--portrait { max-width: 100%; }
}

.treatment-visual--portrait {
  width: fit-content;
  max-width: 100%;
  margin: 0 auto;
}

.treatment-visual--portrait .treatment-visual-media {
  width: fit-content;
  max-width: 100%;
  margin: 0 auto;
}

.treatment-visual--portrait .treatment-visual-media img {
  width: auto;
  max-width: 100%;
  height: auto;
  max-height: clamp(420px, 55vh, 580px);
  aspect-ratio: auto;
  object-fit: contain;
}

/*
 * Depth / gilt frame (v2 item 7)
 * Multi-plane border + inner gold line + deep ambient shadow
 * so the photo reads as an object in space.
 */
.frame-depth {
  isolation: isolate;
}

.frame-depth .treatment-visual-media {
  border: 1px solid rgba(var(--clay-rgb), 0.95);
  box-shadow:
    /* outer gilt halo */
    0 0 0 1px rgba(var(--bg-rgb), 0.85),
    0 0 0 7px rgba(var(--bg-rgb), 0.97),
    0 0 0 8px rgba(var(--clay-rgb), 0.42),
    /* ambient depth */
    0 32px 56px rgba(var(--shadow-rgb), 0.55),
    0 12px 22px rgba(var(--shadow-rgb), 0.4),
    0 2px 0 rgba(var(--gold-light-rgb), 0.08),
    /* soft inner falloff */
    inset 0 0 48px rgba(var(--bg-rgb), 0.18);
  transition:
    box-shadow 0.85s var(--ease-out),
    border-color 0.85s var(--ease-out);
}

/* Inner gilt matte line */
.frame-depth .treatment-visual-media::before {
  content: '';
  position: absolute;
  inset: 11px;
  border: 1px solid rgba(var(--line-rgb), 0.32);
  box-shadow:
    inset 0 0 24px rgba(var(--bg-rgb), 0.22),
    0 0 0 1px rgba(var(--bg-rgb), 0.15);
  pointer-events: none;
  z-index: 2;
  transition: border-color 0.85s var(--ease-out), inset 0.85s var(--ease-out);
}

/* Soft vignette inside the frame (depth, not darken the subject too hard) */
.frame-depth .treatment-visual-media::after {
  content: '';
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 1;
  background:
    radial-gradient(ellipse 85% 75% at 50% 42%, transparent 45%, rgba(var(--bg-rgb), 0.28) 100%),
    linear-gradient(180deg, rgba(var(--bg-rgb), 0.08) 0%, transparent 22%, transparent 70%, rgba(var(--bg-rgb), 0.2) 100%);
}

.frame-depth:hover .treatment-visual-media {
  border-color: rgba(var(--line-rgb), 0.95);
  box-shadow:
    0 0 0 1px rgba(var(--bg-rgb), 0.85),
    0 0 0 7px rgba(var(--bg-rgb), 0.97),
    0 0 0 8px rgba(var(--line-rgb), 0.5),
    0 36px 64px rgba(var(--shadow-rgb), 0.58),
    0 14px 26px rgba(var(--shadow-rgb), 0.42),
    0 2px 0 rgba(var(--gold-light-rgb), 0.12),
    inset 0 0 48px rgba(var(--bg-rgb), 0.14);
}

.frame-depth:hover .treatment-visual-media::before {
  border-color: rgba(var(--gold-light-rgb), 0.42);
  inset: 10px;
}

/* Featured cards: inner gilt matte (box-shadow is on .featured-card-media above) */
.featured-card-media::before {
  content: '';
  position: absolute;
  inset: 10px;
  border: 1px solid rgba(var(--line-rgb), 0.18);
  pointer-events: none;
  z-index: 2;
  opacity: 0.85;
  transition: border-color 0.6s var(--ease-out), opacity 0.6s var(--ease-out);
}

.featured-card:hover .featured-card-media::before {
  border-color: rgba(var(--gold-light-rgb), 0.35);
  opacity: 1;
}

/* Bottom shadow + elliptical mask (bright center, transparent corners = cylinder) */
.treatment-visual::after {
  content: '';
  display: block;
  width: 100%;
  height: clamp(14px, 2.2vw, 20px);
  background: radial-gradient(ellipse 65% 130% at 50% 0%, rgba(var(--hilite-rgb), 0.7) 10%, rgba(var(--hilite-rgb), 0.28) 42%, rgba(var(--hilite-rgb), 0.08) 65%, transparent 82%);
}

/* No ground shadow under depth-framed portraits — frame alone is enough */
.frame-depth.treatment-visual::after {
  display: none;
}

.treatment-visual .treatment-num {
  position: absolute;
  top: 1.25rem;
  left: 1.25rem;
  z-index: 3;
  font-family: var(--serif);
  font-size: 3rem;
  font-style: italic;
  color: rgba(var(--gold-rgb), 0.25);
  line-height: 1;
}

.treatment-copy .eyebrow { margin-bottom: 1rem; }

.treatment-copy h3 {
  font-size: clamp(1.8rem, 3vw, 2.5rem);
  margin-bottom: 1.5rem;
  color: var(--gold-light);
}

/* Plain-language descriptor under the treatment name. The evocative name keeps the
   premium feel; this is the part people actually search for, and it sits inside the
   h3 so it reads as one heading. Quiet by design — it should not compete. */
.treatment-sub {
  display: block;
  margin-top: 0.55rem;
  font-family: var(--sans);
  font-size: 0.7rem;
  font-weight: 400;
  font-style: normal;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--cream-muted);
}

.treatment-copy p {
  color: var(--cream-soft);
  margin-bottom: 1.5rem;
}

.treatment-price {
  font-family: var(--sans);
  font-weight: 300;
  font-size: 0.95rem;
  letter-spacing: 0.05em;
  color: var(--gold);
  padding-top: 1rem;
  border-top: 1px solid rgba(var(--line-rgb), 0.15);
}

.services-note {
  text-align: center;
  max-width: 680px;
  margin: 0 auto 4rem;
  color: var(--cream-muted);
  font-size: 1.05rem;
  line-height: 1.9;
}

.duration-callout {
  margin-bottom: clamp(3rem, 6vw, 5rem);
  padding: clamp(2rem, 4vw, 2.75rem) clamp(2rem, 5vw, 3.5rem);
  text-align: center;
  border: 1px solid rgba(var(--line-rgb), 0.32);
  /* Cool bluish dark — same language as home cards / footer */
  background:
    linear-gradient(
      160deg,
      rgba(var(--line-rgb), 0.1) 0%,
      rgba(var(--bg-cool-card-rgb), 0.55) 50%,
      rgba(var(--bg-cool-rgb), 0.72) 100%
    );
  transform: scale(1);
  transform-origin: center center;
  transition:
    border-color 0.55s var(--ease-out),
    box-shadow 0.55s cubic-bezier(0.22, 1, 0.36, 1),
    transform 0.55s cubic-bezier(0.33, 0, 0.2, 1);
}

.duration-callout .eyebrow {
  margin-bottom: 1rem;
}

.duration-callout-text {
  font-family: var(--serif);
  font-size: clamp(1.15rem, 2.2vw, 1.45rem);
  font-style: italic;
  line-height: 1.55;
  color: var(--gold-light);
  max-width: 620px;
  margin: 0 auto;
}

.duration-callout-text strong {
  font-style: italic;
  font-weight: 300;
  color: var(--gold);
}

/* FAQ — warm elevated band (no cool bluish full-section tint) */
.faq-wrap {
  background: var(--bg-elevated);
  padding: clamp(2.5rem, 5vw, 3.5rem) 0 clamp(5rem, 10vw, 8rem);
}

/* Paint → first question: keep tight (was 4rem inline gap) */
.faq-intro {
  text-align: center;
  margin-bottom: clamp(0.75rem, 1.5vw, 1.15rem);
}

.faq-intro .heading-accent {
  margin-bottom: 0;
}

.faq-intro .heading-accent .deco-paint--hero {
  margin: 1rem auto 0.35rem;
}

.section:has(+ .faq-wrap) {
  padding-bottom: clamp(0.5rem, 1.5vw, 1rem);
}

.treatment:last-of-type {
  padding-bottom: clamp(1.5rem, 3vw, 2.5rem);
}

.faq-item {
  /* No item hairlines — answer uses left border only */
  border: none;
}

.faq-q {
  width: 100%;
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 2rem;
  padding: 2rem 0;
  background: none;
  border: none;
  cursor: pointer;
  text-align: left;
  font-family: var(--serif);
  font-size: clamp(1.1rem, 2vw, 1.35rem);
  color: var(--cream);
  transition: color 0.4s;
}

.faq-q:hover { color: var(--gold); }

.faq-icon {
  flex-shrink: 0;
  width: 24px;
  height: 24px;
  position: relative;
}

.faq-icon::before,
.faq-icon::after {
  content: '';
  position: absolute;
  background: var(--gold);
  transition: transform 0.5s var(--ease-out);
}

.faq-icon::before {
  top: 50%;
  left: 0;
  width: 100%;
  height: 1px;
  transform: translateY(-50%);
}

.faq-icon::after {
  left: 50%;
  top: 0;
  width: 1px;
  height: 100%;
  transform: translateX(-50%);
}

.faq-item.open .faq-icon::after {
  transform: translateX(-50%) rotate(90deg);
  opacity: 0;
}

/*
 * Answer panel: height eases open, then text fades / rises in with a short delay
 * (avoids the abrupt “boom” of plain max-height alone).
 */
.faq-a {
  max-height: 0;
  overflow: hidden;
  opacity: 0;
  transform: translate3d(0, -0.4rem, 0);
  /* Indent so the answer sits under the question title, not flush with it */
  padding: 0 0 0 clamp(1rem, 2.5vw, 1.5rem);
  margin-left: 0.15rem;
  border-left: 1px solid rgba(var(--line-rgb), 0.18);
  color: var(--cream-muted);
  font-size: 0.98rem;
  line-height: 1.85;
  transition:
    max-height 0.55s var(--ease-out),
    opacity 0.28s ease,
    transform 0.28s ease,
    padding 0.45s var(--ease-out),
    border-color 0.4s ease;
}

.faq-item.open .faq-a {
  max-height: 28rem;
  opacity: 1;
  transform: translate3d(0, 0, 0);
  /* No large empty band under the answer — next question’s padding separates items */
  padding: 0.15rem 0 0.35rem clamp(1rem, 2.5vw, 1.5rem);
  border-left-color: rgba(var(--line-rgb), 0.32);
  /* Text reveals a beat after the panel starts opening */
  transition:
    max-height 0.7s var(--ease-out),
    opacity 0.55s cubic-bezier(0.22, 1, 0.36, 1) 0.12s,
    transform 0.55s cubic-bezier(0.22, 1, 0.36, 1) 0.1s,
    padding 0.55s var(--ease-out),
    border-color 0.5s ease 0.08s;
}

@media (prefers-reduced-motion: reduce) {
  .faq-a,
  .faq-item.open .faq-a {
    transition: none;
    transform: none;
  }
}

/* ═══ GALLERY ═══ */
.gallery-stage {
  position: relative;
  overflow: hidden;
}

.deco-mandala--gallery-bg {
  top: 50%;
  left: 50%;
  width: clamp(720px, 92vw, 1040px);
  margin-top: calc(clamp(720px, 92vw, 1040px) / -2);
  margin-left: calc(clamp(720px, 92vw, 1040px) / -2);
  z-index: 0;
  opacity: 0.26;
  filter: sepia(0.4) saturate(1.3) hue-rotate(62deg) brightness(0.9);
}

.gallery-masonry {
  position: relative;
  z-index: 1;
  display: grid;
  grid-template-columns: repeat(12, 1fr);
  grid-auto-rows: 80px;
  gap: 1rem;
}

.gallery-cell {
  position: relative;
  overflow: hidden;
  cursor: pointer;
  border: 1px solid rgb(var(--clay-rgb));
}

.gallery-cell img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* Soft dim + muted colour at rest — calm, not greyed-out */
  opacity: 0.4;
  filter: brightness(0.72) saturate(0.78) contrast(0.98);
  transform-origin: center center;
  transition:
    transform 0.9s cubic-bezier(0.33, 0, 0.2, 1),
    opacity 1.05s cubic-bezier(0.22, 1, 0.36, 1),
    filter 1.05s cubic-bezier(0.22, 1, 0.36, 1);
}

.gallery-cell:hover img {
  transform: scale(1.03);
  /* Slow reveal to full colour + full opacity — brings it forward */
  opacity: 1;
  filter: brightness(1) saturate(1) contrast(1);
}

@media (prefers-reduced-motion: reduce) {
  .gallery-cell img {
    transition: filter 0.35s ease;
  }

  .gallery-cell:hover img {
    transform: none;
  }
}

.gallery-cell:nth-child(1) { grid-column: span 7; grid-row: span 6; }
.gallery-cell:nth-child(2) { grid-column: span 5; grid-row: span 4; }
.gallery-cell:nth-child(3) { grid-column: span 5; grid-row: span 4; }
.gallery-cell:nth-child(4) { grid-column: span 4; grid-row: span 5; }
.gallery-cell:nth-child(5) { grid-column: span 4; grid-row: span 5; }
.gallery-cell:nth-child(6) { grid-column: span 4; grid-row: span 5; }
.gallery-cell:nth-child(7) { grid-column: span 6; grid-row: span 4; }
.gallery-cell:nth-child(8) { grid-column: span 6; grid-row: span 4; }
.gallery-cell:nth-child(9) { grid-column: span 12; grid-row: span 4; }

.gallery-cell .cell-label {
  position: absolute;
  bottom: 0;
  left: 0;
  right: 0;
  z-index: 2;
  padding: 1rem;
  background: linear-gradient(transparent, rgba(var(--bg-rgb), 0.8));
  font-size: 0.6rem;
  letter-spacing: 0.15em;
  text-transform: uppercase;
  color: rgb(var(--clay-rgb));
  opacity: 0;
  transition: opacity 0.4s;
  pointer-events: none;
}

.gallery-cell:hover .cell-label { opacity: 1; }

/* Lightbox */
.lightbox {
  display: none;
  position: fixed;
  inset: 0;
  z-index: 2000;
  background: rgba(var(--bg-rgb), 0.97);
  align-items: center;
  justify-content: center;
  padding: 2rem 5rem;
  opacity: 0;
  transition: opacity 0.5s;
}

.lightbox.active {
  display: flex;
  opacity: 1;
}

.lightbox-stage {
  display: flex;
  align-items: center;
  justify-content: center;
  max-width: 100%;
  max-height: 100%;
  touch-action: pan-y pinch-zoom;
}

.lightbox img {
  max-width: min(92vw, 1200px);
  max-height: 82vh;
  object-fit: contain;
  box-shadow: 0 40px 100px rgba(var(--shadow-rgb), 0.6);
  transition: transform 0.35s ease, opacity 0.35s ease;
  will-change: transform, opacity;
}

.lightbox img.slide-out-left {
  transform: translateX(-40px);
  opacity: 0;
}

.lightbox img.slide-out-right {
  transform: translateX(40px);
  opacity: 0;
}

.lightbox img.slide-in-left {
  transform: translateX(-40px);
  opacity: 0;
}

.lightbox img.slide-in-right {
  transform: translateX(40px);
  opacity: 0;
}

.lightbox-close,
.lightbox-nav {
  position: absolute;
  background: none;
  border: 1px solid rgba(var(--line-rgb), 0.3);
  color: var(--gold);
  width: 48px;
  height: 48px;
  cursor: pointer;
  transition: border-color 0.4s, background 0.4s, opacity 0.4s;
  z-index: 2;
}

.lightbox-close:hover,
.lightbox-nav:hover {
  border-color: var(--gold);
  background: rgba(var(--line-rgb), 0.08);
}

.lightbox-close {
  top: 2rem;
  right: 2rem;
  font-size: 1.5rem;
}

.lightbox-nav {
  top: 50%;
  transform: translateY(-50%);
  display: flex;
  align-items: center;
  justify-content: center;
}

.lightbox-nav svg {
  width: 22px;
  height: 22px;
}

.lightbox-prev { left: 1.5rem; }
.lightbox-next { right: 1.5rem; }

.lightbox-counter {
  position: absolute;
  bottom: 2rem;
  left: 50%;
  transform: translateX(-50%);
  margin: 0;
  font-size: 0.75rem;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: rgba(var(--gold-rgb), 0.65);
}

/* ═══ CONTACT ═══ */
.page-hero--contact {
  padding-bottom: clamp(1.25rem, 2.5vw, 2rem);
}

.page-hero--contact .heading-accent .deco-paint--hero {
  margin: 1.5rem auto 0;
}

.contact-layout {
  display: grid;
  grid-template-columns: 1fr 1.2fr;
  gap: clamp(3rem, 6vw, 6rem);
}

.contact-form-col {
  padding-top: 2rem;
}

.contact-detail {
  padding: 2rem 0;
  border-bottom: 1px solid rgba(var(--line-rgb), 0.1);
}

.contact-detail:first-child { border-top: 1px solid rgba(var(--line-rgb), 0.1); }

.contact-detail h3 {
  font-size: 0.65rem;
  letter-spacing: 0.3em;
  text-transform: uppercase;
  color: rgb(var(--clay-rgb));
  margin-bottom: 0.75rem;
  font-family: var(--sans);
  font-weight: 500;
}

.contact-detail p,
.contact-detail a {
  font-family: var(--serif);
  font-size: 1.25rem;
  color: var(--cream);
}

.contact-detail a:hover { color: var(--gold); }

.contact-languages {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.75rem 1rem;
  margin-top: 1.25rem;
}

.contact-phone-line {
  display: flex;
  align-items: center;
  gap: 0.85rem;
}

.lang-flags--contact {
  max-width: none;
  justify-content: flex-start;
  gap: 0.45rem;
}

.lang-flags--contact .lang-flag img {
  width: 20px;
  height: 20px;
}

.lang-flag--primary {
  opacity: 0.75;
}

.lang-flag--secondary {
  opacity: 0.42;
}

.lang-flags--contact .lang-flag:hover {
  opacity: 1;
}

.contact-form {
  display: flex;
  flex-direction: column;
  gap: 2rem;
}

.contact-privacy-note {
  margin-top: 2rem;
  font-size: 0.75rem;
  color: rgb(var(--clay-rgb));
  line-height: 1.5;
}

.contact-privacy-note a {
  color: var(--gold);
}

.contact-privacy-note a:hover {
  color: var(--gold-light);
}

.form-field label {
  display: block;
  font-size: 0.62rem;
  letter-spacing: 0.25em;
  text-transform: uppercase;
  color: rgb(var(--clay-rgb));
  margin-bottom: 0.75rem;
}

.form-field input,
.form-field textarea {
  width: 100%;
  padding: 1rem 0;
  background: transparent;
  border: none;
  border-bottom: 1px solid rgba(var(--line-rgb), 0.2);
  color: var(--cream);
  font-family: var(--sans);
  font-size: 1rem;
  font-weight: 300;
  transition: border-color 0.4s;
}

.form-field input:focus,
.form-field textarea:focus {
  outline: none;
  border-bottom-color: var(--gold);
}

.form-field textarea {
  min-height: 120px;
  resize: vertical;
}

.form-submit {
  align-self: flex-start;
  margin-top: 1rem;
}

.form-msg {
  padding: 1.25rem;
  display: none;
  font-size: 0.9rem;
  border: 1px solid;
}

.form-msg.success {
  display: block;
  border-color: rgba(110, 180, 130, 0.55);
  color: #8fcfa3;
  background: rgba(110, 180, 130, 0.1);
  font-size: 1rem;
}

.form-msg.success, .form-msg.error { margin-bottom: 1.5rem; }

.form-msg.success::before {
  content: '\2713\00a0';
  font-weight: 700;
}

.form-msg.error {
  display: block;
  border-color: rgba(180, 80, 80, 0.4);
  color: #c07070;
  background: rgba(180, 80, 80, 0.06);
}

/*
 * Full-bleed map band + layered gilt frame (same family as treatment frames / CTA).
 * Breaks out of .container to true viewport width.
 */
.map-band {
  width: 100vw;
  max-width: 100vw;
  margin-top: clamp(3.5rem, 7vw, 5.5rem);
  margin-left: calc(50% - 50vw);
  margin-right: calc(50% - 50vw);
  padding: 0 clamp(1rem, 3.5vw, 2.75rem);
  box-sizing: border-box;
}

/* Outer frame: bronze edge, dark matte, gold halo */
.map-frame {
  position: relative;
  padding: clamp(0.55rem, 1.2vw, 0.85rem);
  background:
    linear-gradient(
      165deg,
      rgba(var(--bg-elevated-rgb), 0.95) 0%,
      var(--bg-cool, #100e16) 55%,
      rgba(var(--bg-cool-soft-rgb), 0.98) 100%
    );
  border: 1px solid rgba(var(--clay-rgb), 0.88);
  box-shadow:
    0 0 0 1px rgba(var(--bg-rgb), 0.9),
    0 0 0 8px rgba(var(--bg-rgb), 0.97),
    0 0 0 9px rgba(var(--clay-rgb), 0.4),
    0 28px 56px rgba(var(--shadow-rgb), 0.45),
    0 10px 22px rgba(var(--shadow-rgb), 0.35),
    0 0 40px rgba(var(--clay-rgb), 0.07);
}

/* Corner brackets (CTA language) */
.map-frame::before,
.map-frame::after {
  content: '';
  position: absolute;
  width: clamp(1.75rem, 3vw, 2.5rem);
  height: clamp(1.75rem, 3vw, 2.5rem);
  border-color: var(--gold);
  border-style: solid;
  opacity: 0.7;
  z-index: 4;
  pointer-events: none;
}

.map-frame::before {
  top: -1px;
  left: -1px;
  border-width: 2px 0 0 2px;
}

.map-frame::after {
  bottom: -1px;
  right: -1px;
  border-width: 0 2px 2px 0;
}

/* Map surface inside the matte */
.map-wrap {
  position: relative;
  height: clamp(280px, 48vh, 520px);
  overflow: hidden;
  background: var(--bg-cool, #100e16);
  border: 1px solid rgba(var(--line-rgb), 0.28);
  box-shadow:
    inset 0 0 0 1px rgba(var(--bg-rgb), 0.35),
    inset 0 0 40px rgba(var(--bg-rgb), 0.2);
}

.map-wrap iframe {
  width: 100%;
  height: 100%;
  border: 0;
  display: block;
  /* Pull toward dark gold / bronze — still readable for navigation */
  filter:
    grayscale(50%)
    sepia(32%)
    saturate(0.7)
    brightness(0.58)
    contrast(1.1)
    hue-rotate(352deg);
}

/* Gold–charcoal veil + vignette (clicks pass through to the map) */
.map-wrap::before {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  background:
    radial-gradient(
      ellipse 85% 75% at 50% 42%,
      transparent 15%,
      rgba(var(--bg-rgb), 0.28) 70%,
      rgba(var(--bg-rgb), 0.55) 100%
    ),
    linear-gradient(
      180deg,
      rgba(var(--bg-cool-rgb), 0.38) 0%,
      rgba(var(--bg-rgb), 0.12) 45%,
      rgba(var(--bg-rgb), 0.42) 100%
    ),
    linear-gradient(
      135deg,
      rgba(var(--line-rgb), 0.1) 0%,
      transparent 40%,
      rgba(var(--clay-rgb), 0.08) 100%
    );
  box-shadow: inset 0 0 0 1px rgba(var(--line-rgb), 0.22);
}

/* Soft warm wash on top — keeps the gold story without hiding roads */
.map-wrap::after {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 2;
  pointer-events: none;
  background: linear-gradient(
    165deg,
    rgba(var(--gold-light-rgb), 0.07) 0%,
    transparent 42%,
    rgba(var(--bg-cool-rgb), 0.2) 100%
  );
  mix-blend-mode: soft-light;
}

/* Unfiltered copy of the map, clipped to Google's info card (top-left), above
   the veil layers so the card is not darkened. Adjust the two sizes together. */
.map-wrap iframe.map-card-frame {
  position: absolute;
  inset: 0;
  z-index: 3;
  filter: none;
  clip-path: inset(8px calc(100% - 312px) calc(100% - 112px) 8px round 6px);
}

@media (prefers-reduced-motion: reduce) {
  .map-wrap iframe {
    filter: grayscale(45%) brightness(0.65) contrast(1.05);
  }
}

@media (max-width: 640px) {
  .map-band {
    padding: 0 0.75rem;
  }

  .map-frame {
    padding: 0.45rem;
  }

  .map-wrap {
    height: min(42vh, 360px);
  }
}

/* Privacy / legal copy */
.legal-page {
  padding: calc(var(--header-h) + clamp(2.5rem, 5vw, 3.5rem)) 0 clamp(4rem, 8vw, 6rem);
}

.legal {
  width: 100%;
  max-width: 780px;
  margin: 0 auto;
  padding: 0 clamp(1.5rem, 5vw, 4rem);
  box-sizing: border-box;
  overflow-wrap: break-word;
  word-wrap: break-word;
}

.legal h1 { margin-bottom: 0.25rem; }

.legal .heading-accent {
  margin: 0.5rem 0 1rem;
}

.legal .heading-accent .deco-paint--hero {
  margin: 0.5rem 0 0;
}

.legal .date {
  color: rgb(var(--clay-rgb));
  font-size: 0.85rem;
  margin-bottom: 2.5rem;
}

.legal h2 {
  font-size: clamp(1.1rem, 3vw, 1.3rem);
  color: var(--gold);
  margin: 2.5rem 0 1rem;
}

.legal p {
  color: var(--cream-soft);
  margin-bottom: 1rem;
  font-size: clamp(0.95rem, 2.5vw, 1rem);
  line-height: 1.8;
}

.legal a {
  color: var(--gold);
}

.legal a:hover {
  color: var(--gold-light);
}

.legal strong {
  color: var(--cream);
  font-weight: 500;
}

/* Footer bridge — transitional accent before footer */
.footer-bridge {
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: clamp(2.5rem, 6vw, 4rem) clamp(1.5rem, 4vw, 2rem) clamp(2rem, 5vw, 3rem);
}

.footer-bridge .ornament {
  margin: 0 0 1.25rem;
  width: min(340px, 72vw);
}

.footer-bridge .deco-paint {
  margin: 0;
}

.footer-bridge--compact {
  padding: clamp(2rem, 5vw, 3rem) clamp(1.5rem, 4vw, 2rem) clamp(1.5rem, 4vw, 2rem);
}

.footer-bridge--compact .ornament {
  margin-bottom: 1rem;
  width: min(260px, 60vw);
}

.section:has(+ .footer-bridge),
.faq-wrap:has(+ .footer-bridge) {
  padding-bottom: clamp(2.5rem, 6vw, 4rem);
}

.section.section--gallery {
  padding-bottom: clamp(0.25rem, 1vw, 0.75rem);
}

.footer-bridge.footer-bridge--gallery {
  padding: clamp(0.5rem, 1.5vw, 1rem) clamp(1.5rem, 4vw, 2rem) clamp(2rem, 5vw, 3rem);
}

/* Same tightening for any page that ends on photographs rather than on copy.
   A full section's padding plus the bridge's own is right after a paragraph and
   far too much after a frame — the images and the spiral stop reading as one
   closing gesture. Matches the gallery page above. */
.footer-bridge.footer-bridge--tight {
  padding: clamp(0.5rem, 1.5vw, 1rem) clamp(1.5rem, 4vw, 2rem) clamp(2rem, 5vw, 3rem);
}

/* The section carrying the closing pair on about.html:
   - a real breath between the parallax band and the frames, where there was
     none (the band is full-bleed and the frames sat straight under it)
   - almost nothing underneath, so the spiral divider sits close
   - a wider gutter between the two frames than the 1rem the gallery grid uses,
     because these two carry captions and need to read as two, not as a strip */
/* Doubled class on purpose. `.section:has(+ .footer-bridge)` above is two
   classes' worth of specificity and puts 2.5–4rem back under any section that
   precedes the bridge — right after copy, wrong after a photograph. The gallery
   rule solves it the same way. */
.section.section--about-coda {
  padding-top: clamp(2rem, 4vw, 3.25rem);
  /* Nothing here. The gap down to the spiral is the bridge's own padding and
     the caption line, and that is already as much as the gallery page has. */
  padding-bottom: 0;
}

/* The caption sits tight under its frame and ends there — the 0.6rem below the
   eyebrow is for a paragraph that follows it in the facets above, and there is
   no paragraph here. */
.about-coda .facet-caption {
  padding-top: 0.7rem;
}

.about-coda .facet-caption .eyebrow {
  margin-bottom: 0;
}

.section--about-coda .about-coda {
  gap: clamp(1.5rem, 2.6vw, 2.25rem);
  margin-top: 0;
}

main:has(+ .footer-bridge) {
  padding-bottom: clamp(3rem, 6vw, 4.5rem);
}

.section.section--cta:has(+ .footer-bridge) {
  padding-bottom: clamp(1rem, 2.5vw, 1.75rem);
}

/* ═══ FOOTER ═══ */
.footer {
  border-top: 1px solid rgba(var(--line-rgb), 0.08);
  padding: clamp(4rem, 8vw, 6rem) 0 2rem;
  margin-top: clamp(4rem, 8vw, 6rem);
}

.footer-bridge + .footer {
  margin-top: 0;
  padding-top: clamp(3rem, 6vw, 4.5rem);
}

/*
 * Footer curtain reveal (v2 item 12)
 * Page sheet scrolls over a fixed footer; at the end the sheet lifts
 * and the footer is revealed underneath (Paper-style, calm).
 * Desktop first; static stack on smaller screens.
 */
.page-sheet {
  position: relative;
  z-index: 2;
  background: var(--bg);
}

/*
 * Reveal footer = distinct under-layer (not the same charcoal as the page).
 * Main block: deeper black + soft gold diagonal weave + warm top glow.
 * Legal bar: pure black strip — second tier of distinction.
 */
.footer.footer--reveal {
  margin-top: 0;
  border-top: none;
  padding: 0;
  background: #080706;
}

/*
 * Footer seam shader strip (v2 item 12b)
 * Belongs to the scrolling page-sheet (content window): last edge of the curtain.
 * Scrolls up with the sheet; fixed footer is revealed underneath.
 */
:root {
  --footer-seam-h: 26px;
}

.footer-seam {
  position: relative;
  z-index: 2;
  height: var(--footer-seam-h);
  width: 100%;
  overflow: hidden;
  background: #0a0908;
  pointer-events: none;
  /* Sit flush as the bottom edge of the content sheet */
  margin: 0;
  flex-shrink: 0;
}

.footer-seam-canvas {
  display: block;
  width: 100%;
  height: 100%;
}

/* Static fallback: black + full logo sweep including cream */
.footer-seam-fallback {
  display: none;
  position: absolute;
  inset: 0;
  background: linear-gradient(
    90deg,
    #0a0908 0%,
    #2a4a28 10%,
    #5a5828 20%,
    #6a5848 32%,
    #c49870 42%,
    #c49060 52%,
    #1a4548 64%,
    #243848 78%,
    #5a5048 90%,
    #0a0908 100%
  );
  opacity: 0.9;
}

/* Soft vertical falloff into black */
.footer-seam::after {
  content: '';
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: linear-gradient(
    180deg,
    rgba(var(--bg-rgb), 0.2) 0%,
    transparent 40%,
    transparent 60%,
    rgba(8, 7, 6, 0.4) 100%
  );
  z-index: 1;
}

.footer-seam.is-static .footer-seam-canvas {
  display: none;
}

.footer-seam.is-static .footer-seam-fallback {
  display: block;
}

@media (prefers-reduced-motion: reduce) {
  .footer-seam-canvas {
    display: none;
  }

  .footer-seam-fallback {
    display: block;
    opacity: 0.7;
  }
}

/*
 * Footer main (address / links) — plain deep black + mouse spotlight.
 * Film grain is clipped off this under-layer (see initGrainFooterCutout).
 * Silk diagonal stays on .footer-legal only.
 */
.footer--reveal .footer-main {
  position: relative;
  overflow: hidden;
  padding: clamp(3.25rem, 6.5vw, 5rem) 0 clamp(2.75rem, 5vw, 3.75rem);
  background:
    radial-gradient(
      560px circle at var(--spot-x, 50%) var(--spot-y, 40%),
      rgba(var(--gold-light-rgb), 0.055) 0%,
      rgba(var(--line-rgb), 0.028) 34%,
      transparent 62%
    ),
    #100e16;
}

/* Gold hairline at the top edge of the revealed footer */
.footer--reveal .footer-main::before {
  content: '';
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: 1px;
  background: linear-gradient(
    90deg,
    transparent 0%,
    rgba(var(--line-rgb), 0.15) 15%,
    rgba(var(--gold-light-rgb), 0.55) 50%,
    rgba(var(--line-rgb), 0.15) 85%,
    transparent 100%
  );
  pointer-events: none;
  z-index: 3;
}

.footer--reveal .footer-main > .container {
  position: relative;
  z-index: 2;
}

/*
 * Copyright / privacy — only place with Silk diagonal (#4).
 * Small material band; main footer = black + spotlight, no hatch.
 */
.footer--reveal .footer-legal {
  position: relative;
  background:
    repeating-linear-gradient(
      -18deg,
      transparent 0,
      transparent 11px,
      rgba(var(--line-rgb), 0.06) 11px,
      rgba(var(--line-rgb), 0.06) 12px
    ),
    linear-gradient(180deg, #0a0908 0%, #050403 55%, #030302 100%);
  border-top: 1px solid rgba(var(--line-rgb), 0.14);
  padding: 1.15rem 0;
}

@media (prefers-reduced-motion: reduce) {
  .header.scrolled,
  .footer--reveal .footer-main {
    /* Static soft glow, no mouse chase */
    --spot-x: 50%;
    --spot-y: 40%;
  }
}

.footer--reveal .footer-legal::before {
  content: '';
  position: absolute;
  top: 0;
  left: 50%;
  transform: translateX(-50%);
  width: min(12rem, 40vw);
  height: 1px;
  background: linear-gradient(
    90deg,
    transparent,
    rgba(var(--line-rgb), 0.35),
    transparent
  );
  pointer-events: none;
}

@media (min-width: 1025px) {
  .page-sheet {
    /* Scroll room = full fixed footer; seam is part of the sheet above it */
    margin-bottom: var(--footer-reveal-h, 320px);
    /* Soft “curtain edge” under the seam as the sheet lifts */
    box-shadow:
      0 32px 72px rgba(var(--shadow-rgb), 0.65),
      0 8px 24px rgba(var(--shadow-rgb), 0.4);
  }

  /* Glow sits under the shader strip (sheet’s bottom edge) */
  .page-sheet .footer-seam {
    box-shadow: 0 1px 0 rgba(var(--gold-light-rgb), 0.2);
  }

  .footer.footer--reveal {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 1;
    width: 100%;
  }

  /* When cookie bar is up, it sits above the fixed footer */
  body:has(.cookie.show) .footer.footer--reveal {
    padding-bottom: 5.5rem;
  }
}

@media (max-width: 1024px) {
  .page-sheet {
    margin-bottom: 0;
    box-shadow: none;
  }

  .footer.footer--reveal {
    position: relative;
    z-index: auto;
  }
}

@media (prefers-reduced-motion: reduce) {
  /* Keep layout; no extra shadow drama required */
  .page-sheet {
    box-shadow: none;
  }
}

/*
 * Three columns on one row: logo · pages · address.
 *
 * The tagline used to live in the first column, stacked under the logo. That
 * gave one column a second line the others did not have, so the three started
 * at different depths and the row read as broken. Moving it to a full-width row
 * of its own fixed the alignment but cost a row, and the sentence was then the
 * only reason the layout needed grid areas, an order swap under 1024, and a
 * width cap that broke it across two lines. It was removed on 2026-08-08 —
 * every complication in this footer traced back to that one element.
 *
 * The logo column is `auto`: it only has to hold the mark and the word, and
 * every pixel it does not take goes to the address, the longest thing down here.
 */
.footer-top {
  display: grid;
  grid-template-columns: auto 1.4fr 1.9fr;
  align-items: center;
  gap: 3rem;
  padding-bottom: 0;
  border-bottom: none;
}

.footer--reveal .footer-top {
  padding-bottom: 0;
}

.footer h4 {
  font-size: 0.62rem;
  letter-spacing: 0.28em;
  text-transform: uppercase;
  color: var(--gold);
  margin-bottom: 1.5rem;
  font-family: var(--sans);
  font-weight: 500;
}

.footer-links { list-style: none; }

.footer-links li {
  margin-bottom: 0.65rem;
  font-size: 0.88rem;
  color: var(--cream-muted);
  line-height: 1.6;
}

.footer-links a {
  color: inherit;
  transition: color 0.4s, padding-left 0.4s;
}

.footer-links a:hover {
  color: var(--cream);
  padding-left: 4px;
}

/*
 * Inline, wrapping, rule-separated. Five stacked links cost five lines of footer
 * to carry five words; laid along a line they cost one or two and the footer
 * loses roughly a third of its height.
 *
 * The rules are drawn with ::after on every item except the last, so the markup
 * stays a plain <ul> — a screen reader still announces a list of five, and no
 * stray "|" characters end up in the page text or in the ES/DE/PL translations.
 *
 * The separator is `pointer-events: none` because it sits inside the <li>, and
 * without it the gap between two links would be a dead hover target that still
 * looks clickable.
 */
/* Centred inside its own column rather than pushed left. It matters most when a
   list wraps: left-aligned, the overflow lands alone against the left edge and
   reads as an orphan; centred, a second line reads as part of the same block.
   The logo column stays where it is — it is an `auto` column, so it is exactly
   as wide as the logo and there is no space inside it to centre within. */
.footer-links--inline {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: center;
  gap: 0.35rem 0.85rem;
}

.footer-links--inline li {
  margin-bottom: 0;
  display: flex;
  align-items: baseline;
  gap: 0.85rem;
}

.footer-links--inline li:not(:last-child)::after {
  content: '|';
  color: rgba(var(--bronze-rgb), 0.45);
  font-size: 0.8em;
  pointer-events: none;
}

/*
 * The address is always longer than the phone and the email together, so it
 * always wrapped to a second line — and the separator belonging to the item
 * before it was left dangling at the end of the first line, pointing at
 * nothing.
 *
 * CSS cannot see where a line breaks, so instead of trying to detect the wrap
 * this makes it deliberate: the address takes a full row of its own, and the
 * separator in front of it is removed. It is also the honest reading of the
 * content — a phone number and an email are two ways to reach the same person,
 * an address is a different kind of fact.
 */
.footer-links--inline .footer-link-break {
  flex-basis: 100%;
  justify-content: center;
}

.footer-links--inline li:has(+ .footer-link-break)::after {
  content: none;
}

/* The nudge-right hover is for a vertical list, where there is empty space to
   move into. On a line it shoves the rule and every item after it sideways. */
.footer-links--inline a:hover {
  padding-left: 0;
}

/* Social icons in the footer: icons rather than words, no separator between them. */
.footer-links--inline li.footer-social { align-items: center; }
.footer-links--inline li.footer-social:has(+ .footer-social)::after { content: none; }
.footer-social-icon { width: 1.5rem; height: 1.5rem; display: block; }


/*
 * The legal bar is three columns in one row — copyright, the disclaimer
 * sentence, privacy — and it stays one row all the way down to the 768px
 * stack. There is no breakpoint in between on purpose.
 *
 * When the row runs out of width the middle column narrows and its text wraps
 * inside itself, so the bar grows by one line of small print rather than by a
 * whole row. Vertically centred, so the two short links sit against the middle
 * of however many lines the sentence takes.
 *
 * This replaced a 1000px rule that dropped the whole sentence to its own
 * full-width line. That cost more vertical space than it saved and invited a
 * second exception at the next awkward width. Letting the text wrap in place
 * needs no breakpoints at all and holds at every width, not just the ones
 * anybody thought to check.
 *
 * min-width: 0 is what makes it work — a flex item will not shrink below its
 * longest word without it, and the row would overflow instead of wrapping.
 *
 * The disclaimer stays a shade dimmer and a shade tighter than the two links:
 * it has to be present on every page without competing with anything.
 */
.footer-disclaimer {
  flex: 1 1 auto;
  min-width: 0;
  text-align: center;
  line-height: 1.6;
  letter-spacing: 0.04em;
  color: rgba(var(--bronze-rgb), 0.72);
}

/* ── Contrast, measured 2026-08-11 ──────────────────────────────────────────
   Every colour in this footer block was set with an alpha, and the alpha is
   what broke them — not the hue. At 11.52px these all need 4.5:1:
     bronze @ 0.70 = 2.63:1   ← the legal disclaimer, the worst on the site
     bronze @ 0.85 = 3.39:1
     clay   @ 0.75 = 3.52:1
     bronze @ 1.00 = 4.13:1
   Clay at full strength measures 5.31:1 and gold 9.45:1, so text goes to clay
   and links to gold. Same swap already made once, on the preloader line. */
.footer-disclaimer a {
  color: var(--gold);
  text-decoration: underline;
  text-underline-offset: 0.2em;
}

.footer-disclaimer a:hover { color: var(--gold-light); }

.footer--reveal .footer-disclaimer { color: rgb(var(--clay-rgb)); }
.footer--reveal .footer-disclaimer a { color: var(--gold); }
.footer--reveal .footer-disclaimer a:hover { color: var(--gold-light); }

.footer-bottom {
  display: flex;
  flex-wrap: nowrap;
  justify-content: space-between;
  align-items: center;
  gap: 2rem;
  padding-top: 0;
  font-size: 0.72rem;
  letter-spacing: 0.08em;
  color: rgb(var(--clay-rgb));
}

/* The two outer columns never wrap — they are short, and letting them break
   would put "Privacy" and "Policy" on separate lines before the sentence in
   the middle had given up any width at all. */
.footer-copy,
.footer-privacy {
  flex: 0 0 auto;
  white-space: nowrap;
}

/* Same measurement pass as .footer-disclaimer above — see the note there. */
.footer--reveal .footer-bottom {
  color: rgb(var(--clay-rgb));
}

.footer-bottom a { color: var(--gold); }
.footer-bottom a:hover { color: var(--gold-light); }

.footer--reveal .footer-bottom a {
  color: var(--gold);
}

.footer--reveal .footer-bottom a:hover {
  color: var(--gold-light);
}

/* Cookie */
/* ═══ Scroll-to-top (all pages, injected by initScrollTop) ═══ */
.scroll-top {
  position: fixed;
  bottom: clamp(1.25rem, 3vw, 2rem);
  right: clamp(1.25rem, 3vw, 2rem);
  z-index: 1400;
  width: 46px;
  height: 46px;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  border: 1px solid rgba(var(--line-rgb), 0.28);
  border-radius: 50%;
  background: rgba(var(--bg-elevated-rgb), 0.72);
  backdrop-filter: blur(8px);
  color: var(--gold);
  cursor: pointer;
  opacity: 0;
  transform: translateY(14px) scale(0.92);
  pointer-events: none;
  transition:
    opacity 0.5s var(--ease-out),
    transform 0.6s var(--ease-out),
    background-color 0.4s var(--ease-out),
    border-color 0.4s var(--ease-out),
    color 0.4s var(--ease-out);
}

.scroll-top.is-visible {
  opacity: 1;
  transform: translateY(0) scale(1);
  pointer-events: auto;
}

.scroll-top:hover {
  background: var(--gold);
  border-color: var(--gold);
  color: var(--bg);
}

.scroll-top:focus-visible {
  outline: 1px solid var(--gold-dim);
  outline-offset: 3px;
}

.scroll-top svg {
  width: 20px;
  height: 20px;
}

/* Lift clear of the cookie bar while it's showing */
body:has(.cookie.show) .scroll-top {
  bottom: clamp(6rem, 11vw, 7rem);
}

@media (prefers-reduced-motion: reduce) {
  .scroll-top,
  .scroll-top.is-visible {
    transform: none;
    transition: opacity 0.3s linear;
  }
}

.cookie {
  position: fixed;
  bottom: 0;
  left: 0;
  right: 0;
  z-index: 1500;
  padding: 1.25rem clamp(1.5rem, 4vw, 4rem);
  background: rgba(var(--bg-elevated-rgb), 0.96);
  backdrop-filter: blur(16px);
  border-top: 1px solid rgba(var(--line-rgb), 0.12);
  display: none;
  align-items: center;
  justify-content: space-between;
  gap: 2rem;
}

.cookie.show { display: flex; }

.cookie p {
  font-size: 0.82rem;
  color: var(--cream-muted);
}

.cookie a { color: var(--gold); text-decoration: underline; }

.cookie-actions {
  display: flex;
  gap: 0.75rem;
  flex-shrink: 0;
}

.cookie-accept,
.cookie-decline {
  padding: 0.65rem 1.25rem;
  background: transparent;
  border: 1px solid var(--gold);
  color: var(--gold);
  font-size: 0.62rem;
  letter-spacing: 0.15em;
  text-transform: uppercase;
  cursor: pointer;
  transition: all 0.4s;
  white-space: nowrap;
}

.cookie-decline {
  border-color: rgba(var(--line-rgb), 0.3);
  color: var(--cream-muted);
}

.cookie-accept:hover {
  background: var(--gold);
  color: var(--bg);
}

.cookie-decline:hover {
  border-color: var(--cream-muted);
  color: var(--cream);
}

.map-placeholder {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 1.25rem;
  height: 100%;
  min-height: 300px;
  /* Same cool dark language as map frame before Maps loads */
  background:
    radial-gradient(ellipse 80% 70% at 50% 45%, rgba(var(--bg-elevated-rgb), 0.35) 0%, transparent 70%),
    linear-gradient(180deg, rgba(var(--bg-cool-card-rgb), 0.5) 0%, var(--bg-cool, #100e16) 100%);
  color: var(--cream-muted);
  font-size: 0.9rem;
  text-align: center;
  padding: 2rem;
  position: relative;
  z-index: 1;
}

.map-placeholder p {
  margin: 0;
  max-width: 320px;
  line-height: 1.6;
}

.map-placeholder-note {
  font-size: 0.8rem;
}

.map-placeholder-note a {
  color: var(--gold);
}

.map-load-btn {
  padding: 0.85rem 2rem;
  font-size: 0.68rem;
  min-width: 200px;
}

.map-placeholder-alt {
  font-size: 0.85rem;
  max-width: 300px;
  line-height: 1.6;
}

.map-placeholder-alt a {
  color: var(--gold);
  text-decoration: underline;
}

.maps-link {
  display: inline-block;
  margin-top: 0.75rem;
  font-size: 0.8rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--gold);
  text-decoration: underline;
  text-underline-offset: 3px;
}

.maps-link:hover {
  color: var(--cream);
}

.contact-detail .maps-link {
  font-family: var(--sans);
  font-size: 0.72rem;
}

/* ═══ RESPONSIVE ═══ */
@media (max-width: 1100px) {
  .editorial {
    display: block;
    position: relative;
    overflow: hidden;
  }

  .editorial-deco {
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    min-height: 0;
    pointer-events: none;
    z-index: 0;
  }

  .editorial-deco-img {
    width: min(78vw, 360px);
    max-width: none;
    max-height: min(62vh, 380px);
    margin: 0;
    -webkit-mask-image: radial-gradient(
      ellipse at center,
      rgba(0, 0, 0, 0.2) 0%,
      rgba(0, 0, 0, 0.09) 42%,
      rgba(0, 0, 0, 0.02) 68%
    );
    mask-image: radial-gradient(
      ellipse at center,
      rgba(0, 0, 0, 0.2) 0%,
      rgba(0, 0, 0, 0.09) 42%,
      rgba(0, 0, 0, 0.02) 68%
    );
  }

  .editorial-body {
    position: relative;
    z-index: 1;
  }
}

@media (max-width: 560px) {
  .editorial-deco {
    display: none;
  }
}

@media (max-width: 1024px) {
  .treatment,
  .treatment:nth-child(even) {
    grid-template-columns: 1fr;
  }

  .treatment:nth-child(even) .treatment-visual,
  .treatment:nth-child(even) .treatment-copy {
    order: unset;
  }

  .split,
  .contact-layout {
    grid-template-columns: 1fr;
  }

  /*
   * Below 1024 the three columns stop paying for themselves and the footer
   * becomes two rows: brand across the top, then Explore and Visit sharing a
   * line. Measured on index.html, the top block goes 323px → ~180px.
   *
   * More than half of that comes from one thing — un-stacking the logo from the
   * tagline. Stacked they are 128px; side by side they are 52px. The column
   * rearrangement below is worth less than that single change.
   *
   * Flex with a basis rather than a fixed two-by-two grid, so each row breaks
   * when its own content runs out of room instead of at a pixel value somebody
   * picked. Around 560px the brand row can no longer hold both parts and drops
   * to two lines by itself; Explore and Visit separate a little below that. No
   * extra breakpoint, and nothing to re-tune at 999px.
   *
   * Above 1024 this does not apply, and must not: the single row there is
   * already 120px, and two rows would cost about 180px.
   */
  /* One column, centred, in document order: logo, pages, address. Three short
     centred blocks — the Explore and Visit headings are gone too, so each is a
     line of links rather than a heading plus a stack. */
  .footer-top {
    grid-template-columns: 1fr;
    gap: 1.75rem;
    text-align: center;
  }

  /* `justify-content: center` does nothing here: .logo-link is a flex box only
     as wide as the mark plus the word, so centring its contents inside itself
     changes nothing while the box stays pinned left. inline-flex lets the
     column's own text-align: center move the whole thing instead. */
  .footer-top .logo-link { display: inline-flex; }

  .footer-links--inline {
    justify-content: center;
  }

  .frame-offset { margin-left: 0; }

  .featured-grid {
    grid-template-columns: 1fr;
    max-width: 440px;
    margin: 0 auto;
  }

  .gallery-masonry {
    display: block;
    column-count: 2;
    column-gap: 0.85rem;
  }

  .deco-mandala--gallery-bg {
    width: clamp(520px, 88vw, 760px);
    margin-top: calc(clamp(520px, 88vw, 760px) / -2);
    margin-left: calc(clamp(520px, 88vw, 760px) / -2);
    opacity: 0.2;
  }

  .gallery-cell {
    break-inside: avoid;
    margin-bottom: 0.85rem;
  }

  .gallery-cell:nth-child(n) {
    grid-column: unset;
    grid-row: unset;
  }

  .gallery-cell img {
    width: 100%;
    height: auto;
    display: block;
    object-fit: cover;
    aspect-ratio: auto;
  }
}

/* 975, not 768 — see the note on the matching query further down. The split
   logo and the inline nav do not both fit until 975px. */
@media (max-width: 975px) {
  .header-inner {
    grid-template-columns: 1fr auto;
  }

  .nav,
  .header-phone {
    display: none;
  }

  .header-contact-block {
    align-items: center;
  }

  .lang-flags {
    gap: 0.2rem;
    flex-wrap: nowrap;
  }

  .lang-flag img {
    width: 14px;
    height: 14px;
  }

  .header-right {
    justify-self: end;
  }

  .menu-toggle { display: block; }

  .nav.open {
    display: flex;
    flex-direction: column;
    position: fixed;
    top: var(--header-h);
    left: 0;
    right: 0;
    bottom: 0;
    background: var(--bg);
    padding: 3rem 2rem;
    gap: 2rem;
    justify-content: center;
    align-items: center;
  }

  .nav.open a {
    font-size: 0.85rem;
  }

  .menu-item {
    grid-template-columns: 1fr;
    gap: 0.75rem;
  }

  .menu-price { text-align: left; }

  .hero-scroll { display: none; }

  .cookie {
    flex-direction: column;
    text-align: center;
    gap: 1rem;
  }

  .cookie-actions {
    width: 100%;
    justify-content: center;
  }

  /* Below this the row has nothing left to give and the three columns stack.
     Document order, so the small print keeps its place in the middle. */
  .footer-bottom {
    flex-direction: column;
    gap: 0.75rem;
    text-align: center;
  }

  .lightbox {
    padding: 4.5rem 1rem 3.5rem;
  }

  .lightbox-nav {
    width: 44px;
    height: 44px;
    background: rgba(var(--bg-rgb), 0.55);
  }

  .lightbox-prev { left: 0.5rem; }
  .lightbox-next { right: 0.5rem; }

  .lightbox img {
    max-height: 76vh;
  }

  .gallery-masonry {
    column-gap: 0.65rem;
  }

  .gallery-cell {
    margin-bottom: 0.65rem;
  }
}

@media (max-width: 480px) {
  .gallery-masonry {
    column-count: 1;
    column-gap: 0;
  }

  .gallery-cell {
    margin-bottom: 0.75rem;
  }
}
/* --- SCROLL REVEALS (v2 item 1) --- */
.reveal {
  opacity: 0;
  transform: translate3d(0, 2.25rem, 0);
  transition:
    opacity 1.05s var(--ease-out),
    transform 1.15s var(--ease-out);
  transition-delay: 0s;
  will-change: opacity, transform;
}

/* Directional entrances (inner pages + any block that should not pop as one unit) */
.reveal.reveal-from-left {
  transform: translate3d(-2.75rem, 1.1rem, 0);
}

.reveal.reveal-from-right {
  transform: translate3d(2.75rem, 1.1rem, 0);
}

.reveal.reveal-from-bottom {
  transform: translate3d(0, 2.5rem, 0);
}

.reveal.reveal-from-top {
  transform: translate3d(0, -1.75rem, 0);
}

.reveal.reveal-delay-0 { transition-delay: 0s; }
.reveal.reveal-delay-1 { transition-delay: 0.12s; }
.reveal.reveal-delay-2 { transition-delay: 0.24s; }
.reveal.reveal-delay-3 { transition-delay: 0.36s; }
.reveal.reveal-delay-4 { transition-delay: 0.48s; }
.reveal.reveal-delay-5 { transition-delay: 0.6s; }

.reveal.in-view {
  opacity: 1;
  transform: translate3d(0, 0, 0);
  will-change: auto;
}

/* Treatment rows: image + copy animate as separate planes */
.treatment .treatment-visual.reveal,
.treatment .treatment-copy.reveal {
  /* keep frame shadows visible during slide-in */
  overflow: visible;
}

/* Home: cite / step rows need block layout for transform reveals */
.pullquote cite.reveal {
  display: block;
}

.expect-step.reveal {
  /* already grid; transform is fine */
}

/*
 * Curated treatment cards — first-scroll cascade (not one block).
 * 01 from left · 02 from deeper bottom · 03 from right
 * Then tilt JS takes over transform (no long CSS transform transition on hover).
 */
.featured-card.reveal-card {
  opacity: 0;
  will-change: opacity, transform;
  transition:
    opacity 1.15s var(--ease-out),
    transform 1.35s var(--ease-out);
}

.featured-card.reveal-card--1 {
  transform: translate3d(-3.25rem, 2.75rem, 0) scale(0.96);
  transition-delay: 0.08s;
}

.featured-card.reveal-card--2 {
  transform: translate3d(0, 4rem, 0) scale(0.95);
  transition-delay: 0.28s;
  transition-duration: 1.25s, 1.45s;
}

.featured-card.reveal-card--3 {
  transform: translate3d(3.25rem, 2.75rem, 0) scale(0.96);
  transition-delay: 0.48s;
}

.featured-card.reveal-card.in-view {
  opacity: 1;
  transform: translate3d(0, 0, 0) scale(1);
}

/*
 * After entrance, drop transform from the transition list so the calm
 * tilt lerp isn’t fighting a 1.3s CSS transition.
 */
.featured-card.reveal-card.in-view.tilt-ready {
  will-change: auto;
  transition:
    opacity 0.5s var(--ease-out),
    border-color 0.7s var(--ease-out),
    box-shadow 0.85s var(--ease-out);
}

.featured-card.reveal-card.in-view.tilt-ready:not(.is-tilting) {
  transform: none;
}

/* Number soft-pops after the card settles */
.featured-card.reveal-card .featured-card-num {
  opacity: 0;
  transform: translate3d(0, 0.6rem, 0);
  transition:
    opacity 0.85s var(--ease-out),
    transform 0.95s var(--ease-out);
  transition-delay: 0s;
}

.featured-card.reveal-card--1.in-view .featured-card-num {
  opacity: 1;
  transform: none;
  transition-delay: 0.45s;
}

.featured-card.reveal-card--2.in-view .featured-card-num {
  opacity: 1;
  transform: none;
  transition-delay: 0.7s;
}

.featured-card.reveal-card--3.in-view .featured-card-num {
  opacity: 1;
  transform: none;
  transition-delay: 0.9s;
}

@media (prefers-reduced-motion: reduce) {
  .reveal,
  .reveal.reveal-from-left,
  .reveal.reveal-from-right,
  .reveal.reveal-from-bottom,
  .reveal.reveal-from-top {
    opacity: 1;
    transform: none;
    transition: none;
    will-change: auto;
  }

  .featured-card.reveal-card,
  .featured-card.reveal-card--1,
  .featured-card.reveal-card--2,
  .featured-card.reveal-card--3 {
    opacity: 1;
    transform: none;
    transition: none;
  }

  .featured-card.reveal-card .featured-card-num {
    opacity: 1;
    transform: none;
    transition: none;
  }

  .featured-card.reveal-card.in-view.tilt-ready {
    transition: none;
  }
}

/* ═══════════════════════════════════════════════════════════
   SHAMANIC / TRANCE IDENTITY LAYER
   Drawn from the new photography: aura rim-glow, twilight
   ember/sage split-toning, circles (gong / drum / ripples).
   One shared rhythm: the "breath" (~7.5s — edge-of-sleep tempo).
   Transform/opacity only — mobile-GPU safe.
   ═══════════════════════════════════════════════════════════ */

:root {
  --ember: #C98A6B;                 /* salt-lamp / candle glow */
  --ember-faint: rgba(var(--ember-rgb), 0.10);
  --tealsage: #7FA08F;              /* muted green from the photos */
  --tealsage-faint: rgba(var(--tealsage-rgb), 0.09);
  --dusk: #6A5A78;                  /* violet dusk — the threshold hour */
  --dusk-faint: rgba(var(--dusk-rgb), 0.09);
  --breath: 7.5s;                   /* shared tempo for all living elements */

  /* Channel tokens for the shamanic trio (see the base :root for the why) */
  --ember-rgb: 201, 138, 107;
  --tealsage-rgb: 127, 160, 143;
  --dusk-rgb: 106, 90, 120;
}

.sym-defs {
  position: absolute;
  width: 0;
  height: 0;
  overflow: hidden;
}

/* ---- Preloader night sigil: draws itself, then breathes ---- */
.preloader-sigil {
  width: 104px;
  height: 104px;
  overflow: visible;
  filter: drop-shadow(0 0 14px rgba(var(--line-rgb), 0.32));
  animation: sigilBreath var(--breath) ease-in-out 2.4s infinite;
}

.preloader-sigil .sig {
  stroke: var(--gold);
  stroke-linecap: round;
  fill: none;
  stroke-dasharray: 1;
  stroke-dashoffset: 1;
  animation: sigilDraw 1.5s var(--ease-out) forwards;
}

/* Crescents thinned from the traced 3.5/5.5 widths — hairline weight to match the light text */
.preloader-sigil .sig-c1 { stroke-width: 1; }
.preloader-sigil .sig-c2 { stroke: var(--bronze); animation-delay: 0.35s; stroke-width: 1; }
.preloader-sigil .sig-sp { stroke: var(--gold-light); animation-delay: 0.75s; animation-duration: 1.3s; }
.preloader-sigil .sig-f  { animation-delay: 1.05s; animation-duration: 0.7s; }
.preloader-sigil .sig-f2 { animation-delay: 1.15s; }
.preloader-sigil .sig-f3 { animation-delay: 1.25s; }
.preloader-sigil .sig-f4 { animation-delay: 1.35s; }
.preloader-sigil .sig-th { stroke: var(--bronze); animation-delay: 1.5s; animation-duration: 0.6s; }

@keyframes sigilDraw {
  to { stroke-dashoffset: 0; }
}

@keyframes sigilBreath {
  0%, 100% { opacity: 1; transform: scale(1); }
  50%      { opacity: 0.72; transform: scale(1.025); }
}

/* ---- Header / footer mark: mute the gloss into the night palette ---- */
.logo-img--tr {
  filter: saturate(0.55) brightness(1.05) drop-shadow(0 0 10px rgba(var(--ember-rgb), 0.22));
}

/* ---- Ornament glyphs (spiral / ripples) replace the generic ◆ ---- */
.ornament .orn-glyph {
  width: 22px;
  height: 22px;
  stroke: var(--bronze);
  stroke-width: 2.5;
  fill: none;
  stroke-linecap: round;
  flex: none;
  animation: ornBreath var(--breath) ease-in-out infinite;
}

@keyframes ornBreath {
  0%, 100% { opacity: 0.6; transform: scale(1) rotate(0deg); }
  50%      { opacity: 0.95; transform: scale(1.08) rotate(2deg); }
}

/* ---- Ambient atmosphere: candlelight drifting in a dark room ----
   Fixed viewport layer inside .page-sheet: paints above the sheet's
   background, below all positioned sections. Masked out near the
   bottom so the footer curtain reveal stays clean black. */
.atmosphere {
  position: fixed;
  inset: 0;
  pointer-events: none;
  overflow: hidden;
  -webkit-mask-image: linear-gradient(180deg, #000 78%, transparent 100%);
  mask-image: linear-gradient(180deg, #000 78%, transparent 100%);
}

.atmo {
  position: absolute;
  border-radius: 50%;
  will-change: transform, opacity;
}

.atmo--ember {
  width: 72vmax;
  height: 72vmax;
  left: -22vmax;
  top: 30vh;
  background: radial-gradient(circle, var(--ember-faint) 0%, rgba(var(--ember-rgb), 0.045) 38%, transparent 68%);
  animation: atmoDriftA 46s ease-in-out infinite alternate;
}

.atmo--sage {
  width: 64vmax;
  height: 64vmax;
  right: -26vmax;
  top: 6vh;
  background: radial-gradient(circle, var(--tealsage-faint) 0%, rgba(var(--tealsage-rgb), 0.04) 40%, transparent 66%);
  animation: atmoDriftB 58s ease-in-out infinite alternate;
}

.atmo--dusk {
  width: 58vmax;
  height: 58vmax;
  left: 24vw;
  bottom: -30vmax;
  background: radial-gradient(circle, var(--dusk-faint) 0%, rgba(var(--dusk-rgb), 0.04) 40%, transparent 66%);
  animation: atmoDriftC 70s ease-in-out infinite alternate;
}

@keyframes atmoDriftA {
  from { transform: translate3d(0, 0, 0); opacity: 0.7; }
  50%  { opacity: 1; }
  to   { transform: translate3d(9vw, -7vh, 0); opacity: 0.65; }
}

@keyframes atmoDriftB {
  from { transform: translate3d(0, 0, 0); opacity: 0.65; }
  50%  { opacity: 1; }
  to   { transform: translate3d(-8vw, 9vh, 0); opacity: 0.8; }
}

@keyframes atmoDriftC {
  from { transform: translate3d(0, 0, 0); opacity: 0.6; }
  to   { transform: translate3d(6vw, -6vh, 0); opacity: 0.95; }
}

/* ---- Aura rim-glow: the photos' backlit halo, echoed in CSS.
   drop-shadow (not box-shadow) so it stacks with the depth frames
   and never fights existing shadows. Static = rendered once. ---- */
.aura {
  filter:
    drop-shadow(0 0 26px rgba(var(--ember-rgb), 0.18))
    drop-shadow(0 0 70px rgba(var(--ember-rgb), 0.10));
}

/* ---- Heading halo — a whisper of the same backlight on display type ---- */
.display-xl,
.display-md {
  text-shadow: 0 0 38px rgba(var(--gold-light-rgb), 0.16);
}

/* ---- Twilight veil: unifies OLD photos with the new photography
   (desaturated, deepened shadows, ember warmth, soft vignette).
   Remove per-image as reshoots arrive. ---- */
.veil {
  position: relative;
}

/* .band-media must stay absolute (parallax fill) — re-assert over the .veil rule above */
.band-media.veil {
  position: absolute;
}

/* The collapsed accordion thumbnails ride the same grade — they are previews of
   these exact images, so any difference reads as two different photo sets. */
.veil img,
.preview-tile-media img {
  filter: saturate(0.62) contrast(1.06) brightness(0.78) sepia(0.16) hue-rotate(-8deg);
}

.veil::after {
  content: '';
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 1;
  background:
    radial-gradient(ellipse 120% 90% at 50% 42%, transparent 48%, rgba(12, 9, 7, 0.42) 100%),
    linear-gradient(180deg, rgba(var(--ember-rgb), 0.05), rgba(var(--bg-cool-rgb), 0.12));
}

/* Hero slides are old photos too — same veil, applied directly */
.hero-slide {
  filter: saturate(0.72) contrast(1.05) brightness(0.85) sepia(0.12) hue-rotate(-8deg);
}

/* ---- CTA sound-rings breathe at double-breath (deep exhale) ---- */
.cta-block-rings {
  animation: ringsBreath calc(var(--breath) * 2) ease-in-out infinite;
}

@keyframes ringsBreath {
  0%, 100% { opacity: 0.55; }
  50%      { opacity: 1; }
}

/* ---- Guards ---- */
@media (max-width: 768px) {
  /* two glows are plenty on a phone */
  .atmo--dusk { display: none; }
}

@media (prefers-reduced-motion: reduce) {
  .preloader-sigil,
  .ornament .orn-glyph,
  .atmo,
  .cta-block-rings {
    animation: none;
  }

  .preloader-sigil .sig {
    animation: none;
    stroke-dashoffset: 0;
  }
}

/* ═══════════════════════════════════════════════════════════════
   SERVICES MENU LAYOUT
   Menu-at-a-glance index + grouped treatments + compact package
   cards. Additive; only services.html uses these classes.
   ═══════════════════════════════════════════════════════════════ */

/* Menu section: anchor for the gap paint, plus a very small top padding so
   "At a Glance" isn't flush against the top of the section (base .section
   padding-top is otherwise large; this replaces the old inline padding-top:0). */
.section.section--menu {
  position: relative;
  padding-top: 0.6rem;
}

/* Gold paint stroke nestled into the existing hero→menu gap. Absolute so it
   takes no layout space — the gap between the hero note and "At a Glance" is
   unchanged. Full-width wrapper; the paint centres itself (margin auto) so its
   breathe animation (which uses transform) is free of any centring transform. */
.menu-paint {
  position: absolute;
  top: -4.75rem;
  left: 0;
  right: 0;
  z-index: 1;
  pointer-events: none;
}

.menu-paint .deco-paint {
  margin-top: 0;
  margin-bottom: 0;
}

/* ---- Menu at a glance (the "menu card") ---- */
/* No outer frame — the four section cards are the frames now. This is just a
   layout wrapper for the heading + the grid of section cards. */
.menu-card {
  margin: 0 auto clamp(3rem, 6vw, 5rem);
}

/* Four named sections, each its own framed card inside the big menu frame. */
.menu-group--card {
  padding: clamp(1.35rem, 2.6vw, 1.9rem) clamp(1.35rem, 2.6vw, 1.9rem);
  border: 1px solid rgba(var(--line-rgb), 0.16);
  background:
    linear-gradient(
      160deg,
      rgba(var(--line-rgb), 0.05) 0%,
      rgba(var(--bg-cool-card-rgb), 0.28) 60%,
      rgba(var(--bg-cool-rgb), 0.4) 100%
    );
}

/* Base transform/transition for the section cards + callout. Includes opacity
   and the site's 1.15s transform so the scroll-reveal (each card carries its own
   .reveal reveal-from-*) fades + slides in exactly like every other section.
   Hover-in gets its own snappy timing from the :hover rule below; the resting
   1.15s transform only affects the imperceptible 1% scale settling back. */
.menu-group--card,
.duration-callout {
  transform: scale(1);
  transform-origin: center center;
  transition:
    opacity 1.05s var(--ease-out),
    border-color 0.55s var(--ease-out),
    box-shadow 0.55s cubic-bezier(0.22, 1, 0.36, 1),
    transform 1.15s var(--ease-out);
}

/* ── Shared hover language ──────────────────────────────────────────────
   Literally the home "Reserve Your Experience" (.cta-block) effect: a slight
   scale-from-centre plus an amber edge glow that arrives on a soft delayed
   beat. ONE source of truth for the big menu frame, its four section cards,
   and the duration callout, so none of them drift into an approximation.
   `.reveal` raises specificity above `.reveal.in-view { transform }`.
   The big frame yields to a hovered section via :not(:has()), so the two
   never fight: hover a gap → the whole menu reacts; hover a section → just it.
   Desktop pointer only. */
@media (hover: hover) and (pointer: fine) {
  /* Segments + duration callout: slight scale-from-centre + amber glow. */
  .menu-group--card:hover,
  .duration-callout.reveal:hover {
    border-color: rgba(var(--line-rgb), 0.38);
    transform: scale(1.01);
    box-shadow:
      0 0 0 1px rgba(var(--line-rgb), 0.1),
      0 0 10px rgba(var(--line-rgb), 0.14),
      0 0 18px rgba(var(--clay-rgb), 0.08);
    transition:
      border-color 0.65s var(--ease-out) 0.08s,
      box-shadow 0.85s cubic-bezier(0.22, 1, 0.36, 1) 0.14s,
      transform 0.6s cubic-bezier(0.33, 0, 0.2, 1) 0.06s;
  }
}

@media (prefers-reduced-motion: reduce) {
  .menu-group--card,
  .duration-callout {
    transition: border-color 0.4s var(--ease-out), box-shadow 0.4s var(--ease-out);
  }
  .menu-group--card:hover,
  .duration-callout.reveal:hover { transform: none; }
}

.menu-card > .eyebrow {
  text-align: center;
  margin-bottom: 0.85rem;
}

.menu-card-title {
  text-align: center;
  font-weight: 400;
  font-size: clamp(1.6rem, 3vw, 2.1rem);
  color: var(--cream);
  margin-bottom: clamp(1.75rem, 3.5vw, 2.5rem);
}

.menu-columns {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: clamp(1.75rem, 3.5vw, 2.75rem) clamp(2.25rem, 5vw, 4rem);
}

.menu-group-name {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  font-family: var(--sans);
  font-size: 0.62rem;
  letter-spacing: 0.28em;
  text-transform: uppercase;
  color: rgb(var(--clay-rgb));
  margin-bottom: 0.75rem;
}

.menu-group-name .orn-glyph {
  width: 17px;
  height: 17px;
  flex: none;
  stroke: var(--bronze);
  stroke-width: 3;
  fill: none;
  stroke-linecap: round;
}

.menu-group-name::after {
  content: '';
  flex: 1;
  height: 1px;
  background: linear-gradient(90deg, rgba(var(--line-rgb), 0.28), transparent);
}

.menu-list {
  list-style: none;
  margin: 0;
  padding: 0;
}

.menu-list a {
  display: flex;
  align-items: baseline;
  gap: 0.8rem;
  padding: 0.52rem 0;
  text-decoration: none;
}

.menu-item-name {
  font-family: var(--serif);
  font-size: clamp(1.02rem, 1.4vw, 1.12rem);
  color: var(--cream-soft);
  transition: color 0.35s ease;
}

.menu-item-dots {
  flex: 1;
  min-width: 1.25rem;
  border-bottom: 1px dotted rgba(var(--line-rgb), 0.35);
  transform: translateY(-0.28em);
  transition: border-color 0.35s ease;
}

.menu-item-meta {
  font-family: var(--sans);
  font-weight: 300;
  font-size: 0.78rem;
  letter-spacing: 0.06em;
  color: var(--cream-muted);
  white-space: nowrap;
  transition: color 0.35s ease;
}

.menu-list a:hover .menu-item-name { color: var(--gold); }
.menu-list a:hover .menu-item-dots { border-color: rgba(var(--line-rgb), 0.7); }
.menu-list a:hover .menu-item-meta { color: var(--gold); }

/* The travel-supplement star under each mobile treatment (not in the menu card).
   Greg, 2026-10-14: people read the price and skip the paragraph, so the
   supplement has to sit next to the number. Quiet, so it never competes with it. */
.treatment-mini .treatment-mini-note {
  font-family: var(--sans);
  font-weight: 300;
  font-style: italic;
  font-size: 0.78rem;
  letter-spacing: 0.05em;
  color: var(--gold);
  opacity: 0.85;
  margin: 0.5rem 0 0;
}

/* Clickable cue: a down-chevron per row signals the item jumps to its full
   details further down the page (color stays gold — the chevron is the new
   affordance). Space is reserved at rest so only opacity/transform animate and
   hover causes no layout shift; kept faintly visible so the cue still reads on
   touch, where there is no hover. */
.menu-list a::after {
  content: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='13' height='13' viewBox='0 0 24 24' fill='none' stroke='%23D4AF87' stroke-width='2.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E");
  flex: none;
  align-self: center;
  line-height: 0;
  opacity: 0.3;
  transform: translateY(-1px);
  transition: opacity 0.35s ease, transform 0.35s ease;
}

/* On hover the single chevron becomes a double chevron — reinforces
   "keep going down to the details". Reverts to single on unhover. */
.menu-list a:hover::after {
  content: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='13' height='16' viewBox='0 0 24 30' fill='none' stroke='%23D4AF87' stroke-width='2.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 6l6 6 6-6'/%3E%3Cpath d='M6 15l6 6 6-6'/%3E%3C/svg%3E");
  opacity: 1;
  transform: translateY(2px);
}

.menu-card-note {
  margin-top: clamp(1.5rem, 3vw, 2rem);
  text-align: center;
}

/* Let the grid track shrink instead of being forced wide by the widest
   row's min-content (the nowrap price) — root cause of the mobile overflow. */
.menu-group { min-width: 0; }

@media (max-width: 760px) {
  .menu-columns { grid-template-columns: 1fr; gap: 1.6rem; }
  .menu-item-meta { font-size: 0.72rem; }
}

/* Narrow phones: the price is white-space:nowrap, so on a single narrow column
   it (and the track's min-content) can spill past the card frame. Stack name
   over price and drop the leader dots; the chevron pins centre-right as the tap
   target. Guarantees the price can never overflow horizontally. */
@media (max-width: 480px) {
  .menu-list a {
    position: relative;
    flex-wrap: wrap;
    align-items: center;
    column-gap: 0.75rem;
    row-gap: 0.1rem;
    padding-right: 1.6rem;
  }
  .menu-item-name { flex: 1 1 100%; min-width: 0; }
  .menu-item-dots { display: none; }
  .menu-item-meta { flex: 0 0 auto; }
  .menu-list a::after {
    position: absolute;
    right: 0;
    top: 50%;
    opacity: 0.6;
    transform: translateY(-50%);
  }
  .menu-list a:hover::after { transform: translateY(-50%); }
}

/* ---- Treatment groups (anchor targets + centred heads) ---- */
.treatment-group {
  padding-top: clamp(3.5rem, 7vw, 6rem);
  scroll-margin-top: 90px;
}

.treatment-group-head {
  text-align: center;
  margin-bottom: clamp(0.5rem, 2vw, 1.5rem);
}

.treatment-group-head .eyebrow { margin-bottom: 1rem; }

.treatment-group-head .ornament {
  justify-content: center;
  margin-top: 1.25rem;
}

/* ---- Compact package cards (no hero image — kills menu sprawl) ---- */
.treatment-minis {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: clamp(1.25rem, 2.5vw, 2rem);
  padding: clamp(2.5rem, 5vw, 3.5rem) 0 clamp(1rem, 3vw, 2rem);
}

.treatment-mini {
  position: relative; /* anchors .treatment-num over the thumbnail */
  display: flex;
  flex-direction: column;
  padding: clamp(1.75rem, 3vw, 2.25rem) clamp(1.5rem, 2.5vw, 2rem);
  border: 1px solid rgba(var(--line-rgb), 0.22);
  background:
    linear-gradient(
      168deg,
      rgba(var(--gold-light-rgb), 0.05) 0%,
      rgba(var(--bg-cool-card-rgb), 0.42) 55%,
      rgba(var(--bg-cool-rgb), 0.6) 100%
    );
  scroll-margin-top: 110px;
  transition:
    border-color 0.5s var(--ease-out),
    transform 0.5s var(--ease-out),
    box-shadow 0.55s cubic-bezier(0.22, 1, 0.36, 1);
}

.treatment-mini:hover {
  border-color: rgba(var(--line-rgb), 0.52);
  transform: translateY(-4px);
  /* Amber edge glow — same recipe as the CTA / menu cards */
  box-shadow:
    0 0 0 1px rgba(var(--line-rgb), 0.1),
    0 0 10px rgba(var(--line-rgb), 0.14),
    0 0 18px rgba(var(--clay-rgb), 0.08);
}

/* The same numeral as the full treatment rows, adapted to a card. There the
   number sits inside a .treatment-visual and offsets from its corner; here the
   card itself is the positioned parent, so the offsets repeat the card's own
   padding to land on the thumbnail's corner, plus a small inset so it sits on
   the picture rather than against its edge. Smaller than 3rem because the card
   is a third of the width — at full size it competed with the heading. */
.treatment-mini .treatment-num {
  position: absolute;
  top: calc(clamp(1.75rem, 3vw, 2.25rem) + 0.6rem);
  left: calc(clamp(1.5rem, 2.5vw, 2rem) + 0.75rem);
  z-index: 3;
  font-family: var(--serif);
  font-size: 2.25rem;
  font-style: italic;
  color: rgba(var(--gold-rgb), 0.25);
  line-height: 1;
}

.treatment-mini .eyebrow {
  font-size: 0.58rem;
  margin-bottom: 0.8rem;
}

/* h3 on standalone pages (back-pain-massage.html), where the card sits under a
   section h2 and skipping a level would break the heading outline; h4 inside
   the services accordion, which is a level deeper. Same card either way. */
.treatment-mini h3,
.treatment-mini h4 {
  font-family: var(--serif);
  font-weight: 400;
  font-size: clamp(1.3rem, 2vw, 1.5rem);
  color: var(--cream);
  margin-bottom: 0.9rem;
}

/* flex-grow deliberately not set here. It used to be `flex-grow: 1`, which was an
   earlier attempt at pushing the price to the bottom of the card — but it matched
   every <p> in the card, including the eyebrow and the price itself, so a card
   with a shorter description shared its spare height between all three. That is
   what put the price rules at different heights: the two shorter cards each gave
   their eyebrow 9 extra pixels and their price 8, and the longest card gave away
   nothing. The bottom alignment is done by `margin-top: auto` on the price
   instead, which absorbs the spare height in one place where it cannot be seen. */
.treatment-mini p {
  font-size: 0.92rem;
  line-height: 1.8;
  color: var(--cream-muted);
  margin-bottom: 1.4rem;
}

/* margin-top: auto is what lines the rules up. The cards are grid items and
   already stretch to equal height, but the descriptions are different lengths,
   so without this each price rule sat directly under its own paragraph and the
   three lines landed at three different heights. Pushing the price block to the
   bottom of the card puts every rule on the same level and leaves the uneven
   space above it, where it is invisible, instead of below it, where it is not. */
.treatment-mini-price {
  font-family: var(--sans);
  font-weight: 300;
  font-size: 0.85rem;
  letter-spacing: 0.05em;
  color: var(--gold);
  margin-top: auto;
  padding-top: 0.9rem;
  border-top: 1px solid rgba(var(--line-rgb), 0.15);
}

/* margin-top: auto pins the BOTTOM of the price block, so the rule above it only
   stays level while every card's price is the same number of lines. It stopped
   being true on 2026-08-13, when Quiet Trance gained a third duration and wrapped
   to two lines while Harmony and Four Hands stayed on one: measured 26-27px out
   at every width where the three sit side by side (768, 1100, 1440, 1920), with
   the card heights themselves still identical. Greg spotted it on the desktop.

   Reserving two lines puts the rules back on one level. Applies to #mobile and
   #extras, the two rows carrying multi-duration prices — Breath Alchemy gained
   a second length the same day and wraps at 768, exactly like Quiet Trance.
   NOT applied to the Reset row: all three of its prices are a single duration,
   they already align, and a min-height there would only add empty space to a
   section Greg has approved.

   3.6em = 2 x the 1.8 line-height, in em so it tracks the font-size at every
   breakpoint; the 0.9rem is the padding, which counts here because the box is
   border-box. CHECK THIS AGAIN DURING ES/DE/PL — a longer translation that wraps
   to three lines breaks it the same way.

   Scoped above 620px as well, because that is where .treatment-minis drops to
   one column: stacked cards have no neighbour to line up with, and reserving
   the second line there would only add empty space under the one-line prices. */
@media (min-width: 621px) {
  #mobile .treatment-mini-price {
    min-height: calc(0.9rem + 3.6em);
  }
}

/* Extras needs the same reservation ONLY where Breath Alchemy actually wraps,
   which is the two-column band. At three columns each card is wide enough to
   hold "20 min — +30€ | 45–90 min — +130€" on one line, so reserving a second
   there would leave visible dead space between the price and the Book link —
   which is exactly what Greg saw on 2026-08-13 when this rule was first applied
   at every width above 620px. Travelling Touch keeps the unconditional version
   above, because Quiet Trance carries three durations and wraps at every width. */
@media (min-width: 621px) and (max-width: 960px) {
  #extras .treatment-mini-price {
    min-height: calc(0.9rem + 3.6em);
  }
}

/* Keeps a duration and its price together when the line wraps. The break falls
   between pairs, at the separator, instead of inside "120 min — 180€". */
.price-pair {
  white-space: nowrap;
}

/* Twilight Cocoon is "coming soon" and so has no Book link, while the two cards
   beside it do. margin-top: auto pins the price block against whatever follows
   it, so Breath Alchemy and Hot Stones sit a button's height higher and
   Twilight Cocoon's rule hung 49px lower than theirs. Pre-existing — found by
   measuring the Extras row on 2026-08-13 while checking a different fix, not
   caused by it, and not something Greg had reported.

   Fixed with a hidden twin of the button rather than a margin, because a margin
   has to be guessed at and then fights the flex free-space distribution: 49.6px
   of margin-bottom moved the rule only 27px. A spacer that IS the button, with
   visibility: hidden, occupies exactly the right space by construction and
   cannot drift when the button's padding or font changes.

   REPLACE with a real Book link when Twilight Cocoon stops being "coming
   soon" — the markup is already the right shape for it. */
.treatment-book--spacer {
  visibility: hidden;
  pointer-events: none;
}

/* Without this the alignment above only half works. The card is a flex column,
   and when one card's description runs a line longer than its neighbours' the
   default flex-shrink squeezes everything else to compensate — the eyebrow lost
   9px and the price rule lost 8px of its own padding, which is exactly the
   misalignment this was meant to fix. Nothing in these cards should shrink; the
   tallest card sets the height and the grid stretches the rest to match. */
.treatment-mini > * { flex-shrink: 0; }

/* "Coming soon" — italic and in clay rather than the gold the live prices use,
   so a decided-but-not-yet-offered item reads differently from a bookable one at
   a glance. Clay is the site's secondary ink; gold would have said "this is the
   price", which is exactly the wrong signal on something nobody can book. */
.coming-soon {
  font-style: italic;
  /* Only the -rgb triplet is tokenised for clay, so this has to go through rgb() */
  color: rgb(var(--clay-rgb));
  letter-spacing: 0.06em;
}

/* Greg's call 2026-08-13: the Book link sat too far below the price on the Reset
   and Extras cards. The price rule and the link belong to each other, and the
   gap read as a break between two things rather than one.

   Almost all of that 29px was NOT this link's margin. `.treatment-mini p` gives
   every paragraph in the card a 1.4rem bottom margin, and the price block is a
   <p>, so it carried 22.4px of its own — and `.treatment-mini p` outranks
   `.treatment-mini-price` on specificity, so zeroing it on the shorter selector
   silently did nothing. It needs the two-class form below. That same 22.4px is
   also why an earlier attempt to reserve the Book link's space with a margin
   came up exactly 22px short.

   Now the link's margin-top is the only thing setting the gap, so it can be
   tuned in one place. The Travelling Touch cards are unaffected — they carry a
   full-width button instead of this link, and Greg confirmed that row is right
   as it stands. */
.treatment-mini .treatment-mini-price { margin-bottom: 0; }
.treatment-mini .treatment-book { margin-top: 0.4rem; }

@media (max-width: 960px) {
  .treatment-minis { grid-template-columns: repeat(2, 1fr); }
}

@media (max-width: 620px) {
  .treatment-minis { grid-template-columns: 1fr; }
}

@media (prefers-reduced-motion: reduce) {
  .treatment-mini,
  .treatment-mini:hover { transform: none; }
}

/* ═══ TREATMENTS ACCORDION (services) ═══
   Four groups, one open at a time. Each group is a raised header bar (the
   decorative section heading, with a +/− glyph) that visibly floats above a
   recessed preview: a row of thumbnails, one per treatment in the group, so
   the count is obvious at a glance. Opening the group hides the preview and
   expands the full big-image + description layout. The preview strip is built
   by initAccordion() in js/main.js. */
/* The accordion manages its own scroll on open; native scroll-anchoring would
   fight that maths when a group collapses above the viewport, so turn it off. */
html {
  overflow-anchor: none;
}

.accordion-list {
  margin-top: clamp(1rem, 3vw, 2.5rem);
  display: flex;
  flex-direction: column;
  gap: clamp(1.5rem, 3.5vw, 2.75rem);   /* clear separation between accordions */
}

.accordion-item {
  position: relative;
  padding-top: 0;                       /* override the stand-alone group spacing */
  scroll-margin-top: 84px;
}

/* ---- The header bar: flat, a single hairline underneath ---- */
.accordion-bar {
  position: relative;
  z-index: 2;
  margin: 0;
  border: 0;
  border-bottom: 1px solid rgba(var(--line-rgb), 0.18);
  transition: border-color 0.5s var(--ease-out);
}

.accordion-item.is-open .accordion-bar {
  border-bottom-color: rgba(var(--line-rgb), 0.42);
}

.accordion-trigger {
  position: relative;
  display: block;
  width: 100%;
  text-align: center;
  background: none;
  border: 0;
  cursor: pointer;
  color: inherit;
  font: inherit;
  padding: clamp(1.5rem, 3.2vw, 2.4rem) clamp(2.75rem, 6vw, 4.5rem);
  transition: background-color 0.5s var(--ease-out);
}

.accordion-trigger:hover {
  background-color: rgba(var(--line-rgb), 0.05);
}

.accordion-trigger:focus-visible {
  outline: 1px solid var(--gold-dim);
  outline-offset: -6px;
}

.accordion-trigger .eyebrow {
  display: block;
  margin-bottom: 0.9rem;
}

.accordion-title {
  display: block;
}

.accordion-trigger .ornament {
  justify-content: center;
  margin: 1.1rem auto 0;
  max-width: 240px;
}

/* +/− glyph, vertically centred at the right edge */
.accordion-icon {
  position: absolute;
  top: 50%;
  right: clamp(1rem, 3vw, 2rem);
  width: 22px;
  height: 22px;
  transform: translateY(-50%);
  pointer-events: none;
}

.accordion-icon::before,
.accordion-icon::after {
  content: '';
  position: absolute;
  left: 50%;
  top: 50%;
  width: 18px;
  height: 1.5px;
  background: var(--gold);
  transform: translate(-50%, -50%);
  transition:
    opacity 0.4s var(--ease-out),
    transform 0.5s var(--ease-out);
}

.accordion-icon::after {
  transform: translate(-50%, -50%) rotate(90deg);
}

/* Open → the vertical stroke rotates away, leaving a minus */
.accordion-item.is-open .accordion-icon::after {
  opacity: 0;
  transform: translate(-50%, -50%) rotate(0deg);
}

/* ---- Collapsed preview: thumbnail row (built by JS) ---- */
.accordion-preview {
  position: relative;
  z-index: 1;
  overflow: hidden;
  cursor: pointer;
  /* Calm ease-in-out (no expo burst) so the fold breathes rather than snaps */
  transition:
    max-height 0.9s var(--ease-slow),
    opacity 0.6s var(--ease-slow),
    transform 0.9s var(--ease-slow);
}

/* Opening: the thumbnails glide up and dissolve behind the header (z-index 2)
   while the full treatments unfold below — a soft swap, not a jump. */
.accordion-item.is-open .accordion-preview {
  pointer-events: none;
  opacity: 0;
  transform: translateY(-1.25rem);
}

.accordion-preview-grid {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: clamp(0.75rem, 1.6vw, 1.3rem);
  padding: clamp(1.25rem, 2.6vw, 2rem) clamp(1.25rem, 2.6vw, 2rem) clamp(0.9rem, 2vw, 1.4rem);
}

.preview-tile {
  flex: 1 1 130px;
  max-width: 220px;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
  padding: 0;
  border: 0;
  background: none;
  font: inherit;
  text-align: center;
  text-decoration: none;
  color: var(--cream-soft);
  cursor: pointer;
  transition: transform 0.4s var(--ease-out);
}

.preview-tile:focus-visible {
  outline: 1px solid var(--gold-dim);
  outline-offset: 3px;
}

.preview-tile:hover {
  transform: translateY(-3px);
}

.preview-tile-media {
  position: relative;
  aspect-ratio: 4 / 3;
  overflow: hidden;
  border: 1px solid rgba(var(--line-rgb), 0.2);
  background: linear-gradient(160deg, rgba(var(--line-rgb), 0.12), rgba(var(--bg-cool-rgb), 0.55));
  box-shadow: 0 6px 16px rgba(var(--shadow-rgb), 0.35);
}

.preview-tile-media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  /* Filter comes from the shared .veil rule so the collapsed thumbnails cannot
     drift from the full images they preview. Do not re-declare it here. */
  transition: transform 0.55s var(--ease-out), filter 0.55s var(--ease-out);
}

/*
 * The three Reset protocol photographs are portrait (769x1153) shown in a 4:3
 * tile, so `cover` scales them to the width and only half the height survives —
 * centred, which took 25% off the top and cut both heads off. A massage
 * thumbnail with no faces in it is the one crop that does not work here.
 *
 * Anchoring the crop near the top keeps the therapist and the client in frame.
 * Scoped to this group rather than to every tile: elsewhere the photographs are
 * landscape and barely cropped, and biasing those upward would push their
 * subjects toward the bottom edge for no reason.
 */
#reset .preview-tile-media img {
  object-position: 50% 12%;
}

/* Per photograph, because the subjects are not at the same height in each frame.
   0% is the top of the image and therefore the hard limit — there is nothing
   above it to reveal. Back Reset stays at 12%: pushed to 0% it keeps the faces
   but loses the hands on the back, which is the thing the picture is of. */
#reset .preview-tile-media img[src$="reset-protocol-neck.jpg"],
#reset .preview-tile-media img[src$="structural-reset.jpg"] {
  object-position: 50% 0%;
}

/* Hover lifts within the veil family — brighter and a touch more colour, but
   never back to the raw file. */
.preview-tile:hover .preview-tile-media img {
  transform: scale(1.05);
  filter: saturate(0.74) contrast(1.06) brightness(0.92) sepia(0.12) hue-rotate(-8deg);
}

/* Image-less treatments (compact "mini" cards) get a symbol tile instead */
.preview-tile-media--glyph {
  display: flex;
  align-items: center;
  justify-content: center;
}

.preview-tile-media--glyph svg {
  width: 40%;
  height: 40%;
  stroke: var(--gold-dim);
  stroke-width: 2;
  fill: none;
  stroke-linecap: round;
  opacity: 0.85;
}

.preview-tile-name {
  font-family: var(--serif);
  font-weight: 400;
  font-size: clamp(0.8rem, 1.35vw, 0.98rem);
  line-height: 1.3;
  color: var(--cream);
  text-align: center;
}

.accordion-preview-count {
  text-align: center;
  font-size: 0.6rem;
  font-weight: 500;
  letter-spacing: 0.26em;
  text-transform: uppercase;
  color: var(--gold);
  padding: 0 1rem clamp(1.1rem, 2.2vw, 1.6rem);
}

/* ---- Collapsible body (full treatments) ---- */
.accordion-body {
  max-height: 0;
  overflow: hidden;
  /* Slow ease-in-out to match the site's unhurried motion */
  transition: max-height 0.9s var(--ease-slow);
}

.accordion-body-inner > .treatment:first-child,
.accordion-body-inner > .treatment-minis:first-child {
  padding-top: clamp(1.75rem, 3.5vw, 2.75rem);
}

/* ═══ back-pain-massage.html section rhythm ═══ */

/*
 * The intro sat flush against the hero — measured 0px, because it carried an
 * inline padding-top: 0. It needs air, but not a full section's worth: this is
 * a continuation of the headline above it, not a new subject.
 */
.section--intro {
  padding-top: clamp(2.5rem, 5vw, 4rem);
  padding-bottom: clamp(3rem, 6vw, 4.5rem);
}

/* Matches the h1's measured width (890px) so the two align as one column */
.section--intro .lead {
  max-width: 890px;
  margin: 0 auto;
}

/* An intro of more than one paragraph needs its own spacing: the global reset
   zeroes every margin and `.lead` restores none, so stacked paragraphs sat
   flush and read as a slab. Same failure `.split-text > * + *` fixes further
   down — this is the version for intro blocks. Specificity beats the rule
   above, so `margin: 0 auto` does not win the top margin back. */
.section--intro .lead + .lead {
  margin-top: 1.35rem;
}

/* The "after an accident" block on back-pain-massage.html. Same failure as the
   two rules above and as `.split-text > * + *`: the global reset zeroes every
   margin and `.body-text` restores none, so four stacked paragraphs would read
   as one slab. Same 1.35rem everywhere else on the page uses, so the rhythm
   matches the split section directly above it.
   The heading needs more air beneath it than the paragraphs need between them,
   which is why the eyebrow and h2 get their own steps. */
.section--accident > .container > * + * {
  margin-top: 1.35rem;
}

.section--accident .eyebrow + h2 {
  margin-top: 0.9rem;
}

.section--accident h2 + .body-text {
  margin-top: 1.75rem;
}

/*
 * The heading accent and the cards were 116px apart — the intro's 60px bottom
 * margin and the grid's own 56px top padding stacking on top of each other,
 * plus the accent's own generous box. Drop the grid padding and halve the
 * margin; the accent already supplies breathing room of its own.
 */
.section--protocols .testimonials-intro {
  margin-bottom: clamp(1rem, 2vw, 1.5rem);
}

.section--protocols .treatment-minis {
  padding-top: 0;
}

/* Two cards rather than three — The Human Craft moved out to its own row.
   Scoped above 620px so it cannot override the single-column phone rule that
   the three-card groups already fall back to. */
@media (min-width: 621px) {
  .treatment-minis--pair {
    grid-template-columns: repeat(2, 1fr);
  }
}

@media (max-width: 620px) {
  .preview-tile { flex-basis: 40%; }
}

@media (prefers-reduced-motion: reduce) {
  .accordion-body,
  .accordion-preview { transition: none; }
  .accordion-icon::before,
  .accordion-icon::after { transition: none; }
  .preview-tile:hover { transform: none; }
  .preview-tile:hover .preview-tile-media img { transform: none; }
}

/* ---- gap-filler cells: explicit placement into the two
   masonry holes (r7-8 c5-7 and r12-13 c1-4). Definite positions are
   placed before auto-flow, so cells 1-9 keep their exact slots. ---- */
.gallery-cell:nth-child(10) {
  grid-column: 5 / span 3;
  grid-row: 7 / span 2;
}

.gallery-cell:nth-child(11) {
  grid-column: 1 / span 4;
  grid-row: 12 / span 2;
}

/* ---- Gallery coda: two wide frames below the parallax band ----
 *
 * A continuation of the masonry above, not a second gallery — same cell, same
 * dim-to-colour hover, same label. It sits after the band so the page reads
 * grid → pause → two last frames, and the pause is what keeps eleven cells
 * from becoming thirteen.
 *
 * Deliberately uneven: 1.15fr / 0.85fr. Both photographs are 16:9 still-lifes
 * of the same room shot the same way, and split down the middle they read as a
 * matched pair, which is the one arrangement that looks like filler. The
 * uneven split gives the eye a reason to go left first.
 *
 * `grid-column: auto` is load-bearing. The nth-child recipes above are written
 * against .gallery-masonry but selected on .gallery-cell alone, so without the
 * reset these two would claim spans of 7 and 5 columns in a 2-column grid.
 */
.gallery-coda {
  display: grid;
  /* Narrow left, wide right. */
  grid-template-columns: 0.85fr 1.15fr;
  gap: 1rem;
  margin-top: 1rem;
}

.gallery-coda .gallery-cell {
  grid-column: auto;
  grid-row: auto;
  /* Each cell states its own box. The earlier version gave only the right-hand
     cell a ratio and let the left stretch to match — which depends on the row
     resolving from a sibling and did not hold. Both are explicit now, so the
     heights are equal by arithmetic rather than by layout side effect. */
  height: auto;
}

/* Equal heights out of unequal widths: the narrower column needs the squarer
   ratio. 16/9 scaled by 0.85/1.15 ≈ 1.183. The photograph is object-fit:
   cover, so it crops into that box rather than distorting, and the lightbox
   opens the original file at its true proportion. */
.gallery-coda .gallery-cell:first-child {
  aspect-ratio: calc((16 / 9) * (0.85 / 1.15));
}

/* The wide one keeps the photograph's own 16:9 — it is the frame doing the
   least cropping, so it takes the shot as it was composed. */
.gallery-coda .gallery-cell:last-child {
  aspect-ratio: 16 / 9;
}

@media (max-width: 860px) {
  .gallery-coda {
    grid-template-columns: 1fr;
    gap: 0.85rem;
    margin-top: 0.85rem;
  }

  /* Stacked, there is no neighbour to match a height to, so the cropped frame
     gets its own shape back. */
  .gallery-coda .gallery-cell:first-child {
    aspect-ratio: 16 / 9;
  }
}

/* ═══════════════════════════════════════════════════════════════
   Nav micro-interactions — logo ripple + flag→WhatsApp cascade
   Pure CSS. Runs only on hover / a slow ambient loop; zero cost
   at load, GPU-composited (transform / opacity / box-shadow only).
   ═══════════════════════════════════════════════════════════════ */

/* ── Logo ripple: per-letter cascade radiating from the centre ──
   On hover the centre image pops first, then each word ripples
   OUTWARD from the logo: "Trance" fires e→T (right-to-left) and
   "Ritual" fires R→l (left-to-right), symmetrically — same pop
   (scale 1.3 + lift) as the flag wave, with a warm gold shimmer. */
.logo-ch {
  display: inline-block;
  will-change: transform;
}

/* Centre image resizes slightly first (delay 0) */
.logo-link--split:hover .logo-img {
  animation: logoImgPop 0.5s ease-in-out;
}

@keyframes logoImgPop {
  0%, 100% { transform: scale(1); }
  50%      { transform: scale(1.12); }
}

/* Letters cascade after the image, stepping outward from centre */
.logo-link--split:hover .logo-ch {
  animation: logoChPop 0.5s ease-in-out both;
}

@keyframes logoChPop {
  0%, 100% { transform: translateY(0) scale(1);        color: var(--cream); }
  50%      { transform: translateY(-2px) scale(1.3);   color: var(--gold-light); }
}

/* Left word "Trance": nearest centre = last letter (e) goes first */
.logo-word--left  .logo-ch:nth-child(6) { animation-delay: 0.12s; } /* e */
.logo-word--left  .logo-ch:nth-child(5) { animation-delay: 0.19s; } /* c */
.logo-word--left  .logo-ch:nth-child(4) { animation-delay: 0.26s; } /* n */
.logo-word--left  .logo-ch:nth-child(3) { animation-delay: 0.33s; } /* a */
.logo-word--left  .logo-ch:nth-child(2) { animation-delay: 0.40s; } /* r */
.logo-word--left  .logo-ch:nth-child(1) { animation-delay: 0.47s; } /* T */

/* Right word "Ritual": nearest centre = first letter (R) goes first */
.logo-word--right .logo-ch:nth-child(1) { animation-delay: 0.12s; } /* R */
.logo-word--right .logo-ch:nth-child(2) { animation-delay: 0.19s; } /* i */
.logo-word--right .logo-ch:nth-child(3) { animation-delay: 0.26s; } /* t */
.logo-word--right .logo-ch:nth-child(4) { animation-delay: 0.33s; } /* u */
.logo-word--right .logo-ch:nth-child(5) { animation-delay: 0.40s; } /* a */
.logo-word--right .logo-ch:nth-child(6) { animation-delay: 0.47s; } /* l */

/* ── Flag cascade → WhatsApp flash ──
   Ambient loop (auto): a gentle wave ripples across the flags every
   ~13s and lands on a soft WhatsApp glow. Animated on the inner <img>
   so the existing per-flag :hover scale (on .lang-flag) still works.
   Hover (fuller/faster): hovering the whole header-right replays a
   quicker cascade once, ending on a brighter WhatsApp pulse. */
.lang-flag img {
  animation: flagAmbient 13s ease-in-out infinite;
  will-change: transform;
}
.lang-flags .lang-flag:nth-child(1) img { animation-delay: 0.00s; }
.lang-flags .lang-flag:nth-child(2) img { animation-delay: 0.13s; }
.lang-flags .lang-flag:nth-child(3) img { animation-delay: 0.26s; }
.lang-flags .lang-flag:nth-child(4) img { animation-delay: 0.39s; }
.lang-flags .lang-flag:nth-child(5) img { animation-delay: 0.52s; }
.lang-flags .lang-flag:nth-child(6) img { animation-delay: 0.65s; }

@keyframes flagAmbient {
  0%, 84%, 93%, 100% { transform: scale(1);                      filter: none; }
  88%                { transform: scale(1.3) translateY(-2px);   filter: brightness(1.15) saturate(1.1); }
}

/* WhatsApp ambient glow, timed to arrive just after the last flag */
.header-right .btn-wa {
  animation: waAmbient 13s ease-in-out infinite;
  animation-delay: 0.82s;
}

@keyframes waAmbient {
  0%, 84%, 96%, 100% {
    box-shadow: 0 0 0 0 rgba(37, 211, 102, 0);
    border-color: rgba(var(--line-rgb), 0.2);
    color: var(--cream-muted);
  }
  90% {
    box-shadow: 0 0 15px 2px rgba(37, 211, 102, 0.5);
    border-color: #25D366;
    color: #25D366;
  }
}

/* Hover: fuller cascade, plays once per hover-enter (no fill → the
   ambient loop and per-flag :hover cleanly resume afterwards) */
.header-right:hover .lang-flag img {
  animation: flagWave 0.9s ease-in-out;
}
.header-right:hover .lang-flags .lang-flag:nth-child(1) img { animation-delay: 0.00s; }
.header-right:hover .lang-flags .lang-flag:nth-child(2) img { animation-delay: 0.13s; }
.header-right:hover .lang-flags .lang-flag:nth-child(3) img { animation-delay: 0.26s; }
.header-right:hover .lang-flags .lang-flag:nth-child(4) img { animation-delay: 0.39s; }
.header-right:hover .lang-flags .lang-flag:nth-child(5) img { animation-delay: 0.52s; }
.header-right:hover .lang-flags .lang-flag:nth-child(6) img { animation-delay: 0.65s; }

/* Same scale / lift / brightness as flagAmbient's peak — hover just
   replays the identical wave on demand. */
@keyframes flagWave {
  0%, 100% { transform: scale(1);                    filter: none; }
  50%      { transform: scale(1.3) translateY(-2px); filter: brightness(1.15) saturate(1.1); }
}

.header-right:hover .btn-wa {
  animation: waWave 0.9s ease-in-out 0.82s;
}

/* Mirrors waAmbient's glow so the hover ending matches the loop */
@keyframes waWave {
  0%, 100% {
    box-shadow: 0 0 0 0 rgba(37, 211, 102, 0);
    border-color: rgba(var(--line-rgb), 0.2);
    color: var(--cream-muted);
  }
  50% {
    box-shadow: 0 0 15px 2px rgba(37, 211, 102, 0.5);
    border-color: #25D366;
    color: #25D366;
  }
}

/* Motion-sensitive visitors: no ripple, no cascade, no ambient loop */
@media (prefers-reduced-motion: reduce) {
  .logo-link--split:hover .logo-img,
  .logo-link--split:hover .logo-ch,
  .lang-flag img,
  .header-right .btn-wa,
  .header-right:hover .lang-flag img,
  .header-right:hover .btn-wa {
    animation: none !important;
  }
}

/* ═══════════════════════════════════════════════════════════════
   Nav contact system — phone-icon popup + language picker + mobile
   burger contact block. Desktop keeps inline nav links; the burger
   (with phone + flags at its base) appears only on mobile.
   ═══════════════════════════════════════════════════════════════ */

/* Circle icon button (phone) — matches the .btn-wa footprint */
.btn-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  width: 34px;
  height: 34px;
  padding: 0;
  border: 1px solid rgba(var(--line-rgb), 0.2);
  border-radius: 50%;
  background: none;
  color: var(--cream-muted);
  cursor: pointer;
  transition: color 0.4s var(--ease-out), border-color 0.4s var(--ease-out);
}
.btn-icon:hover,
.btn-icon[aria-expanded="true"] {
  color: var(--gold);
  border-color: var(--gold-dim);
}
.btn-icon svg { width: 17px; height: 17px; fill: currentColor; }

/* Phone popup: number + flags, revealed on click */
/* Desktop shows the full number + flag row; the compact icon + popup
   is a phone-only pattern (re-enabled in the mobile media block). */
.header-call { position: relative; display: none; }
.call-popup {
  position: absolute;
  top: calc(100% + 0.65rem);
  right: 0;
  display: flex;
  align-items: center;
  gap: 0.85rem;
  padding: 0.6rem 1rem;
  background: rgba(26, 22, 18, 0.98);
  border: 1px solid rgba(var(--line-rgb), 0.18);
  border-radius: 999px;
  box-shadow: 0 18px 50px rgba(var(--shadow-rgb), 0.5);
  white-space: nowrap;
  z-index: 300;
  opacity: 0;
  visibility: hidden;
  transform: translateY(-6px);
  pointer-events: none;
  transition: opacity 0.3s var(--ease-out), transform 0.3s var(--ease-out), visibility 0.3s;
}
.call-popup.is-open {
  opacity: 1;
  visibility: visible;
  transform: translateY(0);
  pointer-events: auto;
}
.call-popup-number {
  font-size: 0.9rem;
  letter-spacing: 0.05em;
  color: var(--cream);
  transition: color 0.3s;
}
.call-popup-number:hover { color: var(--gold); }
.call-popup-sep {
  width: 1px;
  height: 18px;
  background: rgba(var(--line-rgb), 0.25);
  flex-shrink: 0;
}

/* Language picker */
.lang-picker { position: relative; display: inline-flex; }
.lang-picker-toggle {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  height: 34px;
  padding: 0 0.5rem;
  border: 1px solid rgba(var(--line-rgb), 0.2);
  border-radius: 999px;
  background: none;
  color: var(--cream-muted);
  cursor: pointer;
  font-family: inherit;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  transition: color 0.4s var(--ease-out), border-color 0.4s var(--ease-out);
}
.lang-picker-toggle:hover,
.lang-picker-toggle[aria-expanded="true"] {
  color: var(--gold);
  border-color: var(--gold-dim);
}
.lang-picker-flag {
  width: 18px;
  height: 18px;
  border-radius: 50%;
  object-fit: cover;
  display: block;
  flex-shrink: 0;
}
/* Code + chevron stacked to keep the pill narrow (arrow under the letters) */
.lang-picker-stack {
  display: inline-flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  line-height: 1;
  gap: 2px;
}
.lang-picker-code { font-weight: 500; font-size: 0.6rem; }
.lang-picker-chevron {
  width: 9px;
  height: 5px;
  color: currentColor;
  transition: transform 0.35s var(--ease-out);
}
.lang-picker-toggle[aria-expanded="true"] .lang-picker-chevron { transform: rotate(180deg); }

.lang-picker-menu {
  position: absolute;
  top: calc(100% + 0.5rem);
  right: 0;
  width: max-content;
  min-width: 0;
  margin: 0;
  padding: 0.4rem;
  list-style: none;
  background: rgba(26, 22, 18, 0.98);
  border: 1px solid rgba(var(--line-rgb), 0.18);
  border-radius: 14px;
  box-shadow: 0 18px 50px rgba(var(--shadow-rgb), 0.5);
  z-index: 300;
  opacity: 0;
  visibility: hidden;
  transform: translateY(-6px);
  pointer-events: none;
  transition: opacity 0.3s var(--ease-out), transform 0.3s var(--ease-out), visibility 0.3s;
}
.lang-picker-menu.is-open {
  opacity: 1;
  visibility: visible;
  transform: translateY(0);
  pointer-events: auto;
}
.lang-option {
  display: flex;
  align-items: center;
  gap: 0.7rem;
  padding: 0.6rem 0.7rem;
  border-radius: 9px;
  font-size: 0.85rem;
  color: var(--cream);
  cursor: pointer;
  transition: background 0.25s;
}
.lang-option img {
  width: 20px;
  height: 20px;
  border-radius: 50%;
  object-fit: cover;
  flex-shrink: 0;
}
.lang-option.is-active {
  background: rgba(var(--line-rgb), 0.14);
  color: var(--gold-light);
}
.lang-option:not(.is-soon):hover { background: rgba(var(--gold-rgb), 0.1); }
.lang-option.is-soon {
  color: var(--cream-muted);
  cursor: default;
  opacity: 0.65;
}
.lang-option.is-soon em {
  margin-left: auto;
  font-style: normal;
  font-size: 0.6rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--gold-dim);
}

/* Burger contact block hidden by default (shown only inside .nav.open) */
.nav-contact { display: none; }

/* While the burger is open, drop the header's (identity) transform so the
   fixed full-screen menu is contained by the viewport, not the header.
   Header bar also goes solid black instantly — the menu panel starts below
   the bar, so a transparent bar would leave a strip of hero showing. */
body.nav-open .header {
  transform: none !important;
  background-color: var(--bg) !important;
  background-image: none !important;
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
  border-bottom-color: rgba(var(--line-rgb), 0.1);
  transition: none;
}

/* Burger lines morph into an X when the menu is open */
.menu-toggle span { transform-origin: center; }
.menu-toggle[aria-expanded="true"] span:nth-child(1) { transform: translateY(7px) rotate(45deg); }
.menu-toggle[aria-expanded="true"] span:nth-child(2) { opacity: 0; }
.menu-toggle[aria-expanded="true"] span:nth-child(3) { transform: translateY(-7px) rotate(-45deg); }

/* Keep the wordmark on one line (per-letter spans must never wrap) */
.logo-word { white-space: nowrap; }

/* Portrait phones: drop the wordmark, keep just the icon.
   Landscape phones keep the name — there's room. */
@media (max-width: 768px) and (orientation: portrait) {
  .logo-word { display: none; }
}

/* Header collapses to the burger on phones and tablets — narrow OR short
   (landscape). Real laptops keep the inline nav.

   975, not 768. Measured 2026-08-11 across 700–1300px in 25px steps: with the
   nav inline, the gap between the wordmark's painted right edge and the first
   nav link is −44px from 775 to 900, −23 at 925, −3 at 950, and only turns
   positive at 975. Below that the nav is drawn *on top of* the wordmark —
   "HOME" over "RITUAL". iPad Air portrait (820) and iPad Pro 11" portrait
   (834) both sat inside the broken band, so the brand name was mangled on the
   likeliest tablet. The burger is verified working in both orientations. */
@media (max-width: 975px), (orientation: landscape) and (max-height: 500px) {
  .header-inner { grid-template-columns: 1fr auto; }
  .nav { display: none; }
  .menu-toggle { display: block; }

  /* Tighter cluster so the icons hug the right edge, clear of the logo */
  .header-right { gap: 0.45rem; }

  /* Phones: swap the desktop number+flags block for the icon + popup */
  .header-contact-block { display: none; }
  .header-call { display: inline-flex; }

  .nav.open {
    display: flex;
    flex-direction: column;
    position: fixed;
    top: var(--header-h);
    left: 0;
    right: 0;
    bottom: 0;
    justify-self: stretch;
    align-items: center;
    justify-content: flex-start;
    /* Tightened from 1.5rem — the panel read as over-spaced, and the submenu
       added three more lines. The links carry their own padding, so the pitch
       barely moves while every target gets bigger. */
    gap: 0.6rem;
    padding: 2.2rem 2rem 2rem;
    background: var(--bg);
    overflow-y: auto;
  }
  /* Same trick as the submenu: spacing lives in the link, not the gap, so the
     tap target is the whole row. These were 33px on gap alone. */
  .nav.open > a,
  .nav.open .nav-item--has-sub > a {
    font-size: 0.9rem;
    padding: 0.35rem 1rem;
  }

  /* No dropdown on phones. The burger is already a full panel, so the children
     just sit indented under Services — always visible, nothing to tap open,
     and nothing that can misbehave in landscape. */
  .nav.open .nav-item--has-sub {
    flex-direction: column;
    align-items: center;
    gap: 1.1rem;
  }

  .nav.open .nav-sub {
    position: static;
    transform: none;
    opacity: 1;
    visibility: visible;
    pointer-events: auto;
    background: none;
    border: none;
    box-shadow: none;
    padding: 0;
    min-width: 0;
    /* Spacing moves out of the gap and into the links themselves: the visual
       pitch stays the same, but the tap target becomes the whole row instead
       of just the height of the text. Gap-only spacing measured 30px, well
       under the 44px touch guideline. */
    gap: 0.35rem;
    transition: none;
  }

  .nav.open .nav-sub a {
    font-size: 0.78rem;
    color: var(--cream-muted);
    padding: 0.4rem 0.6rem;
  }

  .nav.open .nav-contact {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 1.1rem;
    width: 100%;
    margin-top: auto;
    padding-top: 2rem;
    border-top: 1px solid rgba(var(--line-rgb), 0.15);
  }
  .nav.open .nav-phone {
    display: inline-flex;
    align-items: center;
    gap: 0.6rem;
    font-size: 1.05rem;
    letter-spacing: 0.05em;
    color: var(--gold-light);
  }
  .nav.open .nav-phone svg { width: 18px; height: 18px; fill: currentColor; }
  .nav.open .nav-flags { gap: 0.55rem; }
  .nav.open .nav-flags .lang-flag img { width: 22px; height: 22px; }
}

/* ── Compact phone header ──
   Shorter fixed bar frees vertical space on phones; --header-h feeds the
   burger panel top, call popup and page-hero paddings automatically. */
@media (max-width: 768px) {
  :root { --header-h: 62px; }
  .logo-img { height: 34px; }

  /* Slide dots get their own strip below the CTA button (no overlay) */
  .hero { padding-bottom: 5rem; }
  .hero-dots { bottom: 0.5rem; }
}

@media (orientation: landscape) and (max-height: 500px) {
  :root { --header-h: 52px; }
  .logo-img { height: 28px; }
  .logo-text { font-size: 0.85rem; }
  .logo-link--split { gap: 0.5rem; }

  /* Hero: clear the fixed bar (nav on top, letters below it) and tighten
     the type/rhythm so the short viewport shows more of the scene. */
  .hero {
    padding-top: calc(var(--header-h) + 0.75rem);
    padding-bottom: 4.25rem;
  }
  .hero-title { font-size: clamp(2.5rem, 14vh, 4rem); margin-bottom: 1.25rem; }
  .hero-eyebrow { margin-bottom: 0.75rem; }
  .hero-lead { margin-bottom: 1.5rem; }
  /* Same for landscape: dots below the CTA, not on it */
  .hero-dots { bottom: 0.4rem; }

  /* Burger menu, landscape: tighter rhythm so all five links plus the
     contact row fit the short viewport without scrolling. The visible
     gap shrinks but each link gains padding — tap pitch stays ~38px. */
  .nav.open {
    gap: 0.2rem;
    padding: 0.75rem 2rem 0.75rem;
  }
  .nav.open > a,
  .nav.open .nav-item--has-sub > a { padding: 0.55rem 1.5rem; }

  /*
   * Landscape only: Services and its three children share one row instead of
   * four. The parent sits left, the submenu becomes a column to its right,
   * with a hairline joining them so the grouping is legible. Reclaims a full
   * line of a very short viewport.
   *
   * The children lose their gap and gain padding instead — the visible spacing
   * shrinks while the tap target grows, which is the opposite of what reducing
   * the gap alone would do.
   */
  .nav.open .nav-item--has-sub {
    flex-direction: row;
    align-items: center;
    justify-content: center;
    gap: 1.1rem;
  }

  .nav.open .nav-sub {
    align-items: flex-start;
    gap: 0;
    padding-left: 1.1rem;
    border-left: 1px solid rgba(var(--line-rgb), 0.22);
  }

  /* 0.55rem, not less: it puts the tap target at ~38px. Tighter looked better
     and measured 34px, which is under the 44px touch guideline by too much for
     a link people use one-handed on a phone. The 12px it costs is affordable —
     the two-column arrangement already bought back ~75px. */
  .nav.open .nav-sub a {
    padding: 0.55rem 0.5rem;
    font-size: 0.72rem;
  }

  .nav.open .nav-contact {
    flex-direction: row;
    justify-content: center;
    gap: 1.25rem;
    padding-top: 0.7rem;
  }
  .nav.open .nav-phone { font-size: 0.95rem; }
  .nav.open .nav-flags .lang-flag img { width: 20px; height: 20px; }
}

/* Phone call popup: centered under the bar, near-square corners.
   position: fixed resolves against the header (it has a transform),
   whose box matches the viewport top strip — coordinates line up. */
/* Must track the header query above: that one switches the desktop number
   block for the phone icon, and this is the popup that icon opens. If the two
   ranges disagree, the icon appears with the wrong popup positioning. */
@media (max-width: 975px), (orientation: landscape) and (max-height: 500px) {
  .call-popup {
    position: fixed;
    top: calc(var(--header-h) + 0.6rem);
    left: 50%;
    right: auto;
    transform: translate(-50%, -6px);
    justify-content: center;
    flex-wrap: wrap;
    row-gap: 0.5rem;
    max-width: min(92vw, 430px);
    white-space: normal;
    border-radius: 5px;
    padding: 0.7rem 1rem;
  }
  .call-popup.is-open { transform: translate(-50%, 0); }
  /* Stacked rows don't need the vertical divider */
  .call-popup-sep { display: none; }

  /* Language dropdown: same near-square corners as the call popup,
     tighter rows so the list takes less vertical space */
  .lang-picker-menu { border-radius: 5px; padding: 0.3rem; }
  .lang-option { border-radius: 3px; padding: 0.4rem 0.6rem; }
}

/* Reduced motion: pop instantly, no slide */
@media (prefers-reduced-motion: reduce) {
  .call-popup,
  .lang-picker-menu {
    transition: opacity 0.001s, visibility 0.001s;
    transform: none;
  }
}

/* --- Language picker: the options are real links (2026-10-01) -------------
   They used to be inert <li data-lang> elements that only JavaScript could
   see, so Google could not follow them and the four languages were never
   associated with each other. They are now <li><a href hreflang>.

   The row layout moves from the <li> onto the <a> so the whole padded row
   stays clickable instead of only the text. Appearance is unchanged: the
   padding and radius values here are the ones the later .lang-option
   override already set. */
.lang-option {
  display: block;
  padding: 0;
}

.lang-option > a {
  display: flex;
  align-items: center;
  gap: 0.7rem;
  padding: 0.4rem 0.6rem;
  border-radius: 3px;
  color: inherit;
  text-decoration: none;
}

/* Trap for robots: invisible to people, tempting to a script that fills every field. */
.hp-field {
  position: absolute;
  left: -9999px;
  width: 1px;
  height: 1px;
  overflow: hidden;
  opacity: 0;
}
