/* ============================================================
   Velvet Massage - dark editorial system, oxblood and gold.
   Heavily inspired by House of Honey (Awwwards SOTD 14 Jul 2026):
   two-colour discipline, oversized editorial serif headings,
   serialised meta labels, line-buttons that fill on hover,
   slow staggered reveals, generous vertical rhythm.
   Their #331917 on #EDCCBE becomes our #0E0002 with #C9A45C.
   Display: Fraunces. Body/utility: Instrument Sans.
   ============================================================ */

:root {
  /* ---- Velvet brand tokens (the contract; use these by name) ---- */
  /* The dark ramp is SAMPLED FROM THE LOGO ARTWORK, not chosen by eye. Decoding
     originals/velvet/logo-new-4k.png and averaging patches gives its velvet
     ground as #0E0002, its petal crimson #2F0408..#240308 and its floor
     #030100. The canvas below is that ground exactly; the two surfaces above it
     are lifted along the same hue rather than toward the old pinker wine, so a
     panel reads as the same cloth in a little more light.

     Previously #2A0912 / #3D0F1C / #4A1423, which sat noticeably lighter and
     pinker than the brand artwork it was meant to match. */
  /* THESE FIVE ARE MEASURED OFF THE ARTWORK, originals/velvet/logo-new-4k.png,
     not judged by eye. Sampling 585,480 of its pixels gave:

       #180004   the deepest shadow            74.0% of the frame
       #38080E   the VELVET the mark sits on   19.5%
       #6F1E25   mid petal                      4.0%
       #A34648   lit petal                      1.3%
       #C3926D   the gold rim                   0.6%

     The canvas had been taken to #0E0002, darker than anything in the artwork,
     and the panels to #1B0206, darker still than the velvet the whole identity
     is built on. So the site read as black where the brand reads as deep red,
     and the deep red lotus had nothing to sit against: measured, it came out at
     1.37:1 and vanished, which is why it was briefly recoloured gold. Putting
     the canvas on the artwork's own shadow and the panels on its own velvet is
     what lets the mark stay the colour the owner chose. */
  --oxblood:    #140003;   /* primary brand dark, the canvas: the logo's ground */
  /* The same colour as --oxblood, as an rgb triple, for the alpha scrims painted
     over photographs. Those used to be rgba(42, 9, 18, a) literals of the OLD
     oxblood: 17 of them, covering the hero, every image overlay and the mobile
     menu. So darkening --oxblood changed the flat panels and left every
     photograph at the previous colour, and the new theme looked like the old one.
     Keep this in step with --oxblood; test 8.31 checks the hex, and this is the
     same colour written the only other way CSS will accept for alpha. */
  --canvas-rgb: 20 0 3;
  --wine:       #38080E;   /* panel / card surface */
  --wine-lift:  #4A1018;   /* raised surface */
  --gold:       #C9A45C;   /* THE accent */
  --gold-bright:#E3C88A;   /* light gold for text on oxblood */
  --gold-deep:  #8E6E33;
  --ivory:      #F2E9DC;   /* primary text on dark */
  --muted:      #BCA697;   /* secondary text on dark */
  /* The map pin, and the ONE colour on this site that is deliberately not from
     the artwork. Everything else here is deep red on deep red, which is the
     point of the brand and exactly why a location marker drawn in it would
     disappear: the lit petal #A34648 measures 2.92:1 on the velvet, under the
     3:1 a graphic needs to be distinguishable at all. #E2483F measures 5.07:1
     on the canvas, 4.33:1 on the velvet and 3.80:1 on the raised panel, so the
     pin is found at a glance on every surface it can land on. */
  --pin:        #E2483F;

  /* ---- the older token names, mapped onto the Velvet scheme so
          every existing rule keeps its colour role ---- */
  --black:      var(--oxblood);   /* canvas */
  --black-2:    #240509;          /* raised band: oxblood lifted toward wine */
  --card:       var(--wine);      /* card surface */
  --card-2:     var(--wine-lift);
  --gold-soft:  var(--gold-bright);
  --hairline:   rgba(201, 164, 92, 0.16);
  --hairline-2: rgba(201, 164, 92, 0.30);

  /* legacy aliases kept so nothing breaks */
  --ink: var(--ivory);
  --bone: var(--black);
  --surface: var(--card);
  --surface-2: var(--black-2);
  --taupe: var(--muted);

  --font-display: 'Fraunces', Georgia, 'Times New Roman', serif;
  --font-body:    'Instrument Sans', system-ui, -apple-system, sans-serif;

  --wrap: 1240px;
  --gutter: clamp(20px, 5vw, 64px);
  --radius: 2px;

  --ease: cubic-bezier(0.16, 1, 0.3, 1);       /* long luxurious out-ease */
  --ease-io: cubic-bezier(0.65, 0, 0.35, 1);
  /* error states for the contact form, tokenised so the palette stays in one place */
  --danger-line: #C97C63;
  --danger-text: #E8927C;
  --danger-soft: #F0B7A6;
}

* { box-sizing: border-box; }

html { scroll-behavior: smooth; }

body {
  margin: 0;
  font-family: var(--font-body);
  color: var(--ivory);
  background: var(--black);
  font-size: 16.5px;
  line-height: 1.65;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

img { display: block; max-width: 100%; }
::selection { background: var(--gold); color: var(--black); }

/* ---- shared layout ---- */

.wrap { max-width: var(--wrap); margin: 0 auto; padding: 0 var(--gutter); }

.section { padding: clamp(90px, 13vw, 170px) 0; }
.band { padding: clamp(90px, 12vw, 150px) 0; }

/* serialised eyebrow, House of Honey "N.003" style */
.eyebrow {
  font-size: 11.5px;
  font-weight: 600;
  letter-spacing: 0.3em;
  text-transform: uppercase;
  color: var(--gold);
  margin: 0 0 26px;
  display: flex;
  align-items: center;
  gap: 14px;
}
.eyebrow::before {
  content: "";
  width: 34px;
  height: 1px;
  background: var(--gold);
  display: inline-block;
}

.section-head { max-width: 760px; margin-bottom: clamp(44px, 6vw, 80px); }
.section-head h2 {
  font-family: var(--font-display);
  font-weight: 320;
  font-size: clamp(38px, 5.8vw, 68px);
  line-height: 1.02;
  letter-spacing: -0.015em;
  margin: 0;
  color: var(--ivory);
}
.section-head p {
  margin: 20px 0 0;
  color: var(--muted);
  font-size: 17.5px;
  max-width: 52ch;
}
.section-head .eyebrow { color: var(--gold); font-size: 11.5px; margin: 0 0 26px; }

/* ---- motion: slow, whisper-quiet reveals ---- */
.reveal {
  opacity: 0;
  transform: translateY(26px);
  transition: opacity 0.9s var(--ease), transform 0.9s var(--ease);
  will-change: opacity, transform;
}
.reveal.in { opacity: 1; transform: none; will-change: auto; }
.reveal.d1 { transition-delay: 0.08s; }
.reveal.d2 { transition-delay: 0.16s; }
.reveal.d3 { transition-delay: 0.24s; }

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  .reveal, .reveal.d1, .reveal.d2, .reveal.d3 { opacity: 1; transform: none; transition: none; }
}

/* ============================================================
   Navigation: minimal bar, one persistent CTA
   ============================================================ */

.site-nav {
  position: sticky;
  top: 0;
  z-index: 100;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 18px var(--gutter);
  /* Rumpled oxblood silk sits behind the bar, heavily scrimmed so it reads as
     texture rather than a photograph and never competes with the nav links.
     The scrim keeps contrast well clear of the old flat fill. */
  background:
    linear-gradient(rgb(var(--canvas-rgb) / 0.58), rgb(var(--canvas-rgb) / 0.72)),
    url('/uploads/velvet/sheets.webp') center 38% / cover no-repeat;
  backdrop-filter: saturate(130%) blur(12px);
  border-bottom: 1px solid var(--hairline);
}
/* The bar's height is set by whatever is tallest inside it, and on the pages
   that carry the raster lotus that is the emblem. When the emblem went from
   54px to 68px the band would have gone 91px -> 105px, so the gutter above and
   below it comes in from 18px to 15px and the band lands at 99px instead: the
   logo reads a quarter bigger, the furniture moves 8px. Scoped with :has() so
   the pages still drawing the inline <svg> monogram (therapist, blog, post,
   location) keep their existing 18px gutter and their existing bar height. A
   browser without :has() simply keeps 18px and a slightly taller band. */
.site-nav:has(.brand-emblem img) { padding-block: 15px; }
/* a single hairline of gold light along the bottom edge, as if the silk catches it */
/* the silk is vivid now, so the lockup and links get a soft shadow to stay legible */
.site-nav .brand-word,
.site-nav .brand-tag,
.site-nav .nav-links a { text-shadow: 0 1px 6px rgba(22, 6, 11, 0.75); }

.site-nav::after {
  content: "";
  position: absolute;
  left: 0; right: 0; bottom: -1px;
  height: 1px;
  background: linear-gradient(90deg, transparent, rgba(201, 164, 92, 0.55), transparent);
  pointer-events: none;
}

/* Logo lockup: a fine gold seal monogram + an editorial wordmark whose
   second word sits in italic gold. */
.brand {
  display: inline-flex;
  align-items: center;
  gap: 13px;
  color: var(--ivory);
  text-decoration: none;
  line-height: 1;
}
.brand-emblem {
  color: var(--gold);
  display: inline-flex;
  flex-shrink: 0;
  transition: transform 0.6s var(--ease);
}
.brand-emblem svg { width: 32px; height: 32px; display: block; overflow: visible; }
/* the VM monogram resting on the gold lotus, on a transparent ground so it
   sits on the silk without a plate behind it.

   SIZES, 2026-08-02: the client asked for the logo "a little bigger", about a
   quarter up on the previous 54 / 62. The raster is 512x562, so even the 78px
   footer size only asks 234 device px of it on a 3x screen: nowhere near the
   point where it would go soft. This rule sizes the FOOTER lockup on
   index.html (the only non-nav place an <img> emblem appears); the blog,
   post and location footers still draw an inline <svg> and are sized by
   .site-footer .brand-emblem svg further down. */
.brand-emblem img {
  width: auto;
  height: 78px;
  display: block;
  filter: drop-shadow(0 2px 10px rgba(0, 0, 0, 0.6));
}
.site-nav .brand-emblem img { height: 92px; }
.brand:hover .brand-emblem { transform: rotate(-6deg); }
@media (max-width: 640px) { .site-nav .brand-emblem img { height: 66px; } }
.brand-word {
  font-family: var(--font-display);
  font-weight: 400;
  font-size: 21px;
  letter-spacing: 0.03em;
  color: var(--ivory);
}
.brand-word em { font-style: italic; color: var(--gold-soft); }

/* "soft touch": the tagline half of the lockup. In the nav it sits beside
   the wordmark behind a hairline, so the bar keeps its height; wrapped in
   .brand-lockup (footer, brochure) it stacks underneath instead. */
.brand-tag {
  font-family: var(--font-body);
  font-size: 9.5px;
  font-weight: 600;
  letter-spacing: 0.3em;
  text-transform: uppercase;
  color: var(--gold);
  white-space: nowrap;
}
.site-nav .brand-tag {
  padding-left: 13px;
  border-left: 1px solid var(--hairline-2);
}
@media (max-width: 640px) { .site-nav .brand-tag { display: none; } }

/* 861px to 1120px is the band where the horizontal nav is still on (the
   hamburger only takes over at 860px and below) but the lockup and the links
   do not both fit. Measured on the running site BEFORE this round: at 900px
   "Our team" and "Join us" were already wrapping onto two lines and the
   "soft touch" tagline was overlapping the first link by 26px. The bigger
   emblem deepens that overlap to 39px, so the band is corrected rather than
   left to get worse: the tagline stands down (as it already does at 640px and
   below) and the links close up. Measured after: every link back on one line
   at its natural width and no overlap, from 861px up.

   Scoped to the navs carrying the raster lockup, because those are the ones
   this round made wider. The therapist, blog, post and location navs draw the
   narrower inline <svg> monogram; they have a crowded band of their own around
   861-960px, but it is older than this change and untouched by it, so it is
   left exactly as it is and reported rather than quietly altered here. */
/* Now starts at 1001px, because 861-1000px is a drawer rather than a bar, and
   runs to 1279px rather than 1120px. MEASURED WHY: at 1180px, which is an iPad
   held landscape, the gap fell back to 34px and left the row 14px of slack with
   a 12px font, making 1180 the tightest width on the whole site. Holding the
   closed-up gap until the 1280px step keeps the tablet comfortable. */
@media (min-width: 1001px) and (max-width: 1279px) {
  .site-nav:has(.brand-emblem img) .brand-tag { display: none; }
  .site-nav:has(.brand-emblem img) .nav-links { gap: 16px; }
}

/* ROOM FOR THE SEVENTH LINK, ON ALL FIVE TEMPLATES.
   "Massages" added a link and a gap to a bar these rules already describe as
   tight. index.html carries its own measured copy of this in an inline <style>,
   but an inline style only reaches the page it is in, and the therapist, blog,
   post and city templates gained the same seventh link. Measured in Chrome after
   the link landed, they had NO clear space left at all: the therapist page had
   the lockup and the first link touching at both 1024px and 1280px, and its bar
   only fitted at 11px.

   So the closing-up lives here, where every template reads it, scoped to the
   navs carrying the raster lockup. index.html's inline copy agrees with these
   numbers and simply wins on a tie, which changes nothing. */
/* The 1101-1120px closing-up has gone with the band it served: everything below
   1280px is a drawer now, so there is no bar left down there to close up. */
/* THE BIGGER LINKS ARE PAID FOR BY THE WORDMARK, NOT BY THE GAPS.
   The links went 14px to 15px at the owner's request. Closing the gaps was
   tried first and it is not enough, and the reason is worth writing down: the
   gap ladder narrows the LINKS ROW, but what actually runs out is the air
   between the lockup and the first link, and the lockup is 478px of that. At
   15px with gaps at 12px the measured air was 3px at 1280 and 0px at 1321,
   which renders as "Velvet Tantric MassageOUR MASSEUSES". A slack figure taken
   from the links row against its allotted space read as healthy the whole time,
   which is why this was measured edge to edge instead.
   21px to 19px on the wordmark returns about 40px, which is more than the whole
   gap ladder had to give, and it is the half of the bar the eye is least likely
   to miss two pixels on. Below 1600 only: above it there is room for both. */
@media (min-width: 1280px) and (max-width: 1599px) {
  .site-nav:has(.brand-emblem img) .brand-word { font-size: 19px; }
}
@media (min-width: 1280px) and (max-width: 1320px) {
  .site-nav:has(.brand-emblem img) .brand-tag { display: none; }
  .site-nav:has(.brand-emblem img) .nav-links { gap: 12px; }
}
@media (min-width: 1321px) and (max-width: 1439px) {
  .site-nav:has(.brand-emblem img) .nav-links { gap: 14px; }
}

.brand-lockup { display: inline-flex; flex-direction: column; gap: 8px; min-width: 0; }

/* legacy dot, kept harmless */
.brand-mark { display: none; }

.nav-links { display: flex; align-items: center; gap: 34px; }
.nav-links a {
  color: var(--muted);
  text-decoration: none;
  /* 12px -> 13px. Measured in Chrome at every width the horizontal bar is used:
     with the drawer now taking everything up to 1000px (see the note on the
     hamburger breakpoint below), the narrowest bar is 1024px, where the row has
     81px of room to spare and 14px would still fit. 13px is taken rather than 14
     so the uppercase 0.16em tracking keeps its editorial look. */
  font-size: 14px;
  font-weight: 500;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  position: relative;
  padding: 4px 0;
  transition: color 0.35s var(--ease);
}
.nav-links a:not(.nav-cta)::after {
  content: "";
  position: absolute; left: 0; bottom: -3px;
  width: 100%; height: 1px; background: var(--gold);
  transform: scaleX(0); transform-origin: left;
  transition: transform 0.45s var(--ease);
}
.nav-links a:not(.nav-cta):hover { color: var(--ivory); }
.nav-links a:not(.nav-cta):hover::after { transform: scaleX(1); } /* LINK SIZE: 12px holds to 1279px (at 1200px the six-link row clears the lockup by 0.2px, so there is nothing to spend below 1280). Above it the size rises with the window: 13px at 1280, 15px at 1920. */ @media (min-width: 1280px) { .nav-links a { font-size: clamp(15px, calc(9px + 0.44vw), 19px); } }

.nav-cta {
  border: 1px solid var(--hairline-2);
  color: var(--gold-soft) !important;
  padding: 9px 22px !important;
  border-radius: 999px;
  transition: background 0.35s var(--ease), color 0.35s var(--ease), border-color 0.35s var(--ease);
}
.nav-cta:hover { background: var(--gold); color: var(--black) !important; border-color: var(--gold); }

.nav-toggle {
  display: none;
  flex-direction: column;
  /* 44x44, MEASURED AS 40 x 30.5 BEFORE THIS.
     Three 1.5px bars with 5px gaps is 14.5px of ink, and 8px of padding took it
     to 40 x 30.5: under the 44px floor on the one control that is the ONLY way to
     reach the navigation on a phone, and now the only way up to 1000px. The bars
     are unchanged, so it looks identical; the box around them is the fix. Centred
     rather than padded outward so the bar's height does not move. */
  align-items: center; justify-content: center;
  gap: 5px;
  width: 44px; height: 44px;
  background: none; border: none; cursor: pointer;
  padding: 0; margin: 0 -8px;
}
.nav-toggle span { width: 24px; height: 1.5px; background: var(--ivory); border-radius: 2px; transition: transform 0.3s var(--ease), opacity 0.2s; }
.nav-toggle[aria-expanded="true"] span:nth-child(1) { transform: translateY(6.5px) rotate(45deg); }
.nav-toggle[aria-expanded="true"] span:nth-child(2) { opacity: 0; }
.nav-toggle[aria-expanded="true"] span:nth-child(3) { transform: translateY(-6.5px) rotate(-45deg); }

/* THE HAMBURGER BREAKPOINT, 860px -> 1000px, and this is a fix rather than a
   preference. Measured in Chrome across the band the horizontal bar used to own:

     861px   the wordmark and the first link TOUCH, 0px between them
     880px   the row only fits at 11px, below the 12px it was actually set to
     940px   13px fits, with 53px to spare
     1024px  14px fits, with 81px to spare

   So 861-940px was a bar that did not fit, which is why the comment above this
   block describes a "crowded band around 861-960px" and reports it rather than
   correcting it. Handing that band to the drawer corrects it: the drawer is full
   width, so it has room for every link at 15px and a 57.8px tap target, and the
   bar now only appears from 1001px where it genuinely fits. An iPad held
   portrait at 820px was already a drawer and is unaffected. */
/* 1000px -> 1100px, for the third time and for the same measured reason. The
   mark grew from 145px to 164px in the bar and the type from 13px to 14px, and
   at 1024px that put the lockup and the first link back to touching, with only
   12px of type fitting. Standing the tagline down bought 1440px its room back
   (44px -> 161px) but bought 1024px nothing, because the tagline was already
   down there. So the band that does not fit moved up, and the breakpoint moves
   with it: 1024px is a drawer now, and the bar starts at 1101px where it fits.

   1100px -> 1279px, for the fourth time, and this one is a WORD not a size.
   "OUR TEAM" became "OUR MASSEUSES" when the practice changed what it calls its
   people, and the longer word costs the bar about 60px. Measured on the pages
   carrying the raster lockup, index.html and therapist.html, with the real
   inline gap rather than an invented one:

     1101px  slack 0     1150px  slack 0     1210px  slack 0
     1240px  slack 25    1270px  slack 52    1280px  slack 61

   Zero slack means the labels wrap and collide with the wordmark, so the whole
   band from 1101px to about 1230px was broken the moment the word changed. The
   bar now starts at 1280px, where it clears by 61px, rather than at 1240px
   where it clears by 25px and has no headroom left for the next word anybody
   changes. blog, post, location and services carry the narrower lockup and were
   never affected: they clear by 106px at 1280px. */
@media (max-width: 1279px) {
  .nav-toggle { display: flex; }
  .nav-links {
    /* Hangs off the bottom edge of the bar itself rather than off a hardcoded
       62px. The old number was already 29px short of the 91px bar, so opening
       the menu on a tablet drew the drawer panel over the bottom 10px of the
       logo; at 68px it would have covered 24px of it. calc(100% + 1px) is the
       bar's padding box plus its 1px bottom border, so the panel lands flush
       under the gold hairline on every page and at every width, whatever the
       emblem is doing. Absolute rather than fixed so the containing block is
       the sticky .site-nav by ordinary positioning rules, not by relying on
       backdrop-filter to establish one. */
    position: absolute; inset: calc(100% + 1px) 0 auto 0;
    flex-direction: column; align-items: flex-start; gap: 0;
    background: rgb(var(--canvas-rgb) / 0.98);
    backdrop-filter: blur(12px);
    padding: 8px var(--gutter) 26px;
    border-bottom: 1px solid var(--hairline);
    transform: translateY(-12px); opacity: 0; pointer-events: none;
    transition: transform 0.35s var(--ease), opacity 0.35s var(--ease);
  }
  .nav-links.open { transform: none; opacity: 1; pointer-events: auto; }
  /* 13px -> 15px. The rows are full width, so the limit is the longest word and
     not the row: LOCATIONS measures 107px in the 280px panel at 320px. The open
     panel goes 362px -> 382px tall and each row 54.4px -> 57.8px, which is also a
     better tap target.

     THE ROWS ALSO NEEDED SEPARATING. Measured, row-to-row separation was 0.10px:
     seven full-width targets stacked flush against each other with only a hairline
     between them, so a thumb landing between two rows could take either. The
     hairline stays as the visual divider and 3px of margin gives the miss some
     room to be a miss. */
  .nav-links a {
    width: 100%; padding: 16px 0; font-size: 16px;
    border-bottom: 1px solid var(--hairline);
    margin-bottom: 3px;
  }
  .nav-links a:last-child { margin-bottom: 0; }
  .nav-links a:not(.nav-cta)::after { display: none; }
  .nav-cta { border: none; padding: 16px 0 !important; border-radius: 0; }
  .nav-cta:hover { background: none; color: var(--gold-soft) !important; }
}

/* ============================================================
   Hero: atmosphere + paired editorial headline
   ============================================================ */

.hero {
  position: relative;
  min-height: min(92vh, 820px);
  display: flex;
  align-items: flex-end;
  /* The second half of this is clearance for the chat launcher, which now sits
     in the bottom LEFT corner: see the note above .hero__cta below. It only ever
     wins on a SHORT window, which is the only shape where the foot of this hero
     and the foot of the screen are the same place. 210px - 8vh is the arithmetic
     of that meeting: the hero ends at 123px of nav plus min(92vh, 820px), the
     launcher wants the last 74px, and the difference leaves about 13px of air at
     every height from 640 upwards. Above roughly 1250px tall it drops back below
     the clamp and this reads exactly as it always did. */
  padding: 0 0 max(clamp(56px, 9vw, 110px), calc(210px - 8vh));
  background: var(--black);
  overflow: hidden;
  isolation: isolate;
}
.hero__bg {
  position: absolute; inset: 0; z-index: -2;
  background-size: cover; background-position: center right;
  animation: heroDrift 22s var(--ease-io) both;
}
@keyframes heroDrift {
  from { transform: scale(1.08); }
  to   { transform: scale(1); }
}
.hero__scrim {
  position: absolute; inset: 0; z-index: -1;
  background:
    linear-gradient(90deg, rgb(var(--canvas-rgb) / 0.96) 0%, rgb(var(--canvas-rgb) / 0.72) 44%, rgb(var(--canvas-rgb) / 0.15) 100%),
    linear-gradient(0deg, rgb(var(--canvas-rgb) / 0.9) 0%, transparent 55%);
}
.hero .wrap { width: 100%; }
.hero__inner { max-width: 860px; }
.hero__eyebrow {
  color: var(--gold);
  font-size: 11.5px; font-weight: 600; letter-spacing: 0.3em; text-transform: uppercase;
  margin: 0 0 30px;
}
.hero h1 {
  font-family: var(--font-display);
  font-optical-sizing: auto;
  font-weight: 300;
  font-size: clamp(44px, 7.6vw, 96px);
  line-height: 0.99;
  letter-spacing: -0.02em;
  color: var(--ivory);
  margin: 0;
}
.hero h1 em { font-style: italic; color: var(--gold-soft); font-weight: 320; }
.hero p {
  color: var(--muted);
  font-size: clamp(16px, 1.9vw, 19px);
  max-width: 44ch;
  margin: 28px 0 40px;
}
.hero__cta { display: flex; gap: 16px; flex-wrap: wrap; align-items: center; }

/* ROOM FOR THE CHAT LAUNCHER, which now lives in the bottom LEFT corner.

   WHAT CHANGED UNDER IT. The launcher takes the bottom 66px of a phone screen
   (12px offset plus a 54px pill) and the hero's buttons are left-aligned, so the
   two now want the same corner. Nothing showed while the launcher was on the
   right, because the right-hand corner of this hero is empty at every width,
   which is exactly what the left-hand one is not. Measured on a 390x844 iPhone
   before this rule: the launcher covered 25.8 per cent of BOOK A SESSION and cut
   through the word itself, on the home page and on every city page.

   WHY PADDING IS THE LEVER. The hero is bottom-aligned, so this value is the gap
   between the last button and the foot of the hero, and on a tall phone there
   are a couple of hundred pixels of slack above the text. Raising it lifts the
   whole block; it does not make the hero taller or push the page down. 110px is
   not a new number either: it is the top of the clamp above, so a phone simply
   takes the bottom clearance the widest desktop already has.

   WHAT IT FIXES, MEASURED: 390x844, 393x852, 414x896 and 480x900 all go from
   covered to 28-40px of clear air.

   WHAT IT CANNOT FIX, ALSO MEASURED: below about 700px of viewport height
   (375x667, 360x640) the hero's own content is taller than the screen it has, so
   there is no slack left, the buttons sit at the fold wherever this padding is
   put, and the launcher still clips them. Lifting those needs the hero's TEXT to
   shrink on short screens, which is a design decision and not this rule's to
   take. The same is true of short desktop windows (1024x768, 1280x800), where
   the fix would be to raise this clearance at every width rather than only on
   phones. Both are written up for the owner rather than done here. */
@media (max-width: 480px) {
  .hero { padding-bottom: 110px; }
}

/* staged hero entrance */
.hero__eyebrow, .hero h1, .hero p, .hero__cta { animation: heroRise 1.1s var(--ease) both; }
.hero__eyebrow { animation-delay: 0.1s; }
.hero h1 { animation-delay: 0.22s; }
.hero p { animation-delay: 0.38s; }
.hero__cta { animation-delay: 0.52s; }
@keyframes heroRise {
  from { opacity: 0; transform: translateY(26px); }
  to { opacity: 1; transform: none; }
}
@media (prefers-reduced-motion: reduce) {
  .hero__eyebrow, .hero h1, .hero p, .hero__cta { animation: none; }
  .hero__bg { animation: none; }
}

/* ============================================================
   Buttons: understated line-buttons that fill on hover
   ============================================================ */

.btn {
  display: inline-block;
  font-family: var(--font-body);
  font-size: 12.5px;
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  padding: 15px 34px;
  border-radius: 999px;
  text-decoration: none;
  cursor: pointer;
  background: transparent;
  transition: background 0.4s var(--ease), color 0.4s var(--ease),
              border-color 0.4s var(--ease), transform 0.4s var(--ease),
              box-shadow 0.4s var(--ease);
}
.btn:active { transform: translateY(0) scale(0.985); }

.btn--gold {
  border: 1px solid var(--gold);
  color: var(--gold-soft);
}
.btn--gold:hover {
  background: var(--gold);
  color: var(--black);
  transform: translateY(-2px);
  box-shadow: 0 14px 36px -12px rgba(201, 164, 92, 0.5);
}

.btn--ghost-light {
  border: 1px solid rgba(242, 233, 220, 0.35);
  color: var(--ivory);
}
.btn--ghost-light:hover {
  border-color: var(--ivory);
  background: var(--ivory);
  color: var(--black);
  transform: translateY(-2px);
}

.btn--ink {
  border: 1px solid var(--gold);
  color: var(--gold-soft);
}
.btn--ink:hover {
  background: var(--gold);
  color: var(--black);
  transform: translateY(-2px);
}

/* ============================================================
   Our team: the Spaces Grid, translated
   ============================================================ */

.section--therapists { background: var(--black); }

/* THREE ACROSS, 2026-08-31 (evening), at the owner's request: he wanted the
   masseuses bigger on the homepage, and the way a fixed grid makes a card
   bigger is by dropping a column. Only the top step of the ladder moves, and
   that is deliberate - it is the only one that had room to give.
     up to 560px   one across, 280 to 504px cards. A phone. UNCHANGED, and
                   measured before it was assumed: this step was already one.
     561 to 1020   two across, 241.5 to 443.7px cards. UNCHANGED.
     1021 and up   three across, 285.9 to 345.1px cards, where four gave
                   206.8 to 249.2px.
   Measured in Chrome on the seeded roster: 249.2 -> 345.1 at 1280 and
   248.0 -> 344.0 at 1440 and wider, which is the third the owner asked for
   (+38.5%). The photograph IS the card here - the plate fills the column at
   every step - so one number is both.
   AND IT CLOSES A COLLISION NOBODY HAD SEEN. 1021px was the first width the
   old four-across step took, and it took it at 206.8px a card: the corner
   sash's painted band OVERLAPPED the availability dot there by 10.33px on a
   separating-axis measure, and at 1100 the two cleared each other by 0.99px.
   18.90 measures 375, 768 and 1280, and all three of those are clear, so the
   fault lived in the gap between its viewports. Three across makes the
   narrowest desktop card 285.9px and the same measurement reads 14.25px of
   clear air. 18.90 now takes 1021 as a fourth width, so the pinch point is
   measured rather than stepped over. */
.therapist-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);   /* three across on a wide desktop */
  gap: clamp(22px, 3vw, 40px);
}
@media (max-width: 1020px) { .therapist-grid { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 560px)  { .therapist-grid { grid-template-columns: 1fr; } }

/* hover response is deliberately quiet: the photo breathes, the rule draws.
   (No transform lift here: it would fight the staggered reveal transition.) */
.therapist-card {
  display: block;
  text-decoration: none;
  color: inherit;
  background: transparent;
  border: none;
  overflow: visible;
}

.therapist-photo {
  position: relative;
  aspect-ratio: 3 / 4;
  background: linear-gradient(160deg, #5A1B2B, #28090F);
  display: flex; align-items: center; justify-content: center;
  color: rgba(227, 200, 138, 0.4); font-size: 46px;
  overflow: hidden;
  border-radius: var(--radius);
}
.therapist-photo img {
  width: 100%; height: 100%; object-fit: cover;
  transition: transform 1s var(--ease), filter 1s var(--ease);
  filter: saturate(0.92);
}
.therapist-card:hover .therapist-photo img { transform: scale(1.05); filter: saturate(1.05); }
.therapist-photo::after {
  content: ""; position: absolute; inset: 0;
  background: linear-gradient(0deg, rgb(var(--canvas-rgb) / 0.45) 0%, transparent 40%);
}

.avail-dot {
  position: absolute; top: 14px; left: 14px; z-index: 2;
  display: inline-flex; align-items: center; gap: 7px;
  font-size: 10.5px; font-weight: 600; letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--ivory);
  background: rgb(var(--canvas-rgb) / 0.6);
  backdrop-filter: blur(4px);
  padding: 5px 12px 5px 10px; border-radius: 999px;
}
.avail-dot i { width: 7px; height: 7px; border-radius: 50%; display: inline-block; }
.avail-dot.on i  { background: #9BD4A2; box-shadow: 0 0 8px 1px rgba(155, 212, 162, 0.8); }
.avail-dot.off i { background: var(--muted); box-shadow: none; }

/* THE CORNER SASH, 2026-08-31, reshaped the same evening from the horizontal
   pill that shipped this morning: the owner sketched a band CUTTING ACROSS
   the photograph's top-RIGHT corner at 45 degrees with the words running
   along it, and this is that. The plate's overflow:hidden clips both ends
   flush to its edges and to its 2px radius, so the sash never overhangs and
   there is nothing to hide it with. The availability dot still owns the
   top-left and the ::after scrim only reaches the bottom 40%, so this corner
   is still the one clear ground on the card. The gold is the site's own and
   nothing new was invented: ground and ink are the .btn--gold hover pairing,
   --gold under --black. That is 8.7:1 as flat colours; 18.90 measures 7.40:1
   at worst on the COMPOSITED pixels, because at 9px the darkest pixel of an
   antialiased glyph is not the ink at full coverage. Both clear 4.5:1.

   THE FIVE NUMBERS, AND WHY THEY ARE THESE.
     --sash-reach  how far the band cuts along each edge from the corner, so
                   the chord it sits on is reach x root2 = 147.1px long.
     --sash-band   the gold's thickness.
     --sash-line   the type's own box INSIDE the band. Only that inner box has
                   to clear the corner's taper, which is why the usable run is
                   reach x root2 MINUS line rather than minus band: a 22px
                   band with the words in a 12px box buys 10px of run over a
                   band packed to its edges.
     --sash-run    the words' box, and the whole budget below.
     --sash-over   how much longer the element is than the chord needs at each
                   end. That overhang is what gets clipped away to make the
                   ends flush, and it is drawn as a BORDER in the ground
                   colour rather than as padding on purpose: padding would
                   count into clientWidth, and then an over-long label would
                   spill 40px across the photograph before the ellipsis
                   noticed. As a border, the content box IS the run, and a
                   label that outgrows it is trimmed at the band's own edge.
   The translate puts the element's centre on the corner diagonal - x by
   (width - reach)/2, y by (reach - band)/2 - and the rotate spins it about
   that centre, so the whole shape is derived from the five numbers and moves
   as one when any of them is changed.

   TWO STEPS, BECAUSE THE CARD IS NOT ONE SIZE. The sash is fixed in px on a
   plate whose width runs from 241.5px to 504px across the ladder, so one set
   of numbers cannot be right everywhere: the budget has to be set by the
   NARROWEST card the step renders, and the narrowest card on the whole ladder
   is 241.5px at a 561px viewport, two across, which the three-across change
   did not touch. Hence a base step and a desktop step, and the desktop step
   is scoped to .therapist-photo so the block-card twin behind /services,
   /join-us and the designer canvas - a 205px plate at 1280 - keeps the base
   numbers rather than being handed a 204px chord.

   THE BASE STEP, unchanged tonight to the byte. 104px reach, a 147.08px
   chord, a 12px line box, so 135.08px of usable run and a 126px run. At
   9px/0.06em uppercase that carries BIRMINGHAM & LONDON (118.6px) with 7.4px
   to spare, BACK 12 SEPTEMBER at 100.4px and BACK WEDNESDAY at 92.3px, and
   COMING SOON at 71.9px. On its narrowest card, 241.45px at 561, the painted
   band clears the availability dot by 13.7px. Nothing below 1021px moved in
   this change - not the grid, not the sash - so nothing below 1021px had to
   be re-argued.

   THE DESKTOP STEP, 1021px and up, which is the three-across band and the
   whole point of tonight. Cards are 285.9px at 1021 and 345.1px at 1280 and
   over, against 206.8 and 249.2 before, so the sash keeps the SAME fraction
   of the plate it always had: 144/345.06 is 41.73%, and 104/249.2 was 41.73%.
   144px reach is a 203.65px chord; a 16px line box leaves 187.65px of usable
   run; the run is 176px, so the words' box clears the corner's taper by
   4.12px at each end at 1280 and never less than 3.84px anywhere in the band
   (3.21px before). At 11px/0.14em - the site's own eyebrow
   size, and the house tracking, back after a night at 9px/0.06em -
   BIRMINGHAM & LONDON measures 161.70px and fits with 14.30px to spare, 8.1%
   of the run; BACK WEDNESDAY 125.08px (50.92px spare), BACK 12 SEPTEMBER
   137.67px, COMING SOON 97.56px (78.44px spare). Note what did NOT change:
   about twenty characters, then and now. The extra room bought legibility,
   not length, which is why the two shortened defaults keep their reason -
   AVAILABLE IN BIRMINGHAM & LONDON still wants 255.92px of a 176px run.
   The dot keeps the top-left: its footprint is 100.0 x 27.3 at 14,14, and the
   PAINTED band comes no closer than 14.25px to it at 1021 - the tightest
   desktop card - and 65.15px at 1280. 18.90 re-measures every number in these
   three paragraphs in a real browser at 375, 768, 1021 and 1280: the box, the
   clip, the dot, the glyphs across the band as well as along it, and the
   contrast.

   AND THE CLIP IS NOT WHERE IT LOOKS, which is worth a line because the whole
   shape hangs off it. .therapist-photo declares overflow:hidden thirty lines
   above this rule, and that declaration is DEAD: the cinematic-zoom block far
   below re-declares it on the more specific .therapist-card .therapist-photo,
   and that is the rule holding the sash in. Found by breaking the first one on
   purpose and watching 18.90 stay green - the right answer to an edit that
   changes nothing on screen - and then by breaking the second and watching it
   go red. Anyone moving this shape needs to know which of the two is
   load-bearing.

   Present only when the element is: an unset ribbon renders no element at
   all, so this rule matches nothing on today's live document. */
.t-ribbon {
  --sash-reach: 104px;
  --sash-band: 22px;
  --sash-line: 12px;
  --sash-run: 126px;
  --sash-over: 40px;
  position: absolute; top: 0; right: 0; z-index: 2;
  width: calc(var(--sash-run) + var(--sash-over) * 2);
  height: var(--sash-band);
  padding: calc((var(--sash-band) - var(--sash-line)) / 2) 0;
  border-left: var(--sash-over) solid var(--gold);
  border-right: var(--sash-over) solid var(--gold);
  transform:
    translate(calc((var(--sash-run) + var(--sash-over) * 2 - var(--sash-reach)) / 2),
              calc((var(--sash-reach) - var(--sash-band)) / 2))
    rotate(45deg);
  font-size: 9px; font-weight: 600; letter-spacing: 0.06em; text-transform: uppercase;
  line-height: var(--sash-line);
  text-align: center;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  background: var(--gold);
  color: var(--black);
}

/* THE DESKTOP STEP. Only the five numbers and the type are restated; width,
   height, padding, borders, transform and line-height are all calc()s over
   the properties, so they follow on their own and the shape stays derived
   from one place. Scoped to .therapist-photo on purpose - see the two-steps
   paragraph above - and keyed to 1021px because that is where the grid itself
   goes to three across, so the sash and its plate change size on the same
   line rather than on two that can drift apart. */
@media (min-width: 1021px) {
  .therapist-photo .t-ribbon {
    --sash-reach: 144px;
    --sash-band: 28px;
    --sash-line: 16px;
    --sash-run: 176px;
    --sash-over: 56px;
    font-size: 11px; letter-spacing: 0.14em;
  }
}

.therapist-body { padding: 20px 2px 0; position: relative; }
.therapist-body h3 {
  font-family: var(--font-display); font-weight: 380;
  font-size: 23px; margin: 0; letter-spacing: -0.01em;
  color: var(--ivory);
  display: flex; align-items: baseline; gap: 12px;
}
/* serial number, House of Honey editorial numbering */
.therapist-body h3::before {
  content: attr(data-num);
  font-family: var(--font-body);
  font-size: 11px; font-weight: 600; letter-spacing: 0.18em;
  color: var(--gold);
}
.therapist-body .t-title {
  color: var(--gold-soft); font-size: 11px; font-weight: 600;
  letter-spacing: 0.2em; text-transform: uppercase;
  margin: 8px 0 0;
}
.therapist-body .t-rule { margin-top: 18px; height: 1px; background: var(--hairline); position: relative; overflow: hidden; }
.therapist-body .t-rule::after {
  content: ""; position: absolute; inset: 0; background: var(--gold);
  transform: scaleX(0); transform-origin: left; transition: transform 0.7s var(--ease);
}
.therapist-card:hover .t-rule::after { transform: scaleX(1); }
.therapist-body .t-link {
  margin-top: 14px; font-size: 11px; font-weight: 600;
  letter-spacing: 0.16em; text-transform: uppercase;
  color: var(--muted);
  display: inline-flex; align-items: center; gap: 8px;
  transition: color 0.35s var(--ease);
}
.therapist-card:hover .t-link { color: var(--gold-soft); }
.therapist-body .t-link span { transition: transform 0.4s var(--ease); }
.therapist-card:hover .t-link span:last-child { transform: translateX(5px); }

/* ============================================================
   Gallery
   ============================================================ */

.section--gallery { background: var(--black-2); }
.gallery-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
  grid-auto-rows: 210px;
  gap: 10px;
}
.gallery-grid figure { margin: 0; overflow: hidden; border-radius: var(--radius); cursor: pointer; }
.gallery-grid figure:first-child { grid-column: span 2; grid-row: span 2; }
.gallery-grid img { width: 100%; height: 100%; object-fit: cover; transition: transform 0.9s var(--ease); }
.gallery-grid figure:hover img { transform: scale(1.05); }
@media (max-width: 620px) {
  .gallery-grid { grid-auto-rows: 150px; }
  .gallery-grid figure:first-child { grid-column: span 2; grid-row: span 1; }
}

.lightbox {
  position: fixed; inset: 0; z-index: 999;
  background: rgba(16, 5, 10, 0.94);
  display: none; align-items: center; justify-content: center; padding: 24px;
  cursor: zoom-out;
}
.lightbox.open { display: flex; }
.lightbox img { max-width: 92%; max-height: 90%; border-radius: var(--radius); box-shadow: 0 30px 80px -20px rgba(0,0,0,0.8); }

/* ============================================================
   Locations
   ============================================================ */

.section--locations { background: var(--black); }
.location-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: clamp(20px, 3vw, 36px); }
@media (max-width: 620px) { .location-grid { grid-template-columns: 1fr; } }
.location-card {
  border: 1px solid var(--hairline);
  border-radius: var(--radius);
  padding: 36px 34px 32px;
  background: var(--card);
  transition: border-color 0.45s var(--ease), transform 0.45s var(--ease);
}
.location-card:hover { border-color: var(--hairline-2); transform: translateY(-4px); }
.location-card .num {
  font-family: var(--font-body); font-size: 11px; font-weight: 600;
  letter-spacing: 0.24em; color: var(--gold);
}
.location-card h3 { font-family: var(--font-display); font-weight: 380; font-size: 27px; margin: 14px 0 8px; color: var(--ivory); }
.location-card p { color: var(--muted); margin: 0; }

/* ============================================================
   Contact block, inside the locations section
   ------------------------------------------------------------
   Client-requested (2026-08-01): the opening hours, the phone, the email and
   a working enquiry form, sitting with the four cities rather than bolted on
   somewhere else. A gold hairline separates it from the city cards; the head
   uses the same eyebrow and display-heading idiom as every other chapter, one
   size down so the hierarchy still reads Locations first.

   NO ADDRESS APPEARS HERE, in markup or in content: none is verified.
   ============================================================ */
.contact-block {
  margin-top: clamp(64px, 9vw, 118px);
  padding-top: clamp(52px, 7vw, 92px);
  border-top: 1px solid var(--hairline);
  /* clears the sticky bar with a little air. The bar is 99px on this page now
     that the logo is 68px (was 91px, and this was 96px to match). */
  scroll-margin-top: 104px;
}
.contact-block .section-head { margin-bottom: clamp(34px, 4.6vw, 62px); }
.contact-block .section-head h2 { font-size: clamp(31px, 4.4vw, 52px); }

.contact-block__body {
  display: grid;
  grid-template-columns: minmax(0, 5fr) minmax(0, 7fr);
  gap: clamp(34px, 5vw, 74px);
  align-items: start;
}
@media (max-width: 900px) { .contact-block__body { grid-template-columns: minmax(0, 1fr); } }

/* ---- the details rail ---- */
.contact-detail { display: flex; flex-direction: column; gap: clamp(28px, 3.4vw, 44px); min-width: 0; }
.contact-detail__label {
  font-family: var(--font-body);
  font-size: 11px; font-weight: 600; letter-spacing: 0.24em; text-transform: uppercase;
  color: var(--gold); margin: 0 0 16px;
}
.contact-detail__lead {
  margin: 0 0 14px; color: var(--ivory);
  font-family: var(--font-display); font-weight: 380; font-size: 21px; line-height: 1.4;
}
.contact-detail__note { margin: 15px 0 0; color: var(--muted); font-size: 14px; line-height: 1.6; max-width: 42ch; }
.contact-detail__link {
  display: inline-block;
  font-family: var(--font-display); font-weight: 380;
  font-size: clamp(24px, 2.6vw, 32px); letter-spacing: -0.01em;
  color: var(--gold-soft); text-decoration: none;
  padding-bottom: 4px;
  border-bottom: 1px solid rgba(201, 164, 92, 0.28);
  overflow-wrap: anywhere;   /* a long address must never widen the page */
  transition: color 0.4s var(--ease-lux, var(--ease)), border-color 0.4s var(--ease-lux, var(--ease));
}
.contact-detail__link:hover { color: var(--ivory); border-bottom-color: var(--gold); }
.contact-detail__link--email { font-size: clamp(15.5px, 1.7vw, 21px); }

.contact-hours { margin: 0; }
.contact-hours__row {
  display: flex; align-items: baseline; justify-content: space-between; gap: 14px;
  padding: 9px 0;
  border-bottom: 1px solid var(--hairline);
}
.contact-hours__row:last-child { border-bottom: none; }
.contact-hours dt { color: var(--muted); font-size: 14.5px; }
.contact-hours dd { margin: 0; color: var(--ivory); font-size: 14.5px; text-align: right; font-variant-numeric: tabular-nums; }
.contact-hours__row.is-today dt, .contact-hours__row.is-today dd { color: var(--gold-soft); }
.contact-hours__row.is-today dt::after {
  content: "Today";
  display: inline-block; margin-left: 10px; padding: 2px 8px;
  font-size: 9.5px; font-weight: 600; letter-spacing: 0.16em; text-transform: uppercase;
  color: var(--gold);
  border: 1px solid var(--hairline-2); border-radius: 999px;
}

/* ---- the form ---- */
.contact-form-col { min-width: 0; }
.cform__grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: clamp(16px, 2vw, 22px); }
@media (max-width: 620px) { .cform__grid { grid-template-columns: minmax(0, 1fr); } }
.cform__field { display: flex; flex-direction: column; gap: 8px; min-width: 0; }
.cform__field--wide { grid-column: 1 / -1; }
.cform__labelrow { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; }
.cform__field label {
  font-size: 11px; font-weight: 600; letter-spacing: 0.16em; text-transform: uppercase;
  color: var(--ivory);
}
.cform__req { flex: none; color: var(--gold); font-size: 9.5px; letter-spacing: 0.1em; }
.cform__tag { flex: none; color: var(--muted); font-size: 9.5px; font-weight: 500; letter-spacing: 0.1em; }
.cform__input {
  width: 100%; padding: 13px 15px;
  /* 15.5px -> 16px, and the half a pixel matters more than it looks.
     Mobile Safari ZOOMS THE WHOLE PAGE IN when a text field smaller than 16px
     takes focus, and it does not zoom back out afterwards. So a customer tapping
     the name field on an iPhone was thrown into a magnified, sideways-scrolling
     page in the middle of giving us their booking details. 16px is the threshold,
     not a preference: measured across the site, every one of the seventeen inputs
     was under it. */
  font-family: var(--font-body); font-size: 16px; line-height: 1.5;
  color: var(--ivory);
  background: var(--wine);
  border: 1px solid var(--hairline);
  border-radius: var(--radius);
  transition: border-color 0.3s var(--ease), background 0.3s var(--ease);
}
.cform__input::placeholder { color: var(--muted); opacity: 0.6; }
.cform__input:hover { border-color: var(--hairline-2); }
.cform__input:focus { outline: none; border-color: var(--gold); background: var(--wine-lift); }
textarea.cform__input { resize: vertical; min-height: 132px; }
.cform__field.is-bad .cform__input { border-color: var(--danger-line); }
.cform__err { color: var(--danger-text); font-size: 13.5px; line-height: 1.45; }

.cform__actions { margin-top: clamp(22px, 3vw, 32px); }
.cform__submit { min-width: 208px; }
/* a button that is busy must not keep offering the hover it cannot honour */
.cform__submit:disabled { opacity: 0.55; cursor: progress; }
.cform__submit:disabled::before { display: none; }
.cform__submit:disabled:hover {
  background: transparent; color: var(--gold-soft);
  transform: none; box-shadow: none;
}

/* One region, every outcome. It is never display:none, so an announcement is
   never lost to a live region that did not exist a moment earlier. */
.cform__status { margin: 0; }
.cform__status.is-working { margin-top: 16px; color: var(--gold-soft); font-size: 14.5px; }
.cform__status.is-error {
  margin-top: 18px; padding: 14px 18px;
  border-left: 2px solid var(--danger-line);
  background: rgba(201, 124, 99, 0.1);
  border-radius: 0 var(--radius) var(--radius) 0;
  color: var(--danger-soft); font-size: 15px; line-height: 1.55;
}
.cform__status.is-sent {
  padding: clamp(26px, 4vw, 38px);
  border: 1px solid var(--gold-deep);
  border-radius: var(--radius);
  background: var(--card);
  color: var(--ivory); font-size: 16.5px; line-height: 1.6;
}
.cform__status.is-sent::before {
  content: "✓";
  display: flex; align-items: center; justify-content: center;
  width: 42px; height: 42px; margin: 0 0 18px;
  border-radius: 50%;
  border: 1px solid var(--gold);
  color: var(--gold); font-size: 19px;
}

.cform__again { margin-top: 22px; }
.cform__link {
  padding: 0 0 4px; cursor: pointer;
  background: none; border: none;
  border-bottom: 1px solid var(--gold-deep);
  color: var(--gold-soft);
  font-family: var(--font-body);
  font-size: 11.5px; font-weight: 600; letter-spacing: 0.18em; text-transform: uppercase;
  transition: color 0.35s var(--ease), border-color 0.35s var(--ease);
}
.cform__link:hover { color: var(--ivory); border-bottom-color: var(--gold); }

/* ============================================================
   Dark bands: booking & hiring
   ============================================================ */

.band--booking, .band--hiring { background: var(--black-2); color: var(--ivory); position: relative; overflow: hidden; }
.band--booking::before, .band--hiring::before {
  content: ""; position: absolute; z-index: 0;
  width: 62vw; height: 62vw; border-radius: 50%;
  background: radial-gradient(circle, rgba(201, 164, 92, 0.1), transparent 62%);
  top: -24vw; right: -16vw; pointer-events: none;
}
.band .wrap { position: relative; z-index: 1; }
.band--booking .band-inner, .band--hiring .band-inner { max-width: 780px; }
.band .eyebrow { color: var(--gold); }
.band h2 {
  font-family: var(--font-display); font-weight: 300;
  font-size: clamp(38px, 5.8vw, 72px); line-height: 1.0; letter-spacing: -0.02em;
  margin: 0 0 22px; color: var(--ivory);
}
.band p { color: var(--muted); font-size: 18px; max-width: 50ch; margin: 0 0 36px; }

/* ============================================================
   Reviews
   ============================================================ */

.section--reviews { background: var(--black); }
.review-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: clamp(24px, 4vw, 48px); }
@media (max-width: 860px) { .review-grid { grid-template-columns: 1fr; max-width: 560px; } }
.review { border-top: 1px solid var(--gold); padding-top: 26px; }
.review blockquote {
  font-family: var(--font-display); font-weight: 330; font-style: italic;
  font-size: 22px; line-height: 1.42; margin: 0 0 18px; color: var(--ivory);
  letter-spacing: -0.01em;
}
.review cite { font-style: normal; font-size: 11px; font-weight: 600; letter-spacing: 0.18em; text-transform: uppercase; color: var(--muted); }

/* ============================================================
   Footer: structured, editorial
   ============================================================ */

.site-footer {
  background: var(--black);
  border-top: 1px solid var(--hairline);
  color: var(--muted);
  padding: clamp(56px, 8vw, 90px) 0 40px;
}
.site-footer .wrap { display: block; }
.footer-grid {
  display: grid;
  grid-template-columns: 1.4fr 1fr 1fr;
  gap: clamp(28px, 5vw, 64px);
  margin-bottom: clamp(40px, 6vw, 64px);
}
@media (max-width: 720px) { .footer-grid { grid-template-columns: 1fr; } }
.site-footer .brand-word { font-size: 25px; }
/* Not dead, despite index.html's footer emblem now being an <img>: the blog,
   post and location footers are still built node by node in pages.js and still
   carry an inline <svg> monogram, and this is what sizes them. The homepage
   footer takes its size from .brand-emblem img instead. Checked in the browser
   on 2026-08-02, both cases. */
.site-footer .brand-emblem svg { width: 38px; height: 38px; }
.footer-tag { margin: 14px 0 0; font-size: 14.5px; max-width: 30ch; color: var(--muted); }
.footer-col h4 {
  margin: 0 0 16px;
  font-size: 10.5px; font-weight: 600; letter-spacing: 0.26em; text-transform: uppercase;
  color: var(--gold);
}
.footer-col p { margin: 0 0 12px; font-size: 14.5px; line-height: 1.6; }
.footer-col a { color: var(--muted); text-decoration: none; transition: color 0.3s var(--ease); }
.footer-col a:hover { color: var(--gold-soft); }
.footer-base {
  border-top: 1px solid var(--hairline);
  padding-top: 24px;
  display: flex; justify-content: space-between; gap: 16px; flex-wrap: wrap;
  font-size: 12px; letter-spacing: 0.04em;
}
.footer-base a { color: inherit; text-decoration: none; opacity: 0.85; transition: color 0.3s var(--ease), opacity 0.3s var(--ease); }
.footer-base a:hover, .footer-base a[aria-current="page"] { color: var(--gold-soft); opacity: 1; }

/* ============================================================
   Join us modal + application form

   Markup contract (public/site.js owns the DOM, this file owns
   the look). Every rule degrades: the plain label + input markup
   that exists today still renders correctly on its own.

     .jm-panel > form
       .jm-row                two controls side by side (name + age)
         .jm-field            label + control wrapper
       .jm-req                required marker inside a label
       .jm-optional           "optional" note inside a label
       .jm-hint               helper line under a control
       .jm-photos             multi-photo upload block
         .jm-photos__head     label on the left, .jm-count on the right
         .jm-drop             click / drag target (label, div or button)
           .jm-drop__icon .jm-drop__title .jm-drop__hint
           input[type=file]   transparent, fills .jm-drop
           states: .is-dragover .is-busy
         .jm-thumbs > .jm-thumb        uploaded previews
           img, .jm-thumb__remove, .jm-thumb__bar > i
           states: .is-uploading .is-error

   Colours are contract tokens with a fallback to the current
   palette, so the block stays correct before and after rebrand.
   ============================================================ */

.jm-overlay {
  --jm-scrim:        var(--oxblood, var(--black));
  --jm-surface:      var(--wine, var(--card));
  --jm-surface-2:    var(--wine-lift, var(--card-2));
  --jm-line:         var(--hairline);
  --jm-line-strong:  var(--hairline-2);
  --jm-accent:       var(--gold);
  --jm-accent-bright: var(--gold-bright, var(--gold-soft));
  --jm-accent-deep:  var(--gold-deep);
  --jm-text:         var(--ivory);
  --jm-muted:        var(--muted);
  /* not a brand colour: form error tint, carried over from .jm-error */
  --jm-danger:       var(--danger, #e08f7f);

  position: fixed; inset: 0; z-index: 1100;
  background: var(--jm-scrim);
  background: color-mix(in srgb, var(--jm-scrim) 82%, transparent);
  backdrop-filter: blur(6px);
  display: none; align-items: center; justify-content: center; padding: 20px;
}
.jm-overlay.open { display: flex; }
.jm-panel {
  width: 460px; max-width: 100%;
  max-height: calc(100vh - 40px);
  overflow-y: auto;
  overscroll-behavior: contain;
  scrollbar-width: thin;
  scrollbar-color: var(--jm-line-strong) transparent;
  background: var(--jm-surface);
  border: 1px solid var(--jm-line);
  border-radius: 6px;
  padding: clamp(28px, 5vw, 44px);
  animation: jmIn 0.45s var(--ease);
}
@keyframes jmIn { from { opacity: 0; transform: translateY(18px) scale(0.98); } to { opacity: 1; transform: none; } }
.jm-head { display: flex; justify-content: space-between; align-items: flex-start; gap: 14px; }
.jm-eyebrow { font-size: 11px; font-weight: 600; letter-spacing: 0.28em; text-transform: uppercase; color: var(--jm-accent); margin: 0 0 14px; }
.jm-panel h3 { font-family: var(--font-display); font-weight: 340; font-size: clamp(26px, 4vw, 34px); line-height: 1.05; margin: 0 0 10px; color: var(--jm-text); }
.jm-panel > p, .jm-sub { color: var(--jm-muted); font-size: 15px; margin: 0 0 24px; }
.jm-close {
  background: none; border: 1px solid var(--jm-line); border-radius: 50%;
  width: 36px; height: 36px; color: var(--jm-text); cursor: pointer; font-size: 16px; line-height: 1;
  transition: border-color 0.3s var(--ease), transform 0.3s var(--ease);
  flex-shrink: 0;
}
.jm-close:hover { border-color: var(--jm-accent); transform: rotate(90deg); }
.jm-panel label {
  display: block;
  font-size: 10.5px; font-weight: 600; letter-spacing: 0.16em; text-transform: uppercase;
  color: var(--jm-accent-bright); margin: 16px 0 6px;
}
.jm-panel input, .jm-panel textarea {
  width: 100%;
  border: 1px solid var(--jm-line);
  border-radius: 10px;
  padding: 12px 14px;
  background: var(--jm-surface-2);
  color: var(--jm-text);
  font-family: var(--font-body);
  /* 14.5px -> 16px, the same iOS focus-zoom threshold as .cform__input above.
     This is the form a therapist fills in to apply for work, usually on her own
     phone, so it is the last place to make somebody fight a zoomed page. */
  font-size: 16px;
  outline: none;
  transition: border-color 0.3s var(--ease), background 0.3s var(--ease),
              box-shadow 0.3s var(--ease);
}
.jm-panel input:focus, .jm-panel textarea:focus {
  border-color: var(--jm-accent);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--jm-accent) 18%, transparent);
}
.jm-panel input::placeholder, .jm-panel textarea::placeholder {
  color: color-mix(in srgb, var(--jm-muted) 60%, transparent);
}
.jm-panel textarea { resize: vertical; min-height: 88px; }

/* ---- field furniture ---- */
.jm-row { display: grid; grid-template-columns: minmax(0, 1fr) 116px; gap: 0 16px; }
.jm-field { min-width: 0; }
.jm-field > label:first-child { margin-top: 16px; }
.jm-row .jm-field > label:first-child { white-space: nowrap; }
@media (max-width: 480px) { .jm-row { grid-template-columns: 1fr; } }

.jm-req { color: var(--jm-accent); margin-left: 4px; }
.jm-optional {
  margin-left: 6px; color: var(--jm-muted);
  font-size: 9.5px; font-weight: 500; letter-spacing: 0.12em;
}
.jm-hint { color: var(--jm-muted); font-size: 12.5px; letter-spacing: 0.01em; margin: 7px 0 0; }

/* age: a plain number box, no browser spinners */
.jm-panel input[type="number"] { appearance: textfield; -moz-appearance: textfield; }
.jm-panel input[type="number"]::-webkit-outer-spin-button,
.jm-panel input[type="number"]::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }

/* ---- photos: drop zone ---- */
.jm-photos { margin-top: 4px; }
.jm-photos__head { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; }
.jm-photos__head label { flex: 1 1 auto; }
.jm-count {
  flex: 0 0 auto;
  font-size: 10.5px; font-weight: 600; letter-spacing: 0.16em; text-transform: uppercase;
  color: var(--jm-muted);
}

.jm-panel .jm-drop {
  position: relative;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 7px;
  width: 100%;
  min-height: 118px;
  margin: 0;
  padding: 22px 18px;
  border: 1px dashed var(--jm-line-strong);
  border-radius: 10px;
  background: var(--jm-surface-2);
  color: var(--jm-muted);
  font-family: var(--font-body);
  font-size: 14.5px;
  text-align: center;
  text-transform: none;
  letter-spacing: normal;
  appearance: none; -webkit-appearance: none;
  cursor: pointer;
  transition: border-color 0.3s var(--ease), background 0.3s var(--ease), color 0.3s var(--ease);
}
.jm-panel .jm-drop:hover,
.jm-panel .jm-drop:focus-within { border-color: var(--jm-accent); color: var(--jm-text); }
.jm-panel .jm-drop.is-dragover {
  border-style: solid;
  border-color: var(--jm-accent);
  color: var(--jm-text);
  background: color-mix(in srgb, var(--jm-accent) 12%, var(--jm-surface-2));
}
.jm-panel .jm-drop.is-busy { pointer-events: none; opacity: 0.55; }

/* the real input sits invisibly over the whole zone: native click + drop */
.jm-panel .jm-drop input[type="file"] {
  position: absolute; inset: 0;
  width: auto; height: auto;
  margin: 0; padding: 0;
  border: 0; border-radius: 10px;
  background: none;
  opacity: 0;
  cursor: pointer;
}
.jm-drop__icon {
  width: 34px; height: 34px; border-radius: 50%;
  border: 1px solid var(--jm-line-strong);
  display: flex; align-items: center; justify-content: center;
  color: var(--jm-accent); font-size: 16px; line-height: 1;
}
.jm-drop__title {
  font-size: 11px; font-weight: 600; letter-spacing: 0.16em; text-transform: uppercase;
  color: var(--jm-accent-bright);
}
.jm-drop__hint { font-size: 12.5px; line-height: 1.45; color: var(--jm-muted); margin: 0; }

/* fallback: a bare, visible file input outside a drop zone */
.jm-panel input[type="file"] {
  padding: 9px 12px;
  font-size: 13px;
  color: var(--jm-muted);
  cursor: pointer;
}
.jm-panel input[type="file"]::file-selector-button {
  margin: 0 12px 0 0;
  padding: 8px 18px;
  border: 1px solid var(--jm-accent);
  border-radius: 999px;
  background: transparent;
  color: var(--jm-accent-bright);
  font-family: var(--font-body);
  font-size: 11px; font-weight: 600; letter-spacing: 0.14em; text-transform: uppercase;
  cursor: pointer;
  transition: background 0.3s var(--ease), color 0.3s var(--ease);
}
.jm-panel input[type="file"]::file-selector-button:hover {
  background: var(--jm-accent);
  color: var(--jm-surface);
}

/* ---- photos: thumbnails ---- */
.jm-thumbs {
  list-style: none;
  display: grid; grid-template-columns: repeat(auto-fill, minmax(80px, 1fr)); gap: 10px;
  margin: 12px 0 0; padding: 0;
}
.jm-thumbs:empty { display: none; }
.jm-thumb {
  position: relative;
  aspect-ratio: 1 / 1;
  overflow: hidden;
  border: 1px solid var(--jm-line);
  border-radius: 8px;
  background: var(--jm-surface-2);
}
.jm-thumb img { width: 100%; height: 100%; object-fit: cover; }
.jm-thumb__remove {
  position: absolute; top: 5px; right: 5px; z-index: 2;
  width: 22px; height: 22px;
  display: flex; align-items: center; justify-content: center;
  border: 1px solid var(--jm-line-strong); border-radius: 50%;
  background: color-mix(in srgb, var(--jm-scrim) 72%, transparent);
  color: var(--jm-text);
  font-size: 13px; line-height: 1;
  cursor: pointer; opacity: 0;
  transition: opacity 0.25s var(--ease), border-color 0.25s var(--ease), color 0.25s var(--ease);
}
.jm-thumb:hover .jm-thumb__remove,
.jm-thumb__remove:focus-visible { opacity: 1; }
.jm-thumb__remove:hover { border-color: var(--jm-accent); color: var(--jm-accent-bright); }
@media (hover: none) { .jm-thumb__remove { opacity: 1; } }

.jm-thumb__bar {
  position: absolute; left: 8px; right: 8px; bottom: 8px; z-index: 2;
  height: 2px; border-radius: 2px; overflow: hidden;
  background: color-mix(in srgb, var(--jm-text) 22%, transparent);
}
.jm-thumb__bar > i { display: block; width: 0; height: 100%; background: var(--jm-accent); transition: width 0.3s var(--ease); }
.jm-thumb.is-uploading img { opacity: 0.4; }
.jm-thumb.is-uploading::after {
  content: ""; position: absolute; inset: 0; z-index: 1;
  background: color-mix(in srgb, var(--jm-scrim) 45%, transparent);
  animation: jmPulse 1.2s var(--ease-io) infinite;
}
@keyframes jmPulse { 0%, 100% { opacity: 0.3; } 50% { opacity: 0.7; } }
.jm-thumb.is-error { border-color: var(--jm-danger); }
.jm-thumb.is-error img { opacity: 0.3; }

/* ---- validation + actions ---- */
.jm-panel input.is-invalid, .jm-panel textarea.is-invalid { border-color: var(--jm-danger); }
.jm-panel input:user-invalid, .jm-panel textarea:user-invalid { border-color: var(--jm-danger); }
.jm-error { color: var(--jm-danger); font-size: 13px; min-height: 18px; margin: 10px 0 0; }
.jm-error:not(:empty) { border-left: 2px solid var(--jm-danger); padding-left: 10px; }
.jm-panel .btn { width: 100%; margin-top: 18px; text-align: center; }
.jm-panel button[disabled] { opacity: 0.5; pointer-events: none; }
.jm-success { text-align: center; padding: 26px 0 10px; }
.jm-success .jm-tick {
  width: 54px; height: 54px; margin: 0 auto 18px;
  border: 1px solid var(--jm-accent); border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  color: var(--jm-accent); font-size: 22px;
}
.jm-success h3 { margin-bottom: 8px; }
.jm-success p { color: var(--jm-muted); font-size: 15px; margin: 0; }
@media (prefers-reduced-motion: reduce) {
  .jm-panel { animation: none; }
  .jm-thumb.is-uploading::after { animation: none; }
  .jm-close:hover { transform: none; }
}

/* ============================================================
   Therapist page
   ============================================================ */

.tp-loading { padding: 140px 0; color: var(--muted); text-align: center; }
.tp-empty { padding: clamp(90px, 15vw, 180px) 0; max-width: 560px; }
.tp-empty h1 { font-family: var(--font-display); font-weight: 320; font-size: clamp(30px, 5vw, 46px); margin: 0 0 12px; color: var(--ivory); }
.tp-empty p { color: var(--muted); margin: 0 0 28px; }

/* ---- hero: face first, framed ---- */
.tp-hero { position: relative; background: var(--black); overflow: hidden; isolation: isolate; }
.tp-hero__glow {
  position: absolute; inset: 0; z-index: 0; pointer-events: none;
  background: radial-gradient(70% 60% at 82% 8%, rgba(201, 164, 92, 0.13), transparent 60%);
}
/* The foot is deliberately shorter than the head. Her name used to be followed
   by the tab bar, which is a different subject and wanted the full 76px of air;
   it is now followed by her own photo strip on the same ground, which is the
   same subject. A short foot binds the two into one opening lockup instead of
   leaving them looking like two bands that happen to share a colour. */
.tp-hero__wrap {
  position: relative; z-index: 1;
  display: grid; grid-template-columns: auto 1fr; gap: clamp(28px, 5vw, 70px);
  align-items: center;
  /* THE GALLERY COMES UP. This air was drawn when a portrait plate sat beside
     her name and the band needed room to hold the two together. The plate has
     since moved into the strip, so the air was holding nothing and pushed the
     photographs 653px down a 1280 screen, below the fold on a laptop. */
  padding-top: clamp(28px, 3.6vw, 48px); padding-bottom: clamp(16px, 2vw, 28px);
}
/* Client-requested (2026-08-01): "when opening therapist card reduce the image
   a little bit". The portrait was 380px at its widest and dominated the hero;
   288px is a little over half the area, which reads as a framed plate rather
   than a poster while still being the first thing the eye lands on. The gold
   hairline draws in with it so the proportion holds. */
.tp-hero__photo {
  width: clamp(178px, 21vw, 288px); aspect-ratio: 4 / 5;
  border-radius: var(--radius); overflow: hidden;
  background: linear-gradient(160deg, #5A1B2B, #28090F);
  outline: 1px solid var(--hairline-2);
  outline-offset: 10px;
  box-shadow: 0 30px 72px -30px rgba(0, 0, 0, 0.85);
}
.tp-hero__photo img { width: 100%; height: 100%; object-fit: cover; }
.tp-hero__ph { width: 100%; height: 100%; display: flex; align-items: center; justify-content: center; color: rgba(227, 200, 138, 0.4); font-size: 54px; }
.tp-hero__intro { color: var(--ivory); min-width: 0; display: flex; flex-direction: column; align-items: flex-start; }
.tp-back {
  display: inline-flex; align-items: center; gap: 9px;
  color: var(--muted); text-decoration: none;
  font-size: 11px; font-weight: 600; letter-spacing: 0.18em; text-transform: uppercase;
  margin-bottom: 22px;
  transition: color 0.3s var(--ease), gap 0.3s var(--ease);
}
.tp-back:hover { color: var(--gold-soft); gap: 13px; }
.tp-avail { position: static; display: inline-flex; margin-bottom: 18px; background: rgba(242, 233, 220, 0.07); }
.tp-name {
  font-family: var(--font-display); font-weight: 300;
  font-size: clamp(36px, 5.6vw, 64px); line-height: 1.0; letter-spacing: -0.02em;
  margin: 0; color: var(--ivory);
  /* A NAME MUST NEVER BE CUT OFF, and it was: .tp-hero carries overflow:hidden,
     so a long single word simply lost its last letters at 390px with no scroll
     bar and nothing on the page to say so. Silent, and on the one word on the
     page that has to be right. A name has no spaces to break at, so normal
     wrapping cannot help it; anywhere lets the word itself break. Kate types
     these names and some of them are long. */
  overflow-wrap: anywhere;
}
.tp-title { color: var(--gold-soft); font-size: 11.5px; font-weight: 600; letter-spacing: 0.22em; text-transform: uppercase; margin: 14px 0 0; }
.tp-hours { color: var(--muted); font-size: 15px; margin: 12px 0 26px; }
.tp-hero__intro .btn { margin-top: 4px; }
@media (max-width: 640px) {
  .tp-hero__wrap { grid-template-columns: 1fr; }
  .tp-hero__photo { width: clamp(164px, 46vw, 240px); outline-offset: 8px; }
}

/* ---- gallery: a thin strip of plates + lightbox ----
   Client-requested (2026-08-01), replacing the mosaic of tiles; re-cut on
   2026-08-20 from one plate at a time into a THIN STRIP, because the owner
   asked for "a thin photo gallery carousel about 4 pages fitting nicely on
   the page, with extra that you can click through if you press the arrow".
   No autoplay, still: nothing moves until the visitor moves it.

   THE SIZE IS DECIDED HERE AND NOWHERE ELSE. Verified before it was relied
   on: therapist.js reads offsetLeft/offsetWidth/clientWidth and sets nothing
   but the track's transform, so every number below is free to change without
   the script knowing. What moved is WHERE in here it is decided: the three
   breakpoint rules used to carry a width each, and now carry --tp-view (how
   many plates the stage shows), --tp-cap (how wide one may grow to when there
   are too few to fill the strip) and the plate's shape. The width itself is
   worked out once, from --tp-view and --tp-gap, in the .tp-car__slide rule.
   The only contract is unchanged: the plates must total more than the stage,
   or there is nothing to slide.

   THE FRACTION ON THE END OF --tp-view is the part-plate that peeks past the
   right edge. It is the only thing that tells a visitor there is more to see,
   so it is never a whole number. ---- */
/* HER NAME, THEN HER PICTURES: the strip sits under the hero on the SAME
   ground, so the two read as one opening rather than as two bands. That makes
   the tone change fall where the subject changes, at the tab bar below, which
   is the relief that keeps the page from reading as one long stretch. There is
   no padding-top for the same reason: the hero's short foot is the whole gap
   between her name and her photographs, owned in one place. */
.tp-gallery { background: var(--black); padding: 0 0 clamp(46px, 6.5vw, 84px); }
.tp-gallery__eyebrow {
  font-size: 11px; font-weight: 600; letter-spacing: 0.28em; text-transform: uppercase;
  color: var(--gold); margin: 0;
}

/* --tp-gap is declared on the carousel rather than on the track because the
   plate width is worked out FROM it. Two numbers that must agree, in one
   place: change the gap and the width follows it. */
.tp-car {
  position: relative;
  --tp-gap: clamp(10px, 1.3vw, 20px);
  --tp-view: 1.7;      /* a phone: one plate and most of the second */
  --tp-cap: 214px;
}
.tp-car__head {
  display: flex; align-items: center; justify-content: space-between;
  gap: 20px; flex-wrap: wrap;
  margin: 0 0 clamp(18px, 2.4vw, 28px);
}
.tp-car__controls { display: flex; align-items: center; gap: clamp(10px, 1.2vw, 16px); }
.tp-car__counter {
  margin: 0; color: var(--muted);
  font-size: 11px; font-weight: 600; letter-spacing: 0.22em;
  font-variant-numeric: tabular-nums;
}
.tp-car__arrow {
  flex: 0 0 auto; width: 44px; height: 44px; padding: 0;
  display: inline-flex; align-items: center; justify-content: center;
  cursor: pointer; border-radius: 50%;
  background: transparent;
  border: 1px solid var(--hairline-2);
  color: var(--gold-bright, var(--gold-soft));
  font-family: var(--font-body); font-size: 16px; line-height: 1;
  transition: background 0.4s var(--ease-lux, var(--ease)), color 0.4s var(--ease-lux, var(--ease)),
              border-color 0.4s var(--ease-lux, var(--ease)), transform 0.4s var(--ease-lux, var(--ease)),
              box-shadow 0.4s var(--ease-lux, var(--ease)), opacity 0.4s var(--ease);
}
.tp-car__arrow:hover:not(:disabled) {
  background: var(--gold); border-color: var(--gold); color: var(--oxblood);
  transform: translateY(-2px); box-shadow: var(--glow-gold);
}
.tp-car__arrow:disabled { opacity: 0.26; cursor: default; }

/* The stage clips; the track moves.
   overflow: clip rather than hidden, because hidden still makes a scroll
   container: giving focus to a plate off stage has the browser scroll that
   container to reveal it, which slides the strip out from under the transform
   and leaves the carousel showing one photograph while it believes it is
   showing another. clip cannot scroll, so that cannot happen. The hidden
   above it is the fallback for engines that do not know clip, where
   therapist.js pins scrollLeft back to zero instead. */
.tp-car__stage {
  position: relative;
  overflow: hidden;
  overflow: clip;
  touch-action: pan-y;     /* vertical scrolling stays the page's, horizontal is ours */
  cursor: grab;
}
.tp-car__stage.is-dragging { cursor: grabbing; }
.tp-car__track {
  position: relative;      /* so a slide's offsetLeft is measured against the track */
  display: flex; list-style: none;
  margin: 0; padding: 0;
  gap: var(--tp-gap);
  transform: translate3d(0, 0, 0);
  transition: transform 0.85s var(--ease-lux, var(--ease));
  -webkit-user-select: none; user-select: none;
}
.tp-car__track.is-instant { transition: none; }   /* dragging, and the first measure */

/* THE WIDTH, WORKED OUT ONCE. --tp-view plates and the gaps between them fill
   the stage, so the basis is the stage less those gaps, shared out. The 100% is
   the track's own width, which is the stage's.

   GROW 1, SHRINK 0, AND A CAP, which is what holds the strip at 1, at 4 and at
   30 photographs. Shrink 0 means a long strip keeps its plate size and spills
   past the stage, which is the overflow there is to page through. Grow 1 means
   a SHORT strip - two or three photographs, less than fills the stage - opens
   out to fill it instead of huddling at one end, and --tp-cap stops two
   photographs blowing up into two billboards. One photograph is not this case
   at all: it is .tp-car.is-solo at the foot of this block, a single plate.

   THE SHAPE IS LANDSCAPE AND GETS SQUARER AS THE SCREEN NARROWS. The owner
   asked for THIN, and four plates across a desktop are 250-ish wide, so 3/2
   keeps the whole band under 180px tall. A phone plate is 150-ish wide and the
   same shape there would be a 100px letterbox with a face in it, so the plate
   squares up as it shrinks and the face keeps its room. */
.tp-car__slide {
  flex: 1 0 calc((100% - var(--tp-gap) * (var(--tp-view) - 1)) / var(--tp-view));
  max-width: var(--tp-cap);
  position: relative;
  aspect-ratio: 1 / 1;
  opacity: 0.4; filter: saturate(0.7);
  transition: opacity 0.7s var(--ease-lux, var(--ease)), filter 0.7s var(--ease-lux, var(--ease));
}
/* is-active is set by therapist.js on every plate that is FULLY on the stage,
   not on one chosen plate: with four across, three dimmed and one lit would be
   a strip that looks broken. Only the part-plate at the edge stays dim, which
   is how it reads as the next one rather than as one of these. */
.tp-car__slide.is-active { opacity: 1; filter: none; }
@media (min-width: 561px) {
  .tp-car { --tp-view: 2.4; --tp-cap: 330px; }   /* a tablet: two and two fifths */
  .tp-car__slide { aspect-ratio: 5 / 4; }
}
@media (min-width: 1001px) {
  /* THREE ACROSS NOW, NOT FOUR. The owner asked twice for these bigger: the
     first time bought 4.33 -> 4.15 and 3/2 -> 5/4, which widened the plate by
     11px and stood it up, and she asked again. A tenth of a plate was never
     going to be the answer, so this drops a whole one: 4.15 -> 3.15 takes a
     desktop plate from about 250 wide to about 335, a third bigger in each
     direction, and the band grows with it because the shape is unchanged.

     THE SHAPE IS DELIBERATELY NOT TOUCHED. Standing the plate up as well would
     have been the other half of "bigger" and it is a different page: 5/4 at
     this width is already a 268px band, and a portrait plate would push the
     tabs below the fold on a laptop, which is the exact fault the last
     re-baseline was fixing. One lever at a time, and she can see this one.

     WHAT DOES NOT CHANGE, and both were checked rather than assumed:
     --tp-cap stays at 430 because it is not binding here - a 1100 stage puts
     a plate at 335 - and it only ever bites on a strip too short to fill the
     stage, where the plates grow to a third of it regardless of --tp-view.
     The fraction on the end stays, because that part-plate peeking past the
     edge is the only thing telling a visitor there is a second page.

     ONE THING GETS BETTER BY ACCIDENT: a masseuse with exactly four
     photographs used to have 4 plates against a 4.15 view, so they fitted and
     the strip would not slide at all. Four against 3.15 overflows, so her
     arrows work now. */
  .tp-car { --tp-view: 3.15; --tp-cap: 430px; }
  .tp-car__slide { aspect-ratio: 5 / 4; }
}

.tp-car__plate {
  position: absolute; inset: 0;
  display: block; width: 100%; height: 100%;
  margin: 0; padding: 0; border: none;
  overflow: hidden; border-radius: var(--radius);
  background: linear-gradient(160deg, #5A1B2B, #28090F);
  cursor: zoom-in;
}
/* The photograph is taken out of flow so it can never drive the plate's height:
   the box comes from the slide's ratio alone, whatever shape the upload is. */
.tp-car__plate img {
  position: absolute; inset: 0;
  width: 100%; height: 100%; object-fit: cover;
  object-position: 50% 30%;      /* faces sit high in a portrait */
  display: block;
  transform: scale(1.03);        /* settles out of the zoom as it becomes active */
  transition: transform 1.6s var(--ease-lux, var(--ease)), filter 1.6s var(--ease-lux, var(--ease));
  -webkit-user-drag: none;
}
.tp-car__slide.is-active .tp-car__plate img { transform: scale(1); }
.tp-car__slide.is-active .tp-car__plate:hover img,
.tp-car__slide.is-active .tp-car__plate:focus-visible img { transform: scale(1.06); filter: brightness(1.05); }

/* the gold hairline the studio gallery uses, breathing in on the active plate */
/* the inset scales with the plate: 14px inside a 780px plate was a hairline,
   and inside a 240px one it would be a border */
.tp-car__plate::after {
  content: ""; position: absolute; inset: clamp(8px, 1vw, 14px);
  border: 1px solid rgba(227, 200, 138, 0);
  transition: border-color 0.7s var(--ease-lux, var(--ease)), inset 0.7s var(--ease-lux, var(--ease));
  pointer-events: none;
}
.tp-car__slide.is-active .tp-car__plate::after { border-color: rgba(227, 200, 138, 0.34); }
.tp-car__slide.is-active .tp-car__plate:hover::after,
.tp-car__slide.is-active .tp-car__plate:focus-visible::after { border-color: rgba(227, 200, 138, 0.62); inset: clamp(11px, 1.3vw, 18px); }

/* Sized down with the plate. The scrim used to start 62px up a 585px-tall
   photograph, a tenth of it; the same 62px up a 170px plate is more than a
   third of the picture under a wash. */
.tp-car__cap {
  position: absolute; left: 0; right: 0; bottom: 0;
  padding: clamp(22px, 3.2vw, 40px) clamp(11px, 1.2vw, 18px) clamp(9px, 1vw, 14px);
  background: linear-gradient(to top, rgba(20, 5, 11, 0.88), rgba(20, 5, 11, 0));
  color: var(--ivory); text-align: left;
  font-size: 10.5px; font-weight: 600; letter-spacing: 0.14em; text-transform: uppercase; line-height: 1.4;
  opacity: 0; transform: translateY(8px);
  transition: opacity 0.6s var(--ease-lux, var(--ease)), transform 0.6s var(--ease-lux, var(--ease));
  pointer-events: none;
}
.tp-car__slide.is-active .tp-car__cap { opacity: 1; transform: none; }

/* Dots: each one keeps a fixed 26px slot, so the active pill can grow into a
   gold bar without the row shuffling under the cursor.

   IT WRAPS, AND IT HAS TO. There is one dot per photograph and Kate will add
   photographs, so the row grows 32px each time and stops fitting a phone at
   twelve. Measured in Chrome at 375px before this was here: the document's
   scrollWidth went 375 -> 377 at twelve photographs, 505 at twenty and 665 at
   thirty, which is the whole page sliding sideways under a thumb, not only the
   gallery. Wrapping holds at any number, and suite 18.33 keeps it honest. */
.tp-car__dots {
  display: flex; align-items: center; justify-content: center;
  flex-wrap: wrap; max-width: 100%;
  /* row-gap goes to 0 now the dot itself is 44px tall: the target supplies its
     own breathing room, and with thirty photographs wrapped over three rows the
     old 4px on top of 44px made the index taller than the photographs. */
  gap: 6px; row-gap: 0;   /* the shorthand first, then the row override, or it wins back */
  margin-top: clamp(16px, 2.2vw, 26px);
}
/* 26x24 WAS THE PAINTED DOT, NOT THE TARGET. The visible mark stays exactly the
   size it was, drawn by ::after; the button around it grows to 44px of height so
   a thumb has something to land on. Kate will add photographs, and thirty of
   these wrap across three rows under a phone thumb, which is precisely where an
   undersized target costs somebody the tap. row-gap on .tp-car__dots absorbs the
   extra height, so the row does not grow three times over. */
.tp-car__dot {
  position: relative; flex: 0 0 auto;
  width: 26px; height: 44px; padding: 0;
  background: none; border: none; cursor: pointer;
}
.tp-car__dot::after {
  content: ""; position: absolute; top: 50%; left: 50%;
  width: 7px; height: 7px; margin: -3.5px 0 0 -3.5px;
  border-radius: 999px;
  border: 1px solid var(--gold-deep);
  transition: width 0.5s var(--ease-lux, var(--ease)), margin 0.5s var(--ease-lux, var(--ease)),
              background 0.5s var(--ease-lux, var(--ease)), border-color 0.5s var(--ease-lux, var(--ease));
}
.tp-car__dot:hover::after { border-color: var(--gold); }
.tp-car__dot.is-on::after {
  width: 22px; margin-left: -11px;
  background: var(--gold); border-color: var(--gold);
}

/* ONE PHOTOGRAPH: a single static plate, no chrome, nothing to drag. It is
   explicitly NOT a strip of one - a lone 240px plate stranded in a 1112px band
   is the thing this rule exists to prevent - so the flex sizing above is
   switched off here and the plate takes the measure up to a readable 680px.
   therapist.js puts is-solo on whenever there is fewer than one plate to page,
   so this is a state, not a special case anyone has to remember. */
.tp-car.is-solo .tp-car__controls,
.tp-car.is-solo .tp-car__dots { display: none; }
.tp-car.is-solo .tp-car__stage { cursor: default; }
.tp-car.is-solo .tp-car__slide { flex: 0 0 auto; width: 100%; max-width: 680px; opacity: 1; filter: none; }

/* announced to screen readers, never drawn */
.tp-sr {
  position: absolute !important;
  width: 1px; height: 1px; margin: -1px; padding: 0; border: 0;
  clip: rect(0 0 0 0); clip-path: inset(50%);
  overflow: hidden; white-space: nowrap;
}

.tp-lightbox {
  --lb-pad: clamp(12px, 2vw, 26px);
  --lb-cap: 0px;   /* only a captioned photograph gives up any height */
  position: fixed; inset: 0; z-index: 1200; background: rgba(16, 5, 10, 0.96);
  display: none; align-items: center; justify-content: center;
  padding: var(--lb-pad); cursor: zoom-out; overscroll-behavior: contain;
}
.tp-lightbox.has-cap { --lb-cap: clamp(38px, 4.2vw, 54px); }
.tp-lightbox.open { display: flex; animation: tpFade 0.35s var(--ease); }
/* The figure takes the whole padded overlay, so it has a definite height for
   the photograph's own max-height to resolve against. */
.tp-lightbox__figure {
  margin: 0; width: 100%; height: 100%;
  display: flex; align-items: center; justify-content: center;
}
.tp-lightbox img {
  width: auto; height: auto;
  max-width: 100%; max-height: calc(100% - var(--lb-cap));
  object-fit: contain;
  border-radius: var(--radius);
  outline: 1px solid var(--hairline-2); outline-offset: 7px;
  box-shadow: 0 40px 110px -28px rgba(0, 0, 0, 0.9);
}
.tp-lightbox__cap {
  position: absolute; left: 50%; bottom: clamp(12px, 1.8vw, 22px);
  transform: translateX(-50%);
  max-width: min(60ch, calc(100% - var(--lb-pad) * 2));
  margin: 0; text-align: center;
  color: var(--muted); font-size: 13.5px; letter-spacing: 0.05em; line-height: 1.5;
}
.tp-lightbox__close {
  position: absolute; top: clamp(12px, 1.8vw, 24px); right: clamp(12px, 1.8vw, 24px);
  width: 44px; height: 44px; border-radius: 50%; cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center;
  background: rgb(var(--canvas-rgb) / 0.72);
  border: 1px solid var(--hairline-2);
  color: var(--gold-bright, var(--gold-soft)); font-size: 21px; line-height: 1;
  transition: border-color 0.3s var(--ease), color 0.3s var(--ease);
}
.tp-lightbox__close:hover { border-color: var(--gold); color: var(--ivory); }
/* Nothing in the carousel autoplays, so reduced motion only has to remove the
   travel: the plate still changes, it simply arrives instead of gliding. */
@media (prefers-reduced-motion: reduce) {
  .tp-lightbox.open { animation: none; }
  .tp-car__track, .tp-car__slide, .tp-car__arrow,
  .tp-car__plate img, .tp-car__plate::after, .tp-car__cap, .tp-car__dot::after { transition: none; }
  .tp-car__plate img,
  .tp-car__slide.is-active .tp-car__plate:hover img,
  .tp-car__slide.is-active .tp-car__plate:focus-visible img { transform: none; }
  .tp-car__cap { transform: none; }
  .tp-car__arrow:hover:not(:disabled) { transform: none; }
}

/* ---- tabs ---- */
/* The lift to --black-2 starts HERE, at the tab bar, because this is where the
   page stops being about her and starts being about what you can book. The
   small head is that change having somewhere to land: with none at all the bar
   sat on the join itself and the tone looked like it belonged to the strip
   above rather than to the tabs. */
.tp-tabs-section { background: var(--black-2); padding: clamp(8px, 1.2vw, 16px) 0 clamp(80px, 11vw, 140px); }
.tp-tabbar {
  display: flex; gap: 10px;
  position: sticky; top: 74px; z-index: 20;
  scroll-margin-top: 74px;
  background: var(--black-2);
  border-bottom: 1px solid var(--hairline);
  margin-bottom: clamp(40px, 6vw, 64px);
  padding-top: 10px;
  overflow-x: auto;
}
.tp-tab {
  font-family: var(--font-body); font-size: 11.5px; font-weight: 600;
  letter-spacing: 0.18em; text-transform: uppercase;
  color: var(--muted); background: none; border: none; cursor: pointer;
  padding: 18px 20px; position: relative; white-space: nowrap;
  transition: color 0.35s var(--ease);
}
.tp-tab::after {
  content: ""; position: absolute; left: 20px; right: 20px; bottom: -1px; height: 1.5px;
  background: var(--gold); transform: scaleX(0); transition: transform 0.45s var(--ease);
}
.tp-tab:hover { color: var(--ivory); }
.tp-tab.is-active { color: var(--ivory); }
.tp-tab.is-active::after { transform: scaleX(1); }

.tp-panel { display: none; }
.tp-panel.is-active { display: block; animation: tpFade 0.55s var(--ease); }
@keyframes tpFade { from { opacity: 0; transform: translateY(12px); } to { opacity: 1; transform: none; } }
@media (prefers-reduced-motion: reduce) { .tp-panel.is-active { animation: none; } }

/* ---- profile ---- */
.tp-bio { max-width: 62ch; }
.tp-bio p { font-size: 18.5px; line-height: 1.7; color: var(--ivory); margin: 0 0 22px; }
.tp-bio p:first-child::first-letter {
  font-family: var(--font-display); font-size: 3.4em; line-height: 0.78;
  float: left; padding: 8px 14px 0 0; color: var(--gold); font-weight: 340;
}

/* ---- book online ---- */
.tp-book { max-width: 780px; }
.tp-book__note { color: var(--muted); font-size: 15.5px; margin: 0 0 26px; }
.tp-services { display: flex; flex-direction: column; }
.tp-service {
  display: flex; align-items: center; justify-content: space-between; gap: 20px;
  padding: 24px 0; border-top: 1px solid var(--hairline);
  transition: padding 0.3s var(--ease);
}
.tp-service:last-child { border-bottom: 1px solid var(--hairline); }
.tp-service__head { display: flex; align-items: baseline; gap: 14px; flex-wrap: wrap; }
.tp-service__head h3 { font-family: var(--font-display); font-weight: 380; font-size: 22px; margin: 0; color: var(--ivory); }
.tp-service__dur { font-size: 10.5px; font-weight: 600; letter-spacing: 0.16em; text-transform: uppercase; color: var(--muted); }
.tp-service__desc { color: var(--muted); font-size: 15px; margin: 7px 0 0; max-width: 48ch; }
.tp-service__right { display: flex; align-items: center; gap: 20px; flex-shrink: 0; }
.tp-service__price { font-family: var(--font-display); font-size: 24px; color: var(--gold); font-weight: 380; }
.tp-service__btn { padding: 11px 24px; font-size: 11px; }
@media (max-width: 540px) {
  .tp-service { flex-direction: column; align-items: flex-start; gap: 14px; }
  .tp-service__right { width: 100%; justify-content: space-between; }
}

/* ---- location ---- */
.tp-location { grid-template-columns: 0.9fr 1.1fr; gap: clamp(24px, 4vw, 56px); align-items: center; }
.tp-panel.tp-location.is-active { display: grid; }
.tp-location__text h3 { font-family: var(--font-display); font-weight: 380; font-size: 28px; margin: 0 0 12px; color: var(--ivory); }
.tp-location__addr { color: var(--muted); font-size: 17px; margin: 0 0 20px; line-height: 1.55; }
.tp-maplink {
  color: var(--gold-soft); font-weight: 600; text-decoration: none;
  font-size: 11.5px; letter-spacing: 0.16em; text-transform: uppercase;
  border-bottom: 1px solid var(--gold);
  padding-bottom: 3px;
  transition: color 0.3s var(--ease);
}
.tp-maplink:hover { color: var(--gold); }
.tp-location__img { margin: 0; border-radius: var(--radius); overflow: hidden; }
.tp-location__img img { width: 100%; height: 100%; object-fit: cover; aspect-ratio: 3 / 2; }
@media (max-width: 640px) { .tp-panel.tp-location.is-active { grid-template-columns: 1fr; } }

/* ============================================================
   Velvet plates — full-bleed imagery that drifts behind the
   page as you scroll, so the content sections read as panels
   floating over the studio. Paired with the drift handler in
   site.js (data-drift). Hero drift is handled separately via
   background-position so it does not fight heroDrift's
   transform animation.
   ============================================================ */
.vm-plate {
  position: relative;
  height: clamp(320px, 62vh, 620px);
  overflow: hidden;
  isolation: isolate;
  background: var(--oxblood, #0E0002);
}
.vm-plate__img {
  position: absolute;
  left: 0;
  top: -16%;
  width: 100%;
  height: 132%;          /* headroom so the drift never exposes an edge */
  object-fit: cover;
  will-change: transform;
}
.vm-plate::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(
    180deg,
    var(--oxblood, #0E0002) 0%,
    rgb(var(--canvas-rgb) / 0.34) 26%,
    rgb(var(--canvas-rgb) / 0.34) 74%,
    var(--oxblood, #0E0002) 100%
  );
}
.vm-plate__line {
  position: absolute;
  inset: auto 0 13% 0;
  z-index: 2;
  margin: 0;
  padding: 0 1.5rem;
  text-align: center;
  font-family: var(--font-display, Georgia, serif);
  font-style: italic;
  font-size: clamp(1.15rem, 2.5vw, 1.9rem);
  color: var(--ivory, #F2E9DC);
  text-shadow: 0 2px 30px rgba(0, 0, 0, 0.75);
}

@media (prefers-reduced-motion: reduce) {
  .vm-plate__img { transform: none !important; }
}

/* ============================================================
   LUX PASS — micro-interactions, ornament, and texture.
   Everything here layers on top of the existing rules; nothing
   below changes layout. Respects prefers-reduced-motion.
   ============================================================ */
:root {
  --ease-lux: cubic-bezier(0.22, 0.61, 0.36, 1);
  --glow-gold: 0 10px 40px -12px rgba(201, 164, 92, 0.45);
  --lift-shadow: 0 24px 50px -24px rgba(0, 0, 0, 0.85);
}

/* selection + scrollbar — the details nobody notices until they do */
::selection { background: var(--gold); color: var(--oxblood); }
::-webkit-scrollbar { width: 11px; }
::-webkit-scrollbar-track { background: var(--oxblood); }
::-webkit-scrollbar-thumb {
  background: var(--wine-lift);
  border-radius: 6px;
  border: 2px solid var(--oxblood);
}
::-webkit-scrollbar-thumb:hover { background: var(--gold-deep); }

/* one consistent, elegant focus ring everywhere */
a:focus-visible, button:focus-visible, input:focus-visible,
select:focus-visible, textarea:focus-visible {
  outline: 2px solid var(--gold);
  outline-offset: 3px;
  border-radius: var(--radius);
}

/* ---- buttons: gold sheen sweep + lift ---------------------- */
.btn { position: relative; overflow: hidden; }
.btn::before {
  content: "";
  position: absolute;
  top: 0; bottom: 0; left: -80%;
  width: 60%;
  background: linear-gradient(100deg, transparent 0%,
              rgba(227, 200, 138, 0.35) 50%, transparent 100%);
  transform: skewX(-18deg);
  transition: left 0.7s var(--ease-lux);
  pointer-events: none;
}
.btn:hover::before, .btn:focus-visible::before { left: 120%; }
.btn:hover {
  transform: translateY(-2px);
  box-shadow: var(--glow-gold);
}
.btn:active { transform: translateY(0) scale(0.985); transition-duration: 0.12s; }

/* ---- therapist cards: cinematic zoom + pre-warmed CTA ------ */
.therapist-card { transition: transform 0.6s var(--ease-lux), box-shadow 0.6s var(--ease-lux); }
.therapist-card:hover { transform: translateY(-8px); box-shadow: var(--lift-shadow); }
.therapist-card .therapist-photo { overflow: hidden; }
.therapist-card .therapist-photo img {
  transition: transform 1.4s var(--ease-lux), filter 1.4s var(--ease-lux);
}
.therapist-card:hover .therapist-photo img {
  transform: scale(1.06);
  filter: brightness(1.06) saturate(1.04);
}
.therapist-card:hover .t-book {
  border-color: var(--gold);
  color: var(--gold-bright);
  box-shadow: inset 0 0 0 1px rgba(201, 164, 92, 0.25);
}

/* ---- gallery: hairline gold frame that breathes in --------- */
.gallery-grid figure { position: relative; }
.gallery-grid figure::after {
  content: "";
  position: absolute; inset: 10px;
  border: 1px solid rgba(227, 200, 138, 0.0);
  transition: border-color 0.6s var(--ease-lux), inset 0.6s var(--ease-lux);
  pointer-events: none;
}
.gallery-grid figure:hover::after { border-color: rgba(227, 200, 138, 0.55); inset: 14px; }
.gallery-grid figure img { transition: transform 1.6s var(--ease-lux), filter 1.6s var(--ease-lux); }
.gallery-grid figure:hover img { transform: scale(1.07); filter: brightness(1.05); }

/* ---- reviews: a quiet oversized quote mark ----------------- */
.review { position: relative; transition: border-color 0.5s var(--ease-lux); }
.review::before {
  content: "\201C";
  position: absolute;
  top: 2px; right: 4px;
  font-family: var(--font-display, Georgia, serif);
  font-size: 84px; line-height: 1;
  color: var(--gold);
  opacity: 0.14;
  pointer-events: none;
}
.review:hover { border-color: var(--gold-bright); }

/* ---- location cards ---------------------------------------- */
.location-card { transition: transform 0.55s var(--ease-lux), border-color 0.55s var(--ease-lux), box-shadow 0.55s var(--ease-lux); }
.location-card:hover { transform: translateY(-6px); box-shadow: var(--lift-shadow); }
.location-card .num { transition: color 0.5s var(--ease-lux); }
.location-card:hover .num { color: var(--gold-bright); }

/* ---- staggered grid reveals -------------------------------- */
.reveal > * { transition-delay: inherit; }
.therapist-grid.reveal.in > *,
.gallery-grid.reveal.in > *,
.location-grid.reveal.in > *,
.review-grid.reveal.in > * { animation: luxRise 0.9s var(--ease-lux) both; }
/* The team grid's stagger is on the CARDS, not here: public/site.js gives each
   card .reveal plus d1/d2/d3 on i % 3, and nothing anywhere puts .reveal on the
   grid ELEMENT, so these two lines are inert for this one grid today and are
   kept only so the four grids below read alike. The 4n step went with the
   fourth column. */
.therapist-grid.reveal.in > *:nth-child(2n) { animation-delay: 0.08s; }
.therapist-grid.reveal.in > *:nth-child(3n) { animation-delay: 0.16s; }
.gallery-grid.reveal.in  > *:nth-child(2n) { animation-delay: 0.1s; }
.gallery-grid.reveal.in  > *:nth-child(3n) { animation-delay: 0.2s; }
.location-grid.reveal.in > *:nth-child(2n) { animation-delay: 0.12s; }
.review-grid.reveal.in   > *:nth-child(2n) { animation-delay: 0.12s; }
.review-grid.reveal.in   > *:nth-child(3n) { animation-delay: 0.24s; }
@keyframes luxRise {
  from { opacity: 0; transform: translateY(22px); }
  to   { opacity: 1; transform: none; }
}

/* ---- booking picker (beats therapist.html inline styles) --- */
.tp-book .bk__day {
  transition: border-color 0.35s var(--ease-lux), background 0.35s var(--ease-lux),
              transform 0.35s var(--ease-lux), box-shadow 0.35s var(--ease-lux);
}
.tp-book .bk__day:hover { transform: translateY(-3px); box-shadow: var(--glow-gold); }
.tp-book .bk__day.is-on { box-shadow: inset 0 0 0 1px var(--gold), var(--glow-gold); }
.tp-book .bk__slot {
  transition: border-color 0.3s var(--ease-lux), background 0.3s var(--ease-lux),
              color 0.3s var(--ease-lux), transform 0.3s var(--ease-lux),
              box-shadow 0.3s var(--ease-lux);
}
.tp-book .bk__slot:not(:disabled):hover {
  transform: translateY(-2px);
  border-color: var(--gold);
  color: var(--gold-bright);
  box-shadow: var(--glow-gold);
}
.tp-book .bk__slot.is-on,
.tp-book .bk__slot[aria-pressed="true"] {
  background: var(--gold);
  border-color: var(--gold);
  color: var(--oxblood);
  font-weight: 600;
  box-shadow: var(--glow-gold);
}
.tp-book .tp-tab { transition: color 0.4s var(--ease-lux); position: relative; }
.tp-book .tp-tab::after,
.tp-tabbar .tp-tab::after {
  content: "";
  position: absolute; left: 50%; bottom: -1px;
  width: 0; height: 1px;
  background: var(--gold);
  transition: width 0.45s var(--ease-lux), left 0.45s var(--ease-lux);
}
.tp-tabbar .tp-tab:hover::after { width: 60%; left: 20%; }
.tp-tabbar .tp-tab.is-active::after { width: 100%; left: 0; }

/* ---- ornament dividers (gold filigree, screen-blended) ----- */
.vm-ornament {
  height: 84px;
  margin: -6px auto;
  max-width: 720px;
  background: url('/uploads/velvet/ornament.webp') center / contain no-repeat;
  mix-blend-mode: screen;
  opacity: 0.8;
  pointer-events: none;
}

/* ---- textured bands ---------------------------------------- */
.band--booking {
  background:
    linear-gradient(180deg, rgb(var(--canvas-rgb) / 0.92), rgb(var(--canvas-rgb) / 0.86)),
    url('/uploads/velvet/velvet.webp') center / cover no-repeat fixed;
}
.band--hiring {
  background:
    linear-gradient(90deg, rgb(var(--canvas-rgb) / 0.96) 0%, rgb(var(--canvas-rgb) / 0.82) 55%, rgb(var(--canvas-rgb) / 0.55) 100%),
    url('/uploads/velvet/corridor.webp') right center / cover no-repeat;
}

@media (prefers-reduced-motion: reduce) {
  .btn::before { display: none; }
  .therapist-grid.reveal.in > *, .gallery-grid.reveal.in > *,
  .location-grid.reveal.in > *, .review-grid.reveal.in > * { animation: none; }
  .band--booking { background-attachment: scroll; }
}
@media (max-width: 700px) {
  .band--booking { background-attachment: scroll; }  /* iOS fixed-bg jank */
  .vm-ornament { height: 60px; max-width: 86vw; }
}

/* ============================================================
   LUX PASS II — "The people you will meet" backdrop art.
   Ornament ghosted wide behind the card grid; the lotus sinks
   into the dead space right of the heading, dissolved by a
   radial mask so it melts into the oxblood rather than sitting
   on it. Both layers are screen-blended and behind content.
   ============================================================ */
.section--therapists { position: relative; overflow: hidden; }
.section--therapists > * { position: relative; z-index: 1; }

/* gold filigree, stretched ever-so-subtly behind the cards */
.section--therapists::before {
  content: "";
  position: absolute;
  z-index: 0;
  left: 50%;
  top: 56%;
  width: min(1560px, 130vw);
  height: 440px;
  transform: translate(-50%, -50%);
  background: url('/uploads/velvet/ornament.webp') center / 100% auto no-repeat;
  mix-blend-mode: screen;
  opacity: 0.12;
  pointer-events: none;
}

/* the lotus, melting into the space beside the heading */
.section--therapists::after {
  content: "";
  position: absolute;
  z-index: 0;
  top: clamp(-20px, 2vw, 40px);
  right: max(-90px, -7vw);
  width: min(44vw, 540px);
  aspect-ratio: 1600 / 2549;
  background: url('/uploads/velvet/lotus.webp') center top / cover no-repeat;
  mix-blend-mode: screen;
  opacity: 0.82;
  -webkit-mask-image: radial-gradient(ellipse 62% 52% at 52% 38%, #000 30%, transparent 74%);
          mask-image: radial-gradient(ellipse 62% 52% at 52% 38%, #000 30%, transparent 74%);
  pointer-events: none;
}

@media (max-width: 900px) {
  .section--therapists::after { width: 62vw; right: -14vw; opacity: 0.6; }
  .section--therapists::before { opacity: 0.09; }
}
@media (max-width: 560px) {
  .section--therapists::after { display: none; }  /* too crowded on phones */
}

/* ============================================================
   Journal — the latest posts, at the foot of the home page.
   The section is markup-hidden until site.js has actually got
   posts back, so an unbuilt or empty blog leaves no trace.
   ============================================================ */
.section--journal { background: var(--black); }
.section--journal[hidden] { display: none; }

/* the italic gold clause in an oversized section heading */
.section-head h2 em { font-style: italic; color: var(--gold-soft); }

.journal-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: clamp(24px, 3vw, 44px);
}
@media (max-width: 940px) { .journal-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 620px) { .journal-grid { grid-template-columns: minmax(0, 1fr); } }

.journal-card { min-width: 0; }
.journal-card__link {
  display: flex; flex-direction: column; height: 100%;
  color: inherit; text-decoration: none;
  border-radius: var(--radius);
}
.journal-card__link:focus-visible { outline: 2px solid var(--gold); outline-offset: 6px; }

.journal-card__media {
  position: relative; margin: 0 0 20px; overflow: hidden;
  aspect-ratio: 3 / 2; border-radius: var(--radius);
  background: linear-gradient(160deg, #5A1B2B, #28090F);
}
.journal-card__media img {
  width: 100%; height: 100%; object-fit: cover;
  filter: saturate(0.92);
  transition: transform 1.4s var(--ease), filter 1.4s var(--ease);
}
.journal-card__link:hover .journal-card__media img,
.journal-card__link:focus-visible .journal-card__media img {
  transform: scale(1.06); filter: brightness(1.05) saturate(1.04);
}
/* the same gold hairline as the studio gallery */
.journal-card__media::after {
  content: ""; position: absolute; inset: 10px;
  border: 1px solid rgba(227, 200, 138, 0);
  transition: border-color 0.6s var(--ease), inset 0.6s var(--ease);
  pointer-events: none;
}
.journal-card__link:hover .journal-card__media::after,
.journal-card__link:focus-visible .journal-card__media::after {
  border-color: rgba(227, 200, 138, 0.55); inset: 14px;
}

.journal-card__meta {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: 6px 10px;
  margin: 0 0 12px;
  font-size: 10.5px; font-weight: 600; letter-spacing: 0.2em; text-transform: uppercase;
  color: var(--gold);
}
.journal-card__meta .journal-card__attach { color: var(--muted); }
.journal-card__meta .journal-card__sep { color: var(--gold-deep); }
.journal-card__title {
  font-family: var(--font-display); font-weight: 380;
  font-size: clamp(22px, 2vw, 26px); line-height: 1.2; letter-spacing: -0.01em;
  margin: 0; color: var(--ivory);
  transition: color 0.35s var(--ease);
}
.journal-card__link:hover .journal-card__title,
.journal-card__link:focus-visible .journal-card__title { color: var(--gold-soft); }
.journal-card__excerpt {
  margin: 12px 0 0; color: var(--muted);
  font-size: 15.5px; line-height: 1.62;
}
.journal-card__foot { margin-top: auto; padding-top: 20px; }
.journal-card__rule { height: 1px; background: var(--hairline); position: relative; overflow: hidden; }
.journal-card__rule::after {
  content: ""; position: absolute; inset: 0; background: var(--gold);
  transform: scaleX(0); transform-origin: left;
  transition: transform 0.7s var(--ease);
}
.journal-card__link:hover .journal-card__rule::after,
.journal-card__link:focus-visible .journal-card__rule::after { transform: scaleX(1); }
.journal-card__more {
  margin-top: 14px;
  display: inline-flex; align-items: center; gap: 8px;
  font-size: 11px; font-weight: 600; letter-spacing: 0.16em; text-transform: uppercase;
  color: var(--muted);
  transition: color 0.35s var(--ease);
}
.journal-card__link:hover .journal-card__more,
.journal-card__link:focus-visible .journal-card__more { color: var(--gold-soft); }
.journal-card__more span:last-child { transition: transform 0.4s var(--ease); }
.journal-card__link:hover .journal-card__more span:last-child { transform: translateX(5px); }

.journal-foot { margin-top: clamp(42px, 5vw, 68px); }

.journal-grid.reveal.in > * { animation: luxRise 0.9s var(--ease-lux) both; }
.journal-grid.reveal.in > *:nth-child(2n) { animation-delay: 0.1s; }
.journal-grid.reveal.in > *:nth-child(3n) { animation-delay: 0.2s; }

@media (prefers-reduced-motion: reduce) {
  .journal-grid.reveal.in > * { animation: none; }
  .journal-card__media img,
  .journal-card__media::after,
  .journal-card__rule::after,
  .journal-card__more span:last-child { transition: none; }
  .journal-card__link:hover .journal-card__media img { transform: none; }
}

/* ============================================================
   The location pin
   ------------------------------------------------------------
   One marker, used wherever a place is named: the city heading over a group of
   therapists, and the studio badge on a therapist's own page. An inline SVG
   rather than an emoji or a glyph, because a pin emoji renders as a different
   picture on every platform and picks up the system font's colour, and this one
   has to be the same red everywhere.

   aria-hidden on the svg: the place is already written next to it in words, so
   a screen reader announcing "pin" adds nothing but noise.
   ============================================================ */
.pin-mark {
  display: inline-flex; align-items: center; gap: 6px;
}
.pin-mark svg {
  flex: none;
  width: 1em; height: 1em;
  color: var(--pin);
  /* Optical alignment: a pin's visual weight sits above its point, so it rides
     a shade high against a cap height without this. */
  transform: translateY(0.06em);
}

/* the city under a therapist's name on her card, next to the pin */
.t-city {
  margin: 8px 0 0;
  color: var(--muted);
  font-size: 12.5px;
  font-weight: 500;
  letter-spacing: 0.1em;
  text-transform: uppercase;
}

/* ============================================================
   Stars
   ------------------------------------------------------------
   Two stacked rows of the same svg star: an unfilled one underneath and a
   filled one on top, clipped to a percentage. That is what lets 4.9 out of 5
   draw nine tenths of its fifth star instead of rounding to a number the
   studio did not earn.

   Sized in em so a star always matches the text beside it, at any breakpoint.
   ============================================================ */
.stars {
  position: relative;
  display: inline-block;
  line-height: 1;
  white-space: nowrap;
  flex: none;
}
.stars__back, .stars__fore { display: block; white-space: nowrap; }
.stars__back { color: var(--hairline-2, rgba(201, 164, 92, 0.30)); }
.stars__fore {
  position: absolute; inset: 0;
  overflow: hidden;
  color: var(--gold);
}
.stars svg { width: 1em; height: 1em; display: inline-block; vertical-align: top; }

/* the score line at the top of the reviews band */
.review-summary {
  display: flex; align-items: center; flex-wrap: wrap;
  gap: 10px 14px;
  margin: 0 0 clamp(28px, 4vw, 44px);
}
.review-summary .stars { font-size: 19px; }
.review-summary__score {
  font-family: var(--font-display);
  font-size: 22px; font-weight: 380; color: var(--ivory);
}
.review-summary__line { color: var(--muted); font-size: 14.5px; }

/* a single review's own stars, above its words */
.review .stars { font-size: 15px; margin: 0 0 12px; }

/* ============================================================
   Leaving a review
   ------------------------------------------------------------
   THE STARS ARE REAL RADIO INPUTS with the star as the label. The input itself
   is taken off screen rather than display:none, because a hidden input cannot be
   focused and the control would stop working for anyone using a keyboard.

   The fill is driven by [data-chosen] on the row: stars up to the chosen number
   are gold, the rest are the hairline. Hover and keyboard focus use the same
   selector shape, so one rule paints all three states instead of three rules
   drifting apart.
   ============================================================ */
.rv-form {
  margin: clamp(40px, 6vw, 72px) 0 0;
  padding: clamp(24px, 4vw, 38px);
  background: var(--wine);
  border: 1px solid var(--hairline);
  border-radius: var(--radius);
}
.rv-form__title {
  font-family: var(--font-display);
  font-weight: 380;
  font-size: clamp(21px, 2.6vw, 27px);
  color: var(--ivory);
  margin: 0 0 8px;
}
.rv-form__intro { color: var(--muted); font-size: 15px; line-height: 1.6; margin: 0 0 22px; max-width: 62ch; }
.rv-form__body { display: block; }

.rv-label {
  display: block;
  font-size: 11px; font-weight: 600; letter-spacing: 0.16em; text-transform: uppercase;
  color: var(--gold); margin: 0 0 8px; padding: 0;
}
.rv-hint { color: var(--muted); font-size: 13.5px; margin: 0 0 10px; }
.rv-field { margin: 0 0 18px; }
.rv-input { margin: 0; }

.rv-stars { border: 0; padding: 0; margin: 0 0 22px; min-width: 0; }
.rv-stars__row { display: inline-flex; align-items: center; gap: 6px; }

/* Off screen, not hidden: a display:none input cannot take focus, and the whole
   control would then be unusable without a mouse. */
.rv-stars__input {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0; border: 0;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  overflow: hidden; white-space: nowrap;
}
.rv-stars__label {
  cursor: pointer;
  line-height: 1;
  color: var(--hairline-2, rgba(201, 164, 92, 0.30));
  transition: color 0.18s var(--ease), transform 0.18s var(--ease);
}
.rv-stars__label svg { width: 30px; height: 30px; display: block; }
.rv-stars__label:hover { transform: translateY(-1px); }

/* The chosen state: every star up to and including the chosen one. */
.rv-stars__row[data-chosen="1"] .rv-stars__label:nth-of-type(-n+1),
.rv-stars__row[data-chosen="2"] .rv-stars__label:nth-of-type(-n+2),
.rv-stars__row[data-chosen="3"] .rv-stars__label:nth-of-type(-n+3),
.rv-stars__row[data-chosen="4"] .rv-stars__label:nth-of-type(-n+4),
.rv-stars__row[data-chosen="5"] .rv-stars__label:nth-of-type(-n+5) { color: var(--gold); }

/* Keyboard focus has to be visible on the LABEL, because the input it belongs to
   is off screen and its own outline would be drawn where nobody can see it. */
.rv-stars__input:focus-visible + .rv-stars__label {
  outline: 2px solid var(--gold-bright);
  outline-offset: 3px;
  border-radius: 3px;
}

.rv-submit { margin-top: 4px; }
.rv-form__problems {
  margin: 0 0 16px; padding: 11px 14px;
  border-left: 2px solid var(--pin);
  background: var(--wine-lift);
  color: var(--ivory); font-size: 14.5px; line-height: 1.5;
  border-radius: 0 var(--radius) var(--radius) 0;
}
.rv-form__thanks {
  margin: 0; color: var(--ivory); font-size: 16px; line-height: 1.6;
}

/* Used by the star labels to name themselves without printing the words twice. */
.visually-hidden {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0; border: 0;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  overflow: hidden; white-space: nowrap;
}

/* ============================================================
   The tagline stands down wherever the lotus is carrying the brand
   ------------------------------------------------------------
   MEASURED AFTER THE MARK GREW. The new lotus is 164px wide in the bar against
   the old one's 145, and the nav type went 13px -> 14px in the same round. At
   1024px that left the lockup and the first link TOUCHING, with 0px between them
   and only 12px of type fitting; at 1440px "SOFT TOUCH" ran straight into
   "OUR TEAM" on screen.

   Something had to give, and the tagline is the cheapest thing in the bar: the
   lotus and the business name carry the brand on their own, and the tagline is
   still on the footer and in the page title. It already stood down below 640px
   and across 1001-1320px for the same reason; this simply extends that to every
   width where the raster lockup is present and the bar is not enormous.
   ============================================================ */
@media (max-width: 1599px) {
  .site-nav:has(.brand-emblem img) .brand-tag { display: none; }
}
