/* ==========================================================================
   BEAUTY & WELLNESS addict NY
   Display: Cormorant Garamond (400, 500, 600 + italic)   Text: Manrope (400, 500)
   ========================================================================== */

/* ---------- Tokens ---------- */
:root {
  --ivory:    #F7F3EC;   /* page */
  --cream:    #F2EBE1;   /* first step down from ivory */
  --shell:    #EFE6DB;   /* secondary surface */
  --sand:     #E4D8C9;   /* body text on espresso */
  --taupe:    #B4A395;   /* rules; muted text on espresso only */
  --greige:   #6F6459;   /* muted text on ivory */
  --mocha:    #6B5749;   /* accent + body text */
  --espresso: #2B2320;   /* ink */
  /* Gold, in two tones so it can be both accent and readable text.
     --gold is brass: rules, icons, borders, and text on espresso (5.0:1).
     --gold-ink is the darker cousin for text on ivory (4.9:1). */
  --gold:     #B08D57;
  --gold-ink: #7D5F2C;   /* passes 4.5:1 on ivory, cream and shell alike */
  --gold-soft:rgba(176, 141, 87, .32);
  --gold-hair:rgba(176, 141, 87, .55);  /* the champagne rule, motif 01 */
  --line:     rgba(43, 35, 32, .15);
  --line-soft:rgba(43, 35, 32, .08);
  --line-inv: rgba(247, 243, 236, .20);

  --display: "Cormorant Garamond", "Didot", "Times New Roman", serif;
  --text: "Manrope", "Helvetica Neue", Arial, sans-serif;

  --fs-micro: .75rem;
  --fs-small: .9375rem;
  --fs-body: 1.0625rem;
  --fs-lead: clamp(1.25rem, 1.05rem + .7vw, 1.625rem);
  --fs-h3: clamp(1.3125rem, 1.15rem + .65vw, 1.625rem);
  --fs-h2: clamp(2.25rem, 1.4rem + 2.85vw, 4.125rem);
  --fs-h1: clamp(3.5rem, 1.3rem + 8.2vw, 9.25rem);
  --fs-mega: clamp(3.5rem, 1.3rem + 8.2vw, 9.25rem);

  /* spacing scale — 4 steps, used everywhere */
  --sp-1: clamp(10px, 1.2vw, 16px);
  --sp-2: clamp(20px, 2.4vw, 32px);
  --sp-3: clamp(36px, 4.4vw, 64px);
  --sp-4: clamp(64px, 8vw, 128px);

  --gutter: clamp(22px, 5vw, 88px);
  /* Section padding. Every boundary between two sections spends this twice,
     so the value here is half of what the reader actually sees between one
     section ending and the next beginning. At 9vw/156px that boundary reached
     319px at 1600 and 259px at 1440, which read as dead space rather than
     breathing room. 7vw/124px brings it to 224px and 202px and still leaves
     the page airy. */
  --sec: clamp(72px, 7vw, 124px);
  --sec-tight: clamp(52px, 5vw, 88px);
  --maxw: 1560px;

  --ease: cubic-bezier(.22, .68, .18, 1);
  --dur: .9s;
}

/* ---------- Reset ---------- */
*, *::before, *::after { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }
body {
  margin: 0;
  background: var(--ivory);
  color: var(--espresso);
  font-family: var(--text);
  font-size: var(--fs-body);
  line-height: 1.65;
  font-weight: 400;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  overflow-x: hidden;
}
img, svg, picture { display: block; max-width: 100%; }
img { height: auto; }
button, input, select, textarea { font: inherit; color: inherit; }
h1, h2, h3, h4, p, figure, blockquote { margin: 0; }
ul, ol { margin: 0; padding: 0; list-style: none; }
a { color: inherit; text-decoration: none; }
:focus-visible { outline: 2px solid var(--mocha); outline-offset: 3px; }
::selection { background: var(--sand); color: var(--espresso); }

/* ---------- Layout ---------- */
.shell {
  width: 100%;
  max-width: var(--maxw);
  margin-inline: auto;
  padding-inline: var(--gutter);
}
.section { padding-block: var(--sec); }
.section--tight { padding-block: var(--sec-tight); }
/* Two sections on the same ground read as one continuous idea, so the boundary
   between them does not need two full pads. A change of ground keeps the full
   separation, because that is where a new idea starts. */
.section:not([class*="bg-"]):not(.inv):not(.close)
  + .section:not([class*="bg-"]):not(.inv):not(.close) {
  padding-top: calc(var(--sec) * .55);
}
.rule-top { border-top: 1px solid var(--line); }
.grid {
  display: grid;
  grid-template-columns: repeat(12, minmax(0, 1fr));
  column-gap: clamp(18px, 2.4vw, 40px);
}
.skip {
  position: absolute; left: -9999px; top: 0; z-index: 200;
  background: var(--espresso); color: var(--ivory);
  padding: 14px 22px; font-size: var(--fs-micro);
  letter-spacing: .16em; text-transform: uppercase;
}
.skip:focus { left: 0; }

/* ---------- Type ---------- */
.eyebrow {
  font-size: var(--fs-micro);
  font-weight: 500;
  letter-spacing: .2em;
  text-transform: uppercase;
  color: var(--greige);
}
.display, h1, h2, h3 {
  font-family: var(--display);
  font-weight: 400;
  font-optical-sizing: auto;
  letter-spacing: -.008em;
  line-height: 1.0;
  text-wrap: balance;
}
.h1 { font-size: var(--fs-h1); line-height: .94; }
.h2 { font-size: var(--fs-h2); line-height: 1.0; }
.h3 { font-size: var(--fs-h3); line-height: 1.2; letter-spacing: -.005em; }
.mega { font-size: var(--fs-mega); line-height: .94; letter-spacing: -.014em; }
.it { font-style: italic; color: var(--gold-ink); }
.lead {
  font-size: var(--fs-lead);
  line-height: 1.45;
  color: var(--mocha);
  max-width: 30ch;
  font-family: var(--display);
}
.body { max-width: 56ch; color: var(--mocha); }
.body + .body { margin-top: 1em; }
.small { font-size: var(--fs-small); color: var(--greige); max-width: 58ch; }
.micro {
  font-size: var(--fs-micro);
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--greige);
}

/* Wordmark ---------------------------------------------------------
   Set caps carry the name; "addict" is signed underneath in a monoline
   script and tucked up so the two read as one mark. Monoline matters:
   a high-contrast calligraphic face would lose its thin strokes at the
   16px header size. */
.wordmark { display: inline-block; line-height: 1; text-align: center; }
.wordmark__main {
  display: block;
  font-family: var(--display);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .17em;
  text-indent: .17em;
  font-size: calc(var(--wm, 1rem) * .88);
  line-height: 1;
  white-space: nowrap;
}
.wordmark__sub {
  display: block;
  margin-top: .06em;               /* sits clear of the caps line */
  font-family: "Sacramento", var(--display);
  font-style: normal;
  letter-spacing: .01em;
  text-indent: 0;
  font-size: calc(var(--wm, 1rem) * 1.25);
  line-height: 1;
  color: var(--gold-ink);
}
.wordmark__sub sup {
  font-family: var(--text);
  font-size: .38em;
  font-weight: 500;
  letter-spacing: .16em;
  vertical-align: .8em;
  margin-left: .12em;
  color: var(--greige);
}
.wordmark--inv .wordmark__sub { color: var(--gold); }
.wordmark--inv .wordmark__sub sup { color: var(--taupe); }

/* ---------- Links & buttons ---------- */
.ul-link { position: relative; display: inline-block; padding-bottom: 2px; }
.ul-link::after {
  content: "";
  position: absolute; left: 0; bottom: 0;
  width: 100%; height: 1px;
  background: currentColor;
  transform: scaleX(0); transform-origin: right;
  transition: transform .45s var(--ease);
}
.ul-link:hover::after, .ul-link:focus-visible::after { transform: scaleX(1); transform-origin: left; }

.arrow-link {
  display: inline-flex;
  align-items: center;
  gap: .75em;
  min-height: 44px;
  font-size: var(--fs-micro);
  letter-spacing: .16em;
  text-transform: uppercase;
  font-weight: 500;
  color: var(--espresso);
}
.arrow-link svg { width: 20px; color: var(--gold-ink); transition: transform .45s var(--ease); }
.arrow-link:hover svg, .arrow-link:focus-visible svg { transform: translateX(6px); }
.arrow-link--inv { color: var(--sand); }

.btn {
  --btn-bg: var(--espresso);
  --btn-fg: var(--ivory);
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 54px;
  padding: 17px 34px;
  background: var(--btn-bg);
  color: var(--btn-fg);
  font-size: var(--fs-micro);
  font-weight: 500;
  letter-spacing: .16em;
  text-transform: uppercase;
  border: 1px solid var(--btn-bg);
  cursor: pointer;
  overflow: hidden;
  isolation: isolate;
  transition: color .4s var(--ease);
}
.btn::after {
  content: "";
  position: absolute; inset: 0;
  background: var(--mocha);
  transform: scaleY(0); transform-origin: bottom;
  transition: transform .45s var(--ease);
  z-index: -1;
}
.btn:hover::after, .btn:focus-visible::after { transform: scaleY(1); transform-origin: top; }
.btn--ghost { --btn-bg: transparent; --btn-fg: var(--espresso); border-color: var(--line); }
.btn--ghost::after { background: var(--espresso); }
.btn--ghost:hover, .btn--ghost:focus-visible { color: var(--ivory); }
.btn--inv { --btn-bg: var(--ivory); --btn-fg: var(--espresso); }
.btn--inv::after { background: var(--sand); }
.btn-row { display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-2); }

/* ---------- Header ---------- */
.header {
  position: fixed;
  inset: 0 0 auto 0;
  z-index: 90;
  border-bottom: 1px solid transparent;
  transition: background .45s var(--ease), border-color .45s var(--ease);
}
.header::before {
  content: "";
  position: absolute; inset: 0;
  background: linear-gradient(to bottom, rgba(247,243,236,.94), rgba(247,243,236,0));
  transition: opacity .45s var(--ease);
  pointer-events: none;
}
.header.is-stuck { background: var(--ivory); border-bottom-color: var(--line); }
.header.is-stuck::before { opacity: 0; }
.header__inner {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 24px;
  min-height: 88px;
  transition: min-height .4s var(--ease);
}
.header.is-stuck .header__inner { min-height: 68px; }

.brand { --wm: 1rem; display: flex; align-items: center; gap: 13px; flex: none; padding-block: 6px; }
.brand__mark { width: 32px; height: 32px; object-fit: contain; mix-blend-mode: multiply; flex: none; }
.brand .wordmark__sub { font-size: calc(var(--wm) * 1.25); margin-top: .06em; }
/* The compact header lockup drops the NY suffix: at this size it renders
   below 8px and stops being legible. The footer mark keeps it. */
.brand .wordmark__sub sup { display: none; }

.nav { display: flex; align-items: center; gap: clamp(20px, 2.2vw, 40px); }
.nav__link {
  position: relative;
  font-size: var(--fs-micro);
  letter-spacing: .14em;
  text-transform: uppercase;
  /* The desktop nav shows from 1000px up, which includes touch tablets, so
     the rows carry a full 44px target rather than a mouse-sized one. The
     padding does it, not min-height: the underline below is anchored to the
     bottom of the box, and a flex box would strand it 14px under the text. */
  display: inline-block;
  padding: 14px 0;
  color: var(--espresso);
}
.nav__link::after {
  content: "";
  position: absolute; left: 0; right: 0; bottom: 11px;
  height: 1px; background: currentColor;
  transform: scaleX(0); transform-origin: right;
  transition: transform .4s var(--ease);
}
.nav__link:hover::after, .nav__link:focus-visible::after { transform: scaleX(1); transform-origin: left; }
.nav__link[aria-current="page"]::after { transform: scaleX(1); background: var(--taupe); }
.header__cta { display: none; }
@media (min-width: 1140px) { .header__cta { display: inline-flex; } }
.header__cta.btn { min-height: 44px; padding: 12px 24px; }

/* Three hairlines, matching the rules used across the site. The bars sit
   --burger-gap apart, and the open state translates the outer two by exactly
   that distance so the strokes meet on the centre line. The previous version
   moved them half as far, which left the X split into two offset diagonals —
   the reason it read as broken. Keep the translate and the gap equal. */
.burger {
  --burger-gap: 5px;
  --burger-w: 24px;
  display: inline-flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--burger-gap);
  width: 48px; height: 48px;
  padding: 0;
  background: none; border: 0; cursor: pointer;
  margin-right: -8px;
  color: var(--espresso);
}
.burger span {
  display: block;
  width: var(--burger-w); height: 1px;
  background: currentColor;
  transform-origin: 50% 50%;
  transition: transform .45s var(--ease), opacity .2s var(--ease), width .35s var(--ease);
}
/* The middle bar is shorter at rest, so the mark reads as drawn rather than
   as three identical strokes. It is the one that disappears on open. */
.burger span:nth-child(2) { width: calc(var(--burger-w) - 6px); }
.burger:hover, .burger:focus-visible { color: var(--gold-ink); }
.burger:hover span:nth-child(2) { width: var(--burger-w); }
.burger[aria-expanded="true"] span:nth-child(1) { transform: translateY(calc(var(--burger-gap) + 1px)) rotate(45deg); }
.burger[aria-expanded="true"] span:nth-child(2) { opacity: 0; transform: scaleX(0); }
.burger[aria-expanded="true"] span:nth-child(3) { transform: translateY(calc((var(--burger-gap) + 1px) * -1)) rotate(-45deg); }
.burger[aria-expanded="true"] span:nth-child(1),
.burger[aria-expanded="true"] span:nth-child(3) { width: var(--burger-w); }
@media (min-width: 1000px) { .burger { display: none; } }
@media (max-width: 999px) { .nav--desktop { display: none; } }

/* Mobile menu ---------------------------------------------------- */
.menu {
  position: fixed; inset: 0; z-index: 80;
  background: var(--ivory);
  padding: 104px var(--gutter) 40px;
  display: flex; flex-direction: column;
  overflow-y: auto;
  clip-path: inset(0 0 100% 0);
  visibility: hidden;
  transition: clip-path .6s var(--ease), visibility 0s .6s;
}
.menu.is-open {
  clip-path: inset(0 0 0 0); visibility: visible;
  transition: clip-path .6s var(--ease), visibility 0s 0s;
}
/* Five links used to sit at the top with ~190px of dead space between them
   and the booking block. The nav takes the free height and centres the list
   in it, so the space reads as deliberate on both sides. */
.menu > nav { flex: 1 1 auto; display: flex; align-items: center; }
.menu__list { display: flex; flex-direction: column; width: 100%; }
.menu__item + .menu__item { border-top: 1px solid var(--line-soft); }
.menu__link {
  display: flex; align-items: baseline; gap: 12px;
  padding: 15px 0;
  font-family: var(--display);
  font-size: clamp(1.75rem, 1.1rem + 2.8vw, 2.75rem);
  line-height: 1.1;
  opacity: 0;
}
.menu.is-open .menu__link { opacity: 1; transition: opacity .5s var(--ease) .15s; }
.menu__link em { font-style: italic; font-size: .46em; color: var(--greige); }
/* Holds the booking button alone. The phone and Instagram rows that used to
   sit under it live in the footer and on the Contact page instead. */
.menu__foot { margin-top: auto; padding-top: var(--sp-3); display: flex; }
/* Short screens: tighten so the booking button stays above the fold with
   eight items in the list. */
@media (max-height: 730px) {
  .menu { padding-top: 84px; padding-bottom: 24px; }
  .menu__link { padding: 10px 0; font-size: clamp(1.375rem, 1rem + 2.2vw, 1.75rem); }
  .menu__foot { padding-top: var(--sp-2); }
}
/* No trailing margin: nothing sits under the button now, so the only space
   below it is the menu's own bottom padding. */
.menu__foot .btn { align-self: flex-start; }

/* ---------- Hero title ---------- */
.hero__title { margin-bottom: .38em; }
.hero__title span { display: block; }
.hero { padding-top: 140px; }
@media (min-width: 900px) { .hero { padding-top: 168px; } }




/* ---------- Editorial split ---------- */
.split { row-gap: var(--sp-3); align-items: center; }
.split__media { grid-column: 1 / -1; }
.split__copy { grid-column: 1 / -1; }
@media (min-width: 900px) {
  .split__media { grid-column: 1 / 7; }
  .split__copy { grid-column: 8 / -1; }
  .split--flip .split__media { grid-column: 7 / -1; grid-row: 1; }
  .split--flip .split__copy { grid-column: 1 / 6; grid-row: 1; }
}
.figure { position: relative; overflow: hidden; background: var(--shell); }
.figure img { width: 100%; height: 100%; object-fit: cover; }
.figure--tall { aspect-ratio: 4 / 5; }
.figure--wide { aspect-ratio: 3 / 2; }
.figure__cap { margin-top: var(--sp-1); font-size: var(--fs-micro); color: var(--greige); }
.stack > * + * { margin-top: var(--sp-2); }
.stack--sm > * + * { margin-top: var(--sp-1); }

/* two-column editorial: title left, prose right */
.duo { row-gap: var(--sp-2); }
.duo__title { grid-column: 1 / -1; }
.duo__body { grid-column: 1 / -1; }
@media (min-width: 900px) {
  .duo__title { grid-column: 1 / 7; }
  .duo__body { grid-column: 8 / -1; }
}



/* ---------- Service index ---------- */
.svc {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--sp-1) clamp(24px, 3vw, 56px);
  padding-block: clamp(24px, 2.8vw, 40px);
  border-bottom: 1px solid var(--line-soft);
  align-items: baseline;
}
.svc-list > .svc:first-child { border-top: 1px solid var(--line); }
@media (min-width: 800px) { .svc { grid-template-columns: 1fr 1.5fr auto; } }
.svc__name {
  font-family: var(--display);
  font-weight: 500;
  font-size: clamp(1.375rem, 1.05rem + 1.2vw, 2.125rem);
  line-height: 1.1;
  transition: transform .5s var(--ease);
}
a.svc:hover .svc__name, a.svc:focus-visible .svc__name { transform: translateX(8px); }
.svc__desc { color: var(--mocha); font-size: var(--fs-small); max-width: 52ch; }
.svc__meta { font-size: var(--fs-micro); letter-spacing: .1em; text-transform: uppercase; color: var(--gold-ink); white-space: nowrap; }





/* ---------- Inverted band ---------- */
.inv { background: var(--espresso); color: var(--sand); }
.inv h1, .inv h2, .inv h3, .inv .display { color: var(--ivory); }
.inv .eyebrow, .inv .small, .inv .micro, .inv .figure__cap { color: var(--taupe); }
.inv .svc__meta, .inv .it { color: var(--gold); }
.inv .arrow-link svg { color: var(--gold); }
.close .it, .cta-band .it { color: var(--gold); }
.inv .lead, .inv .body, .inv .svc__desc { color: var(--sand); }
.inv .rule-top, .inv .svc, .inv .svc-list > .svc:first-child { border-color: var(--line-inv); }
.inv .figure { background: #241D1A; }

/* ---------- Pricing ---------- */
/* The sticky offset tracks the header's measured height via --header-h,
   which site.js updates whenever the header shrinks or the window resizes.
   A hardcoded offset cannot work: the header is 88px at rest, shrinks to 68px
   on desktop once stuck, but stays 88px on mobile because the two-line
   wordmark is taller than the shrink target. The previous flat 68px let the
   header cover the top 21px of this bar. The 88px fallback below is the
   at-rest height, so if the script never runs the bar still clears it. */
.price-nav {
  position: sticky; top: var(--header-h, 88px); z-index: 20;
  background: var(--ivory);
  border-bottom: 1px solid var(--line);
}
.price-nav__list {
  display: flex; gap: var(--sp-2);
  overflow-x: auto;
  scrollbar-width: none;
  -webkit-overflow-scrolling: touch;
  overscroll-behavior-x: contain;
  scroll-snap-type: x proximity;
}
.price-nav__list::-webkit-scrollbar { display: none; }
.price-nav__list > li { scroll-snap-align: start; flex: none; }
/* On a phone the row runs edge to edge and the padding moves onto the list,
   so categories scroll out under the fade instead of stopping short of it. */
@media (max-width: 759px) {
  .price-nav .shell { padding-inline: 0; }
  .price-nav__list { padding-inline: var(--gutter); }
}
/* Edge fade: the only hint that the row continues. JS drops .has-more once
   the list is scrolled to the end, and never adds it if nothing overflows. */
.price-nav { position: sticky; }
.price-nav::after {
  content: "";
  position: absolute; top: 0; bottom: 1px; right: 0;
  width: clamp(32px, 10vw, 64px);
  background: linear-gradient(to right, rgba(247, 243, 236, 0), var(--ivory) 78%);
  pointer-events: none;
  opacity: 0;
  transition: opacity .3s var(--ease);
}
.price-nav.has-more::after { opacity: 1; }
.price-nav a {
  white-space: nowrap;
  display: inline-flex; align-items: center;
  min-height: 48px;
  font-size: var(--fs-micro);
  letter-spacing: .12em; text-transform: uppercase;
  color: var(--greige);
  border-bottom: 2px solid transparent;
}
.price-nav a:hover, .price-nav a.is-active { color: var(--espresso); border-bottom-color: var(--espresso); }
.price-nav a:focus-visible { outline-offset: -2px; }

.pr {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: 2px var(--sp-2);
  padding-block: clamp(18px, 2vw, 26px);
  border-bottom: 1px solid var(--line-soft);
  align-items: baseline;
}
.pr:first-child { border-top: 1px solid var(--line); }
.pr__name { font-weight: 500; font-family: var(--display); font-size: clamp(1.1875rem, 1rem + .7vw, 1.5rem); line-height: 1.2; }
.pr__note { display: block; font-size: var(--fs-micro); color: var(--greige); margin-top: 4px; max-width: 46ch; }
.pr__price {
  font-family: var(--display);
  font-weight: 500;
  color: var(--gold-ink);
  font-size: clamp(1.1875rem, 1rem + .7vw, 1.5rem);
  font-variant-numeric: tabular-nums;
  text-align: right;
}
.pr__pkg { display: block; font-family: var(--text); font-size: var(--fs-micro); color: var(--greige); margin-top: 4px; white-space: nowrap; }
.pricelist + .pricelist { margin-top: var(--sp-4); }

.chiplist { display: flex; flex-wrap: wrap; gap: 8px 10px; }
.chiplist li {
  font-size: var(--fs-micro);
  letter-spacing: .06em;
  border: 1px solid var(--gold-soft);
  padding: 7px 13px;
  color: var(--espresso);
}
.inv .chiplist li { border-color: var(--line-inv); color: var(--sand); }

/* ---------- Forms ---------- */
.form { display: grid; gap: var(--sp-2); }
@media (min-width: 700px) { .form__row { display: grid; grid-template-columns: 1fr 1fr; gap: var(--sp-2); } }
.field { display: flex; flex-direction: column; gap: 6px; }
.field label, .field legend {
  font-size: var(--fs-micro);
  letter-spacing: .14em; text-transform: uppercase;
  color: var(--greige);
  padding: 0;
}
.field input, .field select, .field textarea {
  width: 100%;
  min-height: 52px;
  padding: 14px 0;
  background: transparent;
  border: 0;
  border-bottom: 1px solid var(--line);
  border-radius: 0;
  font-size: var(--fs-body);
  transition: border-color .35s var(--ease);
  appearance: none;
}
.field textarea { min-height: 120px; resize: vertical; padding-top: 10px; }
.field select {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath d='M1 1l5 5 5-5' fill='none' stroke='%236B5749'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 2px center;
  padding-right: 26px;
}
.field input:focus, .field select:focus, .field textarea:focus { outline: none; border-bottom-color: var(--espresso); }
.field input:focus-visible, .field select:focus-visible, .field textarea:focus-visible { outline: 2px solid var(--mocha); outline-offset: 4px; }
.radio-row { display: flex; flex-wrap: wrap; gap: 10px; }
.radio { position: relative; display: inline-flex; }
.radio input { position: absolute; opacity: 0; inset: 0; cursor: pointer; }
.radio span {
  display: inline-flex; align-items: center; min-height: 46px;
  padding: 10px 20px;
  border: 1px solid var(--line);
  font-size: var(--fs-micro); letter-spacing: .12em; text-transform: uppercase;
  color: var(--greige);
  transition: border-color .3s var(--ease), color .3s var(--ease), background .3s var(--ease);
}
.radio input:checked + span { border-color: var(--espresso); color: var(--espresso); background: var(--shell); }
.radio input:focus-visible + span { outline: 2px solid var(--mocha); outline-offset: 3px; }
.form__note { font-size: var(--fs-micro); color: var(--greige); line-height: 1.7; max-width: 54ch; }
.form__status { font-size: var(--fs-small); color: var(--mocha); border-left: 2px solid var(--taupe); padding-left: 14px; }
.form__status:empty { display: none; }
.field-error { font-size: var(--fs-micro); color: #8C3B2B; text-transform: none; letter-spacing: 0; }
.field-error:empty { display: none; }
.field input[aria-invalid="true"], .field textarea[aria-invalid="true"], .field select[aria-invalid="true"] { border-bottom-color: #8C3B2B; }

/* ---------- Map ---------- */
.map {
  aspect-ratio: 4 / 3;
  background: var(--shell);
  overflow: hidden;
}
@media (min-width: 900px) { .map { aspect-ratio: 3 / 4; } }
.map iframe {
  display: block;
  width: 100%; height: 100%;
  border: 0;
  /* Warms Google's default green and blue into the site's neutrals without
     hiding the marker. */
  filter: grayscale(.7) sepia(.28) saturate(.85) contrast(.95);
}



/* ---------- Page head ---------- */
.pagehead { padding-top: clamp(140px, 16vw, 220px); }
/* Title block: the subtitle sits directly under the h1 on the same left edge,
   close enough to belong to it. The block was capped at 42ch — 436px, measured
   against the body font rather than the display face it actually holds — which
   broke the subtitle across two lines while over 1100px sat unused beside it.
   The subtitle needs 640px to set on one line, so the cap now clears that with
   room to spare and mobile still wraps at the shell width. */
.pagehead__block { max-width: min(820px, 100%); }
.pagehead__sub {
  margin-top: var(--sp-2);
  max-width: 100%;
  font-family: var(--display);
  font-size: clamp(1.1875rem, 1.02rem + .6vw, 1.5rem);
  line-height: 1.45;
  color: var(--mocha);
}
.pagehead__grid { row-gap: var(--sp-2); align-items: end; }
.pagehead__title { grid-column: 1 / -1; }
.pagehead__aside { grid-column: 1 / -1; }
@media (min-width: 900px) {
  .pagehead__title { grid-column: 1 / 8; }
  .pagehead__aside { grid-column: 9 / -1; padding-bottom: .5em; }
}

/* ---------- Closing CTA ---------- */
.close { background: var(--espresso); color: var(--sand); text-align: center; }
.close .btn { margin-top: var(--sp-3); }

/* ---------- Footer ---------- */
.footer { background: var(--espresso); color: var(--sand); padding-top: var(--sp-4); padding-bottom: var(--sp-2); }
.footer .micro, .footer .small { color: var(--taupe); }
.footer .it { color: var(--gold); }
.footer a:hover { color: var(--ivory); }
.footer__top { row-gap: var(--sp-3); padding-bottom: var(--sp-3); }
.footer__brand { grid-column: 1 / -1; }
@media (min-width: 900px) { .footer__brand { grid-column: 1 / 5; } }
.footer__wordmark { display: block; color: var(--ivory); }
.footer__cols { grid-column: 1 / -1; display: grid; gap: var(--sp-2); grid-template-columns: repeat(2, 1fr); }
@media (min-width: 900px) { .footer__cols { grid-column: 7 / -1; } }
.footer__h { font-size: var(--fs-micro); letter-spacing: .2em; text-transform: uppercase; color: var(--taupe); margin-bottom: var(--sp-1); font-family: var(--text); font-weight: 500; }
.footer__list { display: grid; font-size: var(--fs-small); }
.footer__list li > a { display: inline-flex; align-items: center; min-height: 44px; }
.footer__bottom .ul-link { display: inline-flex; align-items: center; min-height: 44px; }
.footer__by { display: inline-flex; align-items: center; gap: .4em; }

/* Links given a 44px tap box sit taller than their text, so the .ul-link bar
   would land at the bottom of the box instead of under the words. Use a real
   underline on these instead. */
.footer__list li > a,
.footer__bottom .ul-link {
  text-underline-offset: .3em;
  text-decoration-thickness: 1px;
}
.footer__list li > a::after,
.footer__bottom .ul-link::after { content: none; }
.footer__list li > a:hover,
.footer__list li > a:focus-visible { text-decoration: underline; }

/* The studio credit stays underlined so it reads as a link without a hover. */
.footer__by .ul-link { text-decoration-line: underline; }
.footer__bottom {
  border-top: 1px solid var(--line-inv);
  padding-top: var(--sp-2);
  display: flex; flex-wrap: wrap; gap: 8px 28px; justify-content: space-between;
}
.disclaimer {
  border-top: 1px solid var(--line-inv);
  padding-block: var(--sp-2);
  font-size: var(--fs-micro);
  line-height: 1.75;
  color: var(--taupe);
  max-width: 104ch;
}

/* ---------- Reveal -------------------------------------------------
   The entrance animation is an enhancement and is never load-bearing.

   Content is visible by default. It is only hidden while `html.reveal-on`
   is present, and that class is removed by a failsafe timer unless
   site.js confirms it has an observer running. So if the script is
   blocked, fails to load, throws, or the observer never fires, the
   content ends up visible rather than stuck.

   Animated with opacity and transform only. The previous version used a
   clip-path transition, which is the most repaint-fragile property here
   and can silently fail on some Windows GPU drivers, leaving the image
   clipped to nothing. */
.reveal-on [data-reveal] {
  opacity: 0;
  transform: translateY(14px);
  transition: opacity var(--dur) var(--ease) var(--delay, 0s),
              transform var(--dur) var(--ease) var(--delay, 0s);
  will-change: opacity, transform;
}
.reveal-on [data-reveal="img"] { transform: translateY(16px) scale(.97); }

/* Revealed, failed to load, or the failsafe fired: all end up here. */
.reveal-on [data-reveal].is-in,
[data-reveal].is-in {
  opacity: 1;
  transform: none;
  visibility: visible;
  will-change: auto;
}

/* Images are never allowed to disappear, whatever state their wrapper is in. */
[data-reveal] img { opacity: 1; visibility: visible; }

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: .001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .001ms !important;
  }
  /* Reduced motion means no animation, never no image. */
  .reveal-on [data-reveal],
  .reveal-on [data-reveal="img"],
  [data-reveal] {
    opacity: 1 !important;
    transform: none !important;
    clip-path: none !important;
    visibility: visible !important;
  }
  .menu__link { opacity: 1 !important; }
}

/* ---------- Section grounds ---------- */
.bg-cream { background: var(--cream); }
.bg-shell { background: var(--shell); }

/* ==========================================================================
   ART DIRECTION LAYER
   Three motifs carry the identity across every page:
     01  the champagne rule      .rule-g      — draws itself left to right
     02  the signed script       .script      — the wordmark's hand, used sparingly
     03  the editorial mask      [data-reveal="mask"] — image settles out of a wipe
   Everything else is composition and ground. No fourth motif.
   ========================================================================== */

/* ---- Motif 01: the champagne rule ---------------------------------------
   A single hairline that opens a section. It expands on reveal, so the eye
   is led into the heading rather than arriving with it. */
.rule-g {
  display: block;
  width: min(120px, 26vw);
  height: 1px;
  background: var(--gold);
  transform-origin: left;
  margin-bottom: var(--sp-2);
}
.reveal-on [data-reveal] .rule-g,
.reveal-on .rule-g[data-reveal] { transform: scaleX(0); }
[data-reveal].is-in .rule-g,
.rule-g[data-reveal].is-in {
  transform: scaleX(1);
  transition: transform 1.1s var(--ease) calc(var(--delay, 0s) + .1s);
}
.rule-g--wide { width: 100%; }
.inv .rule-g { background: var(--gold); }

/* ---- Motif 02: the signed script -----------------------------------------
   The same hand as "addict" in the wordmark. It marks the two or three
   moments a page actually wants a human voice, never as body copy. */
.script {
  font-family: "Sacramento", var(--display);
  font-style: normal;
  letter-spacing: .01em;
  color: var(--gold-ink);
  text-transform: none;
}
.inv .script, .close .script { color: var(--gold); }
.script--lg { font-size: clamp(2.25rem, 1.4rem + 3.4vw, 4.5rem); line-height: .9; display: block; }

/* ---- Motif 03: the editorial mask ---------------------------------------
   The frame wipes open from the bottom while the photograph inside settles
   back from a slight overscale. Two transforms, no clip-path: the previous
   build proved clip-path is the one property that can silently fail here. */
.reveal-on [data-reveal="mask"] {
  opacity: 1;
  transform: none;
  clip-path: none;
}
.reveal-on [data-reveal="mask"] .figure,
.reveal-on [data-reveal="mask"] .gal__btn { position: relative; overflow: hidden; }
.reveal-on [data-reveal="mask"] .figure::after,
.reveal-on [data-reveal="mask"] .gal__btn::after {
  content: "";
  position: absolute; inset: 0;
  background: var(--ivory);
  transform-origin: bottom;
  transition: transform 1.15s var(--ease) var(--delay, 0s);
  z-index: 2;
}
/* The wipe panel has to match the ground it sits on, or the animation shows
   an ivory rectangle against a cream section on its way out. */
.reveal-on .inv [data-reveal="mask"] .figure::after { background: var(--espresso); }
.reveal-on .bg-cream [data-reveal="mask"] .figure::after,
.reveal-on .bg-cream [data-reveal="mask"] .gal__btn::after { background: var(--cream); }
.reveal-on .bg-shell [data-reveal="mask"] .figure::after,
.reveal-on .abouthero [data-reveal="mask"] .figure::after,
.reveal-on .bg-shell [data-reveal="mask"] .gal__btn::after { background: var(--shell); }
.reveal-on [data-reveal="mask"] img { transform: scale(1.07); transition: transform 1.5s var(--ease) var(--delay, 0s); }
[data-reveal="mask"].is-in .figure::after,
[data-reveal="mask"].is-in .gal__btn::after { transform: scaleY(0); }
[data-reveal="mask"].is-in img { transform: none; }

/* Staggered typography: children of a revealed block arrive in sequence
   rather than together. Used on lists and on the founders block. */
.reveal-on [data-stagger] > * { opacity: 0; transform: translateY(12px); }
[data-stagger].is-in > * {
  opacity: 1; transform: none;
  transition: opacity .8s var(--ease), transform .8s var(--ease);
}
[data-stagger].is-in > *:nth-child(1) { transition-delay: .05s; }
[data-stagger].is-in > *:nth-child(2) { transition-delay: .13s; }
[data-stagger].is-in > *:nth-child(3) { transition-delay: .21s; }
[data-stagger].is-in > *:nth-child(4) { transition-delay: .29s; }
[data-stagger].is-in > *:nth-child(5) { transition-delay: .37s; }
[data-stagger].is-in > *:nth-child(6) { transition-delay: .45s; }

/* ---------- Founders hero ------------------------------------------------
   The practice is the two of them, so the page opens on their own photograph,
   softly out of focus behind a warm veil. Light-grounded, so the type is
   espresso rather than reversed out of a dark scrim. No face is altered: the
   field is a resampled enlargement of the supplied image. */

.hero-fp {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  background: var(--cream);
  min-height: 100svh;
  display: flex;
  align-items: center;
  padding-top: clamp(104px, 9vw, 140px);
  padding-bottom: var(--sec-tight);
}
.hero-fp__bg { position: absolute; inset: 0; z-index: 0; }
.hero-fp__bg picture { display: block; width: 100%; height: 100%; }
.hero-fp__bg img {
  width: 100%; height: 100%;
  object-fit: cover;
  object-position: 50% 30%;
  /* The asset carries the blur as a resample (downsampled to 130px, back up
     to 1600). This pass only softens the resample grid — deliberately light,
     because they should still read as themselves. */
  filter: blur(3px);
  transform: scale(1.05);
}
/* Warm veil. Heavy on the left, where the type sits and has to clear AA;
   thin on the right, where the photograph is the whole point. */
.hero-fp__veil {
  position: absolute; inset: 0; z-index: 1;
  background:
    linear-gradient(100deg, rgba(242,235,225,.95) 0%, rgba(242,235,225,.88) 34%, rgba(242,235,225,.46) 62%, rgba(242,235,225,.14) 100%),
    linear-gradient(to top, rgba(242,235,225,.40), rgba(242,235,225,0) 32%);
}
.hero-fp__inner { position: relative; z-index: 2; width: 100%; }
.hero-fp__grid { align-items: center; row-gap: var(--sp-3); }
.hero-fp__copy { grid-column: 1 / -1; }
@media (min-width: 860px) {
  .hero-fp__copy { grid-column: 1 / 7; }
}
@media (min-width: 1200px) {
  .hero-fp__copy { grid-column: 1 / 6; }
}
/* Sized to its column rather than the viewport: --fs-h1 tops out at 139px,
   which wraps "Look well." onto two lines inside this measure. */
.hero-fp .h1 { color: var(--espresso); font-size: clamp(2.9rem, 1.6rem + 5.2vw, 6.25rem); }
.hero-fp .it { color: var(--gold-ink); }
.hero-fp__lead {
  color: var(--espresso);
  font-family: var(--display);
  font-size: var(--fs-lead);
  line-height: 1.4;
  max-width: 24ch;
  margin-top: var(--sp-2);
}
.hero-fp .btn-row { margin-top: var(--sp-3); }
@media (max-width: 859px) {
  .hero-fp { padding-top: 104px; padding-bottom: clamp(56px, 8vw, 80px); }
  /* The phone gets its own crop of the photograph — thigh up rather than the
     full standing figure, which at this aspect would otherwise sit tiny in the
     middle of the frame. Anchored high so their faces stay in the upper half,
     clear of the copy below. */
  .hero-fp__bg img { object-position: 50% 22%; }
  .hero-fp__lead { max-width: 30ch; }
  /* Stacked, the copy runs the full width and crosses the thin right-hand end
     of the directional veil, landing over the dark hair in the field. Measured
     there it fell to 2.01:1, so the veil is even at this size instead. The
     lead is set in espresso rather than mocha, which buys the contrast that
     would otherwise cost a heavier veil over the photograph. */
  .hero-fp__veil {
    background: linear-gradient(to bottom, rgba(242,235,225,.84) 0%, rgba(242,235,225,.81) 55%, rgba(242,235,225,.79) 100%);
  }
}

/* ---------- Founders ------------------------------------------------------
   Typographic, not a split with a photograph: the hero directly above already
   carries their portrait. Heading left, statement right. */
.founders__grid { row-gap: var(--sp-2); align-items: start; }
.founders__copy { grid-column: 1 / -1; }
.founders__body { grid-column: 1 / -1; }
@media (min-width: 900px) {
  .founders__copy { grid-column: 1 / 6; }
  .founders__body { grid-column: 7 / -1; }
  .founders__grid { row-gap: var(--sp-3); }
}
/* The script sits on its own line as a signed second half of the phrase, so
   the heading breaks where it is meant to instead of wherever the viewport
   runs out. Sized below the serif line above it at every width. */
.founders__head {
  font-size: clamp(1.875rem, 1.3rem + 2.1vw, 3.25rem);
  line-height: 1.08;
  text-wrap: balance;
}
.founders__script {
  display: block;
  font-size: clamp(2rem, 1.35rem + 2.4vw, 3.5rem);
  line-height: 1;
  margin-top: .12em;
}
.founders__body > * + * { margin-top: var(--sp-2); }
.founders__body .body { max-width: 54ch; }
/* Names read as a credit line under the statement: display face, gold rule
   above, and the honorific carried in the name itself rather than as a tag. */
.founders__names {
  display: flex; flex-wrap: wrap; gap: .35em clamp(24px, 4vw, 56px);
  margin-top: var(--sp-3);
  padding-top: var(--sp-2);
  border-top: 1px solid var(--gold-hair);
  font-family: var(--display);
  font-weight: 500;
  font-size: clamp(1.0625rem, .95rem + .45vw, 1.3125rem);
  letter-spacing: .005em;
  color: var(--espresso);
}
.inv .founders__names { color: var(--ivory); }

/* ---------- Reviews -------------------------------------------------------
   Set as pull quotes on a rule, not as cards. Real Google reviews only. */
.reviews { display: grid; gap: var(--sp-3) clamp(28px, 4vw, 72px); }
@media (min-width: 860px) { .reviews { grid-template-columns: repeat(2, 1fr); } }
.review { border-top: 1px solid var(--gold-hair); padding-top: var(--sp-2); }
.review__q {
  font-family: var(--display);
  font-size: clamp(1.25rem, 1.05rem + .9vw, 1.75rem);
  line-height: 1.35;
  color: var(--espresso);
  text-wrap: pretty;
}
.inv .review__q { color: var(--ivory); }
.review__by {
  margin-top: var(--sp-2);
  font-size: var(--fs-micro); letter-spacing: .16em; text-transform: uppercase;
  color: var(--greige);
  display: flex; flex-wrap: wrap; gap: .4em .9em;
}
.inv .review__by { color: var(--taupe); }
.review__src { color: var(--gold-ink); }
.inv .review__src { color: var(--gold); }
.review--lead { grid-column: 1 / -1; }
.review--lead .review__q { font-size: clamp(1.5rem, 1.1rem + 1.9vw, 2.5rem); }

/* ---------- Gallery -------------------------------------------------------
   An asymmetric three-up. The tall frame carries, the two short frames
   support; it is not a grid of equal thumbnails. */
.gallery { display: grid; gap: clamp(14px, 1.8vw, 26px); grid-template-columns: 1fr; }
@media (min-width: 760px) {
  .gallery { grid-template-columns: 1.35fr 1fr; grid-template-rows: auto auto; }
  .gallery__a { grid-row: 1 / 3; }
}
@media (min-width: 760px) {
  .gallery--4 { grid-template-rows: auto auto auto; }
  .gallery--4 .gallery__a { grid-row: 1 / 4; }
}
.gallery .figure--a { aspect-ratio: 3 / 4; height: 100%; }
.gallery .figure--b { aspect-ratio: 4 / 3; }
.gallery .figure--a img { height: 100%; }

/* ---------- Services categories -------------------------------------------
   Each category opens with the rule, a script accent and a photograph that
   runs to the edge of the grid, so the page changes shape as you scroll. */
/* Clears the fixed header plus the sticky category bar, so a tapped category
   lands under the bar rather than behind it. */
.cat { scroll-margin-top: calc(var(--header-h, 88px) + 58px); }
.cat__head { row-gap: var(--sp-2); align-items: end; margin-bottom: var(--sp-3); }
.cat__title { grid-column: 1 / -1; }
.cat__note  { grid-column: 1 / -1; }
@media (min-width: 900px) {
  .cat__title { grid-column: 1 / 7; }
  .cat__note  { grid-column: 8 / -1; padding-bottom: .4em; }
}
/* Every category banner is the same shape, whatever the source photograph.
   The inner .figure has to be given the height explicitly: without it the
   image's height:100% resolves against an auto-height parent, falls back to
   auto, and each banner ends up at its own natural ratio. 2:1 rather than a
   harder crop, because the source photographs are around 3:2 and anything
   wider starts cutting into the subject. */
.cat__banner { aspect-ratio: 2 / 1; overflow: hidden; background: var(--shell); margin-bottom: var(--sp-2); }
@media (max-width: 759px) { .cat__banner { aspect-ratio: 3 / 2; } }
.cat__banner .figure { height: 100%; }
.cat__banner img { width: 100%; height: 100%; object-fit: cover; object-position: 50% 50%; }

/* Treatment row: name, what it does, price. One line of information each. */
.tx {
  display: grid;
  grid-template-columns: 1fr;
  gap: 6px clamp(24px, 3vw, 56px);
  padding-block: clamp(20px, 2.3vw, 30px);
  border-bottom: 1px solid var(--line-soft);
  /* Baseline alignment across a 26px serif, a 15px sans and a 24px serif
     price puts three different first lines on one baseline and reads as
     misalignment. Align to the top instead and pay in the optical offsets
     below, which are the same on every row. */
  align-items: start;
}
.tx__name { padding-top: 0; }
.tx__desc { padding-top: .45em; }
.tx__price { padding-top: .1em; }
.tx-list > .tx:first-child { border-top: 1px solid var(--gold-hair); }
/* The name column is narrower and the supporting column wider than before:
   the supporting line was breaking into three and four short lines while the
   name column sat half empty. */
/* The price track is a fraction, never auto. Each .tx is its own grid, so an
   auto track sized to that row/s own price: "Price at consult" measures 146px
   against 45px for "$150", which pulled the other two columns in by up to 29px
   and left every description starting at a different x. A fixed fraction makes
   all rows resolve to identical tracks. The 160px floor keeps the widest
   price ("Price at consult") on one line down at the 860px breakpoint, where
   .42fr alone would only give it 88px. */
@media (min-width: 860px) { .tx { grid-template-columns: minmax(0,.85fr) minmax(0,2.1fr) minmax(160px,.42fr); } }
.tx__name { font-family: var(--display); font-weight: 500; font-size: clamp(1.1875rem, 1rem + .8vw, 1.625rem); line-height: 1.2; }
.tx__desc { color: var(--mocha); font-size: var(--fs-small); max-width: 76ch; }
/* A short client-written line that opens a description. Set on its own line
   in the display face so it reads as a lead rather than running into the
   sentence behind it. */
.tx__lead { display: block; font-family: var(--display); font-style: italic; font-size: 1.15em; color: var(--gold-ink); margin-bottom: .3em; }
.inv .tx__lead { color: var(--gold); }
.tx__price {
  font-family: var(--display); font-weight: 500;
  font-size: clamp(1.1875rem, 1rem + .7vw, 1.5rem);
  color: var(--gold-ink);
  font-variant-numeric: tabular-nums;
  text-align: left;
  white-space: nowrap;
}
@media (min-width: 860px) { .tx__price { text-align: right; } }
/* The package line ("5 for $200 · 10 for $350") is the other piece of small
   supporting text. It gets to stay on one line rather than stacking into
   three under the price. */
.tx__pkg {
  display: block; font-family: var(--text);
  font-size: .8125rem; color: var(--greige);
  margin-top: 3px; letter-spacing: .01em; line-height: 1.4;
}
@media (min-width: 860px) { .tx__pkg { white-space: nowrap; } }
/* Drip addict runs its rules in champagne rather than the default inverted
   hairline, which is what sets the sub-brand apart on the dark ground. */
.inv .tx, .inv .tx-list > .tx:first-child { border-color: var(--gold-hair); }
.inv .tx__desc { color: var(--sand); }
.inv .tx__price { color: var(--gold); }
.inv .tx__pkg { color: var(--taupe); }
/* A whole row is a hover target, so the price shifts with the name. */
.tx { transition: background .4s var(--ease); }
.tx:hover { background: rgba(176,141,87,.05); }
.inv .tx:hover { background: rgba(176,141,87,.09); }

/* ---- Category intro ------------------------------------------------------
   One short line under the category photograph, introducing the list below.
   It runs the full measure at every width. It previously carried a
   max-width of min(1180px, 88%), which is what made the Hair & Scalp sentence
   break with room still to its right: the sentence needs 1229px on one line,
   and the cap held it to 1127px at 1440 even though 1281px was available.
   No cap here — the width is not the constraint any more.

   Size is set flat at 20px rather than tracking --fs-lead, which topped out at
   26px. That is deliberate and load-bearing: the Skin & Rejuvenation intro is
   the longest of these sentences and measures 1591px at 26px, against a widest
   available measure of 1281px at 1440 and 1400px at 1600. At 20px it needs
   1224px and sets on one line from 1440 up, which is what the client asked for.
   Raise this size and that sentence breaks in two again. */
.cat__intro {
  max-width: 100%;
  margin-bottom: var(--sp-3);
  color: var(--mocha);
  font-family: var(--display);
  font-size: 1.25rem;
  line-height: 1.45;
}
.inv .cat__intro { color: var(--sand); }

/* ---- Vitamin accordion ---------------------------------------------------
   Fourteen compounds, each a real button with aria-expanded and a panel.
   Two columns on wide screens so the list does not run to a metre of rows;
   the gold hairlines are the only rule system on the dark ground. */
.vits { display: grid; column-gap: clamp(28px, 4vw, 72px); align-content: start; }
@media (min-width: 900px) { .vits { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
.vit { border-bottom: 1px solid var(--gold-hair); }
.vit:first-child { border-top: 1px solid var(--gold-hair); }
@media (min-width: 900px) { .vit:nth-child(2) { border-top: 1px solid var(--gold-hair); } }
.vit__head {
  width: 100%;
  display: flex; align-items: center; justify-content: space-between; gap: var(--sp-2);
  min-height: 56px;
  padding: 14px 0;
  background: none; border: 0; text-align: left; cursor: pointer;
  color: var(--ivory);
  font-family: var(--display);
  font-weight: 500;
  font-size: clamp(1.0625rem, .97rem + .35vw, 1.25rem);
  line-height: 1.25;
  transition: color .3s var(--ease);
}
.vit__head:hover, .vit__head:focus-visible { color: var(--gold); }
.vit__head:focus-visible { outline: 2px solid var(--gold); outline-offset: 2px; }
/* Plus that becomes a minus. Two bars, one scaled away. */
.vit__sign { position: relative; width: 12px; height: 12px; flex: none; }
.vit__sign::before, .vit__sign::after {
  content: ""; position: absolute; inset: 0; margin: auto;
  background: var(--gold);
  transition: transform .4s var(--ease);
}
.vit__sign::before { width: 12px; height: 1px; }
.vit__sign::after  { width: 1px; height: 12px; }
.vit__head[aria-expanded="true"] .vit__sign::after { transform: scaleY(0); }
/* Open by default. site.js adds .js-ready to the list once it has wired the
   buttons up, and only then are panels allowed to close — so if the script
   never runs, all fourteen descriptions are simply readable. */
.vit__panel { display: grid; grid-template-rows: 1fr; transition: grid-template-rows .42s var(--ease); }
.vit__panel > div { overflow: hidden; }
.vits.js-ready .vit__panel { grid-template-rows: 0fr; }
.vits.js-ready .vit.is-open .vit__panel { grid-template-rows: 1fr; }
.vit__body {
  padding-bottom: var(--sp-2);
  max-width: 52ch;
  color: var(--sand);
  font-size: var(--fs-small);
  line-height: 1.65;
}

/* ---- Five stars ----------------------------------------------------------
   Drawn once as a symbol and used per review. Decorative: the rating is not
   information the review text does not already carry. */
.stars { display: block; width: 92px; height: 15px; color: var(--gold); margin-bottom: var(--sp-2); }

/* ---------- Meet us -------------------------------------------------------
   Introduces the photographs below it. Measure held short of the gallery so
   the paragraph reads as a lead-in rather than a column of text. */
/* Meet us and the gallery share one 880px column, centred. The width is set
   by the smallest photograph: founders-office.jpg is 352px, so a column wider
   than about 460px starts to soften it, which caps a two-column gallery here.
   Left-aligned at this width it left a 473px void down the right at 1440, so
   the block is centred instead and the space falls evenly on both sides. */
.meetus { max-width: 880px; margin-inline: auto; margin-bottom: var(--sp-4); }
.meetus__body { max-width: 68ch; }
.meetus__body {
  margin-top: var(--sp-2);
  color: var(--mocha);
  font-size: var(--fs-body);
  line-height: 1.7;
}
/* The heart is the client's, kept but held below the cap height so it sits
   with the serif instead of shouting over it. */

/* ---------- Gallery, editorial masonry ------------------------------------
   Mixed spans on a six-column bed. The two client photographs are placed at
   their own aspect ratios rather than cropped to match the room shots. */
/* Multi-column rather than a row grid: every photograph keeps its own aspect
   ratio, so nothing is cropped to fit a cell and no face is trimmed. Two
   columns, not three: with five photographs of very different shapes, three
   columns split 2/2/1 and left the short column ending 397px above the tall
   one. Two columns bring that to about 70px. The 880px cap sits with the
   measure of the Meet us paragraph above, so the two read as one column, and
   it keeps the smallest photograph (352px) from being upscaled past ~1.15. */
.gal { column-count: 1; column-gap: clamp(14px, 1.8vw, 26px); max-width: 880px; margin-inline: auto; }
@media (min-width: 700px) { .gal { column-count: 2; } }
.gal__item { break-inside: avoid; margin-bottom: clamp(14px, 1.8vw, 26px); }
.gal__btn {
  display: block; width: 100%; padding: 0; border: 0;
  cursor: zoom-in; overflow: hidden; position: relative;
  background: var(--shell);
  line-height: 0;
}
.gal__btn img { width: 100%; height: auto; transition: transform .7s var(--ease); }
.gal__btn:hover img { transform: scale(1.02); }
.gal__btn:focus-visible { outline: 2px solid var(--gold-ink); outline-offset: 3px; }
.r-1x1 { aspect-ratio: 1 / 1; }
.r-4x5 { aspect-ratio: 4 / 5; }
.r-4x3 { aspect-ratio: 4 / 3; }
.r-3x2 { aspect-ratio: 3 / 2; }
.r-21x9 { aspect-ratio: 21 / 9; }

/* ---------- Lightbox ------------------------------------------------------
   A dialog over an espresso scrim. It is built and opened by site.js; with
   no JS the buttons stay inert and the gallery is still a grid of visible
   photographs, which is the whole point. */
.lb {
  position: fixed; inset: 0; z-index: 200;
  display: none;
  align-items: center; justify-content: center;
  padding: clamp(16px, 4vw, 56px);
  background: rgba(29, 23, 21, .94);
  opacity: 0;
  transition: opacity .35s var(--ease);
}
.lb.is-open { display: flex; }
.lb.is-shown { opacity: 1; }
.lb__fig { margin: 0; max-width: min(1100px, 100%); max-height: 100%; display: flex; flex-direction: column; gap: var(--sp-2); }
.lb__img {
  max-width: 100%;
  max-height: calc(100vh - clamp(120px, 18vw, 200px));
  width: auto; height: auto;
  object-fit: contain;
  margin-inline: auto;
  transform: scale(.985);
  transition: transform .35s var(--ease), opacity .25s var(--ease);
}
.lb.is-shown .lb__img { transform: none; }
.lb__cap { color: var(--taupe); font-size: var(--fs-micro); letter-spacing: .12em; text-transform: uppercase; text-align: center; }
.lb__btn {
  position: absolute;
  display: inline-flex; align-items: center; justify-content: center;
  width: 52px; height: 52px;
  background: none; border: 1px solid var(--line-inv);
  color: var(--ivory); cursor: pointer;
  transition: border-color .3s var(--ease), background .3s var(--ease);
}
.lb__btn:hover, .lb__btn:focus-visible { border-color: var(--gold); background: rgba(176,141,87,.14); }
.lb__btn svg { width: 17px; height: 17px; }
.lb__close { top: clamp(14px, 3vw, 30px); right: clamp(14px, 3vw, 30px); }
.lb__prev  { left: clamp(10px, 2vw, 28px);  top: 50%; transform: translateY(-50%); }
.lb__next  { right: clamp(10px, 2vw, 28px); top: 50%; transform: translateY(-50%); }
@media (max-width: 620px) {
  .lb__prev { left: 12px; top: auto; bottom: 14px; transform: none; }
  .lb__next { right: 12px; top: auto; bottom: 14px; transform: none; }
}
body.lb-open { overflow: hidden; }

/* ---------- About hero ----------------------------------------------------
   Typography-led on white. No photograph: the Gallery immediately below
   carries the imagery, and the white ground makes the cream Gallery read as
   a deliberate divider rather than another beige band. */
.abouthero {
  background: #FFFFFF;
  padding-top: clamp(126px, 12vw, 176px);
  padding-bottom: clamp(56px, 6vw, 96px);
}
/* Never size this in ch. The block holds a ~138px display face, but ch
   resolves against the body font, so 24ch came out at 249px and the headline
   wrapped into a narrow column with 80% of the row left empty. Same trap as
   .hero-fp__copy and .abouthero__inner. */
/* Same 1240px measure as the gallery below it, so the two blocks share a
   left and right edge down the page. */
.abouthero__inner { max-width: min(1240px, 100%); }
.abouthero .h1 { font-size: clamp(3.5rem, 1.2rem + 8.6vw, 9rem); }
/* The rule, the names and the eyebrow all hang off one left edge; the
   credentials sit on a gold hairline so the block has structure without
   needing an image to prop it up. */
/* The hairline runs the full measure, so the names read as a caption under
   the whole headline rather than a stub beside it. */
.abouthero__names {
  margin-top: var(--sp-3);
  padding-top: var(--sp-2);
  border-top: 1px solid var(--gold-hair);
  display: flex; flex-wrap: wrap; gap: .5em clamp(28px, 5vw, 88px);
  font-family: var(--display);
  font-size: clamp(1.125rem, .95rem + .7vw, 1.5rem);
}



/* ---------- Contact -------------------------------------------------------
   Primary action first, alternates under it, form alongside. */
/* Two matched columns: the ladder on the left, the form on the right.
   Both start on the same line, so the top of the form meets the top of the
   booking button. The map is the flexible element in the left column, so it
   stretches to bring the bottom of the ladder level with the bottom of the
   form instead of either side dangling. */
.contact__grid { row-gap: var(--sp-4); align-items: stretch; }
.contact__col { grid-column: 1 / -1; display: flex; flex-direction: column; }
@media (min-width: 940px) {
  .contact__col--info { grid-column: 1 / 6; }
  .contact__col--form { grid-column: 7 / -1; }
}
.contact__cta { width: 100%; }
.methods { margin-top: var(--sp-2); display: grid; }
/* The map sits directly under the address at the width of the column, and
   takes whatever height is left over. The min-height keeps it usable when
   the form is short; the aspect-ratio only applies once stacked. */
.contact__map { margin-top: var(--sp-2); }
.contact__map iframe { display: block; width: 100%; height: 100%; border: 0; }
/* .map carries its own aspect-ratio, which would fight the fill. Drop it once
   the two columns are side by side and let flex decide the height instead. */
@media (min-width: 940px) {
  .map.contact__map { aspect-ratio: auto; flex: 1 1 auto; min-height: 220px; }
}
@media (max-width: 939px) {
  .map.contact__map { aspect-ratio: 4 / 3; flex: 0 0 auto; }
}
.contact__form-inner { display: flex; flex-direction: column; height: 100%; }
/* The form is a grid elsewhere; here it becomes a flex column so one field
   can absorb the leftover height. */
.contact__form-inner .form {
  display: flex; flex-direction: column; gap: var(--sp-2);
  flex: 1 1 auto;
}
/* The textarea absorbs the slack so the two columns finish together without
   the field growing to an unusable height. */
.contact__form-inner .field--grow { flex: 1 1 auto; min-height: 0; }
.contact__form-inner .field--grow textarea { height: 100%; min-height: 120px; }
.method {
  display: grid; grid-template-columns: 96px 1fr; gap: 4px var(--sp-2);
  align-items: baseline;
  padding-block: var(--sp-2);
  border-top: 1px solid var(--gold-hair);
}
.method:last-child { border-bottom: 1px solid var(--gold-hair); }
.method__k { font-size: var(--fs-micro); letter-spacing: .16em; text-transform: uppercase; color: var(--greige); }
.method__v { font-family: var(--display); font-weight: 500; font-size: var(--fs-h3); }
.method__v a {
  display: inline-flex; align-items: center; min-height: 44px;
  text-decoration-line: underline;
  text-decoration-color: var(--gold);
  text-underline-offset: .18em;
  text-decoration-thickness: 1px;
  transition: text-decoration-color .35s var(--ease), color .35s var(--ease);
}
.method__v a:hover, .method__v a:focus-visible { text-decoration-color: currentColor; color: var(--mocha); }

/* ---------- Button refinement ---------------------------------------------
   Espresso ground, ivory text, and a champagne hairline that appears on
   hover as the fill rises. No pill, no gradient, no glow. */
.btn { border-color: transparent; }
.btn::before {
  content: "";
  position: absolute; inset: 0;
  border: 1px solid var(--gold);
  opacity: 0;
  transition: opacity .45s var(--ease);
  pointer-events: none;
  z-index: 1;
}
.btn:hover::before, .btn:focus-visible::before { opacity: 1; }
.btn--ghost { border-color: var(--line); }
.btn--ghost:hover, .btn--ghost:focus-visible { border-color: transparent; }

/* Image hover: anchored, 1.02, nothing more. */
a .figure img, a.svc .figure img, .fig-hover img { transition: transform .7s var(--ease); }
a:hover .figure img, .fig-hover:hover img { transform: scale(1.02); }

/* ---------- Grounds, extended ---------- */
.bg-sand { background: var(--sand); }
.section.inv + .section.inv { padding-top: calc(var(--sec) * .55); }

/* Reduced motion: the motifs stop moving but never stop being visible. */
@media (prefers-reduced-motion: reduce) {
  .reveal-on [data-reveal="mask"] .figure::after,
    .reveal-on [data-reveal="mask"] .gal__btn::after,
  [data-reveal="mask"] .figure::after,
  [data-reveal="mask"] .gal__btn::after { display: none !important; }
  .reveal-on [data-reveal="mask"] img,
  [data-reveal="mask"] img { transform: none !important; }
  .reveal-on [data-reveal] .rule-g,
  .rule-g, .rule-g[data-reveal] { transform: scaleX(1) !important; }
  .reveal-on [data-stagger] > *,
  [data-stagger] > * { opacity: 1 !important; transform: none !important; }
}

/* ---------- Utilities ---------- */
.mt-1 { margin-top: var(--sp-1); }
.mt-2 { margin-top: var(--sp-2); }
.mt-3 { margin-top: var(--sp-3); }
.mt-4 { margin-top: var(--sp-4); }
.mb-1 { margin-bottom: var(--sp-1); }
.mb-2 { margin-bottom: var(--sp-2); }
.mb-3 { margin-bottom: var(--sp-3); }
.mb-4 { margin-bottom: var(--sp-4); }
.center { text-align: center; }
.mx-auto { margin-inline: auto; }
.col-full { grid-column: 1 / -1; }
.visually-hidden {
  position: absolute; width: 1px; height: 1px; overflow: hidden;
  clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap;
}
