/* happygrieving.org — component styles for the Happy Grieving website.
   Design TOKENS (colour, type, depth) are the shared brand book:
   ../../brand/tokens.css, copied to /assets/tokens.css and linked in <head>
   BEFORE this file. This file only styles components; it never redefines a
   colour or font (change those in brand/tokens.css so the app inherits it). */

*, *::before, *::after { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; scroll-padding-top: var(--header-clear, 6rem); }
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  * { animation-duration: .001ms !important; transition-duration: .001ms !important; }
}

body {
  margin: 0;
  background: var(--ground);
  color: var(--ink);
  font-family: var(--body);
  /* Matches the member webapp exactly: 16px/1.65 with a global -0.005em of
     tracking. Hanken Grotesk sets a touch wider than the old stack, so the
     slightly smaller size and tighter leading land at the same reading
     density rather than a smaller-feeling page. */
  font-size: 1rem;
  line-height: 1.65;
  letter-spacing: -0.005em;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  /* .band and .hero break out to 100vw on purpose (full-bleed section colour
     inside a max-width'd <main>); clip the axis so that trick can never
     open a horizontal scrollbar. */
  overflow-x: clip;
}
/* Paper grain, the app's recipe: a fixed overlay at 3.2% rather than alpha
   baked into the SVG filter. The old version hardcoded black at 6% inside the
   filter matrix, which had no theme variant and reads noticeably stronger on
   the lighter paper ground. As an overlay the strength is one number, and the
   radial sage wash it used to sit over is gone - the hero gradient does that
   job now, where it belongs. */
body::after {
  content: ""; position: fixed; inset: 0; z-index: 90; pointer-events: none;
  opacity: .032;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
  background-size: 160px 160px;
}

/* ---------- layout rails ---------- */
:root {
  --measure: 44rem; --wide: 66rem; --sec-pad: clamp(3rem, 7vw, 5.5rem);
  /* Sticky-chrome heights. These four values used to be four unrelated magic
     numbers (6rem, 6rem, 4.625rem, 8.2rem) that silently depended on each
     other: change the header's padding or font-size and anchor targets start
     landing under the header with nothing to say so. Derive them instead. */
  --header-h: 4.5rem;                                      /* compact header; 4.625rem at >=44rem */
  --header-clear: calc(var(--header-h) + 1.375rem);        /* header + breathing room */
  --family-nav-h: 4.4375rem;                               /* measured height of .family-nav (71px: 44px chip + .8rem x2 padding + border), true at every width now the row never wraps */
  --stack-clear: calc(var(--header-h) + var(--family-nav-h));
}
main { max-width: var(--measure); margin: 0 auto; padding: 2rem 1.35rem 4rem; }
main:has(.library-index), main:has(.family) { max-width: var(--wide); }

/* ---------- section bands ----------
   A full-bleed colour band inside the max-width'd <main>, for section
   rhythm: the page breathes in bands instead of one flat sheet top to
   bottom. Breaks out to the viewport width via the 100vw/-50vw trick (body
   clips the axis above), then re-centres its own content to the reading
   measure. Vertical rhythm comes from --sec-pad, not h2 margins. */
.band {
  width: 100vw; position: relative; left: 50%; right: 50%;
  margin-left: -50vw; margin-right: -50vw;
  padding: var(--sec-pad) 1.35rem;
}
.band > * { max-width: var(--measure); margin-left: auto; margin-right: auto; }
.band > :first-child { margin-top: 0; }
/* .toc/.offerings/.eyebrow/.facts/.bonuses/.apply/.intake each set their own
   margin shorthand later in this file, which - at equal class specificity -
   wins the left/right auto from the rule above by source order and resets it
   to 0, pinning them flush-left instead of centered. Higher-specificity
   override, order-independent. Any new block that sets `margin:` and sits
   directly in a .band belongs in this list. */
.band > :is(.toc, .offerings, .eyebrow, .facts, .bonuses, .apply, .intake) { margin-left: auto; margin-right: auto; }
/* Bands carry hue rather than one repeated tint, so the homepage reads as
   distinct sections the way the app's feature areas do. Washes are the flat
   panel step: no border, no shadow. */
.band--soft { background: var(--sage-wash); }
/* Marbled rather than flat (Rachel's call, 28 Sep 2026) - the same
   two-layer wash the hero uses (radial marigold pool fading in from the
   top, linear rose fading down the whole band), not a literal stone
   texture. Keeps every wash on the site built from the same technique. */
.band--tint {
  background:
    radial-gradient(120% 90% at 50% 0%, var(--marigold-wash), transparent 62%),
    linear-gradient(180deg, var(--rose-wash), transparent);
}
/* One reveal gesture, nothing more: a slow fade-up as each homepage band
   enters. Only ever active once theme.js has added reveal-ready to <html>
   (confirmed IntersectionObserver exists and prefers-reduced-motion is off),
   so without JS - or with motion declined - every .reveal element is just
   plain, fully visible content from the first paint. */
html.reveal-ready .reveal { opacity: 0; transform: translateY(14px); transition: opacity .6s var(--ease), transform .6s var(--ease); }
html.reveal-ready .reveal.is-visible { opacity: 1; transform: none; }

/* ---------- header ----------
   Written mobile-first, and the breakpoints are measured rather than picked.
   The header carries the wordmark, five links (library, trips, group, letter,
   about) and the app pill, which is the site's main call to action.
   The wordmark and the pill show at every width. The five links sit inline
   only from 60rem, and live behind a Menu disclosure below it: five links plus
   a wordmark plus a pill genuinely do not fit sooner. That threshold used to be
   44rem, when the nav had three links and the letter was the pill.
   Budget, measured at the 60rem breakpoint: the row needs about 904px of the
   960px available, so there is roughly 56px of headroom. Another nav item, or
   a materially longer label than "The group program", wraps it. If one is
   added, raise the breakpoint (and theme.js with it) rather than letting it
   wrap: a wrapped header is the exact bug this section already shipped once.
   The theme toggle used to live here as another item; it moved to the footer,
   which is part of what made a single row reachable at all.
   Two of these numbers are load-bearing elsewhere: 60rem is mirrored in
   theme.js (see the note there), and the header's real height feeds
   --header-clear, which is the scroll-padding that keeps anchor targets from
   landing underneath this sticky bar. */
.top {
  position: sticky; top: 0; z-index: 50;
  display: flex; flex-wrap: wrap; gap: .5rem 1rem;
  align-items: center; justify-content: space-between;
  padding: .85rem 1rem;
  background: color-mix(in srgb, var(--surface) 85%, transparent);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  border-bottom: 1px solid var(--line);
}
.top > .brand, .top > nav { max-width: var(--wide); }
.brand {
  font-family: var(--display); font-weight: 400; font-size: 1.3rem;
  color: var(--ink); text-decoration: none; letter-spacing: -.01em;
  display: inline-flex; align-items: center; gap: .5rem;
}
.brand-icon { width: 1.75rem; height: 1.75rem; border-radius: 22%; flex-shrink: 0; }
.top nav { display: flex; flex-wrap: wrap; align-items: center; justify-content: flex-end; gap: .3rem .45rem; }
.top nav a {
  color: var(--muted); text-decoration: none; font-size: .82rem; font-weight: 500;
  padding: .55rem 0; border-radius: var(--radius-pill);
  transition: color var(--dur-quick) var(--ease), background var(--dur-quick) var(--ease);
}
.top nav a:hover { color: var(--ink); }
.top nav a[aria-current] { color: var(--sage-deep); }
/* Full-size header: room for the padded pill-hover links the app uses. */
@media (min-width: 44rem) {
  .top { padding: .85rem 1.35rem; gap: .5rem 1.5rem; }
  .top nav { gap: .35rem 1.15rem; }
  .top nav a { font-size: .95rem; padding: .4rem .75rem; }
  .top nav a:hover { background: color-mix(in srgb, var(--sage-wash) 60%, transparent); }
  .top nav a[aria-current] { background: var(--sage-wash); color: var(--sage-deep); }
  .top nav a.nav-cta { padding: .45rem 1.1rem; }
}
/* Tap targets. The compact header's controls are deliberately small - that is
   what buys the single row - so they grow an invisible hit area instead of
   growing visually. The app's technique, and what gets them past 44px.
   Plain nav links extend vertically only: they sit shoulder to shoulder with
   a .45rem gap, so a horizontal inset would overlap the neighbour's hit area
   and make which-link-did-I-tap ambiguous. */
.top nav a, .social-icon { position: relative; }
.top nav a::before, .social-icon::before {
  content: ""; position: absolute; inset: -.2rem 0;
}
.top nav a.nav-cta::before, .social-icon::before { inset: -.5rem -.25rem; }
/* The header action. Demoted from the solid sage-deep pill (29 Jul 2026):
   with solid pills also living in the hero and the page bodies, three
   identical primary buttons could share one phone viewport, and "which one
   is THE action" stopped meaning anything. The header pill now wears the
   chip form (wash + hairline) so page-level .btn keeps the only solid.
   Selected as `.top nav a.nav-cta` (0,2,2) so it outranks `.top nav a`
   (0,1,2) on specificity - same trick the old solid version used. */
.top nav a.nav-cta {
  color: var(--sage-deep); font-weight: 500;
  border: 1px solid var(--sage); border-radius: var(--radius-pill);
  padding: .45rem 1.1rem; background: var(--sage-wash);
}
.top nav a.nav-cta:hover { background: var(--sage-tint); color: var(--sage-deep); }
.top nav a.nav-cta[aria-current] { background: var(--sage-tint); color: var(--sage-deep); }

/* Theme toggle. Lives in the FOOTER, not the header - the header could not
   fit brand + three links + the letter pill + a toggle on a phone without
   wrapping to two rows, and moving this out is what buys the single row.
   Being in the footer also lets it carry a visible text label, which is more
   discoverable than the bare icon button it used to be. */
.theme-toggle {
  display: inline-flex; align-items: center; gap: .45rem; min-height: 2.75rem;
  padding: .45rem 1rem; border: 1px solid var(--line); border-radius: var(--radius-pill);
  background: transparent; color: var(--muted); cursor: pointer;
  font-family: var(--head); font-weight: 500; font-size: .82rem; line-height: 1;
  transition: background var(--dur-quick) var(--ease), color var(--dur-quick) var(--ease), border-color var(--dur-quick) var(--ease);
}
.theme-toggle:hover { background: var(--sage-wash); color: var(--ink); border-color: var(--sage); }
.theme-toggle .moon { display: none; }
.theme-toggle .sun { display: inline-flex; line-height: 0; }
:root[data-theme="dark"] .theme-toggle .sun { display: none; }
:root[data-theme="dark"] .theme-toggle .moon { display: inline-flex; line-height: 0; }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .theme-toggle .sun { display: none; }
  :root:not([data-theme="light"]) .theme-toggle .moon { display: inline-flex; line-height: 0; }
}
/* The compact header is shorter than the full-size one, so the sticky offsets
   keyed off --header-h must grow back with it or anchor jumps land wrong.
   The compact value is the default (see the layout-rails :root above). */
@media (min-width: 44rem) { :root { --header-h: 4.625rem; } }
/* Below 24rem (384px) the compact header runs out of headroom - measured at
   375px it had only 7px of slack, which a larger system font or a different
   font render would eat. Drop Grief Trips there; it stays in the footer nav
   and in the homepage offerings, so nothing becomes unreachable. 384px is the
   breakpoint rather than 368px specifically to catch the 375px phones. */
@media (max-width: 24rem) {
  .top nav a[href="/trips/"] { display: none; }
}
/* ---------- compact nav menu ----------
   The wordmark stays at every width. It costs ~140px, which means the three
   nav links cannot also sit inline on a phone, so below 44rem they live in a
   disclosure instead. <details> rather than JS: the site's other enhancements
   all degrade, and this gives open/close, keyboard support and the right
   semantics with no script at all. */
.nav-more { position: relative; }
.nav-more > summary {
  display: inline-flex; align-items: center; justify-content: center; gap: .4rem; cursor: pointer;
  min-width: 2.75rem; min-height: 2.75rem;
  list-style: none; padding: .45rem .8rem; border-radius: var(--radius-pill);
  border: 1px solid var(--line); color: var(--muted);
  font-family: var(--head); font-weight: 500; font-size: .82rem; line-height: 1;
  transition: color var(--dur-quick) var(--ease), border-color var(--dur-quick) var(--ease), background var(--dur-quick) var(--ease);
}
.nav-more > summary::-webkit-details-marker { display: none; }
.nav-more > summary::marker { content: ""; }
.nav-more > summary:hover { color: var(--ink); border-color: var(--sage); background: var(--sage-wash); }
.nav-more-bars { display: inline-flex; line-height: 0; }
/* The word "Menu" costs ~40px the phone header does not have. The summary
   carries aria-label="Menu", so the button keeps its name without it. */
.nav-more-word { display: none; }
/* Phones: the wordmark is staying, so the brand type gives a little instead.
   Measured - this trim is worth ~31px, which is what takes 375px from 8px of
   headroom (one bad font render from wrapping) to a comfortable 39px. */
@media (max-width: 24.5rem) {
  .brand { font-size: 1.05rem; gap: .4rem; }
  .brand-icon { width: 1.5rem; height: 1.5rem; }
}
/* The smallest phones still in use (320px: iPhone SE 1st gen, folded
   handsets). Everything gives a little more rather than the wordmark going. */
@media (max-width: 20rem) {
  .top { padding: .75rem .6rem; }
  .brand { font-size: .95rem; }
  .brand-icon { width: 1.35rem; height: 1.35rem; }
}
/* Opaque, deliberately, and this was tested rather than assumed. The header's
   own recipe is a translucent fill plus a backdrop blur, and copying it here
   looked right in the abstract: the menu would read as the header extending
   downward. In practice it was not readable. The header is a thin bar sitting
   over mostly empty page, but this panel lands over body copy, and rendered at
   92%, 96% and even 98% the headline and paragraphs behind it ghosted through
   the menu labels. Legibility wins over the effect. The blur went with it,
   since a backdrop-filter under an opaque fill renders nothing and would just
   be a cost every scroll frame for no visible result.
   The hairline plus the float shadow are what separate it from the page now. */
.nav-more-panel {
  position: absolute; right: 0; top: calc(100% + .6rem); z-index: 60;
  display: flex; flex-direction: column; gap: .15rem; min-width: 13rem;
  padding: .55rem;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  box-shadow: var(--elevation-float);
}
/* Centred, and pill-shaped rather than square-cornered: with the text centred
   a rounded-rect hover reads as a stray box, where a full-width pill matches
   the pills the desktop nav and every button on the site already use. */
.nav-more-panel a {
  display: flex; align-items: center; justify-content: center;
  min-height: 2.75rem;
  padding: .5rem 1rem; border-radius: var(--radius-pill);
  font-size: .95rem; text-align: center;
}
.nav-more-panel a:hover { background: var(--sage-wash); color: var(--ink); }
.nav-more-panel a[aria-current] { background: var(--sage-wash); color: var(--sage-deep); }
/* Open state on the button itself, so the trigger looks pressed while its
   panel is showing rather than the panel appearing from nothing. */
.nav-more[open] > summary { color: var(--ink); border-color: var(--sage); background: var(--sage-wash); }
/* The panel arrives rather than appearing. Scoped below the desktop
   breakpoint, because above it this same element is the inline nav row and has
   no business animating on load. No reduced-motion guard needed here: the
   global rule at the top of this file already clamps every animation-duration
   with !important. */
@media (max-width: 59.99rem) {
  @keyframes navMenuIn {
    from { opacity: 0; transform: translateY(-.4rem) scale(.98); }
    to { opacity: 1; transform: none; }
  }
  .nav-more[open] .nav-more-panel {
    animation: navMenuIn var(--dur-quick) var(--ease);
    transform-origin: top right;
  }
  /* A quiet scrim while the menu is open, so the panel reads as the one live
     layer and the page behind it as paused. 8% ink, not a modal blackout.
     It is part of .nav-more, so a tap on it targets the <details> itself -
     theme.js treats that as an outside click and closes the menu. The
     summary lifts above it so the button stays crisp while open. */
  .nav-more[open]::before {
    content: ""; position: fixed; inset: 0; z-index: 55;
    background: color-mix(in srgb, var(--ink) 8%, transparent);
  }
  .nav-more[open] > summary { position: relative; z-index: 60; }
}
/* Desktop: no menu - the links sit inline in the header row.
   Everything here is scoped to [open], and theme.js is what adds that
   attribute at the same breakpoint. That scoping is deliberate: a closed
   <details> hides its non-summary children, so the worst case without JS is a
   desktop visitor seeing the same Menu button phones get, which is plainer but
   not broken.

   These used to be `display: contents` on both the details and the panel, and
   that shipped a live bug: the header rendered 160px tall with the links
   stacked in three rows on every page at every desktop width. Chromium does not
   account for children hoisted through `display: contents` when it computes a
   flex container's max-content size, so `.top nav` measured its own intrinsic
   width as ~232px (about the width of the CTA pill alone), and the real links
   then overflowed that and wrapped.

   Two fixes that look obvious do NOT work, both tested against a production
   build before landing this: `flex: 1 1 0%` on `.top nav`, and `flex-grow: 1`
   with `flex-basis: 0`. Growing the nav does not help, because the bad size is
   computed inside the hoisted subtree, not from the nav's own width.

   Real boxes are the fix. Keeping the details and the panel as actual flex
   containers means their contents are measured normally. Do not reintroduce
   `display: contents` here. */
@media (min-width: 60rem) {
  .nav-more[open] { display: flex; align-items: center; }
  .nav-more[open] > summary { display: none; }
  .nav-more[open] .nav-more-panel {
    display: flex; flex-direction: row; align-items: center; gap: .35rem;
    position: static; padding: 0; border: 0;
    background: none; box-shadow: none; min-width: 0;
  }
  .nav-more[open] .nav-more-panel a { padding: .4rem .7rem; border-radius: var(--radius-pill); font-size: .95rem; white-space: nowrap; min-height: 0; }
}

/* ---------- typography ----------
   Headings sit at weight 400, matching the member webapp (whose headings
   inherit 400 Newsreader). At display sizes the optical-size axis carries the
   presence that weight used to, and the lighter cut is the single biggest
   part of the app's calmer feel. */
h1, h2, h3, h4 { font-family: var(--display); font-optical-sizing: auto; line-height: 1.15; color: var(--ink); letter-spacing: -.014em; }
h1 { font-size: var(--text-title); font-weight: 400; line-height: 1.25; margin: .6rem 0 .8rem; }
h2 { font-size: clamp(1.5rem, 3.4vw, 2rem); font-weight: 400; margin: 3.4rem 0 .8rem; }
h3 { font-size: 1.1875rem; font-weight: 400; margin: 1.9rem 0 .45rem; }
h1, h2, h3 { text-wrap: balance; }
p { text-wrap: pretty; }
::selection { background: var(--sage-tint); color: var(--ink); }
/* Section label: the editorial spine the homepage bands lacked. Sits tight
   above its h2 (the pair reads as one unit); normal rhythm is preserved
   above the eyebrow itself. 11px/.12em is the app's overline spec. */
.eyebrow {
  font-family: var(--head); font-size: .6875rem; font-weight: 500; letter-spacing: .12em;
  text-transform: uppercase; color: var(--muted); margin: 3.4rem 0 0;
}
.eyebrow + h2 { margin-top: .4rem; }
h4 { font-family: var(--head); font-size: 1rem; font-weight: 600; margin: 1.4rem 0 .3rem; }
p { margin: 0 0 1.15rem; }
main a { color: var(--forest); text-decoration-thickness: 1px; text-underline-offset: .18em; text-decoration-color: color-mix(in srgb, var(--forest) 38%, transparent); transition: text-decoration-color .18s; }
main a:hover { text-decoration-color: var(--forest); }
strong { font-weight: 600; color: var(--ink); }
hr { border: 0; height: 1px; background: linear-gradient(90deg, transparent, var(--line), transparent); margin: 3rem 0; }

/* One global focus rule, the app's. A real outline rather than a box-shadow
   ring, so it follows each control's own border-radius automatically instead
   of needing a per-component radius override (which is what the old
   box-shadow approach forced, including a hack on .library-search input). */
:focus-visible { outline: 2px solid var(--ring); outline-offset: 2px; }
.skip {
  position: absolute; left: .5rem; top: -3rem; z-index: 100;
  padding: .6rem 1.1rem; background: var(--surface); color: var(--sage-deep);
  border: 1px solid var(--line); border-radius: var(--radius-pill);
  font-family: var(--head); font-weight: 500; transition: top .15s;
}
.skip:focus { top: .5rem; box-shadow: var(--ring); }

/* No edge bar (the left-accent style is retired sitewide, 29 Jul 2026): a
   quotation is marked by its indent and by speaking in the serif voice at a
   size body copy never uses, the classic editorial treatment. */
blockquote {
  margin: 1.8rem 0; padding: 0 0 0 1.6rem;
  font-family: var(--serif); font-size: 1.25rem; line-height: 1.55;
  font-style: italic; font-weight: 500;
  font-optical-sizing: auto; color: var(--ink-soft);
}
blockquote p { margin: 0; }

/* quote-lift line (I-030).
   One rule for both themes: a full-height translucent wash. The light theme
   used to run a 42%-height solid band positioned at 82%, which crossed the
   lower x-height and made every lifted sentence read as STRUCK THROUGH -
   "Not fixed. Not bypassed. Held." looked deleted, and the crisis page's
   closing comfort line looked retracted. All three design audits flagged it
   independently (28 Jul 2026). Dark mode had already been fixed to the wash;
   light now matches, and the dark overrides became redundant and are gone. */
mark.lift {
  background: color-mix(in srgb, var(--sage) 32%, transparent);
  color: var(--ink); padding: 0 .12em; border-radius: 2px;
}

/* ---------- homepage hero ----------
   The gradient is the app's landing hero, ported exactly: a radial lilac pool
   over a linear rose fade, both falling away to let the paper ground show
   through. It is two layers, not one linear gradient - the pool is what gives
   the top of the page its soft centre. Full-bleed via the same 100vw trick
   .band uses. */
.hero {
  text-align: center; padding: 3.5rem 1.35rem 2.5rem;
  width: 100vw; position: relative; left: 50%; right: 50%;
  margin-left: -50vw; margin-right: -50vw; margin-top: -2rem;
  background:
    radial-gradient(120% 90% at 50% 0%, var(--lilac-wash), transparent 62%),
    linear-gradient(180deg, var(--rose-wash), transparent);
}
.hero > * { max-width: var(--measure); margin-left: auto; margin-right: auto; }
.hero-mark { display: block; margin: 0 auto 1.5rem; color: var(--sage-deep); }
.hero-mark .tear { fill: var(--rose); }
.hero h1 {
  /* Site-only masthead scale, deliberately NOT the shared --text-display
     token: that caps at 2.375rem, which on a 1200px viewport rendered the
     one moment of arrival at ~38px - a section heading, not a masthead.
     Weight stays 400; Newsreader's optical axis carries the size. The app
     is untouched (raising the token would grow its hero too). */
  font-size: clamp(2.1rem, 1.3rem + 3.6vw, 3.4rem);
  font-weight: 400; letter-spacing: -.01em; line-height: 1.1;
  margin: 0 auto .9rem; max-width: 20ch; text-wrap: balance;
}
.hero h1 em { font-style: italic; color: var(--sage-deep); }
.hero .sub { font-size: 1.125rem; line-height: 1.625; color: var(--muted); max-width: 42ch; margin: 0 auto 1.6rem; text-wrap: balance; }
.hero .cta-row { display: flex; gap: .7rem; justify-content: center; flex-wrap: wrap; }
.hero-divider { display: flex; align-items: center; gap: 1rem; color: var(--faint); margin: 2.5rem 0 1rem; }
.hero-divider::before, .hero-divider::after { content: ""; flex: 1; height: 1px; background: var(--line); }
/* The one moment of brand delight: the tear wells up powder-blue at the lid,
   hangs, then falls and blushes rose. Ported from the app's mark-live. Runs
   once on load; reduced-motion skips it entirely (the global rule zeroes the
   duration, and this belt-and-braces block stops it animating at all). */
@keyframes tear-drip {
  0%   { opacity: 0; transform: translateY(-11px) scale(.25); fill: var(--powder); }
  30%  { opacity: 1; transform: translateY(-9px) scale(.5); fill: var(--powder); }
  55%  { opacity: 1; transform: translateY(-8px) scale(.55); fill: var(--powder); }
  80%  { transform: translateY(-1.5px) scale(.92); fill: var(--rose-tint); }
  100% { opacity: 1; transform: none; fill: var(--rose); }
}
.mark-live .tear {
  transform-box: fill-box; transform-origin: 50% 0%;
  animation: tear-drip 1.8s var(--ease) .4s both;
}
@media (prefers-reduced-motion: reduce) {
  .mark-live .tear { animation: none; fill: var(--rose); }
}

/* ---------- article furniture ---------- */
.crumbs { font-family: var(--head); font-size: .82rem; letter-spacing: .01em; color: var(--ink-soft); margin: 0 0 .3rem; }
.crumbs a { color: var(--ink-soft); text-decoration: none; }
.crumbs a:hover { color: var(--forest); }
.dateline { font-family: var(--head); font-size: .82rem; color: var(--ink-faint); margin: 0 0 1.6rem; }
/* Wash panels from here down. The app's composition rule: a tinted panel is a
   flat hue wash with NO border and NO shadow; only raised cards get elevation,
   and they get no border in return. The 4px sage bar this block carried was
   the last left-edge accent on the site (a style the owner had already
   rejected elsewhere); the small label now does the "this is the answer"
   job the bar was doing. */
.answer {
  background: var(--sage-wash); border: 0;
  border-radius: var(--radius); padding: 1.05rem 1.35rem 1.15rem; margin: 0 0 2rem;
  font-size: 1.05rem; box-shadow: none;
}
.answer p { margin: 0; }
.answer .answer-label {
  font-family: var(--head); font-size: .68rem; font-weight: 600;
  letter-spacing: .12em; text-transform: uppercase;
  color: var(--sage-deep); margin: 0 0 .3rem;
}
.lede { font-size: 1.15rem; line-height: 1.6; color: var(--muted); font-family: var(--serif); }

.author {
  margin: 3rem 0 0; padding: 1.25rem 1.4rem; display: flex; gap: 1rem; align-items: flex-start;
  background: var(--sage-wash); border: 0; border-radius: var(--radius);
  font-size: .93rem; color: var(--muted);
}
.author::before {
  content: "R"; flex: 0 0 auto; width: 2.6rem; height: 2.6rem; border-radius: 50%;
  display: grid; place-items: center; font-family: var(--display); font-weight: 400; font-size: 1.2rem;
  color: var(--paper); background: var(--sage-deep);
}
.author p { margin: 0; }
/* The homepage's closing use of the card: full body size, on the plain band. */
.author--close { margin-top: 0; font-size: 1rem; color: var(--ink-soft); }

/* CTA pathway: the one place a lilac wash appears inside an article, so the
   ask reads as a different kind of block from the reading around it. */
.pathway {
  margin: 2.4rem 0; padding: 1.7rem 1.8rem;
  background: var(--lilac-wash);
  border: 0; border-radius: var(--radius-lg); box-shadow: none;
}
.pathway h2 { margin: 0 0 .35rem; font-size: 1.25rem; }
.pathway p { margin: 0 0 .9rem; color: var(--muted); }
/* Crisis resources: clay is the app's heavy-day hue. Never sage, which is the
   commercial/action colour everywhere else. */
.pathway.resources { background: var(--clay-wash); }
/* The compact letter capture that rides under an offering end-cap. Smaller
   and on the plain ground, so the offering keeps the lilac card and the
   letter reads as a quiet second door rather than a second pitch. */
.pathway-mini {
  margin-top: -1.2rem; padding: 1.1rem 1.35rem;
  background: transparent; border: 1px solid var(--line); border-radius: var(--radius);
}
.pathway-mini p { font-size: .95rem; }

/* The quiet in-page nav on long offer pages: plain anchors in a single line,
   so a decided visitor reaches the price or the form in one tap instead of
   6,000px of scroll. Deliberately not chips or pills - it is wayfinding, not
   an offer. */
.page-jumps { margin: 1.1rem 0 0; font-size: .9rem; color: var(--faint); }
/* Only /groupprogram/ uses this component today, so it carries that page's
   rose accent directly rather than sage. If a second "offer page" ever
   adopts .page-jumps, give it a scoped override instead of changing this
   rule back - don't let the two pages fight over one selector's colour. */
.page-jumps a { color: var(--rose-deep); text-decoration: none; }
.page-jumps a:hover { text-decoration: underline; text-underline-offset: 3px; }

.capture { display: flex; flex-wrap: wrap; gap: .6rem; }
.capture + p { margin-top: 1.15rem; }
/* ---------- one input recipe ----------
   Every text field on the site - the letter capture, the library search, the
   application form - is the same object: the app's recipe. The fill is
   --paper, which on a white card reads as the warm ground showing through,
   not a pink; the hairline is low-contrast enough to read borderless until
   focus moves it to sage. This used to be three separately-maintained copies
   that had already drifted on padding and font-size; the identity is one rule
   now, and only the layout (flex sizing, widths) stays contextual. Focus
   keeps the global :focus-visible ring - two of the old copies set
   outline:none on :focus, which silently removed the keyboard ring. */
.input,
.capture input,
.library-search input,
.apply input[type="text"], .apply input[type="email"], .apply textarea, .apply select,
/* The intake form's own top-level fields (name/location/email/phone/heardAbout)
   never picked up this rule - only .apply did, plus the narrower
   .intake-loss-row rule for the repeatable loss fields - so they rendered as
   bare, unstyled browser defaults next to everything else on the page. */
.intake input[type="text"], .intake input[type="email"], .intake input[type="tel"], .intake select {
  font-family: var(--body); font-size: .95rem; padding: .75rem 1rem;
  border: 1px solid var(--line); border-radius: var(--radius-sm);
  background: var(--paper); color: var(--ink);
  transition: border-color var(--dur-quick) var(--ease);
}
.input::placeholder, .capture input::placeholder, .library-search input::placeholder { color: var(--faint); }
.input:focus, .capture input:focus, .library-search input:focus,
.apply input:focus, .apply textarea:focus, .apply select:focus,
.intake input:focus, .intake select:focus { border-color: var(--rose); }
/* min-width:0 is load-bearing, not tidying. An <input> carries an intrinsic
   minimum width, so a flex item with a 15rem basis refuses to shrink below
   it and runs off the side of a narrow screen at a large font setting,
   where body's overflow-x:clip hides the scroll and just cuts it off. */
.capture input { flex: 1 1 15rem; min-width: 0; }
/* Every interactive control is a pill. Primary is solid sage-deep with paper
   text (not pure white - the app uses the ground colour, which sits softer).
   Press feedback is a scale, not a lift, so a button never casts a shadow it
   didn't have at rest. */
.capture button, .btn {
  display: inline-flex; align-items: center; justify-content: center; gap: .4rem;
  padding: .65rem 1.5rem; font-size: .95rem; font-family: var(--head); font-weight: 500;
  background: var(--sage-deep); color: var(--paper); border: 0; border-radius: var(--radius-pill);
  cursor: pointer; text-decoration: none;
  transition: opacity var(--dur-quick) var(--ease), transform var(--dur-quick) var(--ease);
  box-shadow: var(--elevation-card);
}
.capture button:hover, .btn:hover { opacity: .9; }
.capture button:active, .btn:active { transform: scale(.97); }
.capture-done { font-family: var(--head); font-weight: 500; color: var(--sage-deep); margin: .3rem 0; }
/* Workshop registration (14 Oct 2026): the capture row plus one optional, unticked box.
   The box sits inside a label, so the one-input recipe above (padding, border, fill) would
   otherwise style the checkbox itself; reset it to a plain native control. */
.capture.register .register-opt { flex: 1 1 100%; display: flex; gap: .65rem; align-items: flex-start; font-size: .9rem; line-height: 1.5; color: var(--ink); cursor: pointer; }
.capture.register .register-opt input { flex: none; width: 1.15rem; height: 1.15rem; min-width: 0; margin: .2rem 0 0; padding: 0; border: 0; background: none; accent-color: var(--sage-deep); }
/* Group Program page only: a quiet floating Apply button (site-ui house law -
   calm over conversion, invites once, no badges/counters). `.btn` gives the
   look; this just handles position and the show/hide state theme.js toggles.
   Hidden by default so a no-JS visitor never sees a link that goes nowhere
   useful this early - the real in-page Apply section still works either way. */
.sticky-apply {
  position: fixed; right: 1.1rem; bottom: 1.1rem; z-index: 60;
  opacity: 0; transform: translateY(.5rem); pointer-events: none;
  transition: opacity var(--dur-quick) var(--ease), transform var(--dur-quick) var(--ease);
}
.sticky-apply.is-visible { opacity: 1; transform: translateY(0); pointer-events: auto; }
@media (max-width: 30rem) {
  .sticky-apply { left: 1.1rem; right: 1.1rem; text-align: center; justify-content: center; }
}
/* Both buttons on this page - the floating Apply pill and the form's own
   submit - carry the page's rose accent instead of the sitewide sage .btn,
   matching the price cards and form controls above. Scoped so no other
   page's buttons are touched. */
.sticky-apply, .apply .btn { background: var(--rose-deep); }
/* Once the flex row has wrapped (the input's 15rem basis plus the button no
   longer fit), the button was a stray pill hanging under a full-width field.
   Full width matches the input above it and roughly doubles the tap target
   on the one module that asks for a conversion. 30rem ~= where wrapping
   starts inside the measure's padding at default font sizes. */
@media (max-width: 30rem) {
  .capture button { flex: 1 1 100%; }
}
/* THROUGH (the Group Program page, renamed 29 Oct 2026): the transparent wordmark is the page's
   h1 (alt="THROUGH"), the "by Happy Grieving and The Global Resilience Project" line sits under
   it, and the "next group starts" line is filled in by theme.js. */
.through-title { margin: 0 0 1rem; line-height: 0; }
.through-title img { display: block; width: 100%; max-width: 30rem; height: auto; margin: 0 auto; }
/* The transparent wordmark is dark green, so dark mode gets a lightened copy (same shapes, lifted
   toward ivory); the page's own theme switch and the OS setting both choose between the two. */
.through-logo--dark { display: none !important; }
:root[data-theme="dark"] .through-logo--light { display: none !important; }
:root[data-theme="dark"] .through-logo--dark { display: block !important; }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .through-logo--light { display: none !important; }
  :root:not([data-theme="light"]) .through-logo--dark { display: block !important; }
}
.through-by { text-align: center; margin: 0 0 1.5rem; }
/* The free workshop page carries the THROUGH wordmark too (Rachel, 29 Oct 2026), smaller and linked
   to the program; the palette below follows it there. */
.through-mark { display: flex; align-items: center; gap: .9rem; flex-wrap: wrap; margin: 0 0 1.5rem; }
.through-mark a { flex: 0 0 auto; line-height: 0; }
.through-mark img { display: block; width: 11rem; max-width: 100%; height: auto; border-radius: .75rem; }
.through-mark span { flex: 1 1 12rem; min-width: 0; font-size: .95rem; line-height: 1.45; color: var(--muted); }
.next-start { margin: 1.25rem 0 .75rem; font-size: 1rem; line-height: 1.5; }
.next-start strong { display: block; font-family: var(--head); font-weight: 600; color: var(--ink); }
.next-start span { display: block; font-size: .9rem; color: var(--muted); }
.next-start-links { margin: 0 0 1rem; }
.btn-row { display: flex; flex-wrap: wrap; gap: .6rem; margin: 1rem 0 .4rem; }
/* THROUGH palette (Rachel, 29 Oct 2026): Deep Forest Green #344A38, Muted Sage #8F9D7C, Soft Olive
   #A5AE88, Light Sage #C0C8AA, Warm Ivory #F7F4EC, and Golden Ochre #D4B05A as the accent for CTAs.
   THROUGH is a sub-brand, so the palette lives here, scoped to its page, and not in the shared
   brand/tokens.css (the way Grief Trips keeps its own accents). Light mode only: dark mode keeps the
   site's own dark tokens, which the contrast gate tuned. Forest on Ochre is 4.65:1 and Forest on
   Light Sage 5.5:1, both AA for normal text; white on Ochre is 2.1:1, so the CTA text is Forest. */
:root[data-theme="light"] .page:has(:is(.through-title, .through-mark)) {
  --sage-wash: color-mix(in srgb, #C0C8AA 50%, #F7F4EC); --sage-tint: #C0C8AA; --sage: #A5AE88; --sage-deep: #344A38;
  --ochre-wash: color-mix(in srgb, #D4B05A 30%, #F7F4EC); --ochre: #D4B05A;
}
@media (prefers-color-scheme: light) {
  :root:not([data-theme="dark"]) .page:has(:is(.through-title, .through-mark)) {
    --sage-wash: color-mix(in srgb, #C0C8AA 50%, #F7F4EC); --sage-tint: #C0C8AA; --sage: #A5AE88; --sage-deep: #344A38;
    --ochre-wash: color-mix(in srgb, #D4B05A 30%, #F7F4EC); --ochre: #D4B05A;
  }
}
/* No red on this page (Rachel, 29 Oct 2026: "Red accent becomes green"): the form's selected and
   focus states, the jump links and the like read the site's rose steps, so here they read its
   green ones instead, in light and in dark (each mode's own sage steps; the palette above
   overrides them in light). */
.page:has(.through-title) { --rose-wash: var(--sage-wash); --rose-tint: var(--sage-tint); --rose: var(--sage); --rose-deep: var(--sage-deep); }
/* The Apply band is flat yellow here, not the site's marbled gradient (Rachel, 29 Oct 2026). */
.page:has(.through-title) .band--tint { background: var(--ochre-wash); }
.page:has(.through-title) :is(.sticky-apply, .apply .btn) { background: #D4B05A; color: #344A38; font-weight: 600; }
/* The free workshop page wears THROUGH too (Rachel, 29 Oct 2026: "Yes use the THROUGH colours on the
   workshop page too"): the palette above, the two sign-up boxes on a sage wash instead of lilac, and
   both buttons in Ochre with Forest text, like the program page's apply button. */
.page:has(.through-mark) .pathway { background: var(--sage-wash); }
.page:has(.through-mark) .capture button { background: #D4B05A; color: #344A38; font-weight: 600; }
.btn.ghost {
  background: color-mix(in srgb, var(--surface) 70%, transparent); color: var(--ink);
  border: 1px solid var(--line); box-shadow: none;
}
.btn.ghost:hover { border-color: var(--sage); opacity: 1; }
/* Third tier: tertiary actions (e.g. "browse the whole library" at the foot
   of every article) that deserve a touch more weight than a bare inline
   link but shouldn't compete with a real button. Explicitly resets the pill
   radius and shadow it would otherwise inherit from .btn. */
.btn.quiet {
  background: transparent; color: var(--sage-deep); padding: 0; font-weight: 500;
  border-radius: 0; box-shadow: none;
  text-decoration: underline; text-decoration-color: color-mix(in srgb, var(--sage-deep) 38%, transparent);
  text-underline-offset: .18em;
}
.btn.quiet:hover, .btn.quiet:active { background: transparent; box-shadow: none; transform: none; opacity: 1; text-decoration-color: var(--sage-deep); }
/* Dark mode needs no button override any more: --sage-deep inverts to the
   light, text-capable tone and --paper flips to the dark ground, so the
   primary button reads correctly in both themes from the same declaration.
   That retires the old asymmetric block (and its hardcoded #23272F). */

/* ---------- shared card primitive ----------
   One resting/hover pattern for every "list item as card" surface (article
   TOC list, offerings row, library clusters, related reading, prev/next).
   These five used to disagree on resting elevation, hover distance/direction
   and border-radius (a homepage scroll could mix flat and floating cards in
   the same view); now they compose one rule. `.card` is also a free-standing
   class for anything new that wants the same surface. */
.card, .toc li, .offerings li, .cluster, .related li a {
  background: var(--surface); border: 0; border-radius: var(--radius);
  box-shadow: var(--elevation-card);
  transition: box-shadow var(--dur-quick) var(--ease), transform var(--dur-quick) var(--ease);
}
/* Float is the app's raised-on-interaction elevation; the lift is half a step
   so a grid of cards never feels jumpy. */
.card:hover, .toc li:hover, .offerings li:hover, .cluster:hover, .related li a:hover {
  box-shadow: var(--elevation-float); transform: var(--lift);
}
/* Dark mode: the wash panels and the paper ground sit close enough in value
   that a borderless raised card can dissolve into whatever is behind it -
   the hotline rows already carried this fix. Light stays borderless per the
   elevation doctrine (raised = surface + shadow, no border). */
:root[data-theme="dark"] :is(.card, .toc li, .offerings li, .cluster, .related li a, .price, .bonuses li, .apply) { border: 1px solid var(--line); }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) :is(.card, .toc li, .offerings li, .cluster, .related li a, .price, .bonuses li, .apply) { border: 1px solid var(--line); }
}
/* .price--best is the one raised-family surface that already carries its own
   edge (the 2px sage outline); a hairline under that reads as a double rim. */
:root[data-theme="dark"] .price--best { border: 0; }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .price--best { border: 0; }
}

.related { margin-top: 3rem; border-top: 1px solid var(--line); padding-top: 1rem; }
.related h2 { font-size: 1.15rem; margin-top: .6rem; }
.related ul { margin: 0; padding: 0; list-style: none; display: grid; gap: .5rem; }
/* Same card grammar as .toc: display-serif title, body-sans description.
   These were sans titles with no description - the one card row on the site
   that missed the shared treatment. The first item is the editorial pick
   (build.mjs orders `related` by the author's front matter), so it leads
   with a touch more size, .toc.feature-style. */
.related li a { display: block; padding: .8rem 1.05rem; text-decoration: none; color: var(--ink); font-family: var(--display); font-weight: 500; font-size: 1.15rem; line-height: 1.3; }
.related li a span { display: block; font-family: var(--body); font-size: .92rem; font-weight: 400; color: var(--ink-soft); margin-top: .25rem; line-height: 1.5; }
.related li.feature a { font-size: 1.18rem; padding: 1.05rem 1.2rem; }

.crisis-inline { margin-top: 1.8rem; font-size: .9rem; }
.crisis-inline a { color: var(--ink-soft); }

/* Hotline rows on /when-it-hurts-too-much/. Every line is one full-width
   tel:/sms: link - a person in crisis on a phone taps the row, they never
   memorise and retype a number. The whole row is the target (>=48px), the
   number sits right in the display face, and there is deliberately no hover
   lift: these are not cards to browse, they are buttons to press once.
   Dark gets a hairline because surface-on-paper barely separates there and
   this is the one page where an invisible edge could cost a tap. */
.hotlines { list-style: none; margin: 1rem 0 1.8rem; padding: 0; display: grid; gap: .7rem; }
.hotlines a {
  /* wrap, because the number must NEVER be clipped: at a large font setting
     on a narrow phone the nowrap number plus the name can exceed the row,
     and body's overflow-x:clip would silently cut the number off - on this
     page that is the whole ballgame. Wrapped, the number drops to its own
     line inside the same tap target instead. */
  display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: .35rem 1rem;
  min-height: 3rem; padding: .95rem 1.15rem;
  background: var(--surface); border-radius: var(--radius);
  box-shadow: var(--elevation-card);
  text-decoration: none; color: var(--ink);
}
.hotline-name { display: block; font-family: var(--head); font-weight: 500; font-size: .98rem; }
.hotline-note { display: block; font-size: .85rem; color: var(--muted); margin-top: .15rem; line-height: 1.45; }
.hotline-num {
  font-family: var(--display); font-optical-sizing: auto; font-size: 1.25rem;
  white-space: nowrap; color: var(--sage-deep); flex-shrink: 0;
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .hotlines a { border: 1px solid var(--line); }
}
:root[data-theme="dark"] .hotlines a { border: 1px solid var(--line); }

/* article table of contents */
.article-toc {
  margin: 0 0 2rem; padding: 1rem 1.25rem; background: var(--sage-wash);
  border: 0; border-radius: var(--radius);
}
.article-toc .toc-label {
  font-family: var(--head); font-size: .78rem; font-weight: 600; letter-spacing: .1em;
  text-transform: uppercase; color: var(--ink-faint); margin: 0 0 .5rem;
}
.article-toc ul { margin: 0; padding: 0; list-style: none; display: grid; gap: .3rem; }
.article-toc a { font-family: var(--head); font-size: .95rem; color: var(--ink-soft); text-decoration: none; }
.article-toc a:hover { color: var(--forest); }
/* Above ~60rem, float the TOC into the empty margin as a sticky rail instead
   of an inline box pushing the article down. Scoped to main:has(.article-toc)
   so a short article with no TOC never gets a dead empty 14rem gap - it just
   keeps the single-column layout it always had. */
@media (min-width: 60rem) {
  main:has(.article-toc) { max-width: 58rem; }
  main:has(.article-toc) .article-layout {
    display: grid; grid-template-columns: 14rem 1fr; gap: 2.5rem; align-items: start;
  }
  main:has(.article-toc) .article-toc { position: sticky; top: var(--header-clear); margin: 0; }
}

/* in-cluster prev / next. Demoted from cards (29 Jul 2026): with "Read this
   next" now a real card row above it, two more card surfaces here made the
   article's tail read as three competing decks. These are quiet directional
   links now - the editorial pick gets the cards, the sequence gets a line. */
.prevnext {
  display: grid; grid-template-columns: 1fr 1fr; gap: .8rem; margin-top: 2.6rem;
  border-top: 1px solid var(--line); padding-top: 1.4rem;
}
.prevnext .pn {
  display: flex; flex-direction: column; gap: .2rem; padding: .45rem 0;
  text-decoration: none; color: var(--ink-soft); font-family: var(--head); font-weight: 500; font-size: .95rem;
  transition: color var(--dur-quick) var(--ease);
}
.prevnext .pn:hover { color: var(--forest); }
.prevnext .pn.next { text-align: right; }
.prevnext .pn span { font-family: var(--head); font-size: .78rem; font-weight: 600; letter-spacing: .06em; text-transform: uppercase; color: var(--ink-faint); }
@media (max-width: 34rem) { .prevnext { grid-template-columns: 1fr; } .prevnext .pn.next { text-align: left; } }
.more-nav { margin-top: 1.6rem; font-size: .9rem; color: var(--ink-soft); }
.more-nav a { color: var(--forest); }

/* ---------- lists as cards (library, pillars, latest) ---------- */
.toc { list-style: none; margin: 1.2rem 0 1.4rem; padding: 0; display: grid; gap: .7rem; }
/* Two-up above tablet width: six situation doors (home) or a 13-piece pillar
   list as one full-width column read as a ribbon to scan past, not doors to
   choose between. The featured card keeps the full row and its lead. */
@media (min-width: 44rem) {
  .toc { grid-template-columns: 1fr 1fr; gap: .8rem; }
  .toc li.feature { grid-column: 1 / -1; }
}
.toc li { padding: 1rem 1.15rem; }
/* Card titles are the display serif, like every other title on the page.
   They were --head, which put a 1.18rem sans line directly under a Newsreader
   h2 on the home page and read as a heading in the wrong face. The
   descriptions stay in the body sans: they are body copy, not titles. */
.toc li a { font-family: var(--display); font-weight: 500; font-size: 1.15rem; line-height: 1.3; text-decoration: none; display: block; color: var(--ink); }
.toc li span { display: block; font-size: .92rem; color: var(--ink-soft); margin-top: .25rem; line-height: 1.5; }
/* One lead item, not six equally-weighted rows: a gold hairline top edge
   (the editorial accent, used once here - see brand/BRAND-BOOK.md) and a
   touch more room for the title to breathe. */
.toc li.feature { padding: 1.5rem 1.6rem; border-top: 3px solid var(--ochre); }
.toc li.feature a { font-size: 1.18rem; }
.toc li.feature span { font-size: .98rem; }

/* ---------- library: families + clusters ---------- */
.library-index > .lede { max-width: 46rem; }
.library-search { margin: 1.6rem 0 0; }
/* Recipe comes from the shared input rule; only the search's own scale here. */
.library-search input { width: 100%; padding: .9rem 1.15rem; font-size: 1rem; }
.library-search-status { color: var(--muted); margin: 1rem 0 0; }
/* Jump-nav: pure anchors, sticky just below the site header (--header-h) so
   a visitor can pick a family instead of scrolling past four they don't need.
   scroll-margin-top on .family clears BOTH sticky bars for the anchor jump
   (--stack-clear); that's scoped to the library page only, not the global
   html scroll-padding-top used by article TOCs. */
.family-nav {
  position: sticky; top: var(--header-h); z-index: 40;
  /* One horizontally scrolling row on phones. The chips used to wrap to
     three rows (~176px), which with the header made 29% of a phone viewport
     permanent chrome, and silently broke every anchor jump: --family-nav-h
     in :root assumes the ONE-row height and the sticky offsets derive from
     it. nowrap makes that number true at every width. The scrollbar is
     hidden and the right edge fades so the overflow announces itself. */
  display: flex; flex-wrap: nowrap; gap: .5rem;
  overflow-x: auto; -webkit-overflow-scrolling: touch; scrollbar-width: none;
  mask-image: linear-gradient(90deg, #000 94%, transparent);
  -webkit-mask-image: linear-gradient(90deg, #000 94%, transparent);
  padding: .8rem 0; margin: 1.4rem 0 0;
  background: color-mix(in srgb, var(--paper) 92%, transparent);
  backdrop-filter: blur(8px); -webkit-backdrop-filter: blur(8px);
  border-bottom: 1px solid var(--line);
}
.family-nav::-webkit-scrollbar { display: none; }
@media (min-width: 60rem) {
  .family-nav { flex-wrap: wrap; overflow-x: visible; mask-image: none; -webkit-mask-image: none; }
}
/* Scrollspy state (set by theme.js): the family currently in view. */
.family-nav a[aria-current] { color: var(--sage-deep); border-color: var(--sage); background: var(--sage-wash); }
/* Chips, the app's unselected form: hairline border, muted text, hue border on
   hover. The explicit line-height plus generous padding is load-bearing - it
   is what keeps the tap target at 44px without relying on inherited leading.
   `white-space: nowrap` was here and is gone: it kept a label on one line at
   any cost, so at a large Android font setting "Grief and the rest of your
   life" ran past the screen edge and got clipped by the body's overflow-x.
   A two-line chip is worse-looking than a one-line chip and much better than
   half a chip. max-width keeps it inside the column either way. */
.family-nav a {
  display: inline-flex; align-items: center; line-height: 1;
  font-family: var(--head); font-size: .85rem; font-weight: 500; text-decoration: none;
  color: var(--muted); background: transparent; border: 1px solid var(--line);
  /* nowrap is safe again: the BAR scrolls horizontally now, so a long label
     widens its chip into the scroll instead of running off a clipped page
     (the bug that removed nowrap on 29 Jul). flex-shrink: 0 stops the
     nowrap flex row crushing chips to min-content and stacking the words. */
  border-radius: var(--radius-pill); padding: .9rem 1.1rem;
  white-space: nowrap; flex-shrink: 0;
  transition: color var(--dur-quick) var(--ease), border-color var(--dur-quick) var(--ease), background var(--dur-quick) var(--ease);
}
.family-nav a:hover { color: var(--sage-deep); border-color: var(--sage); background: var(--sage-wash); }
.family {
  margin-top: 3rem; scroll-margin-top: var(--stack-clear);
  border-top: 1px solid var(--line); padding-top: 1.7rem;
}
.family:first-of-type { margin-top: 1.8rem; }
/* Family header: a divider above, an index line, the title, a short accent
   rule under it. The green tab that used to sit beside the title is gone at
   the owner's request (29 Jul 2026).
   The structure here is meant to be information rather than furniture. The
   meta line says how big the section is, which is the part a reader genuinely
   cannot see: the cluster cards below cap their lists at six, so nothing else
   on the page reveals that this family holds 43 pieces. Counts are computed
   in build.mjs, never typed. */
/* Each family borrows one hue from the existing palette, on the meta line
   and the short rule ONLY - the tokens' "wayfinding by colour" doctrine,
   finally used. Deep steps are the machine-tuned text-safe tones, so AA
   holds in both themes. No left tabs, no card washes, per the owner. */
.family--feel { --fam: var(--powder); --fam-deep: var(--powder-deep); --fam-wash: var(--powder-wash); }
.family--time { --fam: var(--ochre);  --fam-deep: var(--ochre-deep);  --fam-wash: var(--ochre-wash); }
.family--who  { --fam: var(--rose);   --fam-deep: var(--rose-deep);   --fam-wash: var(--rose-wash); }
.family--self { --fam: var(--lilac);  --fam-deep: var(--lilac-deep);  --fam-wash: var(--lilac-wash); }
.family--life { --fam: var(--moss);   --fam-deep: var(--moss-deep);   --fam-wash: var(--moss-wash); }

/* Interior arrival: a slim full-bleed wash behind the crumb + h1, so pillar
   pages (organic search's landing pages) open with the same sense of arrival
   the home hero gives, at ~40% of its height and with no CTA theatre. The
   wash fades to the paper ground rather than ending on an edge. */
.page-arrival {
  width: 100vw; position: relative; left: 50%; right: 50%;
  margin: -2rem -50vw 1.6rem;
  padding: 2.4rem 1.35rem 1.9rem;
  background: linear-gradient(180deg, var(--fam-wash, var(--sage-wash)), transparent);
}
.page-arrival > * { max-width: var(--measure); margin-left: auto; margin-right: auto; }
.page-arrival h1 { margin: .5rem auto 0; }
.page-arrival .crumbs { margin-top: 0; }
/* /about/ borrows rose (the people hue) without being a library family. */
.page-arrival--rose { --fam-wash: var(--rose-wash); }
.family-meta {
  font-family: var(--head); font-size: .72rem; font-weight: 600;
  letter-spacing: .12em; text-transform: uppercase;
  color: var(--fam-deep, var(--sage-deep)); margin: 0 0 .5rem;
}
.family-h {
  font-size: clamp(1.5rem, 3.4vw, 2rem);
  padding-bottom: 0; border-bottom: 0;
}
.family-h::after {
  content: ""; display: block;
  width: 2.75rem; height: 3px; border-radius: 2px;
  background: var(--fam-deep, var(--sage-deep)); margin-top: .75rem;
}
.family-intro { color: var(--ink-soft); max-width: 48rem; margin: .5rem 0 0; font-size: 1.02rem; }
.family .clusters { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(17rem, 100%), 1fr)); gap: 1.1rem; margin-top: 1.2rem; }
.cluster { margin: 0; padding: 1.15rem 1.25rem; }
.cluster h3 { margin: 0 0 .5rem; font-size: 1.18rem; }
.cluster h3 a { text-decoration: none; color: var(--ink); }
.cluster h3 a:hover { color: var(--forest); }
.cluster > p { font-size: .92rem; color: var(--ink-soft); margin: 0 0 .7rem; }
.cluster ul { margin: 0; padding: 0; list-style: none; }
.cluster li { margin: 0; border-top: 1px solid var(--line); }
/* .65rem vertical lands each row at ~45px - a real thumb target. They were
   .48rem (~39px), the one list of tappable rows on the site under 44px. */
.cluster li a { display: block; padding: .65rem 0; font-size: .95rem; text-decoration: none; color: var(--ink-soft); transition: color .15s, padding .15s; }
.cluster li a:hover { color: var(--forest); padding-left: .25rem; }
.cluster > p + ul > li:first-child, .cluster h3 + ul > li:first-child { border-top: 0; }
.cluster > p:last-of-type ~ p a { font-family: var(--head); font-weight: 600; font-size: .88rem; color: var(--sage-deep); text-decoration: none; }

/* Cluster carousel: theme.js pages the link list three at a time (swipe,
   dots, arrows). The border-fix below removes the top rule from whichever
   item is first on its page - li[hidden] + li matches the DOM's next
   sibling regardless of the hidden item's rendering, so it catches exactly
   the first visible row of every page after the first. */
.cluster-carousel { position: relative; }
.cluster-track li[hidden] + li { border-top: 0; }
.cluster-nav { display: flex; align-items: center; justify-content: center; gap: .5rem; margin-top: .3rem; }
.cluster-arrow {
  display: flex; align-items: center; justify-content: center;
  width: 2rem; height: 2rem; padding: .55rem; box-sizing: content-box; /* 44px thumb target */
  border: 1px solid var(--line); border-radius: 999px; background: none;
  color: var(--ink-soft); font-size: 1.1rem; line-height: 1; cursor: pointer;
  transition: color var(--dur-quick) var(--ease), border-color var(--dur-quick) var(--ease);
}
.cluster-arrow:hover:not(:disabled) { color: var(--sage-deep); border-color: var(--sage); }
.cluster-arrow:disabled { opacity: .35; cursor: default; }
.cluster-dots { display: flex; align-items: center; gap: .4rem; }
.cluster-dot {
  width: .5rem; height: .5rem; padding: .7rem; box-sizing: content-box; /* 44px thumb target, dot stays small */
  border: none; border-radius: 999px; background: var(--line); cursor: pointer;
  background-clip: content-box; transition: background var(--dur-quick) var(--ease);
}
.cluster-dot:hover { background: var(--sage-tint); background-clip: content-box; }
.cluster-dot[aria-pressed="true"] { background: var(--sage-deep); background-clip: content-box; }

/* ---------- offerings row on home ---------- */
.offerings { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(15rem, 100%), 1fr)); gap: 1rem; margin: 1.4rem 0; padding: 0; list-style: none; }
.offerings li { padding: 1.2rem 1.25rem; }
.offerings li a { font-family: var(--display); font-weight: 500; text-decoration: none; display: block; color: var(--ink); font-size: 1.15rem; line-height: 1.3; }
.offerings li span { font-size: .9rem; color: var(--ink-soft); display: block; margin-top: .35rem; line-height: 1.5; }
/* Honesty labels: which of these five doors are real today vs a waitlist,
   so a visitor never clicks "join" expecting to book and lands on a form
   instead. */
.offerings li .tag {
  display: inline-block; font-family: var(--head); font-size: .68rem; font-weight: 600;
  letter-spacing: .06em; text-transform: uppercase; color: var(--ink-faint); margin: .3rem 0 0;
}
.offerings li .tag--live { color: var(--sage-deep); }
/* A waiting list is real but not bookable yet: gold, not sage, so it never
   reads as "live". ochre-deep holds 6:1+ on the sage and marigold washes in
   both modes. */
.offerings li .tag--waitlist { color: var(--ochre-deep); }
/* An odd count in a 2-up grid leaves the last card as a half-width orphan
   (the groupprogram "what you get" runs seven). Let it take the full row
   instead - a wide closing card reads deliberate, a stranded one doesn't.
   In a single column `1 / -1` is the whole (only) column, so it is a no-op
   on phones; at 3-up it becomes a full-width final row, also composed. */
.offerings li:last-child:nth-child(odd) { grid-column: 1 / -1; }

.disclosure { font-size: .85rem; color: var(--muted); background: var(--ochre-wash); border: 0; padding: .8rem 1.1rem; border-radius: var(--radius-sm); }

/* ---------- a photograph in the page flow ----------
   For real trip photographs (the second photo on the site after Blair's
   portrait). Same card radius + resting elevation as everything raised, so
   a photo reads as part of the system, not a pasted-in rectangle. Source
   discipline: assets are pulled from the Cloudinary library (all assets
   there cleared by Rachel for content use, 9 Jul 2026), faces-not-
   identifiable preferred until the consent register's per-person backfill
   is done, and /assets/ is immutable-cached - a replacement image needs a
   NEW filename. */
.photo { margin: 1.8rem 0; }
.photo img { width: 100%; height: auto; border-radius: var(--radius); box-shadow: var(--elevation-card); }
.photo figcaption { font-size: .82rem; color: var(--muted); margin-top: .55rem; }

/* ---------- a person, with a portrait ----------
   Blair's headshot on /groupprogram/ is the first photograph of anyone on this
   site; everything else under /assets/ is an icon or the OG card. Two things
   follow from that.

   It stacks rather than sitting in a two-up card grid, because there is no
   photograph of Rachel. Side by side, one card with a face and one without
   reads as a broken image; stacked, Blair simply has a portrait and the
   paragraph above her does not, which reads as a choice.

   The image keeps its natural 469:654 ratio rather than being cropped to a
   circle or a square. A crop means guessing where the face sits, and guessing
   wrong on a person's headshot is worse than a plain rectangle.

   ⚠ /assets/* is served immutable with a one-year max-age, and images are not
   content-hashed the way style.css and the scripts are. Replacing a photo at
   the same filename would leave browsers holding the old one for up to a year.
   Give a replacement a new filename. */
.person { display: flex; flex-wrap: wrap; gap: 1.2rem 1.4rem; align-items: center; margin: 1.4rem 0; }
.person-photo {
  width: 9rem; height: auto; flex-shrink: 0;
  border-radius: var(--radius); box-shadow: var(--elevation-card);
}
.person p { margin: 0; flex: 1 1 14rem; }

/* ---------- third-party embed ----------
   Currently one instance sitewide: the community Spotify playlist on
   /playlist/. Kept as its own named block rather than a generic .embed helper,
   because "how many third-party frames does this site load" should stay an
   easy question to answer. If a second embed ever arrives, generalise this
   then, not pre-emptively.
   The iframe carries its own 12px radius from Spotify, so the wrapper only
   handles width, rhythm and the honesty line underneath. */
.embed-playlist { margin: 1.6rem 0; }
/* The frame reserves the player's height up front, so the page does not jump
   when the lazy iframe arrives, and it holds the fallback message underneath. */
.embed-frame {
  position: relative; min-height: 22rem;
  display: grid; place-items: center;
  background: var(--sage-wash); border-radius: var(--radius);
}
.embed-fallback {
  margin: 0; padding: 1.5rem; text-align: center; max-width: 26rem;
  font-size: .92rem; color: var(--muted);
}
.embed-fallback strong { display: block; font-family: var(--head); color: var(--ink); margin-bottom: .4rem; }
/* Absolute, so it covers the fallback once Spotify paints. Its own background
   is opaque, so nothing shows through a loaded player. */
.embed-playlist iframe {
  position: absolute; inset: 0; width: 100%; height: 100%;
  border: 0; border-radius: var(--radius);
}
.embed-playlist .fine { margin: .7rem 0 0; }

/* ---------- line icons ----------
   One rule for every hand-drawn icon the build emits (see ICONS in build.mjs).
   Sage-deep rather than --ink so they read as a quiet second layer under the
   words, never competing with them: the text is what carries the meaning and
   the icon is only a landmark for scanning. currentColor in the SVG means
   this one declaration themes them in both light and dark. */
.ico {
  display: block; width: 1.5rem; height: 1.5rem;
  margin-bottom: .55rem; color: var(--sage-deep);
  flex-shrink: 0;
}
/* Decorative, so they go with the rest of the motion when motion is declined
   and never animate on their own. Nothing to disable here today; the rule
   exists so a later hover flourish cannot be added without honouring it. */
@media (prefers-reduced-motion: reduce) { .ico { transition: none; } }

/* ---------- facts bar ----------
   The at-a-glance strip near the top of an offer page (when it runs, how long,
   how big, where). Deliberately NOT a .card: these are four labels, not four
   things to click, so they get the flat wash and no elevation or hover. */
.facts {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(min(8.5rem, 100%), 1fr));
  gap: .2rem 1rem; margin: 1.5rem 0; padding: 1.15rem 1.3rem; list-style: none;
  background: var(--sage-wash); border-radius: var(--radius);
}
.facts li { padding: .45rem 0; }
.facts li strong { display: block; font-family: var(--head); font-weight: 600; font-size: .97rem; color: var(--ink); }
.facts li span { display: block; font-size: .85rem; color: var(--muted); margin-top: .1rem; line-height: 1.45; }

/* ---------- price cards ----------
   The two payment options as cards, carried over from the grieftrips.com Program
   page this one replaced. .price--best is the recommended tier: it gets the rose
   wash and the badge, so the eye lands there first. Both cards state their real
   total, so the cheaper-per-month option is never the one hiding its arithmetic. */
/* padding-top leaves room for .price-badge, which sits half outside its card's
   top edge. Without it the badge overflows this box and any ancestor that ever
   gains overflow:hidden would clip it. */
.prices {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(min(13rem, 100%), 1fr));
  gap: 1rem; margin: 1.5rem 0; padding: .8rem 0 0; list-style: none;
}
.price {
  position: relative; display: flex; flex-direction: column; gap: .15rem;
  padding: 1.5rem 1.4rem 1.3rem;
  background: var(--surface); border-radius: var(--radius); box-shadow: var(--elevation-card);
}
.price--best { background: var(--sage-wash); box-shadow: none; outline: 2px solid var(--sage); outline-offset: -2px; }
.price-badge {
  position: absolute; top: -.7rem; left: 1.3rem;
  font-family: var(--head); font-size: .68rem; font-weight: 600;
  letter-spacing: .07em; text-transform: uppercase;
  padding: .3rem .7rem; border-radius: var(--radius-pill);
  background: var(--sage-deep); color: var(--paper);
}
.price-name { font-family: var(--head); font-weight: 500; font-size: .95rem; color: var(--muted); }
.price-fig { font-family: var(--display); font-size: 2.35rem; line-height: 1.1; color: var(--ink); font-optical-sizing: auto; }
.price-cur { font-family: var(--head); font-size: .85rem; font-weight: 600; letter-spacing: .06em; color: var(--muted); vertical-align: .35em; }
.price-note { font-size: .87rem; color: var(--muted); }
/* The saving / the real total: same slot in both cards so they compare directly. */
.price-save { margin-top: .5rem; font-family: var(--head); font-weight: 600; font-size: .85rem; color: var(--sage-deep); }

/* ---------- bonuses ---------- */
/* 16rem forces the four bonuses into 2x2 at the reading measure instead of
   the 3+1 orphan the 11rem minimum produced (at the 44rem measure three
   13rem tracks still fit, so 13 was not enough); equal rows keep the pair
   rows level when one description runs longer than its neighbour. */
.bonuses {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(min(16rem, 100%), 1fr));
  grid-auto-rows: 1fr;
  gap: .8rem; margin: 1.2rem 0; padding: 0; list-style: none;
}
.bonuses li { padding: 1rem 1.1rem; background: var(--surface); border-radius: var(--radius-sm); box-shadow: var(--elevation-card); }
.bonuses li strong { display: block; font-family: var(--head); font-weight: 600; font-size: .95rem; color: var(--ink); }
.bonuses li span { display: block; font-size: .87rem; color: var(--muted); margin-top: .2rem; line-height: 1.5; }
/* The value line. Ochre, not sage: sage is the action colour and this is a
   figure to read, not a button to press. */
.value-total {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: .3rem 1rem;
  margin: 1.2rem 0; padding: .95rem 1.2rem;
  background: var(--ochre-wash); border-radius: var(--radius-sm);
}
.value-total strong { font-family: var(--head); font-weight: 600; font-size: 1.05rem; color: var(--ink); }
.value-total span { font-size: .9rem; color: var(--muted); }
/* The struck-through original. Same size as the price beside it so the two
   read as a pair, and muted so the number you actually pay stays the loudest
   thing in the row. */
.value-total .was { font-family: var(--head); font-size: 1.05rem; color: var(--muted); }
.value-total .was s {
  text-decoration-line: line-through;
  text-decoration-thickness: 2px;
  text-decoration-color: color-mix(in srgb, var(--clay-deep) 65%, transparent);
}

/* Visually hidden, still announced. Added for the struck price above: <s> is
   the right element semantically, but screen readers do not announce it by
   default, so on its own the row would read as "$5,200 Yours from $2,400" with
   nothing to say the first number is crossed out. The hidden label supplies
   that. Standard clip-rect recipe rather than display:none, which would remove
   it from the accessibility tree entirely and defeat the point. */
.vh {
  position: absolute; width: 1px; height: 1px; overflow: hidden;
  clip-path: inset(50%); white-space: nowrap; border: 0; padding: 0; margin: -1px;
}

/* ---------- application form ----------
   Longer sibling of .capture: same input recipe, but stacked with visible
   labels because this one asks for a name, a payment preference and (optionally)
   who the person lost. Nothing here is a placeholder-as-label, since a griever
   filling in the story field will lose sight of the question otherwise. */
/* The form sits on its own card, so the point of the page reads as one object
   to fill in rather than loose fields on the page ground. Same surface and
   elevation as .card and the offerings row, so it belongs to the existing
   system rather than introducing a second kind of raised panel. It does NOT
   take .card's hover lift: this one is a thing you type into, not a thing you
   click, and a panel that moves under the cursor while you are filling it in
   is just noise. */
.apply, .intake {
  margin: 1.4rem 0 0;
  background: var(--surface); border-radius: var(--radius);
  box-shadow: var(--elevation-card);
  padding: 1.6rem 1.5rem;
}
@media (min-width: 44rem) { .apply, .intake { padding: 2rem 2rem 1.8rem; } }
.apply-field { margin: 0 0 1.15rem; }
/* :not(.apply-radio) matters: the radio options are themselves <label>s and
   direct children of fieldset.apply-field, so without it this rule outranks
   .apply-radio on specificity and strips the option pills' padding and flex. */
.apply-field > label:not(.apply-radio), .apply-field > legend {
  display: block; font-family: var(--head); font-weight: 500; font-size: .93rem;
  color: var(--ink); margin-bottom: .4rem; padding: 0;
}
.apply-field > label .fine { display: block; font-weight: 400; margin: .15rem 0 0; }
.apply fieldset.apply-field { border: 0; margin: 0 0 1.15rem; padding: 0; }
/* Recipe comes from the shared input rule; the form's fields just go full
   width (box-sizing is global border-box already). */
.apply input[type="text"], .apply input[type="email"], .apply textarea, .apply select,
.intake input[type="text"], .intake input[type="email"], .intake input[type="tel"], .intake textarea, .intake select { width: 100%; }
.apply textarea { resize: vertical; min-height: 6rem; line-height: 1.55; }
/* Radios sit in a column, each a full-width hit area, so a thumb on a phone
   cannot land between two options. */
.apply-radio {
  display: flex; align-items: center; gap: .6rem;
  padding: .6rem .9rem; margin-bottom: .4rem;
  font-size: .95rem; color: var(--ink);
  /* --paper, not --surface: since the form moved onto a card, --surface is the
     card's own colour, and white pills on a white card read as barely-there
     outlines. --paper matches the text inputs above them, so every control you
     can fill in on this card shares one fill. */
  background: var(--paper); border: 1px solid var(--line); border-radius: var(--radius-sm);
  cursor: pointer; transition: border-color var(--dur-quick) var(--ease);
}
.apply-radio:hover { border-color: var(--rose); }
.apply-radio input { accent-color: var(--rose-deep); width: 1.1rem; height: 1.1rem; margin: 0; }
.apply-radio:has(input:checked) { border-color: var(--rose); background: var(--rose-wash); }
.apply-radio:focus-within { border-color: var(--rose); box-shadow: 0 0 0 3px color-mix(in srgb, var(--ring) 25%, transparent); }
.apply > p:last-child, .intake > p:last-child { margin-bottom: 0; }
/* Honeypot. Not display:none, which some bots detect and skip: pushed off-screen
   instead, and already aria-hidden + tabindex="-1" in the markup, so keyboard and
   screen-reader users never reach it either. */
.apply-hp { position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden; }
/* The post-submit acknowledgement, whether inline (JS path) or after the
   worker's redirect back to ?applied=1. Reads as a reply, not a receipt. */
.apply-done {
  margin: 1.4rem 0 0; padding: 1.2rem 1.35rem;
  background: var(--sage-wash); border-radius: var(--radius);
  font-family: var(--head); color: var(--ink);
}
.apply-done p { margin: 0 0 .5rem; }
.apply-done p:last-child { margin-bottom: 0; font-family: var(--body); font-size: .9rem; color: var(--muted); }

/* ---------- group intake (/group-intake/) ----------
   Shares .apply's card/input/radio system (same .btn, same .apply-field,
   same .apply-radio rose focus states) so this page reads as one family
   with /groupprogram/ rather than a second design. Only the repeatable
   "who did you lose" rows and the two read-this text blocks are new. */
.intake-losses-label { font-family: var(--head); font-weight: 500; font-size: .93rem; color: var(--ink); margin: 0 0 .5rem; }
.intake-losses-label .fine { display: block; font-weight: 400; margin: .15rem 0 0; }
.intake-loss-row {
  display: flex; flex-wrap: wrap; gap: .5rem;
  margin-bottom: 1.4rem;
}
/* A visible line between one person's row and the next, not just margin -
   each row grew to four fields plus two date sub-labels, so adjacent people
   need a clearer break than whitespace alone once there's more than one. */
.intake-loss-row + .intake-loss-row {
  padding-top: 1.2rem; border-top: 1px solid var(--line);
}
.intake-loss-row .loss-who { flex: 2 1 10rem; }
.intake-loss-row .loss-relationship, .intake-loss-row .loss-other { flex: 1 1 9rem; }
.intake-loss-row input, .intake-loss-row select {
  font-family: var(--body); font-size: .95rem; padding: .75rem 1rem;
  border: 1px solid var(--line); border-radius: var(--radius-sm);
  background: var(--paper); color: var(--ink); width: 100%;
}
/* Death anniversary and birthday read as sub-questions under "Who did you
   lose?", each with its own small visible label - the rest of this row
   (name, relationship) leans on placeholder text, but a date field has no
   self-evident placeholder, and these two were previously unlabeled
   entirely (aria-label only), which left them looking unlike every other
   labeled field on this form. Same label recipe as .apply-field's, just
   smaller, since these sit a level below the row's own "Who did you lose?"
   question rather than beside it. */
.loss-date-field {
  display: flex; flex-direction: column; gap: .3rem;
  flex: 1 1 11rem;
}
.loss-date-label {
  font-family: var(--head); font-weight: 500; font-size: .82rem; color: var(--ink);
}
/* A full row to itself on a phone, so the remove button never lands under a
   thumb that meant to tap the select next to it. */
.loss-remove {
  flex: 0 0 auto; width: 2.6rem; border: 1px solid var(--line); border-radius: var(--radius-sm);
  background: var(--paper); color: var(--muted); font-size: 1.1rem; line-height: 1; cursor: pointer;
}
.loss-remove:hover { border-color: var(--rose); color: var(--rose-deep); }
.intake-add-loss { margin: 0 0 1.4rem; padding: .5rem 1rem; font-size: .88rem; }
/* The two read-this blocks: --paper, not --surface, same reasoning as the
   radio pills above - the form already sits on --surface, so a same-colour
   block would disappear instead of reading as "pause and read this." */
.intake-privacy, .intake-not-therapy {
  background: var(--paper); border-radius: var(--radius-sm);
  padding: 1.1rem 1.2rem; margin: 0 0 1.15rem;
}
.intake-privacy h2 { font-family: var(--head); font-weight: 600; font-size: .95rem; margin: 0 0 .6rem; }
.intake-privacy p, .intake-not-therapy p { font-size: .9rem; line-height: 1.55; color: var(--muted); margin: 0 0 .7rem; }
.intake-privacy p:last-child, .intake-not-therapy p:last-child { margin-bottom: 0; }
/* align-items: flex-start, not .apply-radio's default center: this label's
   text wraps to several lines (it has two inline links in it), and centering
   would float the checkbox in the middle of that block instead of against
   its first line. The text and links are wrapped in one <span> in the
   markup so they form a single flex item that wraps as normal paragraph
   text, rather than the checkbox's raw text and each <a> becoming separate
   flex items and each getting squeezed into its own narrow column. */
.intake-agree { margin-top: -.3rem; align-items: flex-start; }
.intake-agree input { margin-top: .15rem; }


/* ---------- footer ----------
   Light, matching the app, which has no dark surfaces anywhere. The previous
   deep --forest-ink bookend and its fixed --on-dark/--on-dark-faint text
   tokens are gone; every colour here now flips with the theme like the rest
   of the page. Note the old .bottom nav a used --sage, which did NOT flip
   between modes and would have failed contrast instantly on a light ground -
   it is --sage-deep now, which is gate-checked against paper in both modes. */
.bottom {
  border-top: 1px solid var(--line); background: transparent; color: var(--ink);
  margin-top: 4rem; padding: 3.2rem 1.35rem 3.6rem;
}
.bottom > * { max-width: var(--wide); margin-left: auto; margin-right: auto; }
.belief { font-family: var(--serif); font-style: italic; font-weight: 400; font-optical-sizing: auto; font-size: 1.3rem; color: var(--ink); margin: 0 auto 1.3rem; }
/* Tighter than the .9/1.3 it was: the row read as seven separate things
   rather than one line of navigation. The link padding is untouched at
   .35rem, so each target stays 35px tall and the wrapped rows still clear
   each other by that padding plus the row gap. */
.bottom nav { display: flex; flex-wrap: wrap; gap: .2rem .95rem; margin-bottom: 1.3rem; }
.bottom nav a { color: var(--sage-deep); font-size: .92rem; text-decoration: none; display: inline-block; padding: .35rem 0; transition: color var(--dur-quick) var(--ease); }
.bottom nav a:hover { color: var(--ink); }
.bottom nav a[aria-current] { color: var(--ink); font-weight: 500; }
.social { display: flex; flex-wrap: wrap; align-items: center; gap: .75rem; margin: 0 auto 1.3rem; }
/* The theme toggle sits at the end of the social row, pushed clear of the
   icon buttons so it reads as a setting rather than another social link. */
.social .theme-toggle { margin-left: .5rem; }
.social-icon {
  display: inline-grid; place-items: center; width: 2.5rem; height: 2.5rem;
  border: 1px solid var(--line); border-radius: var(--radius-pill);
  color: var(--sage-deep); transition: color var(--dur-quick) var(--ease), border-color var(--dur-quick) var(--ease), background var(--dur-quick) var(--ease);
}
.social-icon:hover { color: var(--ink); border-color: var(--sage); background: var(--sage-wash); }
.fine { font-size: .82rem; color: var(--muted); margin: .3rem auto; }
.fine a { color: var(--muted); text-decoration: underline; text-decoration-color: color-mix(in srgb, var(--muted) 40%, transparent); }
.fine a:hover { color: var(--ink); }
/* Still a small footnote-sized line (inherits .fine sizing/margin), but the
   link itself stays sage-deep + medium so it doesn't fade into the copyright
   fine print - a visitor scanning fast should still catch it. */
.crisis-line a { color: var(--sage-deep); font-weight: 500; text-decoration-color: color-mix(in srgb, var(--sage-deep) 50%, transparent); }
.crisis-line a:hover { color: var(--ink); }

/* ---------- print ---------- */
@media print {
  .top, .pathway, .related, .crisis-inline, .bottom nav { display: none; }
  body { background: #fff; color: #000; }
}

/* comparison table in an article body (raw HTML rows, e.g. seeing-their-body).
   Stacks into labelled blocks on phones so no cell gets squeezed. */
.compare { width: 100%; border-collapse: collapse; margin: 1.8rem 0; font-size: 0.98rem; line-height: 1.55; }
.compare th, .compare td { text-align: left; vertical-align: top; padding: 0.85rem 0.9rem; border-bottom: 1px solid var(--line); }
.compare thead th { font-weight: 600; color: var(--ink-soft); }
.compare tbody th { font-weight: 600; color: var(--ink); white-space: nowrap; }
@media (max-width: 640px) {
  .compare thead { display: none; }
  .compare tr, .compare th, .compare td { display: block; padding-left: 0; padding-right: 0; }
  .compare tbody th { border-bottom: 0; padding-bottom: 0; font-size: 1.05rem; }
  .compare td::before { content: attr(data-label); display: block; font-weight: 600; color: var(--ink-soft); margin-bottom: 0.2rem; }
}
