/* ==========================================================================
   VoteforChangeMI.com — design system
   --------------------------------------------------------------------------
   Brand source of truth: "vote for change logo/VOTE for CHANGE - Logo Web.html"
     navy   #062B5B   CHANGE, borders, body ink
     blue   #2E7CD6   VOTE, the ballot oval
     gold   #E0A411   the italic "for", the checkmark
     type   Oswald 600 / Archivo Black / Libre Baskerville italic

   MOTIF SYSTEM, taken from the mark itself:
     - the "O" of VOTE is a ballot oval with a gold check inside. That oval
       is reused as bullet, section marker, favicon, and checklist glyph.
     - the lockup sits in a white chip with a thick navy border. That chip
       becomes the card, the button, and the callout.
     - a stylized Michigan silhouette localizes it. The research package is
       explicit that the vernacular should come from Michigan rather than be
       lifted from another campaign's palette.

   CONSTRAINTS THAT DROVE EVERY CHOICE
     - Read on a cheap phone, in sunlight, one-handed, in a parking lot.
     - Two-second attention budget: the answer must be legible before the
       reader decides to scroll.
     - WCAG 2.2 AA on every text/background pair. Ratios are noted inline and
       were computed, not guessed. Gold never carries small text on white
       (2.2:1) — it only appears on navy, or as a fill behind navy.
     - Fonts are self-hosted, subset from the logo file, 25KB total. No CDN,
       no external request, strict CSP stays intact.
   ========================================================================== */

/* --------------------------------------------------------------------------
   1. Brand fonts — subset from the logo file's embedded originals.
   All three are SIL Open Font License; see README for attribution.
   -------------------------------------------------------------------------- */
@font-face {
  font-family: "Oswald VFC";
  src: url("/assets/fonts/oswald-subset.woff2") format("woff2");
  font-weight: 200 700;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Archivo Black VFC";
  src: url("/assets/fonts/archivoblack-subset.woff2") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}
/* Full Latin charset, not just the lockup's "for" — the gold italic serif is
   the most distinctive thing in the mark, so it earns a role beyond the logo
   as a connective-word accent in headlines. 20KB. */
@font-face {
  font-family: "Baskerville VFC";
  src: url("/assets/fonts/librebaskerville-italic-subset.woff2") format("woff2");
  font-weight: 400;
  font-style: italic;
  font-display: swap;
}

/* --------------------------------------------------------------------------
   2. Tokens
   -------------------------------------------------------------------------- */
:root {
  /* Brand */
  --navy:      #062B5B;  /* white on navy = 13.9:1                          */
  --navy-2:    #0B3D77;  /* raised surface on navy                          */
  --blue:      #2E7CD6;  /* BRAND. Large display + UI only (4.2:1 on white) */
  --blue-ink:  #1A5FAB;  /* body links on light — 6.4:1 white, 5.8:1 paper  */
  --blue-lt:   #7FBEF0;  /* small text on navy — 7.0:1                      */
  --gold:      #E0A411;  /* on navy 6.3:1; navy on gold 6.3:1; NEVER on white */
  --urgent:    #B82308;  /* deadlines. Darkened from #C4260D, which measured  */
                         /* 4.44:1 on --paper-2 and missed AA by a hair on   */
                         /* eyebrows, open FAQ summaries and .accent. Now:   */
                         /* 5.8:1 on paper, 4.9:1 on paper-2, white on it    */
                         /* 6.4:1. Strictly better in every existing use.    */

  /* Surfaces — ballot stock, not newsprint.
     Deliberately cool. Warm cream (#F4F1EA-ish) is the single most overused
     background in generated design right now, and it drags a serif/terracotta
     association with it. Optical-scan ballots are printed on cool pale
     grey-blue stock with heavy ink, which is both the truer reference for this
     subject and immediately not that cliché.
     navy on --paper = 12.3:1, on --paper-2 = 10.8:1.                        */
  --paper:     #EEF1F5;  /* ballot stock                                     */
  --paper-2:   #DCE3EC;  /* shaded contest block                             */
  --white:     #FFFFFF;
  --ink:       var(--navy);
  --ink-soft:  #4A5A6B;  /* secondary text — 6.4:1 on paper                 */

  /* Type */
  --font-display: "Archivo Black VFC", "Archivo Black", "Arial Black",
                  "Segoe UI Black", Impact, system-ui, sans-serif;
  --font-cond:    "Oswald VFC", Oswald, "Haettenschweiler", "Arial Narrow",
                  system-ui, sans-serif;
  --font-serif:   "Baskerville VFC", "Libre Baskerville", Georgia,
                  "Times New Roman", serif;
  --font-body:    system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI",
                  Roboto, "Helvetica Neue", Arial, sans-serif;

  /* Fluid scale — clamp() so no breakpoint is ever needed */
  --t-mega: clamp(2.5rem, 10.5vw, 6.5rem);
  --t-xxl:  clamp(1.875rem, 6vw, 3.75rem);
  --t-xl:   clamp(1.5rem, 4vw, 2.375rem);
  --t-lg:   clamp(1.1875rem, 2.6vw, 1.5rem);
  --t-md:   1.0625rem;
  --t-sm:   0.9375rem;
  --t-xs:   0.8125rem;

  /* Space — 4px base */
  --s-1:.25rem; --s-2:.5rem; --s-3:.75rem; --s-4:1rem;
  --s-5:1.5rem; --s-6:2rem;  --s-7:3rem;   --s-8:4rem; --s-9:6rem;

  /* Structure. The logo's 8px border on a 72px cap-height scales to ~3px
     at body sizes, which is where --rule comes from. */
  --rule: 3px;
  --pop: 6px;
  --wrap: 1140px;
  --prose: 68ch;
  /* The mark's checkmark as a mask shape, so anything using it picks its own
     colour. Same stroke geometry as the lockup's SVG. */
  --tick: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M4 12.6L9.4 18L20 6.2' fill='none' stroke='%23000' stroke-width='3.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  --tap: 48px;
}

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

html {
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
  scroll-behavior: smooth;
  /* Clears the only remaining sticky element, the guide pages' section nav.
     Was 6rem when the sticky deadline bar sat above it. */
  scroll-padding-top: 4.5rem;
  /* Lets height/width transition to and from keywords like `auto`. This is
     what makes the ballot's reveal a real accordion instead of a hard-coded
     max-height guess. Ignored where unsupported, which degrades to an instant
     open — still correct, just not animated. */
  interpolate-size: allow-keywords;
}

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font: 400 var(--t-md)/1.6 var(--font-body);
  font-synthesis-weight: none;
  text-rendering: optimizeLegibility;
  overflow-x: hidden;
}

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

h1, h2, h3, h4 {
  margin: 0;
  font-family: var(--font-display);
  font-weight: 400;                 /* Archivo Black has one weight          */
  line-height: 0.98;
  letter-spacing: -0.02em;
  text-transform: uppercase;
  text-wrap: balance;
}
h1 { font-size: var(--t-xxl); }
h2 { font-size: var(--t-xl); }
h3 { font-size: var(--t-lg); letter-spacing: -0.01em; }
h4 { font-size: var(--t-md); letter-spacing: 0; }

p, li { text-wrap: pretty; }
p { margin: 0 0 var(--s-4); }

a { color: var(--blue-ink); text-decoration-thickness: 2px;
    text-underline-offset: 3px; }
a:hover { text-decoration-thickness: 4px; }
strong, b { font-weight: 700; }

:focus-visible {
  outline: var(--rule) solid var(--gold);
  outline-offset: 3px;
  border-radius: 2px;
}
.band--paper2 :focus-visible, .band:not(.band--navy) :focus-visible {
  outline-color: var(--urgent);
}
::selection { background: var(--gold); color: var(--navy); }

/* --------------------------------------------------------------------------
   4. Layout primitives
   -------------------------------------------------------------------------- */
.wrap { width: 100%; max-width: var(--wrap); margin-inline: auto;
        padding-inline: var(--s-5); }
.prose { max-width: var(--prose); }
/* Running prose in a content band is capped by default rather than on request.
   .prose existed but was opt-in, so any paragraph that forgot it ran the full
   column — 119 and 135 characters per line on desktop. Scoped to direct children
   of a band's .wrap so it never touches grid cells, table text, or the footer. */
.band .wrap > p { max-width: var(--prose); }
.band { padding-block: var(--s-8); position: relative; }
.band--tight  { padding-block: var(--s-6); }
.band--navy   { background: var(--navy); color: var(--paper); }
.band--navy-2 { background: var(--navy-2); color: var(--paper); }
.band--paper2 { background: var(--paper-2); }
.band--gold   { background: var(--gold); color: var(--navy); }
.band--urgent { background: var(--urgent); color: var(--white); }

.band--navy a, .band--navy-2 a, .band--urgent a, .hero a { color: var(--blue-lt); }
.band--navy h2, .band--navy-2 h2 { color: var(--white); }
.band--urgent a { color: var(--white); }
.band--gold a { color: var(--navy); }

.stack > * + * { margin-top: var(--s-4); }
.grid { display: grid; gap: var(--s-5); }
@media (min-width:640px)  { .grid--2 { grid-template-columns: repeat(2,1fr); } }
@media (min-width:900px)  { .grid--3 { grid-template-columns: repeat(3,1fr); } }
@media (min-width:900px)  { .grid--4 { grid-template-columns: repeat(2,1fr); } }
@media (min-width:1100px) { .grid--4 { grid-template-columns: repeat(4,1fr); } }

.center { text-align: center; }
/* Hidden by clipping, not by `left: -9999px`.
   The off-screen hack is invisible and harmless in LTR — browsers do not make
   negative-left content scrollable. In RTL the scroll origin moves to the other
   edge, so the same -9999px became 10,000px of real horizontal scroll and the
   Arabic pages could be dragged sideways into empty space. Clipping is
   direction-agnostic and is what .sr in this file already uses. */
.skip {
  position: absolute; top: 0; z-index: 999;
  width: 1px; height: 1px; overflow: hidden;
  clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap;
  background: var(--gold); color: var(--navy);
  padding: var(--s-3) var(--s-5);
  font: 600 var(--t-md)/1 var(--font-cond); text-transform: uppercase;
}
.skip:focus {
  width: auto; height: auto; overflow: visible;
  clip: auto; clip-path: none;
  inset-inline-start: var(--s-4); top: var(--s-4);
  min-height: var(--tap); display: inline-flex; align-items: center;
}

/* MOTIF: divider as a row of ballot ovals rather than a plain rule. */
.perf {
  height: 10px; border: 0; margin: 0;
  background-image: radial-gradient(circle at 5px 5px,
    var(--blue) 0 3.5px, transparent 3.6px);
  background-size: 22px 10px;
  background-repeat: repeat-x;
  opacity: .8;
}
.band--navy .perf, .band--navy-2 .perf {
  background-image: radial-gradient(circle at 5px 5px,
    var(--blue-lt) 0 3.5px, transparent 3.6px);
}

/* --------------------------------------------------------------------------
   5. The logo lockup
   Rebuilt from the source file: white chip, thick navy border, VOTE in
   Oswald blue, "for" in Baskerville italic gold, CHANGE in Archivo Black
   navy. The O is an inline SVG ballot oval so it stays crisp at any size.
   -------------------------------------------------------------------------- */
/* Lockup stacked over its locality line, rather than the line set vertically
   alongside it. Centred so the short word reads as part of the lockup instead
   of drifting to one edge of the much wider chip. */
/* --lockup-size lives on .brand so the wordmark and the locality line can both
   derive from it and stay in proportion wherever the lockup is reused. */
.brand { --lockup-size: 1.375rem;
         display: inline-flex; flex-direction: column; align-items: center;
         gap: .28rem; text-decoration: none; }
/* Gold chip, navy mark, white accents.
   Gold is a light colour — luminance 0.42 — so it can only carry navy type.
   Measured on --gold: navy 6.3:1, white 2.2:1, --blue 1.9:1, --blue-ink 2.9:1.
   That is why VOTE is navy here and not the brand blue: on gold, blue reads at
   1.9:1 and effectively disappears. Navy is the only value in the palette dark
   enough to hold the wordmark on a gold field. */
.lockup {
  display: inline-flex; align-items: baseline; line-height: 1;
  gap: .18em;
  padding: .34em .5em;
  border: var(--rule) solid var(--navy);
  border-radius: 3px;
  background: var(--gold);
  font-size: var(--lockup-size, 1.375rem);
}
.lockup__vote {
  font-family: var(--font-cond); font-weight: 600;
  font-size: 1em; letter-spacing: .02em; color: var(--navy);
  display: inline-flex; align-items: baseline;
}
/* Sized to the cap height of the letters beside it, not to the em.
   The SVG box is not the disc: the circle is r=38 with stroke-width 13, so the
   painted edge lands at radius 44.5 of a 100 viewBox — the disc is 89% of the box.
   At the old .8em box the visible disc came out 15.66px against an 18px cap
   height, so the O sat 13% short of V, T and E and read as a smaller character.

   Oswald's cap height is .8182em (tall, as condensed faces go). Matching it needs
   a box of .8182 / .89 = .919em; the extra 2% is the overshoot type designers give
   round glyphs, because a circle drawn to exactly cap height reads slightly small
   next to flat-sided letters. .919 * 1.02 = .938em. */
.lockup__o {
  --o-cap: .8127;                   /* Oswald cap height, from rendered ink     */
  --o-over: 1.015;                  /* round-glyph overshoot                    */
  --o-disc: .89;                    /* painted disc as a fraction of the box    */
  width: calc(var(--o-cap) * var(--o-over) / var(--o-disc) * 1em);
  height: calc(var(--o-cap) * var(--o-over) / var(--o-disc) * 1em);
  margin: 0 .04em;
  /* align-self: center centres the SVG *box*, and the box includes the transparent
     ring around the disc — so the painted circle rode 1px above the cap line and
     stopped 0.5px short of the baseline. This drops it onto the cap axis: exact at
     the header size, which is the lockup on all 13 pages.

     Residual elsewhere is ±1.25px (footer sits marginally low, mobile marginally
     high) and it is glyph rasterisation snapping to the pixel grid, not an error in
     this number — the cap height a font actually paints is not a clean multiple of
     font-size. Anchoring to the baseline with a negative bottom margin was tried
     instead and only relocated the same residual, so it is not worth the more
     complicated rule. Do not add per-breakpoint nudges to chase it. */
  transform: translateY(.034em);
  align-self: center; flex: none;
}
/* The O is a filled navy seal, not an outline. As an outline the gold field showed
   through the counter and the white check measured 2.21:1 against it — unreadable
   at logo scale. Filled, the check is white on navy at 13.96:1 and becomes the
   sharpest element in the mark, which is the right thing to emphasise. The check
   itself supplies the interior detail, so the shape still reads as a letter
   rather than as a solid dot. */
.lockup__o circle { fill: var(--navy); stroke: var(--navy); stroke-width: 13; }
.lockup__o path {
  fill: none; stroke: var(--white); stroke-width: 13;
  stroke-linecap: round; stroke-linejoin: round;
}
/* Navy, not white. White italic serif on gold is 2.21:1 and there is no way to
   rescue it — gold is too light to carry white type at any size. Navy gives
   6.31:1, and it leaves the white check as the mark's single accent instead of
   two washed-out ones competing. */
.lockup__for {
  font-family: var(--font-serif); font-style: italic;
  /* .58em of a 15px lockup is 8.7px, which is past the point where an italic
     serif at this weight is readable rather than decorative. Floored the same
     way .brand__mi is, so the mark keeps its proportions everywhere it has room
     and only stops shrinking at the small end. */
  font-size: max(0.6875rem, .58em); color: var(--navy);
  align-self: center; margin: 0 .12em;
}
.lockup__change {
  font-family: var(--font-display); font-size: 1em;
  letter-spacing: .005em; color: var(--navy);
}
/* Subordinate state tag — the logo is "VOTE for CHANGE", the site is the
   Michigan program, so MICHIGAN is set as an extension, never inside the chip. */
.brand__mi {
  /* Sized from the lockup rather than fixed, so it reads as the wordmark's
     second line rather than as a caption. The ratio is a variable because it is
     tuned against the chip: in the reference artwork MICHIGAN spans almost the
     full width of the chip above it, and that proportion is what makes it read
     as part of the mark instead of a label stuck underneath. */
  --mi-ratio: 0.88;
  --mi-track: .30em;
  font-family: var(--font-cond);
  font-weight: 600;
  /* Floor of 13px — below that the wide tracking stops working and it reads as
     a caption again, which is the failure mode this replaced. */
  font-size: max(0.8125rem, calc(var(--lockup-size) * var(--mi-ratio)));
  line-height: 1;
  /* Tracking was 0.76em, which spanned the full chip width but pulled the word
     apart into separate letters. Legibility beats matching the artwork's
     proportion: 0.30em still reads as a deliberately letterspaced locality line
     rather than body copy, but it reads as one word. The lost width is bought
     back with point size instead — 0.88 puts it at 19.4px against the 22px
     wordmark, still clearly subordinate, and ~59% of the chip. */
  letter-spacing: var(--mi-track);
  margin-inline-end: calc(var(--mi-track) * -1);   /* cancel the trailing space so */
                                              /* the word optically centres   */
  text-transform: uppercase;
  /* White, not the light blue this used to be. Once the chip went gold, VOTE had
     to go navy, which took blue out of the lockup entirely — light blue down here
     had nothing left to relate to and read as an orphan colour. White picks up
     the "for" and the checkmark, so the chip and the locality line read as one
     object. It is also the highest contrast of the candidates: white 13.96:1 on
     navy, against light blue 7:1 and gold 6.31:1. */
  color: var(--white);
}
@media (max-width: 420px) {
  .brand { --lockup-size: 1.0625rem; }
}

/* --------------------------------------------------------------------------
   5b. MOTIF: the lockup's gold italic serif, promoted to a headline accent
   --------------------------------------------------------------------------
   The italic Baskerville "for" is the most distinctive thing in the mark. Used
   on one connective or qualifying word per headline it ties every page back to
   the logo without repeating the logo. One per headline — never two.
   Colour flips by surface because gold on white is only 2.2:1.
   -------------------------------------------------------------------------- */
.accent {
  font-family: var(--font-serif);
  font-style: italic;
  font-weight: 400;
  color: var(--urgent);              /* light-surface default, 5.2:1        */
  text-transform: none;
  letter-spacing: 0;
  font-size: .82em;                  /* Baskerville runs small next to      */
                                     /* Archivo Black; optically matched    */
}
.hero .accent,
.band--navy .accent,
.band--navy-2 .accent { color: var(--gold); }
.band--urgent .accent { color: var(--white); }
.band--gold .accent   { color: var(--navy); }

/* --------------------------------------------------------------------------
   5c. Card icons — drawn to the mark's own logic: navy structure, one gold
   accent each, stroke weights matched to the lockup's border.
   -------------------------------------------------------------------------- */
.icon {
  width: 2.5rem; height: 2.5rem; flex: none;
  --ic: var(--navy);
  --ia: var(--gold);
}
.icon [data-s] {
  fill: none; stroke: var(--ic); stroke-width: 1.7;
  stroke-linecap: round; stroke-linejoin: round;
}
.icon [data-g] {
  fill: none; stroke: var(--ia); stroke-width: 2.2;
  stroke-linecap: round; stroke-linejoin: round;
}
/* On the gold card, a gold accent would disappear. */
.card--now .icon { --ia: var(--urgent); }
.card--closed .icon { --ic: var(--ink-soft); --ia: var(--ink-soft); }

/* --------------------------------------------------------------------------
   5d. Scroll reveal
   --------------------------------------------------------------------------
   Content is visible by default and stays visible with JS off. The script adds
   .reveal-on to <html>, which is the only thing that arms the hidden state, so
   a failed script can never hide the page. Disabled under reduced motion.
   -------------------------------------------------------------------------- */
@media (prefers-reduced-motion: no-preference) {
  .reveal-on .reveal {
    opacity: 0;
    transform: translateY(14px);
    transition: opacity .5s cubic-bezier(.2,.6,.2,1),
                transform .5s cubic-bezier(.2,.6,.2,1);
  }
  .reveal-on .reveal.is-in { opacity: 1; transform: none; }
  /* Stagger siblings inside a grid so a row of cards arrives in sequence. */
  .reveal-on .reveal:nth-child(2) { transition-delay: .07s; }
  .reveal-on .reveal:nth-child(3) { transition-delay: .14s; }
  .reveal-on .reveal:nth-child(4) { transition-delay: .21s; }
}

/* ==========================================================================
   5e. THE BALLOT
   --------------------------------------------------------------------------
   The homepage is not a landing page with voting decoration on it. It is laid
   out as an optical-scan primary ballot, because that is the artifact this
   whole site exists to get someone to fill in.

   Structure lifted from real primary-ballot standards: equal-width contest
   columns separated by a heavy solid rule, with a centre INSTRUCTION column
   carrying the "do not vote for candidates of more than one party" warning.
   That is genuinely where a ballot puts its most important instruction, and it
   happens to be this site's most important message too.

   The inner pages stay laid out as documents — they are the instruction
   pamphlet to this ballot. Same world, different artifact.
   ========================================================================== */

/* Top matter: the heavy header bar a ballot opens with.
   Carries the Michigan artwork, scrimmed, so the header has the texture of
   printed stock rather than being a flat navy slab. Same measured treatment as
   before: art at low opacity, gradient scrim keeping type on near-pure navy. */
.ballot-head {
  position: relative; overflow: hidden;
  background: var(--navy); color: var(--paper);
  border-bottom: 10px solid var(--gold);
}
.ballot-head::before {
  content: ""; position: absolute; inset: 0; z-index: 0;
  background-image: url("/assets/img/hero-art.webp");
  background-size: cover; background-position: 76% center;
  opacity: .30;
}
/* The scrim runs solid under the type column and then clears out completely, so
   the right-hand panel is the photograph at full strength rather than a dimmed
   suggestion of one. It reaches zero by 82%: past that there is nothing to
   protect, because no text goes there. */
/* The scrim no longer goes fully opaque anywhere: .82 lets ~18% of the footage
   through even behind the type, so the left side carries texture instead of being
   a flat navy slab, then clears completely for the picture on the right.

   .82 is not a taste call — it is the floor. The footage contains PURE WHITE
   pixels (the ABDUL signs, the stage lights; the top 0.1% of pixels still measure
   L=0.95), so every plate figure has to be computed against white rather than
   against the footage's average. Solved against white, .82 keeps every item in
   the top matter above its AA threshold with the tightest at 3.66:1. Drop it
   further and the gold headline goes under 3.0. */
.ballot-head::after {
  content: ""; position: absolute; inset: 0; z-index: 1;
  background: linear-gradient(100deg,
    rgba(6, 43, 91, .82) 0 46%,
    rgba(6, 43, 91, .26) 57%,
    rgba(6, 43, 91, 0) 64%);
}
/* These two were the binding constraint at 13px: gold and light blue are the
   lightest inks on the page, and small text needs 4.5 rather than 3.0, so they
   alone capped show-through at 10%. Paper costs nothing here — the countdown
   digits above them are still gold and carry the accent. */
.eday__t, .eday .count__l { color: var(--paper); }

/* Base video layer. MUST stay above the media queries below: it sets `inset: 0`,
   and when it sat after them that shorthand silently reset the desktop panel's
   left/right back to full width at equal specificity. Same source-order trap the
   nav hit earlier.
   object-position 60% rather than centre: on a phone the box is far taller than
   the footage, so cover crops horizontally and only ~38% of the width survives —
   60% keeps Abdul and Bernie inside that slice instead of framing empty stage. */
.bh__vid {
  position: absolute; inset: 0; z-index: 0;
  width: 100%; height: 100%;
  object-fit: cover; object-position: 60% center;
  opacity: var(--bh-vid-op, .9);
  pointer-events: none;
}

/* Desktop: the footage is a panel on the right rather than a wash behind
   everything. This is what makes the contrast question go away instead of being
   fought — the type sits on flat navy, the rally has its own half, and the two
   meet on a feathered edge so there is no box seam. Full opacity, because it no
   longer has anything to hide behind.

   Type widths are capped to match: the headline's ink ends at 60%, the video
   reaches full strength at ~64%, so they never contend for the same pixels. */
@media (min-width: 701px) {
  /* The three of them span about 58% of the frame, so at cover scale they simply
     do not fit in a narrow strip — the picture needs roughly half the band, and
     the type column has to give up the room. That is the trade: no texture behind
     the headline any more, but all three faces clear of it.
     The mask starts feathering at 48% rather than at the panel edge, so there is
     still a soft wash where the type ends instead of a seam. */
  .bh__vid {
    --bh-vid-op: 1;
    left: auto; right: 0; width: 52%;
    -webkit-mask-image: linear-gradient(to right, transparent 0, #000 22%);
            mask-image: linear-gradient(to right, transparent 0, #000 22%);
  }
  /* Type is capped well short of the picture. The headline drops a step in size
     so it still breaks over three lines inside the narrower column instead of
     four. */
  /* Percentages here are of .ballot-head__in, which is the centred .wrap — not of
     the full band. The wrap is inset, so 45% of it lands near 46% of the band. */
  .ballot-head__in > * { max-width: 45%; }
  .ballot-head h1 { max-width: 45%; }   /* size lives on the base rule below */
  .ballot-head__meta { max-width: 62%; }
}
/* Phone: there is no free half, so the same idea rotates 90°. The picture becomes
   a band across the bottom and the type sits above it on plain navy. Buried behind
   the type at low opacity the rally was invisible, which defeated the point of
   shooting it — here it is a photograph at full strength, and the type never
   crosses it, so again no scrim is doing load-bearing work.
   Both the video and the still confine themselves to the band, so the layout is
   identical whether the footage plays, is blocked, or is refused for reduced
   motion. */
@media (max-width: 700px) {
  .ballot-head { padding-bottom: 52vw; }
  .bh__vid {
    --bh-vid-op: 1;
    top: auto; bottom: 0; height: 52vw;
    object-position: 58% 26%;
    -webkit-mask-image: linear-gradient(to bottom, transparent 0, #000 20%);
            mask-image: linear-gradient(to bottom, transparent 0, #000 20%);
  }
  .ballot-head::before {
    top: auto; bottom: 0; height: 52vw;
    background-image: url("/assets/img/rally-poster.jpg");
    background-position: 58% 26%; opacity: 1;
  }
  /* Nothing to protect: the type is on flat navy above the band. */
  .ballot-head::after { display: none; }
}
/* ---- Rally loop in the top matter -------------------------------------------
   Sits at z-index 0, exactly where the still image was, so the ::after scrim
   still sits between it and the type. That matters: the scrim's left 44% is
   solid navy, which is where the headline lives, so the video cannot affect the
   headline's contrast no matter what frame it is on. Opacity is a variable
   because it was tuned by sampling the real composited pixels across the loop,
   not by eye — see the video contrast check in the scratchpad.

   .has-vid is added by the script only once playback actually starts, and it
   switches off the still. One art layer at a time: the still for no-JS, blocked
   autoplay and reduced-motion; the video for everyone else. Never both, which
   would composite two semi-transparent layers into mud. */
.ballot-head.has-vid::before { background-image: none; }
@media (prefers-reduced-motion: reduce) { .bh__vid { display: none; } }

/* WCAG 2.2.2: the loop starts on its own and repeats, so there has to be a way
   to stop it. Sits over the scrim, out of the way of the type, and is revealed
   by the script only when it can actually do something. */
.bh__vidbtn {
  position: absolute; z-index: 3; right: var(--s-4); bottom: var(--s-4);
  min-height: 44px; padding: 0 var(--s-4);
  font: 600 var(--t-xs)/1 var(--font-cond);
  letter-spacing: .12em; text-transform: uppercase;
  color: var(--paper); background: rgba(6, 43, 91, .82);
  border: var(--rule) solid var(--blue-lt); border-radius: 3px;
  cursor: pointer;
}
.bh__vidbtn:hover { background: var(--navy); color: var(--white); }
.bh__vidbtn[hidden] { display: none; }

.ballot-head__in { position: relative; z-index: 2;
                   padding-block: var(--s-5) var(--s-6); }

/* Election Day countdown in the top matter. The rail's countdown answers "what is
   the next deadline"; this answers "how long do I have at all", which is the
   question people actually arrive with — and after the last deadline passes it is
   the only one still counting. Inside .ballot-head__in, so it clears the artwork
   and scrim on z-index 2. */
.eday { margin-top: var(--s-6); max-width: 32rem; }
.eday__t {
  font: 600 var(--t-xs)/1 var(--font-cond);
  letter-spacing: .18em; text-transform: uppercase;
  color: var(--gold); margin: 0;                    /* 6.31:1 on navy */
}
.eday .count { margin-top: var(--s-4); padding-top: var(--s-4); }

/* The countdown reads as a rail block here, not as a hero flourish. */
.rail .count {
  margin-top: 0; padding-top: 0; border-top: 0;
  gap: var(--s-3);
}
.rail .count__unit { min-width: 3.25rem; }
.rail .count__n { font-size: clamp(1.75rem, 5vw, 2.25rem); color: var(--urgent); }
.rail .count__l { color: var(--ink-soft); }

/* Narrow two-column deadline table for the rail. */
.dates--rail th, .dates--rail td { padding: var(--s-2) 0; }
.dates--rail th {
  text-align: start; font: 700 var(--t-sm)/1.3 var(--font-body);
  padding-inline-end: var(--s-3);
}
.dates--rail td { text-align: end; }
.dates--rail td span[data-when] { display: block; }
.dates--rail .pill { display: inline-block; margin-top: var(--s-1); }
.dates--rail tr[data-state="now"] { background: transparent; }
.dates--rail tr[data-state="past"] th { text-decoration: line-through; }
.ballot-head__meta {
  display: flex; flex-wrap: wrap; gap: var(--s-2) var(--s-6);
  font: 500 var(--t-xs)/1 var(--font-cond);
  letter-spacing: .18em; text-transform: uppercase;
  color: var(--blue-lt);
  padding-bottom: var(--s-4);
  border-bottom: 1px solid rgba(127, 190, 240, .35);
  margin-bottom: var(--s-5);
}
.ballot-head__meta b { color: var(--white); font-weight: 500; }
/* Sized to break over three lines inside a 45%-wide column, since the picture now
   takes the other half. Set on the base rule on purpose: this rule sits after the
   desktop media query, so a font-size declared up there loses to it at equal
   specificity — the same source-order trap the video panel and the nav both hit.
   Change the size here, not there. */
.ballot-head h1 {
  font-size: clamp(1.9rem, 3.5vw, 2.8rem);
  color: var(--white); letter-spacing: -.025em; margin-bottom: var(--s-4);
}
.ballot-head h1 em { font-style: normal; color: var(--gold); display: block; }
.ballot-head__lede {
  font-size: var(--t-lg); line-height: 1.45; max-width: 42ch; margin: 0;
}
/* The two unlocks are a list because they are a list — two separate facts a
   voter checks against themselves, not a sentence to read through. */
.ballot-head__two {
  margin: var(--s-4) 0 0; max-width: 44ch;
  font-size: var(--t-md); line-height: 1.4;
}

/* The two-column body: contests left, instruction rail right, heavy rule
   between them exactly as a printed ballot separates its columns. */
.ballot {
  display: grid;
  gap: 0;
  align-items: start;
}
@media (min-width: 1000px) {
  .ballot { grid-template-columns: minmax(0, 1fr) 20rem; }
  .ballot__rail {
    border-inline-start: 8px solid var(--navy);
    padding-inline-start: var(--s-5);
    position: sticky; top: 1.5rem;
  }
  .ballot__main { padding-inline-end: var(--s-6); }
}
@media (max-width: 999px) {
  .ballot__rail { border-top: 8px solid var(--navy); padding-top: var(--s-5);
                  margin-top: var(--s-6); }
}

/* A contest block. Numbered because a ballot's contests genuinely are
   sequential and the number is how a voter keeps their place. */
.contest { border: 0; margin: 0 0 var(--s-8); padding: 0; }
.contest__head {
  border-bottom: 4px solid var(--navy);
  padding-bottom: var(--s-3); margin-bottom: var(--s-2);
}
.contest__no {
  font: 500 var(--t-xs)/1 var(--font-cond);
  letter-spacing: .2em; text-transform: uppercase; color: var(--urgent);
  display: block; margin-bottom: var(--s-2);
}
.contest__q {
  font-family: var(--font-display); font-weight: 400;
  font-size: var(--t-xl); line-height: 1; letter-spacing: -.02em;
  text-transform: uppercase; color: var(--navy);
  display: block;
}
/* The real ballot phrasing. Not a joke — it is the clearest instruction
   language ever written for this, which is why ballots still use it. */
.contest__rule {
  font: 500 var(--t-sm)/1.3 var(--font-cond);
  letter-spacing: .12em; text-transform: uppercase;
  background: var(--navy); color: var(--white);
  padding: var(--s-2) var(--s-3);
  margin-bottom: var(--s-5);
}

/* ---- SIGNATURE: the mark's ballot oval, made into a working input --------
   The logo's "O" is an oval with a gold check. Here it is an actual control:
   fill it and the page commits to your method and shows the steps for it.
   Pure CSS — the reveal is :checked + a sibling selector, so it works with
   JavaScript switched off.                                                  */
.opt {
  display: grid;
  grid-template-columns: 2.75rem minmax(0, 1fr);
  border-bottom: 1px solid rgba(6, 43, 91, .28);
}
.opt:first-of-type { border-top: 1px solid rgba(6, 43, 91, .28); }

.opt__oval {
  -webkit-appearance: none; appearance: none;
  grid-column: 1; grid-row: 1;
  align-self: start;
  width: 1.9rem; height: 1.35rem; margin: 1.15rem 0 0;
  border: 3px solid var(--navy);
  border-radius: 50%;
  background: var(--white);
  cursor: pointer;
  transition: background-color .12s linear;
}
.opt__oval:hover { background: var(--paper-2); }
.opt__oval:checked {
  background-color: var(--navy);
  /* The gold check from the mark, as an inline SVG data URI so it stays crisp
     at any size. Gradient-drawn checks go blobby at this scale. Permitted by
     the CSP's `img-src 'self' data:`. */
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 26 20'%3E%3Cpath d='M4 10.5 L9.5 16 L21.5 4' fill='none' stroke='%23E0A411' stroke-width='4.2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-size: 74% 74%;
  background-position: center;
  background-repeat: no-repeat;
}
.opt__label {
  grid-column: 2; grid-row: 1;
  padding: var(--s-4) 0;
  cursor: pointer;
  display: block;
}
.opt__name {
  font-family: var(--font-display); font-weight: 400;
  font-size: var(--t-lg); line-height: 1.05; letter-spacing: -.01em;
  text-transform: uppercase; color: var(--navy);
  display: block;
}
.opt__meta {
  font: 500 var(--t-xs)/1.3 var(--font-cond);
  letter-spacing: .1em; text-transform: uppercase;
  color: var(--urgent); display: block; margin-top: var(--s-2);
}
.opt__meta[data-state="past"] { color: var(--ink-soft); }
.opt__blurb { font-size: var(--t-sm); color: var(--ink-soft);
              margin: var(--s-2) 0 0; max-width: 54ch; }

/* The reveal. Hidden until its oval is filled.
   Animates its height open. `interpolate-size: allow-keywords` makes a
   transition to `height: auto` possible at last, and discrete transitions on
   `display` keep it out of the layout when closed — so this is a real
   accordion with no JavaScript and no fixed max-height guess.
   The global prefers-reduced-motion rule collapses these durations. */
.opt__then {
  grid-column: 2; grid-row: 2;
  display: none;
  height: 0;
  overflow: hidden;
  opacity: 0;
  /* room for the CTA's offset shadow, which overflow would otherwise clip */
  padding: 0 0 var(--s-5);
  transition: height .26s cubic-bezier(.2,.6,.2,1),
              opacity .18s ease,
              display .26s allow-discrete;
}
.opt__oval:checked ~ .opt__then {
  display: block;
  height: auto;
  opacity: 1;
}
@starting-style {
  .opt__oval:checked ~ .opt__then { height: 0; opacity: 0; }
}
.opt__oval:checked ~ .opt__label .opt__name { color: var(--urgent); }
.opt:has(.opt__oval:checked) { background: var(--white); }

.then {
  border-inline-start: 4px solid var(--gold);
  padding: var(--s-3) 0 var(--s-3) var(--s-4);
}
.then h4 { font-family: var(--font-cond); font-weight: 600;
           letter-spacing: .1em; margin-bottom: var(--s-2); }
.then ol { margin: 0; padding-inline-start: 1.25rem; font-size: var(--t-sm); }
.then li { margin-bottom: var(--s-2); }
.then > :last-child { margin-bottom: 0; }

/* Rail blocks — the instruction column of a ballot. */
.rail { margin-bottom: var(--s-6); }
.rail__t {
  font: 600 var(--t-xs)/1 var(--font-cond);
  letter-spacing: .18em; text-transform: uppercase;
  color: var(--navy); padding-bottom: var(--s-2);
  border-bottom: 3px solid var(--navy); margin-bottom: var(--s-3);
}
.rail p { font-size: var(--t-sm); }
.rail > :last-child { margin-bottom: 0; }

/* The instruction the ballot itself puts in the middle column. */
.rail--warn {
  background: var(--gold); color: var(--navy);
  padding: var(--s-4); border: 3px solid var(--navy);
}
.rail--warn .rail__t { border-bottom-color: var(--navy); }

/* Perforated tear-off, like the numbered stub on a real ballot. */
.stub {
  border-top: 3px dashed var(--navy);
  padding-top: var(--s-4);
  font: 500 var(--t-xs)/1.5 var(--font-cond);
  letter-spacing: .1em; text-transform: uppercase; color: var(--ink-soft);
}

/* ==========================================================================
   5g. MOVEMENT LAYER
   --------------------------------------------------------------------------
   Progressive-movement visual language, taken from labour print and
   constructivist agitprop rather than from "campaign website": bold condensed
   type, hard diagonals, rubber-stamp overprints, halftone screens, and a
   demand list that moves.

   Restraint note: the page already has a signature (the fillable oval). These
   devices are deliberately confined — one stamp, one ticker, one slant, one
   screen — so they add energy without competing with it.
   ========================================================================== */

/* ---- The demands ticker -------------------------------------------------
   A picket line of named costs. Scroll-driven on purpose: it moves only while
   the reader scrolls, so there is no autoplaying motion to trap anyone under
   WCAG 2.2.2, and nothing to pause. Where scroll timelines are unsupported it
   simply sits still and stays readable.                                    */
.ticker {
  overflow: hidden;
  background: var(--urgent); color: var(--white);
  border-block: 3px solid var(--navy);
}
.ticker__track {
  display: flex; align-items: center; gap: var(--s-5);
  width: max-content; padding-block: var(--s-3);
  font: 600 var(--t-md)/1 var(--font-cond);
  letter-spacing: .14em; text-transform: uppercase;
  will-change: transform;
}
.ticker__track > span { white-space: nowrap; }
/* the mark's oval, used as the separator between demands */
.ticker__dot {
  width: .55rem; height: .55rem; border-radius: 50%;
  background: var(--gold); flex: none;
}
@supports (animation-timeline: scroll()) {
  @media (prefers-reduced-motion: no-preference) {
    .ticker__track {
      animation: ticker-slide linear both;
      animation-timeline: scroll(root block);
    }
  }
}
@keyframes ticker-slide {
  from { transform: translateX(0); }
  to   { transform: translateX(-50%); }
}

/* ---- Rubber-stamp overprint --------------------------------------------
   Rotated, so it reads as stamped onto the page rather than laid out on it.
   This is the one place the grid is allowed to break.                      */
.stamp {
  display: inline-block;
  transform: rotate(-5.5deg);
  border: 4px solid var(--urgent);
  color: var(--urgent);
  background: transparent;
  padding: var(--s-2) var(--s-3);
  font: 600 var(--t-sm)/1.05 var(--font-cond);
  letter-spacing: .1em; text-transform: uppercase;
  text-align: center;
  max-width: 14rem;
}
.band--navy .stamp, .band--navy-2 .stamp,
.ballot-head .stamp, .movement .stamp { border-color: var(--gold); color: var(--gold); }

@supports not (clip-path: polygon(0 0, 100% 1vw, 100% 100%, 0 100%)) {
  
}

/* ---- Retro print process ------------------------------------------------
   Borrowed from mid-century political print and the reference campaign banner:
   newsprint halftone, off-register ink, duotone photography, heavy furniture.
   What is deliberately NOT borrowed: the script signature face, the red-and-
   cream palette, and the layout. Those are that campaign's identity. We take
   the printing process, not the poster.

   OFF-REGISTER INK
   A hard offset of the headline in a second plate colour — what happens when
   a screenprint's plates are a hair out of alignment. Chosen over a distressed
   texture mask because it adds print character without lowering the text's own
   contrast: the glyph colour is untouched, so every measured ratio still holds.
   Offsets are in em so they scale with the fluid type.                     */
.ink {
  text-shadow: .04em .04em 0 var(--blue);
  paint-order: stroke fill;
}
/* Only for the largest display type. Tried on a ~38px heading and the offset
   closed up into what looked like a drop shadow rather than a second plate.
   Misregistration only reads at poster scale, so .ink belongs on the ballot
   header alone. */
.ink em { text-shadow: .04em .04em 0 var(--urgent); }
.band--gold .ink, .band--paper2 .ink { text-shadow: .035em .035em 0 var(--blue); }
@media (max-width: 520px) {
  /* At small display sizes the offset closes up and reads as blur. */
  .ink, .ink em { text-shadow: none; }
}

/* NEWSPRINT SCREEN
   A fine dot grid over flat colour. Applied only to flat fills — never over
   the generated artwork, which carries its own halftone and would moiré. */
/* The halftone composites --paper-2 down to rgb(192,203,217) at a dot centre,
   which drops the 13px --urgent eyebrow to 3.90:1 — under AA for small text.
   Everything else on this band clears it: navy body and headings at 8.50:1, and
   the h2's red .accent is 31px so it only needs 3.0. So the eyebrow goes navy and
   the red stays where it still works — on the headline. Excluded on navy bands,
   where navy-on-navy would be invisible. */
.band--screen:not(.band--navy) .eyebrow { color: var(--navy); }
.band--screen { position: relative; isolation: isolate; }
.band--screen::before {
  content: ""; position: absolute; inset: 0; z-index: -1; pointer-events: none;
  background-image: radial-gradient(var(--navy) 33%, transparent 34%);
  background-size: 5px 5px;
  opacity: .13;
}
.band--gold.band--screen::before,
.band--urgent.band--screen::before {
  background-image: radial-gradient(var(--navy) 33%, transparent 34%);
  opacity: .17;
}
.band--navy.band--screen::before,
.band--navy-2.band--screen::before {
  background-image: radial-gradient(var(--blue-lt) 33%, transparent 34%);
  opacity: .16;
}

/* HEAVY FURNITURE
   The thick rule with a centred device that old political printing used to
   break a page. The device is our ballot oval, not a star — the star is the
   generic americana move and the oval is the mark. */
.furniture {
  display: flex; align-items: center; gap: var(--s-3);
  margin-block: var(--s-6);
}
.furniture::before, .furniture::after {
  content: ""; height: 5px; flex: 1; background: var(--navy);
}
.furniture span {
  width: 1.6rem; height: 1.15rem; flex: none;
  border: 3px solid var(--navy); border-radius: 50%;
  background: var(--gold);
}
.band--navy .furniture::before, .band--navy .furniture::after,
.movement .furniture::before, .movement .furniture::after { background: var(--gold); }
.band--navy .furniture span, .movement .furniture span {
  border-color: var(--gold); background: var(--blue);
}

@supports not (mix-blend-mode: multiply) {
  
}

/* ---- Glossary popovers --------------------------------------------------
   The interactive addition that earns its place. This site is full of words a
   first-time voter does not know — clerk, precinct, drop box, affidavit,
   nonpartisan section. Explaining each inline bloats the ballot; putting them
   on another page hides them.

   Native Popover API plus CSS anchor positioning: tap a term, a definition
   appears tethered to it. Zero JavaScript, and the platform supplies top-layer
   rendering, light-dismiss, Escape-to-close, and focus management — all things
   a hand-rolled tooltip gets wrong.

   Anchoring is implicit: a popover invoked through popovertarget is anchored to
   its invoker, so no per-term anchor-name is needed.
   Baseline 2026 — Chrome 125+, Firefox 132+, Safari 18.2+.               */
.term {
  -webkit-appearance: none; appearance: none;
  background: none; border: 0; padding: 0;
  font: inherit; color: inherit; text-align: start;
  cursor: help;
  text-decoration: underline dotted var(--urgent);
  text-decoration-thickness: 2px;
  text-underline-offset: 3px;
}
.term:hover { text-decoration-style: solid; }
.band--navy .term, .movement .term, .ballot-head .term {
  text-decoration-color: var(--gold);
}

.gloss {
  position: absolute;
  position-area: block-end span-inline-end;
  position-try-fallbacks: flip-block, flip-inline, flip-block flip-inline;
  margin: .45rem 0 0;
  width: max-content;
  max-width: min(40ch, calc(100vw - 2.5rem));
  border: var(--rule) solid var(--navy);
  background: var(--gold);          /* navy on gold = 6.3:1 */
  color: var(--navy);
  padding: var(--s-3) var(--s-4);
  font: 400 var(--t-sm)/1.5 var(--font-body);
  box-shadow: 5px 5px 0 var(--navy);
}
.gloss b { font-family: var(--font-cond); font-weight: 600;
           letter-spacing: .06em; text-transform: uppercase;
           display: block; margin-bottom: var(--s-1); }
.gloss::backdrop { background: transparent; }

/* Entry/exit without JavaScript, using @starting-style and discrete
   transitions on display + overlay. */
.gloss {
  opacity: 0; transform: translateY(-5px);
  transition: opacity .14s ease, transform .14s ease,
              overlay .14s allow-discrete, display .14s allow-discrete;
}
.gloss:popover-open { opacity: 1; transform: none; }
@starting-style {
  .gloss:popover-open { opacity: 0; transform: translateY(-5px); }
}

/* ---- Tally sheet --------------------------------------------------------
   Rankings presented as a printed results table rather than as a row of
   oversized numbers. Same furniture as the ballot's deadline table so the two
   read as pages from the same document.                                    */
.tally { width: 100%; border-collapse: collapse; text-align: start;
         background: var(--white); border: var(--rule) solid var(--navy); }
/* The caption gets its own solid panel. It sits outside the table's background
   box, so in a `.band--screen` section the halftone dots painted straight through
   behind 12px type: the screen composites --paper-2 down to rgb(192,203,217),
   which drops --ink-soft to 4.31:1 — under AA for small text. A source line under
   a table is printed apparatus anyway, so giving it the table's own white plate
   is what it should have been. 7.08:1 on white. */
.tally caption {
  caption-side: bottom; text-align: start;
  margin-top: var(--s-3);
  padding: var(--s-3) var(--s-4);
  background: var(--white);
  border-inline-start: 3px solid var(--paper-2);
  font-size: var(--t-xs); line-height: 1.55; color: var(--ink-soft);
  max-width: 76ch;
}
.tally thead th {
  background: var(--navy); color: var(--white);
  font: 600 var(--t-xs)/1 var(--font-cond);
  letter-spacing: .16em; text-transform: uppercase;
  padding: var(--s-3) var(--s-4); white-space: nowrap;
}
.tally tbody th, .tally tbody td {
  padding: var(--s-4);
  border-top: 1px solid rgba(6, 43, 91, .2);
  vertical-align: baseline;
}
.tally tbody th { font: 400 var(--t-md)/1.4 var(--font-body); color: var(--ink); }
.tally tbody td { font-size: var(--t-md); }
.tally tbody td b {
  font-family: var(--font-cond); font-weight: 600;
  font-size: 1.5rem; color: var(--urgent);
  font-variant-numeric: tabular-nums;
}
/* First-place rows get the gold treatment and the mark's filled oval. */
.tally--first { background: var(--gold); }
.tally--first th, .tally--first td { color: var(--navy); }
.tally__rank {
  display: inline-flex; align-items: center; gap: var(--s-2);
  font: 600 var(--t-sm)/1 var(--font-cond);
  letter-spacing: .1em; text-transform: uppercase;
  white-space: nowrap;
}
.tally__oval {
  width: 1.5rem; height: 1.1rem; flex: none;
  border: 3px solid var(--navy); border-radius: 50%;
  background-color: var(--navy);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 26 20'%3E%3Cpath d='M4 10.5 L9.5 16 L21.5 4' fill='none' stroke='%23E0A411' stroke-width='4.2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-size: 74% 74%; background-position: center;
  background-repeat: no-repeat;
}

/* ---- Ballot progress ----------------------------------------------------
   Scroll-driven, and carrying information rather than decoration: it answers
   "how much of this ballot is left?", which is the one thing a long form owes
   its reader. Driven by a named view timeline on the ballot itself, so it
   tracks the contests rather than the whole document.
   Hidden entirely where scroll timelines are unsupported, since a bar frozen
   at zero would misinform.                                                */
.ballot { view-timeline-name: --ballotview; view-timeline-axis: block; }
.progress__track {
  height: 10px; background: var(--paper-2);
  border: 2px solid var(--navy); overflow: hidden;
}
.progress__bar {
  display: block; height: 100%; background: var(--gold);
  transform: scaleX(0); transform-origin: left center;
}
@supports (animation-timeline: view()) {
  .progress__bar {
    animation: fill-bar linear both;
    animation-timeline: --ballotview;
    animation-range: entry 100% exit 0%;
  }
}
@supports not (animation-timeline: view()) {
  .rail--progress { display: none; }
}
@keyframes fill-bar {
  from { transform: scaleX(0); }
  to   { transform: scaleX(1); }
}

/* ---- Movement band ----------------------------------------------------
   Full-bleed labour-print artwork, slanted at the seam, type on a scrim.
   Bottom-anchored so the raised hands read against the copy rather than
   behind it.                                                              */
.movement {
  position: relative; overflow: hidden; isolation: isolate;
  background: var(--navy); color: var(--paper);
  padding-block: calc(var(--s-8) + 2.4vw);
  clip-path: polygon(0 0, 100% 2.2vw, 100% 100%, 0 calc(100% - 2.2vw));
  margin-block: -1px;
}
.movement::before {
  content: ""; position: absolute; inset: 0; z-index: -2;
  background-image: url("/assets/img/solidarity.webp");
  background-size: cover; background-position: center 82%;
  opacity: .58;
}
.movement::after {
  content: ""; position: absolute; inset: 0; z-index: -1;
  background: linear-gradient(180deg,
    rgba(6, 43, 91, .92) 0 34%,
    rgba(6, 43, 91, .74) 62%,
    rgba(6, 43, 91, .34) 100%);
}
@media (max-width: 760px) {
  .movement::before {
    background-image: url("/assets/img/solidarity-mobile.webp");
    background-position: center bottom; opacity: .48;
  }
  .movement::after {
    background: linear-gradient(180deg,
      rgba(6, 43, 91, .95) 0 46%, rgba(6, 43, 91, .70) 100%);
  }
}
@supports not (clip-path: polygon(0 0, 100% 1vw, 100% 100%, 0 100%)) {
  .movement { clip-path: none; padding-block: var(--s-8); }
}
.movement h2 { color: var(--white); }

/* ---- Slate card --------------------------------------------------------
   The artifact progressive organisations actually hand out: a ballot-ordered
   list you can carry to the polls. It scales to any number of endorsements,
   which the two hard-coded candidate cards did not, and it prints.         */
.slate {
  list-style: none; margin: 0; padding: 0;
  border: var(--rule) solid var(--navy);
  background: var(--white);
}
.slate__row {
  display: grid;
  grid-template-columns: 2.5rem minmax(0, 1fr);
  gap: 0 var(--s-4);
  padding: var(--s-4);
  border-bottom: 1px solid rgba(6, 43, 91, .22);
}
.slate__row:last-child { border-bottom: 0; }
.slate__row:nth-child(odd) { background: var(--paper); }
/* filled oval, matching the mark — this slate is already marked for you */
.slate__mark {
  grid-row: 1 / span 3; align-self: start;
  width: 1.9rem; height: 1.35rem; margin-top: .3rem;
  border: 3px solid var(--navy); border-radius: 50%;
  background-color: var(--navy);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 26 20'%3E%3Cpath d='M4 10.5 L9.5 16 L21.5 4' fill='none' stroke='%23E0A411' stroke-width='4.2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-size: 74% 74%; background-position: center;
  background-repeat: no-repeat;
}
.slate__office {
  font: 600 var(--t-xs)/1 var(--font-cond);
  letter-spacing: .16em; text-transform: uppercase; color: var(--urgent);
}
.slate__name {
  font-family: var(--font-display); font-weight: 400;
  font-size: var(--t-lg); line-height: 1.05; letter-spacing: -.01em;
  text-transform: uppercase; color: var(--navy);
  margin-top: var(--s-1);
}
.slate__why { font-size: var(--t-sm); color: var(--ink-soft);
              margin-top: var(--s-2); }
.slate__why a { color: var(--blue-ink); }

.slate__head {
  background: var(--navy); color: var(--white);
  padding: var(--s-3) var(--s-4);
  font: 600 var(--t-sm)/1.2 var(--font-cond);
  letter-spacing: .14em; text-transform: uppercase;
  display: flex; justify-content: space-between; gap: var(--s-3);
  flex-wrap: wrap;
}
.slate__foot {
  border-top: var(--rule) dashed var(--navy);
  padding: var(--s-3) var(--s-4);
  font: 500 var(--t-xs)/1.5 var(--font-cond);
  letter-spacing: .08em; text-transform: uppercase; color: var(--ink-soft);
  background: var(--paper-2);
}

/* --------------------------------------------------------------------------
   5f. Modern CSS layer
   --------------------------------------------------------------------------
   Scroll-driven animations run the reveals on the compositor with no
   JavaScript at all. Gated behind @supports, and the JS IntersectionObserver
   path stays as the fallback for browsers without it (Firefox, older Safari).
   Verified support: Chromium 115+, Safari 18+, ~84% global mid-2026.
   -------------------------------------------------------------------------- */
@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    /* Deliberately NOT .contest and NOT .opt.
       A ballot does not animate. Fading the contests in made the choices look
       faded — i.e. unavailable — which is the worst thing you can do to a
       voting instruction, and it was decoration rather than meaning. The
       ballot renders present and fully legible on arrival.
       Motion is spent only on .reveal, in the non-ballot sections below. */
    .reveal {
      animation: rise linear both;
      animation-timeline: view();
      animation-range: entry 5% cover 26%;
    }
    /* The rail's rule draws itself as you scroll the contests past it. */
    @media (min-width: 1000px) {
      .ballot__rail { animation: none; }
    }
  }
}
@keyframes rise {
  from { opacity: 0; transform: translateY(16px); }
  to   { opacity: 1; transform: none; }
}

/* Cross-document view transitions: one line, same-origin, and a no-op in
   browsers without it. Chromium + Safari 18.2+. */
@view-transition { navigation: auto; }
@media (prefers-reduced-motion: reduce) {
  @view-transition { navigation: none; }
}

/* --------------------------------------------------------------------------
   6. Eyebrow — MOTIF: led by a ballot oval instead of a dash
   -------------------------------------------------------------------------- */
.eyebrow {
  font: 600 var(--t-xs)/1 var(--font-cond);
  letter-spacing: .18em; text-transform: uppercase;
  display: inline-flex; align-items: center; gap: var(--s-2);
  margin-bottom: var(--s-3);
  color: var(--urgent);
}
.band--navy .eyebrow, .band--navy-2 .eyebrow, .band--urgent .eyebrow,
.hero .eyebrow {
  color: var(--gold);
}
.band--gold .eyebrow { color: var(--navy); }
.eyebrow::before {
  content: ""; flex: none;
  width: .625rem; height: .625rem; border-radius: 50%;
  box-shadow: inset 0 0 0 3px currentColor;
}

/* --------------------------------------------------------------------------
   7. Buttons — the lockup chip, made pressable
   -------------------------------------------------------------------------- */
.btn {
  --btn-bg: var(--urgent);
  --btn-fg: var(--white);
  display: inline-flex; align-items: center; justify-content: center;
  gap: var(--s-3);
  min-height: var(--tap);
  padding: var(--s-3) var(--s-5);
  background: var(--btn-bg); color: var(--btn-fg);
  border: var(--rule) solid var(--navy);
  border-radius: 3px;
  box-shadow: var(--pop) var(--pop) 0 var(--navy);
  font: 600 var(--t-md)/1.1 var(--font-cond);
  letter-spacing: .04em; text-transform: uppercase;
  text-decoration: none; text-align: center; cursor: pointer;
  transition: transform 90ms ease, box-shadow 90ms ease;
}
.btn:hover  { transform: translate(2px,2px); box-shadow: 4px 4px 0 var(--navy); }
.btn:active { transform: translate(var(--pop),var(--pop));
              box-shadow: 0 0 0 var(--navy); }

.btn--lg    { font-size: var(--t-lg); padding: var(--s-4) var(--s-6);
              min-height: 60px; }
.btn--block { display: flex; width: 100%; }
.btn--gold  { --btn-bg: var(--gold);  --btn-fg: var(--navy); }

.btn--paper { --btn-bg: var(--paper); --btn-fg: var(--navy); }
.btn--ghost { --btn-bg: transparent; --btn-fg: var(--navy); box-shadow: none; }
.btn--ghost:hover { --btn-bg: var(--navy); --btn-fg: var(--paper);
                    transform: none; }

.band--navy .btn, .band--navy-2 .btn, .band--urgent .btn, .hero .btn {
  border-color: var(--white); box-shadow: var(--pop) var(--pop) 0 var(--blue);
}
.band--navy .btn:hover, .band--navy-2 .btn:hover, .band--urgent .btn:hover,
.hero .btn:hover {
  box-shadow: 4px 4px 0 var(--blue);
}
.band--navy .btn:active, .band--navy-2 .btn:active, .band--urgent .btn:active,
.hero .btn:active {
  box-shadow: 0 0 0 var(--blue);
}
/* .ballot-head included for the same reason as .official above: it is a navy
   surface that is not a .band--navy. A ghost button there kept its default navy
   text and rendered navy-on-navy at 1.00:1 — an invisible call to action, which
   is the worst possible failure mode for one. */
.band--navy .btn--ghost, .band--urgent .btn--ghost, .hero .btn--ghost,
.ballot-head .btn--ghost {
  --btn-fg: var(--white); box-shadow: none;
}
.band--navy .btn--ghost:hover,
.hero .btn--ghost:hover,
.ballot-head .btn--ghost:hover { --btn-bg: var(--white); --btn-fg: var(--navy); }

.btn-row { display: flex; flex-wrap: wrap; gap: var(--s-5) var(--s-6); }

/* Outbound links must visibly announce they lead to an official .gov source.
   On a voting site, legibility of provenance IS the product. */
.ext::after {
  content: "↗"; font-family: var(--font-body); font-size: .85em;
  margin-inline-start: .35em; display: inline-block;
}
.official {
  font: 400 var(--t-xs)/1.4 var(--font-cond);
  letter-spacing: .1em; text-transform: uppercase;
  color: var(--ink-soft); display: block; margin-top: var(--s-2);
}
/* .ballot-head is navy but is not a .band--navy, so it was missed here. The
   Spanish and Arabic homepages moved their CTA into that header, which put
   --ink-soft on navy at 1.97:1. --blue-lt is 7.0:1 there. */
.band--navy .official, .band--navy-2 .official, .band--urgent .official,
.hero .official, .ballot-head .official {
  color: var(--blue-lt);
}

/* --------------------------------------------------------------------------
   8. Masthead
   -------------------------------------------------------------------------- */
/* A gold trim rule across the very top, like the edge of printed stock. The
   ballot header below carries the heavy 10px gold rule, so this one stays thin
   to avoid two competing gold bands. */
.masthead {
  background: var(--navy); color: var(--paper);
  border-top: 3px solid var(--gold);
  border-bottom: var(--rule) solid var(--navy);
}
.masthead__bar {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--s-4); padding-block: var(--s-3);
}
/* Right-hand cluster: language switch, then the menu. */
.masthead__end {
  display: flex; align-items: center; gap: var(--s-4);
}
@media (min-width: 1260px) { .masthead__end { gap: var(--s-5); } }

/* Language switch, surfaced in the header rather than buried in the footer.
   Michigan's Spanish-speaking and Arabic-speaking communities are a real part
   of this electorate; a language control nobody can find is not a language
   control. */
.langswitch {
  display: flex; align-items: center; gap: var(--s-2);
  font: 600 var(--t-xs)/1 var(--font-cond);
  letter-spacing: .14em; text-transform: uppercase;
  flex: none;
}
/* Arabic made this EN / ES / AR, and the third code plus its separator pushed
   the masthead past the right edge. The switcher is three two-letter codes in a
   heavily tracked condensed face, so the cheapest width is the tracking and the
   gaps — not the tap targets, which stay at their full 48px height.

   The ceiling is 519px, and the number matters. It was 430px, which left the
   band from 431px to 479px with full tracking and no room for it: the menu
   button sat 32px past the bar edge at 431px and was visibly sliced in half.
   That is iPhone Pro Max (430–440) and a lot of Android — not an edge case.

   It survived a sweep because the sweep tested 320 / 360 / 390 / 430 / 700 and
   stepped straight over the band that broke. Overflow checks on this bar need a
   dense ladder, not landmark widths; the fix here is verified every 10px from
   310 to 800. */
@media (max-width: 519px) {
  .langswitch { gap: 2px; letter-spacing: .04em; }
  .langswitch a { padding-inline: 2px; }
  .masthead__end { gap: var(--s-3); }
  /* Only the header bar, not every .wrap — body copy keeps its full gutter.
     The brand falling a few px out of line with the content below is a much
     smaller problem than the whole page scrolling sideways. */
  .masthead__bar { padding-inline: var(--s-4); }
  .menubtn { padding-inline: var(--s-2); }
}
/* 360px is a Galaxy-class Android width and is not an edge case. */
@media (max-width: 380px) {
  .brand { --lockup-size: .9375rem; }
}
/* Below ~340px the brand, three language codes and a menu button cannot share
   one row at any tracking that is still legible, so they stop trying. The bar
   becomes two rows instead of shrinking the wordmark into illegibility.
   iPhone SE (1st gen) and Android Go devices land here. */
@media (max-width: 340px) {
  .masthead__bar { flex-wrap: wrap; row-gap: var(--s-2); }
  .brand, .masthead__end { flex: 1 0 100%; }
  .masthead__end { justify-content: space-between; }
}
.langswitch a {
  color: var(--blue-lt); text-decoration: none;
  min-height: var(--tap); display: inline-flex; align-items: center;
  justify-content: center;
  /* WCAG 2.5.8 AA is 24px in BOTH dimensions. These were 48px tall and fine on
     height, but the narrow-width rule below squeezes padding to 2px and that
     took "ES" down to 21px wide — tall enough and too thin, which the height
     check alone never caught. */
  min-width: 24px;
  padding-inline: var(--s-1);
  border-bottom: 2px solid transparent;
}
/* Wayfinding rules are white, not gold. Gold now carries two jobs in this bar —
   the brand chip and the primary action — and a third meaning ("you are here")
   made none of them legible as a signal. White reads as emphasis of the link
   itself, which is what currency actually is. 13.96:1 on navy. */
.langswitch a:hover { color: var(--white); border-bottom-color: var(--white); }
.langswitch a[aria-current] { color: var(--white); border-bottom-color: var(--white); }
.langswitch span { color: rgba(127, 190, 240, .5); }
.nav { display: flex; align-items: center; gap: var(--s-1); }
.nav a {
  color: var(--paper); text-decoration: none;
  font: 500 var(--t-sm)/1 var(--font-cond);
  letter-spacing: .06em; text-transform: uppercase;
  white-space: nowrap;              /* labels must not wrap to two lines */
  padding: var(--s-3); min-height: var(--tap);
  display: inline-flex; align-items: center;
  border-bottom: var(--rule) solid transparent;
}
.nav a:hover, .nav a[aria-current="page"] {
  border-bottom-color: var(--white); color: var(--white);
}
/* Applies at every desktop width, not just up to 1240px as it once did. The
   .wrap stops growing at its max-width, so the bar gains no room above ~1140px
   while the nav went back to full padding there — which put the eighth item 35px
   past the edge at 1280 and wider, but not at 1140. Wider viewport, worse
   overflow: the kind of thing that hides from a spot check at one size.

   The inline-row breakpoint has moved twice, for the same reason both times:
   1080px → 1200px when "Services" made it nine items, then 1200px → 1260px when
   Arabic made the language switcher EN / ES / AR. Measured each time rather than
   guessed — at 1200 the row overflowed by 23px, at 1240 by 3px, at 1260 by none.
   Every 1080/1079-style pair in this file moves together; they are one decision,
   not four. Anything added to this bar needs the sweep re-run. */
@media (min-width: 1260px) {
  .nav a { padding-inline: var(--s-2); letter-spacing: .03em; }
}
/* The masthead CTA is a nav item, not a hero button: no offset shadow (it
   would be navy-on-navy and invisible anyway) and a tighter box. */
.nav__cta {
  margin-inline-start: var(--s-2);
  box-shadow: none; border-width: 2px;
  padding: var(--s-2) var(--s-4); min-height: 40px;
}
.nav__cta:hover { transform: none; box-shadow: none; }

/* ---- Menu -----------------------------------------------------------------
   The menu is a POPOVER, not a <details>.

   It was a <details> with `display: contents` at desktop widths, which does not
   work: Chromium hides a closed details' contents through the
   ::details-content slot rather than through `display`, so the desktop nav was
   rendering at zero width — invisible — at every width above the breakpoint.

   The Popover API fixes it and is better besides: light-dismiss, Escape, and
   focus management come from the platform, `popovertarget` is declarative so it
   works with scripting off, and the desktop override is a plain author rule,
   which beats the UA's `display: none` by origin rather than by specificity.  */
.menubtn {
  -webkit-appearance: none; appearance: none;
  display: none;                       /* shown only below 1260px */
  align-items: center; justify-content: center; gap: var(--s-2);
  min-height: var(--tap); min-width: var(--tap);
  background: transparent; color: var(--paper);
  font: 600 var(--t-sm)/1 var(--font-cond);
  letter-spacing: .1em; text-transform: uppercase;
  border: var(--rule) solid var(--paper); border-radius: 3px;
  padding-inline: var(--s-3); cursor: pointer;
}
.menubtn:hover { background: var(--paper); color: var(--navy); }

@media (max-width: 1259px) {
  .menubtn { display: inline-flex; }

  /* Restore the closed-popover hide. The base `.nav { display: flex }` is an
     author rule, so it overrides the UA's `[popover]:not(:popover-open)
     { display: none }` by origin — the same precedence that makes the desktop
     row work. Without this the menu is stuck open on phones. */
  .nav[popover]:not(:popover-open) { display: none; }

  /* Anchored to the menu button, so there is no hard-coded top offset to keep
     in sync with the masthead's height. */
  .nav[popover] {
    position: absolute;
    position-area: block-end span-inline-start;
    position-try-fallbacks: flip-inline;
    margin: var(--s-3) 0 0;
    width: min(20rem, calc(100vw - 1.5rem));
    flex-direction: column; align-items: stretch;
    background: var(--navy-2);
    border: var(--rule) solid var(--gold); border-radius: 3px;
    padding: 0;
    box-shadow: 6px 6px 0 rgba(6, 43, 91, .55);
    overflow: hidden;
  }
  .nav[popover]::backdrop { background: rgba(6, 43, 91, .45); }

  /* Full-width rows with a real tap height — this is the primary way anyone on
     a phone moves around the site. */
  .nav[popover] a {
    min-height: 3.5rem;
    padding-inline: var(--s-5);
    font-size: var(--t-md);
    letter-spacing: .08em;
    border-bottom: 1px solid rgba(255, 255, 255, .16);
  }
  /* `:not(.btn)` for the same reason it is on the aria-current rule below, which
     is where this hazard was first found and fixed — and then missed here. The
     row-hover fill is navy, and .btn keeps its own navy text, so hovering the gold
     CTA painted navy on navy at 1.00:1 and the button vanished under the cursor.
     A button is not a row; it gets a lift instead of a fill. */
  .nav[popover] a:not(.btn):hover {
    background: var(--navy); border-bottom-color: var(--white);
  }
  .nav[popover] .btn:hover { filter: brightness(1.08); }
  /* Current page gets a gold bar down its leading edge; a bottom underline is
     invisible in a stacked list.
     `:not(.btn)` matters — on /pledge.html the CTA itself is the current page,
     and painting it navy put navy text on a navy fill at 1:1. A button keeps
     its own colours; it shows currency through the gold ring instead. */
  .nav[popover] a[aria-current]:not(.btn) {
    background: var(--navy);
    box-shadow: inset 4px 0 0 var(--gold);
  }
  .nav[popover] .btn[aria-current] {
    outline: 3px solid var(--white); outline-offset: 2px;
  }
  .nav[popover] .nav__cta {
    margin: var(--s-4) var(--s-5) var(--s-5);
    min-height: var(--tap); border-bottom: 0;
  }

  /* Entry transition, no JS. */
  .nav[popover] {
    opacity: 0; transform: translateY(-6px);
    transition: opacity .16s ease, transform .16s ease,
                overlay .16s allow-discrete, display .16s allow-discrete;
  }
  .nav[popover]:popover-open { opacity: 1; transform: none; }
  @starting-style {
    .nav[popover]:popover-open { opacity: 0; transform: translateY(-6px); }
  }
}

/* Desktop: the nav is simply a row. Author `display` beats the UA popover
   rule, so no !important is needed. */
@media (min-width: 1260px) {
  .nav[popover] {
    display: flex; position: static; inset: auto;
    width: auto; max-width: none; height: auto;
    margin: 0; padding: 0; overflow: visible;
    background: none; border: 0; box-shadow: none;
    opacity: 1; transform: none;
  }
  .nav[popover]::backdrop { display: none; }
}

/* --------------------------------------------------------------------------
   10. Hero
   -------------------------------------------------------------------------- */
.hero {
  position: relative; background: var(--navy); color: var(--paper);
  overflow: hidden; padding-block: var(--s-7) var(--s-8);
  border-bottom: var(--rule) solid var(--navy);
}
/* MOTIF: screenprinted Michigan artwork — the peninsulas as topographic
   contours, seeded with ballot ovals and gold checks. Decorative only.
   Art-directed: the desktop file is landscape with open space at the left for
   type; the mobile file is a portrait crop so the peninsulas still read at
   390px instead of being cropped to noise. */
.hero__art {
  position: absolute; inset: 0; z-index: 0; pointer-events: none;
  opacity: .34;
  background-image: url("/assets/img/hero-art.webp");
  background-size: cover;
  background-position: 72% center;
  background-repeat: no-repeat;
}
@media (max-width: 700px) {
  .hero__art {
    background-image: url("/assets/img/hero-art-mobile.webp");
    background-position: center 22%;
    opacity: .26;
  }
}

/* Scrim. Non-negotiable, not a mood effect: the artwork contains large gold
   areas, and gold-on-gold would drop the hero's gold headline to ~2.9:1. This
   keeps type sitting on effectively pure navy while letting the art breathe on
   the opposite side. Measured after the fact — see README. */
.hero::before {
  content: ""; position: absolute; inset: 0; z-index: 1; pointer-events: none;
  background: linear-gradient(100deg,
    var(--navy) 0 42%,
    rgba(6, 43, 91, .96) 56%,
    rgba(6, 43, 91, .66) 76%,
    rgba(6, 43, 91, .42) 100%);
}
@media (max-width: 700px) {
  /* Type spans the full width on a phone, so the scrim goes vertical and the
     art is only allowed to surface below the fold of the copy. */
  .hero::before {
    background: linear-gradient(to bottom,
      var(--navy) 0 30%,
      rgba(6, 43, 91, .94) 62%,
      rgba(6, 43, 91, .74) 100%);
  }
}

.hero__in { position: relative; z-index: 2; }
.hero h1 {
  font-size: var(--t-mega); color: var(--white);
  letter-spacing: -0.03em; margin-bottom: var(--s-4);
}
.hero h1 em { font-style: normal; color: var(--gold); display: block; }
.hero__lede {
  font-size: var(--t-lg); line-height: 1.45; max-width: 36ch;
  color: var(--paper); font-weight: 400;
}
.hero__cta { margin-top: var(--s-6); }

/* Countdown — Oswald tabular numerals, sized as the loudest thing present */
.count {
  display: flex; flex-wrap: wrap; gap: var(--s-4);
  margin-top: var(--s-6); padding-top: var(--s-5);
  border-top: var(--rule) solid var(--blue);
}
.count__unit { min-width: 4.25rem; }
.count__n {
  display: block;
  font: 600 clamp(2.25rem, 7vw, 3.5rem)/0.9 var(--font-cond);
  letter-spacing: -0.01em; color: var(--gold);
  font-variant-numeric: tabular-nums;
}
.count__l {
  display: block;
  font: 500 var(--t-xs)/1 var(--font-cond);
  letter-spacing: .16em; text-transform: uppercase;
  color: var(--blue-lt); margin-top: var(--s-2);
}
.count__what {
  align-self: center; max-width: 24ch;
  font: 500 var(--t-sm)/1.4 var(--font-body); color: var(--paper);
  border-inline-start: var(--rule) solid var(--blue); padding-inline-start: var(--s-4);
  margin: 0;
}

/* --------------------------------------------------------------------------
   11. Cards — the lockup chip as a container
   -------------------------------------------------------------------------- */
.card {
  background: var(--paper); color: var(--ink);
  border: var(--rule) solid var(--navy); border-radius: 3px;
  box-shadow: var(--pop) var(--pop) 0 var(--navy);
  padding: var(--s-5);
  display: flex; flex-direction: column; gap: var(--s-3);
}
.card > :last-child { margin-bottom: 0; margin-top: auto; }
.card__num {
  font: 600 2.25rem/0.85 var(--font-cond);
  color: var(--blue); letter-spacing: -0.02em;
}
.card h3 { margin-bottom: var(--s-1); }
.card p  { font-size: var(--t-sm); color: var(--ink-soft); margin: 0; }
.card--now { background: var(--gold); }
.card--now p, .card--now .card__num { color: var(--navy); }
.card--closed { opacity: .6; box-shadow: none; }
.card--closed .card__num { color: var(--ink-soft); }

.band--navy .card, .band--navy-2 .card, .band--urgent .card {
  box-shadow: var(--pop) var(--pop) 0 var(--blue);
}
.band--navy .card a { color: var(--blue-ink); }

/* Status pill */
.pill {
  align-self: flex-start;
  font: 600 var(--t-xs)/1 var(--font-cond);
  letter-spacing: .12em; text-transform: uppercase;
  padding: var(--s-2) var(--s-3); border: 2px solid currentColor;
  border-radius: 2px;
}
.pill--open   { color: #0B6B3A; }               /* 6.0:1 on paper */
.pill--urgent { background: var(--urgent); color: var(--white);
                border-color: var(--navy); }
.pill--shut   { color: var(--ink-soft); }

/* The "open" green is only 3.0:1 against gold, which fails AA at this text
   size. On any gold surface it becomes navy (6.3:1). Applies to the open card
   and to the highlighted "open now" row in the deadline table. */
.card--now .pill--open,
.dates tr[data-state="now"] .pill--open,
.band--gold .pill--open { color: var(--navy); }
.dates tr[data-state="now"] .pill--shut { color: #4A3A0C; }

/* --------------------------------------------------------------------------
   12. Key-dates table
   -------------------------------------------------------------------------- */
.dates { width: 100%; border-collapse: collapse; text-align: start;
         font-size: var(--t-sm); }
.dates caption { text-align: start; margin-bottom: var(--s-4);
                 font: 400 var(--t-sm)/1.5 var(--font-body);
                 color: var(--ink-soft); }
.dates th, .dates td { padding: var(--s-3); vertical-align: top;
                       border-bottom: 1px solid rgba(6,43,91,.18); }
.dates thead th {
  font: 600 var(--t-xs)/1 var(--font-cond);
  letter-spacing: .12em; text-transform: uppercase;
  border-bottom: var(--rule) solid var(--navy); white-space: nowrap;
}
.dates td:first-child { font-family: var(--font-cond); font-weight: 500;
                        white-space: nowrap; }
.dates tr[data-state="past"] { color: var(--ink-soft); }
.dates tr[data-state="past"] td:first-child { text-decoration: line-through; }
.dates tr[data-state="now"] { background: var(--gold); color: var(--navy); }
.dates tr[data-state="now"] td { border-bottom-color: var(--navy); }

.scroller { overflow-x: auto; -webkit-overflow-scrolling: touch; }

/* --------------------------------------------------------------------------
   13. Steps + sticky section nav
   -------------------------------------------------------------------------- */
.step { border-inline-start: var(--rule) solid var(--blue);
        padding-inline-start: var(--s-5); margin-bottom: var(--s-6); }
/* Flex, not block: the key line is set at line-height 1, so an inline-block pill
   is taller than its own line and overflowed onto the h4 underneath it. */
.step__k {
  font: 600 var(--t-xs)/1 var(--font-cond);
  letter-spacing: .14em; text-transform: uppercase;
  color: var(--urgent); margin-bottom: var(--s-3);
  display: flex; align-items: center; flex-wrap: wrap; gap: var(--s-2);
}

/* A worked example hung under a format. Deliberately quiet — the format is the
   lesson and the link is only proof it exists, so it must not outrank the copy
   above it. */
.eg {
  margin-top: var(--s-4); padding-top: var(--s-3);
  border-top: 1px solid rgba(6, 43, 91, .18);
  font-size: var(--t-sm);
}
.eg__k {
  display: block;
  font: 600 var(--t-xs)/1 var(--font-cond);
  letter-spacing: .14em; text-transform: uppercase;
  color: var(--ink-soft); margin-bottom: var(--s-2);
}
.eg__n { display: block; color: var(--ink-soft); margin-top: var(--s-2); }
.band--navy .eg, .band--navy-2 .eg { border-top-color: rgba(127, 190, 240, .35); }
.band--navy .eg__k, .band--navy .eg__n,
.band--navy-2 .eg__k, .band--navy-2 .eg__n { color: var(--blue-lt); }

/* Registration methods sort themselves by whether they still work.
   A closed method sinks below the open ones and loses its call to action —
   the page was leading with a big "Register online" button five days after
   online registration had closed, which is the worst thing a GOTV page can do.
   Ordering only kicks in once the date engine writes data-state, so with JS off
   document order stands and every method still states its own deadline. */
.steps { display: flex; flex-direction: column; }
.step { order: 0; }
.step--open { border-inline-start-color: var(--urgent); }
.step[data-state="past"] { order: 2; border-inline-start-color: var(--paper-2); }
.step[data-state="past"] h4 { color: var(--ink-soft); }
.step[data-state="past"] .step__cta { display: none; }

/* Asserts nothing until the engine has run — see the comment in the markup. */
.regnow > p { display: none; margin: 0; font-size: var(--t-lg); line-height: 1.45;
              max-width: var(--prose); }
.regnow[data-state="future"] .regnow__open,
.regnow[data-state="now"] .regnow__open,
.regnow[data-state="past"] .regnow__shut { display: block; }

/* top: 0 now that nothing sticks above it. It was offset by 3.25rem to clear
   the deadline bar, which would otherwise leave a dead gap at the top. */
.subnav { position: sticky; top: 0; z-index: 20;
          background: var(--paper); border-bottom: var(--rule) solid var(--navy); }
.subnav ul { display: flex; margin: 0; padding: 0; list-style: none;
             overflow-x: auto; -webkit-overflow-scrolling: touch; }
.subnav a {
  display: inline-flex; align-items: center;
  min-height: var(--tap); padding: var(--s-3) var(--s-4); white-space: nowrap;
  font: 500 var(--t-sm)/1 var(--font-cond);
  letter-spacing: .06em; text-transform: uppercase;
  color: var(--navy); text-decoration: none;
  border-bottom: var(--rule) solid transparent;
}
.subnav a:hover, .subnav a[aria-current="true"] {
  border-bottom-color: var(--urgent); color: var(--urgent);
}

/* --------------------------------------------------------------------------
   14. Callouts
   -------------------------------------------------------------------------- */
.note { border: var(--rule) solid var(--navy); border-radius: 3px;
        padding: var(--s-5); background: var(--paper-2); }
/* Callouts span the full column, so their text ran to 113 characters per line on
   desktop — well past the 45–75 band where the eye reliably finds the next line.
   The measure token already existed (--prose, 68ch); it was just opt-in via
   .prose, and callouts never opted in. */
.note p { max-width: var(--prose); }
.note--warn { background: var(--gold); color: var(--navy); }
.note--warn a { color: var(--navy); }
.note--stop { background: var(--urgent); color: var(--white); }
.note--stop a { color: var(--white); }
.note--win  { background: var(--white); border-color: var(--blue); }
.note__t {
  font: 400 var(--t-md)/1.15 var(--font-display);
  text-transform: uppercase; letter-spacing: 0;
  margin-bottom: var(--s-3);
  display: flex; gap: var(--s-3); align-items: baseline;
}
.note > :last-child { margin-bottom: 0; }

/* MOTIF: checklist using the gold check from the mark */
/* Filled ballot oval as a list marker.
   This used to be built from two crossed linear-gradients, and because each
   gradient band runs the full width of the circle the result was an ✕, not a ✓ —
   sitting next to lines like "You can register and vote on the same day", where
   it read as the exact opposite of the point. It is now a real tick: the ring is
   ::before, the check is ::after as a masked shape so its colour can be a
   variable and adapt to the surface.
   Gold reads 6.31:1 on navy but only 2.2:1 on paper, which is this palette's one
   trap, so the tick is navy on light surfaces and gold on dark ones. */
.checks {
  --check-ring: var(--blue);
  --check-ink: var(--navy);
  list-style: none; margin: 0; padding: 0;
}
/* .movement was listed here too, but it already carries .band--navy — the third
   selector could never match anything the first did not. */
.band--navy .checks, .ballot-head .checks {
  --check-ring: var(--blue-lt);
  --check-ink: var(--gold);
}
.checks li {
  position: relative; padding-inline-start: 2.25rem; margin-bottom: var(--s-3);
}
.checks li::before,
.checks li::after {
  content: ""; position: absolute; inset-inline-start: 0; top: .15em;
  width: 1.375rem; height: 1.375rem;
}
.checks li::before {
  border-radius: 50%;
  box-shadow: inset 0 0 0 3px var(--check-ring);
}
.checks li::after {
  background-color: var(--check-ink);
  -webkit-mask: var(--tick) center / 62% 62% no-repeat;
          mask: var(--tick) center / 62% 62% no-repeat;
}
/* Without mask support the background-color would paint a solid square, so the
   tick is dropped and the empty oval stands on its own. */
@supports not ((mask-image: none) or (-webkit-mask-image: none)) {
  .checks li::after { display: none; }
}

/* --------------------------------------------------------------------------
   15. Forms
   -------------------------------------------------------------------------- */
.form { max-width: 34rem; }
.field { margin-bottom: var(--s-5); }
.field > label, .fieldset__legend {
  display: block; font: 700 var(--t-sm)/1.35 var(--font-body);
  margin-bottom: var(--s-2);
}
.field .hint { display: block; font-size: var(--t-xs); color: var(--ink-soft);
               margin-bottom: var(--s-2); font-weight: 400; }
.band--navy .hint, .band--navy-2 .hint { color: var(--blue-lt); }

.input, select, textarea {
  width: 100%; min-height: var(--tap); padding: var(--s-3);
  font: 400 var(--t-md)/1.4 var(--font-body); color: var(--ink);
  background: var(--white);
  border: var(--rule) solid var(--navy); border-radius: 3px;
  appearance: none;
}
textarea { min-height: 7rem; resize: vertical; }
select {
  background-image: linear-gradient(45deg, transparent 50%, var(--navy) 50%),
                    linear-gradient(135deg, var(--navy) 50%, transparent 50%);
  background-position: calc(100% - 20px) center, calc(100% - 13px) center;
  background-size: 7px 7px, 7px 7px; background-repeat: no-repeat;
  padding-inline-end: var(--s-8);
}
.input:focus, select:focus, textarea:focus {
  box-shadow: inset 0 0 0 2px var(--gold);
}
.input:user-invalid { border-color: var(--urgent);
                      box-shadow: inset 0 0 0 2px var(--urgent); }

fieldset { border: 0; margin: 0 0 var(--s-5); padding: 0; }
.fieldset__legend { font-size: var(--t-md); }

/* MOTIF: choices as ballot ovals you fill in */
.choice {
  display: flex; gap: var(--s-3); align-items: flex-start;
  min-height: var(--tap); padding: var(--s-3);
  border: var(--rule) solid var(--navy); border-radius: 3px;
  background: var(--white); cursor: pointer;
  font-size: var(--t-sm); margin-bottom: var(--s-2);
}
.choice:hover { background: var(--paper-2); }
.choice input {
  flex: none; width: 1.375rem; height: 1.375rem; margin: 0;
  accent-color: var(--blue-ink); cursor: pointer;
}
.choice:has(input:checked) { background: var(--gold); }
.choice:has(input:focus-visible) { outline: var(--rule) solid var(--urgent);
                                   outline-offset: 2px; }
.choice span b { display: block; font-weight: 700; }
.choice span i { display: block; font-style: normal; color: var(--ink-soft);
                 font-size: var(--t-xs); }
.choice:has(input:checked) span i { color: var(--navy); }

.hp { position: absolute; left: -9999px; }

.legalese { font-size: var(--t-xs); line-height: 1.55; color: var(--ink-soft);
            max-width: 54ch; }
.band--navy .legalese, .band--navy-2 .legalese { color: var(--blue-lt); }

/* --------------------------------------------------------------------------
   16. Share / relational module
   -------------------------------------------------------------------------- */
.share { border: var(--rule) solid var(--white); border-radius: 3px;
         padding: var(--s-5); background: var(--navy-2); }
.share__msg {
  font: 400 var(--t-md)/1.5 var(--font-body);
  background: var(--paper); color: var(--ink);
  border: 0; border-radius: 2px; width: 100%;
  padding: var(--s-4); min-height: 6.5rem; resize: vertical;
}
.copied { font: 600 var(--t-sm)/1 var(--font-cond);
          letter-spacing: .06em; color: var(--gold); }

@media (min-width:640px) {  }

/* --------------------------------------------------------------------------
   18. FAQ
   -------------------------------------------------------------------------- */
/* --------------------------------------------------------------------------
   Down-ballot roster
   --------------------------------------------------------------------------
   Collapsed by chamber, and closed by default. Twenty-three races is genuinely
   overwhelming, and a voter only ever sees the handful drawn for their own
   address — so the page opens with the two statewide races everybody shares and
   lets people expand only the chamber they need. <details> does this natively,
   so it works with JS off and is keyboard-operable for free.
   -------------------------------------------------------------------------- */
.roster__tier {
  border: var(--rule) solid var(--navy); border-radius: 3px;
  background: var(--white); margin-bottom: var(--s-4);
}
.roster__tier summary {
  display: flex; align-items: center; gap: var(--s-4);
  cursor: pointer; padding: var(--s-4) var(--s-5); min-height: var(--tap);
  font: 600 var(--t-md)/1.2 var(--font-cond);
  letter-spacing: .06em; text-transform: uppercase; color: var(--navy);
}
.roster__tier summary::-webkit-details-marker { display: none; }
.roster__tier summary::after {
  content: "+"; margin-inline-start: var(--s-3);
  font: 400 1.5em/1 var(--font-cond); color: var(--urgent);
}
.roster__tier[open] summary::after { content: "\2013"; }
.roster__tier[open] summary { border-bottom: var(--rule) solid var(--navy); }
.roster__count {
  margin-inline-start: auto;
  font: 500 var(--t-xs)/1 var(--font-cond); letter-spacing: .14em;
  color: var(--ink-soft);
}
.roster__tier ul { list-style: none; margin: 0; padding: 0; }
.roster__tier li {
  display: grid; grid-template-columns: 6rem minmax(0, 1fr) auto;
  gap: 0 var(--s-4); align-items: baseline;
  padding: var(--s-3) var(--s-5);
}
.roster__tier li + li { border-top: 1px solid rgba(6, 43, 91, .14); }
.roster__tier li:nth-child(even) { background: var(--paper); }
.roster__seat {
  font: 600 var(--t-xs)/1.4 var(--font-cond);
  letter-spacing: .12em; text-transform: uppercase; color: var(--urgent);
}
.roster__who { font-weight: 700; color: var(--navy); }
@media (max-width: 560px) {
  .roster__tier li { grid-template-columns: minmax(0, 1fr) auto; }
  .roster__seat { grid-column: 1 / -1; margin-bottom: var(--s-1); }
}

.qa { border-top: var(--rule) solid var(--navy); }
.qa details { border-bottom: 1px solid rgba(6,43,91,.2); }
.qa summary {
  cursor: pointer; list-style: none;
  padding: var(--s-4) var(--s-6) var(--s-4) 0; min-height: var(--tap);
  font: 700 var(--t-md)/1.4 var(--font-body); position: relative;
}
.qa summary::-webkit-details-marker { display: none; }
.qa summary::after {
  content: "+"; position: absolute; inset-inline-end: 0; top: var(--s-3);
  font: 600 1.75rem/1 var(--font-cond); color: var(--urgent);
}
.qa details[open] summary::after { content: "–"; }
.qa details[open] summary { color: var(--urgent); }
.qa .qa__body { padding: 0 var(--s-6) var(--s-5) 0; max-width: var(--prose); }
.qa .qa__body > :last-child { margin-bottom: 0; }

/* The component was only ever used on light bands, so its two --urgent accents
   were tuned for paper. Dropped onto a navy band by /get-paid.html they measured
   2.18:1 — an open question's own heading was the least readable text on the
   page. Gold is the surface-appropriate accent here at 6.3:1, and it is already
   what .checks and .eyebrow switch to on navy. */
.band--navy .qa, .band--navy-2 .qa { border-top-color: var(--gold); }
.band--navy .qa details,
.band--navy-2 .qa details { border-bottom-color: rgba(255, 255, 255, .22); }
.band--navy .qa summary::after,
.band--navy-2 .qa summary::after,
.band--navy .qa details[open] summary,
.band--navy-2 .qa details[open] summary { color: var(--gold); }

/* --------------------------------------------------------------------------
   19. Hotline
   -------------------------------------------------------------------------- */
.hotline { display: grid; gap: var(--s-2);
           border: var(--rule) solid var(--navy); border-radius: 3px;
           background: var(--paper); padding: var(--s-5); color: var(--navy); }
.hotline__n {
  font: 600 clamp(1.375rem,4.5vw,2rem)/1 var(--font-cond);
  letter-spacing: .01em; color: var(--navy); text-decoration: none;
  display: inline-block;
  /* This is the number someone taps while standing at a polling place being told
     they cannot vote. It was a 32px target. Nothing on the site has a better claim
     to a full 48px. */
  padding-block: var(--s-2); min-height: var(--tap);
}
.hotline__n:hover { color: var(--urgent); }
.hotline__lang { font: 500 var(--t-xs)/1 var(--font-cond);
                 letter-spacing: .12em; text-transform: uppercase;
                 color: var(--ink-soft); }
.hotline a { color: var(--blue-ink); }

/* --------------------------------------------------------------------------
   20. Footer
   -------------------------------------------------------------------------- */
.foot { background: var(--navy); color: var(--paper);
        padding-block: var(--s-7); border-top: var(--rule) solid var(--gold); }

/* Footer lockup — larger than the masthead's, and left-aligned to sit with the
   left-aligned column beneath it rather than floating centred over it. */
.brand--foot {
  --lockup-size: 1.875rem;
  align-items: flex-start;
  margin-bottom: var(--s-4);
}
@media (max-width: 520px) { .brand--foot { --lockup-size: 1.5rem; } }
.foot a { color: var(--blue-lt); }
.foot__grid { display: grid; gap: var(--s-6); }
/* Four columns since the footer picked up a "Work with us" list. Going straight
   from one to four at 760px squeezed "Michigan Voter Information Center" onto
   three lines, so the tablet range gets two columns and four only arrive when
   there is room for them. */
@media (min-width:760px)  { .foot__grid { grid-template-columns: 1fr 1fr; } }
@media (min-width:1000px) { .foot__grid { grid-template-columns: 1.4fr 1fr 1fr 1fr; } }
.foot h4 { color: var(--white); margin-bottom: var(--s-3);
           font-family: var(--font-cond); font-weight: 600;
           letter-spacing: .14em; font-size: var(--t-xs); }
.foot ul { list-style: none; margin: 0; padding: 0; }
.foot li { font-size: var(--t-sm); }
/* Footer links were 20px tall — under WCAG 2.5.8's 24px minimum, and the rows sat
   close enough together that the spacing exception did not rescue them. These are
   whole-line navigation targets, not links inside a sentence, so the exemption for
   inline text does not apply either. inline-block is required: padding on an
   inline box does not grow the line, so the hit area would not actually change.
   36px tall on a 44px pitch. */
.foot li a { display: inline-block; padding-block: var(--s-2); }
.langs a { display: inline-block; padding: var(--s-2) var(--s-3) var(--s-2) 0; }
.disclaimer {
  margin-top: var(--s-7); padding-top: var(--s-5);
  border-top: var(--rule) solid var(--navy-2);
  font-size: var(--t-xs); line-height: 1.65;
  color: #A9BFD4;                    /* 7.4:1 on navy */
  max-width: 82ch;
}
.disclaimer strong { color: var(--white); }
.langs { display: flex; gap: var(--s-3); flex-wrap: wrap;
         font-size: var(--t-sm); }
.langs a { font-weight: 700; }

/* --------------------------------------------------------------------------
   20b. Pay stub — the shift-math block on /get-paid.html
   --------------------------------------------------------------------------
   The one question someone actually has about paid field work is "what will I
   make?", so the answer is set as the artifact that answers it: a pay stub.
   Tabular figures, a rule above the total, and a torn edge. Reusing .stub's
   dashed perforation would have fought the table, so the tear lives on ::after
   as a zigzag mask instead. Numbers are `font-variant-numeric: tabular-nums`
   so the columns line up digit-for-digit at any size.                        */
.pay {
  background: var(--white); border: var(--rule) solid var(--navy);
  border-radius: 3px; padding: var(--s-5) var(--s-5) var(--s-6);
  position: relative;
}
.pay__t {
  font: 600 var(--t-xs)/1 var(--font-cond);
  letter-spacing: .18em; text-transform: uppercase; color: var(--ink-soft);
  margin: 0 0 var(--s-4); padding-bottom: var(--s-3);
  border-bottom: 1px solid rgba(6, 43, 91, .25);
}
.pay table { width: 100%; border-collapse: collapse; }
.pay th, .pay td {
  padding: var(--s-3) 0; text-align: start; vertical-align: baseline;
  border-bottom: 1px solid rgba(6, 43, 91, .14);
}
.pay th { font: 400 var(--t-md)/1.35 var(--font-body); color: var(--ink); }
/* Hours column: secondary, so it never competes with the dollar figure. */
.pay td:first-of-type {
  font-size: var(--t-sm); color: var(--ink-soft); white-space: nowrap;
  text-align: end; padding-inline-end: var(--s-4);
  font-variant-numeric: tabular-nums;
}
.pay td:last-child {
  font: 600 1.375rem/1 var(--font-cond); color: var(--navy);
  text-align: end; white-space: nowrap;
  font-variant-numeric: tabular-nums;
}
.pay tr:last-child th, .pay tr:last-child td { border-bottom: 0; }
/* The line a stub actually emphasises. */
.pay tr.pay__tot th, .pay tr.pay__tot td {
  border-top: 3px solid var(--navy); border-bottom: 0;
  padding-top: var(--s-4);
}
.pay tr.pay__tot th { font-weight: 700; }
.pay tr.pay__tot td:last-child { font-size: 1.875rem; color: var(--urgent); }
.pay__n {
  display: block; margin: var(--s-5) 0 0;
  font-size: var(--t-sm); line-height: 1.55; color: var(--ink-soft);
  max-width: 62ch;
}
/* Torn bottom edge — a stub is a thing you tear off. */
.pay::after {
  content: ""; position: absolute; left: -3px; right: -3px; bottom: -3px;
  height: 10px; background: var(--navy);
  -webkit-mask: linear-gradient(135deg, #000 25%, transparent 25.5%) 0 0 / 14px 10px repeat-x;
          mask: linear-gradient(135deg, #000 25%, transparent 25.5%) 0 0 / 14px 10px repeat-x;
}

/* --------------------------------------------------------------------------
   20c. Evidence tiers — the claim-grading device on /services.html
   --------------------------------------------------------------------------
   Not decoration. Every price comparison on that page carries a tier saying how
   checkable it is: a published rate card and a thing someone remembers seeing
   are not the same evidence and are not presented as if they were. The chip is
   the structural spine of the section, so it has to read at 11px on three
   surfaces — hence explicit pairs rather than a single tinted background.      */
.tier {
  display: inline-block; white-space: nowrap;
  font: 600 var(--t-xs)/1 var(--font-cond);
  letter-spacing: .12em; text-transform: uppercase;
  padding: var(--s-2) var(--s-3); border-radius: 2px;
}
.tier--1 { background: var(--navy);  color: var(--white); }  /* 13.9:1 */
.tier--2 { background: var(--blue-ink); color: var(--white); } /* 6.4:1 */
.tier--3 { background: var(--gold);  color: var(--navy);  }  /* 6.3:1 */
/* On navy the two dark chips would vanish into the band. */
.band--navy .tier--1, .band--navy-2 .tier--1 {
  background: var(--white); color: var(--navy);
}
.band--navy .tier--2, .band--navy-2 .tier--2 {
  background: var(--blue-lt); color: var(--navy);            /* 7.0:1 */
}

/* Service catalogue rows. A definition list is the honest element here: each
   service is a term, and its price and its explanation are both descriptions of
   it. Two <dd>s per group rather than a <p>, because a bare <p> inside a
   <dl><div> group is invalid — the group's content model is dt+ followed by dd+.
   The price is dd one, the prose is dd two spanning the full row. */
.svc { margin: 0; }
.svc > div {
  display: grid; grid-template-columns: 1fr auto; gap: var(--s-2) var(--s-5);
  align-items: baseline;
  padding: var(--s-4) 0; border-bottom: 1px solid rgba(6, 43, 91, .16);
}
.svc > div:last-child { border-bottom: 0; }
.svc dt { font-weight: 700; color: var(--ink); }
.svc dd {
  margin: 0; text-align: end; white-space: nowrap;
  font: 600 var(--t-md)/1.3 var(--font-cond); color: var(--navy);
  font-variant-numeric: tabular-nums;
}
/* Two classes deep so it beats `.svc dd` (0,1,1) — a single class would lose
   and the prose would render right-aligned on one unwrapping line. */
.svc .svc__d {
  grid-column: 1 / -1; text-align: start; white-space: normal;
  font: 400 var(--t-sm)/1.55 var(--font-body);
  color: var(--ink-soft); max-width: 68ch;
}
@media (max-width: 560px) {
  .svc > div { grid-template-columns: 1fr; }
  .svc dd { text-align: start; }
}

/* --------------------------------------------------------------------------
   20d. Right-to-left — Arabic
   --------------------------------------------------------------------------
   Most of the flipping is already free: the directional box properties in this
   file were converted to logical ones (border-inline-start, padding-inline-*,
   text-align: start/end), which compute identically in LTR and mirror
   themselves in RTL. Nothing below duplicates that work.

   What is left is the set of things a mechanical mirror gets wrong: gradients
   and masks carry an explicit angle, the marquee carries a direction, and the
   wordmark is Latin and must not flip at all.                                */

/* The mark reads "VOTE for CHANGE" in Latin script. It is a logo, not prose —
   in an RTL page it stays exactly as drawn, including its internal order. The
   markup also carries dir="ltr"; this is the belt to that pair of braces. */
[dir="rtl"] .lockup,
[dir="rtl"] .brand__mi { direction: ltr; }

/* ---- Arabic typography ---------------------------------------------------
   The three brand faces are Latin-only subsets cut from the logo file, so they
   have no Arabic glyphs at all — Arabic set in them falls through to whatever
   the browser picks last, which is how you get a page where every heading is a
   different typeface. These stacks are all system fonts, which is not a
   preference: font-src is 'self' and the CSP would block a webfont CDN.        */
[dir="rtl"] {
  --font-display: "Noto Kufi Arabic", "SF Arabic", "Segoe UI", Tahoma,
                  "Geeza Pro", system-ui, sans-serif;
  --font-cond:    "Noto Kufi Arabic", "SF Arabic", "Segoe UI", Tahoma,
                  "Geeza Pro", system-ui, sans-serif;
  --font-serif:   "Noto Naskh Arabic", "SF Arabic", "Times New Roman", serif;
  --font-body:    "Noto Sans Arabic", "SF Arabic", "Segoe UI", Tahoma,
                  system-ui, sans-serif;
}

/* THE important one. This design leans on tracked-out uppercase for eyebrows,
   nav, buttons, table headers and the countdown labels — and both halves of
   that are wrong in Arabic:

     • Arabic is unicase. text-transform: uppercase does nothing to it, but it
       does mangle any Latin that appears inline, e.g. a URL or "MVIC".
     • letter-spacing is actively destructive. Arabic is cursive; letters JOIN.
       Adding tracking pulls the joins apart and renders words as strings of
       disconnected glyphs. It is the single most recognisable sign that nobody
       who reads Arabic looked at the page.

   Everything except the wordmark is reset. .brand wraps both the lockup and
   MICHIGAN, so excluding it protects the whole mark — and .brand__mi's .30em
   tracking, which is load-bearing there, survives untouched.
   Specificity works out to (0,3,0), which clears the tracked component rules
   like .nav a and .foot h4 without needing !important. */
[dir="rtl"] *:not(.brand):not(.brand *) {
  letter-spacing: normal;
  text-transform: none;
}

/* Hero composition mirrors: type on the right, footage on the left. Both the
   panel edge and the scrim that softens it have to turn around with it, or the
   feathered edge ends up on the outside of the frame and the type sits on the
   hard seam instead of the flat navy. */
@media (min-width: 701px) {
  [dir="rtl"] .bh__vid {
    left: 0; right: auto;
    -webkit-mask-image: linear-gradient(to left, transparent 0, #000 22%);
            mask-image: linear-gradient(to left, transparent 0, #000 22%);
  }
  /* 100deg mirrored about the vertical is 260deg. */
  [dir="rtl"] .ballot-head::after {
    background: linear-gradient(260deg,
      rgba(6, 43, 91, .82) 0 46%,
      rgba(6, 43, 91, .26) 57%,
      rgba(6, 43, 91, 0) 64%);
  }
}

/* The ticker scrolls toward the side the reader is coming from, so in RTL it
   has to run the other way or it reads as retreating. */
[dir="rtl"] .ticker__track { animation-direction: reverse; }

/* Numerals stay Western. Michigan's Arabic-speaking voters read their ballot,
   their driver's licence and their clerk's website in Latin digits, so a date
   rendered ٤ أغسطس ٢٠٢٦ would be the one element on the page that does not
   match the paperwork in their hand. The JS locale is pinned to
   ar-u-nu-latn for the same reason. */
[dir="rtl"] .count__n,
[dir="rtl"] .eday .count__n,
[dir="rtl"] .tally,
[dir="rtl"] .dates { font-variant-numeric: tabular-nums; }

/* --------------------------------------------------------------------------
   20e. Sizing corrections found by the full-breakpoint sweep
   --------------------------------------------------------------------------
   Each of these was caught by measuring boxes across 10 widths and 17 pages,
   not by looking. Grouped here so the reasoning stays with the fix.          */

/* A phone number must never break across two lines. 844-YALLA-US was wrapping
   after "844-" on how-to-vote at desktop, which makes it unscannable and splits
   its tap target into an 88px piece and a 21px piece on different rows. This is
   the one string on the site where a line break is a functional bug rather than
   a typographic preference. */
a[href^="tel:"] { white-space: nowrap; }

/* A link that IS the whole paragraph is not "in a sentence", so WCAG 2.5.8's
   inline exemption does not cover it and 24px is the floor. These are the
   "Find your site →" card footers, which measured 20–22px tall.
   padding-block rather than min-height: these stay inline so they can still
   wrap with the text around them when they are genuinely mid-sentence. */
.card p > a:only-child,
.step p > a:only-child,
.prose p > a:only-child,
.hotline p > a:only-child { display: inline-block; padding-block: 3px; }

/* Prose inside a .step had no measure cap, so on /meme-the-vote.html — where
   steps sit in a full-width band rather than a grid column — paragraphs ran to
   116 and the example lines to 132 characters. --prose is the same 68ch cap the
   rest of the site's running text already uses. */
.step p, .step li { max-width: var(--prose); }

/* .roster__who holds a candidate's name on /candidates.html but a paragraph of
   description on /meme-the-vote.html, where the same component is reused for
   meme formats. As a 1fr grid cell that ran to 119 characters on desktop. The
   cap is a no-op on a name. */
.roster__who { max-width: var(--prose); }

/* The rail is a narrow sidebar on desktop and full width on tablet, so its
   small print went from a comfortable column to 103 characters at 768px —
   the widest line on the page set in the smallest type. */
.rail p { max-width: var(--prose); }

/* Cards are ~380px columns on desktop, where measure takes care of itself. At
   834px .grid--3 has already collapsed to a single column but the viewport is
   still wide, so a card becomes ~786px and its prose ran to 91 characters —
   the tablet-only failure mode that neither the phone nor the desktop pass
   shows. No-op at every width where the grid is still multi-column. */
.card p { max-width: var(--prose); }

/* A checkbox inside a .choice label is not really a 22px target — the whole
   label is clickable — but WCAG 2.5.8 measures the control, and 2px is a
   cheaper fix than an argument about it.

   .opt__oval is deliberately NOT included. It is the ballot-oval motif the whole
   homepage is built on, drawn at a real oval's 30x22 proportions, and its
   .opt__label is a 696x136 target sitting right next to it. Rounding the oval to
   satisfy a measurement that is already satisfied by the actual hit area would
   cost the one thing the page is designed around. */
.choice input[type="checkbox"],
.choice input[type="radio"] { min-width: 24px; min-height: 24px; }

/* Copy under a contest block runs the full ballot column — 740px, or 104
   characters at tablet-lg. Everything else in that column is a control or a
   short label, so it never inherited a measure. */
.contest > p { max-width: var(--prose); }

/* The hidden skip link kept its 24px inline padding while clipped to 1px, so
   its box still overlapped the wordmark. Nothing could see or click it, but a
   1px box should actually be 1px. */
.skip { padding: 0; }
.skip:focus { padding: var(--s-3) var(--s-5); }

/* --------------------------------------------------------------------------
   21. Utilities
   --------------------------------------------------------------------------
   These carry !important on purpose. They are single-declaration overrides
   applied at the call site, and they have to beat component rules like
   `.card p` (specificity 0,1,1) which would otherwise win against a plain
   class (0,1,0). This is the one place in the file where !important is correct.

   They exist at all because the Content-Security-Policy is `style-src 'self'`,
   which blocks inline style="" attributes outright. Every layout tweak has to
   be a class or it silently does nothing in production.
   -------------------------------------------------------------------------- */
/* A button always owns its own text colour.
   Must live after every band/nav rule. The link-colour rules above use an
   element selector (`.hero a`, `.band--navy a`, `.nav a` — specificity 0,1,1)
   which out-specifies `.btn` (0,1,0), so without this a gold button inside a
   navy band renders pale blue on gold instead of navy on gold. Matching
   specificity plus later source order settles it without !important. */
a.btn, button.btn { color: var(--btn-fg); }
a.btn:hover, button.btn:hover { color: var(--btn-fg); }

.sr { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
      overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; }
.lede { font-size: var(--t-lg); line-height: 1.5; }
.cond { font-family: var(--font-cond); }

[hidden] { display: none !important; }

/* Spacing */
.mt-0 { margin-top: 0 !important; }
.mt-x { margin: .5rem 0 0 !important; }
.mt-1 { margin-top: var(--s-4) !important; }
.mt-2 { margin-top: var(--s-5) !important; }
.mt-3 { margin-top: var(--s-6) !important; }
.mt-4 { margin-top: 2.5rem !important; }
.mb-0 { margin-bottom: 0 !important; }
.mb-1 { margin-bottom: var(--s-4) !important; }
.mb-2 { margin-bottom: var(--s-5) !important; }
.my-1 { margin-block: var(--s-4) !important; }
.my-2 { margin-block: var(--s-5) !important; }
.my-3 { margin-block: var(--s-6) !important; }
.gap-1 { gap: var(--s-4) !important; }
.flush { padding-inline: 0 !important; }
.minh-0 { min-height: 0 !important; }
.mx-auto { margin-inline: auto !important; }

/* Alignment */
.ai-start  { align-items: start !important; }
.ai-center { align-items: center !important; }

/* Type size */
.fs-xs { font-size: .8125rem !important; }
.fs-s  { font-size: .875rem !important; }
.fs-m  { font-size: .9375rem !important; }

.fw-normal { font-weight: 400 !important; }

.on-white { color: var(--white) !important; }

/* Measure — capped line length for readability */
.mw-44 { max-width: 44ch !important; }
.mw-46 { max-width: 46ch !important; }
.mw-48 { max-width: 48ch !important; }
.mw-50 { max-width: 50ch !important; }
.mw-52 { max-width: 52ch !important; }
.mw-54 { max-width: 54ch !important; }

/* Plain bulleted list inside a card */
.bullets { font-size: .9375rem !important; padding-inline-start: 1.25rem !important; }

/* ---- Gold-surface corrections -------------------------------------------
   Gold is the brightest thing in the palette, so anything mid-tone placed on
   it fails AA. Every one of these was caught by the automated contrast sweep,
   not by eye. Navy on gold is 6.3:1.
   !important because these must beat compound rules like `.band--navy .card a`
   (0,2,1), which otherwise wins on a gold card inside a navy band.
   `:not(.btn)` matters: a button inside a gold surface still owns its own
   colour, and forcing navy onto a navy or red button produced navy-on-navy. */
.card--now a:not(.btn),
.band--gold a:not(.btn),
.card--now .official,
.band--gold .official { color: var(--navy) !important; }

/* On the red band, a gold eyebrow is only 2.6:1. */
.band--urgent .eyebrow { color: var(--white) !important; }

/* aria-current on the nav CTA (`.nav a[aria-current]`, 0,2,1) was overriding
   the button's own text colour and putting white on gold at 2.2:1. */
.nav a.btn[aria-current],
.nav a.btn { color: var(--btn-fg) !important; }

/* Callout variants that sit on a dark band */
.note--ondark {
  background: var(--navy-2) !important;
  color: var(--paper) !important;
  border-color: var(--paper) !important;
}

.note--ondark a { color: var(--blue-lt); }

/* Editorial placeholder. Deliberately hideous so unwritten copy cannot ship
   quietly. Grep the repo for "TODO" before launch. */
/* Hazard stripes, but not behind the words. The stripes used to fill the whole
   block: navy measures 6.31:1 on --gold and 4.93:1 on the darker band, so it
   passed on ratio and was still hard to read, because 10px diagonals directly
   under 14px type interfere with the letterforms themselves. Contrast ratio can
   not see that — it is spatial noise, not a colour problem. So the stripes are
   confined to a band across the top, where they still shout "unwritten copy",
   and the text gets a flat gold field. */
.todo {
  background:
    repeating-linear-gradient(45deg,
      var(--gold) 0 10px, #C98F0E 10px 20px) top left / 100% 16px no-repeat,
    var(--gold);
  color: var(--navy);
  border: var(--rule) dashed var(--navy); border-radius: 3px;
  padding: calc(var(--s-4) + 16px) var(--s-4) var(--s-4);
  font: 500 var(--t-sm)/1.55 var(--font-cond); letter-spacing: .01em;
}

/* --------------------------------------------------------------------------
   22. Motion, forced colors, print
   -------------------------------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: .001ms !important; transition-duration: .001ms !important;
  }
  .btn:hover, .btn:active { transform: none; }
}

@media (forced-colors: active) {
  .btn, .card, .note, .choice, .lockup { border: 3px solid ButtonBorder;
                                         box-shadow: none; }
  .hero__art { display: none; }
  .checks li::before { box-shadow: inset 0 0 0 3px CanvasText; }
}

@media print {
  .masthead, .subnav, .hero__art, .share, .btn { display: none; }
  body { background: #fff; color: #000; font-size: 11pt; }
  .band, .band--navy, .band--navy-2, .band--urgent, .band--gold {
    background: #fff !important; color: #000 !important; padding-block: .5rem;
  }
  .band--navy h2, .band--navy-2 h2 { color: #000 !important; }
  .card { box-shadow: none; border: 1px solid #000; break-inside: avoid; }
  a::after { content: " (" attr(href) ")"; font-size: 9pt; word-break: break-all; }
  .qa details { break-inside: avoid; }
  .qa details:not([open]) .qa__body { display: block; }
  .lockup { border: 2px solid #000; }
}
