/* ==========================================================================
   Carlton for Council, Phase 1 landing page
   Design system + page styles
   Brand colors are derived from the campaign yard-sign logo.
   ========================================================================== */

/* --------------------------------------------------------------------------
   1. Tokens
   -------------------------------------------------------------------------- */
:root {
  /* Brand, supplied by the campaign */
  --orange: hsl(32.5deg 92.31% 54.12%);
  --blue: hsl(205.36deg 80.77% 40.78%);
  --charcoal: hsl(228deg 4.5% 21.76%);

  /* Derived tints and shades */
  --orange-600: hsl(32.5deg 88% 48%);
  --orange-700: hsl(30deg 85% 42%);
  --orange-100: hsl(32.5deg 92% 95%);

  --blue-900: hsl(205.36deg 82% 17%);
  --blue-800: hsl(205.36deg 81% 24%);
  --blue-700: hsl(205.36deg 81% 32%);
  --blue-600: hsl(205.36deg 80.77% 40.78%);
  --blue-500: hsl(205.36deg 70% 50%);
  --blue-100: hsl(205deg 60% 94%);
  --blue-50: hsl(205deg 55% 97%);

  --charcoal-900: hsl(228deg 6% 14%);
  --charcoal-800: hsl(228deg 4.5% 21.76%);
  --charcoal-600: hsl(228deg 4% 34%);
  /* 42% lightness keeps small muted labels above WCAG AA (4.5:1) on white. */
  --charcoal-400: hsl(228deg 5% 42%);

  /* Neutrals */
  --white: #fff;
  --paper: hsl(40deg 20% 98%);
  --line: hsl(210deg 14% 89%);
  --line-strong: hsl(210deg 14% 80%);

  /* Semantic */
  --text: var(--charcoal-800);
  --text-muted: var(--charcoal-600);
  --text-quiet: var(--charcoal-400);
  --bg: var(--white);

  /* Type scale: fluid, capped so nothing is oversized on desktop */
  --step--1: clamp(0.82rem, 0.79rem + 0.14vw, 0.89rem);

  /* Uppercase micro-labels (eyebrows, field labels, status lines, the outlet
     name, the scroll cue). These letter-spaced caps used to be set in raw rem
     values between 0.68 and 0.74rem, which rendered at 10.9 to 11.8px, below the
     12px readability floor. One token now holds them all at a 12px minimum. */
  --label: clamp(0.75rem, 0.72rem + 0.12vw, 0.8rem);
  --step-0: clamp(1rem, 0.96rem + 0.19vw, 1.09rem);
  --step-1: clamp(1.19rem, 1.11rem + 0.36vw, 1.36rem);
  --step-2: clamp(1.41rem, 1.28rem + 0.62vw, 1.7rem);
  --step-3: clamp(1.67rem, 1.46rem + 1.01vw, 2.13rem);
  --step-4: clamp(1.98rem, 1.66rem + 1.6vw, 2.66rem);
  --step-5: clamp(2.36rem, 1.85rem + 2.44vw, 3.32rem);
  --step-6: clamp(2.8rem, 2.03rem + 3.63vw, 4.16rem);

  /* ------------------------------------------------------------------------
     HERO CANDIDATE NAME: "Jason Carlton"
     ------------------------------------------------------------------------
     These control the hero name and NOTHING else. Change the size here (or in
     the breakpoint blocks at the end of this file) and only that heading moves.
     The strapline, lede, badge, dates, buttons and nav are all unaffected.
     ------------------------------------------------------------------------ */
  --hero-candidate-name-size: 82px;
  --hero-candidate-name-weight: 700;
  --hero-candidate-name-leading: 0.94;
  --hero-candidate-name-tracking: -0.01em;
  --hero-candidate-name-transform: uppercase;
  --hero-candidate-name-margin: 0;
  --hero-candidate-name-family: var(--font-display);
  --hero-candidate-name-color: var(--white);

  /* The strapline under the name. Independent of the size above. */
  --hero-strapline-size: 28px;

  /* Spacing */
  --sp-1: 0.25rem;
  --sp-2: 0.5rem;
  --sp-3: 0.75rem;
  --sp-4: 1rem;
  --sp-5: 1.5rem;
  --sp-6: 2rem;
  --sp-7: 3rem;
  --sp-8: 4rem;
  --sp-9: 6rem;

  /* Layout */
  --shell: 1200px;
  --shell-narrow: 760px;
  /* Wider container used ONLY by the trust strip, which needs more room than
     the article measure to keep its four items on one line. */
  --shell-wide: 1480px;
  --gutter: clamp(1.25rem, 4vw, 3rem);
  --header-h: 76px;

  /* Radii */
  --r-sm: 4px;
  --r-md: 8px;
  --r-lg: 14px;
  --r-xl: 22px;

  /* Shadows, restrained, layered */
  --sh-sm: 0 1px 2px hsl(220deg 20% 20% / 0.06), 0 1px 1px hsl(220deg 20% 20% / 0.04);
  --sh-md: 0 2px 4px hsl(220deg 20% 20% / 0.05), 0 6px 16px -6px hsl(220deg 20% 20% / 0.1);
  --sh-lg: 0 4px 8px hsl(220deg 20% 20% / 0.05), 0 18px 40px -12px hsl(220deg 25% 18% / 0.16);
  --sh-orange: 0 2px 6px hsl(30deg 90% 40% / 0.22), 0 10px 24px -8px hsl(30deg 90% 40% / 0.34);

  --ease: cubic-bezier(0.22, 0.61, 0.36, 1);
  --dur: 220ms;

  /* --------------------------------------------------------------------------
     TYPE
     --------------------------------------------------------------------------
     Body is Public Sans, the US Web Design System face, drawn for public-sector
     text that has to be readable by everyone. Taller x-height and more open
     apertures than Inter, and clearer numerals, which matters on a page built
     around three dates.

     Display is Archivo, a VARIABLE font with a real width axis. That is the
     whole reason it was chosen: one family covers condensed and normal width
     through `font-stretch`, so the poster voice on the big name and the
     readable width on smaller headings come from the same typeface.

     There is no family called "Archivo Condensed" on Google Fonts. It used to
     exist and is now folded into the width axis; requesting that name returns
     a 400. Set width with `font-stretch`, never by changing the family.

     THE RULE THAT MATTERS: do not use a narrow width below about 18px. The
     --display-width-* tokens below exist so that stays consistent.
     -------------------------------------------------------------------------- */
  --font-sans: "Public Sans", -apple-system, BlinkMacSystemFont, "Segoe UI",
    Roboto, "Helvetica Neue", Arial, sans-serif;
  --font-display: "Archivo", "Public Sans", -apple-system, BlinkMacSystemFont,
    "Segoe UI", sans-serif;

  /* Width steps. Narrower as the type gets bigger, never the other way round. */
  /* The hero name has to stay on ONE line, and that sets this number.
     Measured: the hero's left column is 528px at a 1200px viewport, and the
     name renders 508px there, leaving about 20px of slack. Archivo's width
     axis stops at 62%, so there is no room to narrow the type further.
     Raise this width, or the sizes in section 17b, and the name wraps. */
  --display-width-poster: 70%;   /* hero name only, the biggest thing on the page */
  --display-width-xl:     79%;   /* section titles, the donate headline */
  --display-width-lg:     84%;   /* mid headings, pull quote */
  --display-width-md:     90%;   /* small headings, date values, numerals */
}

/* --------------------------------------------------------------------------
   2. Reset / base
   -------------------------------------------------------------------------- */
*,
*::before,
*::after { box-sizing: border-box; }

html {
  scroll-behavior: smooth;
  scroll-padding-top: calc(var(--header-h) + 16px);
  -webkit-text-size-adjust: 100%;
}

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: var(--font-sans);
  font-size: var(--step-0);
  line-height: 1.65;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  overflow-x: hidden;
}

body.is-locked { overflow: hidden; }

h1, h2, h3, h4 {
  margin: 0;
  font-family: var(--font-display);
  /* Default width for headings. The big ones narrow further via their own
     rules; the small ones (h3 card titles at ~19px) stay here, where the text
     is still open enough to read at a glance. */
  font-stretch: var(--display-width-md);
  font-weight: 700;
  line-height: 1.06;
  letter-spacing: -0.005em;
  color: var(--charcoal-900);
  text-wrap: balance;
}

p { margin: 0 0 var(--sp-4); text-wrap: pretty; }
p:last-child { margin-bottom: 0; }

a { color: var(--blue-700); text-decoration-thickness: 1px; text-underline-offset: 3px; }

img, svg { max-width: 100%; height: auto; display: block; }

ul { margin: 0; padding: 0; list-style: none; }

:focus-visible {
  outline: 3px solid var(--blue-500);
  outline-offset: 3px;
  border-radius: var(--r-sm);
}

::selection { background: var(--orange); color: var(--charcoal-900); }

/* Sections given tabindex="-1" purely as scroll destinations should not draw
   a focus ring around the whole band; the scroll itself is the feedback. */
section[tabindex="-1"]:focus,
section[tabindex="-1"]:focus-visible { outline: none; }

.skip-link {
  position: absolute;
  top: 0;
  left: var(--sp-4);
  z-index: 200;
  transform: translateY(-120%);
  padding: var(--sp-3) var(--sp-5);
  background: var(--charcoal-900);
  color: var(--white);
  font-weight: 600;
  border-radius: 0 0 var(--r-md) var(--r-md);
  text-decoration: none;
  transition: transform var(--dur) var(--ease);
}
.skip-link:focus { transform: translateY(0); }

.visually-hidden {
  position: absolute !important;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

/* --------------------------------------------------------------------------
   3. Layout primitives
   -------------------------------------------------------------------------- */
.shell {
  width: 100%;
  max-width: var(--shell);
  margin-inline: auto;
  padding-inline: var(--gutter);
}

/* Vertical rhythm, three tiers. Each clamp runs from a compact mobile value to
   a generous desktop one, so sections breathe on large screens without leaving
   dead space on a phone.
     major  : 56px mobile -> 112px desktop  (Meet Jason, Support)
     default: 52px mobile ->  96px desktop  (Priorities, Press, Election, Contact)
     tight  : 40px mobile ->  64px desktop  (utility bands) */
.section { padding-block: clamp(3.25rem, 7vw, 5rem); }
.section--major { padding-block: clamp(3.5rem, 8vw, 7rem); }
.section--tight { padding-block: clamp(2.5rem, 5vw, 4rem); }

.section-head { max-width: 62ch; margin-bottom: var(--sp-7); }

/* Contact section only. The form sits close under this head, so it takes less
   space below it than the other section heads do.
   1.5625rem = 25px, moved out of an inline style and kept at exactly that
   value rather than snapped to the --sp-* scale. */
.section-head--contact { margin-bottom: 1.5625rem; }
.section-head--center { margin-inline: auto; text-align: center; }

.eyebrow {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  margin-bottom: var(--sp-4);
  font-family: var(--font-sans);
  font-size: var(--step--1);
  font-weight: 700;
  letter-spacing: 0.13em;
  text-transform: uppercase;
  color: var(--blue-700);
}
.eyebrow::before {
  content: "";
  width: 26px;
  height: 3px;
  background: var(--orange);
  border-radius: 2px;
}
.eyebrow--center::before { display: none; }
.eyebrow--light { color: hsl(205deg 70% 82%); }

.section-title {
  font-stretch: var(--display-width-xl);
  font-size: var(--step-4);
  text-transform: uppercase;
  letter-spacing: 0.005em;
}

.section-intro {
  margin-top: var(--sp-4);
  font-size: var(--step-1);
  line-height: 1.55;
  color: var(--text-muted);
}

.rule {
  height: 1px;
  border: 0;
  margin: 0;
  background: var(--line);
}

/* --------------------------------------------------------------------------
   4. Buttons
   -------------------------------------------------------------------------- */
.btn {
  --btn-bg: var(--blue-600);
  --btn-fg: var(--white);
  --btn-bd: transparent;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--sp-3);
  min-height: 48px;
  padding: 0.85rem 1.6rem;
  border: 1.5px solid var(--btn-bd);
  border-radius: var(--r-md);
  background: var(--btn-bg);
  color: var(--btn-fg);
  font-family: var(--font-sans);
  font-size: var(--step-0);
  font-weight: 650;
  line-height: 1.1;
  text-decoration: none;
  cursor: pointer;
  transition: transform var(--dur) var(--ease), background-color var(--dur) var(--ease),
    box-shadow var(--dur) var(--ease), border-color var(--dur) var(--ease),
    color var(--dur) var(--ease);
}
.btn:hover { transform: translateY(-1px); }
.btn:active { transform: translateY(0); }

.btn--donate {
  --btn-bg: var(--orange);
  --btn-fg: var(--charcoal-900);
  box-shadow: var(--sh-orange);
  font-weight: 700;
  letter-spacing: 0.01em;
}
.btn--donate:hover { --btn-bg: var(--orange-600); }

.btn--ghost {
  --btn-bg: transparent;
  --btn-fg: var(--charcoal-900);
  --btn-bd: var(--line-strong);
}
.btn--ghost:hover { --btn-bd: var(--charcoal-900); --btn-bg: hsl(210deg 20% 96%); }

.btn--ghost-light {
  --btn-bg: hsl(0deg 0% 100% / 0.06);
  --btn-fg: var(--white);
  --btn-bd: hsl(0deg 0% 100% / 0.4);
}
.btn--ghost-light:hover { --btn-bg: hsl(0deg 0% 100% / 0.14); --btn-bd: var(--white); }

.btn--lg { min-height: 58px; padding: 1.05rem 2.2rem; font-size: var(--step-1); }
/* 44px floor keeps every button a valid touch target. */
.btn--sm { min-height: 44px; padding: 0.6rem 1.2rem; font-size: var(--step--1); }
.btn--block { width: 100%; }

.btn__icon { flex: none; transition: transform var(--dur) var(--ease); }
.btn:hover .btn__icon { transform: translateX(3px); }

/* --------------------------------------------------------------------------
   5. Header
   -------------------------------------------------------------------------- */
/* Fixed, not sticky, so the hero runs underneath it and the header can be
   transparent at the top of the page. The hero adds its own top padding to
   compensate; every other section is reached via scroll-padding-top, so
   nothing is ever hidden behind it and no layout shift occurs on state change.
   Both states paint the same box (same height, same border width), only
   colour, shadow and blur animate. */
.header {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 100;
  background: transparent;
  border-bottom: 1px solid transparent;
  transition: background-color 320ms var(--ease), border-color 320ms var(--ease),
    box-shadow 320ms var(--ease), backdrop-filter 320ms var(--ease);
}

/* Scrolled state: the white sticky header. */
.header.is-stuck {
  background: hsl(0deg 0% 100% / 0.9);
  backdrop-filter: saturate(180%) blur(14px);
  -webkit-backdrop-filter: saturate(180%) blur(14px);
  border-bottom-color: var(--line);
  box-shadow: 0 1px 20px -8px hsl(220deg 30% 20% / 0.22);
}

/* ---- Transparent state over the hero ------------------------------------
   The hero is dark blue, so controls invert to white while the header is
   transparent. Contrast is verified against the hero's own background. ---- */
.header:not(.is-stuck) .brand__name { color: var(--white); }
/* Slightly lighter than --orange: the brand orange lands at 4.49:1 on the hero
   blue, a hair under AA for this 11px label. 62% lightness clears it. */
.header:not(.is-stuck) .brand__sub  { color: var(--orange); }
/* Nothing to do for the mark here. It is a transparent orange sun with no
   backplate, and orange sits clearly on both the dark hero and the white
   stuck header, so it needs no state change of its own. */
.header:not(.is-stuck) .nav__link { color: hsl(205deg 45% 90%); }
.header:not(.is-stuck) .nav__link:hover,
.header:not(.is-stuck) .nav__link[aria-current="true"] { color: var(--white); }
.header:not(.is-stuck) .burger {
  background: hsl(0deg 0% 100% / 0.08);
  border-color: hsl(0deg 0% 100% / 0.38);
}
.header:not(.is-stuck) .burger:hover { border-color: var(--white); }
.header:not(.is-stuck) .burger__bar { background: var(--white); }

/* The drawer is opaque white, so the header reverts to its dark-on-light
   controls whenever the mobile menu is open, whatever the scroll position. */
.header.is-menu-open,
.header.is-menu-open.is-stuck {
  background: var(--white);
  border-bottom-color: var(--line);
}
.header.is-menu-open .brand__name { color: var(--charcoal-900); }
.header.is-menu-open .brand__sub { color: var(--blue-700); }
.header.is-menu-open .burger { background: var(--white); border-color: var(--line-strong); }
.header.is-menu-open .burger__bar { background: var(--charcoal-900); }

/* The brand mark and controls fade colour in step with the background. */
.brand__name,
.brand__sub,
.brand__mark,
.nav__link,
.burger,
.burger__bar {
  transition: color 320ms var(--ease), background-color 320ms var(--ease),
    border-color 320ms var(--ease);
}

.header__inner {
  display: flex;
  align-items: center;
  gap: var(--sp-5);
  min-height: var(--header-h);
}

/* Brand lockup: logo mark + typeset wordmark.
   The mark is a swappable asset; layout does not depend on its dimensions. */
.brand {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-3);
  margin-right: auto;
  text-decoration: none;
  color: inherit;
}
/* The mark is the sun motif from the campaign sign, a transparent PNG with no
   tile, card, border or backplate, so it sits directly on the header in both
   of its states. Square, which is the shape a 76px header has room for.

   The orange reads on the dark hero and on white once the header sticks. It is
   a logotype, which WCAG exempts from the text contrast minimum, so the brand
   colour is used as-is rather than darkened.

   The favicon keeps its blue tile: a transparent mark disappears against a
   dark browser tab, which the header never is. */
.brand__mark {
  flex: none;
  width: 55px;
  height: 55px;
}
.brand__mark img { width: 100%; height: 100%; object-fit: contain; display: block; }
/* min-width: 0 lets this shrink instead of shoving the menu button off the
   edge. The qualifier below is nowrap and sized to about 6px of slack at the
   narrowest width it appears on, so if a device renders the font a shade wider
   than measured it truncates quietly rather than breaking the header. */
.brand__text { display: flex; flex-direction: column; line-height: 1; min-width: 0; }
.brand__name {
  font-family: var(--font-display);
  /* Scales down on narrow phones, where the header also carries the Donate
     button and the menu toggle, so the wordmark has to fit alongside them. */
  font-size: clamp(1.05rem, 4.2vw, 1.63rem);
  font-weight: 700;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  color: var(--charcoal-900);
  white-space: nowrap;
}
.brand__sub {
  margin-top: 4px;
  overflow: hidden;
  text-overflow: ellipsis;
  font-size: var(--label);
  font-weight: 600;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--blue-700);
  white-space: nowrap;
}
/* The qualifier used to be hidden below ~26em, which covered most phones. It
   fits on them, just not at the desktop tracking.

   Measured with the real fonts rather than guessed. "FOR CITY COUNCIL .
   DISTRICT 6" in Public Sans 600 needs 216px at 12px/0.09em, and a 375px
   viewport leaves only 205px once the 48px mark, the gaps and the 46px menu
   button are taken off. At 0.02em it needs 192px, and trimming the header gap
   to 16px gives a 360px viewport 198px to put it in.

   Below 360px it is hidden again, and that is not conservatism: "JASON
   CARLTON" alone needs 155px there against 150px available, so the name is
   already the thing under pressure. */
@media (max-width: 61.99em) {
  .header__inner { gap: var(--sp-4); }
  .brand__sub { letter-spacing: 0.02em; }
}
@media (max-width: 22.49em) {
  .brand__sub { display: none; }
}

.nav { display: none; }
.nav__list { display: flex; align-items: center; gap: clamp(0.5rem, 1.6vw, 1.4rem); }
.nav__link {
  position: relative;
  display: flex;
  align-items: center;
  min-height: 44px;
  padding: var(--sp-2) var(--sp-2);
  color: var(--charcoal-600);
  font-size: 0.95rem;
  font-weight: 600;
  text-decoration: none;
  transition: color var(--dur) var(--ease);
}
.nav__link::after {
  content: "";
  position: absolute;
  left: var(--sp-2);
  right: var(--sp-2);
  /* Sits just under the text rather than at the foot of the 44px hit area. */
  bottom: 9px;
  height: 2px;
  background: var(--orange);
  border-radius: 2px;
  transform: scaleX(0);
  transform-origin: left;
  transition: transform var(--dur) var(--ease);
}
.nav__link:hover,
.nav__link[aria-current="true"] { color: var(--charcoal-900); }
.nav__link:hover::after,
.nav__link[aria-current="true"]::after { transform: scaleX(1); }

.header__cta { display: none; }

.burger {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 46px;
  height: 46px;
  padding: 0;
  border: 1.5px solid var(--line-strong);
  border-radius: var(--r-md);
  background: var(--white);
  cursor: pointer;
  transition: border-color var(--dur) var(--ease), background-color var(--dur) var(--ease);
}
.burger:hover { border-color: var(--charcoal-900); }
.burger__box { position: relative; width: 20px; height: 14px; }
.burger__bar {
  position: absolute;
  left: 0;
  width: 100%;
  height: 2px;
  border-radius: 2px;
  background: var(--charcoal-900);
  transition: transform var(--dur) var(--ease), opacity var(--dur) var(--ease);
}
.burger__bar:nth-child(1) { top: 0; }
.burger__bar:nth-child(2) { top: 6px; }
.burger__bar:nth-child(3) { top: 12px; }
.burger[aria-expanded="true"] .burger__bar:nth-child(1) { transform: translateY(6px) rotate(45deg); }
.burger[aria-expanded="true"] .burger__bar:nth-child(2) { opacity: 0; }
.burger[aria-expanded="true"] .burger__bar:nth-child(3) { transform: translateY(-6px) rotate(-45deg); }

/* Mobile drawer */
.mobile-nav {
  position: fixed;
  inset: var(--header-h) 0 0;
  z-index: 99;
  display: grid;
  grid-template-rows: 1fr auto;
  background: var(--white);
  padding: var(--sp-6) var(--gutter) calc(var(--sp-6) + env(safe-area-inset-bottom));
  overflow-y: auto;
  opacity: 0;
  visibility: hidden;
  transform: translateY(-8px);
  transition: opacity var(--dur) var(--ease), transform var(--dur) var(--ease),
    visibility var(--dur) var(--ease);
}
.mobile-nav.is-open { opacity: 1; visibility: visible; transform: translateY(0); }
.mobile-nav__list { display: flex; flex-direction: column; }
.mobile-nav__link {
  display: block;
  padding: var(--sp-4) 0;
  border-bottom: 1px solid var(--line);
  color: var(--charcoal-900);
  /* Body face, not display: navigation, not display type. */
  font-family: var(--font-sans);
  font-size: var(--step-2);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.02em;
  text-decoration: none;
}
.mobile-nav__link:hover { color: var(--blue-700); }
.mobile-nav__foot { padding-top: var(--sp-6); }
.mobile-nav__meta {
  margin-top: var(--sp-4);
  font-size: var(--step--1);
  color: var(--text-muted);
  text-align: center;
}

/* --------------------------------------------------------------------------
   6. Hero
   -------------------------------------------------------------------------- */
.hero {
  position: relative;
  overflow: hidden;
  background: linear-gradient(168deg, var(--blue-800) 0%, var(--blue-900) 68%, hsl(210deg 60% 12%) 100%);
  color: var(--white);
  isolation: isolate;
}
/* Sunrise ray motif, lifted from the logo mark. Decorative only. */
.hero::before {
  content: "";
  position: absolute;
  right: -14%;
  top: -34%;
  width: 62vw;
  max-width: 760px;
  aspect-ratio: 1;
  background: radial-gradient(circle at 50% 60%, hsl(32.5deg 92% 54% / 0.2) 0%, transparent 62%);
  z-index: -1;
  pointer-events: none;
}
/* Faint grid for depth. The mask is wider than before so the lines carry across
   the candidate side too, but the opacity stays low enough that the background
   never competes with Jason or the copy. */
.hero::after {
  content: "";
  position: absolute;
  inset: 0;
  background-image: linear-gradient(hsl(0deg 0% 100% / 0.045) 1px, transparent 1px),
    linear-gradient(90deg, hsl(0deg 0% 100% / 0.045) 1px, transparent 1px);
  background-size: 72px 72px;
  mask-image: radial-gradient(ellipse 120% 85% at 45% 25%, #000 25%, transparent 82%);
  -webkit-mask-image: radial-gradient(ellipse 120% 85% at 45% 25%, #000 25%, transparent 82%);
  z-index: -1;
  pointer-events: none;
}

/* Mobile: identity, then the photograph, then lede + CTAs + dates. Desktop
   regroups into two columns (see the 62em block at the end of this section). */
/* The first --header-h of top padding replaces the space the header used to
   occupy in flow. Without it the district badge would sit under the header. */
.hero__inner {
  display: grid;
  gap: clamp(2rem, 5vw, 3rem);
  padding-block: calc(var(--header-h) + clamp(1.5rem, 4vw, 3rem))
                 clamp(3.5rem, 8vw, 6rem);
}
/* Mobile order: candidate image first, then badge/name/strapline, then copy +
   CTAs + dates, then the scroll cue. Desktop overrides this with an explicit
   grid placement, so the two-column layout is unaffected. */
.hero__media { order: 1; }
.hero__identity { order: 2; }
.hero__copy { order: 3; }

.hero__district {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-3);
  padding: 0.4rem 0.9rem 0.4rem 0.45rem;
  border: 1px solid hsl(0deg 0% 100% / 0.24);
  border-radius: 999px;
  background: hsl(0deg 0% 100% / 0.07);
  font-size: var(--step--1);
  font-weight: 600;
  letter-spacing: 0.04em;
  color: hsl(205deg 60% 92%);
}
.hero__district-num {
  font-stretch: normal;
  display: inline-grid;
  place-items: center;
  width: 26px;
  height: 26px;
  border-radius: 50%;
  background: var(--orange);
  color: var(--charcoal-900);
  font-family: var(--font-display);
  font-size: 0.95rem;
  font-weight: 700;
}

/* The h1 is a layout wrapper only. It deliberately sets NO type properties, so
   nothing cascades into its two children, each owns its own typography. */
.hero__title {
  margin-top: var(--sp-5);
  color: var(--white);
}
.hero__title span { display: block; }

/* ---- Candidate name -------------------------------------------------------
   Fully independent typography. Every value below is controlled by its own
   custom property, so it can be retuned from the :root block in section 1
   without touching any other element on the page. --------------------------- */
.hero-candidate-name {
  font-stretch: var(--display-width-poster);
  font-family: var(--hero-candidate-name-family);
  font-size: var(--hero-candidate-name-size);
  font-weight: var(--hero-candidate-name-weight);
  line-height: var(--hero-candidate-name-leading);
  letter-spacing: var(--hero-candidate-name-tracking);
  text-transform: var(--hero-candidate-name-transform);
  margin: var(--hero-candidate-name-margin);
  color: var(--hero-candidate-name-color);
  /* Keeps a long name from pushing the layout wider than the viewport. */
  overflow-wrap: break-word;
  hyphens: none;
}

/* ---- Strapline ------------------------------------------------------------
   Sized in rem, NOT in em, so it is unaffected by the name's font size. ------ */
.hero__title .hero__title-sub {
  margin-top: 1rem;
  font-size: var(--hero-strapline-size);
  font-weight: 600;
  line-height: 1.1;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--orange);
}

.hero__rule {
  width: 84px;
  height: 4px;
  margin: var(--sp-5) 0 0;
  border: 0;
  border-radius: 2px;
  background: var(--orange);
}

.hero__lede {
  max-width: 46ch;
  font-size: var(--step-1);
  line-height: 1.6;
  color: hsl(205deg 40% 90%);
}

.hero__actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-4);
  margin-top: var(--sp-6);
}

.hero__dates {
  display: grid;
  gap: var(--sp-4);
  margin-top: var(--sp-7);
  padding-top: var(--sp-6);
  border-top: 1px solid hsl(0deg 0% 100% / 0.14);
}
@media (min-width: 30em) {
  .hero__dates { grid-template-columns: 1fr 1fr; gap: var(--sp-5); }
}
.hero__date-label {
  display: block;
  font-size: var(--label);
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: hsl(205deg 45% 76%);
}
.hero__date-value {
  font-stretch: var(--display-width-md);
  display: block;
  margin-top: var(--sp-1);
  font-family: var(--font-display);
  font-size: var(--step-2);
  font-weight: 600;
  color: var(--white);
}

/* Hero portrait slot: swap the <img> inside, layout is fixed by the frame. */
.hero__media { position: relative; }

.figure-slot {
  position: relative;
  overflow: hidden;
  border-radius: var(--r-lg);
  background: linear-gradient(150deg, hsl(210deg 22% 92%), hsl(210deg 18% 84%));
  box-shadow: var(--sh-lg);
}
.figure-slot::before {
  content: "";
  display: block;
  padding-top: var(--aspect, 125%);
}
.figure-slot picture,
.figure-slot img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
}
/* cover fills the frame without distorting; the source is pre-cropped to the
   frame's own 4:5 ratio, so in practice almost nothing is trimmed. */
.figure-slot img { object-fit: cover; object-position: 50% 22%; }

/* A slot holding a real photograph drops the placeholder's grey ground so no
   striping can show through while the image decodes. */
.figure-slot--filled { background: var(--charcoal-800); }
.figure-slot__placeholder {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--sp-4);
  padding: var(--sp-6);
  text-align: center;
  background-image: repeating-linear-gradient(
    135deg,
    hsl(210deg 16% 88%) 0 14px,
    hsl(210deg 16% 91%) 14px 28px
  );
  color: var(--charcoal-600);
}
.figure-slot__icon {
  display: grid;
  place-items: center;
  width: 62px;
  height: 62px;
  border-radius: 50%;
  background: var(--white);
  color: var(--blue-600);
  box-shadow: var(--sh-sm);
}
.figure-slot__label {
  /* Body face, not display: small uppercase label. */
  font-family: var(--font-sans);
  font-size: var(--step-1);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--charcoal-800);
}
.figure-slot__note {
  max-width: 30ch;
  font-size: var(--step--1);
  line-height: 1.5;
  color: var(--charcoal-600);
}

/* Mobile keeps the photo a touch wider than tall so it and the headline share
   the first screen; desktop returns to a portrait frame. */
/* ---- Hero cutout ---------------------------------------------------------
   The hero photo is a background-removed PNG, so it is presented as a cutout
   standing on the hero gradient: no frame, no border, no rounded corners, no
   shadow box and no scrim, and any of those would draw a visible rectangle around
   the transparent edges. The only shadow is a soft drop-shadow that follows the
   alpha channel, which grounds Jason against the blue without boxing him in.
   -------------------------------------------------------------------------- */
/* A defined image AREA rather than a loose image: the figure sets the box and
   the cutout fills it. object-fit: contain guarantees the aspect ratio is kept
   (nothing is ever stretched) and object-position anchors Jason to the bottom
   of the area, so his head has air above it and nothing is cropped. */
.hero__photo {
  width: 100%;
  /* The photo's own ratio, so cover crops essentially nothing. */
  --aspect: 144%;
  max-width: 400px;
  margin-inline: auto;
}

/* Tablet: a wider frame once there is room for it. */
@media (min-width: 48em) {
  .hero__photo { max-width: 440px; }
}

@media (min-width: 62em) {
  /* 440px at 144% is about 634px tall, which is close to the height the old
     cutout was capped at, so the two hero columns still feel evenly weighted. */
  .hero__photo { max-width: 440px; margin-inline: 0; }
}

/* ---- Candidate composition ------------------------------------------------
   The transparent cutout stands on the hero gradient with an abstract tilted
   outline behind it and a soft glow beneath. Both are pseudo-elements, so they
   are invisible to screen readers and cost nothing in markup. Neither takes
   pointer events, so they cannot interfere with the CTAs or the scroll cue.
   -------------------------------------------------------------------------- */
.hero__media {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  isolation: isolate;
}

/* Tilted outline. Deliberately not a card: hairline border, no fill, corners
   rounded far more than any real component on the page. */
.hero__media::before {
  content: "";
  position: absolute;
  z-index: -2;
  left: 50%;
  top: 50%;
  /* Sized under 100% and nudged left of the image's centre so the rotated
     corners stay inside the viewport once the composition bleeds right. */
  width: min(96%, 480px);
  aspect-ratio: 1 / 1.12;
  translate: -52% -50%;
  rotate: -8deg;
  border: 1px solid hsl(0deg 0% 100% / 0.16);
  border-radius: clamp(28px, 5vw, 52px);
  pointer-events: none;
}

/* Ambient glow so Jason lifts off the background rather than floating on it. */
.hero__media::after {
  content: "";
  position: absolute;
  z-index: -3;
  left: 50%;
  top: 54%;
  width: min(94%, 460px);
  aspect-ratio: 1 / 1.15;
  translate: -50% -50%;
  background: radial-gradient(
    circle at 50% 45%,
    hsl(205deg 80% 58% / 0.2) 0%,
    hsl(32.5deg 92% 54% / 0.07) 42%,
    transparent 68%
  );
  pointer-events: none;
}

.hero__photo { z-index: 1; }

/* Mobile keeps the outline but drops the tilt back, so a rotated shape cannot
   push past the viewport edge on a narrow screen. */
@media (max-width: 47.99em) {
  .hero__media::before { width: min(100%, 420px); rotate: -4deg; }
  .hero__media::after { width: min(92%, 400px); }
}

@media (min-width: 62em) {
  .hero__media { align-items: flex-start; }
}

@media (min-width: 62em) {
  .hero__inner {
    /* The right column carries a square image, so its width is also its height.
       It takes a larger share than the original 1.08/0.92 to give the cutout
       real size, while the left column stays wide enough to keep "JASON
       CARLTON" on one line, because that headline is the anchor of the hero.
       MEASURED 2026-09-13 on the live page: this column is 528px at a 1200px
       viewport, not the ~560px this note used to claim. The name renders 508px
       wide, so there is about 20px of slack and no more. */
    grid-template-columns: 1.01fr 0.99fr;
    align-items: center;
    column-gap: clamp(1.25rem, 2.5vw, 2.25rem);
    row-gap: 0;
  }
  /* Left column stacks identity, copy, then the scroll cue; the photo spans
     all three rows so its height still drives the composition.

     Both columns carry the same 50px top padding so they begin level with each
     other. align-items is start (not center) so that padding resolves to a real
     50px offset rather than being halved by centring. Desktop only: on mobile a
     single stacked column has no alignment to balance, and the value would only
     add a gap between the copy and the photo. */
  .hero__identity {
    grid-column: 1;
    grid-row: 1;
    align-self: end;
    padding-top: 30px;
    padding-bottom: 20px;
  }
  .hero__copy { grid-column: 1; grid-row: 2; align-self: start; }
  .hero__media {
    grid-column: 2;
    grid-row: 1 / span 3;
    align-self: start;
    padding-top: 50px;
  }

  /* The candidate composition bleeds into the empty space between the shell and
     the viewport edge. This buys real size WITHOUT narrowing the left column,
     which is what would wrap the hero name. The clamp collapses to 0 at exactly
     the shell width, so it can never create a scrollbar. */
  .hero__media {
    --bleed: clamp(0px, calc((100vw - var(--shell)) / 2 + 0.5rem), 6rem);
    margin-right: calc(var(--bleed) * -1);
  }
  /* The frame sets its own height from --aspect, so the image needs no cap of
     its own here; .figure-slot img already fills the frame. */
}

/* --------------------------------------------------------------------------
   6b. Hero scroll-down indicator
   --------------------------------------------------------------------------
   A real <button>, so it is focusable and answers Enter/Space for free.
   Sits in the hero's lower-middle band, below the election dates and roughly
   under the gap between the left column and the portrait. It stays in normal
   grid flow rather than being absolutely positioned, so it can never collide
   with the photo or the CTAs at any width.
   -------------------------------------------------------------------------- */
.hero__scroll {
  grid-column: 1 / -1;
  order: 4;
  /* Lower-middle on desktop; see the 62em block below for the exact offset. */
  justify-self: center;
  display: inline-flex;
  flex-direction: column;
  align-items: center;
  gap: var(--sp-3);
  margin-top: var(--sp-6);
  padding: var(--sp-2);
  border: 0;
  background: none;
  color: hsl(205deg 40% 84%);
  font-family: var(--font-sans);
  font-size: var(--label);
  font-weight: 700;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  cursor: pointer;
  transition: color var(--dur) var(--ease);
}
.hero__scroll:hover { color: var(--white); }

/* Mouse-style cue: a rounded capsule with a wheel that drifts down and fades. */
.hero__scroll-mouse {
  position: relative;
  flex: none;
  width: 26px;
  height: 40px;
  border: 2px solid hsl(0deg 0% 100% / 0.45);
  border-radius: 999px;
  transition: border-color var(--dur) var(--ease);
}
.hero__scroll:hover .hero__scroll-mouse { border-color: var(--white); }

.hero__scroll-wheel {
  position: absolute;
  left: 50%;
  top: 8px;
  width: 3px;
  height: 7px;
  margin-left: -1.5px;
  border-radius: 2px;
  background: var(--orange);
  animation: hero-wheel 2.1s var(--ease) infinite;
}

@keyframes hero-wheel {
  0%        { transform: translateY(0);    opacity: 0; }
  18%       { opacity: 1; }
  70%       { transform: translateY(14px); opacity: 0; }
  100%      { transform: translateY(14px); opacity: 0; }
}

.hero__scroll:focus-visible {
  outline: 3px solid var(--orange);
  outline-offset: 4px;
  border-radius: var(--r-sm);
  color: var(--white);
}

/* Desktop: the cue spans the whole grid and centres on the hero itself, not on
   the left column. (It previously sat in column 1 only, which read as
   left-of-centre.) Row 4 keeps it below both the copy and the photo. */
@media (min-width: 62em) {
  .hero__scroll {
    grid-column: 1 / -1;
    grid-row: 4;
    justify-self: center;
    margin-top: clamp(-2.5rem, 3vw, -2.5rem);
  }
}

/* Tablet and below: the grid is a single column, so centring under the content
   is already correct. Slightly tighter top margin at these sizes. */
@media (max-width: 61.99em) {
  .hero__scroll { justify-self: center; margin-top: var(--sp-5); }
}

/* Very short viewports have no spare vertical room for the cue. */
@media (max-height: 34em) and (max-width: 61.99em) {
  .hero__scroll { display: none; }
}

@media (prefers-reduced-motion: reduce) {
  .hero__scroll-wheel { animation: none; opacity: 1; }
}

/* --------------------------------------------------------------------------
   7. Trust strip
   -------------------------------------------------------------------------- */
/* This strip is the one section that opts out of the 1200px --shell. Its four
   items need more room than the article measure to sit on a single line, so it
   uses its own wider container. Everything else on the site keeps --shell. */
.strip {
  background: #043458;
  color: hsl(0deg 0% 100% / 0.9);
  padding-block: var(--sp-5);
}

.strip__inner {
  width: 100%;
  max-width: var(--shell-wide);
  margin-inline: auto;
  padding-inline: var(--gutter);
  /* Mobile: two readable columns rather than four cramped ones. */
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--sp-4) var(--sp-5);
  align-items: start;
}

/* Phones below ~430px: one column. Two columns there would leave each item
   about 88px wide, which wraps the longest label to four lines. */
@media (max-width: 26.87em) {
  .strip__inner { grid-template-columns: minmax(0, 1fr); }
}

/* Desktop single row. The four items measure 1144px of text in total, so the
   row needs that plus three gaps and two gutters. From 1200px up they fit on
   one line with a tightened gutter and gap; below that the 2x2 grid takes over. */
@media (min-width: 75em) {
  .strip__inner {
    display: flex;
    flex-wrap: nowrap;
    align-items: center;
    justify-content: space-between;
    /* 1144px of text leaves only ~56px at a 1200px viewport, so the gutter and
       gaps scale from tight there to comfortable on large screens. */
    padding-inline: clamp(0.75rem, 2.4vw, 3rem);
    gap: clamp(0.55rem, 1.9vw, 2.75rem);
  }
}

.strip__item {
  display: flex;
  align-items: flex-start;
  gap: var(--sp-3);
  margin: 0;
  font-size: var(--step--1);
  font-weight: 600;
  line-height: 1.4;
  letter-spacing: 0.04em;
  /* Long words must never punch out of the grid track and cause overflow. */
  min-width: 0;
  overflow-wrap: break-word;
}
/* Icon aligns to the first line of text, not the middle of a wrapped block. */
.strip__item svg { flex: none; color: var(--orange); margin-top: 0.18em; }

@media (min-width: 75em) {
  .strip__item { align-items: center; white-space: nowrap; }
  .strip__item svg { margin-top: 0; }
}

/* 1200-1279px is the tight band: the four labels need 1144px of text and the
   viewport leaves about 1142px after gutters and gaps. Dropping the tracking
   to normal reclaims roughly 20px, which is enough to hold one line without
   touching the font size. Above 1280px the normal tracking returns. */
@media (min-width: 75em) and (max-width: 79.99em) {
  .strip__item { letter-spacing: 0; }
  .strip__item svg { width: 15px; height: 15px; }
}

/* --------------------------------------------------------------------------
   8. Meet Jason
   -------------------------------------------------------------------------- */
.meet { background: var(--paper); }
.meet__grid { display: grid; gap: clamp(2.5rem, 6vw, 4rem); }
@media (min-width: 62em) {
  .meet__grid { grid-template-columns: 0.82fr 1.18fr; align-items: start; }
}

.meet__aside { position: relative; }
@media (min-width: 62em) {
  .meet__aside { position: sticky; top: calc(var(--header-h) + 2rem); }
}
/* Portrait is squarer on phones so it does not dominate an already long
   section, and returns to a portrait crop once there is a side column. */
/* Set by the photograph, not by taste. Jason spans 1792px of a 2400px-tall
   frame, so anything taller than about 128% starts cutting his hands off one
   side or his shoulder off the other. Re-measure if the photo is ever swapped. */
.meet__aside .figure-slot { --aspect: 128%; }

.meet__facts {
  margin-top: var(--sp-5);
  padding: var(--sp-5);
  background: var(--white);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  box-shadow: var(--sh-sm);
}
.meet__fact + .meet__fact { margin-top: var(--sp-4); padding-top: var(--sp-4); border-top: 1px solid var(--line); }

/* Two columns between 30em and the desktop layout: four stacked rows make an
   already tall mobile section taller for no reading benefit. */
@media (min-width: 30em) and (max-width: 61.99em) {
  .meet__facts {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 0 var(--sp-5);
  }
  .meet__fact + .meet__fact { margin-top: 0; padding-top: 0; border-top: 0; }
  .meet__facts .meet__fact:nth-child(n + 3) {
    margin-top: var(--sp-4);
    padding-top: var(--sp-4);
    border-top: 1px solid var(--line);
  }
}
.meet__fact-label {
  font-size: .75rem;
  font-weight: 500;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--text-quiet);
}
.meet__fact-value {
  margin-top: 2px;
  /* Body face, not display: facts-panel values are data, not display type. */
  font-family: var(--font-sans);
  font-size: var(--step-1);
  font-weight: 600;
  color: var(--charcoal-900);
}

/* Editorial treatment of the biography text. */
.bio { max-width: 64ch; }
.bio__lead {
  font-size: var(--step-2);
  line-height: 1.5;
  font-weight: 500;
  color: var(--charcoal-900);
  margin-bottom: var(--sp-5);
}
.bio p { font-size: 1.05rem; line-height: 1.75; color: var(--text-muted); }
.bio p + p { margin-top: 0; }

.bio__pull {
  margin: var(--sp-6) 0;
  padding: var(--sp-4) 0 var(--sp-4) var(--sp-5);
  border-left: 4px solid var(--orange);
}
@media (min-width: 48em) {
  .bio__pull { padding: var(--sp-5) 0 var(--sp-5) var(--sp-6); }
}
.bio__pull p {
  font-stretch: var(--display-width-lg);
  font-family: var(--font-display);
  font-size: var(--step-3);
  font-weight: 600;
  line-height: 1.2;
  color: var(--charcoal-900);
}
.bio__attr {
  display: block;
  margin-top: var(--sp-3);
  font-family: var(--font-sans);
  font-size: var(--step--1);
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--blue-700);
}

.bio__actions { display: flex; flex-wrap: wrap; gap: var(--sp-4); margin-top: var(--sp-6); }

/* --------------------------------------------------------------------------
   9. Priorities
   -------------------------------------------------------------------------- */
/* Four themes: a 2x2 block reads better than an orphaned fourth card. */
.issues__grid {
  display: grid;
  gap: var(--sp-5);
  grid-template-columns: 1fr;
}
@media (min-width: 40em) {
  .issues__grid { grid-template-columns: repeat(2, 1fr); }
}
@media (min-width: 75em) {
  .issues__grid { grid-template-columns: repeat(4, 1fr); }
}
.issue-card {
  position: relative;
  display: flex;
  flex-direction: column;
  /* 20px inside on phones, 32/24 once there is room. */
  padding: var(--sp-5) 1.25rem;
  background: var(--white);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  box-shadow: var(--sh-sm);
  transition: transform var(--dur) var(--ease), box-shadow var(--dur) var(--ease),
    border-color var(--dur) var(--ease);
}
.issue-card:hover {
  transform: translateY(-3px);
  box-shadow: var(--sh-md);
  border-color: var(--line-strong);
}
.issue-card__num {
  font-stretch: var(--display-width-md);
  font-family: var(--font-display);
  font-size: var(--step-2);
  font-weight: 700;
  /* --orange on white is 2.30:1, which fails WCAG AA even at this size (large
     text needs 3:1). --orange-700 is the same hue at 3.8:1 and already exists
     in the palette, so nothing else has to change. The brand orange stays
     exactly as it is everywhere it sits on a dark background. */
  color: var(--orange-700);
  line-height: 1;
}
.issue-card__title {
  margin-top: var(--sp-4);
  font-size: var(--step-1);
  text-transform: uppercase;
  letter-spacing: 0.02em;
}

@media (min-width: 40em) {
  .issue-card { padding: var(--sp-6) var(--sp-5); }
}

.issue-card__status {
  margin-top: auto;
  padding-top: var(--sp-5);
  font-size: var(--label);
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--text-quiet);
}

.notice {
  display: flex;
  gap: var(--sp-4);
  margin-top: var(--sp-6);
  padding: var(--sp-5);
  background: var(--blue-50);
  border: 1px solid var(--blue-100);
  border-left: 4px solid var(--blue-600);
  border-radius: var(--r-md);
  font-size: 1.0625rem;
  line-height: 1.6;
  color: var(--charcoal-600);
}
.notice svg { flex: none; color: var(--blue-600); margin-top: 2px; }

/* --------------------------------------------------------------------------
   10. Donation section, the primary conversion surface
   -------------------------------------------------------------------------- */
.donate {
  position: relative;
  overflow: hidden;
  background: linear-gradient(160deg, var(--blue-700) 0%, var(--blue-900) 100%);
  color: var(--white);
  isolation: isolate;
}
.donate::before {
  content: "";
  position: absolute;
  left: 50%;
  top: -40%;
  width: min(900px, 120vw);
  aspect-ratio: 1;
  translate: -50% 0;
  background: radial-gradient(circle, hsl(32.5deg 92% 54% / 0.16) 0%, transparent 60%);
  z-index: -1;
}

.donate__inner {
  display: grid;
  gap: clamp(2.5rem, 6vw, 2.5rem);
  align-items: center;
}
@media (min-width: 62em) {
  .donate__inner { grid-template-columns: 0.95fr 1.05fr; }
}

.donate__title { font-stretch: var(--display-width-xl); font-size: var(--step-5); text-transform: uppercase; color: var(--white); }
.donate__lede {
  max-width: 48ch;
  margin-top: var(--sp-5);
  font-size: var(--step-1);
  line-height: 1.6;
  color: hsl(205deg 40% 90%);
}

.donate__points { margin-top: var(--sp-6); display: grid; gap: var(--sp-4); }
.donate__point {
  display: flex;
  align-items: flex-start;
  gap: var(--sp-3);
  font-size: var(--step-0);
  color: hsl(205deg 35% 92%);
}
.donate__point svg { flex: none; margin-top: 3px; color: var(--orange); }

.donate__panel {
  padding: clamp(1.75rem, 4vw, 2.5rem);
  background: var(--white);
  border-radius: var(--r-xl);
  box-shadow: var(--sh-lg);
  color: var(--text);
  text-align: center;
}
.donate__panel-eyebrow {
  font-size: var(--label);
  font-weight: 700;
  letter-spacing: 0.13em;
  text-transform: uppercase;
  color: var(--blue-700);
}
.donate__panel-title { font-stretch: var(--display-width-lg);
  margin-top: var(--sp-3);
  font-size: var(--step-3);
  text-transform: uppercase;
}
.donate__panel-note {
  margin-top: var(--sp-4);
  font-size: 17.5px;
  line-height: 1.6;
  color: var(--text-muted);
}
.donate__panel .btn { margin-top: var(--sp-5); }

.donate__secure {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--sp-2);
  margin-top: var(--sp-5);
  padding-top: var(--sp-5);
  border-top: 1px solid var(--line);
  font-size: 0.78rem;
  font-weight: 600;
  color: var(--text-quiet);
}
.donate__secure svg { flex: none; color: var(--blue-600); }

.donate__disclaimer {
  margin-top: var(--sp-4);
  font-size: var(--label);
  line-height: 1.55;
  color: var(--text-quiet);
}

/* --------------------------------------------------------------------------
   11. Press
   -------------------------------------------------------------------------- */
/* Cards stack with the section's own rhythm between them. */
.press__stack { display: grid; gap: var(--sp-6); }

.press__card {
  display: grid;
  gap: 0;
  overflow: hidden;
  background: var(--white);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  box-shadow: var(--sh-sm);
  transition: box-shadow var(--dur) var(--ease), border-color var(--dur) var(--ease);
}
.press__card:hover { box-shadow: var(--sh-md); border-color: var(--line-strong); }
@media (min-width: 48em) {
  .press__card { grid-template-columns: 0.9fr 1.1fr; align-items: stretch; }
}

/* The video panel. A facade: the YouTube thumbnail plus the existing orange
   play button. The iframe is only injected on click, so no YouTube script or
   player is fetched until the visitor asks for the video. */
.press__visual {
  position: relative;
  min-height: 220px;
  display: grid;
  /* stretch, not center: the media fills the panel's width at 16:9. */
  place-items: stretch;
  align-content: center;
  background: linear-gradient(150deg, var(--charcoal-800), var(--charcoal-900));
  color: var(--white);
  overflow: hidden;
}

/* An article card with a photograph. The panel is already position: relative
   with overflow hidden, so the image fills it and crops rather than
   distorting, and the photo's shape does not have to match the panel's. */
.press__visual--photo { min-height: 240px; }
.press__visual--photo picture,
.press__photo {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border: 0;
  object-fit: cover;
}

/* The outlet label carries its own backdrop here rather than leaning on the
   scrim. A photograph can be any brightness behind it: measured against the
   current one, the scrim alone left the label at 2.1:1 where it crossed the
   bright shelving. Tuning the scrim would fix this picture and break the next
   one, so the label brings its own contrast with it. */
.press__visual--photo .press__outlet {
  padding: 0.3rem 0.6rem;
  border-radius: var(--r-sm);
  background: hsl(228deg 10% 8% / 0.72);
  text-shadow: none;
}

/* Fallback for an article card with no photograph: the panel carries the
   outlet name set in display type instead. */
.press__visual--wordmark {
  place-items: center;
  align-content: center;
  min-height: 180px;
  padding: clamp(1.75rem, 5vw, 2.75rem);
  background: linear-gradient(150deg, var(--blue-700), var(--blue-900));
}
.press__masthead {
  font-family: var(--font-display);
  font-stretch: var(--display-width-lg);
  font-size: var(--step-2);
  font-weight: 700;
  line-height: 1.08;
  letter-spacing: 0.01em;
  text-align: center;
  text-transform: uppercase;
  color: var(--white);
}

/* 16:9 for the media itself, so the video never stretches or distorts. */
.press__media {
  position: relative;
  width: 100%;
  aspect-ratio: 16 / 9;
  padding: 0;
  border: 0;
  background: none;
  color: inherit;
  font: inherit;
  cursor: pointer;
  display: block;
  overflow: hidden;
}
.press__media picture,
.press__media img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border: 0;
  object-fit: cover;
}

/* The injected player replaces the facade button, so it is a direct child of
   .press__visual and carries the 16:9 ratio itself. */
.press__visual > iframe {
  width: 100%;
  aspect-ratio: 16 / 9;
  border: 0;
  display: block;
}

.press__media:hover::after { background: linear-gradient(150deg, hsl(228deg 10% 12% / 0.22), hsl(228deg 10% 8% / 0.4)); }
.press__media.is-playing::after { display: none; }

.press__play {
  position: absolute;
  left: 50%;
  top: 50%;
  translate: -50% -50%;
  z-index: 2;
  display: grid;
  place-items: center;
  width: 74px;
  height: 74px;
  border-radius: 50%;
  background: var(--orange);
  color: var(--charcoal-900);
  box-shadow: 0 0 0 0 hsl(32.5deg 92% 54% / 0.4);
  transition: transform var(--dur) var(--ease), box-shadow var(--dur) var(--ease);
}
.press__card:hover .press__play,
.press__media:hover .press__play {
  transform: scale(1.06);
  box-shadow: 0 0 0 14px hsl(32.5deg 92% 54% / 0.12);
}
.press__media:focus-visible { outline: 3px solid var(--orange); outline-offset: -3px; }

/* Smaller viewports get a slightly smaller target so it stays in proportion. */
@media (max-width: 30em) {
  .press__play { width: 62px; height: 62px; }
}
.press__outlet {
  position: absolute;
  left: var(--sp-5);
  top: var(--sp-5);
  z-index: 2;
  font-size: var(--label);
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: hsl(0deg 0% 100% / 0.82);
  text-shadow: 0 1px 3px hsl(228deg 10% 8% / 0.7);
  pointer-events: none;
}
/* Once the player is in, the overlay label would sit on top of YouTube's own
   controls, so it is removed. */
.press__visual.is-playing .press__outlet { display: none; }

.press__body { padding: clamp(1.5rem, 4vw, 2.5rem); }
.press__kicker {
  font-size: var(--label);
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--blue-700);
}
.press__title { font-stretch: var(--display-width-lg); margin-top: var(--sp-3); font-size: var(--step-2); }

/* The headline is a link to the piece. No underline at rest, so the card does
   not read as underlined text; on hover an orange rule wipes in from the left,
   the same affordance the header nav uses. inline-block keeps the rule to the
   width of the text when the headline fits on one line. */
.press__title-link {
  position: relative;
  display: inline-block;
  padding-bottom: 3px;
  color: inherit;
  text-decoration: none;
}
.press__title-link::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 2px;
  background: var(--orange);
  border-radius: 2px;
  transform: scaleX(0);
  transform-origin: left;
  transition: transform var(--dur) var(--ease);
}
.press__title-link:hover::after,
.press__title-link:focus-visible::after { transform: scaleX(1); }
.press__meta { margin-top: var(--sp-4); font-size: var(--step--1); color: var(--text-muted); }
.press__body .btn { margin-top: var(--sp-5); }

/* --------------------------------------------------------------------------
   12b. Upcoming events
   --------------------------------------------------------------------------
   Cards are white on a white section, so the border does the separating. They
   carry dark text, which is what the h1..h4 colour rule already gives them. If
   this section ever moves to a dark background, set the heading colour
   explicitly or it will render near-black on dark.
   -------------------------------------------------------------------------- */
.events__grid {
  display: grid;
  gap: var(--sp-5);
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 280px), 1fr));
}
.event-card {
  display: flex;
  flex-direction: column;
  gap: var(--sp-3);
  padding: clamp(1.5rem, 3.5vw, 2rem);
  background: var(--white);
  border: 1px solid var(--line);
  border-left: 4px solid var(--orange);
  border-radius: var(--r-lg);
  box-shadow: var(--sh-sm);
}
/* A lone card would otherwise stretch the full shell width and read as a
   banner rather than a card. This also covers the state left behind when the
   expiry script removes all but one. */
.event-card:only-child {
  max-width: 34rem;
  margin-inline: auto;
}
.event-card__when {
  font-size: var(--label);
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--blue-700);
}
.event-card__title {
  font-size: var(--step-1);
  font-stretch: var(--display-width-md);
}
.event-card__host { font-weight: 600; }
.event-card__venue { font-size: var(--step--1); color: var(--text-muted); }

/* Both actions share a row, and wrap to two lines when the card is too narrow
   for them. margin-top: auto pushes the row to the bottom so buttons line up
   across cards carrying different amounts of text. */
.event-card__actions {
  margin-top: auto;
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-3);
}
/* These icons sit before the label and are objects, not directional arrows, so
   they do not slide on hover the way the arrow buttons elsewhere do. */
.event-card__actions .btn:hover .btn__icon { transform: none; }

/* --------------------------------------------------------------------------
   12. Election information
   -------------------------------------------------------------------------- */
.election { background: var(--paper); }
.election__grid {
  display: grid;
  gap: var(--sp-5);
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 250px), 1fr));
}
.date-card {
  position: relative;
  padding: var(--sp-6) var(--sp-5);
  background: var(--white);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  box-shadow: var(--sh-sm);
  overflow: hidden;
}
.date-card::before {
  content: "";
  position: absolute;
  inset: 0 auto 0 0;
  width: 4px;
  background: var(--blue-600);
}
.date-card--urgent::before { background: var(--orange); }
.date-card__label {
  font-size: var(--label);
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--text-quiet);
}
.date-card__value {
  /* Narrower than the shared --display-width-md (90%) on purpose: these sit in
     the tightest boxes on the page, four across. 78% is still a long way from
     the squeezed end of Archivo's axis, so the numerals stay clear. */
  font-stretch: 80%;
  margin-top: var(--sp-2);
  font-family: var(--font-display);
  /* Sized against the real card, which gives about 245px of usable width at
     desktop. A date that breaks after the comma reads as two separate values,
     so these values are deliberately tight. Adding a card to this row makes
     each one narrower and will need the clamp lowered to match. */
  font-size: clamp(1rem, 1.62vw, 1.85rem);
  font-weight: 700;
  text-transform: uppercase;
  color: var(--charcoal-900);
  line-height: 1.05;
  /* nowrap, not balance. A date broken after the comma reads as two separate
     values, which is the whole thing being avoided here. If a date ever does
     overflow, shrink the clamp above rather than letting it wrap. */
  white-space: nowrap;
  padding-top: 12px;
  padding-bottom: 10px;
}
.date-card__note {
  margin-top: var(--sp-3);
  font-size: var(--step--1);
  line-height: 1.6;
  color: var(--text-muted);
}

/* --------------------------------------------------------------------------
   13. Contact
   -------------------------------------------------------------------------- */
/* min-width:0 lets the columns shrink below their content's intrinsic minimum.
   Without it the email address holds the track open and overflows narrow phones. */
.contact__grid { display: grid; gap: clamp(2rem, 5vw, 3.5rem); }
.contact__grid > * { min-width: 0; }
@media (min-width: 62em) {
  .contact__grid { grid-template-columns: 1fr 1fr; align-items: start; }
}

.contact__list { display: grid; gap: var(--sp-5); }

/* The mailing address now sits under the social links in the right column, so
   it needs separating from them.
   2.0625rem = 33px, moved out of an inline style. */
.contact__list--offset { margin-top: 2.0625rem; }
.contact__item {
  display: flex;
  gap: var(--sp-4);
  padding: var(--sp-5);
  background: var(--white);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  box-shadow: var(--sh-sm);
  transition: border-color var(--dur) var(--ease), box-shadow var(--dur) var(--ease);
}
.contact__item:hover { border-color: var(--line-strong); box-shadow: var(--sh-md); }
.contact__icon {
  flex: none;
  display: grid;
  place-items: center;
  width: 46px;
  height: 46px;
  border-radius: var(--r-md);
  background: var(--blue-50);
  color: var(--blue-700);
}
.contact__label {
  font-size: var(--label);
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--text-quiet);
}
.contact__value {
  margin-top: var(--sp-1);
  font-size: var(--step-0);
  font-weight: 600;
  color: var(--charcoal-900);
  font-style: normal;
  line-height: 1.5;
  /* The email address is one long token; allow it to break on tiny screens. */
  overflow-wrap: anywhere;
}
.contact__item { min-width: 0; }
.contact__item > div { min-width: 0; }
/* inline-flex + min-height gives the mailto link a 44px hit area without
   changing how the address block reads. */
.contact__value a {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  color: inherit;
  text-decoration: none;
  border-bottom: 1px solid var(--line-strong);
}
.contact__value a:hover { color: var(--blue-700); border-color: currentColor; }

.social { display: grid; gap: var(--sp-4); }
.social__link {
  display: flex;
  align-items: center;
  gap: var(--sp-4);
  padding: var(--sp-4) var(--sp-5);
  background: var(--white);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  color: var(--charcoal-900);
  text-decoration: none;
  box-shadow: var(--sh-sm);
  transition: transform var(--dur) var(--ease), border-color var(--dur) var(--ease),
    box-shadow var(--dur) var(--ease);
}
.social__link:hover { transform: translateX(3px); border-color: var(--line-strong); box-shadow: var(--sh-md); }
.social__icon {
  flex: none;
  display: grid;
  place-items: center;
  width: 42px;
  height: 42px;
  border-radius: var(--r-md);
  background: var(--charcoal-900);
  color: var(--white);
}
.social__link > span { min-width: 0; overflow-wrap: anywhere; }
.social__name { font-weight: 650; }
.social__handle { font-size: var(--step--1); color: var(--text-muted); }
.social__arrow { margin-left: auto; color: var(--text-quiet); transition: transform var(--dur) var(--ease); }
.social__link:hover .social__arrow { transform: translateX(3px); color: var(--blue-700); }

.contact__panel {
  padding: clamp(1.5rem, 4vw, 2.25rem);
  background: var(--blue-50);
  border: 1px solid var(--blue-100);
  border-radius: var(--r-lg);
}
.contact__panel-title { font-stretch: var(--display-width-lg); font-size: var(--step-2); text-transform: uppercase; }
.contact__panel p { margin-top: var(--sp-4); color: var(--text-muted); font-size: var(--step-0); }

/* --------------------------------------------------------------------------
   14. Footer
   -------------------------------------------------------------------------- */
.footer {
  background: var(--charcoal-900);
  color: hsl(0deg 0% 100% / 0.72);
  /* Bottom is lighter than top by design: the bottom bar already sits on its
     own divider. On mobile the sticky donate bar adds its own clearance. */
  padding-block: clamp(3rem, 6vw, 3.5rem) clamp(2rem, 3vw, 1.5rem);
  font-size: var(--step--1);
  /* Thin orange rule marks the transition from page to footer. */
  border-top: 3px solid var(--orange);
}

/* 1 column on mobile, 2 on tablet, 4 on desktop. The brand column is wider
   because it carries the blurb and the Donate button. */
.footer__top {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--sp-7) var(--sp-6);
  padding-bottom: var(--sp-6);
  border-bottom: 1px solid hsl(0deg 0% 100% / 0.12);
}
/* Tablet: a clean 2x2. The brand column stays in the grid rather than spanning
   the full width, which would orphan the last column on a row of its own. */
@media (min-width: 40em) {
  .footer__top { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (min-width: 62em) {
  .footer__top {
    grid-template-columns: 1.5fr 1fr 1fr 1.2fr;
    gap: var(--sp-7) clamp(1.5rem, 3vw, 3rem);
  }
}
.footer__col { min-width: 0; }

.footer__brand { display: flex; align-items: center; gap: var(--sp-3); }
.footer__brand .brand__mark { border-color: hsl(0deg 0% 100% / 0.2); }
.footer__brand .brand__name { color: var(--white); }
.footer__brand .brand__sub { color: var(--orange); }
.footer__blurb { margin-top: var(--sp-4); max-width: 38ch; line-height: 1.75; }

.footer__heading {
  font-family: var(--font-sans);
  font-size: 1rem;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--white);
  margin: 0 0 var(--sp-4);
}

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

/* Arrow-led links. The arrow is decorative; the label carries the meaning, and
   hover is signalled by colour AND movement, never colour alone. */
.footer__link {
  display: inline-flex;
  align-items: baseline;
  gap: var(--sp-2);
  padding-block: var(--sp-2);
  color: inherit;
  text-decoration: none;
  line-height: 1.5;
  transition: color 200ms var(--ease);
}
.footer__arrow {
  flex: none;
  color: hsl(0deg 0% 100% / 0.38);
  transition: color 200ms var(--ease), transform 200ms var(--ease);
}
.footer__link:hover,
.footer__link:focus-visible { color: var(--orange); }
.footer__link:hover .footer__arrow,
.footer__link:focus-visible .footer__arrow {
  color: var(--orange);
  transform: translateX(4px);
}

.footer__person {
  margin: 0 0 var(--sp-3);
  font-weight: 650;
  color: var(--white);
}

.footer__address {
  font-style: normal;
  line-height: 1.75;
  display: grid;
  gap: var(--sp-3);
}
.footer__email {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  color: inherit;
  text-decoration: none;
  border-bottom: 1px solid hsl(0deg 0% 100% / 0.28);
  padding-bottom: 1px;
  /* One long token; must be free to wrap on narrow screens. */
  overflow-wrap: anywhere;
  transition: color 200ms var(--ease), border-color 200ms var(--ease);
}
.footer__email:hover,
.footer__email:focus-visible { color: var(--orange); border-bottom-color: var(--orange); }
.footer__postal { display: block; }

/* "Send Email" in the footer contact column. Same look as the other footer
   links, but with the tap target a thumb actually needs. */
.footer__link--cta {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  margin-bottom: var(--sp-2);
  font-weight: 600;
}

/* The full campaign sign, moved down here from the header where it could never
   be shown large enough to read. */
.footer__logo {
  width: 100%;
  max-width: 230px;
  height: auto;
  border-radius: var(--r-sm);
}

.footer__social {
  display: flex;
  gap: var(--sp-3);
  margin-top: var(--sp-5);
}
.footer__social-link {
  display: grid;
  place-items: center;
  width: 44px;
  height: 44px;
  border: 1px solid hsl(0deg 0% 100% / 0.22);
  border-radius: 50%;
  color: hsl(0deg 0% 100% / 0.8);
  transition: color 200ms var(--ease), border-color 200ms var(--ease),
    background-color 200ms var(--ease), transform 200ms var(--ease);
}
.footer__social-link:hover,
.footer__social-link:focus-visible {
  background: var(--orange);
  border-color: var(--orange);
  color: var(--charcoal-900);
  transform: translateY(-2px);
}

.footer__bottom {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-3) var(--sp-5);
  justify-content: space-between;
  align-items: center;
  padding-top: var(--sp-5);
  font-size: 0.78rem;
  color: hsl(0deg 0% 100% / 0.62);
}
.footer__copy { color: hsl(0deg 0% 100% / 0.75); }

/* Legally required FPPC disclaimer. Must stay clearly legible: the rules
   require written disclaimers to be printed clearly, so this is deliberately
   not faded into the footer like the copyright line beside it. */
.footer__disclaimer {
  color: hsl(0deg 0% 100% / 0.75);
  font-weight: 600;
  letter-spacing: 0.01em;
}
.footer__legal { max-width: 68ch; line-height: 1.6; }

/* --------------------------------------------------------------------------
   15. Mobile sticky donate bar
   -------------------------------------------------------------------------- */
.sticky-donate {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 90;
  padding: var(--sp-3) var(--gutter) calc(var(--sp-3) + env(safe-area-inset-bottom));
  background: hsl(0deg 0% 100% / 0.94);
  backdrop-filter: saturate(180%) blur(12px);
  -webkit-backdrop-filter: saturate(180%) blur(12px);
  border-top: 1px solid var(--line);
  box-shadow: 0 -4px 20px -10px hsl(220deg 30% 20% / 0.28);
  transform: translateY(110%);
  transition: transform 300ms var(--ease);
}
.sticky-donate.is-visible { transform: translateY(0); }
.sticky-donate__inner {
  display: flex;
  align-items: center;
  gap: var(--sp-4);
  max-width: var(--shell);
  margin-inline: auto;
}
.sticky-donate__copy { flex: 1; min-width: 0; }
.sticky-donate__title {
  /* Body face, not display: small, and it sits in a cramped mobile bar. */
  font-family: var(--font-sans);
  font-size: 1.05rem;
  font-weight: 700;
  text-transform: uppercase;
  color: var(--charcoal-900);
  line-height: 1.1;
}
.sticky-donate__sub {
  font-size: 0.76rem;
  color: var(--text-muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.sticky-donate .btn { flex: none; }

/* Keep the bar from covering the footer's last line on mobile. */
@media (max-width: 61.99em) {
  body { padding-bottom: 0; }
  /* No sticky-bar clearance needed: main.js hides the donate bar as soon as the
     footer enters view, so the bar never covers the bottom of the footer. */
  .footer { padding-bottom: calc(var(--sp-5) + env(safe-area-inset-bottom)); }
}
@media (min-width: 62em) {
  .sticky-donate { display: none; }
}

/* --------------------------------------------------------------------------
   15b. Back to top
   --------------------------------------------------------------------------
   Fixed bottom-right, hidden until the visitor has scrolled. On mobile it is
   lifted clear of the sticky donate bar so the two never overlap; that bar is
   full-width and ~78px tall, and it only exists below 62em.
   -------------------------------------------------------------------------- */
.to-top {
  position: fixed;
  right: clamp(1rem, 3vw, 2rem);
  bottom: calc(78px + clamp(1rem, 3vw, 1.5rem) + env(safe-area-inset-bottom));
  z-index: 89;               /* below the donate bar (90), above page content */
  display: grid;
  place-items: center;
  width: 46px;
  height: 46px;
  padding: 0;
  /* Campaign orange body deepening on hover. The arrow is charcoal, not white:
     white on brand orange measures only 2.3:1, while charcoal reaches 6.88:1
     and still clears 3:1 against the deeper hover fill. */
  border: 1.5px solid hsl(30deg 85% 42% / 0.45);
  border-radius: 50%;
  background: var(--orange);
  color: var(--charcoal-900);
  cursor: pointer;
  box-shadow: 0 4px 10px hsl(30deg 80% 28% / 0.26),
    0 12px 28px -10px hsl(30deg 80% 28% / 0.44);
  opacity: 0;
  visibility: hidden;
  transform: translateY(12px);
  transition: opacity 260ms var(--ease), transform 260ms var(--ease),
    visibility 260ms var(--ease), background-color 260ms var(--ease),
    border-color 260ms var(--ease), color 260ms var(--ease),
    box-shadow 260ms var(--ease);
}
.to-top.is-visible { opacity: 1; visibility: visible; transform: translateY(0); }

/* Hover deepens the same hue rather than switching colour, so the transition
   reads as a shade change instead of a flash. */
.to-top:hover,
.to-top:focus-visible {
  background: var(--orange-700);
  border-color: hsl(30deg 85% 34%);
  color: var(--charcoal-900);
  transform: translateY(-2px);
  box-shadow: 0 6px 14px hsl(30deg 80% 24% / 0.32),
    0 14px 32px -10px hsl(30deg 80% 24% / 0.46);
}
.to-top:active { transform: translateY(0); }
.to-top svg { flex: none; }

/* Desktop: no donate bar in the way, so it sits at the normal bottom offset
   and can be slightly larger. */
@media (min-width: 62em) {
  .to-top {
    width: 52px;
    height: 52px;
    bottom: clamp(1.5rem, 3vw, 2.5rem);
  }
}

@media (prefers-reduced-motion: reduce) {
  .to-top { transition: opacity 1ms linear, visibility 1ms linear; transform: none; }
  .to-top.is-visible { transform: none; }
  .to-top:hover, .to-top:focus-visible { transform: none; }
}

@media print { .to-top { display: none !important; } }

/* --------------------------------------------------------------------------
   16. Reveal animation
   -------------------------------------------------------------------------- */
.reveal {
  opacity: 0;
  transform: translateY(18px);
  transition: opacity 620ms var(--ease), transform 620ms var(--ease);
}
.reveal.is-in { opacity: 1; transform: none; }

/* --------------------------------------------------------------------------
   17. Responsive escalation
   -------------------------------------------------------------------------- */
@media (min-width: 62em) {
  .nav { display: block; }
  .header__cta { display: inline-flex; font-size: 1rem; }
  .burger { display: none; }
  .mobile-nav { display: none; }
}

/* --------------------------------------------------------------------------
   17b. Hero candidate name, responsive sizes
   --------------------------------------------------------------------------
   The single place to retune "Jason Carlton" per breakpoint. Only the name and
   its strapline are touched here; no other type on the page reads these.
   -------------------------------------------------------------------------- */
@media (max-width: 64em) {          /* 1024px and below */
  :root {
    --hero-candidate-name-size: 49px;
    --hero-strapline-size: 25px;
  }
}
@media (max-width: 48em) {          /* 768px and below */
  :root {
    --hero-candidate-name-size: 40px;
    --hero-strapline-size: 21px;
  }
}
/* Mobile type sizes. */
@media (max-width: 47.99em) {       /* 767px and below */
  :root {
    --hero-candidate-name-size: 32px;
    --hero-strapline-size: 26px;
  }
}
/* Below ~360px the specified 36.8px name no longer fits the available width and
   would wrap "JASON CARLTON" across two lines. These two steps are the smallest
   reductions that keep it on one line. */
@media (max-width: 22.5em) {        /* 360px and below */
  :root {
    --hero-candidate-name-size: 29.5px;
    --hero-strapline-size: 24px;
  }
}
@media (max-width: 20em) {          /* 320px */
  :root {
    --hero-candidate-name-size: 25.5px;
    --hero-strapline-size: 21px;
  }
}

/* --------------------------------------------------------------------------
   18. Motion & contrast preferences
   -------------------------------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *,
  *::before,
  *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
    scroll-behavior: auto !important;
  }
  .reveal { opacity: 1; transform: none; }
}

@media (prefers-contrast: more) {
  :root { --line: hsl(210deg 14% 65%); --text-muted: var(--charcoal-800); }
}

@media print {
  .header, .mobile-nav, .sticky-donate, .hero::after { display: none !important; }
  body { color: #000; }
  .hero, .donate, .footer, .strip { background: none !important; color: #000 !important; }
  .hero__title, .donate__title, .footer__brand .brand__name { color: #000 !important; }
}

/* ==========================================================================
   19. MOBILE REFINEMENT
   ==========================================================================
   Everything below applies only at mobile widths. Desktop is untouched: no
   rule here is outside a max-width query, so the two-column hero, desktop
   typography, spacing, cards and footer all keep their existing values.
   ========================================================================== */

/* ---- Centred hero, wherever the layout is stacked ------------------------ */
@media (max-width: 61.99em) {
  .hero__identity,
  .hero__copy { text-align: center; }

  /* .hero__district is inline-flex; text-align on the parent centres it. */
  .hero__rule { margin-inline: auto; }
  .hero__lede { margin-inline: auto; }
  .hero__actions { justify-content: center; }
  .hero__dates { justify-items: center; text-align: center; }
}

/* ---- Mobile proper ------------------------------------------------------- */
@media (max-width: 47.99em) {
  /* Side padding tracks the viewport: 16px at 320, ~18px at 375, 24px at 480. */
  :root { --gutter: clamp(1rem, 5vw, 1.5rem); }

  /* Tighter stack between the hero blocks. Desktop keeps its own gap. */
  .shell.hero__inner { gap: 16px; }

  /* The scroll cue is redundant on a phone, where scrolling is the obvious
     gesture. Desktop and tablet keep it. */
  .hero__scroll { display: none; }

  /* ---- Feature row under the hero: centre icon and label together -------- */
  .strip__item {
    justify-content: center;
    align-items: center;
    text-align: center;
  }
  .strip__item svg { margin-top: 0; }

  /* ---- Meet Jason: the fact boxes under the portrait -------------------- */
  .meet__facts { text-align: center; }
  .meet__fact-label,
  .meet__fact-value { text-align: center; }

  /* Hero type on mobile. The name and strapline are driven by their own
     tokens in section 17b. */
  .hero__district {
    font-size: 12.8px;
    letter-spacing: 0.05em;
  }
  .hero__lede {
    font-size: 18px;
    line-height: 1.6;
    max-width: 34ch;
  }

  /* Election dates: compact enough that each label and value holds one line. */
  .hero__dates { gap: var(--sp-5); }
  .hero__date-label {
    font-size: 12px;
    letter-spacing: 0.08em;
    white-space: nowrap;
  }
  .hero__date-value {
    font-size: 21px;
    white-space: nowrap;
  }

  /* Hero buttons: compact but still comfortable touch targets. Donate stays
     first and therefore primary. */
  .hero__actions { gap: var(--sp-3); }
  .hero__actions .btn--lg {
    min-height: 50px;
    padding: 0.8rem 1.5rem;
    font-size: 16px;
  }

  /* ---- Section rhythm and centring ------------------------------------- */
  .section-head { margin-inline: auto; text-align: center; }
  .eyebrow { justify-content: center; }
  .eyebrow::before { display: none; }

  .section-title { font-size: 28px; line-height: 1.1; }
  .section-intro { font-size: 16px; line-height: 1.6; margin-inline: auto; }

  /* ---- Body copy ------------------------------------------------------- */
  .bio p,
  .press__meta,
  .date-card__note,
  .donate__lede,
  .donate__point,
  .donate__panel-note,
  .contact__panel p,
  .footer__blurb { font-size: 16px; line-height: 1.6; }

  /* The status note sits quieter than body copy on mobile. 12px is the
     readability floor used elsewhere on the site. */
  .notice { font-size: 12px; line-height: 1.6; }

  /* "In his own words" is centred on mobile. */
  .bio { text-align: center; margin-inline: auto; }
  .bio__lead { font-size: 18px; }
  .bio__pull {
    padding: var(--sp-4) var(--sp-4) var(--sp-4) var(--sp-5);
    text-align: center;
  }
  .bio__attr { text-align: center; }

  /* "Support the campaign" + "Get in touch" share one row. Compact padding and
     type keep both on a single line down to 320px without wrapping. */
  .bio__actions {
    flex-wrap: nowrap;
    justify-content: center;
    gap: var(--sp-2);
  }
  .bio__actions .btn {
    flex: 0 1 auto;
    min-width: 0;
    padding: 0.6rem 0.7rem;
    font-size: 13px;
    white-space: nowrap;
  }

  /* ---- Cards: same design, tighter interior --------------------------- */
  .issue-card { padding: 22px 20px; }
  .date-card { padding: 22px 20px; text-align: center; }
  .date-card::before { inset: 0 auto auto 0; width: 100%; height: 4px; }
  .date-card__value { font-size: 26px; }
  .contact__item { padding: 20px; }
  .donate__panel { padding: 24px 20px; }
  .press__body { padding: 24px 20px; }
  .meet__facts { padding: 20px; }

  /* ---- Donation CTA stays prominent ------------------------------------ */
  .donate__copy { text-align: center; }
  .donate__points { justify-items: center; }
  .donate__point { text-align: left; max-width: 34ch; }

  /* "Donate to the campaign" keeps full width for prominence but drops to the
     same compact type and height as the other mobile CTAs. */
  .donate__panel .btn--lg {
    min-height: 50px;
    padding: 0.8rem 1.1rem;
    font-size: 16px;
  }

  /* ---- Contact + press ------------------------------------------------- */
  .contact__panel { text-align: center; }
  .contact__panel .btn { margin-inline: auto; }
  .press__body { text-align: center; }
  .press__body .btn { margin-inline: auto; }

  /* ---- Footer ---------------------------------------------------------- */
  /* The campaign identity column stays centred; the three link/contact columns
     are left-aligned so their lists read as lists rather than centred text. */
  .footer__col--brand { text-align: center; }
  .footer__brand { justify-content: center; }
  .footer__blurb { margin-inline: auto; }

  .footer__col:not(.footer__col--brand) { text-align: left; }
  .footer__col:not(.footer__col--brand) .footer__list { justify-items: start; }
  .footer__col:not(.footer__col--brand) .footer__address { justify-items: start; }
  .footer__social { justify-content: flex-start; }

  .footer__bottom { justify-content: center; text-align: center; }
  .footer__legal { margin-inline: auto; }
}

/* ---- Narrow phones: trim the interior a little further -------------------- */
@media (max-width: 22.5em) {
  .issue-card,
  .date-card { padding: 20px 16px; }
  .donate__panel,
  .press__body { padding: 20px 16px; }
  .hero__actions .btn--lg { padding: 0.8rem 1.15rem; }
}

/* At 320px "Donate to the campaign" needs a little more room than the panel
   leaves it, and would otherwise break onto a second line. */
@media (max-width: 20em) {
  .donate__panel .btn--lg {
    padding: 0.75rem 0.8rem;
    font-size: 15px;
    gap: var(--sp-2);
  }
}

/* ==========================================================================
   CONTACT FORM
   ==========================================================================
   The form is a GoHighLevel embed in a cross-origin iframe, so nothing in this
   stylesheet can reach inside it. All we style is the box it sits in.
   ========================================================================== */
.ghl-form {
  margin-bottom: var(--sp-6);
  border-radius: var(--r-lg);
  overflow: hidden;
}
/* GHL ships a script that resizes the iframe to fit its contents. This
   min-height is what a visitor sees before that script runs, and what they get
   if it never does, so the section is never a thin empty strip. */
.ghl-form iframe {
  display: block;
  width: 100%;
  min-height: 34rem;
  border: 0;
}

/* Shown only while the embed is missing. Matches .contact__panel in the column
   beside it, so a half-configured section still reads as a pair rather than
   looking broken. */
.cform--pending {
  margin-bottom: var(--sp-6);
  padding: clamp(1.4rem, 3.6vw, 2.15rem);
  background: var(--blue-50);
  border: 1px solid var(--blue-100);
  border-radius: var(--r-lg);
}
.cform__pending-title {
  margin: 0 0 var(--sp-3);
  font-weight: 700;
  color: var(--charcoal-900);
}
.cform__pending-text {
  margin: 0;
  font-size: var(--step--1);
  line-height: 1.7;
  color: var(--text-muted);
}
/* Same orange as the old required marker: darker than --orange-700 so it
   clears 4.5:1 on this panel's blue-50 ground. */
.cform__pending-text a { color: hsl(28deg 88% 37%); }

/* ==========================================================================
   MESSAGE PAGES: 404, 500 and thank-you
   ==========================================================================
   Was an inline <style> in 404.html. The three pages share one template now,
   so the rules live here where they are cached with the rest of the CSS.
   ========================================================================== */
.msg {
  display: grid;
  place-items: center;
  min-height: 100svh;
  padding: var(--sp-7) var(--gutter);
  text-align: center;
  background: linear-gradient(168deg, var(--blue-800) 0%, var(--blue-900) 68%,
              hsl(210deg 60% 12%) 100%);
  color: var(--white);
}
.msg__inner { max-width: 34rem; }
.msg__inner.mxw35 { max-width: 35rem; }
.msg__mark {
  display: block;
  width: 64px;
  height: 64px;
  margin: 0 auto var(--sp-5);
  border-radius: 15px;
}
.msg__code {
  margin: 0 0 var(--sp-3);
  font-family: var(--font-display);
  font-size: var(--step-4);
  font-weight: 700;
  line-height: 1;
  color: var(--orange);
}
/* The colour is NOT inherited from .msg. main.css sets a colour on
   h1..h4 directly, and an element's own rule beats a colour inherited from its
   parent, so without this the heading renders near-black on the dark blue. */
.msg__title {
  margin: 0 0 var(--sp-4);
  font-size: var(--step-2);
  line-height: 1.15;
  color: var(--white);
}
.msg__text {
  margin: 0 0 var(--sp-6);
  line-height: 1.7;
  color: hsl(205deg 35% 88%);
}
.msg__actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-3);
  justify-content: center;
}

/* Detail about the donation page, shown in the donate panel.
   This list sits inside .donate__panel, a WHITE card on the dark donate
   section, so it needs DARK text rather than the light text the surrounding
   section uses. Reversing that makes it invisible. */
.donate__facts {
  margin: var(--sp-4) 0 0;
  padding: 0;
  list-style: none;
  display: grid;
  gap: 0.45rem;
  text-align: left;
  font-size: 0.86rem;
  line-height: 1.5;
  color: var(--charcoal-600);        /* 7.49:1 on white */
}
.donate__facts li { position: relative; padding-left: 1rem; }
.donate__facts li::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0.55em;
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: var(--orange-700);     /* the white-background orange */
}
