/* ==============================================================
   Buddha Spa
   1. Tokens
   2. Base
   3. Curtain intro
   4. Hero
   5. Top bar
   6. Header
   7. Drawer
   8. Section furniture — headings, eyebrow, icons, reveal
   9. Ritual band — the scrolling treatment marquee
  10. About
  11. Services — the treatment carousel
  12. Booking — the #book target
  13. Promo banner
  14. Gallery — mosaic, filter, lightbox
  15. Footer
  16. Hanging lantern — reusable decoration
  17. Visit timeline
  18. Page banner — shared by every inner page
  19. About page
  20. Services page
  21. Gallery page
  22. Why choose us — home
  23. Leaf watermark — reusable decoration
  24. About page
  25. Treatment detail page
  26. Contact page
   ============================================================== */


/* ── 1. Tokens ────────────────────────────────────────────── */

:root {
  /* Palette. Everything warm — there is no neutral grey on this
     site, only browns that read as grey next to the gold. */
  --ink:        #0B0603;   /* deepest, behind the curtain      */
  --ink-2:      #130B05;   /* page background                  */
  --surface:    #1D1209;   /* raised panels                    */
  --surface-2:  #2A1A0E;   /* cards                            */

  --gold:       #D9A441;
  --gold-soft:  #EBCE8C;
  --gold-dim:   #A97C2E;
  --ember:      #C4622A;   /* the orange in the lantern glow   */

  /* The warm end of the metal. Solid gold buttons run from the
     pale highlight down through these, so the gradient reads as
     gold catching lamplight rather than flat yellow paint.
     --gold-ember is kept just light enough to hold 4.5:1 against
     the near-black button label at its darkest corner. */
  --gold-warm:  #CE8B33;
  --gold-ember: #C2712A;

  /* Gold mixed toward the ember. Every frame and bloom around an
     image is cast in this rather than flat gold — the light in
     this room is orange, and a straw-yellow halo sitting on an
     amber photograph reads as a sticker rather than as light.
     Kept as a bare triplet so each use sets its own alpha. */
  --glow-rgb: 226, 132, 52;

  --cream:      #F4EADB;   /* display text                     */
  --tan:        #BCA88C;   /* body text                        */
  --tan-dim:    #8C7A63;

  --line:       rgba(217, 164, 65, .22);
  --line-soft:  rgba(217, 164, 65, .12);

  /* Type */
  --font-display: "Libre Baskerville", Georgia, "Times New Roman", serif;
  --font-body:    "Poppins", system-ui, -apple-system, sans-serif;
  --font-deva:    "Noto Serif Devanagari", "Nirmala UI", "Mangal", serif;

  /* One spacing scale. Nothing outside it. */
  --s1: 0.5rem;
  --s2: 1rem;
  --s3: 1.5rem;
  --s4: 2.5rem;
  --s5: 4rem;
  --s6: 6rem;
  --s7: 9rem;

  /* Three radii, plus the arch. */
  --r-sm:   4px;
  --r-md:   14px;
  --r-pill: 999px;
  --r-arch: 50% 50% var(--r-md) var(--r-md) / 34% 34% var(--r-md) var(--r-md);

  /* Two shadows. */
  --shadow-1: 0 18px 40px -28px rgba(0, 0, 0, .9);
  --shadow-2: 0 34px 70px -34px rgba(0, 0, 0, .95);

  /* One easing pair, one duration scale. */
  --ease:       cubic-bezier(.22, 1, .36, 1);
  --ease-inout: cubic-bezier(.65, 0, .35, 1);
  --dur-1: .28s;
  --dur-2: .6s;
  --dur-3: .9s;

  /* Layout */
  --shell-max: 1320px;
  --shell-pad: 1.5rem;

  /* One knob for section rhythm. Worth knowing that stacked
     padding is what makes a page feel loose: the gap between two
     sections is this value TWICE, so what reads on screen is
     6.5rem, not 3.25rem.

     Off the step scale on purpose. --s4 (2.5rem) was too tight
     and --s5 (4rem) was the value that felt wasteful, and there
     is nothing between them — so rather than scatter a magic
     number through twenty rules, the one exception lives here. */
  --sect-pad: 3.25rem;
  --header-h:  64px;
  --topbar-h:  40px;

  /* Curtain timing. Change --intro-total and the whole sequence
     rescales — every keyframe below is a percentage of it. */
  --intro-total: 2.8s;

  /* How long everything on the page waits for the curtain.
     Zero when there is no intro, so pages without it just play. */
  --reveal-delay: 0s;

  /* The two full-bleed backdrops. Both are set here because a
     url() inside a custom property resolves against the
     STYLESHEET, not the page — hence the ../ — which is exactly
     why every other photograph on the site is a real <img>. */
  --hero-bg:  url("../images/hero/hero-bg.png");
  --promo-bg: url("../images/promo/promo.png");
  /* Its own small file on purpose. The footer is on EVERY page,
     and this sits at 16% behind a mask — no detail survives that
     to justify shipping a two-megabyte PNG site-wide. */
  --foot-bg:  url("../images/footer/footer.jpg");
}


/* ── 2. Base ──────────────────────────────────────────────── */

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

.has-intro { --reveal-delay: calc(var(--intro-total) * .60); }

html {
  scroll-behavior: smooth;
  /* Tells the browser the page is dark, so it renders native
     form controls, the date picker popup and the scrollbars to
     match instead of dropping a white panel onto the page. */
  color-scheme: dark;
}

html,
body { overflow-x: clip; }

body {
  margin: 0;
  background: var(--ink-2);
  color: var(--tan);
  font-family: var(--font-body);
  font-size: 15px;
  font-weight: 500;
  line-height: 1.7;
  -webkit-font-smoothing: antialiased;
}

/* No scrolling behind the curtain. */
body.is-locked { overflow: hidden; }

h1, h2, h3, p, figure, blockquote { margin: 0; }
ul, ol { margin: 0; padding: 0; list-style: none; }
img, svg { display: block; max-width: 100%; }

/* Remix Icon glyphs. An icon font is sized by font-size, not by
   width and height — the box follows the glyph, so every rule
   below sets a size rather than a pair of dimensions. */
.ico {
  display: inline-block;
  flex: none;
  line-height: 1;
  font-style: normal;
}
a { color: inherit; text-decoration: none; }
button { font: inherit; color: inherit; }

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

.shell {
  width: 100%;
  max-width: var(--shell-max);
  margin-inline: auto;
  padding-inline: var(--shell-pad);
}

.sr-only {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}


/* ── 3. Curtain intro ─────────────────────────────────────────
   Sequence, as a fraction of --intro-total (2.8s):

     0.00s   closed
     0.00s → 0.75s   part to 30% — the dark centre opens up
     0.42s → 1.23s   gold mark and greeting fade up
     1.54s → 1.90s   greeting lifts away
     1.71s → 2.80s   panels draw fully off
     1.71s → 2.69s   backdrop fades, revealing the hero beneath
     1.96s           hero rises
     2.80s   overlay removed

   The greeting's exit overlaps the panels opening on purpose —
   namaste hands straight over to the hero with no dead beat.

   Everything is CSS animation — the JS only decides whether to
   play it at all, and clears up afterwards.                     */

.curtain {
  position: fixed;
  inset: 0;
  z-index: 9000;
  overflow: hidden;
  /* Nothing animates until .is-playing lands, so the greeting
     cannot flash in an unloaded font. */
}

.curtain__backdrop {
  position: absolute;
  inset: 0;
  background:
    radial-gradient(70% 55% at 50% 46%, rgba(196, 98, 42, .16) 0%, rgba(196, 98, 42, 0) 70%),
    var(--ink);
}

/* --- Panels -------------------------------------------------- */

.curtain__panel {
  position: absolute;
  top: -2%;
  bottom: -2%;
  /* 50.6% each: the overlap guarantees no hairline of hero shows
     through the join before the animation starts. */
  width: 50.6%;
  background-color: #2B1408;

  /* Velvet, in seven layers. Two things sell it, and both were
     missing when these folds were plain vertical stripes:

     1. Folds LEAN. Fabric is gathered at one end of the rail, so
        every fold converges on that end — which is why the angle
        is a per-panel variable and the two panels lean opposite
        ways, each toward its own gather point.

     2. Nothing repeats visibly. The three fold layers run at 58,
        163 and 317px, which share no common factor, so the
        pattern they beat out does not come back around inside
        any screen width.

     Velvet itself is the tonal range: the valleys swallow light
     almost completely and the crest carries a narrow specular
     thread. A stripe with an even light and dark reads as
     painted wood, not cloth.                                    */
  background-image:
    /* 1. the specular thread along each crest                   */
    repeating-linear-gradient(var(--fold-a),
      rgba(255, 214, 150, 0)     0px,
      rgba(255, 214, 150, 0)    26px,
      rgba(255, 214, 150, .12)  29px,
      rgba(255, 214, 150, 0)    32px,
      rgba(255, 214, 150, 0)    58px),
    /* 2. the folds                                              */
    repeating-linear-gradient(var(--fold-a),
      rgba(0, 0, 0, .66)      0px,
      rgba(0, 0, 0, .20)     13px,
      rgba(255, 178, 98, .07) 29px,
      rgba(0, 0, 0, .20)     45px,
      rgba(0, 0, 0, .66)     58px),
    /* 3. a deeper drape leaning a little harder                 */
    repeating-linear-gradient(var(--fold-b),
      rgba(0, 0, 0, .46)      0px,
      rgba(0, 0, 0, 0)       59px,
      rgba(255, 150, 70, .05) 101px,
      rgba(0, 0, 0, 0)      147px,
      rgba(0, 0, 0, .46)    163px),
    /* 4. large-scale undulation across the whole width          */
    repeating-linear-gradient(90deg,
      rgba(0, 0, 0, .28)      0px,
      rgba(0, 0, 0, 0)      128px,
      rgba(0, 0, 0, 0)      192px,
      rgba(0, 0, 0, .28)    317px),
    /* 5. light catching the inner edge, as if from the stage    */
    radial-gradient(130% 80% at var(--lit) 42%,
      rgba(222, 132, 58, .30) 0%, rgba(222, 132, 58, 0) 62%),
    /* 6. the sheen band. Velvet lit from the front is brightest
          across the middle; up at the rail it sits in its own
          shadow, which is what makes it read as hanging         */
    linear-gradient(180deg,
      rgba(0, 0, 0, .58)      0%,
      rgba(0, 0, 0, .18)     17%,
      rgba(255, 170, 92, .07) 44%,
      rgba(0, 0, 0, .20)     76%,
      rgba(0, 0, 0, .62)    100%),
    /* 7. base                                                   */
    linear-gradient(180deg, #52250B 0%, #331709 46%, #190B04 100%);

  /* Cast onto whatever is behind — the dark centre while the
     panels are parted, the hero once they are not. */
  box-shadow: 0 0 70px 6px rgba(0, 0, 0, .85);
}

/* Each panel gathers toward its own end of the rail, so its folds
   lean that way. Two angles rather than one: they converge very
   slowly down the drop, the way gathered cloth actually hangs. */
.curtain__panel--l {
  left: 0;
  --lit: 100%;
  --fold-a: 91.4deg;
  --fold-b: 92.8deg;
  transform-origin: left center;
}
.curtain__panel--r {
  right: 0;
  --lit: 0%;
  --fold-a: 88.6deg;
  --fold-b: 87.2deg;
  transform-origin: right center;
}

/* Shadow gathering toward the meeting edge — this is most of
   what makes the panels read as fabric with depth. */
.curtain__panel::before {
  content: "";
  position: absolute;
  top: 0;
  bottom: 0;
  width: 110px;
  pointer-events: none;
}
/* Deep shadow into the meeting edge, then a narrow bright rim
   right at it: the leading fold is rolled over on itself, and
   the light that gets past the opening catches that roll. Without
   the rim the edge reads as a cut, not a fold. */
.curtain__panel--l::before {
  right: 0;
  background: linear-gradient(90deg,
    rgba(0, 0, 0, 0)         0%,
    rgba(0, 0, 0, .30)      56%,
    rgba(0, 0, 0, .72)      88%,
    rgba(255, 186, 110, .11) 96%,
    rgba(0, 0, 0, .58)     100%);
}
.curtain__panel--r::before {
  left: 0;
  background: linear-gradient(270deg,
    rgba(0, 0, 0, 0)         0%,
    rgba(0, 0, 0, .30)      56%,
    rgba(0, 0, 0, .72)      88%,
    rgba(255, 186, 110, .11) 96%,
    rgba(0, 0, 0, .58)     100%);
}

/* Gold braid down the leading edge — a twisted cord, not a line.
   The twist is the repeating gradient; the mask fades both ends,
   which the old version did with the gradient itself and so could
   not also carry a texture. */
.curtain__panel::after {
  content: "";
  position: absolute;
  top: 0;
  bottom: 0;
  width: 3px;
  background:
    repeating-linear-gradient(180deg,
      rgba(255, 246, 220, .85) 0px,
      rgba(206, 150, 58, .85)  2px,
      rgba(116, 72, 16, .85)   4px,
      rgba(206, 150, 58, .85)  6px),
    linear-gradient(180deg, var(--gold-dim), var(--gold-soft) 50%, var(--gold-dim));
  -webkit-mask-image: linear-gradient(180deg, transparent 0%, #000 8%, #000 92%, transparent 100%);
          mask-image: linear-gradient(180deg, transparent 0%, #000 8%, #000 92%, transparent 100%);
  box-shadow: 0 0 14px rgba(255, 200, 110, .45);
  opacity: .9;
}
.curtain__panel--l::after { right: 0; }
.curtain__panel--r::after { left: 0; }

/* --- Pelmet -------------------------------------------------- */

/* The valance across the rail. The panels gather BEHIND it, which
   is how a stage is actually rigged, and it is the one detail
   that stops two moving rectangles reading as two moving
   rectangles. It flies out upward at the end rather than fading,
   because that is what a real one does.

   Folds here run tighter than the drape below and stay vertical:
   this is the bunched heading, not the hanging fall. */
.curtain__valance {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  z-index: 2;
  height: 10%;
  min-height: 52px;
  background-color: #341A0A;
  background-image:
    repeating-linear-gradient(90deg,
      rgba(0, 0, 0, .64)       0px,
      rgba(0, 0, 0, .16)      11px,
      rgba(255, 186, 110, .09) 21px,
      rgba(0, 0, 0, .16)      31px,
      rgba(0, 0, 0, .64)      42px),
    linear-gradient(180deg,
      rgba(0, 0, 0, .55)      0%,
      rgba(255, 170, 92, .07) 54%,
      rgba(0, 0, 0, .48)    100%),
    linear-gradient(180deg, #5A2A0C 0%, #3A1B0A 70%, #261206 100%);
  box-shadow: 0 16px 36px -10px rgba(0, 0, 0, .85);
}

/* Matching braid along its lower edge. */
.curtain__valance::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 3px;
  background: repeating-linear-gradient(90deg,
    rgba(255, 246, 220, .9) 0px,
    rgba(206, 150, 58, .9)  3px,
    rgba(116, 72, 16, .9)   6px,
    rgba(206, 150, 58, .9)  9px);
  box-shadow: 0 0 16px rgba(255, 200, 110, .45);
}

/* --- Greeting ------------------------------------------------ */

.curtain__greet {
  position: absolute;
  inset: 0;
  z-index: 3;   /* above the pelmet, which is z-index 2 */
  display: grid;
  place-content: center;
  justify-items: center;
  text-align: center;
  padding: var(--s3);
}

.curtain__mark {
  width: 54px;
  height: 54px;
  color: var(--gold);
  opacity: 0;
}

.curtain__hi {
  margin-top: var(--s3);
  font-family: var(--font-deva);
  font-size: clamp(2.4rem, 6vw, 4.2rem);
  line-height: 1.1;
  color: var(--gold-soft);
  opacity: 0;
  text-shadow: 0 0 38px rgba(217, 164, 65, .35);
}

.curtain__sub {
  margin-top: var(--s1);
  font-family: var(--font-display);
  font-size: clamp(.88rem, 1.6vw, 1.18rem);
  letter-spacing: .04em;
  color: var(--gold);
  opacity: 0;
}

.curtain__rule {
  width: 0;
  height: 1px;
  margin-top: var(--s3);
  background: linear-gradient(90deg, transparent, var(--gold), transparent);
}

.curtain__est {
  margin-top: var(--s2);
  font-size: 10px;
  font-weight: 400;
  letter-spacing: .38em;
  text-transform: uppercase;
  color: var(--gold-dim);
  opacity: 0;
}

/* --- Skip ---------------------------------------------------- */

/* tabindex=-1 in the markup: the whole overlay is aria-hidden, so
   this must not be a keyboard stop. Escape and a click anywhere
   also dismiss it. */
.curtain__skip {
  position: absolute;
  right: var(--s3);
  bottom: var(--s3);
  padding: 8px 18px;
  border: 1px solid var(--line);
  border-radius: var(--r-pill);
  background: rgba(0, 0, 0, .35);
  color: var(--gold-dim);
  font-size: 11px;
  font-weight: 400;
  letter-spacing: .22em;
  text-transform: uppercase;
  cursor: pointer;
  opacity: 0;
  transition: color var(--dur-1) var(--ease),
              border-color var(--dur-1) var(--ease);
}
.curtain__skip:hover {
  color: var(--gold-soft);
  border-color: var(--gold);
}

/* --- The sequence -------------------------------------------- */

.curtain.is-playing .curtain__panel--l {
  animation: curtainLeft var(--intro-total) both;
}
.curtain.is-playing .curtain__panel--r {
  animation: curtainRight var(--intro-total) both;
}
.curtain.is-playing .curtain__valance {
  animation: valanceUp var(--intro-total) both;
}
.curtain.is-playing .curtain__backdrop {
  animation: introBackdrop var(--intro-total) both;
}
.curtain.is-playing .curtain__mark {
  animation: introMark var(--intro-total) both;
}
.curtain.is-playing .curtain__hi {
  animation: introHi var(--intro-total) both;
}
.curtain.is-playing .curtain__sub {
  animation: introSub var(--intro-total) both;
}
.curtain.is-playing .curtain__rule {
  animation: introRule var(--intro-total) both;
}
.curtain.is-playing .curtain__est {
  animation: introEst var(--intro-total) both;
}
.curtain.is-playing .curtain__skip {
  animation: introSkip var(--intro-total) both;
}

/* Per-keyframe timing functions: ease-out into the reveal, a flat
   hold while the greeting reads, then ease-in-out as the panels
   draw off. One animation, three different feels.

   NO overshoot on the open. It was tried and it was wrong twice
   over. A curtain pulled on a rail and stopped at a mark does not
   slide back — that is spring behaviour, and a rail is not a
   spring. And the reversal was far bigger than it looked on
   paper: transform-origin sits at the OUTER edge, so shrinking
   scaleX opens the inner edge too, and backing off both at once
   moved that edge about 38px the wrong way over ~200ms. Slow
   enough and large enough to read as a stutter, which is exactly
   what it looked like.

   The weight comes from the ease instead. cubic-bezier(.22,1,.36,1)
   decelerates hard into the stop without ever reversing.

   scaleX still ends at .72 rather than .86 — gathered fabric
   bunches, so the folds should visibly crowd together as the
   panel clears. The small translateY is the hem rising as it
   gathers, and it stays inside the 2% bleed so nothing uncovers.

   Every keyframe lists the same three functions in the same
   order. Mismatched lists make the browser fall back to matrix
   interpolation, which is a different animation from the one
   written here. */
@keyframes curtainLeft {
  0%   { transform: translateX(0)      translateY(0)    scaleX(1);
         animation-timing-function: cubic-bezier(.22, 1, .36, 1); }
  27%  { transform: translateX(-30%)   translateY(0)    scaleX(.95);
         animation-timing-function: linear; }
  61%  { transform: translateX(-30%)   translateY(0)    scaleX(.95);
         animation-timing-function: cubic-bezier(.65, 0, .35, 1); }
  100% { transform: translateX(-101%)  translateY(-.8%) scaleX(.72); }
}
@keyframes curtainRight {
  0%   { transform: translateX(0)      translateY(0)    scaleX(1);
         animation-timing-function: cubic-bezier(.22, 1, .36, 1); }
  27%  { transform: translateX(30%)    translateY(0)    scaleX(.95);
         animation-timing-function: linear; }
  61%  { transform: translateX(30%)    translateY(0)    scaleX(.95);
         animation-timing-function: cubic-bezier(.65, 0, .35, 1); }
  100% { transform: translateX(101%)   translateY(-.8%) scaleX(.72); }
}

/* The pelmet flies out with them. */
@keyframes valanceUp {
  0%   { transform: translateY(0);
         animation-timing-function: linear; }
  61%  { transform: translateY(0);
         animation-timing-function: cubic-bezier(.65, 0, .35, 1); }
  100% { transform: translateY(-101%); }
}

/* The hero fades up through this as it clears. */
@keyframes introBackdrop {
  0%,  61% { opacity: 1; }
  96%      { opacity: 0; }
  100%     { opacity: 0; }
}

@keyframes introMark {
  0%,  15% { opacity: 0; transform: scale(.82); }
  36%      { opacity: 1; transform: scale(1); }
  55%      { opacity: 1; transform: scale(1); }
  68%      { opacity: 0; transform: scale(1) translateY(-10px); }
  100%     { opacity: 0; }
}
@keyframes introHi {
  0%,  19% { opacity: 0; transform: translateY(16px); }
  40%      { opacity: 1; transform: none; }
  55%      { opacity: 1; transform: none; }
  68%      { opacity: 0; transform: translateY(-10px); }
  100%     { opacity: 0; }
}
@keyframes introSub {
  0%,  24% { opacity: 0; transform: translateY(12px); }
  44%      { opacity: 1; transform: none; }
  55%      { opacity: 1; transform: none; }
  68%      { opacity: 0; transform: translateY(-10px); }
  100%     { opacity: 0; }
}
@keyframes introRule {
  0%,  27% { width: 0; opacity: 0; }
  48%      { width: 148px; opacity: 1; }
  55%      { width: 148px; opacity: 1; }
  68%      { width: 148px; opacity: 0; }
  100%     { opacity: 0; }
}
@keyframes introEst {
  0%,  32% { opacity: 0; transform: translateY(8px); }
  52%      { opacity: 1; transform: none; }
  55%      { opacity: 1; transform: none; }
  68%      { opacity: 0; transform: translateY(-8px); }
  100%     { opacity: 0; }
}
@keyframes introSkip {
  0%,  10% { opacity: 0; }
  22%      { opacity: 1; }
  58%      { opacity: 1; }
  66%      { opacity: 0; }
  100%     { opacity: 0; }
}

/* Skipped, or finished: fade whatever is left and stand down. */
.curtain.is-done {
  animation: introDismiss .45s var(--ease) both;
  pointer-events: none;
}
@keyframes introDismiss {
  to { opacity: 0; visibility: hidden; }
}

/* Not playing at all — repeat visit, or reduced motion. */
.no-intro .curtain { display: none !important; }

@media (prefers-reduced-motion: reduce) {
  .curtain { display: none !important; }
}

/* ── 4. Hero ──────────────────────────────────────────────────
   The signature moment. Sequence, offset from --reveal-delay:

     0.00s  ember glow blooms — the room lights up
     0.10s  "Est. 2011" and its rule draw in
     0.22s  headline rises line by line, 120ms apart,
            the gold line landing last
     0.30s  arch wipes open from the bottom while the image
            inside settles from 1.12 to 1.0
     0.72s  paragraph, then buttons, then the marks row
     1.15s  one slow gleam travels across the gold word

   --reveal-delay holds everything back until the curtain has
   cleared. Without the curtain it is 0 and the hero just plays. */

.hero {
  position: relative;
  display: flex;
  align-items: center;
  /* The top bar is in flow above this; the header overlays the
     hero, so only its height needs clearing. */
  min-height: 86svh;
  padding: calc(var(--topbar-h) + var(--header-h) + var(--s4)) 0 var(--s4);
  overflow: hidden;
  background: var(--ink-2);
}
@media (max-width: 899px) {
  .hero { min-height: 88svh; padding-top: calc(var(--header-h) + var(--s4)); }
}

/* Full-bleed background photograph.
   Point --hero-bg at your file (top of this stylesheet) and it
   takes over; until then the gradient beneath stands in.

   The scrim is the first layer, so it sits ON TOP of the image —
   weighted to the left, where the copy is, and clearing by the
   right third so the photograph keeps its detail there. */
.hero__bg {
  position: absolute;
  inset: 0;
  background-image:
    /* Heavy on the left where the copy sits, clearing through the
       middle so the room keeps its detail, then lifting again at
       the right edge — the far side of this photograph is the
       brightest part of it, and left alone it pulls the eye off
       the end of the page. */
    linear-gradient(100deg,
      rgba(11, 6, 3, .72)  0%,
      rgba(11, 6, 3, .46) 34%,
      rgba(11, 6, 3, .10) 60%,
      rgba(11, 6, 3, .22) 82%,
      rgba(11, 6, 3, .44) 100%),
    var(--hero-bg, none),
    radial-gradient(74% 62% at 66% 40%, rgba(212, 112, 42, .46) 0%, rgba(212, 112, 42, 0) 72%),
    linear-gradient(180deg, #2C1B0D 0%, #1D1208 55%, #120A04 100%);
  background-size: cover;
  background-position: center;
  background-repeat: no-repeat;
  /* A very slow settle — the room stops breathing just as the
     copy finishes arriving. */
  animation: bgSettle 2.6s var(--ease) var(--reveal-delay) both;
}

/* Lantern bloom behind the arch. */
.hero__glow {
  position: absolute;
  top: 50%;
  right: -6%;
  width: min(74vw, 900px);
  aspect-ratio: 1;
  transform: translateY(-50%);
  border-radius: 50%;
  background: radial-gradient(circle,
    rgba(224, 120, 48, .38) 0%,
    rgba(206, 104, 44, .16) 40%,
    rgba(196, 98, 42, 0) 70%);
  pointer-events: none;
  animation: glowIn 2.2s var(--ease) var(--reveal-delay) both;
}

.hero__grid {
  position: relative;
  display: grid;
  gap: var(--s5);
  align-items: center;
}
@media (min-width: 1000px) {
  .hero__grid {
    grid-template-columns: minmax(0, 1.05fr) minmax(0, .95fr);
    gap: var(--s6);
  }
}

/* --- Copy ---------------------------------------------------- */

.hero__eyebrow {
  display: flex;
  align-items: center;
  gap: var(--s2);
  font-size: 11px;
  font-weight: 400;
  letter-spacing: .42em;
  text-transform: uppercase;
  color: var(--gold-dim);
  opacity: 0;
  animation: riseIn .9s var(--ease) calc(var(--reveal-delay) + .10s) both;
}
.hero__eyebrow-rule {
  width: 0;
  height: 1px;
  background: linear-gradient(90deg, var(--gold), transparent);
  animation: ruleOut 1s var(--ease) calc(var(--reveal-delay) + .18s) both;
}

.hero__title {
  margin-top: var(--s3);
  font-family: var(--font-display);
  font-weight: 500;
  font-size: clamp(1.6rem, 3.6vw, 2.9rem);
  line-height: 1.2;
  letter-spacing: -.015em;
  color: var(--cream);
}

/* The mask. Each line clips its own inner span, so the text
   appears to rise from behind a solid edge rather than fade.
   The padding/negative-margin pair stops overflow:hidden from
   shaving the descenders off g, y and &. */
.hero__title .line {
  display: block;
  overflow: hidden;
  padding-bottom: .2em;
  margin-bottom: -.2em;
}
.hero__title .line > span {
  display: block;
  font-style: inherit;
  transform: translateY(110%);
  animation: lineUp 1.15s var(--ease) both;
}
.hero__title .line:nth-child(1) > span { animation-delay: calc(var(--reveal-delay) + .22s); }
.hero__title .line:nth-child(2) > span { animation-delay: calc(var(--reveal-delay) + .34s); }
/* Only two lines now; the third rule is kept for when copy grows. */
.hero__title .line:nth-child(3) > span { animation-delay: calc(var(--reveal-delay) + .46s); }

/* The gold line, plus a single gleam that crosses it once it has
   landed. background-clip:text means the sweep travels through
   the letterforms rather than over them. */
.hero__title .line--gold > span {
  color: var(--gold);
  background: linear-gradient(100deg,
    var(--gold)   34%,
    #FFF1CE      48%,
    var(--gold)   62%);
  background-size: 280% 100%;
  background-position: 180% 0;
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
  animation:
    lineUp  1.15s var(--ease) calc(var(--reveal-delay) + .34s) both,
    gleam   1.8s  var(--ease) calc(var(--reveal-delay) + 1.05s) both;
}

.hero__text {
  max-width: 46ch;
  margin-top: var(--s3);
  font-size: 15px;
  line-height: 1.9;
  color: var(--tan);
  animation: riseIn 1s var(--ease) calc(var(--reveal-delay) + .72s) both;
}

.hero__actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s2);
  margin-top: var(--s4);
  animation: riseIn 1s var(--ease) calc(var(--reveal-delay) + .86s) both;
}

.hero__marks {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s1) var(--s3);
  margin-top: var(--s4);
  padding-top: var(--s3);
  border-top: 1px solid var(--line-soft);
  font-size: 11px;
  letter-spacing: .2em;
  text-transform: uppercase;
  color: var(--tan-dim);
  animation: riseIn 1s var(--ease) calc(var(--reveal-delay) + 1s) both;
}
/* Hairline between items only, never leading. */
.hero__marks li + li { position: relative; padding-left: var(--s3); }
.hero__marks li + li::before {
  content: "";
  position: absolute;
  left: 0;
  top: 50%;
  width: 1px;
  height: 11px;
  margin-top: -5px;
  background: var(--line);
}

/* --- Buttons ------------------------------------------------- */

.btn {
  display: inline-flex;
  align-items: center;
  gap: .55rem;
  padding: 13px 28px;
  border-radius: var(--r-pill);
  border: 1px solid transparent;
  font-size: 13px;
  font-weight: 600;
  letter-spacing: .08em;
  text-transform: uppercase;
  cursor: pointer;
  transition: background-color var(--dur-2) var(--ease),
              color var(--dur-2) var(--ease),
              border-color var(--dur-2) var(--ease),
              transform var(--dur-2) var(--ease);
}
.btn .ico { font-size: 16px; }

/* The inset hairline is what reads as metal — without it the
   gradient looks like flat paint. The two outer shadows are a
   close bloom plus a cast glow. */
.btn--gold {
  /* Four stops, not three: pale highlight, gold, then down into
     the orange. The glow that comes off it is warmed to match —
     a straw-yellow bloom under an orange-gold face looks like two
     different light sources. */
  background: linear-gradient(118deg,
    #F4DCA6            0%,
    var(--gold)       34%,
    var(--gold-warm)  64%,
    var(--gold-ember) 100%);
  color: #1A0F04;
  box-shadow:
    inset 0 0 0 1px rgba(255, 233, 176, .45),
    0 0 26px rgba(226, 146, 58, .40),
    0 12px 28px -12px rgba(226, 146, 58, .58);
}
.btn--gold:hover {
  background: linear-gradient(118deg,
    #FFF0C8          0%,
    var(--gold-soft) 34%,
    #D99B3C         64%,
    #CD7B2B        100%);
  transform: translateY(-2px);
  box-shadow:
    inset 0 0 0 1px rgba(255, 245, 214, .6),
    0 0 44px rgba(232, 152, 62, .60),
    0 16px 34px -12px rgba(232, 152, 62, .72);
}

.btn--ghost {
  border-color: var(--line);
  color: var(--gold);
}
.btn--ghost:hover {
  border-color: var(--gold);
  background: rgba(217, 164, 65, .08);
  transform: translateY(-2px);
}

/* Compact. Used where a button is secondary to what is around it
   — a form's submit, a filter reset — not where it is the thing
   being asked for.

   It lives here with the other modifiers rather than down in the
   header, which is where it was: a modifier of .btn written 500
   lines below .btn wins on source order alone, so the phone
   sizing below could not have reached it from up here. */
.btn--sm {
  padding: 9px 18px;
  font-size: 11px;
}
.btn--sm .ico { font-size: 14px; }

/* --- On a phone ----------------------------------------------
   Everything comes down a step. A button sized for a pointer is
   simply large on a 390px screen, and there are pages here that
   stack three of them.

   Not smaller than this, though. The padding is what carries the
   tap target: 11px top and bottom around a 13px line box keeps
   the hit area around 40px, and going further would start
   trading a real miss rate for a few pixels. The hero keeps its
   own tighter rule — it puts two side by side in one row, which
   nothing else does. */
@media (max-width: 599px) {
  .btn {
    padding: 11px 20px;
    font-size: 11.5px;
    letter-spacing: .06em;
  }
  .btn .ico { font-size: 14px; }

  .btn--sm {
    padding: 9px 15px;
    font-size: 10.5px;
  }
  .btn--sm .ico { font-size: 13px; }
}

/* --- Arch ---------------------------------------------------- */

.hero__media {
  position: relative;
  margin: 0;
  justify-self: center;
  width: 100%;
  max-width: 380px;
}

.hero__arch {
  position: relative;
  aspect-ratio: 3 / 4;
  border-radius: var(--r-arch);
  overflow: hidden;
  background: var(--surface);
  box-shadow: var(--shadow-2);
}


/* Holds the slider and carries the entrance wipe. It rises from
   the bottom, echoing the headline. */
.hero__wipe {
  position: absolute;
  inset: 0;
  background:
    radial-gradient(60% 46% at 50% 60%, rgba(230, 140, 56, .40) 0%, rgba(230, 140, 56, 0) 74%),
    linear-gradient(180deg, #3A2513 0%, #241609 58%, #140C05 100%);
  clip-path: inset(100% 0 0 0);
  animation: archWipe 1.25s var(--ease-inout) calc(var(--reveal-delay) + .30s) both;
}

/* --- Slider -------------------------------------------------- */

.hero__slides { position: absolute; inset: 0; }

/* Each slide drifts from 1.08 to 1 across its whole turn, so the
   visible image is always very slowly settling. The long
   transform duration is what makes it a drift rather than a zoom. */
.hero__slide {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  opacity: 0;
  transform: scale(1.08);
  transition:
    opacity 1.4s var(--ease),
    transform 7s linear;
}
.hero__slide.is-active {
  opacity: 1;
  transform: scale(1);
}

.hero__dots {
  display: flex;
  justify-content: center;
  gap: 8px;
  margin-top: var(--s3);
}
.hero__dot {
  width: 7px;
  height: 7px;
  padding: 0;
  border: 0;
  border-radius: var(--r-pill);
  background: rgba(217, 164, 65, .28);
  cursor: pointer;
  transition: width var(--dur-2) var(--ease),
              background-color var(--dur-2) var(--ease);
}
.hero__dot.is-active {
  width: 22px;
  background: var(--gold);
  box-shadow: 0 0 12px rgba(217, 164, 65, .5);
}

/* Gold frame tracing the arch, fading up after it opens.
   box-shadow follows border-radius, so the bloom bends around the
   curve rather than boxing it — which a filter or an offset
   rectangle would not do. */
.hero__arch-line {
  position: absolute;
  inset: -10px;
  border: 1px solid rgba(var(--glow-rgb), .58);
  border-radius: var(--r-arch);
  box-shadow:
    0 0 30px rgba(var(--glow-rgb), .30),      /* cast outward */
    inset 0 0 20px rgba(var(--glow-rgb), .14); /* haze on the image edge */
  pointer-events: none;
  animation: riseIn 1.2s var(--ease) calc(var(--reveal-delay) + .95s) both;
}

/* A second, wider bloom that breathes. Only opacity animates, so
   it costs nothing — and lantern light never sits perfectly still. */
.hero__arch-line::after {
  content: "";
  position: absolute;
  inset: -1px;
  border-radius: inherit;
  box-shadow: 0 0 46px rgba(var(--glow-rgb), .38);
  animation: archPulse 7s ease-in-out infinite;
}

@keyframes archPulse {
  0%, 100% { opacity: .55; }
  50%      { opacity: 1; }
}

/* --- Keyframes ----------------------------------------------- */

@keyframes bgSettle {
  from { opacity: 0; transform: scale(1.08); }
  to   { opacity: 1; transform: none; }
}
@keyframes glowIn {
  from { opacity: 0; transform: translateY(-50%) scale(.86); }
  to   { opacity: 1; transform: translateY(-50%) scale(1); }
}
@keyframes riseIn {
  from { opacity: 0; transform: translateY(18px); }
  to   { opacity: 1; transform: none; }
}
@keyframes ruleOut {
  from { width: 0; opacity: 0; }
  to   { width: 46px; opacity: 1; }
}
@keyframes lineUp {
  from { transform: translateY(110%); }
  to   { transform: translateY(0); }
}
@keyframes gleam {
  from { background-position: 180% 0; }
  to   { background-position: -60% 0; }
}
@keyframes archWipe {
  from { clip-path: inset(100% 0 0 0); }
  to   { clip-path: inset(0 0 0 0); }
}

/* --- Reduced motion ------------------------------------------ */

@media (prefers-reduced-motion: reduce) {
  .hero__bg,
  .hero__glow,
  .hero__eyebrow,
  .hero__eyebrow-rule,
  .hero__title .line > span,
  .hero__text,
  .hero__actions,
  .hero__marks,
  .hero__wipe,
  .hero__slide,
  .hero__arch-line,
  .hero__arch-line::after {
    animation: none;
    opacity: 1;
    transform: none;
    clip-path: none;
    width: auto;
  }
  .hero__eyebrow-rule { width: 46px; }
}

/* --- Small screens ------------------------------------------- */

@media (max-width: 999px) {
  .hero {
    padding: calc(var(--header-h) + var(--s4)) 0 var(--s4);
    text-align: center;
  }
  .hero__grid { gap: var(--s4); }
  .hero__eyebrow { justify-content: center; }
  .hero__text { margin-inline: auto; }
  .hero__actions,
  .hero__marks { justify-content: center; }
  .hero__media { max-width: 340px; }
  .hero__glow { right: 50%; transform: translate(50%, -50%); width: 120vw; }
  @keyframes glowIn {
    from { opacity: 0; transform: translate(50%, -50%) scale(.86); }
    to   { opacity: 1; transform: translate(50%, -50%) scale(1); }
  }
}

/* 899px, the same point the top bar and the nav hand over to the
   drawer — one mobile breakpoint for the whole site, not one per
   component. */
@media (max-width: 899px) {
  /* The badge row goes. Three items across a phone either wrap
     onto three lines or shrink below reading size, and the
     hairline dividers stop making sense either way. */
  .hero__marks { display: none; }

  /* On a phone the copy is centred and runs the full width, so it
     lands on every part of the photograph instead of the quiet
     left third it was cropped for. Two changes to make it read:

     The photograph blurs back into a texture. inset goes negative
     to pay for it — filter:blur samples past the element's own
     edges and finds nothing there, which fades the border out.
     40px of overhang puts that fade outside the hero, which is
     already overflow:hidden.

     And the scrim is rebuilt flat instead of angled. The desktop
     one is weighted to the left because that is where the copy
     sits; on a phone that leaves the right hand end of every line
     on bare photograph, which is exactly what it looked like. */
  .hero__bg {
    inset: -40px;
    filter: blur(4px);
    background-image:
      linear-gradient(180deg,
        rgba(34, 17, 6, .84)  0%,
        rgba(34, 17, 6, .74) 45%,
        rgba(34, 17, 6, .82) 100%),
      var(--hero-bg, none),
      radial-gradient(74% 62% at 66% 40%, rgba(212, 112, 42, .46) 0%, rgba(212, 112, 42, 0) 72%),
      linear-gradient(180deg, #2C1B0D 0%, #1D1208 55%, #120A04 100%);
    /* Fade only. Animating a scale on a filtered, full-screen
       layer makes a phone re-rasterise a blur every frame; the
       settle is a desktop nicety and not worth that. */
    animation-name: bgFade;
  }
}

@keyframes bgFade {
  from { opacity: 0; }
  to   { opacity: 1; }
}

/* Keep the two buttons on one line down to the narrowest phone.
   They fit at full size above 600px, so only the small tier needs
   tightening — and it has to be tightened, because "Explore
   Rituals" at the desktop size alone is wider than half a 320px
   screen.

   flex: 0 1 auto rather than 1 1 0: equal halves would give the
   short label spare room it does not need and starve the long one
   of room it does. They shrink from their own widths instead. */
@media (max-width: 599px) {
  .hero__actions {
    flex-wrap: nowrap;
    justify-content: center;
    gap: 10px;
  }
  .hero__actions .btn {
    flex: 0 1 auto;
    min-width: 0;
    white-space: nowrap;
    padding: 12px 16px;
    font-size: 11px;
    letter-spacing: .05em;
  }
  .hero__actions .btn .ico { font-size: 14px; }
}
/* ── 5. Top bar ───────────────────────────────────────────────
   In normal flow at the very top. The header sits absolutely
   below it, overlaying the hero, until scroll pins it.         */

/* Absolute, not in flow: both bars now sit over the hero so the
   glass has something behind it to blur. Sitting in flow there
   was nothing but page background and the effect did nothing. */
.topbar {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  z-index: 40;
  height: var(--topbar-h);
  background: rgba(10, 6, 3, .42);
  backdrop-filter: blur(14px) saturate(130%);
  -webkit-backdrop-filter: blur(14px) saturate(130%);
  border-bottom: 1px solid rgba(217, 164, 65, .10);
  font-size: 12px;
  color: var(--tan-dim);
  animation: dropIn .9s var(--ease) calc(var(--reveal-delay) + 1.05s) both;
}
.topbar__in {
  display: flex;
  align-items: center;
  justify-content: space-between;
  height: 100%;
}
.topbar__group {
  display: flex;
  align-items: center;
  gap: var(--s3);
}
.topbar__item {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  transition: color var(--dur-1) var(--ease);
}
.topbar__item .ico { font-size: 14px; color: var(--gold-dim); }
a.topbar__item:hover { color: var(--gold-soft); }

.topbar__sep {
  width: 1px;
  height: 14px;
  background: var(--line);
}
.topbar__social { display: inline-flex; gap: 6px; }
.topbar__social a {
  display: grid;
  place-items: center;
  width: 26px;
  height: 26px;
  border-radius: var(--r-pill);
  color: var(--tan-dim);
  transition: color var(--dur-1) var(--ease),
              background-color var(--dur-1) var(--ease);
}
.topbar__social .ico { font-size: 14px; }
.topbar__social a:hover {
  color: var(--gold);
  background: rgba(217, 164, 65, .1);
}

@media (max-width: 899px) { .topbar { display: none; } }


/* ── 6. Header ────────────────────────────────────────────── */

.site-head {
  position: absolute;
  top: var(--topbar-h);
  left: 0;
  right: 0;
  z-index: 50;
  background: rgba(16, 9, 4, .30);
  backdrop-filter: blur(20px) saturate(140%);
  -webkit-backdrop-filter: blur(20px) saturate(140%);
  transition: background-color var(--dur-2) var(--ease);
  animation: dropIn .9s var(--ease) calc(var(--reveal-delay) + 1.15s) both;
}

/* Gold-tinted edge instead of a flat border — it catches the
   light the way a glass panel would, and fades at both ends. */
.site-head::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 1px;
  background: linear-gradient(90deg,
    transparent 0%,
    rgba(217, 164, 65, .30) 22%,
    rgba(217, 164, 65, .30) 78%,
    transparent 100%);
  pointer-events: none;
}
@media (max-width: 899px) { .site-head { top: 0; } }

/* Once the entrance has played, JS pins this on and the animation
   is gone for good.

   It has to be, and this was a real bug: the entrance carries a
   ~2.8s delay with fill `both`, so it holds opacity 0 until then.
   Toggling .is-stuck off while scrolling back up re-applied that
   rule, restarted the delay, and the header vanished for nearly
   three seconds before fading back. */
.site-head.is-ready { animation: none; }

/* Pinned on scroll — drops in from above rather than snapping. */
.site-head.is-stuck {
  position: fixed;
  top: 0;
  /* Denser once it is over real content — 30% would let text
     read through it. */
  background: rgba(11, 6, 3, .74);
  animation: slideDown .42s var(--ease) both;
}

@keyframes slideDown {
  from { transform: translateY(-100%); }
  to   { transform: none; }
}

.site-head__in {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s3);
  height: var(--header-h);
  transition: height var(--dur-2) var(--ease);
}
.site-head.is-stuck .site-head__in { height: 56px; }

/* --- Brand --------------------------------------------------- */

.brand {
  display: inline-flex;
  align-items: center;
  gap: 12px;
  flex: none;
}
.brand__mark {
  width: 31px;
  height: 31px;
  color: var(--gold);
  transition: transform var(--dur-2) var(--ease);
}
.brand:hover .brand__mark { transform: rotate(-6deg) scale(1.04); }

.brand__text strong {
  display: block;
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 16px;
  line-height: 1.15;
  letter-spacing: .01em;
  color: var(--cream);
}
.brand__text em {
  display: block;
  margin-top: 2px;
  font-style: normal;
  font-size: 7.5px;
  letter-spacing: .3em;
  text-transform: uppercase;
  color: var(--gold-dim);
}

/* --- Nav ----------------------------------------------------- */

.mainnav {
  display: flex;
  align-items: center;
  gap: var(--s4);
}
@media (max-width: 1099px) { .mainnav { gap: var(--s3); } }
@media (max-width: 899px)  { .mainnav { display: none; } }

.mainnav__link {
  position: relative;
  padding: 6px 0;
  font-size: 13px;
  font-weight: 500;
  letter-spacing: .1em;
  color: var(--tan);
  transition: color var(--dur-2) var(--ease);
}
/* Underline grows from the centre. */
.mainnav__link::after {
  content: "";
  position: absolute;
  left: 50%;
  bottom: 0;
  width: 0;
  height: 1px;
  background: linear-gradient(90deg, transparent, var(--gold), transparent);
  transform: translateX(-50%);
  transition: width var(--dur-2) var(--ease);
}
.mainnav__link:hover { color: var(--gold-soft); }
.mainnav__link:hover::after { width: 100%; }

.mainnav__link.is-active { color: var(--gold); }
.mainnav__link.is-active::after { width: 100%; }

/* --- Actions ------------------------------------------------- */

.site-head__act {
  display: flex;
  align-items: center;
  gap: var(--s2);
  flex: none;
}

.iconbtn {
  display: grid;
  place-items: center;
  width: 36px;
  height: 36px;
  padding: 0;
  border: 1px solid var(--line-soft);
  border-radius: var(--r-pill);
  background: transparent;
  color: var(--tan);
  cursor: pointer;
  transition: color var(--dur-2) var(--ease),
              border-color var(--dur-2) var(--ease),
              background-color var(--dur-2) var(--ease);
}
.iconbtn .ico { font-size: 17px; }
.iconbtn:hover {
  color: var(--gold);
  border-color: var(--line);
  background: rgba(217, 164, 65, .07);
}

.iconbtn--menu { display: none; }
@media (max-width: 899px) { .iconbtn--menu { display: grid; } }

@media (max-width: 599px) { .site-head__cta { display: none; } }

/* --- Search -------------------------------------------------- */

.searchbar {
  display: grid;
  grid-template-rows: 0fr;
  opacity: 0;
  pointer-events: none;
  transition: grid-template-rows var(--dur-2) var(--ease),
              opacity var(--dur-1) var(--ease);
}
.searchbar.is-open {
  grid-template-rows: 1fr;
  opacity: 1;
  pointer-events: auto;
}
.searchbar__clip { overflow: hidden; }
.searchbar__in { padding-bottom: var(--s3); }

.searchbar form {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 12px 18px;
  border: 1px solid var(--line);
  border-radius: var(--r-pill);
  background: rgba(11, 6, 3, .9);
}
.searchbar form > .ico { font-size: 18px; flex: none; color: var(--gold-dim); }
.searchbar input {
  flex: 1;
  min-width: 0;
  border: 0;
  outline: 0;
  background: transparent;
  font: inherit;
  font-size: 14px;
  color: var(--cream);
}
.searchbar input::placeholder { color: var(--tan-dim); }
.searchbar button {
  flex: none;
  padding: 0;
  border: 0;
  background: transparent;
  color: var(--tan-dim);
  cursor: pointer;
  transition: color var(--dur-1) var(--ease);
}
.searchbar button .ico { font-size: 18px; }
.searchbar button:hover { color: var(--gold); }


/* ── 7. Drawer ────────────────────────────────────────────── */

.drawer-veil {
  position: fixed;
  inset: 0;
  z-index: 70;
  background: rgba(6, 3, 1, .7);
  backdrop-filter: blur(3px);
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--dur-2) var(--ease);
}
.drawer-veil.is-open { opacity: 1; pointer-events: auto; }

.drawer {
  position: fixed;
  top: 0;
  right: 0;
  z-index: 80;
  display: flex;
  flex-direction: column;
  width: min(80vw, 320px);
  height: 100dvh;
  padding: var(--s3);
  background: linear-gradient(180deg, #17100A 0%, #0C0704 100%);
  border-left: 1px solid var(--line-soft);
  overflow-y: auto;
  transform: translateX(100%);
  transition: transform var(--dur-3) var(--ease);
}
.drawer.is-open { transform: none; }

.drawer__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: var(--s3);
}
.drawer__brand {
  font-family: var(--font-display);
  font-size: 17px;
  font-weight: 600;
  color: var(--cream);
}

.drawer__nav { display: flex; flex-direction: column; }
.drawer__link {
  padding: 14px 0;
  border-bottom: 1px solid var(--line-soft);
  font-size: 14px;
  letter-spacing: .08em;
  color: var(--tan);
  transition: color var(--dur-1) var(--ease),
              padding-left var(--dur-1) var(--ease);
}
.drawer__link:hover { color: var(--gold); padding-left: 6px; }
.drawer__link.is-active { color: var(--gold); }

.drawer__cta { justify-content: center; margin-top: var(--s3); }

.drawer__foot {
  display: grid;
  gap: 6px;
  margin-top: auto;
  padding-top: var(--s3);
  font-size: 12.5px;
  color: var(--tan-dim);
}
.drawer__foot a:hover { color: var(--gold); }


/* No curtain, no waiting for one.
   Those delays exist so the bars arrive after the panels have
   drawn off. Where there is no curtain — every inner page, and a
   repeat visit to the home page — --reveal-delay is already 0,
   but the 1.05s and 1.15s offsets are not, so the page would sit
   there with no header for over a second. */
.no-intro .topbar    { animation-delay: .12s; }
.no-intro .site-head { animation-delay: .2s; }

@keyframes dropIn {
  from { opacity: 0; transform: translateY(-14px); }
  to   { opacity: 1; transform: none; }
}

@media (prefers-reduced-motion: reduce) {
  .topbar,
  .site-head { animation: none; }
}


/* ── 8. Section furniture ─────────────────────────────────────
   Everything below the hero shares these: one section rhythm,
   one eyebrow, and one scroll-reveal system.

   The hero plays on a timer because it is the first thing on
   screen and nothing has been scrolled yet. Every section after
   it plays on arrival instead — same easing, same distances, so
   the page feels like one piece of choreography rather than a
   stack of separate effects.                                    */

.sect {
  position: relative;
  padding: var(--sect-pad) 0;
  overflow: clip;
}
/* Override the token, not the padding — so anything else that
   comes to depend on the section rhythm follows automatically. */
@media (max-width: 899px) { .sect { --sect-pad: var(--s4); } }

/* --- Eyebrow -------------------------------------------------
   Diamond, label, rule. The rule is a separate element rather
   than a pseudo so it can draw itself out on reveal.           */

.eyebrow {
  display: flex;
  align-items: center;
  gap: var(--s2);
  font-size: 11px;
  font-weight: 500;
  letter-spacing: .36em;
  text-transform: uppercase;
  color: var(--gold-dim);
}
.eyebrow::before {
  content: "";
  flex: none;
  width: 6px;
  height: 6px;
  background: var(--gold);
  transform: rotate(45deg);
  box-shadow: 0 0 10px rgba(217, 164, 65, .55);
}
/* --- Ornament ------------------------------------------------
   Sits above a centred heading in place of an eyebrow. Markup is
   in includes/ornament.php.

   Deliberately no straight rule in it. A plain gradient line is
   what used to be here and what came out, so putting one back
   under a different name would miss the point. */

.ornament {
  display: block;
  width: 118px;
  height: auto;
  margin: 0 auto;
  color: var(--gold);
  /* drop-shadow follows the drawn strokes, unlike box-shadow,
     which would bloom off the SVG's rectangle instead. */
  filter: drop-shadow(0 0 7px rgba(var(--glow-rgb), .4));
}
.ornament__curl {
  stroke: var(--gold-dim);
  stroke-width: 1.2;
}
.ornament__dot {
  fill: var(--gold);
  stroke: none;
}

/* The heading below it does not need its usual eyebrow gap. */
.sect__head .ornament + .sect__title { margin-top: var(--s2); }

/* --- Section heading ------------------------------------------
   .sect__title is shared, so a heading is the same object whether
   it sits centred above a grid or left of a column. Only the
   wrapper decides which. */

.sect__head {
  max-width: 640px;
  margin-inline: auto;
  text-align: center;
}

.sect__title {
  margin-top: var(--s3);
  font-family: var(--font-display);
  font-weight: 500;
  font-size: clamp(1.55rem, 2.9vw, 2.45rem);
  line-height: 1.24;
  letter-spacing: -.015em;
  color: var(--cream);
}
/* Gold, upright. No italic anywhere on this site — the ital axis
   is not even loaded, so a browser would have to fake one. */
.sect__title .gold {
  color: var(--gold);
  font-style: normal;
}

/* --- Icon ring ------------------------------------------------
   A gold circle around a line icon. Lights up with whatever row
   it happens to sit in. */

.iconring {
  flex: none;
  display: grid;
  place-items: center;
  width: 44px;
  height: 44px;
  border: 1px solid var(--line);
  border-radius: var(--r-pill);
  color: var(--gold);
  transition: border-color var(--dur-2) var(--ease),
              background-color var(--dur-2) var(--ease),
              box-shadow var(--dur-2) var(--ease);
}
.iconring .ico { font-size: 20px; }

.point:hover .iconring,
.card:hover .iconring {
  border-color: var(--gold);
  background: rgba(217, 164, 65, .09);
  box-shadow: 0 0 20px rgba(217, 164, 65, .22);
}

@media (max-width: 599px) {
  .iconring { width: 38px; height: 38px; }
  .iconring .ico { font-size: 18px; }
}

/* --- Scroll reveal -------------------------------------------
   One attribute, four behaviours. JS only ever adds .is-in — it
   owns no timing and no distances, so the whole feel of the page
   is tunable from right here.

   No will-change: opacity and transform are already composited,
   and pinning a layer for every revealable element on the page
   costs real memory for nothing.                                */

[data-reveal] {
  --d: 0s;
  opacity: 0;
  transform: translateY(28px);
  transition:
    opacity   .95s var(--ease) var(--d),
    transform 1.15s var(--ease) var(--d),
    clip-path 1.3s  var(--ease-inout) var(--d);
}

/* Arrives sideways — used for list rows, which read better
   entering along the line they will settle on. */
[data-reveal="left"] { transform: translateX(-26px); }

/* Photographs. No fade — the mask IS the effect, and a fade on
   top of it just makes the image look underexposed on the way in. */
[data-reveal="wipe"] {
  opacity: 1;
  transform: none;
  clip-path: inset(100% 0 0 0);
}

/* Headlines, which carry their own masked lines and so must stay
   visible themselves. */
[data-reveal="lines"] {
  opacity: 1;
  transform: none;
}

[data-reveal].is-in {
  opacity: 1;
  transform: none;
}
/* Only the wipe. Landing clip-path on everything would leave a
   clipping box on each revealed element for good — and a
   clipping box cuts off box-shadow, so every glow on the page
   would quietly lose its bloom. */
[data-reveal="wipe"].is-in { clip-path: inset(0 0 0 0); }

/* A line that rises from behind a solid edge. The padding and
   negative margin cancel out, and exist only so overflow:hidden
   has somewhere to put the descenders on g, y and p. */
.rline {
  display: block;
  overflow: hidden;
  padding-bottom: .2em;
  margin-bottom: -.2em;
}
.rline > span {
  display: block;
  transform: translateY(112%);
  transition: transform 1.2s var(--ease) var(--d, 0s);
}
[data-reveal="lines"].is-in .rline > span { transform: none; }

/* Stagger, so the lines land one after another rather than as a
   block. 120ms is the smallest gap that still reads as sequence. */
.rline:nth-child(2) > span { transition-delay: calc(var(--d, 0s) + .12s); }
.rline:nth-child(3) > span { transition-delay: calc(var(--d, 0s) + .24s); }

/* --- Parallax ------------------------------------------------
   JS writes an inline transform here on every frame that matters.
   This one earns its will-change. */
[data-parallax] { will-change: transform; }

@media (prefers-reduced-motion: reduce) {
  [data-reveal],
  .rline > span {
    opacity: 1 !important;
    transform: none !important;
    clip-path: none !important;
    transition: none !important;
  }
  [data-parallax] { transform: none !important; will-change: auto; }
}


/* ── 9. Ritual band ───────────────────────────────────────────
   Full-bleed marquee of the treatment menu. Two identical copies
   of the list sit in one track; the track translates by exactly
   -50%, which lands copy two precisely where copy one started —
   so the loop has no seam to hide.                              */

.band {
  position: relative;
  overflow: hidden;
  border-top: 1px solid rgba(255, 214, 160, .18);
  border-bottom: 1px solid rgba(90, 34, 8, .9);
  /* Arch colonnade, then the ember wash beneath it. */
  background:
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='74' height='96' viewBox='0 0 74 96'%3E%3Cpath d='M12 96V36a25 25 0 0 1 50 0v60' fill='none' stroke='rgba(255,238,210,0.08)' stroke-width='1.3'/%3E%3C/svg%3E")
      repeat-x center bottom / 74px 132%,
    linear-gradient(180deg, rgba(0, 0, 0, .22) 0%, rgba(0, 0, 0, 0) 36%, rgba(0, 0, 0, .26) 100%),
    linear-gradient(90deg,
      #6E2909  0%,
      #A8430F 20%,
      #C8571A 50%,
      #A8430F 80%,
      #6E2909 100%);
}

/* Soft dark fades at both ends so words enter and leave rather
   than being chopped off by the viewport edge. */
.band::before,
.band::after {
  content: "";
  position: absolute;
  top: 0;
  bottom: 0;
  width: 90px;
  z-index: 2;
  pointer-events: none;
}
.band::before { left: 0;  background: linear-gradient(90deg,  rgba(19, 11, 5, .85), rgba(19, 11, 5, 0)); }
.band::after  { right: 0; background: linear-gradient(270deg, rgba(19, 11, 5, .85), rgba(19, 11, 5, 0)); }

.band__track {
  display: flex;
  width: max-content;
  animation: bandRun 52s linear infinite;
}
/* Let someone actually read a name they spotted. */
.band:hover .band__track { animation-play-state: paused; }

.band__list {
  display: flex;
  flex: none;
  align-items: center;
  height: 74px;
}

.band__item {
  display: inline-flex;
  align-items: center;
  font-family: var(--font-display);
  font-size: clamp(.88rem, 1.5vw, 1.18rem);
  font-weight: 500;
  letter-spacing: .015em;
  line-height: 1;
  white-space: nowrap;
  color: #FCF0DC;
  text-shadow: 0 1px 10px rgba(60, 18, 2, .5);
}
/* The diamond belongs to the item before it, which means the
   last one also gets a separator — exactly what a loop needs. */
.band__item::after {
  content: "";
  width: 6px;
  height: 6px;
  margin: 0 1.7rem;
  background: var(--gold-soft);
  transform: rotate(45deg);
  box-shadow: 0 0 9px rgba(255, 226, 170, .8);
}

@keyframes bandRun {
  from { transform: translateX(0); }
  to   { transform: translateX(-50%); }
}

@media (max-width: 899px) {
  .band__list { height: 60px; }
  .band__item::after { margin: 0 1.1rem; }
  .band::before,
  .band::after { width: 48px; }
}

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


/* ── 10. About ────────────────────────────────────────────────
   Two arches at different depths on the left, the story on the
   right. The arches drift against each other as the page scrolls
   — that gap opening up is the whole reason this section reads
   as having space in it.                                        */

.about { background: var(--ink-2); }

/* start, not center: the copy column is the taller of the two, so
   centring the media floated it down the page and opened a gap
   above the arch that read as a mistake. Topped out, the arch
   begins level with the eyebrow. */
.about__grid {
  display: grid;
  gap: var(--s5);
  align-items: start;
}
@media (min-width: 1000px) {
  .about__grid {
    /* minmax(0,…) and not the default minmax(auto,…): an auto
       track refuses to shrink below its content, and a long word
       in the copy would then push the whole grid off the page. */
    grid-template-columns: minmax(0, .9fr) minmax(0, 1.1fr);
    gap: var(--s6);
  }
}

/* --- The arches ---------------------------------------------- */

.about__media {
  position: relative;
  aspect-ratio: 1 / 1.04;
  margin: 0;
}

/* Centred with margins, not translate(-50%,-50%): the parallax
   writes an inline transform here, and it would wipe the centring
   out. Percentage margins resolve against the container's WIDTH,
   and aspect-ratio:1 makes this box as tall as it is wide, so
   -59% is half of both. */
.about__glow {
  position: absolute;
  top: 46%;
  left: 50%;
  width: 118%;
  aspect-ratio: 1;
  margin-top: -59%;
  margin-left: -59%;
  border-radius: 50%;
  background: radial-gradient(circle,
    rgba(224, 120, 48, .24) 0%,
    rgba(206, 104, 44, .10) 44%,
    rgba(196, 98, 42, 0)   70%);
  pointer-events: none;
}

.about__arch { position: absolute; }
.about__arch--back  { left: 0;  top: 0;      width: 66%; height: 84%; }
.about__arch--front { right: 1%; bottom: 0;  width: 49%; height: 61%; z-index: 2; }

.about__shape {
  position: absolute;
  inset: 0;
  border-radius: var(--r-arch);
  overflow: hidden;
  background: var(--surface);
  box-shadow: var(--shadow-2);
}
/* The wipe lives on its own layer inside the shape, not on the
   shape itself. The shape owns the arch radius and the drop
   shadow, and a clip-path on that element would cut the shadow
   off flush with the photograph. */
.about__wipe {
  position: absolute;
  inset: 0;
}
.about__wipe img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* An amber grade over whatever photograph is dropped in, so a
   replacement lands in the palette without being re-shot. It
   rides inside the wipe, so grade and image arrive together. */
.about__wipe::after {
  content: "";
  position: absolute;
  inset: 0;
  background:
    radial-gradient(72% 56% at 50% 62%, rgba(232, 140, 56, .24) 0%, rgba(232, 140, 56, 0) 74%),
    linear-gradient(180deg,
      rgba(22, 11, 4, .34)  0%,
      rgba(22, 11, 4, .04) 42%,
      rgba(22, 11, 4, .56) 100%);
  pointer-events: none;
}

/* The front arch cuts a dark moat around itself so it separates
   from the one behind instead of merging into it. */
.about__arch--front .about__shape {
  box-shadow: 0 0 0 10px var(--ink-2), var(--shadow-2);
}

/* Gold frame. box-shadow follows border-radius, so the bloom
   bends around the arch rather than boxing it off. */
.about__ring {
  position: absolute;
  inset: -10px;
  border: 1px solid rgba(var(--glow-rgb), .50);
  border-radius: var(--r-arch);
  box-shadow:
    0 0 26px rgba(var(--glow-rgb), .24),
    inset 0 0 18px rgba(var(--glow-rgb), .09);
  pointer-events: none;
}

/* --- The story ----------------------------------------------- */

/* The heading here is .sect__title — see section 8.

   It is capped because this column runs to the right edge of the
   shell, and the two lanterns hang over that edge. An uncapped
   heading grows into them: the copy rewrite took this one from
   24 characters to 41 and it landed straight on the near lamp.
   22ch wraps it well short of them whatever it is changed to
   later, which is the part a shorter heading alone would not
   have fixed. */
.about__copy > .sect__title { max-width: 22ch; }

.about__lead {
  max-width: 54ch;
  margin-top: var(--s3);
  font-size: 15px;
  line-height: 1.9;
  color: var(--tan);
}

.about__points {
  margin-top: var(--s4);
  border-top: 1px solid var(--line-soft);
}

/* One line each, so a flex row rather than a two-row grid — the
   grid's second track had nothing left to put in it. */
.point {
  display: flex;
  align-items: center;
  gap: var(--s3);
  padding: var(--s2) 0;
  border-bottom: 1px solid var(--line-soft);
  transition: padding-left var(--dur-2) var(--ease);
}
.point:hover { padding-left: 8px; }

/* The icon here is .iconring — see section 8. */

.point h3 {
  font-family: var(--font-display);
  font-weight: 500;
  font-size: 1.08rem;
  line-height: 1.4;
  color: var(--cream);
}

/* --- Small screens ------------------------------------------- */

@media (max-width: 999px) {
  .about__grid { gap: var(--s4); }
  .about__media {
    width: min(100%, 460px);
    margin-inline: auto;
  }
}
@media (max-width: 599px) {
  .point { gap: var(--s2); }
  .point h3 { font-size: .94rem; }
}


/* ── 11. Services ─────────────────────────────────────────────
   Cards on a scroll-snap carousel.

   Native overflow scrolling rather than a transform track: it
   gives touch swipe, trackpad swipe, keyboard scrolling and
   snapping for free, and the JS only has to nudge scrollLeft.
   A transform track would mean reimplementing every one of those
   by hand, badly.                                               */

.services {
  background:
    radial-gradient(92% 58% at 50% 0%, rgba(196, 98, 42, .11) 0%, rgba(196, 98, 42, 0) 62%),
    var(--ink);
}

/* --- The rail ------------------------------------------------ */

.carousel {
  --per: 4;                 /* cards in view */
  --gapc: var(--s3);
  display: flex;
  gap: var(--gapc);
  margin-top: var(--s3);
  /* overflow-x:auto forces overflow-y to compute to auto too, so
     a card's lift shadow and hover glow would be sliced off top
     and bottom. This padding gives them somewhere to land. */
  padding: 12px 2px 22px;
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  scroll-behavior: smooth;
  overscroll-behavior-x: contain;
  scrollbar-width: none;
  -ms-overflow-style: none;
}
.carousel::-webkit-scrollbar { display: none; }

@media (max-width: 1199px) { .carousel { --per: 3; } }
@media (max-width: 899px)  { .carousel { --per: 2; } }
/* A fractional count on purpose: the sliver of the next card is
   what tells a thumb there is more to swipe to. */
@media (max-width: 619px)  { .carousel { --per: 1.15; } }

/* --- Card ---------------------------------------------------- */

.card {
  flex: 0 0 calc((100% - (var(--per) - 1) * var(--gapc)) / var(--per));
  scroll-snap-align: start;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  border: 1px solid var(--line-soft);
  border-radius: var(--r-md);
  background: linear-gradient(180deg, var(--surface-2) 0%, var(--surface) 100%);
  /* opacity has to be in this list. A card carries data-reveal,
     and that rule declares its own transition — but this one is
     later in the file at equal specificity, so it replaces it
     outright rather than adding to it. Without opacity named
     here the cards were snapping into view instead of fading,
     because the reveal's transition had already been discarded. */
  transition: opacity .8s var(--ease) var(--d, 0s),
              border-color var(--dur-2) var(--ease),
              transform var(--dur-2) var(--ease),
              box-shadow var(--dur-2) var(--ease);
}
.card:hover {
  border-color: rgba(var(--glow-rgb), .5);
  transform: translateY(-4px);
  box-shadow:
    0 20px 42px -22px rgba(0, 0, 0, .9),
    0 0 30px rgba(var(--glow-rgb), .18);
}

.card__media {
  position: relative;
  aspect-ratio: 4 / 3;
  overflow: hidden;
  background: var(--surface);
}
.card__media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform 1.3s var(--ease);
}
.card:hover .card__media img { transform: scale(1.06); }

/* The same amber grade the about arches use, so a photograph
   dropped in later lands in the palette without a reshoot. The
   dark foot of it is also what the duration pill sits on. */
.card__media::after {
  content: "";
  position: absolute;
  inset: 0;
  background:
    radial-gradient(70% 60% at 50% 52%, rgba(232, 140, 56, .20) 0%, rgba(232, 140, 56, 0) 74%),
    linear-gradient(180deg,
      rgba(20, 10, 4, .22)  0%,
      rgba(20, 10, 4, .08) 38%,
      rgba(24, 12, 5, .82) 100%);
  pointer-events: none;
}

/* Stand-in for a treatment with no photograph yet. Not a broken
   image and not a grey box — it reads as a deliberate panel. */
.card__media--none {
  display: grid;
  place-items: center;
  background:
    radial-gradient(70% 70% at 50% 45%, rgba(224, 120, 48, .22) 0%, rgba(224, 120, 48, 0) 70%),
    linear-gradient(180deg, #3A2513 0%, #241609 60%, #140C05 100%);
}
.card__media--none .iconring { width: 62px; height: 62px; }
.card__media--none .iconring .ico { font-size: 28px; }

.card__time {
  position: absolute;
  left: 12px;
  bottom: 12px;
  z-index: 2;
  padding: 5px 12px;
  border: 1px solid rgba(var(--glow-rgb), .42);
  border-radius: var(--r-pill);
  background: rgba(12, 6, 2, .6);
  backdrop-filter: blur(6px) saturate(130%);
  -webkit-backdrop-filter: blur(6px) saturate(130%);
  font-size: 10px;
  font-weight: 500;
  letter-spacing: .2em;
  text-transform: uppercase;
  color: var(--gold-soft);
}

/* Brand motif, floated on the photograph. */
.card__badge {
  position: absolute;
  top: 12px;
  right: 12px;
  z-index: 2;
  width: 38px;
  height: 38px;
  border-color: rgba(var(--glow-rgb), .42);
  background: rgba(12, 6, 2, .55);
  backdrop-filter: blur(6px) saturate(130%);
  -webkit-backdrop-filter: blur(6px) saturate(130%);
}
.card__badge .ico { font-size: 18px; }

.card__body {
  display: flex;
  flex: 1;
  flex-direction: column;
  gap: 7px;
  padding: var(--s3);
}
.card__name {
  font-family: var(--font-display);
  font-weight: 500;
  font-size: 1.02rem;
  line-height: 1.4;
  color: var(--cream);
}
/* flex:1 so every card's foot rule lands on the same line however
   long the description runs. */
.card__text {
  flex: 1;
  font-size: 13px;
  line-height: 1.7;
  color: var(--tan-dim);
}

.card__foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s2);
  margin-top: 2px;
  padding-top: var(--s2);
  border-top: 1px solid var(--line-soft);
}
/* The price used to sit at the left of .card__foot, with the link
   pushed to the right by space-between. With the price gone the
   row holds one thing, and space-between on a single child does
   nothing at all — it would have sat at the left. */
.card__foot--solo { justify-content: flex-end; }
.card__link {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--tan);
  white-space: nowrap;
  transition: color var(--dur-1) var(--ease), gap var(--dur-1) var(--ease);
}
.card__link .ico { font-size: 15px; }
.card:hover .card__link { color: var(--gold); gap: 10px; }

/* --- Controls ------------------------------------------------ */

.carousel-nav {
  display: flex;
  align-items: center;
  gap: var(--s3);
}

/* A rail, not dots. Ten cards across four viewports makes seven
   stops, and seven dots says nothing useful — the rail shows both
   how far along you are and how much is left. */
.carousel-nav__rail {
  position: relative;
  flex: 1;
  height: 2px;
  border-radius: var(--r-pill);
  background: var(--line-soft);
  overflow: hidden;
}
.carousel-nav__thumb {
  position: absolute;
  top: 0;
  left: 0;
  bottom: 0;
  width: 30%;
  border-radius: inherit;
  background: linear-gradient(90deg, var(--gold-dim), var(--gold));
  transition: transform .25s var(--ease);
}

/* --- Small screens ------------------------------------------- */

@media (max-width: 599px) {
  .card__body { padding: var(--s2) var(--s3) var(--s3); }
  .card__name { font-size: .94rem; }
}


/* ── 12. Booking ──────────────────────────────────────────────
   The #book target. Every Book control on the site aims here —
   the hero, the header, the drawer and all ten service cards —
   so until this existed, twelve links on the home page did
   nothing at all.

   The form opens WhatsApp with the request already written,
   rather than posting anywhere. That is a deliberate choice, not
   a shortcut: PHP's mail() returns false on stock XAMPP and
   fails silently on most shared hosts, and a thank-you screen
   for a message that was never sent is worse than no form. This
   needs no server, works the moment the file is uploaded, and
   WhatsApp is how an Indian spa actually takes bookings.        */

.book {
  background:
    radial-gradient(80% 62% at 50% 100%, rgba(196, 98, 42, .17) 0%, rgba(196, 98, 42, 0) 66%),
    var(--ink-2);
}

/* stretch, not start: whichever column runs longer sets the row
   height and the other fills it, so the two sides always end on
   the same line. Which one is taller changes with the viewport
   as the copy rewraps, so neither can be the fixed one. */
.book__grid {
  display: grid;
  gap: var(--s4);
  align-items: stretch;
}
@media (min-width: 1000px) {
  .book__grid {
    /* The panel is the narrower side now. A form does not need
       the room a paragraph does, and short fields stretched wide
       read as harder to fill in than they are. */
    grid-template-columns: minmax(0, 1.1fr) minmax(0, .9fr);
    gap: var(--s6);
  }
}

/* --- Left: the facts ----------------------------------------- */

.book__lead {
  max-width: 46ch;
  margin-top: var(--s3);
  font-size: 15px;
  line-height: 1.9;
  color: var(--tan);
}

.book__facts {
  display: grid;
  gap: var(--s3);
  margin-top: var(--s4);
}
.book__facts li {
  display: flex;
  align-items: center;
  gap: var(--s3);
}
.book__label {
  display: block;
  font-size: 10px;
  font-weight: 500;
  letter-spacing: .24em;
  text-transform: uppercase;
  color: var(--gold-dim);
}
.book__value {
  display: block;
  margin-top: 3px;
  font-size: 14.5px;
  line-height: 1.5;
  color: var(--cream);
  transition: color var(--dur-1) var(--ease);
}
a.book__value:hover { color: var(--gold); }

/* --- Right: the panel ---------------------------------------- */

/* A flex column that centres its form, so when the panel is the
   shorter side and gets stretched, the extra height lands evenly
   above and below rather than pooling at the bottom. */
.book__panel {
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding: var(--s4);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: linear-gradient(180deg, var(--surface-2) 0%, var(--surface) 100%);
  box-shadow: var(--shadow-2);
}
@media (max-width: 599px) { .book__panel { padding: var(--s3); } }

/* Single-column form: a narrow button hard against the right edge
   looks stranded, and a primary action on a phone wants the full
   width anyway. */
@media (max-width: 619px) {
  .book__submit {
    justify-self: stretch;
    justify-content: center;
  }
  .book__note { text-align: center; }
}

.book__form {
  display: grid;
  gap: var(--s2);
}
@media (min-width: 620px) {
  .book__form { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

.field { display: grid; gap: 6px; }
.field--wide { grid-column: 1 / -1; }

.field label {
  font-size: 10px;
  font-weight: 500;
  letter-spacing: .22em;
  text-transform: uppercase;
  color: var(--gold-dim);
}

.field input,
.field select {
  width: 100%;
  min-width: 0;
  padding: 12px 14px;
  border: 1px solid var(--line-soft);
  border-radius: var(--r-sm);
  background: rgba(11, 6, 3, .55);
  color: var(--cream);
  font: inherit;
  font-size: 14px;
  font-weight: 500;
  transition: border-color var(--dur-1) var(--ease),
              box-shadow var(--dur-1) var(--ease);
}
.field input::placeholder { color: var(--tan-dim); }

/* A ring rather than the default outline: the outline would sit
   square on a rounded field. */
.field input:focus,
.field select:focus {
  outline: 0;
  border-color: var(--gold);
  box-shadow: 0 0 0 3px rgba(var(--glow-rgb), .15);
}

/* justify-self:end is what shrinks it. A grid item stretches to
   fill its cell by default, which is why spanning both columns
   made it full width — the button was never sized to its label. */
.book__submit {
  grid-column: 1 / -1;
  justify-self: end;
  margin-top: var(--s1);
}

.book__note {
  grid-column: 1 / -1;
  font-size: 12px;
  line-height: 1.6;
  color: var(--tan-dim);
}
.book__note a {
  color: var(--gold);
  border-bottom: 1px solid rgba(var(--glow-rgb), .4);
}
.book__note a:hover { color: var(--gold-soft); border-color: var(--gold); }


/* ── 13. Promo banner ─────────────────────────────────────────
   Full bleed, edge to edge — the only block on the page that
   breaks out of the shell's max width, which is what gives it
   its weight between two contained sections.

   The photograph does not sit in a box on the right. It is
   masked, so it dissolves into the warm gradient somewhere
   around the middle and the copy runs on clean colour. A hard
   edge down the centre would look like two panels bolted
   together.                                                     */

.promo {
  position: relative;
  display: flex;
  align-items: center;
  /* Sized by its content now, with min-height only as a floor —
     at 420px and 4rem of padding the copy did not fill it and the
     band was mostly empty colour. */
  min-height: 340px;
  padding: var(--s4) 0;
  /* Full bleed, so this gap has to be a margin. The section above
     already ends on its own padding; this is the extra separation
     that stops the band butting straight into it. */
  margin-top: var(--s3);
  overflow: clip;
  background: linear-gradient(100deg,
    #2B1507  0%,
    #401E0A 38%,
    #58290D 68%,
    #381908 100%);
  border-block: 1px solid var(--line-soft);
}

/* No grade over the photograph. The mask already keeps it off the
   copy, so nothing here needs darkening — the mobile rule below
   is the exception, because there the copy sits ON the image. */
.promo__bg {
  position: absolute;
  inset: 0;
  background-image: var(--promo-bg, none);
  background-size: cover;
  background-position: center right;
  background-repeat: no-repeat;
  /* Dissolve toward the left, so the copy never sits on detail. */
  -webkit-mask-image: linear-gradient(90deg, transparent 6%, rgba(0, 0, 0, .45) 38%, #000 66%);
          mask-image: linear-gradient(90deg, transparent 6%, rgba(0, 0, 0, .45) 38%, #000 66%);
}

.promo__in {
  position: relative;   /* above .promo__bg, which is in flow order below */
  z-index: 1;
}

.promo__copy { max-width: 32rem; }

.promo__text {
  max-width: 44ch;
  margin-top: var(--s3);
  font-size: 15px;
  line-height: 1.9;
  color: var(--tan);
}

.promo__cta { margin-top: var(--s3); }

.promo__marks {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s1) var(--s3);
  margin-top: var(--s3);
  font-size: 11.5px;
  letter-spacing: .12em;
  color: var(--tan);
}
.promo__marks li {
  display: inline-flex;
  align-items: center;
  gap: 9px;
}
.promo__marks li::before {
  content: "";
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: var(--gold);
  box-shadow: 0 0 8px rgba(var(--glow-rgb), .7);
}

/* --- Small screens -------------------------------------------
   The photograph stops being a right-hand panel and becomes a
   backdrop, so the mask turns vertical and a scrim goes over the
   whole thing — the copy is centred here and runs the full
   width, the same problem the hero had. */

@media (max-width: 899px) {
  .promo {
    min-height: 0;
    padding: var(--s4) 0;
    margin-top: var(--s2);
    text-align: center;
  }
  .promo__bg {
    background-position: center;
    background-image:
      linear-gradient(180deg,
        rgba(26, 12, 4, .74)  0%,
        rgba(26, 12, 4, .66) 45%,
        rgba(26, 12, 4, .80) 100%),
      var(--promo-bg, none);
    -webkit-mask-image: linear-gradient(180deg, transparent 0%, rgba(0, 0, 0, .55) 38%, #000 100%);
            mask-image: linear-gradient(180deg, transparent 0%, rgba(0, 0, 0, .55) 38%, #000 100%);
  }
  .promo__copy { max-width: none; }
  .promo__text { margin-inline: auto; }
  .promo__eyebrow,
  .promo__marks { justify-content: center; }
}


/* ── 14. Gallery ──────────────────────────────────────────────
   A mosaic rather than an even grid: tiles come in four sizes on
   a repeating rhythm, and grid-auto-flow:dense backfills the
   holes that leaves. That is also what lets the filter work
   without the layout falling apart — hide a third of the tiles
   and dense simply re-packs the rest.                           */

.gallery { background: var(--ink); }

/* --- Mosaic --------------------------------------------------- */

.gal {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  grid-auto-rows: 180px;
  grid-auto-flow: dense;
  gap: var(--s2);
  margin-top: var(--s3);
}

.gal__item {
  position: relative;
  display: block;
  padding: 0;
  border: 0;
  border-radius: var(--r-md);
  overflow: hidden;
  background: var(--surface);
  cursor: pointer;
}

.gal__item--wide { grid-column: span 2; }
.gal__item--tall { grid-row: span 2; }
.gal__item--big  { grid-column: span 2; grid-row: span 2; }

.gal__item img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform 1.1s var(--ease);
}
.gal__item:hover img,
.gal__item:focus-visible img { transform: scale(1.07); }

/* Dark foot, so a caption always has something to sit on. */
.gal__item::after {
  content: "";
  position: absolute;
  inset: auto 0 0 0;
  height: 55%;
  background: linear-gradient(180deg, rgba(12, 6, 2, 0), rgba(12, 6, 2, .88));
  opacity: 0;
  transition: opacity var(--dur-2) var(--ease);
  pointer-events: none;
}
/* Gold frame, drawn inside the tile so it cannot shift the grid. */
.gal__item::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 2;
  border: 1px solid transparent;
  border-radius: inherit;
  transition: border-color var(--dur-2) var(--ease),
              box-shadow var(--dur-2) var(--ease);
  pointer-events: none;
}
.gal__item:hover::after,
.gal__item:focus-visible::after { opacity: 1; }
.gal__item:hover::before,
.gal__item:focus-visible::before {
  border-color: rgba(var(--glow-rgb), .55);
  box-shadow: inset 0 0 26px rgba(var(--glow-rgb), .18);
}

.gal__cap {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 3;
  padding: var(--s2);
  font-family: var(--font-display);
  font-size: .82rem;
  line-height: 1.4;
  text-align: left;
  color: var(--cream);
  opacity: 0;
  transform: translateY(10px);
  transition: opacity var(--dur-2) var(--ease),
              transform var(--dur-2) var(--ease);
  pointer-events: none;
}
.gal__item:hover .gal__cap,
.gal__item:focus-visible .gal__cap { opacity: 1; transform: none; }

/* --- Lightbox ------------------------------------------------- */

.lightbox {
  position: fixed;
  inset: 0;
  z-index: 9500;
  display: grid;
  grid-template-rows: 1fr auto;
  place-items: center;
  padding: var(--s5) var(--s4) var(--s4);
  background: rgba(6, 3, 1, .93);
  backdrop-filter: blur(12px) saturate(120%);
  -webkit-backdrop-filter: blur(12px) saturate(120%);
  opacity: 0;
  visibility: hidden;
  transition: opacity var(--dur-2) var(--ease),
              visibility var(--dur-2) var(--ease);
}
.lightbox.is-open { opacity: 1; visibility: visible; }

.lightbox__img {
  max-width: min(92vw, 1100px);
  max-height: 72vh;
  width: auto;
  height: auto;
  border-radius: var(--r-md);
  border: 1px solid rgba(var(--glow-rgb), .35);
  box-shadow:
    0 40px 90px -40px rgba(0, 0, 0, 1),
    0 0 50px rgba(var(--glow-rgb), .14);
  animation: lbIn .45s var(--ease) both;
}
@keyframes lbIn {
  from { opacity: 0; transform: scale(.97); }
  to   { opacity: 1; transform: none; }
}

.lightbox__bar {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--s3);
  margin-top: var(--s3);
}
.lightbox__cap {
  font-family: var(--font-display);
  font-size: .9rem;
  text-align: center;
  color: var(--cream);
}
.lightbox__count {
  font-size: 11px;
  letter-spacing: .2em;
  color: var(--gold-dim);
}

.lightbox__close {
  position: absolute;
  top: var(--s3);
  right: var(--s3);
}
/* Vertically centred on the image, not the whole overlay, so they
   do not drift down when the caption bar grows. */
.lightbox__prev,
.lightbox__next {
  position: absolute;
  top: calc(50% - 28px);
  background: rgba(12, 6, 2, .6);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
}
.lightbox__prev { left: var(--s3); }
.lightbox__next { right: var(--s3); }

/* --- Small screens -------------------------------------------- */

@media (max-width: 899px) {
  .gal {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    grid-auto-rows: 150px;
  }
  /* A 2-wide tile IS the full row here, which is fine — but a
     2x2 would eat the whole screen, so it loses a row. */
  .gal__item--big { grid-row: span 1; }
  .gal__cap { font-size: .95rem; padding: var(--s1) var(--s2); }
  .lightbox { padding: var(--s4) var(--s2) var(--s3); }
  .lightbox__prev { left: var(--s1); }
  .lightbox__next { right: var(--s1); }
}
@media (max-width: 519px) {
  .gal { grid-auto-rows: 128px; gap: var(--s1); }
}

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


/* ── 15. Footer ───────────────────────────────────────────────
   The photograph is blended, not laid on. Three things together
   do that, and none of them alone would:

     opacity 16%          — it becomes texture, not a picture
     a mask to transparent — it has no bottom edge to give it away
     the ember glow above  — the page appears to light it

   Laid on at full strength it would read as a photograph with
   text sitting awkwardly on top of it.                          */

.site-foot {
  position: relative;
  overflow: clip;
  padding-top: var(--s5);
  background: var(--ink);
  border-top: 1px solid var(--line-soft);
}

.site-foot__bg {
  position: absolute;
  inset: 0;
  background-image: var(--foot-bg, none);
  background-size: cover;
  background-position: center 35%;
  opacity: .16;
  /* Fades out downward, so the image has no hard lower edge and
     the copyright bar sits on clean colour. */
  -webkit-mask-image: linear-gradient(180deg, #000 0%, rgba(0, 0, 0, .45) 52%, transparent 88%);
          mask-image: linear-gradient(180deg, #000 0%, rgba(0, 0, 0, .45) 52%, transparent 88%);
  pointer-events: none;
}

/* Warm light spilling down from the page above. */
.site-foot__glow {
  position: absolute;
  top: -40%;
  left: 50%;
  width: min(120%, 1200px);
  aspect-ratio: 1;
  margin-left: -60%;
  border-radius: 50%;
  background: radial-gradient(circle,
    rgba(224, 120, 48, .16) 0%,
    rgba(206, 104, 44, .06) 42%,
    rgba(196, 98, 42, 0)   68%);
  pointer-events: none;
}

.site-foot__in {
  position: relative;   /* above both backdrops */
  z-index: 1;
}

.site-foot__grid {
  display: grid;
  gap: var(--s4);
  padding-bottom: var(--s5);
}
@media (min-width: 760px) {
  .site-foot__grid {
    grid-template-columns: minmax(0, 1.7fr) minmax(0, 1fr) minmax(0, 1fr) minmax(0, 1.4fr);
    gap: var(--s5);
  }
}

/* --- Brand column --------------------------------------------- */

.foot__blurb {
  max-width: 34ch;
  margin-top: var(--s3);
  font-size: 13.5px;
  line-height: 1.8;
  color: var(--tan-dim);
}

.foot__social {
  display: flex;
  gap: 10px;
  margin-top: var(--s3);
}
.foot__social a {
  display: grid;
  place-items: center;
  width: 36px;
  height: 36px;
  border: 1px solid var(--line-soft);
  border-radius: var(--r-pill);
  color: var(--tan);
  transition: color var(--dur-1) var(--ease),
              border-color var(--dur-1) var(--ease),
              background-color var(--dur-1) var(--ease);
}
.foot__social .ico { font-size: 16px; }
.foot__social a:hover {
  color: var(--gold);
  border-color: rgba(var(--glow-rgb), .5);
  background: rgba(217, 164, 65, .08);
}

/* --- Link columns --------------------------------------------- */

.foot__head {
  font-size: 10.5px;
  font-weight: 500;
  letter-spacing: .26em;
  text-transform: uppercase;
  color: var(--gold-dim);
}

.foot__list {
  display: grid;
  gap: 11px;
  margin-top: var(--s3);
  font-size: 13.5px;
}
.foot__list a {
  color: var(--tan);
  transition: color var(--dur-1) var(--ease),
              padding-left var(--dur-1) var(--ease);
}
.foot__list a:hover { color: var(--gold); padding-left: 5px; }

/* --- Contact column ------------------------------------------- */

.foot__facts {
  display: grid;
  gap: var(--s2);
  margin-top: var(--s3);
  font-size: 13.5px;
  line-height: 1.6;
}
.foot__facts li {
  display: grid;
  grid-template-columns: 16px minmax(0, 1fr);
  gap: 11px;
  color: var(--tan);
}
.foot__facts .ico {
  font-size: 16px;
  margin-top: 2px;
  color: var(--gold-dim);
}
.foot__facts a { transition: color var(--dur-1) var(--ease); }
.foot__facts a:hover { color: var(--gold); }

/* --- Bottom bar ----------------------------------------------- */

.foot__bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--s2);
  padding: var(--s3) 0;
  border-top: 1px solid var(--line-soft);
  font-size: 12px;
  color: var(--tan-dim);
}
.foot__bar strong {
  font-weight: 500;
  color: var(--tan);
}

.foot__top {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-size: 10.5px;
  font-weight: 500;
  letter-spacing: .2em;
  text-transform: uppercase;
  color: var(--gold-dim);
  transition: color var(--dur-1) var(--ease);
}
.foot__top .ico {
  font-size: 15px;
  transition: transform var(--dur-1) var(--ease);
}
.foot__top:hover { color: var(--gold); }
.foot__top:hover .ico { transform: translateY(-3px); }

/* The build credit, centred in the bar. Quieter than the line
   either side of it — it is a signature, not a third piece of
   information the visitor came for. */
.foot__by {
  font-size: 11.5px;
  color: var(--tan-dim);
}
.foot__by a {
  color: var(--tan);
  transition: color var(--dur-1) var(--ease);
}
.foot__by a:hover { color: var(--gold); }

@media (max-width: 599px) {
  /* Three lines stacked and centred rather than three items
     fighting over one row. */
  .foot__bar { justify-content: center; text-align: center; }
}


/* ── 16. Hanging lantern ──────────────────────────────────────
   Markup and usage live in includes/lantern.php.

   What makes it read as a real hanging lamp rather than a
   wobbling picture, in order of how much each matters:

   1. It pivots at the CEILING. transform-origin sits at the top
      of the cord, not on the lamp. Rotating a pendulum about its
      own middle is the single most common way this effect goes
      wrong, and no amount of easing rescues it.

   2. The easing IS the physics. A pendulum is slowest at the
      ends of its arc and fastest through the bottom, which is
      exactly what ease-in-out describes. Linear would look like
      a metronome; a spring curve would look like it was hit.

   3. It rises at the extremes, for free. Because the pivot is
      above, rotating the assembly lifts the lamp slightly at
      each end of the swing — the real vertical bob of a
      pendulum, with nothing extra animating it.

   4. The flicker runs on its own clock. Sway and flame share no
      duration, so the lamp never pulses in time with itself.

   Cost: one transform and three opacities, all composited. The
   blurs are static, so each blurred layer rasterises once and
   only its opacity changes after that.                          */

.lantern {
  /* Metal and glass, so an instance can be retinted in place. */
  --lant-metal: rgba(217, 164, 65, .8);
  --lant-rib:   rgba(217, 164, 65, .35);
  --lant-glass: rgba(255, 176, 92, .18);

  position: absolute;
  top: 0;
  left: var(--x, 50%);
  z-index: 0;
  width: var(--size, 56px);
  margin-left: calc(var(--size, 56px) / -2);
  /* THE line that matters. See note 1 above. */
  transform-origin: top center;
  pointer-events: none;
  animation: lanternSway var(--dur, 6.4s) ease-in-out var(--delay, 0s) infinite;
}

@keyframes lanternSway {
  0%, 100% { transform: rotate(var(--swing, 2.2deg)); }
  50%      { transform: rotate(calc(var(--swing, 2.2deg) * -1)); }
}

/* Fades in at the top rather than starting flat, so it reads as
   running up into the dark instead of stopping dead. */
.lantern__cord {
  display: block;
  width: 1px;
  height: var(--drop, 90px);
  margin: 0 auto;
  background: linear-gradient(180deg,
    rgba(217, 164, 65, 0)   0%,
    rgba(217, 164, 65, .28) 26%,
    rgba(217, 164, 65, .5) 100%);
}

.lantern__lamp {
  position: relative;
  display: block;
  width: 100%;
}

.lantern__art {
  position: relative;
  display: block;
  width: 100%;
  height: auto;
}
.lantern__metal {
  fill: none;
  stroke: var(--lant-metal);
  stroke-width: 1.6;
  stroke-linejoin: round;
}
.lantern__rib {
  fill: none;
  stroke: var(--lant-rib);
  stroke-width: 1;
}
.lantern__glass {
  fill: var(--lant-glass);
  stroke: var(--lant-metal);
  stroke-width: 1.6;
}
.lantern__bead {
  fill: var(--lant-metal);
  stroke: none;
}

/* The flame itself, sitting behind the translucent glass. */
.lantern__core {
  position: absolute;
  left: 50%;
  top: 40%;
  width: 54%;
  aspect-ratio: 1;
  margin-left: -27%;
  margin-top: -27%;
  border-radius: 50%;
  /* Burnt out to white at the centre. A flame's hottest point
     always reads as colourless — hold it at amber all the way
     through and it looks like a painted disc, not a light. */
  background: radial-gradient(circle,
    rgba(255, 250, 232, 1)   0%,
    rgba(255, 214, 142, .78) 34%,
    rgba(255, 176, 88, .30)  60%,
    rgba(255, 160, 70, 0)    80%);
  filter: blur(1.5px);
  animation: lanternFlicker var(--flick, 4.3s) ease-in-out infinite;
}

/* The halo it throws into the room.
   No beam. A cast cone was tried and it read as a shadow rather
   than as light: a visible edge running down the page is what a
   projector does, not a lamp in a room. Light that falls on
   nothing has nothing to show it. All of it lives in the halo
   now, which is also how it actually looks.

   Two falloffs rather than one. Real light drops away sharply
   close to the source and then trails a long way out — a single
   gradient gives you one of those or the other, never both, and
   that is what makes a CSS glow look flat. */
.lantern__glow {
  position: absolute;
  left: 50%;
  top: 40%;
  width: 440%;
  aspect-ratio: 1;
  margin-left: -220%;
  margin-top: -220%;
  border-radius: 50%;
  background:
    /* the near burn */
    radial-gradient(circle,
      rgba(255, 226, 174, .52)  0%,
      rgba(255, 192, 114, .24) 10%,
      rgba(255, 186, 104, 0)   25%),
    /* the long trail */
    radial-gradient(circle,
      rgba(250, 174, 84, .22)  0%,
      rgba(236, 142, 60, .11) 27%,
      rgba(214, 110, 40, .04) 49%,
      rgba(196, 98, 42, 0)    72%);
  filter: blur(4px);
  animation: lanternFlicker var(--flick, 4.3s) ease-in-out infinite;
}

/* Irregular on purpose. An even sine reads as a pulsing bulb;
   a flame never repeats the same way twice. */
@keyframes lanternFlicker {
  0%   { opacity: .86; }
  8%   { opacity: .97; }
  15%  { opacity: .80; }
  26%  { opacity: 1; }
  34%  { opacity: .88; }
  43%  { opacity: .96; }
  55%  { opacity: .82; }
  66%  { opacity: .94; }
  74%  { opacity: .87; }
  86%  { opacity: 1; }
  94%  { opacity: .90; }
  100% { opacity: .86; }
}

/* A lantern is position:absolute, which makes it paint OVER
   ordinary in-flow content no matter how low its z-index goes —
   positioned beats non-positioned. Lifting the shell into its own
   layer is what puts the lamp behind the words rather than on
   them, at every width. */
.sect > .shell { position: relative; z-index: 1; }

/* Small screens keep their lanterns, but on different terms. A
   phone has no column to hang one beside, so each moves out to
   the gutter on a shorter cord at roughly half size.

   The flame stays lit here. It was switched off once, on the
   grounds that a bright point behind a letter is hard to read
   around — but with the lamps out in the gutter there is nothing
   behind them to read, and a half-size lantern with its flame
   removed has no bright part left at all. It stops looking like
   a light and starts looking like a faded drawing of one.

   Nothing is dimmed either. A flat opacity on the whole lamp
   fades the brass and the cord along with the light, which is
   the one thing that cannot happen: a lamp turned down still has
   a hot centre, it just throws less. */
@media (max-width: 1099px) {
  .lantern {
    left: var(--x-sm, var(--x, 50%));
    width: var(--size-sm, 28px);
    margin-left: calc(var(--size-sm, 28px) / -2);
  }
  .lantern__cord { height: var(--drop-sm, 56px); }

  /* The halo is pulled in rather than turned down. It is a
     proportion of the lamp, and the lamp is half size here, so
     at the desktop 440% it would be a small faint smudge; this
     keeps its reach sensible without touching its brightness. */
  .lantern__glow {
    width: 340%;
    margin-left: -170%;
    margin-top: -170%;
  }
}

@media (prefers-reduced-motion: reduce) {
  .lantern,
  .lantern__core,
  .lantern__glow {
    animation: none;
    transform: none;
  }
}


/* ── 17. Visit timeline ───────────────────────────────────────
   Four steps on a gold thread.

   The thread is built from one segment per step rather than a
   single line behind the row. That matters: a single line has to
   guess where the first and last nodes sit, and that guess is
   wrong the moment the gap or the column count changes. Each
   segment instead measures itself from its own node to the next
   one, in calc, so it stays right at every width.

   It also means the thread DRAWS, segment by segment, as the
   section arrives — which a single line could only do all at
   once.                                                         */

/* Roomier than the standard section rhythm. Two lanterns hang
   from the top edge here, and they need somewhere to hang before
   the heading starts. Overriding the token rather than the
   padding keeps it one value. */
.ritual {
  background: var(--ink-2);
  --sect-pad: var(--s5);
}
/* The site-wide mobile rule sits earlier in the file, so it would
   lose to the line above on specificity — this restates it. */
@media (max-width: 899px) { .ritual { --sect-pad: var(--s4); } }

.steps {
  --node: 76px;
  --lane: var(--s4);
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: var(--lane);
  margin-top: var(--s5);
  list-style: none;
  padding: 0;
}

.step {
  position: relative;
  text-align: center;
}

/* The connector, from the edge of this node to the edge of the
   next. 100% is this column's width, plus the gap between them,
   less one node's width for the two radii. */
.step::before {
  content: "";
  position: absolute;
  top: calc(var(--node) / 2);
  left: calc(50% + var(--node) / 2);
  width: calc(100% + var(--lane) - var(--node));
  height: 1px;
  background: linear-gradient(90deg, var(--gold-dim), rgba(var(--glow-rgb), .55));
  transform: scaleX(0);
  transform-origin: left center;
  transition: transform .75s var(--ease-inout) var(--d, 0s);
}
.step:last-child::before { content: none; }
.steps.is-in .step::before { transform: none; }

.step__node {
  position: relative;
  z-index: 1;
  display: grid;
  place-items: center;
  width: var(--node);
  height: var(--node);
  margin: 0 auto;
  border: 1px solid var(--line);
  border-radius: 50%;
  background:
    radial-gradient(circle at 50% 34%, var(--surface-2) 0%, var(--ink) 78%);
  /* A ring of page colour, so the thread never runs under the
     rim on a fractional pixel. */
  box-shadow: 0 0 0 7px var(--ink-2);
  opacity: 0;
  transform: scale(.86);
  transition: opacity .6s var(--ease) var(--d, 0s),
              transform .6s var(--ease) var(--d, 0s),
              border-color var(--dur-2) var(--ease),
              box-shadow var(--dur-2) var(--ease);
}
.steps.is-in .step__node { opacity: 1; transform: none; }

.step:hover .step__node {
  border-color: rgba(var(--glow-rgb), .6);
  box-shadow:
    0 0 0 7px var(--ink-2),
    0 0 26px rgba(var(--glow-rgb), .28);
}

.step__num {
  font-family: var(--font-display);
  font-weight: 500;
  font-size: 1.35rem;
  line-height: 1;
  color: var(--gold);
}

.step__title {
  margin-top: var(--s3);
  font-family: var(--font-display);
  font-weight: 500;
  font-size: 1.08rem;
  line-height: 1.4;
  color: var(--cream);
}
.step__text {
  max-width: 30ch;
  margin: 8px auto 0;
  font-size: 13px;
  line-height: 1.75;
  color: var(--tan-dim);
}

/* --- Small screens -------------------------------------------
   The thread turns and runs down the left instead of across, so
   the steps read as a sequence rather than as four loose cards. */

@media (max-width: 899px) {
  .steps {
    --node: 58px;
    --lane: var(--s3);
    grid-template-columns: 1fr;
  }
  .step {
    display: grid;
    grid-template-columns: var(--node) minmax(0, 1fr);
    gap: var(--s3);
    align-items: start;
    text-align: left;
  }
  .step::before {
    top: var(--node);
    left: calc(var(--node) / 2);
    width: 1px;
    height: calc(100% + var(--lane) - var(--node));
    background: linear-gradient(180deg, var(--gold-dim), rgba(var(--glow-rgb), .55));
    transform: scaleY(0);
    transform-origin: top center;
  }
  .step__num   { font-size: 1.1rem; }
  .step__title { margin-top: 0; }
  .step__text  { max-width: none; margin-inline: 0; }
}

@media (prefers-reduced-motion: reduce) {
  .step::before,
  .step__node {
    opacity: 1;
    transform: none;
    transition: none;
  }
}


/* ── 18. Inner page top ───────────────────────────────────────
   Pages without a banner have to clear the bars themselves.

   The header is position:absolute over the page rather than in
   flow, so nothing below it is pushed down — without this the
   first section starts underneath it. The sum changes on a phone
   because the top bar is hidden there.                          */

.sect--top { padding-top: calc(var(--topbar-h) + var(--header-h) + var(--s5)); }

@media (max-width: 899px) {
  .sect--top { padding-top: calc(var(--header-h) + var(--s4)); }
}

/* ── 19. About page ───────────────────────────────────────────
   Alternating rows, so the page reads as a story rather than as
   a stack of matching cards.                                    */

.story {
  display: grid;
  gap: var(--s4);
  align-items: center;
}
@media (min-width: 900px) {
  .story {
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    gap: var(--s6);
  }
  /* Every other row swaps sides. Only the PICTURE moves: the
     copy stays in source order, so a screen reader still gets
     heading then text, whichever way round it looks. */
  .story--flip .story__media { order: 2; }
}
.story + .story { margin-top: var(--s6); }
@media (max-width: 899px) { .story + .story { margin-top: var(--s5); } }

.story__media {
  position: relative;
  margin: 0;
}
.story__shape {
  position: relative;
  overflow: hidden;
  aspect-ratio: 4 / 3;
  border-radius: var(--r-md);
  background: var(--surface);
  box-shadow: var(--shadow-2);
}
.story__shape img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}
/* Arch-topped on the first row only, so the motif appears once
   rather than becoming the page's default shape. */
.story--arch .story__shape { aspect-ratio: 3 / 4; border-radius: var(--r-arch); }

.story__frame {
  position: absolute;
  inset: -10px;
  border: 1px solid rgba(var(--glow-rgb), .4);
  border-radius: var(--r-md);
  pointer-events: none;
}
.story--arch .story__frame { border-radius: var(--r-arch); }

/* A caption plate hanging off the corner of the picture. */
.story__tag {
  position: absolute;
  right: -10px;
  bottom: var(--s3);
  z-index: 2;
  padding: 10px 18px;
  border: 1px solid rgba(var(--glow-rgb), .45);
  border-radius: var(--r-sm);
  background: rgba(11, 6, 3, .86);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  font-size: 10px;
  font-weight: 500;
  letter-spacing: .24em;
  text-transform: uppercase;
  color: var(--gold);
}
@media (max-width: 899px) { .story__tag { right: 8px; } }

.story__title {
  margin-top: var(--s2);
  font-family: var(--font-display);
  font-weight: 500;
  font-size: clamp(1.6rem, 2.6vw, 2.15rem);
  line-height: 1.2;
  color: var(--cream);
}
.story__title .gold { color: var(--gold); }
.story__text {
  max-width: 48ch;
  margin-top: var(--s2);
  font-size: 15px;
  line-height: 1.85;
  color: var(--tan);
}
.story__text + .story__text { margin-top: var(--s2); }

/* --- A line worth pulling out --------------------------------- */

.pullquote {
  position: relative;
  max-width: 44rem;
  margin: 0 auto;
  padding: var(--s4) 0 var(--s4) var(--s4);
  border-left: 1px solid rgba(var(--glow-rgb), .45);
  font-family: var(--font-display);
  font-weight: 500;
  font-size: clamp(1.25rem, 2.2vw, 1.7rem);
  line-height: 1.5;
  color: var(--cream);
}
.pullquote cite {
  display: block;
  margin-top: var(--s2);
  font-family: var(--font-body);
  font-size: 11px;
  font-style: normal;
  font-weight: 500;
  letter-spacing: .24em;
  text-transform: uppercase;
  color: var(--gold-dim);
}

/* --- What we believe ------------------------------------------ */

.beliefs {
  display: grid;
  gap: var(--s3);
  margin-top: var(--s5);
  grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
}
.belief {
  padding: var(--s3);
  border: 1px solid var(--line-soft);
  border-radius: var(--r-md);
  background: linear-gradient(180deg, var(--surface-2) 0%, var(--surface) 100%);
  transition: border-color var(--dur-2) var(--ease),
              transform var(--dur-2) var(--ease),
              box-shadow var(--dur-2) var(--ease);
}
.belief:hover {
  border-color: rgba(var(--glow-rgb), .45);
  transform: translateY(-4px);
  box-shadow: 0 18px 38px -22px rgba(0, 0, 0, .9),
              0 0 26px rgba(var(--glow-rgb), .14);
}
.belief h3 {
  margin-top: var(--s2);
  font-family: var(--font-display);
  font-weight: 500;
  font-size: 1.12rem;
  line-height: 1.3;
  color: var(--cream);
}
.belief p {
  margin-top: 7px;
  font-size: 13px;
  line-height: 1.75;
  color: var(--tan);
}


/* ── 20. Services page ────────────────────────────────────────
   The same cards as the home carousel, laid out as a grid, with
   a search-and-filter bar above and a pager below.

   .card needs no changes to work here. Its flex shorthand sets a
   flex-basis, and flex properties are simply ignored on a grid
   item — so one component serves both layouts rather than being
   forked into a second near-copy.                               */

.cardgrid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(255px, 1fr));
  gap: var(--s3);
  margin-top: var(--s4);
}

/* --- Search and filter ---------------------------------------- */

.toolbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--s3);
}

.searchbox {
  display: flex;
  align-items: center;
  gap: 12px;
  flex: 1 1 280px;
  max-width: 420px;
  padding: 11px 16px;
  border: 1px solid var(--line-soft);
  border-radius: var(--r-pill);
  background: rgba(11, 6, 3, .55);
  transition: border-color var(--dur-1) var(--ease),
              box-shadow var(--dur-1) var(--ease);
}
.searchbox:focus-within {
  border-color: var(--gold);
  box-shadow: 0 0 0 3px rgba(var(--glow-rgb), .14);
}
.searchbox > .ico { font-size: 18px; flex: none; color: var(--gold-dim); }
.searchbox input {
  flex: 1;
  min-width: 0;
  border: 0;
  outline: 0;
  background: transparent;
  font: inherit;
  font-size: 14px;
  color: var(--cream);
}
.searchbox input::placeholder { color: var(--tan-dim); }
/* The native clear cross is white and square on a dark field. */
.searchbox input::-webkit-search-cancel-button { display: none; }
.searchbox button {
  flex: none;
  padding: 0;
  border: 0;
  background: transparent;
  color: var(--tan-dim);
  cursor: pointer;
  transition: color var(--dur-1) var(--ease);
}
.searchbox button .ico { font-size: 17px; }
.searchbox button:hover { color: var(--gold); }

.chips {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s1);
}
.chip {
  display: inline-flex;
  align-items: center;
  padding: 8px 16px;
  border: 1px solid var(--line-soft);
  border-radius: var(--r-pill);
  background: transparent;
  color: var(--tan);
  font-size: 11px;
  font-weight: 500;
  letter-spacing: .12em;
  text-transform: uppercase;
  white-space: nowrap;
  cursor: pointer;
  transition: color var(--dur-1) var(--ease),
              border-color var(--dur-1) var(--ease),
              background-color var(--dur-1) var(--ease);
}
.chip:hover { color: var(--gold); border-color: var(--line); }
.chip.is-active {
  border-color: transparent;
  color: #1A0F04;
  background: linear-gradient(118deg,
    #F4DCA6            0%,
    var(--gold)       34%,
    var(--gold-warm)  64%,
    var(--gold-ember) 100%);
  box-shadow: 0 0 18px rgba(var(--glow-rgb), .28);
}

/* --- What the filter found ------------------------------------ */

.resultline {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s2);
  margin-top: var(--s3);
  padding-top: var(--s3);
  border-top: 1px solid var(--line-soft);
  font-size: 12.5px;
  color: var(--tan-dim);
}
.resultline strong { color: var(--cream); font-weight: 500; }
.resultline a {
  color: var(--gold);
  border-bottom: 1px solid rgba(var(--glow-rgb), .4);
  transition: color var(--dur-1) var(--ease);
}
.resultline a:hover { color: var(--gold-soft); }

.noresults {
  padding: var(--s6) var(--s3);
  margin-top: var(--s4);
  border: 1px solid var(--line-soft);
  border-radius: var(--r-md);
  background: rgba(29, 18, 9, .4);
  text-align: center;
}
.noresults .ico { font-size: 32px; color: var(--gold-dim); }
.noresults h2 {
  margin-top: var(--s2);
  font-family: var(--font-display);
  font-weight: 500;
  font-size: 1.4rem;
  color: var(--cream);
}
.noresults p {
  max-width: 40ch;
  margin: 10px auto 0;
  font-size: 14px;
  line-height: 1.75;
  color: var(--tan);
}

/* --- Pager ----------------------------------------------------- */

.pager {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: var(--s1);
  margin-top: var(--s5);
}

.pager__num,
.pager__step {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  min-width: 40px;
  height: 40px;
  padding: 0 14px;
  justify-content: center;
  border: 1px solid var(--line-soft);
  border-radius: var(--r-pill);
  font-size: 12px;
  font-weight: 500;
  letter-spacing: .1em;
  color: var(--tan);
  transition: color var(--dur-1) var(--ease),
              border-color var(--dur-1) var(--ease),
              background-color var(--dur-1) var(--ease);
}
.pager__num { font-family: var(--font-display); font-size: 15px; letter-spacing: 0; }
.pager__step .ico { font-size: 15px; }

a.pager__num:hover,
a.pager__step:hover {
  color: var(--gold);
  border-color: rgba(var(--glow-rgb), .5);
  background: rgba(217, 164, 65, .07);
}

.pager__num.is-active {
  border-color: transparent;
  color: #1A0F04;
  background: linear-gradient(118deg,
    #F4DCA6            0%,
    var(--gold)       34%,
    var(--gold-warm)  64%,
    var(--gold-ember) 100%);
  box-shadow: 0 0 18px rgba(var(--glow-rgb), .28);
}

/* At the first or last page these render as <span>, not <a>, so
   they cannot be clicked or focused. This is only how they look. */
.pager__step:not(a) {
  opacity: .35;
  cursor: default;
}

.pager__gap {
  padding: 0 4px;
  color: var(--tan-dim);
  letter-spacing: .1em;
}

@media (max-width: 599px) {
  .toolbar { flex-direction: column; align-items: stretch; }
  .searchbox { max-width: none; }
  .chips { justify-content: center; }
  .pager__step span { display: none; }
  .pager__step { min-width: 40px; padding: 0 12px; }
}

/* ── 21. Gallery page ─────────────────────────────────────────
   Reuses the mosaic and the lightbox from section 14 — same
   class names, so the same JavaScript drives both.              */

.gallerypage .gal { margin-top: 0; }

/* Counts under the heading, so the page says how much there is
   before anyone starts scrolling. */
.galstat {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  align-items: center;
  gap: var(--s1) var(--s3);
  margin-top: var(--s3);
  font-size: 11px;
  font-weight: 500;
  letter-spacing: .2em;
  text-transform: uppercase;
  color: var(--tan-dim);
}
.galstat li { display: inline-flex; align-items: center; gap: 9px; }
.galstat li::before {
  content: "";
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: var(--gold);
  box-shadow: 0 0 8px rgba(var(--glow-rgb), .7);
}
.galstat strong { color: var(--cream); font-weight: 500; }


/* --- Closing call, shared by all three pages ------------------ */

.endcall {
  text-align: center;
  padding: var(--s5) var(--s3);
  border: 1px solid var(--line-soft);
  border-radius: var(--r-md);
  background:
    radial-gradient(80% 120% at 50% 0%, rgba(196, 98, 42, .16) 0%, rgba(196, 98, 42, 0) 70%),
    linear-gradient(180deg, var(--surface-2) 0%, var(--surface) 100%);
}
.endcall h2 {
  margin-top: var(--s2);
  font-family: var(--font-display);
  font-weight: 500;
  font-size: clamp(1.5rem, 2.6vw, 2.1rem);
  line-height: 1.2;
  color: var(--cream);
}
.endcall h2 .gold { color: var(--gold); }
.endcall p {
  max-width: 46ch;
  margin: var(--s2) auto 0;
  font-size: 14.5px;
  line-height: 1.8;
  color: var(--tan);
}
.endcall__actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--s2);
  margin-top: var(--s4);
}


/* --- Small screens, all three pages --------------------------- */

@media (max-width: 767px) {
  .pullquote { padding-left: var(--s3); }
}
@media (max-width: 479px) {
  .endcall { padding: var(--s4) var(--s2); }
}


/* ── 22. Why choose us ────────────────────────────────────────
   Pitch on the left, four reasons on the right, a branch down
   the middle.

   Three real grid columns rather than two with the branch
   floated between them: the middle column is what holds the two
   sides apart, so the gap cannot collapse and the art cannot
   overlap the words at any width.                               */

.why {
  background:
    radial-gradient(72% 60% at 50% 40%, rgba(196, 98, 42, .10) 0%, rgba(196, 98, 42, 0) 68%),
    var(--ink-2);
}

.why__grid {
  display: grid;
  gap: var(--s4);
  align-items: center;
}
/* Named areas rather than source order, because the button is a
   grid child now: on a wide screen it has to climb back under the
   copy, while in source it sits last so the narrow layout gets
   the order it wants for free.

   row-gap is 0 and .why__cta keeps its own margin-top, so the
   split into two rows does not open a --s5 gap between the
   paragraph and the button that was never there before. */
@media (min-width: 1000px) {
  .why__grid {
    grid-template-columns: minmax(0, 1.05fr) auto minmax(0, .95fr);
    grid-template-areas:
      "copy leaf list"
      "cta  leaf list";
    column-gap: var(--s5);
    row-gap: 0;
  }
  .why__copy { grid-area: copy; }
  .why__cta  { grid-area: cta; }
  .why__leaf { grid-area: leaf; }
  .why__list { grid-area: list; }
}

/* --- The branch ------------------------------------------------ */

.why__leaf {
  display: block;
  justify-self: center;
  width: clamp(90px, 11vw, 165px);
  opacity: .9;
  /* The glow belongs on the artwork, not on its box — drop-shadow
     follows the drawn shape, where box-shadow would bloom off a
     rectangle around a transparent PNG. */
  filter: drop-shadow(0 0 22px rgba(var(--glow-rgb), .28));
}
.why__leaf img,
.why__leaf svg {
  display: block;
  width: 100%;
  height: auto;
}
.why__leaf svg { color: var(--gold); }

/* --- The pitch ------------------------------------------------- */

.why__eyebrow {
  font-size: 11px;
  font-weight: 500;
  letter-spacing: .3em;
  text-transform: uppercase;
  color: var(--gold-dim);
}

.why__title {
  margin-top: var(--s2);
  font-family: var(--font-display);
  font-weight: 500;
  font-size: clamp(1.7rem, 2.9vw, 2.5rem);
  line-height: 1.2;
  letter-spacing: -.015em;
  color: var(--cream);
}
.why__title .gold { color: var(--gold); }

.why__text {
  max-width: 44ch;
  margin-top: var(--s3);
  font-size: 14.5px;
  line-height: 1.85;
  color: var(--tan);
}

.why__cta { margin-top: var(--s4); }

/* --- The four reasons ------------------------------------------ */

.why__list {
  display: grid;
  gap: var(--s3);
  padding: 0;
  list-style: none;
}

.why__item {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: var(--s3);
  align-items: start;
}

.why__icon {
  display: grid;
  place-items: center;
  flex: none;
  width: 50px;
  height: 50px;
  border: 1px solid rgba(217, 164, 65, .35);
  border-radius: 50%;
  background: radial-gradient(circle at 50% 34%, var(--surface-2) 0%, rgba(11, 6, 3, .7) 82%);
  color: var(--gold);
  transition: border-color var(--dur-2) var(--ease),
              box-shadow var(--dur-2) var(--ease);
}
.why__icon .ico { font-size: 21px; }
.why__item:hover .why__icon {
  border-color: rgba(var(--glow-rgb), .7);
  box-shadow: 0 0 22px rgba(var(--glow-rgb), .28);
}

/* Poppins, not the display serif — these are labels, and the
   serif is doing the headline job two columns to the left. */
.why__item h3 {
  font-family: var(--font-body);
  font-weight: 600;
  font-size: 14.5px;
  line-height: 1.35;
  color: var(--cream);
}
.why__item p {
  margin-top: 4px;
  max-width: 34ch;
  font-size: 13px;
  line-height: 1.7;
  color: var(--tan-dim);
}

/* --- Small screens --------------------------------------------- */

@media (max-width: 999px) {
  .why__text { margin-inline: auto; }

  /* A quarter turn, so the branch lies ACROSS the page between
     the pitch and the list rather than standing beside them —
     upright it would be a tall divider the page has to scroll
     past, which is why it used to be hidden outright here.

     A transform does not change the layout box, so the two are
     declared separately: the box gets the turned proportions
     (--len across, .4 of that tall) and the image keeps the
     untimed ones (.4 across, --len tall). inset:0 with margin
     auto centres it, and the rotation happens about that centre,
     so the artwork lands exactly in the box. .4 is the branch's
     own 400x1000 aspect — change the artwork and this changes
     with it. */
  .why__leaf {
    --len: min(72vw, 320px);
    position: relative;
    width: var(--len);
    height: calc(var(--len) * .4);
  }
  .why__leaf img,
  .why__leaf svg {
    position: absolute;
    inset: 0;
    margin: auto;
    width: calc(var(--len) * .4);
    height: var(--len);
    transform: rotate(90deg);
  }

  /* Centred at the foot of the section. The grid's own gap does
     the spacing here, so the margin that separates it from the
     paragraph on a wide screen would only double it. */
  .why__cta {
    justify-self: center;
    margin-top: 0;
  }
}
@media (max-width: 599px) {
  .why__item { gap: var(--s2); }
  .why__icon { width: 44px; height: 44px; }
  .why__icon .ico { font-size: 19px; }
  .why__leaf { --len: min(66vw, 240px); }
}


/* ── 23. Leaf watermark ───────────────────────────────────────
   The same branch as the Why section, very faint, at a section's
   edge. Markup is includes/leaf.php.

   It hangs off the edge on purpose — a decoration fully inside
   the frame reads as content that has been placed, while one
   running off the side reads as texture the page is sitting on.

   Everything it needs is already true of .sect: position
   relative so it has something to anchor to, overflow clip so
   the overhang is cut rather than widening the document, and
   .sect > .shell at z-index 1 so the copy stays above it.       */

.leafmark {
  position: absolute;
  top: var(--lm-top, 50%);
  z-index: 0;
  width: var(--lm-size, clamp(170px, 21vw, 330px));
  opacity: var(--lm-op, .07);
  pointer-events: none;
  user-select: none;
}
.leafmark img {
  display: block;
  width: 100%;
  height: auto;
}

/* Mirrored on the left, so the two sides are not the same
   picture twice. The translate has to be repeated in each
   transform — a second transform declaration replaces the
   first rather than adding to it. */
.leafmark--r {
  right: -5%;
  transform: translateY(-50%) rotate(var(--lm-rot, 0deg));
}
.leafmark--l {
  left: -5%;
  transform: translateY(-50%) scaleX(-1) rotate(var(--lm-rot, 0deg));
}

/* Gone below the point where the columns close up. At phone
   width there is no margin for it to sit in, so it would end up
   directly behind the text rather than beside it. */
@media (max-width: 899px) {
  .leafmark { display: none; }
}


/* ── 24. About page ───────────────────────────────────────────
   Nine bands. The reference alternates cream and dark; this one
   alternates --ink and --ink-2 instead, which are two steps of
   the same brown rather than two different pages. Everything
   else about the rhythm is kept.

   .band--a and .band--b are the two tones. Apply them in turn
   down the page and the seams do the separating that a light
   background was doing in the reference.                        */

/* The two tones. Each also publishes its colour as a triplet,
   because the bleed scrim below has to fade a photograph into
   whichever band it happens to be sitting in — and a gradient
   needs that colour at zero alpha, which a hex token cannot
   give it. */
.band--a { background: var(--ink);   --band-rgb: 11, 6, 3; }
.band--b { background: var(--ink-2); --band-rgb: 19, 11, 5; }


/* --- Bleed row: half the band IS the picture ------------------
   Not the same thing as .split. There the picture is a contained
   card sitting inside the shell with a gutter around it; here it
   runs off the edge of the page and the copy sits in the other
   half of the shell.

   Using one of these for all five rows is what made the page
   read as the same layout five times. */

.bleed {
  position: relative;
  display: flex;
  align-items: center;
  min-height: 30rem;
  overflow: clip;
}

.bleed__media {
  position: absolute;
  top: 0;
  bottom: 0;
  width: 52%;
}

/* --- Full variant: the picture is the whole band --------------
   Not half of it. Its scrim is further down, AFTER the half
   variants' — .bleed--full .bleed__media::after and
   .bleed--l .bleed__media::after have identical specificity, so
   whichever is written last is the one that paints. */
.bleed--full .bleed__media { width: 100%; left: 0; right: 0; }

.bleed__media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.bleed--r .bleed__media { right: 0; }
.bleed--l .bleed__media { left: 0; }

/* Fades into the band on its inner edge, so the photograph ends
   in the page rather than against a cut. */
.bleed__media::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
}
.bleed--r .bleed__media::after {
  background: linear-gradient(90deg,
    rgb(var(--band-rgb))       0%,
    rgba(var(--band-rgb), .55) 22%,
    rgba(var(--band-rgb), 0)   55%);
}
.bleed--l .bleed__media::after {
  background: linear-gradient(270deg,
    rgb(var(--band-rgb))       0%,
    rgba(var(--band-rgb), .55) 22%,
    rgba(var(--band-rgb), 0)   55%);
}

/* The full variant has NO scrim behind the copy. The photograph
   is the design of this band, so it is shown as taken.

   All that is left on top is a seam fade at the very top and
   bottom edges, so the band meets the ones above and below it
   instead of ending on a hard horizontal cut. It is fully clear
   through the middle 88%, which is where every word sits.

   Both classes are in the selector so this outranks the two
   rules above rather than merely following them — an edit that
   moves this block would otherwise silently put the side scrim
   back. */
.bleed--full.bleed--l .bleed__media::after,
.bleed--full.bleed--r .bleed__media::after {
  background: linear-gradient(180deg,
    rgba(var(--band-rgb), .55) 0%,
    rgba(var(--band-rgb), 0)   6%,
    rgba(var(--band-rgb), 0)   94%,
    rgba(var(--band-rgb), .55) 100%);
}

.bleed__in {
  position: relative;
  z-index: 1;
  width: 100%;
  display: grid;
  gap: var(--s5);
}
@media (min-width: 900px) {
  .bleed__in { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  /* Copy takes the half the picture is not in. */
  .bleed--r .bleed__copy { grid-column: 1; }
  .bleed--l .bleed__copy { grid-column: 2; }
}

/* With no scrim on the full variant the copy sits straight on the
   photograph, so legibility has to come from the glyphs rather
   than from a layer over the picture. A shadow the colour of the
   band reads as depth, and — unlike a panel or an overlay —
   costs the photograph nothing. */
.bleed--full .bleed__copy {
  text-shadow:
    0 1px 2px  rgba(var(--band-rgb), .9),
    0 2px 12px rgba(var(--band-rgb), .75),
    0 4px 30px rgba(var(--band-rgb), .55);
}

/* --- Small screens --------------------------------------------
   The picture stops being a half and becomes a backdrop behind
   the copy, because at this width there is no other half to put
   it in. */
@media (max-width: 899px) {
  .bleed {
    min-height: 0;
    padding-block: var(--s5);
  }
  .bleed__media { width: 100%; }

  /* The full variant is listed too, and at the same weight as the
     rule that clears its scrim on desktop — without it that rule
     wins here as well, and on a phone the copy has nothing behind
     it but the photograph. There is no second column to move it
     into at this width, so this one stays. */
  .bleed--r .bleed__media::after,
  .bleed--l .bleed__media::after,
  .bleed--full.bleed--r .bleed__media::after,
  .bleed--full.bleed--l .bleed__media::after {
    background: linear-gradient(180deg,
      rgba(var(--band-rgb), .82) 0%,
      rgba(var(--band-rgb), .88) 60%,
      rgb(var(--band-rgb))      100%);
  }
}

/* --- Split row: picture one side, copy the other --------------
   Used four times on this page. One component, a modifier to
   swap sides — not four near-identical row layouts that drift
   apart the first time one of them is adjusted. */

.split {
  display: grid;
  gap: var(--s4);
  align-items: center;
}
@media (min-width: 900px) {
  .split {
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    gap: var(--s6);
  }
  /* Only the PICTURE moves. The copy stays in source order, so a
     screen reader gets heading then text whichever way round it
     looks. */
  .split--flip .split__media { order: 2; }
}

.split__media {
  position: relative;
  margin: 0;
}
.split__shape {
  position: relative;
  overflow: hidden;
  aspect-ratio: 4 / 3;
  border-radius: var(--r-md);
  background: var(--surface);
  box-shadow: var(--shadow-2);
}
.split__shape img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform 1.4s var(--ease);
}
.split__media:hover .split__shape img { transform: scale(1.04); }

.split__frame {
  position: absolute;
  inset: -10px;
  border: 1px solid rgba(var(--glow-rgb), .35);
  border-radius: var(--r-md);
  pointer-events: none;
}

/* A plate hanging off the picture's corner. */
.split__plate {
  position: absolute;
  right: var(--s3);
  bottom: -18px;
  z-index: 2;
  display: grid;
  justify-items: center;
  gap: 6px;
  padding: 14px 20px;
  border: 1px solid rgba(var(--glow-rgb), .4);
  border-radius: var(--r-md);
  background: rgba(11, 6, 3, .9);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  text-align: center;
}
.split__plate svg { width: 22px; height: 22px; color: var(--gold); }
.split__plate span {
  font-size: 10.5px;
  font-weight: 500;
  letter-spacing: .18em;
  text-transform: uppercase;
  line-height: 1.5;
  color: var(--cream);
}

.split__title {
  margin-top: var(--s2);
  font-family: var(--font-display);
  font-weight: 500;
  font-size: clamp(1.65rem, 2.8vw, 2.3rem);
  line-height: 1.2;
  letter-spacing: -.015em;
  color: var(--cream);
}
.split__title .gold { color: var(--gold); }
.split__text {
  max-width: 48ch;
  margin-top: var(--s3);
  font-size: 14.5px;
  line-height: 1.85;
  color: var(--tan);
}
.split__cta { margin-top: var(--s4); }

/* --- Three chips in a row ------------------------------------- */

.chipline {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s4);
  margin-top: var(--s4);
  padding-top: var(--s4);
  border-top: 1px solid var(--line-soft);
}
.chipline li {
  display: flex;
  align-items: center;
  gap: 12px;
  font-size: 12.5px;
  line-height: 1.4;
  color: var(--cream);
}

/* The label is its own element so the icon has something with a
   definite box to centre against. Left as bare text it is an
   anonymous flex item, which carries the whitespace around it in
   the source and does not sit level with a 42px circle.

   Two lines every time, so the row reads as one band rather than
   three labels of different heights. */
.chip__t {
  display: block;
  text-wrap: balance;
}

.chipline .iconring { width: 42px; height: 42px; }
.chipline .iconring .ico { font-size: 18px; }

/* Divided rather than spaced, when they sit under a paragraph
   rather than under a heading.

   Padding goes on the inner edges only. With it on both sides of
   every item the first icon was indented from the paragraph
   above it while the rule spanning the row was not, so the
   column start did not line up with the copy. */
.chipline--ruled { gap: 0; }
.chipline--ruled li {
  flex: 1 1 0;
  min-width: 150px;
}
.chipline--ruled li + li {
  border-left: 1px solid var(--line-soft);
  padding-left: var(--s3);
  margin-left: var(--s3);
}
@media (max-width: 599px) {
  .chipline--ruled li,
  .chipline--ruled li + li {
    flex: 1 1 100%;
    border-left: 0;
    padding-left: 0;
    margin-left: 0;
  }
  .chipline { gap: var(--s3); }
}

/* --- Values ---------------------------------------------------- */

.values {
  display: grid;
  gap: var(--s3);
  grid-template-columns: repeat(auto-fit, minmax(215px, 1fr));
  margin-top: var(--s5);
}
/* The angle the travelling highlight is currently at.

   It has to be registered rather than a plain custom property:
   an unregistered one is just a token string, so an animation on
   it would jump 0deg -> 360deg in a single step instead of
   sweeping. Registering it gives it a type the engine can
   interpolate. Browsers that do not support @property ignore
   this block and keep the static border on .value below. */
@property --run {
  syntax: "<angle>";
  inherits: false;
  initial-value: 0deg;
}

.value {
  position: relative;
  isolation: isolate;
  padding: var(--s4) var(--s3);
  border: 1px solid var(--line-soft);
  border-radius: var(--r-md);
  background: linear-gradient(180deg, var(--surface-2) 0%, var(--surface) 100%);
  text-align: center;
  transition: border-color var(--dur-2) var(--ease),
              transform var(--dur-2) var(--ease),
              box-shadow var(--dur-2) var(--ease);
}

/* The lit edge. A short bright arc of a conic gradient is spun
   around the card, and a mask throws away everything except a
   thin frame at the boundary — the gradient paints a filled
   rectangle, and the mask is what turns it into an outline. Two
   boxes, the inner excluded from the outer, leaves only the band
   between them.

   inset:-1px so it rides over .value's own border rather than
   sitting inside it, and z-index:-1 so it paints above the card
   background but still behind the icon and the words. */
.value::before,
.value__glow::before {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  padding: var(--run-w, 1px);
  background: conic-gradient(from var(--run),
    rgba(235, 206, 140, 0)  0deg,
    var(--gold-soft)       22deg,
    var(--gold)            42deg,
    var(--ember)           64deg,
    rgba(196, 98, 42, 0)   92deg,
    rgba(196, 98, 42, 0)  360deg);
  -webkit-mask: linear-gradient(#000 0 0) content-box,
                linear-gradient(#000 0 0);
          mask: linear-gradient(#000 0 0) content-box,
                linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
          mask-composite: exclude;
  animation: value-run 5.5s linear infinite;
  animation-delay: var(--run-delay, 0s);
  pointer-events: none;
}

.value::before {
  inset: -1px;
  z-index: -1;
}

/* The bloom under the line — what makes the edge read as lit
   rather than merely coloured.

   It is a wrapper element and not simply a blurred copy of the
   pseudo-element above, because an element's filter is applied
   BEFORE its mask. Blurring the masked ::before directly would
   soften the gradient and then cut a hard-edged frame out of the
   result, leaving no glow at all. Blurring the parent instead
   blurs the already-masked child, which is the way round that
   actually spreads light past the edge. */
.value__glow {
  position: absolute;
  inset: -1px;
  z-index: -1;
  border-radius: inherit;
  filter: blur(6px);
  opacity: .8;
  pointer-events: none;
}
.value__glow::before { --run-w: 2px; }

@keyframes value-run {
  to { --run: 360deg; }
}

/* A lit corner that simply sits still. */
@media (prefers-reduced-motion: reduce) {
  .value::before,
  .value__glow::before { animation: none; }
}

.value:hover {
  border-color: rgba(var(--glow-rgb), .45);
  transform: translateY(-4px);
  box-shadow: 0 18px 38px -22px rgba(0, 0, 0, .9),
              0 0 26px rgba(var(--glow-rgb), .14);
}

/* Filled rather than outlined here, gold into ember. The icon
   itself goes to --ink: a gold line on a gold disc would have
   almost no contrast left. */
.value .iconring {
  margin: 0 auto;
  width: 54px;
  height: 54px;
  border-color: transparent;
  background: linear-gradient(140deg,
    var(--gold-soft)  0%,
    var(--gold)      34%,
    var(--gold-warm) 64%,
    var(--ember)    100%);
  color: var(--ink);
  box-shadow: 0 8px 20px -10px rgba(var(--glow-rgb), .85),
              0 0 22px rgba(var(--glow-rgb), .20);
}
.value .iconring .ico { font-size: 23px; }
.value h3 {
  margin-top: var(--s2);
  font-family: var(--font-display);
  font-weight: 500;
  font-size: 1.12rem;
  line-height: 1.3;
  color: var(--cream);
}
.value p {
  margin-top: 8px;
  font-size: 12.5px;
  line-height: 1.7;
  color: var(--tan);
}

/* --- Stats band ------------------------------------------------ */

.statband {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  border-block: 1px solid var(--line-soft);
}
.statband > div {
  padding: var(--s4) var(--s2);
  text-align: center;
}
.statband > div + div { border-left: 1px solid var(--line-soft); }
.statband b {
  display: block;
  font-family: var(--font-display);
  font-weight: 500;
  font-size: clamp(1.6rem, 3vw, 2.3rem);
  line-height: 1;
  color: var(--cream);
}
.statband span {
  display: block;
  margin-top: 8px;
  font-size: 10.5px;
  font-weight: 500;
  letter-spacing: .2em;
  text-transform: uppercase;
  color: var(--gold-dim);
}
@media (max-width: 767px) {
  .statband { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  /* A left border on every item leaves one hanging at the start
     of the second row, so the rule becomes a grid instead. */
  .statband > div + div { border-left: 0; }
  .statband > div:nth-child(even) { border-left: 1px solid var(--line-soft); }
  .statband > div:nth-child(n + 3) { border-top: 1px solid var(--line-soft); }
}

/* ── 25. Treatment detail page ────────────────────────────────
   pages/service.php. One treatment, in full.

   It borrows rather than redeclares wherever it can: .split__media
   for the photograph, .values for "good to know", .cardgrid and
   .card for what to consider next, .endcall to close. Only what is
   genuinely new to this page is below.                           */

/* --- Breadcrumbs -----------------------------------------------
   This page is two levels down and is the one most likely to be
   arrived at cold from a search result, so it has to say where it
   sits. The last crumb is plain text: a link to the page you are
   already on is a dead control that looks live. */
.crumbs {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  font-size: 11.5px;
  letter-spacing: .04em;
  color: var(--tan-dim);
}
.crumbs a {
  color: var(--tan);
  transition: color var(--dur-1) var(--ease);
}
.crumbs a:hover { color: var(--gold); }
.crumbs .ico { font-size: 14px; opacity: .5; }
.crumbs [aria-current] { color: var(--gold); }

/* --- The treatment itself -------------------------------------- */

/* A lit floor under the opening band, so the treatment's own
   photograph is not the only warm thing on the first screen. */
.svc {
  background:
    radial-gradient(64% 52% at 78% 34%, rgba(var(--glow-rgb), .10) 0%, rgba(var(--glow-rgb), 0) 68%),
    var(--ink-2);
}

.svc__top {
  display: grid;
  gap: var(--s5);
  align-items: center;
  margin-top: var(--s4);
}
@media (min-width: 900px) {
  .svc__top {
    /* Copy wider than the picture. This page is read before it is
       looked at — the decision is being made on price, length and
       pressure, and the photograph is reassurance. */
    grid-template-columns: minmax(0, 1.15fr) minmax(0, .85fr);
    gap: var(--s6);
  }
}

.svc__title {
  margin-top: var(--s2);
  font-family: var(--font-display);
  font-weight: 500;
  font-size: clamp(1.9rem, 4vw, 3rem);
  line-height: 1.12;
  letter-spacing: -.02em;
  color: var(--cream);
}
.svc__lead {
  max-width: 52ch;
  margin-top: var(--s3);
  font-size: 15px;
  line-height: 1.85;
  color: var(--tan);
}

/* The four facts the decision is actually made on. A <dl>, because
   that is what this is: four terms and their values.

   It spans the whole shell, under both columns of the hero rather
   than inside the copy one. Four cells sharing a 700px column gave
   each about fifteen characters a line, so "Light — the heat does
   the work" ran to three lines while the price sat on one and the
   strip came out ragged. The minmax floor is 190px for the same
   reason: below that these values wrap however wide the page is. */
.svc__facts {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
  gap: 1px;
  margin-top: var(--s5);
  /* The gap IS the rule. One background behind a 1px grid gap
     draws every divider at once and never leaves one hanging off
     the end of a wrapped row, which a border-left would. */
  background: var(--line-soft);
  border: 1px solid var(--line-soft);
  border-radius: var(--r-md);
  overflow: hidden;
}
.svc__facts > div {
  padding: 18px 20px 19px;
  background:
    radial-gradient(120% 100% at 50% 0%, rgba(var(--glow-rgb), .07) 0%, rgba(var(--glow-rgb), 0) 70%),
    var(--ink-2);
}
.svc__facts dt {
  display: flex;
  align-items: center;
  gap: 7px;
  font-size: 9.5px;
  font-weight: 500;
  letter-spacing: .2em;
  text-transform: uppercase;
  color: var(--gold-dim);
}
.svc__facts dt .ico {
  font-size: 14px;
  /* The icons carry no information the label does not — they are
     there to make the strip scannable, so they sit back from the
     word rather than competing with it. */
  color: var(--gold);
  opacity: .75;
}
.svc__facts dd {
  margin: 8px 0 0;
  font-size: 13.5px;
  font-weight: 500;
  line-height: 1.45;
  color: var(--cream);
}
.svc__facts .svc__price {
  font-family: var(--font-body);
  font-size: 1.42rem;
  font-weight: 600;
  line-height: 1.1;
  background: linear-gradient(120deg, var(--gold-soft) 0%, var(--gold) 45%, var(--ember) 100%);
  -webkit-background-clip: text;
          background-clip: text;
  color: transparent;
}

.svc__cta {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s2);
  margin-top: var(--s4);
}

/* Stands in for a photograph that does not exist yet. A lit panel
   carrying the treatment's own icon reads as deliberate; a broken
   image or an empty grey box reads as unfinished. */
.svc__noimg {
  display: grid;
  place-items: center;
  width: 100%;
  height: 100%;
  background:
    radial-gradient(70% 70% at 50% 40%, rgba(var(--glow-rgb), .16) 0%, rgba(var(--glow-rgb), 0) 70%),
    linear-gradient(180deg, var(--surface-2) 0%, var(--surface) 100%);
  color: var(--gold);
}
.svc__noimg .ico { font-size: 54px; }

/* --- Book this one if… -----------------------------------------
   Written as situations rather than as benefits, so the list is
   read as a test — "is that me?" — instead of as a sales pitch. */
.goodfor {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(270px, 1fr));
  gap: var(--s3);
  margin-top: var(--s5);
}
.goodfor li {
  position: relative;
  display: flex;
  align-items: flex-start;
  gap: 14px;
  padding: var(--s3) var(--s3) var(--s3) calc(var(--s3) + 3px);
  border: 1px solid var(--line-soft);
  border-radius: var(--r-md);
  background: linear-gradient(180deg, var(--surface-2) 0%, var(--surface) 100%);
  font-size: 14px;
  line-height: 1.6;
  color: var(--cream);
  /* opacity and transform have to be named here at the reveal's
     own timings. These items carry data-reveal, and that rule
     declares its own transition — but this one is later at equal
     specificity, so it replaces it outright rather than adding to
     it. Leave them out and the list appears instantly. */
  transition: border-color var(--dur-2) var(--ease),
              opacity .95s var(--ease) var(--d, 0s),
              transform 1.15s var(--ease) var(--d, 0s);
}
.goodfor li:hover {
  border-color: rgba(var(--glow-rgb), .4);
  transform: translateY(-3px);
  box-shadow: 0 16px 32px -22px rgba(0, 0, 0, .9);
}

/* A lit edge down the left of each tile. inset:-1px so it covers
   the border rather than sitting inside it, and only the left
   corners are rounded so it follows the tile instead of ringing
   it. */
.goodfor li::before {
  content: "";
  position: absolute;
  left: -1px;
  top: -1px;
  bottom: -1px;
  width: 3px;
  border-radius: var(--r-md) 0 0 var(--r-md);
  background: linear-gradient(180deg,
    var(--gold-soft) 0%, var(--gold) 45%, var(--ember) 100%);
  opacity: .55;
  transition: opacity var(--dur-2) var(--ease);
}
.goodfor li:hover::before { opacity: 1; }

.goodfor__tick {
  flex: none;
  display: grid;
  place-items: center;
  width: 26px;
  height: 26px;
  margin-top: 1px;
  border-radius: var(--r-pill);
  background: linear-gradient(140deg, var(--gold-soft) 0%, var(--gold) 45%, var(--ember) 100%);
  color: var(--ink);
  box-shadow: 0 0 16px rgba(var(--glow-rgb), .3);
}
.goodfor__tick .ico { font-size: 16px; }

/* --- How the session goes --------------------------------------
   An <ol>, because these happen in this order — the numbering is
   information, not decoration, so it is in the markup. */
.flow {
  display: grid;
  gap: var(--s4);
  max-width: 760px;
  margin: var(--s5) auto 0;
}
.flow__step {
  position: relative;
  display: flex;
  align-items: flex-start;
  gap: var(--s3);
  padding-bottom: var(--s4);
}
/* The rail joining one step to the next, stopping at the last —
   a line trailing off the final step points at nothing. */
.flow__step:not(:last-child)::before {
  content: "";
  position: absolute;
  left: 21px;
  top: 46px;
  bottom: 0;
  width: 1px;
  background: linear-gradient(180deg,
    rgba(var(--glow-rgb), .45) 0%,
    rgba(var(--glow-rgb), .08) 100%);
}
.flow__step:last-child { padding-bottom: 0; }

.flow__n {
  flex: none;
  display: grid;
  place-items: center;
  width: 43px;
  height: 43px;
  border-radius: var(--r-pill);
  border: 1px solid rgba(var(--glow-rgb), .4);
  /* Opaque, so the rail behind passes beneath the circle rather
     than showing through the middle of the number. */
  background:
    radial-gradient(80% 80% at 50% 25%, rgba(var(--glow-rgb), .18) 0%, rgba(var(--glow-rgb), 0) 72%),
    var(--ink-2);
  box-shadow: 0 0 20px -4px rgba(var(--glow-rgb), .35);
  font-family: var(--font-display);
  font-size: 1.02rem;
  color: var(--gold);
}
.flow__step h3 {
  margin-top: 9px;
  font-family: var(--font-display);
  font-weight: 500;
  font-size: 1.1rem;
  color: var(--cream);
}
.flow__step p {
  margin-top: 8px;
  font-size: 14px;
  line-height: 1.8;
  color: var(--tan);
}

/* --- Things worth knowing --------------------------------------
   Deliberately NOT the .values cards the About page uses. Section
   2 of this page is already a row of four bordered tiles, and a
   second one here made the page read as the same layout twice —
   which is the thing that makes a long page feel like a template.
   No boxes and no borders: an icon, a line, a paragraph, in two
   columns. */
.knows {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(330px, 1fr));
  gap: var(--s4) var(--s6);
  max-width: 940px;
  margin: var(--s5) auto 0;
}
.know {
  display: flex;
  align-items: flex-start;
  gap: 16px;
}
.know__ico {
  flex: none;
  display: grid;
  place-items: center;
  width: 42px;
  height: 42px;
  border-radius: var(--r-pill);
  border: 1px solid rgba(var(--glow-rgb), .32);
  background:
    radial-gradient(80% 80% at 50% 25%, rgba(var(--glow-rgb), .16) 0%, rgba(var(--glow-rgb), 0) 72%),
    var(--ink-2);
  color: var(--gold);
}
.know__ico .ico { font-size: 19px; }
.know h3 {
  margin-top: 8px;
  font-family: var(--font-display);
  font-weight: 500;
  font-size: 1.04rem;
  line-height: 1.35;
  color: var(--cream);
}
.know p {
  margin-top: 7px;
  font-size: 13.5px;
  line-height: 1.8;
  color: var(--tan);
}

.svc__all {
  margin-top: var(--s4);
  text-align: center;
}
.svc__all a {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--tan);
  transition: color var(--dur-1) var(--ease), gap var(--dur-1) var(--ease);
}
.svc__all a:hover { color: var(--gold); gap: 12px; }

/* --- The bar that follows you ----------------------------------
   Narrow screens only. This page runs to six bands, and on a
   phone the Book button leaves the screen a few swipes in and
   does not come back for several thousand pixels.

   Above 900px it is display:none outright rather than merely
   translated away — a fixed bar on a desktop page whose CTA is
   already in view is clutter, and translating it off screen
   leaves it in the tab order where nobody can see it.

   main.js toggles [hidden] and .is-up; with no JavaScript the
   bar simply never appears, which is why [hidden] is the thing
   being toggled rather than a class.                            */
.svcbar {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 60;
  padding: 10px 0 calc(10px + env(safe-area-inset-bottom, 0px));
  border-top: 1px solid rgba(var(--glow-rgb), .3);
  background: rgba(11, 6, 3, .93);
  backdrop-filter: blur(14px);
  box-shadow: 0 -14px 34px -18px rgba(0, 0, 0, .95);
  transform: translateY(100%);
  transition: transform .42s var(--ease);
}
.svcbar.is-up { transform: none; }
.svcbar[hidden] { display: none; }

/* .shell in the markup carries the width and the gutter, so this
   only has to lay the two halves out. Restating them here would
   be a second copy of the page's measure to keep in step. */
.svcbar__in {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s3);
}

.svcbar__meta {
  display: grid;
  gap: 2px;
  min-width: 0;
}
.svcbar__kind {
  font-size: 9.5px;
  font-weight: 600;
  letter-spacing: .2em;
  text-transform: uppercase;
  color: var(--gold-dim);
}
/* One line, cut with an ellipsis. The longest name on the menu is
   "Scrub with Oil & Cream Full Body Massage" — these names are
   long, and wrapping one to two lines would push the bar taller
   than the button it exists to show. */
.svcbar__name {
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
  font-family: var(--font-display);
  font-size: .95rem;
  color: var(--cream);
}

/* Holds the floor open so the bar cannot sit over the last line
   of the footer. It is a real element because the bar is fixed
   and nothing else on the page knows how tall it is. */
.svcbar__spacer { height: 0; }

@media (max-width: 899px) {
  .svcbar__spacer { height: 78px; }
}
@media (min-width: 900px) {
  .svcbar { display: none; }
}

/* --- The card's whole face is the link -------------------------
   The anchor is around the treatment's name, so it reads
   correctly out of context and puts that name in the tab order
   rather than the word "details" four times over. ::after then
   stretches it across the card, which is what makes the picture
   and the price clickable too.

   .card needs position:relative for that, and .card__link stops
   being an anchor in the markup — a second link to the same
   place is a second tab stop that announces nothing new. */
.card { position: relative; }
.card__name a {
  color: inherit;
  transition: color var(--dur-1) var(--ease);
}
.card__name a::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;
  border-radius: inherit;
}
.card:hover .card__name a,
.card__name a:focus-visible { color: var(--gold); }

/* The focus ring has to be drawn on the card, not on the few words
   inside it: the stretched ::after covers the whole card, so a ring
   around the text alone would sit nowhere near what is focused. */
.card:has(.card__name a:focus-visible) {
  outline: 2px solid var(--gold);
  outline-offset: 3px;
}


/* ── 26. Contact page ─────────────────────────────────────────
   pages/contact.php.                                            */

/* The line under a section heading, where one is needed. Most
   sections here carry no sub-heading; this page is the exception,
   because "contact us" alone does not say when we answer. */
.sect__lede {
  max-width: 54ch;
  margin: var(--s3) auto 0;
  font-size: 14.5px;
  line-height: 1.85;
  color: var(--tan);
}

/* --- The four ways to reach us ---------------------------------
   Ordered by how fast each one is answered, not by how much we
   would like it used: putting the form first tells someone in a
   hurry to wait. Each card is one link, so the whole tile is the
   target rather than the few words at the bottom of it. */
.ways {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
  gap: var(--s3);
  margin-top: var(--s5);
}
.way {
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: var(--s4) var(--s3);
  border: 1px solid var(--line-soft);
  border-radius: var(--r-md);
  background: linear-gradient(180deg, var(--surface-2) 0%, var(--surface) 100%);
  /* Same trade the .card rule makes: transform runs at the short
     hover duration, so opacity is what carries the reveal, at the
     reveal's own timing and delay. */
  transition: border-color var(--dur-2) var(--ease),
              transform var(--dur-2) var(--ease),
              box-shadow var(--dur-2) var(--ease),
              opacity .95s var(--ease) var(--d, 0s);
}
.way:hover {
  border-color: rgba(var(--glow-rgb), .45);
  transform: translateY(-4px);
  box-shadow: 0 18px 38px -22px rgba(0, 0, 0, .9),
              0 0 26px rgba(var(--glow-rgb), .14);
}
.way .iconring {
  width: 46px;
  height: 46px;
  border-color: transparent;
  background: linear-gradient(140deg,
    var(--gold-soft) 0%, var(--gold) 34%, var(--gold-warm) 64%, var(--ember) 100%);
  color: var(--ink);
  box-shadow: 0 8px 20px -10px rgba(var(--glow-rgb), .85);
}
.way__h {
  font-family: var(--font-display);
  font-weight: 500;
  font-size: 1.08rem;
  color: var(--cream);
}
.way__p {
  font-size: 12.5px;
  line-height: 1.7;
  color: var(--tan-dim);
}
/* margin-top:auto so the value sits on the card's floor however
   long the line above it runs — four tiles of different heights
   with the value floating mid-air is what makes a row look
   unaligned. */
.way__val {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  margin-top: auto;
  padding-top: 10px;
  font-size: 12.5px;
  font-weight: 600;
  word-break: break-word;
  color: var(--gold);
  transition: gap var(--dur-1) var(--ease);
}
.way__val .ico { font-size: 15px; }
.way:hover .way__val { gap: 11px; }

/* --- Message form ---------------------------------------------- */

.contact__grid {
  display: grid;
  gap: var(--s5);
  align-items: start;
}
@media (min-width: 960px) {
  .contact__grid {
    grid-template-columns: minmax(0, .85fr) minmax(0, 1.15fr);
    gap: var(--s6);
  }
}

.contact__marks {
  display: grid;
  gap: var(--s3);
  margin-top: var(--s4);
  padding-top: var(--s4);
  border-top: 1px solid var(--line-soft);
}
.contact__marks li {
  display: flex;
  align-items: center;
  gap: 14px;
}

/* A textarea is not covered by the .field input/select rules — it
   is a different element, and it also needs its own resize and
   line-height, which neither of those has. */
.field textarea {
  width: 100%;
  min-width: 0;
  padding: 12px 14px;
  border: 1px solid var(--line-soft);
  border-radius: var(--r-sm);
  background: rgba(11, 6, 3, .55);
  color: var(--cream);
  font: inherit;
  font-size: 14px;
  font-weight: 500;
  line-height: 1.7;
  /* Vertical only. A textarea that can be dragged wider breaks
     out of its grid column and over the panel's edge. */
  resize: vertical;
  transition: border-color var(--dur-1) var(--ease),
              box-shadow var(--dur-1) var(--ease);
}
.field textarea::placeholder { color: var(--tan-dim); }
.field textarea:focus {
  outline: 0;
  border-color: var(--gold);
  box-shadow: 0 0 0 3px rgba(var(--glow-rgb), .15);
}

/* Says a field may be left blank, without it reading as part of
   the label — lowercase and unspaced against the tracked caps
   around it, which is what makes it look like an aside. */
.field__opt {
  margin-left: 6px;
  font-size: 9.5px;
  font-weight: 400;
  letter-spacing: 0;
  text-transform: none;
  color: var(--tan-dim);
}

/* --- Map and hours --------------------------------------------- */

.findus {
  display: grid;
  gap: var(--s4);
  margin-top: var(--s5);
}
@media (min-width: 900px) {
  .findus { grid-template-columns: minmax(0, 1.25fr) minmax(0, .75fr); }
}

.findus__map {
  position: relative;
  overflow: hidden;
  min-height: 340px;
  border: 1px solid var(--line-soft);
  border-radius: var(--r-md);
  background: var(--surface);
}
.findus__map iframe {
  display: block;
  width: 100%;
  height: 100%;
  min-height: 340px;
  border: 0;
  /* Google's embed is a white map on a dark page. Inverting the
     lightness and spinning the hue back puts it in the palette
     without an API key or a styled-map ID — the labels stay
     readable and the water stays blue. */
  filter: invert(92%) hue-rotate(180deg) saturate(.7) brightness(.95) contrast(.95);
}

.findus__side {
  padding: var(--s4) var(--s3);
  border: 1px solid var(--line-soft);
  border-radius: var(--r-md);
  background: linear-gradient(180deg, var(--surface-2) 0%, var(--surface) 100%);
}
.findus__h {
  font-family: var(--font-display);
  font-weight: 500;
  font-size: 1.12rem;
  color: var(--cream);
}

.hours {
  margin-top: var(--s3);
  border-top: 1px solid var(--line-soft);
}
.hours__row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s2);
  padding: 10px 0;
  border-bottom: 1px solid var(--line-soft);
  font-size: 13px;
  color: var(--tan);
}
.hours__row.is-today { color: var(--cream); font-weight: 500; }
.hours__tag {
  display: inline-block;
  margin-left: 8px;
  padding: 2px 8px;
  border-radius: var(--r-pill);
  background: rgba(var(--glow-rgb), .16);
  font-size: 9px;
  font-weight: 600;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--gold);
}

.findus__addr {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  margin-top: var(--s3);
  font-size: 13px;
  line-height: 1.7;
  color: var(--tan);
}
.findus__addr .ico { color: var(--gold); font-size: 17px; }

.findus__act {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s2);
  margin-top: var(--s3);
}

/* --- Questions -------------------------------------------------
   <details>, so it works with scripting off and the browser
   handles the keyboard, the open state and the semantics free. */
.faq {
  display: grid;
  gap: 10px;
  max-width: 820px;
  margin: var(--s5) auto 0;
}
.faq__item {
  border: 1px solid var(--line-soft);
  border-radius: var(--r-md);
  background: linear-gradient(180deg, var(--surface-2) 0%, var(--surface) 100%);
  transition: border-color var(--dur-2) var(--ease),
              opacity .95s var(--ease) var(--d, 0s),
              transform 1.15s var(--ease) var(--d, 0s);
}
.faq__item[open] { border-color: rgba(var(--glow-rgb), .35); }

.faq__item summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s3);
  padding: 16px var(--s3);
  cursor: pointer;
  font-family: var(--font-display);
  font-weight: 500;
  font-size: 1rem;
  line-height: 1.45;
  color: var(--cream);
  /* Removes the disclosure triangle. Both are needed: the second
     is what Safari uses, and it ignores the first. */
  list-style: none;
}
.faq__item summary::-webkit-details-marker { display: none; }
.faq__item summary:hover { color: var(--gold); }
.faq__item summary:focus-visible {
  outline: 2px solid var(--gold);
  outline-offset: -2px;
  border-radius: var(--r-md);
}

.faq__sign {
  flex: none;
  font-size: 19px;
  color: var(--gold);
  transition: transform var(--dur-2) var(--ease);
}
/* The plus becomes a minus by turning, so open and shut are the
   same glyph rotated rather than two icons swapped. */
.faq__item[open] .faq__sign { transform: rotate(135deg); }

.faq__a {
  padding: 0 var(--s3) 16px;
}
.faq__a p {
  max-width: 68ch;
  padding-top: 14px;
  border-top: 1px solid var(--line-soft);
  font-size: 14px;
  line-height: 1.85;
  color: var(--tan);
}

@media (max-width: 599px) {
  .svc__facts > div { padding: 12px 14px; }
  .flow__step { gap: var(--s2); }
  .flow__step:not(:last-child)::before { left: 18px; top: 40px; }
  .flow__n { width: 37px; height: 37px; font-size: .92rem; }
  .findus__map, .findus__map iframe { min-height: 260px; }
  .faq__item summary { font-size: .94rem; }
}

/* The inversion is a palette choice, not an effect — so it comes
   off for anyone asking for more contrast, where a hue-rotated
   map is harder to read than the plain one, and NOT for reduced
   motion, which is about movement and has nothing to say here. */
@media (prefers-contrast: more) {
  .findus__map iframe { filter: none; }
}