/* ===========================================================================
 * giving-day.css — visual layer for the Giving Day module.
 *
 * Loaded only by giving pages (see themes/default/giving/page.html and the
 * giving_day module's own templates), never the global shell. Everything is
 * scoped under `.gv-page` so it cannot leak into other public pages or themes.
 *
 * feat-084: rewritten to use the design system's actual component recipes
 * (colors.css / spacing.css / typography.css, vendored under
 * themes/static/themes/design-system/ by feat-083) rather than bespoke
 * pre-design-system values. Structural things (shadows, borders, muted text)
 * now use the design system's fixed neutrals; only genuine brand/accent
 * moments (buttons, badges, the hero, decorative glyphs) use the brand
 * colors. Everything still derives from --brand-primary/--brand-secondary
 * (branding/css.py, per-tenant) and --hubbub-heading-font/--bs-body-font-family
 * (also per-tenant) — dashboard customisation is unaffected; only the fixed
 * shape/neutral layer changed.
 * ------------------------------------------------------------------------- */

.gv-page {
  /* A tiny warm-neutral wash, not the tenant's raw background colour: cards
     (--surface-card, always a fixed white) need to read as lifted off the
     page even when the tenant's own background_colour is plain white too —
     otherwise card and page are the same white and the card shadow is the
     only separation. Derived from the tenant colour, not a fixed value, so a
     tenant with a non-white background still sees their own colour, just
     very slightly deepened. */
  background: color-mix(in oklab, var(--bs-body-bg, #fff), #71717a 4%);
}

/* ---- Button flourishes (colours come from public.css .btn-brand) -------- */
.gv-page .btn-brand,
.gv-page .btn-ghost-brand {
  transition: transform var(--dur-fast) var(--ease-standard), box-shadow var(--dur-fast) var(--ease-standard);
}
.gv-page .btn-brand:hover,
.gv-page .btn-ghost-brand:hover { transform: translateY(-2px); }

/* ---- Progress bars — the design system's ProgressMeter, value for value:
   14px track (20px at --lg), gradient fill with an 8px floor and the
   brand-edge inset, and the "onbrand" tone used on the hero's dark band. --- */
.gv-page .progress { --bs-progress-height: 14px; }
.gv-page .progress-bar {
  width: 0;
  min-width: 8px;
  border-radius: var(--radius-pill);
  background: linear-gradient(90deg, var(--color-primary), color-mix(in oklab, var(--color-primary), var(--color-secondary) 40%));
  box-shadow: inset 0 0 0 1px var(--fill-edge-primary);
  transition: width var(--dur-slow) var(--ease-out);
}
.gv-page .progress-bar.is-filled { width: var(--to, 0); }
.gv-page .progress--lg { --bs-progress-height: 20px; }
.gv-page .progress--onbrand { --bs-progress-bg: color-mix(in oklab, #fff, transparent 76%); }

/* The design system's ProgressMeter head, in its lg + onbrand form (the hero's). */
.gv-meter__head { display: flex; align-items: baseline; justify-content: space-between; gap: 1rem; margin-bottom: .5rem; }
.gv-meter__raised { font-family: var(--font-heading); font-weight: var(--weight-bold); font-size: var(--text-2xl); line-height: 1; color: #fff; }
.gv-meter__goal { font-size: var(--text-sm); color: color-mix(in oklab, #fff, transparent 28%); }
.gv-meter__pct { font-family: var(--font-heading); font-weight: var(--weight-bold); font-size: var(--text-lg); color: #fff; }

/* The design system's IconButton, "outline" variant — a 40px circle, not a
   text-label pill. Used for share links wherever they appear (project page,
   the homepage's "Spread the word" section). */
.gv-share-btn {
  display: inline-flex; align-items: center; justify-content: center;
  width: 40px; height: 40px; border-radius: var(--radius-pill);
  border: 1.5px solid var(--border-strong); background: transparent; color: var(--text-body);
  font-size: 1.1rem; line-height: 0; cursor: pointer;
  transition: border-color var(--dur-fast) var(--ease-standard), color var(--dur-fast) var(--ease-standard);
}
.gv-share-btn:hover, .gv-share-btn:focus-visible {
  border-color: var(--color-primary); color: var(--color-primary);
}

/* ---- Badges — the design system's Badge shape, in the tenant's accent (challenge status) or
   success ("goal met"). --------------------------------------------------------------------- */
.badge.gv-chip {
  padding: .35em .7em;
  font-size: var(--text-xs);
}
.badge.gv-chip--accent {
  background: var(--color-accent); color: var(--on-accent);
  box-shadow: inset 0 0 0 1px var(--fill-edge-accent);
}
.badge.gv-chip--done { background: var(--success-surface); color: var(--success); }

/* ---- Match funding — the design system's MatchFundBanner (themes/components/match_banner.html),
   value for value: the secondary colour, so it reads as a highlight distinct from the primary
   progress meter. -------------------------------------------------------------------------------- */
.gv-match {
  display: flex; align-items: center; gap: .9rem;
  padding: .85rem 1.1rem; border-radius: var(--radius-lg);
  background: var(--color-secondary-subtle); border: 1.5px solid var(--border-brand);
  color: var(--text-body);
}
.gv-match__badge {
  flex: 0 0 auto; display: inline-flex; align-items: center; gap: .4rem;
  background: var(--color-secondary); color: var(--on-secondary); box-shadow: inset 0 0 0 1px var(--fill-edge-secondary);
  font-family: var(--font-heading); font-weight: var(--weight-bold); font-size: var(--text-sm);
  padding: .3rem .7rem; border-radius: var(--radius-pill); white-space: nowrap;
}
.gv-match__body { flex: 1; min-width: 0; line-height: 1.4; }
.gv-match__title { margin: 0; font-weight: var(--weight-semibold); color: var(--text-strong); }
.gv-match__sub { margin: 0; font-size: var(--text-sm); color: var(--text-muted); }
.gv-match__meter {
  margin-top: .55rem; height: 8px; border-radius: var(--radius-pill); overflow: hidden;
  background: color-mix(in oklab, var(--color-secondary), transparent 78%);
}
.gv-match__fill {
  height: 100%; border-radius: var(--radius-pill);
  background: var(--color-secondary); box-shadow: inset 0 0 0 1px var(--fill-edge-secondary);
}
.gv-match--banner { flex-direction: column; align-items: flex-start; }

/* ---- Section utilities ------------------------------------------------- */
.gv-page .section { padding-block: var(--space-8); }
/* Cooler, more neutral grey than the design system's own warm --surface-subtle
   (per explicit preference) — mixed against the tenant's own page background,
   same reasoning as .gv-page's wash above. */
.gv-page .section--tint,
.gv-page--alternate > section:nth-of-type(even) { background: color-mix(in oklab, var(--bs-body-bg, #fff), #71717a 8%); border-block: 1px solid var(--border); }
/* The design system's homepage tells its sections apart by alternating plain and tinted bands. By
   position rather than per fragment, so the rhythm holds whichever optional sections render. */
.gv-page--alternate > section:nth-of-type(odd) { background: none; border-block: 0; }
.gv-page .text-ink-soft { color: var(--text-muted) !important; }
.gv-page .text-brand { color: var(--color-primary) !important; }
.gv-page .font-display { font-family: var(--font-heading) !important; }

.gv-page .gv-section-title {
  font-size: clamp(var(--text-2xl), 4.5vw, var(--text-3xl));
  color: var(--text-strong);
}

/* ---- Hero --------------------------------------------------------------
   The design system sanctions a full-bleed photo + dark scrim only for the
   flagship giving-day homepage (readme: "VISUAL FOUNDATIONS"). The scrim is
   a fixed dark neutral, not brand-tinted, so it reads legibly over any
   photo regardless of the tenant's brand hue; the no-photo fallback keeps a
   brand gradient since there's no image to protect text against. -------- */
.gv-hero {
  position: relative; isolation: isolate; overflow: hidden; color: #fff; text-align: left;
  padding-block: clamp(3.5rem, 9vw, 6.5rem) clamp(3rem, 7vw, 5rem);
  background:
    linear-gradient(180deg,
      color-mix(in oklab, var(--neutral-900) 55%, transparent),
      color-mix(in oklab, var(--neutral-900) 78%, transparent)),
    var(--hero-img, linear-gradient(135deg, var(--color-primary), var(--primary-700)));
  background-size: cover; background-position: center;
}
.gv-hero::after {
  content: ""; position: absolute; inset: 0; z-index: -1;
  background: radial-gradient(ellipse at 50% 0%, transparent 30%, color-mix(in oklab, var(--neutral-900) 45%, transparent) 100%);
  mix-blend-mode: multiply;
}
.gv-badge-live {
  display: inline-flex; align-items: center; gap: .4em;
  background: var(--danger); color: #fff;
  font-size: var(--text-xs); font-weight: var(--weight-semibold);
  letter-spacing: var(--tracking-wide); text-transform: uppercase;
  padding: .35em .7em; border-radius: var(--radius-pill);
}
.gv-badge-live__dot {
  width: 7px; height: 7px; border-radius: var(--radius-pill); background: #fff;
  animation: gv-pulse 1.4s var(--ease-standard) infinite;
}
@keyframes gv-pulse { 0%, 100% { opacity: 1; transform: scale(1); } 50% { opacity: .35; transform: scale(.7); } }

.gv-hero__eyebrow {
  text-transform: uppercase; letter-spacing: var(--tracking-wide); font-size: var(--text-sm);
  font-weight: var(--weight-bold); opacity: .9;
  display: inline-block; padding-bottom: var(--space-3);
  border-bottom: 1px solid color-mix(in oklab, #fff, transparent 65%);
}
.gv-hero__title {
  /* base.css sets a direct `color` on every h1-h6, which wins over the
     inherited white from .gv-hero's `color:#fff` — a direct declaration on
     the element always beats an inherited one, regardless of selector
     specificity. Must be set explicitly here, not assumed from the parent. */
  font-family: var(--font-heading); font-size: clamp(2.2rem, 4.5vw, 3.4rem); font-weight: var(--weight-bold);
  text-shadow: 0 2px 30px rgba(0,0,0,.25);
  color: #fff;
}
.gv-hero__lead {
  font-size: var(--text-md); color: rgba(255,255,255,.92); max-width: 46ch;
  text-shadow: 0 1px 8px rgba(0,0,0,.3);
}
.gv-hero__media {
  border-radius: var(--radius-xl); overflow: hidden; box-shadow: var(--shadow-xl); aspect-ratio: 16/9;
}
.gv-hero__media img { width: 100%; height: 100%; object-fit: cover; display: block; }

/* ---- Stat figures — the design system's StatBlock, "lg". Always white, not --on-primary: they sit on
   the hero's dark scrim, which stays dark whatever the brand colour, and --on-primary turns black for a
   light primary. --------------------------------------------------------- */
.gv-figure__value {
  font-family: var(--font-heading); font-size: var(--text-3xl);
  font-weight: var(--weight-bold); line-height: 1; letter-spacing: var(--tracking-tight);
  color: #fff;
}
.gv-figure__label {
  font-size: var(--text-sm); font-weight: var(--weight-semibold); letter-spacing: var(--tracking-wide);
  text-transform: uppercase; color: rgba(255,255,255,.75);
}

/* The hero's figures on one line while they fit the column, else 2×2 — never 3 + 1. They wrap in
   pairs, so the only break left is between the pairs. */
.gv-hero__figures { display: flex; flex-wrap: wrap; gap: var(--space-5) 3rem; }
.gv-hero__pair { display: flex; gap: 3rem; }

/* The results page's headline figure — the day's final total, set larger than any live figure. */
.gv-results__raised { font-size: clamp(3.5rem, 10vw, 6.5rem); }

/* The pause switch, drawn only in white and the hero's dark scrim — the pair the hero's text already
   relies on. Bootstrap's draws its checked fill in the primary, which may be near the scrim, and its
   knob is an SVG that cannot read a custom property, so the knob here is a gradient sized to
   Bootstrap's (75% of the height) that its background-position rules still slide. */
.gv-hero .form-switch .form-check-input {
  --gv-switch-knob: #fff;
  background-color: transparent;
  border-color: #fff;
  background-image: radial-gradient(circle closest-side, var(--gv-switch-knob) 75%, transparent calc(75% + 1px));
  background-size: 50% 100%;
}
.gv-hero .form-switch .form-check-input:checked {
  --gv-switch-knob: var(--neutral-900);
  background-color: #fff;
}
.gv-hero .form-switch .form-check-input:focus { border-color: #fff; box-shadow: none; }
.gv-hero .form-switch .form-check-input:focus-visible { outline: 2px solid #fff; outline-offset: 3px; }

/* ---- Countdown — the design system's CountdownTimer, "onbrand" tone: a
   translucent dark tile (the number only — the label sits outside it, as its
   own line below), with a light hairline, no blur, no shadow. ------------ */
.gv-countdown__label {
  text-transform: uppercase; letter-spacing: var(--tracking-wide); font-size: var(--text-sm);
  font-weight: var(--weight-bold); color: color-mix(in oklab, #fff, transparent 10%);
}
/* Four equal tile columns filling the progress bar's width, so the tiles' outer edges line up with the
   bar whatever the tenant's font or screen width. The DS sizes tiles by content (min-width 2.6em), which
   comes out wider than the bar. */
.gv-countdown__row {
  display: grid; grid-template-columns: repeat(3, minmax(0, 1fr) auto) minmax(0, 1fr);
  align-items: start; gap: .6rem;
}
/* The DS's 440px cap only beside the image; stacked below lg, the image runs full width and so do these. */
@media (min-width: 992px) {
  .gv-countdown__row, .gv-meter, .gv-hero__figures { max-width: 440px; }
}
.gv-countdown__unit { display: flex; flex-direction: column; align-items: center; gap: .35rem; }
.gv-cd {
  display: block; width: 100%; padding: .4em .3em; text-align: center;
  background: color-mix(in oklab, #000, transparent 78%);
  border: 1px solid color-mix(in oklab, #fff, transparent 78%);
  border-radius: var(--radius-md);
  font-family: var(--font-heading); font-size: var(--text-2xl); font-weight: var(--weight-bold);
  line-height: 1; font-variant-numeric: tabular-nums; color: #fff;
}
.gv-countdown__unit small {
  text-transform: uppercase; letter-spacing: var(--tracking-wide);
  font-size: var(--text-xs); font-weight: var(--weight-semibold);
  color: color-mix(in oklab, #fff, transparent 25%);
}
.gv-countdown__sep { font-size: var(--text-2xl); font-weight: var(--weight-bold); color: color-mix(in oklab, #fff, transparent 55%); line-height: 1; padding-top: .35em; }
/* Phones: one step down so the numbers sit comfortably in ~50-60px tiles. */
@media (max-width: 575.98px) {
  .gv-cd, .gv-countdown__sep { font-size: var(--text-xl); }
  .gv-countdown__row { gap: .35rem; }
}

/* ---- Cards: causes / challenges / voices / features — the design
   system's NonprofitCard recipe (radius-lg, shadow-sm resting, shadow-lg +
   -4px lift on hover, 1.04 image zoom). ----------------------------------- */
.gv-card {
  --bs-card-inner-border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm);
  transition: transform var(--dur-base) var(--ease-out), box-shadow var(--dur-base) var(--ease-out);
}
.gv-card:hover { transform: translateY(-4px); box-shadow: var(--shadow-lg); }

.gv-cause__media { position: relative; aspect-ratio: 16/10; overflow: hidden; background: var(--surface-subtle); }
.gv-cause__media img { width: 100%; height: 100%; object-fit: cover; transition: transform var(--dur-slow) var(--ease-out); }
.gv-card:hover .gv-cause__media img { transform: scale(1.04); }
.gv-cause__flag {
  position: absolute; top: var(--space-3); left: var(--space-3);
  background: var(--color-secondary); color: var(--on-secondary);
  font-weight: var(--weight-bold); font-size: var(--text-xs); letter-spacing: var(--tracking-wide);
  text-transform: uppercase; padding: .35rem .7rem; border-radius: var(--radius-pill);
}
.gv-cause__title { color: var(--text-strong); }
/* Card titles double as links (click-through to the project page, not just the
   Donate button) — the design system's own NonprofitCard title isn't a link at
   all, so without this override base.css's global `a` colour (--text-link)
   wins over the neutral heading colour intended here. */
.gv-cause__title a { color: inherit; text-decoration: none; }
.gv-cause__title a:hover { text-decoration: underline; }
.gv-fig-amount { font-family: var(--font-heading); font-size: var(--text-lg); font-weight: var(--weight-bold); color: var(--text-strong); }
.gv-fig-pct { font-family: var(--font-heading); font-weight: var(--weight-bold); font-size: var(--text-lg); color: var(--color-primary); white-space: nowrap; }

/* ---- Leaderboards — the design system's LeaderboardRow (feat-063, feat-084). The podium's rank is a
   filled secondary disc; every other rank is a muted numeral. The rank is written either way, so
   the disc is never the only signal. ------------------------------------------------------------- */
.gv-board-card { padding: var(--space-5); }
.gv-board { width: 100%; border-collapse: collapse; }
.gv-board td, .gv-board th { padding-block: var(--space-2); vertical-align: middle; }
.gv-board__place { width: 2.75rem; }
.gv-board__name { font-weight: var(--weight-semibold); color: var(--text-strong); padding-inline: var(--space-3); }
.gv-board__name a { color: inherit; text-decoration: none; }
.gv-board__name a:hover { text-decoration: underline; }
.gv-board__figure {
  text-align: right; white-space: nowrap;
  font-family: var(--font-heading); font-weight: var(--weight-bold); font-size: var(--text-md); color: var(--text-strong);
}
.gv-rank {
  display: inline-flex; align-items: center; justify-content: center;
  width: 2rem; height: 2rem; border-radius: var(--radius-pill);
  font-family: var(--font-heading); font-weight: var(--weight-bold); color: var(--text-muted);
}
.gv-rank--podium {
  background: var(--color-secondary); color: var(--on-secondary);
  box-shadow: inset 0 0 0 1.5px var(--fill-edge-secondary);
}

/* ---- Momentum charts (feat-063) — no chart component in the design system, so drawn in its tokens:
   the primary carries the data (and so needs its contrast), the accent only tints the fill and the
   end halo, where a pale accent costs nothing. Text never takes either. Thin marks, a hairline grid,
   strokes that keep their width however the SVG scales. ------------------------------------------ */
.gv-chart {
  padding: var(--space-5);
  --gv-chart-line: var(--color-primary);
  --gv-chart-accent: var(--color-accent, var(--gv-chart-line));
}
.gv-chart__title { font-family: var(--font-heading); font-size: var(--text-lg); font-weight: var(--weight-bold); color: var(--text-strong); }
.gv-chart svg { display: block; width: 100%; height: auto; overflow: visible; }
.gv-chart__line {
  fill: none; stroke: var(--gv-chart-line); stroke-width: 2;
  stroke-linejoin: round; stroke-linecap: round; vector-effect: non-scaling-stroke;
}
.gv-chart__area { fill: var(--gv-chart-line); opacity: 0.1; }
.gv-chart__grid { stroke: var(--border); stroke-width: 1; vector-effect: non-scaling-stroke; }
.gv-chart__tick { fill: var(--text-muted); font-size: 13px; }
.gv-chart__hit { fill: transparent; }
.gv-chart__hit:hover { fill: color-mix(in srgb, var(--gv-chart-line) 8%, transparent); }
.gv-chart__table { --bs-table-bg: transparent; }

/* The d3 redraw (themes/static_src/momentum_chart.js), drawn at its real size. */
.gv-chart__plot { position: relative; border-radius: var(--radius-md); }
.gv-chart__plot:focus-visible { outline: none; box-shadow: var(--shadow-ring); }
.gv-chart__svg { display: block; overflow: visible; }
.gv-chart__stop--top { stop-color: var(--gv-chart-line); stop-opacity: 0.26; }
.gv-chart__stop--bottom { stop-color: var(--gv-chart-accent); stop-opacity: 0.04; }
/* d3 sets font-family="sans-serif" on each axis group; the page's own face should win. */
.gv-chart__axis { font-family: inherit; }
.gv-chart__axis text { fill: var(--text-muted); font-size: var(--text-xs); }
.gv-chart__axis line,
.gv-chart__axis path { stroke: var(--border); }
.gv-chart__axis--x .tick line { display: none; }
.gv-chart__line--draw { stroke-dasharray: 1; stroke-dashoffset: 1; animation: gv-chart-draw 1.2s var(--ease-standard) forwards; }
@keyframes gv-chart-draw { to { stroke-dashoffset: 0; } }
/* A surface-coloured ring keeps the marker legible where it sits on the line. */
.gv-chart__dot,
.gv-chart__crosshair circle { fill: var(--gv-chart-line); stroke: var(--surface-card); stroke-width: 2; }
.gv-chart__halo { fill: var(--gv-chart-accent); opacity: 0.35; }
.gv-chart__end-label { fill: var(--text-strong); font-family: var(--font-heading); font-size: var(--text-sm); font-weight: var(--weight-bold); }
.gv-chart__crosshair line { stroke: var(--text-muted); stroke-width: 1; }
.gv-chart__overlay { fill: transparent; cursor: crosshair; }
.gv-chart__tooltip {
  position: absolute; transform: translate(-50%, -100%);
  padding: var(--space-2) var(--space-3); border-radius: var(--radius-md);
  background: var(--surface-card); border: 1px solid var(--border); box-shadow: var(--shadow-md);
  color: var(--text-body); font-size: var(--text-sm); line-height: 1.3;
  pointer-events: none; white-space: nowrap;
}
.gv-chart__tooltip-time { display: block; color: var(--text-muted); font-size: var(--text-xs); }
.gv-chart__tooltip strong { font-family: var(--font-heading); color: var(--text-strong); }
@media (prefers-reduced-motion: reduce) {
  .gv-chart__line--draw { animation: none; stroke-dasharray: none; stroke-dashoffset: 0; }
}

/* Donor wall, activity feed and donor map (feat-065), ported from giving.css onto design-system
   tokens. */
/* ---- Donor wall ------------------------------------------------------ */
/* The page renders inside the theme's container (themes differ in their `main`), so the band bleeds to
   the viewport edges with a spread shadow clipped to the sides — no 100vw, which overflows by the
   scrollbar's width and scrolls the page sideways. */
/* The wall's figures — the design system's StatBlock row, the heading font on the figures. */
.gv-wall-stats { display: flex; flex-wrap: wrap; justify-content: center; gap: var(--space-4) var(--space-8); margin-bottom: var(--space-6); text-align: center; }
.gv-wall-stats p { display: flex; flex-direction: column; }
.gv-wall-stats__value { font-family: var(--font-heading); font-size: var(--text-3xl); font-weight: var(--weight-bold); line-height: 1.1; color: var(--text-strong); }
.gv-wall-stats__label { font-size: var(--text-xs); font-weight: var(--weight-semibold); letter-spacing: var(--tracking-wide); text-transform: uppercase; color: var(--text-muted); }

/* Columns rather than a grid, so cards of different heights pack without gaps. DOM order still runs
   newest first, and columns flow top to bottom, so reading and focus order follow each column. */
/* A page turn scrolls the results to the top of the viewport; clear the sticky nav so it does not cover the focused entry. */
.gv-wall-results { scroll-margin-top: calc(var(--gp-sticky-offset) + 1rem); }
.gv-wall { columns: 1; column-gap: 1.25rem; }
@media (min-width: 576px) { .gv-wall { columns: 2; } }
@media (min-width: 992px) { .gv-wall { columns: 3; } }
/* No fourth column: a card carries a project, a time and an amount, and at a quarter width they wrap. */
.gv-wall__item { break-inside: avoid; margin-bottom: 1.25rem; }

/* A donor card: the donor row, and above it any message as a speech bubble whose tail points down at
   the avatar. The tint is the bubble's alone, so it always means "this donor's words". */
.gv-donor { background: var(--surface-card); border: 1px solid var(--border); border-radius: var(--radius-lg); box-shadow: var(--shadow-xs); padding: var(--space-4); }
.gv-donor:focus-visible, .gv-donor:focus { outline: none; box-shadow: var(--shadow-ring); }
.gv-donor__message {
  --tail: 0.5rem;
  position: relative; margin: 0 0 calc(var(--space-3) + var(--tail));
  padding: var(--space-3) var(--space-4); border-radius: var(--radius-md);
  background: var(--color-secondary-subtle); color: var(--text-strong); overflow-wrap: anywhere;
}
/* Centred over the 2.25rem avatar, which starts where the bubble does. */
.gv-donor__message::after {
  content: ""; position: absolute; top: 100%; left: calc(1.125rem - var(--tail));
  border: var(--tail) solid transparent; border-bottom: 0; border-top-color: var(--color-secondary-subtle);
}
.gv-donor__by { display: flex; align-items: flex-start; gap: var(--space-3); }
.gv-donor__avatar {
  flex: none; display: grid; place-items: center; width: 2.25rem; height: 2.25rem; border-radius: var(--radius-pill);
  background: var(--color-primary-subtle); color: var(--color-primary); font-size: var(--text-xs); font-weight: var(--weight-bold);
}
.gv-donor__who { flex: 1; min-width: 0; }
.gv-donor__name { margin: 0; font-weight: var(--weight-semibold); color: var(--text-strong); overflow-wrap: anywhere; }
.gv-donor__meta { margin: 0; font-size: var(--text-xs); color: var(--text-muted); overflow-wrap: anywhere; }
.gv-donor__meta a { color: var(--text-body); text-decoration: underline; text-decoration-thickness: 1px; text-underline-offset: .15em; }
.gv-donor__meta a:hover { color: var(--color-primary); }
.gv-donor__amount { flex: none; margin: 0; font-family: var(--font-heading); font-weight: var(--weight-bold); color: var(--color-primary); }

/* ---- Live activity feed: the design system's floating "Live donations" panel ----
   A <details>: closed it is the design system's small primary Button with a dot; open, a raised Card
   whose summary is the header (title, Live badge, minimise). Rows are its ActivityFeedItem. */
:root {
  /* The open panel's height is capped so focus can be scrolled exactly clear of it. */
  --gv-live-panel-height: min(30rem, calc(100vh - 40px));
}
/* WCAG 2.4.11: the panel covers the bottom-right corner, so focus moving to an element scrolls it
   clear — of the open card, or of the minimised button. Nothing below the end of the page can scroll
   clear, so there activity_feed.js minimises the panel and docks it above the footer instead. */
html:has(.gv-live-panel) { scroll-padding-bottom: 4rem; }
html:has(.gv-live-panel__details[open]) { scroll-padding-bottom: calc(var(--gv-live-panel-height) + 20px); }

.gv-live-panel {
  position: fixed; right: 20px; bottom: 20px; z-index: 1020;
  width: 360px; max-width: calc(100vw - 40px);
  display: flex; justify-content: flex-end;
}
.gv-live-panel__details { width: 100%; }
.gv-live-panel__details:not([open]) { width: auto; }
.gv-live-panel__summary {
  list-style: none; cursor: pointer; display: flex; align-items: center; gap: var(--space-2);
}
.gv-live-panel__summary::-webkit-details-marker { display: none; }
.gv-live-panel__summary:focus-visible { outline: 2px solid var(--ring); outline-offset: 2px; }

.gv-live-panel__details:not([open]) .gv-live-panel__summary {
  padding: .45rem .9rem; border-radius: var(--radius-pill);
  background: var(--color-primary); color: var(--on-primary);
  font-size: var(--text-sm); font-weight: var(--weight-semibold); box-shadow: var(--shadow-lg);
}
.gv-live-panel__dot { width: 8px; height: 8px; border-radius: var(--radius-pill); background: currentColor; }
.gv-live-panel__details:not([open]) :is(.gv-badge-live, .gv-live-panel__minimise) { display: none; }

.gv-live-panel__details[open] {
  background: var(--surface-card); border: 1px solid var(--border); border-radius: var(--radius-lg);
  box-shadow: var(--shadow-xl); padding: var(--space-4);
  max-height: var(--gv-live-panel-height); display: flex; flex-direction: column;
}
.gv-live-panel__details[open] > .gv-live-panel__body { display: flex; flex-direction: column; min-height: 0; }
.gv-live-panel__details[open] .gv-live-panel__summary { margin: calc(-1 * var(--space-2)) 0 var(--space-3); }
.gv-live-panel__details[open] .gv-live-panel__dot { display: none; }
.gv-live-panel__details[open] .gv-live-panel__title {
  flex: 1; font-family: var(--font-heading); font-weight: var(--weight-bold); font-size: var(--text-lg); color: var(--text-strong);
}
.gv-live-panel__minimise {
  display: inline-grid; place-items: center; width: 32px; height: 32px; border-radius: var(--radius-md);
  color: var(--text-body);
}
.gv-live-panel__summary:hover .gv-live-panel__minimise { background: var(--surface-subtle); }
.gv-live-panel__body .form-check { font-size: var(--text-sm); }
.gv-live-panel__wall { margin-top: var(--space-3); font-size: var(--text-sm); font-weight: var(--weight-semibold); }

#activity-feed { flex: 0 1 auto; min-height: 0; max-height: 320px; overflow: auto; }
.gv-feed__line {
  display: flex; align-items: center; gap: .75rem; padding: .7rem .25rem; border-bottom: 1px solid var(--border);
}
.gv-feed__line:last-child { border-bottom: 0; }
.gv-avatar--sm { width: 32px; height: 32px; font-size: 12px; }
.gv-feed__goal { background: var(--primary-700); color: #fff; }
.gv-feed__text { flex: 1; min-width: 0; line-height: 1.35; font-size: var(--text-sm); color: var(--text-body); overflow-wrap: anywhere; }
.gv-feed__name { font-weight: var(--weight-semibold); color: var(--text-strong); }
.gv-feed__text a { color: var(--primary-700); }
.gv-feed__message { font-style: italic; margin-top: .1rem; }
.gv-feed__time { display: block; font-size: var(--text-xs); color: var(--text-muted); font-variant-numeric: tabular-nums; }
.gv-feed__amount { flex: none; font-family: var(--font-heading); font-weight: var(--weight-bold); color: var(--color-primary); font-size: var(--text-md); }
.gv-feed__empty { padding: var(--space-3) .25rem; font-size: var(--text-sm); color: var(--text-body); }

/* ---- Donor map ------------------------------------------------------- */
/* Beside the map, the list is held to the map card's height and scrolls inside its card: the card is
   taken out of the flow so only the map sets the row's height. Stacked below lg, it runs full length. */
@media (min-width: 992px) {
  .gv-map-list { position: relative; height: 100%; }
  .gv-map-list > .card { position: absolute; inset: 0 0 auto; max-height: 100%; display: flex; flex-direction: column; }
  .gv-map-list__scroll { min-height: 0; overflow-y: auto; }
}
.gv-map-list__scroll:focus-visible { outline: none; box-shadow: var(--shadow-ring); border-radius: var(--radius-sm); }
/* Neutral, so the brand-tinted shades read against it; the same grey as the tinted sections. */
.gv-map-card { padding: var(--space-5); background: color-mix(in oklab, var(--bs-body-bg, #fff), #71717a 8%); border: 0; }
/* Capped well under the screen, so a tall map (the UK, with Shetland) narrows and centres rather than
   pushing everything below it out of view. */
.gv-map { width: 100%; height: auto; max-height: min(60vh, 30rem); display: block; }
/* A tall map (the UK) is held to the screen; its viewBox keeps the shape centred in the space. */
.gv-map__region { stroke: var(--surface-card); stroke-width: .6; vector-effect: non-scaling-stroke; }
/* One colour per shade, read by both the map's fill and the legend's swatch, so the two cannot drift. */
.gv-map__shade--0 { --shade: color-mix(in srgb, var(--color-secondary) 10%, var(--surface-card)); }
.gv-map__shade--1 { --shade: color-mix(in srgb, var(--color-primary) 22%, var(--surface-card)); }
.gv-map__shade--2 { --shade: color-mix(in srgb, var(--color-primary) 40%, var(--surface-card)); }
.gv-map__shade--3 { --shade: color-mix(in srgb, var(--color-primary) 60%, var(--surface-card)); }
.gv-map__shade--4 { --shade: color-mix(in srgb, var(--color-primary) 80%, var(--surface-card)); }
.gv-map__shade--5 { --shade: var(--primary-700); }
.gv-map__region { fill: var(--shade); }
/* The pointed-at region (donor_map.js), filled in the secondary at full strength, which no shade uses. A fill, not an
   outline: a neighbour drawn after it paints over half of any stroke, so an outline looks uneven. The
   card names its count meanwhile. */
.gv-map__region.is-hovered { fill: var(--color-secondary); }
/* donor_map.js's hover card, positioned at the pointer: a small design-system Card. */
.gv-map-tip {
  position: fixed; z-index: 1080; pointer-events: none; max-width: 16rem;
  padding: var(--space-2) var(--space-3); border-radius: var(--radius-md);
  background: var(--surface-card); border: 1px solid var(--border); box-shadow: var(--shadow-md); color: var(--text-body);
  font-size: var(--text-sm); line-height: 1.35;
}
.gv-map-tip__name { display: block; font-weight: var(--weight-semibold); color: var(--text-strong); }
.gv-map-tip__donors { display: block; color: var(--text-muted); }
.gv-map-legend__swatch { display: inline-block; width: 1.25rem; height: 1.25rem; border-radius: 4px; vertical-align: -.2rem; background: var(--shade); border: 1px solid var(--border); }

/* ---- Challenges — the design system's PageHead, Tabs, Avatar and challenge card. ---- */
.gv-page-head {
  background: linear-gradient(160deg, var(--surface-brand-band), var(--surface-brand-subtle));
  border-bottom: 1px solid var(--border);
}
.gv-tabs { min-width: 0; max-width: 100%; overflow-x: auto; }
.gv-tabs__list { display: flex; gap: .25rem; border-bottom: 1.5px solid var(--border); }
/* Spanning the row, and its rule under every tab even once they scroll: the list is as wide as its
   tabs or the row, whichever is wider. */
.gv-tabs--full .gv-tabs__list { width: max-content; min-width: 100%; }
.gv-tab {
  display: inline-block; padding: .7rem 1rem; margin-bottom: -1.5px;
  font-weight: var(--weight-semibold); color: var(--text-muted); text-decoration: none; white-space: nowrap;
  border-bottom: 2.5px solid transparent;
  transition: color var(--dur-fast) var(--ease-standard), border-color var(--dur-fast) var(--ease-standard);
}
.gv-tab:hover { color: var(--text-strong); }
.gv-tab[aria-current="page"] { color: var(--color-primary); border-bottom-color: var(--color-primary); }
.gv-tab:focus-visible { outline: none; box-shadow: var(--shadow-ring); border-radius: var(--radius-sm); }
.gv-tab__count {
  margin-left: .4rem; font-size: var(--text-xs); font-weight: var(--weight-semibold);
  background: var(--surface-subtle); color: var(--text-muted); border-radius: var(--radius-pill); padding: .1rem .45rem;
}
.gv-tab[aria-current="page"] .gv-tab__count { background: var(--color-primary-subtle); color: var(--color-primary-active); }
/* Label above the select on phones, beside it from md up — inline, the select was truncated. */
.gv-filter { flex-wrap: wrap; width: 100%; }
.gv-filter label { flex-basis: 100%; }
.gv-filter .form-select { width: auto; min-width: 0; }
@media (min-width: 768px) {
  .gv-filter { flex-wrap: nowrap; width: auto; }
  .gv-filter label { flex-basis: auto; }
}
.gv-avatar {
  display: inline-grid; place-items: center; flex: 0 0 auto; width: 36px; height: 36px;
  border-radius: var(--radius-pill); background: var(--color-primary-subtle); color: var(--color-primary-active);
  font-family: var(--font-heading); font-weight: var(--weight-bold); font-size: 14px; line-height: 1;
}
.gv-challenge__pledge { font-family: var(--font-heading); font-weight: var(--weight-bold); font-size: var(--text-lg); color: var(--color-primary); }
.gv-challenge__done { text-align: center; padding: .5rem; border-radius: var(--radius-pill); background: var(--success-surface); color: var(--success); font-weight: var(--weight-semibold); }

/* ---- Scroll-reveal ------------------------------------------------------ */
.gv-page [data-reveal] { opacity: 0; transform: translateY(22px); transition: opacity .7s ease, transform .7s var(--ease-out); transition-delay: calc(var(--i, 0) * 80ms); }
.gv-page [data-reveal].is-in { opacity: 1; transform: none; }

@media (prefers-reduced-motion: reduce) {
  .gv-page [data-reveal] { opacity: 1; transform: none; transition: none; }
  .gv-page .progress-bar { transition: none; }
  .gv-page .btn-brand:hover, .gv-page .btn-ghost-brand:hover, .gv-card:hover { transform: none; }
}
