/* =====================================================================
   LADIES ARISE
   Design direction: "Dawn over Chadwell Heath"
   Plum night giving way to gold light (Isaiah 60:1).
   Palette sampled from the ministry's own mission photography.

   To rebrand the entire site, change the tokens in :root only.
   ===================================================================== */

/* ---------------------------------------------------------------
   1. TOKENS
   --------------------------------------------------------------- */
:root {
  /* Ground */
  --night:        #17091E;
  --night-2:      #200D28;
  --plum:         #3B1740;
  --plum-soft:    #52226044;
  --plum-line:    #5A2A66;

  /* Light */
  --gold:         #E5A93C;
  --gold-lift:    #F7D794;
  --gold-deep:    #8C5E10;   /* 5.24:1 on --cream. Small text safe. */
  --gold-glow:    #B77F1E;   /* decorative gradients only, never text */

  /* Warmth (reserved: leadership + focus states only) */
  --rose:         #D98BA0;

  /* Paper */
  --cream:        #FBF6EF;
  --cream-2:      #F2E9DD;
  --ink:          #221429;
  --ink-soft:     #5C4A63;

  /* Semantic */
  --bg:           var(--night);
  --fg:           var(--cream);
  --fg-muted:     #C3AFCB;

  /* Type */
  --font-display: "Fraunces", "Iowan Old Style", Georgia, serif;
  --font-body:    "Karla", "Helvetica Neue", Arial, sans-serif;

  --fs-display: clamp(3rem, 8.5vw, 7rem);
  --fs-h1:      clamp(2.4rem, 5.5vw, 4.25rem);
  --fs-h2:      clamp(1.9rem, 4vw, 3rem);
  --fs-h3:      clamp(1.3rem, 2.1vw, 1.7rem);
  --fs-lead:    clamp(1.1rem, 1.5vw, 1.35rem);
  --fs-body:    1.0625rem;
  --fs-small:   0.9375rem;
  --fs-eyebrow: 0.75rem;

  /* Space */
  --band-y:     clamp(4.5rem, 9vw, 8.5rem);
  --gutter:     clamp(1.25rem, 4vw, 3rem);

  /* Motion */
  --ease:       cubic-bezier(0.22, 1, 0.36, 1);
  --dur:        0.7s;

  /* Shape */
  --radius:     2px;
  --radius-lg:  4px;
}

/* ---------------------------------------------------------------
   2. RESET
   --------------------------------------------------------------- */
*, *::before, *::after { box-sizing: border-box; }

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
}

body {
  margin: 0;
  background: var(--bg);
  color: var(--fg);
  font-family: var(--font-body);
  font-size: var(--fs-body);
  line-height: 1.65;
  font-optical-sizing: auto;
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}

img, svg, video { display: block; max-width: 100%; }
/* <picture> wraps every photo for the webp source. display:contents keeps
   the <img> as the real grid/flex child so no layout rule needs changing. */
picture { display: contents; }
img { height: auto; }

h1, h2, h3, h4, p, figure, blockquote, ul, ol, dl { margin: 0; }
ul, ol { padding: 0; list-style: none; }

a { color: inherit; text-decoration: none; }

button, input, textarea, select {
  font: inherit;
  color: inherit;
}

/* Accessible focus. Never removed, only restyled. */
:focus-visible {
  outline: 2px solid var(--gold);
  outline-offset: 3px;
  border-radius: var(--radius);
}

.skip-link {
  position: absolute;
  left: 1rem;
  top: -100px;
  z-index: 200;
  background: var(--gold);
  color: var(--night);
  padding: 0.75rem 1.25rem;
  font-weight: 700;
  transition: top 0.2s var(--ease);
}
.skip-link:focus { top: 1rem; }

.visually-hidden {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

/* ---------------------------------------------------------------
   3. TYPOGRAPHY
   --------------------------------------------------------------- */
.display,
.h1, .h2, .h3 {
  font-family: var(--font-display);
  font-weight: 500;
  line-height: 1.04;
  letter-spacing: -0.02em;
  font-variation-settings: "SOFT" 20, "WONK" 1;
  text-wrap: balance;
}

.display { font-size: var(--fs-display); }
.h1      { font-size: var(--fs-h1); }
.h2      { font-size: var(--fs-h2); line-height: 1.1; }
.h3      { font-size: var(--fs-h3); line-height: 1.25; letter-spacing: -0.01em; }

.eyebrow {
  font-family: var(--font-body);
  font-size: var(--fs-eyebrow);
  font-weight: 700;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--gold);
  display: flex;
  align-items: center;
  gap: 0.75rem;
}
.eyebrow::before {
  content: "";
  width: 2rem;
  height: 1px;
  background: var(--gold);
  flex: none;
}

.lead {
  font-size: var(--fs-lead);
  line-height: 1.6;
  color: var(--fg-muted);
  text-wrap: pretty;
}

.prose p + p { margin-top: 1.25em; }
.prose p { text-wrap: pretty; }

.gold { color: var(--gold); }

/* Scripture: the one place display type is allowed to run large in body flow */
.scripture {
  font-family: var(--font-display);
  font-size: clamp(1.6rem, 3.4vw, 2.75rem);
  font-weight: 400;
  font-style: italic;
  line-height: 1.25;
  letter-spacing: -0.015em;
  font-variation-settings: "SOFT" 40, "WONK" 1;
  text-wrap: balance;
}
.scripture__ref {
  display: block;
  margin-top: 1.5rem;
  font-family: var(--font-body);
  font-style: normal;
  font-size: var(--fs-eyebrow);
  font-weight: 700;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--gold);
}

/* ---------------------------------------------------------------
   4. LAYOUT
   --------------------------------------------------------------- */
.wrap {
  width: min(1240px, 100% - (var(--gutter) * 2));
  margin-inline: auto;
}
.wrap--narrow { width: min(760px, 100% - (var(--gutter) * 2)); }
.wrap--mid    { width: min(1000px, 100% - (var(--gutter) * 2)); }

.band {
  padding-block: var(--band-y);
  position: relative;
}
/* The header is fixed, so anchored sections must clear it when jumped to */
.band[id], [id].hero { scroll-margin-top: 5.5rem; }

.band--night { background: var(--night); color: var(--cream); }
.band--plum  { background: var(--plum);  color: var(--cream); }
.band--cream { background: var(--cream); color: var(--ink); }
.band--cream .lead      { color: var(--ink-soft); }
.band--cream .eyebrow   { color: var(--gold-deep); }
.band--cream .eyebrow::before { background: var(--gold-deep); }
.band--tight { padding-block: clamp(3rem, 6vw, 5rem); }

/* Hairline that reads as a horizon, used to separate dark bands */
.band--night + .band--night { border-top: 1px solid var(--plum-line); }

.stack   { display: grid; gap: 1.5rem; }
.stack-sm { display: grid; gap: 0.75rem; }
.stack-lg { display: grid; gap: 2.5rem; }

.cols {
  display: grid;
  gap: clamp(2rem, 5vw, 4.5rem);
  align-items: start;
}
@media (min-width: 900px) {
  .cols--2      { grid-template-columns: 1fr 1fr; }
  .cols--sidebar{ grid-template-columns: minmax(0, 1fr) minmax(0, 1.25fr); }
  .cols--flip   { grid-template-columns: minmax(0, 1.25fr) minmax(0, 1fr); }
}

.section-head {
  display: grid;
  gap: 1.25rem;
  max-width: 46rem;
  margin-bottom: clamp(2.5rem, 5vw, 4rem);
}

/* Atmospheric dawn wash, used behind hero + closing CTA */
.dawn {
  position: relative;
  isolation: isolate;
  overflow: hidden;
}
.dawn::before {
  content: "";
  position: absolute;
  inset: auto 0 -40% 0;
  height: 120%;
  z-index: -2;
  background:
    radial-gradient(ellipse 90% 60% at 50% 100%, #6B2E5A 0%, transparent 62%),
    radial-gradient(ellipse 55% 42% at 50% 100%, var(--gold-glow) 0%, transparent 58%),
    radial-gradient(ellipse 30% 26% at 50% 100%, var(--gold) 0%, transparent 55%);
  opacity: 0.55;
  pointer-events: none;
}
/* Film grain so the large flat gradients never band on cheap displays */
.dawn::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  opacity: 0.16;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='3'/%3E%3C/filter%3E%3Crect width='140' height='140' filter='url(%23n)' opacity='0.5'/%3E%3C/svg%3E");
}

/* ---------------------------------------------------------------
   5. HEADER + NAVIGATION
   --------------------------------------------------------------- */
.site-header {
  position: fixed;
  inset: 0 0 auto 0;
  z-index: 100;
  transition: background 0.35s var(--ease), border-color 0.35s var(--ease), backdrop-filter 0.35s var(--ease);
  border-bottom: 1px solid transparent;
}
.site-header.is-stuck {
  background: rgba(23, 9, 30, 0.88);
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
  border-bottom-color: var(--plum-line);
}

.site-header__inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 2rem;
  padding-block: 1.1rem;
}

/* Brand: rising-sun mark + wordmark */
.brand {
  display: flex;
  align-items: center;
  gap: 0.7rem;
  flex: none;
}
.brand__mark { width: 30px; height: 30px; flex: none; }
.brand__word {
  font-family: var(--font-display);
  font-size: 1.05rem;
  font-weight: 600;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  line-height: 1;
  color: var(--cream);
  font-variation-settings: "SOFT" 0, "WONK" 0;
}
.brand__word span { color: var(--gold); }

.nav { display: flex; align-items: center; gap: clamp(1rem, 2.2vw, 2rem); }

.nav__link {
  font-size: var(--fs-small);
  font-weight: 500;
  letter-spacing: 0.01em;
  color: var(--fg-muted);
  padding-block: 0.4rem;
  position: relative;
  transition: color 0.25s var(--ease);
}
.nav__link::after {
  content: "";
  position: absolute;
  left: 0; bottom: 0;
  width: 100%;
  height: 1px;
  background: var(--gold);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform 0.35s var(--ease);
}
.nav__link:hover,
.nav__link:focus-visible { color: var(--cream); }
.nav__link:hover::after { transform: scaleX(1); }
.nav__link.is-current { color: var(--cream); }
.nav__link.is-current::after { transform: scaleX(1); }

.nav__toggle {
  display: none;
  /* The drawer is positioned and would otherwise paint over this button,
     leaving no visible way to close the menu. Lift it above the drawer. */
  position: relative;
  z-index: 2;
  background: none;
  border: 1px solid var(--plum-line);
  border-radius: var(--radius);
  padding: 0.55rem 0.7rem;
  cursor: pointer;
  color: var(--cream);
}
.nav__toggle svg { width: 20px; height: 20px; }
.nav__toggle .icon-close { display: none; }
.nav__toggle[aria-expanded="true"] .icon-close { display: block; }
.nav__toggle[aria-expanded="true"] .icon-open  { display: none; }

@media (max-width: 900px) {
  .nav__toggle { display: block; }
  .nav {
    position: fixed;
    inset: 0 0 0 auto;
    width: min(340px, 84vw);
    flex-direction: column;
    align-items: flex-start;
    justify-content: flex-start;
    gap: 0.25rem;
    padding: 6rem 2rem 2rem;
    background: var(--night-2);
    border-left: 1px solid var(--plum-line);
    transform: translateX(101%);
    transition: transform 0.45s var(--ease);
    overflow-y: auto;
  }
  .nav.is-open { transform: translateX(0); }
  .nav__link { font-size: 1.15rem; padding-block: 0.85rem; width: 100%; }
  .nav__link::after { display: none; }
  .nav .btn { margin-top: 1.25rem; width: 100%; justify-content: center; }
}

.nav-scrim {
  position: fixed;
  inset: 0;
  z-index: 99;
  background: rgba(9, 4, 12, 0.6);
  opacity: 0;
  visibility: hidden;
  transition: opacity 0.35s var(--ease), visibility 0s linear 0.35s;
}
.nav-scrim.is-open {
  opacity: 1;
  visibility: visible;
  transition: opacity 0.35s var(--ease), visibility 0s linear 0s;
}

/* ---------------------------------------------------------------
   6. BUTTONS
   --------------------------------------------------------------- */
.btn {
  display: inline-flex;
  align-items: center;
  gap: 0.6rem;
  padding: 0.95rem 1.7rem;
  border: 1px solid transparent;
  border-radius: var(--radius);
  font-size: var(--fs-small);
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  cursor: pointer;
  transition: transform 0.25s var(--ease), background 0.25s var(--ease),
              color 0.25s var(--ease), border-color 0.25s var(--ease);
}
.btn:hover { transform: translateY(-2px); }

.btn--primary {
  background: var(--gold);
  color: var(--night);
  border-color: var(--gold);
}
.btn--primary:hover { background: var(--gold-lift); border-color: var(--gold-lift); }

.btn--ghost {
  background: transparent;
  color: var(--cream);
  border-color: #FBF6EF44;
}
.btn--ghost:hover { border-color: var(--gold); color: var(--gold); }

.band--cream .btn--ghost { color: var(--ink); border-color: #22142933; }
.band--cream .btn--ghost:hover { border-color: var(--gold-deep); color: var(--gold-deep); }

.btn__arrow { transition: transform 0.25s var(--ease); }
.btn:hover .btn__arrow { transform: translateX(4px); }

.btn-row { display: flex; flex-wrap: wrap; gap: 1rem; }

/* On narrow screens a long label wraps and strands the arrow against the
   right edge. Stacking full-width, centred buttons reads better and gives
   a bigger tap target. */
@media (max-width: 540px) {
  .btn-row { flex-direction: column; align-items: stretch; }
  .btn { justify-content: center; text-align: center; }
}

/* Quiet inline link with a rule that draws on hover */
.link {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  font-weight: 700;
  font-size: var(--fs-small);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--gold);
  padding-bottom: 0.3rem;
  border-bottom: 1px solid #E5A93C55;
  transition: border-color 0.25s var(--ease), gap 0.25s var(--ease);
}
.link:hover { border-bottom-color: var(--gold); gap: 0.85rem; }
.band--cream .link { color: var(--gold-deep); border-bottom-color: #8C5E1055; }
.band--cream .link:hover { border-bottom-color: var(--gold-deep); }

/* ---------------------------------------------------------------
   7. HERO
   --------------------------------------------------------------- */
.hero {
  min-height: min(100svh, 900px);
  display: grid;
  align-items: center;
  padding-block: clamp(8rem, 16vh, 11rem) clamp(4rem, 9vh, 7rem);
  background: var(--night);
}
.hero--short { min-height: auto; padding-block: clamp(9rem, 20vh, 13rem) clamp(3.5rem, 8vh, 6rem); }

.hero__inner { display: grid; gap: 2rem; max-width: 62rem; }

.hero__title { font-size: var(--fs-display); }

/* The word that rises. Gold, and it arrives last. */
.rise-word {
  display: inline-block;
  color: var(--gold);
  font-style: italic;
  font-variation-settings: "SOFT" 60, "WONK" 1;
}

.hero__sub { max-width: 34rem; }

/* The horizon rule: a gold line that draws itself across on load */
.horizon {
  height: 1px;
  background: linear-gradient(90deg, var(--gold) 0%, var(--gold-glow) 45%, transparent 100%);
  transform-origin: left;
}

.hero__meta {
  display: flex;
  flex-wrap: wrap;
  gap: 0.6rem 2rem;
  font-size: var(--fs-small);
  color: var(--fg-muted);
}
.hero__meta strong { color: var(--cream); font-weight: 700; }

.scroll-cue {
  display: inline-flex;
  align-items: center;
  gap: 0.75rem;
  margin-top: 1rem;
  font-size: var(--fs-eyebrow);
  font-weight: 700;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--fg-muted);
}
/* ---------------------------------------------------------------
   8. SIGNATURE: THE PIVOT
   Limitation on the left, reversal in gold on the right.
   Progress is linked to scroll, not merely triggered by it.
   --------------------------------------------------------------- */
.pivot { display: grid; gap: 0; }

.pivot__row {
  display: grid;
  gap: 0.5rem 2.5rem;
  padding-block: clamp(1.75rem, 3.5vw, 2.75rem);
  border-top: 1px solid var(--plum-line);
  align-items: baseline;
}
.pivot__row:last-child { border-bottom: 1px solid var(--plum-line); }

@media (min-width: 800px) {
  .pivot__row { grid-template-columns: 1fr auto 1fr; }
}

.pivot__was {
  font-family: var(--font-display);
  font-size: clamp(1.15rem, 2.1vw, 1.6rem);
  font-weight: 400;
  line-height: 1.3;
  color: var(--fg-muted);
  position: relative;
  display: inline-block;
  /* Resolved state by default so the section reads without JS. */
  opacity: 0.45;
  transition: opacity 0.1s linear;
}
/* --p is written by JS, 0 to 1, from the row's scroll progress. */
.js .pivot__was { opacity: calc(0.45 + (1 - var(--p, 0)) * 0.55); }
/* The strike-through draws itself as the row is read */
.pivot__was::after {
  content: "";
  position: absolute;
  left: 0;
  top: 55%;
  height: 1px;
  width: 100%;
  background: var(--fg-muted);
  transform: scaleX(1);
  transform-origin: left;
}
.js .pivot__was::after { transform: scaleX(var(--p, 0)); }

.pivot__hinge {
  font-family: var(--font-body);
  font-size: var(--fs-eyebrow);
  font-weight: 700;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--fg-muted);
  padding-top: 0.35em;
}

.pivot__now {
  font-family: var(--font-display);
  font-size: clamp(1.3rem, 2.6vw, 2rem);
  font-weight: 500;
  font-style: italic;
  line-height: 1.25;
  letter-spacing: -0.01em;
  font-variation-settings: "SOFT" 50, "WONK" 1;
  color: var(--gold);
  opacity: 1;
  transition: opacity 0.1s linear, transform 0.1s linear;
}
.js .pivot__now {
  opacity: var(--p, 0);
  transform: translateY(calc((1 - var(--p, 0)) * 14px));
}

/* ---------------------------------------------------------------
   9. STATS
   --------------------------------------------------------------- */
.stats {
  display: grid;
  gap: 1px;
  background: var(--plum-line);
  border: 1px solid var(--plum-line);
}
@media (min-width: 700px) { .stats { grid-template-columns: repeat(3, 1fr); } }

.band--cream .stats { background: #22142922; border-color: #22142922; }

.stat { background: var(--night); padding: clamp(1.75rem, 3.5vw, 2.5rem); }
.band--cream .stat { background: var(--cream); }

.stat__value {
  font-family: var(--font-display);
  font-size: clamp(2.5rem, 5vw, 3.75rem);
  font-weight: 500;
  line-height: 1;
  letter-spacing: -0.03em;
  color: var(--gold);
  font-variation-settings: "SOFT" 20, "WONK" 1;
}
.band--cream .stat__value { color: var(--gold-deep); }
.stat__label {
  margin-top: 0.9rem;
  font-size: var(--fs-small);
  color: var(--fg-muted);
  text-wrap: pretty;
}
.band--cream .stat__label { color: var(--ink-soft); }

/* ---------------------------------------------------------------
   10. PILLAR CARDS
   --------------------------------------------------------------- */
.pillars {
  display: grid;
  gap: 1px;
  background: #22142922;
  border: 1px solid #22142922;
}
@media (min-width: 640px)  { .pillars { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 1000px) { .pillars { grid-template-columns: repeat(3, 1fr); } }

.pillar {
  background: var(--cream);
  padding: clamp(1.75rem, 3vw, 2.5rem);
  display: grid;
  gap: 0.85rem;
  align-content: start;
  transition: background 0.35s var(--ease);
}
.pillar:hover { background: var(--cream-2); }
.pillar__icon { width: 30px; height: 30px; color: var(--gold-deep); }
.pillar__title { font-family: var(--font-display); font-size: 1.3rem; font-weight: 600; letter-spacing: -0.01em; }
.pillar__body { font-size: var(--fs-small); color: var(--ink-soft); text-wrap: pretty; }

/* ---------------------------------------------------------------
   11. LEADERSHIP
   --------------------------------------------------------------- */
.team {
  display: grid;
  gap: clamp(1.5rem, 3vw, 2.25rem);
}
@media (min-width: 640px)  { .team { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 1050px) { .team { grid-template-columns: repeat(4, 1fr); } }

.member { display: grid; gap: 1.25rem; align-content: start; }

.member__frame {
  position: relative;
  aspect-ratio: 4 / 5;
  overflow: hidden;
  background: var(--night-2);
  border: 1px solid var(--plum-line);
}
.member__photo {
  width: 100%; height: 100%;
  object-fit: cover;
  filter: saturate(0.92);
  transition: transform 0.8s var(--ease), filter 0.6s var(--ease);
}
.member:hover .member__photo { transform: scale(1.04); filter: saturate(1.05); }

/* Placeholder for a leader whose photo we do not have yet.
   Designed so it reads as intentional, and so dropping in a real
   photo later needs no layout change at all. */
.member__placeholder {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  align-content: center;
  gap: 0.9rem;
  background:
    radial-gradient(ellipse 80% 60% at 50% 115%, var(--gold-glow) 0%, transparent 60%),
    var(--night-2);
  text-align: center;
  padding: 1.5rem;
}
.member__monogram {
  font-family: var(--font-display);
  font-size: clamp(3rem, 6vw, 4.5rem);
  font-weight: 300;
  line-height: 1;
  letter-spacing: 0.04em;
  color: var(--gold);
  font-variation-settings: "SOFT" 60, "WONK" 1;
}
.member__placeholder-note {
  font-size: var(--fs-eyebrow);
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--fg-muted);
}

.member__role {
  font-size: var(--fs-eyebrow);
  font-weight: 700;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--rose);
}
.member__name {
  font-family: var(--font-display);
  font-size: 1.35rem;
  font-weight: 600;
  line-height: 1.2;
  letter-spacing: -0.01em;
}
.member__bio { font-size: var(--fs-small); color: var(--fg-muted); text-wrap: pretty; }
.band--cream .member__bio { color: var(--ink-soft); }
.band--cream .member__frame { border-color: #22142922; }

/* ---------------------------------------------------------------
   12. GALLERY
   --------------------------------------------------------------- */
.gallery {
  display: grid;
  gap: clamp(0.75rem, 1.5vw, 1.25rem);
  grid-template-columns: repeat(2, 1fr);
}
@media (min-width: 800px) {
  .gallery {
    grid-template-columns: repeat(4, 1fr);
    /* The wide and tall spans do not tessellate on their own and leave
       holes; dense packing backfills them with later photographs. */
    grid-auto-flow: dense;
  }
}

.shot {
  position: relative;
  overflow: hidden;
  background: var(--night-2);
  aspect-ratio: 1 / 1;
  border: 0;
  padding: 0;
  cursor: zoom-in;
  display: block;
}
.shot--tall { aspect-ratio: 1 / 1; }
@media (min-width: 800px) {
  .shot--wide { grid-column: span 2; aspect-ratio: 2 / 1; }
  .shot--tall { grid-row: span 2; aspect-ratio: 1 / 2; }
}
.shot img {
  width: 100%; height: 100%;
  object-fit: cover;
  transition: transform 0.9s var(--ease), filter 0.5s var(--ease);
  filter: saturate(0.9) brightness(0.92);
}
.shot:hover img,
.shot:focus-visible img { transform: scale(1.06); filter: saturate(1.05) brightness(1); }

.shot__caption {
  position: absolute;
  inset: auto 0 0 0;
  padding: 2.5rem 1rem 1rem;
  background: linear-gradient(to top, rgba(23, 9, 30, 0.92), transparent);
  font-size: var(--fs-small);
  color: var(--cream);
  text-align: left;
  opacity: 0;
  transform: translateY(8px);
  transition: opacity 0.35s var(--ease), transform 0.35s var(--ease);
}
.shot:hover .shot__caption,
.shot:focus-visible .shot__caption { opacity: 1; transform: translateY(0); }

/* Lightbox */
.lightbox {
  position: fixed;
  inset: 0;
  z-index: 300;
  display: grid;
  place-items: center;
  padding: clamp(1rem, 4vw, 3rem);
  background: rgba(9, 4, 12, 0.95);
  opacity: 0;
  visibility: hidden;
  /* visibility is NOT interpolated. It flips to visible instantly on open
     (0s delay) and is held until the fade-out finishes on close (0.3s
     delay). Transitioning it normally would leave it computed as hidden
     on the opening frame, and focus() no-ops on an invisible element. */
  transition: opacity 0.3s var(--ease), visibility 0s linear 0.3s;
}
.lightbox.is-open {
  opacity: 1;
  visibility: visible;
  transition: opacity 0.3s var(--ease), visibility 0s linear 0s;
}
.lightbox__img {
  max-width: 100%;
  max-height: 78vh;
  object-fit: contain;
  border: 1px solid var(--plum-line);
}
.lightbox__caption {
  margin-top: 1.25rem;
  text-align: center;
  font-size: var(--fs-small);
  color: var(--fg-muted);
  max-width: 44rem;
}
.lightbox__close {
  position: absolute;
  top: clamp(1rem, 3vw, 2rem);
  right: clamp(1rem, 3vw, 2rem);
  background: none;
  border: 1px solid var(--plum-line);
  border-radius: var(--radius);
  color: var(--cream);
  padding: 0.6rem;
  cursor: pointer;
  transition: border-color 0.25s var(--ease), color 0.25s var(--ease);
}
.lightbox__close:hover { border-color: var(--gold); color: var(--gold); }
.lightbox__close svg { width: 20px; height: 20px; }

/* ---------------------------------------------------------------
   13. FIGURE / EDITORIAL IMAGE
   --------------------------------------------------------------- */
.figure { display: grid; gap: 1rem; }
.figure__img {
  width: 100%;
  aspect-ratio: 4 / 3;
  object-fit: cover;
  border: 1px solid var(--plum-line);
  filter: saturate(0.95);
}
.band--cream .figure__img { border-color: #22142922; }
.figure figcaption {
  font-size: var(--fs-small);
  color: var(--fg-muted);
  padding-left: 1rem;
  border-left: 2px solid var(--gold);
  text-wrap: pretty;
}
.band--cream .figure figcaption { color: var(--ink-soft); border-left-color: var(--gold-deep); }

/* ---------------------------------------------------------------
   14. PULL QUOTE
   --------------------------------------------------------------- */
.pullquote {
  border-left: 2px solid var(--gold);
  padding-left: clamp(1.25rem, 3vw, 2.5rem);
}

/* ---------------------------------------------------------------
   15. ACCORDION (FAQ)
   --------------------------------------------------------------- */
.faq { border-top: 1px solid #22142922; }
.faq__item { border-bottom: 1px solid #22142922; }

.faq__q {
  width: 100%;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1.5rem;
  padding: 1.6rem 0;
  background: none;
  border: 0;
  text-align: left;
  cursor: pointer;
  font-family: var(--font-display);
  font-size: clamp(1.05rem, 1.8vw, 1.3rem);
  font-weight: 600;
  letter-spacing: -0.01em;
  color: var(--ink);
  transition: color 0.25s var(--ease);
}
.faq__q:hover { color: var(--gold-deep); }
.faq__icon { flex: none; width: 18px; height: 18px; color: var(--gold-deep); transition: transform 0.35s var(--ease); }
.faq__q[aria-expanded="true"] .faq__icon { transform: rotate(45deg); }

.faq__a {
  display: grid;
  grid-template-rows: 0fr;
  transition: grid-template-rows 0.4s var(--ease);
}
.faq__a.is-open { grid-template-rows: 1fr; }
.faq__a > div { overflow: hidden; }
.faq__a p {
  padding-bottom: 1.6rem;
  max-width: 52rem;
  color: var(--ink-soft);
  font-size: var(--fs-small);
  text-wrap: pretty;
}

/* ---------------------------------------------------------------
   16. TIMELINE
   Order carries real information here: the ministry's actual history.
   --------------------------------------------------------------- */
.timeline { display: grid; gap: 0; }
.timeline__item {
  display: grid;
  gap: 0.5rem 2.5rem;
  padding-block: clamp(1.5rem, 3vw, 2.25rem);
  border-top: 1px solid var(--plum-line);
}
.timeline__item:last-child { border-bottom: 1px solid var(--plum-line); }
@media (min-width: 720px) { .timeline__item { grid-template-columns: 9rem 1fr; } }

.timeline__year {
  font-family: var(--font-display);
  font-size: 1.5rem;
  font-weight: 500;
  line-height: 1;
  color: var(--gold);
  letter-spacing: -0.01em;
}
.timeline__body { font-size: var(--fs-small); color: var(--fg-muted); max-width: 46rem; text-wrap: pretty; }
.timeline__body strong { color: var(--cream); font-weight: 700; }

.band--cream .timeline__item { border-color: #22142922; }
.band--cream .timeline__year { color: var(--gold-deep); }
.band--cream .timeline__body { color: var(--ink-soft); }
.band--cream .timeline__body strong { color: var(--ink); }

.band--cream .pullquote { border-left-color: var(--gold-deep); }
.band--cream .scripture__ref { color: var(--gold-deep); }

/* ---------------------------------------------------------------
   17. FORMS
   --------------------------------------------------------------- */
.form { display: grid; gap: 1.5rem; }
.form__row { display: grid; gap: 1.5rem; }
@media (min-width: 640px) { .form__row--2 { grid-template-columns: 1fr 1fr; } }

.field { display: grid; gap: 0.55rem; }
.field__label {
  font-size: var(--fs-eyebrow);
  font-weight: 700;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--fg-muted);
}
.field__input,
.field__textarea,
.field__select {
  width: 100%;
  padding: 0.9rem 1rem;
  background: var(--night-2);
  border: 1px solid var(--plum-line);
  border-radius: var(--radius);
  color: var(--cream);
  transition: border-color 0.25s var(--ease), background 0.25s var(--ease);
}
.field__textarea { min-height: 9rem; resize: vertical; }
.field__input:focus,
.field__textarea:focus,
.field__select:focus {
  outline: none;
  border-color: var(--rose);
  background: var(--night);
}
.field__input::placeholder,
.field__textarea::placeholder { color: #8C7A94; }

.field__error {
  font-size: var(--fs-small);
  color: var(--rose);
  min-height: 0;
}
.field.has-error .field__input,
.field.has-error .field__textarea { border-color: var(--rose); }

.form__status {
  padding: 1rem 1.25rem;
  border: 1px solid var(--gold);
  border-radius: var(--radius);
  background: #E5A93C14;
  font-size: var(--fs-small);
  color: var(--gold-lift);
}
.form__status[hidden] { display: none; }

/* ---------------------------------------------------------------
   18. CONTACT DETAILS LIST
   --------------------------------------------------------------- */
.details { display: grid; gap: 0; }
.details__item {
  display: grid;
  gap: 0.35rem;
  padding-block: 1.4rem;
  border-top: 1px solid var(--plum-line);
}
.details__item:last-child { border-bottom: 1px solid var(--plum-line); }
.details__label {
  font-size: var(--fs-eyebrow);
  font-weight: 700;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--gold);
}
.details__value { font-size: var(--fs-lead); }
.details__value a { border-bottom: 1px solid #FBF6EF33; transition: border-color 0.25s var(--ease); }
.details__value a:hover { border-bottom-color: var(--gold); }

.band--cream .details__item  { border-color: #22142922; }
.band--cream .details__label { color: var(--gold-deep); }
.band--cream .details__value a { border-bottom-color: #22142933; }
.band--cream .details__value a:hover { border-bottom-color: var(--gold-deep); }

/* A placeholder value that has not been supplied yet. Reads as
   deliberate rather than broken, and is easy to grep for. */
.tbc {
  font-style: italic;
  color: var(--fg-muted);
}
.tbc::after {
  content: "to be confirmed";
}
.band--cream .tbc { color: var(--ink-soft); }

/* A value that is genuinely pending an announcement rather than merely
   unsupplied. Same visual language as .tbc, but the copy is written into
   the HTML so it survives with JS off. */
.tba {
  font-style: italic;
  color: var(--fg-muted);
}
.band--cream .tba { color: var(--ink-soft); }

/* ---------------------------------------------------------------
   19. FOOTER
   Written mobile-first in the real sense: the base rules are the phone
   layout, sized for thumbs, and each min-width query adds room rather
   than taking it back. The footer is the last thing on every page and
   the first place someone reaches for a phone number, so the targets
   here matter more than they do in the nav.

   The three-column desktop arrangement needs no extra markup. The two
   nav groups pair up at 480px with the brand block spanning both, and
   only at 860px do all three sit in a row.
   --------------------------------------------------------------- */
.site-footer {
  background: var(--night-2);
  border-top: 1px solid var(--plum-line);
  padding-block: 3rem 1.25rem;
}

.footer__top {
  display: grid;
  gap: 2.25rem;
  padding-bottom: 1.75rem;
  border-bottom: 1px solid var(--plum-line);
}

.footer__blurb {
  max-width: 34rem;
  margin-top: 1.15rem;
  font-size: var(--fs-small);
  line-height: 1.65;
  color: var(--fg-muted);
  text-wrap: pretty;
}

.footer__heading {
  font-size: var(--fs-eyebrow);
  font-weight: 700;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--gold);
  margin-bottom: 0.4rem;
}

/* On a phone the list reads as a run of tappable rows: a 48px target,
   a hairline between each, and the whole row live rather than just the
   few words in it. Above 640px it relaxes back into a plain list. */
.footer__list {
  display: grid;
  gap: 0;
  font-size: var(--fs-small);
}
.footer__list li {
  display: flex;
  align-items: center;
  min-height: 48px;
  padding-block: 0.35rem;
  border-bottom: 1px solid color-mix(in oklab, var(--plum-line) 55%, transparent);
  color: var(--fg-muted);
  line-height: 1.5;
}
.footer__list li:last-child { border-bottom: 0; }
.footer__list a {
  display: flex;
  align-items: center;
  flex: 1;
  min-height: 48px;
  color: var(--fg-muted);
  transition: color 0.25s var(--ease), translate 0.25s var(--ease);
}
.footer__list a:hover { color: var(--cream); }

/* A chevron that only appears where the row actually goes somewhere,
   so the address line is not dressed up as a link. */
.footer__list li:has(a)::after {
  content: "";
  flex: none;
  width: 7px;
  height: 7px;
  margin-left: 0.75rem;
  border-right: 1px solid currentColor;
  border-bottom: 1px solid currentColor;
  rotate: -45deg;
  opacity: 0.45;
  transition: translate 0.25s var(--ease), opacity 0.25s var(--ease);
}
.footer__list li:has(a:hover)::after { translate: 3px 0; opacity: 1; }

.socials {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem;
  margin-top: 1.5rem;
}
.socials a {
  display: grid;
  place-items: center;
  width: 48px;
  height: 48px;
  border: 1px solid var(--plum-line);
  border-radius: 50%;
  color: var(--fg-muted);
  transition: color 0.25s var(--ease), border-color 0.25s var(--ease),
              translate 0.25s var(--ease), background 0.25s var(--ease);
}
.socials a:hover {
  color: var(--gold);
  border-color: var(--gold);
  background: color-mix(in oklab, var(--gold) 10%, transparent);
  translate: 0 -2px;
}
.socials svg { width: 19px; height: 19px; }

.footer__bottom {
  display: grid;
  gap: 0.6rem;
  padding-top: 1.5rem;
  font-size: var(--fs-small);
  color: #8C7A94;
  text-wrap: pretty;
}

/* --- 480px: the two link groups pair up, brand spans the row ------ */
@media (min-width: 480px) {
  .footer__top { grid-template-columns: 1fr 1fr; gap: 2.5rem 2rem; }
  .footer__top > :first-child { grid-column: 1 / -1; }
}

/* --- 640px: enough room for a plain list again ------------------- */
@media (min-width: 640px) {
  .footer__list { gap: 0.7rem; }
  .footer__list li {
    display: block;
    min-height: 0;
    padding-block: 0;
    border-bottom: 0;
  }
  .footer__list a { display: inline-flex; min-height: 0; flex: none; }
  .footer__list li:has(a)::after { display: none; }
  .footer__heading { margin-bottom: 1.1rem; }
}

/* --- 860px: all three blocks in a row ---------------------------- */
@media (min-width: 860px) {
  .site-footer { padding-block: clamp(3.5rem, 7vw, 5.5rem) 2rem; }
  .footer__top {
    grid-template-columns: 1.5fr 1fr 1.15fr;
    gap: clamp(2.5rem, 5vw, 4rem);
    padding-bottom: clamp(2.5rem, 5vw, 4rem);
  }
  .footer__top > :first-child { grid-column: auto; }
  .footer__blurb { max-width: 26rem; }
  .footer__bottom {
    grid-auto-flow: column;
    justify-content: space-between;
    align-items: center;
    gap: 2rem;
    padding-top: 2rem;
  }
}

/* ---------------------------------------------------------------
   20. MOTION UTILITIES
   --------------------------------------------------------------- */
/* Progressive enhancement: the finished state is the DEFAULT, so with
   JavaScript unavailable every section is simply visible. The hidden
   starting state is applied only under .js, which an inline script in
   <head> sets before first paint, so there is no flash either way.
   Without this the whole page renders blank when JS fails. */
.reveal {
  transition: opacity 0.8s var(--ease), transform 0.8s var(--ease);
  transition-delay: var(--delay, 0s);
}
.js .reveal { opacity: 0; transform: translateY(22px); }
.js .reveal.is-visible { opacity: 1; transform: none; }

/* Page-load sequence for the hero. Runs once, on load. */
.load-in > * {
  opacity: 0;
  transform: translateY(26px);
  animation: loadIn 0.95s var(--ease) forwards;
}
.load-in > *:nth-child(1) { animation-delay: 0.10s; }
.load-in > *:nth-child(2) { animation-delay: 0.22s; }
.load-in > *:nth-child(3) { animation-delay: 0.34s; }
.load-in > *:nth-child(4) { animation-delay: 0.46s; }
.load-in > *:nth-child(5) { animation-delay: 0.58s; }
.load-in > *:nth-child(6) { animation-delay: 0.70s; }
.load-in > *:nth-child(7) { animation-delay: 0.82s; }
.load-in > *:nth-child(8) { animation-delay: 0.94s; }

@keyframes loadIn {
  to { opacity: 1; transform: none; }
}

/* The horizon draws after the headline has landed.
   This element is also a .load-in child, and a second `animation`
   declaration would replace that one wholesale, stranding it at
   opacity 0. So this keyframe carries the fade as well as the draw. */
.horizon-draw {
  opacity: 0;
  transform: scaleX(0);
  animation: drawLine 1.4s var(--ease) 0.45s forwards;
}
@keyframes drawLine {
  from { opacity: 1; transform: scaleX(0); }
  to   { opacity: 1; transform: scaleX(1); }
}

/* The word "Arise" rises last, and further than the rest */
.rise-word {
  opacity: 0;
  transform: translateY(38px);
  animation: loadIn 1.1s var(--ease) 0.32s forwards;
}

/* ---------------------------------------------------------------
   21. REDUCED MOTION
   Everything above degrades to a static, fully legible page.
   --------------------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }

  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
  }

  .load-in > *,
  .rise-word { opacity: 1; transform: none; animation: none; }
  .horizon-draw { opacity: 1; transform: scaleX(1); animation: none; }
  .reveal, .js .reveal { opacity: 1; transform: none; }

  /* The pivot resolves to its finished state rather than tracking scroll */
  .pivot__was, .js .pivot__was { opacity: 0.5; }
  .pivot__was::after, .js .pivot__was::after { transform: scaleX(1); }
  .pivot__now, .js .pivot__now { opacity: 1; transform: none; }

}

/* ---------------------------------------------------------------
   22. PRINT
   --------------------------------------------------------------- */
@media print {
  .site-header, .nav-scrim, .lightbox, .scroll-cue, .socials { display: none !important; }
  body { background: #fff; color: #000; }
  .band { padding-block: 1.5rem; background: #fff !important; color: #000 !important; }
  .reveal, .js .reveal { opacity: 1; transform: none; }
}

/* =====================================================================
   23. INTERACTIVE LAYER
   AOS integration, Lottie holders, parallax, marquee, tilt, mosaics.
   All of it degrades: see section 21 (reduced motion) and the .js gate.
   ===================================================================== */

/* ---------------------------------------------------------------
   23a. AOS integration
   AOS ships its own easing; this aligns it with the site curve.
   --------------------------------------------------------------- */
[data-aos] {
  transition-timing-function: var(--ease) !important;
}
/* Without JS, AOS's stylesheet would leave everything at opacity 0.
   The <noscript> block in each page head overrides that; this is the
   belt to its braces for when AOS itself fails to initialise. */
html:not(.js) [data-aos] {
  opacity: 1 !important;
  transform: none !important;
}

/* ---------------------------------------------------------------
   23b. Scroll progress
   A gold hairline that fills as the reader moves down the page.
   --------------------------------------------------------------- */
.scroll-progress {
  position: fixed;
  inset: 0 0 auto 0;
  height: 2px;
  z-index: 150;
  background: transparent;
  pointer-events: none;
}
.scroll-progress__bar {
  height: 100%;
  width: 100%;
  transform: scaleX(0);
  transform-origin: left;
  background: linear-gradient(90deg, var(--gold-glow), var(--gold), var(--gold-lift));
  will-change: transform;
}

/* ---------------------------------------------------------------
   23c. Lottie holders
   --------------------------------------------------------------- */
.lottie { display: block; pointer-events: none; }


.lottie--chevron { width: 42px; height: 52px; opacity: 0.9; }

/* ---------------------------------------------------------------
   23d. Decorative SVG orbit
   Concentric dashed rings rotating slowly behind the hero.
   --------------------------------------------------------------- */
.orbit {
  position: absolute;
  inset: 50% auto auto 50%;
  translate: -50% -50%;
  width: min(150vmax, 1600px);
  aspect-ratio: 1;
  z-index: -2;
  opacity: 0.16;
  pointer-events: none;
}
.orbit circle { fill: none; stroke: var(--gold); stroke-width: 0.6; }
.orbit__ring { transform-origin: 50% 50%; animation: spin 90s linear infinite; }
.orbit__ring--rev { animation-duration: 140s; animation-direction: reverse; }
@keyframes spin { to { transform: rotate(360deg); } }

/* ---------------------------------------------------------------
   23e. Split-text heading reveal
   Each word rises behind a clipping mask, so the headline assembles
   itself rather than simply fading in.
   --------------------------------------------------------------- */
.split { display: inline; }
.split__word {
  display: inline-block;
  overflow: hidden;
  vertical-align: bottom;
  padding-bottom: 0.08em;
  margin-bottom: -0.08em;
}
.split__inner { display: inline-block; will-change: transform; }
.js .split__inner { transform: translateY(110%); opacity: 0; }
.js .split.is-in .split__inner {
  transform: translateY(0);
  opacity: 1;
  transition: transform 0.9s var(--ease), opacity 0.7s var(--ease);
  transition-delay: calc(var(--i, 0) * 65ms);
}


/* The hero headline is split into words, which carries its own entrance.
   Suppress the .load-in stagger and the .rise-word keyframe for it, or
   three animations fight over the same element. */
.js .load-in > [data-split] { animation: none; opacity: 1; transform: none; }
.js .split .rise-word { animation: none; opacity: 1; transform: none; }

/* ---------------------------------------------------------------
   23f. Photo marquee
   Two rows drifting opposite ways. The track is duplicated in the
   markup so translateX(-50%) loops seamlessly.
   --------------------------------------------------------------- */
.marquee {
  overflow: hidden;
  position: relative;
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 8%, #000 92%, transparent);
          mask-image: linear-gradient(90deg, transparent, #000 8%, #000 92%, transparent);
}
.marquee + .marquee { margin-top: clamp(0.75rem, 1.5vw, 1.25rem); }

.marquee__track {
  display: flex;
  gap: clamp(0.75rem, 1.5vw, 1.25rem);
  width: max-content;
  animation: marquee 68s linear infinite;
  will-change: transform;
}
.marquee--rev .marquee__track { animation-direction: reverse; }
.marquee:hover .marquee__track { animation-play-state: paused; }

@keyframes marquee {
  from { transform: translateX(0); }
  to   { transform: translateX(-50%); }
}

.marquee__item {
  position: relative;
  flex: none;
  width: clamp(200px, 26vw, 340px);
  aspect-ratio: 4 / 3;
  overflow: hidden;
  border: 1px solid var(--plum-line);
  background: var(--night-2);
}
.marquee__item img {
  width: 100%; height: 100%;
  object-fit: cover;
  filter: saturate(0.85) brightness(0.78);
  transition: filter 0.6s var(--ease), transform 0.9s var(--ease);
}
.marquee__item:hover img { filter: saturate(1.05) brightness(1); transform: scale(1.05); }

/* ---------------------------------------------------------------
   23g. Parallax showcase
   --------------------------------------------------------------- */
.showcase {
  position: relative;
  display: grid;
  gap: clamp(2rem, 5vw, 4rem);
  align-items: center;
}
@media (min-width: 940px) {
  .showcase { grid-template-columns: minmax(0, 1.15fr) minmax(0, 1fr); }
  .showcase--flip .showcase__media { order: 2; }
}

.showcase__media {
  position: relative;
  overflow: hidden;
  aspect-ratio: 4 / 3;
  border: 1px solid var(--plum-line);
  background: var(--night-2);
}
/* The image is taller than its frame so it has somewhere to drift to.
   The excess must be split above and below: left at the top with all
   18% hanging below, a downward parallax pass would slide the image
   off its own top edge and expose the frame behind it. */
.showcase__media img {
  position: absolute;
  inset: -9% 0 auto 0;
  width: 100%;
  height: 118%;
  object-fit: cover;
  will-change: transform;
  filter: saturate(0.95);
}
.band--cream .showcase__media { border-color: #22142922; }

/* Gold rule that draws across the image as it enters view */
.showcase__media::after {
  content: "";
  position: absolute;
  left: 0; right: 0; bottom: 0;
  height: 2px;
  background: linear-gradient(90deg, var(--gold), transparent);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform 1.2s var(--ease) 0.2s;
}
.showcase.is-in .showcase__media::after { transform: scaleX(1); }

.showcase__tag {
  position: absolute;
  left: 0; bottom: 0;
  padding: 0.6rem 1rem;
  background: var(--night);
  border-top: 1px solid var(--plum-line);
  border-right: 1px solid var(--plum-line);
  font-size: var(--fs-eyebrow);
  font-weight: 700;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--gold);
}

/* ---------------------------------------------------------------
   23h. Image reveal mask
   --------------------------------------------------------------- */
.js .mask-reveal { clip-path: inset(100% 0 0 0); transition: clip-path 1.1s var(--ease); }
/* Either the element itself or a watched ancestor may carry .is-in.
   See initInView() in main.js for why the ancestor is the reliable one. */
.js .mask-reveal.is-in,
.js .is-in .mask-reveal { clip-path: inset(0 0 0 0); }

/* ---------------------------------------------------------------
   23i. Mosaic
   --------------------------------------------------------------- */
.mosaic {
  display: grid;
  gap: clamp(0.6rem, 1.2vw, 1rem);
  grid-template-columns: repeat(2, 1fr);
}
@media (min-width: 820px) {
  .mosaic {
    grid-template-columns: repeat(4, 1fr);
    grid-template-rows: repeat(2, minmax(150px, 24vh));
  }
  .mosaic__cell:nth-child(1) { grid-column: span 2; grid-row: span 2; }
  .mosaic__cell:nth-child(4) { grid-column: span 2; }
}
.mosaic__cell {
  position: relative;
  overflow: hidden;
  min-height: 150px;
  background: var(--night-2);
  border: 1px solid var(--plum-line);
}
.mosaic__cell img {
  width: 100%; height: 100%;
  object-fit: cover;
  filter: saturate(0.9) brightness(0.85);
  transition: transform 1s var(--ease), filter 0.6s var(--ease);
}
.mosaic__cell:hover img { transform: scale(1.07); filter: saturate(1.05) brightness(1); }
.band--cream .mosaic__cell { border-color: #22142922; }

/* ---------------------------------------------------------------
   23j. Tilt cards
   --------------------------------------------------------------- */
.tilt {
  transform-style: preserve-3d;
  transition: transform 0.4s var(--ease);
  will-change: transform;
}

/* ---------------------------------------------------------------
   23k. Scripture rotator
   --------------------------------------------------------------- */
.rotator {
  position: relative;
  display: grid;
  min-height: 13em;
  align-items: center;
}
.rotator__item {
  grid-area: 1 / 1;
  opacity: 0;
  transform: translateY(14px);
  transition: opacity 0.8s var(--ease), transform 0.8s var(--ease);
  pointer-events: none;
}
.rotator__item.is-active { opacity: 1; transform: none; pointer-events: auto; }
html:not(.js) .rotator { min-height: 0; display: grid; gap: 3rem; }
html:not(.js) .rotator__item { opacity: 1; transform: none; grid-area: auto; }
html:not(.js) .rotator__dots { display: none; }

.rotator__dots {
  display: flex;
  gap: 0.6rem;
  justify-content: center;
  margin-top: 2.5rem;
}
.rotator__dot {
  width: 36px; height: 3px;
  padding: 0;
  border: 0;
  background: var(--plum-line);
  cursor: pointer;
  position: relative;
  overflow: hidden;
}
.rotator__dot::after {
  content: "";
  position: absolute; inset: 0;
  background: var(--gold);
  transform: scaleX(0);
  transform-origin: left;
}
.rotator__dot.is-active::after { animation: dotFill 7s linear forwards; }
@keyframes dotFill { to { transform: scaleX(1); } }

/* ---------------------------------------------------------------
   23l. Stat rings
   An SVG arc behind each figure that draws as the number counts.
   --------------------------------------------------------------- */
.stat { position: relative; overflow: hidden; }
.stat__ring {
  position: absolute;
  top: clamp(1.2rem, 2.5vw, 1.9rem);
  right: clamp(1.2rem, 2.5vw, 1.9rem);
  width: 46px; height: 46px;
  opacity: 0.55;
}
.stat__ring .track { fill: none; stroke: var(--plum-line); stroke-width: 2; }
.band--cream .stat__ring .track { stroke: #2214291f; }
.stat__ring .arc {
  fill: none;
  stroke: var(--gold);
  stroke-width: 2;
  stroke-linecap: round;
  stroke-dasharray: 126;
  stroke-dashoffset: 126;
  transform: rotate(-90deg);
  transform-origin: 50% 50%;
  transition: stroke-dashoffset 1.6s var(--ease);
}
.band--cream .stat__ring .arc { stroke: var(--gold-deep); }
.stat.is-in .stat__ring .arc { stroke-dashoffset: 34; }

/* ---------------------------------------------------------------
   23m. Magnetic buttons
   --------------------------------------------------------------- */
.magnetic { will-change: transform; }

/* ---------------------------------------------------------------
   23n. Back to top
   --------------------------------------------------------------- */
.to-top {
  position: fixed;
  right: clamp(1rem, 3vw, 2rem);
  bottom: clamp(1rem, 3vw, 2rem);
  z-index: 90;
  display: grid;
  place-items: center;
  width: 48px; height: 48px;
  border-radius: 50%;
  border: 1px solid var(--plum-line);
  background: rgba(32, 13, 40, 0.86);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  color: var(--gold);
  cursor: pointer;
  opacity: 0;
  visibility: hidden;
  translate: 0 12px;
  transition: opacity 0.35s var(--ease), translate 0.35s var(--ease),
              visibility 0s linear 0.35s, border-color 0.25s var(--ease);
}
.to-top.is-visible {
  opacity: 1;
  visibility: visible;
  translate: 0;
  transition: opacity 0.35s var(--ease), translate 0.35s var(--ease),
              visibility 0s linear 0s, border-color 0.25s var(--ease);
}
.to-top:hover { border-color: var(--gold); }
.to-top svg { width: 18px; height: 18px; }

/* ---------------------------------------------------------------
   23o. Section divider
   --------------------------------------------------------------- */
.divider {
  display: block;
  width: 100%;
  height: clamp(40px, 6vw, 88px);
  color: var(--cream);
}
.divider path { fill: currentColor; }

/* ---------------------------------------------------------------
   23q. Reduced motion for everything in section 23
   --------------------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
  .marquee__track { animation: none; transform: none; }
  .marquee { -webkit-mask-image: none; mask-image: none; overflow-x: auto; }
  .orbit__ring { animation: none; }
  /* With motion off the rotator never advances, which would leave
     three of the four passages permanently at opacity 0 unless the
     reader happened to find the dots. Resolve it the same way the
     no-JS case does: stack every passage and drop the controls. */
  .rotator { min-height: 0; display: grid; gap: 3rem; }
  .rotator__item {
    grid-area: auto;
    opacity: 1;
    transform: none;
    pointer-events: auto;
    transition: none;
  }
  .rotator__dots { display: none; }
  .js .split__inner { transform: none; opacity: 1; }
  .js .mask-reveal { clip-path: none; }
  .showcase__media::after { transform: scaleX(1); }
  .showcase__media img { height: 100%; transform: none !important; }
  .stat__ring .arc { transition: none; stroke-dashoffset: 34; }
  .tilt { transform: none !important; }
}


/* =====================================================================
   24. ELEVATION LAYER
   Ambient atmosphere, generated dividers, and the per-page signatures:
   the drawn timeline and struck objections (about), the journey arc
   (missions), the framed figure and form motion (contact, conference).

   Everything here is decorative or additive. Section 24t resolves all
   of it to a finished, legible state under prefers-reduced-motion, and
   anything requiring JS is gated behind .js so a failed script leaves
   the page complete rather than blank.
   ===================================================================== */

/* ---------------------------------------------------------------
   24a. Sitewide grain
   The large flat plum fields band badly on 6-bit laptop panels.
   A single fixed noise layer costs one paint and fixes it everywhere.
   z-index 1 clears the bands (z-index auto) but stays under the
   header (100), the back-to-top (90) and the lightbox (300).
   --------------------------------------------------------------- */
body::after {
  content: "";
  position: fixed;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  opacity: 0.035;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='g'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4'/%3E%3C/filter%3E%3Crect width='160' height='160' filter='url(%23g)'/%3E%3C/svg%3E");
}

/* ---------------------------------------------------------------
   24b. Aurora
   Two slow-drifting pools of light behind the dark bands, so a band
   that would otherwise be flat plum reads as a room with a window
   in it. Injected by JS (decorative only, so no-JS simply omits it).
   --------------------------------------------------------------- */
.aurora {
  position: absolute;
  inset: 0;
  z-index: 0;
  overflow: hidden;
  pointer-events: none;
}
/* No filter: blur() here on purpose. A radial-gradient that fades to
   transparent at 68% is already soft, and A/B-ing the two showed a
   maximum difference of 28 out of 765 across all three channels: not
   perceptible. Blurring it cost up to six large blurred layers per
   page for that, so the gradient does the work alone. */
.aurora::before,
.aurora::after {
  content: "";
  position: absolute;
  width: 46vw;
  aspect-ratio: 1;
  border-radius: 50%;
  will-change: transform;
}
.aurora::before {
  top: -18%;
  left: -8%;
  background: radial-gradient(circle, #B77F1E55 0%, transparent 68%);
  animation: drift-a 26s ease-in-out infinite alternate;
}
.aurora::after {
  bottom: -22%;
  right: -10%;
  background: radial-gradient(circle, #6B2E5A66 0%, transparent 68%);
  animation: drift-b 34s ease-in-out infinite alternate;
}
@keyframes drift-a {
  from { transform: translate3d(0, 0, 0) scale(1); }
  to   { transform: translate3d(12vw, 6vh, 0) scale(1.18); }
}
@keyframes drift-b {
  from { transform: translate3d(0, 0, 0) scale(1.1); }
  to   { transform: translate3d(-10vw, -7vh, 0) scale(0.92); }
}
/* The band's own content must sit above the light pool. Only the
   content wrappers are lifted, never a bare `~ *`: the heroes park
   their orbit and photograph at negative z-index, and a blanket rule
   would drag those in front of the headline. initAtmosphere() also
   declines to touch a hero, so this is the belt to that brace. */
.aurora ~ .wrap,
.aurora ~ .marquee { position: relative; z-index: 1; }

/* ---------------------------------------------------------------
   24c. Pointer glow
   A soft warm light that follows the cursor across the dark bands.
   Fine pointers only: on touch there is no cursor to follow, and the
   repaint would be spent for nothing.
   --------------------------------------------------------------- */
.glow {
  position: absolute;
  inset: 0;
  z-index: 0;
  overflow: hidden;
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.6s var(--ease);
}
.glow.is-live { opacity: 1; }
.glow::before {
  content: "";
  position: absolute;
  top: 0;
  left: 0;
  width: 30rem;
  aspect-ratio: 1;
  margin: -15rem 0 0 -15rem;
  border-radius: 50%;
  background: radial-gradient(circle, #E5A93C22 0%, transparent 66%);
  translate: var(--mx, 50%) var(--my, 50%);
  will-change: translate;
}

/* ---------------------------------------------------------------
   24d. Generated band dividers
   A shallow horizon crest between bands of different colour, so the
   page reads as light rising rather than as stacked rectangles.
   The shape is the brand's own mark: the curve of a sun on a line.
   JS sets the background (band above) and fill (band below); see
   initDividers() for why the colours are read rather than declared.
   --------------------------------------------------------------- */
.divider {
  display: block;
  width: 100%;
  height: clamp(30px, 4vw, 54px);
  margin: 0;
  position: relative;
  z-index: 2;
}
.divider svg { display: block; width: 100%; height: 100%; }
.divider path { fill: currentColor; }
/* A gold hairline riding the crest, which is what makes it a horizon
   rather than a swoosh. Drawn once, when the divider comes into view. */
.divider__rule { fill: none; stroke: var(--gold); stroke-width: 1.5; opacity: 0.7; }
.js .divider__rule {
  stroke-dasharray: 3000;
  stroke-dashoffset: 3000;
  transition: stroke-dashoffset 1.6s var(--ease);
}
.js .divider.is-in .divider__rule { stroke-dashoffset: 0; }

/* ---------------------------------------------------------------
   24e. Signature: the drawn timeline
   The ministry's history is a genuine sequence, so it is the one
   place on the site that earns a rail and ordered markers. The gold
   fill tracks how far the reader has moved through the timeline,
   not merely whether it is on screen.
   --------------------------------------------------------------- */
.timeline--drawn { position: relative; padding-left: clamp(2.5rem, 5vw, 4rem); }
.timeline--drawn::before {
  content: "";
  position: absolute;
  left: clamp(0.75rem, 1.6vw, 1.25rem);
  top: 0;
  bottom: 0;
  width: 1px;
  background: var(--plum-line);
}
.band--cream .timeline--drawn::before { background: #22142926; }

.timeline__rail {
  position: absolute;
  left: clamp(0.75rem, 1.6vw, 1.25rem);
  top: 0;
  width: 1px;
  height: 100%;
  transform: scaleY(var(--p, 0));
  transform-origin: top;
  background: linear-gradient(180deg, var(--gold-lift), var(--gold), var(--gold-glow));
}
.band--cream .timeline__rail {
  background: linear-gradient(180deg, var(--gold), var(--gold-deep));
}

.timeline--drawn .timeline__item { position: relative; border-top-color: transparent; }
.timeline--drawn .timeline__item + .timeline__item { border-top-color: var(--plum-line); }
.band--cream .timeline--drawn .timeline__item + .timeline__item { border-top-color: #22142922; }
.timeline--drawn .timeline__item:last-child { border-bottom: 0; }

/* The node sits on the rail, dark until the reader reaches it */
.timeline__node {
  position: absolute;
  left: calc(clamp(0.75rem, 1.6vw, 1.25rem) - clamp(2.5rem, 5vw, 4rem) - 5px);
  top: calc(clamp(1.5rem, 3vw, 2.25rem) + 0.35em);
  width: 11px;
  height: 11px;
  border-radius: 50%;
  border: 1px solid var(--plum-line);
  background: var(--night);
  transition: background 0.5s var(--ease), border-color 0.5s var(--ease),
              box-shadow 0.6s var(--ease), scale 0.5s var(--ease);
}
.band--cream .timeline__node { background: var(--cream); border-color: #22142933; }
.timeline__item.is-lit .timeline__node {
  background: var(--gold);
  border-color: var(--gold);
  scale: 1.25;
  box-shadow: 0 0 0 5px #E5A93C1f, 0 0 18px #E5A93C66;
}
.timeline__item.is-lit .timeline__year { color: var(--gold-lift); }
.band--cream .timeline__item.is-lit .timeline__year { color: var(--gold-deep); }

/* ---------------------------------------------------------------
   24f. Signature: the struck objections
   The home page pivots a limitation into a reversal. This is the
   same move applied to the reasons women give for not rising: each
   sentence is crossed out as the reader passes it, in the order it
   is read, and the reversal below arrives once they are all gone.
   --------------------------------------------------------------- */
.objections { display: grid; gap: 0.7rem; }
.objections li {
  position: relative;
  padding-left: 1.6rem;
  text-wrap: pretty;
}
.objections li::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0.75em;
  width: 0.85rem;
  height: 1px;
  background: var(--gold-deep);
}
/* The strike is real text decoration rather than a positioned bar.
   A bar would span the whole block, so it would run out past the end
   of the shorter sentences, and where a sentence wraps to two lines
   it would cross only the first. line-through is laid out by the text
   engine: it follows every line and stops where the words do.
   text-decoration-color is animatable, so it can still arrive rather
   than simply appear, and the per-item delay walks it down the list
   in reading order. */
.objections li {
  text-decoration-line: line-through;
  text-decoration-color: transparent;
  text-decoration-thickness: 1px;
  transition: opacity 0.6s var(--ease),
              text-decoration-color 0.7s var(--ease);
  transition-delay: calc(var(--i, 0) * 90ms);
}
.js .objections.is-in li {
  opacity: 0.45;
  text-decoration-color: var(--gold-deep);
}

/* The reversal. Hidden from nobody: it is legible without JS, and
   simply arrives with more ceremony when JS is present. */
.reversal {
  margin-top: clamp(1.75rem, 3vw, 2.5rem);
  padding-top: clamp(1.75rem, 3vw, 2.5rem);
  border-top: 1px solid #22142922;
  font-family: var(--font-display);
  font-size: var(--fs-h3);
  font-weight: 400;
  line-height: 1.35;
  color: var(--gold-deep);
  text-wrap: balance;
}
.js .reversal {
  opacity: 0;
  transform: translateY(14px);
  transition: opacity 0.9s var(--ease) 0.85s, transform 0.9s var(--ease) 0.85s;
}
.js .objections.is-in + .reversal { opacity: 1; transform: none; }

/* ---------------------------------------------------------------
   24g. Signature: the journey arc
   London to the East African mission field, drawn as one dashed
   flight path with a pulse at each end. Vector only: a real map
   would claim a precision about the locations we do not have.
   --------------------------------------------------------------- */
.journey {
  position: relative;
  display: block;
  width: 100%;
  max-width: 62rem;
  margin-inline: auto;
  aspect-ratio: 8 / 3;
  overflow: visible;
}
.journey__ground { fill: none; stroke: var(--plum-line); stroke-width: 1; }
.band--cream .journey__ground { stroke: #22142926; }

.journey__path {
  fill: none;
  stroke: var(--gold);
  stroke-width: 2;
  stroke-linecap: round;
  stroke-dasharray: 7 9;
}
.js .journey__path {
  stroke-dashoffset: 900;
  transition: stroke-dashoffset 2.6s var(--ease) 0.25s;
}
/* The dash pattern is what makes it read as a route; animating the
   offset both draws it and keeps the dashes travelling along it. */
.js .journey.is-in .journey__path { stroke-dashoffset: 0; }

.journey__node { fill: var(--gold); }
.journey__halo {
  fill: none;
  stroke: var(--gold);
  stroke-width: 1.5;
  transform-origin: center;
  transform-box: fill-box;
  opacity: 0;
}
.js .journey.is-in .journey__halo { animation: halo 3.4s var(--ease) infinite; }
.js .journey.is-in .journey__halo--late { animation-delay: 1.7s; }
@keyframes halo {
  0%   { opacity: 0.85; scale: 0.4; }
  70%  { opacity: 0;    scale: 2.2; }
  100% { opacity: 0;    scale: 2.2; }
}

.journey__label {
  font-family: var(--font-body);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  fill: var(--fg-muted);
}
.band--cream .journey__label { fill: var(--ink-soft); }
.journey__label--lead { fill: var(--gold); }
.band--cream .journey__label--lead { fill: var(--gold-deep); }

.js .journey__label { opacity: 0; transition: opacity 0.8s var(--ease) 1.9s; }
.js .journey.is-in .journey__label { opacity: 1; }

/* The viewBox is fixed, so the whole diagram scales by width alone:
   at 350px the 800-unit box renders at 0.44, and an 11px label lands
   at under 5 real pixels. Everything is therefore scaled UP in user
   units here so it arrives back at a legible size, and the ground
   curve is dropped: at this scale it no longer reads as a horizon and
   only crowds the labels. */
@media (max-width: 720px) {
  .journey__ground { display: none; }
  /* The y positions are attributes, so the 18-unit gap between the two
     label lines cannot shrink with the type. At 24 units the lines all
     but touch, so the second line goes: "Chadwell Heath" and "East
     Africa" carry the point on their own. */
  .journey__label--sub { display: none; }
  .journey__label  { font-size: 24px; letter-spacing: 0.1em; }
  .journey__path   { stroke-width: 4; stroke-dasharray: 12 14; }
  .journey__halo   { stroke-width: 3; r: 11; }
  .journey__node   { r: 9; }
}

/* ---------------------------------------------------------------
   24h. Corner-framed figure
   Four gold brackets that draw in around a photograph as it lands.
   Used sparingly: the one image on a page that carries the argument.
   --------------------------------------------------------------- */
.framed { position: relative; }
.framed__corner {
  position: absolute;
  width: clamp(22px, 3vw, 38px);
  height: clamp(22px, 3vw, 38px);
  border: 0 solid var(--gold);
  opacity: 0;
  transition: opacity 0.7s var(--ease) 0.35s, translate 0.9s var(--ease) 0.35s;
  pointer-events: none;
}
.framed__corner--tl { top: -1px; left: -1px;  border-top-width: 2px; border-left-width: 2px;  translate: 14px 14px; }
.framed__corner--tr { top: -1px; right: -1px; border-top-width: 2px; border-right-width: 2px; translate: -14px 14px; }
.framed__corner--bl { bottom: -1px; left: -1px;  border-bottom-width: 2px; border-left-width: 2px;  translate: 14px -14px; }
.framed__corner--br { bottom: -1px; right: -1px; border-bottom-width: 2px; border-right-width: 2px; translate: -14px -14px; }
.framed.is-in .framed__corner,
.is-in .framed .framed__corner { opacity: 1; translate: 0 0; }
html:not(.js) .framed__corner { opacity: 1; translate: 0 0; }

/* ---------------------------------------------------------------
   24i. Feature panel
   The registration and "get involved" calls, lifted off the band
   with a slow gold sweep so the primary action on the page is the
   brightest thing on it.
   --------------------------------------------------------------- */
.panel {
  position: relative;
  overflow: hidden;
  padding: clamp(2rem, 5vw, 3.5rem);
  border: 1px solid var(--plum-line);
  border-radius: var(--radius-lg);
  background: linear-gradient(180deg, #2A0F33 0%, #1C0A24 100%);
}
.band--cream .panel {
  border-color: #22142922;
  background: linear-gradient(180deg, #FFFFFF 0%, var(--cream-2) 100%);
}
.panel::before {
  content: "";
  position: absolute;
  inset: -40% -10% auto -10%;
  height: 90%;
  background: radial-gradient(ellipse at 50% 100%, #E5A93C2e 0%, transparent 65%);
  pointer-events: none;
}
/* The lit frame is drawn in 25g, which carries light all the way
   around the panel rather than only across its top edge. */
.panel > * { position: relative; }

/* ---------------------------------------------------------------
   24k. Field motion
   A gold rule that draws under an input on focus. The border still
   carries the error state; this is only the "you are here" signal.
   --------------------------------------------------------------- */
.field { position: relative; }
.field__input,
.field__textarea,
.field__select { position: relative; z-index: 1; }
/* Placed in the grid immediately after the control, then pulled up
   over its bottom border by the row-gap plus its own height. Absolute
   positioning would have anchored it to the foot of .field, which is
   below the error message rather than under the input. */
.field__rule {
  height: 2px;
  margin-block: calc(-0.55rem - 2px) 0;
  background: var(--gold);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform 0.45s var(--ease);
  pointer-events: none;
}
.field:focus-within .field__rule { transform: scaleX(1); }
.field.has-error .field__rule { background: var(--rose); transform: scaleX(1); }

/* The state after submitting. Deliberately not called "sent": this
   form hands the message to the visitor's own email app, and it is
   they who send it. Saying otherwise would be a lie the interface
   tells on the one screen where being believed matters. */
.form__status--handoff {
  display: grid;
  justify-items: center;
  gap: 0.9rem;
  text-align: center;
  padding: clamp(1.5rem, 4vw, 2.25rem);
  background: #E5A93C12;
}
.form__status--handoff svg { width: 46px; height: 46px; }
.form__status__title {
  font-family: var(--font-display);
  font-size: var(--fs-h3);
  font-weight: 500;
  line-height: 1.2;
  color: var(--cream);
}
.form__status__body { font-size: var(--fs-small); color: var(--fg-muted); text-wrap: pretty; }
.form__status__body a { color: var(--gold); border-bottom: 1px solid #E5A93C55; }
.form__status__body a:hover { border-bottom-color: var(--gold); }

/* ---------------------------------------------------------------
   24l. Gallery lift
   A gold hairline that draws across the foot of a photograph on
   hover, matching the showcase rule so the two read as one family.
   --------------------------------------------------------------- */
.shot::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 2px;
  background: linear-gradient(90deg, var(--gold), transparent);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform 0.7s var(--ease);
  pointer-events: none;
}
.shot:hover::after,
.shot:focus-visible::after { transform: scaleX(1); }

/* ---------------------------------------------------------------
   24m. Lottie holders added in this pass
   --------------------------------------------------------------- */
.lottie--flame { width: clamp(56px, 7vw, 88px); height: clamp(56px, 7vw, 88px); flex: none; }
.lottie--rise  { width: clamp(88px, 11vw, 140px); height: clamp(88px, 11vw, 140px); }

/* A flame set beside a heading, so the ministry's first name is
   present as a mark rather than only as a sentence. */
.heading-flame {
  display: flex;
  align-items: center;
  gap: clamp(0.5rem, 1.5vw, 1rem);
}

/* ---------------------------------------------------------------
   24n. Ambient hero glyph
   The brand mark, animated, used once per interior page as the
   counterweight to the headline. It is the logo, so it introduces
   no new vocabulary to the page.
   --------------------------------------------------------------- */
.hero__inner { position: relative; z-index: 1; }

.hero__glyph {
  position: absolute;
  right: clamp(1rem, 6vw, 5rem);
  bottom: clamp(1.5rem, 5vh, 4rem);
  opacity: 0.75;
  z-index: 0;
  pointer-events: none;
}
@media (max-width: 860px) { .hero__glyph { display: none; } }

/* ---------------------------------------------------------------
   24o. Cream-band variants for pieces first built on the dark bands
   --------------------------------------------------------------- */
.band--cream .panel .lead { color: var(--ink-soft); }
.band--cream .aurora::before { background: radial-gradient(circle, #E5A93C33 0%, transparent 68%); }
.band--cream .aurora::after  { background: radial-gradient(circle, #D98BA033 0%, transparent 68%); }

/* ---------------------------------------------------------------
   24p. Hero photography
   Every hero stands on a real photograph rather than flat plum.

   Provenance: the only photographs the ministry has supplied are
   from the East Africa outreach, so each hero carries a visible
   credit saying so. That is what keeps them honest behind copy
   about a London conference. See README, "Photography provenance",
   and CONTENT-TODO for the conference photography still wanted;
   swapping a file in is the only change needed when it arrives.

   The image sits at z-index -3, which places it behind the .dawn
   gradient (-2) and the grain (-1). The dawn wash therefore falls
   over the photograph, which is the whole design direction rather
   than a coincidence: light breaking over a real room.
   --------------------------------------------------------------- */
.hero--media { position: relative; isolation: isolate; overflow: hidden; }

.hero__media {
  position: absolute;
  inset: 0;
  z-index: -3;
  overflow: hidden;
  pointer-events: none;
}
/* Taller than the frame, with the excess split top and bottom, so the
   parallax pass has headroom in both directions. Same reasoning as
   .showcase__media img. */
.hero__media img {
  position: absolute;
  inset: -8% 0 auto 0;
  width: 100%;
  height: 116%;
  object-fit: cover;
  filter: saturate(0.78) contrast(1.06) brightness(0.68);
  will-change: transform;
}

/* The scrim. Heaviest on the left, under the headline, easing off to
   the right where the photograph is allowed to be seen. Measured, not
   guessed: the cream headline clears WCAG AA against every part of
   the band it actually covers. */
.hero__media::after {
  content: "";
  position: absolute;
  inset: 0;
  background:
    /* Top band. The eyebrow is 12px gold, the tightest contrast on the
       page, and it sits high in the hero where the vertical scrim below
       does not reach. Measured: without this the gold lands at 3.9:1
       over the brightest patch of photograph, short of the 4.5 small
       text needs. This carries it to about 5.3:1, and darkens the
       ground under the fixed header at the same time. */
    linear-gradient(to bottom,
      rgba(23, 9, 30, 0.70) 0%,
      rgba(23, 9, 30, 0.50) 24%,
      rgba(23, 9, 30, 0.18) 44%,
      transparent 62%),
    linear-gradient(100deg,
      rgba(23, 9, 30, 0.95) 0%,
      rgba(23, 9, 30, 0.90) 34%,
      rgba(23, 9, 30, 0.68) 62%,
      rgba(32, 13, 40, 0.52) 100%),
    linear-gradient(to top,
      rgba(23, 9, 30, 0.96) 0%,
      rgba(23, 9, 30, 0.30) 42%,
      transparent 70%);
}

/* The lead over a photograph.
   --fg-muted is a lavender picked to sit quietly on a flat plum band;
   its luminance is about half of cream's, so over a photograph it
   needs an almost black ground to clear 4.5:1. Rather than darken the
   picture until the lavender works, the text moves most of the way to
   cream. Enough hierarchy below the headline survives, and it buys
   several points of contrast back. */
.hero--media .hero__sub {
  color: color-mix(in oklab, var(--cream) 82%, var(--fg-muted));
}

/* Phone: the type runs the full width of the picture instead of
   sitting in the darkened left third, so the scrim stops being a
   left-to-right wash and becomes an even one. */
@media (max-width: 700px) {
  .hero__media::after {
    background:
      linear-gradient(to bottom,
        rgba(23, 9, 30, 0.78) 0%,
        rgba(23, 9, 30, 0.60) 38%,
        rgba(23, 9, 30, 0.66) 72%,
        rgba(23, 9, 30, 0.92) 100%),
      linear-gradient(100deg,
        rgba(23, 9, 30, 0.50) 0%,
        rgba(23, 9, 30, 0.42) 60%,
        rgba(32, 13, 40, 0.38) 100%);
  }
}

/* Photo credit. Small, in the corner, the way a credit is set in
   print. It is real information, not decoration, so it is not
   aria-hidden and it is not the first thing anyone reads. */
.hero__credit {
  position: absolute;
  right: var(--gutter);
  bottom: clamp(0.9rem, 2.5vh, 1.6rem);
  z-index: 1;
  max-width: 18rem;
  text-align: right;
  font-size: 0.6875rem;
  font-weight: 600;
  letter-spacing: 0.13em;
  text-transform: uppercase;
  line-height: 1.5;
  color: #B7A3C0;
  text-wrap: balance;
}
/* On a narrow screen the credit drops into the flow under the hero
   copy. It is not inside .wrap, so it has to bring its own gutter or
   it runs to the edge of the screen. */
@media (max-width: 700px) {
  .hero__credit {
    position: static;
    max-width: none;
    text-align: left;
    margin: 1.5rem var(--gutter) 0;
    padding-top: 1rem;
    border-top: 1px solid var(--plum-line);
  }
}

/* The rings are dialled right back over a photograph: at full strength
   they read as scratches on the print rather than as atmosphere. */
.hero--media .orbit { opacity: 0.09; }

/* ---------------------------------------------------------------
   24q. The mark, set in the closing call
   The animated brand mark moved out of the heroes when they gained
   photographs: a drawn sun on top of a photographed room is two
   pictures fighting. It reads better as punctuation at the foot of
   the page, above the last thing the page asks.
   --------------------------------------------------------------- */
.mark-rise {
  width: clamp(74px, 9vw, 112px);
  height: clamp(74px, 9vw, 112px);
  margin-inline: auto;
  margin-bottom: clamp(0.25rem, 1.5vw, 0.75rem);
  opacity: 0.9;
}

@media (prefers-reduced-motion: reduce) {
  .hero__media img { transform: none !important; }
}

/* ---------------------------------------------------------------
   24t. Reduced motion for everything in section 24
   Each effect resolves to its finished state. Nothing is removed,
   because removing a reveal leaves content invisible.
   --------------------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
  .aurora::before,
  .aurora::after      { animation: none; }
  .glow               { display: none; }

  .js .divider__rule  { stroke-dashoffset: 0; transition: none; }
  .timeline__rail     { transform: scaleY(1); }
  .timeline__node     { transition: none; }

  .objections li                    { transition: none; }
  .js .objections.is-in li          { opacity: 0.45; text-decoration-color: var(--gold-deep); }
  .js .reversal                     { opacity: 1; transform: none; transition: none; }

  .js .journey__path  { stroke-dashoffset: 0; transition: none; }
  .js .journey__label { opacity: 1; transition: none; }
  .js .journey.is-in .journey__halo { animation: none; opacity: 0.5; }

  .framed__corner     { opacity: 1; translate: 0 0; transition: none; }
  .field__rule        { transition: none; }
  .shot::after        { transition: none; }
}


/* =====================================================================
   25. THE DAWN ENGINE
   The design direction is "dawn over Chadwell Heath". Until now that
   has been a gradient standing in for a sunrise. This section builds
   the sunrise itself, and then spends what it learns doing so on the
   rest of the site.

   Nothing here is required to read a page. Every effect is either
   purely decorative, gated behind @supports, or resolves to a finished
   state in 25z under prefers-reduced-motion.
   ===================================================================== */

/* ---------------------------------------------------------------
   25a. Registered properties
   A custom property is a string until it is registered with a type.
   Registering these makes them interpolatable, which is what lets a
   gradient angle or a colour stop animate at all: without @property
   the browser has nothing to tween between and the value snaps.
   --------------------------------------------------------------- */
@property --spin {
  syntax: "<angle>";
  inherits: false;
  initial-value: 0deg;
}
@property --sheen {
  syntax: "<percentage>";
  inherits: false;
  initial-value: -30%;
}
@property --ring-angle {
  syntax: "<angle>";
  inherits: false;
  initial-value: 0deg;
}
@property --lift {
  syntax: "<number>";
  inherits: false;
  initial-value: 0;
}

/* A spring. Overshoots once and settles, which reads as something with
   mass rather than something being faded in by a computer. */
:root {
  --spring: linear(
    0, 0.006, 0.025 2.8%, 0.101 6.1%, 0.539 18.9%, 0.721 25.3%, 0.849 31.5%,
    0.937 38.1%, 0.968 41.8%, 0.991 45.7%, 1.006 50.1%, 1.015 55%, 1.017 63.9%,
    1.001 82.8%, 1
  );
  --ease-out-expo: cubic-bezier(0.16, 1, 0.3, 1);
}

/* ---------------------------------------------------------------
   25b. The sunrise
   The horizon rule was already the hero's dividing line between the
   headline and everything under it. This makes it a real horizon: it
   runs the full width of the screen, and the sun crests it.

   The sun and its rays are children of the rule, so they are anchored
   to it rather than guessed at with magic numbers. Move the rule in
   the flow and the whole sunrise follows.
   --------------------------------------------------------------- */
/* The stage is what breaks out to the full width of the screen and
   what the sun is positioned against. The line is a separate child, so
   .horizon-draw can scale it from 0 to 1 without dragging the sun and
   the rays through the same transform. */
.horizon-stage {
  position: relative;
  width: 100vw;
  margin-inline: calc(50% - 50vw);
  height: 1px;
}

.horizon--dawn {
  display: block;
  width: 100%;
  height: 1px;
  background: linear-gradient(90deg,
    transparent 0%,
    color-mix(in oklab, var(--gold-glow) 55%, transparent) 14%,
    var(--gold) 55%,
    var(--gold-lift) var(--sun-x, 78%),
    color-mix(in oklab, var(--gold-glow) 60%, transparent) 92%,
    transparent 100%);
  /* The light arrives at the sun and spreads outwards along the line,
     rather than wiping in from the left edge like a loading bar. */
  transform-origin: var(--sun-x, 78%) 50%;
}

/* Both sit behind the headline. .hero__inner carries z-index 1, which
   makes it a stacking context, so a negative z-index here stays inside
   that context: under the type, over the photograph and the grain. */
.horizon__sun,
.horizon__rays {
  position: absolute;
  bottom: 0;
  left: var(--sun-x, 78%);
  z-index: -1;
  pointer-events: none;
}

.horizon__sun {
  /* Dropped below the line rather than centred on it, so what shows
     above the horizon is the crescent and the near halo while the
     bright core stays under it. Chosen by measurement, not by eye:
     centred, the halo put the home headline at 2.8:1, under the 3:1
     large-text floor. At this drop it measures 6.8:1. Re-measure if
     the sun's size or the headline's length changes. */
  translate: -50% 62%;
  width: var(--sun-size, clamp(13rem, 26vw, 26rem));
  /* Flattened rather than round. A sun this low is spread along the
     horizon by the atmosphere, and the flatter it is the less of its
     halo reaches up into the headline, which is what has to stay
     legible. Measured against the type; see the contrast note in
     README under Hero photography. */
  height: calc(var(--sun-size, clamp(13rem, 26vw, 26rem)) * 0.66);
  border-radius: 50%;
  background: radial-gradient(ellipse,
    #FFF7E4 0%,
    var(--gold-lift) 8%,
    var(--gold) 17%,
    color-mix(in oklab, var(--gold-glow) 72%, transparent) 27%,
    color-mix(in oklab, #7A3566 45%, transparent) 45%,
    transparent 68%);
  /* Softens the banding a hard radial stop leaves on 6-bit panels. */
  filter: blur(1.5px);
  animation: sun-breathe 11s ease-in-out infinite;
}

/* Light shafts, thrown from the same point the sun sits on.
   repeating-conic-gradient draws them; the mask is what stops it
   reading as a novelty pinwheel and makes it read as haze. */
.horizon__rays {
  translate: -50% 0;
  width: var(--rays-size, clamp(34rem, 96vw, 88rem));
  aspect-ratio: 2 / 1;
  transform-origin: 50% 100%;
  background: repeating-conic-gradient(from -90deg at 50% 100%,
    transparent 0deg 2.4deg,
    color-mix(in oklab, var(--gold) 22%, transparent) 2.4deg 3.2deg,
    transparent 3.2deg 6.6deg);
  -webkit-mask-image: radial-gradient(ellipse 58% 96% at 50% 100%,
    #000 2%, rgba(0, 0, 0, 0.45) 38%, transparent 76%);
          mask-image: radial-gradient(ellipse 58% 96% at 50% 100%,
    #000 2%, rgba(0, 0, 0, 0.45) 38%, transparent 76%);
  /* Rotating the element is composited. Animating the gradient's own
     `from` angle would repaint the whole conic every frame. */
  animation: rays-drift 26s ease-in-out infinite alternate;
}

@keyframes sun-breathe {
  0%, 100% { opacity: 0.82; scale: 1; }
  50%      { opacity: 1;    scale: 1.05; }
}
@keyframes rays-drift {
  from { rotate: -3.5deg; opacity: 0.72; }
  to   { rotate:  3.5deg; opacity: 1; }
}

/* The home page gets the biggest sun; the interior pages get a smaller
   one further out, so arriving at the home page feels like the larger
   room rather than the same room again. */
/* These three variables have to live on the stage, not on the line.
   The sun and the rays are the line's SIBLINGS, so a value set on
   .horizon--dawn never reaches them: they would silently keep the
   fallbacks in the var() calls. */
.hero--dawnbreak .horizon-stage { --sun-x: 86%; }

.hero--short .horizon-stage {
  --sun-size: clamp(9rem, 17vw, 17rem);
  --rays-size: clamp(26rem, 70vw, 62rem);
  --sun-x: 87%;
}

/* Under 900px the headline runs the full width, so the sun moves out
   to the edge and the shafts step back. */
@media (max-width: 900px) {
  .horizon-stage { --sun-x: 93%; --sun-size: clamp(9rem, 30vw, 16rem); }
  .horizon__rays { opacity: 0.5; }
}
/* On a phone the lead paragraph runs the full width of the screen, so
   it passes straight through where the sun's core sits. Measured, the
   lead landed between 2.0:1 and 3.5:1 against it, well under the 4.5
   that body text needs. The sun therefore becomes a quiet presence at
   the very edge: mostly off-screen, and dimmed through its own
   keyframe, because `opacity` alone would be overridden by the
   breathing animation it already runs. */
@media (max-width: 600px) {
  .horizon-stage { --sun-x: 99%; --sun-size: clamp(7rem, 30vw, 11rem); }
  .horizon__sun  { animation-name: sun-quiet; }
  .horizon__rays { display: none; }
}
@keyframes sun-quiet {
  0%, 100% { opacity: 0.38; scale: 1; }
  50%      { opacity: 0.52; scale: 1.04; }
}

/* ---------------------------------------------------------------
   25c. Light across the word
   The one word the whole ministry is named for. A band of brighter
   gold travels across it, which is the same event the hero depicts,
   at the scale of a single word.

   background-clip: text needs a transparent fill, so the guard matters:
   without support the text would be invisible rather than merely flat.
   --------------------------------------------------------------- */
@supports ((background-clip: text) or (-webkit-background-clip: text)) {
  /* Specificity again. Section 23e carries
     `.js .split .rise-word { animation: none }` at 0,3,0, to stop the
     word's own loadIn keyframe fighting the split reveal. This rule
     has to clear that bar, and it deliberately sets ONLY `animation`,
     so 23e keeps supplying the opacity and transform the word needs
     while the split is running. */
  .js .hero .split .rise-word,
  .hero .rise-word {
    /* .rise-word sits at opacity 0 and is brought back by its own
       loadIn keyframe. A second `animation` declaration replaces that
       list wholesale, so declaring word-light here strands the word at
       zero: invisible with no JS, and saved only by accident with JS,
       because 23e happens to set opacity on .split .rise-word. The
       finished state is declared outright instead. Same rake the
       README flags for .horizon-draw. */
    opacity: 1;
    transform: none;
    background-image: linear-gradient(100deg,
      var(--gold) 0%,
      var(--gold) 36%,
      #FFF8E7 46%,
      var(--gold-lift) 52%,
      var(--gold) 64%,
      var(--gold) 100%);
    background-size: 300% 100%;
    background-position: 100% 0;
    background-repeat: no-repeat;
    -webkit-background-clip: text;
            background-clip: text;
    color: transparent;
    animation: word-light 8s var(--ease) 1.8s infinite;
  }
}
/* The position must stay inside 0%-100%. An oversized background is
   offset by (container - image) x position, so anything outside that
   range slides the image clear of part of the box; with a transparent
   fill and no-repeat, those letters would not be pale, they would be
   gone. Both ends of the gradient are plain gold, so the loop restart
   from 0% back to 100% is invisible. */
@keyframes word-light {
  0%        { background-position: 100% 0; }
  42%, 100% { background-position: 0% 0; }
}

/* ---------------------------------------------------------------
   25d. The headline settles
   Fraunces is a variable face with SOFT and WONK axes. The headline
   lands soft and wide and firms up into its final shape, so the type
   itself performs the arrival instead of merely fading in.
   Runs once, on load, and only on the hero.
   --------------------------------------------------------------- */
/* Specificity is deliberate. Section 23e carries
   `.js .load-in > [data-split] { animation: none }` at 0,3,0 to stop
   three entrances fighting over the headline; this has to clear that
   bar or it would be cancelled with nothing to show for it. */
.js .hero .load-in > .hero__title,
.js .hero .load-in > .h1 {
  animation: type-settle 1.5s var(--ease-out-expo) both;
}
/* SOFT only. WONK swaps to genuinely different letterforms, and doing
   that while the words are still rising behind their clipping masks
   reads as a rendering fault rather than as type settling. SOFT
   changes terminals and barely moves the metrics. */
@keyframes type-settle {
  from { font-variation-settings: "SOFT" 100, "WONK" 1; }
  to   { font-variation-settings: "SOFT" 20,  "WONK" 1; }
}

/* ---------------------------------------------------------------
   25e. Buttons with weight
   A specular band crosses the primary button on hover, and the whole
   thing rises on a spring rather than a linear lift.
   --sheen is registered in 25a, so it can be interpolated; an
   unregistered custom property would jump from one end to the other.
   --------------------------------------------------------------- */
.btn { position: relative; overflow: hidden; isolation: isolate; }
.btn:hover { transition-timing-function: var(--spring); }

.btn--primary::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background: linear-gradient(105deg,
    transparent var(--sheen),
    color-mix(in oklab, #FFFFFF 62%, transparent) calc(var(--sheen) + 12%),
    transparent calc(var(--sheen) + 26%));
  transition: --sheen 0.75s var(--ease);
}
.btn--primary:hover::before,
.btn--primary:focus-visible::before { --sheen: 105%; }

/* The ghost button fills from the edge the pointer is nearest, which
   is a small thing that makes it feel answered rather than styled. */
.btn--ghost { position: relative; }
.btn--ghost::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background: color-mix(in oklab, var(--gold) 14%, transparent);
  scale: 0 1;
  transform-origin: left;
  transition: scale 0.45s var(--ease-out-expo);
}
.btn--ghost:hover::before,
.btn--ghost:focus-visible::before { scale: 1 1; }

/* ---------------------------------------------------------------
   25f. Cards that answer the pointer
   A soft light follows the cursor across the card it is over. The
   coordinates come from initSpotlight(); with no JS the variables are
   never set and the fallback keeps the highlight off the card.
   --------------------------------------------------------------- */
.pillar,
.member,
.stat,
.mosaic__cell,
.shot { position: relative; isolation: isolate; }

.pillar::before,
.member::before,
.stat::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.4s var(--ease);
  background: radial-gradient(
    18rem 18rem at var(--px, -100%) var(--py, -100%),
    color-mix(in oklab, var(--gold) 22%, transparent) 0%,
    transparent 62%);
}
.pillar:hover::before,
.member:hover::before,
.stat:hover::before { opacity: 1; }

.band--cream .pillar::before,
.band--cream .stat::before {
  background: radial-gradient(
    18rem 18rem at var(--px, -100%) var(--py, -100%),
    color-mix(in oklab, var(--gold) 26%, transparent) 0%,
    transparent 62%);
}

/* ---------------------------------------------------------------
   25g. The panel's turning edge
   A conic gradient rotated behind the panel and clipped to a 1px
   frame, so the border itself carries light around it. This replaces
   the single travelling hairline from 24i, which only lit the top.
   --------------------------------------------------------------- */
.panel::after {
  content: "";
  position: absolute;
  inset: 0;
  /* Load-bearing. This frame covers the whole panel, and a pseudo
     element is hit-tested as part of its owner, so without this it
     swallows every click inside: the contact form's submit button,
     its inputs, and the registration links. */
  pointer-events: none;
  border-radius: inherit;
  padding: 1px;
  background: conic-gradient(from var(--ring-angle),
    transparent 0deg,
    color-mix(in oklab, var(--gold) 85%, transparent) 42deg,
    var(--gold-lift) 60deg,
    color-mix(in oklab, var(--gold) 85%, transparent) 78deg,
    transparent 132deg,
    transparent 360deg);
  /* Two masks composited: everything, minus the inside. What is left
     is the frame. -webkit-mask-composite uses the older keywords. */
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
          mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
          mask-composite: exclude;
  animation: ring-turn 9s linear infinite;
  /* Replaces the travelling top hairline this rule used to draw. */
  width: auto;
  height: auto;
  transform: none;
}
@keyframes ring-turn { to { --ring-angle: 360deg; } }

/* ---------------------------------------------------------------
   25h. Scroll-driven animation
   Where the browser can drive an animation from the scroll position
   itself, it does so on the compositor: no scroll listener, no layout
   read, no jank. JS stays the fallback everywhere it already worked.
   --------------------------------------------------------------- */
@supports (animation-timeline: scroll()) {
  /* initProgress() stands down when this is supported; see main.js. */
  .scroll-progress__bar {
    animation: progress-fill linear both;
    animation-timeline: scroll(root block);
  }
  @keyframes progress-fill {
    from { transform: scaleX(0); }
    to   { transform: scaleX(1); }
  }
}

@supports (animation-timeline: view()) {
  /* The hero releases the page: as it leaves, its content drifts up
     and dims, so the fold reads as a handover rather than a cut. */
  .js .hero--dawnbreak .hero__inner {
    animation: hero-release linear both;
    animation-timeline: view();
    animation-range: exit -5% exit 75%;
  }
  @keyframes hero-release {
    to { opacity: 0; translate: 0 -5vh; filter: blur(3px); }
  }

  /* The band dividers draw themselves against the scroll instead of
     firing once on an observer callback. */
  .js .divider__rule {
    animation: crest-draw linear both;
    animation-timeline: view();
    animation-range: entry 10% cover 32%;
    transition: none;
  }
  @keyframes crest-draw {
    from { stroke-dashoffset: 3000; }
    to   { stroke-dashoffset: 0; }
  }

  /* Photographs settle from slightly over-scaled as they arrive. */
  .js .showcase__media img,
  .js .mosaic__cell img {
    animation: photo-settle linear both;
    animation-timeline: view();
    animation-range: entry 0% cover 42%;
  }
  @keyframes photo-settle {
    from { scale: 1.12; }
    to   { scale: 1; }
  }
}

/* ---------------------------------------------------------------
   25i. Moving between pages
   The site is six documents, so a link is a full navigation. A
   cross-document view transition keeps the header and the footer
   pinned while the content changes underneath them, which makes six
   documents behave like one.

   Unknown at-rules are ignored, so browsers without it simply
   navigate. 25z turns it off for reduced motion.
   --------------------------------------------------------------- */
@view-transition { navigation: auto; }

.site-header { view-transition-name: site-header; }
.site-footer { view-transition-name: site-footer; }
.scroll-progress { view-transition-name: scroll-progress; }

::view-transition-old(root) {
  animation: vt-leave 220ms var(--ease) both;
}
::view-transition-new(root) {
  animation: vt-arrive 420ms var(--ease-out-expo) both;
}
@keyframes vt-leave  { to   { opacity: 0; translate: 0 -1.5vh; } }
@keyframes vt-arrive { from { opacity: 0; translate: 0  2.5vh; } }

/* The pinned chrome crossfades in place rather than sliding with the
   page, which is the entire point of naming it. */
::view-transition-old(site-header),
::view-transition-new(site-header),
::view-transition-old(site-footer),
::view-transition-new(site-footer) {
  animation-duration: 200ms;
  animation-timing-function: var(--ease);
}

/* ---------------------------------------------------------------
   25j. Form state, from the form itself
   :has() lets the field react to its own control without JS having to
   label it. :user-invalid is the polite one: it holds its tongue until
   someone has actually finished with the field.
   --------------------------------------------------------------- */
.field:has(:user-invalid) .field__rule { background: var(--rose); scale: 1 1; }
.field:has(:user-invalid) .field__label { color: var(--rose); }
.field:has(:focus-visible) .field__label { color: var(--gold); }
.field__label { transition: color 0.25s var(--ease); }

/* A textarea that grows with what is typed into it, where supported. */
.field__textarea { field-sizing: content; max-height: 22rem; }

/* The submit button knows when the form is unfinished. Advisory only:
   the button is never disabled, and initForm() still does the real
   validating and the real messaging. */
.form:has(:user-invalid) .btn[type="submit"] {
  border-color: color-mix(in oklab, var(--rose) 60%, var(--gold));
}

/* ---------------------------------------------------------------
   25k. Gold that glows
   A drop-shadow in the gold itself, used only on the marks and rules
   that sit on the dark bands. Never on text: a glow behind a letter
   costs contrast, and the small gold text has none to spare.
   --------------------------------------------------------------- */
.band--night .brand__mark,
.band--plum .brand__mark,
.site-header .brand__mark,
.mark-rise,
.hero__glyph {
  filter: drop-shadow(0 0 14px color-mix(in oklab, var(--gold) 34%, transparent));
}

/* The eyebrow's rule picks up the light as its section arrives. */
.eyebrow::before {
  background: linear-gradient(90deg, transparent, var(--gold));
  transition: width 0.6s var(--ease-out-expo);
}
.band--cream .eyebrow::before {
  background: linear-gradient(90deg, transparent, var(--gold-deep));
}
.section-head:hover .eyebrow::before { width: 3.2rem; }

/* ---------------------------------------------------------------
   25l. Cards on the pointer, in three dimensions
   .tilt already rotates from JS. This gives the rotation somewhere to
   happen, and a highlight that moves against it, so the card reads as
   a surface catching light rather than a rectangle being skewed.
   --------------------------------------------------------------- */
.pillars,
.team { perspective: 1400px; }
.tilt { transform-style: preserve-3d; }

/* ---------------------------------------------------------------
   25z. Reduced motion, for everything in section 25
   The sunrise stays: it is a picture, and a still picture is a
   legitimate resting state. What stops is anything that moves.
   --------------------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
  .horizon__sun  { animation: none; opacity: 0.9; scale: 1; }
  .horizon__rays { animation: none; rotate: 0deg; opacity: 0.85; }

  .js .hero .split .rise-word,
  .hero .rise-word { animation: none; background-position: 40% 0; }

  .js .hero .load-in > .hero__title,
  .js .hero .load-in > .h1 { animation: none; }

  .btn--primary::before  { display: none; }
  .btn--ghost::before    { transition: none; }
  .panel::after          { animation: none; --ring-angle: 42deg; }
  .eyebrow::before       { transition: none; }

  .js .hero--dawnbreak .hero__inner,
  .js .showcase__media img,
  .js .mosaic__cell img { animation: none; opacity: 1; scale: 1; filter: none; }
  .js .divider__rule    { animation: none; stroke-dashoffset: 0; }

  /* A page transition is motion between documents. There is no
     resting state to fall back to, so it is simply not run. */
  @view-transition { navigation: none; }
}
