/* styles.css */

/* ── Tokens ───────────────────────────────────────── */
:root {
  --bg:     #F5F2EE;
  --ink:    #1A1A18;
  --muted:  #6B6B65;
  --accent: #4F6F8F;
  --rule:   #E8E4DF;
  --max-w:  1200px;
  --pad:    clamp(32px, 5vw, 60px);

  /* Motion. Built-in CSS easings are too weak to read as intentional —
     these are the strong curves from Emil Kowalski's standard. Durations
     are longer than a typical marketing site on purpose: this page has to
     be regulating, not activating. See CLAUDE.md. */
  --ease-out:    cubic-bezier(0.23, 1, 0.32, 1);
  --ease-in-out: cubic-bezier(0.77, 0, 0.175, 1);
  --dur-fast:   160ms;   /* press feedback */
  --dur-ui:     220ms;   /* hover, links */
  --dur-reveal: 900ms;   /* entrances */
  --dur-scene: 1100ms;   /* illustration fades */
  --stagger:     90ms;

  --highlight:  #A9CCE8;   /* the blue sweep behind a lifted word */

  /* Type ramp. One scale; a role combines a step with a face and weight.
     The two ends are far apart on purpose (display 112, the home hero up
     to 176 bounded by viewport height; labels 13, meta 11): a section is
     announced small and the one big thing inside it carries the contrast. */
  --t-display:    clamp(56px, 7.6vw, 112px);
  --t-display-sm: clamp(46px, 5.6vw, 84px);
  --t-statement:  clamp(28px, 3.4vw, 48px);
  --t-title:      clamp(26px, 2.4vw, 32px);
  --t-lead:       clamp(18px, 1.6vw, 22px);
  --t-body:       17px;
  --t-small:      15px;
  --t-label:      13px;
  --t-meta:       11px;

  /* Spacing scale. Tight inside a group, generous between groups; the
     contrast between the two is what reads as rhythm. */
  --s-1: 8px;  --s-2: 12px; --s-3: 16px; --s-4: 24px;
  --s-5: 32px; --s-6: 48px; --s-7: 72px; --s-8: 96px;

  --ink-soft: #4A4A45;   /* running text — a step darker than --muted (8:1) */
  --nav-h: 80px;
}

/* ── Reset ────────────────────────────────────────── */
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
html { font-size: 16px; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; }
body {
  background: var(--bg);
  color: var(--ink);
  font-family: 'DM Sans', sans-serif;
  font-weight: 300;
  line-height: 1.75;
}
img, svg { display: block; max-width: 100%; }
a { color: inherit; }

/* ── Typography scale ─────────────────────────────── */
.display {
  font-family: "gelica", serif;
  font-weight: 800;
  font-size: var(--t-display);
  /* 0.92, not 0.85: Gelica is round and open, not a condensed grotesk */
  line-height: 0.92;
  letter-spacing: -0.025em;
  color: var(--ink);
}
.display--sm { font-size: var(--t-display-sm); line-height: 0.95; }

/* Section label. Small and quiet on purpose: the ramp has two ends, display
   and body, and a section is announced at the small end so the big type
   inside it (a place-name, a moment) is the only large thing nearby. */
.title {
  font-family: 'DM Sans', sans-serif;
  font-weight: 400;
  font-size: var(--t-label);
  line-height: 1.4;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--ink-soft);
  margin-bottom: var(--s-5);
}
.pull {
  font-family: "gelica", serif;
  font-weight: 300;
  font-style: italic;
  font-size: var(--t-lead);
  line-height: 1.6;
  color: var(--muted);
  max-width: 56ch;
}
/* A pull set in ink, directly under a title, with room before the prose */
.lede { color: var(--ink); margin-bottom: var(--s-5); }
.eyebrow {
  font-family: 'DM Sans', sans-serif;
  font-weight: 400;
  font-size: var(--t-label);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--accent);
}
.body-text {
  font-family: 'DM Sans', sans-serif;
  font-weight: 400;
  font-size: var(--t-body);
  line-height: 1.75;
  color: var(--ink-soft);
  max-width: 52ch;   /* DM Sans has a wide ch: 52ch is about 72 characters */
}
.footnote {
  font-family: 'DM Sans', sans-serif;
  font-weight: 300;
  font-style: italic;
  font-size: var(--t-label);
  color: var(--muted);
}

/* Every hero opens the same way: the mascot beside the page's eyebrow,
   then the headline leads. The mascot is a companion, not the hero. */
.eyebrow-row { display: flex; align-items: center; gap: var(--s-3); margin-bottom: var(--s-3); }

/* Prose rhythm: paragraphs inside a group are one step apart; the ruled
   variant separates beats of a story with a hairline under the measure. */
.prose > * + * { margin-top: var(--s-4); }
.prose--ruled > * + * {
  margin-top: var(--s-6);
  padding-top: var(--s-6);
  border-top: 1px solid var(--rule);
}
.mb-3 { margin-bottom: var(--s-3); }
.mb-4 { margin-bottom: var(--s-4); }
.mb-5 { margin-bottom: var(--s-5); }
.mb-6 { margin-bottom: var(--s-6); }
.mt-5 { margin-top: var(--s-5); }

/* ── Accent color ─────────────────────────────────── */
.accent { color: var(--accent); }

/* ── Layout ───────────────────────────────────────── */
.container {
  width: 100%;
  max-width: var(--max-w);
  margin: 0 auto;
  padding: 0 var(--pad);
}
.section {
  padding-top: var(--s-8);
  padding-bottom: var(--s-8);
}
.section--sm {
  padding-top: var(--s-6);
  padding-bottom: var(--s-6);
}
/* Inner-page hero: fills the first screen below the nav and no more, so the
   headline is always in the first viewport. */
.hero-page {
  display: flex;
  flex-direction: column;
  padding-top: var(--s-6);
  padding-bottom: 0;
}
.hero-page .plate { padding-top: var(--s-4); }

/* ── Rules ────────────────────────────────────────── */
.rule {
  border: none;
  border-top: 1px solid var(--rule);
  margin: 0;
}
.rule-dash {
  border: none;
  border-top: 1px dashed var(--rule);
  margin: 0;
}
.rule--page { max-width: var(--max-w); margin-left: auto; margin-right: auto; }
.rule-accent {
  width: 36px;
  height: 1px;
  background: var(--accent);
  border: none;
  margin: var(--s-4) 0;
}
/* drawn on, left to right, the way the pair rules draw */
.rule-accent.draw { transform: scaleX(0); transform-origin: 0 50%; transition: transform 700ms var(--ease-out) 200ms; }
.rule-accent.draw.visible { transform: scaleX(1); }
@media (prefers-reduced-motion: reduce) { .rule-accent.draw { transform: none; transition: none; } }

/* ── Links ────────────────────────────────────────── */
.link {
  color: var(--accent);
  font-family: 'DM Sans', sans-serif;
  font-weight: 400;
  font-size: var(--t-body);
  text-decoration: underline;
  text-underline-offset: 3px;
  transition: color var(--dur-ui) var(--ease-out);
}
/* opacity 0.7 dropped the hover state to 2.73:1; darken instead */
@media (hover: hover) and (pointer: fine) {
  .link:hover { color: #3E5A76; }
}
.link-arrow::after { content: ' →'; }

/* ── CTA Button ───────────────────────────────────────
   White on --accent measures 5.25:1; the softer blue could not carry
   white text at all (2.26:1), which is why this uses the full accent. */
.btn {
  display: inline-flex;
  align-items: center;
  background: var(--accent);
  color: #FFFFFF;
  font-family: 'DM Sans', sans-serif;
  font-weight: 400;
  font-size: var(--t-label);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  text-decoration: none;
  padding: 17px 36px;
  border: 1px solid var(--accent);
  border-radius: 999px;
  cursor: pointer;
  /* name the properties; never `all` */
  transition: transform var(--dur-fast) var(--ease-out),
              background var(--dur-ui) var(--ease-out),
              color var(--dur-ui) var(--ease-out),
              border-color var(--dur-ui) var(--ease-out);
}
/* The bubble's light, on a button. Not its glass: there is nothing behind
   a button on cream paper worth seeing through, and the accent has to keep
   its 5.25:1 against white. So the fill stays solid and only the lighting
   is borrowed — a lit top edge, the ground falling away at the foot, one
   small sheen, and a shadow underneath so the pill sits off the page. */
.btn {
  position: relative; overflow: hidden;
  box-shadow:
    0 12px 24px -16px rgba(20, 18, 14, 0.55),
    inset 0 1px 0 rgba(255, 255, 255, 0.34),
    inset 0 -12px 20px -16px rgba(0, 0, 0, 0.32);
}
.btn::before {
  content: ''; position: absolute; pointer-events: none;
  left: 5%; top: 5%; width: 90%; height: 40%;
  border-radius: 50%;
  /* a lens of light lying along the top, not a blob sitting on the word */
  background: radial-gradient(closest-side, rgba(255,255,255,0.26) 0%, rgba(255,255,255,0) 100%);
}
.btn:active { transform: scale(0.97); box-shadow: 0 5px 12px -10px rgba(20,18,14,0.5), inset 0 1px 0 rgba(255,255,255,0.22); }
@media (hover: hover) and (pointer: fine) {
  .btn:hover {
    background: #FFFFFF; color: var(--accent); border-color: var(--accent);
    box-shadow:
      0 14px 28px -18px rgba(20, 18, 14, 0.4),
      inset 0 1px 0 rgba(255, 255, 255, 0.9),
      inset 0 -12px 20px -18px rgba(79, 111, 143, 0.28);
  }
  .btn:hover::before { background: radial-gradient(closest-side, rgba(255,255,255,0.95) 0%, rgba(255,255,255,0) 100%); }
}
:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }

/* ── Entrances ────────────────────────────────────────
   Transitions, not keyframes, so a fast scroll back and forth retargets
   instead of restarting. Strong ease-out, and slower than a marketing
   site would use — this page settles a reader rather than impressing one. */
.fade-in {
  opacity: 0;
  transform: translateY(38px);
  transition: opacity var(--dur-reveal) var(--ease-out),
              transform var(--dur-reveal) var(--ease-out);
}
.fade-in.visible { opacity: 1; transform: none; }
.fade-in[data-i="1"] { transition-delay: calc(var(--stagger) * 1); }
.fade-in[data-i="2"] { transition-delay: calc(var(--stagger) * 2); }
.fade-in[data-i="3"] { transition-delay: calc(var(--stagger) * 3); }
.fade-in[data-i="4"] { transition-delay: calc(var(--stagger) * 4); }

/* A line uncovered rather than faded — ink meeting paper. */
.wipe { display: block; overflow: hidden; padding-bottom: 0.1em; }   /* room for descenders at tight leading */
.display > .wipe { margin-bottom: -0.1em; }   /* lines inside one headline keep their leading; a heading that is itself the wipe keeps its margin */
.wipe > span {
  display: block;
  opacity: 0;
  transform: translateY(115%);   /* past the descender room, so nothing peeks */
  transition: transform 760ms var(--ease-out),
              opacity 640ms var(--ease-out);
}
.wipe.visible > span { opacity: 1; transform: translateY(0); }
.wipe[data-i="1"] > span { transition-delay: calc(var(--stagger) * 1); }
.wipe[data-i="2"] > span { transition-delay: calc(var(--stagger) * 2); }

/* A blue highlighter drawn across a word. clip-path, not background-size:
   background-size is a paint property, clip-path runs on the compositor. */
.lift { position: relative; white-space: nowrap; isolation: isolate; }
.lift::before {
  content: '';
  position: absolute;
  z-index: -1;
  left: -0.12em; right: -0.12em;
  top: 0.06em;  bottom: 0.04em;
  background: var(--highlight);
  opacity: 0.75;
  border-radius: 0.06em;
  clip-path: inset(0 100% 0 0);
  transition: clip-path 700ms var(--ease-out);
}
.visible .lift::before,
.lift.visible::before { clip-path: inset(0 0 0 0); }

/* Slide-up entrance */
.fade-up {
  opacity: 0;
  transform: translateY(80px);
  transition: opacity var(--dur-reveal) var(--ease-out),
              transform var(--dur-reveal) var(--ease-out);
}
.fade-up.visible { opacity: 1; transform: none; }

@media (prefers-reduced-motion: reduce) {
  .fade-in, .fade-up { opacity: 1; transform: none; transition: none; }
  .wipe > span { opacity: 1; transform: none; transition: none; }
  .lift::before { clip-path: inset(0 0 0 0); transition: none; }
}

/* ── Scroll-linked drift ─────────────────────────────
   A picture moves a little slower than the words beside it as the page
   scrolls, so the two separate — depth from things being apart. Bound to
   the scroll position, linear, no easing: it is the reader moving the page,
   never the page moving on its own. Browsers without scroll timelines
   simply get the still layout. */
@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    .moment__art, .closer__art, .portrait__figure, .hero-art__plate, .plate img {
      animation: drift-y linear both;
      animation-timeline: view();
      animation-range: cover 0% cover 100%;
    }
    @keyframes drift-y { from { translate: 0 4vh; } to { translate: 0 -4vh; } }
  }
}

/* ── Thistle background ───────────────────────────── */
.thistle-bg {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translateX(-50%) translateY(-50%);
  width: 120vw;
  height: auto;
  pointer-events: none;
  opacity: 0;
  transition: opacity 2.4s var(--ease-out);
  will-change: transform;
}
.thistle-bg.visible {
  opacity: 1;
}
@media (max-width: 680px) {
  .thistle-bg {
    width: auto;
    height: 100%;
    max-width: none;
  }
}
@media (prefers-reduced-motion: reduce) {
  .thistle-bg { opacity: 1; transition: none; }
}

/* ── Illustration layers ──────────────────────────────
   Every asset is a transparent ink cutout: flat ink RGB with all the tone
   in the alpha channel, and a hard alpha floor so "empty" paper is really
   empty. That means no rectangle, no edge to feather, and no blend mode —
   the drawing simply sits on the page. Assets are generated with margin so
   nothing is ever severed by its own frame. See CLAUDE.md. */

/* A full-bleed illustration band, uncropped. */
.scene {
  position: relative;
  width: 100%;
  overflow: hidden;
}
.scene__img {
  display: block;
  width: 100%;
  height: auto;
  pointer-events: none;
  opacity: 0;
  transition: opacity var(--dur-scene) var(--ease-out);
}
.scene.visible .scene__img { opacity: 1; }

/* Night band: full tonal inversion, owns its own ground. */
.scene--night { background: #201E18; }
.scene--night .scene__img {
  opacity: 0;
  /* the drawing's snow dissolves into the night ground at its foot, so the
     bubble below lies on night rather than on a cut edge */
  -webkit-mask-image: linear-gradient(to bottom, #000 76%, transparent 100%);
          mask-image: linear-gradient(to bottom, #000 76%, transparent 100%);
}

.scene__caption {
  position: absolute;
  /* the moon sits centre-right and the headline was landing on it; hold the
     caption to the left third and let it wrap there */
  max-width: min(var(--max-w), 46vw);
  left: 0; transform: none;
  padding-left: var(--pad);
  top: clamp(32px, 7vh, 76px);
  left: 50%;
  transform: translateX(-50%);
  width: 100%;
  max-width: var(--max-w);
  padding: 0 var(--pad);
  z-index: 2;
  color: #F5F2EE;
}
.scene__caption .display { color: #F5F2EE; line-height: 0.95; font-size: var(--t-display-sm); }
@media (max-width: 820px) { .scene__caption { max-width: 82vw; } }
/* On a phone the band is only ~400px tall and the caption was landing on
   the treeline. Put it in flow above the drawing, on the night ground. */
@media (max-width: 600px) {
  .scene__caption {
    position: static; transform: none; width: auto; max-width: none;
    padding: var(--s-6) var(--pad) var(--s-4);
  }
}

/* ── A page's one illustration ────────────────────────
   One per page, maximum. It holds still: a second scroll-driven moment
   on the same page stops the first feeling special, and this site is
   meant to settle a reader rather than perform at them. */
.plate {
  position: relative;
  width: 100%;
  max-width: var(--max-w);
  margin: 0 auto;
  padding: clamp(30px, 6vh, 70px) var(--pad) 0;
  opacity: 0;
  transform: translateY(28px);
  transition: opacity var(--dur-scene) var(--ease-out),
              transform var(--dur-scene) var(--ease-out);
}
.plate.visible { opacity: 1; transform: none; }
.plate img { display: block; width: 100%; height: auto; }
.plate--bleed { max-width: none; padding-left: 0; padding-right: 0; }
@media (prefers-reduced-motion: reduce) {
  .plate { opacity: 1; transform: none; transition: none; }
}

/* ── The work: rules draw, then the copy rises ────────── */
.pairs { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 260px), 1fr)); gap: 0; }
.pair { padding: var(--s-5) 0; position: relative; }
.pair:not(:last-child) { padding-right: clamp(20px, 4vw, 64px); }
.pair::before {
  content: ''; position: absolute; top: 0; left: 0; right: 0; height: 1px;
  background: var(--rule);
  transform: scaleX(0); transform-origin: 0 50%;
  transition: transform 900ms var(--ease-out);
}
.pair.visible::before { transform: scaleX(1); }
.pair > * {
  opacity: 0; transform: translateY(26px);
  transition: opacity 800ms var(--ease-out), transform 800ms var(--ease-out);
}
.pair.visible > * { opacity: 1; transform: none; }
.pair.visible h3 { transition-delay: 180ms; }
.pair.visible p  { transition-delay: 300ms; }
.pair:nth-child(2)::before { transition-delay: 120ms; }
.pair:nth-child(3)::before { transition-delay: 240ms; }
.pair:nth-child(2).visible h3 { transition-delay: 300ms; }
.pair:nth-child(2).visible p  { transition-delay: 420ms; }
.pair:nth-child(3).visible h3 { transition-delay: 420ms; }
.pair:nth-child(3).visible p  { transition-delay: 540ms; }
.pair h3 { font-family: "gelica", serif; font-weight: 800; font-size: var(--t-lead); line-height: 1.2; margin-bottom: var(--s-2); }
.pair p  { font-size: var(--t-small); line-height: 1.75; color: var(--ink-soft); }

/* ── A sentence set large enough to be read slowly ────── */
.statement {
  font-family: "gelica", serif; font-weight: 300;
  font-size: var(--t-statement); line-height: 1.3;
  letter-spacing: -0.015em; max-width: 20ch;
}
.statement em { font-style: italic; font-weight: 800; }
.statement em, .statement .lift { white-space: nowrap; }   /* a set phrase never leaves one word orphaned on the next line */
.statement .joy { color: var(--accent); }

@media (prefers-reduced-motion: reduce) {
  .pair::before { transform: scaleX(1); transition: none; }
  .pair > * { opacity: 1; transform: none; transition: none; }
}

/* ── Testimonial cards ────────────────────────────── */
.testimonials { position: relative; overflow: hidden; }
.testimonial-stack {
  display: flex;
  flex-direction: column;
  gap: var(--s-6);
}
.testimonial-stack .testimonial {
  width: 46%;
}
.testimonial-stack .testimonial:nth-child(1) { align-self: flex-start; }
.testimonial-stack .testimonial:nth-child(2) { align-self: flex-end; }
.testimonial-stack .testimonial:nth-child(3) { align-self: flex-start; }

@media (max-width: 680px) {
  .testimonial-stack .testimonial {
    width: 100%;
    align-self: stretch !important;
    padding: var(--s-4) var(--s-3);   /* reading width before ornament on a phone */
  }
}

.testimonial {
  /* Not a card. A patch of paper so the quote stays legible where the
     thistle passes behind it — no border, no radius, nothing to read as a box.
     (Tried without, thistle faded instead: Stephen preferred this.) */
  background: rgba(245, 242, 238, 0.92);
  padding: var(--s-5) var(--s-6);
}

.testimonial__quote {
  font-family: "gelica", serif;
  font-weight: 300;
  font-style: italic;
  font-size: var(--t-body);
  line-height: 1.7;
  color: var(--ink);
  margin-bottom: var(--s-3);
}

.testimonial__source {
  font-family: 'DM Sans', sans-serif;
  font-size: var(--t-meta);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--muted);
}

/* ── Scroll fade-in for cards ─────────────────────── */
.fade-card {
  opacity: 0;
  transform: translateY(20px);
  transition: opacity var(--dur-scene) var(--ease-out),
              transform var(--dur-scene) var(--ease-out);
}
.fade-card.visible {
  opacity: 1;
  transform: translateY(0);
}
.fade-source {
  opacity: 0;
  transition: opacity 500ms var(--ease-out);
}
.fade-card.visible .fade-source {
  opacity: 1;
  transition: opacity 500ms var(--ease-out) 400ms;
}
@media (prefers-reduced-motion: reduce) {
  .fade-card { opacity: 1; transform: none; transition: none; }
  .fade-source { opacity: 1; transition: none; }
}

/* ── Nav ──────────────────────────────────────────── */
.nav {
  position: sticky;
  top: 0;
  z-index: 10;
  background: var(--bg);
  padding: var(--s-2) var(--pad);   /* ~80px tall; it was 112 */
}
.nav__inner {
  max-width: var(--max-w);
  margin: 0 auto;
  display: flex;
  justify-content: space-between;
  align-items: center;
}
.nav__logo {
  font-family: "gelica", serif;
  font-weight: 700;
  font-size: 14px;
  letter-spacing: 0.05em;
  text-decoration: none;
  color: var(--ink);
}
.nav--hidden { visibility: hidden; }
/* Menu open: the bar rides above the overlay, so the logo and the mascot stay
   exactly where they were and the mascot itself is the way back out. */
.nav.nav--open { z-index: 101; background: transparent; }
.nav__trigger.no-ring:focus-visible { outline: none; }   /* closed with a mouse or finger: focus comes home quietly */

/* ── Nav trigger ──────────────────────────────────── */
.nav__trigger {
  background: none;
  border: none;
  cursor: pointer;
  padding: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  color: var(--ink);
}
.nav__menu-label {
  font-family: 'DM Sans', sans-serif;
  font-weight: 400;
  font-size: var(--t-label);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--muted);
  line-height: 1;
}

/* ── Nav overlay ──────────────────────────────────── */
.overlay {
  position: fixed;
  inset: 0;
  z-index: 100;
  background: var(--bg);
  visibility: hidden;
  opacity: 0;
  transition: opacity 0.3s ease, visibility 0s linear 0.3s;
  padding: var(--pad);
  display: flex;
  flex-direction: column;
  justify-content: center;
}
.overlay:focus { outline: none; }   /* it takes focus on open only so Tab starts inside it */
.overlay.is-open {
  visibility: visible;
  opacity: 1;
  transition: opacity 0.3s ease, visibility 0s linear 0s;
}
.overlay__nav {
  display: flex;
  flex-direction: column;
  gap: 16px;
}
.overlay__link {
  font-family: "gelica", serif;
  font-weight: 800;
  font-size: var(--t-display-sm);
  line-height: 1;
  letter-spacing: -0.02em;
  color: var(--ink);
  text-decoration: none;
  transition: color 0.2s ease;
}
.overlay__link:hover { color: var(--accent); }
.nav__noscript {
  display: flex;
  gap: 20px;
  flex-wrap: wrap;
}
.nav__noscript a {
  font-family: 'DM Sans', sans-serif;
  font-size: 13px;
  color: var(--accent);
  text-decoration: underline;
}

/* ── *hope character ──────────────────────────────────── */
@keyframes hope-float {
  0%, 100% { transform: translateY(0); }
  50%       { transform: translateY(-10px); }
}
@keyframes hope-blink {
  0%, 90%, 100% { transform: scaleY(1); }
  95%           { transform: scaleY(0.1); }
}
@keyframes hope-wave {
  from { transform: rotate(-15deg); }
  to   { transform: rotate(15deg); }
}
@keyframes hope-wave-loop {   /* same motion; a new name restarts it on open */
  from { transform: rotate(-15deg); }
  to   { transform: rotate(15deg); }
}
@keyframes hope-drift {
  0%, 100% { transform: translate(0, 0) rotate(0deg); }
  50%      { transform: translate(12px, -16px) rotate(10deg); }
}
/* Practice page: walk-in — slow, gentle, opacity handled here */
@keyframes hope-walk-in {
  0%   { transform: translateX(-125%) translateY(0);    opacity: 0; }
  5%   { transform: translateX(-112%) translateY(-6px); opacity: 1; }
  13%  { transform: translateX(-94%)  translateY(0); }
  21%  { transform: translateX(-76%)  translateY(-6px); }
  29%  { transform: translateX(-58%)  translateY(0); }
  37%  { transform: translateX(-40%)  translateY(-6px); }
  45%  { transform: translateX(-23%)  translateY(0); }
  53%  { transform: translateX(-9%)   translateY(-5px); }
  61%  { transform: translateX(0)     translateY(0); }
  68%  { transform: translateX(0)     translateY(-3px); }
  74%  { transform: translateX(0)     translateY(0); }
  100% { transform: translateX(0)     translateY(0); }
}
/* Leg stride — stops swinging at 65% (when body has arrived), settles to 0 */
@keyframes hope-leg-left-stride {
  0%   { transform: rotate(-22deg); animation-timing-function: ease-in-out; }
  13%  { transform: rotate(22deg);  animation-timing-function: ease-in-out; }
  26%  { transform: rotate(-22deg); animation-timing-function: ease-in-out; }
  39%  { transform: rotate(22deg);  animation-timing-function: ease-in-out; }
  52%  { transform: rotate(-22deg); animation-timing-function: ease-in-out; }
  65%  { transform: rotate(0deg);   animation-timing-function: ease-out; }
  100% { transform: rotate(0deg); }
}
@keyframes hope-leg-right-stride {
  0%   { transform: rotate(22deg);  animation-timing-function: ease-in-out; }
  13%  { transform: rotate(-22deg); animation-timing-function: ease-in-out; }
  26%  { transform: rotate(22deg);  animation-timing-function: ease-in-out; }
  39%  { transform: rotate(-22deg); animation-timing-function: ease-in-out; }
  52%  { transform: rotate(22deg);  animation-timing-function: ease-in-out; }
  65%  { transform: rotate(0deg);   animation-timing-function: ease-out; }
  100% { transform: rotate(0deg); }
}
/* Arm swing — opposite phase, also stops at 65% */
@keyframes hope-arm-left-stride {
  0%   { transform: rotate(18deg);  animation-timing-function: ease-in-out; }
  13%  { transform: rotate(-18deg); animation-timing-function: ease-in-out; }
  26%  { transform: rotate(18deg);  animation-timing-function: ease-in-out; }
  39%  { transform: rotate(-18deg); animation-timing-function: ease-in-out; }
  52%  { transform: rotate(18deg);  animation-timing-function: ease-in-out; }
  65%  { transform: rotate(0deg);   animation-timing-function: ease-out; }
  100% { transform: rotate(0deg); }
}
@keyframes hope-arm-right-stride {
  0%   { transform: rotate(-18deg); animation-timing-function: ease-in-out; }
  13%  { transform: rotate(18deg);  animation-timing-function: ease-in-out; }
  26%  { transform: rotate(-18deg); animation-timing-function: ease-in-out; }
  39%  { transform: rotate(18deg);  animation-timing-function: ease-in-out; }
  52%  { transform: rotate(-18deg); animation-timing-function: ease-in-out; }
  65%  { transform: rotate(0deg);   animation-timing-function: ease-out; }
  100% { transform: rotate(0deg); }
}
/* Arms settle to resting/meditation position */
@keyframes hope-arm-left-settle {
  from { transform: rotate(0deg); }
  to   { transform: rotate(14deg); }
}
@keyframes hope-arm-right-settle {
  from { transform: rotate(0deg); }
  to   { transform: rotate(-14deg); }
}
/* Breathing — arms gently rise and fall */
@keyframes hope-breathe-left {
  0%, 100% { transform: rotate(14deg); }
  50%       { transform: rotate(-6deg); }
}
@keyframes hope-breathe-right {
  0%, 100% { transform: rotate(-14deg); }
  50%       { transform: rotate(6deg); }
}

.hope {
  display: block;
  fill: none;
}
.hope--companion {
  width: 60px;
  height: auto;
  flex-shrink: 0;
  overflow: visible;   /* arms and the thought bubble reach past the viewBox */
}
/* The inner-page poses were drawn at 444px with a 1.5-unit stroke; at 60px
   that is a hairline. Match the home mascot's 3-unit stroke. */
.hope--companion path, .hope--companion line { stroke-width: 3; }
.hope--companion circle[stroke], .hope--companion ellipse[stroke] { stroke-width: 2; }
.hope--nav {
  height: 40px;
  width: 40px;
}
.hope--overlay {
  height: 120px;
  width: 120px;
  position: absolute;
  bottom: 40px;
  right: 60px;
}

/* hope__eye: requires transform-box so blink squeezes in place */
.hope__eye {
  transform-box: fill-box;
  transform-origin: center;
}
/* hope__arm--wave: rotates around shoulder at approx (0%, 100%) of path bbox */
.hope__arm--wave {
  transform-box: fill-box;
  transform-origin: 0% 100%;
}
/* ── Circled word (Practice page headline) ────────── */
.circled-word {
  position: relative;
  display: inline-block;
  white-space: nowrap;
}
.word-circle {
  position: absolute;
  top: -18%;
  left: -6%;
  width: 112%;
  height: 136%;
  overflow: visible;
  pointer-events: none;
}
.word-circle path {
  stroke-dasharray: 380;
  stroke-dashoffset: 0; /* visible by default (no-JS / reduced-motion) */
}
@media (prefers-reduced-motion: no-preference) {
  .word-circle path {
    stroke-dashoffset: 380;
    opacity: 0;
    animation: word-circle-draw 2s ease-out 1s both;
  }
  @keyframes word-circle-draw {
    to { stroke-dashoffset: 0; opacity: 1; }
  }
}

/* ── Highlight sweep ──────────────────────────────── */
/* Multi-word phrases wrap, so they use a background sweep rather than
   .lift's clip-path box; same colour and curve as .lift so they read as
   one device. Single words use .lift. */
.highlight-sweep {
  color: var(--ink);   /* muted grey on the tint measured 3.6:1 */
  background-image: linear-gradient(transparent 8%, var(--highlight) 8%, var(--highlight) 90%, transparent 90%);
  background-size: 0% 100%;
  background-repeat: no-repeat;
  background-position: left center;
  -webkit-box-decoration-break: clone;
          box-decoration-break: clone;
  animation: highlight-sweep 700ms var(--ease-out) 500ms forwards;
}
@keyframes highlight-sweep {
  to { background-size: 110% 100%; }
}
.highlight-hover {
  display: inline;
  background-image: linear-gradient(transparent 8%, var(--highlight) 8%, var(--highlight) 90%, transparent 90%);
  background-size: 0% 100%;
  background-repeat: no-repeat;
  background-position: left center;
  -webkit-box-decoration-break: clone;
          box-decoration-break: clone;
  transition: background-size 700ms var(--ease-out);
}
a:hover .highlight-hover {
  background-size: 110% 100%;
}
@media (prefers-reduced-motion: reduce) {
  .highlight-sweep {
    background-size: 110% 100%;
    animation: none;
  }
}

/* hope__leg: pivot at hip */
.hope__leg--left {
  transform-box: view-box;
  transform-origin: 40px 80px;
}
.hope__leg--right {
  transform-box: view-box;
  transform-origin: 60px 80px;
}
/* hope__arm--left/right: pivot at body-edge shoulder */
.hope__arm--left {
  transform-box: view-box;
  transform-origin: 10px 55px;
}
.hope__arm--right {
  transform-box: view-box;
  transform-origin: 90px 55px;
}

@media (prefers-reduced-motion: no-preference) {
  /* The nav mascot is fixed on screen for the whole visit. Looping motion
     there is motion in the reader's periphery, which this site does not do.
     It comes alive when the reader points at it, and inside the overlay,
     which they opened deliberately. See CLAUDE.md. */
  .hope--nav   { animation: none; }
  .hope--overlay { animation: hope-float 4s ease-in-out infinite; }
  .hope--overlay .hope__eye { animation: hope-blink 3s infinite; }
  @media (hover: hover) and (pointer: fine) {
    .nav__trigger:hover .hope--nav,
    .nav__trigger:focus-visible .hope--nav { animation: hope-float 4s ease-in-out infinite; }
    .nav__trigger:hover .hope--nav .hope__eye { animation: hope-blink 3s infinite; }
  }
  /* A few waves, then rest. An infinite 1.5s loop beside the headline was
     the fastest motion on the site. Inside the overlay — opened on purpose —
     it may keep waving. */
  .hope__arm--wave { animation: hope-wave 2s ease-in-out 4 alternate both; }
  .overlay.is-open .hope__arm--wave { animation: hope-wave-loop 2s ease-in-out infinite alternate; }

  /* Practice page: walk in → sit → breathe */
  .hope--practice { animation: hope-walk-in 3.8s linear both; }   /* walks in, settles, and stays settled */
  .hope--practice .hope__eye { animation: none; }
  .hope--practice .hope__leg--left {
    animation: hope-leg-left-stride 3.8s linear 1 forwards;
  }
  .hope--practice .hope__leg--right {
    animation: hope-leg-right-stride 3.8s linear 1 forwards;
  }
  .hope--practice .hope__arm--left {
    animation:
      hope-arm-left-stride  3.8s linear     0s   1 forwards,
      hope-arm-left-settle  1.0s ease-out   3.8s 1 forwards;
  }
  .hope--practice .hope__arm--right {
    animation:
      hope-arm-right-stride  3.8s linear     0s   1 forwards,
      hope-arm-right-settle  1.0s ease-out   3.8s 1 forwards;
  }
}

/* ── Contact Form ─────────────────────────────────── */
.form { width: 100%; }
.form__field { margin-bottom: var(--s-6); }
.form__label {
  display: block;
  font-family: 'DM Sans', sans-serif;
  font-weight: 400;
  font-size: var(--t-small);
  color: var(--ink-soft);
  margin-bottom: var(--s-1);
}
.form__optional { color: var(--muted); font-weight: 300; }
.form__input,
.form__textarea {
  width: 100%;
  background: transparent;
  border: none;
  border-bottom: 1px solid var(--ink);
  border-radius: 0;
  padding: 8px 0;
  font-family: "gelica", serif;
  font-weight: 300;
  font-style: italic;
  font-size: var(--t-body);
  color: var(--ink);
  outline: none;
  transition: border-color 0.2s ease;
  -webkit-appearance: none;
}
.form__input:focus,
.form__textarea:focus {
  border-bottom-color: var(--accent);
}
.form__input::placeholder,
.form__textarea::placeholder {
  /* opacity 0.5 on --muted measured 2.2:1. A solid mid-tone reads the same
     but passes. */
  color: #68675F;
  opacity: 1;
}
.form__textarea {
  resize: vertical;
  min-height: 80px;
}

/* ── Footer ───────────────────────────────────────── */
.footer {
  padding: var(--s-7) var(--pad);
  border-top: 1px solid var(--rule);
  max-width: var(--max-w);
  margin: 0 auto;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 28px;
}
.footer__row {
  width: 100%;
  display: flex;
  justify-content: space-between;
  align-items: center;
  flex-wrap: wrap;
  gap: 16px;
}
.footer__copy {
  font-family: 'DM Sans', sans-serif;
  font-weight: 300;
  font-size: var(--t-meta);
  color: var(--muted);
}
.footer__links {
  display: flex;
  gap: 24px;
  list-style: none;
}
.footer__links a {
  font-family: 'DM Sans', sans-serif;
  font-weight: 400;
  font-size: var(--t-label);
  color: var(--muted);
  text-decoration: none;
  letter-spacing: 0.04em;
  transition: color 0.2s ease;
}
.footer__links a:hover { color: var(--accent); }

/* ── FAQ Accordion ────────────────────────────────── */
.faq__list { width: 100%; }
.faq__item {
  border-top: 1px solid var(--rule);
}
.faq__item:last-child {
  border-bottom: 1px solid var(--rule);
}
.faq__item summary {
  list-style: none;
  cursor: pointer;
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: var(--s-4) 0;
  font-family: "gelica", serif;   /* the questions carry the page: the serif, bold, a step up */
  font-weight: 400;   /* Regular, a size up; the kit has 200/400/700/900 only */
  font-size: clamp(21px, 2vw, 27px);
  line-height: 1.2;
  letter-spacing: -0.01em;
  color: var(--ink);
  gap: var(--s-4);
}
.faq__item summary::-webkit-details-marker { display: none; }
.faq__item summary::after {
  content: '+';
  font-family: 'DM Sans', sans-serif;
  font-weight: 300;
  font-size: 28px;
  color: var(--muted);
  flex-shrink: 0;
  line-height: 1;
}
.faq__item[open] summary::after {
  content: '−';
}
/* The answer starts matching [open] the moment <details> opens, which
   starts this entrance. Opacity and transform only — never max-height,
   which animates layout and never animated the close anyway. */
@keyframes faq-answer-in {
  from { opacity: 0; transform: translateY(-4px); }
}
.faq__item[open] .faq__answer {
  animation: faq-answer-in 250ms var(--ease-out) both;
}
@media (prefers-reduced-motion: reduce) {
  .faq__item[open] .faq__answer { animation: none; }
}
.faq__answer {
  padding-bottom: var(--s-4);
  font-family: 'DM Sans', sans-serif;
  font-weight: 400;
  font-size: var(--t-body);
  line-height: 1.75;
  color: var(--ink-soft);
  max-width: 65ch;
}
/* answers that run to more than one paragraph */
.faq__answer .body-text + .body-text { margin-top: var(--s-3); }
.faq__subhead + .body-text { margin-top: var(--s-2); }
.faq__subhead {
  margin-top: var(--s-5);
  font-family: 'DM Sans', sans-serif;
  font-size: var(--t-label);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ink);
}

/* ── About photo ──────────────────────────────────── */
.about-photo {
  display: flex;
  justify-content: flex-start;
  margin-bottom: var(--s-7);
}
/* Credentials: two columns, no hairline under every row */
.creds {
  list-style: none;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 320px), 1fr));
  gap: var(--s-3) var(--s-6);
  font-family: 'DM Sans', sans-serif;
  font-weight: 400;
  font-size: var(--t-body);
  line-height: 1.75;
  color: var(--ink-soft);
}
.about-photo img {
  width: 500px;
  max-width: 100%;
  height: auto;
  display: block;
}

/* ── Mobile overrides ─────────────────────────────── */
@media (max-width: 600px) {
  .section { padding-top: 56px; padding-bottom: 56px; }
  .section--sm { padding-top: var(--s-5); padding-bottom: var(--s-5); }
  .hero-page { padding-top: var(--s-5); padding-bottom: var(--s-6); }
}
@media (max-width: 480px) {
  .footer__row {
    flex-direction: column;
    align-items: flex-start;
  }
  .form__textarea {
    height: 100px;
  }
}


/* ── Page transitions ─────────────────────────────────
   Cross-document View Transitions, no library: the wordmark and the nav
   mascot stay put while the rest of the page crossfades on the site's
   curve. Browsers without it simply navigate. */
@view-transition { navigation: auto; }
.nav__logo img { view-transition-name: wordmark; }
.hope--nav    { view-transition-name: mascot; }
/* The new page rises like a hill from the bottom edge and covers the old
   one; the sun (or moon) carries its own name and travels to its new hour. */
@keyframes vt-dim  { to   { opacity: 0.55; } }
@keyframes vt-rise { from { clip-path: ellipse(170% 0% at 50% 100%); } to { clip-path: ellipse(170% 125% at 50% 100%); } }
::view-transition-old(root) { animation: 900ms cubic-bezier(0.45, 0, 0.2, 1) both vt-dim; }
::view-transition-new(root) { animation: 900ms cubic-bezier(0.45, 0, 0.2, 1) both vt-rise; }
::view-transition-group(sun) { animation-duration: 900ms; animation-timing-function: cubic-bezier(0.45, 0, 0.2, 1); }
::view-transition-old(wordmark), ::view-transition-new(wordmark),
::view-transition-old(mascot),   ::view-transition-new(mascot) { animation: none; mix-blend-mode: normal; }
@media (prefers-reduced-motion: reduce) {
  ::view-transition-group(*), ::view-transition-old(*), ::view-transition-new(*) { animation: none !important; }
}

/* ── Loader ───────────────────────────────────────────
   First visit in a session only: the mascot draws itself while fonts and
   the hero arrive, then the page is uncovered. About a second, once. The
   <html> class is set by a two-line script in <head>, so there is no flash. */
.loader {
  position: fixed; inset: 0; z-index: 200;
  background: var(--bg);
  display: grid; place-items: center;
  opacity: 0; visibility: hidden;
  transition: opacity 420ms var(--ease-out), visibility 0s linear 420ms;
}
html.is-loading .loader { opacity: 1; visibility: visible; transition: none; }
.loader .hope { width: 96px; height: auto; overflow: visible; }
.loader .hope path, .loader .hope line { stroke-dasharray: 320; stroke-dashoffset: 320; }
.loader .hope circle { opacity: 0; }
@keyframes draw-on { to { stroke-dashoffset: 0; } }
@keyframes fade-on { to { opacity: 1; } }
html.is-loading .loader .hope path,
html.is-loading .loader .hope line { animation: draw-on 900ms var(--ease-out) forwards; }
html.is-loading .loader .hope path:nth-of-type(2) { animation-delay: 260ms; }   /* antenna */
html.is-loading .loader .hope line                { animation-delay: 420ms; }   /* legs */
html.is-loading .loader .hope path:last-of-type   { animation-delay: 640ms; }   /* smile */
html.is-loading .loader .hope circle              { animation: fade-on 320ms var(--ease-out) 820ms forwards; }

/* ── A hero with the type set into the drawing's sky ──────
   The plate runs edge to edge; from 720px up the copy sits over the part
   of the picture that was drawn empty for it (a modifier names which
   side). Below that the copy stays in flow above the picture. */
.hero-art { position: relative; padding-top: 0; }
.hero-art .hero-art__copy { padding-top: var(--s-5); }
.hero-art__plate { max-width: none; padding: var(--s-5) 0 0; margin: 0; }
.hero-art__plate img { width: 100%; height: auto; display: block; }
@media (min-width: 720px) {
  .hero-art__plate { padding-top: 0; }
  .hero-art__copy {
    position: absolute; z-index: 3; top: var(--s-6);
    left: 50%; transform: translateX(-50%);
    width: 100%; max-width: var(--max-w);
    padding: 0 var(--pad);
    pointer-events: none;
  }
  .hero-art__copy a, .hero-art__copy button { pointer-events: auto; }
  .hero-art__copy > * { width: min(100%, 52%); }
  .hero-art--sky-right  .hero-art__copy > * { margin-left: auto; width: min(100%, 36%); }
  .hero-art--sky-left   .hero-art__copy > * { width: min(100%, 40%); }
  .hero-art--sky-centre .hero-art__copy > * { margin-left: 22%; width: min(100%, 52%); }
  .hero-art__copy { top: var(--s-6); }
}
/* A plate split into layers: the figure rides above the copy, registered
   to the plate by the percentages the decomposition gave back. */
.hero-art__stack { position: relative; }
.hero-art__plate .hero-art__layer {
  position: absolute; z-index: 4; pointer-events: none;
  left: 5.336%; top: 3.776%; width: 74.89%; height: auto;
}
.hero-art__after { padding-top: var(--s-6); padding-bottom: var(--s-7); }   /* room before the section rule; the lede was reading as underlined */
.hero-art__after .statement { max-width: 22ch; }   /* the same sentence-set-large as Home's, under every inner hero */
/* A plate with no empty sky (Practice: cloud strokes across the top) keeps
   its headline in flow above the picture, on every screen size. */
@media (min-width: 720px) {
  .hero-art--flow .hero-art__copy { position: static; transform: none; padding-top: var(--s-5); padding-bottom: var(--s-3); }
  .hero-art--flow .hero-art__copy > * { width: min(100%, 60%); margin-left: 0; }
  .hero-art--flow .clock { top: var(--s-5); left: 72%; }   /* beside the headline, in the copy's empty right */
  /* the Practice plate's tall trees run off its top edge; dissolve that edge so nothing is cut flat */
  .hero-art--flow .hero-art__plate img:not(.clock) { -webkit-mask-image: linear-gradient(to bottom, transparent 0, #000 9%, #000 82%, transparent 100%); mask-image: linear-gradient(to bottom, transparent 0, #000 9%, #000 82%, transparent 100%); }
}

/* ── Develop: a plate that comes up with the scroll ─────
   Scroll-driven where supported (reversible, on the compositor); the
   IntersectionObserver fallback adds .visible and fades it once. */
@keyframes develop { from { opacity: 0; } to { opacity: 1; } }
@supports (animation-timeline: view()) {
  .develop { animation: develop linear both; animation-timeline: view(); animation-range: entry 5% entry 45%; }
}
.develop:not(.visible) { opacity: 0; transition: opacity var(--dur-scene) var(--ease-out); }
.develop.visible { opacity: 1; }
@supports (animation-timeline: view()) { .develop:not(.visible) { opacity: 1; transition: none; } }
@media (prefers-reduced-motion: reduce) { .develop, .develop:not(.visible) { animation: none; opacity: 1; } }

/* ── Portrait: a photograph printed on the page ────────
   No vignette, no cutout, no frame. The rectangle is honest — it is a
   photograph on a page of drawings — so it is treated like a plate in a
   magazine: cropped close, black and white, multiplied onto the paper so
   her highlights are the cream itself, a caption set beneath it, and room
   around it. */
.portrait { display: grid; grid-template-columns: minmax(0, 5fr) minmax(0, 7fr); align-items: center; gap: var(--s-7); padding-top: var(--s-7); padding-bottom: 0; }
.portrait__figure { margin: 0; width: min(100%, 420px); }
.portrait__figure img {
  width: 100%; height: auto; aspect-ratio: 4 / 5; object-fit: cover; object-position: 50% 15%;
  display: block;
  filter: grayscale(1) contrast(1.06);
  mix-blend-mode: multiply;
}
.portrait__figure figcaption {
  margin-top: var(--s-3);
  font-family: 'DM Sans', sans-serif; font-weight: 400; font-size: var(--t-meta);
  letter-spacing: 0.12em; text-transform: uppercase; color: var(--muted);
}
.portrait__copy { padding-bottom: var(--s-6); }
@media (max-width: 760px) { .portrait { grid-template-columns: 1fr; gap: var(--s-5); padding-top: var(--s-6); } .portrait__figure { width: 100%; } .portrait__copy { padding-bottom: 0; } }

/* ── The story: chapters along a road ─────────────────
   Four beats down one column; beside them the pair walk — a frame sequence
   on a canvas that sits still in the viewport and advances one step per
   ten pixels of scroll, so they walk exactly as fast as the reader reads,
   and stop when they stop. Without JS the column keeps its dashed road. */
.story { display: grid; grid-template-columns: minmax(180px, 1.6fr) minmax(0, 4fr); gap: var(--s-7); }
.story__road { position: relative; }
.story__road::before {
  content: ''; position: absolute; top: 0; bottom: 0; left: 50%;
  border-left: 1px dashed var(--rule);
}
.story__road.has-walk::before { display: none; }
.story__walk {
  /* a little wider than its column, stepping into the page margin */
  display: block; width: 125%; max-width: none; margin-left: -25%; height: auto;
  position: sticky; top: calc(50vh - 120px);
  opacity: 0; transition: opacity var(--dur-scene) var(--ease-out);
}
.story__walk.is-drawn { opacity: 1; }
.chapter { margin-top: var(--s-7); }   /* place-name over its paragraph: the name needs the column's full width at display size */
.story__chapters > .chapter:first-of-type { margin-top: 0; }   /* the label's own margin is the gap */
.chapter__head {
  /* the place-name carries the rhythm: display size against body size */
  font-family: "gelica", serif; font-weight: 800;
  font-size: var(--t-display-sm); line-height: 0.95; letter-spacing: -0.02em;
  margin: 0 0 var(--s-3); text-wrap: balance;
}
.chapter .body-text { font-size: var(--t-body); max-width: 48ch; }
@media (max-width: 900px) {
  .story { grid-template-columns: 1fr; }
  .story__road { display: none; }
  /* on a phone the pair walk in flow at the head of the story instead */
  .story__road.has-walk { display: block; }
  .story { gap: var(--s-6); }
  .story__walk { position: static; width: min(100%, 440px); max-width: 100%; margin: 0 auto; }
}

/* Plates that run to their own bottom edge dissolve into the page there */
.plate-fade, .hero-art__plate img, .hero-art__layer {
  -webkit-mask-image: linear-gradient(to bottom, #000 82%, transparent 100%);
          mask-image: linear-gradient(to bottom, #000 82%, transparent 100%);
}

/* ── Opener: the flight ───────────────────────────────
   A pinned stage and 140vh of travel. Thirty frames from one clip, drawn
   to a canvas by scroll position; the last frame is the tuck. */
.opener { position: relative; height: 240vh; }
.opener__stage { position: sticky; top: var(--nav-h); height: calc(100dvh - var(--nav-h)); display: grid; place-items: center; overflow: hidden; }   /* below the sticky nav, so the whole stage — and the skip link at its foot — is on screen */
/* On a wide screen the donkey flies low in the stage, a known distance from
   its foot, so the headline that follows can come up right under the hooves
   (the hero pulls up over that foot by less than the padding). */
@media (min-width: 761px) { .opener__stage { align-items: end; padding-bottom: 12vh; } }
.opener__donkey {
  width: min(88vw, 1300px); max-width: none; height: auto;
  max-height: calc(100dvh - var(--nav-h) - 2 * var(--s-6));   /* short viewports: keep both wingtips on the stage */
  opacity: 0; transition: opacity var(--dur-scene) var(--ease-out);
}
.opener__donkey.is-drawn { opacity: 1; }
@media (max-width: 760px) { .opener { height: 200vh; } .opener__donkey { width: 100vw; } }   /* whole wingspan on a phone: the frames have no margin to crop */
@media (prefers-reduced-motion: reduce) {
  .opener { height: auto; }
  .opener__stage { position: static; height: auto; min-height: 70vh; }
  .opener__donkey { opacity: 1; transition: none; }
}

/* ── Hero: the headline over the hillside ─────────────
   The landscape is a skyline cut: transparent above its own silhouette,
   opaque paper below. The sun sits behind it and rises with the scroll,
   so the hills occlude it — depth by occlusion, not by speed. */
.hero-scene {
  position: relative; overflow: hidden; min-height: 100dvh;
  display: flex; flex-direction: column;
  padding-top: var(--s-3);
  /* main.js pulls this up over the opener's empty foot (measured from the
     tucked frame's ink) so the headline arrives right under the hooves */
}
.hero-scene__sun {
  position: absolute; z-index: 0; pointer-events: none;
  right: -16%; bottom: -34%;
  width: min(66vw, 940px); height: auto; max-width: none;
  transform-origin: 50% 50%; will-change: transform;
  /* no view-transition-name here: scaling this disc down to an inner
     page's small sun, or up from it, would be an approaching object */
}
.hero-scene__copy { position: relative; z-index: 2; order: 1; padding-bottom: var(--s-4); }
.hero-scene__copy .eyebrow { letter-spacing: 0.16em; }
.hero-scene h1 { font-size: clamp(58px, min(10.4vw, 14.5vh), 176px); }
.hero-scene .pull { max-width: 36ch; }   /* stops short of the sun's rays */
.hero-scene picture { display: contents; }   /* the <picture> wrapper must not become the flex item */
.hero-scene__art {
  position: relative; z-index: 1; pointer-events: none;
  order: 2; margin-top: auto; align-self: center; flex-shrink: 0;
  width: max(100%, 118vw); height: auto; max-width: none;
  -webkit-mask-image: linear-gradient(to bottom, #000 84%, transparent 100%);
          mask-image: linear-gradient(to bottom, #000 84%, transparent 100%);
}
/* Narrow screens: the copy is stacked above the hills, so the sun has to
   stay low — mostly behind the horizon, rays showing — and rise less. */
@media (max-width: 820px) {
  .hero-scene__sun { right: -20%; bottom: -24%; width: 76vw; }
  /* the art is short here, so its dissolve is short too — otherwise the
     sun's lower rays showed through the fading ground */
  .hero-scene__art { -webkit-mask-image: linear-gradient(to bottom, #000 93%, transparent 100%); mask-image: linear-gradient(to bottom, #000 93%, transparent 100%); }
  .hero-scene::after { height: 12vw; }   /* 7% of a 48.1vw picture = 3.4vw of dissolve */
}
/* The paper the ground dissolves into. The hillside's bottom edge is masked
   to transparent so it melts into the page rather than stopping on a ruled
   line — but the sun sits behind that same edge, so its lower rays were
   showing through the dissolve and reading as a second sun below the
   horizon. This band is real paper: in front of the sun (both z-index 0,
   and ::after paints last) and behind the art (z-index 1). Opaque across
   the whole of the art's fade, then soft-edged upward into ground that is
   already opaque, so nothing about the horizon changes except that it now
   occludes. */
.hero-scene::after {
  content: ''; position: absolute; z-index: 0; pointer-events: none;
  left: 0; right: 0; bottom: 0; height: 20vw;
  background: var(--bg);
  /* the art's dissolve is the bottom 16% of a 48.1vw-tall picture = 7.7vw;
     this is opaque for 9.6vw, so the dissolve lands entirely on paper */
  -webkit-mask-image: linear-gradient(to bottom, transparent 0%, #000 52%);
          mask-image: linear-gradient(to bottom, transparent 0%, #000 52%);
}

@media (prefers-reduced-motion: reduce) { .hero-scene__sun { transform: translate3d(0,-30vh,0) !important; } }

/* ── About Meghan: a bubble resting on the night ───────
   A soap bubble drawn in this site's light rather than a browser's. The
   reference is the glass sphere on floot.com, with its whole reason for
   existing — thin-film iridescence — taken out, because the illustrations
   here are monochrome and the bubble has to belong to them. What is left
   is the part that actually makes a bubble read as a bubble: a round but
   hand-blown form, a hairline of film, light gathering on the side the
   light comes from, two glints, and an edge thin enough to see the night
   through. All of it is tone. None of it is hue.

   GEOMETRY. The film is an ellipse — every border-radius value sits near
   50% on BOTH axes, which is what keeps it curving all the way round
   instead of flattening into a capsule when the copy runs tall. An
   ellipse only contains its own text box if it is drawn about 1.6x that
   box, hence `inset: -30%`; so the measure, not the shape, is what gives
   way on a narrow screen (58vw keeps the film inside the viewport). The
   bubble is its own object: it sits on the paper and only its crown dips
   into the base of the night.

   The film is thin enough to read the night through and only thickens
   toward the lit core; the copy sits low, over paper, so nothing is ever
   set over ink.

   Nothing loops. The one thing that moves is the light, and only because
   a pointer moved it — see main.js. */

/* where the light is coming from, as a fraction of the film box */
@property --px { syntax: "<number>"; inherits: true; initial-value: 0.33; }
@property --py { syntax: "<number>"; inherits: true; initial-value: 0.26; }
/* how far off head-on the light is, 0 at the centre and 1 at the rim */
@property --pd { syntax: "<number>"; inherits: true; initial-value: 0.82; }

.about-bubble {
  position: relative; z-index: 2;
  margin-top: -4vh;
  /* the film hangs 30% of the copy's height below the box it is drawn
     around, so the section has to carry that plus its own foot */
  padding-bottom: 190px;
}
.bubble {
  position: relative; display: block;
  width: min(520px, 58vw);
  margin-inline: auto;
  padding: var(--s-2) var(--s-3);
  --px: 0.33; --py: 0.26; --pd: 0.82;
  transition: --px 420ms var(--ease-out), --py 420ms var(--ease-out),
              --pd 420ms var(--ease-out);

  /* One outline, used by the film and by the glints above it. Both need it:
     a pseudo-element with no border-radius has no shape to clip its own
     background to, and several stacked radial gradients leave enough of a
     wash in the corners of that box to show as a rectangle past the bubble. */
  --film-shape: 62% 38% 46% 54% / 44% 56% 44% 56%;

  /* The film's colour, in one place. It is a near-white that has been
     breathed on by --accent: the core is white with the faintest cool
     cast, and the film picks up more of the blue the thinner it gets,
     which is how a real film behaves. Set these four back to
     255 254 251 / 245 242 238 / 245 242 238 / 255 255 255 and --film-iris
     to 0 for the monochrome bubble. */
  --film-core:  250 252 255;   /* the lit core */
  --film-mid:   232 241 250;
  --film-thin:  220 234 247;   /* where the film is thinnest */
  --film-rim:   189 214 237;   /* and gathers again at the edge */
  --film-shade:  42 66 92;     /* the side the light has left */
  --film-iris: 1.35;           /* strength of the two interference smears */
}
.bubble > * { position: relative; z-index: 1; }

/* the film */
.bubble::before {
  content: ''; position: absolute; z-index: 0;
  inset: -30%;
  /* Lopsided on purpose — a hand-blown bubble, not a geometric one. Each
     opposing pair in --film-shape sums to 100% (62+38, 46+54 across; 44+56
     down both sides), which is the condition for no straight run anywhere
     on the outline: it curves the whole way round while never being
     symmetrical. The tilt is on the film alone, so the copy stays upright. */
  border-radius: var(--film-shape);
  transform: rotate(-4deg);
  /* thin at the rim, gathered toward the lit core; the whole film is the
     page's own cream at varying strength, never a hue */
  background: radial-gradient(118% 118% at calc(var(--px) * 100%) calc(var(--py) * 100%),
    rgb(var(--film-core) / 0.54) 0%,
    rgb(var(--film-mid)  / 0.38) 46%,
    rgb(var(--film-thin) / 0.22) 86%,
    rgb(var(--film-rim)  / 0.46) 100%);
  /* the night read through the film: softened, and lifted enough that the
     film still reads as lighter than the paper it lies on */
  -webkit-backdrop-filter: blur(26px) brightness(1.32);
          backdrop-filter: blur(26px) brightness(1.32);
  box-shadow:
    /* it is off the paper, not printed on it */
    0 46px 90px -52px rgba(20, 18, 14, 0.5),
    /* the film itself — two hairlines, because part of the bubble is over
       the night and part over paper, and no one edge colour holds both */
    0 0 0 1px rgb(var(--film-shade) / 0.16),
    inset 0 0 0 1px rgb(var(--film-core) / 0.55),
    /* light gathering on the side the light is on … */
    inset calc((0.5 - var(--px)) * 190px) calc((0.5 - var(--py)) * 165px) 70px -44px rgba(255, 255, 255, 0.98),
    /* the rim catches a crescent on that side too, and more of one the
       further off head-on the light gets */
    inset calc((0.5 - var(--px)) * 26px) calc((0.5 - var(--py)) * 24px) 7px -4px
      rgb(255 255 255 / calc(0.18 + 0.30 * var(--pd))),
    /* … and falling away opposite it */
    inset calc((0.5 - var(--px)) * -210px) calc((0.5 - var(--py)) * -185px) 84px -50px rgb(var(--film-shade) / 0.34);
}
/* The glints. A specular on a sphere is not a sticker that slides around:
   as the light goes off head-on it narrows, it dims, and the reflection off
   the far wall of the film pulls away from the one off the near wall. That
   separation is the give-away that this is a bubble and not a bead, so all
   three are driven off --pd rather than left fixed. */
.bubble::after {
  content: ''; position: absolute; z-index: 0; pointer-events: none;
  inset: -30%;
  border-radius: var(--film-shape);
  transform: rotate(-4deg);
  background:
    /* the near-wall reflection */
    radial-gradient(calc(27% - 7% * var(--pd)) calc(17.5% - 6.5% * var(--pd))
        at calc(var(--px) * 100%) calc(var(--py) * 100%),
      rgb(255 255 255 / calc(1 - 0.17 * var(--pd))) 0%, rgb(255 255 255 / 0) 100%),
    /* and the far-wall one, which separates from it as the angle steepens */
    radial-gradient(calc(3.9% - 0.6% * var(--pd)) calc(2.6% - 0.5% * var(--pd))
        at calc(var(--px) * 100% + (3% + 14% * var(--pd)))
           calc(var(--py) * 100% - (1% + 6% * var(--pd))),
      rgb(255 255 255 / calc(1 - 0.1 * var(--pd))) 0%, rgb(255 255 255 / 0) 100%),
    /* under them, two broad smears of the site's own two blues at different
       lightness. That difference is the whole of the iridescence here —
       thin-film interference read in one hue instead of the full spectrum
       floot.com uses, because this palette has one hue. */
    radial-gradient(36% 24% at calc(var(--px) * 100% + (4% + 11% * var(--pd)))
                              calc(var(--py) * 100% + (5% + 13% * var(--pd))),
      rgb(79 111 143 / calc(0.20 * var(--film-iris))) 0%, rgb(79 111 143 / 0) 100%),
    radial-gradient(32% 21% at calc(var(--px) * 100% - (5% + 12% * var(--pd)))
                              calc(var(--py) * 100% + (2% + 8.5% * var(--pd))),
      rgb(169 204 232 / calc(0.34 * var(--film-iris))) 0%, rgb(169 204 232 / 0) 100%);
}

/* Without backdrop-filter there is nothing to see through, so the film
   goes back to being paper rather than a grey wash over the night. */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .bubble::before { background: var(--bg); }
}
@media (prefers-reduced-motion: reduce) { .bubble { transition: none; } }

/* Phones. A phone-width ellipse can only be as tall as the text inside it,
   and at a full measure that came out an almond rather than a bubble. So the
   copy is set a little smaller and tighter here — not to save space, but to
   keep the form round. */
@media (max-width: 760px) {
  .about-bubble { margin-top: -2vh; padding-bottom: 150px; }
  .bubble { width: min(100%, 68vw); padding: var(--s-3); }
  .bubble::before, .bubble::after { inset: -21%; }
  .bubble .display--sm { font-size: 36px; }
  .bubble .body-text { font-size: 16px; line-height: 1.62; }
  .bubble .mb-5 { margin-bottom: var(--s-3); }
  .bubble .mb-4 { margin-bottom: var(--s-3); }
}
/* Under 600px the caption sits in flow at the foot of the band, so give the
   band a run of empty night below it for the bubble to lie on. */
/* Under 600px the picture is the tall left-half crop (82.5vw), so its 7%
   dissolve is 5.8vw and the band has to reach further up. */
@media (max-width: 600px) { .hero-scene::after { height: 18vw; } }

@media (max-width: 600px) { .scene--night { padding-bottom: calc(12vh + var(--s-5)); } }

/* ── Home closer: the thorn-tree moment beside the words ─────── */
.closer { display: grid; grid-template-columns: minmax(0, 5fr) minmax(0, 7fr); gap: var(--s-6); align-items: center; }
.closer__copy .display { margin-bottom: var(--s-4); }
.closer__copy .pull { margin-bottom: var(--s-5); }
.closer__art { width: 100%; height: auto; display: block; }
@media (max-width: 760px) { .closer { grid-template-columns: 1fr; } }

/* ── The clock: the sun or moon, on every page ────────
   Carries view-transition-name: sun, so the same disc travels to its new
   hour when the page changes. On the inner pages it sits in the part of
   the plate's sky that was drawn empty, never over ink. */
.clock { position: absolute; z-index: 2; pointer-events: none; height: auto; max-width: none; view-transition-name: sun; }
.clock--morning { width: clamp(120px, 12.5vw, 180px); left: 27%; top: 2%; }   /* About: between the owl and the ears */
.clock--noon    { width: clamp(90px, 8.5vw, 120px);   left: 58%; top: 1%; }   /* Practice: high, above the headline, clear of the trees */
.clock--afternoon { width: clamp(110px, 11vw, 160px); left: 62%; top: 4%; }   /* Who I Work With: in the sky the pair are looking toward */
.clock--dusk    { width: clamp(140px, 15vw, 220px);   left: 40%; top: 6%; }   /* FAQ: low-ish, between the tree and the copy */
.clock--night   { width: clamp(150px, 15vw, 220px);   left: 40%; top: 1%; }   /* Contact: the moon, in the open sky between the pair and the saplings */
@media (max-width: 719px) { .clock, .hero-art__plate .clock { display: none; } }
/* On a phone the plates have no sky to spare, so the clock rides in the
   eyebrow row instead — small, opposite the mascot — and still carries the
   sun/moon between pages. */
.clock-m { display: none; }
@media (max-width: 719px) {
  .clock-m { display: block; width: 44px; height: auto; margin-left: auto; view-transition-name: sun; }
}
/* the hero copy's column-width rule must not stretch the accent rule; in a
   right-hand sky box it keeps to the headline's left edge (100% − 36%) */
.hero-art .hero-art__copy > .rule-accent { width: 36px; }
@media (min-width: 720px) { .hero-art--sky-right .hero-art__copy > .rule-accent { margin-left: 64%; } }

/* ── Night: the contact page inverts ──────────────────
   Ink ground, cream lines. Same tokens, swapped, so every drawn thing —
   the mascot, the rules, the inputs — turns to cream on its own. */
html.night {
  --bg: #201E18; --ink: #F5F2EE; --ink-soft: #D8D3CB; --muted: #A9A49B;
  --rule: #3A372F; --accent: #A9CCE8; --highlight: #4F6F8F;
}
/* The two display headings on Contact, in the blue. --accent itself sits at
   3.2:1 on the night ground — legal at display size, but it reads as a dark
   patch rather than as a colour. This is the same blue lifted until it reads
   as blue (6.5:1). The rest of the page's accent uses are small text, where
   the deeper value still has the edge. */
html.night { --accent-lift: #7FA6CB; }
html.night .display { color: var(--accent-lift); }

html.night .btn { background: var(--ink); color: var(--bg); border-color: var(--ink); box-shadow: 0 14px 30px -18px rgba(0,0,0,0.7), inset 0 1px 0 rgba(255,255,255,0.7), inset 0 -12px 20px -16px rgba(32,30,24,0.22); }
html.night .btn::before { background: radial-gradient(closest-side, rgba(255,255,255,0.85) 0%, rgba(255,255,255,0) 100%); }
@media (hover: hover) and (pointer: fine) {
  html.night .btn:hover { background: transparent; color: var(--ink); box-shadow: 0 14px 30px -20px rgba(0,0,0,0.6), inset 0 1px 0 rgba(245,242,238,0.34), inset 0 -12px 20px -18px rgba(0,0,0,0.3); }
  html.night .btn:hover::before { background: radial-gradient(closest-side, rgba(245,242,238,0.22) 0%, rgba(255,255,255,0) 100%); }
}
html.night .form__input::placeholder, html.night .form__textarea::placeholder { color: var(--muted); }
html.night .hero-art__plate img {
  -webkit-mask-image: linear-gradient(to bottom, transparent 0, #000 14%, #000 78%, transparent 100%);
          mask-image: linear-gradient(to bottom, transparent 0, #000 14%, #000 78%, transparent 100%);
}

/* ── Contact: the form on the night ───────────────────── */
.contact { display: grid; grid-template-columns: minmax(0, 5fr) minmax(0, 7fr); gap: var(--s-7); align-items: start; }
.contact__left .display { margin-bottom: var(--s-4); }
.contact__right .lede { margin-bottom: var(--s-6); }
.contact__submit { margin-top: var(--s-2); }
@media (max-width: 760px) { .contact { grid-template-columns: 1fr; gap: var(--s-6); } }

/* About: the column sits right and high; the near wingtip crosses the
   corner of the first letter and nothing else */
@media (min-width: 720px) {
  /* About takes Practice's treatment: the headline in flow above the plate at
     full display size, the whole drawing beneath it. The morning sun lives
     inside the plate's stack, so it sits in the drawing's sky between the
     owl and the ears, behind the donkey layer. */
  .page-about .clock--morning { left: 27%; top: 2%; }
  /* inside the plate it would otherwise take `.hero-art__plate img { width: 100% }` */
  .page-about .hero-art__plate .clock--morning { width: clamp(120px, 12.5vw, 180px); }
  /* Practice and Who I Work With: the sun sits inside the plate's stack, in the pair's sky */
  .hero-art__plate .clock--noon { width: clamp(90px, 8.5vw, 120px); left: 58%; top: 6%; }
  .hero-art__plate .clock--afternoon { width: clamp(110px, 11vw, 160px); left: 73%; top: 7%; }   /* clear of the BZ1 ears, in the sky they face */
  .page-faq .hero-art h1 { font-size: clamp(44px, 5.2vw, 76px); }   /* "questions." is one word and must fit the 36% sky box */
}

/* ── Practice: the work as three moments ───────────────
   Picture left, words right, the same side each time: a list, not a
   zigzag. The picture carries the idea; the words stay short. */
.moments { display: grid; gap: var(--s-7); margin-top: var(--s-4); }
.moment { display: grid; grid-template-columns: minmax(0, 6fr) minmax(0, 6fr); gap: var(--s-6); align-items: center; }
.moment__art { width: 100%; height: auto; display: block; }
.moment__head { font-family: "gelica", serif; font-weight: 800; font-size: clamp(40px, 5vw, 72px); line-height: 0.95; letter-spacing: -0.02em; margin: 0 0 var(--s-4); text-wrap: balance; }
.moment .body-text { font-size: var(--t-body); }
@media (max-width: 760px) { .moment { grid-template-columns: 1fr; gap: var(--s-3); } }

/* ── Without JavaScript, everything is simply visible ─────
   The entrances are an enhancement main.js switches on by removing
   .no-js from <html> before first paint. */
.no-js .fade-in, .no-js .fade-up, .no-js .fade-card, .no-js .fade-source, .no-js .plate,
.no-js .wipe > span, .no-js .scene__img, .no-js .thistle-bg, .no-js .pair > *, .no-js .develop,
.no-js .rule-accent.draw, .no-js .opener__donkey { opacity: 1 !important; transform: none !important; transition: none !important; }
.no-js .pair::before { transform: none; }
.no-js .lift::before { clip-path: none; }
.no-js .opener { height: auto; }
.no-js .opener__stage { position: static; height: auto; }

/* ── Skip link, current page, onward links ───────────── */
.skip {
  position: absolute; left: var(--pad); top: -100px; z-index: 300;
  background: var(--ink); color: var(--bg); padding: 10px 16px; border-radius: 999px;
  font-family: 'DM Sans', sans-serif; font-size: var(--t-label); text-decoration: none;
}
.skip:focus-visible { top: 12px; outline-offset: 3px; }
.overlay__link[aria-current="page"] { text-decoration: underline; text-decoration-color: var(--accent); text-underline-offset: 0.12em; text-decoration-thickness: 3px; }
.opener__skip {
  position: absolute; right: var(--pad); bottom: var(--s-5); z-index: 5;
  color: var(--muted); text-decoration: none; letter-spacing: 0.12em;
}
.opener__skip:hover { color: var(--ink); }
.opener__skip { transition: opacity 400ms var(--ease-out); }
.opener__skip.is-gone { opacity: 0; pointer-events: none; }   /* once the flight is mostly flown, the way out is the page itself */
.next { padding-top: 0; }
.next__aside { margin-top: var(--s-4); }

/* ── Who I Work With: the path ─────────────────────────
   A dashed pen line winds down between the six areas and draws itself as
   the reader scrolls. The mascot walks its tip, and a small sprig inks in
   at each bend. Everything follows the reader's scroll; nothing moves
   while they are still. On a phone the line runs down the left edge. */
.focus__head { font-family: "gelica", serif; font-weight: 800; font-size: var(--t-statement); line-height: 1.05; letter-spacing: -0.015em; margin: 0 0 var(--s-3); text-wrap: balance; color: var(--ink); }
.ink-item { scroll-margin-top: calc(var(--nav-h) + var(--s-7)); }
.inkpath { position: relative; }
.inkpath__svg { position: absolute; left: 0; top: 0; width: 100%; overflow: visible; pointer-events: none; }
.inkpath__line { stroke: var(--ink-soft); stroke-width: 2.2; stroke-dasharray: 10 9; stroke-linecap: round; }
.ink-node path { fill: none; stroke: var(--ink); stroke-width: 1.2; stroke-linecap: round; stroke-linejoin: round;
  stroke-dasharray: 1; stroke-dashoffset: 1; stroke-opacity: 0;
  transition: stroke-dashoffset 1100ms var(--ease-out), stroke-opacity 200ms linear; }
/* hidden until reached: a round cap still paints a dot on an undrawn stroke */
.ink-node.is-drawn path { stroke-dashoffset: 0; stroke-opacity: 1; }
/* cream grounds knock the dashed line out behind the mascot and each sprig,
   so nothing is ever drawn through them */
.ink-node__ground, .hope--ink .hope__ground { fill: var(--bg); stroke: none; }
.hope--ink { position: absolute; left: 0; top: 0; width: 40px; height: auto; z-index: 2; will-change: transform; }
.ink-item { position: relative; z-index: 1; width: min(100%, 46%); margin-bottom: 128px; }
.ink-item:last-of-type { margin-bottom: 0; }
.ink-item--right { margin-left: auto; }
.ink-item .body-text { max-width: 46ch; }
@media (max-width: 719px) {
  .ink-item { width: 100%; padding-left: 52px; margin-bottom: var(--s-7); }
  .hope--ink { width: 32px; }
}
@media (prefers-reduced-motion: reduce) { .ink-node path { transition: none; } }
/* Each area sits in its own hand-blown bubble, the same
   family as Meghan's on Home, no two alike. The bubbles are opaque and
   sit above the pen line, so where the path passes one it goes behind
   it: depth from occlusion, not from motion. */
.ink-item { isolation: isolate; width: min(100%, 42%); padding-inline: 4.5%; }
/* The film is drawn well clear of the copy, so the words have air around
   them. It moves on two layers: the individual `translate` and `rotate`
   properties carry the one-time settle as it arrives; `transform` carries
   the scroll drift (--dy, --dr), which the scroll loop in main.js sets every frame, so it
   has no transition of its own. Nothing moves while the reader is still. */
.ink-item::before, .ink-item::after {
  content: ''; position: absolute; z-index: -1; inset: -34% -9%;
  border-radius: var(--shape);
  transform: translateY(var(--dy, 0px)) rotate(calc(var(--r) + var(--dr, 0deg)));
  opacity: 0; translate: 0 56px; rotate: 6deg;
  transition: opacity 1600ms var(--ease-out), translate 2400ms var(--ease-out), rotate 2400ms var(--ease-out);
}
.ink-item.is-settled::before, .ink-item.is-settled::after { opacity: 1; translate: 0 0; rotate: 0deg; }
/* the copy arrives with its bubble, on the same trigger and clock, rising
   less than the film behind it, so even the entrance has depth */
.ink-item > * { opacity: 0; translate: 0 22px;
  transition: opacity 1600ms var(--ease-out), translate 2400ms var(--ease-out); }
.ink-item.is-settled > * { opacity: 1; translate: 0 0; }
.ink-item::before {
  background: radial-gradient(118% 118% at var(--lx, 33%) var(--ly, 26%), #FDFCF9 0%, #F9F7F3 52%, #F4F1EC 88%, #E6E8EA 100%);
  box-shadow:
    0 30px 60px -44px rgba(20, 18, 14, 0.30),
    0 0 0 1px rgb(42 66 92 / 0.10),
    inset 0 0 0 1px rgb(255 255 255 / 0.7),
    inset 28px 34px 54px -38px rgba(255, 255, 255, 1),
    inset -36px -42px 70px -48px rgb(42 66 92 / 0.20);
}
.ink-item::after {
  pointer-events: none;
  background:
    radial-gradient(13% 8% at calc(var(--lx, 33%) - 3%) calc(var(--ly, 26%) - 10%), rgb(255 255 255 / 0.9) 0%, rgb(255 255 255 / 0) 100%),
    radial-gradient(2.4% 1.6% at calc(var(--lx, 33%) + 10%) calc(var(--ly, 26%) - 16%), rgb(255 255 255 / 0.9) 0%, rgb(255 255 255 / 0) 100%);
}
.ink-item .body-text { max-width: none; }
.ink-item .focus__head { font-size: clamp(26px, 2.6vw, 40px); }
@media (max-width: 719px) {
  .ink-item { width: auto; margin-left: 34px; padding: 20% 12% 22%; }
  .ink-item::before, .ink-item::after { inset: 0 -6%; }
  .ink-item .focus__head { font-size: 28px; }
  .ink-item .body-text { font-size: 15px; line-height: 1.55; }
}
@media (prefers-reduced-motion: reduce) {
  .ink-item > * { transition: none; opacity: 1 !important; translate: none; }
  .ink-item::before, .ink-item::after { transition: none; opacity: 1 !important; translate: none; rotate: none; transform: rotate(var(--r)); }
}
/* without the script nothing would ever settle, so show everything at rest */
.no-js .ink-item > *, .no-js .ink-item::before, .no-js .ink-item::after { opacity: 1; translate: none; rotate: none; }

/* Contact form: honeypot and the status line after sending */
.form__trap { position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden; }
.form__status { margin-bottom: var(--s-6); font-size: var(--t-lead); line-height: 1.5; }
.form__status.is-error { color: var(--accent); }
.form.is-sent .form__field, .form.is-sent .contact__submit { display: none; }
