/* ══════════════════════════════════════════════════════════════════════
   Highveld CID — sheet 01
   A topographic sheet, not a website. See DESIGN.md for the contract.
   ═══════════════════════════════════════════════════════════════════ */

@font-face {
  font-family: 'Archivo';
  src: url('../fonts/archivo-latin-var.woff2') format('woff2-variations');
  font-weight: 100 900;
  font-stretch: 62% 125%;
  font-display: swap;
}

:root {
  /* Plate inks — see DESIGN.md § Color */
  --paper:       #F1F1EA;
  --paper-sunk:  #E6E6DC;
  --ink:         #141C17;
  --ink-soft:    #5C665D;
  --veg-deep:    #1A6339;
  --seal-green:  #207848;
  --leaf:        #80B848;
  --veg-pale:    #DEE8D1;
  --water:       #7FA3BE;
  --brand-green:  #23723F;   /* the client's green — see DESIGN.md § Color */
  --error:        #874545;   /* functional validation ink only, not a brand colour */
  --plate-dark:  #101A13;
  /* The mission band's ground. Deliberately far enough from --brand-green
     (1.84:1) that it can never be read as the acting colour, and far enough
     from --plate-dark in hue that the two dark passages are not the same
     passage twice. Paper on it measures 9.61. */
  --plate-green: #17452D;

  --rule:        color-mix(in srgb, var(--ink) 16%, transparent);
  --rule-strong: color-mix(in srgb, var(--ink) 32%, transparent);

  /* 4px scale, one rhythm throughout */
  --s1: .25rem;  --s2: .5rem;   --s3: .75rem;  --s4: 1rem;
  --s6: 1.5rem;  --s8: 2rem;    --s12: 3rem;   --s16: 4rem;
  --s24: 6rem;   --s32: 8rem;

  --sheet: 1440px;
  --gutter: 1.5rem;
  --margin: 1.25rem;

  /* The vertical rhythm every section band shares. */
  --band: clamp(2.75rem, 5.5vw, 4rem);

  /* How long a head plate takes to fade in or out. site.js staggers the two
     halves of a turn by HERO_CLEAR, which is tuned against this value and
     this curve — change one and re-check the other. */
  --hero-fade: 520ms;

  --ease: cubic-bezier(.16, 1, .3, 1);
}

@media (min-width: 60rem) {
  :root { --gutter: 2rem; --margin: 2.5rem; }
}

/* ─────────────────────────────────────────────────────────────  RESET ── */

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

/* Layout display values outrank the UA's [hidden] rule, so state it here. */
[hidden] { display: none !important; }

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  scroll-padding-top: 5rem;
}

body {
  font-family: 'Archivo', ui-sans-serif, system-ui, -apple-system, 'Segoe UI', sans-serif;
  font-variation-settings: 'wdth' 100, 'wght' 400;
  font-size: 1.0625rem;
  line-height: 1.65;
  color: var(--ink);
  background: var(--paper);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  overflow-x: hidden;
}

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

a { color: inherit; }

button, input, select, textarea { font: inherit; color: inherit; }

:where(a, button, input, select, [tabindex]):focus-visible {
  outline: 2px solid var(--brand-green);
  outline-offset: 2px;
  border-radius: 1px;
}

.skip {
  position: absolute; left: var(--s4); top: var(--s4);
  z-index: 100; padding: var(--s3) var(--s4);
  min-height: 44px; display: inline-flex; align-items: center;
  background: var(--brand-green); color: #fff;
  text-decoration: none; font-size: .875rem;
  transform: translateY(-250%);
}
.skip:focus { transform: none; }

/* ────────────────────────────────────────────────────────── TYPOGRAPHY ── */

.marginalia {
  font-variation-settings: 'wdth' 70, 'wght' 550;
  font-size: .6875rem;
  letter-spacing: .14em;
  text-transform: uppercase;
  line-height: 1.4;
  color: var(--ink-soft);
  display: flex;
  flex-wrap: wrap;
  gap: 0 var(--s3);
}
.sheet-title {
  font-variation-settings: 'wdth' 78, 'wght' 700;
  font-size: clamp(2.75rem, 8.4vw, 6rem);
  line-height: .92;
  letter-spacing: -.03em;
  text-wrap: balance;
}

.head {
  font-variation-settings: 'wdth' 85, 'wght' 650;
  font-size: clamp(1.75rem, 3.4vw, 2.75rem);
  line-height: 1.08;
  letter-spacing: -.02em;
  text-wrap: balance;
}

.lede {
  font-size: clamp(1.0625rem, 1.55vw, 1.25rem);
  line-height: 1.55;
  max-width: 34ch;
  color: color-mix(in srgb, var(--ink) 88%, var(--paper));
}
.prose { max-width: 64ch; }
/* Sibling form, not descendant. `.prose p + p` could never match: the `.prose`
   elements ARE the paragraphs, so a `<p>` inside one cannot exist. Two
   paragraphs with genuinely different jobs in take-part's "Your need" were
   running with a measured 0px between them, as one slab. */
.prose + .prose { margin-top: var(--s4); }
.prose strong { font-variation-settings: 'wdth' 100, 'wght' 680; }
.prose em { font-style: italic; }
.prose--tight { max-width: 56ch; color: color-mix(in srgb, var(--ink) 84%, var(--paper)); }

/* ──────────────────────────────────────────────────────────────  RAIL ── */

.rail {
  position: sticky; top: 0; z-index: 50;
  background: color-mix(in srgb, var(--paper) 92%, transparent);
  backdrop-filter: saturate(1.4) blur(10px);
  border-bottom: 1px solid var(--rule);
}

.rail__in {
  max-width: var(--sheet);
  margin-inline: auto;
  padding: var(--s3) var(--margin);
  display: flex;
  align-items: center;
  gap: var(--s6);
}

.rail__mark {
  display: flex; align-items: center; gap: var(--s2);
  min-height: 44px;
  text-decoration: none; flex: none;
}
.rail__mark img { width: 48px; height: 48px; }
.rail__name {
  font-variation-settings: 'wdth' 80, 'wght' 700;
  font-size: .9375rem;
  letter-spacing: -.01em;
}
.rail__nav {
  margin-left: auto;
  display: flex; align-items: center; gap: var(--s1);
  font-variation-settings: 'wdth' 82, 'wght' 550;
  font-size: .8125rem;
}
.rail__nav a {
  text-decoration: none;
  padding: .55rem var(--s3);
  min-height: 44px;
  display: inline-flex; align-items: center;
  color: var(--ink-soft);
  transition: color .16s var(--ease);
}
.rail__nav a:hover { color: var(--ink); }
.rail__nav a:not(.rail__cta) { display: none; }
@media (min-width: 52rem) { .rail__nav a:not(.rail__cta) { display: inline-flex; } }

.rail__cta {
  background: var(--brand-green);
  color: #fff !important;
  padding-inline: var(--s4);
}
.rail__cta:hover { background: color-mix(in srgb, var(--brand-green) 84%, var(--ink)); }

/* ─────────────────────────────────────────────────────────  SHEET HEAD ── */

.sheet-head {
  position: relative;
  /* Tightened 2026-08-06: was clamp(38rem, 86svh, 54rem) with 4rem/6rem
     padding, which left a visible gap under the rail before the title. */
  min-height: clamp(30rem, 72svh, 44rem);
  display: grid;
  align-items: center;
  padding: var(--s6) var(--margin) var(--s12);
  overflow: hidden;
}

.plate { position: absolute; inset: 0; }

/* Stacked: the model is never loaded under this breakpoint (see HERO_AT in
   site.js, which has to stay the inverse of this query) — ~340 KB gzipped is
   not worth a phone's data — so the head is a clean title block on paper. */
@media (max-width: 51.9375rem) {
  .sheet-head { min-height: auto; padding-block: var(--s8) var(--s12); }
  .plate--head { display: none; }
  .sheet-head__note { margin-top: var(--s6); }
}

.plate--head {
  /* Static graticule so the head reads as a sheet before the model loads —
     and permanently, wherever the model is not loaded at all. */
  background:
    repeating-linear-gradient(to right,  var(--rule) 0 1px, transparent 1px 8.333%),
    repeating-linear-gradient(to bottom, var(--rule) 0 1px, transparent 1px 6rem),
    var(--paper);
  background-position: calc(var(--margin) * -1) 0;
}

/* The head model, parked clear of the type column. 'contain' rather than
   'cover' so the indicative boundary is never cropped by the viewport — a
   cut boundary is a different claim.

   In carousel mode all three projections stack here on identical geometry
   and only their opacity differs. The shared frame build_models.py forces on
   all three keeps the district the same size and in the same place across a
   turn — but the drawings themselves do not coincide, so site.js sequences
   the fades and never shows two at once. See show() there. */
.plate__hero {
  position: absolute;
  top: 0; right: 0;
  height: 100%;
  width: min(76%, 1360px);
  max-width: none;
  object-fit: contain;
  object-position: right center;
  opacity: 0;
  transition: opacity var(--hero-fade) var(--ease);
}
.plate__hero-veil {
  position: absolute; inset: 0;
  pointer-events: none;
  /* Tuned to the headline's longest line. At three lines that is "We need
     your help", which ends around 46% of the viewport, so the veil holds
     near-solid to 32% and is clear by 72% — enough to keep the type column
     clean without washing out more of the drawing than it has to. Re-check
     this if the headline or its line breaks change. */
  background: linear-gradient(to right,
    var(--paper) 0%,
    color-mix(in srgb, var(--paper) 92%, transparent) 32%,
    color-mix(in srgb, var(--paper) 38%, transparent) 52%,
    transparent 72%);
  opacity: 0;
  transition: opacity var(--hero-fade) var(--ease);
}
.plate--head.has-hero .plate__hero-veil { opacity: 1; }
/* Exactly one plate is current at a time. In 'still' mode that is the only
   plate there is, so the two modes share this one rule. */
.plate--head.has-hero .plate__hero.is-current { opacity: 1; }

/* max-width is the sheet MINUS its gutters, matching `.wrap`'s content box.
   It used to be the full `--sheet`, and because `.sheet-head` supplies the
   gutter as padding while `.wrap` supplies it as padding *inside* its own
   max-width, the two boxes centred differently: the divergence opened at
   1440px and reached a full 40px by 1520px, so at 1920 the h1 sat at 240
   and every other block on the page sat at 280. The page's largest line was
   the one thing off the page's own left edge. Fixed 2026-08-08. */
.sheet-head__block {
  position: relative;
  max-width: calc(var(--sheet) - var(--margin) * 2);
  margin-inline: auto;
  width: 100%;
}

.sheet-title { margin-bottom: var(--s4); }
/* The reader is the subject of the sentence, so the reader is the one word
   in the acting colour. 5.21 on paper, and it is display size. */
.sheet-title .accent { color: var(--brand-green); }

.actions {
  margin-top: var(--s6);
  display: flex; flex-wrap: wrap; gap: var(--s3);
}

.sheet-head__note {
  margin-top: var(--s6);
  padding-top: var(--s3);
  border-top: 1px solid var(--rule);
  max-width: 22rem;
  color: var(--brand-green);
}

/* The projection selector. Marginalia about the drawing, sat with the
   boundary note rather than laid over the plate as carousel furniture: it
   is outside #plate-head (which is aria-hidden) and on the paper side of
   the veil, where marginalia ink is guaranteed to read. Built by site.js
   only when a carousel actually exists. */
.plate-ctl {
  margin-top: var(--s2);
  max-width: 22rem;
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0 var(--s2);
}
.plate-ctl__label { flex: none; margin-right: var(--s1); }

.plate-ctl__step,
.plate-ctl__play {
  position: relative;
  font-variation-settings: 'wdth' 70, 'wght' 550;
  font-size: .6875rem;
  letter-spacing: .12em;
  text-transform: uppercase;
  font-variant-numeric: tabular-nums;
  color: var(--ink-soft);
  background: none;
  border: 1px solid transparent;
  border-radius: 1px;
  padding: .3rem var(--s2);
  /* 36px + the 4px hit extension each side clears 44, which the shortest
     mark (0°) would otherwise miss. */
  min-width: 2.25rem;
  cursor: pointer;
  transition: color .16s var(--ease), border-color .16s var(--ease);
}
/* The mark stays marginalia-sized; the target does not. This extends the
   hit area to 44px without inflating the box, so the row reads as a line of
   sheet annotation rather than as a strip of buttons. The insets are set so
   neighbouring targets meet exactly and never overlap. */
.plate-ctl__step::after,
.plate-ctl__play::after {
  content: '';
  position: absolute;
  inset: -9px -4px;
}
.plate-ctl__step:hover,
.plate-ctl__play:hover { color: var(--ink); border-color: var(--rule-strong); }

/* The plate on the sheet right now, in the ink that means "this one". */
.plate-ctl__step[aria-pressed='true'] {
  color: var(--brand-green);
  border-color: var(--brand-green);
}

/* Set off from the projections by the sheet's own separator, so it does not
   read as a fourth plate to choose. */
.plate-ctl__play { margin-left: var(--s3); }
.plate-ctl__play::before {
  content: '·';
  position: absolute;
  left: calc(var(--s3) * -1);
  color: var(--rule-strong);
}

/* ───────────────────────────────────────────────────────────  BUTTONS ── */

.btn {
  display: inline-flex; align-items: center; justify-content: center;
  min-height: 48px;
  padding: 0 var(--s6);
  font-variation-settings: 'wdth' 88, 'wght' 620;
  font-size: .9375rem;
  letter-spacing: .005em;
  text-decoration: none;
  border: 1px solid transparent;
  border-radius: 1px;
  cursor: pointer;
  transition: background-color .18s var(--ease), color .18s var(--ease),
              border-color .18s var(--ease);
}
.btn--primary { background: var(--brand-green); color: #fff; }
.btn--primary:hover { background: color-mix(in srgb, var(--brand-green) 82%, var(--ink)); }
.btn--ghost { border-color: var(--rule-strong); color: var(--ink); background: transparent; }
.btn--ghost:hover { border-color: var(--ink); background: color-mix(in srgb, var(--ink) 5%, transparent); }
.btn--sm { padding: .5rem .875rem; font-size: .75rem; }
.btn[disabled] { opacity: .55; cursor: not-allowed; }
.btn[data-busy] .btn__label { opacity: .6; }

/* ──────────────────────────────────────────────────────────  SECTIONS ── */

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

/* One band rhythm for every section, tightened 2026-08-06 on client
   feedback that the page was too airy. Was a flat var(--s24) = 6rem. */
.section { padding-block: var(--band); }
.section + .section { border-top: 1px solid var(--rule); }

/* A section's opening block: eyebrow, heading, standfirst. Was `.note-grid`,
   a two-column grid reserving a 10rem margin column for notes. Nothing was
   ever put in that column on either page, so all it did was push every
   heading 13rem (10rem + the 3rem gap = 208px) right of the content it
   introduces, while the lists, the video and the map stayed at the content
   edge. Removed 2026-08-08.

   The page now has one left edge. Where a block wants a margin rail, it
   brings its own and keeps it inside its own measure: the numbered keys on
   `.legend__row` and `.offer__row`, and the date column on `.event`. An
   outer rail would have sat 208px further out than those and read as a
   second, competing margin.

   Plain block flow, not a grid: the old wrapper's `gap` never applied, since
   it only ever had one child, and a gap here would double up with the margin
   below. The eyebrow sits tight against the heading by design. */
.intro .head + .prose { margin-top: var(--s4); }

/* ────────────────────────────────────────────────────────────  RIBBON ── */

/* The seal's ring unrolled into a moving band, breaking hero from mission.
   Sunk stock, so the run of values down the page is paper → sunk → green →
   paper → sunk → near-black rather than three whites and a black.

   It does not move until site.js says so. No JavaScript means a static band
   of names, which is a perfectly good divider, and it means the pause
   control is never on the page without something behind it. */
.ribbon {
  overflow: hidden;
  background: var(--paper-sunk);
  border-block: 1px solid var(--rule);
  padding-block: var(--s4);
  position: relative;
}

.ribbon__track {
  display: flex;
  width: max-content;
  animation: ribbon-run 23s linear infinite;
  animation-play-state: paused;
}
.ribbon.is-running .ribbon__track { animation-play-state: running; }

/* Shift by exactly ONE of the six identical sets (1/6 = 16.6667%), so the
   loop lands on a frame indistinguishable from the start. Shifting by more
   than one is the usual bug: the five sets still on screen have to be wider
   than the viewport at the end of the cycle or a gap runs in behind them,
   and six sets of five names clears roughly 3400px of viewport. */
@keyframes ribbon-run {
  from { transform: translate3d(0, 0, 0); }
  to   { transform: translate3d(-16.6667%, 0, 0); }
}

.ribbon__set {
  display: flex;
  align-items: center;
  list-style: none;
  padding: 0;
  margin: 0;
  flex: none;
}
.ribbon__set li {
  font-variation-settings: 'wdth' 76, 'wght' 600;
  font-size: .8125rem;
  letter-spacing: .18em;
  text-transform: uppercase;
  color: color-mix(in srgb, var(--ink) 78%, var(--paper-sunk));
  white-space: nowrap;
  padding-inline: var(--s4);
}
/* The separator the seal itself uses between the five names. */
.ribbon__set li::after {
  content: '∗';
  margin-left: var(--s6);
  color: var(--seal-green);
  letter-spacing: 0;
}

/* Sits over the band's own ground so the names run out behind it rather
   than being chopped off at its edge. The left padding IS the fade zone, so
   it has to be wide enough to swallow a name. */
.ribbon__hold {
  position: absolute;
  right: 0; top: 0; bottom: 0;
  display: flex; align-items: center;
  padding-inline: var(--s16) var(--margin);
  border: 0;
  font-variation-settings: 'wdth' 70, 'wght' 550;
  font-size: .6875rem;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--ink-soft);
  cursor: pointer;
  background: linear-gradient(to right,
    transparent 0, var(--paper-sunk) var(--s12), var(--paper-sunk) 100%);
}
.ribbon__hold:hover { color: var(--ink); }

@media (prefers-reduced-motion: reduce) {
  /* Nothing to pause, so site.js offers no control and the band just sits. */
  .ribbon__track { animation: none; }
}

/* ───────────────────────────────────────────────────────────  MISSION ── */

/* The page's second ground, and the main answer to "too white". The scroll
   now paces hero (paper) → mission (deep green) → the work (paper) →
   register (sunk) → colophon (near-black), which is the density pacing
   DESIGN.md § Layout always asked for. It went missing when the District and
   Revision sections were deleted, because .section--status carried the only
   other ground on the page. */
.section--mission {
  background: var(--plate-green);
  color: var(--paper);
}
/* Where the ground changes, the ground is the separator. A hairline rule on
   top of it is just a seam. */
.section--mission + .section,
.section--volunteer { border-top: 0; }

/* Brand green is invisible on this ground (1.84:1), so anything focusable
   that lands here needs the paper ring instead. Nothing does yet; this is
   here so that the first CTA added to the mission does not ship unfocusable. */
.section--mission :where(a, button, [tabindex]):focus-visible {
  outline-color: var(--paper);
}

/* Centred, and the only centred passage on the sheet. That is the point:
   the page is a left-aligned column throughout, so one centred band reads as
   a deliberate pause rather than as a change of mind. It works here and
   would not work anywhere else, because this is the one section that is a
   statement rather than a list, a form or a drawing. */
.mission {
  max-width: 46rem;
  margin-inline: auto;
  text-align: center;
}
.mission__eyebrow {
  justify-content: center;
  color: color-mix(in srgb, var(--paper) 66%, var(--plate-green));
}
.mission__head {
  margin-top: var(--s4);
  margin-inline: auto;
  max-width: 20ch;
  color: var(--paper);
}
.mission__body {
  margin-top: var(--s6);
  margin-inline: auto;
  max-width: 56ch;
  font-size: clamp(1.0625rem, 1.35vw, 1.1875rem);
  line-height: 1.62;
  color: color-mix(in srgb, var(--paper) 88%, var(--plate-green));
}

/* The campaign's real name, closing the section on a rule that runs the
   full block. This is the section's visual anchor instead of an image:
   there is no photograph to use, and the campaign name is the one piece of
   real content that belongs here and is not already said above. */
.mission__campaign {
  margin-top: var(--s8);
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  gap: var(--s4);
}
.mission__campaign::before,
.mission__campaign::after {
  content: '';
  height: 1px;
  background: color-mix(in srgb, var(--paper) 28%, var(--plate-green));
}
.mission__campaign span {
  font-variation-settings: 'wdth' 74, 'wght' 560;
  font-size: .75rem;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: color-mix(in srgb, var(--paper) 74%, var(--plate-green));
}

/* ────────────────────────────────────────────────────────  VOLUNTEER ── */

/* Sunk stock. It carried the deed and the form; it now carries the page's one
   call to action, and the change of ground is what marks that as the end of
   the argument rather than another section of it. */
.section--volunteer { background: var(--paper-sunk); }

/* ────────────────────────────────────────────────────────────  LEGEND ── */

.legend-head { max-width: 20ch; }
.legend-block { margin-top: var(--s8); }

/* The two tiers are one section but two different asks: an afternoon, or a
   standing expert commitment. They used to be told apart by a one-line
   subtitle, which was not enough. Each now opens with its own eyebrow, its
   own heading and its own sentence, and the second is separated by a full
   rule and real space rather than sitting in the same rhythm as the first. */
.legend-block--expert {
  margin-top: var(--s16);
  padding-top: var(--s12);
  border-top: 2px solid var(--ink);
}

.legend-block__eyebrow { color: var(--brand-green); }

.legend-block__head {
  margin-top: var(--s2);
  font-variation-settings: 'wdth' 84, 'wght' 660;
  font-size: clamp(1.375rem, 2.4vw, 1.875rem);
  line-height: 1.1;
  letter-spacing: -.018em;
}
.legend-block__sub {
  margin-top: var(--s3);
  padding-bottom: var(--s4);
  border-bottom: 1px solid var(--rule-strong);
  max-width: 52ch;
  font-size: 1rem;
  color: color-mix(in srgb, var(--ink) 80%, var(--paper));
}

/* A map legend runs in columns; one column across a 1440px sheet leaves
   the right half of every rule empty. */
.legend { list-style: none; padding: 0; }
@media (min-width: 64rem) {
  .legend { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); column-gap: var(--s12); }
}

.legend__row {
  display: grid;
  grid-template-columns: 2.25rem 3rem minmax(0, 1fr);
  align-items: start;
  gap: var(--s4);
  padding-block: var(--s6);
  border-bottom: 1px solid var(--rule);
}
@media (max-width: 40rem) {
  .legend__row { grid-template-columns: 2rem 2.5rem minmax(0, 1fr); gap: var(--s3); }
}

.legend__key {
  font-variation-settings: 'wdth' 70, 'wght' 620;
  font-size: .75rem;
  letter-spacing: .08em;
  font-variant-numeric: tabular-nums;
  color: var(--brand-green);
  padding-top: .3rem;
}

.legend__sym {
  width: 3rem; height: 2.25rem;
  border: 1px solid var(--rule);
  background: var(--paper-sunk);
  background-repeat: no-repeat;
  background-position: center;
  background-size: 100% 100%;
  transition: transform .18s var(--ease), border-color .18s var(--ease);
}
@media (max-width: 40rem) { .legend__sym { width: 2.5rem; height: 2rem; } }

.legend__row:hover .legend__sym,
.legend__row:focus-within .legend__sym {
  transform: translateY(-2px);
  border-color: var(--rule-strong);
}

.legend__text { display: grid; gap: var(--s1); max-width: 52ch; }
.legend__text b {
  font-variation-settings: 'wdth' 92, 'wght' 640;
  font-size: 1.0625rem;
}
.legend__text { color: color-mix(in srgb, var(--ink) 82%, var(--paper)); }
.legend__text b { color: var(--ink); }

/* ─────────────────────────────────────────────────────────  THE DEED ── */

/* What is left of the deed after the pledge text and the form went to Google
   (2026-08-08). It is a document, not a form: keyline, seal, one paragraph in
   our own voice, and deliberately NO ruled lines. An empty ruled line under a
   label reads as a field, and nothing on this page takes input any more, so a
   line that invites typing is a promise the page cannot keep. */
.deed {
  background: #FBFBF6;
  border: 1px solid var(--rule-strong);
  padding: var(--s8) var(--s6);
  position: relative;
  max-width: 34rem;
}
@media (min-width: 40rem) { .deed { padding: var(--s12) var(--s8); } }

.deed::before {
  /* Inner keyline, as on a printed certificate */
  content: '';
  position: absolute; inset: 5px;
  border: 1px solid var(--rule);
  pointer-events: none;
}

.deed__head {
  display: flex; align-items: center; gap: var(--s4);
  margin-bottom: var(--s6);
}
.deed__seal { width: 56px; height: 56px; flex: none; }
.deed__title {
  font-variation-settings: 'wdth' 76, 'wght' 700;
  font-size: clamp(1.5rem, 3.6vw, 1.875rem);
  line-height: 1;
  letter-spacing: -.025em;
  margin-top: var(--s1);
}
.deed__body {
  color: color-mix(in srgb, var(--ink) 84%, var(--paper));
  max-width: 44ch;
}

/* ────────────────────────────────────────────────────────  THE OFFER ── */

/* What the volunteer form asks, shown before anyone is sent to it. Same
   numbered-row vocabulary as the legend, because it is the same kind of list:
   a key, then a term and its gloss. */
/* One composed block, not a list with a button parked beside it. It was a
   two-column grid — the three kinds of help on the left, a sticky action
   column on the right — which read as a sidebar and gave the most important
   control on the site the least presence on it.

   Now the three kinds run across as a set, and the action closes them
   underneath on the full measure, behind the same 2px rule the expert tier
   uses to mark a new movement. The sticky behaviour went with the layout: it
   was inherited from the old inline form, which was tall enough to need it.
   Changed 2026-08-08. */
.offer {
  margin-top: var(--s12);
  display: grid;
  gap: var(--s12);
}

/* Three across only from 72rem. Below that the cells are narrower than the
   gloss needs — at 60rem each one gave the text about 200px and broke
   "Hands on a Saturday, equipment, materials..." over eight lines. Three
   items also do not fall into two columns without leaving an orphan, so it
   stacks until it can run across properly. */
.offer__list { list-style: none; padding: 0; display: grid; gap: var(--s6); }
@media (min-width: 72rem) {
  .offer__list { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--s8); }
}
.offer__row {
  display: grid;
  grid-template-columns: 2.5rem minmax(0, 1fr);
  gap: var(--s4);
  align-items: baseline;
  padding-top: var(--s4);
  border-top: 1px solid var(--rule);
}
.offer__key {
  font-variation-settings: 'wdth' 76, 'wght' 620;
  font-size: .8125rem; letter-spacing: .08em;
  color: var(--brand-green);
}
.offer__text { color: color-mix(in srgb, var(--ink) 82%, var(--paper)); }
.offer__text b {
  display: block;
  color: var(--ink);
  font-variation-settings: 'wdth' 84, 'wght' 680;
  font-size: 1.0625rem;
  margin-bottom: var(--s1);
}
.formnote { font-size: .8125rem; color: var(--ink-soft); max-width: 46ch; }

/* ─────────────────────────────────────────────────────────  THE CLOSE ── */

/* How a section ends on its ask: a 2px --ink rule across the content measure,
   then the action beneath it on the full width, with any small print beside
   the button rather than under it, so the note reads as a footnote to the
   action instead of as part of the control.

   One class, because there was one pattern and two copies of it. `.offer__do`
   (the volunteer close on page one) and `.ways__do` (the pledge close on page
   two) were byte-identical, and take-part's other three asks needed the same
   thing again — a third copy would have been the point at which the page had
   three ways of doing one job. Merged 2026-08-13.

   Ideas and needs previously ended on `.actions`, the hero's inline row, which
   is a pair of links under a headline and carries no weight of its own. That
   is what made those two asks easy to miss: a hairline ghost outline trailing
   off the end of a paragraph, with nothing marking where the reading stops and
   the doing starts. */
.act {
  display: grid;
  gap: var(--s6);
  padding-top: var(--s8);
  border-top: 2px solid var(--ink);
}
/* Where the close follows a section's opening block directly, it carries its
   own space. Inside `.offer` and `.ways` it does not: there it is a grid child
   and the parent's own gap already sets the distance. */
.intro + .act { margin-top: var(--s12); }

/* Full width only where a thumb is the pointer; auto from 34rem so it stops
   being a slab across a laptop. */
.act .btn { width: 100%; }
@media (min-width: 34rem) {
  .act .btn { width: auto; justify-self: start; }
}
@media (min-width: 72rem) {
  .act {
    grid-template-columns: auto minmax(0, 1fr);
    align-items: center;
    gap: var(--s12);
  }
  .act .btn {
    min-height: 56px;
    padding-inline: var(--s12);
    font-size: 1rem;
  }
}

/* ────────────────────────────────────────────────────────  THE STRIP ── */

/* A dated event. Not a competing call to action, so it is a band of type
   rather than a card with a button of its own weight. It removes itself once
   its date passes; see § DATED CONTENT in site.js. */
.strip {
  background: var(--plate-green);
  color: color-mix(in srgb, var(--paper) 92%, var(--plate-green));
  padding-block: var(--s6);
}
.strip__in { display: grid; gap: var(--s3); }
@media (min-width: 56rem) {
  .strip__in {
    grid-template-columns: auto minmax(0, 1fr) auto;
    align-items: center;
    gap: var(--s8);
  }
}
/* 64%, not 58%: 58 measured 4.38:1 on this ground and small uppercase needs
   4.5. 64 gives 4.96, which also matches the colophon eyebrow's 5.02, so both
   marginalia read at the same weight. See DESIGN.md § Accessibility. */
.strip .marginalia { display: block; color: color-mix(in srgb, var(--paper) 64%, var(--plate-green)); }
.strip__when b {
  font-variation-settings: 'wdth' 80, 'wght' 680;
  font-size: 1.0625rem;
  color: var(--paper);
  white-space: nowrap;
}
.strip__what { font-size: .9375rem; max-width: 56ch; }
/* Brand green is unreadable on this ground, so the button borrows paper. */
.strip .btn--ghost { border-color: color-mix(in srgb, var(--paper) 44%, transparent); color: var(--paper); }
.strip .btn--ghost:hover { border-color: var(--paper); background: color-mix(in srgb, var(--paper) 12%, transparent); }
.strip a:focus-visible { outline-color: var(--paper); }

/* ─────────────────────────────────────────────────────────  THE BAND ── */

/* The strip's grammar — eyebrow, line, action — in the pale register, used
   for the invitation at the end of the legend. Same three cells and the same
   breakpoint, so the page has one way of making a band rather than two.

   Ground is --veg-pale, the plate's own vegetation fill, so it reads as part
   of the sheet rather than as a new colour, and it avoids putting a third
   dark passage between the mission and the colophon. Measured on it:
   --ink 13.71, --ink-soft 4.72 (the eyebrow, so it needs no lightening the
   way the strip's did), --brand-green 4.67. All AA at normal size.
   See DESIGN.md § Accessibility. */
.band {
  background: var(--veg-pale);
  border-block: 1px solid color-mix(in srgb, var(--ink) 12%, transparent);
  padding-block: var(--s6);
}
.band__in { display: grid; gap: var(--s3); }
@media (min-width: 56rem) {
  .band__in {
    grid-template-columns: auto minmax(0, 1fr) auto;
    align-items: center;
    gap: var(--s8);
  }
}
.band__lead {
  font-variation-settings: 'wdth' 84, 'wght' 620;
  font-size: 1.0625rem;
  max-width: 64ch;
  text-wrap: balance;
}
.band__do a {
  color: var(--brand-green);
  font-variation-settings: 'wdth' 88, 'wght' 620;
  font-size: .9375rem;
  text-underline-offset: .25em;
  text-decoration-thickness: 1px;
}
.band__do a:hover { text-decoration-thickness: 2px; }

/* ────────────────────────────────────────────────────────────  VIDEO ── */

/* A facade. The poster and the play control are the whole component until
   someone clicks, at which point site.js swaps in the iframe. */
/* Full content measure. It was capped at 60rem, which left it 400px short of
   the sheet's own width and made the only photograph on the site the smallest
   considered element on it. */
.video { margin-top: var(--s8); }
.video__play,
.video__frame {
  display: block;
  width: 100%;
  aspect-ratio: 16 / 9;
  border: 1px solid var(--rule-strong);
  background: var(--plate-dark);
}
/* text-align, because this is a <button> and the UA centres its contents.
   That is what was putting the label in the middle of the frame. */
.video__play { position: relative; padding: 0; cursor: pointer; overflow: hidden; text-align: start; }
.video__frame { border-width: 1px; }
.video__poster { width: 100%; height: 100%; object-fit: cover; display: block; }

.video__icon {
  position: absolute; inset: 0; margin: auto;
  width: 4.5rem; height: 4.5rem;
  border-radius: 50%;
  background: var(--brand-green);
  transition: transform .2s var(--ease), background .2s var(--ease);
}
/* The triangle, struck from the circle rather than set in it. */
.video__icon::after {
  content: '';
  position: absolute; inset: 0; margin: auto;
  width: 0; height: 0;
  border-left: 1.25rem solid var(--paper);
  border-block: .75rem solid transparent;
  transform: translateX(.2rem);
}
.video__play:hover .video__icon { transform: scale(1.06); background: color-mix(in srgb, var(--brand-green) 84%, var(--ink)); }

.video__label {
  position: absolute; left: 0; right: 0; bottom: 0;
  padding: var(--s6) var(--s4) var(--s3);
  color: var(--paper);
  font-variation-settings: 'wdth' 76, 'wght' 600;
  font-size: .8125rem; letter-spacing: .1em; text-transform: uppercase;
  background: linear-gradient(to top, rgb(20 28 23 / .72), transparent);
}

/* ──────────────────────────────────────────────────  TAKE PART PAGE ── */

/* The second page's own opening. It carries an h1, so it needs the sheet's
   first-band spacing without the head plate's machinery. */
.section--lead { padding-block: var(--s16) var(--s12); }
.section--lead .head { max-width: 18ch; }

/* A page title, one step above a section head and well below the sheet
   title. The h1 here computed to exactly the same 44px/wdth 85/wght 650 as
   all five h2 under it, so the page had no title: its most important line
   was set at the size of the things subordinate to it. The sheet title is
   96px and belongs to the front page alone; this sits between them. */
.head--page {
  font-size: clamp(2.25rem, 4.6vw, 3.5rem);
  font-variation-settings: 'wdth' 82, 'wght' 680;
  letter-spacing: -.025em;
}

/* The run of grounds down this page. It used to be paper, sunk, paper, paper,
   paper: three consecutive papers spanning 28% of the document, which is the
   same structural fault the client reported as "too white" on the front page
   and which DESIGN.md § Light settles with "three would be a theme". It is
   now paper, sunk, sunk, veg-pale, paper, plate-dark. No new colour: both
   grounds are already in the palette, and `--veg-pale` is the ground the
   front page's `.band#ideas` uses for this same invitation and this same
   form, so the two finally rhyme.

   `--paper-sunk` under the pledge does a second job. `.deed` is #FBFBF6 and
   measures 1.09:1 on paper, so the one white document laid on map stock was
   a white rectangle on white. On sunk it measures 1.21:1, and the object
   reads as an object. */
.section--events,
.section--pledge { background: var(--paper-sunk); }
.section--ideas  { background: var(--veg-pale); }

/* Where the ground changes, the ground is the separator; a hairline on top of
   it is just a seam. These are the three boundaries where it changes.
   The doubled class is load-bearing: `.section + .section` is (0,2,0) and a
   single modifier is (0,1,0), so `.section--events { border-top: 0 }` never
   applied and a seam shipped under the lead for the whole life of the page.
   Events to pledge keeps its rule, because that boundary is sunk to sunk. */
.section.section--events,
.section.section--ideas,
.section.section--needs { border-top: 0; }

.events { list-style: none; padding: 0; margin-top: var(--s12); display: grid; gap: var(--s8); }
.event {
  display: grid; gap: var(--s4);
  padding-top: var(--s6);
  border-top: 1px solid var(--rule-strong);
}
@media (min-width: 44rem) {
  .event { grid-template-columns: 7rem minmax(0, 1fr); gap: var(--s8); }
}

/* The date reads as a stamp, which is what makes the list scannable once
   there is more than one entry in it. */
.event__date { display: grid; gap: var(--s1); align-content: start; }
.event__day {
  font-variation-settings: 'wdth' 76, 'wght' 700;
  font-size: 1.5rem; line-height: 1; letter-spacing: -.02em;
  color: var(--brand-green);
}
.event__month {
  font-variation-settings: 'wdth' 76, 'wght' 600;
  font-size: .75rem; letter-spacing: .1em; text-transform: uppercase;
  color: var(--ink-soft);
}

.event__body { display: grid; gap: var(--s2); }
.event__head {
  font-variation-settings: 'wdth' 84, 'wght' 680;
  font-size: 1.25rem; letter-spacing: -.01em;
}
.event__note { color: color-mix(in srgb, var(--ink) 82%, var(--paper)); max-width: 56ch; }
.event__do { margin-top: var(--s2); }

/* The empty state, which is where this section spends most of its life. Every
   entry removes itself the day after its date, so the page's lead section is
   a heading and a promise of "the quickest way in" with nothing under it for
   most of the year. It now carries the standing route instead, which
   reinforces the front page's one ask rather than competing with it.
   `:has()` hides it whenever there is still something dated to show. */
.events__empty {
  margin-top: var(--s8);
  display: grid; gap: var(--s3); justify-items: start;
}
.events__empty .btn { margin-top: var(--s1); }
/* Hidden while there is still something dated to show, and the empty list's
   own top margin collapses with it so the section does not leave a hole. */
.events:has(.event) + .events__empty { display: none; }
.events:not(:has(.event)) { margin-top: 0; }

/* The pledge: the deed, then the action closing underneath it on the full
   measure. Same shape as the volunteer block, which is what this comment
   always claimed and what stopped being true when that block was rebuilt.

   It was deed left, action parked in a right column, and it failed the way
   `.offer` failed: `.deed` stops at its 34rem cap while the column keeps
   growing, so at 1920 there was a 240px empty gutter, a 208px button, and
   183px of dead column under it. The fault got worse the wider the window.
   The deed keeps its measure on purpose. It is a document lying on map
   stock, not a panel that should fill the sheet, and now that the ground is
   sunk the space around it reads as the stock. Rebuilt 2026-08-08. */
.ways { margin-top: var(--s12); display: grid; gap: var(--s12); }

/* ──────────────────────────────────────────────────────────  COLOPHON ── */

.colophon {
  background: var(--plate-dark);
  color: color-mix(in srgb, var(--paper) 78%, var(--plate-dark));
  padding-block: var(--s12) var(--s8);
}
.colophon .marginalia { color: color-mix(in srgb, var(--paper) 52%, var(--plate-dark)); }
.colophon a { color: var(--paper); text-decoration-color: color-mix(in srgb, var(--paper) 40%, transparent); text-underline-offset: 3px; }
.colophon a:hover { text-decoration-color: var(--leaf); }

/* Three tiers, not four columns in a row. The four things the footer carries
   are different in kind — who this is, how to reach them, a door for someone
   who needs help, and the licence small print — and as four near-equal cells
   (312 / 265 / 328 / 359 at 1920) they had no relationship to each other.
   The colophon was designed for three and a fourth was added without
   redesigning it. Rebuilt 2026-08-08. */
.colophon__stack { display: grid; gap: var(--s8); }

.colophon__top { display: grid; gap: var(--s8); }
@media (min-width: 48rem) {
  .colophon__top {
    grid-template-columns: auto minmax(0, 1fr);
    gap: var(--s8) var(--s16);
    align-items: start;
  }
  .colophon__door { grid-column: 1 / -1; }
}
@media (min-width: 72rem) {
  .colophon__top { grid-template-columns: auto minmax(0, 1fr) minmax(20rem, 26rem); }
  .colophon__door { grid-column: 3; }
}

/* A lockup, not a stack. The seal sat above "Covering" and a vertical list
   of the five suburbs, which made a 112px-wide cell 279px tall and forced the
   whole top row to that height when nothing else in it exceeded 156. Beside
   it, in two columns, the same content is 88px tall and the row is set by its
   real content instead. */
.colophon__id { display: flex; align-items: center; gap: var(--s4); }
.colophon__cover { display: grid; gap: var(--s2); }
.colophon__contact { display: grid; gap: var(--s3); align-content: start; }

/* Bordered, so it reads as a door and not as another paragraph of terms.
   Border only, no lifted ground: a 4% paper wash put the eyebrow at 4.59
   against 5.05 on the plate itself, and 5.05 is what the strip's eyebrow
   was pinned to. See DESIGN.md § Accessibility. */
.colophon__door {
  display: grid; gap: var(--s3); align-content: start;
  padding: var(--s6);
  border: 1px solid color-mix(in srgb, var(--paper) 24%, transparent);
}
.colophon__door .colophon__small { max-width: none; }

/* Small print, below a rule, which is where small print belongs. */
.colophon__fine {
  display: grid; gap: var(--s3);
  padding-top: var(--s6);
  border-top: 1px solid color-mix(in srgb, var(--paper) 16%, transparent);
}
/* Sources and privacy are two statements, not one. Side by side they are two
   short blocks instead of one five-line slab at full measure. */
.colophon__fine-cols { display: grid; gap: var(--s3); }
@media (min-width: 56rem) {
  .colophon__fine-cols { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--s12); }
}
/* 62ch, not 78: `ch` measures the zero glyph's advance, which is wider than
   this face's average character, so 78ch rendered about 98 characters per
   line against a 65-75 comfort band. Measured, not guessed. */
.colophon__fine .colophon__small { max-width: 62ch; }

/* The seal is dark ink; on the dark plate it needs its paper back. */
.colophon__stamp {
  display: grid; place-items: center;
  width: 88px; height: 88px;
  border-radius: 50%;
  background: var(--paper);
  flex: none;
}
.colophon__stamp img { width: 64px; height: 64px; }

.colophon__areas {
  list-style: none; padding: 0;
  /* Two columns rather than a wrapping row: these names are uneven in length
     and a flex row breaks them into four ragged lines. A grid keeps them in
     three tidy ones. It reads down the columns rather than across the rows,
     which does not matter here because this is a `ul` and the five suburbs
     have no order. */
  display: grid; grid-template-columns: repeat(2, auto); gap: 0 var(--s4);
  justify-content: start;
  font-variation-settings: 'wdth' 76, 'wght' 600;
  font-size: .8125rem; letter-spacing: .1em; text-transform: uppercase;
  line-height: 1.5;
  color: var(--paper);
}
.colophon__big {
  font-variation-settings: 'wdth' 82, 'wght' 620;
  font-size: 1.5rem; letter-spacing: -.01em;
  font-variant-numeric: tabular-nums;
}
.colophon__big a {
  text-decoration: none;
  display: inline-flex; align-items: center; min-height: 44px;
}
.colophon__big a:hover { color: var(--leaf); }
.colophon__small { font-size: .8125rem; line-height: 1.7; max-width: 40ch; }

/* ────────────────────────────────────────────────────────────  MOTION ── */

/* The page's authored moments went with the flat plate and the model series
   (client, 2026-08-06). What is left is the head model's fade-in and the
   legend rows' hover, both plain transitions declared where they belong.
   The damper below still governs everything. */

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
  }
}

@media print {
  .rail, .actions, .skip, .video, .act { display: none; }
  body { background: #fff; }
  .section { padding-block: var(--s8); break-inside: avoid; }
  /* The deed is the one thing here worth the ink, so let it print whole. */
  .deed { break-inside: avoid; }
}
