/* ==========================================================================
   Rooted & Grounded Ministries
   One stylesheet. No build step.

   Contents
   1.  Fonts
   2.  Tokens
   3.  Reset and base
   4.  Typography
   5.  Layout primitives
   6.  Motion
   7.  Navigation
   8.  Hero
   9.  Sections
   10. Footer
   11. Reduced motion
   ========================================================================== */


/* 1. Fonts ================================================================= */

/* Self hosted, latin subset only. Montserrat is a variable font, so it is
   declared across a weight range. Declaring it at a single fixed weight would
   render every element at its default instance, which is Thin. */

@font-face {
  font-family: 'Cormorant Garamond';
  font-style: normal;
  font-weight: 300;
  font-display: swap;
  src: url('assets/fonts/cormorant-300.woff2') format('woff2');
}

@font-face {
  font-family: 'Cormorant Garamond';
  font-style: italic;
  font-weight: 300;
  font-display: swap;
  src: url('assets/fonts/cormorant-300-italic.woff2') format('woff2');
}

@font-face {
  font-family: 'Montserrat';
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url('assets/fonts/montserrat-var.woff2') format('woff2-variations'),
       url('assets/fonts/montserrat-var.woff2') format('woff2');
}


/* 2. Tokens ================================================================ */

:root {
  /* Palette. Exact values from the brand guide. */
  --cream:      #E8E0D6;
  --sage:       #78826E;
  --charcoal:   #4D4D47;
  --moss:       #B0B299;
  --slate:      #788794;
  --brown:      #8C7A63;

  /* Two derived shades, same hue, darkened for contrast compliance only.
     Sage on cream measures 3.08:1 and fails 4.5:1, so sage is never used for
     small text. Cream on brown measures 3.16:1 and fails for body copy.
       --sage-ink  on cream = 4.63:1
       --brown-deep with cream text = 4.53:1 */
  --sage-ink:   #5E6556;
  --brown-deep: #70624F;

  /* Surfaces.
     --paper is white. The brief said cream not white, and Robby overruled it
     after seeing the built page. Charcoal on white measures 8.51:1.
     --slate-deep is slate darkened until cream text clears 4.5:1. Slate as
     given fails against both cream (2.82:1) and charcoal (2.31:1), so it
     cannot carry text at all without this. */
  --paper:      #FFFFFF;
  --slate-deep: #596570;

  /* Type */
  --font-display: 'Cormorant Garamond', Georgia, 'Times New Roman', serif;
  --font-body:    'Montserrat', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;

  /* Scale. Four sizes exist on this site. There is deliberately nothing
     between the section heading and the body size. That cliff is the design. */
  /* The floor is set by the longest authored headline line, which measures
     10.007em. At 1.9rem that is 304px inside the 327px column of a 375px
     screen, so it holds on the narrowest phone in common use. */
  --fs-display: clamp(1.9rem, 6vw, 5.25rem);
  /* Floor set by the longest authored section line, 10.555em wide. At
     1.85rem that is 312px inside the 327px column of a 375px screen. */
  --fs-section: clamp(1.85rem, 4.5vw, 3.5rem);
  /* A fifth step, used only for the hero subheader. A couple of points above
     body, set in the body face, so it reads as a lead line rather than as a
     second heading. */
  --fs-lead:    clamp(1.25rem, 1.6vw, 1.375rem);
  --fs-body:    17px;
  --fs-eyebrow: 11px;

  /* Space */
  --pad-section: clamp(6rem, 14vh, 12rem);
  --w-max:  1200px;
  --w-text: 620px;
  --gutter: clamp(1.5rem, 6vw, 5rem);

  /* Motion */
  --ease: cubic-bezier(0.22, 0.61, 0.36, 1);
  --dur:  400ms;
}


/* 3. Reset and base ======================================================== */

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

html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  background: var(--cream);
  color: var(--charcoal);
  font-family: var(--font-body);
  font-size: var(--fs-body);
  font-weight: 300;
  line-height: 1.7;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

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

/* Radius is 0 everywhere. Buttons are the only exception and they get 2px. */
* { border-radius: 0; }

a { color: inherit; }

:focus-visible {
  outline: 2px solid var(--sage-ink);
  outline-offset: 3px;
}

.skip-link {
  position: absolute;
  left: -9999px;
  top: 0;
  background: var(--charcoal);
  color: var(--cream);
  padding: 0.75rem 1.25rem;
  z-index: 100;
}
.skip-link:focus { left: 0; }

.visually-hidden {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}


/* 4. Typography ============================================================ */

.display {
  font-family: var(--font-display);
  font-weight: 300;
  font-size: var(--fs-display);
  line-height: 0.95;
  letter-spacing: 0.02em;
  margin: 0;
}

.heading {
  font-family: var(--font-display);
  font-weight: 300;
  font-size: var(--fs-section);
  line-height: 1.05;
  letter-spacing: 0.01em;
  margin: 0;
}

/* One word in the hero carries italic. The real Cormorant italic face is
   loaded at this weight, so nothing is synthesised. */
.display em, .heading em {
  font-style: italic;
  font-weight: 300;
}

/* Italic is otherwise reserved for pull quotes and scripture. */
.quote {
  font-family: var(--font-display);
  font-style: italic;
  font-weight: 300;
  font-size: var(--fs-section);
  line-height: 1.25;
  margin: 0;
}

.eyebrow {
  font-family: var(--font-body);
  font-size: var(--fs-eyebrow);
  font-weight: 400;
  line-height: 1.4;
  text-transform: uppercase;
  letter-spacing: 0.18em;
  color: var(--sage-ink);
  margin: 0 0 2rem;
}

.body {
  max-width: var(--w-text);
  margin: 0;
}
.body + .body { margin-top: 1.5rem; }

/* Measure is capped near 62 characters. */
.measure { max-width: 62ch; }

.link {
  font-family: var(--font-body);
  font-size: var(--fs-eyebrow);
  font-weight: 400;
  text-transform: uppercase;
  letter-spacing: 0.18em;
  color: var(--sage-ink);
  text-decoration: none;
  border-bottom: 1px solid currentColor;
  padding-bottom: 0.35em;
  display: inline-block;
  transition: color var(--dur) var(--ease);
}
.link:hover { color: var(--charcoal); }

/* Hairline. Never full width. */
.rule {
  border: 0;
  height: 1px;
  background: var(--sage);
  opacity: 0.3;
  width: 60px;
  margin: 0;
}


/* 5. Layout primitives ===================================================== */

.wrap {
  max-width: var(--w-max);
  margin: 0 auto;
  padding-inline: var(--gutter);
}

.section { padding-block: var(--pad-section); }

/* Rhythm comes from structure, not from alternating colour. These offsets
   let two adjacent cream sections read as distinctly as two different
   surfaces would, while staying quiet. */
.offset-a { margin-left: 0; }
.offset-b { margin-left: clamp(0rem, 12vw, 14rem); }
.offset-c { margin-left: clamp(0rem, 6vw, 7rem); }

/* Surfaces.

   Every section declares the ground it sits on, so the page reads as a
   sequence of rooms rather than one continuous sheet. Cream and paper
   alternate to carry the reading, and the saturated fields are the punctuation
   between them.

   Fields run edge to edge and take more vertical space than a reading section,
   so arriving at one feels like walking into a different room rather than
   passing a coloured band. */

.surface        { background: var(--cream); color: var(--charcoal); }
.surface--paper { background: var(--paper); color: var(--charcoal); }

.field { padding-block: calc(var(--pad-section) * 1.25); }

.field--moss  { background: var(--moss);       color: var(--charcoal); }
.field--slate { background: var(--slate-deep); color: var(--cream); }
.field--brown { background: var(--brown-deep); color: var(--cream); }

/* Moss carries display type only. Charcoal on moss is 3.92:1, which clears
   the 3:1 large text threshold but not the 4.5:1 body one, so nothing set at
   reading size goes on it. */

/* Full cream, no opacity. Cream on either field measures about 4.5:1, which
   is the whole reason both deep shades exist. Dropping it to 0.75 blends it
   toward the background and lands at 3.3:1, which fails at eyebrow size. */
.field--slate .eyebrow,
.field--brown .eyebrow { color: var(--cream); }

.field--slate .link,
.field--brown .link { color: var(--cream); }
.field--slate .link:hover,
.field--brown .link:hover { color: var(--cream); opacity: 0.7; }

.field--slate .rule,
.field--brown .rule { background: var(--cream); }

/* Sage ink is unreadable on both fields. Full cream for the same reason as
   the eyebrow above: at this size there is no contrast to spend on fading. */
.field--slate .note,
.field--brown .note { color: var(--cream); }


/* 5b. Composition ==========================================================

   Four moves, and no decoration. The brief rules out shadows, gradients,
   cards, and icon rows, so the design has to come from where type sits, what
   stays still while other things move, and the brand mark used at a scale it
   was never meant to be read at.

   1. A split: the label and heading hold one narrow column while the reading
      sits in another, and the two swap sides down the page so the eye travels
      laterally instead of tracking one gutter.
   2. The label stays put while its section scrolls under it.
   3. A short hairline opens each section, aligned to the column edge.
   4. Panels with a hairline grid, warming on hover. */

.split { display: grid; gap: clamp(2.5rem, 5vw, 4rem); }

@media (min-width: 900px) {
  .split { grid-template-columns: 5fr 7fr; align-items: start; }
  /* Alternating sides. The label column moves to the right and the reading
     to the left, which is what keeps a long page from feeling like a form. */
  .split--flip > :first-child { order: 2; }
  .split--flip { grid-template-columns: 7fr 5fr; }
}

/* The label holds while the section moves past it. */
@media (min-width: 900px) {
  .split__label { position: sticky; top: clamp(7rem, 14vh, 10rem); }
}

/* The opening mark. A hairline the width of the label, never full width. */
.split__label .eyebrow,
.marked .eyebrow { position: relative; padding-top: 1.75rem; }

.split__label .eyebrow::before,
.marked .eyebrow::before {
  content: '';
  position: absolute;
  top: 0;
  left: 0;
  width: 58px;
  height: 1px;
  background: currentColor;
  opacity: 0.45;
}



/* 6. Motion ================================================================ */

/* Section entry. A 400ms fade and rise, nothing more.
   Scoped to .js. Without JavaScript nothing here applies and the page renders
   complete and visible, which is the correct fallback for a content site. */
.js .reveal {
  opacity: 0;
  transform: translate3d(0, 16px, 0);
  transition: opacity var(--dur) var(--ease), transform var(--dur) var(--ease);
}
.js .reveal.is-in { opacity: 1; transform: none; }

/* Line reveal for display type. Each line sits inside a mask and rises into
   place. JS builds the mask elements. Without JS the text renders normally,
   which is why the masked state is only applied once the markup exists. */
.lines__mask { display: block; overflow: hidden; }

/* Authored lines can never rewrap. If one is too wide for its container the
   fitter in main.js scales the block's type down to fit, so the failure mode
   is slightly smaller type rather than a broken break. Scoped to .js because
   the fitter is what guarantees no overflow; without scripting the lines are
   allowed to wrap naturally instead, which is homely but never clips. */
.js [data-lines-authored] .lines__line { white-space: nowrap; }
.lines__line {
  display: block;
  transform: translate3d(0, 110%, 0);
  transition: transform 900ms var(--ease);
}
.lines.is-in .lines__line { transform: none; }
.lines__line:nth-child(1) { transition-delay: 0ms; }
.lines__mask:nth-child(2) .lines__line { transition-delay: 90ms; }
.lines__mask:nth-child(3) .lines__line { transition-delay: 180ms; }
.lines__mask:nth-child(4) .lines__line { transition-delay: 270ms; }


/* 7. Navigation ============================================================ */

.nav {
  position: fixed;
  inset: 0 0 auto 0;
  z-index: 50;
  background: transparent;
  transition: background var(--dur) var(--ease), border-color var(--dur) var(--ease);
  border-bottom: 1px solid transparent;
}
.nav.is-scrolled {
  background: var(--cream);
  border-bottom-color: rgba(120, 130, 110, 0.3);
}

.nav__inner {
  max-width: var(--w-max);
  margin: 0 auto;
  padding: 1.5rem var(--gutter);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 2rem;
}

.nav__brand { display: block; line-height: 0; }
.nav__brand img { width: clamp(150px, 20vw, 210px); }

.nav__links {
  display: flex;
  align-items: center;
  gap: clamp(1.5rem, 3vw, 2.75rem);
  list-style: none;
  margin: 0;
  padding: 0;
}

.nav__link {
  font-size: var(--fs-eyebrow);
  font-weight: 400;
  text-transform: uppercase;
  letter-spacing: 0.18em;
  text-decoration: none;
  color: var(--charcoal);
  padding-bottom: 0.25em;
  border-bottom: 1px solid transparent;
  transition: border-color var(--dur) var(--ease);
}
.nav__link:hover,
.nav__link[aria-current='page'] { border-bottom-color: var(--sage); }

.nav__toggle {
  display: none;
  background: none;
  border: 0;
  padding: 0.5rem;
  cursor: pointer;
  color: var(--charcoal);
}
.nav__toggle span {
  display: block;
  width: 22px;
  height: 1px;
  background: currentColor;
  transition: transform var(--dur) var(--ease), opacity var(--dur) var(--ease);
}
.nav__toggle span + span { margin-top: 6px; }
.nav__toggle[aria-expanded='true'] span:nth-child(1) { transform: translateY(7px) rotate(45deg); }
.nav__toggle[aria-expanded='true'] span:nth-child(2) { opacity: 0; }
.nav__toggle[aria-expanded='true'] span:nth-child(3) { transform: translateY(-7px) rotate(-45deg); }

@media (max-width: 767px) {
  .nav__toggle { display: block; }
  .nav__links {
    position: fixed;
    inset: 0;
    flex-direction: column;
    justify-content: center;
    align-items: flex-start;
    gap: 2.5rem;
    background: var(--cream);
    padding: 0 var(--gutter);
    transform: translate3d(0, -100%, 0);
    transition: transform var(--dur) var(--ease);
  }
  .nav__links.is-open { transform: none; }
  .nav__link { font-size: 13px; }
}


/* 8. Hero ================================================================== */

/* No image. The emptiness is the image. Roughly seventy percent of the first
   screen is bare cream, the display line sits on the optical third, and the
   scale drop from display to body happens with nothing in between. */

/* The whole composition sits at the top and the emptiness falls below it.

   Centring put 27vh of empty cream above the headline, which on the home page
   is scrolled through the moment the entry sequence ends, and that space was
   the lag between the verse and the headline. Pushing the foot to the bottom
   with space-between fixed the lag and opened an equally bad hole between the
   sub line and the button. So neither: the group runs from the top in its own
   spacing, and what is left over collects underneath, where it is the quiet
   the hero was always meant to have. */
.hero {
  min-height: 100svh;
  display: flex;
  flex-direction: column;
  justify-content: flex-start;
  padding-block: clamp(7rem, 13vh, 9.5rem) clamp(3rem, 8vh, 5rem);
  position: relative;
}

.hero__inner {
  max-width: var(--w-max);
  margin: 0 auto;
  padding-inline: var(--gutter);
  width: 100%;
}

/* No authored break. The line balancer evens the rows out itself, which is
   what stops a short line sitting next to a long gap. The only manual touch
   is the non breaking space in the markup tying "&" to "Grounded", since an
   ampersand alone at the end of a line is the one break the balancer will
   still happily make. */
/* The lines are authored in the markup, so nothing here decides a break. */
.hero__title { max-width: 100%; }

/* The very smallest screens still in use, 320px wide. The column drops to
   272px there, which the 1.9rem floor would overrun by 32px, so the floor
   comes down just for them rather than shrinking the scale for everyone. */
@media (max-width: 360px) {
  :root {
    --fs-display: 1.65rem;
    --fs-section: 1.55rem;
  }
}

/* The subheader. Same face as the body copy beneath it, a couple of points
   larger and weighted, so it separates by emphasis rather than by size.
   Equal space above and below, so it sits balanced between the display line
   and the body rather than hanging off either one.

   Note: the brief says Montserrat at 300 or 400 only and never bold body.
   This is a deliberate exception for the one lead line, at 600 rather than
   700, so it carries against 300 without shouting on a page this quiet. */
.hero__lead {
  font-family: var(--font-body);
  font-size: var(--fs-lead);
  font-weight: 600;
  line-height: 1.5;
  letter-spacing: 0.005em;
  margin: clamp(2rem, 4.5vh, 3rem) 0 0;
  max-width: 42ch;
}

/* Scripture in the lead slot. Same size and position, set in the display
   italic because the type rule reserves italic for scripture and pull quotes,
   and bold sans would be the wrong register for it. */
.hero__lead--verse {
  font-family: var(--font-display);
  font-style: italic;
  font-weight: 300;
  font-size: calc(var(--fs-lead) * 1.35);
  letter-spacing: 0.01em;
}

/* The reference, on its own line beneath. Body face at eyebrow scale so it
   reads as a citation, matching the treatment in the entry sequence. */
.hero__ref {
  display: block;
  margin-top: 0.9rem;
  font-family: var(--font-body);
  font-style: normal;
  font-size: var(--fs-eyebrow);
  font-weight: 400;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--sage-ink);
}

.hero__sub {
  margin: clamp(2rem, 4.5vh, 3rem) 0 0;
  max-width: 56ch;
}

/* Primary action. The one filled control on the site, so it stays primary by
   being the only one. Cream on sage-ink measures 4.63:1, which passes. */
.btn {
  display: inline-block;
  border: 1px solid var(--sage-ink);
  border-radius: 2px;
  background: var(--sage-ink);
  color: var(--cream);
  font-family: var(--font-body);
  font-size: var(--fs-eyebrow);
  font-weight: 400;
  text-transform: uppercase;
  letter-spacing: 0.18em;
  text-decoration: none;
  padding: 0.95rem 1.6rem;
  cursor: pointer;
  transition: background var(--dur) var(--ease), color var(--dur) var(--ease);
}
.btn:hover { background: transparent; color: var(--sage-ink); }

/* The hero button is an anchor to the one form on the site, which lives in
   the footer. Smooth scrolling is set here rather than in script so it also
   works with JavaScript off, and so the reduced motion block can override it
   in a single place. The offset keeps the fixed navigation from covering the
   form on arrival. */
html { scroll-behavior: smooth; }

#subscribe { scroll-margin-top: clamp(6rem, 14vh, 9rem); }

/* Beehiiv injects an iframe here. Its type, spacing, and button are its own
   and cannot be reached from this stylesheet, so it is given room and left
   alone rather than half styled. */
.subscribe-embed {
  margin-top: 1.5rem;
}

/* Beehiiv's form page carries a fixed 80px padding on every side that cannot
   be styled away from outside, and its loader parks the iframe at a 2000px
   placeholder. So the wrapper is a window: it crops the frame to just the
   form, shifted up and left by exactly that padding. Verified at 344px and
   440px that the padding is 80px at both, so the crop holds at every column
   width, and the form fills the wrapper edge to edge, left aligned with the
   text above it. */
.subscribe-embed {
  position: relative;
  overflow: hidden;
  height: 56px;
}
.subscribe-embed iframe {
  position: absolute;
  top: -76px;
  left: -80px;
  width: calc(100% + 160px);
  max-width: none;
  height: 300px !important;
  display: block;
}

.hero__actions {
  display: flex;
  align-items: center;
  gap: clamp(1.5rem, 4vw, 3rem);
  flex-wrap: wrap;
}

/* In flow, not absolute, so it can never land on top of the copy above it
   however long that copy gets. */
.hero__foot {
  max-width: var(--w-max);
  width: 100%;
  margin: clamp(3rem, 7vh, 4.5rem) auto 0;
  padding-inline: var(--gutter);
}

/* The dissolve. Driven by --p from main.js, one value, three stagger points.
   Type drifts up, lifts a hair, and fades as the visitor scrolls away. */
.hero__title, .hero__lead, .hero__sub, .hero__foot {
  will-change: transform, opacity;
}
.hero__sub {
  opacity: calc(1 - var(--l-sub, 0));
  transform: translate3d(0, calc(var(--l-sub, 0) * -48px), 0);
  filter: blur(calc(var(--l-sub, 0) * 5px));
}
.hero__lead {
  opacity: calc(1 - var(--l-lead, 0));
  transform: translate3d(0, calc(var(--l-lead, 0) * -56px), 0);
  filter: blur(calc(var(--l-lead, 0) * 5px));
}
.hero__title {
  opacity: calc(1 - var(--l-title, 0));
  transform: translate3d(0, calc(var(--l-title, 0) * -64px), 0)
             scale(calc(1 + var(--l-title, 0) * 0.04));
  filter: blur(calc(var(--l-title, 0) * 6px));
}
.hero__foot {
  opacity: calc(1 - var(--l-foot, 0));
  transform: translate3d(0, calc(var(--l-foot, 0) * -32px), 0);
}

/* Large blurred type is expensive on mobile GPUs. Drift and fade carry it. */
@media (max-width: 767px) {
  .hero__sub, .hero__lead, .hero__title { filter: none; }
}


/* Repeat visits this session skip the entry outright. main.js removes the
   element; this hides it in the moment before that script runs. */
.no-entry .entry { display: none; }


/* 9. Sections ============================================================== */

.statement { max-width: 22ch; }

/* The four questions. They hold their own section, so they are set one to a
   line rather than run together as a paragraph. Display italic rather than the
   body face, so they read as something asked of the reader rather than more
   description. No bullets and no rules between them: the space is the only
   separation they need, and each one is meant to be answered on its own. */
/* Half of what this was. The questions still want air, but at the earlier
   height the gaps read as absence rather than as pacing. */
.section--asks { padding-block: calc(var(--pad-section) * 0.95); }

.asks {
  list-style: none;
  margin: clamp(2rem, 6vh, 3.5rem) 0 0;
  padding: 0;
  font-family: var(--font-display);
  font-style: italic;
  font-weight: 300;
  font-size: clamp(1.4rem, 3vw, 2.4rem);
  line-height: 1.35;
  color: var(--sage-ink);
}

/* Still separated, but closer, so the five read as one passage. */
.asks li + li { margin-top: clamp(1.25rem, 5vh, 3rem); }

/* Each question is its own reveal target, so they arrive one at a time as you
   scroll rather than all at once. Slower and further than the site default,
   because this is the one place where the motion is the point. The delays
   keep the cascade even on a tall screen where two enter view together. */
.js .asks li {
  opacity: 0;
  transform: translate3d(0, 32px, 0);
  transition: opacity 900ms var(--ease), transform 900ms var(--ease);
}
.js .asks li.is-in { opacity: 1; transform: none; }

.js .asks li:nth-child(2) { transition-delay: 100ms; }
.js .asks li:nth-child(3) { transition-delay: 200ms; }
.js .asks li:nth-child(4) { transition-delay: 300ms; }
.js .asks li:nth-child(5) { transition-delay: 400ms; }

/* Wrapped questions on narrow screens indent their second line, so a long
   question still reads as one item rather than two. */
.asks li {
  padding-left: 1.25em;
  text-indent: -1.25em;
}

/* The six lines. A quiet list. No numbers, no cards, no icons. */
.list { border-top: 1px solid rgba(120, 130, 110, 0.3); }

/* The six, as panels.

   The grid gap is the hairline. Setting the container to sage and the panels
   to the surface colour means the rules between them are the gaps themselves,
   so there are no borders to misalign at the seams.

   Each panel shows only its mark and its name. On hover it warms to cream,
   grows slightly into its neighbours, and opens its description. */

.cards {
  display: grid;
  gap: 1px;
  background: rgba(120, 130, 110, 0.3);
  border: 1px solid rgba(120, 130, 110, 0.3);
}
@media (min-width: 780px) {
  .cards { grid-template-columns: 1fr 1fr; }
}

.card {
  position: relative;
  z-index: 1;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  min-height: clamp(13rem, 24vh, 17rem);
  padding: clamp(1.75rem, 3.5vw, 2.75rem);
  background: var(--paper);
  text-decoration: none;
  color: inherit;
  transition: background var(--dur) var(--ease), transform 500ms var(--ease);
}

/* It grows into its neighbours rather than pushing them, which is why it has
   to come forward in the stack. No shadow does the lifting. The surface
   change and the scale do. */
/* The grown, warmed state. On devices that hover it rides :hover; on touch
   it rides .is-open, set by the accordion in main.js, because touch fakes
   hover on tap and the fake one sticks. */
@media (hover: hover) {
  .card:hover {
    background: var(--cream);
    transform: scale(1.035);
    z-index: 3;
  }
}
.card:focus-visible,
.card.is-open {
  background: var(--cream);
  z-index: 3;
}

/* Geometric marks, not pictorial icons. Hairline stroke, no fill, all six on
   the same 24 unit grid so they read as one set. */
.card__mark {
  width: 30px;
  height: 30px;
  margin-bottom: auto;
  fill: none;
  stroke: var(--sage-ink);
  stroke-width: 1;
  stroke-linecap: round;
  stroke-linejoin: round;
  opacity: 0.85;
  transition: opacity var(--dur) var(--ease);
}
@media (hover: hover) { .card:hover .card__mark { opacity: 1; } }
.card.is-open .card__mark { opacity: 1; }

/* Touch affordance: a small plus in the corner, turning to a close mark when
   the card is open, so a tappable card does not look like a dead one. */
@media (hover: none) {
  .card::before {
    content: '+';
    position: absolute;
    top: clamp(1.5rem, 3vw, 2.5rem);
    right: clamp(1.5rem, 3vw, 2.5rem);
    font-family: var(--font-display);
    font-size: 1.6rem;
    line-height: 1;
    color: var(--sage-ink);
    transition: transform var(--dur) var(--ease);
  }
  .card.is-open::before { transform: rotate(45deg); }
}

.card__name {
  font-family: var(--font-display);
  font-size: clamp(1.6rem, 2.6vw, 2.15rem);
  font-weight: 300;
  line-height: 1.1;
  margin: 1.5rem 0 0;
}

/* The description is held at zero height and opened on hover. Animating grid
   rows from 0fr to 1fr lets it expand to its own natural height without
   anyone having to measure it or hard code one. */
.card__reveal {
  display: grid;
  grid-template-rows: 0fr;
  opacity: 0;
  margin-top: 0;
  transition: grid-template-rows 500ms var(--ease),
              opacity 350ms var(--ease),
              margin-top 500ms var(--ease);
}
.card__reveal > div { overflow: hidden; }

@media (hover: hover) {
  .card:hover .card__reveal {
    grid-template-rows: 1fr;
    opacity: 1;
    margin-top: 1rem;
  }
}
/* Focus opening is a keyboard affordance, so it lives with hover devices.
   On touch, tap focus in some browsers matches :focus-visible and was a
   second, unscripted way for cards to fall open. */
@media (hover: hover) {
  .card:focus-visible .card__reveal {
    grid-template-rows: 1fr;
    opacity: 1;
    margin-top: 1rem;
  }
}
.card.is-open .card__reveal {
  grid-template-rows: 1fr;
  opacity: 1;
  margin-top: 1rem;
}

.card__desc { margin: 0; max-width: 34ch; }

.card__note {
  font-size: var(--fs-eyebrow);
  text-transform: uppercase;
  letter-spacing: 0.18em;
  color: var(--sage-ink);
  margin: 1rem 0 0;
}


/* Each row is its own small composition: the name held left, the sentence
   set in from it. On hover a hairline draws across underneath and the row
   steps right, so the list answers without anything appearing or moving in. */
.list__item {
  position: relative;
  display: grid;
  gap: 0.5rem clamp(2rem, 5vw, 4rem);
  text-decoration: none;
  color: inherit;
  padding-block: clamp(2rem, 4vh, 3rem);
  border-bottom: 1px solid rgba(120, 130, 110, 0.3);
  transition: transform var(--dur) var(--ease);
}
@media (min-width: 780px) {
  .list__item { grid-template-columns: 5fr 7fr; align-items: baseline; }
}

.list__item::after {
  content: '';
  position: absolute;
  left: 0;
  bottom: -1px;
  height: 1px;
  width: 0;
  background: var(--sage-ink);
  transition: width 600ms var(--ease);
}
.list__item:hover { transform: translateX(0.75rem); }
.list__item:hover::after,
.list__item:focus-visible::after { width: 100%; }

.list__name {
  font-family: var(--font-display);
  font-size: clamp(1.75rem, 3vw, 2.5rem);
  font-weight: 300;
  line-height: 1.1;
  margin: 0 0 0.75rem;
}

.list__note {
  font-size: var(--fs-eyebrow);
  text-transform: uppercase;
  letter-spacing: 0.18em;
  color: var(--sage-ink);
  margin: 0.9rem 0 0;
}

/* Two blocks, held apart by space rather than by a box. */
.pair { display: grid; gap: clamp(3rem, 8vh, 6rem); }
@media (min-width: 900px) {
  .pair { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: clamp(3rem, 6vw, 6rem); }
}

.event { padding-block: clamp(1.5rem, 3vh, 2.5rem); }
.event + .event { border-top: 1px solid rgba(120, 130, 110, 0.3); }
.event__meta {
  font-size: var(--fs-eyebrow);
  text-transform: uppercase;
  letter-spacing: 0.18em;
  color: var(--sage-ink);
  margin: 0 0 0.75rem;
}
.event__name {
  font-family: var(--font-display);
  font-size: clamp(1.5rem, 2.5vw, 2rem);
  font-weight: 300;
  line-height: 1.15;
  margin: 0 0 0.5rem;
}

/* Who is actually running it. Set below the title and smaller than body copy,
   because it is the qualifier on the name rather than a claim of its own. */
.event__host {
  font-family: var(--font-body);
  font-size: 15px;
  line-height: 1.6;
  color: var(--sage-ink);
  max-width: var(--w-text);
  margin: 0 0 0.5rem;
}


/* 9b. Inner pages ========================================================== */

/* Page opener. The same restraint as the home hero, at less than half the
   height, so an inner page does not pretend to be a front door. */
.opener {
  padding-block: clamp(9rem, 22vh, 14rem) clamp(3rem, 8vh, 6rem);
}

/* The only photograph on the site. Shown whole and uncropped, sitting
   alongside the writing about the two of them. */
.people {
  display: grid;
  gap: clamp(2.5rem, 6vw, 4.5rem);
  align-items: start;
}
@media (min-width: 900px) {
  .people { grid-template-columns: 1fr 1fr; }
}
.people figure { margin: 0; }
.people img { width: 100%; }

/* Their degrees are their own. Everything else is said about the two of
   them together, so the credentials sit apart and quiet, below the story
   rather than instead of it. */
.credits {
  display: grid;
  gap: clamp(2rem, 5vw, 3.5rem);
  margin-top: clamp(3rem, 8vh, 5rem);
}
@media (min-width: 780px) {
  .credits { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
.credits p { margin: 0; }
.credits .body { font-size: 15px; }

/* A value, or a programme. One shape, used for both, held apart by space
   and a hairline rather than by a box. */
/* What We Do ===============================================================

   One ground for the whole page. The interest is meant to come from the jump
   between a name set very large and prose set small beside it, and from the
   reading column changing sides on the way down. Colour is not doing any of
   the work here. */

.index-wrap { padding-block: 0 calc(var(--pad-section) * 0.6); }

.index { list-style: none; margin: 0; padding: 0; }

.index__row { border-top: 1px solid rgba(120, 130, 110, 0.3); }
.index__row:last-child { border-bottom: 1px solid rgba(120, 130, 110, 0.3); }

.index__link {
  display: grid;
  grid-template-columns: 3.5rem 1fr;
  align-items: baseline;
  gap: 0 clamp(1rem, 3vw, 2.5rem);
  padding-block: clamp(1.25rem, 3vh, 2rem);
  text-decoration: none;
  color: var(--charcoal);
  transition: color var(--dur) var(--ease);
}

@media (min-width: 800px) {
  .index__link { grid-template-columns: 3.5rem 1fr auto; }
}

.index__num {
  font-family: var(--font-body);
  font-size: var(--fs-eyebrow);
  letter-spacing: 0.18em;
  color: var(--sage-ink);
  transition: color var(--dur) var(--ease);
}

.index__name {
  font-family: var(--font-display);
  font-weight: 300;
  font-size: clamp(1.6rem, 3.4vw, 2.75rem);
  line-height: 1.1;
  /* Moves toward the reader on hover. The only motion the row has. */
  transition: transform var(--dur) var(--ease);
}

.index__note {
  grid-column: 2;
  font-family: var(--font-body);
  font-size: var(--fs-eyebrow);
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--sage-ink);
  margin-top: 0.5rem;
}
@media (min-width: 800px) {
  .index__note { grid-column: 3; margin-top: 0; text-align: right; }
}

@media (hover: hover) {
  .index__link:hover .index__name { transform: translateX(clamp(0.5rem, 1.5vw, 1.25rem)); }
  .index__link:hover .index__num  { color: var(--charcoal); }
}
.index__link:focus-visible { outline: 2px solid var(--sage-ink); outline-offset: 4px; }


/* Each of the four. The name is the largest thing on the page and the prose
   is deliberately small next to it. That gap is the design. */
.work { padding-block: var(--pad-section); }
.work + .work { border-top: 1px solid rgba(120, 130, 110, 0.3); }

.work__num {
  font-family: var(--font-body);
  font-size: var(--fs-eyebrow);
  letter-spacing: 0.18em;
  color: var(--sage-ink);
  margin: 0 0 clamp(1.5rem, 4vh, 2.5rem);
}

.work__name { margin: 0 0 clamp(2rem, 5vh, 3.25rem); }

/* Below 900px the prose simply follows the name. Above it, the reading column
   sits to one side and swaps sides section by section, which is what gives
   the page its rhythm now that the grounds are all the same. */
.work__body { max-width: var(--w-text); }

@media (min-width: 900px) {
  .work__body { margin-left: 50%; }
  .work--flip .work__body { margin-left: 0; margin-right: 50%; }
  .work--flip .work__num,
  .work--flip .work__name { text-align: right; }
  .work--flip .work__name .lines__line { display: block; }
}


/* The four values. A set, so they are laid out as one, two up on desktop and
   stacked below 900px. The name takes the item size already used by event
   names rather than the section size, so Our Values stays the only heading in
   the room. */
.values { display: grid; gap: clamp(2.5rem, 7vh, 4rem) clamp(2.5rem, 5vw, 4.5rem); }
@media (min-width: 900px) {
  .values { grid-template-columns: 1fr 1fr; }
}
.values__name {
  font-family: var(--font-display);
  font-size: clamp(1.5rem, 2.5vw, 2rem);
  font-weight: 300;
  line-height: 1.15;
  margin: 0 0 1rem;
}
/* The reading measure is the column here, not the fixed text width. */
.values .body { max-width: none; }

/* Books. The same quiet list as everything else on the site: a title you can
   click and one line saying what it is. The title carries the display face so
   it reads as a title without italics, which are reserved for scripture. */
.books {
  list-style: none;
  margin: clamp(2rem, 5vh, 3rem) 0 0;
  padding: 0;
  max-width: var(--w-text);
}

.books li + li { margin-top: clamp(1.5rem, 3vh, 2rem); }

.books__title {
  display: inline-block;
  font-family: var(--font-display);
  font-size: clamp(1.25rem, 1.8vw, 1.5rem);
  font-weight: 300;
  line-height: 1.2;
  color: var(--charcoal);
  text-decoration: none;
  border-bottom: 1px solid var(--sage);
  padding-bottom: 0.1em;
  transition: color var(--dur) var(--ease), border-color var(--dur) var(--ease);
}
.books__title:hover {
  color: var(--sage-ink);
  border-bottom-color: var(--sage-ink);
}

.books__meta {
  display: block;
  font-family: var(--font-body);
  font-size: 15px;
  line-height: 1.6;
  color: var(--sage-ink);
  margin-top: 0.6rem;
}

/* Marks something that is planned rather than built. Used where the copy
   would otherwise imply it already exists. */
.note {
  font-size: var(--fs-eyebrow);
  text-transform: uppercase;
  letter-spacing: 0.18em;
  color: var(--sage-ink);
  margin: 1.5rem 0 0;
}

/* Givebutter mounts here. Nothing is faked in the meantime. */
/* Give page. The argument reads down the left, the means to act on it sits
   beside it on the right and stays put while you read. Stacked below 900px,
   argument first, because the case has to be made before the form matters. */
.give-grid { display: grid; gap: clamp(2.5rem, 5vw, 4rem); }

@media (min-width: 900px) {
  .give-grid { grid-template-columns: 7fr 5fr; align-items: start; }
  .give-grid__panel { position: sticky; top: clamp(7rem, 14vh, 10rem); }
}

/* The Givebutter form. A third party widget with its own type and controls,
   none of which can be restyled from out here, so it is simply given room at
   its own natural width rather than dressed up to look like something it is
   not. The reserved height stops the column jumping when it loads. */
.embed {
  margin-block: clamp(1.5rem, 3vh, 2rem) 0;
  max-width: 440px;
  min-height: 380px;
}


/* 10. Footer =============================================================== */

.footer {
  background: var(--cream);
  border-top: 1px solid rgba(120, 130, 110, 0.3);
  padding-block: clamp(4rem, 10vh, 7rem) 3rem;
  font-size: 15px;
}

.footer__grid {
  display: grid;
  gap: clamp(2.5rem, 6vh, 4rem);
}
@media (min-width: 900px) {
  /* The third column holds the Beehiiv form, whose loader drops into a
     cramped mobile layout in any container under roughly 400px. The column
     is sized past that threshold so the form renders at full desktop width,
     and the gap is tightened to pay for it. */
  .footer__grid {
    grid-template-columns: 0.85fr 1.05fr 1.6fr;
    gap: clamp(2rem, 4vw, 3.5rem);
  }
}

.footer__monogram { width: 44px; margin-bottom: 1.75rem; }

.footer a { color: inherit; text-decoration: none; border-bottom: 1px solid rgba(120, 130, 110, 0.5); }
.footer a:hover { border-bottom-color: var(--charcoal); }

.footer__list { list-style: none; margin: 0; padding: 0; }
.footer__list li + li { margin-top: 0.6rem; }

.form { display: flex; gap: 0.75rem; flex-wrap: wrap; margin-top: 1.25rem; }

.form input[type='email'] {
  flex: 1 1 12rem;
  min-width: 0;
  font-family: var(--font-body);
  font-size: 15px;
  font-weight: 300;
  color: var(--charcoal);
  background: transparent;
  border: 0;
  border-bottom: 1px solid var(--sage);
  padding: 0.6rem 0;
}
.form input[type='email']::placeholder { color: var(--sage-ink); opacity: 0.8; }

.form button {
  border-radius: 2px;
  border: 1px solid var(--sage-ink);
  background: transparent;
  color: var(--sage-ink);
  font-family: var(--font-body);
  font-size: var(--fs-eyebrow);
  font-weight: 400;
  text-transform: uppercase;
  letter-spacing: 0.18em;
  padding: 0.7rem 1.4rem;
  cursor: pointer;
  transition: background var(--dur) var(--ease), color var(--dur) var(--ease);
}
.form button:hover { background: var(--sage-ink); color: var(--cream); }

.footer__legal {
  margin-top: clamp(3rem, 8vh, 5rem);
  padding-top: 2rem;
  border-top: 1px solid rgba(120, 130, 110, 0.3);
  font-size: 13px;
  color: var(--sage-ink);
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem 2rem;
}
.footer__legal p { margin: 0; }


/* 11. Entry sequence =======================================================
   Four steps: travel through the forest, the forest opens onto a plane, a
   single tree resolves into the mark, then the mark rises into the nav.

   Steps one to three play themselves from --t. Step four is driven by the
   visitor's own scroll from --s. Both values are written by main.js.

   The forest and the plane are abstract vector in brand colour. Every layer
   below carries a data-plate attribute, so a real image can be dropped in as
   a background on that element later without touching the choreography. */

/* Nothing here fades. Every move is geometry: things travel in depth, pass
   the camera, and leave the frame because they are too close to be in it.
   The one colour change is the sky, which is a dawn rather than a dissolve.
   The sky finishes on exactly the page's cream, so the sequence does not end,
   it simply becomes the page. */

.entry { display: none; }

/* Only ever shown when scripted. Without JS the page starts at the hero. */
/* 170vh means 70vh of actual scroll travel. This one number sets the pace of
   the entire opening: every beat inside it is a fraction of this, so raising
   it slows the zoom and lengthens the verse together, and lowering it speeds
   both up. At 28vh the whole sequence ran in about two turns of a wheel,
   which is why it read as fast however the fractions were arranged.

   At 70vh: the zoom takes 32vh, the verse holds still for 27vh, and the tail
   behind it is under 1vh. */
.js .entry {
  display: block;
  position: relative;
  height: 170vh;
  z-index: 40;
}

.entry__sticky {
  position: sticky;
  top: 0;
  height: 100vh;
  height: 100svh;
  overflow: hidden;
  background: var(--cream);
}

/* The tree we travel into. Centred, so the zoom carries you through the
   middle of it rather than past it.

   max-width has to be released here. The global image reset caps every image
   at the width of its container, which would silently stop the zoom the
   moment the tree reached the edge of the screen, which is exactly where it
   is supposed to keep going. */
/* The opening frame, in CSS.

   main.js sizes and places all three of these on every frame, but it cannot
   do it before the browser has painted, and until then an SVG with no width
   takes whatever the box gives it. On a phone that meant the tree arriving
   at full bleed for a beat before the script caught it, which is the first
   thing a visitor from the QR code saw.

   These are the p=0 values of the very same formulas, so the first painted
   frame is already the lockup and main.js agrees with it rather than
   correcting it. Kept in svh to match .entry__sticky, since the mobile
   address bar makes vh and svh different heights.

     mark width   RING_START * 446/487   23.811svh
     height stays auto, from the viewBox, because main.js sets width only
     word width   markW * WORD_RATIO 30.764svh
     word top     markH/2 + markW * GAP_RATIO   15.662svh */
.entry__tree {
  position: absolute;
  left: 50%;
  top: 50%;
  max-width: none;
  width: 23.811svh;
  transform: translate3d(-11.906svh, -13svh, 0);
  transform-origin: 50% 50%;
  will-change: transform, opacity;
}

/* The ellipse. Same viewBox as the tree, so the two overlay exactly and
   reproduce the mark without any positioning guesswork. It opens outward
   ahead of the tree and simply leaves the frame, no fade. */
.entry__ring {
  position: absolute;
  left: 50%;
  top: 50%;
  max-width: none;
  width: 23.811svh;
  transform: translate3d(-11.906svh, -13svh, 0);
  transform-origin: 50% 50%;
  will-change: transform;
}

/* The wordmark, seated below the mark at the lockup's own proportions.
   Sized and placed from main.js against the ellipse, so the opening frame
   matches the artwork rather than approximating it. */
.entry__word {
  position: absolute;
  left: 50%;
  top: 50%;
  max-width: none;
  width: 30.764svh;
  transform: translate3d(-15.382svh, 15.662svh, 0);
  will-change: transform;
}

/* Revealed once you are inside the tree. Held below display scale on
   purpose, so it reads as a held breath rather than competing with the
   headline that follows it. */
.entry__verse {
  position: absolute;
  left: 50%;
  top: 50%;
  /* Scripture carries the italic, per the type rule. The real Cormorant
     italic face is loaded at this weight, so nothing is synthesised. */
  font-style: italic;
  /* Wide enough to hold the whole verse on one line down to about a 520px
     viewport. Below that it balances onto two rather than stranding a word. */
  width: min(92vw, 1000px);
  margin: 0;
  text-align: center;
  text-wrap: balance;
  font-family: var(--font-display);
  font-weight: 300;
  font-size: clamp(1.75rem, 3.4vw, 2.75rem);
  line-height: 1.15;
  letter-spacing: 0.01em;
  color: var(--charcoal);
  /* Its p=0 state, for the same reason as the mark above: before main.js has
     painted a frame, the verse would otherwise sit fully visible across the
     lockup. It is not read until you are inside the tree. */
  opacity: 0;
  transform: translate3d(-50%, calc(-50% + 2.5vh), 0);
  will-change: transform, opacity;
}

/* The reference under the verse. Set in the body face at eyebrow scale so it
   reads as a citation rather than as a second line of scripture. It rides the
   same transform as the verse because it sits inside it. */
.entry__ref {
  display: block;
  /* The citation is not scripture, so it stands upright. */
  font-style: normal;
  margin-top: 1.5rem;
  font-family: var(--font-body);
  font-size: var(--fs-eyebrow);
  font-weight: 400;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--sage-ink);
}

/* Sits over the wordmark and takes over from it as it blows away. */
.entry__dust {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
}

.entry__cue {
  position: absolute;
  left: 0; right: 0;
  bottom: clamp(2rem, 6vh, 4rem);
  text-align: center;
  overflow: hidden;
  /* Its p=0 state. Defaulting to 0 held the cue below its mask until the
     first frame ran, so the opening briefly had no invitation to scroll. */
  --cue: 1;
}
.entry__cue p {
  font-size: var(--fs-eyebrow);
  text-transform: uppercase;
  letter-spacing: 0.18em;
  margin: 0;
  color: var(--sage);
  transform: translate3d(0, calc((1 - var(--cue, 0)) * 130%), 0);
  will-change: transform;
}

/* The nav simply is not there while the sequence is running, and is there
   once it is over. No travel, no landing. */
.js.entry-active .nav { opacity: 0; pointer-events: none; }
.nav { transition: opacity 500ms var(--ease); }


/* 12. Reduced motion ======================================================= */

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
  .reveal { opacity: 1; transform: none; }
  .js .asks li { opacity: 1; transform: none; }
  .lines__line { transform: none; }
  /* The sequence is skipped outright rather than played fast. */
  .js .entry { display: none; }
  /* The hero renders fully composed and static. No half animation. */
  .hero__title, .hero__lead, .hero__sub, .hero__foot {
    opacity: 1 !important;
    transform: none !important;
    filter: none !important;
  }
}
