/* ==========================================================================
   SÉNIA AESTHETICS — stylesheet
   1. Variables
   2. Reset
   3. Base elements
   4. Layout helpers
   5. Buttons
   6. Hero
   7. Section 01 — The Sénia Experience
   8. Section 02 — Treatments
   9. Section 03 — The Sénia Approach (living line)
  10. Section 04 — The Space
  11. Section 05 — The Invitation
  12. Footer
  13. Accessibility
   ========================================================================== */

/* 1. Variables
   ------------------------------------------------------------------------ */
:root {
  /* Warm travertine palette, taken from the hero photography */
  --color-bg: #f6f1ea;
  --color-surface: #fffdfa;
  --color-ink: #2b241c;
  --color-ink-soft: #6d5f50;
  --color-ivory: #fbf8f3;
  --color-sand: #f2ece2;
  --color-accent: #a68a64;
  --color-accent-deep: #7a6344;
  --color-border: rgba(43, 36, 28, 0.18);
  --color-hairline: rgba(43, 36, 28, 0.15);

  /* Typography */
  --font-body: "Helvetica Neue", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  --font-display: Georgia, "Times New Roman", serif;
  --line-height-body: 1.6;

  /* Spacing scale */
  --space-xs: 0.5rem;
  --space-sm: 1rem;
  --space-md: 1.5rem;
  --space-lg: 2.5rem;
  --space-xl: 4rem;

  /* Layout */
  --content-width: 1280px;
  --gutter: 1.5rem;

  /* Motion */
  --fade: 900ms;
  --ease-silk: cubic-bezier(0.16, 1, 0.3, 1);
  --ease-soft: cubic-bezier(0.25, 0.46, 0.45, 0.94);
}

@media (min-width: 768px) {
  :root {
    --gutter: 2.5rem;
  }
}

@media (min-width: 1280px) {
  :root {
    --gutter: 4rem;
  }
}

/* 2. Reset
   ------------------------------------------------------------------------ */
*,
*::before,
*::after {
  box-sizing: border-box;
}

* {
  margin: 0;
  padding: 0;
}

html {
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
}

img,
picture,
video,
canvas,
svg {
  display: block;
  max-width: 100%;
}

input,
button,
textarea,
select {
  font: inherit;
  color: inherit;
}

button {
  background: none;
  border: 0;
  cursor: pointer;
}

ul[role="list"],
ol[role="list"] {
  list-style: none;
}

a {
  color: inherit;
  text-decoration: none;
}

/* 3. Base elements
   ------------------------------------------------------------------------ */
html,
body {
  min-height: 100%;
}

body {
  background-color: var(--color-bg);
  color: var(--color-ink);
  font-family: var(--font-body);
  line-height: var(--line-height-body);
  overflow-x: hidden;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

h1,
h2,
h3,
h4 {
  font-family: var(--font-display);
  font-weight: 400;
  line-height: 1.08;
  text-wrap: balance;
}

p {
  text-wrap: pretty;
}

/* 4. Layout helpers
   ------------------------------------------------------------------------ */
.container {
  width: 100%;
  max-width: var(--content-width);
  margin-inline: auto;
  padding-inline: var(--gutter);
}

/* 5. Buttons
   ------------------------------------------------------------------------ */
.button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 54px;
  padding: 0 var(--space-lg);
  border: 1px solid transparent;
  border-radius: 999px;
  font-size: 0.8125rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  text-align: center;
  transition: background-color 300ms ease, color 300ms ease, border-color 300ms ease;
}

.button--primary {
  background-color: var(--color-ink);
  color: var(--color-bg);
}

.button--ghost {
  border-color: var(--color-border);
  color: var(--color-ink);
}

@media (hover: hover) {
  .button--primary:hover {
    background-color: #43382c;
  }

  .button--ghost:hover {
    border-color: var(--color-ink);
    background-color: rgba(255, 253, 250, 0.55);
  }
}

/* 6. Hero
   ------------------------------------------------------------------------ */
.hero {
  position: relative;
  display: flex;
  width: 100%;
  min-height: 100vh;        /* fallback for older browsers */
  min-height: 100svh;       /* stable: does not jump when the Safari bar collapses */
  overflow: hidden;
  background-color: var(--color-bg);
}

/* --- media layer --- */
.hero__media {
  position: absolute;
  inset: 0;
  background-color: var(--color-bg); /* never a black or empty area */
}

.hero__video,
.hero__poster,
.hero__poster img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;              /* fills the frame, never distorts */
  object-position: center 45%;
}

.hero__video {
  background-color: var(--color-bg);
}

/* The reserve photograph sits on top and fades away once the video
   is actually rendering frames. */
.hero__poster {
  z-index: 1;
  opacity: 1;
  visibility: visible;
  transition: opacity var(--fade) ease, visibility 0s;
}

.hero[data-video-state="playing"] .hero__poster {
  opacity: 0;
  visibility: hidden;
  transition: opacity var(--fade) ease, visibility 0s linear var(--fade);
}

/* Static gradient only — no blur, no filters, nothing the iPhone has to
   recompute on every frame. */
.hero__scrim {
  position: absolute;
  inset: 0;
  z-index: 2;
  pointer-events: none;
  background-image: linear-gradient(
    to top,
    rgba(246, 241, 234, 0.97) 0%,
    rgba(246, 241, 234, 0.92) 22%,
    rgba(246, 241, 234, 0.62) 44%,
    rgba(246, 241, 234, 0) 68%
  );
}

/* --- content layer --- */
.hero__inner {
  position: relative;
  z-index: 3;
  display: flex;
  align-items: flex-end;
  width: 100%;
  max-width: var(--content-width);
  margin-inline: auto;
  padding: var(--space-xl) var(--gutter)
           calc(var(--space-lg) + env(safe-area-inset-bottom, 0px));
}

.hero__content {
  width: 100%;
}

.hero__eyebrow {
  margin-bottom: var(--space-md);
  color: var(--color-accent-deep);
  font-size: 0.6875rem;
  letter-spacing: 0.34em;
  text-transform: uppercase;
}

.hero__title {
  max-width: 11ch;
  font-size: clamp(2.75rem, 12vw, 4rem);
}

.hero__text {
  max-width: 34ch;
  margin-top: var(--space-md);
  color: var(--color-ink-soft);
  font-size: 1rem;
}

.hero__actions {
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
  margin-top: var(--space-lg);
}

/* --- small phones --- */
@media (max-width: 480px) {
  .hero__title {
    font-size: clamp(2.5rem, 11.5vw, 3.25rem);
  }

  .hero__text {
    font-size: 0.9375rem;
  }
}

/* --- landscape phones: the hero must not become a letterbox --- */
@media (orientation: landscape) and (max-height: 520px) {
  .hero {
    min-height: 34rem;
  }

  .hero__inner {
    align-items: center;
    padding-block: var(--space-lg);
  }

  .hero__title {
    font-size: clamp(2rem, 5.5vw, 3rem);
  }
}

/* --- desktop: editorial composition against the empty wall --- */
@media (min-width: 768px) and (orientation: landscape) {
  .hero__video,
  .hero__poster,
  .hero__poster img {
    object-position: center 50%;
  }

  .hero__scrim {
    background-image: linear-gradient(
      to right,
      rgba(246, 241, 234, 0.94) 0%,
      rgba(246, 241, 234, 0.82) 26%,
      rgba(246, 241, 234, 0.42) 48%,
      rgba(246, 241, 234, 0) 70%
    );
  }

  .hero__inner {
    align-items: center;
    padding-block: var(--space-xl);
  }

  .hero__title {
    max-width: 13ch;
    font-size: clamp(3.25rem, 5.4vw, 5.25rem);
  }

  .hero__text {
    max-width: 38ch;
    margin-top: var(--space-lg);
    font-size: 1.0625rem;
  }

  .hero__actions {
    flex-direction: row;
    flex-wrap: wrap;
    margin-top: var(--space-xl);
  }
}

/* 7. Section 01 — The Sénia Experience
   ------------------------------------------------------------------------ */
.experience {
  background-color: var(--color-ivory);
  padding-block: clamp(5.5rem, 15vw, 11rem);
}

.experience__inner {
  width: 100%;
  max-width: var(--content-width);
  margin-inline: auto;
  padding-inline: var(--gutter);
}

/* --- index label --- */
.experience__index {
  display: flex;
  align-items: center;
  gap: 0.875rem;
  color: var(--color-accent-deep);
  font-size: 0.6875rem;
  letter-spacing: 0.28em;
  line-height: 1;
  text-transform: uppercase;
}

.experience__index::after {
  content: "";
  flex: 1 1 auto;
  max-width: 5rem;
  height: 1px;
  background-color: var(--color-hairline);
}

.experience__number {
  color: var(--color-ink);
}

/* --- headline --- */
.experience__title {
  margin-top: clamp(2.25rem, 7vw, 3.5rem);
  font-size: clamp(2.25rem, 9.5vw, 3rem);
  letter-spacing: 0.005em;
  line-height: 1.12;
  text-transform: uppercase;
  text-wrap: pretty;
}

.experience__line {
  display: block;
}

/* --- supporting copy --- */
.experience__text {
  max-width: 38ch;
  margin-top: clamp(1.75rem, 5vw, 2.5rem);
  color: var(--color-ink-soft);
  font-size: 1rem;
}

/* --- hairline --- */
.experience__rule {
  height: 1px;
  margin-top: clamp(3rem, 10vw, 5rem);
  background-color: var(--color-hairline);
}

/* --- closing line --- */
.experience__closing {
  margin-top: clamp(1.75rem, 5vw, 2.5rem);
  font-size: clamp(0.8125rem, 3.4vw, 0.9375rem);
  letter-spacing: 0.2em;
  line-height: 1.7;
  text-transform: uppercase;
}

/* --- desktop: asymmetric editorial grid ---
   The headline font size is tuned so the two written lines stay two lines
   at every width from 1024px up, instead of re-wrapping into four. */
@media (min-width: 1024px) {
  .experience__inner {
    display: grid;
    grid-template-columns: repeat(12, 1fr);
    column-gap: var(--space-lg);
  }

  .experience__index {
    grid-column: 1 / 6;
    grid-row: 1;
  }

  .experience__title {
    grid-column: 1 / 8;
    grid-row: 2;
    font-size: clamp(2.5rem, 4vw, 3.75rem);
    line-height: 1.06;
  }

  .experience__text {
    grid-column: 8 / 13;
    grid-row: 2;
    align-self: end;
    max-width: 32ch;
    margin-top: 0;
    padding-bottom: 0.4em;
    font-size: 1.0625rem;
  }

  .experience__rule {
    grid-column: 1 / 13;
    grid-row: 3;
  }

  .experience__closing {
    grid-column: 7 / 13;
    grid-row: 4;
    letter-spacing: 0.26em;
  }
}

/* Past 1280px the container stops growing, so the headline gets one more
   column instead of more pixels it cannot use. */
@media (min-width: 1280px) {
  .experience__title {
    grid-column: 1 / 9;
    font-size: clamp(3.25rem, 4vw, 3.75rem);
  }

  .experience__text {
    grid-column: 9 / 13;
  }
}

/* --- reveal on scroll ---
   Everything is visible by default. The hidden state only exists once
   main.js confirms the browser can observe and the reader has not asked
   for reduced motion, so the copy can never be stranded invisible. */
.reveal-ready [data-reveal] {
  opacity: 0;
  transform: translateY(1.5rem);
  transition:
    opacity 900ms cubic-bezier(0.22, 0.61, 0.36, 1),
    transform 900ms cubic-bezier(0.22, 0.61, 0.36, 1);
  transition-delay: var(--reveal-delay, 0ms);
  will-change: opacity, transform;
}

.reveal-ready .is-revealed [data-reveal] {
  opacity: 1;
  transform: none;
}

/* The hairline draws itself instead of sliding. */
.reveal-ready .experience__rule {
  opacity: 1;
  transform: scaleX(0);
  transform-origin: left center;
  transition: transform 1100ms cubic-bezier(0.22, 0.61, 0.36, 1);
  transition-delay: var(--reveal-delay, 0ms);
}

.reveal-ready .is-revealed .experience__rule {
  transform: scaleX(1);
}

.experience__index { --reveal-delay: 0ms; }
.experience__line:nth-child(1) { --reveal-delay: 90ms; }
.experience__line:nth-child(2) { --reveal-delay: 200ms; }
.experience__text { --reveal-delay: 340ms; }
.experience__rule { --reveal-delay: 440ms; }
.experience__closing { --reveal-delay: 560ms; }

/* 8. Section 02 — Treatments
   ------------------------------------------------------------------------ */
.treatments {
  background-color: var(--color-sand);
  /* Lighter at the top: section 01 already ends on a generous margin,
     and two full paddings back to back read as an empty screen. */
  padding-block: clamp(4rem, 10vw, 7rem) clamp(6rem, 16vw, 12rem);
}

.treatments__inner {
  width: 100%;
  max-width: var(--content-width);
  margin-inline: auto;
  padding-inline: var(--gutter);
}

/* --- header --- */
.treatments__index {
  display: flex;
  align-items: center;
  gap: 0.875rem;
  color: var(--color-accent-deep);
  font-size: 0.6875rem;
  letter-spacing: 0.28em;
  line-height: 1;
  text-transform: uppercase;
}

.treatments__index::after {
  content: "";
  flex: 1 1 auto;
  max-width: 5rem;
  height: 1px;
  background-color: var(--color-hairline);
}

.treatments__number {
  color: var(--color-ink);
}

.treatments__title {
  margin-top: clamp(2.25rem, 7vw, 3.5rem);
  font-size: clamp(2.5rem, 10.5vw, 3.25rem);
  letter-spacing: 0.005em;
  line-height: 1.08;
  text-transform: uppercase;
}

.treatments__line {
  display: block;
}

.treatments__intro {
  max-width: 34ch;
  margin-top: clamp(1.75rem, 5vw, 2.5rem);
  color: var(--color-ink-soft);
  font-size: 1rem;
}

/* --- the three treatments --- */
.treatments__list {
  list-style: none;
  margin-top: clamp(4rem, 12vw, 7rem);
}

.treatment__media {
  position: relative;
  overflow: hidden;
  aspect-ratio: 4 / 5;           /* reserved before the photo loads */
  background-color: var(--color-ivory);
}

.treatment__frame,
.treatment__frame > * {
  width: 100%;
  height: 100%;
}

/* All three photographs are 3:4. The frames crop between 6% and 11%, so the
   focal point of each is nudged into the safe band: no face, hand or device
   is ever cut, at any of the frame ratios or at any tested phone width. */
.treatment__image {
  object-fit: cover;
  object-position: center 45%;
}

.treatment--b .treatment__image {
  object-position: center 42%;
}

.treatment--c .treatment__image {
  object-position: center 46%;
}

.treatment__body {
  margin-top: var(--space-md);
}

.treatment__count {
  display: flex;
  align-items: center;
  gap: 0.875rem;
  color: var(--color-accent-deep);
  font-size: 0.6875rem;
  letter-spacing: 0.24em;
  line-height: 1;
}

.treatment__count::after {
  content: "";
  flex: 1 1 auto;
  max-width: 3.5rem;
  height: 1px;
  background-color: var(--color-hairline);
}

.treatment__name {
  margin-top: var(--space-sm);
  font-size: clamp(1.5rem, 5.5vw, 1.875rem);
  letter-spacing: 0.01em;
  line-height: 1.15;
  text-transform: uppercase;
}

.treatment__text {
  max-width: 34ch;
  margin-top: 0.75rem;
  color: var(--color-ink-soft);
  font-size: 0.9375rem;
}

/* --- mobile rhythm ---
   Three vertical blocks that alternate width, proportion and the side the
   number sits on, so the column never reads as a repeated card. */
@media (max-width: 1023px) {
  .treatment + .treatment {
    margin-top: clamp(4.5rem, 15vw, 6.5rem);
  }

  .treatment--b .treatment__media {
    width: 86%;
    margin-left: auto;
    aspect-ratio: 2 / 3;
  }

  .treatment--b .treatment__body {
    padding-left: 14%;
  }

  .treatment--b .treatment__count {
    flex-direction: row-reverse;
  }

  .treatment--c .treatment__media {
    width: 92%;
  }

  .treatment--c .treatment__body {
    padding-left: 8%;
  }
}

/* --- desktop: staggered editorial row, not three equal columns --- */
@media (min-width: 1024px) {
  .treatments__head {
    display: grid;
    grid-template-columns: repeat(12, 1fr);
    column-gap: var(--space-lg);
  }

  .treatments__index {
    grid-column: 1 / 6;
    grid-row: 1;
  }

  .treatments__title {
    grid-column: 1 / 8;
    grid-row: 2;
    font-size: clamp(2.75rem, 4.4vw, 4rem);
  }

  .treatments__intro {
    grid-column: 8 / 13;
    grid-row: 2;
    align-self: end;
    margin-top: 0;
    padding-bottom: 0.4em;
    font-size: 1.0625rem;
  }

  .treatments__list {
    display: grid;
    grid-template-columns: repeat(12, 1fr);
    column-gap: var(--space-lg);
    align-items: start;
  }

  .treatment--a {
    grid-column: 1 / 5;
  }

  .treatment--b {
    grid-column: 6 / 9;
    margin-top: 7rem;
  }

  .treatment--c {
    grid-column: 10 / 13;
    margin-top: 2.5rem;
  }

  .treatment--a .treatment__media { aspect-ratio: 3 / 4; }
  .treatment--b .treatment__media { aspect-ratio: 2 / 3; }
  .treatment--c .treatment__media { aspect-ratio: 4 / 5; }

  .treatment__body {
    margin-top: var(--space-lg);
  }
}

/* --- motion: "the art of stillness" -------------------------------------

   Three distinct reveals that share one vocabulary: the same easing family,
   related durations, and copy that always arrives after its photograph has
   started moving. Nothing pins, nothing scrubs the scroll, nothing recalculates
   layout — every moving property is transform, opacity or clip-path.

   The whole choreography is written so the RESTING state is the plain rule and
   the starting state only exists under :not(.is-revealed). If the script never
   runs, if the observer never fires, or if the reader asked for reduced motion,
   every photograph and every line of copy is simply there. */

.treatments__index { --reveal-delay: 0ms; }
.treatments__line:nth-child(1) { --reveal-delay: 120ms; }
.treatments__line:nth-child(2) { --reveal-delay: 260ms; }
.treatments__intro { --reveal-delay: 460ms; }

.treatment__media { --reveal-delay: 0ms; }

/* Copy enters late enough that the picture is already in motion. */
.treatment--a .treatment__count { --reveal-delay: 620ms; }
.treatment--a .treatment__name { --reveal-delay: 720ms; }
.treatment--a .treatment__text { --reveal-delay: 940ms; }

.treatment--b .treatment__count { --reveal-delay: 520ms; }
.treatment--b .treatment__name { --reveal-delay: 620ms; }
.treatment--b .treatment__text { --reveal-delay: 820ms; }

.treatment--c .treatment__count { --reveal-delay: 460ms; }
.treatment--c .treatment__name { --reveal-delay: 560ms; }
.treatment--c .treatment__text { --reveal-delay: 720ms; }

/* Slower and softer than section 01 — this section is meant to breathe. */
.reveal-ready .treatments [data-reveal] {
  transition-duration: 1100ms;
  transition-timing-function: var(--ease-soft);
}

/* No photograph ever fades. Each one is uncovered its own way. */
.reveal-ready .treatment__media {
  opacity: 1;
  transform: none;
}

/* 01 — SILK REVEAL ------------------------------------------------------
   A panel in the section's own ivory slips downward off the picture. Its
   leading edge is feathered and starts outside the frame, so what passes
   over the photograph is a soft edge of cloth rather than the hard line of
   a blind or a progress bar. */
.treatment--a .treatment__media {
  position: relative;
}

.treatment__veil {
  position: absolute;
  top: -14%;
  right: 0;
  left: 0;
  height: 114%;
  z-index: 2;
  pointer-events: none;
  background-image: linear-gradient(
    to bottom,
    rgba(242, 236, 226, 0) 0%,
    rgba(242, 236, 226, 0.55) 6%,
    rgba(242, 236, 226, 0.92) 11%,
    var(--color-sand) 15%,
    var(--color-sand) 100%
  );
  /* Resting position: already gone. Nothing can leave it covering. */
  transform: translateY(100%);
}

.reveal-ready .treatment__veil {
  transition: transform 1500ms var(--ease-silk);
}

.reveal-ready .treatment--a:not(.is-revealed) .treatment__veil {
  transform: translateY(0);
}

.reveal-ready .treatment--a .treatment__image {
  transition: transform 1800ms var(--ease-silk);
}

.reveal-ready .treatment--a:not(.is-revealed) .treatment__image {
  transform: scale(1.07);
}

/* 02 — OPENING FRAME ----------------------------------------------------
   The frame starts as a low horizontal band and opens from the centre until
   the whole composition is in view. Clipping the frame is what grows — the
   photograph keeps its proportions throughout and is never stretched. */
.reveal-ready .treatment--b .treatment__media {
  transition: clip-path 1500ms var(--ease-silk);
  transition-delay: var(--reveal-delay, 0ms);
}

.reveal-ready .treatment--b:not(.is-revealed) .treatment__media {
  clip-path: inset(41% 0 41% 0);
}

.reveal-ready .treatment--b .treatment__image {
  transition: transform 1700ms var(--ease-silk);
}

.reveal-ready .treatment--b:not(.is-revealed) .treatment__image {
  transform: scale(1.05);
}

/* 03 — COMPOSITION IN HARMONY -------------------------------------------
   Picture and copy start a little apart and settle into alignment, the way
   two elements find their place on a spread. Small distances, opposite
   directions, no overshoot. */
.reveal-ready .treatment--c .treatment__media {
  transition: transform 1400ms var(--ease-silk);
}

.reveal-ready .treatment--c .treatment__body {
  transition: transform 1400ms var(--ease-silk);
  transition-delay: 120ms;
}

.reveal-ready .treatment--c:not(.is-revealed) .treatment__media {
  transform: translateX(-26px);
}

.reveal-ready .treatment--c:not(.is-revealed) .treatment__body {
  transform: translateX(14px);
}

/* The hairline arrives last, once both halves have stopped moving. */
.treatment--c .treatment__name {
  position: relative;
}

.treatment--c .treatment__name::after {
  content: "";
  position: absolute;
  bottom: -0.85rem;
  left: 0;
  width: 3.5rem;
  height: 1px;
  background-color: var(--color-hairline);
  transform: scaleX(1);
  transform-origin: left center;
}

.treatment--c .treatment__text {
  margin-top: 1.9rem;
}

.reveal-ready .treatment--c .treatment__name::after {
  transition: transform 1000ms var(--ease-soft);
  transition-delay: 1150ms;
}

.reveal-ready .treatment--c:not(.is-revealed) .treatment__name::after {
  transform: scaleX(0);
}

/* --- mobile: the same three ideas, retuned for a thumb ------------------
   Shorter travel, shorter durations, and a vertical convergence for 03 that
   suits a column instead of a spread. Section 01 is left alone. */
@media (max-width: 1023px) {
  .reveal-ready .treatments [data-reveal] {
    transition-duration: 700ms;
  }

  /* The media blocks are excluded here rather than reset afterwards: a later
     `transform: none` on them outranks each treatment's own starting offset
     and silently cancels it. */
  .reveal-ready .treatments [data-reveal-group]:not(.is-revealed)
    [data-reveal]:not(.treatment__media) {
    transform: translateY(0.875rem);
  }

  .reveal-ready .treatment__veil {
    transition-duration: 1000ms;
  }

  .reveal-ready .treatment--a .treatment__image {
    transition-duration: 1250ms;
  }

  .reveal-ready .treatment--a:not(.is-revealed) .treatment__image {
    transform: scale(1.04);
  }

  .reveal-ready .treatment--b .treatment__media {
    transition-duration: 1050ms;
  }

  .reveal-ready .treatment--b:not(.is-revealed) .treatment__media {
    clip-path: inset(36% 0 36% 0);
  }

  .reveal-ready .treatment--b .treatment__image {
    transition-duration: 1200ms;
  }

  .reveal-ready .treatment--b:not(.is-revealed) .treatment__image {
    transform: scale(1.035);
  }

  .reveal-ready .treatment--c .treatment__media,
  .reveal-ready .treatment--c .treatment__body {
    transition-duration: 950ms;
  }

  .reveal-ready .treatment--c:not(.is-revealed) .treatment__media {
    transform: translateY(-16px);
  }

  .reveal-ready .treatment--c:not(.is-revealed) .treatment__body {
    transform: translateY(9px);
  }

  .reveal-ready .treatment--c .treatment__name::after {
    transition-delay: 800ms;
  }

  /* Copy delays tighten so a block finishes while it is still on screen. */
  .treatment--a .treatment__count { --reveal-delay: 420ms; }
  .treatment--a .treatment__name { --reveal-delay: 500ms; }
  .treatment--a .treatment__text { --reveal-delay: 650ms; }

  .treatment--b .treatment__count { --reveal-delay: 380ms; }
  .treatment--b .treatment__name { --reveal-delay: 460ms; }
  .treatment--b .treatment__text { --reveal-delay: 600ms; }

  .treatment--c .treatment__count { --reveal-delay: 320ms; }
  .treatment--c .treatment__name { --reveal-delay: 400ms; }
  .treatment--c .treatment__text { --reveal-delay: 540ms; }
}

/* 9. Section 03 — The Sénia Approach
   ------------------------------------------------------------------------
   A pause after the photographs of section 02: no images, no icons, just
   type, air and one hairline that keeps track of where the reader is.

   Everything here is legible with no JavaScript at all. The resting state
   below is the FINISHED state — full contrast, full rail. The progressive
   states only exist once main.js adds `approach-ready`, so a step can never
   be left dim or a description left hidden. */
.approach {
  background-color: var(--color-ivory);
  padding-block: clamp(5rem, 13vw, 9rem) clamp(6rem, 16vw, 11rem);
}

.approach__inner {
  width: 100%;
  max-width: var(--content-width);
  margin-inline: auto;
  padding-inline: var(--gutter);
}

/* --- left column --- */
.approach__index {
  display: flex;
  align-items: center;
  gap: 0.875rem;
  color: var(--color-accent-deep);
  font-size: 0.6875rem;
  letter-spacing: 0.28em;
  line-height: 1;
  text-transform: uppercase;
}

.approach__index::after {
  content: "";
  flex: 1 1 auto;
  max-width: 5rem;
  height: 1px;
  background-color: var(--color-hairline);
}

.approach__number {
  color: var(--color-ink);
}

.approach__title {
  margin-top: clamp(2.25rem, 7vw, 3.25rem);
  font-size: clamp(2.25rem, 9.5vw, 3rem);
  letter-spacing: 0.005em;
  line-height: 1.1;
  text-transform: uppercase;
}

.approach__line {
  display: block;
}

.approach__text {
  max-width: 36ch;
  margin-top: clamp(1.75rem, 5vw, 2.25rem);
  color: var(--color-ink-soft);
  font-size: 1rem;
}

/* --- the track --- */
.approach__track {
  position: relative;
  margin-top: clamp(3.5rem, 11vw, 5rem);
  padding-left: 2.75rem;
}

.approach__rail {
  position: absolute;
  top: var(--rail-top, 0px);
  left: 3px;
  width: 1px;
  height: var(--rail-height, 100%);
  background-color: var(--color-hairline);
}

/* The travelling line: a warmer, darker tone over the base hairline.
   Only transform moves, so the scroll stays on the compositor. */
.approach__rail-fill {
  display: block;
  width: 100%;
  height: 100%;
  background-color: var(--color-accent-deep);
  opacity: 0.8;
  transform-origin: top center;
  transform: scaleY(1);
}

.approach__steps {
  list-style: none;
}

.approach__step {
  position: relative;
}

.approach__step + .approach__step {
  margin-top: clamp(3.25rem, 10vw, 4.5rem);
}

.approach__marker {
  position: absolute;
  top: 0.4rem;
  left: -2.75rem;
  width: 7px;
  height: 7px;
  border: 1px solid var(--color-accent-deep);
  border-radius: 50%;
  background-color: var(--color-accent-deep);
}

.approach__step-number {
  color: var(--color-accent-deep);
  font-size: 0.6875rem;
  letter-spacing: 0.24em;
  line-height: 1;
}

.approach__step-name {
  color: var(--color-ink);
}

.approach__step-text {
  max-width: 34ch;
  margin-top: 0.75rem;
  color: var(--color-ink-soft);
  font-size: 0.9375rem;
}

/* --- desktop: two asymmetric areas, the left one held in place --- */
@media (min-width: 1024px) {
  .approach__inner {
    display: grid;
    grid-template-columns: repeat(12, 1fr);
    column-gap: var(--space-lg);
    align-items: start;
  }

  .approach__intro {
    grid-column: 1 / 7;
    /* Sticky, not pinned: it holds inside this section and releases at its
       end, because that is all a sticky element can do. No scroll is
       blocked, no artificial height is added. */
    position: sticky;
    top: clamp(5rem, 16vh, 8rem);
  }

  .approach__title {
    font-size: clamp(2.25rem, 3.5vw, 2.75rem);
  }

  .approach__track {
    grid-column: 8 / 13;
    margin-top: 0.5rem;
    padding-left: 3.5rem;
  }

  .approach__rail {
    left: 3px;
  }

  .approach__marker {
    left: -3.5rem;
  }

  .approach__step + .approach__step {
    margin-top: clamp(4.5rem, 9vw, 7rem);
  }

  .approach__step-text {
    font-size: 1rem;
  }
}

/* --- progressive states, only once the script is running --------------- */
.approach-ready .approach__rail-fill {
  transform: scaleY(var(--approach-progress, 0));
}

/* A step that has not been reached stays perfectly readable — it is only
   quieter, never hidden. */
.approach-ready .approach__step-name {
  color: var(--color-ink-soft);
  transition: color 700ms var(--ease-soft);
}

.approach-ready .approach__step.is-reached .approach__step-name {
  color: var(--color-ink);
}

.approach-ready .approach__marker {
  border-color: var(--color-hairline);
  background-color: var(--color-ivory);
  transform: scale(0.8);
  transition:
    background-color 600ms var(--ease-soft),
    border-color 600ms var(--ease-soft),
    transform 600ms var(--ease-soft);
}

.approach-ready .approach__step.is-reached .approach__marker {
  border-color: var(--color-accent-deep);
  background-color: var(--color-accent-deep);
  transform: scale(1);
}


.approach-ready .approach__step-number {
  opacity: 0.55;
  transition: opacity 600ms var(--ease-soft);
}

.approach-ready .approach__step.is-reached .approach__step-number {
  opacity: 1;
}

/* The description enters once, the first time its step comes into view, and
   then stays. Scrolling back up moves the highlight, never the copy. */
.approach-ready .approach__step-text {
  opacity: 0;
  transform: translateY(0.75rem);
  transition:
    opacity 900ms var(--ease-soft),
    transform 900ms var(--ease-soft);
  transition-delay: 120ms;
}

.approach-ready .approach__step.is-seen .approach__step-text {
  opacity: 1;
  transform: none;
}

/* --- mobile: lighter, shorter, no sticky ------------------------------- */
@media (max-width: 1023px) {
  .approach__intro {
    position: static;
  }

  .approach-ready .approach__step-text {
    transform: translateY(0.5rem);
    transition-duration: 650ms;
    transition-delay: 80ms;
  }

  .approach-ready .approach__step-name {
    transition-duration: 500ms;
  }

  .approach-ready .approach__marker,
  .approach-ready .approach__step-number {
    transition-duration: 420ms;
  }

}

/* --- Living line: the travelling point --------------------------------
   A fine ring with a small core, riding a hairline whose warm fill
   dissolves at its leading edge. The dissolve matters: a solid bar ending
   in a knob is the shape of a loading indicator, and this is meant to read
   as a path being walked. */
.approach__rail-fill {
  background-image: linear-gradient(
    to bottom,
    var(--color-accent-deep) 0%,
    var(--color-accent-deep) 62%,
    rgba(122, 99, 68, 0.35) 88%,
    rgba(122, 99, 68, 0) 100%
  );
  background-color: transparent;
  opacity: 0.85;
}

.approach__traveller {
  position: absolute;
  top: var(--rail-top, 0px);
  left: 3px;
  width: 13px;
  height: 13px;
  margin-top: -6px;
  margin-left: -6px;
  border: 1px solid var(--color-accent-deep);
  border-radius: 50%;
  background-color: var(--color-ivory);
  opacity: 0;
  pointer-events: none;
}

.approach__traveller::after {
  content: "";
  position: absolute;
  top: 50%;
  left: 50%;
  width: 3px;
  height: 3px;
  margin: -1.5px 0 0 -1.5px;
  border-radius: 50%;
  background-color: var(--color-accent-deep);
}

/* It only exists while the script is driving it. */
.approach-ready .approach__traveller {
  opacity: 1;
  transform: translateY(calc(var(--approach-progress, 0) * var(--rail-height, 0px)));
  transition: opacity 600ms var(--ease-soft);
}

/* --- the step as a control -------------------------------------------- */
.approach__heading {
  font: inherit;
  font-weight: 400;
}

.approach__trigger {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0.875rem;
  width: 100%;
  /* Comfortable to hit with a thumb without drawing a box around itself. */
  min-height: 48px;
  padding: 0.25rem 0 0.5rem;
  text-align: left;
  cursor: pointer;
}

.approach__trigger:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: 6px;
}

.approach__step-name {
  display: block;
  font-family: var(--font-display);
  font-size: clamp(1.375rem, 5vw, 1.625rem);
  letter-spacing: 0.02em;
  line-height: 1.15;
  text-transform: uppercase;
}

/* --- the complementary phrase ------------------------------------------
   Its room is reserved at all times, so revealing it moves nothing else on
   the page. Opacity and transform only — no height animation. */
.approach__note {
  margin-top: 0.85rem;
  color: var(--color-accent-deep);
  font-family: var(--font-display);
  font-size: 0.9375rem;
  font-style: italic;
  line-height: 1.5;
}

@media (hover: hover) {
  .approach__trigger:hover .approach__step-name {
    color: var(--color-ink);
  }
}

/* --- active step ------------------------------------------------------- */
.approach-ready .approach__note {
  opacity: 0;
  transform: translateY(0.4rem);
  transition:
    opacity 700ms var(--ease-soft),
    transform 700ms var(--ease-soft);
}

.approach-ready .approach__step.is-active .approach__note {
  opacity: 1;
  transform: none;
}

/* The small shift in composition that marks the chosen step. */
.approach-ready .approach__heading,
.approach-ready .approach__step-text,
.approach-ready .approach__note {
  transition-property: opacity, transform, color;
  transition-duration: 700ms;
  transition-timing-function: var(--ease-soft);
}

.approach-ready .approach__step.is-active .approach__heading,
.approach-ready .approach__step.is-active .approach__step-text,
.approach-ready .approach__step.is-active .approach__note {
  transform: translateX(8px);
}

.approach-ready .approach__step.is-active .approach__step-text {
  color: #55483b;
}

.approach-ready .approach__step.is-active .approach__step-name {
  color: var(--color-ink);
}

.approach-ready .approach__step.is-active .approach__marker {
  border-color: var(--color-accent-deep);
  background-color: var(--color-accent-deep);
  transform: scale(1.45);
}

@media (max-width: 1023px) {
  .approach-ready .approach__step.is-active .approach__heading,
  .approach-ready .approach__step.is-active .approach__step-text,
  .approach-ready .approach__step.is-active .approach__note {
    transform: translateX(5px);
  }

  .approach-ready .approach__step.is-active .approach__marker {
    transform: scale(1.35);
  }

  .approach-ready .approach__note {
    transition-duration: 520ms;
  }
}

/* 10. Section 04 — The Space
   ------------------------------------------------------------------------
   An architectural gallery that changes only when someone chooses to change
   it. Nothing here is driven by scroll and nothing rotates on a timer. */
.space {
  background-color: var(--color-sand);
  padding-block: clamp(4.5rem, 12vw, 8rem) clamp(6rem, 16vw, 11rem);
}

.space__inner {
  width: 100%;
  max-width: var(--content-width);
  margin-inline: auto;
  padding-inline: var(--gutter);
}

.space__index {
  display: flex;
  align-items: center;
  gap: 0.875rem;
  color: var(--color-accent-deep);
  font-size: 0.6875rem;
  letter-spacing: 0.28em;
  line-height: 1;
  text-transform: uppercase;
}

.space__index::after {
  content: "";
  flex: 1 1 auto;
  max-width: 5rem;
  height: 1px;
  background-color: var(--color-hairline);
}

.space__number {
  color: var(--color-ink);
}

.space__title {
  margin-top: clamp(2.25rem, 7vw, 3.25rem);
  font-size: clamp(2.25rem, 9.5vw, 3rem);
  letter-spacing: 0.005em;
  line-height: 1.1;
  text-transform: uppercase;
}

.space__line {
  display: block;
}

.space__text {
  max-width: 36ch;
  margin-top: clamp(1.75rem, 5vw, 2.25rem);
  color: var(--color-ink-soft);
  font-size: 1rem;
}

/* --- the stage --- */
.space__gallery {
  margin-top: clamp(3rem, 9vw, 4.5rem);
}

.space__stage {
  position: relative;
  aspect-ratio: 4 / 5;          /* space reserved before anything loads */
  overflow: hidden;
  background-color: var(--color-ivory);
}

.space__slide {
  position: absolute;
  inset: 0;
  opacity: 0;
  pointer-events: none;
}

.space__slide.is-active {
  opacity: 1;
  pointer-events: auto;
}

.space__frame,
.space__frame picture,
.space__image {
  width: 100%;
  height: 100%;
}

.space__image {
  object-fit: cover;
  object-position: center;
}

.space__placeholder {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  height: 100%;
  padding: 1rem;
  background-color: #e9e0d3;
  border: 1px dashed rgba(43, 36, 28, 0.38);
  color: var(--color-ink-soft);
  text-align: center;
}

.space__placeholder-tag {
  color: var(--color-ink);
  font-size: 0.625rem;
  letter-spacing: 0.24em;
  text-transform: uppercase;
}

.space__placeholder-file {
  font-family: var(--font-display);
  font-size: 1.0625rem;
}

.space__placeholder-spec {
  font-size: 0.625rem;
  letter-spacing: 0.1em;
}

/* --- caption and controls --- */
.space__bar {
  margin-top: var(--space-md);
}

.space__caption {
  font-family: var(--font-display);
  font-size: clamp(1.25rem, 4.5vw, 1.5rem);
  letter-spacing: 0.02em;
  text-transform: uppercase;
}

.space__controls {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem;
  margin-top: var(--space-md);
}

.space__control {
  display: flex;
  align-items: baseline;
  gap: 0.75rem;
  min-height: 48px;
  padding: 0.75rem 0;
  color: var(--color-ink-soft);
  cursor: pointer;
  transition: color 500ms var(--ease-soft);
}

.space__control + .space__control {
  margin-left: var(--space-lg);
}

.space__control-number {
  font-size: 0.625rem;
  letter-spacing: 0.24em;
}

.space__control-name {
  position: relative;
  font-size: 0.8125rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  white-space: nowrap;
}

/* The thin rule under the chosen control — the only decoration here. */
.space__control-name::after {
  content: "";
  position: absolute;
  right: 0;
  bottom: -0.55rem;
  left: 0;
  height: 1px;
  background-color: var(--color-accent-deep);
  transform: scaleX(0);
  transform-origin: left center;
  transition: transform 600ms var(--ease-soft);
}

.space__control.is-active {
  color: var(--color-ink);
}

.space__control.is-active .space__control-name::after {
  transform: scaleX(1);
}

.space__control:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: 6px;
}

@media (hover: hover) {
  .space__control:hover {
    color: var(--color-ink);
  }
}

/* --- desktop: a wide cinematic frame ---------------------------------- */
@media (min-width: 1024px) {
  .space__head {
    display: grid;
    grid-template-columns: repeat(12, 1fr);
    column-gap: var(--space-lg);
  }

  .space__index {
    grid-column: 1 / 6;
    grid-row: 1;
  }

  .space__title {
    grid-column: 1 / 8;
    grid-row: 2;
    font-size: clamp(2.5rem, 4vw, 3.25rem);
  }

  .space__text {
    grid-column: 8 / 13;
    grid-row: 2;
    align-self: end;
    margin-top: 0;
    padding-bottom: 0.4em;
    font-size: 1.0625rem;
  }

  .space__stage {
    aspect-ratio: 16 / 9;
  }

  /* Name on the left, controls on the right, both below the picture so no
     copy is ever laid over an unknown part of the photograph. */
  .space__bar {
    display: flex;
    align-items: flex-end;
    justify-content: space-between;
    gap: var(--space-lg);
    margin-top: var(--space-lg);
  }

  .space__controls {
    margin-top: 0;
  }
}

/* --- the transition ----------------------------------------------------
   Opacity and a minimal approach in scale. Both are composited, so a change
   costs nothing in layout. Without the script the first slide is the one
   carrying `is-active` in the markup, so the section still shows a picture. */
.space-ready .space__slide {
  transform: scale(1.035);
  transition:
    opacity 1100ms var(--ease-soft),
    transform 1400ms var(--ease-silk);
}

.space-ready .space__slide.is-active {
  transform: none;
}

.space-ready .space__caption {
  transition:
    opacity 420ms var(--ease-soft),
    transform 420ms var(--ease-soft),
    letter-spacing 420ms var(--ease-soft);
}

.space-ready .space__caption.is-changing {
  opacity: 0;
  transform: translateY(0.35rem);
  letter-spacing: 0.12em;
}

@media (max-width: 1023px) {
  .space-ready .space__slide {
    transform: scale(1.025);
    transition-duration: 800ms, 1000ms;
  }

  .space-ready .space__caption {
    transition-duration: 340ms;
  }
}

/* 11. Section 05 — The Invitation
   ------------------------------------------------------------------------
   The closing pause: one hairline frame that opens around the headline while
   the ground beneath it warms from sand to ivory.

   Everything below rests in its FINISHED state — frame open, ground ivory,
   every word in place. The narrow, unlit starting state is written only
   under `.invitation-ready`, the class the script adds to <html>. So if the
   script never runs, or the reader has asked for less motion, the section is
   simply a finished, readable page.

   Two composited properties carry the whole movement: the scale of the frame
   and the opacity of the wash. Both read the same number, --invitation-open,
   which the script writes on the section and nothing else ever computes. */
.invitation {
  position: relative;
  /* The wash can never reach past the section and open a horizontal scroll. */
  overflow: hidden;
  background-color: var(--color-bg);
  padding-block: clamp(5rem, 14vw, 8rem) clamp(6rem, 16vw, 10rem);

  /* How tight the frame is at the start. Both values are chosen so the rule
     never crosses a word at any point in its travel. */
  --frame-x: 0.94;
  --frame-y: 0.84;
}

/* The ivory ground, laid over the sand one and faded in as the frame opens.
   A background-color cannot be animated cheaply; an opacity can. */
.invitation__wash {
  position: absolute;
  inset: 0;
  z-index: 0;
  background-color: var(--color-ivory);
  opacity: 1;
  pointer-events: none;
}

.invitation__inner {
  position: relative;
  z-index: 1;
  width: 100%;
  max-width: var(--content-width);
  margin-inline: auto;
  padding-inline: var(--gutter);
}

.invitation__stage {
  position: relative;
  padding: clamp(3.5rem, 9vw, 8rem) clamp(1.25rem, 8vw, 7rem);
}

/* The frame is built at its OPEN size and scaled down while closed, so the
   state it has to look perfect in — the one it comes to rest in — is
   scale(1), where the rule is exactly one pixel. */
.invitation__frame {
  position: absolute;
  inset: 0;
  z-index: 0;
  border: 1px solid var(--color-hairline);
  pointer-events: none;
}

.invitation__content {
  position: relative;
  z-index: 1;
}

/* --- the words -------------------------------------------------------- */
.invitation__index {
  display: flex;
  align-items: center;
  gap: 0.875rem;
  color: var(--color-accent-deep);
  font-size: 0.6875rem;
  letter-spacing: 0.28em;
  line-height: 1;
  text-transform: uppercase;
}

.invitation__index::after {
  content: "";
  flex: 1 1 auto;
  max-width: 4rem;
  height: 1px;
  background-color: var(--color-hairline);
}

.invitation__number {
  color: var(--color-ink);
}

.invitation__title {
  margin-top: clamp(1.75rem, 6vw, 2.75rem);
  font-size: clamp(1.9375rem, 7.6vw, 4rem);
  letter-spacing: 0.005em;
  line-height: 1.08;
  text-transform: uppercase;
}

/* The two lines are the two the copy was written with, not a wrap the
   browser happened to choose at this width. */
.invitation__line {
  display: block;
}

.invitation__text {
  max-width: 34ch;
  margin-top: clamp(1.5rem, 5vw, 2.25rem);
  color: var(--color-ink-soft);
  font-size: 1rem;
}

.invitation__action {
  margin-top: clamp(2.25rem, 7vw, 3.25rem);
}

/* --- the invitation itself ---------------------------------------------
   A line that reaches toward the arrow, and an arrow that shifts a little to
   meet it. Nothing springs, nothing glows. */
.invitation__cta {
  display: flex;
  align-items: center;
  width: 100%;
  min-height: 58px;
  padding: 0 1.5rem;
  border: 1px solid var(--color-border);
  border-radius: 999px;
  background-color: transparent;
  color: var(--color-ink);
  font-size: 0.75rem;
  letter-spacing: 0.18em;
  line-height: 1;
  text-transform: uppercase;
  cursor: pointer;
  transition:
    background-color 520ms var(--ease-soft),
    border-color 520ms var(--ease-soft);
}

.invitation__cta-label {
  flex: 0 0 auto;
}

.invitation__cta-line {
  flex: 1 1 auto;
  height: 1px;
  min-width: 1.5rem;
  margin-inline: 1rem 0.75rem;
  background-color: currentColor;
  opacity: 0.4;
  transform: scaleX(0.3);
  transform-origin: left center;
  transition:
    transform 560ms var(--ease-silk),
    opacity 560ms var(--ease-soft);
}

.invitation__cta-arrow {
  flex: 0 0 auto;
  font-size: 0.9375rem;
  line-height: 1;
  transition: transform 560ms var(--ease-silk);
}

/* The arrow moves 5px inside 24px of padding, so it never leaves the button
   it belongs to and the target never changes shape. */
@media (hover: hover) {
  .invitation__cta:hover {
    border-color: var(--color-accent);
    background-color: var(--color-sand);
  }

  .invitation__cta:hover .invitation__cta-line {
    opacity: 0.75;
    transform: scaleX(1);
  }

  .invitation__cta:hover .invitation__cta-arrow {
    transform: translateX(5px);
  }
}

/* Touch and keyboard get the same gesture without borrowing hover: a finger
   never hovers, and a focus ring on its own would say nothing about the
   control it is sitting on. */
.invitation__cta:focus-visible,
.invitation__cta:active {
  border-color: var(--color-accent);
  background-color: var(--color-sand);
}

.invitation__cta:focus-visible .invitation__cta-line,
.invitation__cta:active .invitation__cta-line {
  opacity: 0.75;
  transform: scaleX(1);
}

.invitation__cta:focus-visible .invitation__cta-arrow,
.invitation__cta:active .invitation__cta-arrow {
  transform: translateX(5px);
}

.invitation__cta:active .invitation__cta-line,
.invitation__cta:active .invitation__cta-arrow {
  transition-duration: 180ms;
}

/* No contact destination has been given yet, so the control does not pretend
   to have one: the pointer does not change and the click does nothing. */
.invitation__cta[aria-disabled="true"] {
  cursor: default;
}

.invitation__pending {
  margin-top: var(--space-md);
  color: var(--color-accent-deep);
  font-size: 0.625rem;
  letter-spacing: 0.24em;
  text-transform: uppercase;
}

/* --- desktop ------------------------------------------------------------
   The frame has far more room to travel here, and it opens on both axes:
   sideways for breadth, downward for calm. The button stops stretching and
   becomes an object sitting in the space instead. */
@media (min-width: 768px) {
  .invitation {
    --frame-x: 0.88;
    --frame-y: 0.78;
  }

  .invitation__cta {
    width: auto;
    min-width: 21rem;
    max-width: 26rem;
    padding: 0 1.75rem;
  }
}

/* The same editorial grammar the rest of the page uses: the headline holds
   the left of the frame, the support line and the invitation sit in the
   right-hand column, and the emptiness between them is deliberate. */
@media (min-width: 1024px) {
  .invitation__content {
    display: grid;
    grid-template-columns: repeat(12, 1fr);
    column-gap: var(--space-md);
  }

  .invitation__index {
    grid-column: 1 / 8;
    grid-row: 1;
  }

  .invitation__title {
    grid-column: 1 / 8;
    grid-row: 2;
    font-size: clamp(3rem, 4.6vw, 4rem);
  }

  .invitation__text {
    grid-column: 8 / 13;
    grid-row: 2;
    align-self: end;
    margin-top: 0;
    padding-bottom: 0.45em;
    font-size: 1.0625rem;
  }

  .invitation__action {
    grid-column: 8 / 13;
    grid-row: 3;
    margin-top: clamp(2rem, 3vw, 2.75rem);
  }

  .invitation__cta {
    min-width: 0;
    width: 100%;
    max-width: none;
  }
}

/* --- the movement -------------------------------------------------------
   Only present once the script is driving it. */
.invitation-ready .invitation__frame {
  transform: scale(
    calc(1 - (1 - var(--frame-x)) * (1 - var(--invitation-open, 0))),
    calc(1 - (1 - var(--frame-y)) * (1 - var(--invitation-open, 0)))
  );
  /* Short and linear: enough to smooth a coarse trackpad step, too short to
     lag behind a fast flick. */
  transition: transform 110ms linear;
}

.invitation-ready .invitation__wash {
  opacity: var(--invitation-open, 0);
  transition: opacity 160ms linear;
}

/* The headline arrives once and then holds absolutely still. The frame is the
   only thing that keeps moving around it, so the words never have to be read
   through a transform. */
.invitation-ready .invitation__index,
.invitation-ready .invitation__title {
  opacity: 0;
  transform: translateY(14px);
  transition:
    opacity 1000ms var(--ease-silk),
    transform 1000ms var(--ease-silk);
}

.invitation-ready .invitation__title {
  transition-delay: 140ms;
}

.invitation-ready .invitation.is-entered .invitation__index,
.invitation-ready .invitation.is-entered .invitation__title {
  opacity: 1;
  transform: none;
}

/* Support text and invitation follow the frame, each at its own point in the
   opening. Once they have arrived they stay: a button that faded out from
   under a thumb reaching for it would be a bug, not a flourish. */
.invitation-ready .invitation__text,
.invitation-ready .invitation__action {
  opacity: 0;
  transform: translateY(12px);
  transition:
    opacity 900ms var(--ease-soft),
    transform 900ms var(--ease-soft);
}

.invitation-ready .invitation.is-told .invitation__text,
.invitation-ready .invitation.is-offered .invitation__action {
  opacity: 1;
  transform: none;
}

@media (max-width: 767px) {
  .invitation-ready .invitation__index,
  .invitation-ready .invitation__title {
    transform: translateY(10px);
    transition-duration: 800ms;
  }

  .invitation-ready .invitation__text,
  .invitation-ready .invitation__action {
    transform: translateY(8px);
    transition-duration: 700ms;
  }
}

/* 12. Footer
   ------------------------------------------------------------------------
   The full stop. The whole page is warm and light, so the one dark block
   belongs at the very end, where it tells the eye the reading is over. The
   ground is the same deep brown the body copy is set in — nothing new was
   added to the palette, it is simply inverted.

   To flip the footer back to light, the two values below are the only edit:
   --footer-ground: var(--color-sand); --footer-ink: var(--color-ink);

   The entrance borrows the reveal system the rest of the page uses, so this
   section carries no script of its own and inherits its guarantees: nothing
   is ever hidden unless main.js has confirmed it can reveal it again. */
.footer {
  --footer-ground: var(--color-ink);
  --footer-ink: #efe7db;
  --footer-ink-soft: rgba(239, 231, 219, 0.62);
  --footer-hairline: rgba(239, 231, 219, 0.18);

  background-color: var(--footer-ground);
  color: var(--footer-ink);
  padding-block: clamp(4rem, 12vw, 7rem) clamp(2.5rem, 7vw, 3.5rem);
  /* Home-screen apps and notched phones: the last line never sits under the
     system bar. */
  padding-bottom: calc(clamp(2.5rem, 7vw, 3.5rem) + env(safe-area-inset-bottom, 0px));
}

.footer__inner {
  width: 100%;
  max-width: var(--content-width);
  margin-inline: auto;
  padding-inline: var(--gutter);
}

/* --- wordmark ---------------------------------------------------------- */
.footer__wordmark {
  font-family: var(--font-display);
  font-size: clamp(2rem, 9vw, 3.25rem);
  letter-spacing: 0.04em;
  line-height: 1.05;
  text-transform: uppercase;
}

/* Two words, two lines on a phone and one line once there is room — a
   planned break either way, never a wrap the browser chose. */
.footer__word {
  display: block;
}

/* A quiet restatement of what the place is, so the left of the footer is not
   empty while the index fills the right. Same sentence the Hero opens with. */
.footer__tagline {
  max-width: 32ch;
  margin-top: clamp(1.5rem, 5vw, 2rem);
  color: var(--footer-ink-soft);
  font-size: 0.9375rem;
  line-height: 1.6;
}

/* --- index ------------------------------------------------------------- */
.footer__nav {
  margin-top: clamp(2.5rem, 8vw, 3.5rem);
}

.footer__list {
  list-style: none;
}

.footer__link {
  display: flex;
  align-items: baseline;
  gap: 0.875rem;
  /* Comfortable to hit with a thumb without drawing a box around itself. */
  min-height: 44px;
  padding-block: 0.6rem;
  color: var(--footer-ink-soft);
  text-decoration: none;
  transition: color 420ms var(--ease-soft);
}

.footer__link-number {
  flex: 0 0 auto;
  font-size: 0.625rem;
  letter-spacing: 0.24em;
}

.footer__link-name {
  position: relative;
  font-size: 0.8125rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
}

/* The same thin rule the gallery controls use, in the inverted palette. */
.footer__link-name::after {
  content: "";
  position: absolute;
  right: 0;
  bottom: -0.3rem;
  left: 0;
  height: 1px;
  background-color: currentColor;
  transform: scaleX(0);
  transform-origin: left center;
  transition: transform 520ms var(--ease-soft);
}

@media (hover: hover) {
  .footer__link:hover {
    color: var(--footer-ink);
  }

  .footer__link:hover .footer__link-name::after {
    transform: scaleX(1);
  }
}

.footer__link:focus-visible,
.footer__link:active {
  color: var(--footer-ink);
}

.footer__link:focus-visible .footer__link-name::after,
.footer__link:active .footer__link-name::after {
  transform: scaleX(1);
}

/* Where the index links land. Without this the heading sits flush against
   the top edge of the screen. It changes nothing that is drawn — only the
   resting point of a jump. */
#experience-title,
#treatments-title,
#approach-title,
#space-title,
#invitation-title {
  scroll-margin-top: clamp(2rem, 8vw, 4rem);
}

/* --- the dividing rule -------------------------------------------------- */
.footer__rule {
  display: block;
  height: 1px;
  margin-top: clamp(2.5rem, 8vw, 4rem);
  background-color: var(--footer-hairline);
}

/* --- the small print ---------------------------------------------------- */
.footer__base {
  margin-top: clamp(2rem, 6vw, 2.75rem);
}

.footer__note {
  max-width: 44ch;
  color: var(--footer-ink-soft);
  font-size: 0.8125rem;
  line-height: 1.65;
}

.footer__sign {
  margin-top: clamp(1.75rem, 5vw, 2.25rem);
}

/* The signature. Set in the display face so it reads as a name rather than
   as small print. */
.footer__credit {
  color: var(--footer-ink);
  font-family: var(--font-display);
  font-size: 1.0625rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

.footer__legal {
  margin-top: 0.6rem;
  color: var(--footer-ink-soft);
  font-size: 0.6875rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
}

/* --- back to top -------------------------------------------------------- */
.footer__up {
  display: inline-flex;
  align-items: center;
  gap: 0.75rem;
  min-height: 44px;
  margin-top: clamp(2.5rem, 7vw, 3rem);
  color: var(--footer-ink-soft);
  font-size: 0.6875rem;
  letter-spacing: 0.24em;
  text-decoration: none;
  text-transform: uppercase;
  transition: color 420ms var(--ease-soft);
}

.footer__up-arrow {
  display: block;
  font-size: 0.875rem;
  line-height: 1;
  transition: transform 520ms var(--ease-silk);
}

@media (hover: hover) {
  .footer__up:hover {
    color: var(--footer-ink);
  }

  .footer__up:hover .footer__up-arrow {
    transform: translateY(-4px);
  }
}

.footer__up:focus-visible,
.footer__up:active {
  color: var(--footer-ink);
}

.footer__up:focus-visible .footer__up-arrow,
.footer__up:active .footer__up-arrow {
  transform: translateY(-4px);
}

/* The focus ring has to be visible on the dark ground, where the warm accent
   the rest of the page uses would nearly disappear. */
.footer a:focus-visible {
  outline: 2px solid var(--footer-ink);
  outline-offset: 4px;
}

/* --- desktop ------------------------------------------------------------
   Wordmark on the left, the index in a column on the right, and the small
   print and the signature facing each other across the bottom. */
@media (min-width: 768px) {
  .footer__word {
    display: inline;
  }

  .footer__word + .footer__word::before {
    content: " ";
  }
}

@media (min-width: 1024px) {
  .footer__top {
    display: grid;
    grid-template-columns: repeat(12, 1fr);
    column-gap: var(--space-lg);
    align-items: start;
  }

  .footer__wordmark {
    grid-column: 1 / 8;
    grid-row: 1;
    font-size: clamp(2.5rem, 3.6vw, 3.25rem);
  }

  .footer__tagline {
    grid-column: 1 / 7;
    grid-row: 2;
  }

  /* Spans both rows so the index keeps its own vertical rhythm next to the
     wordmark and the line under it. */
  .footer__nav {
    grid-column: 9 / 13;
    grid-row: 1 / 3;
    margin-top: 0.4rem;
  }

  .footer__base {
    display: grid;
    grid-template-columns: repeat(12, 1fr);
    column-gap: var(--space-lg);
    align-items: end;
  }

  .footer__note {
    grid-column: 1 / 7;
  }

  .footer__sign {
    grid-column: 9 / 13;
    margin-top: 0;
  }

  /* On the right edge, under the index it belongs to. */
  .footer__up {
    display: flex;
    width: fit-content;
    margin-inline-start: auto;
  }
}

/* --- the entrance -------------------------------------------------------
   One order, read top to bottom, in the same easing as the rest of the page.
   Without the script every one of these is already in place. */
.footer__wordmark { --reveal-delay: 0ms; }
.footer__tagline { --reveal-delay: 100ms; }
.footer__item:nth-child(1) { --reveal-delay: 200ms; }
.footer__item:nth-child(2) { --reveal-delay: 260ms; }
.footer__item:nth-child(3) { --reveal-delay: 320ms; }
.footer__item:nth-child(4) { --reveal-delay: 380ms; }
.footer__item:nth-child(5) { --reveal-delay: 440ms; }
.footer__note { --reveal-delay: 460ms; }
.footer__credit { --reveal-delay: 520ms; }
.footer__legal { --reveal-delay: 580ms; }
.footer__up { --reveal-delay: 620ms; }

/* The rule draws itself instead of sliding, the way the one in section 01
   does. It deliberately carries no data-reveal: kept out of the generic
   system it competes with nothing, and the resting state stays the drawn
   line. */
.reveal-ready .footer__rule {
  transform: scaleX(0);
  transform-origin: left center;
  transition: transform 1100ms var(--ease-silk);
  transition-delay: 420ms;
}

.reveal-ready .footer.is-revealed .footer__rule {
  transform: scaleX(1);
}

/* Shorter distances on a phone, where the whole block is read at once. */
@media (max-width: 767px) {
  .reveal-ready .footer [data-reveal] {
    transform: translateY(1rem);
    transition-duration: 760ms;
  }

  .reveal-ready .footer.is-revealed [data-reveal] {
    transform: none;
  }

}

/* 13. Accessibility
   ------------------------------------------------------------------------ */
:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: 3px;
}

.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

.skip-link {
  position: absolute;
  top: var(--space-xs);
  left: var(--space-xs);
  z-index: 100;
  padding: var(--space-xs) var(--space-sm);
  background-color: var(--color-surface);
  color: var(--color-ink);
  border: 1px solid var(--color-border);
  border-radius: 4px;
  transform: translateY(-200%);
}

.skip-link:focus-visible {
  transform: translateY(0);
}

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}
