/* =============================================================================
   styles.css — screen styles for the MA 2026 Ballot Question Guide
   -----------------------------------------------------------------------------
   FILENAME MATTERS: index.html links "styles.css". GitHub Pages serves files
   case- and name-exact, so a repo file named style.css ships an unstyled site.
   Structure:
     1. Token layer (:root)        ← brand primitives → derived → semantic roles
     2. Base / reset
     3. Accessibility utilities
     4. Layout: header / main / footer
     5. Components: buttons, chips, banners, callouts, progress bar
     6. Views: welcome / question / summary (ballot card) / privacy / how-to
     7. Motion, forced-colors, responsive
   Design rules baked in:
     - Everything references tokens; no raw colors/sizes below :root.
     - Choice/status meaning never relies on color ALONE — text/ARIA carries it,
       color is redundant reinforcement.
     - Brand blue and brand green are IDENTITY colors, not text colors. Anything
       with text on it uses the derived *-ink variants (see §1).
     - Progress/completion state is NEUTRAL, never a vote color. A voter who
       votes NO on everything must not see an interface that turns "yes-green"
       to congratulate them.
     - Mobile-first; the reading column is capped for legibility.
   Typography (cSPA theme): the site's families are named first — Proxima Nova
   for text, FF Meta Serif for display — with system fallbacks behind them. Both
   are Adobe Fonts and not openly licensed, so nothing is vendored: no
   @font-face, no CDN, zero network requests. The canvas image export reads the
   resolved body stack, so page and export can't diverge.
   Palette source: cspa.tufts.edu (tuftstheme CSS, 2026-09): accessible blue
   #3172AE, dark blue #2A5C89, urban gray #646469, hillside green #566C11,
   Barnum red #CB333B, lyceum orange #D45D00, pastel blue #EFF5FB, text
   #161616, tile fill #F1F1F1, utility bar #333.
   ========================================================================== */
/* =============================================================================
   1. TOKEN LAYER — the theming seam
   Ratios in comments are measured against #fff unless stated. AA body text
   needs 4.5:1; large/bold text and non-text UI edges need 3:1.
   ========================================================================== */
:root {
  /* T-1: declare the intent so auto-dark engines leave the light theme alone. */
  color-scheme: light;
  /* --- Brand primitives: the Tufts palette as used on cspa.tufts.edu. --------
     Fills, rules, and stripes. The blue (5.12) now clears AA for white text,
     which is exactly how the site uses it (header band, footer, buttons). */
  --brand-blue:  #3172AE;   /* Tufts "accessible blue" — bands, buttons */
  --brand-green: #566C11;   /* Tufts "hillside green" — 5.9 on white */
  --brand-gray:  #646469;   /* Tufts "urban gray" — report-tile top rule */
  --brand-dark:  #333333;   /* the site's dark utility bar */
  --brand-red:   #CB333B;   /* Tufts "Barnum red" */
  --brand-orange: #D45D00;  /* Tufts "lyceum orange" (PMS 717) — the NO fill */
  /* --- Derived, contrast-verified ------------------------------------------ */
  --blue-ink:    #2A5C89;   /* 7.02 — Tufts dark blue; focus ring, halos */
  --blue-deep:   #204B72;   /* 8.99 — the site's button hover */
  --blue-mid:    #3172AE;   /* 5.12 — brand blue is itself AA-safe for text */
  --blue-tint:   #EFF5FB;   /* Tufts "pastel blue"; brand blue on it ≈ 4.7 */
  --green-ink:   #4A5D0E;   /* 7.3 — hillside green darkened for ink */
  --orange-ink:  #8F3F00;   /* 7.3 — lyceum orange darkened for ink, same way */
  --green-mid:   #566C11;   /* 5.9 */
  --green-tint:  #EEF2E2;   /* brand green at ~8% on white */
  --orange-tint: #FBEFE6;   /* lyceum orange at ~8% on white, same way */
  --gray-ink:    #333333;   /* 12.6 — secondary text, and DECIDED state */
  --gray-mid:    #646469;   /* 5.88 — muted text (official gray, as-is) */
  --gray-line:   #CBCACA;   /* decorative hairlines (site rule color) */
  --gray-line-strong: #BBBBBB; /* interactive control borders (site #bbb) */
  /* --- Semantic roles: components reference ONLY these. -------------------- */
  --color-bg:          #FFFFFF;   /* the site's page is white; tiles carry gray */
  --color-surface:     #FFFFFF;
  --color-tile:        #F1F1F1;   /* report-tile fill (news-card__item) */
  --color-tile-rule:   #98979A;   /* its heavy top rule: urban gray lightened one
                                     notch, halfway to --gray-line (was #646469) */
  --color-border:      var(--gray-line);        /* decorative only */
  --color-border-ui:   var(--gray-line-strong); /* T-4: form controls, segments */
  --color-text:        #161616;
  --color-text-muted:  var(--gray-mid);
  --color-accent:      var(--brand-blue);   /* identity fills, rules, stripes */
  --color-accent-ink:  var(--blue-mid);     /* anything with text on/in it */
  --color-accent-weak: var(--blue-tint);
  --color-brand-green:     var(--brand-green);
  --color-brand-green-ink: var(--green-ink);
  /* --- Feedback / notice tints --------------------------------------------- */
  --color-notice-bg:   #FFF7E6;
  --color-notice-ink:  #7A5B00;
  --color-warn-bg:     #FDECEA;
  --color-warn-ink:    #8F2A1F;   /* 7.06 — also the destructive-action ink */
  /* --- Vote direction ------------------------------------------------------
     The three hues are the cSPA brand palette and do not change. They are
     used as ACCENTS on white, never as fills: a left bar on the selected
     choice (over a ~8% tint of the hue), a bottom bar on a strip segment. Any
     text in a choice color (the selected label, the strip label, the card
     rows, the image export) uses the -ink variants: green-ink 7.3, orange-ink
     7.3 on white (6.4 / 6.5 on their tints); the grey is its own ink (5.9).
     The WORD is always drawn too, so grayscale loses nothing. */
  --color-choice-yes:       #62A60A;   /* brand green */
  --color-choice-no:        #D45D00;   /* PMS 717 orange */
  --color-choice-undecided: #646469;   /* brand cool grey */
  --color-choice-yes-ink:       var(--green-ink);
  --color-choice-no-ink:        var(--orange-ink);
  --color-choice-undecided-ink: var(--color-choice-undecided);
  --color-choice-yes-tint:      var(--green-tint);
  --color-choice-no-tint:       var(--orange-tint);
  /* --- Progress strip -------------------------------------------------------
     Every segment is white with a gray border; the stored choice is a 4px bar
     along the bottom edge in the --color-choice-* hue with the label in the
     matching -ink, and an unanswered segment has no bar and a dark label. The
     aria-label still says the choice in words. Completion ("All set") stays
     green-ink; it is about finishing, not about a vote direction. */
  /* --- Per-question identity palette (image export only) --------------------
     The on-screen views no longer carry a per-question color; the ballot-card
     IMAGE EXPORT (app.js §19) still colors its row badges from this ramp via
     --q-accent-count, so it stays. Brand-anchored: Tufts accessible blue →
     Tufts teal → hillside green × three lightness tiers; nothing red, nothing
     amber, so it cannot collide with the vote colors.
     ⚠ Adding a hue means bumping --q-accent-count; app.js reads it from here. */
  --q-accent-count: 9;
  --q-accent-1: #5B93C9;  --q-accent-2: #2E9FA4;  --q-accent-3: #7F9A3C;
  --q-accent-4: #3172AE;  --q-accent-5: #007F85;  --q-accent-6: #566C11;
  --q-accent-7: #2A5C89;  --q-accent-8: #005E63;  --q-accent-9: #3E4F0C;
  /* --- Typography: the site's two families, system fallbacks behind them ---
     "proxima-nova" / "ff-meta-serif-web-pro" are the Adobe Fonts names the site
     loads; a visitor with them installed gets the real faces, everyone else gets
     the fallback stack. Display = serif (site h1, department band, card
     headings); body = sans. */
  --font-sans:    "proxima-nova", "Proxima Nova", system-ui, -apple-system,
                  "Segoe UI", Roboto, "Noto Sans", Helvetica, Arial, sans-serif;
  --font-serif:   "ff-meta-serif-web-pro", "FF Meta Serif", Georgia,
                  "Noto Serif", "Times New Roman", serif;
  --font-display: var(--font-serif);
  --font-body:    var(--font-sans);
  --font-mono:    ui-monospace, "SFMono-Regular", Menlo, Consolas, monospace;
  /* --- Spacing scale --- */
  --space-1: 0.25rem;
  --space-2: 0.5rem;
  --space-3: 0.75rem;
  --space-4: 1rem;
  --space-5: 1.5rem;
  --space-6: 2.5rem;
  /* --- Shape & layout (T-12 policy: containers --radius, controls
     --radius-sm, pills 999px. Applied consistently below.) --- */
  --radius:      0;          /* the site is square-cornered */
  --radius-sm:   2px;
  --radius-pill: 999px;
  /* Band inset: the header bars and the footer fill the viewport, and their
     content sits 10% in from each edge (an 80% span) — between the full-width
     header and the 44rem reading column. Below 30rem it is the main column's
     1rem gutter (see the narrow block). */
  --band-inset: 10%;
  --max-width:   44rem;      /* main column cap (~704px): card, strip, controls */
  /* Welcome tile cap (2026-09-20; was a 38rem cap on every prose block,
     2026-09-19): the intro tile stops at 41rem (656px) so it sits closer to
     the width of the h1 above it. Nothing else uses the token — the how-to
     and privacy views and the question view's prose run the full 44rem
     column again. Inert wherever the column is already narrower (below a
     688px viewport, so at 390px and 320px nothing moves). */
  --reading-width: 41rem;
  /* T-3: the ring is blue-ink (6.47), not brand blue (3.43) — the old ring was
     effectively invisible against the primary button's own fill. */
  --focus-ring:        3px solid var(--blue-ink);
  --focus-ring-offset: 2px;
  --shadow-card: 0 1px 2px rgba(16, 24, 40, 0.06),
                 0 4px 16px rgba(16, 24, 40, 0.06);
}
/* =============================================================================
   2. BASE / RESET
   ========================================================================== */
*,
*::before,
*::after { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; }
body {
  margin: 0;
  background: var(--color-bg);
  color: var(--color-text);
  font-family: var(--font-body);
  font-size: 1.0625rem;          /* 17px base */
  font-weight: 400;
  line-height: 1.62;
}
/* app.js toggles the [hidden] attribute for view switching and for every
   conditional region (banners, QR block, print-only lines); make sure it wins
   over any display value a component sets. */
[hidden] { display: none !important; }
/* View headings take the site's serif display face; h2/h3 stay in the sans,
   as the site's own h2 does. Sizes are unchanged from the sans-only scale. */
h1, h2, h3 {
  font-family: var(--font-sans);
  color: var(--color-text);
  font-weight: 700;
}
h1 { font-family: var(--font-display); font-size: 1.6875rem; letter-spacing: 0; line-height: 1.22; margin: 0 0 var(--space-4); }
h2 { font-size: 1.25rem;   letter-spacing: -0.006em; line-height: 1.25; margin: var(--space-5) 0 var(--space-3); }
h3 { font-size: 1.0625rem; letter-spacing: 0;        line-height: 1.3;  margin: var(--space-4) 0 var(--space-2); }
p { margin: 0 0 var(--space-4); }
a { color: var(--color-accent-ink); }
img, svg, canvas { max-width: 100%; height: auto; }
/* Headings that receive programmatic focus shouldn't show a persistent outline
   on click, only on keyboard focus. */
:focus-visible {
  outline: var(--focus-ring);
  outline-offset: var(--focus-ring-offset);
  border-radius: var(--radius-sm);
}
:focus:not(:focus-visible) { outline: none; }
[tabindex="-1"]:focus { outline: none; }   /* view <h1> focus target: no ring */
/* T-8: the sticky header (and, on the question view, the sticky strip under
   it) would otherwise cover an anchor landing. --header-h is measured by
   app.js; the fallback is the desktop header height. */
.view h1,
.progress-seg { scroll-margin-top: calc(var(--header-h, 6.75rem) + 4rem); }
/* =============================================================================
   3. ACCESSIBILITY UTILITIES
   ========================================================================== */
/* Skip link: off-screen until focused. */
.skip-link {
  position: absolute;
  left: var(--space-3);
  top: -3rem;
  z-index: 100;
  padding: var(--space-2) var(--space-3);
  background: var(--color-surface);
  color: var(--color-accent-ink);
  border: 1px solid var(--color-border-ui);
  border-radius: var(--radius-sm);
  transition: top 120ms ease;
}
.skip-link:focus { top: var(--space-3); }
/* Screen-reader-only helper. */
.visually-hidden {
  position: absolute !important;
  width: 1px; height: 1px;
  margin: -1px; padding: 0; border: 0;
  clip: rect(0 0 0 0); clip-path: inset(50%);
  overflow: hidden; white-space: nowrap;
}
/* B-33: the tier-2 clipboard textarea needs to be OFF-SCREEN but NOT clipped —
   selection-based execCommand('copy') is unreliable on genuinely clipped
   elements in some engines. Separate class so .visually-hidden keeps its
   normal meaning, and still no inline styles (CSP stays strict). */
.offscreen-copy {
  position: fixed;
  left: -9999px;
  top: 0;
  width: 1px;
  height: 1px;
  padding: 0;
  border: 0;
  opacity: 0;
}
/* =============================================================================
   4. LAYOUT — header / main / footer
   ========================================================================== */
/* Off-screen SVG sprite (the two marks, see index.html). */
.svg-sprite { position: absolute; width: 0; height: 0; overflow: hidden; }
.cspa-mark { display: block; fill: currentColor; }
/* The site header, two bars as on cspa.tufts.edu: a #333 utility bar with the
   Tufts wordmark and school, then the accessible-blue department band. White
   on the blue measures 5.12:1. Everything in it is currentColor so the marks
   recolor with the bars. */
.site-header {
  position: sticky;
  top: 0;
  z-index: 20;
  color: #fff;
  background: var(--brand-blue);
}
.site-header__utility {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  min-height: 2.5rem;
  padding: var(--space-1) var(--band-inset);
  background: var(--brand-dark);
  color: #fff;
  /* the site's inset hairline between the bars */
  box-shadow: inset 0 -1px 0 0 rgba(255, 255, 255, 0.5);
}
.site-header__tufts-link { display: block; flex: 0 0 auto; color: inherit; }
.site-header__tufts {
  display: block;
  width: 5rem;            /* wordmark viewBox is 210.5×90 → 80×34px */
  height: 2.125rem;
  fill: currentColor;
}
.site-header__school {
  color: inherit;
  text-decoration: none;
  padding-left: var(--space-3);
  border-left: 1px solid rgba(255, 255, 255, 0.7);
  font-size: 0.8rem;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  line-height: 1.2;
}
.site-header__band {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2) var(--space-4);
  padding: var(--space-2) var(--band-inset);
  background: var(--brand-blue);
}
.site-header__brand {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  min-width: 0;
  text-decoration: none;
  color: #fff;
}
.site-header__mark { width: 2rem; height: 1.85rem; flex: 0 0 auto; }
.site-header__text { display: flex; flex-direction: column; min-width: 0; }
/* Narrow-only affiliation line (utility bar collapsed into the band). */
.site-header__affil {
  display: none;
  align-items: center;
  gap: var(--space-2);
  font-size: 0.65rem;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  opacity: 0.92;
}
.site-header__affil-tufts { width: 3.25rem; height: 1.4rem; fill: currentColor; }
.site-header__org {
  font-family: var(--font-display);
  font-weight: 500;
  font-size: 1.15rem;
  line-height: 1.2;
}
/* The site's outlined "alt-invert" button: white rule and label on the band. */
.site-header__summary-link {
  display: inline-block;
  padding: var(--space-2) var(--space-3);
  font-weight: 600;
  text-decoration: none;
  color: #fff;
  background: transparent;
  border: 2px solid #fff;
  white-space: nowrap;
  transition: background 120ms ease, color 120ms ease;
}
.site-header__summary-link:hover { color: var(--brand-blue); background: #fff; }
/* A blue-ink ring vanishes on the blue band; use white there. */
.site-header a:focus-visible { outline-color: #fff; }
.site-header__progress { font-variant-numeric: tabular-nums; }
.app-status {
  max-width: var(--max-width);
  margin: var(--space-3) auto 0;
  padding: 0 var(--space-4);
  color: var(--color-text-muted);
}
/* B-18a: app.js no longer toggles [hidden] here (hidden live regions swallow
   announcements in some AT pairs); emptiness drives the visual state instead. */
.app-status:empty { display: none; }
.app-status .btn { margin-top: var(--space-2); }
.main {
  max-width: var(--max-width);
  margin: 0 auto;
  padding: var(--space-5) var(--space-4) var(--space-6);
}
.is-loading .site-footer { visibility: hidden; }
/* The site's footer, as on cspa.tufts.edu: a full-width accessible-blue band
   with white text (5.12:1 — AA for every size used here). Left, the Tufts
   wordmark with UNIVERSITY beneath (the same sprite symbol as the utility bar,
   drawn larger); right, the Contact block; under both, a hairline rule and the
   bottom row (copyright left, legal links right, separated by a thin vertical
   rule). The band fills the viewport and the content is inset --band-inset
   (10%) from each edge, the same as the header's bars. Below 30rem the pieces
   stack and the bottom row wraps. Print hides the footer (print.css). */
.site-footer {
  margin-top: var(--space-6);
  padding: var(--space-6) var(--band-inset) var(--space-5);
  background: var(--brand-blue);
  color: #fff;
  font-size: 0.9rem;
  line-height: 1.5;
}
.site-footer a { color: #fff; }
.site-footer__main {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: var(--space-5) var(--space-6);
}
.site-footer__tufts-link { display: block; flex: 0 0 auto; color: inherit; }
.site-footer__tufts {
  display: block;
  width: 11rem;           /* wordmark viewBox is 210.5×90 → 176×75px */
  height: 4.7rem;
  fill: currentColor;
}
.site-footer__contact { flex: 0 1 auto; }
.site-footer__heading {
  margin: 0 0 var(--space-2);
  font-family: var(--font-body);
  font-size: 1rem;
  font-weight: 700;
  color: inherit;         /* white on the band; the h2 rule would ink it */
}
.site-footer__contact p { margin: 0 0 var(--space-2); }
.site-footer__contact p:last-child { margin-bottom: 0; }
.site-footer__bottom {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  align-items: baseline;
  gap: var(--space-2) var(--space-4);
  margin-top: var(--space-5);
  padding-top: var(--space-3);
  border-top: 1px solid #fff;
  font-size: 0.8rem;
}
.site-footer__copyright { margin: 0; }
.site-footer__legal {
  display: flex;
  flex-wrap: wrap;
  list-style: none;
  margin: 0;
  padding: 0;
}
.site-footer__legal li + li {
  margin-left: var(--space-3);
  padding-left: var(--space-3);
  border-left: 1px solid #fff;   /* the thin vertical rule between the links */
}
/* =============================================================================
   5. COMPONENTS — buttons, chips, banners, callouts, progress bar
   ========================================================================== */
/* --- Buttons (shared by <button> and .btn anchors) --- */
.btn {
  display: inline-block;
  padding: var(--space-3) var(--space-4);
  font: inherit;
  font-weight: 700;
  text-align: center;
  text-decoration: none;
  color: var(--color-text);
  background: var(--color-surface);
  border: 1px solid var(--color-border-ui);   /* T-4: an interactive edge */
  border-radius: var(--radius-sm);
  cursor: pointer;
  transition: background 120ms ease, border-color 120ms ease, color 120ms ease;
}
.btn:hover { border-color: var(--color-accent-ink); color: var(--color-accent-ink); }
/* The primary fill is blue-INK, not brand blue: white-on-#3E8EDE measured
   3.43:1 and failed AA for the button's own label. */
.btn--primary {
  color: #fff;
  background: var(--color-accent-ink);
  border-color: var(--color-accent-ink);
}
.btn--primary:hover {
  color: #fff;
  background: var(--blue-deep);
  border-color: var(--blue-deep);
}
/* T-3: a dark ring vanishes on a dark fill. Invert it and add an outer halo. */
.btn--primary:focus-visible {
  outline: 3px solid #fff;
  outline-offset: 0;
  box-shadow: 0 0 0 5px var(--blue-ink);
}
/* Quiet button: destructive-but-legitimate actions that should be findable,
   not prominent. T-11: the destructive signal is now persistent (gray-ink
   border + ink label), with red reserved for hover/focus — previously the red
   only appeared once the pointer was already on the button. */
.btn--quiet {
  padding: var(--space-2) var(--space-3);
  font-size: 0.9rem;
  color: var(--gray-ink);
  background: transparent;
  border-color: var(--gray-ink);
}
.btn--quiet:hover,
.btn--quiet:focus-visible { color: var(--color-warn-ink); border-color: var(--color-warn-ink); }
/* Explicit destructive confirm (step 2 of the erase flow). White on
   --color-warn-ink measures 7.06:1. */
.btn--danger {
  color: #fff;
  background: var(--color-warn-ink);
  border-color: var(--color-warn-ink);
}
.btn--danger:hover { color: #fff; background: #7A2419; border-color: #7A2419; }
/* A button that reads as a link ("Read more" / "Show less"). */
.link-btn {
  padding: 0;
  font: inherit;
  font-weight: 700;
  color: var(--color-accent-ink);
  background: none;
  border: 0;
  cursor: pointer;
  text-decoration: underline;
  text-underline-offset: 0.15em;
}
.link-btn:hover { color: var(--blue-deep); }
/* --- Banners (order-uncertified / webview notices) --- */
.banner {
  padding: var(--space-3) var(--space-4);
  border-radius: var(--radius-sm);
  border: 1px solid var(--color-border);
  margin: 0 0 var(--space-4);
  font-size: 0.95rem;
}
.banner--notice { background: var(--color-notice-bg); color: var(--color-notice-ink); border-color: var(--color-notice-ink); }
/* --- Callout: the yes/no inversion notice — highest-value element --- */
.callout {
  padding: var(--space-4);
  border-radius: var(--radius);
  margin: 0 0 var(--space-5);
}
.callout--inversion {
  background: var(--color-accent-weak);   /* the blue tint, not the red warn */
  color: var(--color-accent-ink);
  border: 0;
}
.callout--inversion h2 { margin-top: 0; color: inherit; }
/* --- Clickable progress bar (question + card views) — the jump navigation ---
   Every segment is white with a gray border; the stored choice is a 4px
   bottom bar in the shared --color-choice-* hue (a ::after, so the outline can
   stay a box-shadow) plus the label in the matching -ink, and unanswered has
   no bar and the dark label. The current question carries a
   two-ring outline. Each segment's aria-label states the choice in words, so
   nothing rests on color alone; forced-colors mode keeps a shape distinction
   (dashed = unanswered). Segments share the row equally so nine "Q" labels
   stay on one line down to 320px. */
.progress-bar {
  display: flex;
  gap: var(--space-1);
  margin: var(--space-4) 0 var(--space-5);
}
.progress-seg {
  position: relative;
  overflow: hidden;
  flex: 1 1 0;
  min-width: 0;
  min-height: 2.25rem;
  display: grid;
  place-items: center;
  font-weight: 700;
  font-size: 0.8rem;
  font-variant-numeric: tabular-nums;
  text-decoration: none;
  white-space: nowrap;
  color: var(--color-text);
  background: var(--color-surface);
  border: 2px solid var(--color-border-ui);
  border-radius: var(--radius-sm);
  transition: box-shadow 120ms ease;
}
.progress-seg::after {
  content: "";
  position: absolute;
  left: 0; right: 0; bottom: 0;
  height: 4px;
  background: transparent;
}
.progress-seg.is-yes       { color: var(--color-choice-yes-ink); }
.progress-seg.is-no        { color: var(--color-choice-no-ink); }
.progress-seg.is-undecided { color: var(--color-choice-undecided-ink); }
.progress-seg.is-yes::after       { background: var(--color-choice-yes); }
.progress-seg.is-no::after        { background: var(--color-choice-no); }
.progress-seg.is-undecided::after { background: var(--color-choice-undecided); }
/* The question view's strip sticks directly under the sticky header. It bleeds
   to the column edges with a solid ground so reading text scrolls beneath it
   cleanly; z-index sits below the header (20) and above the watermark (10). */
.progress-bar--sticky {
  position: sticky;
  top: var(--header-h, 6.75rem);
  z-index: 15;
  margin: calc(-1 * var(--space-5)) calc(-1 * var(--space-4)) var(--space-5);
  padding: var(--space-3) var(--space-4);
  background: var(--color-bg);
  box-shadow: 0 1px 0 0 var(--color-border);
}
/* Hover is a soft ring; the current question is a white gap plus an ink ring,
   which reads on every fill. Focus keeps the global focus-visible outline. */
.progress-seg:hover        { box-shadow: 0 0 0 2px var(--color-accent-weak); }
.progress-seg.is-active    { box-shadow: 0 0 0 2px var(--color-bg), 0 0 0 4px var(--gray-ink); }
/* =============================================================================
   6. VIEWS
   ========================================================================== */
/* --- 6a. Welcome --- */
/* The intro takes the site's report-tile treatment (see .means). */
.welcome__intro {
  margin: 0 0 var(--space-5);
  padding: var(--space-4);
  background: var(--color-tile);
  border-top: 6px solid var(--color-tile-rule);
  font-size: 1.075rem;
  line-height: 1.6;
}
.welcome__intro p:last-child { margin-bottom: 0; }
/* "First time? More about how this works." under the Get started button: a
   muted line at the body size (2026-09-20; was 0.9rem), not a bold link. */
.welcome__howto {
  margin: 0;
  color: var(--color-text-muted);
}
/* Reading width (see --reading-width): the Welcome tile only. */
.welcome__intro { max-width: var(--reading-width); }
/* --- Draft watermark (meta.draft) ---
   One fixed, clipped layer over the viewport: it can't affect layout, and
   pointer-events:none means it never takes a click. It is painted ABOVE the
   page at 6% opacity rather than beneath it, because the white ballot card and
   gray tiles would hide anything underneath; below the sticky header (z 20).
   print.css repositions it onto the printed card. */
.watermark {
  position: fixed;
  inset: 0;
  z-index: 10;
  display: grid;
  place-items: center;
  overflow: hidden;
  pointer-events: none;
  user-select: none;
}
.watermark__text {
  font-family: var(--font-sans);
  font-weight: 700;
  font-size: clamp(5rem, 28vw, 18rem);
  letter-spacing: 0.12em;
  line-height: 1;
  color: var(--gray-ink);
  opacity: 0.06;
  transform: rotate(-30deg);
  white-space: nowrap;
}
/* --- 6d. Privacy (#v=privacy) and how-to (#v=howto) views: plain
   paragraphs, or the numbered steps, under the h1; then the closing line with
   its cross-link, then the back link. The card's link into the privacy view
   lives in .hint--saved. The steps stay at the body size. */
.privacy { margin: 0 0 var(--space-5); }
.privacy p:last-child { margin-bottom: 0; }
.howto {
  margin: 0 0 var(--space-5);
  padding-left: 1.35rem;
}
.howto li { margin-bottom: var(--space-3); padding-left: var(--space-1); }
.howto li:last-child { margin-bottom: 0; }
.closing { margin: 0 0 var(--space-5); }
/* --- 6b. Question view --- */
.back a { text-decoration: none; font-weight: 700; }   /* the privacy view's back link */
.eyebrow {
  font-size: 0.8rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--color-text-muted);
  margin-bottom: var(--space-1);
}
/* Question label: a plain small-caps eyebrow above the title — no pill (no
   fill, border, or radius). Sized like the card's Q labels (.ballot-row__num:
   the body size, bold, the muted ink), uppercase with the eyebrow's tracking,
   and the same gap to the title the pill had. */
.view--question .eyebrow {
  display: block;
  margin-bottom: var(--space-3);
  padding: 0;
  font-size: inherit;     /* the body's 17px, as the card's Q# inherits it */
  color: var(--color-text-muted);
  background: none;
  border: 0;
  border-radius: 0;
}
/* A YES/NO means block: the site's report-tile treatment (news-card__item) —
   light gray fill under a heavy urban-gray top rule. */
.means {
  margin: 0 0 var(--space-5);
  padding: var(--space-4);
  background: var(--color-tile);
  border: 0;
  border-top: 6px solid var(--color-tile-rule);
  border-radius: var(--radius);
}
.means__row { margin-bottom: var(--space-3); }
.means__row:last-child { margin-bottom: 0; }
.means dt { color: var(--color-text-muted); font-size: 0.9rem; }
.means dd { margin: 0; }
/* The words YES / NO in the two headings take the choice inks (weight is the
   <strong>'s, unchanged). On the tile: green-ink 6.4:1, orange-ink 6.4:1. */
.means__yes { color: var(--color-choice-yes-ink); }
.means__no  { color: var(--color-choice-no-ink); }
/* The executive summary is the default reading layer: give it more air than UI
   text now that everything shares one family. The lead is always visible and
   ends with "… Read more" inline; the rest opens in place beneath it and the
   last Key findings item ends with " Show less" inline, the same way. */
.summary { font-size: 1.075rem; line-height: 1.68; }
.summary p:last-child { margin-bottom: 0; }
.summary--lead { margin-bottom: var(--space-5); }
.summary__more { white-space: nowrap; }
.summary-more { margin: 0 0 var(--space-5); }
.summary-more > .summary { margin-top: var(--space-4); }
.summary__less { white-space: nowrap; }
.summary__guide { margin: var(--space-4) 0 0; font-weight: 700; }
.summary--findings-heading {
  margin: var(--space-5) 0 var(--space-2);
  font-size: 1.125rem;
}
.summary--findings {
  margin: 0 0 var(--space-5);
  padding-left: 1.35rem;
  line-height: 1.5;
}
.summary--findings li {
  margin-bottom: var(--space-3);
  padding-left: var(--space-1);
}
.summary--findings li:last-child { margin-bottom: 0; }
.fiscal {
  padding: var(--space-3) var(--space-4);
  background: var(--color-accent-weak);
  border-radius: var(--radius-sm);
  font-size: 0.95rem;
}
/* Full guide expander */
.full-guide {
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
  margin: var(--space-5) 0;
  background: var(--color-surface);
}
.full-guide > summary {
  cursor: pointer;
  padding: var(--space-3) var(--space-4);
  font-weight: 700;
  color: var(--color-accent-ink);
}
.full-guide__body { padding: 0 var(--space-4) var(--space-4); }
.full-guide__body table {
  width: 100%;
  border-collapse: collapse;
  margin: var(--space-3) 0;
  font-size: 0.95rem;
}
.full-guide__body th,
.full-guide__body td {
  border: 1px solid var(--color-border);
  padding: var(--space-2) var(--space-3);
  text-align: left;
  vertical-align: top;
}
.full-guide__body th { background: var(--color-tile); }
/* Supports / opposes */
.stances {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--space-4);
  margin: var(--space-5) 0;
}
.stances h2 { font-size: 1rem; margin-top: 0; }
.stances ul { margin: 0; padding-left: 1.1rem; }
/* Dig-deeper collapsibles */
.deeper__item {
  border-top: 1px solid var(--color-border);
  padding: var(--space-2) 0;
}
.deeper__item > summary { cursor: pointer; font-weight: 700; }
/* Decide (choice + note): no box, no heading. Yes/No are a segmented row of
   equal-width radios; the skip control ("Not sure, skip for now" / "Skipped
   for now" once stored) is a smaller link-styled radio under it, in the same
   group. */
.decide { margin: var(--space-5) 0; }
.choice-group { margin: 0 0 var(--space-4); }
.choice-row {
  display: flex;
  margin: 0 0 var(--space-2);
}
/* Choice radio buttons — color reinforces the aria-checked state, but the
   label text and radio semantics are the source of truth. Segments share
   their inner borders; the checked one is lifted so its border wins. The
   checked one takes a ~8% tint of its hue, a 4px left bar in the hue (an inset
   shadow, so the row's geometry never moves) and the label in the matching
   ink. */
.choice-btn {
  flex: 1 1 0;
  min-width: 0;
  padding: var(--space-3) var(--space-2);
  font: inherit;
  font-weight: 700;
  color: var(--color-text);
  background: var(--color-surface);
  border: 2px solid var(--color-border-ui);
  border-radius: 0;
  cursor: pointer;
  transition: border-color 120ms ease, background 120ms ease, color 120ms ease;
}
.choice-btn + .choice-btn { margin-left: -2px; }
.choice-btn:first-child { border-radius: var(--radius-sm) 0 0 var(--radius-sm); }
.choice-btn:last-child  { border-radius: 0 var(--radius-sm) var(--radius-sm) 0; }
.choice-btn:hover,
.choice-btn:focus-visible,
.choice-btn[aria-checked="true"] { position: relative; z-index: 1; }
.choice-btn:hover { border-color: var(--color-text-muted); }
.choice-btn[data-choice="yes"][aria-checked="true"] {
  color: var(--color-choice-yes-ink); background: var(--color-choice-yes-tint);
  box-shadow: inset 4px 0 0 0 var(--color-choice-yes);
}
.choice-btn[data-choice="no"][aria-checked="true"] {
  color: var(--color-choice-no-ink); background: var(--color-choice-no-tint);
  box-shadow: inset 4px 0 0 0 var(--color-choice-no);
}
.choice-btn[data-choice="undecided"][aria-checked="true"] {
  color: var(--color-choice-undecided-ink); box-shadow: inset 4px 0 0 0 var(--color-choice-undecided);
}
/* Secondary radio: visibly smaller and link-styled, never a filled button.
   Selected = same size, the grey ink, no underline, and the left-bar
   treatment scaled down (3px), no tint — the word plus aria-checked carry the
   state, the bar reinforces it. The selector repeats the attributes so it
   outranks the rule above. */
.choice-btn--secondary,
.choice-btn.choice-btn--secondary[data-choice="undecided"][aria-checked="true"] {
  flex: none;
  display: inline-block;
  width: auto;
  padding: var(--space-1) 0;
  font-size: 0.9rem;
  font-weight: 700;
  color: var(--color-accent-ink);
  background: none;
  border: 0;
  border-radius: 0;
  text-decoration: underline;
  text-underline-offset: 0.15em;
}
.choice-btn.choice-btn--secondary:hover { color: var(--blue-deep); border: 0; }
.choice-btn.choice-btn--secondary[data-choice="undecided"][aria-checked="true"] {
  color: var(--color-choice-undecided-ink);
  text-decoration: none;
  padding-left: var(--space-2);
  box-shadow: inset 3px 0 0 0 var(--color-choice-undecided);
}
/* All-decided line: shown under the choice controls only while every question
   has a stored choice (app.js toggles [hidden]). A second way to the card;
   Prev/Next are unchanged. The link matches the Prev/Next text links. */
.decide__done { margin: 0 0 var(--space-4); }
.decide__done a {
  font-weight: 700;
  color: var(--color-accent-ink);
  text-decoration: underline;
  text-underline-offset: 0.15em;
}
.decide__done a:hover { color: var(--blue-deep); }
.decide__note label { display: block; font-weight: 700; margin-bottom: var(--space-1); }
.decide__note textarea {
  width: 100%;
  padding: var(--space-3);
  font: inherit;
  color: var(--color-text);
  background: var(--color-bg);
  border: 1px solid var(--color-border-ui);
  border-radius: var(--radius-sm);
  resize: none;                    /* app.js grows `rows` with the content */
}
.hint { color: var(--color-text-muted); font-size: 0.85rem; margin: var(--space-1) 0 0; }
/* Prev/Next: text links with arrows on one line — nothing button-like, so they
   can't be mistaken for the choice row. Next hugs the right edge even when
   Previous is absent (first question). The 14rem cap is what keeps the
   "full title if it fits" branch (applyResponsiveNavLabels) from firing: the
   short labels fit inside it at every width, a titled label never does. */
.qnav {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: var(--space-3);
  margin-top: var(--space-5);
}
.qnav__link {
  flex: 0 1 auto;
  max-width: 14rem;
  min-width: 0;
  white-space: nowrap;
  font-weight: 700;
  color: var(--color-accent-ink);
  text-decoration: underline;
  text-underline-offset: 0.15em;
}
.qnav__link:hover { color: var(--blue-deep); }
.qnav__next { margin-left: auto; text-align: right; }
.qnav__label--full { white-space: nowrap; }
.qnav__label--short { display: none; }
.qnav .use-short-label .qnav__label--full { display: none; }
.qnav .use-short-label .qnav__label--short { display: inline; }
/* --- 6c. Summary / ballot card --- */
.ballot-card {
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
  box-shadow: var(--shadow-card);
  padding: var(--space-5);
}
/* The card's masthead rule is the site's blue band color. print.css collapses
   it to ink. */
.ballot-card__head {
  border-bottom: 3px solid var(--brand-blue);
  padding-bottom: var(--space-3);
}
.ballot-card__head h1 { margin: 0; }
.ballot-card__date { color: var(--color-text-muted); margin: var(--space-1) 0 0; }
.ballot-card__sub  { margin: 0; }
.ballot-card__view-control {
  display: flex;
  justify-content: flex-end;
  margin-top: var(--space-3);
}
.ballot-card__view-control .btn {
  padding: var(--space-2) var(--space-3);
  font-size: 0.9rem;
}
.ballot-card__rows { list-style: none; margin: var(--space-4) 0; padding: 0; }
/* Row = a fixed-width lead column (Q#, with YES / NO directly beneath it in
   the choice ink; undecided and never-answered show nothing beneath) and the
   body column (title, then the detailed-mode sentence, note, Q9 reminder).
   The fixed lead width is what keeps the titles aligned across the rows. */
.ballot-row {
  display: grid;
  grid-template-columns: 3.5rem minmax(0, 1fr);
  gap: 0 var(--space-3);
  align-items: start;
  padding: var(--space-3) 0;
  border-bottom: 1px solid var(--color-border);
}
/* Screen-only report-tile treatment for the rows: gray fill, heavy top rule,
   a gap between tiles, and a 4px left bar in the row's choice hue (the same
   inset-shadow treatment as a selected choice button; grey for anything but
   yes/no). Scoped to screen so print.css's row layout (hairline rows, no
   fill, no bar) is untouched and the printed card stays exactly as it was. */
@media screen {
  .ballot-row {
    padding: var(--space-3) var(--space-3) var(--space-3) var(--space-3);
    margin-bottom: var(--space-2);
    background: var(--color-tile);
    border-bottom: 0;
    border-top: 5px solid var(--color-tile-rule);
  }
  .ballot-row.is-yes       { box-shadow: inset 4px 0 0 0 var(--color-choice-yes); }
  .ballot-row.is-no        { box-shadow: inset 4px 0 0 0 var(--color-choice-no); }
  .ballot-row.is-undecided { box-shadow: inset 4px 0 0 0 var(--color-choice-undecided); }
}
.ballot-row__lead,
.ballot-row__body { display: flex; flex-direction: column; gap: var(--space-1); min-width: 0; }
.ballot-row__num   { font-weight: 700; color: var(--color-text-muted); font-variant-numeric: tabular-nums; }
.ballot-row__title { font-weight: 700; }
.ballot-row__choice {
  font-weight: 700;
  text-align: left;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  font-variant-numeric: tabular-nums;
}
.ballot-row__choice:empty { display: none; }
/* Color reinforcement; app.js adds a modifier class per choice. Words on the
   tile use the -ink variants, not the fills. */
.ballot-row__choice.is-yes { color: var(--color-choice-yes-ink); }
.ballot-row__choice.is-no  { color: var(--color-choice-no-ink); }
.ballot-row__note {
  margin: 0;
  font-size: 0.9rem;
  color: var(--color-text-muted);
  font-style: italic;
}
.ballot-row__meaning {
  margin: 0;
  font-size: 0.9rem;
  color: var(--color-text-muted);
}
.ballot-row__inversion {
  margin: 0;
  font-size: 0.85rem;
  font-weight: 700;
  color: var(--color-warn-ink);
}
.ballot-card__status { margin: var(--space-4) 0; }
/* "All set!" is one of the few places brand green carries meaning without
   implying a vote direction: it's about completion, not about yes. */
.ballot-card__status.is-complete p { color: var(--color-brand-green-ink); font-weight: 700; }
.ballot-card__actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
  margin: var(--space-4) 0 var(--space-2);
}
/* Press-and-hold overlay: the exported card for coarse-pointer devices with
   no file sharing. A fixed layer above the header (z 20) and the watermark
   (z 10 — the DRAFT mark is drawn into the image itself in draft mode), on
   the page ground: a bar with the one instruction line and Close, then the
   image scaled down to the layer's width, so a phone never scrolls sideways;
   the box scrolls vertically when the card is taller than the screen. The
   src is still the full 1080px JPEG — press-and-hold saves the full-size
   file, not what the screen shows. */
.image-overlay {
  position: fixed;
  inset: 0;
  z-index: 30;
  display: flex;
  flex-direction: column;
  background: var(--color-bg);
  color: var(--color-text);
}
.image-overlay__bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
  border-bottom: 1px solid var(--color-border);
  background: var(--color-surface);
}
.image-overlay__line { margin: 0; font-weight: 700; }
.image-overlay__close { padding: var(--space-2) var(--space-3); font-size: 0.9rem; flex: 0 0 auto; }
.image-overlay__scroll {
  flex: 1 1 auto;
  min-height: 0;
  overflow: auto;
  -webkit-overflow-scrolling: touch;
  padding: var(--space-4);
  background: var(--color-tile);
}
.image-overlay__img {
  display: block;
  box-sizing: border-box;    /* the 1px border sits inside the 100%, not beside it */
  max-width: 100%;           /* the 1080px width attribute is the ceiling, not the floor */
  height: auto;
  border: 1px solid var(--color-border-ui);
  background: #fff;
}
/* Tier-3 image export fallback: a visible, press-and-hold-to-save preview for
   webviews that ignore the download attribute. The <img> is a data: URI, which
   `img-src 'self' data:` already permits — no CSP change needed. */
.image-fallback {
  margin: var(--space-3) 0;
  padding: var(--space-4);
  background: var(--color-notice-bg);
  color: var(--color-notice-ink);
  border: 1px solid var(--color-notice-ink);
  border-radius: var(--radius-sm);
}
.image-fallback p { margin: 0 0 var(--space-2); font-size: 0.95rem; }
.image-fallback img {
  display: block;
  width: 100%;
  height: auto;
  border: 1px solid var(--color-border-ui);
  border-radius: var(--radius-sm);
  background: #fff;
}
/* QR block. app.js keeps this [hidden] while QR generation is paused, so the
   rules below only take effect once a real generator is vendored in. */
.ballot-card__qr {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  padding: var(--space-4);
  background: var(--color-tile);
  border-radius: var(--radius);
}
.qr {
  width: 128px;
  height: 128px;
  flex: 0 0 auto;
  display: grid;
  place-items: center;
  background: var(--color-surface);
  border: 1px solid var(--color-border-ui);
}
.qr canvas, .qr svg, .qr img { display: block; }
.qr__label { margin: 0; font-size: 0.9rem; color: var(--color-text-muted); }
.ballot-card__foot {
  margin-top: var(--space-4);
  padding-top: var(--space-3);
  border-top: 1px solid var(--color-border);
  font-size: 0.85rem;
  color: var(--color-text-muted);
}
.ballot-card__foot p { margin: 0 0 var(--space-1); }
/* Print-only resume URL (shown by app.js only in fragment-fallback mode). On
   screen it's redundant with the address bar, so it never appears here; the
   print stylesheet reveals it. */
.resume-print { display: none; }
/* Erase control, below the card: findable, deliberately not prominent. */
.ballot-card__manage {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: var(--space-2);
  margin: var(--space-4) 0 var(--space-2);
}
/* Step 2 of the two-step erase. Deliberately looks like a decision point. */
.confirm {
  width: 100%;
  padding: var(--space-3) var(--space-4);
  background: var(--color-warn-bg);
  color: var(--color-warn-ink);
  border: 1px solid var(--color-warn-ink);
  border-radius: var(--radius);
}
.confirm__q { margin: 0 0 var(--space-3); font-size: 0.95rem; font-weight: 700; }
.confirm__actions { display: flex; flex-wrap: wrap; gap: var(--space-3); }
.noscript-msg {
  max-width: var(--max-width);
  margin: var(--space-5) auto;
  padding: var(--space-4);
  background: var(--color-warn-bg);
  color: var(--color-warn-ink);
  border-radius: var(--radius-sm);
}
/* Tier-3 clipboard fallback: visible, pre-selected textarea. */
.copy-fallback {
  margin: var(--space-3) 0;
  padding: var(--space-4);
  background: var(--color-notice-bg);
  color: var(--color-notice-ink);
  border: 1px solid var(--color-notice-ink);
  border-radius: var(--radius-sm);
}
.copy-fallback p { margin: 0 0 var(--space-2); font-size: 0.95rem; }
.copy-fallback__text {
  width: 100%;
  padding: var(--space-3);
  font: inherit;
  font-family: var(--font-mono);   /* keeps the resume URL scannable */
  font-size: 0.85rem;
  color: var(--color-text);
  background: var(--color-surface);
  border: 1px solid var(--color-border-ui);
  border-radius: var(--radius-sm);
  resize: vertical;
}
/* Muted empty-state list item (e.g. "No organized opposition."). */
.is-muted { color: var(--color-text-muted); font-style: italic; list-style: none; }
/* =============================================================================
   7. MOTION, FORCED COLORS, RESPONSIVE
   ========================================================================== */
/* Respect reduced-motion: kill transitions/animations. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    transition-duration: 0.01ms !important;
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    scroll-behavior: auto !important;
  }
}
/* T-6. Windows High Contrast / forced-colors replaces every author color, so
   any state carried by hue alone disappears. Keep the SHAPE distinction
   (solid vs dotted) and preserve the pressed choice buttons' fill, since
   aria-checked styling is meaningful state, not decoration. */
@media (forced-colors: active) {
  .progress-seg { border: 2px solid CanvasText; }
  .progress-seg.is-unanswered { border-style: dashed; }
  .progress-seg.is-active     { outline: 2px solid Highlight; outline-offset: 2px; }
  /* Shadows and backgrounds are dropped here, so the left bar becomes a
     Highlight border and the checked label takes the Highlight color. */
  .choice-btn[aria-checked="true"] { color: Highlight; border-left: 4px solid Highlight; }
  .choice-btn.choice-btn--secondary[data-choice="undecided"][aria-checked="true"] { color: Highlight; border: 0; border-left: 3px solid Highlight; }
  .site-header, .site-footer, .ballot-card__head { border-bottom-color: CanvasText; }
}
/* Narrow phones: stack the two-column stances and give controls room. */
@media (max-width: 30rem) {
  h1 { font-size: 1.4375rem; }
  :root { --band-inset: var(--space-4); }   /* the main column's gutter */
  /* Header collapses to one bar: the affiliation moves into the band. */
  .site-header__utility { display: none; }
  .site-header__affil { display: flex; }
  .site-header__mark { width: 1.75rem; height: 1.6rem; }
  .site-header__org { font-size: 1rem; }
  .view h1, .progress-seg { scroll-margin-top: calc(var(--header-h, 8.5rem) + 4rem); }
  .stances { grid-template-columns: 1fr; }
  .ballot-card__qr { flex-direction: column; align-items: flex-start; }
  .ballot-card__manage { align-items: stretch; }
  .ballot-card__actions .btn { flex: 1 1 100%; }
  .qnav__label--full { display: none; }
  .qnav__label--short { display: inline; }
  /* Footer: wordmark, then the contact block, then the bottom row, which
     wraps onto two lines (copyright, then the legal links). */
  .site-footer__main { flex-direction: column; }
  .site-footer__bottom { flex-direction: column; align-items: flex-start; }
}
/* Larger screens: a touch more headline scale. */
@media (min-width: 48rem) {
  h1 { font-size: 1.9375rem; }
}
