/* ===========================================================
   Amélie Blancher — style.css
   Quiet-wealth editorial aesthetic. No frameworks.
   =========================================================== */

/* ---------- Self-hosted typeface ----------
   Inter, SIL OFL 1.1 — see fonts/OFL.txt. Files under fonts/ so the page
   makes zero third-party network requests. One family, five static
   weights (300/400/500/700/800); editorial contrast comes from
   weight/tracking, not a second typeface — see CLAUDE.md for why there's
   no --font-secondary anymore, and for the bold/heavy direction pivot. */
@font-face {
  font-family: 'Inter';
  src: url('fonts/Inter-Light.woff2') format('woff2');
  font-weight: 300;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: 'Inter';
  src: url('fonts/Inter-Regular.woff2') format('woff2');
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: 'Inter';
  src: url('fonts/Inter-Medium.woff2') format('woff2');
  font-weight: 500;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: 'Inter';
  src: url('fonts/Inter-Bold.woff2') format('woff2');
  font-weight: 700;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: 'Inter';
  src: url('fonts/Inter-ExtraBold.woff2') format('woff2');
  font-weight: 800;
  font-style: normal;
  font-display: swap;
}

/* Playfair Display, SIL OFL 1.1 — see fonts/PlayfairDisplay-OFL.txt.
   Deliberate, scoped exception to "one typeface" (2026-08-10, client
   request, matching a specific hero mockup): used ONLY for the hero
   statement via --font-display, nowhere else. Don't reach for
   --font-display in other components without checking first — see
   CLAUDE.md for why this exists and how narrow its intended use is. */
@font-face {
  font-family: 'Playfair Display';
  src: url('fonts/PlayfairDisplay-Regular.woff2') format('woff2');
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: 'Playfair Display';
  src: url('fonts/PlayfairDisplay-Italic.woff2') format('woff2');
  font-weight: 400;
  font-style: italic;
  font-display: swap;
}
@font-face {
  font-family: 'Playfair Display';
  src: url('fonts/PlayfairDisplay-Medium.woff2') format('woff2');
  font-weight: 500;
  font-style: normal;
  font-display: swap;
}

/* ---------- Design tokens ---------- */
:root {
  /* --- Typography --- */
  --font-primary: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  --font-display: 'Playfair Display', Georgia, 'Times New Roman', serif;

  /* --- Color Palette (Quiet Wealth / Minimalist) --- */
  --color-bg: #FAFAF7;         /* Warm Alabaster / Soft Ivory */
  --color-surface: #F4F4F0;    /* Muted Stone */
  --color-text: #1A1A1A;       /* Deep Charcoal / Near Black */
  --color-muted: #6E6E6E;      /* Editorial Gray — darkened from #767676 2026-08-16 for
                                   WCAG AA contrast (4.34:1 → 4.88:1 on --color-bg; the
                                   original failed AA's 4.5:1 minimum for normal text) */
  --color-border: #E5E5DF;     /* Fine Hairline Border */
  /* No accent color — deliberately monochrome, see CLAUDE.md. (A scoped
     --color-accent-acqua briefly existed for the hero canvas's cursor glow;
     removed 2026-08-17 at explicit client request — "don't use the acqua color
     at all." The thread is plain --color-bg white now, no accent anywhere.) */

  /* --- Spacing Scale --- */
  --space-unit: 1rem;
  --space-xs: 0.5rem;
  --space-sm: 1rem;
  --space-md: 2rem;
  --space-lg: 4rem;
  --space-xl: 8rem;

  /* --- Non-scale structural helpers (layout/motion, not design tokens) --- */
  --container-max: 1280px;
  --transition: 220ms ease;
  /* Header is fixed and overlays the hero (see .site-header) — tracks its
     actual box height so the hero can sit flush underneath it and
     in-page anchor scrolls can offset past it. Scales fluidly with
     viewport width (see .site-header) rather than jumping at breakpoints.
     Sized to comfortably fit the header's actual (modest) content — see
     the .wordmark-name comment in the Header section — not the taller
     188px this used to be when the wordmark itself was much bigger. */
  --header-h: clamp(72px, 6vw, 100px);
}

/* ---------- Dark mode (2026-08-17) ----------
   System-preference only, no in-page toggle — the site follows the
   visitor's OS setting via prefers-color-scheme and offers no override UI,
   an explicit client choice over a manual toggle. Every component is
   already driven off the five --color-* tokens (see "Design tokens"
   above), so the whole site re-themes by redefining just those five here;
   nothing else in this file references a raw hex color except the one
   documented #FFFFFF exception on .btn-primary's hover state, overridden
   separately just below. (A --color-hero-thread token used to live here,
   for the generative hero canvas's traced line — removed 2026-10-11
   alongside that whole canvas, see the Hero section's own notes.)
   Values are the light palette's own relationships inverted (near-black
   warm charcoal bg, soft warm-white
   text, same subtlety of surface/border steps as light mode), not a
   generic gray-scale invert — chosen and contrast-checked
   (text/bg 16.3:1, muted/bg 6.6:1, both comfortably clearing WCAG AA)
   rather than eyeballed. `color-scheme: dark` lets native form controls
   (the booking form's select/inputs) pick up a dark UI automatically. */
@media (prefers-color-scheme: dark) {
  :root {
    color-scheme: dark;
    --color-bg: #141311;         /* Near-black Warm Charcoal */
    --color-surface: #1D1B18;    /* Muted Stone, dark */
    --color-text: #F2F0EA;       /* Soft Warm White, not true white — same
                                     "not pure white/black" restraint as the
                                     light palette's ivory/charcoal choice */
    --color-muted: #9C9A93;      /* Editorial Gray, dark — 6.6:1 on --color-bg */
    --color-border: #2A2824;     /* Fine Hairline Border, dark */
  }
  /* .btn-primary's own dark-mode hover-color override lives with that
     component's rule further down (source order matters here — a copy
     placed up in this token block would lose to the light-mode rule's
     later, equal-specificity `color: #FFFFFF`), not repeated here. */
}

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

html {
  scroll-behavior: smooth;
  /* Header is fixed/overlaid — without this, an in-page nav jump lands
     a target's heading directly under it. */
  scroll-padding-top: var(--header-h);
}

body {
  background-color: var(--color-bg);
  color: var(--color-text);
  font-family: var(--font-primary);
  letter-spacing: -0.01em;
  margin: 0;
  padding: 0;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

img { max-width: 100%; display: block; }

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

ul, ol { list-style: none; margin: 0; padding: 0; }

h1, h2, h3, h4 {
  font-family: var(--font-primary);
  font-weight: 700;
  letter-spacing: -0.03em;
  margin: 0;
}

blockquote, p, figure { margin: 0; }

button, input, select, textarea {
  font: inherit;
  color: inherit;
  background: none;
  border: 0;
}

/* ---------- Language toggle plumbing ---------- */
html[lang="fr"] .lang-en { display: none; }
html[lang="en"] .lang-fr { display: none; }

/* ---------- Utility ---------- */
.skip-link {
  position: absolute;
  left: -999px;
  top: 0;
  background: var(--color-text);
  color: var(--color-bg);
  padding: var(--space-sm) var(--space-md);
  z-index: 200;
}
.skip-link:focus { left: var(--space-xs); top: var(--space-xs); }

.section-inner {
  max-width: var(--container-max);
  margin: 0 auto;
  padding: 0 clamp(var(--space-md), 5vw, var(--space-lg));
}

.section-title {
  font-weight: 800;
  font-size: clamp(1.9rem, 3.4vw, 2.8rem);
  line-height: 1.1;
  color: var(--color-text);
  max-width: 22ch;
}

.section-note {
  margin-top: calc(var(--space-sm) + var(--space-xs) / 2);
  max-width: 46ch;
  color: var(--color-muted);
  font-size: 0.95rem;
}

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.6em;
  padding: 0.95em calc(var(--space-md) - var(--space-xs) / 2);
  font-size: 0.78rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  border: 1px solid var(--color-text);
  cursor: pointer;
  transition: background var(--transition), color var(--transition), border-color var(--transition);
}

/* Outline-by-default, filled-on-hover CTA (2026-08-16, explicit client
   request, applied identically to `.nav-toggle` below — see that rule's
   comment) — a real reversal of the immediately preceding decision on this
   button ("remove any visual change on hover, only the cursor should
   change"): hover now has a deliberate visual change again, restoring the
   sitewide outline/fill-on-hover button convention this codebase used
   earlier in its history, before the liquid-glass detour (see git history
   for that full sequence if ever worth revisiting). Idle: transparent fill,
   `1px solid var(--color-text)` border, `var(--color-text)` label. Hover/
   focus: fills solid `var(--color-text)` (`#1A1A1A`), label flips to
   `#FFFFFF`. `#FFFFFF` has no existing token — every other "light" color on
   this site is the warm ivory `--color-bg` (`#FAFAF7`), not true white — a
   deliberate, narrow exception rather than a token substitution.
   `border-radius: 8px` (unchanged from the prior solid-CTA pass). This
   button has no natural "selected" persistent state (it's a plain submit
   button, not a toggle) — `:hover`/`:focus-visible` cover it; `.nav-toggle`
   additionally maps "selected" to `[aria-expanded="true"]` since that
   button actually toggles open/closed. */
.btn-primary {
  gap: 0.5rem;
  padding: 0.875rem 1.3125rem;
  font-family: var(--font-primary);
  font-weight: 500;
  font-size: 1.1875rem;
  line-height: 1.3;
  letter-spacing: -0.1px;
  text-transform: none;
  color: var(--color-text);
  background: transparent;
  border: 1px solid var(--color-text);
  border-radius: 8px;
  transition: background var(--transition), color var(--transition);
}
.btn-primary:hover,
.btn-primary:focus-visible {
  background: var(--color-text);
  color: #FFFFFF;
}
.btn-primary:focus-visible {
  outline: 2px solid var(--color-text);
  outline-offset: 2px;
}
/* Dark mode: the fill above is var(--color-text), which is light in this
   scheme, so the hardcoded light-mode #FFFFFF label (see the comment on
   this rule above) would be nearly invisible — swap to var(--color-bg)
   (dark in this scheme) instead, the same bg/text inversion the button
   already does. Placed after the base rule on purpose — equal specificity,
   so source order is what makes this win. */
@media (prefers-color-scheme: dark) {
  .btn-primary:hover,
  .btn-primary:focus-visible {
    color: var(--color-bg);
  }
}

.btn-block { width: 100%; }

/* ---------- TK placeholder frames ---------- */
.tk-frame {
  position: relative;
  width: 100%;
  aspect-ratio: var(--ratio, 3/4);
  border: 1px dashed var(--color-border);
  background: repeating-linear-gradient(135deg, var(--color-surface) 0 2px, transparent 2px 14px);
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
}
.tk-label {
  font-size: 0.68rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--color-muted);
  padding: 0 var(--space-md);
}

/* ---------- Header ----------
   Fixed and overlaid on the hero (2026-08-10, from the Figma mockup, node
   5:33): transparent at scroll-top so the hero photo shows through behind
   the wordmark/nav, solid + blurred once .is-scrolled is toggled by
   script.js. Because a fixed header no longer occupies flow space, the
   hero starts right underneath it — that's what produces the peek-through
   effect, not a negative margin. See the .wordmark-name comment below for
   why the actual sizing is deliberately modest rather than chasing the
   Figma file's literal pixel values — that's been tried and reverted
   twice. */
.site-header {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 100;
  height: var(--header-h);
  background: transparent;
  /* border-bottom removed 2026-10-11 at explicit client request — it cut visibly
     across the hero photo at scroll-top, which reads worse now that the hero is a
     real photo (previously a generative canvas/AI photo where a hard line read as
     more intentional). See CLAUDE.md: this reverses an earlier explicit decision to
     keep this divider permanent/scroll-independent — don't reintroduce it without
     checking first, it's a real reversal, not an oversight. */
  transition: background var(--transition), backdrop-filter var(--transition);
}
.site-header.is-scrolled {
  background: color-mix(in srgb, var(--color-bg) 92%, transparent);
  backdrop-filter: saturate(140%) blur(6px);
}

.header-inner {
  height: 100%;
  /* 2026-10-12: was var(--container-max) (1280px) — on any viewport wider
     than that, .header-inner sat centered in a noticeably narrower box than
     .hero-inner (1760px, see .hero above), so the wordmark/nav cluster got
     pulled inward toward the viewport's center relative to the hero photo,
     which is anchored against the wider box. That's exactly what put the
     nav links (PROTOCOLES/RENDEZ-VOUS) over the photo's hair and made them
     unreadable — confirmed by the client's own screenshot, not guessed.
     Matching this cap to .hero-inner's 1760px (not removing it outright)
     fixes the root cause directly: the header and the hero composition now
     share the same coordinate system, so "bump the logo left, the nav items
     right" happens automatically at any viewport width up to 1760px, rather
     than needing a separate, hand-tuned margin/padding push that would only
     be correct at one specific width. The Figma mock itself supports this —
     its own header spans the full 1440px reference canvas with just edge
     padding (44px, close to this rule's own clamp(1.25rem, 3vw, 3rem) ≈
     43.2px at 1440px), not a separate, narrower sub-container. */
  max-width: 1760px;
  margin: 0 auto;
  padding: 0 clamp(1.25rem, 3vw, 3rem);
  display: flex;
  flex-wrap: nowrap;
  align-items: center;
  justify-content: space-between;
  gap: clamp(var(--space-sm), 2.5vw, var(--space-md));
}

.wordmark {
  display: block;
  min-width: 0;
  padding-bottom: 0.15em;
  border-bottom: 1px solid transparent;
}
/* Deliberately modest, conventional sizing (2026-08-10, third header
   pass) — the two prior passes both chased a literal Figma pixel value
   (56px wordmark, 32px nav) via clamp() maxes tuned to be *reached* at
   some reference width (first the mockup's own ~1728px, then this
   site's 1280px container). Both looked "typographically unbalanced" —
   oversized relative to how a persistent nav actually reads — and the
   1728px version also overflowed the real 1280px container outright.
   This pass stops chasing a specific px target altogether: sizes are
   small, restrained, and have enormous headroom against any container
   width (wordmark + nav + gaps + padding comes to roughly 500-550px at
   full size, well under half of even the narrowest supported width),
   so there's no overflow/collision risk left to reason about. If "match
   the Figma numbers exactly" is wanted again, that's a real reversal —
   ask first, since it's failed twice already for the reasons above. */
.wordmark-name {
  display: block;
  font-family: var(--font-primary);
  font-weight: 600;
  letter-spacing: -0.02em;
  font-size: clamp(1.2rem, 1.4vw, 1.65rem);
  line-height: 1.2;
  white-space: nowrap;
}

/* Icon-only hamburger (2026-08-16, replacing the plain "Menu" text button).
   Went through several earlier passes (acqua-tinted → colorless →
   charliedeets.com-derived light glass → solid dark fill with no hover → an
   outline/fill-on-hover match with `.btn-primary` → border removed → CSS-
   drawn bars tuned through six thickness values → replaced with inline SVG
   icons from Lucide, ISC license, https://lucide.dev, "menu"/"x", exact
   source in index.html, not a CDN/package so the site stays zero-third-
   party-network-requests — see git history for the full sequence).
   **No state-based visual change at all now** (2026-08-16, explicit client
   request, scoped to this button only, not `.btn-primary`): the
   background/text-color fill this button briefly shared with `.btn-primary`
   on hover/focus/open is gone — background stays `transparent` and the icon
   stays `var(--color-text)` in every state. The *only* thing that changes
   between idle and open is which of the two SVGs is visible
   (`.nav-toggle-svg--menu` ↔ `.nav-toggle-svg--close`, via the
   `[aria-expanded]` rules below) — not a color swap, an icon swap.
   `:focus-visible` keeps its outline regardless — that's a distinct
   keyboard-accessibility affordance (an outline ring), not the background/
   text "color swap" this request removed, and a real focus indicator is
   still required (see "Accessibility" below); don't remove that too without
   being asked. Icon size bumped 18px → 22px the same request, still well
   inside the 2.5rem (40px) button circle. `border-radius: 999px` and
   `border: none` (from the prior pass) are otherwise unchanged. */
.nav-toggle {
  display: none;
  align-items: center;
  justify-content: center;
  width: 2.5rem;
  height: 2.5rem;
  padding: 0;
  border: none;
  border-radius: 999px;
  background: transparent;
  color: var(--color-text);
  cursor: pointer;
}
.nav-toggle:focus-visible {
  outline: 2px solid var(--color-text);
  outline-offset: 2px;
}

.nav-toggle-svg {
  width: 22px;
  height: 22px;
}
.nav-toggle-svg--close { display: none; }
.nav-toggle[aria-expanded="true"] .nav-toggle-svg--menu { display: none; }
.nav-toggle[aria-expanded="true"] .nav-toggle-svg--close { display: block; }

.primary-nav {
  display: flex;
  flex-wrap: nowrap;
  align-items: center;
  gap: clamp(var(--space-sm), 2.2vw, var(--space-lg));
}

/* See the .wordmark-name comment above — same "stop chasing a literal
   px target" reasoning. Approach/Treatments/Booking only — Contact was
   dropped from the navbar per the Figma mockup; the FR/EN toggle and
   the mobile "Menu" button are real site features the mockup's crop
   doesn't show, kept as-is. */
.primary-nav ul {
  display: flex;
  flex-wrap: nowrap;
  gap: clamp(1rem, 1.4vw, 1.75rem);
}

.primary-nav a {
  display: inline-block;
  font-weight: 500;
  font-size: clamp(0.72rem, 0.85vw, 0.85rem);
  letter-spacing: 0.05em;
  text-transform: uppercase;
  white-space: nowrap;
  padding-bottom: 0.2rem;
  border-bottom: 1px solid transparent;
}

.lang-toggle {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  font-size: 0.75rem;
  letter-spacing: 0.03em;
}

.lang-btn {
  cursor: pointer;
  /* Horizontal padding widened 2026-08-16 (0.15rem → 0.3rem each side) —
     measured rendered width was ~20px, under WCAG 2.5.8's 24px touch
     target minimum; this is a real standalone button (unlike e.g.
     .service-toggle, whose actual click target is the much larger
     <summary> it sits inside), so the gap was real, not a measurement
     artifact. Height (~26.5px) already cleared the minimum. */
  padding: 0.2rem 0.3rem;
  color: var(--color-muted);
  border-bottom: 1px solid transparent;
}
.lang-btn[aria-pressed="true"] {
  color: var(--color-text);
  border-bottom-color: var(--color-text);
}

.lang-toggle-divider { color: var(--color-text); }

/* ---------- Text-link hover convention ----------
   An underline, applied identically everywhere a text link is hoverable
   — nav, wordmark, footer links — built with the exact same mechanism
   .lang-btn's *selected-language* indicator already used
   (`border-bottom`, not `text-decoration`), so hover and "currently
   selected" read as the same visual language instead of two different
   underline renderings sitting next to each other (a native
   `text-decoration-line` underline is noticeably thicker/differently
   offset than a 1px `border-bottom` at these font sizes/weights — that
   mismatch was the bug fixed here, confirmed via a client screenshot
   comparing "BOOKING" on hover against "EN" selected). `.wordmark` and
   `.site-footer a` gained a `padding-bottom`/`border-bottom: 1px solid
   transparent` (reserved, invisible space) to match; `.primary-nav a`
   and `.lang-btn` already had that pattern from before either hover
   pass and just needed their hover rule pointed at it again. A prior
   pass used a font-weight increase instead of any underline, at
   explicit client request; that was reverted the same day, also at
   explicit client request — don't flip either of these again without
   checking, both have been tried and reverted once already. */
.site-footer a {
  padding-bottom: 0.15em;
  border-bottom: 1px solid transparent;
}
.wordmark:hover,
.wordmark:focus-visible,
.primary-nav a:hover,
.primary-nav a:focus-visible,
.lang-btn:hover,
.site-footer a:hover {
  border-bottom-color: var(--color-text);
}

/* ---------- Hero ----------
   Ninth pass (2026-08-10). Pass 7 tied .hero-copy's *horizontal* position
   to .hero-photo's via --hero-photo-w/--hero-overlap (see below), but
   still positioned .hero-photo vertically centered — with .hero-copy
   separately bottom-anchored to .hero-inner. On a short viewport that's
   fine (both land in roughly the same vertical band), but on a tall one
   the vertically-centered photo can end well above where the bottom-
   anchored text sits, so the two stop visually overlapping even though
   the horizontal math still says they should — the overlap was real but
   not reliably *visible*. This pass fixes both what was asked for
   directly: the photo is now top-anchored (`top: 0`, touching
   .hero-inner's — and so .hero's — own top edge, per explicit client
   direction) and noticeably larger; and it fixes the resulting vertical
   side-effect by tying .hero-copy's `bottom` to --hero-photo-h the same
   way pass 7 tied `right` to --hero-photo-w, so the text sits a fixed
   distance above the photo's actual bottom edge regardless of .hero's
   total height, instead of independently anchored to .hero-inner's own
   bottom.
   --hero-overlap also went up (clamp max 2.5rem → 5rem) — pass 7's
   overlap was real (verified by hand, not a leftover bug) but subtle
   enough to read as no overlap at all, which is what "the hero text and
   image don't overlap — they should" was pointing at.
   Trade-off worth flagging: --hero-photo-w/--hero-photo-h are larger
   than pass 6-8's, which were sized specifically to stay at/under
   assets/hero.jpg's native 1024×705px so object-fit: cover never had to
   upscale it (an explicit earlier requirement: "make sure it doesn't
   lower its resolution"). At these bigger sizes the source does get
   upscaled somewhat, more so once a display's own pixel ratio is
   factored in — some softening on very high-density monitors is the
   real, unavoidable cost of "scaled up" against this particular
   (fairly modest-resolution) placeholder photo. A higher-resolution
   source image would remove this trade-off entirely; the fix isn't
   purely in CSS.
   Tenth pass, same day: --hero-photo-h dropped its `52rem` cap
   entirely (now plain `90vh`) — that fixed ceiling was what left a
   large, increasingly visible gap below the photo on tall viewports,
   since .hero itself (`min-height: 92vh`) kept growing with the
   viewport while the photo's height stopped at a fixed 832px. Tracking
   `vh` directly instead keeps the photo a near-constant proportion of
   .hero's own height on any screen. --hero-photo-w kept its rem cap
   (`40rem` → `48rem`, a modest bump) so the box doesn't get
   uncomfortably narrow-and-tall as height grows unchecked — width and
   height are deliberately capped differently now, not an oversight.
   No CTA button (removed per request — .hero-meta is gone).
   The statement itself sets font-family explicitly to --font-display
   (Playfair Display), the one deliberate, scoped exception to the
   single-typeface system — see the @font-face comment near the top of
   this file and CLAUDE.md for why. */
/* ---------- Hero (rebuilt 2026-10-12) ----------
   Replaced wholesale to match an authoritative Figma mock Amélie built
   herself (file "Amélie Blancher | Brand identity", node 69:2 for light
   mode, 77:177 for dark — see CLAUDE.md's Hero redesign history, pass 36,
   for the full reasoning and everything this supersedes). Every position
   below is a percentage read directly off that mock's 1440×982 reference
   frame — not an independently vw-tuned guess the way passes 6–35 built up
   (that whole --hero-overlap/--hero-copy-w/--hero-group-w/--hero-shift-left/
   --hero-photo-right-nudge token system is gone; percentages on an
   absolutely positioned child already resolve against .hero-inner's own
   box, so they scale correctly at any size with no separate vw coefficient
   needed per element). This also replaces the dramatically-overlapping,
   group-centered composition passes 7–35 iterated on with the mock's much
   more conservative layout — left-anchored text, right-anchored photo,
   negligible overlap. If a future request wants that earlier overlapping
   look back, that's a real reversal of this mock, worth confirming with
   Amélie first, not a tuning tweak to silently make. */
.hero {
  position: relative;
  min-height: var(--hero-photo-h);
  overflow: hidden;
  /* FIXED 2026-10-12 (same day as the rebuild, caught from client feedback —
     "the hero image position is not accurate"): width used to be DERIVED
     from a flat 100vh height, while `.hero-photo`'s `right` offset was a
     WIDTH-relative percentage — two different dimensions feeding into what's
     supposed to be one coherent box. That only produces the mock's intended
     655:982 box at the mock's own exact 1440:982 aspect ratio; at any other
     window shape (i.e. nearly every real device — 1512×982, 1728×1117,
     1440×900 all have a wider-than-1440:982 ratio) the derived width came
     out narrower than the mock's own 45.486% proportion, throwing off the
     whole composition (and, as a direct consequence, how it related to
     `.hero-copy` sitting next to it — not a separate bug in the text's own
     numbers, see `.hero-copy` below, just a downstream effect of this one).
     **Fixed by flipping which dimension is primary**: `--hero-photo-w` is
     now the width-relative value (vw, matching the mock's own 655/1440
     proportion, approximating `.hero-inner`'s actual width — exact up to the
     1760px cap, the same long-standing approximation every pre-10-12 pass
     used), and `--hero-photo-h` is derived FROM width via the mock's ratio,
     capped at 100vh so the box still can't exceed roughly one screen's
     height on tall/narrow windows. `.hero-photo`'s `right` (below) is vw too
     now, not %, specifically so it tracks the *same* approximation as
     `--hero-photo-w` — mixing a vw-based width with a %-based position would
     reopen the identical class of bug this fix is for, just between a
     different pair of properties. */
  --hero-photo-w: min(45.486vw, 58rem);
  /* min(100vh, width-derived-height) — mirrors the exact pattern every pass
     from 9 through 24 used (see the historical block comment above this
     rule), just with the mock's 655:982 ratio instead of the old photos'
     1144:1724, and 100vh instead of 90vh (see pass 36's own note, still
     valid: the mock shows the photo flush to the canvas's top and bottom
     with no margin). On any viewport close to or wider than 1440:982 (i.e.
     most real devices), 100vh is what actually binds; the width-derived term
     is the safety net for tall/narrow windows, same role --hero-photo-h's
     old ratio term always played before `object-fit: cover` briefly removed
     it in pass 35. */
  --hero-photo-h: min(100vh, calc(var(--hero-photo-w) * 982 / 655));
  /* New: a hairline border under the whole hero section, matching the mock
     exactly (#E5E5DF light / #2A2824 dark — both already --color-border in
     their respective schemes, no new token needed). This is a real,
     deliberate reversal of the 2026-08-10 decision to remove every
     inter-section border on the page ("section boundaries are whitespace-
     only end to end," see "Content status"/Protocols section in CLAUDE.md)
     — confirmed via this Figma mock, not something to silently re-remove if
     a future "clean up borders" request comes in without checking this one
     specifically first. */
  border-bottom: 1px solid var(--color-border);
}

.hero-inner {
  position: absolute;
  inset: 0;
  /* Kept from the pre-10-12 version — a fixed-width editorial composition
     still benefits from a cap wider than the site's normal 1280px reading
     container on very wide monitors, same reasoning as before; the mock
     itself doesn't speak to viewports past 1440px, so this is a carried-
     over safety measure, not something the mock contradicts. */
  max-width: 1760px;
  margin: 0 auto;
}

.hero-copy {
  position: absolute;
  z-index: 1;
  /* left/width: percentages of the mock's 1440-wide frame (174/1440,
     463/1440) — width-relative, tracking `.hero-inner`'s actual box exactly
     at any size, including past the 1760px cap. Unchanged by the vertical
     fix below.
     2026-10-12: re-anchored BOTTOM, not top — explicit client correction
     ("it should be 141px from the hero bottom divider"), after the
     top-anchored `top: 44.094%` version (same day, earlier) didn't land in
     the right spot. 141px is the mock's own number: its text box runs
     `top:433` to `433+408=841` on the 982px-tall reference frame, and
     `982-841=141` — the gap from the text box's bottom to the hero's own
     bottom edge (now a real line, see `.hero`'s `border-bottom` above).
     Deliberately a fixed px, not a percentage/vw scaled value — matching
     the client's literal instruction, not reinterpreted as a proportion.
     This also fully sidesteps the `line-height` leading discrepancy noted
     in the previous version of this comment (Figma's `line-height:0`-on-
     container-with-`1.3`-on-spans trick keeps its first line flush against
     `top`, which this codebase's plain `line-height:1.3` on `.hero-title`
     doesn't replicate) — anchoring from the BOTTOM instead means that
     mismatch no longer matters, since nothing here reads `top` any more.
     Height stays unset/content-driven, same convention as the rest of the
     site's text blocks — the mock's own 408px box height is this specific
     FR sentence's actual wrapped extent, not a hard constraint on future
     copy or the EN translation. */
  left: 12.083%;
  width: 32.153%;
  bottom: 141px;
}

.hero-title {
  font-family: var(--font-display);
  font-weight: 500;
  letter-spacing: 0;
  /* 52px at the mock's 1440px reference width = 3.611vw. rem floor/ceiling
     are safety bounds for very narrow/wide desktop windows, not
     independently tuned sizes — the mock only speaks to the 1440px case. */
  font-size: clamp(1.75rem, 3.611vw, 3.6rem);
  line-height: 1.3;
  color: var(--color-text);
  margin: 0;
}
.hero-title em { font-style: italic; }

.hero-photo {
  position: absolute;
  z-index: 0;
  top: 0;
  /* 274/1440 — the mock's right margin for the photo. vw, not % (fixed
     2026-10-12, see --hero-photo-w's own note on `.hero`) — has to track
     the same vw-based approximation --hero-photo-w uses, or the two drift
     apart at any aspect ratio other than the mock's own 1440:982. */
  right: 19.028vw;
  width: var(--hero-photo-w);
  height: var(--hero-photo-h);
}
.hero-photo-img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
  /* contain, not cover (reverting pass 35's crop-to-fit, now that the mock
     gives an exact box size instead of "as big as possible") —
     assets/AB hands alpha.png is a true alpha cutout, not pass 35's flat
     studio backdrop, and its canvas ratio already matches
     --hero-photo-w/-h exactly (both derived from the same 655/982 figure
     from the mock), so contain and cover render identically here regardless
     — contain is simply the more correct choice semantically (it never
     crops, even if the ratio ever drifts slightly), and there's no longer a
     bottom-biased object-position to carry over either, since nothing is
     being cropped. The alpha channel also sidesteps the whole backdrop-
     color-seam problem passes 24/25 fought hard to fix on the old opaque
     photos — transparent pixels just show .hero's own --color-bg straight
     through, correct in both schemes with zero extra work. */
  object-fit: contain;
  object-position: center center;
}

/* ---------- Philosophy ----------
   Text-only, no image/grid/borders (2026-08-10 — the portrait .tk-frame
   and the box-grid treatment it sat in were both removed at the client's
   request). .philosophy-copy is pushed into the container's right portion
   via margin-left: auto while staying left-aligned internally, rather
   than a real grid with an empty left cell — same visual result, one
   less div. If a real portrait photo is wanted here later, this is a
   genuine reversal of "remove the image" and worth raising again rather
   than silently re-adding a .tk-frame. */
/* Top/bottom padding are intentionally asymmetric: the gap before "Une
   sélection de rituels." below is this section's own padding-bottom PLUS
   the .protocols section's matching padding-top stacking together (no
   border between them), so it reads roughly twice as tall as a single
   section's padding.
   The hero has no padding of its own, so padding-top here was the only
   contributor to the hero→Approche gap and it read visually thinner than
   the Approche→Protocoles gap below it — doubled to balance the two. */
.philosophy {
  padding-top: clamp(calc(var(--space-lg) * 2), 20vw, calc(var(--space-xl) * 2));
  padding-bottom: clamp(var(--space-lg), 10vw, var(--space-xl));
}

.philosophy-grid {
  max-width: var(--container-max);
  margin: 0 auto;
  padding: 0 clamp(var(--space-md), 5vw, var(--space-lg));
}

.philosophy-copy {
  margin-left: auto;
  max-width: 34rem;
  text-align: left;
}

/* .section-title resets to margin: 0 sitewide; this section needs its own
   gap now that .prose follows it directly (no pull-quote in between any
   more — see CLAUDE.md). */
.philosophy-copy .section-title {
  margin-bottom: clamp(var(--space-md), 4vw, var(--space-lg));
}

/* Multi-column justified body copy — the editorial "story" grid from the
   references. `columns` auto-collapses to 1 when the container is narrower
   than the given column width, so this degrades gracefully on its own; the
   720px breakpoint below just makes that explicit. */
.prose {
  columns: 2 16rem;
  column-gap: var(--space-lg);
  max-width: 56rem;
}
.prose p {
  color: var(--color-muted);
  font-weight: 400;
  text-align: justify;
  hyphens: auto;
  break-inside: avoid;
  margin: 0 0 var(--space-sm);
}
.prose strong {
  color: var(--color-text);
  font-weight: 700;
}

/* ---------- Protocols / Services ---------- */
.protocols {
  padding: clamp(var(--space-lg), 10vw, var(--space-xl)) 0;
  background: var(--color-bg);
}

.section-head { max-width: 60ch; margin-bottom: clamp(var(--space-lg), 6vw, calc(var(--space-lg) + var(--space-md))); }

.service-group + .service-group { margin-top: clamp(var(--space-lg), 8vw, var(--space-xl)); }

/* Larger/darker 2026-08-19 at Amélie's request ("Visage"/"Corps" were hard
   to see) — up from 0.78rem/--color-muted, same weight/tracking/uppercase
   otherwise. */
.service-group-title {
  font-family: var(--font-primary);
  font-weight: 500;
  font-size: 1rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--color-text);
  padding-bottom: var(--space-sm);
  margin-bottom: var(--space-xs);
  border-bottom: 1px solid var(--color-border);
}

/* .service-item is now a plain <li> wrapping a <details> — the two-column
   (number | body) grid that used to live on .service-item itself moved to
   .service-summary, since that's the row that's always visible. Expanded
   content (.service-detail) is a separate full-width block below it, not
   part of that grid — see the "Protocols" section notes in CLAUDE.md for
   why this became an accordion (2026-08-16, real benefits/experience/
   contraindication copy from Amélie replaced the old one-line synthesis). */
.service-item {
  padding: clamp(var(--space-md), 3.5vw, calc(var(--space-md) + var(--space-sm) / 2)) 0;
  border-bottom: 1px solid var(--color-border);
}

.service-item:last-child { border-bottom: none; }

.service-item details { display: block; }

/* Plain <li>, not a .service-item — a sub-group label inline within the
   Visage <ol> (between the Personnalisé/Intra-Buccal/Détox trio and the
   3 short 30-min protocols that follow), reusing .service-group-restriction's
   small-caps muted styling rather than introducing a new eyebrow pattern.
   No border/grid of its own; sits flush in the list's padding rhythm. */
.service-divider { padding: var(--space-xs) 0 var(--space-sm); }

.service-summary {
  display: grid;
  grid-template-columns: 1fr;
  cursor: pointer;
  list-style: none;
}
.service-summary::-webkit-details-marker { display: none; }

.service-heading {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-md);
  flex-wrap: wrap;
}

.service-name {
  font-size: clamp(1.25rem, 2.2vw, 1.6rem);
}

.service-price {
  font-family: var(--font-primary);
  font-weight: 500;
  font-size: 0.95rem;
  letter-spacing: 0.03em;
  color: var(--color-text);
  white-space: nowrap;
}

.service-desc {
  color: var(--color-muted);
  max-width: 58ch;
  margin-top: var(--space-xs);
}

.service-meta {
  display: flex;
  align-items: center;
  gap: var(--space-md);
  margin-top: var(--space-sm);
  font-size: 0.72rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--color-muted);
}

.service-cure {
  padding-left: var(--space-md);
  border-left: 1px solid var(--color-border);
}

/* Toggle affordance for the accordion — text label plus a +/− that flips
   on [open], pushed to the end of .service-meta rather than styled as a
   separate button, so it reads as part of the same small-caps meta row
   as duration/cure. */
.service-toggle {
  margin-left: auto;
  transition: color var(--transition);
}
.service-toggle::after {
  content: " +";
}
details[open] .service-toggle::after {
  content: " \2212";
}
.service-summary:hover .service-toggle,
.service-summary:focus-visible .service-toggle {
  color: var(--color-text);
}

/* Expanded content: benefits/experience/steps/contraindications. Not
   aligned to .service-body's left edge (the number column above it) —
   full width reads better for justified-feeling body copy at this
   density, and avoids a fragile hand-tuned offset. */
.service-detail {
  padding-top: var(--space-lg);
  color: var(--color-muted);
}

.detail-heading {
  font-family: var(--font-primary);
  font-weight: 700;
  font-size: 0.72rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--color-text);
  margin: var(--space-lg) 0 var(--space-sm);
}
.detail-heading:first-child { margin-top: 0; }

.detail-text { max-width: 65ch; }

.detail-list {
  max-width: 65ch;
}
.detail-list li {
  position: relative;
  padding-left: 1.2em;
  margin-bottom: var(--space-xs);
}
.detail-list li:last-child { margin-bottom: 0; }
.detail-list li::before {
  content: "\30FB";
  position: absolute;
  left: 0;
  color: var(--color-muted);
}
.detail-list strong { color: var(--color-text); font-weight: 700; }

.detail-steps {
  counter-reset: step;
}
.detail-steps li::before {
  content: counter(step) ".";
  position: absolute;
  left: 0;
  color: var(--color-muted);
}
.detail-steps li {
  counter-increment: step;
}

.service-option {
  padding-bottom: var(--space-md);
  margin-bottom: var(--space-sm);
  border-bottom: 1px solid var(--color-border);
}

/* Corps/Body subsection intro — the female-only note plus Amélie's
   Renata França training quote, sitting between the group title and the
   first protocol. Section-level rather than repeated per card. */
.service-group-note { margin-bottom: clamp(var(--space-md), 4vw, var(--space-lg)); }

.service-group-restriction {
  font-size: 0.72rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  font-style: italic;
  color: var(--color-muted);
}

/* "Protocoles courts" label only, not the Corps group's "réservés aux
   femmes" restriction (same .service-group-restriction class, different
   instance) — larger/darker 2026-08-19 alongside .service-group-title,
   same request. */
.service-divider .service-group-restriction {
  font-size: 0.92rem;
  color: var(--color-text);
}

.service-group-quote {
  margin-top: var(--space-sm);
  max-width: 62ch;
  color: var(--color-muted);
}

/* ---------- Booking ---------- */
/* padding-top stays the single-section token — it stacks with .protocols's
   own padding-bottom above it (borderless transition) to read at the same
   weight as the rest of the page's inter-section gaps. padding-bottom is
   doubled: unlike the other transitions, Booking→Footer is the one spot
   where the next element (.site-footer) has its own border-top instead of
   being a plain whitespace continuation, so Booking's own bottom padding
   was the *entire* visible gap before that border — reading noticeably
   thinner than the ~2x gaps everywhere else on the page. Doubled here,
   same fix as .philosophy's padding-top, to match. */
.booking {
  padding-top: clamp(var(--space-lg), 10vw, var(--space-xl));
  padding-bottom: clamp(calc(var(--space-lg) * 2), 20vw, calc(var(--space-xl) * 2));
}

.booking-grid {
  display: grid;
  grid-template-columns: 0.85fr 1.15fr;
  gap: clamp(var(--space-lg), 6vw, var(--space-xl));
  align-items: start;
}

/* position: sticky (pinning this column near the top while the taller form
   scrolls past on the right) removed 2026-10-12, explicit client request — it
   was pinning close enough to the fixed header to read as "stuck to the
   navbar while scrolling," not as a deliberate sticky-sidebar pattern. No
   baseline rule needed any more now that it's just normal in-flow content;
   `.booking-grid`'s own `align-items: start` already keeps it from stretching
   to match the taller form column's height. */

.booking-form {
  display: flex;
  flex-direction: column;
  gap: var(--space-md);
  background: var(--color-bg);
}

.form-row { display: flex; flex-direction: column; gap: var(--space-xs); }

.form-row-split {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--space-md);
}
.form-field { display: flex; flex-direction: column; gap: var(--space-xs); }

.booking-form label {
  font-size: 0.7rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--color-muted);
}

.booking-form input,
.booking-form select,
.booking-form textarea {
  border: 0;
  border-bottom: 1px solid var(--color-border);
  padding: 0.6em 0.1em;
  background: transparent;
  transition: border-color var(--transition);
}

.booking-form select { cursor: pointer; }

.booking-form input:focus,
.booking-form select:focus,
.booking-form textarea:focus {
  outline: none;
  border-bottom-color: var(--color-text);
}

.booking-form textarea { resize: vertical; min-height: 6rem; }

.form-note {
  font-size: 0.85rem;
  color: var(--color-text);
  border-top: 1px solid var(--color-border);
  padding-top: var(--space-sm);
}

/* ---------- Footer ----------
   Light, hairline-bordered grid instead of a solid inverted band — the
   reference's boxed-cell footer treatment: columns divided by border-left,
   not gap, each with its own inset padding. */
.site-footer {
  background: var(--color-bg);
  color: var(--color-text);
  border-top: 1px solid var(--color-border);
  padding: clamp(calc(var(--space-lg) - var(--space-sm)), 7vw, calc(var(--space-lg) + var(--space-md))) 0 var(--space-md);
}

.footer-grid {
  display: grid;
  grid-template-columns: 1.3fr 1fr 1fr;
  border-bottom: 1px solid var(--color-border);
}

.footer-block {
  padding: 0 calc(var(--space-md) + var(--space-xs)) calc(var(--space-md) + var(--space-xs));
  border-left: 1px solid var(--color-border);
}
.footer-block:first-child { border-left: 0; padding-left: 0; }

.footer-wordmark {
  font-family: var(--font-primary);
  font-weight: 800;
  letter-spacing: -0.03em;
  font-size: 1.1rem;
  color: var(--color-text);
}

.footer-tagline {
  margin-top: var(--space-xs);
  font-size: 0.85rem;
  color: var(--color-muted);
}

.footer-heading {
  font-size: 0.7rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--color-muted);
  margin-bottom: calc(var(--space-sm) - var(--space-xs) / 2);
}

.site-footer address,
.site-footer p { font-style: normal; font-size: 0.9rem; color: var(--color-text); }

.footer-bottom { padding-top: var(--space-md); }
.legal { font-size: 0.75rem; color: var(--color-muted); }

/* ---------- Responsive ---------- */
@media (max-width: 900px) {
  .booking-grid { grid-template-columns: 1fr; }
  .booking-intro { position: static; max-width: 26rem; }

  /* Philosophy: right-aligned column loses its reference container width
     once the page is this narrow — let it take the full text measure. */
  .philosophy-copy { margin-left: 0; max-width: none; }

  /* Footer: single column once there's no room for 4 across; dividers
     switch from border-left (side by side) to border-top (stacked). */
  .footer-grid { grid-template-columns: 1fr; }
  .footer-block {
    border-left: 0;
    padding-left: 0;
    border-top: 1px solid var(--color-border);
    padding-top: calc(var(--space-md) + var(--space-xs));
  }
  .footer-block:first-child { border-top: 0; padding-top: 0; }

  /* Hero: below the floating-photo composition's readable width, drop the
     absolutely positioned card and floating photo back to normal flow —
     stacked text above photo, full width. .hero-inner becomes display:
     contents — it generates no box of its own at this breakpoint, so
     .hero-copy/.hero-photo are effectively direct children of .hero
     again and its order/flex-column rules below apply to them
     unchanged, instead of needing every rule duplicated one level up. */
  .hero { display: flex; flex-direction: column; min-height: auto; }
  .hero-inner { display: contents; }
  .hero-copy {
    position: static;
    width: auto;
    /* .hero still starts at the very top of the page underneath the fixed
       .site-header (see the block comment above this breakpoint) — on
       desktop that's fine because .hero-copy is absolutely positioned low
       enough to clear it, but here it's the first static-flow child of
       .hero, so without accounting for --header-h the fixed header sits
       directly on top of the start of the text instead of clearing it. */
    padding: calc(var(--header-h) + clamp(var(--space-lg), 8vw, var(--space-xl))) clamp(var(--space-md), 5vw, var(--space-lg)) clamp(var(--space-lg), 8vw, var(--space-xl));
    background: var(--color-bg);
    order: 1;
  }
  .hero-title {
    margin: 0;
    /* .hero-copy is full-width flow content here, not squeezed into the
       desktop column's narrow 32vw/38rem cap, so it can run noticeably
       larger than the desktop clamp's 1.5rem floor without wrapping
       awkwardly. */
    font-size: clamp(2rem, 8vw, 2.75rem);
  }
  .hero-photo {
    /* relative, not static (2026-08-17) — .hero-photo used to *be* the <img>
       itself, and static was enough to drop it back into normal document flow.
       Now it's a wrapper around an absolutely-positioned child (.hero-photo-img,
       inset:0 — formerly also a canvas sibling, removed 2026-10-11, but the
       wrapping requirement is unchanged), so it still needs to be a positioned
       element itself — otherwise that child skips past it and positions against
       .hero instead, covering the entire hero section rather than staying
       confined to this box. relative keeps it in normal flow (same as static
       would) while still being a valid containing block. */
    position: relative;
    inset: auto;
    transform: none;
    width: 100%;
    height: auto;
    /* 2026-08-17: was 4/3 (landscape) — a stale leftover from the original
       hero.jpg placeholder era (pass 1-11), never revisited once the hero
       became a portrait AI photo (1144x1724, same ratio all three cycling
       photos share) composited with object-fit: contain (pass 16+). A 4/3
       box holding a ~0.66-ratio portrait under `contain` left large empty
       letterboxed margins on tablet/mobile widths — harmless in itself
       (the margin is transparent, same --color-bg as the page), but it
       made every photo's own baked-in dark studio backdrop (a slightly
       different, warmer black than --color-bg) read as a visibly distinct
       rectangle floating in the middle of the section, most obvious in
       dark mode against the near-black page background ("the hero on
       tablets breaks" / "the bg color and the hero images have a weird
       different black"). Matching the box's ratio to the photo's own
       (1144/1724) removes the letterboxing entirely at this breakpoint —
       verified via headless Chrome at 820x1180 in dark mode, comparing
       before/after. Desktop was never affected (its own aspect ratio,
       driven by --hero-photo-w/-h, already tracks close to the photo's
       real proportions), which is why this only showed up on tablet/mobile.
       2026-10-11: updated to the hands photo's own ratio (1168x1750) once it
       replaced the AI woman/man photos this was tuned to — same fix, same
       reasoning, new source dimensions. Still correct 2026-10-12 when the
       photo itself changed again (assets/AB hands.jpg -> assets/AB hands
       alpha.png, see CLAUDE.md's Hero section pass 36) — both share the same
       1168x1750 target dimensions, so this ratio didn't need to move. */
    aspect-ratio: 1168 / 1750;
    order: 2;
  }
  /* The explicit object-fit/object-position override that used to live here
     (added 2026-10-11 when the desktop base rule briefly used object-fit:
     cover, see pass 35) was removed 2026-10-12 — pass 36 reverted the desktop
     rule back to contain/center center (see .hero-photo-img above `.hero`'s
     own rules), so this breakpoint's override became identical to the base
     rule and was dead weight. If desktop ever goes back to cover, re-add an
     override here rather than letting mobile silently inherit cropping. */
}

@media (max-width: 720px) {
  .nav-toggle { display: inline-flex; }

  .primary-nav {
    position: absolute;
    top: 100%;
    left: 0;
    right: 0;
    background: var(--color-bg);
    border-bottom: 1px solid var(--color-border);
    flex-direction: column;
    align-items: flex-start;
    gap: var(--space-md);
    padding: var(--space-md) clamp(var(--space-md), 5vw, var(--space-lg)) var(--space-md);
    display: none;
  }
  .primary-nav.is-open { display: flex; }
  .primary-nav ul { flex-direction: column; gap: var(--space-sm); }
  .primary-nav a { font-size: 0.85rem; }

  .prose { columns: 1; }

  .form-row-split { grid-template-columns: 1fr; }
}
