/* brand.css — the Trust's own additions layered after tokens.css and
   theme.css (loaded last in <head>, via layout.html's "head" block, so
   it wins ties on source order). theme.css carries colour/shape as
   design tokens; this file carries the two things tokens.css has no
   slot for — the heading/wordmark typefaces, and the handful of
   one-off marketing-page sections (hero, timeline, causes) that aren't
   part of rastrillo's own vocabulary. Self-hosted fonts, served from
   this app's own static/fonts/ — nothing here fetches off-origin. */

@font-face {
  font-family: "Newsreader";
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url("fonts/newsreader.woff2") format("woff2");
}
@font-face {
  font-family: "Newsreader";
  font-style: italic;
  font-weight: 400 600;
  font-display: swap;
  src: url("fonts/newsreader-italic.woff2") format("woff2");
}
@font-face {
  font-family: "Bricolage Grotesque";
  font-style: normal;
  font-weight: 500 800;
  font-display: swap;
  src: url("fonts/bricolage-grotesque.woff2") format("woff2");
}
@font-face {
  font-family: "Source Sans 3";
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url("fonts/source-sans-3.woff2") format("woff2");
}
@font-face {
  font-family: "Source Sans 3";
  font-style: italic;
  font-weight: 400 600;
  font-display: swap;
  src: url("fonts/source-sans-3-italic.woff2") format("woff2");
}

:root {
  --wft-serif: "Newsreader", "Iowan Old Style", Georgia, serif;
  --wft-logo: "Bricolage Grotesque", "Source Sans 3", system-ui, sans-serif;
  /* The logo's gold, stated directly — distinct from --rst-accent
     (which is the green/gold PAIR that swaps with light/dark mode).
     The original's active-nav underline is always this exact gold,
     regardless of light or dark mode, matching the logo mark. */
  --wft-gold: #d6a21a;
}

/* Default link colour/underline, matching the original everywhere
   (not just marketing pages) — rastrillo's base theme leaves <a>
   unstyled otherwise, which renders browser-default blue. */
a {
  color: var(--rst-accent);
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 0.18em;
}
a:hover { color: var(--rst-accent-strong); text-decoration-color: currentColor; }

/* The original has exactly one button size everywhere (48px min
   height, 1.25rem side padding, 1rem text) — no separate "lg" variant
   the way rst-btn's size modifiers imply. rastrillo's own "lg" is
   actually SMALLER than this (14px text, 1.15rem padding), so using it
   anywhere was a mismatch from the start; templates now just use
   rst-btn/rst-btn="primary" and get the original's one size from here.
   The nav's own button is deliberately wider still (a separate, more
   specific rule), by request. */
.wft-marketing [rst-btn] {
  box-sizing: border-box;
  appearance: none;
  min-height: 48px;
  padding: 0.7rem 1.25rem;
  font-size: 1rem;
  line-height: 1.2;
}
/* The original's outline/secondary button (e.g. "Read our story",
   "Start a grant enquiry email") is see-through with green text and
   border — tokens.css's own [rst-btn] base is an opaque card
   (background: var(--rst-surface), ink-coloured text), a filled admin
   look rather than this outline one. Fills solid green with white text
   on hover, same as the original, rather than just shifting its own
   border/text colour the way [rst-btn]'s base :hover rule does. */
.wft-marketing [rst-btn]:not([rst-btn~="primary"]) {
  background: transparent;
  color: var(--rst-accent);
  border-color: var(--rst-accent);
}
.wft-marketing [rst-btn]:not([rst-btn~="primary"]):hover {
  background: var(--rst-accent);
  border-color: var(--rst-accent);
  color: var(--rst-on-accent);
}

/* ---------- marketing typography ----------
   rastrillo's base scale (--rst-fs-base: 14px, no unscoped h1/h2/h3
   size at all — font-size/weight for a heading comes entirely from
   whatever compact admin component it sits in, e.g. .rst-page-header
   h1 at 1.375rem) is built for a dense CRUD dashboard, not body copy.
   The public pages (home, news, apply, grants-info, privacy) opt into
   the original site's actual type scale instead, via a body class, so
   the trustee tool's screens (Requests/Awarded/Reports/grant forms)
   keep rastrillo's compact scale untouched. */
/* Selectors below are deliberately .wft-marketing (one class + one
   element = specificity 0,1,1), NOT body.wft-marketing (0,1,2) — kept
   equal to a plain component selector like .wft-article h2 so that a
   later, more specific-looking override (e.g. a heading's own
   margin-bottom) wins on source order rather than losing outright to
   this base rule regardless of where it's written. */
.wft-marketing {
  font-size: 1.0625rem;
  line-height: 1.6;
}
.wft-marketing h1,
.wft-marketing h2,
.wft-marketing h3 {
  font-family: var(--wft-serif);
  font-weight: 600;
  line-height: 1.12;
  letter-spacing: -0.01em;
  text-wrap: balance;
  margin: 0;
}
.wft-marketing h1 { font-size: clamp(2.4rem, 5.2vw, 4rem); letter-spacing: -0.02em; }
.wft-marketing h2 { font-size: clamp(1.85rem, 3.4vw, 2.6rem); }
.wft-marketing h3 { font-size: 1.4rem; line-height: 1.3; }
.wft-marketing p { margin: 0 0 1rem; }
.wft-marketing p:last-child { margin-bottom: 0; }

.wft-lead {
  font-size: clamp(1.15rem, 1.6vw, 1.3rem);
  line-height: 1.55;
  color: var(--rst-text-muted);
  max-width: 52ch;
}

/* tokens.css's .rst-page/[rst-page] is 64rem/1024px — the admin tool's
   own dashboard measure. The original's actual column is 1160px, which
   the home page's bands already use (.wft-band-inner); this carries
   the same width to every OTHER marketing page's content (News, Apply,
   Grants info, Privacy), scoped to .wft-marketing so the trustee
   tool's screens keep rastrillo's own, narrower column. */
.wft-marketing [rst-page] {
  max-width: 1160px;
  /* Matches a .section's own bottom padding on the original — the
     page's default (admin-dashboard) bottom padding read as tight
     next to the footer on pages that end in ordinary boxed content
     rather than a full-bleed band (Apply, News, Privacy). */
  padding-bottom: clamp(3.5rem, 8vw, 6.5rem);
}
/* rst-page's own bottom padding shows as a sliver of plain page
   background between the last full-bleed band and the footer, on
   pages that end in one (home, grants-info) — zeroed only then, since
   pages that end in ordinary boxed content (apply, news, privacy)
   still need that padding as their gap before the footer. */
.wft-marketing [rst-page]:has(> .wft-band:last-child) { padding-bottom: 0; }

/* rst-box/rst-box-head/rst-callout's default margins (1.5rem above a
   heading, 1rem around a box/callout) are an admin-dashboard rhythm —
   tight enough that News/Apply/Grants info/Privacy's section-to-
   section and box-to-text spacing read as cramped next to the home
   page's generous band padding. Scoped the same way as everything
   else here, so the trustee tool's own screens keep the compact
   rhythm that actually suits a dense CRUD list. */
.wft-marketing [rst-box-head] { margin-top: 3rem; margin-bottom: 1rem; }
/* :first-of-type (not :first-child) — the page-hero before it is a
   <section>, so the first <div rst-box-head> is still the first
   sibling div; :first-child would miss that and double up with the
   page-hero's own bottom margin/border instead. */
.wft-marketing [rst-box-head]:first-of-type { margin-top: 0; }
.wft-marketing [rst-box] { padding: 1.75rem 2rem; margin: 0 0 1rem; }
.wft-marketing [rst-callout] { padding: 1.1rem 1.25rem; margin: 1.5rem 0; }

/* ---------- grants info page ---------- */

/* The divider line sits below the facts cards, not between the lead
   paragraph and the cards — so wft-page-hero's own border is
   suppressed on this one page (:has(+ .wft-facts)) and restated here
   instead, after the cards. */
.wft-page-hero:has(+ .wft-facts) { border-bottom: 0; }
.wft-facts {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 1rem;
  margin: 2rem 0 0;
  padding: 0 0 clamp(2rem, 5vw, 3.5rem);
  border-bottom: 1px solid var(--rst-line);
}
@media (max-width: 760px) {
  .wft-facts { grid-template-columns: 1fr; }
}
.wft-facts div { background: var(--rst-surface); border: 1px solid var(--rst-line); border-radius: var(--rst-radius); padding: 1.25rem; }
.wft-facts dt { font-size: 0.9rem; font-weight: 600; color: var(--rst-text-muted); }
.wft-facts dd { margin: 0.35rem 0 0; font-family: var(--wft-serif); font-size: 1.35rem; line-height: 1.25; }

.wft-plain-list { margin: 0 0 1rem; padding-left: 1.2rem; }
.wft-plain-list li { margin: 0.45rem 0; padding-left: 0.25rem; }
.wft-plain-list li::marker { color: var(--rst-accent); }

.wft-numbered { margin: 1.25rem 0 1.5rem; padding-left: 0; list-style: none; counter-reset: item; }
.wft-numbered li { counter-increment: item; position: relative; padding: 0.8rem 0 0.8rem 2.75rem; border-bottom: 1px solid var(--rst-line); }
.wft-numbered li:first-child { border-top: 1px solid var(--rst-line); }
.wft-numbered li::before {
  content: counter(item);
  position: absolute;
  left: 0;
  top: 0.75rem;
  width: 1.8rem;
  height: 1.8rem;
  border-radius: 50%;
  display: grid;
  place-items: center;
  font: 600 0.95rem/1 inherit;
  color: var(--rst-accent);
  background: var(--rst-accent-soft);
}
.wft-numbered strong { display: block; font-weight: 600; }
.wft-numbered span { color: var(--rst-text-muted); font-size: 0.96rem; }

/* ---------- apply page ----------
   The original's form card is capped at 40rem — roughly half the
   1160px column, not the full width — and its field labels are a
   full-strength, bold line, not tokens.css's quiet muted one (right
   for a dense settings panel, not a one-off public form). */
.wft-apply-box { max-width: 40rem; }
/* tokens.css centres a switch's track against its label text by
   default — right for a short one-line label, but this one wraps onto
   2-3 lines at mobile widths, where a vertically-centred track reads
   as floating mid-sentence rather than introducing it. */
.wft-switch-wrap { align-items: flex-start; }
.wft-switch-wrap [rst-switch-track] { margin-top: 0.15rem; }
.wft-apply-box [rst-form] h2 { margin-top: 1rem; }
.wft-apply-box [rst-form] h2:first-child { margin-top: 0; }

/* ---------- address lookup (static/address-lookup.js) ---------- */
.wft-address-lookup { margin-bottom: 0.75rem; }
.wft-address-results {
  margin: 0.35rem 0 0;
  padding: 0;
  list-style: none;
  border: 1px solid var(--rst-line);
  border-radius: var(--rst-radius);
  overflow: hidden;
}
.wft-address-results li + li { border-top: 1px solid var(--rst-line); }
.wft-address-results button {
  display: block;
  width: 100%;
  padding: 0.6rem 0.85rem;
  border: 0;
  background: var(--rst-surface);
  color: inherit;
  font: inherit;
  text-align: left;
  cursor: pointer;
}
.wft-address-results button:hover,
.wft-address-results button:focus-visible { background: var(--rst-accent-soft); }
.wft-address-message { padding: 0.6rem 0.85rem; color: var(--rst-text-muted); }

/* ---------- org lookup (static/org-lookup.js) ---------- */
.wft-org-lookup { margin-bottom: 1rem; }
.wft-org-status { margin: 0.5rem 0 0; color: var(--rst-text-muted); }
.wft-org-lookup [data-org-results] { margin-top: 0.5rem; }
.wft-org-lookup [data-org-group="external"] { display: grid; gap: 0.75rem; }
.wft-org-lookup [data-org-results] h3 { margin: 0 0 0.25rem; font-size: 0.9rem; }
.wft-org-results {
  margin: 0;
  padding: 0;
  list-style: none;
  border: 1px solid var(--rst-line);
  border-radius: var(--rst-radius);
  overflow: hidden;
}
.wft-org-results li + li { border-top: 1px solid var(--rst-line); }
.wft-org-results button {
  display: block;
  width: 100%;
  padding: 0.6rem 0.85rem;
  border: 0;
  background: var(--rst-surface);
  color: inherit;
  font: inherit;
  text-align: left;
  cursor: pointer;
}
.wft-org-results button:hover,
.wft-org-results button:focus-visible { background: var(--rst-accent-soft); }
.wft-org-message { margin: 0.35rem 0 0; color: var(--rst-text-muted); }
.wft-marketing [rst-field-label] {
  font-weight: 600;
  font-size: 1rem;
  color: var(--rst-text);
}
/* tokens.css's hint/help/error text is --rst-fs-xs (11.5px) everywhere,
   public site and trustee tool alike. Unscoped (not just .wft-marketing,
   as an earlier pass had it): an error message is save-blocking text,
   not a secondary note, and the trustee tool's own "Enter the amount
   awarded..."/"Enter the date paid..." messages need to be at least as
   legible as the data they're guarding, not smaller. Matches body size
   rather than splitting the difference. */
small[rst-field-hint],
[rst-field-help],
[rst-field-error] {
  font-size: var(--rst-fs-base);
}

/* ---------- privacy page: prose ----------
   Matches the original: Privacy and Cookies side by side in a two-
   column grid, with Contact about privacy on its own below. */
.wft-prose-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: clamp(2rem, 6vw, 5rem);
  align-items: start;
  margin-bottom: clamp(2rem, 6vw, 5rem);
}
@media (max-width: 760px) {
  .wft-prose-grid { grid-template-columns: 1fr; }
}
.wft-prose { max-width: 68ch; }
.wft-prose h2 { font-size: clamp(1.6rem, 3vw, 2.1rem); margin-bottom: 1rem; }
.wft-prose h3 { font-size: 1.2rem; margin: 1.75rem 0 0.5rem; }
.wft-prose p { color: var(--rst-text-muted); }
/* Grants info reuses .wft-prose-grid for its two-column layout but
   without the .wft-prose class on each column (no 68ch measure wanted
   there) — still needs the same gap under each column's own h2. */
.wft-prose-grid h2 { margin-bottom: 1rem; }
.wft-prose-grid h3 { margin-top: 2rem; margin-bottom: 0.5rem; }

/* ---------- marketing pages: hero page header ----------
   The large serif title + lead paragraph used by News, Apply, Grants
   info and Privacy — matching the original's .page-hero, not
   rastrillo's compact page-header partial (kept for the trustee
   tool's own admin screens). */
.wft-page-hero {
  padding-block: clamp(2rem, 5vw, 3.5rem) clamp(1.5rem, 3vw, 2rem);
  border-bottom: 1px solid var(--rst-line);
  margin-bottom: 2rem;
}
.wft-page-hero h1 { margin-bottom: 1rem; }

/* Scoped dark mode: the same toggle theme.css gives :root, but on any
   element. A section carrying data-theme="dark" gets the dark half of
   every light-dark() token inside it, regardless of the page's own
   scheme — this is how the homepage's closing band gets the Trust's
   forest-green/gold "dark band" look as an ordinary rst-box, with no
   separate colours to maintain. */
/* color-scheme alone only changes how light-dark() resolves for a
   property declared fresh within this subtree — it does not re-resolve
   an already-computed `color` a child merely inherited from outside
   the scope. So this rule also restates color directly, which (being
   declared here, inside the new color-scheme) picks up the dark half
   and then inherits correctly to every descendant that doesn't set its
   own. */
[data-theme="dark"] { color-scheme: dark; color: var(--rst-text); }
[data-theme="light"] { color-scheme: light; color: var(--rst-text); }

/* ---------- brand mark (header + footer) ---------- */

.wft-brand {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  text-decoration: none;
  color: var(--rst-text);
}
.wft-brand img { width: 52px; height: 39px; }
.wft-brand-name {
  font-family: var(--wft-logo);
  font-weight: 800;
  font-size: 1.3rem;
  line-height: 1;
  letter-spacing: -0.02em;
  display: flex;
  flex-direction: column;
}
.wft-brand-name small {
  margin-top: 0.3rem;
  font-family: var(--wft-logo);
  font-weight: 500;
  font-size: 0.75rem;
  line-height: 1;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--rst-accent);
}
/* Below ~430px the full lockup (mark + "The Wood Family" + "Charitable
   Trust") plus the Menu button no longer fit one row, so Menu wrapped
   to a row of its own under empty space — costing a second row of
   chrome, on every page, before any content. Dropping the subtitle and
   shrinking the mark/wordmark is enough to keep both on one row. */
@media (max-width: 430px) {
  .wft-brand-name small { display: none; }
  .wft-brand-name { font-size: 1.05rem; }
  .wft-brand img { width: 40px; height: 30px; }
}

/* ---------- header / nav (shell overrides) ----------
   rastrillo's stock topbar is a small, muted-text nav bar with no
   sticky/blur treatment and a bordered account menu. The original
   site's header is a sticky, blurred bar with full-weight ink-coloured
   nav links and a solid green "Apply for a grant" button — matched
   here rather than left at the framework default. */

/* [rst-shell-topbar] is layout.html's one full-page wrapper — header,
   main and footer all live inside it, as plain block children, so the
   footer's own position was wherever the page's content happened to
   end. On every short page (sign-in, delete-confirm, Security, a 404,
   a nearly-empty Awarded) that left a bare strip of page background
   below it rather than the footer actually closing the page. A flex
   column at least viewport-tall, with the footer pushed down by its
   own margin-top: auto, is the standard fix — and costs main nothing,
   since a block child already fills a flex column's cross axis and
   margin-top: auto consumes whatever space is left over before it. */
/* Restores the margin [rst-box] used to contribute before it was
   removed as a redundant outer box around [rst-stats] on Cashbook and
   Reports (rst-stats already draws its own complete box) — [rst-stats]
   itself carries no margin of its own. */
[rst-stats] { margin: var(--rst-sp-4) 0; }

[rst-shell-topbar] {
  display: flex;
  flex-direction: column;
  min-height: 100dvh;
}
[rst-shell-foot] {
  margin-top: auto;
}

[rst-shell-bar] {
  position: sticky;
  top: 0;
  z-index: 50;
  background: var(--rst-bg);
  padding-inline: 0;
}

/* Aligns the header's content to the original site's own 1160px
   column (not tokens.css's .rst-page, which is 64rem/1024px — a
   purely admin-dashboard measure that wraps text noticeably sooner
   than the original ever did, e.g. the homepage timeline's entries).
   [rst-shell-bar] itself stays full-width for its background. */
.wft-bar-inner {
  max-width: 1160px;
  margin-inline: auto;
  width: 100%;
  box-sizing: border-box;
  padding-inline: var(--rst-sp-4);
  min-block-size: 72px;
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--rst-sp-4);
}

/* The original's nav sits hard right, after the brand — tokens.css
   instead reserves that position for the account menu. Moving the
   auto margin to nav pushes nav (and everything after it in the row)
   right as a cluster, with account trailing further right still. */
[rst-shell-nav] { margin-inline-start: auto; }
[rst-shell-account] { margin-inline-start: 0; }

[rst-shell-nav] a:not([rst-btn]) {
  font-weight: 600;
  font-size: 0.98rem;
  line-height: 1;
  color: var(--rst-text);
  border-block-end: 0;
  border-radius: 6px;
  padding: 0.6rem 0.75rem;
}
[rst-shell-nav] a:not([rst-btn]):hover {
  background: color-mix(in oklab, currentColor 12%, transparent);
  color: var(--rst-text);
}
[rst-shell-nav] a[aria-current]:not([rst-btn]) {
  font-weight: 600;
  text-decoration: underline;
  text-decoration-color: var(--wft-gold);
  text-decoration-thickness: 3px;
  text-underline-offset: 0.45em;
}
/* nav and its children were stretching to the header bar's own
   73px height (nav's cross-axis align-items resolves to "stretch" by
   default, and its align-self against .wft-bar-inner wasn't pinned
   either), rather than sizing to content and centering — which is why
   the button's green fill spilled past the header's own bottom edge
   into the section below. align-self:center on both fixes the actual
   cause; the button's own height then comes from its padding alone. */
[rst-shell-nav] { align-self: center; }
[rst-shell-nav] a { align-self: center; }
/* tokens.css's own wide-viewport rule for nav children
   ([rst-shell-topbar] [rst-shell-tail] > [rst-shell-nav] a {
   padding-inline: 0 }) is MORE specific (0,3,1) than a plain
   [rst-shell-nav] a[rst-btn] (0,2,1) — it was winning outright and
   zeroing the button's horizontal padding regardless of what this
   rule declared, which is why widening it kept silently not working.
   Matched here at equal structure (0,4,1) to actually beat it, and
   min-height/align-self restated for the same reason .wft-marketing
   [rst-btn]'s own min-height:48px (needed generally, for buttons in
   running text) was overriding this one's — on a flex row the
   general rule's min-height doesn't match THIS row's natural height,
   which is why the stretch-past-the-header bug kept coming back. */
[rst-shell-topbar] [rst-shell-tail] > [rst-shell-nav] a[rst-btn] {
  padding-inline: 1.25rem;
}
/* Same collision, same fix, for the plain links (Our story, What we
   support, News, Contact) — tokens.css's padding-inline: 0 targets
   every <a> in the nav, not just the one with [rst-btn]. */
[rst-shell-topbar] [rst-shell-tail] > [rst-shell-nav] a:not([rst-btn]) {
  padding-inline: 0.75rem;
}
[rst-shell-nav] a[rst-btn] {
  align-self: center;
  margin-inline-start: 0.5rem;
  min-height: 0;
  padding-block: 0.7rem;
  font-size: 1rem;
  line-height: 1.2;
}
/* tokens.css's own [rst-shell-nav] a (0,1,1) is more specific than its
   own [rst-btn~="primary"] (0,1,0), so without this the button's white
   text always lost to the shell nav's muted grey — found by checking
   getComputedStyle directly, not by inspecting source alone. */
[rst-shell-nav] a[rst-btn~="primary"],
[rst-shell-nav] a[rst-btn~="primary"]:hover { color: var(--rst-on-accent); }
[rst-shell-nav] a[rst-btn][aria-current] { box-shadow: inset 0 -3px 0 var(--wft-gold); }

[rst-shell-account] summary {
  font-weight: 600;
  font-size: 0.95rem;
  color: var(--rst-text);
}

/* ---------- footer (shell override) ----------
   Matches the original's dark green footer exactly: brand + address on
   the left, a link list on the right, a copyright line underneath. The
   green is the brand's own green-deep, stated directly rather than via
   the generic dark-mode --rst-bg token (deliberately darker, for a
   whole page in dark mode) — the footer keeps this exact shade
   regardless of the visitor's light/dark choice elsewhere on the page. */

[rst-shell-foot] {
  background: #1f3a18;
  color: #cfd6c6;
  text-align: left;
  padding-block: 2.5rem 1.75rem;
  font-size: 0.95rem;
}
[rst-shell-foot] a { color: #f4efe3; text-decoration-color: rgba(244, 239, 227, 0.45); }
[rst-shell-foot] a:hover { text-decoration-color: currentColor; }
/* Signed in, the footer-grid (brand/address/links) is gone entirely —
   without this, its padding/margin stayed reserved anyway, showing as
   a band of empty green with a stray border-top crossing it and
   nothing above or below. */
[rst-shell-foot]:has(.wft-footer--compact) { padding-block: 1.25rem; }
.wft-footer--compact .wft-footer-bottom { margin-top: 0; padding-top: 0; border-top: 0; }

.wft-footer {
  max-width: 1160px;
  margin-inline: auto;
  width: 100%;
  box-sizing: border-box;
  padding-inline: var(--rst-sp-4);
}
.wft-footer-grid {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  gap: 2rem;
}
.wft-footer-brand {
  display: flex;
  align-items: flex-start;
  gap: 1rem;
  max-width: 40rem;
}
.wft-footer-brand img { width: 64px; height: 48px; flex: none; }
.wft-footer-brand strong {
  display: block;
  font-family: var(--wft-logo);
  font-size: 1.2rem;
  font-weight: 800;
  letter-spacing: -0.01em;
  color: #fff;
  margin-bottom: 0.4rem;
}
.wft-footer-links { list-style: none; margin: 0; padding: 0; display: grid; gap: 0; }
.wft-footer-links a,
.wft-footer-links button {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  padding: 0.2rem 0;
  font: inherit;
  color: #f4efe3;
  background: none;
  border: 0;
  text-align: left;
  text-decoration: underline;
  text-decoration-color: rgba(244, 239, 227, 0.45);
  text-decoration-thickness: 1px;
  text-underline-offset: 0.18em;
  cursor: pointer;
  box-sizing: border-box;
}
.wft-footer-links a:hover,
.wft-footer-links button:hover { text-decoration-color: currentColor; }
.wft-footer-bottom {
  margin-top: 1.5rem;
  padding-top: 1rem;
  border-top: 1px solid rgba(244, 239, 227, 0.18);
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
}
.wft-footer-bottom p { margin: 0; }
.wft-version { font-size: 0.75rem; color: rgba(244, 239, 227, 0.6); margin-top: 0.2rem; }

.wft-theme-switch {
  display: inline-flex;
  gap: 0.15rem;
  padding: 0.2rem;
  border-radius: var(--rst-radius-pill);
  background: rgba(255, 255, 255, 0.08);
}
/* Same fight the pagination [hidden] rule below already won once: an
   author `display` rule beats the browser's own `[hidden]{display:none}`,
   so chrome.html's `hidden` attribute on this element did nothing on
   its own — the three buttons rendered dead but visible with JS off. */
.wft-theme-switch[hidden] { display: none; }
.wft-theme-switch button {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  font: inherit;
  font-size: 0.85rem;
  font-weight: 600;
  color: #cfd6c6;
  background: none;
  border: 0;
  border-radius: var(--rst-radius-pill);
  padding: 0.4rem 0.9rem;
  cursor: pointer;
  box-sizing: border-box;
}
.wft-theme-switch button[aria-pressed="true"] { background: #fff; color: #1f3a18; }

/* tokens.css reserves a scrollbar gutter on <html> precisely so a
   scrollbar appearing/disappearing never shifts the layout sideways —
   but the CSS `vw` unit is defined against the full viewport including
   any scrollbar regardless of that reservation, so .wft-band/
   .wft-hero-band's `width: 100vw` below can still run a scrollbar's
   width wider than the page's own content box on a classic (non-
   overlay) scrollbar — a permanent few-pixel horizontal scrollbar on
   most Windows/Linux desktop browsers. Clipping it here is a narrower
   fix than reworking the full-bleed technique itself, and can't affect
   this app's own internal horizontal scrollers (.wft-table-scroll,
   .wft-admin-tabs further down), which manage their own overflow
   independently of the document's. */
html { overflow-x: clip; }

/* ---------- homepage: full-bleed section bands ----------
   The original's home page is a stack of full-viewport-width bands,
   alternating paper/beige/dark-green, with the original's own 1160px
   column of content centred inside each (NOT tokens.css's .rst-page,
   which is 64rem/1024px — a narrower, admin-dashboard measure that
   wraps text sooner than the original did, e.g. an extra line in the
   homepage timeline) — not a stack of bordered "cards" (rst-box's own
   look, right for the admin tool's dashboard screens but not what the
   original marketing page does). .wft-band breaks out of .rst-page's
   own centred, padded box (the usual trick: 100vw + negative margins
   pulled from the viewport, not the parent) so the colour spans edge
   to edge while .wft-band-inner re-centres the actual content to the
   same column the header and footer already use.

   Hero gets its own wrapper (.wft-hero-band, no .wft-band class) with
   different padding and deliberately NOT matched by the `+` selector
   below — the original's hero is a plain .hero, not a .section, so the
   first true .section (Our story) that follows it has no top border,
   exactly as here. */

.wft-band-inner {
  max-width: 1160px;
  margin-inline: auto;
  padding-inline: var(--rst-sp-4);
  box-sizing: border-box;
}
.wft-hero-band {
  /* Needs the same full-bleed treatment as .wft-band below — without
     it, .rst-page's own padding/centring left the hero's text ~84px
     further right than every other section's, out of line with the
     header and footer column despite sharing the same background
     colour (which is why it wasn't visible without measuring). */
  width: 100vw;
  margin-left: calc(-50vw + 50%);
  margin-right: calc(-50vw + 50%);
  padding-block: clamp(2.5rem, 6vw, 5rem) clamp(3rem, 7vw, 5.5rem);
}
.wft-band {
  width: 100vw;
  /* The correct full-bleed formula: "50%" in a margin resolves against
     the containing block's content width, which already nets out
     .rst-page's own side padding and centring — the position:relative
     + left/right:50% variant this replaced does NOT (it offsets
     against the band's own 100vw width instead), and measured 84px
     out of alignment with the header/footer column as a result. */
  margin-left: calc(-50vw + 50%);
  margin-right: calc(-50vw + 50%);
  padding-block: clamp(3.5rem, 8vw, 6.5rem);
}
.wft-band + .wft-band { border-top: 1px solid var(--rst-line); }
.wft-band--alt { background: var(--rst-surface-2); }
.wft-band--dark {
  background: #1f3a18;
  color: #f4efe3;
}
.wft-band--dark .wft-lead,
.wft-band--dark p { color: #cfd6c6; }
.wft-band--dark h2 { color: #fff; }
.wft-band--dark a:not([rst-btn]) { color: #e8bd4a; }
/* Needed as its own rule: .wft-band--dark's fixed gold (0,2,0) is more
   specific than the generic a:hover (0,1,1), so without this the
   generic hover colour change never got a chance to apply here at
   all — hovering these links did visibly nothing.

   Lightens rather than darkens: this is a light-on-dark pairing
   (gold text on forest green), so darkening toward black on hover —
   the usual move for a dark-on-light link — instead dropped it to
   about 3.3:1 against the band, under the 4.5:1 text floor. #f2cd66
   is a lighter gold (~9.8:1 here) that stays legible on hover.
   Hardcoded hex, not a --rst-accent-strong var(): .wft-band--dark
   never sets data-theme="dark"/color-scheme locally (every colour on
   this band is a fixed hex for exactly that reason), so a light-dark()
   token here would resolve to its LIGHT branch — #1f3a18, the band's
   own near-invisible background colour — while the page itself is in
   light mode. */
.wft-band--dark a:not([rst-btn]):hover {
  color: #f2cd66;
  text-decoration: underline;
  text-decoration-color: currentColor;
}
.wft-band--dark h3 { color: #fff; }
/* tokens.css's own :focus-visible (scoped to .rst-page via :where())
   rings in var(--rst-accent) — this band sits inside .rst-page but,
   like its own link colour above, never sets data-theme="dark", so in
   light mode that ring resolves to the light accent (a dim green,
   ~1.55:1 here) against this band's permanently-dark background: a
   real sub-3:1 WCAG 2.4.11 failure, not just a taste mismatch. Same
   fixed gold as .wft-btn-on-dark, for the same reason var() can't be
   used here. Specificity beats the :where() default without needing
   !important. */
.wft-band--dark :focus-visible {
  outline: 2px solid #e8bd4a;
  outline-offset: 2px;
}

.wft-steps {
  list-style: none;
  margin: 2.5rem 0 0;
  padding: 0;
  counter-reset: step;
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 1.5rem;
}
.wft-steps li {
  counter-increment: step;
  padding-top: 1.25rem;
  border-top: 1px solid rgba(244, 239, 227, 0.25);
}
.wft-steps li::before {
  content: counter(step);
  display: block;
  font-family: var(--wft-serif);
  font-size: 2.4rem;
  line-height: 1;
  color: #e8bd4a;
  margin-bottom: 0.75rem;
}
.wft-steps h3 { font-size: 1.25rem; margin-bottom: 0.4rem; }
.wft-steps p { font-size: 0.98rem; }
@media (max-width: 760px) {
  .wft-steps { grid-template-columns: 1fr; gap: 0; }
  .wft-steps li { padding: 1.25rem 0; }
}

.wft-actions { display: flex; flex-wrap: wrap; align-items: center; gap: 1.25rem; margin-top: 2.5rem; }
/* The hero uses this on a <p> (original: 1.75rem there vs 2.5rem for
   the steps-list actions, a <div>) — needs the p. prefix to beat
   .wft-marketing p's margin-top:0, the same recurring specificity gap
   as the other p.wft-* fixes above. */
p.wft-actions { margin-top: 1.75rem; }
/* The original's button ON the dark band is gold, not green — a
   green button on a dark green background loses contrast. Stated
   directly (not via the light/dark-swapping --rst-accent token) so it
   stays gold regardless of the visitor's own light/dark choice,
   matching the original's fixed, non-toggle-aware design. */
.wft-btn-on-dark,
.wft-btn-on-dark:hover {
  background: #e8bd4a;
  border-color: #e8bd4a;
  color: #1f3a18;
}
.wft-btn-on-dark:hover { background: #f2cd66; border-color: #f2cd66; }

.wft-section-head { max-width: 62ch; margin-bottom: 2.5rem; }
.wft-section-head h2 { margin-bottom: 1rem; }

/* ---------- homepage: hero ---------- */

.wft-hero {
  display: grid;
  gap: 2rem;
  align-items: center;
  grid-template-columns: 1fr;
}
@media (min-width: 860px) {
  .wft-hero { grid-template-columns: 1.1fr 1fr; }
}
.wft-hero h1 { margin-bottom: 1.25rem; }
.wft-hero h1 em { font-style: italic; font-weight: 500; color: var(--rst-accent); }
.wft-hero-photo { margin: 0; }
.wft-hero-photo img {
  width: 100%;
  height: auto;
  display: block;
  border-radius: var(--rst-radius);
  box-shadow: var(--rst-shadow-lift);
}
.wft-hero-photo figcaption {
  margin-top: 0.75rem;
  font-size: 0.85rem;
  color: var(--rst-text-muted);
}
p.wft-hero-meta {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem 1.2rem;
  margin-top: 1.25rem;
  font-size: 0.9rem;
  color: var(--rst-text-muted);
}

/* ---------- homepage: story + timeline ----------
   Matches the original site's two-column layout: the story text in a
   wider left column, the timeline in a narrower column alongside it
   (not stacked below) — collapsing to one column on narrow screens. */

.wft-story-grid {
  display: grid;
  grid-template-columns: minmax(0, 1.25fr) minmax(0, 1fr);
  gap: clamp(2rem, 6vw, 5.5rem);
  align-items: start;
}
.wft-story-text h2 { margin-bottom: 1.25rem; }
.wft-story-text p { max-width: 64ch; }
p.wft-family {
  margin-top: 1.75rem;
  padding-top: 1.25rem;
  border-top: 1px solid var(--rst-line);
  font-size: 0.98rem;
  color: var(--rst-text-muted);
}
.wft-family strong { color: var(--rst-text); font-weight: 600; }

@media (max-width: 960px) {
  .wft-story-grid { grid-template-columns: 1fr; }
}

/* Matches the original site's timeline exactly: a vertical gold-to-
   green gradient line with a small ringed circle at each entry, the
   year in large serif green type to its left. */

.wft-timeline { list-style: none; margin: 0; padding: 0; position: relative; }
.wft-timeline::before {
  content: "";
  position: absolute;
  left: 4.6rem;
  top: 0.6rem;
  bottom: 0.6rem;
  width: 2px;
  background: linear-gradient(var(--rst-accent-soft), var(--rst-accent));
}
.wft-timeline li {
  display: grid;
  grid-template-columns: 4.6rem 1fr;
  gap: 0 1.6rem;
  padding: 0 0 1.9rem;
  position: relative;
}
.wft-timeline li:last-child { padding-bottom: 0; }
.wft-timeline li::after {
  content: "";
  position: absolute;
  left: calc(4.6rem - 5px);
  top: 0.55rem;
  width: 12px;
  height: 12px;
  border-radius: 50%;
  background: var(--rst-bg);
  border: 2px solid var(--rst-accent);
}
.wft-timeline .wft-year {
  font-family: var(--wft-serif);
  font-size: 1.5rem;
  font-weight: 600;
  line-height: 1.1;
  color: var(--rst-accent);
  font-variant-numeric: lining-nums;
}
.wft-timeline h3 { font-size: 1.15rem; margin: 0.1rem 0 0.3rem; }
.wft-timeline p { font-size: 0.98rem; margin: 0; color: var(--rst-text-muted); }
.wft-timeline p a { white-space: nowrap; }

@media (max-width: 640px) {
  .wft-timeline::before { left: 3.9rem; }
  .wft-timeline li { grid-template-columns: 3.9rem 1fr; gap: 0 1.3rem; }
  .wft-timeline li::after { left: calc(3.9rem - 5px); }
  .wft-timeline .wft-year { font-size: 1.25rem; }
}

/* ---------- news / article pages ----------
   Matches the original site's article + photo + date treatment. */

.wft-article + .wft-article {
  margin-top: clamp(3rem, 7vw, 5rem);
  padding-top: clamp(2.5rem, 6vw, 4rem);
  border-top: 1px solid var(--rst-line);
}
.wft-article { max-width: 68ch; }
.wft-article h2 { font-size: clamp(1.6rem, 3vw, 2.2rem); margin-bottom: 1.25rem; }
.wft-date {
  font-size: 0.9rem;
  font-weight: 600;
  letter-spacing: 0.02em;
  color: var(--rst-text-muted);
  margin-bottom: 0.5rem;
}
.wft-fineprint { color: var(--rst-text-muted); font-size: 0.92rem; }
.wft-actions + .wft-fineprint { margin-top: 1rem; }
.wft-photo { margin: 2rem 0; }
.wft-photo img {
  width: 100%;
  height: auto;
  aspect-ratio: 3 / 2;
  object-fit: cover;
  border-radius: var(--rst-radius);
  box-shadow: var(--rst-shadow-lift);
}
.wft-photo.wft-natural img { aspect-ratio: auto; }
.wft-photo.wft-portrait { max-width: 420px; }
.wft-photo figcaption {
  margin-top: 0.75rem;
  font-size: 0.92rem;
  color: var(--rst-text-muted);
  max-width: 60ch;
}

/* ---------- homepage: causes grid ---------- */

.wft-causes {
  display: grid;
  gap: 0 clamp(2rem, 5vw, 4rem);
  grid-template-columns: 1fr;
  border-top: 1px solid var(--rst-line);
}
@media (min-width: 640px) {
  .wft-causes { grid-template-columns: repeat(2, 1fr); }
}
.wft-cause { padding: 1.9rem 0; border-bottom: 1px solid var(--rst-line); }
.wft-cause h3 { margin-bottom: 0.6rem; }
.wft-cause p { color: var(--rst-text-muted); margin: 0 0 0.6rem; max-width: 52ch; }
.wft-cause .wft-example { font-size: 0.92rem; color: var(--rst-text); }
.wft-cause .wft-example span { color: var(--rst-text-muted); }

/* p.wft-support-note (not just .wft-support-note): needs to tie
   .wft-marketing p's specificity (0,1,1), which otherwise wins outright
   — its margin:0 0 1rem shorthand sets margin-top:0 as a real longhand
   declaration, at specificity .wft-support-note's margin-top alone
   can't beat, zeroing the gap above this note regardless of source
   order. */
p.wft-support-note {
  margin-top: 2rem;
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem 2rem;
  color: var(--rst-text-muted);
  font-size: 1rem;
}

/* ---------- homepage: how we give ---------- */

.wft-give-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: clamp(2rem, 6vw, 5rem);
}
.wft-give-grid h3 { margin-bottom: 1rem; }
@media (max-width: 960px) {
  .wft-give-grid { grid-template-columns: 1fr; }
}
.wft-checks { list-style: none; margin: 0 0 1.25rem; padding: 0; }
.wft-checks li { position: relative; padding: 0.55rem 0 0.55rem 2rem; border-bottom: 1px solid var(--rst-line); }
.wft-checks li::before {
  content: "";
  position: absolute;
  left: 0.15rem;
  top: 1.05rem;
  width: 0.8rem;
  height: 0.45rem;
  border-left: 2px solid var(--rst-accent);
  border-bottom: 2px solid var(--rst-accent);
  transform: rotate(-45deg);
}
p.wft-aside-note {
  margin-top: 1.5rem;
  padding: 1.1rem 1.25rem;
  background: var(--rst-surface);
  border: 1px solid var(--rst-line);
  border-radius: var(--rst-radius);
}
.wft-aside-note + .wft-aside-note { margin-top: 1rem; }

/* ---------- homepage: latest news teaser ---------- */

.wft-news-teaser {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.4fr);
  gap: clamp(1.5rem, 5vw, 4rem);
  align-items: start;
}
@media (max-width: 960px) {
  .wft-news-teaser { grid-template-columns: 1fr; }
}
.wft-news-teaser h3 { font-size: clamp(1.4rem, 2.4vw, 1.8rem); margin-bottom: 0.8rem; }
.wft-muted { color: var(--rst-text-muted); }

/* ---------- homepage: contact ---------- */

.wft-contact-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.15fr);
  gap: clamp(2rem, 6vw, 5rem);
  align-items: start;
}
@media (max-width: 960px) {
  .wft-contact-grid { grid-template-columns: 1fr; }
}
.wft-contact-list { margin: 1.75rem 0 0; padding: 0; }
.wft-contact-list dt { font-size: 0.9rem; font-weight: 600; color: var(--rst-text-muted); margin-top: 1.1rem; }
.wft-contact-list dt:first-child { margin-top: 0; }
.wft-contact-list dd { margin: 0.15rem 0 0; font-size: 1.1rem; }

.wft-contact-form h3 { margin-bottom: 0.35rem; }
.wft-contact-form > p:not(.wft-fineprint) { color: var(--rst-text-muted); }
.wft-field { margin-top: 1.1rem; }
.wft-field label { display: block; font-weight: 600; margin-bottom: 0.35rem; }
.wft-field input,
.wft-field textarea {
  width: 100%;
  box-sizing: border-box;
  font: inherit;
  color: inherit;
  background: var(--rst-surface);
  border: 1px solid var(--rst-line-strong);
  border-radius: var(--rst-radius-sm);
  padding: 0.7rem 0.85rem;
  min-height: 48px;
}
.wft-field textarea { min-height: 150px; resize: vertical; }
/* The one placeholder in the app (Requests' search field) was left at
   the browser's own UA default grey, which measured 3.25:1 in dark
   mode — under the 4.5:1 floor. --rst-text-muted is the same token
   every other piece of muted text already uses, measured well clear
   of that floor in both themes. */
[rst-input]::placeholder { color: var(--rst-text-muted); opacity: 1; }
/* Neither tokens.css nor this file styled [aria-invalid="true"] at
   all — the attribute was correctly set server-side on every rejected
   field (field-text.html's own partial does it whenever Error is
   passed), but nothing painted it, so "please fix the highlighted
   fields below" highlighted nothing. Matches the error text's own
   colour token, so the field and the message under it read as one
   signal. */
[rst-input][aria-invalid="true"],
[rst-textarea][aria-invalid="true"],
.wft-field input[aria-invalid="true"],
.wft-field textarea[aria-invalid="true"] {
  border-color: var(--rst-tone-negative-fg);
}
[rst-input][aria-invalid="true"]:focus-visible,
[rst-textarea][aria-invalid="true"]:focus-visible {
  outline-color: var(--rst-tone-negative-fg);
}
.wft-field input:focus-visible,
.wft-field textarea:focus-visible {
  outline: 3px solid var(--rst-accent);
  outline-offset: 1px;
  border-color: var(--rst-accent);
}

/* tokens.css's own :focus-visible rule is scoped to .rst-page and a
   handful of component roots (deliberately — :where() so an app rule
   can override it), which leaves the header bar and the footer — both
   outside .rst-page — on the browser's own default ring. Extending the
   same treatment here rather than editing the vendored tokens.css
   file directly. */
:where([rst-shell-topbar], [rst-shell-foot]) :focus-visible {
  outline: 2px solid var(--rst-accent);
  outline-offset: 2px;
}

/* tokens.css's mobile nav-disclosure summary is min-block-size: 24px
   (WCAG 2.2 AA's own floor) — bumped here, not in the vendored file,
   since this is the one nav control nearly every mobile visit taps. */
[rst-shell-topbar] [rst-shell-menu] > summary {
  min-block-size: 44px;
  box-sizing: border-box;
}

/* ---------- admin area: cross-nav tabs ----------
   Requests/Awarded/Reports used to live in the header nav alongside
   the marketing links; moved to a page-level tab row (now that the
   header shows only account info when signed in), using rastrillo's
   own segmented-tabs component rather than a hand-rolled one — but
   restyled flat (plain links, a rule under the whole row, the active
   tab getting a green outline box rather than a filled pill) to match
   the original admin tool's own tab look, not rst-seg-tabs's default
   pill-on-tinted-background style. */
.wft-admin-bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
  margin: 1rem 0 1.5rem;
  border-bottom: 1px solid var(--rst-line);
}
/* Below 640px the five tabs' flex-wrap broke the tab row itself onto a
   second line (with the last tab left orphaned alone on it) before
   "Show figures" ever got a chance to drop to its own row. One
   horizontally-scrollable strip reads as "there's more this way," the
   same affordance .wft-table-scroll already gives the tables below;
   wrapping mid-row instead read as the layout breaking. */
@media (max-width: 640px) {
  /* The regression this fixes: .wft-admin-tabs-wrap is itself a flex
     item of .wft-admin-bar, and a flex item's default min-width is
     auto — its own content's min-content size, not 0 — so the wrap
     (and the page under it) was forced as wide as all five tabs laid
     end to end, 44px past the viewport, before the nav's own
     overflow-x:auto ever got a chance to constrain and scroll it
     internally. min-width: 0 is what lets a flex item shrink smaller
     than its content and hand that content to its own scroll
     container instead of its parent's layout. */
  .wft-admin-tabs-wrap {
    min-width: 0;
    max-width: 100%;
  }
  .wft-admin-tabs[rst-seg-tabs] {
    flex-wrap: nowrap;
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
    max-width: 100%;
  }
  .wft-admin-tabs[rst-seg-tabs] a { flex: none; }
  /* Absolutely positioned over .wft-admin-tabs-wrap, not a child of
     the scrolling nav itself: a child would reserve its own 28px of
     the nav's flex layout and shift every tab's position/scroll width
     by that much. An overlay sits on top of the nav's own edges
     without taking any of its layout space or moving a single tab,
     and (unlike a ::after on the nav, which scrolls WITH the content
     and only ever shows at the true end) stays pinned to the visible
     edge of the scrollport at every scroll position — the cue a
     trustee needs is "there's more this way right now," not "you've
     reached the end." */
  .wft-admin-tabs-fade {
    display: block;
    position: absolute;
    inset-block: 0;
    inline-size: 28px;
    pointer-events: none;
  }
  .wft-admin-tabs-fade--start {
    inset-inline-start: 0;
    background: linear-gradient(to left, transparent, var(--rst-bg) 80%);
  }
  .wft-admin-tabs-fade--end {
    inset-inline-end: 0;
    background: linear-gradient(to right, transparent, var(--rst-bg) 80%);
  }
}
/* .wft-flat-tabs: the shared look for every segmented-tab row in the
   tool — the top-level admin tabs AND Cashbook's own Current/Savings/
   Investment switcher, which used to keep rst-seg-tabs's own default
   (a white pill floating on a tinted track) while the admin tabs got
   this flat treatment instead. Two tab rows with opposite active-state
   polarities on the same screen was the inconsistency; one ruleset,
   applied to both navs, is the fix. */
.wft-flat-tabs[rst-seg-tabs] {
  background: none;
  border-radius: 0;
  gap: 0.25rem;
  padding: 0;
  margin: 0;
  border-bottom: 0;
}
.wft-flat-tabs[rst-seg-tabs] a {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  padding: 0.6rem 1rem;
  border-radius: 6px;
  color: var(--rst-text-muted);
  font-weight: 600;
  box-sizing: border-box;
}
.wft-flat-tabs[rst-seg-tabs] a:hover { color: var(--rst-text); background: none; }
/* Was an inset box-shadow ring — the same shape, width and colour as
   :focus-visible's own outline inside .rst-page (tokens.css), so
   tabbing onto the current tab showed what looked like one ring with
   no way to tell "this is where you are" from "this is what you just
   focused." A filled background reads as "current location" without
   borrowing the focus ring's own visual vocabulary, so the two stay
   distinct when they land on the same tab at once. */
.wft-flat-tabs[rst-seg-tabs] a[aria-current] {
  background: var(--rst-accent-soft);
  color: var(--rst-accent);
  font-weight: 600;
}
/* Hidden by default — only meaningful once the strip scrolls, below;
   present in the markup unconditionally only so there's nothing to get
   conditionally wrong in the template at the breakpoint boundary. */
.wft-admin-tabs-wrap { position: relative; }
.wft-admin-tabs-fade { display: none; }

/* ---------- figures toggle ----------
   See static/figures-init.js (sets the default, before first paint)
   and figures-toggle.js (owns the button). Blurred rather than
   emptied or replaced: the screen keeps its normal shape and scrolls
   the same way whether figures are shown or not, so showing someone
   the system with this on doesn't look broken, just private. Targets
   the two hooks every admin table/stat already carries — [rst-stat-num]
   for KPI tiles, .wft-num for every numeric table cell (amounts and
   counts alike) — so a page needs no per-figure markup of its own.

   td.wft-num specifically, not the bare class: .wft-num also marks the
   numeric <th> in every one of these tables (for right-alignment), and
   the unqualified selector was blurring "Requested"/"Total awarded"/
   "Amount awarded" right along with the figures underneath them — the
   one thing a trustee needs legible to know what's hidden. */
[data-figures="hidden"] [rst-stat-num],
[data-figures="hidden"] td.wft-num {
  filter: blur(0.3em);
  user-select: none;
}

/* ---------- Requests: search, status filter, sortable headers ----------
   AGENTS.md named this a later revisit, once a list screen got busy
   enough for search/filter to matter — this is that revisit for
   Requests specifically (Awarded and Reports are untouched). A plain
   GET form, so it works with JS disabled, and sortable headers are
   plain links carrying the current q/status forward — no client-side
   table logic, the server re-queries and re-renders. */
.wft-grants-filter {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: 1rem;
}
.wft-grants-filter [rst-field] { margin: 0; flex: 1 1 14rem; }
.wft-table-scroll th a {
  color: inherit;
  text-decoration: none;
}
.wft-table-scroll th a:hover { text-decoration: underline; }
.wft-sort-hint { color: var(--rst-text-faint); font-weight: 400; }

/* ---------- admin tool: the plain <table> listings ----------
   AGENTS.md's settled decision: Requests/Awarded/Reports use plain
   <table> markup, not rst-list's grid idiom — named simple enough for
   three small listings that tokens.css itself never styles a bare
   <table>, so it rendered with the browser's own centred <th> and no
   column rhythm at all.

   .wft-table-scroll used to carry overflow-x: auto up here too, to
   contain a wide row inside the card rather than let it spill past
   the rounded border (a seven-figure award on a narrow screen, before
   this). That's gone: a <th> under position: sticky silently never
   sticks — it just sits at its static position and scrolls away with
   everything else — if any ancestor between it and the page is itself
   a scroll container, even one (like that div) that never actually
   has anything to scroll; confirmed with a stripped-down repro outside
   this app, nothing about this table or Chromium build specifically.
   Sticky and the horizontal-scroll fallback can't both live on this
   div. Measured instead: at 641–1024px wide, with a long organisation
   name and a seven-figure amount seeded in, none of these tables
   actually overflow their card any more — the stacked-card layout
   below 640px (further down this file) now carries the narrow-screen
   case on its own, which it didn't yet when the fallback was added.
   Revisit if a real listing ever does overflow again above 640px. */
.wft-table-scroll {
  overflow-x: visible;
}
.wft-table-scroll table {
  width: 100%;
  border-collapse: collapse;
  font-variant-numeric: tabular-nums;
}
/* Sticky: the header row stays visible at the top of the viewport as
   the page scrolls past a long listing. top: 89px clears
   [rst-shell-bar]'s own sticky header (z-index: 50) rather than
   sitting under it — 89px, not .wft-bar-inner's own min-block-size:
   72px: the bar itself adds tokens.css's shell-bar padding-block
   (0.5rem top and bottom, 16px) and its 1px border-block-end on top
   of that inner row, and this needs the bar's own rendered height,
   not its inner content box's minimum. Measured, not guessed — get it
   wrong and this header sits a few pixels into the real one instead
   of under it, invisibly: both read as "stuck", correct scroll
   offset, right text, nothing about it looks broken except that the
   real header is drawn on top (z-index: 50 against this one's 2) and
   hides the top slice. The z-index here only needs to clear the
   table's own rows as they scroll past, not the page header, so it
   stays well below 50. Needs its own opaque background — .rst-box is
   var(--rst-surface), not the page's background — or row text would
   show through the header as it scrolls underneath. Turned off below
   640px, where the stacked-card layout further down has no thead left
   to stick. */
.wft-table-scroll th {
  text-align: left;
  font-size: var(--rst-fs-sm);
  font-weight: 600;
  color: var(--rst-text-muted);
  white-space: nowrap;
  padding: 0 0.75rem 0.6rem 0;
  border-bottom: 1px solid var(--rst-line-strong);
  position: sticky;
  top: 89px;
  z-index: 2;
  background: var(--rst-surface);
}

/* A row a URL fragment jumps straight to (a broken-chain report
   linking to #e-N on /events or /security-log) otherwise lands with no
   visual marker at all — nothing on the page said "this one". The
   scroll-margin-top matches the sticky header's own `top: 89px` above,
   so the target row settles below it instead of partly hidden under
   it. Never colour alone: :target already carries its own meaning
   from the URL fragment itself (this IS the thing that was linked
   to), so a background tint alone is acceptable here, unlike a status
   that needs an independent text label. */
.wft-table-scroll tbody tr:target {
  scroll-margin-top: 100px;
  background: rgba(220, 38, 38, 0.08);
}
.wft-table-scroll td {
  padding: 0.65rem 0.75rem 0.65rem 0;
  border-bottom: 1px solid var(--rst-line);
  vertical-align: top;
}
/* The Category cell's own rst-dropdown summary carries tokens.css's
   shared 0.4rem top padding (sized for a comfortable click target,
   same as every other rst-dropdown in the app, e.g. the account
   menu) — stacked on top of this table's own 0.65rem cell padding,
   that pushed the pill noticeably lower than the plain text in every
   sibling cell on the same row. Negative margin-top cancels exactly
   that extra padding so the pill's own text lines up with its row,
   without shrinking the clickable area: the padding is still there,
   the whole padded box just sits 0.4rem higher. Safe because the cell
   itself has more top padding (0.65rem) than this pulls back (0.4rem)
   — the summary never escapes the cell's own padding box. Cashbook is
   the only table with a dropdown in a data cell; scoped narrowly
   rather than touching every rst-dropdown in the app. */
.wft-table-scroll td[data-th="Category"] [rst-dropdown] > summary {
  margin-top: -0.4rem;
}
/* Jumping to a row via #transaction-{id} (cashbook's category/review
   forms round-trip here, see cashbookReturnURL) landed the row flush
   with the viewport's own top edge — exactly where both sticky layers
   sit on top of it (the shell bar, 89px, and this table's own th row
   stacked under it, ~29px measured in a real browser), so the row the
   trustee just edited was scrolled TO, then hidden behind both of
   them: the next row down was the first one actually visible, reading
   as "it jumped past the line I changed." scroll-margin-top reserves
   that space for the browser's own anchor/scrollIntoView scroll, same
   fix as a fixed-header site needs for any #anchor link. Measured, not
   guessed — a few px of headroom on top of the exact 89+29 sum. */
.wft-table-scroll tbody tr {
  scroll-margin-top: 126px;
}
.wft-table-scroll tr:last-child td { border-bottom: 0; }
.wft-table-scroll th:last-child,
.wft-table-scroll td:last-child { padding-inline-end: 0; }
.wft-table-scroll .wft-num {
  text-align: right;
}
.wft-table-scroll .wft-nowrap {
  white-space: nowrap;
}

/* Below 640px the horizontal scroll above stopped being a scroll and
   started being a clip: the viewport is narrower than even the first
   few columns' min-content width added together, so Status — the one
   column the app's own convention insists must "never [rely on]
   colour alone, always a text label" — sat mostly off-screen to the
   right of a 390px phone with no visible cue that there was more to
   scroll to. Stacked cards instead: each row becomes its own bordered
   block, each cell keeps its column header as an inline label via
   data-th (set once per <td> in the three templates that use this
   class), and the organisation-name cell — already the row's own link
   — doubles as the card's title rather than repeating "Organisation"
   above itself. This does trade away native <table> semantics for a
   screen reader at this width, the same trade AGENTS.md already named
   for the horizontal-scroll table itself ("revisit if the list
   screens get busy enough... to matter") — three small listings, not
   a data grid a screen-reader user needs row/column navigation
   through. */
@media (max-width: 640px) {
  .wft-table-scroll { overflow-x: visible; margin: 0; padding: 0; }
  .wft-table-scroll table,
  .wft-table-scroll tbody,
  .wft-table-scroll tr,
  .wft-table-scroll th,
  .wft-table-scroll td { display: block; width: auto; }
  .wft-table-scroll th { position: static; }
  .wft-table-scroll thead tr {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip: rect(0 0 0 0);
    white-space: nowrap;
    border: 0;
    padding: 0;
    margin: -1px;
  }
  /* Requests is the one table in the tool with real sort links in its
     header (Organisation/Requested/Status/Received) — visually hiding
     the whole thead along with the other, link-free tables took sort
     itself away from a sighted mobile user, not just its old table-row
     presentation. A compact wrapped strip above the cards keeps every
     link (and the column it belongs to) genuinely visible, not just
     present for a screen reader. */
  .wft-table-scroll--sortable thead tr {
    position: static;
    width: auto;
    height: auto;
    overflow: visible;
    clip: auto;
    white-space: normal;
    display: flex;
    flex-wrap: wrap;
    gap: 0.4rem 0.9rem;
    margin: 0 0 0.85rem;
    padding: 0;
  }
  .wft-table-scroll--sortable th {
    display: inline-flex;
    padding: 0;
    border: 0;
    font-size: var(--rst-fs-sm);
  }
  .wft-table-scroll tbody tr {
    border: 1px solid var(--rst-line);
    border-radius: var(--rst-radius-sm);
    padding: 0.85rem 1rem;
    margin-bottom: 0.75rem;
  }
  .wft-table-scroll tbody tr:last-child { margin-bottom: 0; }
  .wft-table-scroll td {
    border-bottom: 0;
    padding: 0.3rem 0;
    text-align: left !important;
  }
  .wft-table-scroll td[data-th]::before {
    content: attr(data-th);
    display: block;
    font-size: var(--rst-fs-sm);
    font-weight: 600;
    color: var(--rst-text-muted);
    margin-bottom: 0.15rem;
  }
  .wft-table-scroll td.wft-td-title {
    padding-top: 0;
    font-weight: 600;
    font-size: 1.05rem;
  }
  .wft-table-scroll td.wft-td-title::before { content: none; }
  /* The event log's Hash cell is one long unbroken string — the one
     cell in any of these three tables that isn't naturally short, so
     it's the one that needs an explicit wrap permission rather than
     silently overflowing its own card. */
  .wft-table-scroll .wft-hash code { overflow-wrap: anywhere; }
}

/* Cashbook's Category cell wraps the status-pill trigger in
   [rst-dropdown]'s own summary, whose padding (tokens.css) stacks on
   top of the pill's own inline-start padding — the dot ends up ~18px
   right of the column's left edge instead of lined up under the
   "Category" header the way every other column's content is.
   Zeroing just the summary's own inline-start padding leaves the
   pill's padding (shared by every status-pill in the app) as the only
   offset, same as the Actions column's pill below. */
.wft-table-scroll [data-th="Category"] [rst-dropdown] > summary {
  padding-inline-start: 0;
}

/* Cashbook's Actions cell: a status-pill plus a single toggle used to
   be a bare [rst-btn], which wrapped onto two lines the moment the
   column narrowed below "Mark unreviewed"'s own width and read as a
   stray box. A row-menu kebab (tokens.css's own per-row action idiom)
   keeps a fixed-width trigger regardless of label length; the pill
   stays inline beside it so the state is still readable without
   opening the menu. */
.wft-row-actions {
  display: flex;
  align-items: center;
  gap: 0.4rem;
}

/* ---------- Inbox: a synced email's own body ----------
   Graph hands this app plain text (see graph.go's Prefer header), so
   there is never HTML here to sanitise — just line breaks to keep. A
   <pre> already preserves them; the one rule needed on top is wrapping
   a long line instead of making the card scroll sideways, which plain
   <pre> does not do on its own. */
.wft-email-body {
  white-space: pre-wrap;
  word-wrap: break-word;
  font-family: inherit;
}

/* A grant's own Summary/Notes are a <p>, not a <pre> — plain text a
   trustee or applicant typed with real line breaks in a <textarea>,
   which html/template's default escaping keeps as literal "\n"
   characters a <p> otherwise collapses into one run-on paragraph.
   pre-line (not pre-wrap) is the right rule for prose text: it keeps
   the line breaks but still collapses any run of plain spaces, which
   pre-wrap would preserve literally. */
.wft-prewrap {
  white-space: pre-line;
}

/* ---------- Event log: a CRUD entry's own payload ----------
   Collapsed by default (<details>, no JS needed) so the common case —
   skimming the Summary column — stays uncluttered; a trustee who wants
   the actual field values opens it per row. */
.wft-event-payload {
  margin-top: 0.35rem;
}
.wft-event-payload summary {
  cursor: pointer;
  font-size: var(--rst-fs-xs);
  color: var(--rst-text-muted);
}
.wft-event-payload pre {
  background: var(--rst-surface-2);
  border: 1px solid var(--rst-line);
  border-radius: var(--rst-radius-sm);
  margin: 0.4rem 0 0;
  padding: 0.6rem 0.75rem;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: var(--rst-fs-xs);
  white-space: pre-wrap;
  word-wrap: break-word;
}

/* ---------- Pagination: "go to page" ----------
   Styling for chrome.html's own override of the vendored "pagination"
   partial (adapted from the sibling ips app) — a small GET form beside
   the numbered strip, and the number field the current-page pill turns
   into on click (pagergoto.js).

   The current-page pill is a <button> (pagergoto.js turns a click on
   it into the number field beside it) — tokens.css's own shared pill
   rule only reaches `a` and `span`, the two elements the vendored
   partial used, so a bare button carried none of it: no border, no
   radius, no padding, nothing but the browser's own button chrome
   under the [aria-current="page"] fill tokens.css already paints on
   top. That's the flat, ill-shaped block a trustee saw instead of a
   pill. Restating the shared pill properties for `button` here, after
   tokens.css, is the fix — can't extend that file's own selector list
   without editing a file this app keeps purely vendored. */
[rst-pagination] button {
  appearance: none;
  background: none;
  border: 1px solid var(--rst-line);
  border-radius: var(--rst-radius-sm);
  color: var(--rst-text-muted);
  cursor: pointer;
  display: inline-block;
  font: inherit;
  font-size: var(--rst-fs-sm);
  min-inline-size: 2rem;
  padding: 0.3rem 0.6rem;
  text-align: center;
}
/* [hidden] has to win over that shared `display: inline-block` two
   rules up, or toggling it from script — the pill swapping for the
   number field it becomes, the fallback goto box once the pill takes
   over — changes nothing on screen, since a plain [hidden] alone
   loses that fight on specificity. */
[rst-pagination] [hidden] {
  display: none;
}
[rst-pagination] > [data-pager-goto-input] {
  border-radius: var(--rst-radius-sm);
  font-size: var(--rst-fs-sm);
  font-variant-numeric: tabular-nums;
  padding: 0.3rem 0.4rem;
  text-align: center;
  width: 2.75rem;
}
[rst-pagination-goto] {
  align-items: center;
  display: flex;
  gap: 0.4rem;
  margin-inline-start: var(--rst-sp-2);
}
[rst-pagination-goto] label {
  color: var(--rst-text-muted);
  font-size: var(--rst-fs-sm);
  white-space: nowrap;
}
[rst-pagination-goto] [rst-input] {
  min-inline-size: 3.5rem;
  padding: 0.3rem 0.4rem;
  text-align: center;
}
/* "Showing 51–55 of 55" beside the pager, pushed apart by
   space-between the same way ips's own .ips-list-foot does it — the
   summary reads first (left, where a reader's eye already is after
   the table), the pager sits at the right where "turn the page" is
   expected. Wraps to its own line under the pager on a narrow screen
   rather than squeezing the numbered strip. nav's own block margin
   (tokens.css) is reset to 0 here: the row wrapper owns the spacing
   above/below the pair instead, or the two would double up. */
.wft-pager-row {
  align-items: center;
  display: flex;
  flex-wrap: wrap;
  gap: var(--rst-sp-3);
  justify-content: space-between;
  margin: var(--rst-sp-4) 0;
}
.wft-pager-row [rst-pagination] {
  margin: 0;
}
.wft-pager-summary {
  color: var(--rst-text-muted);
  font-size: var(--rst-fs-sm);
  margin: 0;
}

/* An unread row reads heavier, the same bold-row convention every
   mail client uses — weight, not colour alone, carries the signal
   (state-never-colour-alone applies to text weight too: a screen
   reader doesn't announce bold, but nothing here is ONLY told through
   it — Status is still a plain-text pill regardless). */
.wft-inbox-unread td { font-weight: 600; }

/* The nav badge on "Inbox" — see admin-tabs in chrome.html. A number,
   not a dot: "state is never colour alone" extends to "never a bare
   dot" here too — a trustee needs the count, not just "something's
   new". */
.wft-nav-badge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 1.25rem;
  height: 1.25rem;
  padding: 0 0.35rem;
  margin-inline-start: 0.35rem;
  border-radius: var(--rst-radius-pill);
  background: var(--rst-accent);
  color: var(--rst-on-accent);
  font-size: var(--rst-fs-sm);
  font-weight: 600;
  line-height: 1;
}

/* ---------- Inbox: folder filter strip ----------
   A three-pane Outlook-style layout (folders | list | reading pane)
   lived here briefly on 2026-10-03 and was reverted the same day —
   see AGENTS.md. This is what's left of it: a horizontal strip of
   status filters with counts, replacing the old status <select>+View
   form — a trustee wants to switch what they're looking at, not
   describe a query. */
.wft-inbox-folders {
  display: flex;
  flex-wrap: wrap;
  gap: var(--rst-sp-2);
  margin-bottom: var(--rst-sp-4);
}
.wft-inbox-folders a {
  display: flex;
  align-items: center;
  gap: var(--rst-sp-2);
  padding: var(--rst-sp-2) var(--rst-sp-3);
  border-radius: var(--rst-radius-sm);
  color: var(--rst-text);
  text-decoration: none;
}
.wft-inbox-folders a:hover { background: var(--rst-surface); }
.wft-inbox-folders a[aria-current] {
  background: var(--rst-accent-soft);
  font-weight: 600;
}
.wft-inbox-folder-count {
  font-size: var(--rst-fs-sm);
  color: var(--rst-text-muted);
}
.wft-inbox-folders a[aria-current] .wft-inbox-folder-count { color: inherit; }

/* Previous/Newer-Older navigation on a message's own page — see
   showInbox/prevNextInbox in inbox.go. */
.wft-inbox-prev-next {
  display: flex;
  justify-content: space-between;
  margin-top: var(--rst-sp-4);
}

/* The Trust-year select ("6 Apr 2024 – 5 Apr 2025") was rendered
   field-select's Short variant (tokens.css's [rst-input~="short"],
   8rem — sized for a postcode, not a date range), which clipped the
   label to "6 Apr 2024 – 5 /". Full-width by default (reports.html no
   longer passes Short), capped here so it doesn't stretch edge to
   edge of the box on a wide desktop viewport. */
/* [rst-input], not .rst-input: field-select's own partial renders
   rst-input as a bare attribute, never a class, so the class-selector
   version of this rule never matched anything — confirmed by an
   independent Fable-model audit, this had been a dead rule from the
   day it was written, and the select has been full-width in
   production the whole time. */
#year[rst-input] { max-width: 16rem; }
