/* Celebrate Diversity — Component CSS v2.0
 * Requires cd-tokens.css to be loaded first.
 *
 * Naming: .cd-<component>[--<variant>]  /  .cd-<component>__<part>
 * Every class here maps 1:1 to a component documented in 03-components.md
 * and to a WordPress block/pattern listed in 04-wordpress-components.md.
 */

/* ══════════════════════════════════════════════════════════════
 * TYPE ROLES — apply these classes, never raw font sizes
 * ══════════════════════════════════════════════════════════════ */

.cd-hero-title {
  /* [v2.0.2] At 56px on a 334px mobile column a single long word ("gathering")
   * pushed the heading 21px past the viewport on the events page. Display type
   * this large will always meet a word it cannot fit; breaking it is better than
   * a horizontal scrollbar. */
  overflow-wrap: break-word;
  font-family: var(--cd-font-display);
  font-size: var(--cd-hero-size);
  line-height: var(--cd-hero-lh);
  letter-spacing: var(--cd-hero-track);
  text-transform: uppercase;
  max-width: var(--cd-measure-hero);
  margin: 0;
}
.cd-hero-title em {              /* the ONE permitted serif accent */
  font-family: var(--cd-font-accent);
  font-style: italic;
  font-weight: 400;
  text-transform: none;
  letter-spacing: -0.01em;
  /* [v2.0.8] Soft lilac rather than full lilac. The accent is part of the
   * headline, not an interjection into it: at 13.48:1 on navy it sits close to
   * the 16.84:1 of the words either side, so the change of face and case does
   * the work and the colour supports it. Full lilac read at 9.59:1, far enough
   * from the rest of the line to look like a different element. */
  color: var(--cd-lilac-soft);
}

.cd-section-title {
  font-family: var(--cd-font-display);
  font-size: var(--cd-section-size);
  line-height: var(--cd-section-lh);
  letter-spacing: var(--cd-section-track);
  text-transform: uppercase;
  max-width: var(--cd-measure-section);
  margin: 0;
}

/* [v2.0.2] cd_render_section_header() has always emitted this wrapper, but no
 * rule for it existed anywhere in the stylesheet, so it computed to display:block
 * and the action link fell directly beneath the heading with a measured gap of
 * 0px. Across the built site that turned 21 links on 11 pages into what read as
 * subtitles: "ALL PROGRAMS" under "Our programs", "ASK ABOUT THIS PROGRAM" under
 * every one of the fifteen program headings.
 *
 * Baseline alignment rather than centre: the link is metadata sitting on the
 * heading's own line, not a control floated beside a box.
 * See defect log DL-005. */
.cd-section-header {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--cd-space-6);
  margin-block-end: var(--cd-space-6);
}
.cd-section-header .cd-section-title { flex: 0 1 auto; }
.cd-section-header .cd-btn--text     { flex: 0 0 auto; white-space: nowrap; }
/* [v2.1] The category mark sits opposite its title. `margin-left: auto` rather
 * than relying on space-between, because the header may also carry an action
 * link and the mark must stay hard right of both. Aligned to the title's cap
 * height rather than its baseline: the header is a baseline flex row, and a
 * square mark hung on a baseline sits visibly low. */
.cd-section-header__icon {
  flex: 0 0 auto;
  margin-left: auto;
  align-self: center;
  /* [v2.1.1] Doubled from 56px. At the smaller size the category mark read as a
   * stray glyph beside a 54px section title rather than as the title's
   * counterweight; the header is a wide row and the right-hand end needs enough
   * mass to answer the left. Held at 0.5 so growing it adds presence without
   * competing with the heading it sits opposite. */
  width: 112px;
  height: 112px;
  -webkit-mask: var(--cd-icon) center / contain no-repeat;
          mask: var(--cd-icon) center / contain no-repeat;
  /* [v2.1.1] Full strength, not 0.5. .cd-section-title sets no colour of its
   * own and inherits the band's ink, and this mark is painted from
   * currentColor off that same inherited value, so at opacity 1 the two are
   * exactly the same colour by construction rather than by a matched literal.
   * The 0.5 was holding it back to a grey that belonged to nothing. */
  background-color: currentColor;
}
@media (max-width: 700px) { .cd-section-header__icon { width: 80px; height: 80px; } }

@media (max-width: 700px) {
  /* Side by side would leave the title two or three words per line. */
  .cd-section-header {
    flex-direction: column;
    align-items: flex-start;
    gap: var(--cd-space-3);
  }
}

.cd-feature-title {
  font-family: var(--cd-font-display);
  font-size: var(--cd-feature-size);
  line-height: var(--cd-feature-lh);
  letter-spacing: var(--cd-feature-track);
  text-transform: uppercase;
  color: var(--cd-blue);          /* feature headlines are blue ON WHITE ONLY */
  max-width: 18ch;
  margin: 0;
}

.cd-card-title {
  font-family: var(--cd-font-display);
  font-size: var(--cd-card-size);
  line-height: var(--cd-card-lh);
  letter-spacing: var(--cd-card-track);
  text-transform: uppercase;
  margin: 0;
}

/* [v2.1.3] STATEMENT - the organisation's own voice, given weight.
 *
 * Two weights of the same idea, and neither is a quotation.
 *
 * The panel borrows the testimony band's green and its display-caps type, but
 * it is a panel inside a section rather than a full-bleed band, and it carries
 * no attribution line. Rule 5 keeps it flat: nothing on green casts a shadow.
 *
 * The serif variant puts Instrument Serif to work outside a hero headline for
 * the first time. The design language originally read "the one permitted italic
 * accent, inside a hero headline only"; the giving band widened that to a
 * display lead-in (DL-044) and this widens it again to a standalone line. The
 * rule that still holds, and the one worth keeping, is that the serif never
 * sets body copy: it marks a sentence out as different from the paragraphs
 * around it. See defect log DL-051. */
.cd-statement { text-wrap: pretty; }
/* The selector is deliberately heavier than it looks. Global styles emit
 *   :root :where(p) { max-width: var(--cd-measure-body); }
 * which caps every paragraph on the site at 556px. `:where()` contributes no
 * specificity, so that rule and a bare `.cd-statement--panel` tie at 0,1,0 and
 * the inline global block wins on order. The panel was rendering 556 x 712,
 * a column of twelve lines cramped into the left third of a 1328px band, while
 * its own stylesheet said 762px, a column of twelve lines cramped into the
 * left third of a 1328px band.
 *
 * Fixed at source instead: that cap now targets .wp-block-paragraph rather
 * than every <p>, and sits in :where() so it carries no specificity. This
 * component therefore needs no scoping trick to state its own width. */
.cd-statement--panel {
  background: var(--cd-green);
  color: var(--cd-on-green);
  --cd-cut: var(--cd-cut-lg);
  padding: var(--cd-space-8) var(--cd-space-9);
  /* [v2.3.1] Symmetric. The bottom margin was 0 on the reasoning that the panel
   * ended a section; on About a Contact us button follows it and sat hard
   * against the panel's lower edge, reading as a collision rather than a pair.
   * A block that can be followed by anything needs space on both sides. */
  margin-block: var(--cd-space-8);
  /* Fills the band. A statement of this length has no business being set in a
   * narrow column: the measure that suits 22 capitals of giving-band statement
   * suits nothing about 150 characters. */
  max-width: none;
  /* The mark sits beside the text, not above it. */
  display: flex;
  align-items: center;
  gap: var(--cd-space-8);
  font-family: var(--cd-font-display);
  /* A step down from --cd-quote-size. A pull quote is short and can afford
   * 42px; at that size this ran to twelve lines. */
  font-size: 36px;
  --cd-statement-lh: 1.12;
  line-height: var(--cd-statement-lh);
  letter-spacing: var(--cd-quote-track);
  text-transform: uppercase;
  /* A green object on a light ground floats, exactly as .cd-card--green does.
   * Rule 5 governs what sits ON a coloured band, not a coloured object on a
   * light one; the suppression below covers the case where this panel is
   * placed on a coloured band. */
  box-shadow: var(--cd-shadow);
}
.cd-statement__text {
  margin: 0;
  flex: 1 1 auto;
  min-width: 0;
  text-wrap: balance;
}
/* Four lines tall, expressed as four lines rather than as a pixel value: em
 * resolves against the panel's own font-size, so the mark tracks the type down
 * to the 26px mobile step without a second number to keep in sync. */
.cd-statement__mark {
  flex: 0 0 auto;
  width:  calc(4 * var(--cd-statement-lh) * 1em);
  height: calc(4 * var(--cd-statement-lh) * 1em);
  -webkit-mask: var(--cd-icon) center / contain no-repeat;
          mask: var(--cd-icon) center / contain no-repeat;
  background-color: currentColor;
}
@media (max-width: 700px) {
  /* Stacked, and the mark stops competing with the sentence. */
  .cd-statement--panel { flex-direction: column; align-items: flex-start; gap: var(--cd-space-6); }
  .cd-statement__mark  { width: calc(3 * var(--cd-statement-lh) * 1em); height: calc(3 * var(--cd-statement-lh) * 1em); }
}

.cd-statement--serif {
  font-family: var(--cd-font-accent);
  font-style: italic;
  font-size: var(--cd-serif-size);
  line-height: var(--cd-serif-lh);
  max-width: none;
  margin-block: var(--cd-space-8);
  color: var(--cd-ink);
}
@media (max-width: 700px) {
  .cd-statement--panel { padding: var(--cd-space-6); font-size: 26px; }
  .cd-statement--serif { font-size: 23px; }
}

.cd-quote {
  font-family: var(--cd-font-display);
  font-size: var(--cd-quote-size);
  line-height: var(--cd-quote-lh);
  letter-spacing: var(--cd-quote-track);
  text-transform: uppercase;
  text-wrap: pretty;
  margin: 0;
}

.cd-figure {
  font-family: var(--cd-font-display);
  font-size: var(--cd-figure-size);
  line-height: var(--cd-figure-lh);
  letter-spacing: var(--cd-figure-track);
}

.cd-standfirst {
  font-family: var(--cd-font-body);
  font-size: var(--cd-standfirst-size);
  line-height: var(--cd-standfirst-lh);
  max-width: var(--cd-measure-standfirst);
  text-wrap: pretty;
  margin: 0;
}

/* [v2.1.4] The measure defaults for CORE blocks, and only for core blocks.
 *
 * These three lived in theme.json as styles.blocks."core/paragraph".css and so
 * on. WordPress compiles that to `:root :where(p) { ... }`, `:root :where(h1,
 * h2, ...)`, using the element as the block's selector. The intent was "a core
 * paragraph should not run 145 characters"; the effect was a 556px cap on
 * EVERY paragraph element on the site, our own components included, applied
 * from an inline stylesheet that no component author would think to look at.
 *
 * The statement panel is what exposed it: its own rule said 762px, it rendered
 * 556, and even qualifying the selector as `p.cd-statement--panel` at 0,1,1 did
 * not reliably beat the 0,1,0 global rule. See defect log DL-052.
 *
 * Two changes make it behave:
 *
 *   1. Target the block CLASS, not the element. A core paragraph carries
 *      .wp-block-paragraph; a component that happens to render a <p> does not,
 *      so it is no longer caught by a rule about something else.
 *   2. Wrap in :where(), giving it zero specificity. It is a default, and a
 *      default should lose to anything that states an opinion. Any component
 *      rule now wins with no scoping tricks and no !important.
 *
 * Heading stays at 22ch, unchanged, to preserve the existing behaviour rather
 * than quietly retune it - though see DL-004 on why ch is a poor unit here. */
/* [v2.1.6] Copy sitting directly on a band fills the band.
 *
 * The default was --cd-measure-body, 556px, the 66-character reading measure.
 * Beside a full-bleed object it read as stranded: the panel spanned 1328px and
 * the sentences about it sat in the left 42% with no edge in common. Text on
 * the background is part of the band's composition and should share its edges.
 *
 * Text INSIDE an object is unaffected. Cards, panels, program blocks and
 * listing rows all set their own measures against their own box. That is the
 * distinction that matters: a container decides for its own contents, and only
 * text with no container of its own defers to the band.
 *
 * The cost, stated plainly because it is real: at 1328px this is roughly 158
 * characters a line against the 66 that long-form reading wants. Any section
 * carrying more than a few sentences should opt back down with measure="body".
 * See defect log DL-054. */
:where(.wp-block-paragraph, .wp-block-list) { max-width: none; }

/* [v2.1.5] The wide opt-in. One class on the section, and everything inside it
 * that carries a measure follows: core paragraphs and lists, and the serif
 * statement. It beats the :where() defaults above on specificity alone, which
 * is exactly what those defaults were de-specified for. */
/* Two ways back down from the band width, per section. `wide` is a middle
 * setting for a short passage that should not run the full band; `body` is the
 * 66-character reading measure, for anything read as prose rather than
 * scanned. */
.cd-section--measure-wide :is(.wp-block-paragraph, .wp-block-list, .cd-statement--serif) {
  max-width: var(--cd-measure-wide);
}
.cd-section--measure-body :is(.wp-block-paragraph, .wp-block-list, .cd-statement--serif) {
  max-width: var(--cd-measure-body);
}
:where(.wp-block-heading) { max-width: 22ch; }

.cd-body { font-size: var(--cd-body-size); line-height: var(--cd-body-lh); max-width: var(--cd-measure-body); }
.cd-body-small { font-size: var(--cd-small-size); line-height: var(--cd-small-lh); max-width: 60ch; }

.cd-meta {
  font-family: var(--cd-font-mono);
  font-size: var(--cd-meta-size);
  letter-spacing: var(--cd-meta-track);
  text-transform: uppercase;
  color: var(--cd-ink-meta);
}

/* ══════════════════════════════════════════════════════════════
 * THE CUT — one shape, applied by rule, sized by --cd-cut
 * ══════════════════════════════════════════════════════════════ */

/* [v2.0.3] Two rules, one per direction, and every object that carries a cut
 * matches one of them. The SIZE comes from --cd-cut on the element itself, so a
 * component changes its cut by setting one property and nothing else.
 *
 * These are rules rather than custom-property recipes on purpose. A recipe
 * declared on :root resolves its var(--cd-cut) against :root and inherits the
 * resolved string, so a component override cannot reach it. See the note in
 * cd-tokens.css. Declared here, var(--cd-cut) resolves against the matched
 * element, which is what makes the knob work.
 *
 * Every value is a single length, so every arc is a quarter-circle. No
 * percentages: those resolve per axis and can only make ellipses on a box that
 * is not square. See defect log DL-033. */
.cd-cut-tr,
.cd-card,
.cd-figure-img--tr,
.cd-mark-panel--tr,
.cd-statement--panel,
.cd-event-thumb,
/* [v2.0.7] .cd-post-thumb had set --cd-cut: var(--cd-cut-md) since v2.0.5 but
 * was never named in this list, so the knob was turned and nothing read it: the
 * post thumbnails painted square while the event thumbnails beside them, same
 * skeleton and same intent, carried the cut. Setting --cd-cut without matching
 * a rule here is the second half of the DL-033 trap, and it fails silently in
 * the opposite direction: there, a rule with no knob; here, a knob with no
 * rule. Found on the kitchen sink. See defect log DL-039. */
.cd-post-thumb,
.cd-partner,
.wp-block-post .wp-block-post-featured-image {
  border-radius: var(--cd-radius-base) var(--cd-cut)
                 var(--cd-radius-base) var(--cd-radius-base);
}

.cd-cut-br,
.cd-card--br,
.cd-mark-panel--br,
.cd-figure-img--br {
  border-radius: var(--cd-radius-base) var(--cd-radius-base)
                 var(--cd-cut) var(--cd-radius-base);
}

/* Right-anchored full-bleed panels only. */
.cd-cut-panel,
.cd-hero__media {
  border-radius: var(--cd-cut) 0 0 0;
}

/* ══════════════════════════════════════════════════════════════
 * SECTION — the outer wrapper for every band of the page
 * Ground colour is set by a --<name> variant class.
 * ══════════════════════════════════════════════════════════════ */

.cd-section { padding: var(--cd-section-y) var(--cd-gutter); }
.cd-section__inner { max-width: var(--cd-max-width); margin: 0 auto; }

/* [v2.4] Split section: copy beside a supporting image.
 *
 * The header spans both columns and the two children below it fall into place
 * in source order, so nothing depends on a block knowing where it sits. The
 * image column is fixed rather than fractional: a supporting figure should not
 * grow with the viewport while the copy it supports stays at its measure. */
.cd-section--split .cd-section__inner {
  display: grid;
  /* [v2.4.1] 630px on a desktop, half again on the client's read of the first
   * pass, but as a ceiling rather than a fixed track.
   *
   * A flat 630px is only correct at the widest step. Between the 900px stack
   * and about 1300px it eats the copy: measured at a 1000px viewport the inner
   * band is 888px, so a fixed 630 plus the 80px gap would have left the text
   * 178px, about 21 characters a line. The clamp holds 630 wherever there is
   * room for it and gives ground below that, so the two columns fail gracefully
   * instead of at one breakpoint. */
  grid-template-columns: minmax(0, 1fr) clamp(300px, 48%, 630px);
  gap: var(--cd-space-8) var(--cd-space-10);
  align-items: start;
}
.cd-section--split .cd-section-header {
  grid-column: 1 / -1;
  /* The header carries its own margin-block-end for flow layout. In a grid the
   * row gap already separates it from the row below, so the two stacked and
   * left about 125px under the title. The grid owns the spacing here. */
  margin-block-end: 0;
}
@media (max-width: 900px) {
  .cd-section--split .cd-section__inner { grid-template-columns: 1fr; }
}

/* [v2.0.2] Bands butt against each other: the colour change IS the join, so any
 * gap between two grounds reads as a rendering fault rather than a rhythm.
 *
 * WordPress applies --wp--style--block-gap as margin-block-start to every
 * top-level child of the content wrapper, which put an 8px white line between
 * all 59 adjacent band pairs on the built site. Marking the band blocks
 * alignfull removes it, but align is an editor-facing attribute and can be
 * cleared, so the guarantee belongs here rather than in block.json alone.
 * See defect log DL-002. */
:is(.cd-hero, .cd-stats, .cd-section, .cd-give, .cd-dispatch)
  + :is(.cd-hero, .cd-stats, .cd-section, .cd-give, .cd-dispatch) {
  margin-block-start: 0;
}

/* [v2.0.2] Bands whose content is not wrapped in .cd-section__inner cap their
 * own content instead. Padding of max(gutter, half the overflow) holds the
 * standard gutter up to 1552px and centres a --cd-max-width column beyond it,
 * so a 1920px screen does not stretch a hero headline across 1808px.
 * Needed once the bands genuinely bleed. See defect log DL-001. */
.cd-hero, .cd-stats, .cd-give {
  padding-inline: max(var(--cd-gutter), (100% - var(--cd-max-width)) / 2);
}

.cd-section--navy       { background: var(--cd-navy);       color: var(--cd-on-navy); }
.cd-section--blue       { background: var(--cd-blue);       color: var(--cd-on-blue); }
.cd-section--green      { background: var(--cd-green);      color: var(--cd-on-green); }
.cd-section--mint       { background: var(--cd-mint);       color: var(--cd-on-mint); }
.cd-section--lilac      { background: var(--cd-lilac);      color: var(--cd-on-lilac); }
.cd-section--white      { background: var(--cd-white);      color: var(--cd-ink); }
.cd-section--chalk      { background: var(--cd-chalk);      color: var(--cd-ink); }
.cd-section--navy-ghost { background: var(--cd-navy-ghost); color: var(--cd-ink); }
.cd-section--blue-ghost { background: var(--cd-blue-ghost); color: var(--cd-ink); }
.cd-section--blue-soft  { background: var(--cd-blue-soft);  color: var(--cd-ink); }
.cd-section--teal-ghost   { background: var(--cd-teal-ghost);   color: var(--cd-ink); }
.cd-section--moss-ghost   { background: var(--cd-moss-ghost);   color: var(--cd-ink); }
.cd-section--plum-ghost   { background: var(--cd-plum-ghost);   color: var(--cd-ink); }
.cd-section--maroon-ghost { background: var(--cd-maroon-ghost); color: var(--cd-ink); }
.cd-section--brown-ghost  { background: var(--cd-brown-ghost);  color: var(--cd-ink); }
.cd-section--gold-ghost   { background: var(--cd-gold-ghost);   color: var(--cd-ink); }
.cd-section--mint-soft  { background: var(--cd-mint-soft);  color: var(--cd-on-mint); }
.cd-section--lilac-ghost{ background: var(--cd-lilac-ghost);color: var(--cd-ink); }
.cd-section--grey-ghost { background: var(--cd-grey-ghost); color: var(--cd-ink); }

/* ══════════════════════════════════════════════════════════════
 * PILL — the eyebrow / category / label. Always filled, never outlined.
 * ══════════════════════════════════════════════════════════════ */

.cd-pill {
  display: inline-flex;
  align-items: center;
  gap: var(--cd-space-2);
  align-self: flex-start;
  width: fit-content;
  white-space: nowrap;
  padding: 7px 16px;
  border-radius: var(--cd-radius-pill);
  font-family: var(--cd-font-display);
  font-size: var(--cd-pill-size);
  letter-spacing: var(--cd-pill-track);
  text-transform: uppercase;
}
.cd-pill--blue-soft { background: var(--cd-blue-soft);  color: var(--cd-navy); }
.cd-pill--lilac     { background: var(--cd-lilac);      color: var(--cd-navy); }
.cd-pill--mint      { background: var(--cd-mint);       color: var(--cd-on-mint); }
.cd-pill--navy      { background: var(--cd-navy);       color: var(--cd-on-navy); }
.cd-pill--deep-blue { background: #04173A;              color: #8CCBEA; }

/* [v2.0.7] The soft tints are described in cd-tokens.css as the step for
 * "objects sitting ON a light ground (cards, pills, stripes)", but only one of
 * the six had ever been given a pill. The other five complete the family, so a
 * pill can be tuned to the band it sits in rather than reaching for a full
 * colour and shouting.
 *
 * All six take navy ink. Measured: 13.15:1 (navy-soft) to 14.71:1 (mint-soft),
 * so every one clears AAA and no per-tone text colour is needed. */
.cd-pill--navy-soft  { background: var(--cd-navy-soft);  color: var(--cd-navy); }
.cd-pill--green-soft { background: var(--cd-green-soft); color: var(--cd-navy); }
.cd-pill--mint-soft  { background: var(--cd-mint-soft);  color: var(--cd-navy); }
.cd-pill--lilac-soft { background: var(--cd-lilac-soft); color: var(--cd-navy); }
.cd-pill--grey-soft  { background: var(--cd-grey-soft);  color: var(--cd-navy); }

/* Extra 6 soft tints, for the pill on each theme card. Navy ink throughout,
 * 13.6:1 to 14.1:1. */
.cd-pill--teal-soft   { background: var(--cd-teal-soft);   color: var(--cd-navy); }
.cd-pill--moss-soft   { background: var(--cd-moss-soft);   color: var(--cd-navy); }
.cd-pill--plum-soft   { background: var(--cd-plum-soft);   color: var(--cd-navy); }
.cd-pill--maroon-soft { background: var(--cd-maroon-soft); color: var(--cd-navy); }
.cd-pill--brown-soft  { background: var(--cd-brown-soft);  color: var(--cd-navy); }
.cd-pill--gold-soft   { background: var(--cd-gold-soft);   color: var(--cd-navy); }

/* ══════════════════════════════════════════════════════════════
 * BUTTONS
 * --give   = Ember. Donate/Give ONLY. One per screen.
 * --primary= Navy. Every other primary action.
 * --ghost  = Outlined. Secondary action beside a primary.
 * --text   = Inline link-button inside cards.
 * ══════════════════════════════════════════════════════════════ */

.cd-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--cd-space-3);
  min-height: var(--cd-tap-min);
  padding: 17px 30px;
  border: 0;
  border-radius: var(--cd-radius-pill);
  font-family: var(--cd-font-display);
  font-size: 15px;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  text-decoration: none;
  cursor: pointer;
  /* colour and border-color join the transition here: the ghost button now
   * changes both on hover, and without them it snapped while the lift eased. */
  transition: transform var(--cd-dur) var(--cd-ease),
              background-color var(--cd-dur) var(--cd-ease),
              border-color var(--cd-dur) var(--cd-ease),
              box-shadow var(--cd-dur) var(--cd-ease),
              color var(--cd-dur) var(--cd-ease),
              filter var(--cd-dur) var(--cd-ease);
}
.cd-btn:hover  { filter: brightness(0.94); transform: translateY(-1px); text-decoration: none; }
.cd-btn:active { transform: translateY(0); }

.cd-btn--give    { background: var(--cd-ember); color: var(--cd-on-ember); }
.cd-btn--primary { background: var(--cd-navy);  color: var(--cd-on-navy); }
.cd-btn--ghost   { background: transparent; color: currentColor; border: 0; box-shadow: inset 0 0 0 3px #5C6B8C; }

/* [v2.8] The ghost hover fills the pill instead of washing it.
 *
 * It was rgba(255,255,255,0.08), which measured 1.20:1 to 1.24:1 against the
 * six dark hero grounds and 1.06:1 against gold - a state change the eye cannot
 * find. On the darker grounds it also read as grime rather than light, because
 * a white wash over a saturated colour desaturates it.
 *
 * Filling opaque is what makes one rule work on seven different hero grounds:
 * the ground stops participating, so the ink can be navy every time instead of
 * inheriting whatever the band happened to set. Border matched to the fill, so
 * the outline resolves into a solid pill rather than leaving a rim of the old
 * grey around the new colour. See defect log DL-069. */
/* [DL-080] Primary and ghost share one hover: blue-soft fill, navy label.
 *
 * The ghost has behaved this way since DL-069; the primary was still doing
 * nothing but a 6% dim of its navy fill, which is the same non-event the ghost's
 * old white wash was. The two secondary-and-main actions now answer the pointer
 * the same way, and it matches what the primary already did on dark grounds.
 *
 * Navy on blue-soft is 13.63:1.
 *
 * The ring is navy here, not blue-soft, because on the light grounds these sit
 * on the fill has no edge of its own: blue-soft measures 1.35:1 against white,
 * 1.23:1 against grey-ghost and 1.15:1 against chalk, well under the 3:1 WCAG
 * 1.4.11 asks between a control and its surroundings. Navy gives 15.72:1 to
 * 18.38:1 on all of them. 3px, the system's one ring weight. */
.cd-btn--primary:hover,
.cd-btn--primary:focus-visible,
.cd-btn--ghost:hover,
.cd-btn--ghost:focus-visible {
  background: var(--cd-btn-ghost-hover);
  color: var(--cd-navy);
  box-shadow: inset 0 0 0 3px var(--cd-navy);
  filter: none;
}

/* On a dark ground the same hover has to be restated, not just adjusted.
 *
 * The primary's resting rule there - `.cd-hero .cd-btn--primary`, which fills it
 * white - is (0,2,0), and the shared hover above is (0,1,1). The resting rule
 * wins, so on every hero and navy band the primary would simply not respond to
 * the pointer. The deleted rule this replaces was not redundant after all: it
 * was carrying the specificity, which is the only thing it was doing and the
 * reason it looked redundant.
 *
 * The ring matches the fill here rather than going navy, because blue-soft on
 * navy is 13.63:1 and separates by itself - a navy ring would only draw a line
 * inside it. Same discipline as the giving button: a ring only where the fill
 * cannot carry its own edge. */
:is(.cd-hero, .cd-section--navy, .cd-section--green, .cd-section--mint,
    .cd-section--blue, .cd-section--lilac)
  :is(.cd-btn--primary, .cd-btn--ghost):is(:hover, :focus-visible) {
  background: var(--cd-btn-ghost-hover);
  color: var(--cd-navy);
  box-shadow: inset 0 0 0 3px var(--cd-btn-ghost-hover);
  filter: none;
}

/* Gold is the one light hero ground in the system, and no light fill separates
 * from it: blue-soft manages 1.49:1 there, mint 1.26:1, against 6:1 or better on
 * the other six. The fill has to move away from the ground, and on a light
 * ground that means darker, not lighter.
 *
 * So the Children's hero inverts - navy fill, light ink, 12.9:1 - which is the
 * same exception --cd-on-gold already makes for text. One ground, one rule, and
 * the hover reads as the same gesture on all seven. */
.cd-hero--gold .cd-btn--ghost:hover,
.cd-hero--gold .cd-btn--ghost:focus-visible {
  background: var(--cd-navy);
  box-shadow: inset 0 0 0 3px var(--cd-navy);
  color: var(--cd-on-navy);
}

/* [DL-076] The giving button inverts on hover: the whole pill goes navy, with
 * the ember label. Ember on navy is 6.52:1, the same figure as the resting
 * state, because contrast is symmetric - the inversion costs nothing.
 *
 * brightness() is dropped: dimming an inverted button only muddies it. */
.cd-btn--give:hover,
.cd-btn--give:focus-visible {
  background: var(--cd-navy);
  color: var(--cd-ember);
  filter: none;
}

/* [DL-077] ...except on a dark ground, where a navy fill IS the ground.
 *
 * Measured, navy on the navy hero is 1.00:1: the pill does not darken on hover,
 * it disappears, and DONATE NOW is left as floating ember text at the moment the
 * visitor reaches for the primary giving control. The ring is the only thing
 * holding the shape there, which is precisely why it is here and not on the
 * light grounds, where the fill already separates at 14.71:1 and 16.84:1 and a
 * ring is decoration the client was right to cut.
 *
 * 3px rather than 2px, per the client: at 2px the ring read as a hairline
 * artefact against a large dark field rather than as the edge of a control.
 *
 * The ground list is the system's own dark set, not `.cd-hero` alone, so a
 * Donate button dropped into a navy or green section later is covered without
 * anyone having to remember this rule exists. */
:is(.cd-hero, .cd-section--navy, .cd-section--green, .cd-section--mint,
    .cd-section--blue, .cd-section--lilac)
  :is(.cd-btn--give:hover, .cd-btn--give:focus-visible) {
  box-shadow: inset 0 0 0 3px var(--cd-ember);
}

.cd-btn--sm { padding: 14px 26px; font-size: 14px; }

.cd-btn--text {
  display: inline-flex;
  align-items: center;
  min-height: var(--cd-tap-min);
  font-family: var(--cd-font-display);
  font-size: var(--cd-pill-size);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  /* [v2.0.1] Was var(--cd-blue). Logo blue at 13px fails AA on every ground in
   * the system - 3.63:1 on white, 3.11:1 on chalk, 2.62:1 on deep green, and
   * 1:1 on a blue card, where it is literally invisible.
   *
   * WCAG large text is >=24px at any weight or >=18.66px bold. At 13px the
   * button is neither, so the 3:1 allowance never applied to it. See defect 8.
   *
   * Inheriting the ground's own foreground is fail-safe: it cannot be wrong on a
   * ground the system defines, and the underline carries the affordance. */
  color: currentColor;
  text-decoration: underline;
  text-underline-offset: 4px;
  text-decoration-thickness: 2px;
}
.cd-btn--text:hover { text-decoration-thickness: 3px; }

/* [v2.0.2] .cd-btn--text inherited padding:17px 30px and justify-content:center
 * from .cd-btn, neither of which a text link wants.
 *
 * Standalone, that made the box 187px wide for 127px of text, so every action
 * link on the site sat 30px right of the heading it belonged to. Inside a card
 * the anchor stretched to the full 335px inner width and centred itself, giving
 * measured indents of 114px, 97px and 80px for "Learn more", "Find a program"
 * and "Volunteer with us" - the offset varied with label length, so no two cards
 * in a row ever aligned.
 *
 * The 44px tap target is preserved with vertical padding pulled back out by an
 * equal negative margin, so touch keeps its area and the text keeps its
 * alignment. See defect log DL-006. */
.cd-btn--text {
  align-self: flex-start;
  justify-content: flex-start;
  min-height: 0;
  padding: calc((var(--cd-tap-min) - 1.4em) / 2) 0;
  margin-block: calc(((var(--cd-tap-min) - 1.4em) / 2) * -1);
}

/* ══════════════════════════════════════════════════════════════
 * CARD — the workhorse. One cut corner, top-right by default.
 * ══════════════════════════════════════════════════════════════ */

.cd-card {
  display: flex;
  flex-direction: column;
  gap: var(--cd-space-4);
  justify-content: space-between;
  min-height: 230px;
  padding: 30px 30px 34px;
  /* [v2.0.3] The v2.0.2 fix for DL-007 used min(120px, 28%). That solved the
   * crushing but introduced elongation: a percentage radius resolves per axis,
   * so a 652x279 two-up card rendered a 120px x 78px arc. Superseded by the
   * --cd-cut knob, which is always a single length.
   *
   * Cards take the medium step. Measured across the site their short side runs
   * 212px to 416px, and it is the HEIGHT that binds, not the width: 60px is
   * 14% to 28% of that, where 120px was 29% to 57%. See defect log DL-033. */
  --cd-cut: var(--cd-cut-md);
}

/* [v2.0.2] Inside a grid the cards share row tracks, so pill, title, body and
 * action land on the same line in every card of a row.
 *
 * Before this, .cd-card used justify-content:space-between against a min-height,
 * so a title that wrapped to two lines pushed its body down while its neighbour's
 * stayed put. Measured stagger of the first body line within a single row:
 * 73px on the thank-you page, 51px on About, 46px on Impact. Three cards side by
 * side read as three unrelated boxes.
 *
 * subgrid is the honest fix: the alignment constraint lives between the cards,
 * so it is expressed between them rather than faked inside each one. The card
 * keeps its flex layout when used outside a grid. See defect log DL-008. */
@supports (grid-template-rows: subgrid) {
  .cd-grid > .cd-card {
    display: grid;
    grid-row: span 4;
    grid-template-rows: subgrid;
    row-gap: var(--cd-space-4);
    justify-content: stretch;
  }
  /* [v2.1] Five tracks, but only where icons exist. cd_render_card_grid() adds
   * cd-grid--icons when any card in the grid carries one, and backfills a
   * placeholder into the rest so every card in the row still offers the same
   * slots. Keying the span off the grid rather than the card is what keeps the
   * other fourteen card grids on the site at four tracks: giving every card an
   * always-present empty icon slot would have added a dead 16px row-gap to all
   * of them. */
  .cd-grid--icons > .cd-card { grid-row: span 5; }
  /* The action sits at the top of its own track, not floated to the card floor. */
  .cd-grid > .cd-card > .cd-btn--text { align-self: start; }
}

/* [v2.0.2] Neutral cards render no pill, which collapsed the row and left their
 * titles hard against the top padding while every other card on the site carried
 * a pill. The renderer now emits this spacer so the pill track exists whether or
 * not it is filled. See defect log DL-028. */
.cd-card__slot { display: block; min-height: 0; }

/* Coloured cards: no shadow needed, the colour does the lifting */
.cd-card--navy  { background: var(--cd-navy);  color: var(--cd-on-navy); }
.cd-card--green { background: var(--cd-green); color: var(--cd-on-green); }
.cd-card--blue  { background: var(--cd-blue);  color: var(--cd-on-blue); }

/* The Extra 6: one ground per program theme. Each pairs with its own ink, so
 * gold quietly takes navy while the other five take near-white, and no card
 * needs to know which. */
/* [v2.1] The icon is masked, not drawn.
 *
 * Every file in assets/icons is a flat white silhouette. `mask-image` uses the
 * shape's alpha and throws its colour away, so `background-color: currentColor`
 * paints it in whatever ink the card already uses: near-white on teal, moss,
 * plum, maroon and brown, navy on gold, navy again on a white card. One asset,
 * every ground, and it follows any future restyle on its own. The card supplies
 * only --cd-icon.
 *
 * The consequence to know: this works because the sources are silhouettes. A
 * multi-colour or gradient icon would mask down to a flat shape. */
.cd-card__icon {
  width: 48px;
  height: 48px;
  -webkit-mask: var(--cd-icon) center / contain no-repeat;
          mask: var(--cd-icon) center / contain no-repeat;
  background-color: currentColor;
}
/* [v2.1] A backfilled slot holds a row track open and paints nothing. Without
 * this it painted a solid 48px square: `mask: var(--cd-icon)` with no value is
 * an invalid mask, the mask is dropped, and `currentColor` fills the whole box.
 * A missing icon became a white block on the card, which is a far louder
 * failure than the gap it was standing in for. */
.cd-card__icon--empty { background-color: transparent; }

/* [v2.5.2] The mark placement: beside the content rather than above it.
 *
 * Absolutely positioned, so it consumes no subgrid track and the card keeps the
 * four slots every other card in its grid has. The card reserves room for it in
 * padding instead, which is what stops the copy running underneath: the mark is
 * out of flow, so nothing else will move for it.
 *
 * Held at 0.6 because it is a marker beside a detail, not the subject of the
 * card the way the top-slot icons are on the programme grid. */
/* [v2.5.3] 102px, half again on the client's read, at full strength. The
 * reserved padding grows with it: the mark is out of flow and cannot push the
 * copy, so the card has to hold the space open itself, and 128px of padding
 * around a 102px mark would have left the email address running under it. */
.cd-card--has-mark { position: relative; padding-right: 152px; }
.cd-card__mark {
  position: absolute;
  right: 30px;
  top: 50%;
  transform: translateY(-50%);
  width: 102px;
  height: 102px;
  -webkit-mask: var(--cd-icon) center / contain no-repeat;
          mask: var(--cd-icon) center / contain no-repeat;
  background-color: currentColor;
}
@media (max-width: 560px) {
  .cd-card--has-mark { padding-right: 112px; }
  .cd-card__mark { width: 72px; height: 72px; right: 20px; }
}

.cd-card--teal   { background: var(--cd-teal);   color: var(--cd-on-teal); }
.cd-card--moss   { background: var(--cd-moss);   color: var(--cd-on-moss); }
.cd-card--plum   { background: var(--cd-plum);   color: var(--cd-on-plum); }
.cd-card--maroon { background: var(--cd-maroon); color: var(--cd-on-maroon); }
.cd-card--brown  { background: var(--cd-brown);  color: var(--cd-on-brown); }
.cd-card--gold   { background: var(--cd-gold);   color: var(--cd-on-gold); }

/* [v2.5] A detail panel, for contact points and reference facts.
 *
 * blue-soft, not a ghost tint. The first attempt used navy-ghost and there was
 * not enough of it: 1.09:1 against a white section is a difference you have to
 * be told about. The soft step is the one cd-tokens.css describes as being for
 * "objects sitting ON a light ground", which is exactly what this is, and it
 * reads at 1.35:1 with navy ink at 13.63:1 on it. */
.cd-card--blue-soft { background: var(--cd-blue-soft); color: var(--cd-ink); box-shadow: var(--cd-shadow-soft); }

/* White cards on a ghost/grey ground: shadow required to hold the edge */
.cd-card--white { background: var(--cd-white); color: var(--cd-ink); box-shadow: var(--cd-shadow-soft); }

/* A coloured card sitting on a LIGHT ground takes the full shadow */
.cd-section--chalk .cd-card--navy,
.cd-section--chalk .cd-card--green,
.cd-section--chalk .cd-card--blue,
.cd-section--white .cd-card--navy,
.cd-section--white .cd-card--green,
.cd-section--white .cd-card--blue,
.cd-section--chalk :is(.cd-card--teal, .cd-card--moss, .cd-card--plum,
                       .cd-card--maroon, .cd-card--brown, .cd-card--gold),
.cd-section--white :is(.cd-card--teal, .cd-card--moss, .cd-card--plum,
                       .cd-card--maroon, .cd-card--brown, .cd-card--gold),
/* A theme card on its own ghost band: gold on gold-ghost separates at only
 * 1.83:1, so the shadow is doing the work of an edge here, not decoration. */
:is(.cd-section--teal-ghost, .cd-section--moss-ghost, .cd-section--plum-ghost,
    .cd-section--maroon-ghost, .cd-section--brown-ghost, .cd-section--gold-ghost)
  :is(.cd-card--teal, .cd-card--moss, .cd-card--plum,
      .cd-card--maroon, .cd-card--brown, .cd-card--gold) { box-shadow: var(--cd-shadow); }

.cd-card__body { font-size: var(--cd-small-size); line-height: var(--cd-small-lh); }
.cd-card--navy  .cd-card__body { color: #AEBAD3; }
.cd-card--green .cd-card__body { color: var(--cd-green-body); }
.cd-card--white .cd-card__body { color: var(--cd-ink-muted); }

/* ══════════════════════════════════════════════════════════════
 * GRIDS
 * ══════════════════════════════════════════════════════════════ */

.cd-grid       { display: grid; gap: var(--cd-grid-gap); }
.cd-grid--2    { grid-template-columns: repeat(2, 1fr); }
.cd-grid--3    { grid-template-columns: repeat(3, 1fr); }
.cd-grid--4    { grid-template-columns: repeat(4, 1fr); }
.cd-grid--wide { grid-template-columns: 1.15fr 1fr; gap: var(--cd-space-8); }
.cd-grid--media{ grid-template-columns: 420px 1fr; gap: var(--cd-space-10); align-items: center; }
/* [v2.0.2] Used by cd/dispatch when no image is set, so the block stops emitting
 * a second column it has nothing to put in. See defect log DL-020. */
.cd-grid--single { grid-template-columns: 1fr; }

@media (max-width: 1024px) {
  .cd-grid--3, .cd-grid--4 { grid-template-columns: repeat(2, 1fr); }
  .cd-grid--wide, .cd-grid--media { grid-template-columns: 1fr; gap: var(--cd-space-7); }
}
@media (max-width: 680px) {
  .cd-grid--2, .cd-grid--3, .cd-grid--4 { grid-template-columns: 1fr; }
}

/* ══════════════════════════════════════════════════════════════
 * HERO — navy ground, right-anchored image panel with top-left cut,
 * content layered above it.
 * ══════════════════════════════════════════════════════════════ */

.cd-hero {
  position: relative;
  overflow: hidden;
  background: var(--cd-navy);
  color: var(--cd-on-navy);
  /* [v2.0.2] Was `76px gutter 0`, with the bottom padding borrowed from
   * .cd-hero__lede. A hero without a lede therefore had none at all.
   * See defect log DL-021. */
  padding-block: var(--cd-section-y-hero);
}
.cd-hero__media {
  position: absolute;
  inset: 0 0 0 auto;
  width: var(--cd-hero-media-w);
  /* The one panel-step object: 520px wide, so 220px is 42% of the short side.
   * It is the signature sweep and it is circular, which is what matters. */
  --cd-cut: var(--cd-cut-panel);
  overflow: hidden;
  background: #0A1A4C;
}
/* [v2.1] The six program themes take their own ground from the Extra 6, so a
 * theme page opens in the same colour its card carried on the home page. The
 * ink comes from each colour's --cd-on-* partner, which is why gold can join a
 * set of five dark bands without a special case anywhere in the markup. */
.cd-hero--teal   { background: var(--cd-teal);   color: var(--cd-on-teal); }
.cd-hero--moss   { background: var(--cd-moss);   color: var(--cd-on-moss); }
.cd-hero--plum   { background: var(--cd-plum);   color: var(--cd-on-plum); }
.cd-hero--maroon { background: var(--cd-maroon); color: var(--cd-on-maroon); }
.cd-hero--brown  { background: var(--cd-brown);  color: var(--cd-on-brown); }
.cd-hero--gold   { background: var(--cd-gold);   color: var(--cd-on-gold); }

/* The standfirst is keyed to navy by default; on a themed band it should simply
 * be the band's own ink, held back a little rather than recoloured. */
:is(.cd-hero--teal, .cd-hero--moss, .cd-hero--plum,
    .cd-hero--maroon, .cd-hero--brown, .cd-hero--gold) .cd-hero__lede .cd-standfirst {
  color: inherit;
  opacity: 0.86;
}

/* [v2.1] The mark panel. No backing colour and no cut: the 220px sweep is the
 * signature of a full-bleed photograph, and there is no photograph here, so it
 * would only draw an edge around nothing. The mark floats on the band itself.
 *
 * Masked rather than served as a white file, so it takes the band's own ink and
 * the gold hero gets a navy mark instead of a white one washing out against it.
 * aria-hidden on the panel: it is atmosphere, and the heading beside it already
 * names the theme. */
.cd-hero__media--icon {
  background: none;
  border-radius: 0;
  display: grid;
  place-items: center;
  pointer-events: none;
}
.cd-hero__mark {
  width: min(340px, 66%);
  aspect-ratio: 1 / 1;
  -webkit-mask: var(--cd-icon) center / contain no-repeat;
          mask: var(--cd-icon) center / contain no-repeat;
  background-color: currentColor;
  opacity: var(--cd-hero-icon-opacity);
}

.cd-hero__media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  /* [v2.0.7] Knocked back over the panel's own navy so the photograph reads as
   * mood behind the headline rather than as a competing subject. */
  opacity: var(--cd-hero-media-opacity);
}
.cd-hero__content,
.cd-hero__lede {
  position: relative;
  z-index: 2;
}

/* [v2.0.2] The panel is 520px wide and the text reserved 300px for it, so the
 * headline ran 164px into the photograph and overlapped it by 275px vertically -
 * the entire height of the type. It stayed readable on the home page only
 * because that particular string happened to clear the 220px curve.
 *
 * Two hard-coded numbers that had to agree and did not is the whole fault, so
 * the reserve is now derived from the panel width rather than restated. Both
 * come from --cd-hero-media-w and cannot drift apart again.
 *
 * The reserve applies only where a panel actually exists. Sixteen of the
 * seventeen heroes on the site have no media, and those get the full width
 * rather than a 520px hole reserved for nothing. See defect log DL-003. */
.cd-hero:has(.cd-hero__media) .cd-hero__content,
.cd-hero:has(.cd-hero__media) .cd-hero__lede {
  max-width: calc(100% - var(--cd-hero-media-w) - var(--cd-space-8));
}
.cd-hero__content { display: flex; flex-direction: column; gap: 26px; align-items: flex-start; }
.cd-hero__content > *, .cd-hero__lede > * { max-width: 100%; }
.cd-hero__lede    { display: flex; flex-direction: column; gap: var(--cd-space-6); padding: 40px 0 0; }
.cd-hero__lede .cd-standfirst { color: var(--cd-navy-body); }
.cd-hero__actions { display: flex; gap: 14px; flex-wrap: wrap; }

@media (max-width: 900px) {
  /* [v2.0.2] The media panel is the hero's first child in source order, which is
   * what the absolute positioning wants on desktop. Once it goes static for
   * mobile that same order put the photograph ABOVE the pill and the headline,
   * against a spec that calls for a 260px band BELOW the text. `order` fixes the
   * stack without disturbing the desktop geometry or the reading order for a
   * screen reader. See defect log DL-015. */
  .cd-hero {
    display: flex; flex-direction: column;
    padding-block: var(--cd-section-y-mob);
    padding-inline: var(--cd-gutter-mobile);
  }
  .cd-hero__content { order: 1; }
  .cd-hero__lede    { order: 2; }
  .cd-hero__media {
    order: 3;
    position: relative; inset: auto; width: 100%; height: 260px;
    --cd-cut: var(--cd-cut-md);
    margin-top: 32px;
  }
  .cd-hero:has(.cd-hero__media) .cd-hero__content,
  .cd-hero:has(.cd-hero__media) .cd-hero__lede { max-width: 100%; }
  .cd-hero__lede { padding: 32px 0 0; }
  /* Buttons share a width so the pair does not read as two different controls. */
  .cd-hero__actions .cd-btn { flex: 1 1 auto; justify-content: center; }
}

/* ══════════════════════════════════════════════════════════════
 * STAT BAND — mint ground. Evidence. 3 figures + 1 note.
 * ══════════════════════════════════════════════════════════════ */

.cd-stats { background: var(--cd-mint); color: var(--cd-on-mint); padding-block: var(--cd-section-y-tight); }
.cd-stats__grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: var(--cd-space-7); align-items: end; }
.cd-stat { display: flex; flex-direction: column; gap: var(--cd-space-2); }
.cd-stat__label {
  font-family: var(--cd-font-display);
  font-size: 13px; letter-spacing: 0.08em; text-transform: uppercase;
}
.cd-stats__note {
  font-size: 17px; line-height: 1.55;
  border-left: 1px solid var(--cd-on-mint); padding-left: var(--cd-space-5);
}
@media (max-width: 900px) { .cd-stats__grid { grid-template-columns: repeat(2, 1fr); } }

/* ══════════════════════════════════════════════════════════════
 * QUOTE / TESTIMONY — green ground, circular portrait mask
 * ══════════════════════════════════════════════════════════════ */

.cd-testimony { background: var(--cd-green); color: var(--cd-on-green); padding: var(--cd-space-10) var(--cd-gutter); }
.cd-testimony__portrait {
  width: 340px; height: 340px; border-radius: var(--cd-radius-pill); overflow: hidden; background: #0E5B48; flex: none;
}
.cd-testimony__portrait img { width: 100%; height: 100%; object-fit: cover; display: block; }
.cd-testimony__attrib {
  font-family: var(--cd-font-mono); font-size: var(--cd-meta-size);
  letter-spacing: 0.16em; text-transform: uppercase; color: var(--cd-green-meta);
}
@media (max-width: 680px) { .cd-testimony__portrait { width: 220px; height: 220px; } }

/* ══════════════════════════════════════════════════════════════
 * FIGURE / IMAGE — cut corner + shadow. Light grounds only.
 * ══════════════════════════════════════════════════════════════ */

.cd-figure-img {
  /* [v2.0.2] The radius moved to the --tr/--br modifiers below. cd_render_figure()
   * has always emitted one of the two from the block's `cut` attribute, but
   * neither selector existed, so every figure on all six theme pages rendered
   * the same top-right cut and the alternation the language calls for had never
   * once appeared. See defect log DL-017. */
  overflow: hidden;
  box-shadow: var(--cd-shadow);
  background: var(--cd-grey-soft);
}
/* Figures run 1328x885, so the large step is 13.6% of the short side. */
.cd-figure-img { --cd-cut: var(--cd-cut-lg); }

/* [v2.0.4] A figure shrinks with the viewport but the step does not follow it
 * automatically, because the step is a length by design. Measured at 390px a
 * program figure is 350x263, where the large step is 45.7% of the short side
 * and breaches the 40% guard. The crossover sits near 430px, so the step drops
 * one notch below 520px. Caught by the cut QA check, not by eye. */
@media (max-width: 520px) {
  .cd-figure-img { --cd-cut: var(--cd-cut-md); }
}
.cd-figure-img img { width: 100%; height: 100%; object-fit: cover; display: block; }
/* [v2.0.5] Rule 5 in one place, for every object that floats.
 *
 * Was four selectors covering .cd-figure-img only, and it omitted the lilac
 * ground even though rule 5 names it: "Nothing on navy, green, mint, blue or
 * lilac ever does." Listing thumbnails are added here because they now cast the
 * shadow too, and the suppression list should not have to be restated once per
 * component. */
:is(.cd-section--navy, .cd-section--green, .cd-section--mint,
    .cd-section--blue, .cd-section--lilac)
  :is(.cd-figure-img, .cd-post-thumb, .cd-event-thumb, .cd-partner,
      .cd-statement--panel, .cd-mark-panel) { box-shadow: none; }

.cd-caption { font-size: 14px; line-height: 1.5; color: var(--cd-ink-meta); }
.cd-caption__credit {
  /* [v2.0.1] Was #8A8F98. The v2.0 note approved it as a trailing fragment on
   * the strength of a 3.2:1 reading against WHITE - but 3.2:1 fails AA at 14px
   * anywhere, and on the system's other light grounds it is worse: 2.91:1 on
   * blue ghost, 2.78:1 on chalk.
   * The credit now shares the caption's colour and is distinguished by its
   * "Photo:" prefix rather than by being fainter. See defect 10. */
  color: var(--cd-ink-meta);
}

/* ══════════════════════════════════════════════════════════════
 * GIVING BAND — mint-soft ground + one ember button
 * ══════════════════════════════════════════════════════════════ */

.cd-give {
  background: var(--cd-mint-soft); color: var(--cd-on-mint);
  padding-block: var(--cd-section-y-tight);
  display: flex; align-items: center; justify-content: space-between; gap: var(--cd-space-8);
}
.cd-give__title {
  font-family: var(--cd-font-display); font-size: 46px; line-height: 0.98;
  letter-spacing: -0.035em; text-transform: uppercase; margin: 0;
  /* [v2.0.2] Was 22ch. The ch unit is the advance of the "0" glyph, which is not
   * the average letter in any face. Package defect 3 caught this for body copy,
   * where Archivo 400 makes ch OVERSTATE by 1.283x. Archivo Black in uppercase
   * runs the other way: 1ch measures 30.68px against an average capital of
   * 34.64px, so ch UNDERSTATES by 0.886x and 22ch delivered 675px, which fits
   * 19 characters rather than 22.
   *
   * The direction reverses between faces, which is the actual lesson: ch never
   * means characters. Stated in pixels, 22 characters of Archivo Black at 46px
   * is 762px. See defect log DL-004. */
  max-width: var(--cd-measure-statement);
}
/* [v2.0.8] The giving band is composed as two lines: a soft opening in
 * Instrument Serif italic, sentence case, resolving onto the noun in Archivo
 * Black caps beneath it. The hero runs the opposite way, display first and
 * serif last, because there the accent is the resolution rather than the setup.
 *
 * `display: block` makes that break structural. Left to wrap on its own the
 * composition would depend on the statement's exact width against
 * --cd-measure-statement, which is how "AN" ended up orphaned onto line two by
 * 11px. Two lines because they were designed as two, not because they did not
 * fit.
 *
 * No colour change: the band already spends the page's one ember on its button
 * and rule 2 allows exactly one, so the lead leans on face and case alone and
 * keeps --cd-on-mint. Lilac was considered and rejected here: both lilac tokens
 * are built to sit on navy and read at 1.53:1 and 1.09:1 on this mint ground.
 * See defect log DL-044. */
.cd-give__title em {
  display: block;
  font-family: var(--cd-font-accent);
  font-style: italic;
  font-weight: 400;
  text-transform: none;
  letter-spacing: -0.01em;
}
@media (max-width: 900px) {
  .cd-give { flex-direction: column; align-items: flex-start; gap: var(--cd-space-6); }
  .cd-give__title { font-size: 34px; }
}

/* ══════════════════════════════════════════════════════════════
 * HEADER / NAV
 * ══════════════════════════════════════════════════════════════ */

.cd-header {
  background: var(--cd-grey-ghost);
  display: flex; align-items: center; justify-content: space-between;
  padding: 22px var(--cd-gutter); gap: var(--cd-space-7);
}
/* [v2.0.2] Was 310px, rendering 310 x 107px and driving a 151px header. The
 * tagline inside that SVG came out at roughly 9px and was illegible at any
 * viewport, so the header was paying 107px of height for words nobody could
 * read. The tagline now lives in the footer at 19px, where it can be.
 * Dropping it also buys back 110px of header width, which is what let the
 * desktop navigation come back. See defect log DL-030 and DL-010. */
.cd-header__logo { width: 200px; height: auto; flex: none; display: block; }
/* [DL-071] The .cd-nav rules that used to sit here are gone. Nothing on this
 * site has ever carried that class: the core Navigation block renders
 * .wp-block-navigation on a <nav> and .wp-block-navigation-item__content on the
 * links, and the working rules live in the header block below. The note there
 * about "the package sets this on .cd-nav a ... so the rule never landed" had
 * already spotted it; the dead vocabulary was left behind, and cost an hour when
 * the programs panel was first written against it and rendered nothing. */
.cd-header__actions { display: flex; align-items: center; gap: var(--cd-space-7); }

/* ══════════════════════════════════════════════════════════════
 * FOOTER
 * ══════════════════════════════════════════════════════════════ */

/* [v2.0.2] The footer was one flex row with justify-content:space-between, which
 * pinned the brand block to the left edge and the links to the right and left
 * 799px of empty navy between them. The two link columns rendered 73px and 72px
 * wide, jammed against the right margin with no headings, so eight navigation
 * links read as a loose list rather than a sitemap. Total height 667px, most of
 * it nothing.
 *
 * A four-column grid closes the gap and gives each column a heading.
 * See defect log DL-012. */
.cd-footer {
  background: var(--cd-navy);
  color: var(--cd-navy-body);
  padding-block: var(--cd-space-10);
  padding-inline: max(var(--cd-gutter), (100% - var(--cd-max-width)) / 2);
}
.cd-footer__inner { display: flex; flex-direction: column; gap: var(--cd-space-8); }
.cd-footer__grid {
  display: grid;
  grid-template-columns: 1.6fr 1fr 1fr 1.4fr;
  gap: var(--cd-space-8);
  align-items: start;
}
/* [v2.3.3] Logo and tagline centred together, and told to be.
 *
 * The logo was already sitting centred in the column while the tagline ran from
 * the left edge, so the two read as unrelated. Nothing here asked for that
 * centring, which is the part worth fixing: an alignment that happens by
 * accident is one nobody can rely on. Both are now centred deliberately.
 *
 * The gap is --cd-space-7, 40px, which is just over the 1cm asked for
 * (1cm = 37.8 CSS px). It was --cd-space-4, 16px. */
.cd-footer__brand {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: var(--cd-space-7);
}
.cd-footer__logo  { height: 44px; width: auto; display: block; }

/* [v2.0.2] Was 12px mono uppercase with 0.14em tracking in the muted colour -
 * the exact treatment this system uses for timestamps, bylines and photo
 * credits. The organisation's tagline was set in the metadata voice.
 * See defect log DL-026. */
/* [v2.3.2] The organisation's tagline, set as the hero sets it: display face in
 * caps, resolving onto the serif accent. Same two faces and the same order as
 * the home page headline, at a size of its own. */
.cd-footer__tagline {
  font-family: var(--cd-font-display);
  font-size: var(--cd-footer-tagline-size);
  line-height: var(--cd-footer-tagline-lh);
  letter-spacing: var(--cd-footer-tagline-track);
  text-transform: uppercase;
  color: var(--cd-on-navy);
  max-width: 13ch;
  margin: 0;
}
.cd-footer__tagline em {
  font-family: var(--cd-font-accent);
  font-style: italic;
  font-weight: 400;
  text-transform: none;
  letter-spacing: -0.01em;
  color: var(--cd-lilac-soft);
}

.cd-footer__col { display: flex; flex-direction: column; gap: var(--cd-space-2); font-size: 15px; }
.cd-footer__heading {
  font-family: var(--cd-font-display);
  font-size: var(--cd-pill-size);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--cd-navy-meta);
  margin: 0 0 var(--cd-space-2);
}
.cd-footer__note { color: var(--cd-navy-meta); margin: var(--cd-space-2) 0 0; }

.cd-footer__acknowledgement,
.cd-footer__legal { border-top: 1px solid var(--cd-rule-dark); padding-top: var(--cd-space-6); }
.cd-footer__acknowledgement p:last-child { max-width: var(--cd-measure-body); margin: var(--cd-space-3) 0 0; }
.cd-footer__legal {
  display: flex; flex-wrap: wrap; gap: var(--cd-space-4);
  justify-content: space-between; align-items: baseline;
  font-family: var(--cd-font-mono); font-size: var(--cd-meta-size);
  color: var(--cd-navy-meta);
}
.cd-footer__legal p { margin: 0; }

.cd-footer a { color: inherit; text-decoration: none; }
.cd-footer a:hover { color: var(--cd-white); text-decoration: underline; }

@media (max-width: 900px) {
  .cd-footer__grid { grid-template-columns: 1fr 1fr; }
  .cd-footer__brand { grid-column: 1 / -1; }
}
@media (max-width: 560px) {
  .cd-footer__grid { grid-template-columns: 1fr; }
}

/* ══════════════════════════════════════════════════════════════
 * RESPONSIVE TYPE — fluid down, never below the floor
 * ══════════════════════════════════════════════════════════════ */

@media (max-width: 1200px) {
  .cd-hero-title    { font-size: clamp(56px, 8vw, 104px); }
  .cd-section-title { font-size: clamp(36px, 5vw, 54px); }
  .cd-feature-title { font-size: clamp(30px, 4vw, 44px); }
  .cd-quote         { font-size: clamp(28px, 4vw, 42px); }
  .cd-figure        { font-size: clamp(52px, 7vw, 78px); }
}
@media (max-width: 900px) {
  .cd-section { padding: var(--cd-section-y-mob) var(--cd-gutter-mobile); }
  .cd-give, .cd-stats, .cd-testimony, .cd-header, .cd-footer { padding-left: var(--cd-gutter-mobile); padding-right: var(--cd-gutter-mobile); }
  .cd-footer { padding-block: var(--cd-space-8); }
  .cd-header { flex-wrap: wrap; }
}

/* [v2.0.1] Section padding options.
 * 04-wordpress-components.md specifies the cd/section block offers
 * Standard (72px) / Tight (48px) / Generous (100px), but no modifiers existed.
 * Standard is the default and needs no class. */
.cd-section--pad-tight    { padding-block: var(--cd-section-y-mob); }
.cd-section--pad-generous { padding-block: var(--cd-space-10); }

/* [v2.0.1] Numbered list — spec 03-components.md §14, no CSS shipped in v2.0.
 * Two-column bordered rows. The index is a CSS counter so the numbering cannot
 * drift from the markup. */
/* [v2.0.2] The list was capped only by the body measure on its text, while each
 * row's rule spanned the full 1232px band - the rule ran 676px past the content
 * it was separating, and the last row left a line with nothing under it.
 * Capping the list itself pulls the rules back to the column they belong to.
 * See defect log DL-023. */
.cd-numbered {
  counter-reset: cd-n;
  display: flex;
  flex-direction: column;
  max-width: var(--cd-measure-list);
}
.cd-numbered__item {
  counter-increment: cd-n;
  display: grid;
  grid-template-columns: 64px 1fr;
  gap: var(--cd-space-5);
  padding: var(--cd-space-6) 0;
  border-top: 1px solid var(--cd-rule-light);
  align-items: baseline;
}
.cd-numbered__item:last-child { border-bottom: 1px solid var(--cd-rule-light); }

/* [v2.4.2] Two columns, filled top to bottom. The row count comes from PHP,
 * which is the only place that knows how many items there are.
 *
 * The rule moves from the top of each item to the bottom. With a top rule the
 * first column would have ended open while the second closed on :last-child,
 * because CSS cannot say "last item in this column". A bottom rule on every
 * item closes both columns and needs no counting. */
.cd-numbered--2col {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  grid-auto-flow: column;
  grid-template-rows: repeat(var(--cd-numbered-rows, 4), auto);
  column-gap: var(--cd-space-10);
  max-width: none;
}
.cd-numbered--2col .cd-numbered__item {
  border-top: 0;
  border-bottom: 1px solid var(--cd-rule-light);
}
@media (max-width: 900px) {
  .cd-numbered--2col { display: flex; flex-direction: column; }
  .cd-numbered--2col .cd-numbered__item { border-top: 1px solid var(--cd-rule-light); border-bottom: 0; }
  .cd-numbered--2col .cd-numbered__item:last-child { border-bottom: 1px solid var(--cd-rule-light); }
}
.cd-numbered__item::before {
  content: counter(cd-n, decimal-leading-zero);
  /* [v2.0.2] Was the mono metadata face at --cd-meta-size, which rendered the
   * ordinal at roughly 9px in light grey inside a 64px column: the component's
   * organising device was the least visible thing in it. It now carries the
   * display face at card size, which is what a numbered index is for.
   * See defect log DL-022. */
  font-family: var(--cd-font-display);
  font-size: var(--cd-card-size);
  line-height: var(--cd-card-lh);
  letter-spacing: var(--cd-card-track);
  color: var(--cd-ink-meta);
  font-variant-numeric: tabular-nums;
}
.cd-numbered__title {
  font-family: var(--cd-font-display);
  font-size: var(--cd-card-size);
  line-height: var(--cd-card-lh);
  letter-spacing: var(--cd-card-track);
  text-transform: uppercase;
  margin: 0 0 var(--cd-space-3);
}
.cd-numbered__body { font-size: var(--cd-small-size); line-height: var(--cd-small-lh); color: var(--cd-ink-muted); margin: 0; }

/* on dark grounds the rules and index invert */
.cd-section--navy .cd-numbered__item,
.cd-section--green .cd-numbered__item { border-color: var(--cd-rule-dark); }
.cd-section--navy .cd-numbered__item::before { color: var(--cd-navy-meta); }
.cd-section--navy .cd-numbered__body { color: var(--cd-navy-body); }
.cd-section--green .cd-numbered__item::before { color: var(--cd-green-meta); }
.cd-section--green .cd-numbered__body { color: var(--cd-green-body); }

/* [v2.0.1] Prose wrapper. .cd-body caps the measure; this also spaces the run
 * and gives subheadings the 40/16 rhythm the spec asks for. */
.cd-prose > * + * { margin-top: var(--cd-space-5); }
.cd-prose h2, .cd-prose h3 { margin-top: var(--cd-space-7); margin-bottom: var(--cd-space-4); }
.cd-prose p, .cd-prose ul, .cd-prose ol { max-width: var(--cd-measure-body); }

/* [v2.3] Two-column prose. The column width does the limiting, so the paragraph
 * cap comes off: leaving it on would have held each paragraph to 556px inside a
 * 660px column and left a ragged strip down the middle of each one.
 *
 * `columns` rather than a grid, so the text flows: four paragraphs balance
 * across the two columns as one passage instead of being dealt into fixed
 * cells. One column below 900px, where two would be about 35 characters each.
 * See defect log DL-059. */
.cd-prose--2col {
  /* The block itself carries .cd-body, which caps it at 556px. Left on, the two
   * columns split 556 rather than the band and came out at about 250px each:
   * narrower than the single column they replaced, which is the opposite of
   * what the columns were for. */
  max-width: none;
  columns: 2;
  column-gap: var(--cd-space-10);
}
.cd-prose--2col p,
.cd-prose--2col ul,
.cd-prose--2col ol { max-width: none; }
.cd-prose--2col > *:first-child { margin-top: 0; }
@media (max-width: 900px) { .cd-prose--2col { columns: 1; } }

/* [v2.0.1] Links inside headings inherit the heading's colour.
 *
 * The v2.0.1 link rule (navy + underline) is correct for prose, but a linked
 * headline is not prose: .cd-feature-title is logo blue at 44px, which the
 * accessibility spec approves as large text. Without this, wrapping a feature
 * title in an anchor silently repainted it navy and underlined it. */
h1 a, h2 a, h3 a, h4 a, h5 a, h6 a,
.cd-hero-title a, .cd-section-title a, .cd-feature-title a,
.cd-card-title a, .cd-quote a, .cd-numbered__title a {
  color: inherit;
  text-decoration: none;
}
h1 a:hover, h2 a:hover, h3 a:hover, h4 a:hover, h5 a:hover, h6 a:hover,
.cd-feature-title a:hover, .cd-card-title a:hover {
  text-decoration: underline;
  text-underline-offset: 4px;
}

/* ══════════════════════════════════════════════════════════════
 * [v2.0.1] HEADER — RESPONSIVE
 *
 * v2.0 shipped .cd-header with no responsive rules at all. This is the exact
 * failure documented in the v0.1 analysis, where the Donate button broke across
 * seven lines at 360px inside a 192px-tall header.
 *
 * Three requirements drive this block:
 *   - Rule 1: the tagline lockup never renders below 300px. Below that we must
 *     swap to the plain lockup, not shrink the tagline into illegibility.
 *   - 03-components.md §5: the Donate button is ALWAYS visible and never goes
 *     into the hamburger.
 *   - 06-accessibility-and-qa.md §7: no horizontal scroll at 320px equivalent.
 * ══════════════════════════════════════════════════════════════ */

.cd-header__logo--tagline { display: none; }   /* [v2.0.2] see DL-030 */
.cd-header__logo--plain   { display: block; }

/* Rule 1: the lockup must never be squashed below its floor. The anchor wrapping
 * it is a flex child and will shrink without this. */
.cd-header__logo-link { flex: none; display: block; line-height: 0; }

/* The Donate button must never wrap, at any width. */
.cd-header .cd-btn { white-space: nowrap; flex: none; }

@media (max-width: 1100px) {
  .cd-header { gap: var(--cd-space-5); padding-inline: var(--cd-gutter-tablet); }
  .cd-header .wp-block-navigation__container { gap: var(--cd-space-5); }
  .cd-header__actions { gap: var(--cd-space-5); }
}

/* The tagline lockup needs 300px. Below 820px the header cannot afford that plus
 * a 44px hamburger and the Donate button, so swap to the plain lockup - which is
 * exactly what rule 1 prescribes rather than shrinking the tagline. */
@media (max-width: 560px) {
  .cd-header { padding: var(--cd-space-4) var(--cd-gutter-mobile); gap: var(--cd-space-3); }
  .cd-header__logo--plain  { width: 168px; }
  .cd-header .cd-btn--sm { padding-inline: var(--cd-space-4); }
}

@media (max-width: 380px) {
  .cd-header__logo--plain { width: 140px; }
}

/* [v2.0.1] Nav collapse breakpoint - MEASURED, not chosen.
 *
 * 03-components.md §5 says collapse below 900px, on the assumption of FOUR nav
 * items. The client has deferred the four-item cap and the site currently runs
 * SEVEN. Measured at 1920px:
 *
 *     seven items, natural single-line width ........ 876px
 *     + tagline lockup (rule 1 floor) ............... 310px
 *     + Donate button ............................... 120px
 *     + gutters and gaps ............................ 192px
 *     ------------------------------------------------------
 *     header requires ............................. ~1498px
 *
 * The inline nav therefore does not fit at 1440px, let alone 900px. Rule 1 is
 * non-negotiable and the nav count is the deferred decision, so the lockup wins
 * and the nav collapses to a disclosure below 1500px.
 *
 * This is the measured cost of seven items and it reverses the moment the nav
 * drops to four, at which point ~947px is enough and the 900px breakpoint in
 * the spec becomes correct again. The four benchmark foundations in the v0.1
 * analysis (Ford, Gates, Wellcome, charity: water) all suppress desktop nav
 * behind a disclosure by choice, so this is not off-brand in the meantime.
 */
/* [v2.0.2] The breakpoint above was 1499px, which put the entire information
 * architecture behind an unlabelled glyph at 1440px - the commonest desktop
 * width there is, and the width this site is designed at. Eight nav items
 * measure 782px and the donate button 120px; against a 310px logo plus gutters
 * and gaps that genuinely did not fit, which is why the number was set there.
 *
 * Taking the header logo down to 200px (DL-030) buys back 110px. Measured at
 * 1600px the row needs exactly 1310px: 200 logo + 814 nav + 120 donate + 96
 * padding + 80 gaps. The breakpoint is set at 1340px, which is the measurement
 * plus a little slack rather than a guess, so 1440px carries the full menu and
 * anything genuinely too narrow for it still gets the disclosure.
 * See defect log DL-010. */
@media (max-width: 1359px) {
  .cd-header .wp-block-navigation__responsive-container:not(.is-menu-open) {
    display: none;
  }
  .cd-header .wp-block-navigation__responsive-container-open:not(.always-shown) {
    display: flex;
  }
}
/* Above the breakpoint the disclosure button must not linger beside the menu. */
@media (min-width: 1360px) {
  .cd-header .wp-block-navigation__responsive-container-open:not(.always-shown) {
    display: none;
  }
}

/* The core Navigation block is used so the menu stays editable in
 * Appearance → Menus and the mobile disclosure keeps core's accessibility
 * behaviour: real button, aria-expanded, Escape to close, focus trapped. */
/* [v2.0.2] The menu inherited the 8px default block gap, so eight uppercase
 * items ran together as one string: "HOME ABOUT US PROGRAMS EVENTS CONTRIBUTE
 * POSTS CONTACT US". Uppercase at 15px with 0.05em tracking needs real
 * separation to read as discrete destinations. See defect log DL-010. */
.cd-header .wp-block-navigation__container { gap: var(--cd-nav-gap); }
.cd-header .wp-block-navigation__submenu-container { gap: 0; }

.cd-header .wp-block-navigation {
  font-family: var(--cd-font-body);
  font-weight: 600;
  font-size: var(--cd-nav-size);
  letter-spacing: var(--cd-nav-track);
  text-transform: uppercase;
  color: var(--cd-navy);
}
.cd-header .wp-block-navigation a,
.cd-header .wp-block-navigation-item__content {
  color: inherit;
  text-decoration: none;
  /* WCAG 2.5.5: the package sets this on .cd-nav a, but the core Navigation
   * block renders .wp-block-navigation-item__content instead, so the rule
   * never landed and links measured 35px. */
  min-height: var(--cd-tap-min);
  display: inline-flex;
  align-items: center;
}
/* [DL-071] Was var(--cd-blue): 3.33:1 on the grey-ghost header, where 17px at
 * weight 600 is small text and wants 4.5:1. Third time this trap has been hit
 * (see .cd-btn--text and the prose-link rule) and the third time the answer is
 * the same: logo blue is not a text colour at this size. Navy stays, and the
 * underline carries the affordance. */
.cd-header .wp-block-navigation a:hover {
  color: var(--cd-navy);
  text-decoration: underline;
  text-underline-offset: 6px;
  text-decoration-thickness: 2px;
}
/* [DL-074] `> a` no longer covers it. With the submenu opening on click, core
 * renders the Programs item as a <button>, so the ancestor underline - the one
 * thing telling a reader which section they are in - stopped painting on all six
 * theme pages and on /programs/ itself the moment the mode changed.
 *
 * Matched on .wp-block-navigation-item__content, which core puts on both the
 * anchors and the button, and scoped to direct children of the top-level
 * container so the rule cannot reach inside the panel and underline a row
 * there. */
.cd-header .wp-block-navigation__container > .current-menu-item > .wp-block-navigation-item__content {
  border-bottom: 3px solid var(--cd-blue);
  padding-bottom: 3px;
}
/* [DL-071] The current section, not just the current page. WordPress has been
 * emitting current-menu-ancestor on Programs all along whenever one of the six
 * theme pages is open, but only current-menu-item was styled - and on a theme
 * page that class lands on a link inside a closed dropdown. So all six theme
 * pages showed no active item anywhere in the visible bar. Blue-soft rather than
 * blue, so ancestor and current page stay distinguishable when Programs is the
 * open page itself. */
/* :not(.current-menu-item), because on /programs/ the item is both - the page
 * you are on AND the parent of the panel's rows. Without the guard the ancestor
 * rule, which is written second, would win the tie and paint the quiet underline
 * over the loud one. */
.cd-header .wp-block-navigation__container > .current-menu-ancestor:not(.current-menu-item) > .wp-block-navigation-item__content {
  border-bottom: 3px solid var(--cd-blue-soft);
  padding-bottom: 3px;
}
/* Tap targets: 06-accessibility-and-qa.md requires 44px. */
.cd-header .wp-block-navigation__responsive-container-open,
.cd-header .wp-block-navigation__responsive-container-close {
  min-width: var(--cd-tap-min);
  min-height: var(--cd-tap-min);
  display: inline-flex; align-items: center; justify-content: center;
}
.cd-header .wp-block-navigation__responsive-container.is-menu-open {
  background: var(--cd-navy);
  color: var(--cd-on-navy);
  padding: var(--cd-space-8) var(--cd-gutter-mobile);
}
.cd-header .wp-block-navigation__responsive-container.is-menu-open a { color: var(--cd-on-navy); }

/* ══════════════════════════════════════════════════════════════
 * [v2.0.1] FOOTER — full composition
 *
 * v2.0 specified the brand/links row only. A real footer also carries an
 * Acknowledgement of Country and the legal line, and the v0.1 analysis found
 * the previous site rendered the Acknowledgement at 11.2px / 60% opacity /
 * 145 characters per line - simultaneously the smallest, faintest and
 * widest-set text on the entire website, styled as legal boilerplate.
 *
 * Here it is given an actual editorial role: a pill label, body-size type at
 * full contrast, and a capped measure. It reads as a statement of values,
 * which is what it is.
 * ══════════════════════════════════════════════════════════════ */

.cd-footer {
  flex-direction: column;
  align-items: stretch;
  gap: var(--cd-space-8);
}
.cd-footer__top {
  display: flex; justify-content: space-between; align-items: flex-end;
  gap: var(--cd-space-8); flex-wrap: wrap;
}
.cd-footer__brand {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: var(--cd-space-7);
}
.cd-footer__cols { display: flex; gap: var(--cd-space-10); flex-wrap: wrap; }
.cd-footer__col { display: flex; flex-direction: column; }
.cd-footer__col a {
  color: #A9B3C9;                       /* 7.6:1 on navy */
  font-size: 15px; line-height: 1.9;
  text-decoration: none;
  min-height: var(--cd-tap-min);
  display: inline-flex; align-items: center;
}
.cd-footer__col a:hover { color: var(--cd-white); text-decoration: underline; }

.cd-footer__acknowledgement {
  display: flex; flex-direction: column; gap: var(--cd-space-4);
  padding-top: var(--cd-space-7);
  border-top: 1px solid var(--cd-rule-dark);
}
.cd-footer__acknowledgement p:not(.cd-pill) {
  font-size: var(--cd-small-size);       /* 16px, not 11.2px */
  line-height: 1.65;
  /* [v2.3.2] The footer's own body colour rather than near-white. The
   * acknowledgement was the brightest text in the footer, brighter than the
   * headings above it, which read as emphasis nobody asked for. --cd-navy-body
   * is what everything else here uses and still clears 10.39:1 on navy. */
  color: inherit;
  /* Full width. The 66-character cap was right for a column of reading copy and
   * wrong here: this is one paragraph across the foot of the page, and capping
   * it left it in the left third under a full-width rule. */
  max-width: none;
  margin: 0;
}
.cd-footer__legal p {
  font-family: var(--cd-font-mono);
  font-size: var(--cd-meta-size);
  letter-spacing: var(--cd-meta-track);
  color: var(--cd-navy-meta);            /* 6.5:1 on navy */
  margin: 0;
}

@media (max-width: 680px) {
  .cd-footer { padding: var(--cd-space-8) var(--cd-gutter-mobile); gap: var(--cd-space-7); }
  .cd-footer__top { flex-direction: column; align-items: flex-start; gap: var(--cd-space-7); }
  .cd-footer__cols { gap: var(--cd-space-8); }
}

/* [v2.0.1] Submenu toggle tap target.
 *
 * The v0.1 analysis found the previous site's Programs dropdown toggle was
 * 10x10px on tablet and 11x11px on desktop - not operable, and well under the
 * 44px that 06-accessibility-and-qa.md requires. WordPress core ships the same
 * small chevron button, so it is corrected here rather than inherited. */
.cd-header .wp-block-navigation__submenu-icon,
.cd-header .wp-block-navigation-submenu__toggle {
  min-width: var(--cd-tap-min);
  min-height: var(--cd-tap-min);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0;
}
.cd-header .wp-block-navigation-item__content + .wp-block-navigation__submenu-icon { margin-left: 0; }

/* [v2.0.1] Links on dark grounds.
 *
 * The v2.0.1 link rule makes prose links navy with an underline, which is right
 * on white and invisible on navy. The cd-ground-* section styles set their own
 * link colour, but a core Cover block with a dark overlay is not one of those,
 * so navy links inside a navy Cover measured 1:1 - literally invisible.
 *
 * Blue soft is 13.63:1 on navy. */
.wp-block-cover a:where(:not(.wp-element-button)),
.wp-block-cover .wp-block-navigation a {
  color: var(--cd-blue-soft);
}
.wp-block-cover a:where(:not(.wp-element-button)):hover { color: var(--cd-mint); }

/* [v2.0.1] The primary button on a navy ground.
 *
 * .cd-btn--primary is navy on navy-on-navy grounds - literally invisible. It
 * only surfaces when a hero's primary action is NOT a giving action, which the
 * v2.0 hero example never was, so it was never hit. Measured on the Programs
 * hero: button background rgb(2,15,63) on hero background rgb(2,15,63).
 *
 * On navy and deep green the primary button inverts. Navy on white is 18.38:1. */
.cd-hero .cd-btn--primary,
.cd-section--navy .cd-btn--primary,
.cd-section--green .cd-btn--primary {
  background: var(--cd-white);
  color: var(--cd-navy);
}
/* The dark-ground hover that used to live here said exactly what the shared
 * primary/ghost hover above now says, so it is gone rather than left restating
 * it in three more selectors. */

/* [v2.0.1] Rule 6 for query-loop text.
 *
 * A Query Loop renders post excerpts through their own blocks, which the core
 * paragraph measure never reaches. Measured on the Events page: excerpts
 * running 74 to 82 characters per line.
 *
 * [v2.1.4] `.wp-block-post-content p` was removed from this list, and it is
 * worth saying why at length because it cost an hour.
 *
 * That selector reads as "paragraphs in post content". It is actually the
 * wrapper WordPress puts around the content of EVERY page, so it matched every
 * paragraph on the site at 0,1,1 - element plus class - which quietly outranks
 * any component that styles itself with a single class. A component could
 * declare max-width and be silently overruled by a rule written for blog
 * excerpts, from a selector naming neither the component nor anything visible
 * in its markup.
 *
 * It cost real time: the statement panel rendered 556px against its own
 * declared width, and the first suspect was a global-styles rule that turned
 * out to be innocent. Removing that global rule did not fix it, because this
 * one was underneath doing the same thing.
 *
 * Nothing is lost by removing it. Core paragraphs inside post content are
 * already covered by the :where(.wp-block-paragraph) default above, which is
 * what that default is for and which components can override by simply having
 * an opinion.
 *
 * The general rule this leaves behind: a measure default belongs on the thing
 * it describes, at zero specificity. Never on a container that happens to
 * contain it. */
:where(.wp-block-post-excerpt__excerpt),
:where(.wp-block-post-excerpt p) {
  max-width: var(--cd-measure-body);
}


/* ══════════════════════════════════════════════════════════════
 * FORM CONTROLS — the one place the cut corner does not go
 * ══════════════════════════════════════════════════════════════ */

/* [v2.0.2] The 120px cut was landing on inputs roughly 40px tall. The browser
 * clamps a radius it cannot fit, so the top-right corner became a near
 * semicircle while the other three stayed at 8px: it read as a broken border,
 * not as the brand shape. The textarea compounded it by putting the resize
 * handle in the opposite corner.
 *
 * The cut is a large-surface device. Below roughly 120px of height it stops
 * reading as a cut and starts reading as a mistake, so form controls take the
 * plain base radius and the buttons carry the brand shape instead.
 * See defect log DL-024. */
.cd-form input,
.cd-form select,
.cd-form textarea,
.nf-form-cont input:not([type="submit"]),
.nf-form-cont select,
.nf-form-cont textarea {
  border-radius: var(--cd-radius-base);
}


/* ══════════════════════════════════════════════════════════════
 * QUERY LOOP — events and posts listings
 * ══════════════════════════════════════════════════════════════ */

/* [v2.0.2] The events listing writes cd-event-row / cd-event-date /
 * cd-event-body / cd-event-thumb into its markup, and not one of those four
 * classes had a rule anywhere in the stylesheet - the same fault as the missing
 * .cd-section-header. The row therefore fell back to a bare flex with an 8px gap
 * and `flex: 0 1 auto` on every child, so the date column collapsed to 35px and
 * broke "Apr 22 2026" onto three lines, while the thumbnail took whatever was
 * left and rendered at its natural size.
 *
 * Measured before: nine thumbnails at nine widths (539, 476, 514, 514, 703, 886,
 * 855, 918, 881) and heights from 303 to 516, each starting at a different left
 * edge, with row heights swinging from 402px to 597px.
 * See defect log DL-009. */
.cd-event-row {
  display: flex;
  align-items: flex-start;
  gap: var(--cd-space-6);
}
.cd-event-date  { flex: 0 0 92px; }
.cd-event-body  { flex: 1 1 auto; min-width: 0; }
.cd-event-thumb { flex: 0 0 360px; }

.cd-event-thumb,
.wp-block-post .wp-block-post-featured-image {
  aspect-ratio: 3 / 2;
  overflow: hidden;
  box-shadow: var(--cd-shadow);
  background: var(--cd-grey-soft);
  /* 360x240: the medium step is 25% of the short side. The old percentage
   * rendered this as a 101px x 67px ellipse. See defect log DL-033. */
  --cd-cut: var(--cd-cut-md);
  margin: 0;
}
.cd-event-thumb img,
.wp-block-post .wp-block-post-featured-image img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

/* [v2.0.2] Correction to the first reading of this defect: the date is not a
 * wrapped string. The markup deliberately emits three elements, cd-ev-mon /
 * cd-ev-day / cd-ev-yr, as a stacked calendar stamp. None of the three had any
 * CSS, so all three rendered as identical 12px mono lines and the stamp read as
 * a broken fragment. Same fault as .cd-section-header and .cd-event-row: a class
 * written into markup that the stylesheet never defined.
 *
 * Styled as intended, the day carries the weight and the month and year frame
 * it. See defect log DL-009. */
.cd-event-date {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  line-height: 1;
  color: var(--cd-ink-meta);
}
.cd-ev-mon, .cd-ev-yr {
  font-family: var(--cd-font-mono);
  font-size: var(--cd-meta-size);
  letter-spacing: var(--cd-meta-track);
  text-transform: uppercase;
}
.cd-ev-day {
  font-family: var(--cd-font-display);
  font-size: 40px;
  line-height: 1;
  letter-spacing: -0.02em;
  color: var(--cd-navy);
  margin-block: 2px;
}
.wp-block-post .wp-block-post-date:not(.cd-ev-mon):not(.cd-ev-day):not(.cd-ev-yr) {
  white-space: nowrap;
  font-family: var(--cd-font-mono);
  font-size: var(--cd-meta-size);
  letter-spacing: var(--cd-meta-track);
  color: var(--cd-ink-meta);
}

/* Rows butted together with a 0px gap once the thumbnails were on a grid, so
 * four uniform 240px images formed one unbroken column of navy. A rule and real
 * vertical space separate one event from the next. */
.wp-block-post-template > .wp-block-post + .wp-block-post { margin-block-start: 0; }
.cd-event-row {
  padding-block: var(--cd-space-6);
  border-top: 1px solid var(--cd-rule-light);
}
.wp-block-post:last-child .cd-event-row { border-bottom: 1px solid var(--cd-rule-light); }

/* Post titles in a listing are not section titles: 54px display caps wrapped
 * "Children's Week 2025 - Art Workshop for Children" onto six lines. */
.wp-block-post .wp-block-post-title {
  font-size: var(--cd-card-size);
  line-height: var(--cd-card-lh);
  letter-spacing: var(--cd-card-track);
  max-width: var(--cd-measure-body);
  margin-block: 0 var(--cd-space-3);
}

@media (max-width: 780px) {
  .cd-event-row  { flex-wrap: wrap; }
  .cd-event-thumb{ flex: 1 0 100%; order: 3; }
}

/* [v2.0.4] POST ROW.
 *
 * The blog index was rendering Twentig's stock two-column grid, so a single
 * post got a 672 x 448 image: half the page width and the loudest thing on it,
 * for a post whose own excerpt is two lines. A listing thumbnail is a cue to
 * help you choose, not the content itself.
 *
 * Same skeleton as .cd-event-row so the two listings read as one family, with
 * two differences that suit an article rather than an event: no calendar stamp,
 * because a post's date is reference not headline, and the category leads the
 * meta line instead. Thumbnail 280px against the event listing's 360px, since
 * a post row carries an excerpt and the picture should not outweigh it. */
.cd-post-row {
  display: flex;
  align-items: flex-start;
  gap: var(--cd-space-7);
  padding-block: var(--cd-space-6);
  border-top: 1px solid var(--cd-rule-light);
}
.wp-block-post:last-child .cd-post-row { border-bottom: 1px solid var(--cd-rule-light); }

.cd-post-body  { flex: 1 1 auto; min-width: 0; }
.cd-post-thumb {
  flex: 0 0 280px;
  aspect-ratio: 3 / 2;
  overflow: hidden;
  margin: 0;
  --cd-cut: var(--cd-cut-md);
  /* [v2.0.5] A listing thumbnail is an editorial image and lifts off a light
   * ground like every other one. .cd-figure-img has carried --cd-shadow since
   * v2.0; the two listing thumbnails were simply never given it, so posts and
   * events sat flat on the page while the theme-page figures floated.
   *
   * The grey fill is the same placeholder .cd-figure-img uses: it holds the
   * shape while the image loads, and gives the box something to be if a post
   * has no photograph. Suppressed on coloured grounds by the rule 5 block
   * above. */
  box-shadow: var(--cd-shadow);
  background: var(--cd-grey-soft);
}
.cd-post-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }

.cd-post-meta {
  /* [v2.0.6] Declares its own flex. It had been relying on the `is-layout-flex`
   * class WordPress generates for a group block, which meant the component only
   * held its shape in that one markup context: written by hand, as on the
   * kitchen sink page, the category and date stacked and the separator dropped
   * to its own line. A component should not depend on who wrote its wrapper. */
  display: flex;
  flex-wrap: wrap;
  gap: var(--cd-space-3);
  align-items: baseline;
  font-family: var(--cd-font-mono);
  font-size: var(--cd-meta-size);
  letter-spacing: var(--cd-meta-track);
  text-transform: uppercase;
  color: var(--cd-ink-meta);
  margin-block-end: var(--cd-space-3);
}
.cd-post-meta a { color: inherit; }
/* Category and date are separate blocks with only a flex gap between them, so
 * "NEWS 10 MAY 2026" read as one string. Same separator as the photo credit. */
.cd-post-meta > * + *::before { content: "\00B7"; margin-right: var(--cd-space-3); }
.cd-post-title  { margin-block: 0 var(--cd-space-3); max-width: var(--cd-measure-body); }
.cd-post-excerpt{ margin-block: 0; max-width: var(--cd-measure-body); }

@media (max-width: 780px) {
  .cd-post-row   { flex-wrap: wrap; gap: var(--cd-space-5); }
  .cd-post-thumb { flex: 1 0 100%; order: 3; }
}

/* [v2.0.2] The contact form rendered roughly 370px wide inside a 1344px band,
 * leaving the right two thirds of the site's primary conversion page empty.
 * The contact detail cards used to sit in their own band below, doing nothing
 * for the form; side by side they balance it and the page loses a whole
 * redundant section. See defect log DL-011. */
.cd-contact-cols { gap: var(--cd-space-10); align-items: flex-start; }
/* [v2.5.1] The detail panels stack in this column rather than sitting side by
 * side, so the grid gap is doing the work of vertical rhythm, not of a gutter.
 * 24px is right between cards in a row and too tight between two stacked
 * panels: 1cm more, per the client, so 62px. The ABN line takes the same
 * clearance from the panel above it, up from 15px, so the block below reads as
 * separate from the panels rather than attached to the last one. */
.cd-contact-cols .cd-grid--2 { grid-template-columns: 1fr; gap: 62px; }
.cd-contact-cols .cd-grid--2 + .cd-meta { margin-top: 53px; }
@media (max-width: 900px) {
  .cd-contact-cols { gap: var(--cd-space-7); }
}

/* ══════════════════════════════════════════════════════════════
 * PARTNER LOGOS
 * ══════════════════════════════════════════════════════════════ */

/* [v2.0.2] The partners pattern was never migrated to the design language. It
 * carried its own inline <style> block, a nested grey panel sitting inside a
 * white band (a panel within a panel), square corners against a system built on
 * one cut corner, a centred heading and a centred italic footnote on a site with
 * no other centred text, and logos at wildly different optical sizes because
 * each was only capped by max-height.
 *
 * A fixed optical box is what makes six marks of different proportions read as
 * one row: each logo is centred in the same area and constrained on both axes,
 * so a wide wordmark and a square roundel carry equal weight.
 * See defect log DL-016 and DL-025. */
.cd-partners {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--cd-grid-gap);
  list-style: none;
  margin: var(--cd-space-7) 0 0;
  padding: 0;
  /* [v2.0.9] Capped rather than filling the 1328px content width. Three square
   * tiles across the full width computed to 427px each, which is a lot of white
   * around a logo. 1008px gives 320px tiles, a quarter smaller: (1008 - 2 x 24)
   * / 3. The cap only binds on desktop; the 2- and 1-column steps below still
   * fill whatever width they are given. */
  max-width: 1008px;
}
/* [v2.0.9] Square tiles, centred content, and the shadow every other white
 * object on a light ground carries.
 *
 * The tile was a wide short slab with its label and mark pushed to the left
 * edge, which read as a fragment of a card rather than a tile. A square is the
 * neutral frame for marks of wildly different proportions: nothing about a
 * logo's own aspect gets to set the box.
 *
 * On centring and the cut: the arc is 60px in the top-right corner, so the
 * padding has to clear it or the label runs under the curve. Padding is
 * therefore --cd-cut rather than a spacing step: it is derived from the corner
 * it has to avoid, so changing the cut cannot silently reintroduce the overlap.
 * See defect log DL-045. */
.cd-partner {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  gap: var(--cd-space-3);
  background: var(--cd-white);
  --cd-cut: var(--cd-cut-md);
  aspect-ratio: 1 / 1;
  padding: var(--cd-cut);
  box-shadow: var(--cd-shadow);
}
.cd-partner__kind {
  font-family: var(--cd-font-display);
  font-size: var(--cd-pill-size);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--cd-ink-meta);
}
.cd-partner__mark {
  display: flex;
  align-items: center;
  justify-content: center;
  /* [v2.0.9] `align-self: stretch` is load-bearing, not cosmetic. Three of the
   * six partner SVGs (brimbank, donatelife, good-things) carry a viewBox and no
   * width/height attributes, so they have an intrinsic ratio but no intrinsic
   * size. Centring the tile's column made this span shrink to its content, the
   * content had no width to give, and those three logos collapsed to nothing
   * while the three with real width/height attributes carried on rendering. A
   * blank box is the worst possible failure here: the tile still looks
   * deliberate. See defect log DL-045.
   *
   * [v2.0.3] Height was 104px against a short tile. The tile is now square, so
   * the mark takes the room the label does not. */
  align-self: stretch;   /* full WIDTH: the cross axis of the tile's column */
  height: 99px;          /* a definite main-axis size, so the tile can centre
                          * label and mark together as one group rather than
                          * letting the mark absorb the free space and strand
                          * the label at the top edge */
}
.cd-partner__mark img {
  /* Raised from 64px: a 64px mark inside the square read as an accident rather
   * than a composition. Scaled with the tile when it came down to 320px, so the
   * mark holds the same share of the box it was tuned to. */
  max-height: 99px;
  max-width: 100%;
  width: auto;
  height: auto;
  object-fit: contain;
}
@media (max-width: 900px) { .cd-partners { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 560px) { .cd-partners { grid-template-columns: 1fr; } }

/* ══════════════════════════════════════════════════════════════
 * NOTICE — a caveat the visitor must not miss
 * ══════════════════════════════════════════════════════════════ */

/* [v2.0.2] "The donate button is not yet connected" is the single most important
 * fact on the donate page and rendered as ordinary body copy with a bold lead-in,
 * below the amount cards, where it read as an afterthought. A visitor could pick
 * an amount, press the button and find nothing happens.
 *
 * The ember accent is the system's one attention colour and this is exactly the
 * job it exists for. See defect log DL-029. */
.cd-notice {
  border-left: 4px solid var(--cd-ember);
  background: var(--cd-white);
  border-radius: var(--cd-radius-base);
  padding: var(--cd-space-5) var(--cd-space-6);
  max-width: var(--cd-measure-body);
  margin-block-start: var(--cd-space-7);
}
.cd-notice__label {
  font-family: var(--cd-font-display);
  font-size: var(--cd-pill-size);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--cd-ink-meta);
  margin: 0 0 var(--cd-space-3);
}
.cd-notice p:last-child { margin: 0; }

/* ══════════════════════════════════════════════════════════════
 * PROGRAM — one program beside its photograph
 * ══════════════════════════════════════════════════════════════ */

/* [v2.0.4] The theme pages stacked pill, title, body, then a full content-width
 * photograph, and repeated that identical slab up to five times per page.
 * Measured: the image was 61% to 77% of every block, every block came out about
 * 1400px tall whether the body ran 118 characters or 367, and the fifteen
 * programs across six pages took 32,113px of scroll between them. Nothing
 * distinguished one program from the next except the ground colour.
 *
 * Media beside text roughly halves that. Alternating the side turns a queue
 * into a rhythm, which is the whole difference between a list and a spread.
 *
 * Source order is always text then media; the visual swap is done with
 * `order`, so the reading and screen-reader order never changes. */
.cd-program {
  display: grid;
  grid-template-columns: 1fr 46%;
  gap: var(--cd-space-10);
  align-items: start;
}
.cd-program--media-left .cd-program__text  { order: 2; }
.cd-program--media-left .cd-program__media { order: 1; }
.cd-program--media-left { grid-template-columns: 46% 1fr; }

/* [v2.2.1] A mark panel takes half the column a photograph does: 23% of the
 * band is about 305px against the photograph's 611px. A photograph needs its
 * size to show anything; a mark is legible small, and at 611px it was reading
 * as the subject of the block rather than as its label. The text column gains
 * the difference. */
/* Columns sized to their contents, not to shares of the band. The program body
 * is capped at the 66-character measure, so a 1fr text column left roughly
 * 300px of dead space between the last word and the panel: a gap between two
 * elements reads as a fault, where the same space at the outer edge reads as
 * margin. Sizing both columns and packing them to the start puts the whitespace
 * outside the pair. The media-left rows already read correctly for this reason,
 * because there the text simply follows the panel. */
.cd-program--mark {
  grid-template-columns: 3fr 1fr;
}
.cd-program--mark.cd-program--media-left {
  grid-template-columns: 1fr 3fr;
}
/* [v2.2.2] The text column has to actually fill its three quarters, or the row
 * is 75/25 on paper and 75% of that on screen.
 *
 * .cd-program__text is a flex column with align-items: flex-start, so its
 * children are shrink-to-fit; the body was additionally capped at the
 * 66-character measure. Together those left the text occupying about 556px of a
 * 936px column and the pair covering three quarters of the band, which is what
 * the row looked like: correctly proportioned and short of the edge.
 *
 * Stretching the children and lifting the body cap fills it. The measure this
 * gives up is real: roughly 110 characters a line against 66. It is a deliberate
 * trade for the composition, it applies to mark rows only, and a photograph row
 * keeps the reading measure. See defect log DL-057. */
.cd-program--mark .cd-program__text { align-items: stretch; }
.cd-program--mark .cd-program__body { max-width: none; }
.cd-program--mark .cd-program__title { max-width: 26ch; }
.cd-program--mark .cd-pill,
.cd-program--mark .cd-btn--text { align-self: flex-start; }
/* The facts strip carries the same 556px cap, so its rules stopped short of the
 * body's right edge and the block read as two different column widths stacked.
 * Widened to match, which also lets each value sit on one line. */
.cd-program--mark .cd-facts { max-width: none; }

.cd-program__text {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--cd-space-5);
}
.cd-program__title {
  font-family: var(--cd-font-display);
  font-size: var(--cd-feature-size);
  line-height: var(--cd-feature-lh);
  letter-spacing: var(--cd-feature-track);
  text-transform: uppercase;
  max-width: 18ch;
  margin: 0;
}
.cd-program__body {
  font-size: var(--cd-body-size);
  line-height: var(--cd-body-lh);
  max-width: var(--cd-measure-body);
  margin: 0;
}

/* A fixed crop. Without one an uploaded portrait sets the block height: the
 * cultural page was carrying a 1328 x 1771 image, 77% of its block. */
.cd-program__media { margin: 0; }
.cd-program__media .cd-figure-img { aspect-ratio: 4 / 3; }

/* [v2.2] MARK PANEL - the media side when there is no photograph worth showing.
 *
 * Square on purpose. The figure beside it is 4/3, and a square is the shape
 * that says "this is a mark, not a cropped photograph": nothing about it
 * pretends to be a window onto something.
 *
 * The mark is painted from currentColor off the panel's own ink, so a gold
 * panel gets a navy mark and the other five get near-white, from one white
 * file, exactly as the cards and heroes do.
 *
 * Sized as a share of the panel rather than in pixels, so it holds its
 * proportion at every column width without a breakpoint to maintain. */
.cd-program__media .cd-mark-panel {
  aspect-ratio: 1 / 1;
  display: grid;
  place-items: center;
  --cd-cut: var(--cd-cut-lg);
  box-shadow: var(--cd-shadow);
}
.cd-mark-panel--teal   { background: var(--cd-teal);   color: var(--cd-on-teal); }
.cd-mark-panel--moss   { background: var(--cd-moss);   color: var(--cd-on-moss); }
.cd-mark-panel--plum   { background: var(--cd-plum);   color: var(--cd-on-plum); }
.cd-mark-panel--maroon { background: var(--cd-maroon); color: var(--cd-on-maroon); }
.cd-mark-panel--brown  { background: var(--cd-brown);  color: var(--cd-on-brown); }
.cd-mark-panel--gold   { background: var(--cd-gold);   color: var(--cd-on-gold); }
.cd-mark-panel--navy   { background: var(--cd-navy);   color: var(--cd-on-navy); }
.cd-mark-panel--green  { background: var(--cd-green);  color: var(--cd-on-green); }
.cd-mark-panel--blue   { background: var(--cd-blue);   color: var(--cd-on-blue); }
.cd-mark-panel__mark {
  width: 52%;
  aspect-ratio: 1 / 1;
  -webkit-mask: var(--cd-icon) center / contain no-repeat;
          mask: var(--cd-icon) center / contain no-repeat;
  background-color: currentColor;
}

/* ── The facts strip ──────────────────────────────────────────
 * The old layout had nowhere to put the practical detail, so it carried none:
 * a visitor could read a whole program and still not know who it is for,
 * when it runs, where, or whether it costs anything. Two columns of small
 * label-and-value pairs, which is the one piece of furniture a program page
 * genuinely needs and the most magazine thing on the page. */
.cd-facts {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--cd-space-4) var(--cd-space-6);
  margin: 0;
  padding-block: var(--cd-space-5);
  border-top: 1px solid var(--cd-rule-light);
  border-bottom: 1px solid var(--cd-rule-light);
  width: 100%;
  max-width: var(--cd-measure-body);
}
.cd-fact dt {
  font-family: var(--cd-font-mono);
  font-size: var(--cd-meta-size);
  letter-spacing: var(--cd-meta-track);
  text-transform: uppercase;
  color: var(--cd-ink-meta);
  margin: 0 0 4px;
}
.cd-fact dd {
  font-size: var(--cd-small-size);
  line-height: var(--cd-small-lh);
  margin: 0;
}
.cd-section--navy .cd-facts, .cd-section--green .cd-facts { border-color: var(--cd-rule-dark); }
.cd-section--navy .cd-fact dt { color: var(--cd-navy-meta); }

@media (max-width: 900px) {
  .cd-program,
  .cd-program--media-left,
  .cd-program--mark,
  .cd-program--mark.cd-program--media-left { grid-template-columns: 1fr; gap: var(--cd-space-7); }
  /* [v2.2.1] Capped on mobile too. Left to fill a stacked column the panel came
   * out at 350px, larger than the 305px it takes on a 1440px desktop, which
   * inverts the whole point of shrinking it: a mark should not become the
   * biggest thing on the page on the smallest screen. */
  .cd-program--mark .cd-mark-panel { max-width: 220px; }
  /* Stacked, the photograph always follows the text it belongs to. */
  .cd-program--media-left .cd-program__text  { order: 1; }
  .cd-program--media-left .cd-program__media { order: 2; }
}
@media (max-width: 560px) {
  .cd-facts { grid-template-columns: 1fr; }
}

/* ══════════════════════════════════════════════════════════════
 * ON-PAGE INDEX — a way into a page that carries several programs
 * ══════════════════════════════════════════════════════════════ */

/* [v2.0.4] The theme pages run up to five programs and offered no way to
 * reach any of them but scrolling: 7,240px of it on wellbeing. A short index at
 * the top is the cheapest possible fix and doubles as a statement of what the
 * page contains. */
.cd-onpage__label {
  font-family: var(--cd-font-mono);
  font-size: var(--cd-meta-size);
  letter-spacing: var(--cd-meta-track);
  text-transform: uppercase;
  color: var(--cd-ink-meta);
  margin: 0 0 var(--cd-space-4);
}
.cd-onpage__list {
  display: flex;
  flex-wrap: wrap;
  gap: var(--cd-space-3) var(--cd-space-6);
  counter-reset: cd-idx;
  list-style: none;
  margin: 0;
  padding: 0;
}
.cd-onpage__list li { counter-increment: cd-idx; }
.cd-onpage__list li::before {
  content: counter(cd-idx, decimal-leading-zero);
  font-family: var(--cd-font-mono);
  font-size: var(--cd-meta-size);
  letter-spacing: var(--cd-meta-track);
  color: var(--cd-ink-meta);
  margin-right: var(--cd-space-3);
}
.cd-onpage__list a {
  font-family: var(--cd-font-display);
  font-size: var(--cd-pill-size);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: currentColor;
  text-decoration: underline;
  text-underline-offset: 4px;
  text-decoration-thickness: 2px;
}

/* [DL-070] An anchored section must not land under the sticky header.
 *
 * This rule predates the header actually being sticky, and its 56px was a guess
 * at a header that did not exist yet. The header is 107px, so every one of the
 * 15 fragment links from the programs index into the theme pages
 * (/wellbeing/#music-for-mindfulness) would have put its target 51px underneath
 * the thing that is meant to be helping.
 *
 * Measured header heights plus a little air so the heading is not flush against
 * the header's edge: 107 + 24, and 90 + 24 below 560px where the header shrinks.
 *
 * scroll-margin on the target rather than scroll-padding on the scrollport,
 * because the two ADD - setting both would have pushed anchors 187px down. */
[id] { scroll-margin-top: 131px; }
@media (max-width: 560px) { [id] { scroll-margin-top: 114px; } }


/* ══════════════════════════════════════════════════════════════
 * SINGLE POST / EVENT  [v2.6]
 *
 * Posts and events fell through to the parent theme's stock single.html and
 * were the only pages on the site not built from this language: a bare title,
 * a 3/2 featured image at full content width, and no giving band. The child
 * template composes the same core blocks the blog index uses and styles them
 * here, exactly as home.html does (DL-034).
 *
 * The opening is not cd/hero and cannot be: a hero takes its words as
 * attributes, and these come from the post. It is a navy band carrying the core
 * post blocks, dressed to read as one. See defect log DL-066.
 * ══════════════════════════════════════════════════════════════ */

.cd-posthead { padding-block: var(--cd-section-y-hero); }
.cd-posthead__meta {
  gap: var(--cd-space-5);
  align-items: center;
  margin-block-end: var(--cd-space-6);
}
/* post-terms renders a div of links; this is the pill treatment applied to it
 * rather than a cd/pill, because the term comes from the post. */
.cd-posthead__cat {
  display: inline-flex;
  align-items: center;
  padding: 7px 16px;
  border-radius: var(--cd-radius-pill);
  background: var(--cd-blue-soft);
  color: var(--cd-navy);
  font-family: var(--cd-font-display);
  font-size: var(--cd-pill-size);
  letter-spacing: var(--cd-pill-track);
  text-transform: uppercase;
}
.cd-posthead__cat a { color: inherit; text-decoration: none; }
.cd-posthead__date {
  font-family: var(--cd-font-mono);
  font-size: var(--cd-meta-size);
  letter-spacing: var(--cd-meta-track);
  text-transform: uppercase;
  color: var(--cd-navy-meta);
  margin: 0;
}
.cd-posthead__title {
  font-family: var(--cd-font-display);
  font-size: var(--cd-hero-size);
  line-height: var(--cd-hero-lh);
  letter-spacing: var(--cd-hero-track);
  text-transform: uppercase;
  color: var(--cd-on-navy);
  max-width: var(--cd-measure-hero);
  overflow-wrap: break-word;
  margin: 0;
}

/* The featured image, at the size the client set: no larger than the Contribute
 * split panel, so it illustrates the post instead of announcing it. It was
 * running the full content width above the article. The cut and the shadow are
 * the figure treatment, so it reads as an object rather than a banner. */
.cd-post-figure {
  max-width: clamp(300px, 48%, 630px);
  /* Centred. The article below it now runs the full band, so a figure hugging
   * the left edge read as the leftover of a narrower column that is no longer
   * there. */
  margin: 0 auto var(--cd-space-8);
  overflow: hidden;
  --cd-cut: var(--cd-cut-lg);
  border-radius: var(--cd-radius-base) var(--cd-cut)
                 var(--cd-radius-base) var(--cd-radius-base);
  box-shadow: var(--cd-shadow);
  background: var(--cd-grey-soft);
}
.cd-post-figure img { width: 100%; height: 100%; object-fit: cover; display: block; }

/* Previous / next. Two links that have to sit at opposite ends and still read
 * as a pair, so they share a treatment and differ only in which way they point. */
.cd-postnav { gap: var(--cd-space-6); align-items: baseline; }
.cd-postnav a {
  font-family: var(--cd-font-display);
  font-size: 15px;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--cd-navy);
  text-decoration: none;
  border-bottom: 2px solid currentColor;
  padding-bottom: 2px;
}
.cd-postnav a:hover { color: var(--cd-blue); }
.cd-postnav__next { margin-left: auto; text-align: right; }

/* [v2.6] A post title is not an authored headline: it is whatever someone typed,
 * and the template has to hold any length. A fixed size cannot. At 56px on a
 * 390px screen "CELEBRATION" needs about 407px against 350 available, so
 * break-word split it as CELEBRATI / ON.
 *
 * Fluid sizing is right here and would be wrong on a page hero, where the words
 * are chosen and a designer can see them. Here nobody sees the words until
 * after they are published. */
@media (max-width: 700px) {
  .cd-posthead__title { font-size: clamp(34px, 11vw, 56px); }
  .cd-post-figure { max-width: none; }
  .cd-postnav__next { margin-left: 0; text-align: left; }
}


/* [v2.7] 404. The one page nobody plans and everybody eventually sees.
 *
 * Built from the same blocks as everything else rather than left to the parent
 * theme's stock pattern: a hero, a split section, and the giving band. The
 * split's first column holds the message and the way out, which is the whole
 * job of this page: say what happened, then hand back a route.
 *
 * The statement is the serif variant, because the tone here is warmer than the
 * rest of the site and the serif is where this language keeps its warmth. */
.cd-404__say { display: flex; flex-direction: column; align-items: flex-start; gap: var(--cd-space-7); }
.cd-404__say .cd-statement--serif { margin-block: 0; }

/* The split's image track is sized for the landscape panels used everywhere
 * else. This photograph is portrait (0.80), so at the full 630px track it
 * stands 787px tall against a left column of about 150px, and the section
 * turns into a tall picture with a caption stranded at the top of it.
 *
 * Two corrections, both local to this page: cap the panel so the joke stays a
 * joke rather than a billboard, and centre the row so the message sits level
 * with the pup's face instead of floating above it. */
.cd-section__inner:has(> .cd-404__say) { align-items: center; }
.cd-section__inner:has(> .cd-404__say) > .cd-figure-wrap {
  max-width: 460px;
  justify-self: center;
  margin-inline: auto;
}

/* ══════════════════════════════════════════════════════════════
 * ARCHIVE HEAD AND POST CARDS
 *
 * Category, tag, author and date archives were the last surfaces still
 * rendering from the parent theme. The child theme had no archive template, so
 * /category/events/ fell through to Twenty Twenty-Five's archive.html: a bare
 * query title colliding with the sticky header, a two-up loop with no panel,
 * full-bleed featured images running edge to edge of each column, and rows of
 * unequal height because nothing tied the cards to each other. Same blind spot
 * as DL-034 (the blog index) and DL-066 (single posts): an archive is not a
 * page, so it never appeared in a page-by-page sweep.
 *
 * Both parts below are CSS components over core query-loop blocks, not cd/*
 * blocks. A cd/* block takes static attributes and cannot read the post being
 * looped; only core's post-title, post-date, post-terms and friends can. This
 * is the same technique .cd-post-row already uses, which is why the listing
 * vocabulary here (.cd-post-meta, .cd-post-title, .cd-post-excerpt) is shared
 * with it rather than duplicated. See defect log DL-068.
 * ══════════════════════════════════════════════════════════════ */

/* ── The pseudo-hero ──────────────────────────────────────────
 * An archive title is dynamic, so cd/hero (which takes a static headline) is
 * out. This is the .cd-posthead treatment applied to core's query-title: the
 * same navy band, the same opening height, the same type role.
 *
 * The prefix span stays and is styled as the eyebrow rather than hidden. It
 * carries the one piece of information the title alone cannot: /category/events/
 * and the /events/ page would otherwise both open with the word EVENTS. It also
 * keeps this template honest for tag, author and date archives, which need
 * "Tag:", "Author:" and "Archives:" respectively and would need three separate
 * templates if the label were hard-coded. */
.cd-archive-head { padding-block: var(--cd-section-y-hero); }
/* get_the_archive_title() returns `Category: <span>Events</span>`: the label is
 * a bare text node and only the term name is wrapped. So the eyebrow treatment
 * goes on the heading itself and the title treatment on the span - the reverse
 * of the obvious way round, and the reason there is no prefix class to hook. */
.cd-archive-head__title {
  font-family: var(--cd-font-mono);
  font-size: var(--cd-meta-size);
  line-height: 1;
  letter-spacing: var(--cd-meta-track);
  text-transform: uppercase;
  color: var(--cd-navy-meta);
  margin: 0;
}
.cd-archive-head__title span {
  display: block;
  font-family: var(--cd-font-display);
  font-size: var(--cd-hero-size);
  line-height: var(--cd-hero-lh);
  letter-spacing: var(--cd-hero-track);
  text-transform: uppercase;
  color: var(--cd-on-navy);
  max-width: var(--cd-measure-hero);
  overflow-wrap: break-word;
  margin-block-start: var(--cd-space-5);
}
.cd-archive-head__desc {
  font-size: var(--cd-standfirst-size);
  line-height: var(--cd-standfirst-lh);
  color: var(--cd-navy-body);
  max-width: var(--cd-measure-wide);
  margin-block: var(--cd-space-6) 0;
}
.cd-archive-head__desc p { margin: 0; max-width: none; }

/* ── The grid ─────────────────────────────────────────────────
 * Three across at the client's 1cm gutter. post-template renders a <ul>, so the
 * list affordances have to be cleared before it can be a grid. */
.cd-post-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--cd-gap-1cm);
  list-style: none;
  margin: 0;
  padding: 0;
}

/* ── The card ─────────────────────────────────────────────────
 * The <li> post-template emits IS the panel. There is no inner wrapper group,
 * because post-template gives its children no class we could hook and adding a
 * group would put a second box inside the box for nothing.
 *
 * subgrid for the same reason as DL-008: three cards in a row are one row, and
 * the alignment constraint lives between them, so it is expressed between them.
 * Four tracks - image, meta, title, excerpt - so a title that wraps to three
 * lines in one card does not push its excerpt below its neighbour's. row-gap is
 * restated here because a subgrid item inherits the parent's gap otherwise, and
 * the 1cm gutter between cards is not the rhythm wanted inside one.
 *
 * Padding sits on the children rather than on the panel: the photograph has to
 * reach the panel edge while the copy is inset, and a padded grid container
 * cannot do both. */
.cd-post-grid > .wp-block-post {
  display: flex;
  flex-direction: column;
  gap: var(--cd-space-4);
  background: var(--cd-white);
  color: var(--cd-ink);
  box-shadow: var(--cd-shadow-soft);
  overflow: hidden;
  --cd-cut: var(--cd-cut-md);
  border-radius: var(--cd-radius-base) var(--cd-cut)
                 var(--cd-radius-base) var(--cd-radius-base);
}
@supports (grid-template-rows: subgrid) {
  .cd-post-grid > .wp-block-post {
    display: grid;
    grid-row: span 5;
    grid-template-rows: subgrid;
    row-gap: var(--cd-space-4);
  }
  /* Five tracks, not four, because post-excerpt emits two paragraphs - the
   * excerpt and the more-link - and the link is the card's action. Left in one
   * track it floats up under a short excerpt and down under a long one, so
   * three cards of equal height still read as three unrelated boxes at the one
   * point the reader is being asked to click. A nested subgrid puts the link on
   * its own line across the whole row. */
  .cd-post-grid .cd-post-excerpt {
    display: grid;
    grid-row: span 2;
    grid-template-rows: subgrid;
    row-gap: var(--cd-space-4);
    align-content: start;
  }
}
.cd-post-grid > .wp-block-post > * { padding-inline: 26px; }
.cd-post-grid > .wp-block-post > *:last-child { padding-block-end: 28px; }

/* The photograph. It is the panel's own top edge, so it takes the panel's cut
 * by being clipped to it rather than carrying one of its own - the shared rule
 * near the top of this file gives every .wp-block-post featured image a cut,
 * which inside a panel would paint a second arc a few pixels inside the first. */
.cd-post-grid > .wp-block-post > .wp-block-post-featured-image {
  padding-inline: 0;
  margin: 0;
  border-radius: 0;
  background: var(--cd-grey-soft);
}
.cd-post-grid > .wp-block-post > .wp-block-post-featured-image img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}
/* Every post carries a featured image today. If one ever does not, the block is
 * omitted entirely and the card's four subgrid tracks would shift up by one,
 * pulling that card out of line with the rest of its row. Holding the track is
 * cheaper than discovering it. */
.cd-post-grid > .wp-block-post:not(:has(> .wp-block-post-featured-image))::before {
  content: "";
  display: block;
  aspect-ratio: 3 / 2;
  background: var(--cd-grey-soft);
}

.cd-post-grid .cd-post-meta { margin-block-end: 0; }
.cd-post-grid .cd-post-title {
  font-family: var(--cd-font-display);
  font-size: var(--cd-post-card-size);
  line-height: var(--cd-post-card-lh);
  letter-spacing: var(--cd-post-card-track);
  text-transform: uppercase;
  max-width: none;
  margin: 0;
}
.cd-post-grid .cd-post-title a { color: inherit; text-decoration: none; }
.cd-post-grid .cd-post-title a:hover { text-decoration: underline; }
.cd-post-grid .cd-post-excerpt {
  max-width: none;
  font-size: var(--cd-small-size);
  line-height: var(--cd-small-lh);
  color: var(--cd-ink-muted);
}
.cd-post-grid .cd-post-excerpt p { margin: 0; max-width: none; }
.cd-post-grid .wp-block-post-excerpt__more-text { margin-block-start: var(--cd-space-4); }
/* currentColor and an underline, not blue: this link is 12px, so it is neither
 * of WCAG's large-text cases and the 3:1 allowance never applies to it. Same
 * reasoning, and the same fix, as .cd-btn--text - see defect 8. */
.cd-post-grid .wp-block-post-excerpt__more-text a {
  font-family: var(--cd-font-mono);
  font-size: var(--cd-meta-size);
  letter-spacing: var(--cd-meta-track);
  text-transform: uppercase;
  color: currentColor;
  text-decoration: underline;
  text-underline-offset: 4px;
  text-decoration-thickness: 2px;
}
.cd-post-grid .wp-block-post-excerpt__more-text a:hover { text-decoration-thickness: 3px; }

@media (max-width: 1100px) { .cd-post-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 700px)  { .cd-post-grid { grid-template-columns: 1fr; } }

/* ── Pagination ───────────────────────────────────────────────
 * Its own blue-soft band, per the client, matching the prev/next band on single
 * posts: the same "where next" job gets the same ground wherever it appears.
 * It had no styling of any kind before this - core defaults on the blog index. */
.cd-pagination {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--cd-space-5);
  font-family: var(--cd-font-mono);
  font-size: var(--cd-meta-size);
  letter-spacing: var(--cd-meta-track);
  text-transform: uppercase;
}
.cd-pagination a, .cd-pagination .page-numbers { color: var(--cd-navy); text-decoration: none; }
.cd-pagination a:hover { text-decoration: underline; }
.cd-pagination .wp-block-query-pagination-numbers { display: flex; align-items: center; gap: var(--cd-space-4); }
.cd-pagination .page-numbers.current {
  display: inline-flex;
  align-items: center;
  padding: 5px 12px;
  border-radius: var(--cd-radius-pill);
  background: var(--cd-navy);
  color: var(--cd-on-navy);
}

/* A band with nothing in it is a stripe of colour, not a section. query-pagination
 * renders nothing at all when the query fits on one page, which leaves the
 * blue-soft band around it painting 112px of tint between the last card and the
 * giving band on any category with fewer than nine posts - /category/news/ has
 * four. Written against the section rather than the pagination so it holds for
 * any block that can render itself away. */
.cd-section:not(:has(> .cd-section__inner > *)) { display: none; }

/* An archive title is a term name: like .cd-posthead__title it is content
 * nobody sees at the size it will be set until after it is published, and
 * EVENTS duly broke as EVEN / TS at 390px. It takes the same fluid treatment
 * single post titles do.
 *
 * These live here rather than in the RESPONSIVE TYPE block above because
 * `.cd-archive-head__title span` is 0,1,1 in both places and the base rule is
 * further down the file. Equal specificity, later wins, and the media query
 * would silently do nothing - which is exactly what it did the first time.
 * Anything overriding this component belongs after this component. */
@media (max-width: 1200px) {
  .cd-archive-head__title span { font-size: clamp(56px, 8vw, 104px); }
}
@media (max-width: 700px) {
  .cd-archive-head__title span { font-size: clamp(34px, 11vw, 56px); }
  .cd-archive-head { padding-block: var(--cd-section-y-mob); }
}

/* ══════════════════════════════════════════════════════════════
 * STICKY HEADER
 *
 * Asked for by the client, and outstanding since the May critique flagged it
 * as issue 1: all four benchmark sites pin their header, and Programs is about
 * 10,000px tall, so the logo, the navigation and the whole giving path left the
 * screen within one scroll and did not come back.
 *
 * Twentig One already has the plumbing - it applies exactly this rule to a
 * header template part containing a group with `is-position-sticky`, the class
 * the editor's Position control writes. This restates it against our own header
 * class rather than opting in through that control, for the DL-002 reason: an
 * editor attribute can be cleared by anyone editing the header part, and a
 * guarantee the whole site depends on should not be one checkbox away from
 * disappearing.
 *
 * Sticky goes on the OUTER template-part element, not on .cd-header. A sticky
 * box travels only within its own parent's padding box, and .cd-header's parent
 * is exactly as tall as .cd-header, so sticky applied there has nowhere to go
 * and silently does nothing.
 *
 * `top` inherits the admin-bar variable so the pinned header sits below the WP
 * toolbar for logged-in editors instead of underneath it.
 * ══════════════════════════════════════════════════════════════ */
header.wp-block-template-part:has(> .cd-header) {
  position: sticky;
  top: var(--wp-admin--admin-bar--height, 0px);
  z-index: 101;
}

/* The header ground is grey-ghost, and it scrolls over white and chalk bands
 * that are within a couple of percent of it. Without an edge the pinned header
 * dissolves into the page behind it: the hairline and the shadow are what make
 * it read as a layer above the content rather than part of it.
 *
 * Both are unconditional rather than applied on scroll, because detecting
 * "stuck" needs JavaScript and this theme ships none. At rest the header sits
 * on the hero's own edge, where a 1px rule is invisible anyway. */
.cd-header {
  border-bottom: 1px solid var(--cd-rule-light);
  box-shadow: 0 6px 18px -12px rgba(2, 15, 63, 0.35);
}


/* ══════════════════════════════════════════════════════════════
 * THE PROGRAMS PANEL  [DL-071]
 *
 * The Programs dropdown was six bare links in a narrow column, on a site where
 * every one of those six theme pages already has a colour of its own from the
 * Extra 6 and an icon of its own in the hero. None of that reached the one place
 * a reader chooses between them.
 *
 * It is now a panel: a row of copy per theme on the left, and on the right an
 * icon that changes as the reader moves down the list, its ground taking that
 * theme's colour. The whole preview is two pseudo-elements on the submenu list
 * and a stack of :has() rules - no markup for it exists, so there is nothing to
 * keep in sync with the menu and nothing for a screen reader to read twice.
 * Matching is on href, not on position, so reordering the menu cannot break it.
 * ══════════════════════════════════════════════════════════════ */

/* Desktop only. Below the disclosure breakpoint the very same <ul> is rendered
 * inline inside the full-screen overlay, where core sets it static and full
 * width - a 620px white grid with a floating icon panel would land in the middle
 * of the navy overlay. The panel IS the desktop treatment; the overlay gets its
 * own, below. Above 1360px the disclosure button is display:none, so the overlay
 * cannot be opened and the two never meet. */
@media (min-width: 1360px) {
  .cd-header .wp-block-navigation .has-child > .wp-block-navigation__submenu-container {
    --cd-navpanel-ground: var(--cd-navy);
    --cd-navpanel-icon: url("../icons/schedule.svg");
    --cd-cut: var(--cd-cut-md);

    /* NO `position` here. Core sets `position: absolute` on the submenu, and
     * overriding it to relative - which the first draft did, to create a
     * containing block for the two pseudo-elements - drops a 620px box into the
     * navigation row. At 1280px that pushed About Us onto two lines and shoved
     * the Donate button off the right edge of the page, on every page of the site.
     *
     * No containing block was needed: an absolutely positioned element is already
     * one for its own ::before and ::after. */
    display: grid;
    /* Two columns, after the client's World Vision reference. Six programs in
     * one column made a 950px drop that ran off the bottom of a laptop screen;
     * in two it is about 400px and the whole set is takeable in one look, which
     * is the only reason a panel beats a list. */
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 2px var(--cd-space-4);
    padding: var(--cd-space-6);
    padding-right: 250px;
    /* !important, and it is the right tool here rather than a smell.
     *
     * The navigation block's overlay colours are what paint the mobile menu
     * navy, and WordPress applies them by putting has-navy-background-color and
     * has-grey-ghost-color on BOTH the overlay container and, on the way past,
     * every submenu <ul> inside it. Preset colour classes are emitted with
     * !important by construction, so on desktop the panel was painting navy with
     * near-white labels - navy on navy, invisible - and no amount of specificity
     * could answer it. Dropping the block attributes fixed the panel and left
     * the mobile overlay unpainted, which is the worse of the two bugs.
     *
     * So the attributes stay, the overlay keeps working, and the desktop panel
     * overrides the two declarations that leaked. Scoped inside the >=1360px
     * query, so the overlay's own colours are untouched where they matter. */
    background: var(--cd-white) !important;
    color: var(--cd-ink) !important;
    border: 0;
    /* The cut on the bottom right, per the client. It is the corner furthest from
     * the item that opened the panel, so the shape opens away from the menu bar
     * rather than clipping the corner the reader's eye just came from. */
    /* Square across the top, so the panel reads as hanging off the header
     * rather than as a card floating near it - the reference does the same.
     * One cut corner survives, bottom right, per the client. */
    border-radius: 0 0 var(--cd-cut) var(--cd-radius-base);
    /* And flush with the header's lower edge. Core opens the submenu at
     * `top: 100%` of its item, which is the bottom of a 46px link inside a
     * 107px header - the panel started 27px up inside the header and sat across
     * its hairline. Measured: the item's box runs y34 to y80 and the header ends
     * at 107, so 73px from the item's top puts the panel exactly on the edge.
     * Both numbers are fixed above the 1360px breakpoint this rule lives in. */
    top: 73px;
    box-shadow: var(--cd-shadow);
    overflow: hidden;
  }

  /* "All programs" spans the pair and takes a rule under it: it is the index the
   * other six sit inside, not a seventh sibling. Without it, switching the menu
   * to open-on-click would have made /programs/ unreachable from the navigation
   * - core renders a click-mode parent as a button, and although the parent here
   * keeps its link, the panel should carry the route too.
   *
   * Both panels open with a spanning row now: "All programs" in one, and the
   * injected header in the other. This was briefly scoped to the Programs panel,
   * when News & Events had no first row worth spanning and this rule stretched
   * News across both columns with Events stranded under the divider. */
  .cd-header .wp-block-navigation .has-child > .wp-block-navigation__submenu-container > li:first-child {
    grid-column: 1 / -1;
    padding-bottom: var(--cd-space-4);
    margin-bottom: var(--cd-space-3);
    border-bottom: 1px solid var(--cd-rule-light);
  }

  /* The scrim. What makes a panel read as a layer over the page rather than a
   * box drawn on it, and it is the reference's most copyable single detail.
   *
   * Fixed, and starting below the header so the menu itself is never dimmed.
   * `pointer-events: none` deliberately: the panel closes on focusout, so a
   * click anywhere both dismisses it and does whatever that click was for. A
   * scrim that swallowed the first click would cost every visitor a click to
   * escape a menu they opened by accident. */
  .cd-header .wp-block-navigation .has-child:has(> .wp-block-navigation-submenu__toggle[aria-expanded="true"])::after {
    content: "";
    position: fixed;
    inset: 107px 0 0 0;
    background: rgba(2, 15, 63, 0.45);
    pointer-events: none;
    z-index: -1;
  }
  @media (prefers-reduced-motion: no-preference) {
    .cd-header .wp-block-navigation .has-child::after { transition: opacity var(--cd-dur) var(--cd-ease); }
  }

  /* ── The preview: ground, then icon, both on the list itself ── */
  .cd-header .wp-block-navigation .has-child > .wp-block-navigation__submenu-container::before,
  .cd-header .wp-block-navigation .has-child > .wp-block-navigation__submenu-container::after {
    content: "";
    position: absolute;
    right: var(--cd-space-6);
    top: var(--cd-space-6);
    bottom: var(--cd-space-6);
    width: 200px;
    pointer-events: none;
    transition: background-color var(--cd-dur) var(--cd-ease);
  }
  .cd-header .wp-block-navigation .has-child > .wp-block-navigation__submenu-container::before {
    background-color: var(--cd-navpanel-ground);
    border-radius: var(--cd-radius-base) var(--cd-radius-base)
                   var(--cd-cut) var(--cd-radius-base);
  }
  /* The icon is one white SVG used as a mask, so a single file works on all seven
   * grounds - the same technique the hero marks and the card icons use. */
  .cd-header .wp-block-navigation .has-child > .wp-block-navigation__submenu-container::after {
    background-color: rgba(255, 255, 255, 0.92);
    -webkit-mask: var(--cd-navpanel-icon) no-repeat center / 96px 96px;
            mask: var(--cd-navpanel-icon) no-repeat center / 96px 96px;
  }
  /* Resting state is the Programs page's own icon on navy. Each row then claims
   * the preview while it is hovered or focused, so keyboard users travelling the
   * list with Tab see exactly what a mouse user sees. */
  .cd-header .wp-block-navigation .has-child > .wp-block-navigation__submenu-container:has(a[href*="/education/"]:is(:hover, :focus-visible)) {
    --cd-navpanel-ground: var(--cd-teal);   --cd-navpanel-icon: url("../icons/teach.svg");
  }
  .cd-header .wp-block-navigation .has-child > .wp-block-navigation__submenu-container:has(a[href*="/wellbeing/"]:is(:hover, :focus-visible)) {
    --cd-navpanel-ground: var(--cd-moss);   --cd-navpanel-icon: url("../icons/mental-health.svg");
  }
  .cd-header .wp-block-navigation .has-child > .wp-block-navigation__submenu-container:has(a[href*="/art/"]:is(:hover, :focus-visible)) {
    --cd-navpanel-ground: var(--cd-plum);   --cd-navpanel-icon: url("../icons/paint-palette.svg");
  }
  .cd-header .wp-block-navigation .has-child > .wp-block-navigation__submenu-container:has(a[href*="/cultural/"]:is(:hover, :focus-visible)) {
    --cd-navpanel-ground: var(--cd-maroon); --cd-navpanel-icon: url("../icons/culture.svg");
  }
  .cd-header .wp-block-navigation .has-child > .wp-block-navigation__submenu-container:has(a[href*="/community/"]:is(:hover, :focus-visible)) {
    --cd-navpanel-ground: var(--cd-brown);  --cd-navpanel-icon: url("../icons/community.svg");
  }
  .cd-header .wp-block-navigation .has-child > .wp-block-navigation__submenu-container:has(a[href*="/childrens/"]:is(:hover, :focus-visible)) {
    --cd-navpanel-ground: var(--cd-gold);   --cd-navpanel-icon: url("../icons/kids-playing-ball.svg");
  }
  /* Gold is the light ground of the Extra 6 and takes navy ink everywhere else in
   * the system (--cd-on-gold); a near-white icon on it would all but disappear,
   * so the Children's preview inverts exactly as the gold hero does. */
  .cd-header .wp-block-navigation .has-child > .wp-block-navigation__submenu-container:has(a[href*="/childrens/"]:is(:hover, :focus-visible))::after {
    background-color: var(--cd-navy);
  }

  /* ── News & Events ────────────────────────────────────────────
   * The second panel. Its resting preview and its two rows are set here rather
   * than inheriting the Programs default, which is the /programs/ page's own
   * icon and would be wrong above a list of stories.
   *
   * Resting icon is the megaphone, standing for the section as a whole; the two
   * rows carry news.svg and events.svg. The megaphone also appears inside
   * news.svg, so the resting-to-News swap reads as a relation rather than a
   * jump, and Events breaks cleanly away from both. */
  .cd-header .wp-block-navigation .has-child > .wp-block-navigation__submenu-container:has(a[href*="/events/"]) {
    --cd-navpanel-icon: url("../icons/megaphone.svg");
  }
  .cd-header .wp-block-navigation .has-child > .wp-block-navigation__submenu-container:has(a[href*="/posts/"]:is(:hover, :focus-visible)) {
    --cd-navpanel-ground: var(--cd-blue);   --cd-navpanel-icon: url("../icons/news.svg");
  }
  .cd-header .wp-block-navigation .has-child > .wp-block-navigation__submenu-container:has(a[href*="/events/"]:is(:hover, :focus-visible)) {
    --cd-navpanel-ground: var(--cd-teal);   --cd-navpanel-icon: url("../icons/events.svg");
  }

  /* ── The rows ────────────────────────────────────────────────── */
  .cd-header .wp-block-navigation .has-child > .wp-block-navigation__submenu-container > li { padding: 0; }
  .cd-header .wp-block-navigation .has-child > .wp-block-navigation__submenu-container > li > a.wp-block-navigation-item__content {
    display: block;
    padding: 12px 14px;
    border-radius: var(--cd-radius-base);
    text-decoration: none;
    color: var(--cd-ink);
    transition: background-color var(--cd-dur) var(--cd-ease);
  }
  .cd-header .wp-block-navigation .has-child > .wp-block-navigation__submenu-container > li > a:is(:hover, :focus-visible) {
    background: var(--cd-chalk);
  }
  .cd-header .wp-block-navigation .has-child .wp-block-navigation__submenu-container .wp-block-navigation-item__label {
    display: block;
    font-family: var(--cd-font-display);
    font-size: 17px;
    line-height: 1.15;
    letter-spacing: -0.01em;
    text-transform: uppercase;
  }
}

.cd-navcard__desc {
  display: block;
  margin-block-start: 4px;
  font-family: var(--cd-font-body);
  font-weight: 400;
  font-size: 14px;
  line-height: 1.4;
  letter-spacing: 0;
  text-transform: none;
  color: var(--cd-ink-muted);
}

/* [DL-071] The breakpoint moves because the item count did.
 *
 * DL-010 set it at 1340px and said so plainly: seven items measure 814px, the
 * lockup wins, and "it reverses the moment the nav drops to four". It has now
 * dropped to six - Home removed, because the logo beside it already goes home,
 * and Posts renamed to News, which is both shorter and what the page it opens
 * calls itself.
 *
 * Re-measured at 1440px: logo 194 + nav 727 + donate 120 + two 56px gutters +
 * two 40px gaps = 1233px. With the nav gap stepping to 26px just above the
 * breakpoint the row needs about 1190, so the disclosure now takes over at
 * 1209px and a 1280px laptop gets the real menu back.
 *
 * 26px between uppercase items is the floor worth holding: DL-010's other half
 * was that the 8px default ran eight items together into one string. */
/* [DL-071] The current SECTION, not just the current page.
 *
 * The markup already carried `current-menu-ancestor` on Programs whenever one
 * of the six theme pages was open - WordPress has been emitting it all along -
 * but only `[aria-current="page"]` was styled, and on a theme page that lands on
 * a link inside a closed dropdown. So on all six theme pages the visible bar
 * showed no active item at all, and the menu could not answer "where am I".
 *
 * The ancestor gets the same underline as the current page, one step quieter, so
 * the two states are legible together when Programs itself is open. */


/* [DL-071] The hover colour was --cd-blue on grey-ghost: 3.33:1, where 17px at
 * weight 600 is small text and needs 4.5:1. Third time this exact trap has been
 * hit (see .cd-btn--text and the prose-link rule), and the third time the answer
 * is the same - logo blue is not a text colour at this size. Navy at full
 * strength with the underline carrying the affordance instead. */


/* [DL-071] Hide on scroll down, return on scroll up.
 *
 * The Twentig plugin watches for `tw-sticky-scroll-up` anywhere inside the
 * header template part and, finding it, wires the Interactivity API directives
 * onto the outer <header> that toggle `.is-hidden`. All this file has to supply
 * is the movement: the plugin ships `transform: translateY(-100%)` with no
 * transition at all, so out of the box the header vanishes and reappears in a
 * single frame, which reads as a rendering fault rather than as a gesture.
 *
 * A pinned header costs 107px of every viewport it is pinned to - about an
 * eighth of a laptop screen. Giving it back while the reader is moving down the
 * page, and returning it the instant they move up, is the whole point: the menu
 * is there when someone reaches for it and out of the way when they do not. */
header.wp-block-template-part:has(> .cd-header) {
  transition: transform 240ms var(--cd-ease);
}
@media (prefers-reduced-motion: reduce) {
  header.wp-block-template-part:has(> .cd-header) { transition: none; }
}

/* [DL-071] The programs list inside the mobile overlay.
 *
 * Same six items, same descriptions, no panel: on a phone the submenu is part of
 * the menu rather than a layer above it, so it takes an indent and a rule
 * instead of a box. The descriptions earn their place here more than anywhere -
 * on a small screen "Cultural" and "Community" are two words a thumb away from
 * each other, and the line underneath is what tells them apart. */
.cd-header .wp-block-navigation__responsive-container.is-menu-open .has-child .wp-block-navigation__submenu-container {
  /* Indented off the RIGHT edge, because that is the edge the overlay's own
   * items are aligned to. The first draft indented from the left, which put a
   * left-aligned group of six inside a right-aligned list of six and gave the
   * menu two competing edges. */
  padding-left: 0;
  padding-right: var(--cd-space-5);
  border-right: 1px solid var(--cd-rule-dark);
  border-left: 0;
  text-align: right;
  gap: var(--cd-space-4);
}
/* Core makes the submenu anchor a flex row, so the label and the description
 * become two flex items side by side. Long labels wrapped and looked stacked,
 * which hid it - only ART was short enough to leave its description sitting on
 * the same line, reading "ART Creative practice for mental wellbeing". The
 * anchor has to be a block for the two spans to stack every time. */
.cd-header .wp-block-navigation__responsive-container.is-menu-open .has-child .wp-block-navigation__submenu-container > li > a.wp-block-navigation-item__content {
  display: block;
}
.cd-header .wp-block-navigation__responsive-container.is-menu-open .cd-navcard__desc {
  color: var(--cd-navy-body);
  text-transform: none;
  letter-spacing: 0;
  font-size: 15px;
  font-weight: 400;
}

/* [DL-071] The panel's own width, at a specificity that actually wins.
 *
 * Core's open rule is
 *   .wp-block-navigation .has-child:not(.open-on-click):hover > .wp-block-navigation__submenu-container
 * which is (0,5,0) once :not(.open-on-click) and :hover are counted - both
 * contribute to the class column, and the first draft's arithmetic missed them.
 * The component rule is (0,4,0), so `width: auto` won and the panel opened at
 * about 360px: the copy crushed to three lines a row and the preview, absolutely
 * positioned 250px from the right, landed on top of the text.
 *
 * This is (0,6,0) and states only the two declarations core's open rule also
 * sets. Everything else the component owns uninterrupted. */
@media (min-width: 1360px) {
  .cd-header .cd-header__actions .wp-block-navigation .has-child > .wp-block-navigation-submenu__toggle[aria-expanded="true"] ~ .wp-block-navigation__submenu-container {
    width: 720px;
    max-width: calc(100vw - 2 * var(--cd-gutter));
    /* And the side it grows from.
     *
     * This navigation is justified right, and core has a rule for exactly that
     * case - `.items-justified-right ... .wp-block-navigation__submenu-container
     * { right: 0; left: auto }`, also (0,5,0) - which is a sensible default for
     * a short dropdown near the right edge of a page. For a 720px panel it means
     * growing LEFTWARDS from the item: measured, the panel ran from x=50 back
     * under the logo, its right edge pinned to the Programs item, so the copy
     * sat 570px away from the word that opened it.
     *
     * Left-aligned to the item instead, and it fits from the breakpoint up: at
     * 1360px the item starts around x=500 with 800px of room, at 1440px it is
     * x=618 with 766px. */
    left: 0;
    right: auto;
  }

  /* News & Events opens the other way.
   *
   * The rule above left-aligns a panel to the item that opens it, which is right
   * for Programs - second in a five-item bar, so 720px from its left edge lands
   * at 1250 with the gutter at 1384. News & Events is fourth: measured, its item
   * starts at x=917, so the same rule put the panel's right edge at 1637 and
   * a third of it off the side of the page.
   *
   * This is core's own behaviour for a right-justified navigation, which the
   * rule above overrides; here it is simply allowed to stand. The panel's right
   * edge meets the item's, and it grows leftwards into the space that is
   * actually there.
   *
   * Which panel needs which depends on where its item sits in the bar, so this
   * is scoped by a link only that panel contains rather than by position - but
   * it does mean a reordered menu wants re-measuring. */
  .cd-header .cd-header__actions .wp-block-navigation .has-child > .wp-block-navigation-submenu__toggle[aria-expanded="true"] ~ .wp-block-navigation__submenu-container:has(a[href*="/events/"]) {
    left: auto;
    right: 0;
  }
}

/* [DL-082] The panel header: a title and one line, spanning both columns.
 *
 * Not a link and not a row - it is the label for what is underneath, so it takes
 * no hover ground and no pointer cursor. It matches the row labels in face and
 * case but sits a step larger, which is the only thing distinguishing a heading
 * from a destination when both are set in the display face in caps. */
.cd-navpanel__head { padding: 12px 14px; }
.cd-navpanel__title {
  display: block;
  font-family: var(--cd-font-display);
  font-size: 20px;
  line-height: 1.15;
  letter-spacing: -0.015em;
  text-transform: uppercase;
  color: var(--cd-ink);
}
.cd-navpanel__lede {
  display: block;
  margin-block-start: 5px;
  font-family: var(--cd-font-body);
  font-weight: 400;
  font-size: 14px;
  line-height: 1.4;
  letter-spacing: 0;
  text-transform: none;
  color: var(--cd-ink-muted);
}
/* In the overlay the panel's colours are the overlay's, and the header aligns to
 * the same right edge as everything else in it. */
.cd-header .wp-block-navigation__responsive-container.is-menu-open .cd-navpanel__title { color: var(--cd-on-navy); }
.cd-header .wp-block-navigation__responsive-container.is-menu-open .cd-navpanel__lede  { color: var(--cd-navy-body); }
