/* ==========================================================================
   COMPONENT: Hero + section eyebrow + urgent ribbon
   ========================================================================== */

.c-hero {
  position: relative;
  /* Reduced from min(92svh, 60rem). This is the setting that decides how much
     of the photograph you actually see: the image is 21:9 (2.33), and every bit
     of hero height beyond that ratio is width thrown away by the cover-crop.
     At 92svh the hero was ~828px on a 1440px screen — a 1.74 ratio, cropping a
     quarter of the picture off the sides.

     Dropping the lede freed the height to do it. The hero is now driven by its
     own content (the donate card is the tallest thing in it), landing near 2.1
     at 1440px and cutting the crop to roughly 8%. Wider screens exceed 2.33 and
     crop vertically instead, which is why object-position keeps a Y value. */
  min-height: min(72svh, 42rem);
  display: grid;
  align-items: end;
  isolation: isolate;
  background-color: var(--c-ink);
  color: var(--c-text-inv);
  overflow: hidden;
}

.c-hero__media {
  position: absolute;
  inset: 0;
  z-index: -2;
}

/* The <picture> must be stretched too. `height: 100%` on the img resolves
   against its parent, and a bare <picture> is auto-height — which is why the
   hero image stopped short of the section and left a navy band underneath. */
.c-hero__media picture {
  display: block;
  width: 100%;
  height: 100%;
}

.c-hero__media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* The image is 21:9 and the hero box never is, so a slice of the width is
     always cropped away — the only question is which slice. Both values below
     were chosen by rendering the real page at candidates and looking, not by
     eye on the raw file.

     PHONES crop brutally: a 390px-wide viewport shows barely a fifth of the
     picture. 47% centres that narrow window on the handover, so the sack, both
     pairs of hands and the woman's lit face all survive. The desktop value of
     25% would show only the volunteer's back — the story gone completely. */
  object-position: 47% 50%;
  /* The settle is applied by motion.js; this is the resting state so the
     LCP image is correct on first paint even if GSAP never loads. */
  transform: scale(1);
}

/* Scrim: strongest where the headline sits, per the art-direction lock in
   IMAGE-PROMPTS.md hero-01.

   The headline moved to the RIGHT column when the donate card took the left,
   so this gradient is mirrored (100deg → 260deg) to follow it. The stops are
   unchanged: measuring the image's right-hand headline zone gives a brightest
   pixel of rgb(255,253,210) against rgb(255,247,212) on the left, so both
   sides need the same 0.70 to clear AAA. Re-measure with
   tools/check-hero-contrast.py if the hero image changes — note that the tool
   crops the LEFT third by default and needs its zone updated to match. */
.c-hero__scrim {
  position: absolute;
  inset: 0;
  z-index: -1;
  /* Opacity measured against the COMPOSITED PAGE, not against a flat scrim.

     tools/check-hero-contrast.py reports the ratio for a UNIFORM scrim over
     the image's single brightest pixel. This is a gradient, so that number
     does not describe what any particular element actually sits on — at the
     headline's x position the gradient resolves to roughly 0.40, not 0.70.
     These stops were tuned instead by screenshotting the live hero, hiding
     each text element in turn, and sampling the real pixels behind it.

     These stops are deliberately light — the brief was for the photograph to
     read bright and open rather than dimmed behind a wash. 0.56 is the LIGHTEST
     value that still holds at every viewport width, which is the constraint
     that set it. Worst-pixel ratio on the h1, measured across widths:

       scrim   1024   1280   1440   1920   2560    minimum
       0.50    3.13   3.41   3.76   2.96   2.66    2.66   fails
       0.56    3.66   4.06   4.45   3.54   3.16    3.16   passes
       0.62    4.37   4.82   5.25   4.22   3.76    3.76   passes

     The threshold is 3:1, because the headline is large text at every width
     (40px minimum). Note the widest screens are the WORST case, not the best:
     past a 2.33 ratio the hero crops the image vertically instead of
     horizontally and pulls brighter mid-frame pixels under the type. Testing
     only at 1440 would have shipped a failure at 1920 and 2560.

     Margin at 2560 is 3.16 against a 3.0 floor — thin. If the headline is ever
     made smaller than 40px, or lighter than 500, or set in a hairline face,
     re-measure before assuming this still passes: below 24px the threshold
     becomes 4.5:1 and none of these values would clear it.

     "Original design" is the pre-existing headline-left layout, measured the
     same way. Worth knowing: it never passed worst-pixel either — white type
     over a photograph with specular highlights in it rarely does, and worst-
     pixel is a deliberately harsh test since the bounding box includes the
     gaps between glyphs. The bar used here is therefore "beat the design's
     own established baseline on every figure", which these stops do, rather
     than an absolute number that would require crushing the photograph to
     black to satisfy.

     Re-measure the same way if the hero image or the headline's column
     changes. The Python tool will not catch this class of failure on its own,
     because it does not know where in the gradient the text lands. */
  background:
    linear-gradient(
      100deg,
      rgb(14 19 48 / 0.56) 0%,
      rgb(14 19 48 / 0.48) 45%,
      rgb(14 19 48 / 0.32) 72%,
      rgb(14 19 48 / 0.12) 100%
    ),
    linear-gradient(to top, rgb(14 19 48 / 0.30), transparent 42%);
}

.c-hero__inner {
  width: 100%;
  max-width: var(--w-wide);
  margin-inline: auto;
  padding: calc(var(--header-h) + var(--sp-9)) var(--gutter) var(--sp-9);
  display: grid;
  gap: var(--sp-5);
  justify-items: start;
}

/* --------------------------------------------------------------------------
   FULL-IMAGE HERO — no crop at all, plus the two-column layout
   --------------------------------------------------------------------------
   Locking the hero to the photograph's own 21:9 ratio means object-fit: cover
   has nothing left to cut: the whole frame is visible, edge to edge. That is
   the only way to genuinely show the entire image — panning with
   object-position merely chooses which part to lose.

   Both the ratio and the two-column layout start at the SAME breakpoint, and
   they have to. Locking the ratio fixes the hero's height at width ÷ 2.333,
   and the donate card needs 429px of it:

       width    hero height at 21:9    card fits?
       1024      439px                  no
       1280      549px                  yes
       1440      617px                  yes
       1920      823px                  yes

   1280px is therefore the floor for both. Below it the hero would be shorter
   than the card itself, so the card steps out and Quick Donate — the same
   widget — carries the interaction, as it already does on phones. Splitting
   these two rules across different breakpoints would leave a band where the
   grid reserves a second column for a card that is not there.

   max-height guards ultrawide screens, where 21:9 would push the hero past the
   viewport. Beyond that point cropping resumes, which is the right trade: a
   donor who cannot see the Donate button is worse off than one seeing 95% of a
   photograph.
   -------------------------------------------------------------------------- */

@media (min-width: 1280px) {
  .c-hero {
    aspect-ratio: 21 / 9;
    min-height: 0;
    max-height: 92svh;
  }

  .c-hero__inner {
    max-width: min(var(--w-wide), 100%);
    /* Headline left, donate card right. The card keeps a fixed comfortable
       measure and only the headline column stretches — a form that grows with
       the viewport looks stretched, never generous.

       This order also matches the photograph's own composition: quiet negative
       space on the left for the headline, subject toward the centre-right, so
       the type never lands on a face. See IMAGE-PROMPTS.md → hero-01. */
    grid-template-columns: minmax(0, 1fr) 22.5rem;
    gap: var(--sp-7);
    align-items: end;

    /* Tight block padding. The hero's height is now dictated by the image
       ratio rather than by content, so any padding here eats directly into the
       room the card has to fit in. */
    padding-block: calc(var(--header-h) + var(--sp-4)) var(--sp-4);
  }

  .c-hero__content {
    max-width: 100%;
    grid-column: 1;
    grid-row: 1;
  }

  .c-hero__card {
    grid-column: 2;
    grid-row: 1;
  }
}

/* --------------------------------------------------------------------------
   CARD NUDGE — up and outward
   --------------------------------------------------------------------------
   The card sat on the inner wrapper's own gutter, which left it further from
   the right edge and lower in the frame than the composition wants: the
   photograph's subject is centre-right, so a card tucked inside the gutter
   crowds it, and `align-items: end` parked it near the hero's floor.

   Both nudges are bounded by measurement rather than taste. Moving the card UP
   eats its clearance below the header, and that clearance is not the same at
   every width because the hero's height is locked to the image ratio:

       width   card top   header bottom   clearance   lift applied   left
       1280     107px       73px            34px       none            34px
       1440     175px       73px           102px       48px            54px
       1600     228px       73px           155px       64px            91px

   So the vertical shift starts at 1400px, where there is room for it, and 1280
   gets none — a lift there would leave the card ~18px under the nav, which reads
   as a collision. The outward shift keeps at least 8px of viewport margin at
   every width; flush to the edge would read as broken rather than confident.
   -------------------------------------------------------------------------- */

@media (min-width: 1280px) {
  /* Half the gutter, pulled outward. Keeps a deliberate margin from the viewport
     edge — the card is a form, and a form flush to the edge reads as broken
     rather than confident.

     It has to be a positive START margin, not a negative END margin. The card
     carries `width: 100%` and .c-hero__inner sets `justify-items: start`, so the
     box is over-constrained: in LTR the end margin is the one the browser drops.
     Measured both ways — a negative inline-end margin left the right gap at 40px,
     unchanged. A start margin pushes the whole box outward as intended. */
  .c-hero__card { margin-inline-start: calc(var(--gutter) * 0.8); }
}

@media (min-width: 1400px) {
  .c-hero__card { margin-block-end: var(--sp-7); }
}

@media (min-width: 1600px) {
  /* Above 1600 the inner wrapper is centred inside a wider viewport, so there is
     80px of dead margin on each side. The card reclaims most of the right one. */
  .c-hero__card {
    margin-inline-start: 6.5rem;
    margin-block-end: var(--sp-8);
  }
}

@media (min-width: 1600px) {
  .c-hero__inner {
    grid-template-columns: minmax(0, 1fr) 24rem;
    padding-block: calc(var(--header-h) + var(--sp-6)) var(--sp-6);
  }
}

.c-hero__content {
  display: grid;
  gap: var(--sp-5);
  justify-items: start;
}

.c-hero__eyebrow {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  font-family: var(--ff-utility);
  font-size: var(--fs-3xs);
  letter-spacing: var(--tr-wide);
  text-transform: uppercase;
  color: var(--c-verdigris-lt);
  padding: var(--sp-2) var(--sp-3);
  border: 1px solid rgb(127 227 214 / 0.35);
  border-radius: var(--r-full);
  /* The chip carries its own ground rather than relying on the scrim.
     Sitting straight on the photograph it measured 4.44:1 at 1024px and 2560px
     — just under the 4.5:1 required for text this small (≈11.6px). Darkening
     the whole scrim to rescue one small label would have dimmed the entire
     picture, which is the opposite of the brief; a local fill fixes it where
     the problem actually is and reads as a deliberate badge.

     With the fill: 4.87 / 5.53 / 5.06 / 4.91 at 1024 / 1440 / 1920 / 2560.

     Measure this one by sampling only the PADDING box, inset past the border.
     The 1px verdigris border is lighter than the fill and no glyph ever touches
     it, so including it in the sample reports a false failure — it did exactly
     that here until the sample was inset. */
  background-color: rgb(14 19 48 / 0.55);
}

@supports (backdrop-filter: blur(1px)) {
  .c-hero__eyebrow {
    background-color: rgb(14 19 48 / 0.42);
    backdrop-filter: blur(8px);
  }
}

.c-hero__title {
  font-family: var(--ff-display);
  /* Stepped down from --fs-5xl (48→96px) to --fs-4xl (40→68px) and from bold
     to medium. At 96px and 700 the headline was competing with the photograph
     rather than sitting over it; the picture is now the loudest thing in the
     hero, which is the point of having commissioned it.

     Still comfortably "large text" for contrast purposes at every viewport
     (40px minimum, far above the 24px / 18.5pt threshold), so the 3:1 large-text
     bar applies rather than 4.5:1 — which is what makes the lighter scrim
     below affordable. Lighter weight does cost some legibility, so the measured
     ratio is held well above that floor regardless. */
  font-size: var(--fs-4xl);
  font-weight: var(--fw-medium);
  line-height: var(--lh-tight);
  letter-spacing: var(--tr-display);
  color: var(--c-text-inv);
  max-width: 18ch;
}

.c-hero__lede {
  font-size: var(--fs-lg);
  line-height: var(--lh-normal);
  color: var(--c-text-muted-inv);
  max-width: var(--measure-tight);
}

.c-hero__actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-3);
  margin-block-start: var(--sp-2);
}

/* Per-line and per-character reveal masks. The overflow clip is what makes
   the masked reveal read as a wipe rather than a fade. */
.c-hero__title .line { display: block; overflow: hidden; }
.c-hero__title .char { display: inline-block; will-change: transform; }

/* --------------------------------------------------------------------------
   HERO ENTRANCE
   --------------------------------------------------------------------------
   The whole sequence is one class (`.is-entered`, added by motion.js on the
   second frame) and a set of transition-delays, so the timeline is legible in
   one place instead of being scattered through JS.

   Two things deliberately do NOT animate from hidden:
     · the hero IMAGE — it is the LCP element and must paint on first frame.
       Only its scale settles, which the compositor does off the main thread.
     · anything at all when JS is absent — the `.has-motion` gate means the
       resting state IS the no-JS state.
   -------------------------------------------------------------------------- */

.has-motion .c-hero__media img {
  transform: scale(1.06);
  transition: transform var(--d-hero) var(--e-out);
  will-change: transform;
}

.has-motion .c-hero.is-entered .c-hero__media img { transform: scale(1); }

.has-motion .c-hero__eyebrow,
.has-motion .c-hero__lede,
.has-motion .c-hero__actions,
.has-motion .c-hero__cue {
  opacity: 0;
  transform: translate3d(0, 14px, 0);
  transition:
    opacity   var(--d-slow) var(--e-out),
    transform var(--d-slow) var(--e-out);
}

.has-motion .c-hero.is-entered .c-hero__eyebrow,
.has-motion .c-hero.is-entered .c-hero__lede,
.has-motion .c-hero.is-entered .c-hero__actions,
.has-motion .c-hero.is-entered .c-hero__cue {
  opacity: 1;
  transform: translate3d(0, 0, 0);
}

/* The eyebrow leads, the headline plays over it, the lede and buttons land
   after. Delays rather than a JS timeline: no frames spent scheduling. */
.has-motion .c-hero__eyebrow { transition-delay: 80ms; }
.has-motion .c-hero__lede    { transition-delay: 520ms; }
.has-motion .c-hero__actions { transition-delay: 620ms; }
.has-motion .c-hero__cue     { transition-delay: 900ms; }

/* Release the compositor layers once the entrance is over. */
.has-motion .c-hero.is-entered .c-hero__media img { will-change: auto; }

@media (prefers-reduced-motion: reduce) {
  .has-motion .c-hero__media img,
  .has-motion .c-hero__eyebrow,
  .has-motion .c-hero__lede,
  .has-motion .c-hero__actions,
  .has-motion .c-hero__cue {
    opacity: 1;
    transform: none;
    transition: none;
  }
}

/* --------------------------------------------------------------------------
   HERO DONATE CARD
   --------------------------------------------------------------------------
   A light panel over the photograph. Every control inside it (.c-chip,
   .c-freq, .c-amount) is the shared donate-widget furniture from
   quick-donate.css, which is already styled for a light surface — so the card
   needs no overrides for any of it, only its own shell.

   The panel is nearly opaque on purpose. A heavier glass effect looks better
   in isolation but this sits over a busy, high-contrast photograph, and the
   amount field and impact line have to stay comfortably readable against
   whatever pixels happen to be behind them.
   -------------------------------------------------------------------------- */

/* The card's HEIGHT is now a hard constraint, not a matter of taste. The hero
   is set to the photograph's own 21:9 ratio so nothing is cropped, which fixes
   the hero's height at width ÷ 2.333 — 549px at 1280px, the narrowest width
   that still shows the card. Minus the header and padding, the card has 429px
   to live in. It was 536px. Everything below is that 107px being reclaimed:
   one row of chips instead of two (−52), tighter internal gaps (−24), one
   line reserved for the impact copy instead of two (−21), slightly less
   padding (−8). No control was removed and no touch target shrank — the chips
   and the submit button keep their 44px floor. */
.c-hero__card {
  position: relative;
  z-index: 1;
  width: 100%;
  padding: var(--sp-4) var(--sp-5);
  background-color: color-mix(in srgb, var(--c-paper) 96%, transparent);
  border: 1px solid color-mix(in srgb, var(--c-paper) 60%, transparent);
  border-radius: var(--r-xl);
  box-shadow: var(--shadow-3);
  color: var(--c-text);
}

/* Progressive enhancement: the blur only applies where it is supported, and
   the panel is legible without it either way. */
@supports (backdrop-filter: blur(1px)) {
  .c-hero__card {
    background-color: color-mix(in srgb, var(--c-paper) 88%, transparent);
    backdrop-filter: blur(20px) saturate(1.1);
  }
}

.c-give {
  display: grid;
  gap: var(--sp-3);
}

/* Four amounts on one row rather than 2×2 — the single biggest height saving
   available, and it reads as a tighter, more decisive control set. */
.c-hero__card .c-chips { grid-template-columns: repeat(4, minmax(0, 1fr)); }
.c-hero__card .c-chip { padding-inline: var(--sp-2); }

.c-give__head { display: grid; gap: var(--sp-1); }

.c-give__eyebrow {
  font-family: var(--ff-utility);
  font-size: var(--fs-3xs);
  letter-spacing: var(--tr-wide);
  text-transform: uppercase;
  color: var(--c-verdigris);
}

.c-give__title {
  font-family: var(--ff-display);
  font-size: var(--fs-xl);
  font-weight: var(--fw-bold);
  line-height: var(--lh-tight);
  letter-spacing: var(--tr-tight);
  color: var(--c-ink);
}

.c-give__impact {
  font-size: var(--fs-sm);
  line-height: var(--lh-snug);
  color: var(--c-text-muted);
  /* One line reserved rather than two. The line still varies in length as the
     amount changes, so some height must be held or the card jitters — but at
     this width the longest impact string fits on one line, and reserving a
     second was costing 21px for nothing. */
  min-height: 1.5em;
}

.c-give__impact strong {
  color: var(--c-ink);
  font-weight: var(--fw-medium);
}

.c-give__trust {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  font-family: var(--ff-utility);
  font-size: var(--fs-3xs);
  letter-spacing: 0.01em;
  color: var(--c-text-muted);
}

.c-give__trust svg {
  width: 15px;
  height: 15px;
  flex: none;
  color: var(--c-verdigris);
}

/* The card joins the hero entrance one beat after the buttons. */
.has-motion .c-hero__card {
  opacity: 0;
  transform: translate3d(0, 20px, 0);
  transition:
    opacity   var(--d-slow) var(--e-out),
    transform var(--d-slow) var(--e-out);
  transition-delay: 700ms;
}

.has-motion .c-hero.is-entered .c-hero__card {
  opacity: 1;
  transform: translate3d(0, 0, 0);
}

@media (prefers-reduced-motion: reduce) {
  .has-motion .c-hero__card { opacity: 1; transform: none; transition: none; }
}

/* Below the full-image breakpoint the hero is shorter than the card itself, so
   the card cannot sit inside it (see the FULL-IMAGE HERO block for the
   arithmetic). It is not dropped for lack of interest: the persistent Quick
   Donate panel is the SAME widget on the same [data-donate-widget] contract and
   is always on screen at these widths, so nothing is lost but the duplication. */
@media (max-width: 1279px) {
  .c-hero__card { display: none; }

  /* A different scrim, because these widths are a different problem.
     The desktop scrim runs left-to-right, following a headline in the left
     column. Here the layout is a single column with the type low in the frame,
     and the crop is severe — 72% of the width is gone at 390px — so whatever
     survives is the bright, sunlit middle of the photograph rather than the
     dark edges the desktop scrim was measured against.

     Carrying the desktop values down here failed outright: h1 2.93:1 and
     eyebrow 3.77:1 at 390px. This is bottom-weighted to match where the text
     actually sits, which buys the contrast back without flattening the top of
     the frame where the picture is still doing its work.

     Measured h1: 4.90 / 4.47 / 5.24 / 5.25 / 4.22 at 360 / 390 / 430 / 768 / 1024. */
  .c-hero__scrim {
    background:
      linear-gradient(
        to top,
        rgb(14 19 48 / 0.80) 0%,
        rgb(14 19 48 / 0.62) 45%,
        rgb(14 19 48 / 0.30) 100%
      );
  }

  /* The eyebrow sits higher up the frame than the headline, which is exactly
     where a bottom-weighted scrim is weakest — 3.83:1 at 390px on the values
     above. Rather than darken the whole top of the picture for one small label,
     the chip goes fully opaque here.

     Translucency was tried first and does not get there: 0.70 reached only
     4.36:1 against a 4.5 requirement, because the sunlit wall behind it on a
     phone is genuinely bright. A solid ground removes the dependency on the
     photograph altogether and measures 6.9:1. The glass treatment is kept for
     desktop, where the pixels behind it are dark enough to afford it. */
  .c-hero__eyebrow {
    background-color: var(--c-ink);
    backdrop-filter: none;
  }
}

/* Scroll cue */
.c-hero__cue {
  position: absolute;
  inset-block-end: var(--sp-5);
  inset-inline-end: var(--gutter);
  display: none;
  align-items: center;
  gap: var(--sp-2);
  font-family: var(--ff-utility);
  font-size: var(--fs-3xs);
  letter-spacing: var(--tr-wide);
  text-transform: uppercase;
  color: var(--c-text-muted-inv);
  text-decoration: none;
}

@media (min-width: 768px) {
  .c-hero__cue { display: inline-flex; }
}

/* From 1280px the donate card occupies the bottom-right corner, and the cue was
   sitting underneath it — measured, the two boxes overlapped at 1280, 1440 and
   1600. (They overlapped before the card was nudged up, too, by 48px at 1440;
   the nudge halved it rather than caused it.) The gap between the headline column
   and the card is empty at every width in this range, so the cue moves there. */
@media (min-width: 1280px) {
  .c-hero__cue {
    inset-inline-end: auto;
    inset-inline-start: 50%;
    translate: -50% 0;
  }
}

.c-hero__cue-line {
  display: block;
  width: 1px;
  height: 40px;
  background: linear-gradient(to bottom, currentColor, transparent);
  animation: cue-slide 2.4s var(--e-in-out) infinite;
}

@keyframes cue-slide {
  0%, 100% { transform: scaleY(0.4); transform-origin: top; opacity: 0.5; }
  50%      { transform: scaleY(1);   transform-origin: top; opacity: 1; }
}

@media (prefers-reduced-motion: reduce) {
  .c-hero__cue-line { animation: none; }
}

/* --------------------------------------------------------------------------
   INNER-PAGE HERO — compact, no image scrim gymnastics
   -------------------------------------------------------------------------- */

.c-pagehead {
  background-color: var(--c-ink);
  color: var(--c-text-inv);
  padding-block: var(--sp-8) var(--sp-8);
  position: relative;
  overflow: hidden;
}

.c-pagehead__motif {
  position: absolute;
  inset-block-start: -30%;
  inset-inline-end: -8%;
  width: 46rem;
  max-width: 70%;
  opacity: var(--motif-opacity);
  pointer-events: none;
  color: var(--c-verdigris-lt);
}

.c-pagehead__inner {
  position: relative;
  width: 100%;
  max-width: var(--w-wide);
  margin-inline: auto;
  padding-inline: var(--gutter);
  display: grid;
  gap: var(--sp-4);
}

.c-pagehead__title {
  font-family: var(--ff-display);
  font-size: var(--fs-4xl);
  color: var(--c-text-inv);
  max-width: 20ch;
}

.c-pagehead__lede {
  font-size: var(--fs-lg);
  color: var(--c-text-muted-inv);
  max-width: var(--measure);
}

/* --------------------------------------------------------------------------
   PAGEHEAD — MEDIA VARIANT
   --------------------------------------------------------------------------
   `.c-pagehead--media` backs the masthead with a photograph instead of flat
   ink. The plain variant is right for policy and account pages, where a
   picture would be decoration; it is wrong for a landing page like /appeals/,
   where the flat navy band was the least considered thing on the screen.

   Structure it expects:
     <section class="c-pagehead c-pagehead--media">
       <div class="c-pagehead__media"><picture>…</picture></div>
       <div class="c-pagehead__scrim"></div>
       <svg class="c-pagehead__motif">…</svg>
       <div class="c-pagehead__inner">…</div>
     </section>
   -------------------------------------------------------------------------- */

.c-pagehead--media {
  /* Taller than the flat variant — a masthead carrying an image needs room to
     read as one. */
  min-height: clamp(18rem, 30vw, 26rem);
  display: grid;
  align-items: end;
  /* The block sits off the bottom edge rather than against it. The bottom
     padding was --sp-7 (48px), which read as the type resting on the floor of
     the masthead; lifting it clear gives the title somewhere to sit and lets the
     photograph run underneath it.

     This is not free: the scrim is bottom-weighted, so raising the type moves it
     onto a LIGHTER part of the gradient. Re-measured after the change across all
     five Zakat pages and the Appeals masthead — see the scrim note below. */
  padding-block: var(--sp-9) clamp(3rem, 6vw, 7rem);
  isolation: isolate;
}

/* --------------------------------------------------------------------------
   FULL-IMAGE MASTHEAD — no crop
   --------------------------------------------------------------------------
   Matching the box to the cover photograph's own 16:9 ratio leaves object-fit:
   cover nothing to cut, so the whole frame is visible. Panning cannot achieve
   this — object-position only picks which part to lose.

   Only from 1024px up. The ratio fixes the height at width ÷ 1.778, and on a
   390px phone that is a 219px strip — far too short to hold the eyebrow, a
   68px title and a two-line lede. Below this breakpoint min-height wins and the
   image crops, which is unavoidable at that shape.

   max-height guards very wide screens, where 16:9 would otherwise push the
   masthead past the viewport and bury the page's actual content — on the
   calculator page that would mean burying the calculator.
   -------------------------------------------------------------------------- */

@media (min-width: 1024px) {
  .c-pagehead--media {
    aspect-ratio: 16 / 9;
    min-height: 0;
    /* There is an unavoidable conflict here, so it is worth naming. The cover is
       16:9. A full-bleed 16:9 image on a 16:9 MONITOR needs the entire viewport
       height — so "show the whole image" and "show any page content" cannot both
       be true on such a screen. The cap is the dial between them:

         78svh  →  13–22% cropped, content clearly above the fold
         92svh  →  0% cropped on 16:10 laptops (1440×900, 1280×800),
                   8% on true 16:9 (1366×768, 1920×1080, 2560×1440)
         none   →  0% cropped everywhere, masthead fills the whole first screen

       92svh is the compromise: nothing cropped on the commonest laptop shapes,
       a token 8% on 16:9 desktops, and the page below still announces itself. */
    max-height: 92svh;
    /* width: 100% is load-bearing, not tidiness. With aspect-ratio set, a
       max-height that actually bites makes the browser derive the WIDTH from the
       clamped height instead of cropping — the masthead shrank to 1248px on a
       1440px screen and left a bare strip of page background down the right.
       Pinning the width forces the clamp to spend itself on height, which is
       what "crop past this point" is supposed to mean. */
    width: 100%;
  }
}

.c-pagehead__media {
  position: absolute;
  inset: 0;
  z-index: -2;
}

/* Same trap as the home hero: `height: 100%` on the img resolves against the
   <picture>, which is auto-height unless told otherwise. */
.c-pagehead__media picture {
  display: block;
  width: 100%;
  height: 100%;
}

.c-pagehead__media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* Per-page crop. The whole Zakat section shares one cover photograph, and
     each page sets --pagehead-pos to frame a different part of it — so moving
     through the section does not feel like the same picture five times, without
     needing five photographs. Falls back to the sensible centre crop. */
  object-position: var(--pagehead-pos, center 42%);
}

/* Bottom-weighted, because the type sits at the bottom of this masthead.
   Stops measured against the composited page across all five Zakat pages and
   their five different crops, at 1024 / 1440 / 1920 — see the note on
   .c-hero__scrim for why a flat-scrim calculation cannot answer this.

   Roughly half the weight it started at. Two things paid for that: the cover
   photograph is genuinely dark where the type falls (lower-left third measures
   mean luminance 6.2), and the lede gave up its muted tone. Sweeping the
   candidates gave tightest headroom above floor:

       0.90   +1.12      0.58   +1.31
       0.70   +1.23      0.46   +1.32   ← here
                         0.34   +0.67

   0.46 is both the brightest sensible value AND the one with the most headroom,
   which is not a coincidence: past that point the title becomes the binding
   constraint instead of the eyebrow and the margin collapses again.

   BUT the value cannot be global. This component now backs two very different
   photographs: the Zakat cover (lower-left mean luminance 6) and the Appeals
   masthead (volunteers at mean 89, nearly fifteen times brighter). Dropping to
   0.46 for Zakat took the Appeals title to 2.59:1 — a straight failure. So the
   default below stays heavy enough for a bright image, and a genuinely dark
   cover opts into the lighter veil explicitly. */
.c-pagehead__scrim {
  position: absolute;
  inset: 0;
  z-index: -1;
  background:
    linear-gradient(
      to top,
      rgb(14 19 48 / 0.90) 0%,
      rgb(14 19 48 / 0.78) 38%,
      rgb(14 19 48 / 0.52) 72%,
      rgb(14 19 48 / 0.38) 100%
    );
}

/* THREE veil tiers, because the mastheads now carry photographs of very
   different brightness and one value cannot serve them:

     default (0.90)  bright images — the Appeals volunteers shot (type-zone mean
                     luminance 89) needs this much or the title drops to 2.59:1
     --veil-mid      mid-tone images — the /where-we-work/ aerial (mean 80, but
                     with a bright sky); measured headroom above floor:
                       light 0.48  +0.68     mid 0.66  +2.91     default  +8.06
     --veil-light    genuinely dark covers — the Zakat still-life (mean 6)

   Set it from the page data (`veil: 'light' | 'mid'`) and re-measure whenever
   the image behind it changes. This is the setting that fails silently. */
.c-pagehead--veil-mid .c-pagehead__scrim {
  /* Brightened from 0.66 to 0.56. The binding constraint is the LEDE, not the
     title and not the eyebrow — the eyebrow carries its own solid ink fill and
     the title is large text on a 3:1 floor, so neither responds much to the
     veil. Sweeping the Fundraise section (five pages × four widths), tightest
     headroom above floor on title + lede:

       0.66  +3.09      0.56  +1.44   ← here
       0.60  +2.02      0.52  +0.85      0.48  +0.39      0.38  fails

     Then the lede was stepped up past 24px (see .c-pagehead--media
     .c-pagehead__lede), which moved the binding constraint from the lede to the
     TITLE at 1024px and allowed a further brightening. Re-swept on that basis:

       0.56  +1.27      0.50  +0.66   ← here
       0.54  +1.04      0.48  +0.46
       0.52  +0.82      0.46  +0.26      0.38  fails

     Then the measuring method itself turned out to be the real constraint. All
     of the figures above sampled each element's BOUNDING BOX, and a short 96px
     title sits in a 20ch box — so most of what was being measured was empty
     photograph where no glyph falls. Re-measured with a glyph mask (capture the
     region twice, once with the text transparent; the pixels that changed are
     the ink; evaluate the background only there):

       veil    box-sampled     glyph-masked
       0.50    +0.66           +2.10
       0.38    fails           +0.81
       0.28    fails           +0.06
       0.20    fails           fails

     0.36 sits just above the 0.38 measurement with a comfortable margin, and is
     40% of the weight this component started at. The binding element is the lede
     at 1920px, where the crop pulls the brightest part of the frame under it.

     If you tune this again, measure with the glyph mask. Bounding-box sampling
     understates contrast badly wherever a box is wider than its text, which is
     true of every short title on this site. Worth noting for future tuning: an earlier sweep appeared
     to show 0.66 as the brightest passing value, with the eyebrow as the
     constraint at exactly 4.50 — that was an artefact of measuring a pill-shaped
     chip with a rectangular sample, which includes area outside the shape. The
     eyebrow cannot be measured that way and does not constrain this. */
  background:
    linear-gradient(
      to top,
      rgb(14 19 48 / 0.36) 0%,
      rgb(14 19 48 / 0.29) 58%,
      rgb(14 19 48 / 0.14) 85%,
      rgb(14 19 48 / 0.04) 100%
    );
}

/* Opt-in for covers that are already dark where the type falls. Set it from the
   page data (`veil: 'light'`), never by default — and re-measure if the image
   behind it changes, because this is the setting that will silently fail. */
/* Stops pushed UP the frame rather than made darker. Lifting the type off the
   bottom edge moved it onto a lighter part of the old gradient and took the
   /zakat/ title at 1024px down to 3.26:1 — passing, but only +0.26 above floor.
   Extending the dark band higher follows the type instead of dimming the whole
   picture to catch it:

     stops 38/72   tightest +0.26   (title @1024)
     stops 50/80   tightest +0.79
     stops 58/85   tightest +0.99   ← here, and the binding element moves back
                                      to the eyebrow, which has its own ground

   Peak opacity barely moves (0.46 → 0.48), so the photograph is unchanged where
   you actually look at it. */
.c-pagehead--veil-light .c-pagehead__scrim {
  background:
    linear-gradient(
      to top,
      rgb(14 19 48 / 0.48) 0%,
      rgb(14 19 48 / 0.42) 58%,
      rgb(14 19 48 / 0.20) 85%,
      rgb(14 19 48 / 0.06) 100%
    );
}

/* The motif reads as a watermark over ink; over a photograph it turns to mud.
   Pulled back rather than removed, so the girih language survives. */
.c-pagehead--media .c-pagehead__motif { opacity: calc(var(--motif-opacity) * 0.45); }

.c-pagehead--media .c-pagehead__inner { gap: var(--sp-3); }

.c-pagehead--media .c-pagehead__title { font-size: var(--fs-5xl); }

/* Phones need their own, much heavier scrim — and finding out why is worth
   recording. Below 1024px the masthead cannot take the image's 16:9 ratio (it
   would be a 219px strip), so it crops ~45% horizontally and shows the BRIGHT
   CENTRE of the cover instead of the dark lower-left the desktop scrim was
   measured against. The 0.46 desktop value dropped the title to 1.7:1 there.

   Swept the same way: 0.74 gives +0.42 headroom, 0.82 gives +0.94, 0.88 gives
   +0.95. Taking 0.82 — past it the gain flattens and only the picture suffers.

   Worth stating plainly: my first sweep tested 1024/1440/1920 only and missed
   this outright. A scrim has to be measured at the widths where the CROP
   changes, not just where the layout does.

   Note the selector below: it has to name --veil-light as well.
   `.c-pagehead--veil-light .c-pagehead__scrim` is a two-class selector and
   outranks a bare `.c-pagehead__scrim` whatever the source order, so an
   override that does not mention the variant simply loses to it. */
@media (max-width: 1023px) {
  .c-pagehead__scrim,
  .c-pagehead--veil-mid .c-pagehead__scrim,
  .c-pagehead--veil-light .c-pagehead__scrim {
    background:
      linear-gradient(
        to top,
        rgb(14 19 48 / 0.82) 0%,
        rgb(14 19 48 / 0.70) 45%,
        rgb(14 19 48 / 0.44) 100%
      );
  }
}

/* The lede is full white over a photograph, not the muted tone used on the flat
   ink variant. It is the element that sets how heavy the scrim has to be:
   measured across all five Zakat pages, the muted tone was the tightest
   constraint everywhere, and it capped the scrim at 0.70 before the lede fell
   under 4.5:1. Un-muting it let the scrim come down to 0.46 — the difference
   between a photograph you can see and one washed flat behind a navy veil.

   A tonal nicety is not worth that much of the picture. */
.c-pagehead--media .c-pagehead__lede {
  color: var(--c-text-inv);
  /* Stepped up from --fs-lg (18→21px) to --fs-xl (22→28px). This is what makes a
     brighter photograph possible, not just a nicer lede.

     The lede was the single constraint on how light the veil could go: at 18–21px
     it is "small text" and needs 4.5:1, while the title next to it is large text
     on a 3:1 floor and the eyebrow carries its own solid ground. Past 24px the
     lede becomes large text too, and its floor drops to 3:1 — which is worth far
     more brightness than the two or three points of opacity it costs.

     Note the floor only drops where the clamp actually reaches 24px, so the
     narrow-viewport scrim below still has to hold the 4.5:1 case. */
  font-size: var(--fs-xl);
  line-height: var(--lh-snug);
  /* A tighter measure than the flat variant's --measure. Two reasons, and the
     second is the one that matters: 46ch is simply a better reading measure for
     a lede, and — measured — the text block was spanning 4–96% of the masthead
     width at 1024px because this line wrapped so wide. That left no part of the
     frame the scrim could leave alone. Narrowing the text lets the dark weight
     sit left-of-centre instead of across the whole width. */
  max-width: var(--measure-tight);
}

/* The eyebrow is verdigris on ink elsewhere; over a photograph it needs its own
   ground to clear 4.5:1, exactly as the hero's does. */
.c-pagehead__eyebrow {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  align-self: start;
  justify-self: start;
  font-family: var(--ff-utility);
  font-size: var(--fs-3xs);
  font-weight: var(--fw-medium);
  letter-spacing: var(--tr-wide);
  text-transform: uppercase;
  color: var(--c-verdigris-lt);
  padding: var(--sp-2) var(--sp-3);
  border: 1px solid rgb(127 227 214 / 0.35);
  border-radius: var(--r-full);
  /* SOLID, not translucent. This chip has now failed its 4.5:1 requirement
     three separate times as different photographs went behind it — 4.36 on the
     Zakat cover at 390px, then 3.84 / 4.03 / 4.41 on the brighter aerial used by
     /where-we-work/. Each time the fix was to push the alpha up for that one
     case. A solid ground ends the pattern: verdigris on ink measures ~13:1 and
     cannot be affected by whatever image sits behind it.

     The glass effect was a nicety on a 100px badge. Not worth re-measuring
     every time someone changes a masthead photograph. */
  background-color: var(--c-ink);
}

/* --------------------------------------------------------------------------
   EYEBROW — used on ~20 section headers
   -------------------------------------------------------------------------- */

.c-eyebrow {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  font-family: var(--ff-utility);
  font-size: var(--fs-3xs);
  font-weight: var(--fw-medium);
  letter-spacing: var(--tr-wide);
  text-transform: uppercase;
  color: var(--c-verdigris-ink);
}

.c-eyebrow::before {
  content: "";
  width: 22px;
  height: 1px;
  background-color: currentColor;
  flex: none;
}

.is-inverted .c-eyebrow { color: var(--c-verdigris-lt); }

/* --------------------------------------------------------------------------
   URGENT RIBBON — dismissible, state in localStorage
   -------------------------------------------------------------------------- */

.c-ribbon {
  background-color: var(--c-indigo);
  color: var(--c-text-inv);
}

.c-ribbon[hidden] { display: none; }

.c-ribbon__inner {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-3) var(--sp-4);
  width: 100%;
  max-width: var(--w-wide);
  margin-inline: auto;
  padding: var(--sp-3) var(--gutter);
}

.c-ribbon__tag {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  font-family: var(--ff-utility);
  font-size: var(--fs-3xs);
  letter-spacing: var(--tr-wide);
  text-transform: uppercase;
  color: var(--c-ink);
  background-color: var(--c-marigold);
  padding: 0.2rem var(--sp-2);
  border-radius: var(--r-sm);
  flex: none;
}

.c-ribbon__text {
  flex: 1 1 16rem;
  font-size: var(--fs-sm);
  line-height: 1.4;
}

.c-ribbon__close {
  display: grid;
  place-items: center;
  width: 36px;
  height: 36px;
  border-radius: var(--r-full);
  color: var(--c-text-muted-inv);
  flex: none;
}

.c-ribbon__close:hover { color: var(--c-text-inv); background-color: rgb(255 255 255 / 0.1); }
.c-ribbon__close svg { width: 16px; height: 16px; }

/* ==========================================================================
   ERROR PAGE — 404 / 500
   --------------------------------------------------------------------------
   The 404 image was commissioned with its subject in the right third and the
   left two thirds deliberately empty, so the message sits in the space the
   photograph was composed to leave. Same logic as the hero.

   Scrim opacity is MEASURED, not guessed: the brightest pixel in the text zone
   is rgb(194,155,145), and white text over ink at 0.55 gives 7.53:1 — AAA.
   The previous treatment rendered the image at 0.18 opacity behind a flat ink
   ground, which wasted it entirely.
   ========================================================================== */

.c-errorpage {
  position: relative;
  min-height: min(82svh, 46rem);
  display: grid;
  align-items: center;
  isolation: isolate;
  background-color: var(--c-ink);
  color: var(--c-text-inv);
  overflow: hidden;
}

.c-errorpage__media { position: absolute; inset: 0; z-index: -2; }

.c-errorpage__media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center 60%;
}

.c-errorpage__scrim {
  position: absolute;
  inset: 0;
  z-index: -1;
  background:
    linear-gradient(
      100deg,
      rgb(14 19 48 / 0.72) 0%,
      rgb(14 19 48 / 0.55) 42%,
      rgb(14 19 48 / 0.22) 70%,
      rgb(14 19 48 / 0.10) 100%
    );
}

.c-errorpage__inner {
  position: relative;
  width: 100%;
  max-width: var(--w-wide);
  margin-inline: auto;
  padding: var(--sp-9) var(--gutter);
}

.c-errorpage__content {
  display: grid;
  gap: var(--sp-5);
  justify-items: start;
  max-width: 34rem;
}

.c-errorpage__title {
  font-family: var(--ff-display);
  font-size: var(--fs-4xl);
  font-weight: var(--fw-bold);
  line-height: var(--lh-tight);
  letter-spacing: var(--tr-display);
  color: var(--c-text-inv);
}

.c-errorpage__lede {
  font-size: var(--fs-lg);
  line-height: var(--lh-normal);
  color: var(--c-text-muted-inv);
  max-width: var(--measure-tight);
}

/* 500 has no photograph — the ink ground carries it. */
.c-errorpage--plain { min-height: min(60svh, 34rem); }
