/* Brand-personality layer — the thin custom CSS theme.json cannot express.

   The .dfc-brush helpers were removed 2026-08-10. They pointed at brush-amber.svg,
   brush-sky.svg and brush-green.svg, none of which have ever existed in this repo,
   and no template or post used the classes: the rules resolved to nothing on every
   page. Brush strokes are art-directed per placement rather than applied by a
   utility class, so there is nothing to replace them with. Brand rule if they come
   back: max 3 per view, similar relative sizes, never over text, never on a button
   (style guide 5.3). */

/* ── Global header ───────────────────────────────────────── */
/* Sticky header: the whole header (announce + yellow nav bar) pins to the top
   while scrolling. Sticky MUST live on the <header> wrapper — putting it on
   .dfc-header alone only sticks within the short <header> box, so it scrolls
   away after ~120px. */
.wp-site-blocks > header {
  position: sticky;
  top: 0;
  z-index: 1000;
  /* NO shadow and no rule beneath: the yellow bar must sit seamless on the
     yellow Programs band / hero (2026-08-12). The hue change alone separates
     it from white content while scrolling. */
}
/* THE HEADER NAV IS NOT STYLED HERE. Look for .dfc-nav__link, further down.
   Everything that used to sit at this spot targeted .dfc-header
   .wp-block-navigation — the CORE navigation block, which this theme has not
   rendered since the nav became theme-rendered out of inc/nav.php (see
   docs/design-system.md §10.7; the core block cannot produce the mega panel's
   markup). Those rules set a font family, uppercase, 0.06em tracking, weight
   500, colors, a hover and a 0.98rem font-size, and matched nothing: the string
   wp-block-navigation appears zero times in the rendered markup of every page
   and zero times anywhere in the theme. Removed 2026-09-09, with the submenu
   panel rules and the 980px text-transform reset that were dead the same way.

   They were not harmless. Asked to make the nav text bigger, the obvious move is
   to grep the header for a nav font-size, find the 0.98rem that used to be on
   this line, change it, and ship a deploy that alters nothing on screen. */

/* Heading links (program names, etc.) must inherit the heading's Veneer font/color
   instead of resetting to the body link style. */
.wp-block-heading a { font-family: inherit; color: inherit; text-decoration: none; }
.wp-block-heading a:hover { text-decoration: underline; }

/* Header sizing. Height is derived: logo height + 16px clear top and bottom,
   rounded to the 8pt grid. All three values are tokens so the breakpoints in
   dfc-tokens.css move them together. See docs/design-system.md §10.7. */
.dfc-header > .wp-block-group {
  min-height: var(--header-h);
  gap: var(--nav-gap);
  padding-inline: var(--gutter);
}

/* The logo is sized by WIDTH, never height: the lockup is 278.2x105.4 and
   scaling by height makes the 120px minimum width impossible to reason about. */
.dfc-header-logo { display: inline-flex; align-items: center; flex: none; }
.dfc-header-logo img { width: var(--logo-w); height: auto; display: block; }

/* Donate sits hard right, after the nav rather than sharing its spacing. */
.dfc-header-cta { flex: none; margin: 0; }

/* No white gap between header/main/footer (kill block-gap on the top-level frame) */
.wp-site-blocks > main { margin-block-start: 0; margin-block-end: 0; }
.wp-site-blocks > footer { margin-block-start: 0; }
.wp-site-blocks > main > .wp-block-post-content > *:first-child,
.wp-site-blocks > main > *:first-child { margin-top: 0; }
.wp-site-blocks > main > .wp-block-post-content > *:last-child,
.wp-site-blocks > main > *:last-child { margin-bottom: 0; }
.dfc-home-hero { margin-top: 0; }
/* The dropdown-panel rules that sat here styled the core block's
   .wp-block-navigation__submenu-container and were dead for the same reason as
   the block above. The real dropdowns are .dfc-panel / .dfc-linklist / .dfc-rail. */
.dfc-announce a { color: var(--color-yellow); }

/* ── Global footer ───────────────────────────────────────── */
/* Links must be light on the navy footer (theme default link is navy). */
.dfc-footer a { color: var(--color-yellow); text-decoration: none; }
.dfc-footer a:hover { text-decoration: underline; }
.dfc-footer .wp-block-button a { color: var(--color-navy); }
.dfc-footer .is-style-none { list-style: none; padding-left: 0; }
.dfc-footer .is-style-none li { margin-bottom: 0.4rem; }

/* ── Display headlines (the DFC condensed/uppercase look) ── */
/* Big section headers + hero use the headline face, uppercase, tight.
   Barlow Condensed carries the hierarchy; Zing Rust is reserved for the hero
   and the title bands and never appears inside content. */
h1.wp-block-heading,
h2.wp-block-heading {
  text-transform: uppercase;
  letter-spacing: 0.01em;
  line-height: 1.08;
}
/* The italic that used to sit here was removed 2026-09-10, Dan: the Contact h1
   was rendering slanted. It dates from 2026-05-30, when the display face was
   Veneer Rough out of the Adobe kit and had a real italic to select. That kit
   went away on 2026-08-08; Zing Rust replaced it, one upright weight with no
   italic at all (fonts.css says so at the top), so the rule had stopped picking
   a face and started asking the browser to synthesise an oblique instead.

   Deleted rather than overridden. Contact is the only page on the site that
   authors its own level-1 wp:heading, checked across all 38 pages on
   2026-09-10; every other h1 comes from wp:post-title or from
   .dfc-prog-hero__title, and neither of those carries .wp-block-heading. So
   there is nothing left for the rule to style correctly.

   .dfc-page-hero .wp-block-heading still carries font-style:normal !important
   to undo this on hero bands. That override now undoes nothing, but it stays:
   page CONTENT can still carry an inline italic, which is what it names. */

/* ── Visually hidden, still in the outline ────────────────────────────────
   Added 2026-09-10. Three pages had a section heading removed because the hero
   above it already said the same words: Events ("Upcoming Events"), Our Team
   ("Staff") and Our Board ("Board of Directors"). Removing the visible text was
   right. Removing the HEADING was not: each of those sections is followed by
   h3s, so the outline went h1 -> h3 and the design-system audit failed the
   deploy on all three.

   A heading that is read but not seen is the standard answer to exactly this
   (WCAG technique H69), and it is a real improvement rather than a way around
   the gate: a screen-reader user still gets a landmark for the list, which is
   the thing the sighted user gets from the layout.

   The clip+1px recipe rather than display:none or visibility:hidden, both of
   which take the element out of the accessibility tree and would defeat the
   point. */
.dfc-sr-only {
  position: absolute !important;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* ── Diagonal section cuts (brand's angled bands) ────────── */
.dfc-cut-bottom { clip-path: polygon(0 0, 100% 0, 100% 90%, 0 100%); }
.dfc-cut-top    { clip-path: polygon(0 10%, 100% 0, 100% 100%, 0 100%); }
.dfc-cut-both   { clip-path: polygon(0 7%, 100% 0, 100% 93%, 0 100%); }

/* The floating Donate button was removed 2026-08-08. The header now carries the
   primary amber CTA and the footer carries a second one, which is the model the
   design system describes. Two Donate buttons on screen at once was clutter. */

/* Pull a section up under the diagonal above it (no white gap). */
.dfc-overlap-up { margin-top: -1px; }

/* ── Color bands must butt, not float ─────────────────────────────────────
   Core's flow layout ships :root :where(.is-layout-flow) > * with
   margin-block-start: var(--wp--style--block-gap), which is 24px here. Between
   two full-bleed color bands that margin is a 24px stripe of page background,
   so a green section sitting above a navy one showed a white bar between them.
   Reported on /programs/camp-morris/ and /get-involved/volunteer/, 2026-09-01.

   Collapsing the gap only where BOTH neighbours carry a background keeps the
   normal 24px rhythm between ordinary content blocks. .dfc-overlap-up (-1px)
   and .dfc-diag-top (-46px) are excluded because their negative margin IS the
   diagonal overlap, and this rule outspecifies them. Core's own selector is
   inside :where(), so it carries no specificity and needs no !important here.

   This replaces the per-section .dfc-overlap-up patching that was papering over
   the same root cause one band at a time. */
.wp-block-post-content.is-layout-flow > .has-background + .has-background:not(.dfc-diag-top):not(.dfc-overlap-up) {
  margin-block-start: 0 !important;
}
/* The trailing half of the same pair. Measured 2026-09-01: /get-involved/volunteer/
   still showed 40px and /get-involved/events/ 32px after the rule above, because
   those bands carry margins authored INTO the block content
   ("style":{"spacing":{"margin":{"top":"40px"}}}), which render as an inline style
   and outrank any stylesheet rule.

   Hence !important, deliberately. A full-bleed color band is a section: it owns its
   own 56-64px padding and must never also carry an external margin, because that
   margin can only ever be a stripe of whatever is behind it. Encoding that here
   fixes every page at once and keeps fixing it when someone types a margin into a
   band next month, which editing the two offending pages would not.

   :has() picks the band that PRECEDES another band, so a band followed by ordinary
   content keeps its bottom margin. */
.wp-block-post-content.is-layout-flow > .has-background:has(+ .has-background):not(.dfc-diag-top):not(.dfc-overlap-up) {
  margin-block-end: 0 !important;
}

/* ── Home hero: full-bleed photo + Veneer headline ────────────────────────
   The yellow diagonal band was removed 2026-09-08 at Dan's request, together
   with the .dfc-diag-top cut the Programs band used to take out of the bottom
   of the photo. The hero is the photograph now, with nothing laid over it. */
.dfc-home-hero {
  position: relative;
  /* 42.5vw is 1 / 2.353 — the wide crop's OWN ratio (2000x850). The rule reads
     "be as tall as the photograph wants to be", floored and capped.

     Asked 2026-09-08 whether this wanted a fifth breakpoint. It does not: a
     breakpoint snaps, and this is continuous sizing, not reflow. At a fixed
     620px the box is 2.06:1 at 1280px and crops the SIDES, which reads as a
     photograph; at 2048px it is 3.30:1 and cover throws away 29% off the
     bottom, which reads as a banner strip. That is a ratio problem, and a
     ratio is what this fixes.

     620px floor holds every viewport up to 1459px exactly as it was, which is
     the laptop case that already looked right. 860px cap is the natural height
     at 2048px and the point past which the 2000px source starts upscaling.
     Same idiom as .dfc-spark below, and as this hero's own H1 and subhead. */
  min-height: clamp(620px, 42.5vw, 860px);
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  background-position: 50% 0%;
  background-size: cover;
  background-repeat: no-repeat;
  background-color: var(--color-navy);
}
.dfc-home-hero::after {
  content: "";
  position: absolute;
  inset: 0;
  /* EXCEPTION: a scrim over a photograph, not elevation. Navy-based so it tints
     toward the brand rather than muddying to gray. No token: tuned to this image.

     FLAT, not a ramp (2026-09-01). It was a six-stop gradient shaped to where the
     text sits. Removing it entirely left the photo too bright to read white text
     on, so it is back as an EVEN overlay at Dan's request. One value across the
     whole hero is also the more defensible choice: the shaped ramp had to be
     re-measured every time the hero photograph changed, because its stops were
     keyed to that specific image's bright and dark regions. A flat scrim is not.

     0.58 is the floor the old ramp used behind the text block (it ran 0.58 to
     0.72 there). Holding that value everywhere makes the top of the hero darker
     than before and the very bottom lighter, so verify with the rendered audit
     after any hero photo change: the subhead is normal-size white text and needs
     4.5:1, the H1 is large and needs 3:1. */
  background: rgba(26, 50, 94, 0.58);
  z-index: 1;
}
/* Hero photo as a real <img> (LCP element): webp, prioritized, and now the only layer. */
.dfc-home-hero__bg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* Top center. Was 50% 20%, which anchored the crop below the top of the source
     and rode the group high enough for the yellow band to clip the tallest heads.
     0% shows the top of the frame (sky and treeline), so the kids sit lower and
     clear the band, which is itself 20px shallower now. Reported 2026-09-01. */
  object-position: 50% 0%;
  z-index: 0;
}
/* <picture> is only a source-selection wrapper here — the hero art-directs to a
   portrait crop under 768px. display:contents keeps it out of the layout so the
   absolutely-positioned img still resolves against the section. */
.dfc-home-hero picture { display: contents; }
/* .dfc-home-hero__band was the yellow diagonal wedge across the top of the
   photo. Removed 2026-09-08 with its markup (post 2016), so the rules are gone
   rather than left orphaned. If a diagonal is ever wanted back here, take it
   from .dfc-cut-bottom above rather than reviving a one-off. */
/* ── The brush mark behind the "navigate T1D alone" photo ─────────────────
   Position and direction are Dan's, from a marked-up screenshot (2026-09-16):
   it RISES up and to the right from behind the photograph and leaves by the
   right edge of the screen. An earlier pass had it falling away from the
   photo's bottom; that is the thing this replaced, not a variant of it.

   Anchoring. transform-origin is the box's left-centre, so `translate 0 -50%`
   pins the stroke's blunt left end on the (left, top) point and the rotation
   swings everything else up and right from there. That point is INSIDE the
   photograph, which is the whole trick: the art's left end is a hard vertical
   cut and the picture is the only thing that hides it. What reads is a stroke
   emerging from behind the photo, not one starting beside it.

   Layering, three rules that only work together:
     - `isolation: isolate` on the band makes it the stacking context, so the
       z-index:-1 below is measured against the band, not the root. Without it
       the mark sinks under the off-white background and disappears.
     - the figure is position:relative with NO z-index. That gives the
       pseudo-element something to resolve against WITHOUT opening a stacking
       context, which is what lets the -1 escape up to the band.
     - z-index:-1 then paints above the band's background but below everything
       in flow, so the photograph covers the blunt end and no heading is ever
       painted over.

   `overflow-x: clip` is why "off the screen" does not also mean a horizontal
   scrollbar: the band is full-bleed, so its right edge IS the screen's. clip
   rather than hidden, because overflow-y must stay visible.

   Never stretched: background-size is contain, the box carries the art's own
   764x196, and the SVG is preserveAspectRatio="xMidYMid meet". */
.dfc-home-hero + .dfc-overlap-up + .wp-block-group {
  overflow-x: clip;
  isolation: isolate;
}
.dfc-home-hero + .dfc-overlap-up + .wp-block-group figure.wp-block-image {
  position: relative;
}
.dfc-home-hero + .dfc-overlap-up + .wp-block-group figure.wp-block-image::before {
  content: "";
  position: absolute;
  /* Inside the photo, right of centre and low, so the stroke is hidden until
     it clears the picture's right edge and is still climbing when it does. */
  left: 62%;
  top: 84%;
  /* The width is DERIVED, not chosen, and that is the only reason the mark
     reliably leaves the screen instead of stopping short in the gutter.

     The content column is capped at 1200px and centred, so the photo's left
     edge is always 50vw + 116px and this anchor is 50vw + 416px. The distance
     left to cover is therefore 50vw - 416px, and at 33 degrees the stroke
     needs that over cos(33) = 59.6vw - 496px. 62vw - 490px is that plus about
     50px of overshoot, so the tail is cut by the screen rather than meeting
     it. Change the anchor or the container width and this constant is wrong.

     Sizing this way also answers "make it ~30% smaller": at Dan's 2048px it
     comes out at 780px against the 1024px it used to be. */
  width: clamp(320px, calc(62vw - 490px), 1200px);
  aspect-ratio: 764 / 196;
  transform-origin: 0 50%;
  translate: 0 -50%;
  rotate: -33deg;
  background: url("../../img/brush-divider-amber.svg") no-repeat 50% 50% / contain;
  pointer-events: none;
  z-index: -1;
}
/* Under 1400px there is nowhere for this mark to be.

   The photo's right edge is 50vw + 600px, so the gutter it trails into is
   (100vw - 1200px) / 2: about 120px at 1440, 40px at 1280, and 24px once the
   column stops being capped. A stroke hidden BEHIND the photograph can only
   show in that gutter, so below about 1400px it renders as a 20-40px crumb at
   the screen edge. Aiming it out of the photo's TOP instead only moves the
   crumb onto the sky band above, where it reads as a smear.

   So it is not drawn. This is decoration with a hard spatial requirement, and
   a quiet absence beats a fragment that looks like a rendering fault. If the
   mark is wanted at laptop widths it has to fall BELOW the photo rather than
   rise, because that is the only empty region at those sizes — that is a
   different composition and it needs Dan's eyes, not a smaller clamp. */
@media (max-width: 1399px) {
  .dfc-home-hero + .dfc-overlap-up + .wp-block-group figure.wp-block-image::before {
    content: none;
  }
}

.dfc-home-hero__circle {
  position: absolute;
  right: -40px; top: 26%;
  width: clamp(140px, 18vw, 220px);
  opacity: 0.92;
  /* UNDER the scrim (was z-index 2, i.e. above it). At 390px the clamp floor
     holds the circle at 140px while the H1 box grows to the full column, so the
     decoration overlaps the headline. Sky #2AA9E0 is 2.43:1 against white,
     which fails AA for large text (3:1), and sitting above the scrim the circle
     was exempt from the only thing correcting it. Below the scrim it gets the
     same navy tint as the photo. Measured: mobile H1 area under 3:1 fell from
     2.8% to 0.5%. */
  z-index: 0;
  pointer-events: none;
}
.dfc-home-hero__inner { position: relative; z-index: 4; max-width: 1000px; padding: 60px 24px 40px; text-align: center; }
.dfc-home-hero__title {
  color: var(--color-white);
  /* The hero is a DISPLAY moment, not a heading. design-system 4.1 reserves the
     display face for exactly two places and this is one of them. */
  font-family: var(--font-display);
  /* Zing Rust ships in a single 400 weight. Without this reset the theme.json h1
     weight (700) makes the browser synthesize a bold, which fills in the grunge
     texture and renders a mushy lookalike instead of the real face. */
  font-weight: 400;
  font-style: normal;
  text-transform: uppercase;
  line-height: 0.95;
  font-size: clamp(2.4rem, 6vw, 4.6rem);
  /* EXCEPTION: legibility shadow for text sitting on a photograph. See style-guide 6.3. */
  text-shadow: 0 3px 20px rgba(0,0,0,0.5);
  margin: 0 0 0.3em;
}
.dfc-home-hero__sub {
  color: var(--color-white);
  font-size: clamp(1.05rem, 2vw, 1.35rem);
  margin: 0 auto 1.4em;
  max-width: 660px;
  /* EXCEPTION: legibility shadow for text sitting on a photograph. See style-guide 6.3. */
  text-shadow: 0 2px 12px rgba(0,0,0,0.5);
}

/* ── Home "Our Mission": the copy runs BESIDE the photo and then UNDER it ──
   Until 2026-10-01 this band was a 58/42 wp:columns pair, so the copy was pinned
   to 668px for its whole length. The photo is 460px tall and the copy is ~720px,
   so "Programs that support you" and its five bullets cleared the bottom of the
   photo and then ran on for another 260px with 484px of empty off-white beside
   them. Columns cannot fix that: a flex item has no way to reflow into the space
   its neighbour stopped occupying. So the photo is a right float inside one
   flow-layout group and the copy wraps around it.

   display:flow-root, not overflow:hidden, contains the float: it keeps the band's
   bottom edge below the photo without turning the group into a scroll container.

   A right float needs the figure FIRST in source order, which is why the image
   block now leads this group. Below the columns breakpoint the float is dropped
   and order:1 puts the photo back after the copy — the reading order the stacked
   columns used to give, and the one the band was written for. */
.dfc-wrap-figure { display: flow-root; }

.dfc-wrap-figure > figure.wp-block-image {
  float: right;
  width: 42%;
  max-width: 484px;
  /* 48px is the blockGap the two columns carried; keeping it means the beside-copy
     measure is unchanged at 668px and only the under-copy gains width. */
  margin: 0 0 var(--space-6) var(--space-8);
}

/* The photo is 968x920. At 484px wide it is 460px tall uncropped, which is already
   the cap the columns version set; keeping cap + crop here means a future swap to a
   portrait file cannot quietly make the float taller than the copy that wraps it. */
.dfc-wrap-figure > figure.wp-block-image img {
  display: block;
  width: 100%;
  max-height: 460px;
  object-fit: cover;
  object-position: 50% 40%;
}

@media (max-width: 781px) {
  /* Flex only so `order` is available. Every other child keeps order 0 and so keeps
     its source order; the figure alone moves to the end. */
  .dfc-wrap-figure { display: flex; flex-direction: column; }
  .dfc-wrap-figure > figure.wp-block-image {
    float: none;
    width: 100%;
    max-width: none;
    margin: var(--space-6) 0 0;
    order: 1;
  }
  .dfc-wrap-figure > figure.wp-block-image img { max-height: 320px; }
}


/* ── Home "Our Programs" card: whole card is the link, Learn More on hover ──
   Two things were wrong before. Only the H3 was clickable, which is a small
   target on a card that is mostly photograph, and the H3 was the only thing that
   reacted, so the card gave no hover affordance at all.

   The link is still the single <a> in the H3. Its ::after is stretched over the
   whole card, which makes the entire card clickable without nesting a second
   link or wrapping the group in raw HTML, so the block stays editable in
   Gutenberg and the link keeps exactly one accessible name ("Camp Morris").

   The label is Barlow Condensed, NOT Zing Rust. design-system 4.1 scopes the
   display face to the hero and page-title bands and forbids it inside content,
   and a hover overlay on a card is content. Requested as Zing Rust 2026-09-01
   and built in the headline face for that reason.

   White on navy at 0.82 over the photo measures 12.64:1 against the flat navy
   and never goes below that, because the overlay only ever darkens what is
   behind it. The label is decorative: the link already announces the programme
   name, so nothing here needs to reach the accessibility tree. */
.dfc-progcard { position: relative; overflow: hidden; }

/* Every card fills its column, so all four white boxes end on the same line.
   Feedbucket 964251. The four descriptions are different lengths, so the width
   at which each one wraps to an extra line is different, and core's columns
   stretch the COLUMN to the tallest of the row while leaving the white group
   inside it sized to its own content. The result is one box hanging lower than
   its neighbours over the sky band, and which box that is changes with the
   viewport: Teen Retreat around 1024-1100px (its copy is the longest, so it is
   the first to reach three lines), Camp to Community around 782-900px, Family
   Retreat in the band between them. Measured spread was 12-35px.

   height, not min-height, and no flex on the column: the column is already a
   stretched flex item, so 100% of it is the tallest card in the row, and the
   extra space lands as padding below the last line of the short cards. Nothing
   is needed below 782px, where core stacks the columns and each card is a row
   of its own, but the rule is harmless there because a stacked column is
   exactly as tall as its card. */
.dfc-progcard { height: 100%; }

/* The photograph is a 1:1 box, whatever file happens to be in it. Every image in
   this row was a 500x500 square until 2026-09-14, when Family Retreat took a
   16:9 photograph and the row broke: its picture came in about 100px shorter
   than its neighbours, so its title, format line and description all started
   higher and the four cards stopped reading as a set. Nothing in the markup
   enforced the ratio — it was a coincidence of four files that happened to be
   square, and the first non-square one exposed it.

   A no-op for the three squares, and it means the next person can drop any
   photograph in here without measuring it first. Centred because all four are
   photographs of people with the subject in the middle.

   height:auto is load-bearing for the same reason as .dfc-polaroid img: an image
   whose markup carries width/height attributes has a definite height, and
   aspect-ratio is ignored outright unless one axis is auto. */
.dfc-progcard figure.wp-block-image img {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 1 / 1;
  object-fit: cover;
  object-position: 50% 50%;
}

/* The format line ("Week-Long Camp") belongs TO the programme name, so it sits
   right under it instead of a block-gap away. Core's constrained layout puts
   margin-block-start: 24px on every child, which read as three separate items
   stacked rather than a title with a subtitle and then a description. Only the
   first paragraph is pulled up; the description below it keeps the full gap. */
.dfc-progcard .wp-block-heading { margin-block-end: 0; }
.dfc-progcard .wp-block-heading + p { margin-block-start: 4px; }

/* The whole card, via the heading's own link. z-index keeps it over the photo
   but under the overlay, which is pointer-events:none anyway. */
.dfc-progcard .wp-block-heading a::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 2;
}
/* No underline on the title: the CARD is the hover target now, not the words. */
.dfc-progcard .wp-block-heading a:hover { text-decoration: none; }

.dfc-progcard::after {
  content: "Learn More";
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  background: rgba(26, 50, 94, 0.82);
  color: var(--color-white);
  font-family: var(--font-headline);
  font-weight: var(--weight-bold);
  font-size: var(--text-xl);
  text-transform: uppercase;
  letter-spacing: var(--tracking-label);
  opacity: 0;
  transition: opacity var(--duration) var(--ease);
  z-index: 3;
  pointer-events: none;
}
/* focus-within, not just hover: a keyboard user tabbing to the card gets the
   same affordance a mouse user gets. Without it the overlay is invisible to
   anyone not using a pointer. */
.dfc-progcard:hover::after,
.dfc-progcard:focus-within::after { opacity: 1; }

/* The ring goes on the CARD, not the link. A navy focus ring sitting under a
   navy overlay would be invisible; outside the card it is 4.72:1 on the sky
   band behind it. */
.dfc-progcard:focus-within {
  outline: var(--focus-width) solid var(--color-focus);
  outline-offset: var(--focus-offset);
}
.dfc-progcard .wp-block-heading a:focus-visible { outline: none; }

@media (prefers-reduced-motion: reduce) {
  .dfc-progcard::after { transition: none; }
}

/* Reusable buttons for raw-HTML sections (mirror theme buttons) */
/* Button base. Values come from docs/design-system.md §10.1 — 600 weight, 0.05em
   tracking and 12px/32px padding. These previously read 700 / 0.03em / 13px 30px,
   which was drift rather than a decision. */
.dfc-btn {
  display: inline-block;
  /* border-box is REQUIRED, not tidiness. With the default content-box the
     min-height below becomes the CONTENT height and the 24px padding plus 4px
     border are added on top, rendering a 72px button instead of a 44px one. */
  box-sizing: border-box;
  font-family: var(--font-label);
  font-size: var(--text-base);
  font-weight: var(--weight-semibold);
  line-height: 1;
  text-transform: uppercase;
  letter-spacing: var(--tracking-label);
  padding: var(--space-3) var(--space-6);
  border-radius: var(--radius);
  border: 2px solid transparent;
  text-decoration: none;
  margin: 6px;
  min-height: 44px;              /* EXCEPTION: WCAG touch target, not a grid value */
}
.dfc-btn--amber { background: var(--color-amber); color: var(--color-navy); }
.dfc-btn--amber:hover { background: var(--color-cta-hover); }
.dfc-btn--amber:active { background: var(--color-cta-active); }

/* Secondary. Navy fill, white label. Supporting actions that are not the main ask. */
.dfc-btn--secondary { background: var(--color-navy); color: var(--color-text-on-dark); }
.dfc-btn--secondary:hover { background: var(--color-navy-hover); color: var(--color-text-on-dark); }
.dfc-btn--secondary:active { background: var(--color-navy-active); }

.dfc-btn--outline { border-color: var(--color-white); color: var(--color-white); }
.dfc-btn--outline:hover { background: var(--color-white); color: var(--color-navy); }

/* ── Button hover treatments (darken own color + subtle lift) ── */
.wp-block-button__link, .dfc-btn {
  transition: background-color 0.15s ease, color 0.15s ease, transform 0.15s ease, box-shadow 0.15s ease;
}
.wp-block-button__link:hover, .dfc-btn:hover {
  transform: translateY(-2px);
  box-shadow: var(--shadow);
}
.wp-block-button__link.has-amber-background-color:hover { background-color: var(--color-cta-hover) !important; }
.wp-block-button__link.has-sky-background-color:hover   { background-color: var(--color-sky-hover) !important; }
.wp-block-button__link.has-navy-background-color:hover  { background-color: var(--color-navy-hover) !important; }
@media (prefers-reduced-motion: reduce) {
  .wp-block-button__link:hover, .dfc-btn:hover { transform: none; }
}

/* ── Buttons go full width on a phone ─────────────────────────────────────
   design-system.md §10.1 has said "buttons go full-width below 767px" since
   the system was written, and nothing in the theme ever implemented it. A
   flex row of buttons therefore stacked at each button's own intrinsic width,
   so the homepage CTA band came down as a ragged column of 181px, 260px,
   116px, 210px and 190px centred on a 342px screen — five different left
   edges down the most important band on the site. Splitting that row into
   two on 2026-09-15 made it visible: Donate stopped being alone on its line
   and started sharing one with Become a Sponsor.

   Scoped to core button BLOCKS on purpose. `.dfc-btn` is deliberately NOT in
   this selector: the header Donate is a 72px chip in the top bar and the
   Events cards carry their own Register buttons inside a card, so stretching
   either to the full width of its parent would be wrong. Those are
   components that size themselves, not rows of editor buttons.

   The 44px result is also exactly the minimum touch target §10.1 asks for. */
@media (max-width: 767px) {
  .wp-block-buttons > .wp-block-button { width: 100%; }
  .wp-block-buttons > .wp-block-button > .wp-block-button__link {
    display: block;
    width: 100%;
    text-align: center;
  }
}

/* ── Diagonal section transitions (brand angled bands) ── */
.dfc-diag-top {
  clip-path: polygon(0 46px, 100% 0, 100% 100%, 0 100%);
  margin-top: -46px;
  padding-top: 92px !important;
  position: relative;
}
/* Display "eyebrow"/section labels on color bands */
.dfc-eyebrow {
  font-family: var(--wp--preset--font-family--headline);
  font-style: normal;
  text-transform: uppercase;
}

/* ── Inner-page hero band (program + detail pages): Veneer italic title + brand diagonal bottom edge ── */
.dfc-page-hero {
  clip-path: polygon(0 0, 100% 0, 100% calc(100% - 42px), 0 100%);
  padding-bottom: 84px !important;
}
.dfc-page-hero .wp-block-heading {
  font-family: var(--wp--preset--font-family--headline);
  font-style: normal !important; /* override the inline italic baked into program-hero H1s (Zing Rust is upright-only) */
  line-height: 0.92;
  font-size: clamp(2.5rem, 6vw, 4.6rem);
  margin-bottom: 0.2em;
}
.dfc-page-hero p { font-size: clamp(1.05rem, 1.9vw, 1.3rem); max-width: 760px; }

/* ── Program hero: title + promise + dates + CTAs left, polaroid scatter right
   ─────────────────────────────────────────────────────────────────────────
   Built 2026-09-08 for /programs/camp-morris/, reusable on any program page.
   It replaces the .dfc-page-title band rather than sitting under it, so the
   page opens with one masthead instead of a title slab followed by a stranded
   sentence. The band is hidden by :has() below, which means a page opts in by
   containing the hero and nothing has to be registered in functions.php.

   Off-white, not white: it separates the hero from the white content bands
   under it without introducing a colour. Navy on #FDFBF7 is 11.79:1. */
.dfc-prog-hero {
  background: var(--color-off-white);
  /* 48/56, not 64/80. Measured 2026-09-09: the hero was 817px tall at every
     width from 1280 up, which is taller than the viewport on a 1280-class
     laptop, so the calls to action sat below the fold on exactly the machines
     most likely to be reading this page. */
  padding: var(--space-8) var(--space-5) var(--space-8);
  /* The diagonal brush deliberately overhangs its column so its ends show past
     the photo cluster. Clipping at the section edge keeps that overhang from
     ever becoming a page-level horizontal scrollbar, which is the failure this
     hero has already produced once at 1024px. */
  overflow: hidden;
}
/* A page opts out of the coloured title band by containing the hero (which
   carries its own h1), or by marking any block .dfc-hide-page-title when it
   supplies its own h1 without the hero chrome. Contact does the latter: its
   band said "Contact" above a lede that said "Questions? Drop us a line", and
   the real heading, "Send Us a Message", was an h2 further down. */
main:has(.dfc-prog-hero) .dfc-page-title,
main:has(.dfc-hide-page-title) .dfc-page-title { display: none; }
/* ...and with it hidden, .entry-content becomes main's SECOND child, so core's
   :root :where(.is-layout-flow) > :first-child { margin-block-start: 0 } never
   matches it and it takes the 24px flow gap instead. display:none removes the
   band from layout but not from the DOM, so :first-child still counts it.
   That gap rendered as a white strip between the yellow header and the hero on
   every page that opts into this hero: camp-morris, teen-retreat, our-team and
   our-board. Reported 2026-09-10, Dan.

   Paired with the rule above deliberately: the two are the same decision, and
   whoever removes one has to see the other. Specificity is (0,2,1) against
   core's (0,1,0), so this wins without !important. */
main:has(.dfc-prog-hero) > .entry-content,
main:has(.dfc-hide-page-title) > .entry-content { margin-block-start: 0; }

/* The same seam at the hero's BOTTOM edge. post-content is a `default` (flow)
   layout in templates/page.html, so every top-level block takes a block-gap
   margin, and the hero's gap shows the white page background through it. It is
   invisible wherever the next band is white, which is why nobody caught it on
   Camp Morris or Our Team; on /get-involved/events/ the next band is off-white
   and the gap reads as a white stripe across the page. Reported 2026-09-10, Dan.

   Keyed off the hero rather than off each page's first band, so a page opting
   into the hero gets this without anyone remembering to, exactly the way the
   two rules above already work. */
.dfc-prog-hero + * { margin-block-start: 0; }

.dfc-prog-hero__inner {
  /* var(--wide), not a restated 1200. The content bands below this hero were
     authored at 1100px and looked narrower than it; they are the deviation, so
     they were brought up to the same measure rather than the hero brought down.
     Reported 2026-09-08. */
  max-width: var(--wide);
  margin: 0 auto;
  display: grid;
  /* 1.2 / 0.8, not 1 / 1.05. The photo cluster was 673px against 413px of text,
     so the hero was sized entirely by the photographs and centring split the
     260px difference into two 130px voids around the text. Widening the text
     column does not help, because the subhead is capped at 34ch and the facts
     list is short: that column stays ~413px however much room it is given. The
     only real lever is the cluster, so the cluster narrows. */
  grid-template-columns: minmax(0, 1.2fr) minmax(0, 0.8fr);
  gap: var(--space-10);
  /* Centre stays right. The voids were the symptom of the height mismatch, not
     the cause; top-aligning would just move all of the dead space to one end. */
  align-items: center;
}
/* Text-only variant: About pages use this hero purely to replace the coloured
   title band with a title and a lede, and carry no photo column. Detected by
   :has() rather than by a modifier class, so a page that later gains photos
   picks up the two-column layout without anyone editing markup twice.
   NOTE 2026-09-08: the class is still named for programs because it was built
   there first. It is now on About pages too. Rename to .dfc-hero. */
.dfc-prog-hero__inner:not(:has(.dfc-prog-hero__photos, .dfc-prog-hero__aside)) {
  grid-template-columns: minmax(0, 1fr);
}
/* The 900px cap is on the TEXT, not on the container (2026-09-10, Dan). It used
   to be on .dfc-prog-hero__inner, which centred a 900px column inside the 1200px
   measure and pushed the hero's left edge 150px right of every content band
   below it. Reported on Teen Retreat; our-team and our-board had the same offset
   against their bands and nobody had named it yet. Capping the text instead
   keeps the lede at the same reading measure it always had while the container
   keeps var(--wide), so the title, the lede and the first heading of the page
   now start on one left edge. */
.dfc-prog-hero__inner:not(:has(.dfc-prog-hero__photos, .dfc-prog-hero__aside)) .dfc-prog-hero__text {
  max-width: 900px;
}

.dfc-prog-hero__title {
  font-family: var(--font-display);
  /* Zing Rust ships one 400 weight and theme.json sets h1 to 700. Without this
     the browser synthesises a bold, which fills in the grunge texture and
     renders a mushy lookalike. Same reset as .dfc-home-hero__title. */
  font-weight: 400;
  font-style: normal;
  font-size: clamp(2.5rem, 5.5vw, 4.25rem);
  line-height: 0.98;
  text-transform: uppercase;
  color: var(--color-navy);
  margin: 0 0 var(--space-4);
}
/* The opening sentence of the page, which is what design-system 4.3 calls a
   Lead: 22px desktop and laptop, 20px tablet and mobile, at --leading-normal.

   It used to be clamp(1.05rem, 1.9vw, 1.3rem), which lands at 20.8px on a
   1440px screen -- a width that is on no step of the scale. 20px is the H5 step
   and 22px is the Lead step, so a fluid value between them is the one size this
   paragraph cannot correctly be, and the rendered audit failed it as body copy
   (2026-09-16) because 20.8px is not the 16-18px body is allowed either. The
   same clamp also collapsed to its 16.8px floor on a phone, where the scale
   asks for 20px, so the lede read as body copy on exactly the screens where the
   distinction between a promise and a paragraph carries the most.

   Two media queries, the only two the scale uses. Not a clamp: the tiers are
   flat by design (see 4.3, "Four breakpoints, three type tiers"), and a clamp
   cannot express a flat tier without pinning both ends to the same value. */
.dfc-prog-hero__sub {
  font-family: var(--font-body);
  font-size: var(--text-xl);
  line-height: var(--leading-normal);
  color: var(--color-navy);
  /* NO max-width here (2026-09-10, Dan). 34ch was a desktop two-column
     constraint, and it was the narrowest thing in the hero: the title and the
     facts row both ran past it, so the lede wrapped at 453px in a column with
     ~680px available and the band read as ragged. Reported on Teen Retreat,
     Family Retreat and Camp to Community, all of which run the two-column hero.
     The grid column is the measure now: about 62 characters at desktop, and the
     900px text block on a photo-less hero. Below 980 the hero stacks to the full
     band width, where a cap IS needed, and the media query still sets 46ch. */
  margin: 0 0 var(--space-5);
}
/* The date chip is Yellow, not Amber: design-system.md reserves Amber for CTAs,
   and this is information, not an action. Navy on Yellow is 9.18:1. */
.dfc-prog-hero__when {
  display: inline-block;
  background: var(--color-yellow);
  color: var(--color-navy);
  font-family: var(--font-label);
  font-size: var(--text-base);
  font-weight: var(--weight-semibold);
  text-transform: uppercase;
  letter-spacing: var(--tracking-label);
  padding: var(--space-2) var(--space-4);
  border-radius: var(--radius-pill);
  margin: 0 0 var(--space-3);
}
/* The facts under the promise: label, then value, one pair per line. A <dl> is
   what a run of label/value pairs is, so the bold is structural rather than a
   <strong> doing the work of markup.

   Set at the SAME size as the promise above it, not the 16px it used to be.
   These three lines are the answer to "is this for my kid, and when" and they
   were being read as a caption because they were sized like one. */
/* The hero's primary call to action, when a programme's registration is open.
   Amber is the CTA fill, and on this hero's off-white ground it is 1.83:1, so
   the fill is not what makes the control's edge visible: the navy border is,
   at 11.79:1. WCAG 1.4.11 wants 3:1 on a control boundary, and the brand guide
   puts a 2px navy border on every amber button for exactly this reason. Scoped
   to the hero so the four existing --amber buttons, which each carry their own
   modifier and their own surface, are untouched. */
.dfc-prog-hero__cta .dfc-btn--amber { border-color: var(--color-navy); }
/* Moved onto the Lead step with .dfc-prog-hero__sub above, and for the reason
   the comment over the <dl> already gives: these lines are sized to match the
   promise on purpose, so that they read as an answer rather than a caption.
   The pairing was the decision; 20.8px was only ever the number it happened to
   produce. Both now resolve through the same two tokens, so the next person to
   move one moves the other by definition. */
.dfc-prog-hero__facts {
  font-family: var(--font-body);
  font-size: var(--text-xl);
  line-height: var(--leading-normal);
  color: var(--color-navy);
  margin: 0 0 var(--space-6);
  display: grid;
  gap: var(--space-1);
}
/* flex, so a long value wraps under itself instead of under its own label. */
.dfc-prog-hero__facts > div { display: flex; flex-wrap: wrap; column-gap: .4em; }
.dfc-prog-hero__facts dt { font-weight: var(--weight-bold); }
.dfc-prog-hero__facts dd { margin: 0; }
/* align-items: center, not the default stretch. The row holds a <button> and an
   <a>; the button is the taller box, the anchor stretched to match it, and
   because an inline-block anchor puts its text at the top of its own padding
   the Volunteer label sat visibly high against the Register label beside it.
   Reported 2026-09-08. Centring makes each control its own natural height. */
.dfc-prog-hero__cta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  margin: 0 0 0 -6px;
}

/* ── A CTA that is not open yet ───────────────────────────────────────────
   Grey FILL with navy text, not grey text. Greying the label is the usual way
   to do this and it lands at 4.44:1 on #F5F5F5, under AA. Disabled controls are
   technically exempt from 1.4.3, but this site's rule is every pairing clears
   AA and there is no reason to spend the exemption: dropping the amber is what
   communicates "closed", and navy on gray-light reads 11.70:1.

   The date is never in the button. A control says what happens when you press
   it, and this one cannot be pressed, so the when goes in the line beneath. */
.dfc-btn--soon {
  /* Grey FILL with NAVY label. Greying the label as well is the usual disabled
     treatment and lands at 4.44:1 on #F5F5F5, under AA; dropping the amber is
     what communicates "closed", and navy on gray-light reads 11.70:1.

     No `font: inherit`. That shorthand was here to normalise the <button>'s UA
     styling and it also reset line-height, which is why this control rendered
     taller than the <a> beside it. Each property is set on its own instead, and
     line-height matches .dfc-btn so the pair are the same height. */
  background: var(--color-gray-light);
  color: var(--color-navy);
  border: 2px solid var(--color-border-strong);
  cursor: not-allowed;
  font-family: var(--font-label);
  font-size: var(--text-base);
  font-weight: var(--weight-semibold);
  line-height: 1;
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
}
/* The lift on .dfc-btn:hover would suggest it still does something. */
.dfc-btn--soon:hover { transform: none; background: var(--color-gray-light); }
.dfc-btn-note {
  font-family: var(--font-body);
  font-size: var(--text-base);
  color: var(--color-navy);
  margin: var(--space-3) 0 0;
}
.dfc-btn-note strong { font-weight: var(--weight-bold); }

/* ── Where the hero's buttons land ────────────────────────────────────────
   Both of them jump down this page rather than off it, and the header is
   sticky. A browser scrolls an anchor to y=0, so without a scroll-margin the
   target's first 96px sit behind the yellow nav bar -- and since both targets
   are groups with 32px of padding, the part that ends up underneath is the
   heading the visitor was just sent to. Measured on the rebuild: #dates landed
   at 0 and "2026 Dates & Locations" at 32.

   #dates is the "2026 Dates & Locations" group, which the amber button and the
   "Location: See below" fact row both point at; #faqs is the FAQ band. IDs
   rather than classes because these two anchors live in page content, where a
   WordPress anchor field writes an id and nothing else to hang this on. Same
   value as .dfc-c2cstop further down, which solved this for the map pins. */
#dates,
#faqs {
  scroll-margin-top: calc(var(--header-h) + var(--space-5));
}

/* ── The polaroids ────────────────────────────────────────────────────────
   A real polaroid is a square image window on a taller frame with a deep
   bottom lip, so the frame is padded 10px on three sides and the caption
   block makes the fourth. Captions are Barlow Condensed, NOT a script face:
   the brand is three families and a handwriting font would be a fourth. */
.dfc-prog-hero__photos {
  position: relative;
  /* 2x2. It was a single overlapping row hung from the stroke; the grid gives
     each frame roughly half again the width at this column, which is what the
     photographs needed, and it turns the brush from a washing line into a mark
     the cluster sits on. */
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  /* Rows tighter than columns. The frames already carry a caption lip and a few
     degrees of rotation, so an equal 24px gap read as a wide band of dead
     off-white between the two rows. */
  column-gap: var(--space-5);
  row-gap: var(--space-1);
  /* start, NOT the default stretch. A stretched frame keeps its image at the
     aspect ratio and dumps the surplus height into the bottom lip, so the lips
     came out at different depths across the four. Reported 2026-09-08. */
  align-items: start;
  /* Clearance, not breathing room. A frame rotated 4deg pushes its corners
     about (height x sin4)/2 past its layout box, and at 1024px that put the
     last frame 17px outside the viewport and gave the page a horizontal
     scrollbar. Measured 2026-09-08. */
  padding: 16px;
}
/* The hero's right column, when what belongs there is not photography.
   Camp to Community runs in four towns on four dates, so the first question its
   hero has to answer is WHERE and WHEN, not what it looks like. The schedule
   takes the slot the photo cluster holds elsewhere, and the two-column
   detection above accepts either.

   White on the hero's off-white, the same call .dfc-steps makes on the same
   ground: grey on off-white is about 1.06:1 and would be no card at all, so the
   card is white with a hairline and elevation. Navy on white is 12.64:1. */
.dfc-prog-hero__aside {
  background: var(--color-white);
  border: 1px solid var(--color-border);
  border-radius: 10px;
  box-shadow: var(--shadow);
  padding: var(--space-5);
  color: var(--color-navy);
}
.dfc-hero-sched__head {
  font-family: var(--font-label);
  text-transform: uppercase;
  letter-spacing: var(--tracking-eyebrow);
  font-size: 0.72rem;
  font-weight: var(--weight-bold);
  color: var(--color-navy);
  margin: 0 0 var(--space-3);
}
.dfc-hero-sched { list-style: none; margin: 0; padding: 0; display: grid; }
/* A rule between rows, not around them: four towns read as one schedule rather
   than as four cards inside a card. */
.dfc-hero-sched li {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: 2px var(--space-3);
  padding: var(--space-3) 0;
  border-top: 1px solid var(--color-border);
}
.dfc-hero-sched li:first-child { border-top: 0; padding-top: 0; }
.dfc-hero-sched li:last-child { padding-bottom: 0; }
.dfc-hero-sched__place {
  font-family: var(--font-headline);
  font-weight: var(--weight-bold);
  font-size: 1.15rem;
  line-height: 1.15;
}
/* The date is the thing being scanned for, so it keeps the body face rather
   than shrinking into a label. */
.dfc-hero-sched__date { font-size: 0.92rem; line-height: 1.3; }

/* One photograph instead of the 2x2 (Family Retreat, 2026-09-10, Dan). A
   programme with a single group shot has nothing to build a cluster from, and
   forcing that shot through the cluster's 1:1 crop cuts people out of both ends
   of a 17:10 frame. The polaroid frame stays; it just takes the whole column at
   the photograph's own ratio.

   The brush circle shrinks with it. At 104%/96% it is sized for a cluster
   taller than it is wide, and behind a single wide frame it read as a stripe
   running off both sides; 88% of a shorter box keeps it a mark the photograph
   sits on. */
.dfc-prog-hero__photos--single { grid-template-columns: minmax(0, 1fr); }
/* And it gets HALF the hero, not the 40% the cluster leaves it. The two-column
   split is sized for a 2x2 of square frames, which is tall enough to hold the
   right-hand side on its own; one wide frame in that same column is a strip
   about 215px deep next to Teen Retreat's 528px block, and it reads as a
   thumbnail of a photograph rather than a photograph. At 1fr 1fr the photograph
   is 516px wide instead of 402px and, cut to 3:2, the block is about 400px deep
   -- the mass Teen Retreat (454x528) and Camp Morris carry.

   :has() on the inner, so it follows the LAYOUT the hero chose rather than a
   page: the cluster heroes keep the split they were designed with, and any
   future single-photograph hero gets this one without anybody remembering to
   add a class.

   INSIDE a min-width query, which is not decoration. The hero stacks to one
   column in @media (max-width: 980px), and a media query adds no specificity
   while :has() does -- so unqualified, this rule wins over the stacking rule
   and keeps two columns all the way down. Measured before that was caught: a
   119px-wide photograph on a 390px phone. */
@media (min-width: 981px) {
  .dfc-prog-hero__inner:has(.dfc-prog-hero__photos--single) {
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  }
}
.dfc-prog-hero__photos--single .dfc-polaroid img { aspect-ratio: auto; }
.dfc-prog-hero__photos--single::before {
  width: 88%;
  height: 128%;
  translate: calc(-50% + 150px) -50%;
}

/* The brand's brush CIRCLE, img/circle-brush-amber.svg. That is circle-blue.svg
   recoloured from #28a8e0 to the brand amber; the brand guide allows a brush
   stroke in any brand colour, so this is a sanctioned variant rather than a new
   asset. It replaces the straight diagonal stroke, which had to be 155% of the
   column just to clear the cluster it sat behind and still only showed at two
   corners. A circle wraps the 2x2 instead of cutting across it.

   preserveAspectRatio="none" in the file, so the round source can be pulled to
   the cluster's proportions: the grid is taller than it is wide, and a true
   circle sized to the width would leave both rows hanging out of it.

   Behind the frames. z-index is untouched because the frames each carry one and
   this pseudo-element does not. */
.dfc-prog-hero__photos::before {
  content: "";
  position: absolute;
  left: 50%; top: 50%;
  width: 104%;
  height: 96%;
  /* Pushed well right of centre so the circle breaks OUT of the photo column on
     that side rather than framing the cluster evenly (Dan, 2026-09-08). The
     hero clips its own overflow, so the stroke runs off into the outer margin
     and stops at the section edge instead of giving the page a scrollbar. */
  translate: calc(-50% + 200px) -50%;
  background: url("../../img/circle-brush-amber.svg") no-repeat 50% 50% / 100% 100%;
  pointer-events: none;
}

/* A wide photograph sitting between a section heading and the content under it.
   First used on Family Retreat (2026-09-11).

   24:7, and the shallowness IS the component. The band exists to break a long
   run of white cards without spending half a screen doing it; a 21:9 study sat
   515px tall in the 1200px column and pushed the activity cards off the first
   screen entirely. 24:7 brings it to 350px and the cards back up.

   BOTH CROPS ANCHOR AT CENTRE, and that is a property of THIS FILE, not a default
   to assume for the next one. Until 2026-09-14 this said `center 85%`, because the
   only source then available was the 1208x728 copy embedded in the 2025 annual
   report PDF — already cropped tight by the report's designer, with the family in
   its lower third, where a centred crop kept canopy and threw the people away.
   The camera original then turned up (4032x3024, shot 19 Oct 2024): a much wider
   frame with the family near the middle. The master cut from it is 16:9 CENTRED
   ON THEM, so centre is now correct and 85% would crop their feet off.

   Reusing this component with a different photograph means re-deriving the anchor
   from that photograph. */
.dfc-photo-band { margin: var(--space-8) 0 var(--space-2); }
.dfc-photo-band img {
  display: block;
  width: 100%;
  /* Same reason as .dfc-polaroid img below: the markup carries width/height for
     CLS, which gives the box a definite height, and aspect-ratio is ignored
     outright unless one axis is auto. */
  height: auto;
  aspect-ratio: 24 / 7;
  object-fit: cover;
  object-position: center center;
  border-radius: var(--radius);
  background: var(--color-off-white);
}
.dfc-photo-band figcaption {
  font-family: var(--font-label);
  font-size: var(--text-sm);
  font-weight: var(--weight-semibold);
  color: var(--color-navy);
  margin-top: var(--space-3);
  line-height: 1.25;
}

/* The ratio does NOT carry to phone widths, and holding it there is the one way
   this component goes visibly wrong. 24:7 of a 342px column is a 100px strip:
   the family becomes a smudge and the band reads as a texture rather than a
   photograph. 16:9 gives it 192px and the people, the lake and the dock all
   come back. Measured at 390px, 2026-09-11.

   16:9 is also the master's own ratio, so below this breakpoint the photograph is
   shown WHOLE — cover crops nothing, and the phone gets the full frame the camera
   shot. That is why the master was cut to 16:9 rather than to the 24:7 the desktop
   band actually displays: one file, no art direction, no <picture>. */
@media (max-width: 781px) {
  .dfc-photo-band img { aspect-ratio: 16 / 9; }
}
.dfc-polaroid {
  position: relative;
  margin: 0;
  flex: 0 0 30%;
  background: var(--color-white);
  padding: 10px 10px 0;
  border-radius: 2px;
  box-shadow: var(--shadow-lg);
}
.dfc-polaroid:not(:first-child) { margin-left: -7.5%; }
/* No clothespins. They were drawn in the first study and cut on sight: a little
   coloured rectangle pinning each frame to the stroke reads as clip art, and it
   also forces the literal reading that the stroke is a washing line. Without
   them the stroke is just a brush mark the photographs sit on top of, which is
   what the brand guide actually describes a stroke doing. */
.dfc-polaroid img {
  display: block;
  width: 100%;
  /* height:auto is LOAD-BEARING, not tidiness. The markup carries width/height
     attributes for CLS, and those give the box a definite height. aspect-ratio
     only resolves when one axis is auto, so without this the ratio below is
     ignored outright and the frames render at the file's pixel height: 594px
     tall in a 259px column, which is how the first deploy looked. */
  height: auto;
  aspect-ratio: 1 / 1;
  object-fit: cover;
  background: var(--color-off-white);
}
/* Placeholder frame, for a programme whose photography has not been shot or
   chosen yet (Teen Retreat, 2026-09-10). A real <img> pointed at a file that
   does not exist renders a broken-image glyph and a failed request, and a 1x1
   transparent gif renders an empty white square that reads as a bug. This is a
   frame with no image in it at all, carrying the same 1:1 box, the same lip and
   the same caption, so the hero's layout is exactly what it will be once the
   photographs land and swapping one in is a one-line edit.

   Deliberately conspicuous. Off-white ground, a dashed navy edge and the word
   in the caption, because a placeholder that looks finished is a placeholder
   that ships. Navy on off-white is 11.79:1. */
.dfc-polaroid__ph {
  display: flex;
  align-items: center;
  justify-content: center;
  aspect-ratio: 1 / 1;
  background: var(--color-off-white);
  border: 2px dashed var(--color-navy);
  border-radius: 2px;
  font-family: var(--font-label);
  font-size: 0.78rem;
  font-weight: var(--weight-bold);
  text-transform: uppercase;
  letter-spacing: var(--tracking-label);
  color: var(--color-navy);
  text-align: center;
  padding: var(--space-2);
}
/* A cluster made of placeholder frames has no intrinsic width to stretch from.
   Below 980px the cluster is capped at 720px and centred with auto margins, and
   auto inline margins switch a grid item off stretch onto fit-content, so its used
   width becomes its max-content. A frame holding a photograph supplies that from
   the file's own dimensions; a frame holding nothing supplies only the width of
   the word inside it, which collapsed the whole column to 144px through the
   768-980 band. Measured 2026-09-14 on Camp to Community.

   :has(), so it only ever applies to a cluster that is standing in for
   photography. The clusters that already ship photographs keep the fit-content
   sizing they were built with. */
.dfc-prog-hero__photos:has(.dfc-polaroid__ph) { width: 100%; }
.dfc-polaroid figcaption {
  /* Handwriting, not set type. A label in Barlow read as something printed on
     the frame at the factory; the point of a polaroid caption is that a person
     wrote it. See the exception note at the top of fonts.css: this selector is
     the only place --font-script is allowed.

     No uppercase and no letter-spacing. Both are label treatments and both make
     a script face look like a mistake. The size goes up from 14px to 19px
     because Caveat's x-height is far smaller than Barlow's at the same nominal
     size, so 14px Caveat reads noticeably smaller than the 14px it replaces. */
  font-family: var(--font-script);
  font-size: 1.1875rem;
  font-weight: 600;
  color: var(--color-navy);
  /* A FIXED lip. The depth of the white under the photo is the most
     recognisable thing about a polaroid, so it has to be identical on all four;
     centring inside a fixed box keeps one depth whatever the caption does. */
  height: 46px;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0 6px;
  line-height: 1.1;
  text-align: center;
}
/* Rotation only, and row two MIRRORS row one: same angles, opposite signs. Both
   rows leaning the same way read as a repeated pattern rather than a pile of
   photographs. -4/+3 over +4/-3.

   No vertical stagger. In a grid a margin-top on any frame grows its whole ROW,
   so the stagger this used to carry was itself the gap between the rows.

   The angles CYCLE every four rather than stopping at four. A cluster is a 2x2
   and never has a fifth frame, so nothing about the clusters changes; a
   .dfc-carousel--polaroid track holds as many slides as the programme has
   photographs, and the Family Retreat hero has six. Written as a flat
   :nth-child(4), slides five and six came past dead upright in the middle of a
   stack that tilts, which reads as the tilt having failed rather than as a
   choice. The z-index rungs repeat with the angles, which only matters where
   frames overlap -- in a cluster, which never gets that far. */
.dfc-polaroid:nth-child(4n + 1) { rotate: -4deg; z-index: 4; }
.dfc-polaroid:nth-child(4n + 2) { rotate: 3deg;  z-index: 3; }
.dfc-polaroid:nth-child(4n + 3) { rotate: 4deg;  z-index: 2; }
.dfc-polaroid:nth-child(4n)     { rotate: -3deg; z-index: 1; }
/* The same four angles inside a LOOPING carousel, keyed off data-dfc-slot
   rather than :nth-child. carousel.js puts a copy of the last slide in front
   of the first so the track can go round instead of back, which moves every
   real slide one child along; data-dfc-slot is the place it had before that,
   and the copies carry their original's. Without this each frame comes up at
   its neighbour's angle and a copy disagrees with the slide it stands in
   for, so the invisible swap at the end of a lap shows as a twitch.
   Scoped to --polaroid: --band undoes the tilt on purpose and must stay flat. */
.dfc-carousel--polaroid .dfc-polaroid[data-dfc-slot="1"] { rotate: -4deg; }
.dfc-carousel--polaroid .dfc-polaroid[data-dfc-slot="2"] { rotate: 3deg; }
.dfc-carousel--polaroid .dfc-polaroid[data-dfc-slot="3"] { rotate: 4deg; }
.dfc-carousel--polaroid .dfc-polaroid[data-dfc-slot="4"] { rotate: -3deg; }

@media (max-width: 980px) {
  .dfc-prog-hero { padding: var(--space-8) var(--space-5) var(--space-10); }
  .dfc-prog-hero__inner { grid-template-columns: minmax(0, 1fr); gap: var(--space-8); }
  /* The Lead step below 981px is 20px, and it stays 20px all the way down --
     Lead is one of the rows in design-system 4.3 whose tablet and mobile tiers
     are the same, so there is no @media (max-width: 767px) partner for this. */
  .dfc-prog-hero__sub,
  .dfc-prog-hero__facts { font-size: var(--text-lg); }
  .dfc-prog-hero__sub { max-width: 46ch; }
  /* The hero has stacked, so the line gets the full measure. Capped so four
     frames do not stretch to 300px each on a wide tablet. */
  .dfc-prog-hero__photos { max-width: 720px; margin-inline: auto; }
  /* One placeholder frame is a SQUARE, and a square at the 720px the cap allows
     is a 720px-tall box sitting under a stacked column of text. Held near the
     width the frame has on desktop instead, so the empty slot reads as a photo
     to come rather than as the page. A real photograph keeps the full 720px;
     :has() confines this to the placeholder. */
  .dfc-prog-hero__photos--single:has(.dfc-polaroid__ph) { max-width: 480px; }
  /* And the brush circle comes back to centre with it. On the two-column layout
     the circle is pushed right so it breaks OUT of the photo column and is
     clipped by the section edge. Stacked, the frame is centred in the full
     measure and there is nothing for it to run off into, so the same push just
     parks a ring beside the frame.

     EVERY single frame, not only the placeholder one. This was scoped to the
     placeholder while C2C was the only --single hero standing empty, with a note
     that the photograph variant had the same tell and was a separate change.
     Camp to Community has its group shot now (2026-09-21) and Family Retreat has
     had one all along, so that separate change is this line: without it the two
     heroes that actually carry a photograph are the two the rule misses. */
  .dfc-prog-hero__photos--single::before { translate: -50% -50%; }
}
@media (max-width: 767px) {
  /* Below 768 a four-across line puts each frame under 100px, which is smaller
     than its own caption needs. Back to 2x2, and the twine comes down with it:
     a string over two rows would have to sag twice and reads as a mistake. */
  .dfc-prog-hero__photos {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: var(--space-4);
    align-items: start;
    /* Keeps the rotation clearance, drops the twine's headroom. */
    padding: 0 8px;
  }
  .dfc-prog-hero__photos::before { display: none; }
  /* One frame stays one frame. The 2x2 above is the rule for a CLUSTER, and the
     single-photo variant loses its one-column override to it: same specificity,
     later in the file. Without this, a hero carrying one frame renders it at
     half the width of the phone with nothing beside it.

     The order matters. This has to sit AFTER the rule it is correcting, so keep
     it below .dfc-prog-hero__photos in this block, not above it. */
  .dfc-prog-hero__photos--single { grid-template-columns: minmax(0, 1fr); }
  .dfc-polaroid { flex: none; padding: 6px 6px 0; }
  .dfc-polaroid:not(:first-child) { margin-left: 0; }
  /* Shallower angles: at ~150px wide the frames sit close enough that 4deg
     reads as a mistake rather than a scatter. */
  .dfc-polaroid:nth-child(4n + 1) { rotate: -2.5deg; margin-top: 0; }
  .dfc-polaroid:nth-child(4n + 2) { rotate: 2deg;    margin-top: 0; }
  .dfc-polaroid:nth-child(4n + 3) { rotate: 2.5deg;  margin-top: 0; }
  .dfc-polaroid:nth-child(4n)     { rotate: -2deg;   margin-top: 0; }
  /* The looping carousel's slot angles, at the same shallower tier. */
  .dfc-carousel--polaroid .dfc-polaroid[data-dfc-slot="1"] { rotate: -2.5deg; }
  .dfc-carousel--polaroid .dfc-polaroid[data-dfc-slot="2"] { rotate: 2deg; }
  .dfc-carousel--polaroid .dfc-polaroid[data-dfc-slot="3"] { rotate: 2.5deg; }
  .dfc-carousel--polaroid .dfc-polaroid[data-dfc-slot="4"] { rotate: -2deg; }
  .dfc-polaroid figcaption { font-size: 1rem; height: 38px; }
}

@media (max-width: 781px) {
  /* Wins over the clamp above: this rule is later in the file and the clamp's
     620px floor would otherwise apply all the way down to 320px. */
  .dfc-home-hero { min-height: 480px; }
  .dfc-page-hero { clip-path: polygon(0 0, 100% 0, 100% calc(100% - 26px), 0 100%); padding-bottom: 64px !important; }
}

/* ---- Legal / policy pages (Privacy Policy, Terms of Use) --------------
   Added 2026-08-03, mirroring the understrap child theme on prod/staging.
   The wrapper class is applied in the page content itself, so it is
   environment-independent. The block theme renders the title as
   .wp-block-post-title rather than .entry-header, hence both selectors. */
/* Gutter-padded and capped at the WIDE measure, exactly like .dfc-page-title,
   so the body text starts on the same left edge as the title above it. The
   understrap container used to supply this; the block theme supplies nothing,
   and from launch (2026-10-08) both pages ran flush to the viewport edge. */
.dfc-policy {
  box-sizing: border-box;
  max-width: calc(var(--wide) + 2 * var(--gutter));
  margin-inline: auto;
  padding: 3rem var(--gutter) 4rem;
  text-align: left;
}
.dfc-policy > h2:first-child { margin-top: 0; }
.dfc-policy h2 { margin-top: 2.75rem; margin-bottom: .75rem; }
.dfc-policy p, .dfc-policy li { line-height: 1.7; }
.dfc-policy ul { margin: 0 0 1.5rem 1.25rem; }
.dfc-policy li { margin-bottom: .6rem; }

/* Space between the site header and the page title */
main:has(.dfc-policy) .wp-block-post-title,
article:has(.dfc-policy) .entry-header { padding-top: 2.5rem; }

/* 767, not 768. design-system.md §6.1 puts the mobile tier at <=767px and line
   576 of that doc specifically calls out the legacy site for switching at 768.
   This rule was the one place in the theme that repeated it, so at exactly
   768px the legal pages took mobile padding inside a tablet type scale.
   Corrected 2026-09-08. */
@media (max-width: 767px) {
  .dfc-policy { padding: 2rem var(--gutter) 2.5rem; }
  main:has(.dfc-policy) .wp-block-post-title,
  article:has(.dfc-policy) .entry-header { padding-top: 1.5rem; }
}

/* ---- Events page: self-reflowing card grid ----------------------------
   Added 2026-08-04. This replaces paired wp:columns rows. Those rows were
   why past events had to be pruned by hand: dfc-events.js hides an expired
   card, but in a column pair that leaves a half-width orphan and an empty
   row behind. One grid container closes the gap on its own, so the page
   now maintains itself as events pass.

   Two columns, stacking at 781px, matches what wp:columns did before, so
   the layout is unchanged to look at. */
.dfc-event-grid {
  display: grid;
  /* Three across (2026-09-10, Dan). Two made each card enormous and pushed the
     third and fourth events below the fold on a laptop; the whole point of this
     grid is that a family can see what is coming up at a glance. The clamp and
     the Learn more dialog below are what make three viable, because the card no
     longer has to be as tall as its longest description. */
  grid-template-columns: repeat(3, 1fr);
  gap: 24px;
}
/* The documented tablet tier, matching .dfc-steps and .dfc-board. */
@media (max-width: 980px) {
  .dfc-event-grid { grid-template-columns: repeat(2, 1fr); }
}

/* Core's flow layout adds vertical margins between children. The grid gap
   already handles spacing, so cancel them or every row gains 24px twice. */
.dfc-event-grid > * { margin-block: 0 !important; }

/* ── Volunteer role grid ──────────────────────────────────────────────────
   The four volunteer roles were full-width white slabs stacked down an
   off-white band, so /get-involved/volunteer/ read as a long scroll of
   near-identical boxes and no two roles could be compared without scrolling
   between them. Two across, equal height (2026-09-10, Dan).

   Two, not the event grid's three. A role card is mostly prose with one button
   under it; at three across the descriptions set to roughly 30 characters a
   line, which is below the readable measure. Same 24px gap and the same
   flow-margin cancel as .dfc-event-grid, and it drops to one column at the
   same documented phone tier. */
.dfc-role-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: var(--space-5);
}
.dfc-role-grid > * { margin-block: 0 !important; }
/* Fill the track so two cards in a row read as one band. */
.dfc-role-grid > .wp-block-group { height: 100%; }
@media (max-width: 781px) {
  .dfc-role-grid { grid-template-columns: 1fr; }
}

/* Fill the track so cards in a row read as one band. */
.dfc-event-grid > .dfc-event { height: 100%; }

@media (max-width: 781px) {
  .dfc-event-grid { grid-template-columns: 1fr; }
}

/* ── The event photo links to that event's page ───────────────────────────
   Added 2026-09-16 (Dan). The picture was the one part of a card that did
   nothing: Register Now leaves for CampInTouch and Learn more opens the
   description in a dialog, so there was no route from the grid to the
   programme page that explains the event. The photo is now that route.

   The anchor is wrapped INSIDE the figure by the image block itself
   (linkDestination: custom), so it stays editable in Gutenberg and the card
   keeps its markup shape. Two consequences that need the rules below:

   1. The card's 10px top corners are an inline radius on the FIGURE, and the
      img took them by inheriting from it. With an <a> in between, that chain
      breaks unless the anchor inherits too, so the photo would square off at
      the top of every card.
   2. The anchor is inline by default, which leaves a descender gap under the
      image and makes the hit area shorter than the picture looks. display:
      block fixes both.

   The hover is a small zoom held inside overflow: hidden, the quiet version
   of what .dfc-progcard does with its full-card overlay. An event card
   already carries two labelled controls, so a third "Learn More" wash over
   the photo would be the loudest thing in the grid. */
.dfc-event > .wp-block-image a {
  display: block;
  overflow: hidden;
  border-radius: inherit;
}
.dfc-event > .wp-block-image a img {
  display: block;
  width: 100%;
  border-radius: inherit;
  transition: transform var(--duration) var(--ease);
}
.dfc-event > .wp-block-image a:hover img,
.dfc-event > .wp-block-image a:focus-visible img { transform: scale(1.04); }
/* An event with nowhere to link has a BARE img, which matches neither selector
   above, so it squared off at the top of a 10px-rounded card while every
   programme card rounded correctly. Found on the Oct 23 meet & greet
   (2026-09-21): a meet & greet has no programme page to link to, and on a
   full-bleed navy graphic the square corners are unmissable.

   WP core's own fallback does not cover it. Core ships
   .wp-block-image[style*="border-radius"] img, and the figure's inline style
   reads border-top-left-radius — which does not contain that substring — so
   that rule matches no card here, linked or not.

   display: block for the reason the anchor has it: an inline img leaves a
   descender gap under the picture. No hover zoom, because there is nothing to
   click. */
.dfc-event > .wp-block-image img {
  display: block;
  border-radius: inherit;
}
/* The ring sits outside the photo, where it is on white rather than over the
   picture it is meant to outline. */
.dfc-event > .wp-block-image a:focus-visible {
  outline: var(--focus-width) solid var(--color-focus);
  outline-offset: var(--focus-offset);
}
@media (prefers-reduced-motion: reduce) {
  .dfc-event > .wp-block-image a img { transition: none; }
  .dfc-event > .wp-block-image a:hover img,
  .dfc-event > .wp-block-image a:focus-visible img { transform: none; }
}

/* ── Event description clamp + Learn more ─────────────────────────────────
   Same shape as the People directory's bio clamp, and for the same reason: at
   three across, one 90-word description sets the height of every card in its
   row. PROGRESSIVE ENHANCEMENT in that order, exactly as people.js does it:
   the server prints the whole description and hides the button, and dfc-events.js
   adds .dfc-event--clamped and un-hides the button only where the clamp really
   cut something. With JavaScript off nothing disappears and a crawler still
   reads every word from the initial HTML.

   Clamping by LINES, not max-height, so the cut never lands mid-line and no
   fade mask is needed to disguise a half row. */
.dfc-event--clamped .dfc-event__desc {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 4;
  line-clamp: 4;
  overflow: hidden;
}
/* A trailing margin inside a -webkit-box adds a phantom line to the measurement. */
.dfc-event--clamped .dfc-event__desc > :last-child { margin-bottom: 0; }

/* The card's actions. Register Now stays the prominent one; Learn more is the
   quiet outline beside it, the same navy-outline treatment .dfc-person__more
   uses and for the same reason: amber is CTA-only, and four amber pills on one
   grid would outshout Donate. */
.dfc-event__actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
  margin-top: var(--space-4);
}
.dfc-event__more {
  display: inline-flex;
  align-items: center;
  box-sizing: border-box;
  min-height: 44px;
  padding: var(--space-2) var(--space-4);
  background: transparent;
  border: 2px solid var(--color-navy);
  border-radius: var(--radius);
  color: var(--color-navy);
  font-family: var(--font-label);
  font-size: var(--text-sm);
  font-weight: var(--weight-semibold);
  line-height: 1;
  text-transform: uppercase;
  letter-spacing: var(--tracking-label);
  cursor: pointer;
  transition: background-color 0.15s ease, color 0.15s ease;
}
.dfc-event__more:hover { background: var(--color-navy); color: var(--color-text-on-dark); }
.dfc-event__more:focus-visible {
  outline: var(--focus-width) solid var(--color-focus);
  outline-offset: 2px;
}
.dfc-event__more[hidden] { display: none; }

/* ── Full-description dialog ──────────────────────────────────────────────
   Native <dialog>, the third use of this pattern on the site after the bio
   dialog and the Impact page's modal. showModal() supplies focus trapping,
   Escape, inertness, ::backdrop and focus restoration; none of it is worth
   reimplementing, and it needs no JS from the forms plugin. */
.dfc-event-modal {
  border: 0;
  padding: 0;
  background: transparent;
  width: min(42rem, calc(100vw - 2 * var(--space-5)));
  max-height: calc(100vh - 2 * var(--space-6));
  color: var(--color-navy);
}
.dfc-event-modal::backdrop {
  background: rgba(20, 38, 74, 0.6);
  backdrop-filter: blur(3px);
  -webkit-backdrop-filter: blur(3px);
}
.dfc-event-modal__in {
  position: relative;
  background: var(--color-white);
  border: 2px solid var(--color-navy);
  border-radius: var(--radius-lg);
  padding: var(--space-6);
  box-shadow: 0 24px 70px rgba(0, 0, 0, 0.35);
  /* The panel scrolls, not the page, so a long description on a short laptop
     never pushes the close button off the bottom. */
  max-height: calc(100vh - 2 * var(--space-6));
  overflow-y: auto;
}
.dfc-event-modal__title {
  font-family: var(--font-headline);
  font-weight: var(--weight-bold);
  font-size: var(--text-2xl);
  line-height: var(--leading-tight);
  color: var(--color-navy);
  /* Clear of the close button. */
  padding-right: 44px;
  margin: 0 0 var(--space-2);
}
.dfc-event-modal__when {
  font-size: var(--text-sm);
  font-weight: var(--weight-bold);
  margin: 0 0 var(--space-4);
}
.dfc-event-modal__body p { margin: 0 0 12px; }
.dfc-event-modal__body p:last-child { margin-bottom: 0; }
.dfc-event-modal__close {
  position: absolute;
  top: var(--space-4);
  right: var(--space-4);
  width: 44px;
  height: 44px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: transparent;
  border: 2px solid var(--color-navy);
  border-radius: var(--radius-pill);
  color: var(--color-navy);
  font-size: 1.4rem;
  line-height: 1;
  cursor: pointer;
}
.dfc-event-modal__close:hover { background: var(--color-navy); color: var(--color-text-on-dark); }
.dfc-event-modal__close:focus-visible {
  outline: var(--focus-width) solid var(--color-focus);
  outline-offset: 2px;
}
@media (prefers-reduced-motion: reduce) {
  .dfc-event__more { transition: none; }
}

/* Revealed by dfc-events.js only when every dated event has expired. */
.dfc-events-empty { display: none; }
.dfc-events-empty.is-visible { display: block; }

/* ---- People directory (DFC People plugin) -----------------------------
   Added 2026-08-04. Matches the hand-built Our Team / Our Board cards this
   replaces, so swapping the plugin in does not change the design.

   2026-09-08: three across on desktop, and long bios clamp to a summary with
   a "Read full bio" dialog. Sixteen board members at two across, each card
   stretched to the tallest bio in its row, ran the page to 11,300px. The two
   changes only work together: three narrower columns with the FULL bio in
   each is taller still, because a narrow column wraps the same words into
   more lines. Clamping is what makes the third column pay.

   The bio is always in the page source. The clamp is applied by JavaScript
   (.dfc-person--clamped), so with JS off nothing is hidden from a reader or
   from a crawler. See dfc-people/assets/people.js. */
.dfc-people-grid {
  display: grid;
  gap: 24px;
  margin: 32px 0;
}
.dfc-people-grid--1 { grid-template-columns: 1fr; }
.dfc-people-grid--2 { grid-template-columns: repeat(2, 1fr); }
.dfc-people-grid--3 { grid-template-columns: repeat(3, 1fr); }
.dfc-people-grid--4 { grid-template-columns: repeat(4, 1fr); }

/* The sanctioned breakpoints, and only those: design system 6.1 says tablet
   (768-980) is two columns and mobile is one. This grid used to switch at 1080
   and 781, which were a twelfth and an eighth distinct breakpoint in a
   stylesheet the design system asks to hold three - and the 781 in particular
   put a 768px tablet in ONE column while its container was still 705px wide. */
@media (max-width: 980px) {
  .dfc-people-grid--3,
  .dfc-people-grid--4 { grid-template-columns: repeat(2, 1fr); }
}

@media (max-width: 767px) {
  .dfc-people-grid { grid-template-columns: 1fr; }
}

.dfc-person {
  background: var(--color-gray-light);
  color: var(--color-navy);
  border-radius: 10px;
  padding: 24px;
}
.dfc-person__photo {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 1 / 1;
  object-fit: cover;
  border-radius: 8px;
  margin: 0 0 16px;
}
/* A 1:1 photo takes whatever width the card gives it, and in a single column
   the card IS the container. At a 767px tablet that was a 671px-tall portrait
   in a 1,008px card, which made the tablet the tallest viewport on the whole
   site - 18,245px on Our Board, worse than any phone. Two columns fix 768-980;
   this fixes every remaining single-column case, including a grid an editor
   sets to one column at any width.

   Cap the WIDTH, never the height. A max-height with object-fit: cover would
   letterbox a portrait headshot and take the top of the head off; capping
   width keeps the ratio, so the photograph is simply smaller. */
.dfc-people-grid--1 .dfc-person__photo {
  max-width: 320px;
  margin-inline: auto;
}
@media (max-width: 767px) {
  .dfc-people-grid .dfc-person__photo {
    max-width: 320px;
    margin-inline: auto;
  }
}
.dfc-person__name {
  font-family: var(--font-label);
  color: var(--color-navy);
  margin: 0 0 4px;
}
.dfc-person__title {
  font-weight: 700;
  margin: 0 0 12px;
}
/* A missing title must look unfinished, never like a considered blank. */
.dfc-person__title--todo { color: var(--color-error); }

.dfc-person__bio p { margin: 0 0 12px; }
.dfc-person__bio p:last-child { margin-bottom: 0; }

/* The summary. Five lines is about 35 words at a three-across column width:
   enough for who someone is and their connection to T1D, which is what the
   card is for. The class is added by JavaScript, never by PHP, so an
   unenhanced page shows the whole bio.

   -webkit-line-clamp is the interoperable spelling (Chrome, Safari, Firefox
   68+); the unprefixed line-clamp is listed after it for browsers that have
   moved on. Clamping by lines rather than by max-height means the cut never
   lands mid-line, so no fade mask is needed to disguise a half-row of text. */
.dfc-person--clamped .dfc-person__bio {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 5;
  line-clamp: 5;
  overflow: hidden;
}
/* Inside a -webkit-box the paragraph margins survive but a trailing one adds
   a phantom line to the clamp measurement. */
.dfc-person--clamped .dfc-person__bio > :last-child { margin-bottom: 0; }

/* Read full bio.
   NOT amber: amber is CTA-only and sixteen amber pills would outshout Donate.
   A navy outline gives the 3:1 boundary every button on this site needs
   without claiming to be the page's primary action. 44px tall for the touch
   target. Hidden by PHP and revealed only where the clamp really cut text. */
.dfc-person__more {
  display: inline-flex;
  align-items: center;
  box-sizing: border-box;
  margin: 14px 0 0;
  min-height: 44px;
  padding: var(--space-2) var(--space-5);
  background: transparent;
  border: 2px solid var(--color-navy);
  border-radius: var(--radius);
  color: var(--color-navy);
  font-family: var(--font-label);
  font-size: var(--text-base);
  font-weight: var(--weight-semibold);
  line-height: 1;
  text-transform: uppercase;
  letter-spacing: var(--tracking-label);
  cursor: pointer;
  transition: background-color 0.15s ease, color 0.15s ease;
}
.dfc-person__more:hover {
  background: var(--color-navy);
  color: var(--color-text-on-dark);
}
.dfc-person__more:focus-visible {
  outline: var(--focus-width) solid var(--color-focus);
  outline-offset: 2px;
}
.dfc-person__more[hidden] { display: none; }

/* ── Full-bio dialog ──────────────────────────────────────────────────────
   Native <dialog>, same pattern as the Impact page's modal: showModal() gives
   focus trapping, Escape, inertness, ::backdrop and focus restoration free. */
.dfc-person-modal {
  border: 0;
  padding: 0;
  background: transparent;
  width: min(46rem, calc(100vw - 2 * var(--space-5)));
  max-height: calc(100vh - 2 * var(--space-6));
  color: var(--color-navy);
  /* Only the panel scrolls. <dialog> defaults to overflow:auto, and the panel's
     border pushed it a few px past this max-height, so a long bio (Anne's,
     2026-10-08) showed a scrollbar inside a scrollbar. */
  overflow: hidden;
}
/* Set by people.js for as long as a bio dialog is open. showModal() does not
   stop the page behind the backdrop from scrolling; --dfc-scrollbar carries
   the width of the scrollbar this hides, so the layout does not jump sideways
   as it goes. */
html.dfc-modal-open {
  overflow: hidden;
  padding-right: var(--dfc-scrollbar, 0px);
}
.dfc-person-modal::backdrop {
  background: rgba(20, 38, 74, 0.6);
  backdrop-filter: blur(3px);
  -webkit-backdrop-filter: blur(3px);
}
.dfc-person-modal__in {
  position: relative;
  background: var(--color-white);
  border: 2px solid var(--color-navy);
  border-radius: var(--radius-lg);
  padding: var(--space-6);
  box-shadow: 0 24px 70px rgba(0, 0, 0, 0.35);
  /* The panel, not the page, scrolls: a 280-word bio on a short laptop must
     not push the close button off the bottom. */
  box-sizing: border-box;
  max-height: calc(100vh - 2 * var(--space-6));
  overflow-y: auto;
}
.dfc-person-modal__head {
  display: flex;
  align-items: center;
  gap: var(--space-5);
  /* Clear of the close button. */
  padding-right: 44px;
  margin: 0 0 var(--space-5);
}
.dfc-person-modal__photo .dfc-person__photo {
  width: 96px;
  height: 96px;
  margin: 0;
  border-radius: 50%;
}
.dfc-person-modal__name {
  font-family: var(--font-label);
  font-size: var(--text-2xl);
  color: var(--color-navy);
  margin: 0 0 4px;
}
.dfc-person-modal__title {
  font-weight: 700;
  margin: 0;
}
/* One step under body text: a dialog this narrow read as a wall at 18px. */
.dfc-person-modal__bio { font-size: var(--text-base); }
.dfc-person-modal__bio p { margin: 0 0 12px; }
.dfc-person-modal__bio p:last-child { margin-bottom: 0; }
.dfc-person-modal__close {
  position: absolute;
  top: var(--space-2);
  right: var(--space-2);
  display: flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  box-sizing: border-box;
  background: transparent;
  border: 0;
  border-radius: 50%;
  cursor: pointer;
  font-size: 1.9rem;
  line-height: 1;
  color: var(--color-navy);
}
.dfc-person-modal__close:hover { background: var(--color-gray-light); }
.dfc-person-modal__close:focus-visible {
  outline: var(--focus-width) solid var(--color-focus);
  outline-offset: 2px;
}

@media (max-width: 600px) {
  .dfc-person-modal__in { padding: var(--space-5) var(--space-4) var(--space-6); }
  .dfc-person-modal__head { gap: var(--space-4); }
  .dfc-person-modal__photo .dfc-person__photo { width: 72px; height: 72px; }
}

.dfc-person__links { margin: 14px 0 0; display: flex; flex-wrap: wrap; gap: 12px; }
.dfc-person__link {
  font-family: var(--font-label);
  font-size: 13px;
  text-transform: uppercase;
  letter-spacing: .04em;
  color: var(--color-navy);
}
.dfc-person__link:focus-visible { outline: 3px solid var(--color-sky); outline-offset: 2px; }

/* Editor placeholder for the People block. */
.dfc-people-placeholder {
  border: 1px dashed var(--color-border);
  border-radius: 8px;
  padding: 16px;
  background: var(--color-gray-light);
  color: var(--color-navy);
  font-family: var(--font-label);
}

/* ── Page-title band ──────────────────────────────────────────────────────
   Band color follows the JOURNEY, set by the body class in functions.php,
   never by page ID. Titles are navy on light bands and white on dark ones,
   so every combination clears AA. See docs/design-system.md §10.6.

   COLOURED BANDS ARE RETIRED, estate-wide, 2026-09-08 (Dan). The band used to
   take its colour from the journey: yellow on Programs, sky on Get Help, dark
   green on Give, navy on About, green on Utility. Programs went first, because
   a yellow band under a yellow header rendered as a 190px slab with the title
   marooned in the middle of it; the same objection turned out to apply to all
   of them. Every band is now transparent with navy type, which reads 11.79:1
   on off-white and lets the page surface run straight up to the header.

   The .j-* classes still exist and still set the body class, because the
   journey is used elsewhere. They no longer set a band colour. Do not
   reintroduce one per page type. */
.dfc-page-title {
  background: var(--band-bg);
  color: var(--band-text);
  /* Gutter-padded and full-bleed, exactly like the .dfc-prog-hero section. */
  padding: var(--space-10) var(--gutter) var(--space-8);
  /* Explicit, not inherited. The band renders inside wp:group so a centered
     alignment authored anywhere up the tree would otherwise reach it, and a
     60px display title drifting off the content-left edge is exactly what the
     2026-09-08 review flagged. */
  text-align: left;
}
/* ...and text-align alone was not enough, which is what the client saw on
   2026-09-10: every title band still LOOKED centred beside /get-involved/events/.
   The reason is the MEASURE, not the alignment. The band is a wp:group with a
   constrained layout, so WordPress emits

     .is-layout-constrained > :where(:not(.alignleft):not(.alignright):not(.alignfull))
       { max-width: var(--wp--style--global--content-size); margin-left: auto !important }

   which pins the h1 to the 780px CONTENT measure and centres it. At 1440px that
   put T1D SUPPORT RESOURCES at x=330 while the page's own sections started at
   x=150 and the Events hero title started at x=120 - a 60px display title
   stranded 210px inside its own page. Events escaped it only because it opens
   with .dfc-prog-hero instead, whose __inner sits on the WIDE measure.

   So give the band's contents that same wide measure. WordPress already forces
   the auto side-margins with !important, so max-width is the only thing left to
   set, and brand.css is enqueued after global-styles-inline-css, which is what
   lets an equal-specificity selector win. Measured against the Events hero at
   1440 / 1280 / 1024 / 768 / 390: 120 / 40 / 24 / 24 / 24 on both. Identical.

   This also fixes a second thing at once. The band carried ZERO horizontal
   padding, so under 780px the title sat flush to the viewport edge (x=0) while
   every content section kept its 24px gutter. That is why the gutter went onto
   the band above rather than onto the h1. */
.dfc-page-title > * {
  max-width: var(--wide);
}
/* ...and the content under it now does the same. Pages built on the locked
   off-white lede (FAQs, Disclaimer, Scholarships, Fundraise Your Fee, Packing
   List and the rest) put their content on three left edges, none of them the
   title's: the lede on its 900px measure, sections on 1100px, a few on the
   780px default. At 1440 that was 270 / 170 / 330 against a title at 120.
   Program pages already sat on the wide measure at 120, which is the edge the
   client asked for on 2026-10-08. Scoped to pages that open with that lede, so
   home and the program pages are untouched; centred blocks stay centred. */
main:has(> .wp-block-post-content > .wp-block-group.alignfull.has-off-white-background-color:first-child)
  .wp-block-post-content > .is-layout-constrained > :where(:not(.alignleft):not(.alignright):not(.alignfull)) {
  max-width: var(--wide);
}
/* The coloured callout boxes inside those sections (the yellow "how it works"
   panels) kept the 780px default inside the box, so their heading and text sat
   centred at 330 in a box that now starts at 120. Fill the box instead; its own
   padding is the inset. */
main:has(> .wp-block-post-content > .wp-block-group.alignfull.has-off-white-background-color:first-child)
  .wp-block-post-content > .is-layout-constrained > .wp-block-group.has-background.is-layout-constrained > :where(:not(.alignleft):not(.alignright):not(.alignfull)) {
  max-width: none;
}
.dfc-page-title h1 {
  font-family: var(--font-display);
  /* Same single-weight reset as the home hero: stop synthetic bold from
     destroying Zing Rust's texture. */
  font-weight: 400;
  font-size: var(--text-5xl);
  line-height: 1.05;
  text-transform: uppercase;
  color: inherit;
  margin: 0;
}
/* The display face on a page that supplies its own h1 in normal flow, rather
   than through the title band or the hero (Contact, 2026-09-10, Dan). Same spec
   as .dfc-page-title h1 including the weight-400 reset, which is load-bearing:
   Zing Rust ships one weight and theme.json sets h1 to 700, so without it the
   browser synthesises a bold and fills in the grunge texture.

   Paired with .dfc-hide-page-title, which is how such a page turns the band off
   in the first place. */
.dfc-hide-page-title h1 {
  font-family: var(--font-display);
  font-weight: 400;
  font-size: var(--text-5xl);
  line-height: 1.05;
  text-transform: uppercase;
}
.dfc-page-title .dfc-eyebrow {
  font-family: var(--font-label);
  font-size: var(--text-base);
  font-weight: var(--weight-semibold);
  text-transform: uppercase;
  letter-spacing: var(--tracking-eyebrow);
  color: inherit;
  margin: 0 0 var(--space-2);
}
/* The eyebrow used to be hidden on Utility alone, because green could not carry
   16px type at AA. With every band transparent the eyebrow is navy on the page
   surface everywhere, so the contrast reason is gone and the rule with it. */

/* No .j-* band colours. The default in dfc-tokens.css is transparent + navy and
   every journey now uses it. Left here as the place a band would go if one is
   ever wanted again, so the next person finds the decision rather than the
   absence of one. */

@media (max-width: 980px) { .dfc-page-title h1 { font-size: var(--text-4xl); } }
@media (max-width: 767px) { .dfc-page-title h1 { font-size: var(--text-3xl); } }

/* ── Amber buttons on light surfaces need a visible edge ──────────────────
   WCAG 1.4.11 asks for 3:1 on the boundary of an interactive control. Amber
   measures 1.80:1 on white, 1.74:1 on off-white and 1.65:1 on light gray, so
   a filled amber button has no discernible edge on any of them. The navy
   border reads at 11.59:1 or better and costs no layout change, since the
   button spec already reserves a 2px transparent border.

   Widened 2026-08-10 from amber alone to EVERY filled button. Naming one colour
   was the bug: sky is 2.68:1 on white and 2.59:1 on off-white and had no edge at
   all across 37 pages, and the file block's Download button was amber with no
   edge and matched no selector we had. Every accent we use is under 3:1 on every
   light surface, so the rule is the principle rather than a list of colours.

   On navy and dark green the edge is simply invisible, and that is fine: the fill
   already carries the boundary there, amber on navy being 7.02:1. 1.4.11 asks for
   A 3:1 boundary, not specifically a border. Outline buttons are excluded because
   their border is the design. */
.wp-block-button__link:not(.is-style-outline),
.wp-block-file__button,
.dfc-btn--amber {
  border: 2px solid var(--color-navy);
}

/* ── Promo banner ─────────────────────────────────────────────────────────
   A full-width call-out strip. YELLOW, not amber: amber is reserved for
   things you click, and a non-clickable amber block dilutes that meaning.
   Navy on yellow is 9.09:1. See docs/design-system.md §3.3. */
.dfc-banner {
  background: var(--color-yellow);
  color: var(--color-navy);
  font-family: var(--font-label);
  font-size: var(--text-xl);
  font-weight: var(--weight-bold);
  text-transform: uppercase;
  letter-spacing: var(--tracking-label);
  text-align: center;
  padding: var(--space-3) var(--space-4);
  margin: 0 0 var(--space-5);
}

/* ── Card ─────────────────────────────────────────────────────────────────
   The component documented in docs/design-system.md §10.3. It was specified
   but never implemented, so any content using .dfc-card rendered unstyled. */
.dfc-card {
  background: var(--color-surface);
  border-radius: var(--radius-lg);
  padding: var(--space-6);
  box-shadow: var(--shadow);
  transition: box-shadow var(--duration) var(--ease),
              transform var(--duration) var(--ease);
}
.dfc-card:hover {
  box-shadow: var(--shadow-lg);
  transform: translateY(-2px);
  z-index: var(--z-raised);
}

/* ── Card row that stays three across at tablet ───────────────────────────
   Put on a core wp:columns block. Without it, core collapses every columns
   block to one per row below 782px, so a three-card row goes straight from
   three to one and the whole 768-980px tier reads as a phone.

   EXCEPTION to design-system 6.2, which says card grids are 3 at wide, 2 at
   tablet, 1 at mobile. Chosen deliberately for the homepage Connect row on
   2026-09-30: at 768px each card is 224px wide, which is tight, and two-up
   was the alternative shown and rejected because it strands the third card
   full width under the other two. Any other card grid should follow 6.2
   unless somebody makes the same call again, out loud.

   The stack still happens, at the documented mobile breakpoint (767px) rather
   than core's 781px. That is the only media query here, and it is one of the
   three in design-system 6.1 — core's 781px is not.

   Specificity, because this is the part that silently fails: core's collapse
   is `.wp-block-columns:not(.is-not-stacked-on-mobile) > .wp-block-column`
   at !important, which is three classes. A two-class selector loses to it
   however late it is loaded, so the selectors below carry four and do not
   depend on stylesheet order. */
.wp-block-columns.dfc-cards-3up {
  flex-wrap: nowrap !important;
}
.wp-block-columns.dfc-cards-3up:not(.is-not-stacked-on-mobile) > .wp-block-column {
  flex-basis: 0 !important;
  flex-grow: 1;
}

@media (max-width: 767px) {
  .wp-block-columns.dfc-cards-3up {
    flex-wrap: wrap !important;
  }
  .wp-block-columns.dfc-cards-3up:not(.is-not-stacked-on-mobile) > .wp-block-column {
    flex-basis: 100% !important;
  }
}

/* Lead paragraph. The opening sentence of a page, one step up from body. */
.dfc-lead {
  font-size: var(--text-xl);
  line-height: var(--leading-normal);
  color: var(--color-text);
}

/* ── Icon list ────────────────────────────────────────────────────────────
   A list of things the page is asserting, each marked with an icon that says
   something about the line rather than a repeated tick.

   Every icon is drawn with a MASK, never an inline SVG with a fill: a data URI
   carrying its own hex would put a raw colour in this stylesheet, which the
   design-system audit fails on, correctly. The shapes live as --icon-* tokens
   so no component restates the path data either.

   Navy, changed 2026-09-08. Green was the first choice on the reasoning that it
   is decoration-only in this palette, which is true, but a green mark next to
   navy text read as a second colour arriving for no reason. Navy is 11.79:1 on
   off-white, far past the 3:1 WCAG 1.4.11 asks of a meaningful graphic, and it
   keeps the list as one object rather than text plus ornament. */
.dfc-iconlist {
  list-style: none;
  /* Symmetric. It had margin only underneath, so the first icon sat hard against
     the paragraph above while the last had 32px below it. Reported 2026-09-08.
     Block margins collapse, so this does not double against the paragraph's. */
  margin: var(--space-6) 0;
  padding: 0;
  display: grid;
  gap: var(--space-5);
}
.dfc-iconlist li {
  position: relative;
  padding-left: var(--space-8);
  line-height: var(--leading-normal);
  --icon: var(--icon-tick);
}
.dfc-iconlist li::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0.1em;
  width: 1.625rem;
  height: 1.625rem;
  background-color: var(--color-navy);
  -webkit-mask: var(--icon) no-repeat center / contain;
  mask: var(--icon) no-repeat center / contain;
}
.dfc-iconlist li.i-people  { --icon: var(--icon-people); }
.dfc-iconlist li.i-medical { --icon: var(--icon-medical); }
.dfc-iconlist li.i-heart   { --icon: var(--icon-heart); }

/* ── Section split: two thirds of prose, one third of something else ──────
   Explicit fractions rather than wp:columns, because the right-hand third
   holds a scroll-snapped carousel and core's column collapse at 781px would
   fight the snap container's own breakpoint. Stacks at 980, the documented
   tablet tier, with the media column first so it is not stranded at the bottom
   of a long read on a phone. */
.dfc-split23 {
  display: grid;
  grid-template-columns: minmax(0, 2fr) minmax(0, 1fr);
  gap: var(--space-10);
  align-items: start;
}
@media (max-width: 980px) {
  .dfc-split23 { grid-template-columns: minmax(0, 1fr); gap: var(--space-8); }
  .dfc-split23 > .dfc-carousel { order: -1; }
}

/* ── Photo carousel ───────────────────────────────────────────────────────
   Scroll-snap, not a slider library. The track is a real overflow container,
   so touch swipe, trackpad, keyboard arrows and a screen reader's scroll all
   work before a single line of JS runs, and the buttons below only call
   scrollBy(). With JS off it degrades to a scrollable strip rather than to one
   frozen slide, which is what most slider plugins do.

   Autoplay is OPT-IN, per carousel, with data-dfc-carousel-autoplay="<ms>" in
   the markup. It is off by default and it stays off for a carousel sitting in
   a narrow column beside body copy. WCAG 2.2.2 is what makes it opt-in: the
   pause control carousel.js injects, and the reduced-motion switch that turns
   the whole thing off rather than slowing it down, are the price of it. */
.dfc-carousel { position: relative; }
.dfc-carousel__track {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: 100%;
  gap: 0;
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  scrollbar-width: none;
  border-radius: var(--radius);
}
.dfc-carousel__track::-webkit-scrollbar { display: none; }
.dfc-carousel__track figure { margin: 0; scroll-snap-align: center; }
.dfc-carousel__track img {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 4 / 5;
  object-fit: cover;
  border-radius: var(--radius);
  background: var(--color-surface-sunk);
}
.dfc-carousel__controls {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-4);
  margin-top: var(--space-3);
}
.dfc-carousel__btn {
  width: 44px;                    /* EXCEPTION: WCAG touch target, not a grid value */
  height: 44px;
  display: grid;
  place-items: center;
  padding: 0;
  border: 2px solid var(--color-navy);
  border-radius: var(--radius-pill);
  background: transparent;
  color: var(--color-navy);
  cursor: pointer;
  font-family: var(--font-label);
  font-size: var(--text-lg);
  line-height: 1;
}
.dfc-carousel__btn:hover { background: var(--color-navy); color: var(--color-white); }

/* The pause control, injected by carousel.js on a carousel that rotates by
   itself. It shares .dfc-carousel__btn so it is the same 44px WCAG target as
   the arrows, and a little more air marks it as a different KIND of control:
   the arrows answer "which photograph", this one answers "should it move at
   all". It is built in script, never in markup, so a page whose JS failed can
   never show a pause button that pauses nothing. */
.dfc-carousel__btn--toggle {
  margin-inline-start: var(--space-2);
  /* Transparent on the resting page, and present the moment anybody reaches
     for it. Opacity rather than display or visibility for three reasons: the
     controls row keeps its width so nothing shifts when it appears, the button
     stays in the tab order, and :focus-within below can therefore reveal it
     for a keyboard user at the instant they land on it. */
  opacity: 0;
  transition: opacity 120ms ease;
}
.dfc-carousel:hover .dfc-carousel__btn--toggle,
.dfc-carousel:focus-within .dfc-carousel__btn--toggle { opacity: 1; }
/* Nothing to hover with, so nothing would ever reveal it. */
@media (hover: none) {
  .dfc-carousel__btn--toggle { opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
  .dfc-carousel__btn--toggle { transition: none; }
}
.dfc-carousel__btn--toggle svg { display: block; }
.dfc-carousel__dots { display: flex; gap: var(--space-2); }
.dfc-carousel__dot {
  width: 10px; height: 10px; padding: 0;
  border-radius: 50%;
  border: 1px solid var(--color-navy);
  background: transparent;
  cursor: pointer;
}
.dfc-carousel__dot[aria-current="true"] { background: var(--color-navy); }

/* ── Photo carousel: the framed variant ───────────────────────────────────
   For a carousel that replaces a full-column photograph rather than sitting in
   the narrow third of a .dfc-split23. First used on Teen Retreat (2026-09-14),
   in place of the montage strip that stood between the section copy and the
   financial panel.

   ONE PHOTOGRAPH AT A TIME, AND NARROWER THAN THE COLUMN IT SITS IN. Both
   halves of that matter and they pull against each other.

   The first draft showed two frames and a peek, on the reasoning that a 3:2
   photograph across the whole 1200px column is 800px tall and buries the cost
   panel. It does. But two photographs side by side stop being a photograph and
   start being a filmstrip: the eye has nowhere to rest and the half-frame at
   the edge reads as a layout mistake before it reads as an affordance.

   860px is the answer to the height problem instead. One 3:2 frame at 860 is
   573px, which fits a laptop screen with the heading above it, and the 170px
   of off-white either side frames the picture rather than cropping it. Below
   about 940px the column is narrower than 860 anyway and this cap stops
   applying on its own, with no breakpoint to maintain.

   3:2 because the sources are 4:3 and 3:4 camera originals out of the client's
   2026 Teen Retreat handbook. 3:2 is the shallowest crop that still keeps a
   standing teenager whole in the portrait ones.

   Everything else is the base component untouched: 100% columns, centre
   snapping, no gap, the radius on the track. */
.dfc-carousel--band {
  /* What the photograph has to share the screen with: the sticky 96px header
     it must not slide under, the 44px control row plus its 12px margin, and
     enough air top and bottom that it is not wedged against either. */
  /* The white frame, as numbers the cap can do arithmetic with: 10px either
     side of the photograph, 10px above it and a 46px caption lip below. Taken
     straight off .dfc-polaroid -- change them there and change them here. */
  --band-frame: 20px;
  --band-lip: 56px;
  --band-max: calc(1200px + var(--band-frame));

  /* What the photograph has to share the screen with: the sticky 96px header it
     must not slide under, the 44px control row plus its 12px margin, the frame's
     own lip, and enough air top and bottom that it is not wedged against
     either. */
  --band-chrome: calc(var(--header-h, 96px) + 104px + var(--band-lip));

  margin-inline: auto;
  max-width: var(--band-max);
  /* SIZED FROM THE AVAILABLE HEIGHT, NOT JUST A WIDTH. 1200px is the full content column,
     and it is not always the width that fits. At a 1280x720 window -- what a
     maximised browser on a scaled 1080p laptop actually gives you -- a frame
     at full width put its top behind the sticky header, and you could not see
     the whole picture and the banner at once.

     The frame is 21:10, so width IS height x 2.1, and the height the viewport
     can spare is all that is needed to work the width back out. Below roughly
     a 771px window the cap starts biting and the picture shrinks to fit rather
     than hiding under the header; above it, 1200px wins and nothing changes.

     21:10 REPLACED 3:2 ON 2026-09-15, asked for as "longer horizontally, same
     height". At the full column that is 1200x571 against the old 860x573: the
     same height to within two pixels, 340px more picture. It is a re-CUT, not
     a stretch -- every frame was cropped again from the handbook original, and
     teen-ropes-ready had to be anchored to the top of its 3:4 source because a
     centred 2.1:1 crop of a standing portrait removes the head.

     svh, not vh: on a phone vh is the tallest the viewport ever gets, with the
     browser chrome retracted, which is the one measurement that guarantees the
     thing does not fit at rest. The 480px floor stops a pathologically short
     window collapsing it to a thumbnail. */
  max-width: min(var(--band-max), max(500px, calc((100svh - var(--band-chrome)) * 2.1 + var(--band-frame))));
}

/* -- The band wears the polaroid ------------------------------------------
   Asked for on 2026-09-15 as "something that matches the feel of the polaroid
   without changing the size of the picture", the polaroid in question being the
   hero cluster directly above it.

   It REUSES .dfc-polaroid rather than imitating it. The white frame, the 2px
   corner, the shadow, the fixed 46px lip and the Caveat caption all arrive as
   the component the rest of the site already uses, so the two objects on this
   page cannot drift apart later.

   THE PHOTOGRAPH DOES NOT MOVE A PIXEL. The frame is 10px of white either side
   and 10 above, 46 below, so it grows OUTWARD: the carousel caps at 1220 rather
   than 1200 and the picture inside is still exactly 1200x571. The 20px of
   overhang sits inside the 24px padding the band's group already carries, so
   nothing is clipped and no other content on the page moves.

   NO TILT, which is the one part of the polaroid deliberately left off. Four
   degrees on a near-square hero frame reads as a snapshot dropped on the page;
   the same four on a 1220x627 letterbox is a 43px drop corner to corner and
   reads as a broken layout. It would also have to be paid for in track padding,
   and track padding narrows the slides -- which is the one thing the brief
   ruled out. */
.dfc-carousel--band .dfc-polaroid {
  /* The cluster's overlap and lean. Neither survives a scroll track: that
     negative margin would drag every slide left of its own snap point. */
  margin: 0;
  rotate: none;
}
.dfc-carousel--band .dfc-carousel__track {
  /* The corner belongs to the frame, and it is the polaroid's 2px rather than
     the component's 8px. A radius on the scroll container would clip the white
     frame into a shape a polaroid does not have. */
  border-radius: 0;
  /* --shadow-lg is 0 12px 28px: 40px of it falls below the lip and 16px above,
     and overflow-x computes overflow-y to auto, which would slice both flat.
     The negative margin hands the height back so buying the shadow does not
     push the section taller. BLOCK padding only -- inline padding would narrow
     the slides, and the slides are the picture. */
  padding-block: 16px 40px;
  margin-block: -16px -34px;
}
.dfc-carousel--band .dfc-carousel__track img {
  aspect-ratio: 21 / 10;
  /* The corners belong to the frame now. */
  border-radius: 0;
}

/* The ratio does NOT carry to phone widths, the same call .dfc-photo-band makes
   a few hundred lines up and for the same reason. 21:10 of a 342px column is a
   163px strip and the teenagers in it stop being legible as people. 16:9 gives
   192px and brings them back.

   Kept as close to the file's own ratio as legibility allows, because the crop
   now runs the other way: the file IS 21:10, so a taller box overflows
   HORIZONTALLY and object-fit takes it off the sides. At 16:9 that is 9% off
   each edge, which the group shots survive. At 3:2 it would be 14% a side and
   the outermost teenager in teen-group-lake loses an arm.

   This is the trade that comes with re-cutting the files rather than cropping
   3:2 originals in CSS: better pixels and no dead space on a laptop, less room
   to manoeuvre on a phone. Worth it, but it is the reason a per-photograph
   object-position would not help here -- the overflow is sideways, and a
   vertical anchor has nothing to move. */
@media (max-width: 781px) {
  .dfc-carousel--band .dfc-carousel__track img { aspect-ratio: 16 / 9; }
}

/* ── Photo carousel: the polaroid variant ─────────────────────────────────
   The hero's photo column, one frame at a time, rotating on its own. First
   used on Teen Retreat (2026-09-15), in place of the 2x2 cluster.

   IT IS A TRADE, NOT AN UPGRADE, and it is made per page. The cluster shows
   four frames at once at about 215px each; one frame in the same column is
   about 430px, so the same four photographs are shown at twice the size and
   only one at a time. Everything at a glance, or one thing worth looking at.
   Camp Morris keeps the cluster deliberately.

   THE POLAROID FRAME IS KEPT WHOLE. .dfc-polaroid still supplies the white
   border, the deep caption lip and the shadow; only the parts that belong to a
   CLUSTER are undone here. The overlap, the per-child fan and the z-index
   ladder all assume four frames scattered across one row, and inside a scroll
   container they do nothing but clip.

   THE TRACK CLIPS BOTH AXES, AND THAT IS WHAT THE VERTICAL PADDING IS FOR.
   overflow-x: auto computes overflow-y to auto as well, so --shadow-lg
   (0 12px 28px) was being sliced flat along the top and bottom edge of every
   frame — the frame read as pasted onto the page rather than sitting on it.
   Padding inside the scroll container gives the shadow its room; the negative
   margins hand most of that height back to the layout, so buying the shadow
   does not push the hero 48px taller. */
.dfc-prog-hero__photos--carousel {
  grid-template-columns: minmax(0, 1fr);
  /* The track buys its own clearance on all four sides below, for the shadow
     and for the tilt. This ring is what keeps the shadow off the hero's own
     edge at narrow widths. */
  padding: 0 14px;
}
/* The brush circle, sized for one upright frame rather than a 2x2 block and
   pushed less far right. At +200px it cleared a 464px-wide cluster; behind a
   single column that same push ran the whole stroke off the outer edge and
   left nothing on the near side of the photograph. */
.dfc-prog-hero__photos--carousel::before {
  width: 112%;
  height: 94%;
  translate: calc(-50% + 120px) -50%;
}
.dfc-carousel--polaroid .dfc-carousel__track {
  padding-block: 22px 40px;
  margin-block: -22px -30px;
  /* Sideways clearance for the tilt, which the shadow alone did not need. A
     frame rotated 4deg pushes each corner about (height x sin4)/2 past its box.
     The slides are grid columns at 100% of the CONTENT box, so this padding
     also narrows them by exactly as much as the rotation needs, and the
     negative margin hands the width back to the hero. Snapping stays correct
     because the base component centres its slides rather than aligning them to
     an edge. */
  padding-inline: 18px;
  margin-inline: -18px;
  /* The radius here belongs to the frame, which is 2px, not to the track. A
     var(--radius) corner on the scroll container clipped the white frame's
     own corners into a shape the polaroid does not have. */
  border-radius: 0;
}
/* That clearance is a function of the frame's HEIGHT, so it is not a constant.
   Once the hero stacks, the photographs get the full 720px column instead of a
   third of the row, the frame roughly doubles, and 18px stops being enough --
   the corners came back clipped square by about 9px a side. Measured at 900px,
   2026-09-15. */
@media (max-width: 980px) {
  .dfc-carousel--polaroid .dfc-carousel__track {
    padding-inline: 32px;
    margin-inline: -32px;
  }
}
/* Undo the cluster's SPACING, but not its tilt. Same specificity as the rules
   being corrected, later in the file, which is the pattern
   .dfc-prog-hero__photos--single already uses a few hundred lines up — so this
   block has to stay below them.

    used to be here and was taken out on 2026-09-15: the angle is
   the house style for a photograph in a hero, every other programme page has
   it, and losing it on Teen Retreat alone made this hero the odd one out.
   The nth-child angles a few hundred lines up now reach the slides, so the
   four come past at -4, +3, +4 and -3 degrees and the carousel reads as a
   stack of polaroids being flipped through rather than a photo viewer.

   Rotation inside a scroll container costs clearance, which is what the
   padding/margin pair on the track below buys. Without it the corners are
   clipped square by overflow and the tilt looks like a rendering fault. */
.dfc-carousel--polaroid .dfc-polaroid {
  margin: 0;
}
.dfc-carousel--polaroid .dfc-carousel__track img {
  /* The base carousel frames at 4:5 with a radius of its own. A polaroid's
     window is square and its corners are the frame's. */
  aspect-ratio: 1 / 1;
  border-radius: 0;
}

/* ── Numbered steps, side by side ─────────────────────────────────────────
   Built from the Programs cards in the mega panel (.dfc-prog), because a family
   that has just used that menu to get here should meet the same object again:
   a badge, a condensed headline, a line of body copy, in that order, ranged
   left. The badge is where the photo's date pill sits on .dfc-prog__when, which
   is why it is yellow and why the step number reads as a chip rather than as a
   caption floating above the text.

   Numbered because it genuinely is a sequence. Forms cannot be completed before
   the camper is enrolled and enrolment cannot happen before registration opens,
   so the number carries information rather than decorating the list. The number
   stays a CSS counter, so reordering the steps in the editor renumbers them. */
.dfc-steps {
  list-style: none;
  margin: 0 0 var(--space-6);
  padding: 0;
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--space-5);
  counter-reset: dfc-step;
  /* Centred in the band. The row used to live inside the 2/3 text column, which
     left it hanging under the carousel with the full-width cost band beneath it. */
  max-width: 960px;
  margin-inline: auto;
}
/* The section heading above the row, centred over it (2026-09-10, Dan). The row
   is a centred 960px band inside a 1200px section, so a left-ranged heading
   started 120px outside the cards it introduces and read as belonging to the
   paragraph above instead. */
.dfc-steps__head {
  max-width: 960px;
  /* space-5 above (2026-09-10, Dan). The carousel beside the text column ends
     well below the prose, so the heading needs to clear the taller of the two
     rather than sit at the paragraph's natural rhythm. */
  margin: var(--space-5) auto var(--space-4);
  text-align: center;
}
.dfc-steps li {
  counter-increment: dfc-step;
  /* WHITE with a real shadow, not the sunk grey the mega panel uses. That panel
     works because it sits on WHITE, where #F5F5F5 reads as a recessed tile. This
     band is off-white, and grey on off-white measures about 1.06:1, so the same
     CSS produced three invisible rectangles. Reported 2026-09-08 as "unstyled",
     which was accurate. White plus a hairline plus elevation is the same idea
     (a tile whose edges you can see) executed for the surface it is actually on. */
  background: var(--color-white);
  border: 1px solid var(--color-border);
  border-radius: 10px;
  box-shadow: var(--shadow);
  padding: var(--space-5);
  /* Ranged left, not centred (2026-09-10). Centred text was the only thing
     holding three tiles of unequal length together when they had no internal
     structure. Now the pill, the headline and the note give every card the same
     three-beat shape, and a shared left edge lines those beats up across the row
     the way the mega panel's cards do. */
  text-align: left;
  font-size: 0.95rem;
  line-height: var(--leading-normal);
  color: var(--color-navy);
  transition: transform var(--duration) var(--ease),
              box-shadow var(--duration) var(--ease),
              border-color var(--duration) var(--ease);
}
.dfc-steps li:hover {
  transform: translateY(-3px);
  box-shadow: var(--shadow-lg);
  border-color: var(--color-sky);
}
/* The step chip. Yellow ground with navy text is the mega panel's date badge
   (.dfc-prog__when) and is already an approved pairing in the regression at
   9.09:1, so it clears AA at any size. Pill radius rather than the badge's 4px:
   this is a counter, not a date, and the fully round end stops it reading as a
   second, smaller card corner inside the card. inline-block so the ground hugs
   the label instead of running the width of the tile. */
.dfc-steps li::before {
  content: "Step " counter(dfc-step);
  display: inline-block;
  font-family: var(--font-label);
  font-size: 0.78rem;
  font-weight: var(--weight-bold);
  text-transform: uppercase;
  letter-spacing: .1em;
  background: var(--color-yellow);
  color: var(--color-navy);
  padding: 5px 12px;
  border-radius: var(--radius-pill);
  margin-bottom: var(--space-3);
}
/* Matches .dfc-prog__name exactly: Barlow Condensed bold at 1.3rem. This is the
   line that makes the row scannable, so a parent can read three headlines and
   know the shape of the process without reading the notes under them. */
.dfc-steps__name {
  display: block;
  font-family: var(--font-headline);
  font-weight: var(--weight-bold);
  font-size: 1.3rem;
  line-height: 1.15;
  color: var(--color-navy);
  margin: 0 0 var(--space-2);
}
/* Navy, never muted, on the same reasoning as .dfc-prog__note. */
.dfc-steps__note {
  display: block;
  font-size: 0.9rem;
  line-height: 1.5;
  color: var(--color-navy);
}
@media (prefers-reduced-motion: reduce) {
  .dfc-steps li { transition: none; }
  .dfc-steps li:hover { transform: none; }
}
@media (max-width: 980px) {
  .dfc-steps { grid-template-columns: minmax(0, 1fr); }
}

/* A closing line that the section is building towards. Bold at lead size, so
   it reads as the point rather than as another paragraph. */
.dfc-statement {
  font-size: var(--text-xl);
  line-height: var(--leading-normal);
  font-weight: var(--weight-bold);
  color: var(--color-navy);
  margin: 0;
}

/* -- Testimonials ---------------------------------------------------------
   Families in their own words. STACKED full width, not side by side, and that
   is the whole design decision: the two we have run 180 words and 60, so a
   two-column grid is one tall card beside a short one with half a column of
   empty space under it, and every future quote of a different length makes it
   worse. Stacked, length stops being a layout problem.

   White tile on an off-white band with a real shadow -- the same recipe, and
   the same reasoning, as .dfc-steps above: grey on off-white measures about
   1.06:1 and would be no card at all.

   The leading edge is a 4px sky rule. Sky is decoration in this palette and
   carries no text here, which is exactly what it is allowed to do, and it is
   what marks the block as somebody speaking rather than more body copy. The
   brand's brush stroke is the other sanctioned way to mark a pull quote
   (style-guide 5.2); a rule was chosen because these sit in a stack and three
   strokes down one column reads as wallpaper, which 5.3 warns against. */
/* No max-width here. The measure is set by the SECTION's contentSize, so the
   heading and the cards share one left edge; capping the wrapper instead left a
   left-ranged heading hanging 120px outside the cards it introduces, which is
   the same mistake .dfc-steps__head above exists to correct. */
.dfc-quotes {
  display: grid;
  gap: var(--space-6);
  margin: 0;
}
.dfc-quote {
  margin: 0;
  background: var(--color-white);
  border: 1px solid var(--color-border);
  border-left: 4px solid var(--color-sky);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow);
  padding: var(--space-6);
}
.dfc-quote blockquote { margin: 0; }
.dfc-quote p { margin: 0 0 var(--space-4); line-height: var(--leading-normal); }
.dfc-quote p:last-child { margin-bottom: 0; }
/* The attribution is a LABEL, not a line of the quote: the label face, uppercase,
   so the eye stops reading in the family's voice at exactly the right place. The
   em dash is decoration and lives here rather than in the markup, where a screen
   reader would announce it as punctuation in front of the name. */
.dfc-quote figcaption {
  margin-top: var(--space-5);
  font-family: var(--font-label);
  font-size: var(--text-sm);
  font-weight: var(--weight-semibold);
  text-transform: uppercase;
  letter-spacing: var(--tracking-label);
  color: var(--color-navy);
}
.dfc-quote figcaption::before { content: "\2014\00a0"; }

/* ── A quote with the family's photograph ─────────────────────────────────
   Camp to Community, 2026-09-21. Beside the four-paragraph Patrick card, the
   one-paragraph Boone quote read as the carousel having run out of material.
   A photograph of the family who said it fixes that, and does something the
   sentences cannot: the page's whole claim is that you will meet people like
   you, and this is what those people look like.

   PHOTO LEFT, 260px. Deliberately not an avatar. This is a family of four and
   at thumbnail size four faces are four smudges. 260px inside the 890px card
   leaves a 598px text column, which is the first time this component has had
   a measure at all: the photo-less card runs the full 890px, well past the
   66-character line the style guide asks for.

   The text is CENTRED against the photo by two 1fr spacer rows, above and
   below the pair. Top-aligned was the first draft and it was wrong: 130px of
   quote against a 279px photo left the attribution stranded mid-card over
   150px of white. Spacer rows keep the blockquote and its figcaption their
   own natural distance apart -- figcaption's margin-top, unchanged -- and put
   the slack outside them, which is the reasoning of `align-items: center` on
   the quotes track below, applied inside one card.

   figcaption stays a DIRECT CHILD of the figure. Wrapping it with the
   blockquote in one div would make this an ordinary two-item flex row and
   save the spacer rows entirely; it is also invalid, because figcaption's
   parent must be a figure. The spacer rows are the price of that. */
.dfc-quote--photo {
  display: grid;
  grid-template-columns: 260px minmax(0, 1fr);
  grid-template-rows: 1fr auto auto 1fr;
  column-gap: var(--space-6);
}
/* The selector is qualified up to .dfc-carousel__track because that block
   above puts `aspect-ratio: 4 / 5; object-fit: cover` on EVERY img in a track,
   which is right for the photo carousels and wrong here: it would re-crop a
   picture that was already cropped to frame these four people, taking 21px off
   each side to make a 279px frame 325px tall. Unqualified, .dfc-quote__photo
   loses that fight on specificity and the override reads as a no-op. */
.dfc-carousel__track .dfc-quote__photo {
  grid-column: 1;
  grid-row: 1 / -1;
  aspect-ratio: auto;
  align-self: center;
  display: block;
  width: 100%;
  /* Load-bearing, as everywhere else in this file: the markup carries the
     intrinsic width/height so the browser reserves the box before the file
     arrives, and without this the attribute height wins and the picture is
     squashed to 965px of a 260px column. */
  height: auto;
  /* --radius, not the card's --radius-lg. A 16px corner on a 260px picture
     reads as a second card inside the card; 8px reads as the photograph
     having been placed there. */
  border-radius: var(--radius);
}
/* WORDPRESS AUTO-SIZES, and this is a trap rather than a preference.
   WP 6.7 rewrites the sizes attribute of every LAZY image in post content,
   prepending "auto, ", and ships a stylesheet of its own that reads
     img:is([sizes=auto i],[sizes^="auto," i]) { contain-intrinsic-size: 3000px 1500px }
   The spec puts `contain: size` on an image sized that way, so the picture
   stops deriving its height from its own ratio and takes the 1500px
   placeholder instead -- 260px wide by 1500px tall, and it STAYS there after
   the file has loaded. That is what shipped on 2026-09-21 and it is not
   visible in a local preview, because nothing rewrites the attribute there.

   THE FIX IS IN THE MARKUP AND IT CAN ONLY BE IN THE MARKUP. The img carries
   loading="eager" explicitly. Omitting the attribute is NOT enough: WordPress
   then decides for itself, marks a below-the-fold content image lazy, and
   rewrites sizes anyway -- which is what happened on the first attempt here.

   There is no CSS escape hatch, and a rule that looks like one was shipped
   and reverted the same afternoon. A rule of the shape
     .dfc-quote__photo[sizes^="auto" i] { contain: none }
   reads like a belt-and-braces second lock and does nothing: the
   containment is imposed by the HTML spec on any image sized this way, not
   by a UA stylesheet rule, so author CSS never gets to win. Measured with
   that rule live, the computed value stayed "size". Do not add it back. If a
   quote photo is ever 1500px tall again, look at the markup. */
.dfc-quote--photo blockquote { grid-column: 2; grid-row: 2; }
.dfc-quote--photo figcaption { grid-column: 2; grid-row: 3; }

/* One column below the point where a 260px photo leaves the text under 40
   characters. 781px is this file's standard tablet break. The photo does not
   go full width here: a 220px frame above left-ranged text keeps it a
   photograph of a family rather than a banner, and keeps the card short
   enough that the quote is still on screen with it. */
@media (max-width: 781px) {
  .dfc-quote--photo {
    grid-template-columns: minmax(0, 1fr);
    grid-template-rows: auto auto auto;
  }
  /* Qualified for the same reason as the desktop rule above: unqualified,
     .dfc-carousel__track img wins on specificity and holds the photo at 100%
     of the card. */
  .dfc-carousel__track .dfc-quote__photo {
    grid-column: 1;
    grid-row: 1;
    width: 220px;
    max-width: 100%;
    margin-bottom: var(--space-5);
  }
  .dfc-quote--photo blockquote { grid-column: 1; grid-row: 2; }
  .dfc-quote--photo figcaption { grid-column: 1; grid-row: 3; }
}

/* ── The quotes as a carousel, not a stack ────────────────────────────────
   Camp to Community, 2026-09-16. Two testimonials stacked ran the band to the
   better part of two screens, and the second one sat below the fold on every
   laptop: in practice the page showed one testimonial and hid the other. One
   at a time, with the reader driving, shows both.

   It reuses .dfc-carousel wholesale rather than growing a second rotator. The
   track is the same scroll-snap container, so swipe, trackpad and arrow keys
   work with carousel.js absent, and with JS off the band degrades to a
   scrollable strip of quotes rather than to one frozen quote.

   NO AUTOPLAY, and this is the variant where that matters most. The photo
   carousels rotate at 3000ms because a photograph is taken in at a glance. The
   Patrick quote is four paragraphs. WCAG 2.2.2 would sell us autoplay for the
   price of the pause button carousel.js injects; we are not buying it for
   something a reader is halfway through a sentence of. */
.dfc-carousel--quotes .dfc-carousel__track {
  /* Room for the card's shadow, which a scroll container otherwise clips flush
     against its edge. BLOCK ONLY, and that is not a tidiness preference.
     carousel.js decides which slides are in the accessibility tree by comparing
     each slide's offsetLeft against the track's scroll viewport, with a 1px
     tolerance sized for a track with no inline padding. 4px of padding-inline
     pushes the second slide 4px inside that tolerance, and both quotes get
     announced when only one is on show. Sideways there is nothing to clip
     anyway: the card spans the full track, so its left and right shadow falls
     outside the band whatever this value is. */
  padding-block: var(--space-2) var(--space-4);
  padding-inline: 0;
  /* The cards are the rounded thing in this variant, not the track. */
  border-radius: 0;
}
/* THE TRACK keeps the height of the tallest quote — the slides are grid items
   in one row — and that is wanted: advancing must not pull the controls out
   from under the cursor that is about to click them again.

   THE CARD does not. Stretching it was the first draft and it was wrong. The
   Patrick quote is four paragraphs and the Boone quote is one, so on a 375px
   screen a stretched Boone card is 942px tall around 420px of text: 522px of
   hollow white that reads as a rendering fault. Centring the card instead puts
   that slack outside it, as band background above and below a correctly sized
   pull quote, which is what a short quote is supposed to look like. Desktop
   never showed the fault as loudly (187px) and gets the same treatment. */
.dfc-carousel--quotes .dfc-carousel__track { align-items: center; }

/* ON A PHONE the centred slack was still 540-630px of empty band, so there
   carousel.js sizes the track to the quote on show and the quote sits at the
   top of it. overflow-y must be hidden or the taller neighbours make the track
   scroll vertically inside itself. Without JS the height is never set and this
   is just the desktop band, top-aligned. */
@media (max-width: 767px) {
  .dfc-carousel--quotes .dfc-carousel__track {
    align-items: start;
    overflow-y: hidden;
    transition: height 0.25s ease;
  }
}
@media (max-width: 767px) and (prefers-reduced-motion: reduce) {
  .dfc-carousel--quotes .dfc-carousel__track { transition: none; }
}

/* A short, centered single-message page: careers with no openings, thank-you
   pages, simple confirmations. */
.dfc-careers {
  max-width: var(--content);
  margin-inline: auto;
  padding-block: var(--section-y);
}

/* ── Footer ───────────────────────────────────────────────────────────────
   Rebuilt 2026-08-08. What was here before: no logo, socials as three text
   links, three different link colours, and five centre-stacked blocks of legal
   copy burning 250px of height. One of its links was a 404.

   White is the only text colour on this navy surface (design-system 3.2), so
   hierarchy comes from size, weight and spacing rather than from colour. The
   only accent is the amber CTA and the yellow rule under each column label,
   which is decoration, not text. */
.dfc-footer { padding: var(--space-8) var(--gutter) var(--space-5); }
.dfc-footer a { color: var(--color-text-on-dark); text-decoration: none; }
.dfc-footer a:hover,
.dfc-footer a:focus-visible { text-decoration: underline; text-underline-offset: .18em; }
.dfc-footer a:focus-visible { outline: var(--focus-width) solid var(--color-focus); outline-offset: var(--focus-offset); }
/* Buttons carry their own surface and label colours. Without this the footer
   link rule above repaints the amber CTA's label white, which is 1.8:1. */
.dfc-footer .dfc-btn--amber { color: var(--color-navy); }
.dfc-footer .dfc-btn:hover { text-decoration: none; }

/* Five tracks, brand column wider. Grid rather than wp:columns so tablet gets
   two sensible columns instead of one 1200px-tall stack. */
.dfc-foot-grid {
  display: grid;
  grid-template-columns: 1.4fr repeat(3, 1fr) 1.25fr auto;
  gap: var(--space-8) var(--space-6);
  align-items: start;
}
.dfc-foot-grid > * { margin-block: 0; }

.dfc-foot-logo { display: inline-block; margin-bottom: var(--space-4); }
.dfc-foot-logo img { width: 200px; height: auto; display: block; }
.dfc-foot-address {
  font-style: normal;
  font-size: var(--text-sm);
  line-height: 1.7;
  margin: 0 0 var(--space-4);
}
.dfc-foot-address a { text-decoration: underline; text-underline-offset: .18em; }
.dfc-foot-ein { font-size: var(--text-sm); line-height: 1.7; margin: 0; }

/* Candid transparency seal. Its own grid track at the far right, top-aligned
   with the column labels, so it reads as a counterweight to the logo in the
   opposite corner. Self-hosted SVG rather than the widgets.guidestar.org embed,
   which sets AWSALB/AWSALBCORS on every page load before consent, so this costs
   no third-party connection.
   Left at its own pale grey-blue: it is a third-party mark and recoloring it
   to the DFC palette would misrepresent it. That is why it is the one thing in
   this footer not painted navy/white.
   84px, not the artwork's own 108px, so it sits at the scale of a column label
   rather than dominating the corner. It previously ran full size BELOW the EIN
   inside the brand column, which made that column 393px tall against 225px for
   the tallest other column: a 168px overhang into a band nothing else reached.
   No media query of its own. The 980px and 600px rules below reset
   grid-template-columns outright, so the seal flows into the two-column and
   single-column stacks as an ordinary grid item. */
.dfc-foot-seal { display: inline-block; }
.dfc-foot-seal img { width: 84px; height: auto; display: block; }
.dfc-foot-seal:hover, .dfc-foot-seal:focus-visible { text-decoration: none; }

.dfc-foot-label {
  font-family: var(--font-label);
  font-size: var(--text-base);
  font-weight: var(--weight-bold);
  text-transform: uppercase;
  letter-spacing: var(--tracking-label);
  color: var(--color-text-on-dark);
  margin: 0 0 var(--space-4);
  padding-bottom: var(--space-2);
  /* Decoration, not text: the yellow gives each column a visible anchor
     without putting an accent colour on any words. */
  border-bottom: 3px solid var(--color-yellow);
  display: inline-block;
}
.dfc-foot-links { list-style: none; margin: 0; padding: 0; font-size: var(--text-sm); }
.dfc-foot-links li { margin: 0 0 var(--space-3); }
.dfc-foot-links li:last-child { margin-bottom: 0; }
.dfc-foot-note { font-size: var(--text-sm); line-height: 1.6; margin: 0 0 var(--space-4); }
.dfc-foot-cta { margin-bottom: var(--space-5); white-space: nowrap; }
/* The annual and impact reports, under the sign-up button (2026-09-09).
   They were the last two items in Get Involved, where "2025 Spring Impact
   Report" measured 171px against that column's 165px and wrapped to two
   lines. This column is 206px, so it sits on one line. The rule only buys
   separation from the social icons below: .dfc-foot-cta already spaces the
   button off the list above it. */
.dfc-foot-links--reports { margin: 0 0 var(--space-5); }

/* Icons, not the words FACEBOOK INSTAGRAM LINKEDIN. 44px targets, which is the
   pointer-target minimum, so the circle is a real hit area not a decoration. */
.dfc-social { list-style: none; display: flex; gap: var(--space-3); margin: 0; padding: 0; }
.dfc-social a {
  display: inline-flex; align-items: center; justify-content: center;
  width: 44px; height: 44px; box-sizing: border-box;
  border: 2px solid var(--color-text-on-dark);
  border-radius: var(--radius-pill);
  transition: background var(--duration-fast) var(--ease), border-color var(--duration-fast) var(--ease);
}
.dfc-social a:hover, .dfc-social a:focus-visible {
  background: var(--color-yellow); border-color: var(--color-yellow); text-decoration: none;
}
.dfc-social svg { width: 20px; height: 20px; fill: currentColor; display: block; }
.dfc-social a:hover svg, .dfc-social a:focus-visible svg { fill: var(--color-navy); }

/* The same row of icons on a LIGHT surface (contact page yellow card,
   2026-09-10, Dan). The footer's ring is --color-text-on-dark, which is white
   and therefore invisible on yellow, so the variant swaps the ring and the
   glyph to navy: 9.09:1 on #FDD769, well past the 3:1 WCAG 1.4.11 asks of a
   control boundary. Hover inverts to a navy disc with a yellow glyph rather
   than the footer's yellow disc, which would vanish into the card. */
/* .dfc-social zeroes its own margin for the footer, where the row is the last
   thing in a flex column and spacing comes from the column's gap. In the
   contact card it follows a heading in normal flow, and that margin:0 beats
   core's block gap on specificity, so the icons sat hard against "Follow Us". */
.dfc-social--on-light { margin-top: var(--space-4); }
.dfc-social--on-light a { border-color: var(--color-navy); color: var(--color-navy); }
.dfc-social--on-light a:hover,
.dfc-social--on-light a:focus-visible {
  background: var(--color-navy); border-color: var(--color-navy);
}
.dfc-social--on-light a:hover svg,
.dfc-social--on-light a:focus-visible svg { fill: var(--color-yellow); }

/* One legal row instead of five stacked centre-aligned blocks. */
.dfc-foot-legal {
  display: flex; flex-wrap: wrap; justify-content: space-between;
  align-items: center; gap: var(--space-3);
  margin-top: var(--space-8); padding-top: var(--space-5);
  border-top: 1px solid var(--color-rule-on-dark);
  font-size: var(--text-sm);
}
.dfc-foot-copy { margin: 0; }
.dfc-legal-links { list-style: none; display: flex; flex-wrap: wrap; gap: var(--space-5); margin: 0; padding: 0; }
.dfc-legal-links a { text-decoration: underline; text-underline-offset: .18em; }
.dfc-foot-fineprint {
  margin: var(--space-4) 0 0;
  font-size: var(--text-xs);
  line-height: 1.6;
  max-width: 90ch;
}
/* The constrained group layout gives every DIRECT child auto side margins, so
   anything narrower than the container gets centred. An override here ties with
   that rule on specificity and loses on source order, so the fine print is
   wrapped in .dfc-foot-bottom instead: the wrapper is the constrained child and
   is full width, which leaves the fine print free to sit flush left under the
   copyright where it belongs. */

@media (max-width: 980px) {
  /* Two even columns. Spanning the brand across both left half the row empty. */
  .dfc-foot-grid { grid-template-columns: repeat(2, 1fr); gap: var(--space-6); }
}
@media (max-width: 600px) {
  .dfc-foot-grid { grid-template-columns: 1fr; }
  .dfc-foot-legal { justify-content: flex-start; }
}

/* ── Type specimen ────────────────────────────────────────────────────────
   Shows what a heading LOOKS like without being one. A specimen on the style
   guide is a demonstration, not document structure, so it must not compete
   with the real page title for the single H1. */
.dfc-typespec {
  font-family: var(--font-headline);
  color: var(--color-text);
  margin: 0 0 var(--space-4);
}
.dfc-typespec--h1 {
  font-size: var(--text-4xl);
  font-weight: var(--weight-bold);
  line-height: var(--leading-tight);
}

/* ── Main navigation + Programs mega panel ────────────────────────────────
   Direction A from docs/header-nav-concepts.html. The panel is a full-width
   band under the header, not a box hanging off one item: that was the main
   reason the old dropdown read as unfinished. Opens on hover AND focus-within,
   so it works from the keyboard with no JavaScript. */
.dfc-nav { flex: 1; }
/* The list packs RIGHT, against the Donate button, leaving the open space next
   to the logo. In the drawer this is inert: a column with auto height has no
   free space to distribute. */
.dfc-nav__list { list-style: none; display: flex; justify-content: flex-end; align-items: center; gap: var(--nav-gap); margin: 0; padding: 0; }
.dfc-nav__item { position: static; }          /* static so the panel can span the header */
/* Belt and braces for the wpautop artifact fixed in inc/nav.php: an orphan </p>
   becomes an empty <p> whose 18px margins pushed the menu below the centerline
   shared by the logo and the Donate button. The nav renders as a dynamic block
   now so nothing should produce one, and if anything ever does again it will not
   move the menu. */
.dfc-nav > p:empty { display: none; }

.dfc-nav__link {
  font-family: var(--font-label);
  /* Tiered, because one number cannot be right at every width: 18px desktop,
     16px in the 981-1099px band where the overflow budget bites, 20px in the
     drawer where no budget applies. The tiers and the measurements behind them
     live with the token in dfc-tokens.css; docs/design-system.md §10.7 carries
     the table. Do not put a literal size back on this line — the whole point of
     the token is that the three tiers move together and stay documented.

     History, since it explains the shape: this shipped at a hard-coded 0.95rem
     (15.2px), off the type scale and under the 16px §10.7 had specified since
     launch, while the panels it opens carry 16.8px link titles and 20.8px
     program names — the parent read smaller than its own children. */
  font-size: var(--nav-size);
  /* Items with a panel are <button>, leaf items (Contact) are <a>. Buttons get
     the UA's line-height while anchors inherit the body's, so without pinning
     it the leaf links render a taller box and sit off the shared centerline.
     1 also matches .dfc-btn, keeping the row centered on the Donate button. */
  line-height: 1;
  font-weight: var(--weight-semibold);
  text-transform: uppercase;
  letter-spacing: var(--tracking-label);
  color: var(--color-navy);
  background: none;
  border: 0;
  /* Both borders, not just the bottom one. The underline affordance needs a 2px
     bottom border, and with nothing balancing it the box grew 2px downward while
     the flex row centered the BOX, leaving the text 1.6px above the centerline
     the logo and Donate button share. A transparent top border of the same width
     makes the box symmetric so the glyphs land where the box does. */
  border-top: 2px solid transparent;
  border-bottom: 2px solid transparent;
  /* 12px, not 10px, so the item clears the 44px hit area §10.7 requires at every
     tier: 12 + 12 + the line box + the two 2px borders, which is 46px at the
     18px desktop size and exactly 44px in the 16px laptop band — the band is
     what sets the floor. At 10px padding it measured 39.2px under the old
     15.2px type, and 16px type alone only reached 40px. The row is centred in a
     96px header, so the extra costs nothing visually. Measured 2026-09-09; the
     drawer was already compliant through its own min-height. */
  padding: 12px 2px;
  cursor: pointer;
  text-decoration: none;
  display: inline-flex;
  align-items: center;
  gap: 6px;
}
.dfc-nav__link i {
  width: 7px; height: 7px;
  border-right: 2px solid currentColor;
  border-bottom: 2px solid currentColor;
  transform: rotate(45deg) translate(-1px, -1px);
  transition: transform var(--duration-fast) var(--ease);
}
.dfc-nav__link:focus-visible { outline: var(--focus-width) solid var(--color-focus); outline-offset: var(--focus-offset); }

.dfc-panel-wrap { display: none; }
/* Click/tap toggling, set by nav.js. Works at every width, so a keyboard or a
   touch laptop is not stuck waiting for a hover that never arrives. */
.dfc-nav__item.is-open > .dfc-panel-wrap { display: block; }
.dfc-nav__item.is-open > .dfc-nav__link { border-bottom-color: var(--color-sky); }
.dfc-nav__item.is-open > .dfc-nav__link i { transform: rotate(-135deg) translate(-2px, -2px); }

/* Hover belongs to pointers only. Below 981px the nav is a drawer, and a :hover
   panel on a touch screen sticks open until you tap somewhere else. */
@media (min-width: 981px) {
  .dfc-nav__item:hover > .dfc-nav__link,
  .dfc-nav__item:focus-within > .dfc-nav__link { border-bottom-color: var(--color-sky); }
  .dfc-nav__item:hover > .dfc-nav__link i,
  .dfc-nav__item:focus-within > .dfc-nav__link i { transform: rotate(-135deg) translate(-2px, -2px); }
  .dfc-nav__item:hover > .dfc-panel-wrap,
  .dfc-nav__item:focus-within > .dfc-panel-wrap { display: block; }
}

/* The hamburger. Hidden until the drawer breakpoint. */
.dfc-nav__toggle {
  display: none;
  align-items: center; gap: 8px;
  font-family: var(--font-label); font-size: 0.95rem;
  font-weight: var(--weight-semibold); text-transform: uppercase;
  letter-spacing: var(--tracking-label);
  color: var(--color-navy); background: none;
  border: 2px solid var(--color-navy); border-radius: var(--radius-sm);
  padding: 8px 12px; min-height: 44px; box-sizing: border-box; cursor: pointer;
}
.dfc-nav__toggle:focus-visible { outline: var(--focus-width) solid var(--color-focus); outline-offset: var(--focus-offset); }
.dfc-nav__bars, .dfc-nav__bars::before, .dfc-nav__bars::after {
  display: block; width: 18px; height: 2px; background: currentColor;
  transition: transform var(--duration-fast) var(--ease), opacity var(--duration-fast) var(--ease);
}
.dfc-nav__bars { position: relative; }
.dfc-nav__bars::before, .dfc-nav__bars::after { content: ''; position: absolute; left: 0; }
.dfc-nav__bars::before { top: -6px; }
.dfc-nav__bars::after  { top: 6px; }
.dfc-nav.is-open .dfc-nav__bars { background: transparent; }
.dfc-nav.is-open .dfc-nav__bars::before { transform: translateY(6px) rotate(45deg); }
.dfc-nav.is-open .dfc-nav__bars::after  { transform: translateY(-6px) rotate(-45deg); }

.dfc-panel {
  position: absolute; left: 0; right: 0;
  background: var(--color-surface);
  border-top: 3px solid var(--color-sky);
  box-shadow: var(--shadow-lg);
  z-index: var(--z-header);
}
.dfc-panel__in { max-width: var(--wide); margin: 0 auto; padding: var(--space-6) var(--gutter) var(--space-5); }
/* Eyebrow and compare line share one header row (2026-09-08). Stacked they
   would have been two label-ish lines above the cards costing a mega panel the
   vertical space it can least afford; on one row they read as the panel's
   header and the cards still start where they used to. */
.dfc-panel__head {
  display: flex; flex-wrap: wrap; align-items: baseline;
  justify-content: space-between; gap: var(--space-2) var(--space-4);
  margin-bottom: var(--space-4);
}
.dfc-panel__eyebrow {
  font-family: var(--font-label);
  text-transform: uppercase;
  letter-spacing: var(--tracking-eyebrow);
  font-size: 0.7rem; font-weight: var(--weight-bold);
  color: var(--color-muted); margin: 0;
}
/* Moved above the cards (2026-09-08, Anne). It used to be the first of two rows
   under them, which put the muted "not sure which one" line between the cards
   and the cost band; the band is the one a family acts on, so it takes the slot
   directly under the four cards and keeps the single divider rule that
   separates "pick a program" from "pay for one". */
.dfc-panel__compare {
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-3);
  font-size: var(--text-sm); color: var(--color-muted);
}
.dfc-panel__compare a {
  font-family: var(--font-label); text-transform: uppercase;
  letter-spacing: var(--tracking-label); font-weight: var(--weight-semibold);
  font-size: 0.84rem; color: var(--color-navy);
  text-decoration: underline; text-underline-offset: .2em;
}

/* Cost band, and since 2026-09-08 the only thing under the cards. Sky tint
   ground so it reads as a distinct offer rather than a muted footer line, navy
   copy (the only text color on a light surface), and two navy buttons: white on
   navy is 12.61:1 and the fill carries its own boundary against the tint, so no
   border is needed. Amber stays reserved for Donate. The top margin matches the
   gap the compare line used to hold, so the cards sit no tighter than before. */
.dfc-panel__aid {
  margin-top: var(--space-5); padding-top: var(--space-4);
  border-top: 1px solid var(--color-border);
}
.dfc-aid {
  display: flex; flex-wrap: wrap; align-items: center;
  gap: var(--space-2) var(--space-5);
  background: var(--color-info-tint);
  border-radius: 10px;
  padding: var(--space-3) var(--space-4);
}
/* No flex-grow on the question: growing it pushed the two buttons to the far
   right edge of the band, 500px from the sentence they answer. The row packs
   left so the ask and the two ways to solve it read as one line. */
.dfc-aid__q {
  margin: 0; flex: 0 1 auto;
  font-family: var(--font-body); font-weight: var(--weight-semibold);
  font-size: var(--text-base); line-height: 1.4;
  color: var(--color-navy);
}
.dfc-aid__links { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2); }
/* Tighter than the page buttons: this is a menu row, not a hero. 44px stays. */
.dfc-aid__btn { margin: 0; font-size: var(--text-sm); padding: var(--space-3) var(--space-4); }

/* The card. The DATE leads, not the photo: for a camp the first question is
   always when it runs and whether the child is the right age. */
.dfc-board { display: grid; grid-template-columns: repeat(4, 1fr); gap: var(--space-5); }
.dfc-prog {
  position: relative; display: block; text-decoration: none; color: inherit;
  /* SUNK, not white (2026-09-01). A white card on the white mega panel was
     separated only by --color-border at 1.26:1, so the cards read as one flat
     sheet. Light gray is the token defined for exactly this ("sunk / inset
     surfaces", design-system 3), and at 1.09:1 against the panel it is the
     strongest separation the palette can give without inventing a value.
     Everything warmer or cooler (off-white 1.03:1, the sky/green/yellow tints
     1.08 to 1.13:1) sits in the same narrow band, so the choice came down to
     which token MEANS this, not which is most visible. The promo rail stays on
     --color-info-tint so it still reads as the accent rather than a fourth card. */
  background: var(--color-surface-sunk);
  border: 1px solid var(--color-border);
  border-radius: 10px; overflow: hidden;
  transition: transform var(--duration) var(--ease),
              box-shadow var(--duration) var(--ease),
              border-color var(--duration) var(--ease);
}
.dfc-prog:hover { transform: translateY(-3px); box-shadow: var(--shadow); border-color: var(--color-sky); }
.dfc-prog:focus-visible { outline: var(--focus-width) solid var(--color-focus); outline-offset: var(--focus-offset); }
.dfc-prog__when {
  position: absolute; top: 10px; left: 10px; z-index: var(--z-raised);
  font-family: var(--font-label); font-weight: var(--weight-bold);
  font-size: 0.92rem; letter-spacing: .04em; text-transform: uppercase;
  background: var(--color-yellow); color: var(--color-navy);
  padding: 5px 10px; border-radius: var(--radius-sm);
}
.dfc-prog__media { display: block; height: 104px; background-size: cover; background-position: 50% 40%; }
.dfc-prog__body { display: block; padding: var(--space-3) var(--space-4) var(--space-4); }
.dfc-prog__who {
  display: block; font-family: var(--font-label); text-transform: uppercase;
  letter-spacing: .12em; font-size: 0.66rem; font-weight: var(--weight-bold);
  /* Navy, not muted. --color-muted is scoped by dfc-tokens.css to white and
     off-white surfaces, and on the card's new light-gray it measures 4.43:1,
     under the 4.5:1 this 0.66rem label needs. Navy is 11.59:1 there. Hierarchy
     now comes from size, weight and the 0.12em tracking rather than from color,
     which is the same call the footer already makes. (Sky is still wrong here:
     2.6:1 on white, worse on gray.) */
  color: var(--color-navy);
}
.dfc-prog__name {
  display: block; font-family: var(--font-headline); font-weight: var(--weight-bold);
  font-size: 1.3rem; line-height: 1.15; margin: 3px 0 5px;
}
/* Navy for the same reason as __who above: muted is 4.43:1 on the sunk card. */
.dfc-prog__note { display: block; font-size: 0.84rem; line-height: 1.45; color: var(--color-navy); }

/* Every other menu: same chrome, plain link list. */
/* The link menus are CARDS, same as the Programs board (2026-09-01). They used
   to be bare text on the white panel with light gray appearing only on hover, so
   the four link menus read as a list while Programs read as a designed panel.
   Same sunk surface and same hairline as .dfc-prog, so all five mega menus are
   visibly one component with two densities. The promo rail keeps
   --color-info-tint and stays the odd one out on purpose. */
.dfc-linklist { list-style: none; margin: 0; padding: 0; display: grid; grid-template-columns: 1fr 1fr; gap: var(--space-3) var(--space-4); }
/* Equal card heights across a row. The grid already stretches the <li>, but the
   <a> inside it only ever grew to fit its own text, so a one-line description sat
   in a shorter card than a two-line one. Making the <li> a grid hands the row
   height straight through to the card. (.dfc-board needs none of this: its cards
   ARE the grid items.) */
.dfc-linklist li { display: grid; }
.dfc-linklist a {
  display: block; text-decoration: none; color: inherit;
  background: var(--color-surface-sunk);
  border: 1px solid var(--color-border);
  border-radius: 10px;
  padding: 14px 16px;
  transition: transform var(--duration) var(--ease),
              box-shadow var(--duration) var(--ease),
              border-color var(--duration) var(--ease);
}
/* Matches .dfc-prog:hover. The card lifts; it does not change fill, because the
   fill is now the resting state rather than the hover affordance. */
.dfc-linklist a:hover { transform: translateY(-3px); box-shadow: var(--shadow); border-color: var(--color-sky); }
.dfc-linklist a:focus-visible { outline: var(--focus-width) solid var(--color-focus); outline-offset: var(--focus-offset); }
.dfc-linklist strong { display: block; font-family: var(--font-headline); font-weight: var(--weight-semibold); font-size: 1.05rem; }
/* Navy, not muted, for the same reason as .dfc-prog__note: --color-muted is
   scoped to white and off-white and measures 4.43:1 on the sunk card, under the
   4.5:1 this size needs. Navy is 11.59:1. */
.dfc-linklist span { display: block; font-size: 0.84rem; color: var(--color-navy); line-height: 1.45; margin-top: 3px; }

/* ── Mega-panel promo rail ────────────────────────────────────────────────
   The link menus have no photography to build a card board from, so the rail
   carries the visual weight instead: one point of view and one next step per
   menu. Info tint rather than light gray because navy on #E7F5FC is 11.35:1 and
   the pairing is already in the regression's approved list, so the card reads
   as sky-flavored brand surface instead of a neutral box. */
.dfc-panel__in.has-rail {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 268px;
  gap: var(--space-6);
  align-items: start;
}
.dfc-rail {
  background: var(--color-info-tint);
  /* The tint alone is 1.11:1 against the white panel, which is no card at all.
     The hairline is what makes it read as one. This is a decorative divider,
     not an interactive boundary, so --color-border's 1.26:1 is the right token
     and 1.4.11 does not apply to it. */
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
  padding: var(--space-4) var(--space-4) var(--space-5);
  /* Navy on the tint, never muted: --color-muted is scoped to white and
     off-white surfaces by dfc-tokens.css and has no ratio on this one. */
  color: var(--color-navy);
}
.dfc-rail__eyebrow {
  font-family: var(--font-label);
  text-transform: uppercase;
  letter-spacing: var(--tracking-eyebrow);
  font-size: 0.7rem;
  font-weight: var(--weight-bold);
  margin: 0 0 var(--space-2);
}
.dfc-rail__title {
  font-family: var(--font-headline);
  font-weight: var(--weight-semibold);
  font-size: 1.12rem;
  line-height: 1.2;
  margin: 0 0 var(--space-2);
}
.dfc-rail__body {
  font-size: 0.86rem;
  line-height: 1.5;
  margin: 0 0 var(--space-4);
}
.dfc-rail__cta { width: 100%; text-align: center; }

@media (max-width: 980px) {
  .dfc-board { grid-template-columns: repeat(2, 1fr); }
  .dfc-linklist { grid-template-columns: 1fr; }
  /* The rail is supporting content. Below the links, not beside them. */
  .dfc-panel__in.has-rail { grid-template-columns: 1fr; gap: var(--space-5); }

  /* Drawer. Without this the six top-level items simply keep going: the row
     measured 823px inside a 390px viewport and took the whole page with it. */
  .dfc-nav { flex: none; position: static; }
  .dfc-nav__toggle { display: inline-flex; }
  /* Wherever the hamburger shows, it takes the far-right corner and Donate sits
     between it and the logo (client request, 2026-10-08). Visual order only:
     the drawer anchors to the header, not the button, so it still opens full
     width, and tab order stays logo, menu, Donate. */
  .dfc-header > .wp-block-group > .dfc-header-cta { order: 1; }
  .dfc-header > .wp-block-group > .dfc-nav { order: 2; }
  .dfc-nav__list {
    display: none;
    position: absolute; left: 0; right: 0; top: 100%;
    flex-direction: column; gap: 0;
    /* The desktop row is justify-content: flex-end and align-items: center.
       Both leaked into the drawer, and flex-end is the reason a menu taller than
       the screen OPENED AT ITS BOTTOM with no way to scroll back up: a flex
       container packs overflow past its START edge, and overflow above the top
       is unreachable — scrollHeight stayed equal to clientHeight, so the
       scrollbar did not even exist. Tapping Programs put its own list item 694px
       above the drawer. Reset both here; do not rely on the base rule. */
    justify-content: flex-start; align-items: stretch;
    background: var(--color-surface);
    border-top: 3px solid var(--color-sky);
    box-shadow: var(--shadow-lg);
    padding: 0 var(--gutter) var(--space-4);
    /* A fallback only. The promo bar sits above the header, is authored content
       so its height is unknown to CSS (94px at 375px wide), and every pixel of
       it pushed the drawer that far below the fold. nav.js measures the real
       distance on open and overrides this. */
    max-height: calc(100vh - var(--header-h));
    max-height: calc(100dvh - var(--header-h)); overflow-y: auto;
    overscroll-behavior: contain;
    z-index: var(--z-header);
  }
  .dfc-nav.is-open .dfc-nav__list { display: flex; }
  .dfc-nav__item { width: 100%; }
  .dfc-nav__link {
    width: 100%; justify-content: space-between;
    padding: var(--space-3) 0; min-height: 44px; box-sizing: border-box;
    border-bottom: 1px solid var(--color-border);
    /* No font-size here: --nav-size already steps UP to 20px at this breakpoint
       in dfc-tokens.css, which keeps all three nav tiers in one place. */
  }
  /* In the drawer the panel is inline content, not a band over the page. */
  .dfc-panel { position: static; border-top: 0; box-shadow: none; }
  .dfc-panel__in { padding: var(--space-3) 0 var(--space-4); }
  .dfc-panel__head { margin-bottom: var(--space-3); }
  /* In the drawer the two aid buttons get the full width each, same as the rail
     CTA: side by side they wrapped to 2 lines of uppercase label at 390px. */
  .dfc-aid { gap: var(--space-3); }
  .dfc-aid__links { width: 100%; }
  .dfc-aid__btn { flex: 1 1 100%; text-align: center; }
}

@media (max-width: 600px) {
  .dfc-board { grid-template-columns: 1fr; }
}

@media (max-width: 480px) {
  /* Icon-only hamburger. Logo, Menu and Donate plus gutters wanted 419px inside
     a 390px viewport, and the logo is already at the brand-guide floor of 120px,
     so the word is the only slack left. Donate keeps its full size: it is the
     conversion. The label stays in the DOM for screen readers. */
  .dfc-nav__toggle { gap: 0; padding: 8px 10px; }
  .dfc-nav__toggle-text {
    position: absolute; width: 1px; height: 1px;
    overflow: hidden; clip-path: inset(50%); white-space: nowrap;
  }

  /* The header runs a tighter gutter and gap than the page below it. Even with
     an icon-only toggle the row still wanted 383px at 360px wide, which is a
     live phone size (SE, small Android), not an edge case. */
  .dfc-header > .wp-block-group { gap: var(--space-3); padding-inline: var(--space-4); }
  .dfc-header-cta { padding-inline: var(--space-3); font-size: 0.9rem; }
}

/* ── Stat figures ────────────────────────────────────────────
   The annual report's big numbers were inline-styled at 3.5rem and 2.75rem.
   Neither is on the type scale, and inline CSS is what the token rule exists to
   prevent. Two tiers kept, snapped to the nearest tokens. Yellow on navy is
   9.09:1, so the colour was never the problem. */
.dfc-stat__num {
  font-family: var(--font-headline);
  font-size: var(--text-4xl);
  font-weight: var(--weight-bold);
  line-height: 1;
}
.dfc-stat__num--sm { font-size: var(--text-3xl); }

/* ── Text on dark surfaces ───────────────────────────────────
   design-system 3.2: "White is the only text color on dark surfaces (Navy,
   Dark Green)" and "on dark surfaces, [links are] white underlined".

   theme.json pins headings and links to navy at element level. That beats the
   has-white-color the author put on the surrounding group, so a heading inside
   a navy band rendered NAVY ON NAVY: a literal 1:1, invisible. The FAQ page had
   navy links on dark green at 1.08:1. Both were live.

   Buttons are excluded: they carry their own surface and label colors. */
.has-navy-background-color :is(h1, h2, h3, h4, h5, h6),
.has-green-dark-background-color :is(h1, h2, h3, h4, h5, h6) {
  color: var(--color-white);
}
.has-navy-background-color a:not(.dfc-btn):not(.wp-block-button__link),
.has-green-dark-background-color a:not(.dfc-btn):not(.wp-block-button__link) {
  color: var(--color-white);
  text-decoration: underline;
  text-underline-offset: .18em;
}

/* ── Third-party widgets and long strings ────────────────────
   The Givebutter signup widget sets its own typeface (Matter) on the custom
   element, and everything it renders inherits that. One page in a different
   body face reads as a broken page. !important is needed because the widget
   sets it inline on the host. */
givebutter-widget,
givebutter-widget * { font-family: var(--font-body) !important; }

/* The donate BUTTON widget renders its <button> with Givebutter's own
   button-class="mr-auto", which pins it to the left of the host element. The host
   spans the full container width, so in every centered context we have — the CTA
   band, the "Make a Donation" block on /give/ — the button landed hard left while
   the heading above it stayed centered. Measured: band center 640px, button center 60px.

   The button lives in a shadow root, so no selector of ours reaches mr-auto.
   Shrinking the HOST to its content and centering that gets the same result from
   outside: fit-content collapses the host onto the real button width, and the auto
   margins then center the host itself. Scoped to .dfc-gb-center so a widget that is
   meant to sit left (a future inline or footer placement) is untouched. */
.dfc-gb-center givebutter-widget {
  display: block;
  width: fit-content;
  margin-inline: auto;
}

/* The header and the Give nav panel render the donate button as the widget itself,
   so the host has to behave like the .dfc-btn it replaced rather than like a block.
   fit-content keeps it off the nav (the host would otherwise eat the free space in
   the flex row) and flex:none stops it being squeezed when the nav grows.

   The widget's own button is a fixed 52px against our 44px .dfc-btn, and it cannot
   be restyled from here: it lives in the vendor's shadow root, which is also why the
   LABEL is fixed up from JS (assets/js/givebutter-brand.js) rather than in this file.
   52px inside a 96px header still clears the centerline the logo shares, so the
   difference was accepted rather than fought. */
givebutter-widget.dfc-header-cta,
givebutter-widget.dfc-rail__cta {
  display: block;
  width: fit-content;
  flex: none;
  margin: 0;
}

/* At 480px the header button gives up padding and type size to fit (see the note on
   the icon-only hamburger above). The widget cannot: its padding is inside the shadow
   root. Measured at a 390px viewport it renders 104px against the 72px our own button
   shrinks to, and the row still fits because the gap absorbs the difference — but the
   margin is thin, so if the logo or the hamburger ever grows again, this is the line
   that will break first. */

/* Long unbreakable strings have to wrap instead of pushing the page sideways.
   An email address on /get-help/t1d-resources/ measured 325px and took a 360px
   viewport to 381px, so the whole page scrolled horizontally. */
main :is(p, li, a, dd, td, address) { overflow-wrap: break-word; }

/* ══════════════════════════════════════════════════════════════════════════
   IMPACT FUND CAMPAIGN STAGE  (/impact/)
   ══════════════════════════════════════════════════════════════════════════
   A montage of real DFC photography that starts grey. Two things put color
   back: the CAMPAIGN (a radial reveal sized by money raised, so the grey ring
   left over IS the gap to goal) and the VISITOR (a spark cannon that paints
   color along each spark's path). Engine: assets/js/impact.js.

   Registered so the reveal radius can actually transition. Without @property a
   custom property is an unanimated string and the color would snap instead of
   flooding, which kills the whole effect. */
@property --dfc-spark-r { syntax: "<percentage>"; inherits: false; initial-value: 0%; }

/* Full bleed straight under the header bar. The journey title band is the right
   default everywhere else, but here the photograph IS the page title, and a
   green band above it would put a stripe between the header and the artwork. */
.dfc-impact-page .dfc-page-title { display: none; }
/* Hiding the band is not enough. It is a sibling of the post content inside a
   flow-layout main, so core still put its 24px block gap above the content and
   left a stripe between the header bar and the photograph. Measured 24px on all
   three viewports before this rule. */
.dfc-impact-page .wp-block-post-content { margin-block-start: 0; }

.dfc-spark {
  position: relative;
  overflow: hidden;
  /* The second term is a CROP CEILING, not a size preference.

     The montage frames are 1.78:1 and this hero is much wider, so cover throws
     away height -- 27% of it at 1440, 33% at 1920. With a flat 720px cap a 2560
     display took that to 50%, and at that point only half of each photograph is
     on screen: spark-2 and spark-11 have their two faces at 0.18 and 0.77 of the
     frame height, which is a 0.59 span that simply does not fit in a 0.50 window
     at any focal point. No value in inc/impact.php can fix that; it is the frame
     that is wrong.

     100vw / 2.8 holds the hero at 2.8:1 or squarer, which keeps the crop at or
     under 36.5% however wide the monitor gets. It only engages above ~2016px,
     so every viewport below that renders exactly as before. */
  min-height: max(clamp(460px, 74vh, 720px), calc(100vw / 2.8));
  background: var(--color-navy-active);
  display: grid;
  place-items: center;
  isolation: isolate;
}
.dfc-spark > * { grid-area: 1 / 1; }

/* ── the photo stack ───────────────────────────────────────────────────── */
/* z-index, not auto. This is the whole bug behind the colour half flashing
   and fading on every photo change.

   position:absolute with z-index:auto does NOT create a stacking context, so
   these slides' own z-index (3 when .is-on) escaped into .dfc-spark's context
   and competed directly with .dfc-spark__lit at z-index 2. The grey slide
   therefore painted ON TOP of the entire colour layer, and colour was only
   visible in the gap where the outgoing grey slide had fallen back to 2 and the
   incoming one had not yet faded in. Measured on compositor frames: saturation
   below the line swung 94%, grey then a spike then a fade, once per reveal.

   Giving the container a z-index makes it a stacking context, so the slides'
   z-indexes stay local and the whole grey stack sits below the colour stack. */
.dfc-spark__grey { position: absolute; inset: 0; z-index: 1; }
.dfc-spark__slide {
  position: absolute; inset: 0;
  z-index: 1;
  background-size: cover;
  /* Per-photo focal point set inline from PHP, one measured value per frame.
     cover discards 27-36.5% of the height depending on the monitor (the ceiling
     is set by the aspect cap on .dfc-spark above), and one global anchor cannot
     keep faces safe across twelve different compositions. Each value is the
     focal point that maximises the smallest clearance between that photo's eyes
     band and the crop edges across every width the hero reaches; the derivation
     and the two tight cases are written up beside the array in inc/impact.php. */
  background-position: 50% var(--dfc-pos, 20%);
  filter: grayscale(1) contrast(0.84) brightness(0.6);
  opacity: 0;
  transition: opacity 1.5s ease, filter 1.4s ease;
}
.dfc-spark__slide.is-on { opacity: 1; z-index: 3; }
/* The outgoing frame, held opaque underneath the incoming one for the length of
   the fade. Without this the stack momentarily goes translucent and the grey
   beneath it bleeds through, which is what made the colour half look washed
   out. No transition on the way out: it is simply covered, then dropped. */
.dfc-spark__slide.was-on { opacity: 1; z-index: 2; transition: none; }

/* Cold wash. Desaturation alone reads as "old photo"; the navy tint reads as
   the feeling the campaign copy actually describes. */
.dfc-spark__chill {
  position: absolute; inset: 0;
  /* Weighted to the top, which is the half that stays grey the longest. The
     bottom stop is light so the colour rising into it is not immediately
     knocked back down by the wash it is passing through. */
  background: linear-gradient(180deg,
    rgba(20, 38, 74, 0.6), rgba(20, 38, 74, 0.3) 45%, rgba(20, 38, 74, 0.3));
}

/* Sparks paint into this one. JS composites the color plate through a mask
   canvas, so it is transparent everywhere a spark has not travelled. */
.dfc-spark__paint {
  position: absolute; inset: 0; width: 100%; height: 100%;
  z-index: 2; pointer-events: none;
  -webkit-mask-image: linear-gradient(to top, transparent 0%, transparent var(--dfc-fill), rgba(0, 0, 0, 1) calc(var(--dfc-fill) + 1%));
  mask-image: linear-gradient(to top, transparent 0%, transparent var(--dfc-fill), rgba(0, 0, 0, 1) calc(var(--dfc-fill) + 1%));
}

.dfc-spark__fx {
  position: absolute; inset: 0; width: 100%; height: 100%;
  z-index: 4; pointer-events: none;
  -webkit-mask-image: linear-gradient(to top, transparent 0%, transparent var(--dfc-fill), rgba(0, 0, 0, 1) calc(var(--dfc-fill) + 3%));
  mask-image: linear-gradient(to top, transparent 0%, transparent var(--dfc-fill), rgba(0, 0, 0, 1) calc(var(--dfc-fill) + 3%));
}
.dfc-spark__flash {
  position: absolute; inset: 0; z-index: 6; opacity: 0; pointer-events: none;
  background: var(--color-white);
}
.dfc-spark__flash.is-go { animation: dfc-flash 0.75s ease-out forwards; }
@keyframes dfc-flash { 0% { opacity: 0 } 8% { opacity: 0.9 } 100% { opacity: 0 } }

/* charge rings collapse inward before the spark */
.dfc-spark__ring {
  position: absolute; left: 50%; top: 50%; translate: -50% -50%;
  border-radius: 50%; border: 2px solid rgba(255, 255, 255, 0.7);
  width: 10px; height: 10px; opacity: 0; z-index: 5; pointer-events: none;
}
.dfc-spark.is-charging .dfc-spark__ring { animation: dfc-suck 0.86s cubic-bezier(0.5, 0, 0.75, 0) forwards; }
.dfc-spark.is-charging .dfc-spark__ring:nth-of-type(2) { animation-delay: 0.1s; }
.dfc-spark.is-charging .dfc-spark__ring:nth-of-type(3) { animation-delay: 0.2s; }
@keyframes dfc-suck {
  0%   { width: 480px; height: 480px; opacity: 0; }
  35%  { opacity: 0.85; }
  100% { width: 6px; height: 6px; opacity: 0; }
}


/* ── the control panel, one cluster that never moves ───────────────────────
   The first build swapped .dfc-spark__invite out for .dfc-spark__after on
   ignition, which threw the button off the hero at the exact moment the visitor
   was looking at it. Now a single panel stays put and only its TEXT changes, so
   nothing jumps and the donate button lands where the eye already is. */
.dfc-spark__panel {
  position: relative; z-index: 7; text-align: center;
  /* Sits above the optical centre. Dead-centring the cluster inside a frame that
     also carries a money read-out along the bottom edge leaves it looking low,
     and the deeper bottom padding keeps it clear of that read-out. */
  padding: var(--space-6) var(--gutter) var(--space-16);
  max-width: 52rem;
  display: flex; flex-direction: column; align-items: center;
  align-self: start; margin-top: clamp(var(--space-5), 8vh, var(--space-10));
}
/* The eyebrow does real work here: it is the line that tells a visitor what the
   page is asking of them, so it is sized as a statement, not a label. */
/* The campaign lockup, and the LAST of the three hero lines: the 2026 brief
   orders them title, slogan, lockup. It used to sit above the title at 44px,
   which was the right weight for a line that opened the panel and the wrong one
   for a line that closes it -- 44px under a 28px slogan under a 60px title
   inverts the hierarchy and makes the tagline shout over the campaign name. */
.dfc-spark__pre {
  font-family: var(--font-label); text-transform: uppercase;
  letter-spacing: var(--tracking-eyebrow); font-weight: var(--weight-bold);
  font-size: clamp(1.125rem, 2vw, 1.5rem); line-height: var(--leading-tight);
  color: var(--color-yellow);
  /* space-8, not space-5. The eyebrow used to be followed by a note paragraph,
     and when that came off the hero the eyebrow landed straight on top of the
     button row with only 24px between a line of type and a 48px control. The
     gap the note used to occupy is now explicit spacing instead of a side
     effect of there being text in the way. */
  margin: 0 0 var(--space-8);
  text-shadow: 0 2px 20px rgba(20, 38, 74, 0.8);
}
.dfc-spark__h1 {
  font-family: var(--font-display); font-weight: 400;
  text-transform: uppercase; color: var(--color-white);
  font-size: clamp(2rem, 5.6vw, 3.8rem); line-height: 0.96;
  margin: 0 0 var(--space-3);
  text-shadow: 0 4px 30px rgba(20, 38, 74, 0.7);
}
/* The hero subhead, and therefore a LEAD: the same role .dfc-home-hero__sub
   plays on the home hero and .dfc-prog-hero__sub on a program one, both of
   which are --text-xl. It sat at --text-lg, which is 20px, the gap between body
   copy and a lead and a size the scale does not have a step for. */
.dfc-spark__say {
  color: var(--color-white); font-size: var(--text-xl);
  margin: 0 auto var(--space-2); max-width: 46ch;
  text-shadow: 0 2px 16px rgba(20, 38, 74, 0.85);
}

.dfc-spark__actions {
  display: flex; flex-wrap: wrap; gap: var(--space-3);
  align-items: center; justify-content: center;
}

/* Shared button shape so the swap does not change the cluster's footprint. */
.dfc-spark__donate,
.dfc-cannon {
  font-family: var(--font-label); font-weight: var(--weight-bold);
  text-transform: uppercase; letter-spacing: var(--tracking-label);
  font-size: var(--text-lg); line-height: 1;
  border-radius: var(--radius-pill);
  padding: var(--space-4) var(--space-8);
  min-height: 56px; box-sizing: border-box; cursor: pointer;
  text-decoration: none; display: inline-flex; align-items: center; justify-content: center;
}

/* State 2. Now amber, now primary, and still pulsing so the eye goes to it. */
/* The double border marks the primary action. Navy inner edge for the 3:1
   boundary the amber fill cannot give on its own, then a white ring outside it
   so the button reads as lifted off the photograph rather than sitting in it. */
.dfc-spark__donate {
  color: var(--color-navy); background: var(--color-amber);
  border: 3px solid var(--color-navy);
  animation: dfc-glow 2.6s ease-in-out infinite;
}
.dfc-spark__donate:hover { background: var(--color-cta-hover); }
.dfc-spark__donate:focus-visible { outline: var(--focus-width) solid var(--color-focus-on-dark); outline-offset: 4px; }

/* Secondary, and deliberately a different species from the donate button: solid
   navy with a single hairline instead of amber with a double border. It is free
   and it is fun, but it is not the ask, and two buttons in the same treatment
   would make the visitor choose between equals. White on navy is 12.64:1, and
   navy against the photograph carries its own edge. */
.dfc-cannon {
  color: var(--color-white); background: var(--color-navy);
  border: 2px solid rgba(255, 255, 255, 0.75);
  box-shadow: 0 6px 18px rgba(20, 38, 74, 0.5);
  transition: transform var(--duration-fast) var(--ease),
              background var(--duration-fast) var(--ease),
              box-shadow var(--duration-fast) var(--ease);
}
.dfc-cannon:hover { background: var(--color-navy-hover); border-color: var(--color-white); }
.dfc-cannon:active { transform: translateY(3px); box-shadow: 0 2px 8px rgba(20, 38, 74, 0.5); }
.dfc-cannon:focus-visible { outline: var(--focus-width) solid var(--color-focus-on-dark); outline-offset: 4px; }
.dfc-cannon.is-kick { animation: dfc-kick 0.3s cubic-bezier(0.2, 0, 0.2, 1); }
@keyframes dfc-kick { 0% { transform: translateY(4px) } 60% { transform: translateY(-3px) } 100% { transform: none } }

/* The donate button pulses. It used to be the SECOND of two pulses, the first
   being the ignite button that gated the hero; that button and its keyframe are
   gone, so this is now simply the one thing on the hero asking to be pressed. */
/* The white ring is re-declared on every keyframe because animating one layer of
   a box-shadow list means restating the whole list. */
@keyframes dfc-glow {
  0%, 100% {
    box-shadow: 0 0 0 4px var(--color-white),
                0 0 0 4px rgba(249, 181, 43, 0.6),
                0 10px 28px rgba(20, 38, 74, 0.5);
  }
  50% {
    box-shadow: 0 0 0 4px var(--color-white),
                0 0 34px 12px rgba(253, 215, 105, 0.6),
                0 10px 28px rgba(20, 38, 74, 0.5);
  }
}

.dfc-spark__tally {
  margin: var(--space-5) auto 0; max-width: 52ch;
  color: var(--color-white);
  text-shadow: 0 2px 14px rgba(20, 38, 74, 0.9);
}
/* The count sits on its own line. Inline, a number this much larger than the
   copy stretches only the line box it lands in, so the paragraph ends up with
   one tall line and the rest tight. Its own block keeps the leading even. */
.dfc-spark__tally strong {
  display: block; margin-bottom: var(--space-1);
  font-family: var(--font-label); font-weight: var(--weight-bold);
  font-size: var(--text-4xl); line-height: 1;
  font-variant-numeric: tabular-nums; color: var(--color-yellow);
  text-shadow: 0 0 26px rgba(253, 215, 105, 0.65);
  animation: dfc-count-pulse 2s ease-in-out infinite;
}
/* It jumps when a spark is sent, so it is worth a glance even at rest. */
@keyframes dfc-count-pulse {
  0%, 100% { transform: scale(1); text-shadow: 0 0 20px rgba(253, 215, 105, 0.5); }
  50%      { transform: scale(1.07); text-shadow: 0 0 34px rgba(253, 215, 105, 0.95); }
}
.dfc-spark__tally-text {
  display: block;
  font-family: Figtree, sans-serif;
  font-size: var(--text-base); line-height: 1.55;
}

@media (max-width: 781px) {
  .dfc-spark__panel { padding: var(--space-6) var(--space-4) var(--space-12); }
  .dfc-spark__actions { flex-direction: column; }
  .dfc-spark__donate,
  .dfc-cannon { width: 100%; max-width: 21rem; font-size: var(--text-base); padding: var(--space-4) var(--space-5); }
}

@media (prefers-reduced-motion: reduce) {
  .dfc-spark__donate { animation: none; }
  .dfc-spark__tally strong { animation: none; }
}



/* The spark cannon, spent. Five free sparks per page view, then the button
   stands down and the donate button is the only thing still moving, because at
   that point it is the only thing left to do. */
.dfc-cannon.is-spent,
.dfc-cannon:disabled {
  opacity: 0.45; cursor: not-allowed;
  background: var(--color-navy-active);
  border-color: rgba(255, 255, 255, 0.35);
  box-shadow: none; transform: none;
}
/* One accent moving at a time: once the free option is spent the donate glow
   speeds up rather than competing with a second animation. */
.dfc-spark.is-spent .dfc-spark__donate { animation-duration: 1.5s; }

/* ── the rising waterline, and the gauge that reads it ─────────────────────
   Rebuilt 2026-09-02. The reveal used to be a radial bloom from the centre and
   the meter was a horizontal bar at the bottom, which meant two separate shapes
   telling the same story in two different languages.

   Now there is one number, --dfc-fill, and two things read it: a colour
   waterline that rises from the bottom of the photograph, and a vertical gauge
   pinned to the right edge at the full height of the hero. They are the same
   level by construction, so "filling the world with colour" is literal and the
   gauge is simply the scale beside it. Inherits so both children see it. */
@property --dfc-fill { syntax: "<percentage>"; inherits: true; initial-value: 0%; }

.dfc-spark {
  --dfc-fill: 0%;
  /* Slow on purpose. This is the moment the page exists for, and a fast fill
     reads as a loading bar rather than a tide coming in. */
  transition: --dfc-fill 3.4s cubic-bezier(0.33, 0, 0.2, 1) 0.55s;
}

/* The full-colour copy of the montage, revealed from the bottom up. The feather
   above the line is the wet edge: without it the boundary is a hard rule and
   looks like a crop, not a water level. */
.dfc-spark__lit {
  position: absolute; inset: 0; z-index: 2; pointer-events: none;
  /* These stops are an alpha stencil, not colour: black means "show the pixel",
     transparent means "hide it". Written as rgba so it is unmistakably opacity
     rather than a brand value that escaped the token system. */
  -webkit-mask-image: linear-gradient(to top,
    rgba(0, 0, 0, 1) 0%, rgba(0, 0, 0, 1) var(--dfc-fill),
    rgba(0, 0, 0, 0.55) calc(var(--dfc-fill) + 2.5%),
    transparent calc(var(--dfc-fill) + 7%));
  mask-image: linear-gradient(to top,
    rgba(0, 0, 0, 1) 0%, rgba(0, 0, 0, 1) var(--dfc-fill),
    rgba(0, 0, 0, 0.55) calc(var(--dfc-fill) + 2.5%),
    transparent calc(var(--dfc-fill) + 7%));
}
/* Under the waterline the lights are on. Pushed well past a straight
   desaturation reversal: the grey half is dimmed to 0.6 brightness, so matching
   it at 1.0 still reads as flat next to it. Lifting saturation and brightness
   together is what makes the boundary feel like a change in the world rather
   than a filter toggle. */
/* Nothing. Below the line is simply the photograph as shot, in full colour.
   Every filter that was here (and there were several, escalating) was me
   compensating for a colour layer that had no images in it. Once the images
   landed, the compensation became the problem: a boosted, blown-out half that
   shifted every time the montage changed underneath it. */
.dfc-spark__lit .dfc-spark__slide {
  filter: none;
  transition: opacity 1.5s ease;
}

/* A warm band of light sitting on the surface, so the line itself glows. */
/* A LINE, not a wash. At 34% tall with a screen blend this was glowing over a
   third of the hero, and because it straddled the boundary it kept lifting the
   colour half toward white. Now it is a thin bright seam that marks the level
   and leaves the photograph either side of it alone. */
/* Transparent. The colour boundary is legible on its own, and a hard rule across
   the full width of the photograph competed with it rather than clarifying it.
   The element stays so the boundary can be given a treatment later without
   restoring markup. */
.dfc-spark__waterline {
  position: absolute; left: 0; right: 0; z-index: 5; pointer-events: none;
  height: 1px;
  bottom: var(--dfc-fill);
  background: transparent;
}
.dfc-spark.is-lit .dfc-spark__waterline { opacity: 1; }

/* ── .dfc-spark--plain: the hero with the SPARK GAME switched off ─────────────
   Set by DFC_SPARK_MECHANIC in inc/impact.php. Nothing above is deleted; this
   block sits on top of it, so flipping the constant back restores the original
   hero with no CSS to put back.

   What this class now turns off is the INTERACTION and its pyrotechnics: the
   spark cannon, the ignition flash, the flying sparks and the canvases they
   paint into, the out-of-sparks modal and the bottom money read-out.

   What it no longer turns off is the CAMPAIGN: the grey photo stack and the
   colour rising through it to the level the money has reached. Feedbucket
   #991519 (Anne, 2026-09-21) asked for that back, and it is the page's whole
   argument -- grey is what is still to raise. It was collateral damage from
   switching the game off, not a design decision anyone made about it.

   .dfc-spark__chill DELIBERATELY STAYS. It reads as mood, but it is doing
   legibility work: the hero's H1 and lead are white over photography, and the
   navy wash is what holds them above AA. The grey stack dims its slides to
   brightness(0.6) and the colour stack does not, so below the waterline the
   wash is the only thing between white type and a bright photograph. */
.dfc-spark--plain .dfc-spark__paint,
.dfc-spark--plain .dfc-spark__fx,
.dfc-spark--plain .dfc-spark__flash {
  display: none;
}
/* The encouragement line rode the fill head beside the money. Two labels on one
   marker is clutter at this size, and the money is the one that pairs with the
   goal at the top of the track. */
.dfc-spark--plain .dfc-spark__cheer { display: none; }

/* ── the cluster: sized up, and given its own ground to stand on ──────────────
   Feedbucket #991520 (Anne, 2026-09-21): "Make this all larger font -- right
   now getting lost on page -- maybe with Grayscale it will be better."

   Both halves of that are addressed, and they are one problem. Switching the
   spark game off had left the panel alone on a 666px photograph with nothing
   else in the frame to give it scale, and it had also taken away the grey,
   which was the thing making white type readable in the first place. The type
   did not change; everything around it did.

   SIZE. Every step lands on a scale token now, where the old values were
   one-offs chosen against a busier frame: 72 / 36 / 28 / 22 is --text-6xl /
   3xl / 2xl / xl. The gaps between them are wider than before, so title then
   slogan then lockup reads as three ranks rather than three similar lines.

   GROUND. Larger type alone does not fix legibility over a photograph, and the
   backdrop here is now two different things at once: dimmed grey above the
   waterline, full-colour photography below it, with the boundary moving as the
   campaign moves. Type that is only legible above the line stops being legible
   the month the campaign passes it. So the panel carries its own scrim rather
   than depending on where the waterline happens to sit. */
.dfc-spark--plain .dfc-spark__h1     { font-size: clamp(2.25rem, 6.4vw, 4.5rem); }
.dfc-spark--plain .dfc-spark__slogan { font-size: clamp(1.5rem, 3.4vw, 2.25rem); }
.dfc-spark--plain .dfc-spark__pre    { font-size: clamp(1.25rem, 2.6vw, 1.75rem); }
/* The buttons come up with them, including below 781px where the shared mobile
   rule drops them to 16px. Measured at 390px: the wider label is 293px inside a
   336px pill, so the larger type still fits the stacked full-width shape. */
.dfc-spark--plain .dfc-spark__donate,
.dfc-spark--plain .dfc-spark__more { font-size: var(--text-xl); }

/* The scrim. An ellipse rather than a band: it has to cover the cluster without
   drawing an edge across the photograph, and a rectangle with a hard boundary
   reads as a panel sitting ON the image instead of light coming off it.

   z-index -1 inside .dfc-spark__panel, which is position:relative with z-index 7
   and therefore its own stacking context. The scrim lands behind the panel's own
   text and still well above the photo stacks at 1 and 2. Doing this on the stage
   instead would have needed a fourth z-index between the colour layer and the
   panel, which is the sort of thing that breaks the next time a layer is added.

   0.72 at the centre is what carries white type over the brightest slide in the
   montage once the waterline is above the panel. It is deliberately more than
   the .dfc-spark__chill wash underneath it, because that wash is sized for the
   whole hero and this is sized for four lines of type. */
/* Sized from the LINE ENDS, not from the block. The first version of this was
   inset by space-6/space-10 and measured out at 4.15:1 on the worst frame,
   which passes but only just -- and the reason was geometric rather than a
   question of strength. A radial ellipse falls off in BOTH axes at once, so the
   H1, which is the widest line and the highest one in the panel, sat at 0.81 of
   the gradient radius and was getting essentially no scrim at all: the biggest
   line on the page was the one it helped least. Reaching further sideways pulls
   every line end back inside the useful part of the falloff.

   Stops chosen against measured backdrops rather than picked by eye. Sampling
   the region behind this panel in all twelve frames, at the worst crop, taking
   mean + 1SD as the bright patch and compositing chill then scrim, spark-7 (the
   porch, the brightest of the twelve) is the binding case. Alpha needed there
   for 4.5:1 is 0.21 at the H1 ends, 0.31 at the slogan ends and 0.54 at the
   lockup ends; these stops deliver 0.21, 0.34 and 0.61.

   4.5 is the NORMAL-text threshold and every line here is 28px or larger, so
   this is deliberately a step past what AA asks of it. The backdrop is a
   photograph that changes every few seconds and a waterline that moves as the
   campaign does, so the margin is doing real work rather than decorating a
   number. */
.dfc-spark__panel::before {
  content: "";
  position: absolute;
  z-index: -1;
  inset: calc(var(--space-8) * -1) calc((var(--space-16) + var(--space-6)) * -1);
  background: radial-gradient(ellipse at center,
    rgba(20, 38, 74, 0.72) 0%,
    rgba(20, 38, 74, 0.62) 40%,
    rgba(20, 38, 74, 0.36) 62%,
    rgba(20, 38, 74, 0.18) 74%,
    rgba(20, 38, 74, 0) 88%);
  pointer-events: none;
}

/* ── the bar's two labels ─────────────────────────────────────────────────────
   The goal is a LINE on the track, not the top of it, and the money rides the
   fill head, so the bar reads bottom to top as "here is what we have, here is
   the goal, and here is the room past it". Both labels sit to the RIGHT of the
   16px track, which is the only side with room: the track is at
   left: var(--space-5).

   Both are uppercase label face, which is what every other number on this page
   uses, and both carry a navy shadow rather than a plate, so the bar stays a
   line of light on the photograph rather than becoming a widget. */
.dfc-spark__cap {
  position: absolute; left: calc(100% + var(--space-3));
  /* Set from impact.js so the tick, the band and the fill curve are one number
     rather than three. The fallback matches GOAL_MARK there. */
  bottom: var(--dfc-goal-mark, 86%);
  transform: translateY(50%);
  margin: 0; white-space: nowrap;
  font-family: var(--font-label); font-weight: var(--weight-bold);
  text-transform: uppercase; letter-spacing: var(--tracking-label);
  font-size: var(--text-base); line-height: 1;
  color: var(--color-white);
  text-shadow: 0 2px 14px rgba(20, 38, 74, 0.95), 0 0 24px rgba(20, 38, 74, 0.8);
}

/* The money rides the fill head: the bottom of the label sits at the top of the
   fill, pulled back half its own height so it is centred on the marker rather
   than sitting above it. */
.dfc-spark__raised {
  position: absolute; left: calc(100% + var(--space-3));
  bottom: var(--dfc-fill);
  transform: translateY(50%);
  margin: 0; white-space: nowrap;
  font-family: var(--font-label); font-weight: var(--weight-bold);
  font-variant-numeric: tabular-nums;
  font-size: var(--text-2xl); line-height: 1;
  color: var(--color-yellow);
  text-shadow: 0 2px 14px rgba(20, 38, 74, 0.95), 0 0 26px rgba(253, 215, 105, 0.5);
}

/* ── the stretch above the goal ───────────────────────────────────────────────
   Feedbucket #991519, Anne's own follow-up: "Maybe leave room at top so if we
   exceed the goal we can [do] sparkles or something."

   The room is reserved in the NUMBER, not here: fillFor() in impact.js puts
   100% of goal at --dfc-goal-mark and only lets the fill climb past it when the
   campaign does. So the band this draws is not decoration sitting on top of a
   full bar -- it is genuinely unreached, and the colour on the photograph stops
   at the same line, because both read the same --dfc-fill.

   That is the part worth keeping: at goal the photograph is 86% in colour and a
   band of grey is still there. Filling the frame is what EXCEEDING the goal
   buys, which is a better ending than hitting it exactly. */
.dfc-spark__gauge-track::before {
  content: "";
  position: absolute; left: 0; right: 0;
  bottom: var(--dfc-goal-mark, 86%); top: 0;
  border-radius: var(--radius-pill) var(--radius-pill) 0 0;
  /* Barely there on purpose. It has to read as "there is more track up here"
     without competing with the lit fill below it, which is the thing the eye is
     supposed to follow. */
  background: linear-gradient(to top,
    rgba(253, 215, 105, 0.16), rgba(253, 215, 105, 0.05));
}

/* The goal line itself. Wider than the 16px track and centred on it, so it
   reads as a mark ACROSS the bar rather than a segment of it -- the track has
   overflow: visible precisely so this can overhang. */
.dfc-spark__gauge-track::after {
  content: "";
  position: absolute; left: 50%; bottom: var(--dfc-goal-mark, 86%);
  width: 30px; height: 2px; translate: -50% 50%;
  border-radius: 2px;
  background: var(--color-white);
  box-shadow: 0 0 8px rgba(20, 38, 74, 0.9);
}

/* Past the goal. The reserved band stops being empty and starts being the
   story, so it lights rather than merely tinting, the goal line goes amber
   because it is now behind us, and the total turns white against it. */
.dfc-spark.is-over .dfc-spark__gauge-track::before {
  background: linear-gradient(to top,
    rgba(249, 181, 43, 0.35), rgba(253, 215, 105, 0.18));
  box-shadow: inset 0 0 12px rgba(253, 215, 105, 0.5);
}
.dfc-spark.is-over .dfc-spark__gauge-track::after {
  background: var(--color-amber);
  box-shadow: 0 0 10px 2px rgba(249, 181, 43, 0.85), 0 0 2px rgba(20, 38, 74, 0.9);
}
.dfc-spark.is-over .dfc-spark__cap {
  color: var(--color-amber);
  text-shadow: 0 2px 14px rgba(20, 38, 74, 0.95), 0 0 22px rgba(249, 181, 43, 0.7);
}
.dfc-spark.is-over .dfc-spark__raised {
  color: var(--color-white);
  text-shadow: 0 2px 14px rgba(20, 38, 74, 0.95), 0 0 30px rgba(253, 215, 105, 0.9);
}

@media (max-width: 781px) {
  /* The tick cannot overhang as far beside a 22px-offset panel. */
  .dfc-spark__gauge-track::after { width: 22px; }
}

@media (max-width: 781px) {
  /* The panel is full-width here and the scrim has to stop short of the frame
     edge, or its soft boundary lands on the hero's own edge and reads as a
     band. */
  .dfc-spark__panel::before { inset: calc(var(--space-5) * -1) calc(var(--space-4) * -1); }
  .dfc-spark__cap    { font-size: var(--text-xs); left: calc(100% + var(--space-2)); }
  .dfc-spark__raised { font-size: var(--text-lg); left: calc(100% + var(--space-2)); }
}

/* ── the vertical gauge ─────────────────────────────────────────────────── */
/* Left edge, so the level and the money it represents read as one unit: the
   gauge, then "raised so far" immediately to its right. On the right-hand side
   the number and the bar it belongs to sat at opposite ends of the frame. */
/* No padding and no flex column any more. The track has to be exactly as tall
   as the hero, because the gauge fill and the colour waterline both express
   --dfc-fill as a percentage of their own container: if those containers are
   different heights, the same number lands in two places. */
.dfc-spark__gauge {
  position: absolute; top: 0; bottom: 0; left: var(--space-5); z-index: 9;
  width: 16px;
  pointer-events: none;
  opacity: 0; transition: opacity 0.6s ease 0.35s;
}
.dfc-spark.is-lit .dfc-spark__gauge { opacity: 1; }

.dfc-spark__gauge-track {
  position: absolute; inset: 0;
  border-radius: var(--radius-pill);
  background: rgba(20, 38, 74, 0.55);
  border: 2px solid rgba(255, 255, 255, 0.3);
  overflow: visible;
}
.dfc-spark__gauge-fill {
  position: absolute; left: 0; right: 0; bottom: 0;
  height: var(--dfc-fill);
  border-radius: var(--radius-pill);
  background: linear-gradient(to top,
    var(--color-green) 0%,
    var(--color-sky) 26%,
    var(--color-yellow) 64%,
    var(--color-amber) 88%,
    var(--color-white) 100%);
  box-shadow:
    0 0 12px rgba(253, 215, 105, 0.9),
    0 0 36px rgba(249, 181, 43, 0.55),
    inset 1px 0 0 rgba(255, 255, 255, 0.45);
}
/* The hot head marks the level, and it is where the sparks come off. */
.dfc-spark__gauge-fill::after {
  content: ""; position: absolute; left: 50%; top: 0;
  width: 18px; height: 18px; translate: -50% -50%;
  border-radius: 50%;
  background: var(--color-white);
  box-shadow:
    0 0 10px 3px rgba(255, 255, 255, 0.95),
    0 0 28px 10px rgba(253, 215, 105, 0.8),
    0 0 56px 20px rgba(249, 181, 43, 0.4);
  animation: dfc-head 1.5s ease-in-out infinite;
}
@keyframes dfc-head {
  0%, 100% { transform: scale(1); opacity: 1; }
  50%      { transform: scale(1.35); opacity: 0.85; }
}

/* The message rides the top of the fill, so encouragement arrives at the exact
   height the campaign has reached rather than sitting in a fixed slot. */
.dfc-spark__cheer {
  position: absolute; left: calc(100% + var(--space-3));
  bottom: var(--dfc-fill);
  transform: translateY(50%);
  margin: 0; white-space: nowrap;
  font-family: var(--font-label); font-weight: var(--weight-bold);
  text-transform: uppercase; letter-spacing: var(--tracking-label);
  font-size: var(--text-base); color: var(--color-white);
  text-shadow: 0 2px 14px rgba(20, 38, 74, 0.95), 0 0 24px rgba(249, 181, 43, 0.5);
  pointer-events: none;
}

@media (max-width: 781px) {
  .dfc-spark__cheer {
    left: calc(100% + var(--space-2));
    font-size: var(--text-xs);
    white-space: normal; max-width: 8rem; line-height: 1.15;
  }
}

.dfc-spark__pct {
  font-family: var(--font-label); font-weight: var(--weight-bold);
  font-size: var(--text-lg); line-height: 1;
  font-variant-numeric: tabular-nums;
  color: var(--color-yellow);
  text-shadow: 0 0 18px rgba(253, 215, 105, 0.7);
}

/* ── the money, bottom left ────────────────────────────────────────────── */
.dfc-spark__hud {
  position: absolute; left: calc(var(--space-5) + 16px + var(--space-4)); bottom: 0; z-index: 9;
  padding: var(--space-5) var(--gutter) var(--space-5) var(--space-4);
  display: flex; flex-direction: column; gap: 2px;
  color: var(--color-white); text-align: left;
  opacity: 0; transform: translateY(10px);
  transition: opacity 0.6s ease 0.5s, transform 0.6s ease 0.5s;
}
.dfc-spark.is-lit .dfc-spark__hud { opacity: 1; transform: none; }
.dfc-spark__k {
  font-family: var(--font-label); text-transform: uppercase;
  letter-spacing: var(--tracking-eyebrow); font-size: var(--text-xs);
  font-weight: var(--weight-semibold); opacity: 0.85;
}
.dfc-spark__v {
  font-family: var(--font-label); font-weight: var(--weight-bold);
  font-size: var(--text-4xl); line-height: 0.95;
  font-variant-numeric: tabular-nums; color: var(--color-yellow);
  text-shadow: 0 0 26px rgba(253, 215, 105, 0.55);
}
.dfc-spark__goal { font-size: var(--text-sm); opacity: 0.92; }

/* The panel has to clear the gauge on the right and the money bottom left. */
/* SYMMETRIC. Padding on one side only shifts the text inside a box that is
   itself centred, which is what pushed the whole cluster right of centre.
   Clearing the gauge on both sides keeps the copy optically centred on the
   page while still guaranteeing it can never run under the gauge. */
.dfc-spark__panel { padding-inline: calc(var(--gutter) + var(--space-5) + 16px); }

@media (max-width: 781px) {
  .dfc-spark__gauge { left: var(--space-3); width: 12px; }



  .dfc-spark__hud { left: calc(var(--space-3) + 12px + var(--space-3)); padding: var(--space-4) var(--space-4) var(--space-4) 0; }
  .dfc-spark__v { font-size: var(--text-2xl); }
  .dfc-spark__panel { padding-inline: calc(var(--space-3) + 22px); }
}

@media (prefers-reduced-motion: reduce) {
  .dfc-spark { transition: none; }
  .dfc-spark__gauge-fill::after { animation: none; }
  .dfc-spark__waterline { transition: none; }
}


/* ── out-of-sparks modal ───────────────────────────────────────────────────
   Native <dialog>. showModal() supplies focus trapping, Escape to dismiss,
   inertness for the rest of the page and ::backdrop, none of which needs
   reimplementing. */
.dfc-spark__modal {
  border: 0; padding: 0; background: transparent;
  /* Genuinely 60% of the viewport. A 40rem cap was binding first and rendering
     it at about 40%. The line length is controlled by the title's own measure
     instead, so the box can be wide without the sentence becoming one long run. */
  width: 60vw;
  min-width: min(21rem, calc(100vw - 2 * var(--space-4)));
  max-width: calc(100vw - 2 * var(--space-5));
  color: var(--color-white);
}
.dfc-spark__modal::backdrop {
  /* 80% opaque so the photograph stays faintly readable behind it: the modal is
     interrupting a moment, not replacing the page. */
  background: rgba(20, 38, 74, 0.6);
  backdrop-filter: blur(3px);
  -webkit-backdrop-filter: blur(3px);
}
.dfc-spark__modal-in {
  position: relative;
  background: var(--color-navy);
  border: 2px solid var(--color-yellow);
  border-radius: var(--radius-lg);
  padding: var(--space-10) var(--space-8) var(--space-8);
  text-align: center;
  box-shadow: 0 24px 70px rgba(0, 0, 0, 0.55);
  display: flex; flex-direction: column; align-items: center;
}
.dfc-spark__modal-eyebrow {
  font-family: var(--font-label); text-transform: uppercase;
  letter-spacing: 0.08em; font-weight: var(--weight-bold);
  font-size: clamp(1.25rem, 2.6vw, 1.85rem); line-height: 1.1;
  color: var(--color-yellow);
  margin: 0 0 var(--space-4);
}
.dfc-spark__modal-title {
  font-family: var(--font-headline); font-weight: var(--weight-bold);
  font-size: var(--text-2xl); line-height: 1.25;
  color: var(--color-white); margin: 0 0 var(--space-6);
  max-width: 30ch; text-wrap: balance;
}
.dfc-spark__modal-cta { width: auto; min-width: 15rem; }
/* Corner dismiss. 44px square so it meets the touch target minimum without a
   visible box crowding the corner. */
.dfc-spark__modal-close {
  position: absolute; top: var(--space-2); right: var(--space-2);
  display: flex; align-items: center; justify-content: center;
  width: 44px; height: 44px; box-sizing: border-box;
  background: transparent; border: 0; cursor: pointer;
  font-size: 1.9rem; line-height: 1; color: var(--color-white);
  border-radius: 50%;
}
.dfc-spark__modal-close:hover { color: var(--color-yellow); background: var(--color-navy-hover); }
.dfc-spark__modal-close:hover { color: var(--color-yellow); }
.dfc-spark__modal-close:focus-visible {
  outline: var(--focus-width) solid var(--color-focus-on-dark);
  outline-offset: 3px; border-radius: var(--radius-sm);
}

/* ── mobile ────────────────────────────────────────────────────────────── */
@media (max-width: 781px) {
  .dfc-spark { min-height: 76vh; }
  .dfc-spark__slide { background-position: 50% 38%; }
  .dfc-cannon { width: 100%; max-width: 22rem; }
}

@media (prefers-reduced-motion: reduce) {
  .dfc-spark__slide { transition: none; }
}

/* ── Camp to Community: the North Carolina locations map ──────────────────
   Rendered by inc/c2c-map.php, which carries the reasoning for why this is a
   hand-built SVG and not Leaflet + OpenStreetMap tiles. Feedbucket 964351.

   Every color here is a token. The map is the one place on the site where an
   accent (sky) covers a large area AND carries text on top of it, so the two
   pairings it depends on are called out where they are used. Both are already
   in the PAIRINGS_TEXT list in tests/brand-regression.mjs. */

/* A query CONTAINER, because this block sits in the 64% column of a two-column
   section. Keyed to the viewport, its breakpoints fire at the wrong moment: at a
   900px viewport the column is about 500px, which is still 'desktop' to a media
   query and had the cards two-up at 240px each. The block has to answer to the
   width it actually got. */
.dfc-c2c { margin-top: var(--space-6); container-type: inline-size; }

/* ── the map ── */
.dfc-ncmap { margin: 0; }
.dfc-ncmap__svg {
  display: block;
  width: 100%;
  height: auto;
  overflow: visible;         /* Wilmington labels seaward, past the viewBox edge */
}

.dfc-ncmap__state {
  fill: var(--color-sky);
  stroke: var(--color-navy);
  stroke-width: 3;
  stroke-linejoin: round;    /* the Appalachian crest is a run of sharp corners */
}

/* The tap target, not the artwork. The pin glyph is ~22 units wide, which at a
   360px render is about 8px: far under any usable target. This disc is 52 units
   across and invisible, so the target is generous at every width while the
   drawing stays the right size for the map. */
.dfc-ncmap__hit { fill: transparent; pointer-events: all; }

.dfc-ncmap__stop { cursor: pointer; }

/* transform-origin at the tip. The glyph is drawn tip-at-origin (DFC_C2C_PIN),
   so the tip is the bottom centre of its own fill-box -- which means a hover
   scale grows the pin upward and OFF its city, never away from it. */
.dfc-ncmap__pin {
  transform-box: fill-box;
  transform-origin: 50% 100%;
  transition: transform var(--duration-fast) var(--ease);
}
.dfc-ncmap__pin-body {
  fill: var(--color-amber);
  /* Amber is 1.49:1 against the sky fill, so the fill cannot be what makes the
     pin visible. The navy edge is: navy on sky is 4.73:1. Same reason the brand
     guide puts a 2px navy border on every amber button. */
  stroke: var(--color-navy);
  stroke-width: 3.5;
  stroke-linejoin: round;
}
.dfc-ncmap__pin-eye { fill: var(--color-navy); }

/* Spent dates. Hollow rather than faded: dropping the opacity would take the
   navy label down with it and there is no contrast budget to spend here. */
.dfc-ncmap__stop.is-past .dfc-ncmap__pin-body { fill: var(--color-white); }

.dfc-ncmap__name {
  font-family: var(--font-label);
  font-size: 21px;                        /* user units, ~15px at the rendered width */
  font-weight: var(--weight-semibold);
  letter-spacing: var(--tracking-label);
  fill: var(--color-navy);                /* navy on sky, 4.73:1 */
  /* A white outline UNDER the glyphs. Two labels cross the coastline stroke and
     one sits half over the page background; without this they read as broken. */
  paint-order: stroke fill;
  stroke: var(--color-white);
  stroke-width: 4;
  stroke-linejoin: round;
}

.dfc-ncmap__stop:hover .dfc-ncmap__pin,
.dfc-ncmap__stop:focus-visible .dfc-ncmap__pin { transform: scale(1.18); }
.dfc-ncmap__stop:hover .dfc-ncmap__name,
.dfc-ncmap__stop:focus-visible .dfc-ncmap__name { text-decoration: underline; }
/* The outline is the real focus indicator; the thicker edge is the belt and
   braces, because an outline on an SVG <a> is the newest of these behaviours. */
.dfc-ncmap__stop:focus-visible {
  outline: var(--focus-width) solid var(--color-focus);
  outline-offset: var(--focus-offset);
}
.dfc-ncmap__stop:focus-visible .dfc-ncmap__pin-body { stroke-width: 5; }

.dfc-ncmap__cap {
  font-size: var(--text-sm);
  color: var(--color-text-muted);
  margin-top: var(--space-3);
}

/* ── the cards the pins point at ── */
.dfc-c2cstops {
  list-style: none;
  margin: var(--space-6) 0 0;
  padding: 0;
  display: grid;
  /* auto-fit rather than a fixed 2, so the count follows the real width with no
     breakpoint to keep in sync: two-up in the column, one-up on a phone. */
  grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
  gap: var(--space-4);
}
.dfc-c2cstop {
  background: var(--color-surface-sunk);
  border: 1px solid var(--color-border);
  border-left: 4px solid var(--color-sky);
  border-radius: var(--radius);
  padding: var(--space-5);
  /* Clear the 96px header when a pin scrolls this card into view. */
  scroll-margin-top: calc(var(--header-h) + var(--space-5));
}
/* A jump that scrolls the page and highlights nothing reads as a jump that did
   not work. This is what tells the visitor which pin they just pressed. */
.dfc-c2cstop:target {
  border-left-color: var(--color-amber);
  background: var(--color-warning-tint);
  box-shadow: var(--shadow);
}
.dfc-c2cstop.is-past { border-left-style: dashed; border-left-color: var(--color-border-strong); }

.dfc-c2cstop__when {
  font-family: var(--font-label);
  font-size: var(--text-sm);
  font-weight: var(--weight-semibold);
  text-transform: uppercase;
  letter-spacing: var(--tracking-label);
  margin: 0;
}
.dfc-c2cstop__flag {
  display: inline-block;
  margin-left: var(--space-2);
  padding: 2px var(--space-2);
  border-radius: var(--radius-pill);
  background: var(--color-navy);
  color: var(--color-text-on-dark);       /* white on navy, 12.6:1 */
  font-size: var(--text-xs);
}
.dfc-c2cstop__city {
  font-family: var(--font-headline);
  font-size: var(--text-2xl);
  line-height: var(--leading-heading);
  margin: var(--space-1) 0 0;
}
.dfc-c2cstop__venue { margin: var(--space-1) 0 0; font-size: var(--text-base); }

.dfc-c2cstop__facts {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: var(--space-1) var(--space-3);
  margin: var(--space-4) 0 var(--space-4);
}
.dfc-c2cstop__facts dt {
  font-family: var(--font-label);
  font-size: var(--text-sm);
  font-weight: var(--weight-semibold);
  text-transform: uppercase;
  letter-spacing: var(--tracking-label);
}
.dfc-c2cstop__facts dd { margin: 0; font-size: var(--text-base); }

.dfc-c2cstop__cta { margin: 0; }
.dfc-c2cstop__closed { margin: 0; font-size: var(--text-base); color: var(--color-text-muted); }
.dfc-c2cstops__empty { margin: var(--space-6) 0 0; }

/* ── narrow: measured against the block's own width, not the window's ── */
@container (max-width: 33rem) {
  /* Under about 530px the map is ~200px tall and the labels come out at 11px.
     They go, and the pins grow to stay tappable. Nothing is lost: every pin has
     a full-width card below it carrying the same city, date and link, so the map
     can be the picture and the cards can be the interface. That equivalence is
     also what satisfies WCAG 2.2 target size here. */
  .dfc-ncmap__name { display: none; }
  .dfc-ncmap__pin { transform: scale(2.1); }
  .dfc-ncmap__stop:hover .dfc-ncmap__pin,
  .dfc-ncmap__stop:focus-visible .dfc-ncmap__pin { transform: scale(2.3); }
}

@media (prefers-reduced-motion: reduce) {
  .dfc-ncmap__pin { transition: none; }
}

/* ── FAQ accordions ───────────────────────────────────────────────────────
   Built on the card in design-system 10.3 rather than left as bare <details>.
   The section used to be a wall of underlined summaries on dark green; dark
   green is retired as a background (2026-09-08) and the band is white now, so
   each question becomes a card the reader can see the edges of.

   The marker is a rotating chevron drawn with a border, not a glyph: the UA
   triangle differs on every platform and cannot be coloured reliably. The
   default marker is removed on both the standard and the WebKit pseudo, or
   Safari keeps drawing its own alongside. */
.dfc-faq .wp-block-details {
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
  box-shadow: var(--shadow-sm);
  padding: 0;
  margin: 0 0 var(--space-3);
  overflow: hidden;
}
.dfc-faq .wp-block-details[open] { box-shadow: var(--shadow); }
.dfc-faq .wp-block-details > summary {
  list-style: none;
  cursor: pointer;
  position: relative;
  display: block;
  padding: var(--space-5) var(--space-10) var(--space-5) var(--space-5);
  font-family: var(--font-headline);
  font-size: var(--text-lg);
  font-weight: var(--weight-semibold);
  line-height: var(--leading-snug);
  color: var(--color-navy);
}
.dfc-faq .wp-block-details > summary::-webkit-details-marker { display: none; }
.dfc-faq .wp-block-details > summary::marker { content: ""; }
.dfc-faq .wp-block-details > summary:hover { background: var(--color-surface-alt); }
.dfc-faq .wp-block-details > summary:focus-visible {
  outline: var(--focus-width) solid var(--color-focus);
  outline-offset: calc(var(--focus-offset) * -1);
}
.dfc-faq .wp-block-details > summary::after {
  content: "";
  position: absolute;
  right: var(--space-5);
  top: 50%;
  width: 10px;
  height: 10px;
  margin-top: -7px;
  border-right: 2px solid var(--color-navy);
  border-bottom: 2px solid var(--color-navy);
  rotate: 45deg;
  transition: rotate var(--duration) var(--ease);
}
.dfc-faq .wp-block-details[open] > summary::after { rotate: -135deg; margin-top: -3px; }
/* The panel is inset to the summary's text, so the answer reads as belonging
   to the question rather than starting a new column. */
.dfc-faq .wp-block-details > :not(summary) {
  margin: 0 var(--space-5) var(--space-5);
}
.dfc-faq .wp-block-details > :not(summary):first-of-type { margin-top: 0; }
@media (prefers-reduced-motion: reduce) {
  .dfc-faq .wp-block-details > summary::after { transition: none; }
}

/* ── The whole FAQ set behind one control ─────────────────────────────────
   Camp Morris carries thirteen questions. Closed, that is still thirteen
   rows, and the long ones ("I can't afford camp, but want to send my
   child/children...") wrap to three lines on a phone, so the set filled
   about two screens with text most visitors scroll straight past.

   One outer <details> holding the thirteen inner ones. Nesting is the whole
   mechanism: no JavaScript, keyboard and screen-reader behaviour for free,
   and every answer stays in the DOM, so this costs nothing in search.

   The control is navy-outlined rather than amber. Amber is CTA-only
   (design-system 2), and opening a list is not the action the page is
   asking for. It is deliberately NOT the question card above: a card that
   opens into more cards reads as a rendering bug.

   Every selector here carries .dfc-faq because the block above styles
   .dfc-faq .wp-block-details, which this wrapper also matches. */
.dfc-faq .dfc-faq__all {
  border: 2px solid var(--color-navy);
  box-shadow: none;
  margin-bottom: 0;
}
.dfc-faq .dfc-faq__all[open] { box-shadow: var(--shadow); }

.dfc-faq .dfc-faq__all > summary {
  /* The chevron above is positioned against --space-5 from the right; keep
     the same right padding so the two carets line up down the section. */
  padding: var(--space-4) var(--space-10) var(--space-4) var(--space-5);
  font-family: var(--font-label);
  font-size: var(--text-lg);
  letter-spacing: 0.02em;
}
.dfc-faq .dfc-faq__all > summary:hover { background: var(--color-surface-sunk); }

/* The thirteen questions, revealed. Tighter than the 24px the panel rule
   gives them, and flush left with the control's own label. */
.dfc-faq .dfc-faq__all > .wp-block-details {
  margin: 0 var(--space-4) var(--space-3);
}
.dfc-faq .dfc-faq__all > .wp-block-details:first-of-type { margin-top: var(--space-4); }

/* ── The namesake band: who Camp Morris is named after ─────────────────
   On --color-info-tint rather than white (2026-09-10, Dan). This band sits
   between the FAQ on off-white and the navy donate band, and on white it had
   no edges of its own: the tribute read as the tail of the FAQ section instead
   of as its own moment. The tint is the same pale blue the documents card and
   the aid row already use, so it is a surface the page has established rather
   than a sixth background. Navy on #E7F5FC is 11.36:1.

   The background is set here, by class, rather than through the editor's
   background control, because #E7F5FC is a token and not a palette slug: it has
   no `info-tint` entry in theme.json, and adding one would be a brand change.

   Vertically centred, because the photograph card is much shorter than the
   biography beside it and top-alignment left it hanging off the first third of
   the text. The image is capped below the column width so the portrait is a
   portrait rather than a panel, and stays centred in its card. */
.dfc-namesake { background: var(--color-info-tint); }
/* The editor's flow layout puts 24px between adjacent top-level blocks. Between
   two bands of the same colour that gap is invisible, which is why it went
   unnoticed for every other band on this page; between this one and the navy
   donate band below it, the page background showed through as a white stripe.
   Zeroed on both edges so the band butts against its neighbours the way a
   full-bleed band should. */
.dfc-namesake { margin-top: 0; }
.dfc-namesake + * { margin-top: 0; }
.dfc-namesake .wp-block-image img {
  max-width: 300px;
  margin-inline: auto;
}
.dfc-namesake .wp-block-image { text-align: center; }

/* ── Cost row: the money on the left, the paperwork on the right ──────────
   Two thirds and one third inside the yellow band, so the fee and the payment
   plan read as prose while the documents stay a discrete thing you can find
   again. Collapses at the documented tablet tier.

   NO align-items (2026-09-10, Dan): the default stretch is what makes the
   documents card run the full height of the band. It used to be `start`, which
   sized the card to its three links and left the yellow showing beneath it, so
   the card read as a sticker dropped onto the band rather than as its right
   hand column.

   For that to give EQUAL space above and below the card, the heading has to be
   the first child of the left column rather than a sibling above the grid. As a
   sibling it pushed the grid down, so the card cleared the band's top padding by
   the height of a heading and cleared the bottom by nothing. Inside the column,
   the grid starts at the band's padding edge and both gaps are the band's own
   32px. */
.dfc-costrow {
  display: grid;
  grid-template-columns: minmax(0, 2fr) minmax(0, 1fr);
  gap: var(--space-6);
  text-align: left;
}
/* The band's own heading (2026-09-10, Dan). The yellow box was the only major
   block on the page that opened straight into body copy, so it read as a
   continuation of the steps above rather than as the answer to "what does this
   cost". Navy on #FDD769 is 9.09:1 and is already an approved pairing. Ranged
   left, because the band's content is. */
.dfc-costrow__head {
  font-family: var(--font-headline);
  font-size: var(--text-2xl);
  line-height: var(--leading-tight);
  color: var(--color-navy);
  margin: 0 0 var(--space-4);
}
.dfc-costrow > * { min-width: 0; }
/* The band became real blocks on 2026-09-14 so the dates chart could be a
   dynamic block rather than four typed rows, and WordPress's flow layout then
   put its own block-gap margin on every child after the first. Two things broke
   quietly: the documents card picked up a 24px top margin, so it no longer
   started level with the left column and the equal space Dan's note above
   describes was gone; and the heading's own 16px turned into 40px. This
   component does its spacing itself -- zero the layout's and let it. */
.dfc-costrow > *,
.dfc-costrow > .wp-block-group > * { margin-block-start: 0; }
.dfc-costrow p { margin: 0 0 var(--space-4); }
.dfc-costrow p:last-child { margin-bottom: 0; }
@media (max-width: 980px) {
  .dfc-costrow { grid-template-columns: minmax(0, 1fr); }
}

/* ── The dates chart in that band's left column (Feedbucket 976484) ───────
   Replaces the "Financial information" prose, which said one thing the hero
   facts already said. The fee did not go away, it moved beneath the chart,
   so the band still answers "what does this cost" -- it just answers "where
   and when" first, which is the question people arrive with.

   A real <table> and not a copy of .dfc-hero-sched: this is two labelled
   columns compared down the page, and the header row is what makes it a
   chart rather than a list to anybody reading it with a screen reader. The
   hero keeps its <ul> because it has no header row and one reading order.

   Rules BETWEEN rows, none around them, same as .dfc-hero-sched: four towns
   are one schedule, not four boxes. --color-border cannot do that job here;
   #E5E5E5 on #FDD769 is 1.10:1 and simply disappears, which is why
   --color-rule-on-yellow exists at 1.48:1. */
.dfc-c2cchart {
  width: 100%;
  border-collapse: collapse;
  text-align: left;
}
.dfc-c2cchart :is(th, td) {
  padding: var(--space-3) var(--space-4) var(--space-3) 0;
  border-top: 1px solid var(--color-rule-on-yellow);
  /* middle, not baseline: the last column holds a 44px button, and a button
     baseline-aligned with two lines of text sits visibly low in its row. */
  vertical-align: middle;
}
/* The column labels are the band's only eyebrow-scale type, and they carry the
   same treatment .dfc-hero-sched__head uses for the same purpose. */
.dfc-c2cchart thead th {
  border-top: 0;
  padding-top: 0;
  font-family: var(--font-label);
  text-transform: uppercase;
  letter-spacing: var(--tracking-eyebrow);
  font-size: 0.72rem;
  font-weight: var(--weight-bold);
}
/* Town and date keep the hero schedule's pairing exactly: the place in the
   headline face, the date in the body face at 0.92rem, because the date is the
   thing being scanned for and must not shrink into a label. */
.dfc-c2cchart tbody th {
  font-family: var(--font-headline);
  font-weight: var(--weight-bold);
  font-size: 1.15rem;
  line-height: 1.15;
}
.dfc-c2cchart tbody td { font-size: 0.92rem; line-height: 1.3; }
.dfc-c2cchart td:last-child { padding-right: 0; }
.dfc-c2cchart tbody tr:last-child :is(th, td) { padding-bottom: 0; }
/* Needs an explicit top margin: .dfc-costrow p zeroes the margin on its last
   paragraph, and after this move the fee IS the last paragraph. */
.dfc-costrow .dfc-c2cchart__cost { margin-top: var(--space-5); }

/* ── One Register button per date, in a third column ──────────────────────
   Asked for 2026-09-14, and it is also what makes the table worth being a
   table: location, date, and the thing you do about it, on one line.

   Right-aligned so the buttons form a single edge down the band rather than
   a ragged one that follows the length of each date. */
.dfc-c2cchart__act { text-align: right; white-space: nowrap; }
/* .dfc-btn carries margin: 6px for buttons sitting loose in a row of buttons.
   In a table cell that margin is a gap the row rules have to clear. */
.dfc-c2cchart__cta { margin: 0; }
.dfc-c2cchart__empty { margin: 0; }

/* ── narrow: measured against the column's own width, not the window's ──
   The same approach as the map above, and for the same reason: this table sits
   in a 2fr grid column, so the window width says very little about how much
   room it actually has. At 390px the band is one column and this column is
   about 278px wide -- three columns cannot fit, and a nowrap button would push
   a horizontal scrollbar across the page.

   Stacked, each row reads "Boone, NC / September 26, 2026 / Register for Boone",
   which needs no column headings, so they go. display:block drops the native
   table semantics in most browsers, which is why the markup carries explicit
   role="table"/"row"/"rowheader"/"cell" -- they are redundant in the wide form
   and load-bearing here. */
.dfc-costrow > * { container-type: inline-size; }
@container (max-width: 34rem) {
  .dfc-c2cchart,
  .dfc-c2cchart tbody,
  .dfc-c2cchart tr,
  .dfc-c2cchart :is(th, td) { display: block; }
  .dfc-c2cchart thead { display: none; }
  .dfc-c2cchart tr {
    border-top: 1px solid var(--color-rule-on-yellow);
    padding: var(--space-4) 0;
  }
  .dfc-c2cchart tbody tr:first-child { border-top: 0; padding-top: 0; }
  .dfc-c2cchart tbody tr:last-child { padding-bottom: 0; }
  .dfc-c2cchart :is(th, td) { border-top: 0; padding: 0; }
  .dfc-c2cchart__act {
    text-align: left;
    white-space: normal;
    margin-top: var(--space-3);
  }
  /* The button goes full width once it is on its own line: at this size it is
     the row's only target, and a 278px column leaves nothing to sit beside. */
  .dfc-c2cchart__cta { display: block; text-align: center; }
}

/* The ask-and-answer row from the Programs mega panel, reused in structure AND
   in colour: --color-info-tint #E7F5FC, the same pale blue that row uses in the
   menu (Dan, 2026-09-08). I had overridden it to white on the argument that the
   tint would read as an unrelated sticker on yellow; the counter-argument is
   that it makes the component recognisable as the same thing in both places,
   which matters more. Navy on it is 11.36:1. */
.dfc-costrow .dfc-aid { background: var(--color-info-tint); }

/* Camp documents, on the same --color-info-tint as the aid row beside it, so the
   two supporting cards in this band read as one family rather than two. Navy on
   #E7F5FC is 11.36:1. The list is a stack of real link targets, not bullets. */
.dfc-doccard {
  background: var(--color-info-tint);
  border-radius: 10px;
  padding: var(--space-5);
  box-shadow: var(--shadow-sm);
}
.dfc-doccard :is(h3, h4) {
  font-family: var(--font-headline);
  font-size: var(--text-xl);
  line-height: var(--leading-tight);
  color: var(--color-navy);
  margin: 0 0 var(--space-3);
}
.dfc-doccard ul { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--space-3); }
/* Scoped to the list 2026-09-10. This was written for the card's document
   links, which are one per row, but it also caught any link inside the card's
   prose: the mailto in "contact us at info@thedfc.org" was rendering as its
   own block line at small text in the middle of a sentence. */
.dfc-doccard ul a {
  display: block;
  font-size: var(--text-sm);
  line-height: var(--leading-snug);
  color: var(--color-navy);
}
.dfc-doccard p a {
  color: var(--color-navy);
}
/* ══════════════════════════════════════════════════════════════════════════
   IMPACT FUND: CREATE POSSIBILITY  (/impact/, below the hero)
   ══════════════════════════════════════════════════════════════════════════
   A milestone ladder that ticks itself off the campaign total, over an artwork
   painted in one brush stroke per milestone reached. Engine:
   assets/js/possible.js. Markup: inc/possible.php.

   The whole section is navy so it reads as the same object as the hero rather
   than as a content band that happens to follow it. White on navy is 14.94:1;
   the two accents used here (yellow, sky) carry marks, eyebrows and state
   labels on navy, never body copy. */

.dfc-possible {
  background: var(--color-navy);
  color: var(--color-text-on-dark);
  padding-block: var(--section-y);
  padding-inline: var(--gutter);
}
.dfc-possible__in { max-width: var(--wide); margin-inline: auto; }

/* The eyebrow is gone from this section -- the theme line it carried is now the
   heading below. The rule stays because .dfc-possible is used by the unset-state
   markup further down, and deleting a class that is still emitted is how a page
   ends up with unstyled text. */
.dfc-possible__eyebrow {
  font-family: var(--font-label);
  text-transform: uppercase;
  letter-spacing: var(--tracking-eyebrow);
  font-weight: var(--weight-bold);
  font-size: var(--text-md);
  color: var(--color-yellow);
  margin: 0 0 var(--space-2);
}
.dfc-possible__h2 {
  font-family: var(--font-headline);
  font-weight: var(--weight-bold);
  text-transform: uppercase;
  /* Ceiling is --text-3xl, not --text-5xl. The rendered audit holds a section
     h2 to 28-40px and 5vw against a --text-5xl ceiling measured 60px at 1440,
     which is display size: this is a heading on a page, not a hero. */
  font-size: clamp(var(--text-2xl), 4vw, var(--text-3xl));
  line-height: var(--leading-tight);
  color: var(--color-text-on-dark);
  margin: 0 0 var(--space-4);
}

/* AFTER .dfc-possible__h2, and that is load-bearing. Both selectors are one
   class, so they tie on specificity and source order decides. This block sat
   above it first and the heading rendered Barlow Condensed 36px -- the element
   carries BOTH classes, so the base rule simply won. The deploy gate caught it
   (the font role and the 44-60px band both failed), which is the whole reason
   the audit names this class rather than loosening "main h2". */

/* ── the themed section band heading ─────────────────────────────────────────
   Feedbucket #991526: "BIG HEADING -- Zing Rust", clicked on the line that said
   "Create Possibility".

   This is the display face inside page content, which design-system 8.1 used to
   forbid in as many words. The rule was AMENDED rather than excepted, and the
   clause is written there: hero, page-title bands, and at most two themed
   section bands per page at 2.75rem or larger. Every reason the original rule
   gave survives that -- it was about a single-weight face being asked to carry
   H2 through H6, and about a textured face at 20px being hard to read. One 60px
   band heading is neither, and /impact/ still sits inside the two-to-three uses
   per page ceiling because this template hides the page-title band, so the hero
   H1 and this are the only two.

   Sized UNDER the hero on purpose. 60px against the hero 72px keeps the page
   with one entrance and this as a chapter opening, rather than two things
   competing to be the first thing you read. */
.dfc-possible__h2--display {
  font-family: var(--font-display);
  font-weight: 400;
  letter-spacing: 0;
  font-size: clamp(2.75rem, 5vw, 3.75rem);
  line-height: var(--leading-display);
  color: var(--color-yellow);
  margin: 0 0 var(--space-3);
}

/* The question, demoted to a lead rather than dropped: it is the search-friendly
   phrasing and the line that says what the section answers. White, not yellow,
   so the eye reads the display line first and this second. */
.dfc-possible__q {
  font-family: var(--font-body);
  color: var(--color-white);
  margin: 0 0 var(--space-4);
}
/* The lede is measure-limited, not container-limited. The section runs to
   1200px because the artwork wants the room; a 1200px line of body copy does
   not. This site has already had that exact bug once, on the page-title band. */
/* Carries .dfc-lead in the markup, which sets the size. It is NOT restated here:
   20px would be the dead zone between body copy (16-18) and a lead (21-22), and
   the rendered audit fails anything that lands in it. Only the colour is
   overridden, because .dfc-lead assumes a light surface and this band is navy. */
.dfc-possible__lede {
  max-width: 60ch;
  margin: 0 0 var(--space-8);
  color: var(--color-text-on-dark);
}

/* ── the ladder ────────────────────────────────────────────────────────── */

.dfc-possible__ladder {
  list-style: none;
  margin: 0 0 var(--space-6);
  padding: 0;
  display: grid;
  gap: var(--space-3);
}

/* Two columns from tablet up. A six-rung ladder in a single column pushes the
   artwork most of a screen below the fold, and the artwork is the payoff. */
@media (min-width: 768px) {
  .dfc-possible__ladder { grid-template-columns: 1fr 1fr; gap: var(--space-3) var(--space-5); }
}

.dfc-possible__step {
  position: relative;
  display: grid;
  grid-template-columns: auto 1fr;
  grid-template-areas:
    "mark amount"
    "mark label"
    "mark note";
  column-gap: var(--space-4);
  align-items: start;
  padding: var(--space-4) var(--space-5);
  border-radius: var(--radius-lg);
  /* A hairline, not a fill. Six filled cards on navy read as six buttons, and
     none of these is clickable. */
  border: 2px solid var(--color-rule-on-dark);
  background: rgba(20, 38, 74, 0.55);
}
.dfc-possible__step.is-met { border-color: var(--color-sky); }
/* The reveal rung is the one that is different, so it is allowed to look it. */
.dfc-possible__step.is-final { border-color: var(--color-yellow); }
@media (min-width: 768px) {
  .dfc-possible__step.is-final { grid-column: 1 / -1; }
}

/* The tick, drawn in CSS rather than shipped as an icon file: it is a circle
   and two strokes, and its colour has to follow the met state anyway. */
.dfc-possible__mark {
  grid-area: mark;
  width: 32px;
  height: 32px;
  border-radius: var(--radius-pill);
  border: 3px solid var(--color-border-strong);
  margin-top: var(--space-1);
  position: relative;
}
.dfc-possible__step.is-met .dfc-possible__mark {
  border-color: var(--color-sky);
  background: var(--color-sky);
}
.dfc-possible__step.is-met .dfc-possible__mark::after {
  content: "";
  position: absolute;
  left: 9px;
  top: 4px;
  width: 7px;
  height: 13px;
  border: solid var(--color-navy);
  border-width: 0 3px 3px 0;
  transform: rotate(45deg);
}
.dfc-possible__step.is-final.is-met .dfc-possible__mark {
  border-color: var(--color-yellow);
  background: var(--color-yellow);
}

.dfc-possible__amount {
  grid-area: amount;
  font-family: var(--font-headline);
  font-weight: var(--weight-bold);
  font-size: var(--text-2xl);
  line-height: var(--leading-tight);
  color: var(--color-text-on-dark);
}
/* Reached / still to go, in words. The colour is the fast read; this is the
   one that survives a screen reader and a monochrome print. */
.dfc-possible__state {
  grid-area: amount;
  justify-self: end;
  align-self: center;
  font-family: var(--font-label);
  text-transform: uppercase;
  letter-spacing: var(--tracking-label);
  font-weight: var(--weight-bold);
  font-size: var(--text-sm);
  color: var(--color-yellow);
}
/* White, not --color-border-strong. That token is for interactive BOUNDARIES
   and it is documented as 3.28:1; as text on this card it measures 4.24:1,
   which is under AA for a 14px label. The hollow ring beside it already carries
   the unmet state, so the difference between reached and not does not have to
   be bought by making one of the two labels hard to read. */
.dfc-possible__step.is-open .dfc-possible__state { color: var(--color-text-on-dark); }

.dfc-possible__label {
  grid-area: label;
  font-family: var(--font-headline);
  font-weight: var(--weight-semibold);
  font-size: var(--text-xl);
  line-height: var(--leading-heading);
  color: var(--color-text-on-dark);
  margin-top: var(--space-1);
}
.dfc-possible__note {
  grid-area: note;
  font-size: var(--text-base);
  line-height: var(--leading-normal);
  color: var(--color-text-on-dark);
  margin-top: var(--space-2);
  max-width: 48ch;
}

.dfc-possible__progress {
  font-family: var(--font-label);
  text-transform: uppercase;
  letter-spacing: var(--tracking-label);
  font-weight: var(--weight-bold);
  font-size: var(--text-md);
  color: var(--color-yellow);
  margin: 0 0 var(--space-6);
}

/* ── the artwork ───────────────────────────────────────────────────────── */

.dfc-possible__art { margin: 0; }

.dfc-possible__frame {
  position: relative;
  aspect-ratio: 16 / 9;
  border-radius: var(--radius-lg);
  overflow: hidden;
  background: var(--color-navy-active);
  isolation: isolate;
}

.dfc-possible__base {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

/* The obscured state applies ONLY once the engine has loaded the artwork and
   added .is-ready.

   That ordering is the whole no-JavaScript story. Without the script the class
   never lands, the wash never applies, and a visitor simply sees the picture.
   Putting the blur in the default state instead would leave anyone without
   JavaScript staring at a permanently smeared image and no way to clear it.

   Scaled up a little because a blur samples past its own edges and would
   otherwise leave a soft transparent rim inside the frame. */
.dfc-possible.is-ready .dfc-possible__base {
  filter: blur(26px) saturate(0.12) brightness(0.5);
  transform: scale(1.08);
}

/* The canvas carries the uncovered artwork, pixel-exact, over the wash. */
.dfc-possible__paint {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
  z-index: var(--z-raised);
  pointer-events: none;
}

/* Artwork failed to load: stand the effect down rather than leave an empty
   canvas over a picture the <img> underneath may still be showing. */
.dfc-possible.is-fallback .dfc-possible__paint { display: none; }
.dfc-possible.is-fallback .dfc-possible__base { filter: none; transform: none; }

/* The artwork slot label. Rendered only while dfc_impact_reveal_is_placeholder()
   is true, so swapping the artwork file removes it with no CSS change. */
.dfc-possible__badge {
  position: absolute;
  z-index: var(--z-overlay);
  top: var(--space-4);
  left: var(--space-4);
  margin: 0;
  font-family: var(--font-label);
  text-transform: uppercase;
  letter-spacing: var(--tracking-label);
  font-weight: var(--weight-bold);
  font-size: var(--text-sm);
  color: var(--color-navy);
  background: var(--color-yellow);
  border: 2px solid var(--color-navy);
  border-radius: var(--radius-pill);
  padding: var(--space-2) var(--space-4);
}

.dfc-possible__caption {
  font-size: var(--text-base);
  line-height: var(--leading-normal);
  color: var(--color-text-on-dark);
  margin-top: var(--space-4);
  max-width: 68ch;
}

/* ── the ask ───────────────────────────────────────────────────────────── */

.dfc-possible__cta-row { margin: var(--space-8) 0 0; }
.dfc-possible__cta {
  font-family: var(--font-label);
  font-weight: var(--weight-bold);
  text-transform: uppercase;
  letter-spacing: var(--tracking-label);
  font-size: var(--text-lg);
  line-height: 1;
  color: var(--color-cta-text);
  background: var(--color-cta);
  /* Amber is 1.80:1 on white and needs its 2px navy border on light surfaces.
     Here it sits on navy, where the field already gives it a 3:1 boundary, so
     the border is white instead and does the opposite job: it lifts the button
     off a dark field rather than fencing it on a light one. */
  border: 3px solid var(--color-white);
  border-radius: var(--radius-pill);
  padding: var(--space-4) var(--space-8);
  min-height: 56px;
  box-sizing: border-box;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  text-decoration: none;
}
.dfc-possible__cta:hover { background: var(--color-cta-hover); }
.dfc-possible__cta:focus-visible {
  outline: var(--focus-width) solid var(--color-focus-on-dark);
  outline-offset: var(--focus-offset);
}

/* ── the hero second button, and the hero slogan ───────────────────────── */

/* Ghost sibling of .dfc-spark__donate and .dfc-cannon. Shares their shape so
   the cluster footprint does not change between states. */
.dfc-spark__more {
  font-family: var(--font-label);
  font-weight: var(--weight-bold);
  text-transform: uppercase;
  letter-spacing: var(--tracking-label);
  font-size: var(--text-lg);
  line-height: 1;
  border-radius: var(--radius-pill);
  padding: var(--space-4) var(--space-8);
  min-height: 56px;
  box-sizing: border-box;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  text-decoration: none;
  color: var(--color-white);
  /* Not transparent. This button sits over a photograph that changes every few
     seconds, and a transparent fill would make the label contrast whatever the
     next slide happens to be. The navy scrim holds white type above 7:1 no
     matter what is behind it. */
  background: rgba(20, 38, 74, 0.72);
  border: 2px solid var(--color-white);
}
.dfc-spark__more:hover { background: var(--color-navy-hover); }
.dfc-spark__more:focus-visible {
  outline: var(--focus-width) solid var(--color-focus-on-dark);
  outline-offset: 4px;
}

/* The 2026 slogan leads the revealed say-line and is allowed to be louder than
   the sentence that follows it. */
/* Sized up when the lockup moved below it. This is the line carrying the
   campaign message, so between a 60px title and a 24px tagline it is the one
   that should read second, not third. */
/* The supporting note, below the brief's three hero lines. Body size on
   purpose: it explains the mechanic, and anything larger would read as a fourth
   headline competing with the lockup immediately above it. */
.dfc-spark__note {
  color: var(--color-white);
  font-size: var(--text-md);
  line-height: var(--leading-normal);
  margin: 0 auto var(--space-6);
  max-width: 46ch;
  text-shadow: 0 2px 16px rgba(20, 38, 74, 0.85);
}

.dfc-spark__slogan {
  display: block;
  font-family: var(--font-headline);
  font-weight: var(--weight-bold);
  font-size: clamp(1.25rem, 2.6vw, 1.75rem);
  line-height: var(--leading-tight);
  margin-bottom: var(--space-2);
}

@media (max-width: 767px) {
  /* The state label cannot share a row with a 28px figure at this width, so it
     drops below the note. It needs its own ROW to drop into: two items given
     the same grid area are placed on top of each other, and the first version
     of this rule reused "note" and printed REACHED straight through the middle
     of the sentence underneath it. */
  .dfc-possible__step {
    padding: var(--space-4);
    grid-template-areas:
      "mark amount"
      "mark label"
      "mark note"
      "mark state";
  }
  .dfc-possible__state { grid-area: state; justify-self: start; margin-top: var(--space-2); }
  .dfc-possible__badge { font-size: var(--text-xs); padding: var(--space-1) var(--space-3); }
}

@media (prefers-reduced-motion: reduce) {
  /* The reveal still happens, it just arrives finished: possible.js skips
     straight to the painted state. Nothing here needs to animate. */
  .dfc-possible__mark { transition: none; }
}

/* ── placeholder notice, for page content awaiting real copy ───────────────
   Used by the Impact Fund testimonial and get-involved bands while their copy
   is invented. It is deliberately loud: a soft grey "draft" note gets read past
   and ends up launched. Delete the blocks that carry this class, not the rule,
   when the real copy lands.

   Navy on yellow is 9.94:1, so the notice meets AA as body copy rather than
   relying on being decoration. */
.dfc-ph-note {
  border: 2px solid var(--color-navy);
  background: var(--color-yellow);
  color: var(--color-navy);
  border-radius: var(--radius);
  padding: var(--space-3) var(--space-4);
  font-size: var(--text-base);
  line-height: var(--leading-normal);
}
.dfc-ph-note strong {
  font-family: var(--font-label);
  text-transform: uppercase;
  letter-spacing: var(--tracking-label);
}

/* ── get-involved cards, on the navy band at the foot of /impact/ ──────────
   A class rather than the block editor's border controls, because those write
   a literal hex into the markup and the token rule in docs/design-system.md §1
   says a value that has a token does not get restated. --color-rule-on-dark is
   that token, and it exists precisely for a hairline on navy. */
.dfc-involve-card {
  border: 2px solid var(--color-rule-on-dark);
  border-radius: var(--radius-lg);
  /* Equal height across the row. The four cards carry different amounts of
     copy, and without this the buttons sit at four different heights. */
  height: 100%;
}
.dfc-involve-card .wp-block-buttons { margin-top: var(--space-4); }

/* ── quote cards in a columns row ──────────────────────────────────────────
   Same idiom as .dfc-role-grid and .dfc-event-grid above: the column is already
   a flex child that stretches, so it is the GROUP inside it that has to be told
   to fill it. Without this a row of quotes is as ragged as the quotes are long,
   and a two-line testimonial next to a six-line one reads as a layout fault
   rather than as someone having been brief. */
.dfc-quote-grid > .wp-block-column > .wp-block-group { height: 100%; }

/* ── Create Possibility, UNCONFIRMED state ─────────────────────────────────
   Rendered while DFC has not supplied the milestone ladder. There are no
   invented amounts and no invented copy anywhere in this state: the rungs are
   labelled slots, and the reveal is driven by a preview control instead of by a
   campaign total that does not exist yet.

   The whole state removes itself when real milestones arrive, because the PHP
   condition is simply "the milestone array has rows in it". Nothing here has to
   be found and deleted by hand. */

/* One note for the section, rather than a chip on every value. Yellow, because
   this is the same "read me before you react" role the placeholder chips had,
   and navy on yellow is 9.94:1. */
.dfc-possible__unset-note {
  border: 2px solid var(--color-yellow);
  border-radius: var(--radius-lg);
  /* No fill at all. A yellow tint was the first attempt and the contrast check
     rejected it: a semi-transparent background is one the checker cannot resolve,
     so it read the tint as solid yellow and scored the yellow title on it at 1:1.
     It was right to stop -- a background whose real contrast cannot be verified
     statically is one nobody can vouch for. The border alone carries the role,
     and the text keeps the section navy it was measured against. */
  padding: var(--space-4) var(--space-5);
  margin: 0 0 var(--space-6);
  max-width: 68ch;
}
.dfc-possible__unset-note p {
  margin: 0;
  font-size: var(--text-base);
  line-height: var(--leading-normal);
  color: var(--color-text-on-dark);
}
.dfc-possible__unset-title {
  font-family: var(--font-label);
  text-transform: uppercase;
  letter-spacing: var(--tracking-label);
  font-weight: var(--weight-bold);
  color: var(--color-yellow) !important;
  font-size: var(--text-md) !important;
  margin-bottom: var(--space-2) !important;
}

/* A rung with nothing in it yet. Dashed, because a dashed edge is the one
   border convention that already means "not filled in", and it distinguishes an
   empty slot from the solid-edged unreached rung of a real ladder. */
.dfc-possible__step.is-slot {
  border-style: dashed;
  border-color: var(--color-border-strong);
  background: transparent;
}
.dfc-possible__step.is-slot .dfc-possible__mark {
  border-style: dashed;
  background: none;
}
/* Preview-painted rungs light up the same way a reached rung would, so the
   control demonstrates the tick as well as the brush. */
.dfc-possible__step.is-slot.is-painted {
  border-style: solid;
  border-color: var(--color-sky);
}
.dfc-possible__step.is-slot.is-painted .dfc-possible__mark {
  border-style: solid;
  border-color: var(--color-sky);
  background: var(--color-sky);
}
.dfc-possible__step.is-slot.is-painted .dfc-possible__mark::after {
  content: "";
  position: absolute;
  left: 9px;
  top: 4px;
  width: 7px;
  height: 13px;
  border: solid var(--color-navy);
  border-width: 0 3px 3px 0;
  transform: rotate(45deg);
}
.dfc-possible__step.is-slot.is-final.is-painted {
  border-color: var(--color-yellow);
}
.dfc-possible__step.is-slot.is-final.is-painted .dfc-possible__mark {
  border-color: var(--color-yellow);
  background: var(--color-yellow);
}

/* "Milestone 3" is a position, not a figure, so it does not get the figure's
   weight. Dropping it a step is what stops a slot reading as a value. */
.dfc-possible__step.is-slot .dfc-possible__amount {
  font-size: var(--text-xl);
  color: var(--color-text-on-dark);
}
.dfc-possible__step.is-slot .dfc-possible__state {
  color: var(--color-yellow);
}
.dfc-possible__step.is-slot .dfc-possible__label {
  font-weight: var(--weight-semibold);
  color: var(--color-text-on-dark);
}

/* The empty copy area. A ruled box the size of the real thing, rather than
   lorem: lorem invites people to read it and then to react to it, and a grey bar
   says nothing about how much room the rung actually has. */
.dfc-possible__slot {
  grid-area: note;
  display: block;
  margin-top: var(--space-2);
  min-height: 3.2rem;
  border: 1px dashed var(--color-rule-on-dark);
  border-radius: var(--radius-sm);
  /* Faint ruled lines, so the box reads as "copy goes here" at a glance and at
     a distance, without carrying any words of its own. */
  background: repeating-linear-gradient(
    to bottom,
    transparent 0,
    transparent 1.5rem,
    var(--color-rule-on-dark) 1.5rem,
    var(--color-rule-on-dark) calc(1.5rem + 1px)
  );
}

/* ── the preview control ───────────────────────────────────────────────────
   Review scaffolding. It exists only in the unconfirmed state and disappears
   with it, and it says on its face that it is not live data -- a control that
   moves a fundraising meter had better not be mistaken for the meter. */
.dfc-possible__preview {
  margin-top: var(--space-6);
  padding: var(--space-4) var(--space-5);
  border: 2px dashed var(--color-rule-on-dark);
  border-radius: var(--radius-lg);
  max-width: 68ch;
}
.dfc-possible__preview-title {
  font-family: var(--font-label);
  text-transform: uppercase;
  letter-spacing: var(--tracking-label);
  font-weight: var(--weight-bold);
  font-size: var(--text-md);
  color: var(--color-yellow);
  margin: 0 0 var(--space-2);
}
.dfc-possible__preview-help {
  font-size: var(--text-base);
  line-height: var(--leading-normal);
  color: var(--color-text-on-dark);
  margin: 0 0 var(--space-4);
}
.dfc-possible__preview-btns {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
}
.dfc-possible__preview-btn {
  font-family: var(--font-label);
  font-weight: var(--weight-bold);
  text-transform: uppercase;
  letter-spacing: var(--tracking-label);
  font-size: var(--text-base);
  line-height: 1;
  /* 44px on both axes. WCAG 2.2 asks 24; this site asks 44, and a row of small
     square buttons is exactly where that difference is felt. */
  min-width: 44px;
  min-height: 44px;
  padding: var(--space-3);
  box-sizing: border-box;
  cursor: pointer;
  border-radius: var(--radius);
  color: var(--color-text-on-dark);
  background: transparent;
  border: 2px solid var(--color-border-strong);
}
.dfc-possible__preview-btn:hover {
  border-color: var(--color-white);
}
.dfc-possible__preview-btn:focus-visible {
  outline: var(--focus-width) solid var(--color-focus-on-dark);
  outline-offset: var(--focus-offset);
}
.dfc-possible__preview-btn.is-on {
  color: var(--color-navy);
  background: var(--color-sky);
  border-color: var(--color-sky);
}
