/* ==========================================================================
   Flora Psychotherapy — Layout, components, motion
   Built on tokens.css. No value here that isn't a token or derived from one.
   ========================================================================== */

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

/* The hidden attribute has to beat component display rules such as .btn's
   inline-flex, otherwise JS-hidden controls stay on screen. */
[hidden] { display: none !important; }

html {
  -webkit-text-size-adjust: 100%;
  /* Interior pages have no hero, so they sit at the finished value. */
  --hp: 1;
  background: var(--surface);
}
html.home { --hp: 0; }

body {
  margin: 0;
  background: transparent;
  color: var(--ink);
  font-family: var(--font-body);
  font-size: var(--fs-body);
  line-height: var(--lh-body);
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}

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

a { color: var(--brand); text-decoration-thickness: 1px; text-underline-offset: 3px; }
a:hover { text-decoration-color: var(--sage-text); }

::selection { background: var(--sage-band); color: var(--ink); }

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

.skip-link {
  position: absolute; left: var(--sp-2); top: -100px; z-index: 200;
  background: var(--brand); color: #fff; padding: 12px 20px;
  border-radius: var(--radius-full); font-weight: 600; font-size: var(--fs-small);
  transition: top var(--dur-fast) var(--ease);
}
.skip-link:focus { top: var(--sp-2); }

/* Available to screen readers and to the document outline, but not painted. Used for
   headings that a sighted reader gets from context but an assistive-technology user
   would otherwise miss, e.g. the post grid on the blog index. */
.visually-hidden {
  position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0;
  overflow: hidden; clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap;
  border: 0;
}

/* ---------- Typography ---------- */
h1, h2, h3 { font-family: var(--font-display); font-weight: 400; margin: 0; }
h4, h5, h6 { font-family: var(--font-body); font-weight: 600; margin: 0; }
p { margin: 0 0 1em; }
p:last-child { margin-bottom: 0; }

.display, .h1, .h2, .h3 { font-family: var(--font-display); font-weight: 400; }
.display { font-size: var(--fs-display); line-height: var(--lh-display); letter-spacing: var(--ls-display); }
.h1      { font-size: var(--fs-h1);      line-height: var(--lh-h1);      letter-spacing: var(--ls-display); }
.h2      { font-size: var(--fs-h2);      line-height: var(--lh-h2);      letter-spacing: var(--ls-h2); }
.h3      { font-size: var(--fs-h3);      line-height: var(--lh-h3);      letter-spacing: var(--ls-h3); font-weight: 500; }
.h4      { font-family: var(--font-body); font-weight: 600;
           font-size: var(--fs-h4);      line-height: var(--lh-h4); }

.display em, .h1 em, .h2 em, .h3 em { font-style: italic; }

.lede    { font-size: var(--fs-body-lg); line-height: var(--lh-body); max-width: var(--measure); }
.prose   { max-width: var(--measure); }
.prose p + p { margin-top: 1.1em; }
.small   { font-size: var(--fs-small); }
.muted   { color: var(--ink-muted); }

.eyebrow {
  font-size: var(--fs-caption); font-weight: 600; letter-spacing: var(--ls-caption);
  text-transform: uppercase; color: var(--sage-text); margin: 0 0 var(--sp-2);
}

/* ---------- Layout ---------- */
.container { width: 100%; max-width: var(--container); margin-inline: auto; padding-inline: var(--sp-3); }
.section   { padding-block: var(--sp-6); }
.section--tight { padding-block: var(--sp-4); }

/* Section blend. Every change of background colour is ramped across a strip rather
   than landing on a hard edge, the way newgenre.studio carries its hero gradient down
   into the section beneath it. Interpolating in oklab avoids the muddy grey midpoint
   a plain sRGB ramp produces between cream and sage. */
.blend {
  height: clamp(48px, 5vw, 96px);
  background: linear-gradient(to bottom, var(--from), var(--to));
  background: linear-gradient(to bottom in oklab, var(--from), var(--to));
}

/* Full-bleed band. Cuts the page rather than sitting on it. Signature Move 1. */
.band { padding-block: var(--sp-8); }
/* Quiet statement band: the page colour, given room by its padding alone. */
.band--quiet { background: transparent; padding-block: var(--sp-12); }
.band--quiet.pinned { padding-block: 0; }
@media (max-width: 720px) { .band--quiet { padding-block: var(--sp-8); } }
.band--brand { background: var(--brand); color: var(--surface); }
.band--brand .h2, .band--brand a:not(.btn) { color: var(--surface); }

/* Offset split. The image side flips between sections so the rhythm never repeats. */
.split { display: grid; gap: var(--sp-8); align-items: center; grid-template-columns: 1fr; }
@media (min-width: 900px) {
  .split { grid-template-columns: 1.05fr 0.95fr; gap: var(--sp-12); }
  .split--flip > *:first-child { order: 2; }
}

/* ---------- Blossom motif ---------- */
.blossom { display: block; color: var(--sage-deco); }
.blossom-divider { display: flex; align-items: center; gap: var(--sp-2); }
.blossom-divider::before,
.blossom-divider::after { content: ''; flex: 1; height: 1px; background: var(--divider); }

/* ---------- Buttons ---------- */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  font-family: var(--font-body); font-weight: 600; font-size: 1rem;
  padding: 16px 32px; border-radius: var(--radius-full); border: none;
  cursor: pointer; text-decoration: none; min-height: 52px;
  transition: background-color var(--dur-fast) var(--ease),
              color var(--dur-fast) var(--ease),
              transform var(--dur-fast) var(--ease),
              box-shadow var(--dur-fast) var(--ease);
}
.btn--primary { background: var(--brand); color: #fff; }
.btn--primary:hover { background: var(--brand-hover); transform: translateY(-1px); box-shadow: var(--shadow-sm); }
.btn--secondary { background: transparent; color: var(--brand); border: 1.5px solid var(--brand); }
.btn--secondary:hover { background: var(--brand); color: #fff; transform: translateY(-1px); }
.btn--onbrand { background: var(--surface); color: var(--brand); }
.btn--onbrand:hover { background: #fff; transform: translateY(-1px); }
.btn[disabled] { background: var(--divider); color: var(--ink-muted); cursor: not-allowed; }

/* Ghost link whose underline grows from nothing on hover. */
.link-grow { position: relative; text-decoration: none; font-weight: 500; color: var(--brand); }
.link-grow::after {
  content: ''; position: absolute; left: 0; bottom: -3px; height: 1.5px; width: 0;
  background: currentColor; transition: width var(--dur-mid) var(--ease);
}
.link-grow:hover::after { width: 100%; }

/* ---------- Nav ---------- */
/* Three columns: links, centred logo, actions. The 1fr on either side of an auto
   centre keeps the logo optically centred in the viewport regardless of how wide the
   link list or the button happens to be.

   The bar no longer persists down the page. It is still position:fixed, but main.js
   drives --nav-y and --nav-o so it travels up and out as you scroll: at exactly
   scroll speed on interior pages, which looks identical to it simply scrolling away,
   and on a longer fade across the home page's hero. Keeping it fixed rather than
   putting it in flow means every existing --nav-h layout offset stays correct. */
.nav {
  position: fixed; inset-inline: 0; top: 0; z-index: 100;
  display: grid; grid-template-columns: 1fr auto 1fr; align-items: center;
  gap: var(--sp-3);
  padding: var(--sp-2) var(--sp-3); min-height: var(--nav-h);
  /* Always opaque. At the top of a page the cream matches the page behind it, so the
     bar is invisible until it needs not to be. */
  background: var(--surface);
  transform: translateY(calc(-1 * var(--nav-y, 0px)));
  opacity: var(--nav-o, 1);
}
.nav__links   { grid-column: 1; justify-self: start; }
.nav__logo    { grid-column: 2; justify-self: center; }
.nav__actions { grid-column: 3; justify-self: end;
                display: flex; align-items: center; gap: var(--sp-2); }

/* Below the desktop breakpoint the link list is hidden, so a centred logo would sit
   in an empty room with the menu button alone on the right. Back to the familiar
   logo-left, button-right bar. */
@media (max-width: 1119px) {
  .nav { display: flex; justify-content: space-between; }
  .nav__cta { display: none; }
}

/* --nav-h has to track the logo, and the logo only shrinks to 140px at 720px, not at
   the 1119px layout breakpoint. Tying the height to the wrong one left the bar 130px
   tall while everything offsetting against it assumed 89px, so on a tablet or a phone
   in landscape the menu panel opened 41px underneath the bar. */
@media (max-width: 720px) {
  :root { --nav-h: 89px; }
}

main { position: relative; }

.nav__logo { display: flex; align-items: center; flex-shrink: 0; }
.nav__logo img { width: 242px; height: auto; }

/* Two rows rather than one. Five items in a single line crowd the left third and
   push against the centred logo; wrapping them into a short block gives the logo its
   room back. The max-width is what forces the break, so the row split stays put
   instead of depending on the viewport. */
.nav__links { display: none; align-items: center; gap: 4px var(--sp-3);
              flex-wrap: wrap; }
/* Forces the wrap at the point chrome.py places it. No max-width is involved, so the
   break stays where it was put even if a label is renamed or the font metrics shift. */
.nav__break { flex-basis: 100%; height: 0; margin: 0; }
@media (min-width: 1120px) { .nav__links { display: flex; } }
.nav__link {
  font-size: 0.8125rem; font-weight: 500; color: var(--ink); text-decoration: none;
  padding-block: 4px; border-bottom: 2px solid transparent;
  transition: border-color var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease);
}
.nav__link:hover { border-bottom-color: var(--brand); }
.nav__link[aria-current] { border-bottom-color: var(--brand); }

/* ---------- Dropdowns ----------
   Opens on hover and on :focus-within, so one set of markup serves a mouse and a
   keyboard: tabbing to the parent link reveals the submenu, which makes its links
   focusable on the next Tab. The parent stays a real link to the section index, so
   a tap on a touch device still goes somewhere sensible. */
.nav__item { position: relative; display: flex; align-items: center; }
.nav__link--parent { display: inline-flex; align-items: center; gap: 5px; }
.nav__chev { transition: transform var(--dur-fast) var(--ease); opacity: .65; }
.nav__item:hover .nav__chev,
.nav__item:focus-within .nav__chev { transform: rotate(180deg); opacity: 1; }

.nav__sub {
  position: absolute; top: 100%; left: -14px; z-index: 5;
  min-width: 232px; margin: 0; padding: 8px; list-style: none;
  background: var(--surface); border-radius: var(--radius-md);
  box-shadow: var(--shadow-md);
  opacity: 0; visibility: hidden; transform: translateY(-6px);
  /* Closes as soon as the pointer leaves. The ::before bridge below already covers
     the gap between trigger and panel, so the close needs only a short grace period
     rather than a long one. */
  transition: opacity 120ms var(--ease),
              transform 120ms var(--ease),
              visibility 0s linear 120ms;
}
/* An invisible bridge across the gap between the trigger and the panel. */
.nav__sub::before { content: ''; position: absolute; inset: -14px 0 100% 0; }

.nav__item:hover .nav__sub,
.nav__item:focus-within .nav__sub {
  opacity: 1; visibility: visible; transform: translateY(0);
  transition-delay: 0s;
}

.nav__sub a {
  display: block; padding: 9px 14px; border-radius: var(--radius-sm);
  font-size: 0.9375rem; font-weight: 500; color: var(--ink); text-decoration: none;
  transition: background-color var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease);
}
.nav__sub a:hover, .nav__sub a:focus-visible { background: var(--raised); color: var(--brand); }
.nav__sub a[aria-current="page"] { color: var(--brand); background: var(--raised); }

/* Right-hand menus flip so they never run off the viewport edge. */
.nav__item:nth-last-of-type(-n+2) .nav__sub { left: auto; right: -14px; }

@media (prefers-reduced-motion: reduce) {
  .nav__sub { transition: none; }
  .nav__chev { transition: none; }
}

.nav__cta { padding: 11px 22px; font-size: 0.9375rem; min-height: 44px; }

.nav__toggle {
  display: inline-flex; flex-direction: column; justify-content: center; gap: 5px;
  width: 44px; height: 44px; padding: 10px; background: none; border: none; cursor: pointer;
}
@media (min-width: 1120px) { .nav__toggle { display: none; } }
.nav__toggle span {
  display: block; height: 1.5px; width: 100%; background: var(--ink); border-radius: 2px;
  transition: transform var(--dur-mid) var(--ease), opacity var(--dur-fast) var(--ease);
}
.nav__toggle[aria-expanded="true"] span:nth-child(1) { transform: translateY(6.5px) rotate(45deg); }
.nav__toggle[aria-expanded="true"] span:nth-child(2) { opacity: 0; }
.nav__toggle[aria-expanded="true"] span:nth-child(3) { transform: translateY(-6.5px) rotate(-45deg); }

.nav__panel {
  position: fixed; inset: var(--nav-h) 0 auto 0; z-index: 98;
  background: var(--surface); box-shadow: var(--shadow-md);
  padding: var(--sp-2) var(--sp-3) var(--sp-4);
  /* Momentum scrolling on iOS, and a scroll that stops at the panel's own edges
     instead of dragging the page behind it. */
  -webkit-overflow-scrolling: touch; overscroll-behavior: contain;
  display: flex; flex-direction: column; gap: var(--sp-1);
  max-height: calc(100dvh - var(--nav-h)); overflow-y: auto;
  transform: translateY(-8px); opacity: 0; visibility: hidden;
  transition: opacity var(--dur-mid) var(--ease), transform var(--dur-mid) var(--ease), visibility var(--dur-mid);
}
.nav__panel.is-open { transform: none; opacity: 1; visibility: visible; }
/* Sizes here are deliberately a quarter down from the body scale: this is a list of
   destinations to scan, not prose to read, and the panel has to fit on a phone without
   the whole nav becoming a scroll of its own. */
.nav__panel a:not(.btn) { font-size: 0.9375rem; font-weight: 500; color: var(--ink);
                text-decoration: none; padding: 10px 0; }

/* One row per section: the label navigates, the arrow expands. */
.nav__group { border-bottom: 1px solid var(--divider); }
.nav__group-head { display: flex; align-items: center; justify-content: space-between; gap: var(--sp-2); }
.nav__group-head .nav__panel-link { flex: 1; }
.nav__group-toggle {
  display: inline-flex; align-items: center; justify-content: center;
  width: 44px; height: 44px; margin-right: -10px;
  background: none; border: 0; cursor: pointer; color: var(--ink); flex-shrink: 0;
}
.nav__group-toggle .nav__chev { width: 13px; height: 13px; opacity: .55;
                                transition: transform var(--dur-fast) var(--ease); }
.nav__group-toggle[aria-expanded="true"] .nav__chev { transform: rotate(180deg); opacity: 1; }
.nav__group-panel { padding-bottom: 6px; }

/* A top-level item with no children sits on the same rhythm as a group head. */
.nav__panel > a.nav__panel-link { display: block; border-bottom: 1px solid var(--divider); }

.nav__panel a.nav__panel-sub { display: block; font-size: 0.875rem; font-weight: 400;
                               color: var(--ink-muted); padding: 8px 0 8px 16px;
                               border-bottom: 0; }
.nav__panel a.nav__panel-sub::before { content: '\2014'; margin-right: 8px; opacity: .45; }
.nav__panel a.nav__panel-sub[aria-current="page"] { color: var(--brand); font-weight: 500; }
.nav__panel .btn { margin-top: var(--sp-3); }
@media (min-width: 1120px) { .nav__panel { display: none; } }

/* ---------- Hero: the dark opening ----------
   The page opens on a dark screen and warms out of it as you scroll, the way
   newgenre.studio hands its hero over to the page below. Everything here is driven
   by --hp, the 0-1 progress main.js writes to <html> as the hero's tall parent
   travels past its pinned stage.

   The one rule this section is built around: a background and the text on it must
   never travel in opposite directions. If the ground goes dark-to-light while the
   text goes light-to-dark, the two cross, and at the crossover contrast is 1:1 and
   the words are gone. The photographic opening never tests that rule: the room is
   light and so is the Cream it warms into, so every piece of type here is dark from
   first paint to last. The rule still governs anything that replaces it. */
:root { --hp: 0; --warm: 0; }
.hero { position: relative; background: var(--surface); }
.hero__stage {
  position: sticky; top: 0; min-height: 100svh;
  display: flex; align-items: center;
  padding-top: calc(var(--nav-h) + var(--sp-6));
  padding-bottom: var(--sp-6);
}
/* Tall enough that the whole warm-up takes real scrolling. A short hero makes the
   colour change read as a jump cut; this spreads it over about a screen and a half. */
.hero { height: 210vh; }
@media (min-width: 900px) { .hero { height: 230vh; } }
/* On a phone the opening was two full screens of scrolling before any content arrived,
   with the page apparently frozen while the sticky stage warmed up. Same effect, a
   third of the travel: the ground still warms and the second line still lands, but the
   page starts moving much sooner. Desktop is untouched. */
@media (max-width: 720px) { .hero { height: 145vh; } }

/* Three stacked layers, all inside the pinned stage so they stay locked to the
   viewport while the parent travels past:
     1. the photograph, cover-cropped
     2. a Cream scrim that is clear over the shelves and near-solid under the text
     3. a full Cream sheet that fades up with --warm as the page warms out of the photo
   The scrim is not decoration. The wall is striped in Oxblood and Sage, and the
   darkest pixel in the text column measures 1.05:1 against Ink. Type set straight onto
   this photograph would be unreadable wherever it crossed a stripe, and which stripe it
   crosses changes with viewport, so the scrim is sized against a pure black pixel rather
   than against the crop I happened to measure: at 0.74 even black yields 7.8:1 for Ink.
   Muted Rose cannot survive this lighter veil (3.7:1 against black), so the note is set
   in Ink and separated from the headline by size rather than by colour. */
/* The photograph starts below the bar rather than running under it, and sits inset
   from the edges as a rounded panel. overflow:hidden was already here, so the radius
   clips the scrim and the warm sheet along with the image. */
.hero__ground {
  position: absolute; inset: var(--nav-h) var(--sp-3) var(--sp-3); z-index: 0;
  overflow: hidden; border-radius: var(--radius-lg);
}
@media (max-width: 720px) {
  .hero__ground { inset: var(--nav-h) var(--sp-2) var(--sp-2);
                  border-radius: var(--radius-md); }
}
/* z-index matters here and is easy to get wrong: a pseudo-element paints *below* its
   host's real children, so without these the scrim would sit under the photograph and
   do nothing at all. Photo, then scrim, then the warm sheet. */
.hero__photo {
  position: absolute; inset: 0; z-index: 0; width: 100%; height: 100%;
  object-fit: cover; object-position: 32% center;
}
.hero__ground::before {
  content: ''; position: absolute; inset: 0; z-index: 1;
  background: linear-gradient(90deg,
              rgba(247,240,234,0) 30%,
              rgba(247,240,234,0.54) 44%,
              rgba(247,240,234,0.74) 56%,
              rgba(247,240,234,0.78) 100%);
}
/* Below the split the text runs full width, so a gradient keyed to the right-hand side
   protects nothing: the first third of every line would sit on bare photograph. The
   scrim becomes an even veil instead, and the crop shifts to the plain wall so there is
   less for it to have to cover. */
@media (max-width: 899px) {
  .hero__photo { object-position: 70% center; }
  .hero__ground::before { background: rgba(247, 240, 234, 0.74); }
}
.hero__ground::after {
  content: ''; position: absolute; inset: 0; z-index: 2;
  background: var(--surface); opacity: 0;
}
.js .hero__ground::after { opacity: var(--warm, 0); }

/* The shelves live in the left third of the photograph, so the column sits right while
   the photograph is there. Once the room has dissolved into Cream a right-offset column
   has nothing to be offset from and just reads as lopsided, so it drifts back to the
   page centre as --warm comes up. Transform only, so no layout is recalculated and the
   two-stage swap keeps landing in the same place.

   --shift is the exact distance from flush-right to centred: half the leftover space in
   the container once the column has taken its width. */
.hero__body { position: relative; z-index: 1; max-width: 1320px; }
.hero__col { width: min(100%, 40rem); margin-left: auto; text-align: center; }
@media (min-width: 900px) {
  .hero__col {
    --shift: calc((min(1320px, 100vw) - 2 * var(--sp-3) - 40rem) / 2);
    --drift: clamp(0, (var(--hp) - 0.06) / 0.22, 1);
  }
  .js .hero__col { transform: translate3d(calc(var(--shift) * var(--drift) * -1), 0, 0); }
}
/* Both stages occupy one grid cell, so the second appears exactly where the first
   was standing instead of below it. The cell is sized by the taller of the two,
   which keeps the CTA from moving when they swap. */
.hero__swap { display: grid; }
.hero__swap > * { grid-area: 1 / 1; align-self: center; }
.hero__first .display {
  max-width: 15ch; margin-inline: auto;
  /* Smaller than the page-wide display token: this one lives in a 48% column, and the
     full 5.5rem strands single words on their own lines at desktop widths. */
  font-size: clamp(2.5rem, 4vw, 3.9rem);
}
.hero__eyebrow { margin-top: var(--sp-4); }
.js .hero__eyebrow { animation: rise-in 0.6s var(--ease-out) 1.15s both; }
.hero__cta {
  position: relative; z-index: 1;
  display: flex; align-items: center; justify-content: center;
  gap: var(--sp-3); flex-wrap: wrap; margin-top: var(--sp-8);
}
.js .hero__cta { animation: rise-in 0.6s var(--ease-out) 1.45s both; }
@keyframes rise-in { from { opacity: 0; translate: 0 14px; } to { opacity: 1; translate: 0 0; } }

/* Stage one leaves early, while the ground behind it is still dark. */
.js .hero__first {
  opacity: clamp(0, 1 - var(--hp) / 0.22, 1);
  transform: translate3d(0, calc(clamp(0, var(--hp) / 0.22, 1) * -32px), 0);
}
/* Stage two is triggered, not scrubbed: crossing --hp 0.26 starts it and it plays out
   on its own clock, so the words keep arriving whether or not the visitor keeps
   scrolling.

   0.26 is not an arbitrary threshold. Because the paragraph can now finish while the
   scroll sits still, it can outrun the ground, so the trigger has to be a point where
   the ground is already safe for dark ink on its own: it measures 5.21:1 there, and
   anything earlier fails (4.30:1 at 0.24, 2.83:1 at 0.20). The class comes off again
   below the threshold, because scrolling back up re-darkens the ground and dark words
   left behind on it would be exactly the failure this whole hero is built to avoid. */
.hero__second { max-width: 100%; }
.hero__second .hw { display: inline-block; }
.js .hero__second .hw {
  opacity: 0; translate: 0 0.25em;
  transition: opacity var(--dur-mid) var(--ease);
}
.js .hero__second.is-revealed .hw {
  animation: word-in 0.5s var(--ease-out) both;
  animation-delay: calc(var(--i) * 45ms);
}

/* A quiet cue that there is more below, gone well before the ground moves. The
   wrapper owns the scroll-driven fade and the child owns the entrance, because an
   animation with a `both` fill outranks a plain opacity on the same element. */
.hero__scroll {
  position: absolute; inset-inline: 0; bottom: var(--sp-4); z-index: 1;
  width: max-content; margin-inline: auto;
  pointer-events: none;
}
.js .hero__scroll { opacity: clamp(0, 1 - var(--hp) / 0.10, 1); }
.hero__scroll-in {
  display: grid; place-items: center; gap: 6px;
  font-size: var(--fs-caption); letter-spacing: var(--ls-caption);
  text-transform: uppercase; color: var(--ink-muted);
}
.js .hero__scroll-in { animation: rise-in 0.6s var(--ease-out) 1.7s both; }
.hero__scroll-in span { display: block; width: 1px; height: 26px; background: var(--divider); }

/* No script means no --hp, so the hero settles the same way it does under reduced
   motion: the two halves stop sharing a cell and both are read against the photograph. */
.no-js .hero { height: auto; }
.no-js .hero__stage { position: static; min-height: 100svh; }
.no-js .hero__swap { display: block; }
.no-js .hero__swap > * { grid-area: auto; }
.no-js .hero__second { margin-top: var(--sp-4); }
.no-js .hero__scroll { display: none; }
/* No script means the bar never learns it has cleared the photograph, so it stays
   solid rather than frosted. Readable on both the photo and the Cream below it. */
.no-js.home .nav { background: var(--surface); backdrop-filter: none; }

/* ---------- Hero colour on the home page ----------
   The opening is light now, and so is everything it warms into, so every piece of
   type here is dark from first paint to last. Nothing interpolates against its own
   ground, which is what the old Oxblood opening had to work around. */
.home .hero .display { color: var(--ink); }
/* Sage Text is the eyebrow colour everywhere else on the site, where it sits on plain
   Cream at 4.54:1. It cannot come along here: measured against the scrimmed photograph
   it falls to 2.84:1, and getting it to 4.5:1 would need a backing of L 0.87, which is
   essentially white and would erase the room. Oxblood instead, which ties the line to
   the button and the logo and measures 8.6:1 on the same worst pixel. */
.home .hero .eyebrow { color: var(--brand); }
.home .hero__second { color: var(--ink); }
.home .hero__note { color: var(--ink); }
/* The cue sits at the horizontal centre, outside the text column, where the scrim is
   only about 0.66. Ink-muted measured 3.76:1 there; Ink measures 8.0:1. */
.home .hero__scroll-in { color: var(--ink); }
.home .hero__scroll-in span { background: var(--ink); opacity: .4; }
/* Oxblood on the scrimmed wall, white label on Oxblood. No ring: the fill carries
   its own edge here, unlike on the dark opening this replaced. */
.home .hero .btn--primary { background: var(--brand); color: #fff; box-shadow: none; }
.home .hero .btn--primary:hover { background: #3D0F0F; }

/* ---------- Nav over the photograph ----------
   The bar can no longer be transparent: it sits on a striped photograph whose darkest
   pixels measure about 1:1 against Ink, and the stripe under any given link changes
   with viewport. So it becomes frosted glass instead, which is what makes the links
   legible without hiding the room behind them. 0.72 rather than the 0.50 first tried,
   because at 0.50 the worst case is 3.9:1 and links are small text needing 4.5:1;
   0.72 puts the floor at 7.5:1. The blur is doing real work too, averaging the
   stripes out so the bar reads as one surface rather than a striped panel. */
/* The bar sits on Cream on every page now: the home page's photograph starts below
   it rather than passing under it, so there is nothing to frost. */

/* Word-by-word reveal, shared by the hero headline and the manifesto. Mechanism and
   values lifted from newgenre.studio: opacity 0 to 1 with a 0.25em rise, 0.5s ease-out,
   staggered per word by an inline animation-delay. The hidden state lives in the `both`
   fill rather than a bare rule, so a visitor without script gets the words, not a blank. */
@keyframes word-in {
  from { opacity: 0; translate: 0 0.25em; }
  to   { opacity: 1; translate: 0 0; }
}
.word { display: inline-block; }
.js .word { animation: word-in 0.5s var(--ease-out) both; }

/* ---------- Pinned manifesto ----------
   A tall parent with a sticky stage. --p is the 0-1 progress main.js writes onto the
   section as it travels past, and it drives two overlapping things so the scene is
   never sitting still: the block drifts slowly upward throughout, and the supporting
   line fades up across the back half. The words themselves are time-based, fired once
   by an observer, so they keep arriving whether or not the visitor keeps scrolling.

   These rules were lost in an earlier edit that replaced a range of lines by number and
   overshot. Without them the section stopped pinning, stopped drifting, and lost its
   centring, which is what made the heading look off-axis from the line beneath it. */
.pinned { position: relative; }
.pinned__stage {
  position: sticky; top: 0; min-height: 100svh;
  display: grid; align-content: center;
}
.pinned__inner { text-align: center; }
.js .pinned__inner { transform: translate3d(0, calc(var(--p, 0) * -48px), 0); }
/* The hidden state sits in the .js rule so a scriptless visitor still gets the line. */
.js .pinned__sub {
  --s: clamp(0, (var(--p, 0) - 0.55) / 0.35, 1);
  opacity: var(--s);
  transform: translate3d(0, calc((1 - var(--s)) * 18px), 0);
}

.pinned__words .pw { display: inline-block; opacity: 0; translate: 0 0.25em; }
.pinned__words.is-revealed .pw { animation: word-in 0.5s var(--ease-out) both; }
.no-js .pinned__words .pw { opacity: 1; translate: none; }

/* ---------- Cards ---------- */
.grid { display: grid; gap: var(--sp-3); }
.grid--4 { grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); }
.grid--3 { grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); }

.card {
  background: var(--raised); border-radius: var(--radius-md); padding: var(--sp-4);
  display: flex; flex-direction: column; text-decoration: none; color: inherit;
  transition: transform var(--dur-mid) var(--ease), box-shadow var(--dur-mid) var(--ease);
}
a.card:hover { transform: translateY(-2px); box-shadow: var(--shadow-md); }
.card__icon { color: var(--sage-deco); margin-bottom: var(--sp-2); }
.card h3 { font-family: var(--font-body); font-weight: 600; font-size: var(--fs-h4);
           line-height: var(--lh-h4); margin-bottom: var(--sp-1); }
.card p { font-size: var(--fs-small); color: var(--ink-muted); }
.card__more { margin-top: auto; padding-top: var(--sp-3); font-size: var(--fs-small);
              font-weight: 600; color: var(--brand); }

/* ---------- Team ---------- */
.person { text-align: left; }
.person img { border-radius: var(--radius-md); aspect-ratio: 1/1; object-fit: cover; width: 100%; }
.person__name { margin-top: var(--sp-2); font-family: var(--font-display); font-weight: 400;
                font-size: var(--fs-h3); line-height: var(--lh-h3); letter-spacing: var(--ls-h3); }
.person__role { font-size: var(--fs-small); color: var(--ink-muted); margin-top: 2px; }
.person p { font-size: var(--fs-small); margin-top: var(--sp-2); }

/* Two clinicians share the row; the grid is capped so the portraits stay a sensible
   size rather than ballooning to half the container. Left-aligned under the heading. */
.team-grid { display: grid; gap: var(--sp-6); grid-template-columns: 1fr; max-width: 880px; }
@media (min-width: 780px) { .team-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }

.petstrip {
  display: flex; align-items: center; gap: var(--sp-3); flex-wrap: wrap;
  margin-top: var(--sp-6); padding: var(--sp-3) var(--sp-4);
  background: var(--surface); border-radius: var(--radius-full);
  max-width: 880px;   /* aligns with .team-grid rather than running the full container */
}
.petstrip__pics { display: flex; flex-shrink: 0; }
.petstrip__pics img {
  width: 64px; height: 64px; border-radius: 50%; object-fit: cover;
  border: 3px solid var(--surface);
}
.petstrip__pics img + img { margin-left: -18px; }
.petstrip p { font-size: var(--fs-small); color: var(--ink-muted); margin: 0; max-width: 62ch; }
.petstrip strong { color: var(--ink); font-weight: 600; }
@media (max-width: 560px) {
  .petstrip { border-radius: var(--radius-md); }
}

/* ---------- Steps ---------- */
.steps { counter-reset: step; }
.step { position: relative; padding-top: var(--sp-3); border-top: 1px solid var(--divider); }
.step::before {
  counter-increment: step; content: counter(step, decimal-leading-zero);
  font-family: var(--font-body); font-size: var(--fs-caption); font-weight: 600;
  letter-spacing: var(--ls-caption); color: var(--sage-text); display: block; margin-bottom: var(--sp-2);
}
.step h3 { font-family: var(--font-body); font-weight: 600; font-size: var(--fs-h4); margin-bottom: var(--sp-1); }
.step p { font-size: var(--fs-small); color: var(--ink-muted); }

/* ---------- Accordion ---------- */
.acc { border-top: 1px solid var(--divider); }
.acc:last-of-type { border-bottom: 1px solid var(--divider); }
.acc__btn {
  width: 100%; display: flex; align-items: flex-start; justify-content: space-between; gap: var(--sp-3);
  background: none; border: none; cursor: pointer; text-align: left;
  padding: var(--sp-3) 0; font-family: var(--font-body); font-size: var(--fs-h4);
  font-weight: 500; color: var(--ink); line-height: var(--lh-h4);
}
.acc__btn:hover { color: var(--brand); }
.acc__sign { flex-shrink: 0; width: 20px; height: 20px; margin-top: 3px; position: relative; }
.acc__sign::before, .acc__sign::after {
  content: ''; position: absolute; inset: 50% 0 auto 0; height: 1.5px; background: currentColor;
  transition: transform var(--dur-mid) var(--ease);
}
.acc__sign::after { transform: rotate(90deg); }
.acc__btn[aria-expanded="true"] .acc__sign::after { transform: rotate(0deg); }
.acc__panel { display: grid; grid-template-rows: 0fr; transition: grid-template-rows var(--dur-mid) var(--ease); }
.acc__panel > div { overflow: hidden; }
.acc__btn[aria-expanded="true"] + .acc__panel { grid-template-rows: 1fr; }
.acc__panel p { font-size: var(--fs-small); color: var(--ink-muted); max-width: var(--measure);
                padding-bottom: var(--sp-3); }

/* ---------- Instagram strip ----------
   Heading centred over a full-bleed row of square tiles, the Sol Wellness shape. */
.section.ig { padding-bottom: 0; }   /* the strip must meet the closing band, no gap */
.ig__heading { text-align: center; margin-bottom: var(--sp-6); }
.ig__heading a {
  color: inherit; text-decoration: underline;
  text-decoration-thickness: 1.5px; text-underline-offset: 6px;
  text-decoration-color: var(--sage-deco);
  transition: text-decoration-color var(--dur-fast) var(--ease);
}
.ig__heading a:hover { text-decoration-color: var(--brand); }

.ig__strip {
  list-style: none; margin: 0; padding: 0;
  display: grid; grid-auto-flow: column;
  grid-auto-columns: minmax(0, 1fr);
}
.ig__tile { position: relative; overflow: hidden; }
.ig__tile a { display: block; position: relative; }
.ig__tile img {
  width: 100%; aspect-ratio: 1 / 1; object-fit: cover; display: block;
  transition: transform var(--dur-slow) var(--ease), filter var(--dur-mid) var(--ease);
}
/* A wash of the brand colour on hover, with the mark showing through. */
.ig__tile a::after {
  content: ''; position: absolute; inset: 0; background: var(--brand);
  opacity: 0; transition: opacity var(--dur-mid) var(--ease);
}
.ig__tile a:hover img, .ig__tile a:focus-visible img { transform: scale(1.04); }
.ig__tile a:hover::after, .ig__tile a:focus-visible::after { opacity: 0.22; }

/* Only on the reel, because only the reel is a video. */
.ig__play {
  position: absolute; inset: auto var(--sp-2) var(--sp-2) auto;
  display: grid; place-items: center; width: 34px; height: 34px;
  border-radius: 50%; background: rgba(61,15,15,0.55); color: #fff;
  -webkit-backdrop-filter: blur(4px); backdrop-filter: blur(4px);
  z-index: 1; pointer-events: none;
}

@media (max-width: 900px) {
  /* Below tablet the row becomes a swipeable rail rather than six slivers. */
  .ig__strip {
    grid-auto-columns: 46vw;
    overflow-x: auto; scroll-snap-type: x mandatory;
    -webkit-overflow-scrolling: touch; scrollbar-width: none;
    /* Without this, swiping past the last tile hands the gesture to the browser and
       triggers back-navigation on iOS and Android. Lenis ships a rule that would do
       this, but it is scoped to .lenis-smooth, a class the page never actually gets. */
    overscroll-behavior-x: contain;
  }
  .ig__strip::-webkit-scrollbar { display: none; }
  .ig__tile { scroll-snap-align: start; }
}

/* ---------- Interior page chrome ----------
   Interior heroes are shorter and left-aligned so they read as a different beat
   from the home page's staged split, while using the same type and colour. */
.page-hero { padding-top: calc(var(--nav-h) + var(--sp-8)); padding-bottom: var(--sp-6); }
/* Cap the headline, not the whole column: the lede and any sub-copy still run to
   the normal reading measure underneath it. */
.page-hero__inner h1 { max-width: 18ch; }
.page-hero--wide .page-hero__inner h1 { max-width: 26ch; }
.page-hero__inner .lede,
.page-hero__inner .small { max-width: var(--measure); }
.page-hero .lede { margin-top: var(--sp-3); }
@media (max-width: 720px) {
  .page-hero { padding-top: calc(var(--nav-h) + var(--sp-4)); padding-bottom: var(--sp-4); }
  .page-hero__inner h1, .page-hero--wide .page-hero__inner h1 { max-width: none; }
}

/* Tick lists used across the service pages. */
.ticks { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--sp-2); }
.ticks li { display: flex; gap: var(--sp-2); align-items: flex-start; }
.ticks .blossom { color: var(--sage-deco); flex: none; margin-top: 5px; }

/* Simple definition rows used by Rates and Contact. */
.rows { border-top: 1px solid var(--divider); }
.row { display: flex; flex-wrap: wrap; gap: var(--sp-2) var(--sp-4);
       justify-content: space-between; align-items: baseline;
       padding: var(--sp-3) 0; border-bottom: 1px solid var(--divider); }
.row__label { font-weight: 500; }
.row__value { font-family: var(--font-display); font-size: var(--fs-h3);
              letter-spacing: var(--ls-h3); white-space: nowrap; }
.row__note { flex-basis: 100%; font-size: var(--fs-small); color: var(--ink-muted); }

/* Post / article list. */
.posts { display: grid; gap: var(--sp-4); grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); }
.post-card { display: flex; flex-direction: column; text-decoration: none; color: inherit;
             background: var(--raised); border-radius: var(--radius-md); overflow: hidden;
             transition: transform var(--dur-mid) var(--ease), box-shadow var(--dur-mid) var(--ease); }
.post-card:hover { transform: translateY(-2px); box-shadow: var(--shadow-md); }
.post-card img { aspect-ratio: 3/2; object-fit: cover; width: 100%; }
.post-card__body { padding: var(--sp-3) var(--sp-4) var(--sp-4); display: flex;
                   flex-direction: column; gap: 8px; flex: 1; }
.post-card__date { font-size: var(--fs-caption); letter-spacing: var(--ls-caption);
                   text-transform: uppercase; color: var(--ink-muted); }
.post-card h3 { font-family: var(--font-display); font-weight: 400; font-size: var(--fs-h3);
                line-height: var(--lh-h3); letter-spacing: var(--ls-h3); }
.post-card p { font-size: var(--fs-small); color: var(--ink-muted); margin: 0; }

/* Long-form article body. */
.article { max-width: var(--measure); }
.article h2 { font-size: var(--fs-h2); line-height: var(--lh-h2); letter-spacing: var(--ls-h2);
              margin: var(--sp-6) 0 var(--sp-2); }
.article h3 { font-size: var(--fs-h3); margin: var(--sp-4) 0 var(--sp-1); }
.article ul, .article ol { padding-left: 1.2em; display: grid; gap: 8px; margin: 0 0 1em; }
.article blockquote { margin: var(--sp-4) 0; padding-left: var(--sp-3);
                      border-left: 2px solid var(--sage-deco); font-family: var(--font-display);
                      font-size: var(--fs-h3); line-height: var(--lh-h3); }

/* Forms. */
.field { display: grid; gap: 6px; margin-bottom: var(--sp-3); }
.field label { font-size: var(--fs-caption); font-weight: 600; letter-spacing: var(--ls-caption);
               text-transform: uppercase; color: var(--ink-muted); }
.field input, .field select, .field textarea {
  font-family: var(--font-body); font-size: var(--fs-body); color: var(--ink);
  background: var(--canvas); border: 1px solid var(--ink-muted);
  border-radius: var(--radius-sm); padding: 13px 16px; width: 100%;
}
.field textarea { min-height: 140px; resize: vertical; }
.field input:focus-visible, .field select:focus-visible, .field textarea:focus-visible {
  border-color: var(--brand); outline: 2px solid var(--brand); outline-offset: 2px; }
.field__hint { font-size: var(--fs-small); color: var(--ink-muted); text-transform: none;
               letter-spacing: normal; font-weight: 400; }
.choice { display: grid; gap: var(--sp-2); grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); }
.choice label { display: flex; align-items: center; gap: 10px; cursor: pointer;
                background: var(--canvas); border: 1px solid var(--divider);
                border-radius: var(--radius-sm); padding: 14px 16px;
                font-size: var(--fs-body); font-weight: 400; text-transform: none;
                letter-spacing: normal; color: var(--ink); }
/* flex-shrink:0 matters: without it the control is squeezed whenever its label
   wraps to a second line, so options end up with visibly different sized boxes. */
.choice input { accent-color: var(--brand); width: 18px; height: 18px;
                flex: none; margin: 0; }
.choice label { align-items: center; }
.choice label:has(input:checked) { border-color: var(--brand); box-shadow: inset 0 0 0 1px var(--brand); }
.hp-field { position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden; }

/* Chips: focus-area tags on the clinician pages. */
.chips { list-style: none; margin: 0; padding: 0; display: flex; flex-wrap: wrap; gap: 8px; }
.chips li { font-size: var(--fs-small); padding: 7px 14px; border-radius: var(--radius-full);
            background: var(--surface); border: 1px solid var(--divider); color: var(--ink); }
/* Chips need a lighter ground when the section they sit on is already tinted. This used
   to key off the inline style attribute; the build now lifts those into classes, so the
   section carries an explicit .section--tint marker instead. */
.section--tint .chips li { background: var(--canvas); border-color: transparent; }


/* Aside cards on the intake page. */
.aside-card { background: var(--canvas); border-radius: var(--radius-md); padding: var(--sp-4); }
.aside-card .ticks li { align-items: flex-start; }

/* Form wrapper. */
.form fieldset legend { margin-bottom: var(--sp-2); }

/* Multi-step intake. Steps are plain fieldsets until JS turns them into steps, so
   the form still works, and still submits, with JavaScript off. */
.fstep { border: 0; padding: 0; margin: 0 0 var(--sp-4); }
.fstep legend { padding: 0; margin-bottom: var(--sp-3); }
.fstep legend:focus-visible { outline: 2px solid var(--brand); outline-offset: 4px; }
.step-nav { display: flex; gap: var(--sp-2); flex-wrap: wrap; margin-top: var(--sp-4); }
.steps-progress { display: flex; align-items: center; gap: var(--sp-2); margin-bottom: var(--sp-4); }
.steps-progress__bar { flex: 1; height: 3px; background: var(--divider);
                       border-radius: var(--radius-full); overflow: hidden; }
.steps-progress__bar > span { display: block; height: 100%; width: 10%;
                              background: var(--brand); border-radius: var(--radius-full);
                              transition: width var(--dur-mid) var(--ease); }
.steps-progress__label { font-size: var(--fs-caption); font-weight: 600;
                         letter-spacing: var(--ls-caption); text-transform: uppercase;
                         color: var(--ink-muted); white-space: nowrap; }
.notice { background: var(--raised); border-radius: var(--radius-md); padding: var(--sp-3) var(--sp-4); }
.notice p { font-size: var(--fs-small); margin: 0; }

/* Error page. */
.err { min-height: 62svh; display: grid; place-items: center; text-align: center;
       padding-top: var(--nav-h); }

/* Hero split used by the service pages: sketch alongside the heading. */
@media (min-width: 900px) {
  .split--hero { grid-template-columns: minmax(0, 1.05fr) minmax(0, 0.75fr); align-items: center; }
}
/* The mark sits straight on the page now, with no plate behind it, so it carries no
   radius or shadow either: both would draw the edge of a card that is not there. */
.hero-sketch img { width: 100%; height: auto; }
@media (max-width: 899px) { .hero-sketch { max-width: 340px; } }

/* Partner marks. Each carries its own width rather than a shared max-height, for two
   reasons: the two logos have very different proportions (Bosnar's is square, Tight's is
   a wide wordmark) so one shared axis would make one dwarf the other, and a width the
   browser can resolve before the file loads lets it reserve the box from the width/height
   attributes. Sizing with `width:auto` plus max-constraints cannot, and shifts on load. */
.partner-logo { display: grid; place-items: center; }
.partner-logo img { width: min(100%, var(--logo-w)); height: auto; }
@media (max-width: 899px) {
  .partner-logo { justify-items: start; }
  .partner-logo img { width: min(100%, calc(var(--logo-w) * 0.74)); }
}

/* Fitzy and Maui, side by side with their own copy beneath each. */
.pets-grid { display: grid; gap: var(--sp-6); grid-template-columns: 1fr; }
@media (min-width: 780px) { .pets-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
.pet img { width: 100%; aspect-ratio: 4/3; object-fit: cover;
           border-radius: var(--radius-md); }
.pet__name { margin-top: var(--sp-3); font-family: var(--font-display); font-weight: 400;
             font-size: var(--fs-h2); line-height: var(--lh-h2); letter-spacing: var(--ls-h2); }
.pet__role { font-size: var(--fs-small); color: var(--ink-muted); margin: 4px 0 var(--sp-2); }
.pet .prose p { font-size: var(--fs-small); }
.pet__focus { color: var(--ink-muted); }

/* Our Story: the circular mark sits alongside the opening paragraphs. */
.story-mark { float: left; width: 104px; height: auto; margin: 4px var(--sp-3) var(--sp-2) 0;
              shape-outside: circle(50%); }
@media (max-width: 560px) { .story-mark { float: none; display: block;
                                          margin: 0 0 var(--sp-3); width: 88px; } }
/* The portrait was overpowering the copy beside it. */
.story-figure { max-width: 460px; margin-inline: auto; }

/* ---------- Footer ---------- */
.footer { background: var(--brand); color: var(--surface); padding-block: var(--sp-8) var(--sp-4); }
.footer a:not(.btn) { color: var(--surface); text-decoration: none; opacity: 0.85; }
.footer a:not(.btn):hover { opacity: 1; text-decoration: underline; }
.footer__grid { display: grid; gap: var(--sp-6); grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); }
.footer__brandmark .mark { display: flex; align-items: center; gap: 10px; margin-bottom: var(--sp-2); }
.footer__brandmark .mark svg { color: var(--sage-band); flex-shrink: 0; }
.footer__brandmark .wordmark { font-family: var(--font-display); font-size: 1.75rem;
                               line-height: 1; letter-spacing: var(--ls-display); color: var(--surface); }
.footer__brandmark .wordmark small { display: block; font-family: var(--font-body);
                                     font-size: 0.625rem; font-weight: 600; letter-spacing: 0.2em;
                                     text-transform: uppercase; opacity: .7; margin-top: 5px; }
.footer h2 { font-family: var(--font-body); font-size: var(--fs-caption); font-weight: 600;
             letter-spacing: var(--ls-caption); text-transform: uppercase; margin-bottom: var(--sp-2); opacity: 0.75; }
.footer ul { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 10px; }
.footer li { font-size: var(--fs-small); }
.footer__crisis { margin-top: var(--sp-6); padding: var(--sp-3);
                  border: 1px solid rgba(247,240,234,0.25); border-radius: var(--radius-sm);
                  font-size: var(--fs-small); }
.footer__crisis strong { display: block; margin-bottom: 4px; }
.footer__base { margin-top: var(--sp-6); padding-top: var(--sp-3);
                border-top: 1px solid rgba(247,240,234,0.22); font-size: var(--fs-caption);
                opacity: 0.75; display: flex; flex-wrap: wrap; gap: var(--sp-2) var(--sp-4);
                justify-content: space-between; }

/* ---------- Scroll reveal ---------- */
.js .reveal { opacity: 0; transform: translateY(24px);
              transition: opacity var(--dur-slow) var(--ease), transform var(--dur-slow) var(--ease); }
.js .reveal.is-in { opacity: 1; transform: none; }

/* ---------- Small screens ----------
   The 96/128px section rhythm is right on desktop and far too airy on a phone,
   where it turns into dead scroll between blocks. */
@media (max-width: 720px) {
  .section { padding-block: 40px; }
  .section--tight { padding-block: var(--sp-3); }
  .band { padding-block: var(--sp-6); }
  .band--brand { padding-block: var(--sp-6) !important; }
  .hero__stage { padding-top: calc(var(--nav-h) + var(--sp-4)); padding-bottom: var(--sp-4); }
  .nav__logo img { width: 140px !important; }
  .split { gap: var(--sp-6); }
  .footer { padding-block: var(--sp-6) var(--sp-4); }
  .footer__grid { gap: var(--sp-4); }
  /* Footer links are a list of destinations, not links inside a sentence, so the 44px
     touch-target guidance applies to them. Inline anchors are only as tall as their
     text (20px here) with 10px between, which is a mis-tap waiting to happen on a
     phone. Growing the anchor itself rather than the row keeps the visual rhythm
     close to what it was while making the whole row tappable. */
  .footer li a:not(.btn) { display: block; padding-block: 12px; }
  .footer ul { gap: 0; }
  /* The crisis numbers run inline in a sentence, so they inherit the text's own 20px
     height. These are the links most likely to be tapped in a hurry, by someone who
     is not at their most precise, so they get a real target even at the cost of a
     looser line. Same for the legal pair in the base row. */
  .footer__crisis a, .footer__base a { display: inline-block; padding-block: 11px; }
  .footer__crisis { line-height: 1.35; }
  .footer__crisis { margin-top: var(--sp-4); }
  .footer__base { margin-top: var(--sp-4); }
  .hero__scroll { top: calc(100svh - 88px); bottom: auto; }
}

/* ---------- Page transitions ----------
   Cross-document View Transitions: the browser fades the old page into the new one
   natively, with no JS and no risk of a blank frame. Browsers without support
   (Firefox at time of writing) simply navigate normally, and get the fade-in below
   instead, so nobody sees a broken state. */
@view-transition { navigation: auto; }

::view-transition-old(root) { animation: vt-out 240ms var(--ease) both; }
::view-transition-new(root) { animation: vt-in  320ms var(--ease) both; }
@keyframes vt-out { to   { opacity: 0; transform: translateY(-8px); } }
@keyframes vt-in  { from { opacity: 0; transform: translateY(10px); } }

/* The nav and footer are the same on every page, so carrying them across the
   transition stops them flickering while the body changes. */
.nav, .footer { view-transition-name: none; }

@supports not (view-transition-name: none) {
  .js body { animation: page-in 320ms var(--ease) both; }
}
@keyframes page-in { from { opacity: 0; } to { opacity: 1; } }

/* ---------- Reduced motion ----------
   design.md is explicit that an anxious visitor is exactly who turns this on, so
   every effect resolves to its finished state rather than degrading to something
   half-animated. Nothing on the page needs motion to be understood. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.001ms !important; animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important; scroll-behavior: auto !important;
  }
  .js .reveal { opacity: 1; transform: none; }
  .js .word { opacity: 1; animation: none; }
  ::view-transition-old(root), ::view-transition-new(root) { animation: none !important; }
  .js body { animation: none !important; }
  /* Without scroll there is no swap to stage, so the two halves stop sharing a cell
     and the hero settles as an ordinary opening with everything already said. */
  .hero { height: auto !important; }
  .hero__stage { position: static; min-height: 0; }
  .hero__swap { display: block; }
  .hero__swap > * { grid-area: auto; }
  .js .hero__first { opacity: 1 !important; transform: none !important; }
  .js .hero__second .hw { opacity: 1 !important; translate: none !important; }
  .home .hero__second { margin-top: var(--sp-4); }
  .js .hero__scroll, .js .hero__scroll-in { opacity: 0; }
  .pinned { height: auto !important; }
  .pinned__stage { position: static; min-height: 0; }
  .pinned__inner, .pinned__sub { transform: none !important; }
  .pinned__sub { opacity: 1 !important; }
  .pinned__words .pw { opacity: 1; translate: none; animation: none !important; }
}

/* ---------- Lenis (required by the smooth-scroll library) ---------- */
html.lenis,html.lenis body{height:auto}.lenis.lenis-smooth [data-lenis-prevent]{overscroll-behavior:contain}.lenis.lenis-stopped{overflow:clip}.lenis.lenis-smooth iframe{pointer-events:none}

