/* ═══════════════════════════════════════════════════════════════
   SOLA SUGARING — West Hazleton, PA
   Vanilla CSS. No build step, no framework.

   v2 note: the canvas flipped. Cream is now the default surface —
   dark green is the deliberate exception (the two sections we kept,
   the footer bookend, and the new full-bleed "break" statements).
   Any section NOT tagged .on-dark below renders on the light scope.
   ═══════════════════════════════════════════════════════════════ */

/* ───────────────────────────────────────────────────────────────
   1. LICENSED TYPEFACES
   Sola's branding package fonts. Drop .woff2 files into
   /assets/fonts and these take over from the Google fallbacks
   automatically — no other change needed.
   See assets/fonts/README.md for exact filenames.
   ─────────────────────────────────────────────────────────────── */
@font-face {
  font-family: "Lovechild";
  src: url("assets/fonts/Lovechild-Regular.woff2") format("woff2");
  font-weight: 400; font-style: normal; font-display: swap;
}
@font-face {
  font-family: "Oaxaca";
  src: url("assets/fonts/Oaxaca-Regular.woff2") format("woff2");
  font-weight: 400; font-style: normal; font-display: swap;
}
@font-face {
  font-family: "Carnaby Street Blowout";
  src: url("assets/fonts/CarnabyStreet-Blowout.woff2") format("woff2");
  font-weight: 400; font-style: normal; font-display: swap;
}
@font-face {
  font-family: "FreightSans Pro";
  src: url("assets/fonts/FreightSansPro-Book.woff2") format("woff2");
  font-weight: 400; font-style: normal; font-display: swap;
}
@font-face {
  font-family: "FreightSans Pro";
  src: url("assets/fonts/FreightSansPro-BookItalic.woff2") format("woff2");
  font-weight: 400; font-style: italic; font-display: swap;
}
@font-face {
  font-family: "FreightSans Pro";
  src: url("assets/fonts/FreightSansPro-Bold.woff2") format("woff2");
  font-weight: 700; font-style: normal; font-display: swap;
}

/* ───────────────────────────────────────────────────────────────
   2. TOKENS
   ─────────────────────────────────────────────────────────────── */
:root {
  /* Brand palette — from the Sola branding package. Unchanged by the v2
     canvas flip; only which one dominates has changed. */
  --green-950: #01301b;   /* deepest value on the site. There is no black. */
  --green-900: #024226;   /* PANTONE 343C */
  --green-800: #02371f;
  --green-700: #10704b;   /* PANTONE 7726C */
  --green-500: #28ad6d;   /* PANTONE 2414C */
  --sand:      #f3e8d7;   /* PANTONE OFF WHITE — the primary canvas now */
  --orange:    #f2612b;   /* PANTONE 166C — the single accent */
  --yellow:    #f8982c;   /* PANTONE 2011C */
  --red:       #bb2025;   /* PANTONE 200C */

  /* ── Scope system ──
     Everything below is scope-relative: --ink/--line/--accent-text mean
     "primary text," "hairline," "accent-as-text" — NOT a literal colour.
     :root is the LIGHT scope (cream canvas, dark-green ink) because that's
     now the norm. `.on-dark` re-declares the same variable names for the
     handful of sections that stay green-on-cream's inverse: the two kept
     sections (Ethos, Ritual), the Menu card, the Footer bookend, and the
     new Break statements. Every component below reads only these tokens,
     never a literal --sand/--green-900 for text, so it never has to know
     which scope it's sitting in.

     Ratios are measured on a canvas composite against each scope's actual
     background, not eyeballed — see the note further down in Pass 3 of
     the original build: faint text on a rich background reads as elegant
     in a screenshot and is unreadable on a laptop in daylight. */
  --ink:        var(--green-900);                                          /* light: 9.6:1 */
  --ink-strong: color-mix(in srgb, var(--green-900) 88%, var(--sand) 12%); /* ~7.5:1 */
  --ink-mid:    color-mix(in srgb, var(--green-900) 72%, var(--sand) 28%); /* ~5:1 — AA floor for body */
  --ink-soft:   color-mix(in srgb, var(--green-900) 58%, var(--sand) 42%); /* ~4.2:1 — ≥18.66px only */
  --line:       color-mix(in srgb, var(--green-900) 14%, var(--sand) 86%);
  --line-soft:  color-mix(in srgb, var(--green-900) 8%,  var(--sand) 92%);

  /* Orange-as-TEXT needs its own scope-relative token: pure --orange only
     clears ~2.7:1 against cream (light accents on a light canvas are a
     contrast trap), but reads fine as-is against the dark scope. Purely
     decorative uses (the nav mark, the marquee dot) stay literal --orange
     in both scopes — logotypes and non-text graphics aren't held to the
     text ratio, and are exempt under WCAG 1.4.11.
     Percentages below aren't eyeballed — swept in-browser against real
     cream (a first pass at 70%/62% measured 4.4:1 and 4.19:1, both short
     of the 4.5 floor; these clear ~5.1–5.3:1 with room to spare). */
  --accent-text:  color-mix(in srgb, var(--orange) 60%, var(--green-950) 40%);
  --confirm-text: color-mix(in srgb, var(--green-500) 48%, var(--green-950) 52%);

  /* Type */
  --f-display: "Lovechild", "Fraunces", "Iowan Old Style", Georgia, serif;
  --f-sub:     "Oaxaca", "Outfit", "Avenir Next", system-ui, sans-serif;
  --f-detail:  "Carnaby Street Blowout", "Oswald", "Haettenschweiler", sans-serif;
  --f-body:    "FreightSans Pro", "Source Sans 3", -apple-system, system-ui, sans-serif;

  /* Rhythm */
  --gut: clamp(1.5rem, 4vw, 3rem);
  --edge: clamp(1.25rem, 5vw, 5.5rem);
  --section-y: clamp(6rem, 14vh, 11rem);
  --measure: 62ch;

  /* Motion — everything here is deliberately slow */
  --ease-spa: cubic-bezier(0.22, 1, 0.36, 1);
  --ease-soft: cubic-bezier(0.4, 0, 0.2, 1);
  --dur: 1.1s;
}

/* The dark scope. Re-declares the same names against green-900, so any
   component using --ink/--line/--accent-text works unmodified inside it. */
.on-dark {
  --ink:          var(--sand);
  --ink-strong:   color-mix(in srgb, var(--sand) 80%, transparent);
  --ink-mid:      color-mix(in srgb, var(--sand) 66%, transparent);
  --ink-soft:     color-mix(in srgb, var(--sand) 58%, transparent);
  --line:         color-mix(in srgb, var(--sand) 16%, transparent);
  --line-soft:    color-mix(in srgb, var(--sand) 9%,  transparent);
  --accent-text:  var(--orange);
  --confirm-text: color-mix(in srgb, var(--green-500) 70%, var(--sand) 30%);
  background: var(--green-900);
  color: var(--ink);
}

/* ───────────────────────────────────────────────────────────────
   3. RESET
   ─────────────────────────────────────────────────────────────── */
*, *::before, *::after { box-sizing: border-box; }
* { margin: 0; padding: 0; }

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: auto; /* Lenis owns scrolling */
}

body {
  min-height: 100vh;
  background: var(--sand);
  color: var(--ink);
  font-family: var(--f-body);
  font-size: clamp(1rem, 0.95rem + 0.2vw, 1.075rem);
  line-height: 1.72;
  font-weight: 400;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-rendering: optimizeLegibility;
  overflow-x: clip;
}

img, svg { display: block; max-width: 100%; }
button, input, select, textarea { font: inherit; color: inherit; }
a { color: inherit; text-decoration: none; }
ol, ul { list-style: none; }
::selection { background: var(--orange); color: var(--green-950); }

:focus-visible {
  outline: 2px solid var(--orange);
  outline-offset: 4px;
  border-radius: 2px;
}

/* Visually hidden but announced — for labels a design doesn't have room to
   show (the newsletter's single email field reads fine without a visible
   label next to it, but screen readers still need one). */
.sr-only {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* ───────────────────────────────────────────────────────────────
   4. ATMOSPHERE — grain + cursor
   ─────────────────────────────────────────────────────────────── */

/* Film grain. Sits above everything, touches nothing.

   PERF: this is a blended, full-viewport layer, so its area is paid for on
   every frame — it cannot be composited away. Keep `inset` tiny: it only
   needs to be big enough to hide the edges as the drift translates it.
   An earlier -120% made this ~11x the viewport and stalled the compositor
   outright. -8px + an 8px drift is all the cover it needs. */
.grain {
  position: fixed;
  inset: -8px;
  z-index: 9000;
  pointer-events: none;
  opacity: 0.22;
  mix-blend-mode: multiply; /* overlay darkened the cream too much; multiply reads as paper grain */
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='220' height='220'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.82' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='220' height='220' filter='url(%23n)' opacity='0.5'/%3E%3C/svg%3E");
  animation: grain-drift 8s steps(6) infinite;
}
@keyframes grain-drift {
  0%, 100% { transform: translate3d(0, 0, 0); }
  16%  { transform: translate3d(-4px, -6px, 0); }
  33%  { transform: translate3d(6px, 2px, 0); }
  50%  { transform: translate3d(-2px, 5px, 0); }
  66%  { transform: translate3d(5px, -4px, 0); }
  83%  { transform: translate3d(-6px, 3px, 0); }
}

/* Custom cursor — a soft, translucent circle. Tokenised so it reads
   correctly whether it's floating over a cream or a dark-green section. */
.cursor {
  position: fixed;
  top: 0; left: 0;
  width: 34px; height: 34px;
  z-index: 8500;
  pointer-events: none;
  border-radius: 50%;
  border: 1px solid color-mix(in srgb, var(--green-900) 32%, transparent);
  background: color-mix(in srgb, var(--green-900) 6%, transparent);
  /* No backdrop-filter here. This element moves every frame, and a blurred
     backdrop would force the compositor to re-blur what's under it each time. */
  opacity: 0;
  transform: translate(-50%, -50%);
  transition: opacity 0.5s var(--ease-soft),
              width 0.55s var(--ease-spa),
              height 0.55s var(--ease-spa),
              background 0.55s var(--ease-soft),
              border-color 0.55s var(--ease-soft);
}
.cursor__dot {
  position: absolute;
  top: 50%; left: 50%;
  width: 3px; height: 3px;
  border-radius: 50%;
  background: var(--green-900);
  transform: translate(-50%, -50%);
  transition: opacity 0.4s var(--ease-soft), transform 0.5s var(--ease-spa);
}
.cursor.is-live { opacity: 1; }
.cursor.is-link {
  width: 58px; height: 58px;
  background: color-mix(in srgb, var(--green-900) 9%, transparent);
  border-color: color-mix(in srgb, var(--green-900) 22%, transparent);
}
.cursor.is-link .cursor__dot { opacity: 0; transform: translate(-50%, -50%) scale(0.3); }
.cursor.is-magnetic {
  width: 76px; height: 76px;
  background: color-mix(in srgb, var(--orange) 16%, transparent);
  border-color: color-mix(in srgb, var(--orange) 60%, transparent);
}
.cursor.is-magnetic .cursor__dot { opacity: 0; }
/* Over a dark section the ring needs to flip to sand, or it vanishes into
   the green. Toggled by main.js via IntersectionObserver-free scroll check
   — see .cursor.is-on-dark in main.js's buildCursor(). */
.cursor.is-on-dark {
  border-color: color-mix(in srgb, var(--sand) 42%, transparent);
  background: color-mix(in srgb, var(--sand) 7%, transparent);
}
.cursor.is-on-dark .cursor__dot { background: var(--sand); }

/* Only for devices with a real pointer. */
@media (hover: none), (pointer: coarse) {
  .cursor { display: none; }
}

/* ───────────────────────────────────────────────────────────────
   5. TYPE SYSTEM
   ─────────────────────────────────────────────────────────────── */

/* Display — Lovechild. The voice of the brand. */
.h-display, .wordmark {
  font-family: var(--f-display);
  font-weight: 400;
  font-variation-settings: "SOFT" 28, "WONK" 0, "opsz" 120; /* Fraunces fallback only */
  line-height: 0.99;
  letter-spacing: -0.018em;
  text-wrap: balance;
}
.h-display {
  /* Fixes a bug class hit four separate times now (Studio, Ritual, Menu,
     Why via a ch-based parent max-width; Newsletter via a parent's
     justify-items:center) — a heading sitting inside a non-stretch grid/
     flex container shrink-wraps to its own content's natural width rather
     than filling the track, which squeezes it into extra line-wraps beyond
     what its own <br> tags intend. A display heading should never want to
     be narrower than its column; width:100% removes the shrink-to-fit
     tendency at the source instead of patching each container as it comes
     up again. (Not added to the shared .wordmark rule above — things like
     the hero word or footer wordmark DO want to size to their own content.) */
  width: 100%;
  font-size: clamp(2.4rem, 1.3rem + 4.6vw, 5.6rem);
  color: var(--ink);
}
.h-display em {
  font-style: italic;
  color: var(--accent-text);
  font-variation-settings: "SOFT" 60, "WONK" 0, "opsz" 120;
}

/* Subhead — Oaxaca. Rounded, generous, all caps. */
.eyebrow, .sub {
  font-family: var(--f-sub);
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.16em;
  font-size: 0.72rem;
  line-height: 1;
}

/* Detail — Carnaby Street Blowout. Condensed, small, structural. */
.detail {
  font-family: var(--f-detail);
  font-weight: 400;
  text-transform: uppercase;
  letter-spacing: 0.13em;
  font-size: 0.7rem;
  line-height: 1.4;
  color: var(--ink-mid);
}
.detail--rule {
  display: flex;
  align-items: center;
  gap: 0.9rem;
  color: var(--accent-text);
  white-space: nowrap;
}
.detail--rule::after {
  content: "";
  flex: 1;
  height: 1px;
  background: currentColor;
  opacity: 0.32;
}

/* Body */
.body-lg {
  font-size: clamp(1.1rem, 1rem + 0.5vw, 1.32rem);
  line-height: 1.62;
  color: var(--ink);
}

.eyebrow {
  display: flex;
  align-items: center;
  gap: 0.85rem;
  color: var(--ink-mid);
}
.eyebrow i {
  width: 26px; height: 1px;
  background: currentColor;
  opacity: 0.5;
}

/* ───────────────────────────────────────────────────────────────
   6. BUTTONS & LINKS
   ─────────────────────────────────────────────────────────────── */
.btn {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.6rem;
  padding: 1.05rem 2.1rem;
  /* The border IS the affordance on a ghost button — WCAG 1.4.11 wants 3:1
     for UI boundaries, so this can't drop into the hairline range. */
  border: 1px solid var(--ink-soft);
  border-radius: 999px;
  font-family: var(--f-sub);
  font-size: 0.74rem;
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.15em;
  overflow: hidden;
  cursor: pointer;
  background: transparent;
  isolation: isolate;
  transition: color 0.7s var(--ease-spa), border-color 0.7s var(--ease-spa);
}
.btn span { position: relative; z-index: 2; }
/* The fill rises slowly from the floor of the button. Text always goes to
   green-950 on hover regardless of scope — the fill is always orange, and
   orange always wants dark text over it. */
.btn::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;
  background: var(--orange);
  transform: translateY(101%);
  transition: transform 0.85s var(--ease-spa);
}
.btn:hover { color: var(--green-950); border-color: var(--orange); }
.btn:hover::before { transform: translateY(0); }

/* Solid CTA: a confident filled pill, inverted relative to its own scope —
   dark-on-cream by default, sand-on-green inside .on-dark. Before the v2
   canvas flip this was hardcoded sand-on-dark; a solid sand pill sitting on
   a now-cream Hero would have all but disappeared. */
.btn--solid {
  background: var(--ink);
  color: var(--sand);
  border-color: var(--ink);
}
.on-dark .btn--solid { color: var(--green-950); }
.btn--ghost { color: var(--ink); }
.btn--wide { width: 100%; padding-block: 1.2rem; }

/* Quiet link with a rule that draws in */
.link-quiet {
  position: relative;
  font-family: var(--f-sub);
  font-size: 0.74rem;
  text-transform: uppercase;
  letter-spacing: 0.15em;
  color: var(--ink-strong);
  padding-bottom: 4px;
  transition: color 0.6s var(--ease-soft);
}
.link-quiet::after {
  content: "";
  position: absolute;
  left: 0; bottom: 0;
  width: 100%; height: 1px;
  background: currentColor;
  transform: scaleX(0);
  transform-origin: right;
  transition: transform 0.75s var(--ease-spa);
}
.link-quiet:hover { color: var(--ink); }
.link-quiet:hover::after { transform: scaleX(1); transform-origin: left; }

/* ───────────────────────────────────────────────────────────────
   7. LOADER
   Always dark, always brief — independent of page scroll scope.
   ─────────────────────────────────────────────────────────────── */
.loader {
  position: fixed;
  inset: 0;
  z-index: 9500;
  display: grid;
  place-items: center;
  background: var(--green-950);
}
.loader__inner { display: grid; justify-items: center; gap: 1.6rem; }
.loader__mark {
  /* Real logo file now (assets/logo/logo-orange.png), already orange in
     the file itself — no more `color` tinting to do, that only worked on
     the old currentColor-based inline SVG placeholder. */
  width: 46px;
  opacity: 0;
}
.loader__word {
  font-family: var(--f-display);
  font-variation-settings: "SOFT" 28, "WONK" 0, "opsz" 120;
  font-size: 2rem;
  letter-spacing: 0.02em;
  color: var(--sand);
  overflow: hidden;
}
.loader__word span { display: block; transform: translateY(110%); }
.loader__bar {
  width: 132px; height: 1px;
  background: color-mix(in srgb, var(--sand) 16%, transparent);
  overflow: hidden;
}
.loader__bar i {
  display: block;
  height: 100%;
  width: 100%;
  background: var(--orange);
  transform: scaleX(0);
  transform-origin: left;
}
/* Pre-JS: keep the page still under the loader. */
html.is-loading body { overflow: hidden; }

/* ───────────────────────────────────────────────────────────────
   8. NAV
   Always-fixed chrome, independent of whatever section scrolls beneath
   it. Default state assumes it's over the (light) Hero; `.is-stuck`
   doubles as `.on-dark` (see main.js) because the moment it grows its own
   translucent chip, that chip is dark and works over anything below it.
   ─────────────────────────────────────────────────────────────── */
.nav {
  position: fixed;
  top: 0; left: 0; right: 0;
  z-index: 800;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--gut);
  padding: clamp(1rem, 2.2vw, 1.7rem) var(--edge);
  color: var(--ink);
  transition: background 0.8s var(--ease-soft),
              backdrop-filter 0.8s var(--ease-soft),
              padding 0.8s var(--ease-spa),
              color 0.6s var(--ease-soft);
}
.nav::after {
  content: "";
  position: absolute;
  left: var(--edge); right: var(--edge); bottom: 0;
  height: 1px;
  background: var(--line);
  transform: scaleX(0);
  transform-origin: center;
  transition: transform 1.1s var(--ease-spa);
}
.nav.is-stuck {
  background: color-mix(in srgb, var(--green-950) 74%, transparent);
  backdrop-filter: blur(14px) saturate(1.2);
  padding-block: clamp(0.7rem, 1.4vw, 1rem);
}
.nav.is-stuck::after { transform: scaleX(1); }
.nav { transition-property: background, backdrop-filter, padding, color; }

.nav__brand { display: flex; align-items: center; gap: 0.7rem; }
/* Real logo file now (logo-green.png) — already coloured in the file
   itself, so the old currentColor tinting (and its hover colour-shift)
   no longer does anything and has been dropped. A different hover
   treatment (scale, opacity) could replace it if wanted. */
.nav__mark { width: 22px; }
.nav__word {
  font-family: var(--f-display);
  font-variation-settings: "SOFT" 28, "WONK" 0, "opsz" 90;
  font-size: 1.35rem;
  letter-spacing: 0.01em;
}

.nav__links { display: flex; gap: clamp(1.3rem, 2.2vw, 2.2rem); }
.nav__links a {
  position: relative;
  font-family: var(--f-sub);
  font-size: 0.72rem;
  text-transform: uppercase;
  letter-spacing: 0.14em;
  color: var(--ink-mid);
  padding-block: 4px;
  transition: color 0.6s var(--ease-soft);
}
.nav__links a::after {
  content: "";
  position: absolute;
  left: 0; bottom: 0;
  width: 100%; height: 1px;
  background: var(--orange);
  transform: scaleX(0);
  transform-origin: right;
  transition: transform 0.7s var(--ease-spa);
}
.nav__links a:hover { color: var(--ink); }
.nav__links a:hover::after { transform: scaleX(1); transform-origin: left; }
.nav__links a.is-current { color: var(--ink); }
.nav__links a.is-current::after { transform: scaleX(1); transform-origin: left; opacity: 0.5; }

.nav__cta { padding: 0.8rem 1.6rem; }

.nav__burger {
  display: none;
  width: 44px; height: 44px;
  margin-right: -10px;
  border: 0;
  background: none;
  cursor: pointer;
  position: relative;
  color: var(--ink);
}
.nav__burger i {
  position: absolute;
  left: 11px;
  width: 22px; height: 1.5px;
  background: currentColor;
  border-radius: 2px;
  transition: transform 0.6s var(--ease-spa), opacity 0.4s var(--ease-soft);
}
.nav__burger i:nth-child(1) { top: 18px; }
.nav__burger i:nth-child(2) { top: 25px; }
.nav__burger.is-open i:nth-child(1) { transform: translateY(3.5px) rotate(45deg); }
.nav__burger.is-open i:nth-child(2) { transform: translateY(-3.5px) rotate(-45deg); }

/* ───────────────────────────────────────────────────────────────
   9. MOBILE SHEET — should feel like an iOS sheet, not a website menu.
   Always dark, regardless of scroll position — an intentional full-screen
   takeover, not a scoped section, so it keeps its own hardcoded palette.
   ─────────────────────────────────────────────────────────────── */
.sheet {
  position: fixed;
  inset: 0;
  z-index: 790;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  padding: calc(env(safe-area-inset-top) + 6.5rem) var(--edge)
           calc(env(safe-area-inset-bottom) + 2.5rem);
  background: color-mix(in srgb, var(--green-950) 92%, transparent);
  backdrop-filter: blur(26px) saturate(1.3);
  clip-path: inset(0 0 100% 0);
  visibility: hidden;
  transition: clip-path 0.9s var(--ease-spa), visibility 0s linear 0.9s;
}
.sheet.is-open {
  clip-path: inset(0 0 0% 0);
  visibility: visible;
  transition: clip-path 0.9s var(--ease-spa), visibility 0s linear 0s;
}
.sheet__grip {
  position: absolute;
  top: calc(env(safe-area-inset-top) + 0.6rem);
  left: 50%;
  width: 38px; height: 4px;
  border-radius: 999px;
  background: color-mix(in srgb, var(--sand) 16%, transparent);
  transform: translateX(-50%);
}
.sheet__nav { display: grid; gap: 0.2rem; }
.sheet__nav a {
  display: flex;
  align-items: baseline;
  gap: 1rem;
  padding-block: 0.75rem;
  border-bottom: 1px solid color-mix(in srgb, var(--sand) 9%, transparent);
  font-family: var(--f-display);
  font-variation-settings: "SOFT" 28, "WONK" 0, "opsz" 100;
  font-size: clamp(1.7rem, 8vw, 2.6rem);
  line-height: 1;
  color: var(--sand);
  opacity: 0;
  transform: translateY(22px);
}
.sheet__nav a em {
  font-family: var(--f-detail);
  font-style: normal;
  font-size: 0.62rem;
  letter-spacing: 0.12em;
  color: var(--orange);
}
.sheet__foot { display: grid; gap: 0.4rem; opacity: 0; }
.sheet__tel { font-family: var(--f-display); font-size: 1.5rem; color: var(--sand); }

/* ───────────────────────────────────────────────────────────────
   10. PLATES — art-directed image frames
   Two families now:
     .plate          — moody, saturated, dark. For the Studio gallery and
                       the Ritual hover-peeks, which are deliberately rich
                       "windows" set into whichever page they sit on.
     .plate--light   — pale and airy, for real editorial photography (the
                       Hero and About portraits) — the brief wants the
                       photos' natural light to breathe, which a dark
                       duotone plate would fight.
   Every plate degrades gracefully with zero photography: the <img> only
   appears once a real file loads successfully (onerror hides it — see
   index.html), so a missing photo just shows the art beneath it.
   ─────────────────────────────────────────────────────────────── */
.plate {
  position: relative;
  overflow: hidden;
  background: var(--green-950);
  isolation: isolate;
}
.plate--arch {
  border-radius: 50vw 50vw 6px 6px / 32% 32% 6px 6px;
}
.plate__art {
  position: absolute;
  inset: 0;
  transform: scale(1.06);
}
/* Every dark scene shares this: a soft light source, a horizon, and grain. */
.plate__art::after {
  content: "";
  position: absolute;
  inset: 0;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='g'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2'/%3E%3C/filter%3E%3Crect width='180' height='180' filter='url(%23g)' opacity='0.42'/%3E%3C/svg%3E");
  mix-blend-mode: soft-light;
  opacity: 0.85;
}

/* Dawn — orange sun low in a green field */
.plate__art--dawn {
  background:
    radial-gradient(58% 42% at 50% 78%, color-mix(in srgb, var(--yellow) 88%, transparent) 0%, transparent 62%),
    radial-gradient(90% 70% at 50% 96%, color-mix(in srgb, var(--orange) 76%, transparent) 0%, transparent 68%),
    linear-gradient(178deg, var(--green-950) 4%, var(--green-700) 52%, var(--green-500) 88%);
}
/* Dusk — deep, cool, a single warm ember */
.plate__art--dusk {
  background:
    radial-gradient(46% 34% at 68% 26%, color-mix(in srgb, var(--orange) 62%, transparent) 0%, transparent 66%),
    radial-gradient(80% 60% at 20% 88%, color-mix(in srgb, var(--green-700) 92%, transparent) 0%, transparent 70%),
    linear-gradient(200deg, var(--green-700) 0%, var(--green-900) 46%, var(--green-950) 100%);
}
/* Ember — the warmest plate, closest to the sugar itself.
   The mid-stop is mixed from --red and --green-950 rather than picked by eye;
   every colour on this site has to be derivable from the branding package. */
.plate__art--ember {
  background:
    radial-gradient(52% 52% at 38% 42%, color-mix(in srgb, var(--yellow) 92%, transparent) 0%, transparent 60%),
    radial-gradient(76% 66% at 74% 84%, color-mix(in srgb, var(--red) 52%, transparent) 0%, transparent 68%),
    linear-gradient(160deg,
      var(--orange) 0%,
      color-mix(in srgb, var(--red) 58%, var(--green-950)) 58%,
      var(--green-950) 100%);
}
/* Moss — quiet, botanical, mostly green */
.plate__art--moss {
  background:
    radial-gradient(44% 40% at 26% 22%, color-mix(in srgb, var(--green-500) 90%, transparent) 0%, transparent 64%),
    radial-gradient(70% 58% at 82% 76%, color-mix(in srgb, var(--yellow) 34%, transparent) 0%, transparent 66%),
    linear-gradient(190deg, var(--green-700) 0%, var(--green-900) 54%, var(--green-950) 100%);
}

/* Light plate — pale wash, no grain-noise, no duotone. A soft "waiting for
   a photograph" frame that already feels intentional and airy on its own. */
.plate--light {
  background: linear-gradient(165deg,
    color-mix(in srgb, var(--sand) 90%, var(--green-500) 10%) 0%,
    color-mix(in srgb, var(--sand) 80%, var(--yellow) 20%) 100%);
  border: 1px solid var(--line);
}

/* The photo slot. An <img> only ever appears with a real, loaded src —
   index.html hides it onerror, leaving the plate art as the fallback.
   True colour always — this used to duotone real photos in the Studio
   gallery (luminance-only, tinted green/orange by whichever plate art sat
   behind them), which read fine as a design idea with no photos in it, but
   once real photography was actually in place it just looked like an
   unwanted colour filter. Client call: no tint, ever, on a real photo. */
.plate img {
  position: absolute;
  inset: 0;
  width: 100%; height: 100%;
  object-fit: cover;
  opacity: 0;
  transition: opacity 1.4s var(--ease-soft);
}
.plate img[src]:not([src=""]) { opacity: 1; }

/* Captions sit on unpredictable gradients, so they get a scrim rather than a
   blend mode — `difference` turned these cyan over the warm plates. */
.plate figcaption {
  position: absolute;
  left: 0; right: 0; bottom: 0;
  z-index: 3;
  padding: 2.6rem 1.1rem 0.95rem;
  color: var(--sand);
  background: linear-gradient(to top,
    color-mix(in srgb, var(--green-950) 76%, transparent) 0%,
    transparent 100%);
}

/* ───────────────────────────────────────────────────────────────
   11. REVEALS — set the "before" state; GSAP animates to "after"
   ─────────────────────────────────────────────────────────────── */
.reveal, .reveal-lines { opacity: 0; }
/* The mask's overflow:hidden clips to exactly one line-height's box, but the
   display faces (Lovechild especially — big script loops on capitals like
   "G", deep tails on "g"/"y") draw ink outside that box. Padding the mask
   gives that ink somewhere to go; the matching negative margin cancels the
   padding back out of the flow so it doesn't push the next line down. */
.line-mask {
  display: block;
  overflow: hidden;
  padding-top: 0.16em;
  padding-bottom: 0.22em;
  margin-top: -0.16em;
  margin-bottom: -0.22em;
}
.line-mask > span { display: block; will-change: transform; }
.char { display: inline-block; will-change: transform; }

/* If JS never arrives, nothing stays hidden. */
html.no-js .reveal,
html.no-js .reveal-lines { opacity: 1 !important; }
html.no-js .loader { display: none; }

/* ───────────────────────────────────────────────────────────────
   12. LAYOUT — the Vogue spread
   ─────────────────────────────────────────────────────────────── */
.spread {
  display: grid;
  grid-template-columns: minmax(140px, 1fr) minmax(0, 3.1fr);
  gap: var(--gut);
  padding-inline: var(--edge);
}
.spread__aside { padding-top: 0.7rem; }
.spread__main { max-width: 46ch; }

section { position: relative; }

/* ───────────────────────────────────────────────────────────────
   13. HERO — light scope. A real editorial photograph, not a mood plate.
   ─────────────────────────────────────────────────────────────── */
.hero {
  position: relative;
  min-height: 100svh;
  display: grid;
  align-content: center;
  padding: 8rem var(--edge) 5rem;
  overflow: clip;
}
.hero__plate {
  position: absolute;
  /* Centred with auto margins, NOT a transform.
     GSAP owns `transform` on this element for the parallax, and it writes
     `translate: none; rotate: none; scale: none` alongside it to keep the
     individual transform properties from interfering — so any centring done
     via `transform` OR `translate` here gets wiped the moment the parallax
     initialises, dumping the plate half its width to the right.
     inset+margin:auto centres in layout, where GSAP can't reach it. */
  inset: 0;
  margin: auto;
  width: clamp(300px, 38vw, 520px);
  height: clamp(400px, 64vh, 660px);
  z-index: 0;
  opacity: 0.94;
}
.hero__plate .plate { width: 100%; height: 100%; }

/* A soft cream halo behind the text, independent of whatever the photo
   behind it contains — real photography has faces and patterns an abstract
   gradient never did, so the text can no longer assume a calm backdrop. */
.hero__type {
  position: relative;
  z-index: 2;
  display: grid;
  justify-items: center;
  gap: clamp(1.4rem, 3vw, 2.2rem);
  text-align: center;
}
.hero__type::before {
  content: "";
  position: absolute;
  inset: -3rem -4rem;
  z-index: -1;
  background: radial-gradient(58% 58% at 50% 50%,
    color-mix(in srgb, var(--sand) 92%, transparent) 0%,
    color-mix(in srgb, var(--sand) 60%, transparent) 55%,
    transparent 78%);
  filter: blur(6px);
}
.hero__word {
  font-size: clamp(5rem, 21vw, 17rem);
  line-height: 0.82;
  letter-spacing: -0.04em;
  color: var(--orange);
}
.hero__sub {
  margin-top: -0.6rem;
  letter-spacing: 0.32em;
}
.hero__lede {
  max-width: 40ch;
  font-size: clamp(1.02rem, 0.95rem + 0.45vw, 1.24rem);
  line-height: 1.6;
  color: var(--ink-strong);
  text-wrap: balance;
}
.hero__actions {
  display: flex;
  align-items: center;
  gap: clamp(1.2rem, 3vw, 2.4rem);
  flex-wrap: wrap;
  justify-content: center;
  margin-top: 0.6rem;
}

.hero__scroll {
  position: absolute;
  left: 50%; bottom: 2.2rem;
  transform: translateX(-50%);
  display: grid;
  justify-items: center;
  gap: 0.7rem;
  z-index: 2;
}
.hero__scroll i {
  width: 1px; height: 46px;
  background: linear-gradient(var(--ink-soft), transparent);
  overflow: hidden;
  position: relative;
}
.hero__scroll i::after {
  content: "";
  position: absolute;
  inset: 0;
  background: var(--orange);
  animation: scroll-drip 3.4s var(--ease-soft) infinite;
}
@keyframes scroll-drip {
  0%   { transform: translateY(-100%); }
  55%  { transform: translateY(100%); }
  100% { transform: translateY(100%); }
}

/* ───────────────────────────────────────────────────────────────
   14. ABOUT — light scope. Modelled on a reference layout the client
   supplied: a giant word behind an overlapping portrait, a script caption
   underneath, reimagined here in cream + green instead of the reference's
   dark/moody original.
   ─────────────────────────────────────────────────────────────── */
.about { padding-block: var(--section-y); text-align: center; }
.about__kicker { justify-content: center; }
.about__stage {
  position: relative;
  margin-top: clamp(2rem, 5vw, 4rem);
  min-height: clamp(320px, 52vw, 560px);
  display: grid;
  place-items: center;
  /* Everything below is sized off this ONE value instead of two
     independently-tuned clamps. Previously .about__word's font-size and
     .about__portrait's width each had their own clamp() curve, hand-tuned
     at different breakpoints — they drifted apart badly enough that the
     portrait covered 65–76% of "DANI" at every viewport width tested
     (mobile was worst: 75.6%), leaving only thin slivers of the D and I
     visible on each side. Locking both to one variable means they can't
     drift apart again, at any width, ever. */
  /* Widened the ceiling significantly — same 9.5rem mobile floor as before
     (that's the value that fixed "looks like Roman numerals" on phones;
     don't touch it), but the desktop end now climbs to 25rem instead of
     15rem, which is what actually restores the portrait to ~340px there
     (its original, larger size) while --dani-word's 0.85 ratio keeps "DANI"
     just as legible as it is on mobile — both grow together. */
  --dani-size: clamp(9.5rem, 1rem + 30vw, 25rem);
}
.about__word {
  position: absolute;
  inset: 0;
  z-index: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: var(--dani-size);
  line-height: 0.86;
  letter-spacing: -0.02em;
  text-transform: uppercase;
  color: var(--ink);
  pointer-events: none;
}
.about__portrait {
  position: relative;
  z-index: 2;
  /* Client wanted the photo bigger still after seeing 0.85 (~38% coverage,
     340px at desktop) — explicitly fine with covering more of the word:
     "most of D, some of N visible" is enough. Measured per-LETTER visible
     percentage in-browser (not just an aggregate coverage %, since the
     portrait isn't symmetric on the word's actual letterforms — "i" stays
     ~100% visible far longer than "D" does as the photo grows, because the
     portrait centers on the .about__stage box, not on the word's own
     visual centre). 1.2 lands at D≈80%, N≈35%, I≈100% at desktop widths —
     matches "most of D, some of N" and is a clear, decisive size increase,
     not a small nudge. */
  width: calc(var(--dani-size) * 1.2);
  aspect-ratio: 4 / 5;
  border-radius: 6px;
  overflow: hidden;
  box-shadow: 0 34px 64px -24px color-mix(in srgb, var(--green-900) 38%, transparent);
}
.about__portrait .plate { width: 100%; height: 100%; }
.about__portrait img { object-position: center 22%; } /* favour the face over the room around it */
.about__script {
  position: relative;
  z-index: 2;
  margin-top: 1.5rem;
  font-family: var(--f-display);
  font-style: italic;
  font-variation-settings: "SOFT" 62, "WONK" 0, "opsz" 40;
  font-size: clamp(1.4rem, 1.1rem + 1.4vw, 2rem);
  color: var(--accent-text);
}
.about__manifesto {
  margin: clamp(2.5rem, 5vw, 4rem) auto 0;
  max-width: 50ch;
  font-family: var(--f-display);
  font-style: italic;
  font-variation-settings: "SOFT" 30, "WONK" 0, "opsz" 40;
  font-size: clamp(1.1rem, 1rem + 0.6vw, 1.5rem);
  line-height: 1.55;
  color: var(--ink-strong);
}
.about__cta { margin-top: 2rem; display: flex; justify-content: center; }

/* ───────────────────────────────────────────────────────────────
   15. BREAK — the full-bleed typographic interstitial. Solid dark-green,
   one flowing statement staggered across two scales, nothing else: no
   kicker, no button, no numbering. A palate cleanser between chapters.
   ─────────────────────────────────────────────────────────────── */
.break {
  /* Bottom trimmed slightly relative to top — symmetric padding here read
     as slightly bottom-heavy, since line--b is shorter/smaller than
     line--a and doesn't visually balance the whitespace under it. */
  padding-top: clamp(5.5rem, 15vw, 10rem);
  /* Trimmed an extra 5px off the bottom on top of the earlier 1.125rem cut —
     shortens the gap into whichever dark section immediately follows each
     Break (Break 1 → Ethos, Break 2 → Ritual) by that much. Both Breaks
     share this one rule, so the fix applies to both transitions at once. */
  padding-bottom: calc(clamp(5.5rem, 15vw, 10rem) - 1.125rem - 5px);
  padding-inline: var(--edge);
  display: grid;
}
.break__inner { width: 100%; max-width: 1400px; margin-inline: auto; }
.break__line {
  font-family: var(--f-display);
  font-variation-settings: "SOFT" 32, "WONK" 0, "opsz" 130;
  line-height: 0.96;
  letter-spacing: -0.02em;
  text-wrap: balance;
}
.break__line--a {
  font-size: clamp(3rem, 1.4rem + 9vw, 10.5rem);
  color: var(--ink);
}
.break__line--b {
  margin-top: 0.4em;
  margin-left: clamp(1.5rem, 9vw, 15rem);
  font-size: clamp(1.7rem, 1rem + 4vw, 4.6rem);
  font-style: italic;
  color: var(--accent-text);
}
/* Both Break statements stay left-aligned, matching the rag of the rest of
   the page — a right-aligned break read like it was trailing off rather
   than stating something plainly. The second one still reads as distinct
   from the first: a noticeably tighter indent on its second line, rather
   than mirrored alignment. */
.break--alt .break__line--b { margin-left: clamp(1rem, 3vw, 3.5rem); }

/* ───────────────────────────────────────────────────────────────
   16. ETHOS — dark scope (kept)
   ─────────────────────────────────────────────────────────────── */
.ethos { padding-block: var(--section-y) 0; }
/* Ethos, Ritual, and Studio all set explicit <br> line breaks in their
   h-display headings — "One room, kept<br>like a private house." is meant
   to read as two lines. The shared .spread__main { max-width: 46ch } above
   is tuned for BODY-COPY paragraphs; applied to a heading set in a huge
   display face, it squeezed the box narrow enough that each of those two
   intended lines wrapped AGAIN on its own, turning 2 lines into 4 (Studio)
   and 2 into 5 (Ritual). Ethos already had this override; Ritual and
   Studio didn't. */
.ethos .spread__main,
.ritual .spread__main,
.studio .spread__main { max-width: none; }
.ethos__cols {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--gut);
  margin-top: clamp(2.5rem, 5vw, 4rem);
  max-width: 68ch;
}
.ethos__cols p { color: var(--ink-strong); }
.ethos__cols .body-lg { color: var(--ink); }
.ethos__tagline {
  margin-top: 1.2rem;
  font-family: var(--f-display);
  font-style: italic;
  font-variation-settings: "SOFT" 40, "WONK" 0, "opsz" 60;
  font-size: clamp(1.2rem, 1rem + 0.8vw, 1.6rem);
  color: var(--accent-text);
}

/* Marquee — slow enough to read, quiet enough to ignore */
.marquee {
  margin-top: clamp(4rem, 9vw, 7rem);
  padding-block: 1.5rem;
  border-block: 1px solid var(--line);
  overflow: hidden;
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 12%, #000 88%, transparent);
          mask-image: linear-gradient(90deg, transparent, #000 12%, #000 88%, transparent);
}
.marquee__track {
  display: flex;
  align-items: center;
  width: max-content;
  will-change: transform;
}
/* Margin, not `gap`. With `gap`, 16 children have only 15 gaps, so the
   track's true midpoint sits half a gap away from where the second half
   starts — the xPercent:-50 loop then jumped 20.8px on every cycle. A
   trailing margin on every child makes the two halves exactly equal. */
.marquee__track > * { margin-right: 2.6rem; }
.marquee__track span {
  font-family: var(--f-detail);
  text-transform: uppercase;
  letter-spacing: 0.16em;
  font-size: 0.76rem;
  color: var(--ink-mid);
  white-space: nowrap;
}
/* Drawn, not typed. An earlier separator was a ✳ (U+2733), which none of
   the four families actually ship — every one silently fell back to a
   system font, so the mark changed shape per OS. A dot we draw ourselves
   can't drift. */
.marquee__track i {
  flex: none;
  width: 4px;
  height: 4px;
  border-radius: 50%;
  background: var(--orange);
}

/* ───────────────────────────────────────────────────────────────
   17. STUDIO — light scope. Gallery plates stay deliberately dark
   "windows" set into the page — a rhyme with the dark chapters elsewhere,
   not a contradiction of the light canvas.
   ─────────────────────────────────────────────────────────────── */
.studio { padding-block: var(--section-y); }
.gallery {
  display: grid;
  grid-template-columns: repeat(12, 1fr);
  gap: var(--gut);
  align-items: center;
  padding: clamp(3rem, 6vw, 5rem) var(--edge) 0;
}
.gallery__a { grid-column: 1 / 5; aspect-ratio: 3 / 4; }
.gallery__b { grid-column: 6 / 9; aspect-ratio: 4 / 5; border-radius: 4px; align-self: end; }
.gallery__c { grid-column: 10 / 13; aspect-ratio: 3 / 4; align-self: start; }
.pull {
  grid-column: 5 / 13;
  grid-row: 2;
  padding-top: clamp(2rem, 4vw, 3.5rem);
  display: grid;
  gap: 1rem;
  justify-items: start;
}
.pull p {
  font-family: var(--f-display);
  font-variation-settings: "SOFT" 40, "WONK" 0, "opsz" 120;
  font-size: clamp(1.4rem, 1rem + 1.5vw, 2.2rem);
  line-height: 1.24;
  color: var(--ink);
  text-wrap: balance;
}
.pull cite { font-style: normal; }

/* ───────────────────────────────────────────────────────────────
   18. RITUAL — dark scope (kept). Four movements.
   ─────────────────────────────────────────────────────────────── */
.ritual { padding-block: var(--section-y); }
.movements {
  margin-top: clamp(3rem, 6vw, 5rem);
  border-top: 1px solid var(--line);
}
.movement {
  position: relative;
  display: grid;
  grid-template-columns: 4.5rem minmax(0, 1fr) 5rem;
  align-items: start;
  gap: var(--gut);
  padding: clamp(2rem, 3.4vw, 2.9rem) var(--edge);
  border-bottom: 1px solid var(--line);
  overflow: clip;
  transition: background 0.9s var(--ease-soft);
}
.movement::before {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(90deg, color-mix(in srgb, var(--green-700) 26%, transparent), transparent 62%);
  opacity: 0;
  transition: opacity 0.9s var(--ease-soft);
}
.movement:hover::before { opacity: 1; }
.movement > * { position: relative; z-index: 2; }

.movement__num {
  font-family: var(--f-detail);
  font-size: 0.72rem;
  letter-spacing: 0.12em;
  color: var(--orange);
  padding-top: 0.55rem;
}
.movement__body h3 {
  font-family: var(--f-display);
  font-variation-settings: "SOFT" 28, "WONK" 0, "opsz" 110;
  font-weight: 400;
  font-size: clamp(1.7rem, 1.2rem + 1.6vw, 2.6rem);
  line-height: 1.06;
  color: var(--ink);
  transition: transform 0.9s var(--ease-spa), color 0.7s var(--ease-soft);
}
.movement:hover .movement__body h3 { transform: translateX(10px); color: var(--yellow); }
.movement__body p {
  max-width: 52ch;
  margin-top: 0.7rem;
  color: var(--ink-mid);
  font-size: 0.98rem;
  line-height: 1.65;
  transition: color 0.7s var(--ease-soft);
}
.movement:hover .movement__body p { color: var(--ink-strong); }
.movement__time {
  font-family: var(--f-detail);
  font-size: 0.7rem;
  letter-spacing: 0.1em;
  color: var(--ink-mid);
  text-align: right;
  padding-top: 0.6rem;
  transition: color 0.7s var(--ease-soft);
}
.movement:hover .movement__time { color: var(--ink-strong); }

/* A sliver of the plate peeks in from the right edge on hover. */
.movement__peek {
  position: absolute;
  top: 50%; right: var(--edge);
  z-index: 1;
  width: 190px; height: 118px;
  border-radius: 999px 999px 4px 4px / 46% 46% 4px 4px;
  overflow: hidden;
  transform: translate(24px, -50%) scale(0.86);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.9s var(--ease-spa), transform 1.1s var(--ease-spa);
}
.movement__peek .plate__art { position: absolute; inset: 0; }
.movement:hover .movement__peek { opacity: 0.5; transform: translate(0, -50%) scale(1); }

/* ───────────────────────────────────────────────────────────────
   18b. WHY CHOOSE SUGARING — light scope. A photo and a benefits list,
   the same asymmetric two-column instinct as .spread elsewhere, just with
   a real image standing in for the aside column instead of a numeral.
   ─────────────────────────────────────────────────────────────── */
.why { padding-block: var(--section-y); }
.why__grid {
  display: grid;
  grid-template-columns: minmax(0, 0.85fr) minmax(0, 1fr);
  gap: clamp(2.5rem, 6vw, 5rem);
  align-items: center;
  padding-inline: var(--edge);
}
.why__photo {
  aspect-ratio: 4 / 5;
  border-radius: 6px;
  box-shadow: 0 34px 64px -28px color-mix(in srgb, var(--green-900) 32%, transparent);
}
/* No max-width here — same mistake as the earlier Studio/Ritual/Menu bugs:
   a ch-based cap sized for body text squeezes the h-display heading inside
   it into extra, choppy line-wraps ("Gentler by / design, / not by /
   accident." instead of the intended 2 lines). .why__lede and .why__list p
   already carry their own explicit widths below, so removing this parent
   cap only frees the heading — the paragraphs don't get any wider. */
.why__copy .detail--rule { margin-bottom: 0.5rem; }
.why__lede {
  margin-top: 1.4rem;
  max-width: 44ch;
  color: var(--ink-strong);
  font-size: 1.05rem;
}
.why__list {
  margin-top: clamp(2rem, 4vw, 3rem);
  display: grid;
  gap: 1.6rem;
}
.why__list li {
  padding-top: 1.4rem;
  border-top: 1px solid var(--line);
}
.why__list li:first-child { border-top: 1px solid var(--line); }
.why__list h3 {
  font-family: var(--f-display);
  font-variation-settings: "SOFT" 28, "WONK" 0, "opsz" 60;
  font-weight: 400;
  font-size: clamp(1.15rem, 1rem + 0.6vw, 1.4rem);
  color: var(--ink);
}
.why__list p {
  margin-top: 0.4rem;
  max-width: 46ch;
  font-size: 0.95rem;
  line-height: 1.6;
  color: var(--ink-mid);
}

/* ───────────────────────────────────────────────────────────────
   19. WORDS — real Google reviews, an infinite Swiper coverflow.
   Section background is a hair darker than the page's own cream so the
   sand-coloured cards read as "lighter than their surroundings" without
   needing true white anywhere — the brand rule is "there is no white,"
   and a card that's merely the same cream as the page would vanish into
   it without some way to separate figure from ground.
   ─────────────────────────────────────────────────────────────── */
.words {
  padding: var(--section-y) 0;
  background: color-mix(in srgb, var(--sand) 92%, var(--green-500) 8%);
}
.words .detail--rule { color: var(--red); margin-inline: var(--edge); }

.reviews-swiper {
  margin-top: clamp(3rem, 6vw, 5rem);
  padding: 1rem var(--edge) 3.5rem;
  /* On a very large monitor this had no width limit at all, so it stretched
     edge to edge — meaning coverflow tried to show MORE simultaneous slide
     positions (there's simply more room) than 6 real reviews, even looped,
     comfortably cover, and the far side ran out of content to display.
     Capping the container is a fix that holds regardless of how wide a
     future monitor gets, rather than guessing at "enough" loop slides for
     any specific width. */
  max-width: 1200px;
  margin-inline: auto;
  /* Fallback for no-JS / Swiper-failed-to-load: Swiper's own bundled CSS
     already gives .swiper-wrapper `display:flex`, so without any JS at all
     this is still a perfectly usable horizontally-scrolling row of cards —
     buildReviewsSwiper() only adds the loop/coverflow/autoplay polish on
     top of that baseline, never replaces it. */
  overflow-x: auto;
}
/* Once Swiper actually initialises it adds this class — at that point the
   native scrollbar is redundant (Swiper drives its own drag/coverflow via
   transforms) and was showing up as a plain grey browser scrollbar under
   the cards, which reads as an unstyled leftover next to Swiper's own
   pagination dots. Hide it only once Swiper is actually running the show. */
.reviews-swiper.swiper-initialized { overflow: hidden; }
.reviews-swiper .swiper-slide {
  width: min(86vw, 380px);
  height: auto;
}

.review-card {
  /* A fixed floor, not a fixed height — real reviews run from one line
     (Drew Dolder, 237px of natural content at this width) to a full
     paragraph (Katelyn Kadelak, 384px). A hard height would either clip
     the long ones or leave the short ones swimming in empty space; a
     min-height this is the actual measured height of the longest review,
     so every card reads as "the same size," and the one genuinely long
     review is the only one that isn't sitting on extra padding. */
  min-height: 390px;
  height: 100%;
  display: grid;
  align-content: start;
  gap: 1rem;
  padding: 2rem;
  /* True white, not --sand — a deliberate, explicit exception to this
     site's "there is no white" rule (see the token comments in §2). These
     cards are meant to read as an actual embedded Google review widget,
     and Google's own cards are white; --sand here would look like a
     re-skinned imitation instead of the real thing. */
  background: #fff;
  border-radius: 14px;
  box-shadow: 0 24px 48px -28px color-mix(in srgb, var(--green-950) 45%, transparent);
}
.review-card__head { display: flex; align-items: center; gap: 0.85rem; }
.review-card__avatar {
  flex: none;
  width: 42px; height: 42px;
  border-radius: 50%;
  display: grid;
  place-items: center;
  font-family: var(--f-sub);
  font-weight: 600;
  font-size: 1rem;
  color: var(--sand);
}
/* Cycled from the brand palette, not Google's default random greys — an
   embedded review widget with brand-off avatar colours would read as a
   raw third-party embed; these read as native to the page instead. */
.review-card__avatar[data-tone="1"] { background: var(--orange); }
.review-card__avatar[data-tone="2"] { background: var(--green-700); }
.review-card__avatar[data-tone="3"] { background: color-mix(in srgb, var(--yellow) 90%, var(--green-950) 10%); }
.review-card__avatar[data-tone="4"] { background: var(--green-500); }
.review-card__who { display: grid; gap: 0.15rem; }
.review-card__who strong {
  font-family: var(--f-sub);
  font-weight: 600;
  font-size: 0.92rem;
  color: var(--ink);
}
.review-card__stars {
  /* Google's own star gold (#FBBC04, one of their brand colours), not this
     site's --yellow token — the brief for this card is "look like an actual
     Google review," and a brand-mixed amber reads as an approximation next
     to the real thing. Safe contrast-wise: #FBBC04 on white is ~1.6:1, but
     WCAG's text-contrast floor doesn't apply here — a 5-star rating is
     conveyed redundantly by the aria-label, so the glyphs themselves are
     decorative rather than the only carrier of the information. */
  color: #FBBC04;
  letter-spacing: 0.15em;
  font-size: 0.85rem;
}
.review-card blockquote p {
  font-size: 0.95rem;
  line-height: 1.62;
  color: var(--ink-strong);
}

.reviews-swiper .swiper-pagination { position: static; margin-top: 1.5rem; }
.reviews-swiper .swiper-pagination-bullet {
  background: var(--ink-soft);
  opacity: 1;
  width: 7px; height: 7px;
  transition: background 0.5s var(--ease-soft), transform 0.5s var(--ease-spa);
}
.reviews-swiper .swiper-pagination-bullet-active {
  background: var(--orange);
  transform: scale(1.3);
}

.words__cta { display: flex; justify-content: center; margin: 1rem var(--edge) 0; }

/* ───────────────────────────────────────────────────────────────
   20. THE MENU — dark scope (kept idea, deepest tone as its own "card").
   A Michelin card, not a pricing table.
   ─────────────────────────────────────────────────────────────── */
.menu {
  --ink: var(--sand);
  --ink-strong: color-mix(in srgb, var(--sand) 80%, transparent);
  --ink-mid: color-mix(in srgb, var(--sand) 66%, transparent);
  --ink-soft: color-mix(in srgb, var(--sand) 58%, transparent);
  --line: color-mix(in srgb, var(--sand) 16%, transparent);
  --line-soft: color-mix(in srgb, var(--sand) 9%, transparent);
  --accent-text: var(--orange);
  background: var(--green-950); /* one shade deeper than the other dark sections — its own "card" */
  color: var(--ink);
  padding-block: var(--section-y);
}
.menu__head {
  /* No max-width here — same bug as the earlier Studio/Ritual headings:
     `ch` is sized off the BODY font (nearly constant across viewports),
     so a `58ch` cap barely changes as the viewport grows, while the
     h-display heading inside it keeps scaling up via its own vw-based
     clamp(). On a normal monitor the mismatch is small enough to miss; on
     a very large one, "A menu of services" gets squeezed into three
     choppy lines. .menu__note below already has its OWN 50ch cap for
     itself, so removing this parent cap doesn't widen the paragraph too —
     only frees the heading, which had no cap of its own. */
  padding-inline: var(--edge);
  display: grid;
  gap: 1.4rem;
  justify-items: center;
  text-align: center;
  margin-inline: auto;
}
.menu__head .detail--rule { width: 100%; }
.menu__head .detail--rule::before {
  content: "";
  flex: 1;
  height: 1px;
  background: currentColor;
  opacity: 0.28;
}
.menu__note {
  color: var(--ink-mid);
  font-size: 0.98rem;
  font-style: italic;
  max-width: 50ch;
}

.menu__body {
  padding: clamp(3.5rem, 7vw, 6rem) var(--edge) 0;
}

/* No more index sidebar to switch courses — with only two short lists left
   (see the note in main.js where buildMenuAccordion used to live), a
   click-to-switch nav had nothing left to earn its keep managing. Both
   courses just sit stacked and always visible now, centred to a readable
   measure instead of stretching full-bleed now that the sidebar column
   is gone. */
.menu__courses {
  max-width: 54rem;
  margin-inline: auto;
  display: grid;
  gap: clamp(4rem, 8vw, 7rem);
}

.course__head {
  display: grid;
  gap: 0.5rem;
  padding-bottom: 1.6rem;
  border-bottom: 1px solid var(--line);
  position: relative;
}
.course__num {
  font-family: var(--f-detail);
  font-size: 0.66rem;
  letter-spacing: 0.14em;
  color: var(--orange);
}
.course__head h3 {
  font-family: var(--f-display);
  font-variation-settings: "SOFT" 28, "WONK" 0, "opsz" 120;
  font-weight: 400;
  font-size: clamp(2rem, 1.4rem + 2.2vw, 3.4rem);
  line-height: 1;
  color: var(--ink);
}
.course__head p {
  font-style: italic;
  color: var(--ink-mid);
  font-size: 0.95rem;
  max-width: 46ch;
}

.course__list { display: grid; }

/* A dish. Leader dots between name and price — the menu tell. */
.dish {
  position: relative;
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: baseline;
  gap: 1rem;
  padding: 1.5rem 0;
  border-bottom: 1px solid var(--line-soft);
  transition: padding-left 0.85s var(--ease-spa);
}
.dish::before {
  content: "";
  position: absolute;
  left: -1.4rem; top: 50%;
  width: 5px; height: 5px;
  border-radius: 50%;
  background: var(--orange);
  transform: translateY(-50%) scale(0);
  transition: transform 0.7s var(--ease-spa);
}
.dish:hover { padding-left: 1rem; }
.dish:hover::before { transform: translateY(-50%) scale(1); }

.dish__name h4 {
  font-family: var(--f-display);
  font-variation-settings: "SOFT" 28, "WONK" 0, "opsz" 60;
  font-weight: 400;
  font-size: clamp(1.15rem, 1rem + 0.6vw, 1.5rem);
  line-height: 1.2;
  color: var(--ink);
  transition: color 0.6s var(--ease-soft);
}
.dish:hover .dish__name h4 { color: var(--yellow); }
.dish__name p {
  margin-top: 0.3rem;
  max-width: 48ch;
  font-size: 0.92rem;
  line-height: 1.6;
  font-style: italic;
  color: var(--ink-mid);
  transition: color 0.6s var(--ease-soft);
}
.dish:hover .dish__name p { color: var(--ink-strong); }

.dish__meta {
  display: flex;
  align-items: baseline;
  gap: 1.5rem;
  font-family: var(--f-detail);
  letter-spacing: 0.08em;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.dish__time { font-size: 0.68rem; color: var(--ink-mid); }
.dish__time::after { content: " min"; }
.dish__price { font-size: 0.95rem; color: var(--ink-strong); }
.dish__price::before { content: "$"; font-size: 0.7em; vertical-align: 0.32em; margin-right: 1px; opacity: 0.6; }
.dish__price--free::before { content: none; }
.dish:hover .dish__price { color: var(--orange); }

/* ───────────────────────────────────────────────────────────────
   20b. FAQ — light scope. Simple drop-downs: each question toggles
   independently (unlike the Menu accordion, where only one category shows
   at a time), plain opacity fade with no y-slide — the same lesson just
   learned from the Menu's dish animation, applied here from the start.
   ─────────────────────────────────────────────────────────────── */
.faq {
  /* padding-inline was simply missing — every other section has it, this
     one didn't, which is exactly why it read as stuck against the left
     edge of the browser at every viewport width, not just a narrow one. */
  padding-block: var(--section-y);
  padding-inline: var(--edge);
  text-align: center;
}
.faq__list {
  margin-top: clamp(2.5rem, 5vw, 4rem);
  max-width: 54rem;
  margin-inline: auto; /* centers the block itself on wide viewports */
  text-align: left;    /* ...while the actual question/answer rows stay left-aligned, more readable than centered prose */
  border-top: 1px solid var(--line);
}
.faq__item { border-bottom: 1px solid var(--line); }
.faq__q {
  width: 100%;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1.5rem;
  padding: 1.5rem 0;
  background: none;
  border: 0;
  cursor: pointer;
  text-align: left;
  font-family: var(--f-display);
  font-variation-settings: "SOFT" 28, "WONK" 0, "opsz" 60;
  font-size: clamp(1.1rem, 1rem + 0.5vw, 1.35rem);
  color: var(--ink);
  transition: color 0.5s var(--ease-soft);
}
.faq__q:hover { color: var(--accent-text); }
/* A plain drawn +, not an icon font or SVG — rotates one bar away to read
   as a − once open, same restrained, line-drawn language as the rest of
   the site's small marks (the marquee dot, the newsletter arrow). */
.faq__icon { flex: none; position: relative; width: 18px; height: 18px; color: var(--ink-mid); }
.faq__icon::before, .faq__icon::after {
  content: "";
  position: absolute;
  top: 50%; left: 50%;
  background: currentColor;
  transform: translate(-50%, -50%);
  transition: transform 0.5s var(--ease-spa), opacity 0.5s var(--ease-soft);
}
.faq__icon::before { width: 14px; height: 1.5px; }
.faq__icon::after { width: 1.5px; height: 14px; }
.faq__item.is-open .faq__q { color: var(--accent-text); }
.faq__item.is-open .faq__icon::after { transform: translate(-50%, -50%) rotate(90deg); opacity: 0; }
.faq__a { padding-bottom: 1.6rem; max-width: 62ch; }
.faq__a p { color: var(--ink-mid); line-height: 1.65; }

/* ───────────────────────────────────────────────────────────────
   21. RESERVE — light scope now (was implicitly dark before, inheriting
   the old body colour). Every literal --sand/--green-950 below has been
   swapped for scope tokens so the form reads correctly on cream.
   ─────────────────────────────────────────────────────────────── */
.reserve {
  position: relative;
  padding: var(--section-y) var(--edge);
  overflow: clip;
}
/* The wrapper does the centring; the <img> inside carries [data-parallax],
   so GSAP never touches this element's transform. See note on .hero__plate.
   Real logo file now (logo-orange.png) — the old `color: green-700` tint
   only worked on the currentColor-based placeholder SVG and did nothing
   to a raster image, so it's gone; the watermark reads as a very faint
   orange now instead, which is correct given it's genuinely the orange
   mark used everywhere else (loader, this spot). */
.reserve__mark {
  position: absolute;
  top: 50%; right: -6vw;
  width: min(48vw, 560px);
  transform: translateY(-50%);
  opacity: 0.1;
  pointer-events: none;
}
.reserve__inner {
  position: relative;
  z-index: 2;
  max-width: 44rem;
  display: grid;
  gap: 1.6rem;
}
.reserve__lede { max-width: 46ch; color: var(--ink-mid); }

.reserve__cta {
  display: flex;
  align-items: center;
  gap: clamp(1.6rem, 4vw, 2.6rem);
  flex-wrap: wrap;
  margin-top: clamp(1.5rem, 3vw, 2.5rem);
}
.reserve__social {
  display: inline-flex;
  align-items: center;
  gap: 0.6rem;
}
.reserve__social-icon { width: 20px; height: 20px; flex-shrink: 0; }

/* ───────────────────────────────────────────────────────────────
   21b. NEWSLETTER — light scope. Reads as a quiet interstitial, not a
   banner: bounded by plain hairlines top and bottom (same device as the
   Ethos marquee), no photo, no dark overlay, no colour block. The brief
   was explicit about not wanting the generic dark-image-banner newsletter
   pattern — this is the opposite of that by design.
   ─────────────────────────────────────────────────────────────── */
.newsletter {
  padding: var(--section-y) var(--edge);
  text-align: center;
  border-block: 1px solid var(--line);
}
.newsletter__inner {
  /* 34rem (544px) was sized around the old "A little sweetness, once a
     month." heading. The new "Launch your journey in beauty." needs 648px
     for "Launch your journey" alone at h-display size — measured directly,
     not guessed — so it was wrapping into 3 lines instead of the intended
     2. Widened with margin to spare. .newsletter__lede has its own,
     separate max-width below, so this doesn't widen the body copy too. */
  max-width: 44rem;
  margin-inline: auto;
  display: grid;
  gap: 1.4rem;
  justify-items: center;
}
.newsletter .detail--rule { justify-content: center; }
.newsletter__lede {
  max-width: 42ch;
  color: var(--ink-mid);
}
.newsletter__form {
  width: 100%;
  max-width: 26rem;
  margin-top: 0.8rem;
  display: grid;
  gap: 0.6rem;
}
.newsletter__field {
  display: flex;
  align-items: center;
  gap: 1rem;
  border-bottom: 1px solid var(--line);
  padding-bottom: 0.7rem;
  transition: border-color 0.6s var(--ease-soft);
}
.newsletter__field:focus-within { border-color: var(--orange); }
.newsletter__field input {
  flex: 1;
  min-width: 0;
  background: transparent;
  border: 0;
  font-size: 1rem;
  color: var(--ink);
  text-align: left;
}
.newsletter__field input::placeholder { color: var(--ink-soft); font-style: italic; }
.newsletter__field input:focus { outline: none; }
.newsletter__submit {
  /* 44px, not 42 — the touch-target floor established earlier in this
     project (Apple HIG minimum), kept consistent here too. */
  flex: none;
  width: 44px; height: 44px;
  border-radius: 50%;
  border: 1px solid var(--ink-soft);
  background: transparent;
  color: var(--ink);
  display: grid;
  place-items: center;
  cursor: pointer;
  transition: background 0.6s var(--ease-soft), border-color 0.6s var(--ease-soft), color 0.6s var(--ease-soft);
}
.newsletter__submit svg { width: 18px; height: 18px; }
.newsletter__submit:hover {
  background: var(--orange);
  border-color: var(--orange);
  color: var(--green-950);
}
.newsletter__status {
  min-height: 1.2rem;
  font-size: 0.88rem;
  font-style: italic;
  color: var(--confirm-text);
  opacity: 0;
  transform: translateY(6px);
  transition: opacity 0.8s var(--ease-soft), transform 0.8s var(--ease-spa);
}
.newsletter__status.is-shown { opacity: 1; transform: none; }
.newsletter__status.is-error { color: var(--red); }

/* ───────────────────────────────────────────────────────────────
   22. FOOTER — dark scope. The closing bookend to the loader's opening one.
   ─────────────────────────────────────────────────────────────── */
.foot {
  background: var(--green-950);
  padding: clamp(4rem, 8vw, 6rem) var(--edge) 2rem;
  border-top: 1px solid var(--line-soft);
}
.foot__top {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 2fr);
  gap: var(--gut);
  padding-bottom: clamp(3rem, 6vw, 5rem);
}
.foot__brand { display: grid; gap: 1rem; justify-items: start; align-content: start; }
.foot__mark { width: 40px; } /* real logo file, already coloured — see .nav__mark */
.foot__brand .wordmark { font-size: 2.6rem; color: var(--ink); }
.foot__cols {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--gut);
}
.foot__cols > div { display: grid; gap: 0.7rem; align-content: start; }
.foot__cols p:not(.detail) {
  font-size: 0.92rem;
  line-height: 1.7;
  color: var(--ink-mid);
}
.foot__cols a { transition: color 0.5s var(--ease-soft); }
.foot__cols a:hover { color: var(--orange); }
.foot__placeholder {
  /* Visibly-bracketed contact details are placeholders on purpose — see
     README.md. Understated but not invisible, so it can't ship by accident. */
  color: var(--ink-soft);
  border-bottom: 1px dashed var(--line);
}
.foot__bar {
  display: flex;
  justify-content: space-between;
  gap: 1rem;
  padding-top: 1.6rem;
  border-top: 1px solid var(--line-soft);
}

/* ───────────────────────────────────────────────────────────────
   23. RESPONSIVE
   ─────────────────────────────────────────────────────────────── */
@media (max-width: 1024px) {
  .spread { grid-template-columns: 1fr; gap: 1.2rem; }
  .spread__main { max-width: none; }
  .why__grid { grid-template-columns: 1fr; }
  .why__photo { max-width: 420px; margin-inline: auto; aspect-ratio: 4/3; }
  .why__copy { max-width: none; }
  .gallery { grid-template-columns: repeat(6, 1fr); }
  .gallery__a { grid-column: 1 / 4; }
  .gallery__b { grid-column: 4 / 7; }
  .pull { grid-column: 1 / 7; grid-row: auto; }
  .gallery__c { grid-column: 2 / 6; }
  .words__grid { grid-template-columns: 1fr; gap: 2.5rem; }
  .testimony { border-bottom: 1px solid var(--line); padding-bottom: 2.5rem; }
  .testimony:last-child { border: 0; padding-bottom: 0; }
  .foot__top { grid-template-columns: 1fr; gap: 3rem; }
}

@media (max-width: 860px) {
  .nav__links, .nav__cta { display: none; }
  .nav__burger { display: block; }
  .movement { grid-template-columns: 3rem minmax(0, 1fr); }
  .movement__time { grid-column: 2; text-align: left; padding-top: 0.9rem; color: var(--ink-mid); }
  .movement__peek { display: none; }
  .movement:hover .movement__body h3 { transform: none; }
  /* .about__word's font-size is no longer overridden per breakpoint — it's
     driven by --dani-size on .about__stage now, which already scales down
     correctly on its own. A separate override here is exactly what let it
     drift out of proportion with .about__portrait in the first place. */
}

@media (max-width: 620px) {
  :root { --section-y: clamp(4.5rem, 12vh, 7rem); }

  .hero { padding-top: 7rem; }
  .hero__plate { width: 78vw; height: 52vh; opacity: 0.9; }
  .hero__word { font-size: clamp(4.5rem, 30vw, 8rem); }
  .hero__actions { flex-direction: column; gap: 0.75rem; width: 100%; }
  .hero__actions .btn { width: 100%; }
  /* A thumb needs 44px (Apple HIG); this link was 24px tall. Standalone
     links get a real target on touch — the inline links in the footer
     prose are exempt under WCAG 2.5.8 and stay as they are. */
  .hero__actions .link-quiet {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-height: 44px;
    width: 100%;
    padding-bottom: 0;
  }
  .hero__actions .link-quiet::after { bottom: 10px; left: 50%; width: 8.5em; margin-left: -4.25em; }

  .about__stage { min-height: clamp(260px, 78vw, 400px); }
  /* .about__portrait's width is no longer overridden per breakpoint either
     — same reasoning as .about__word above. Both now derive from the one
     --dani-size variable, so they can't drift apart at any width. */

  .break__line--b { margin-left: 1rem; }
  .break--alt .break__line--b { margin-left: 1rem; }

  .ethos__cols { grid-template-columns: 1fr; gap: 1.4rem; }
  .foot__cols { grid-template-columns: 1fr; gap: 2rem; }
  .foot__bar { flex-direction: column; gap: 0.4rem; }

  .reserve__mark { opacity: 0.07; right: -30vw; width: 90vw; }

  /* The menu goes to a single stacked card — price sits under the name,
     the way a phone menu should read. */
  .dish { grid-template-columns: 1fr; gap: 0.55rem; padding: 1.3rem 0; }
  .dish__meta { gap: 1rem; }
  .dish:hover { padding-left: 0; }
  .gallery { grid-template-columns: 1fr; }
  .gallery__a, .gallery__b, .gallery__c { grid-column: 1; }
  .gallery__b { aspect-ratio: 5 / 4; }
  .gallery__c { display: none; } /* the third plate is a desktop luxury */
  .pull {
    grid-column: 1;
    /* .pull's own justify-items:start (desktop) shrink-wraps its <p> to its
       content's natural width rather than filling the available column —
       fine on desktop where the block sits in an 8-of-12-column span wide
       enough that shrinking barely shows, but on mobile's single narrow
       column the same shrink-to-fit made the quote render noticeably
       narrower than the rest of the page's text. Stretch it here instead. */
    justify-items: stretch;
  }
  .pull p { text-align: left; }
}

/* ───────────────────────────────────────────────────────────────
   24. REDUCED MOTION — the calm version of a calm site
   ─────────────────────────────────────────────────────────────── */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
  .grain { animation: none; }
  .reveal, .reveal-lines { opacity: 1 !important; }
  .cursor { display: none; }
}

/* ═══════════════════════════════════════════════════════════════
   25. CAMPAIGN — website giveaway banner          ⚠ TEMPORARY ⚠
   ───────────────────────────────────────────────────────────────
   Everything below belongs to the "free website for an esthetician"
   campaign and is designed to be deleted whole. Nothing above this
   line was modified to accommodate it — the two rules that touch
   existing components (.nav, .sheet) are overrides scoped to
   html.promo-on, so with the campaign off they never apply and the
   site computes exactly as it did before.

   TO REMOVE AFTER THE CAMPAIGN
     1. index.html  — set SOLA_PROMO = false  (kills it instantly), or
                      delete the two blocks marked "CAMPAIGN"
     2. this file   — delete from this header to the end of the file
     3. delete the  /giveaway  folder
   ═══════════════════════════════════════════════════════════════ */

/* Two variables, not one, and the split matters:

     --promo-bar  the banner's OWN height. Always defined, never changes.
     --promo-h    how much vertical space the banner is currently
                  reserving — i.e. how far everything else moves down.
                  0px unless the campaign is on and undismissed.

   Collapsing these into one variable is the obvious thing to do and it's
   wrong: `height` is not a transitioned property, so on dismissal the bar
   would snap from 44px to 0 while its contents were still mid-fade,
   jumping them upward as they recentred on a zero-height flex box. Keeping
   the bar's height constant lets it slide out cleanly while --promo-h —
   which only the nav and sheet read — animates the layout back.

   `0px` not `0`, because it's used inside calc().
   ─── one source of truth for the height is --promo-bar ─── */
:root {
  --promo-bar: 44px;   /* 44 = the touch-target floor used site-wide */
  --promo-h: 0px;
}
@media (min-width: 620px) {
  :root { --promo-bar: 40px; }
}
html.promo-on { --promo-h: var(--promo-bar); }

/* The banner sits ABOVE .nav (z-800) and below the cursor (8500), grain
   (9000) and loader (9500) — so the loader still covers it on entry, the
   way it covers everything else.

   position:fixed is load-bearing: out of flow means the document height
   never changes, so there is no layout shift on first paint, nothing for
   ScrollTrigger to re-measure, and dismissing it cannot reflow the page
   under a reader's thumb. */
.promo {
  position: fixed;
  top: 0; left: 0; right: 0;
  z-index: 810;
  height: var(--promo-bar);
  display: flex;
  align-items: center;
  justify-content: center;
  padding-left: 0.9rem;
  padding-right: 2.75rem; /* clears the absolutely-positioned dismiss button */
  background: var(--orange);
  /* Dark green on orange — 4.5:1, and the exact pairing .btn:hover already
     uses (§6). Orange is the one brand colour never used as a surface
     anywhere else on the site, which is what makes a solid orange bar read
     as a temporary announcement rather than a section. */
  color: var(--green-950);
  /* visibility is delayed by the full slide duration so the bar stays
     rendered while it travels, then drops out of the accessibility tree
     the moment it's off-screen. */
  transition: transform 0.55s var(--ease-spa),
              opacity 0.4s var(--ease-soft),
              visibility 0s linear 0.55s;
}

/* Hidden state — also the state on a page load where the visitor dismissed
   it on a previous visit, and the state when SOLA_PROMO is false. The class
   is applied by an inline <head> script BEFORE first paint, so this resolves
   during initial style computation and the transition never fires on load:
   no flash of a banner that then disappears. */
html:not(.promo-on) .promo {
  transform: translateY(-100%);
  opacity: 0;
  visibility: hidden;   /* also drops it out of the accessibility tree */
  pointer-events: none;
}

/* The whole bar is the target, not just the words — a thumb on a phone
   shouldn't have to find a 5-character link. */
.promo__link {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  min-width: 0; /* lets the text shrink instead of overflowing the flex row */
  font-family: var(--f-sub);
  /* Sentence case, not the uppercase-with-0.13em-tracking used by every
     other small label on the site. Deliberate: it's the cheapest way to
     read as "not part of the design system" while still being set in the
     design system's own typeface. */
  font-size: clamp(0.7rem, 3vw, 0.82rem);
  font-weight: 500;
  letter-spacing: 0.005em;
  line-height: 1;
}
.promo__text {
  /* Never wrap, never overflow. At 320px the clamp above has already sized
     this to fit; the ellipsis is a floor, not an expectation. */
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  /* Overrides the line-height:1 inherited from .promo__link. At exactly 1,
     this span's own line box is tighter than the font's real glyph metrics,
     so overflow:hidden (needed above for the ellipsis) was clipping the
     descenders — the tail of every "g", "y", "p". 1.2 gives the glyphs
     room without visibly changing the bar's height (.promo still centers
     this via align-items on a taller fixed-height flex row). */
  line-height: 1.2;
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 3px;
  text-decoration-color: color-mix(in srgb, var(--green-950) 40%, transparent);
  transition: text-decoration-color 0.5s var(--ease-soft);
}
.promo__link:hover .promo__text { text-decoration-color: currentColor; }
.promo__go {
  flex: none;
  width: 15px; height: 15px;
  transition: transform 0.6s var(--ease-spa);
}
.promo__link:hover .promo__go { transform: translateX(4px); }

/* 44px square — the touch-target floor this project set for
   .newsletter__submit and the Hero's mobile links. */
.promo__x {
  position: absolute;
  top: 0; right: 0;
  width: 44px; height: 100%;
  display: grid;
  place-items: center;
  border: 0;
  background: none;
  color: inherit;
  cursor: pointer;
  opacity: 0.62;
  transition: opacity 0.4s var(--ease-soft);
}
.promo__x:hover { opacity: 1; }
.promo__x i {
  position: absolute;
  width: 13px; height: 1.5px;
  border-radius: 2px;
  background: currentColor;
}
.promo__x i:nth-child(1) { transform: rotate(45deg); }
.promo__x i:nth-child(2) { transform: rotate(-45deg); }

/* ── The two displacement overrides ──
   Both scoped to html.promo-on, so when the campaign is off (or dismissed)
   they stop applying and the originals in §8/§9 take over untouched. */

/* §8's .nav is `top: 0`. Push it down by exactly the banner's height. */
html.promo-on .nav { top: var(--promo-h); }

/* `top` is not in §8's transition-property list, so without this the nav
   would jump the instant the banner is dismissed instead of following it
   up. Declared unscoped (a plain re-declaration of §8's line + `top`)
   because it has to still be in effect on the way OUT, after .promo-on has
   already been removed from <html>. Harmless when the campaign is off —
   `top` never changes then. */
.nav { transition-property: background, backdrop-filter, padding, color, top; }

/* §9's .sheet clears the nav with a 6.5rem top pad. With the nav pushed
   down, that pad has to grow by the same amount or the first menu item
   ("01 Home") slides under it. */
html.promo-on .sheet {
  padding-top: calc(env(safe-area-inset-top) + 6.5rem + var(--promo-h));
}

/* ── Persistent discovery after dismissal ──
   The footer credit line already reads "Made by @dlxmr on Instagram" —
   the single highest-intent spot on the page for this audience, since an
   esthetician impressed by the site goes looking for exactly that. This
   makes the giveaway the second half of that sentence rather than adding
   new furniture to a client's footer. */
.foot__promo {
  color: var(--orange);
  transition: opacity 0.5s var(--ease-soft);
}
.foot__promo:hover { opacity: 0.75; }
.sheet__promo {
  font-family: var(--f-sub);
  font-size: 0.72rem;
  text-transform: uppercase;
  letter-spacing: 0.14em;
  color: var(--orange);
}

/* ═════════════ END CAMPAIGN — nothing below this line ═════════════ */
