/* =============================================================================
   Keep It Honest GA — styles
   Ground Game Georgia system: deep navy ground, Georgia peach accent, white
   reading islands. Hairline rules, interval mark. Functional red for errors
   only. Self-contained: system fonts, no external requests.
   ============================================================================= */

/* ---------------------------------------------------------------------------
   KIT TYPE — Ground Game Georgia Brand Guidelines v1.0 (Feb 2026).
   Body / H3 / labels are Source Sans Pro, which ships today as Source Sans 3
   (SIL OFL, the same family renamed upstream). H1 and H2 are specified as
   Tungsten, a licensed Hoefler face that cannot legally be self-hosted, so
   Oswald stands in for it: condensed, heavy, the same role in the layout.
   SWAP POINT: if the party licenses Tungsten, put it first in --display and
   nothing else changes.

   Both files are VARIABLE fonts covering their whole weight range, which is
   why four weights of Source Sans cost one 28KB file rather than four. They
   are vendored because the CSP is `font-src 'self'`, and they carry a version
   in the filename because /vendor/* is immutable-cached for a year: NEVER
   edit a vendor file in place, ship a new filename.
   --------------------------------------------------------------------------- */
@font-face {
  font-family: "Source Sans 3";
  src: url("vendor/sourcesans3-var.v1.woff2") format("woff2-variations");
  font-weight: 200 900; font-style: normal; font-display: swap;
}
/* ARROWS. The latin subset carries no U+2192, so every "Get my assignment →",
 * "Save & next question →" and "Open ↗" fell back to Arial: a hairline glyph
 * beside 700-weight text, on the most repeated element in the app. This 1.6KB
 * face covers only the arrow codepoints and is declared LAST for the same
 * family, so the browser uses it for exactly those characters and Source Sans
 * for everything else. */
@font-face {
  font-family: "Source Sans 3";
  src: url("vendor/sourcesans3-arrows.v1.woff2") format("woff2");
  unicode-range: U+2190-2193, U+2197, U+25B6;
  font-weight: 200 900; font-style: normal; font-display: swap;
}
@font-face {
  font-family: "Oswald";
  src: url("vendor/oswald-var.v1.woff2") format("woff2-variations");
  font-weight: 200 700; font-style: normal; font-display: swap;
}

:root {
  /* ======================================================================
     BRAND — Ground Game Georgia, Brand Guidelines v1.0 (Feb 2026).
     --brand-navy and --brand-peach are the kit's DEEP NAVY and GEORGIA
     PEACH exactly, and WHITE is #FFFFFF below. The kit supplies no second
     navy and no second peach, so -deep and -dk are ours, derived at a
     fixed offset from the two official values. Everything else on this
     page derives from these four: change one here, not in twenty places.
     ====================================================================== */
  --brand-navy:      #0D212E;   /* KIT — DEEP NAVY      rgb(13, 33, 46)  */
  --brand-navy-deep: #07161E;   /* derived: navy -6,-11,-16             */
  --brand-peach:     #F59679;   /* KIT — GEORGIA PEACH  rgb(245,150,121) */
  --brand-peach-dk:  #E2764F;   /* derived: peach, darkened for edges   */

  /* ======================================================================
     TWO THEMES. LIGHT IS THE DEFAULT; dark is opt-in via data-theme="dark"
     on <html>. Both are built from the same two kit colours, so neither is a
     re-skin: the ground flips and the ink flips with it.

     What does NOT flip: the site header and the hero band stay navy in both.
     They carry the co-brand lockup in white, and a navy chrome over a white
     document is the stronger brand read anyway. Their inks are pinned further
     down so they cannot follow the theme and vanish.

     --island is the reading-island surface: the verified reference answer and
     every box a volunteer types into. In dark mode it is white against navy
     chrome. In light mode the ground is near-white, so the CARD carries a
     slight tint and the islands stay pure white — the same relationship,
     inverted, with no structural change.

     --field-edge is separate from --rule on purpose. --rule draws decorative
     hairlines, where WCAG asks nothing. --field-edge draws control boundaries,
     which must clear 3:1, and on a white page a hairline grey cannot.
     ====================================================================== */

  /* ---- surfaces ----
     The first light build put a navy header and a navy hero band on a white
     body. Measured, the step from chrome to content was 16.5:1 — two different
     worlds stacked, which is exactly how it read. Every zone is now within about
     1.1:1 of its neighbour: white header, warm peach wash for the hero, a soft
     grey desk, white cards. The brand still lands, it just lands warmly. */
  --canvas:    #F4F7F9;   /* the desk */
  --card:      #FFFFFF;   /* the paper */
  --sunken:    #EDF2F5;
  --island:    #FFFFFF;
  --wash:      #FDF3EF;   /* peach at ~6% — the hero band */
  --reference-bg: var(--wash);
  --chrome:    #FFFFFF;   /* header ground */
  --chrome-ink: var(--brand-navy);
  --rule:      #DCE3E8;   /* decorative hairlines */
  --field-edge: #7E8E9B;  /* control boundaries — 3.37:1 on white */

  /* ---- ink ----
     --cobalt-2 must NOT become peach: it carries link and hover TEXT, and peach
     on white is ~2:1. Peach is for fills, rules and marks only, in both themes. */
  --cobalt:    #0D212E;
  --cobalt-2:  #0D212E;
  --text:      #16232E;
  --muted:     #5A6B78;
  --muted-2:   #626E7A;   /* placeholders — 5.21:1 on white, 4.84:1 on --canvas.
                              Was #6B7885: fine on a white card at 4.52:1, but only
                              4.2:1 on the canvas, where the staff status line sits. */
  --grey:      #B9C4CC;

  /* ---- accent ---- */
  --accent:      var(--brand-peach);
  --accent-dk:   var(--brand-peach-dk);
  --accent-ink:  var(--brand-navy);   /* navy text ON peach = 7.47:1 */
  /* peach EDGES. Kit peach is 2.21:1 on white and the -dk shade is only 2.77:1
     on the warm wash, both under the 3:1 a boundary needs, so light mode uses a
     deeper peach still. Same hue family, just dark enough to be a boundary. */
  --edge-accent: #CC5A32;
  --accent-soft: rgba(245,150,121,.18);
  --cyan:      var(--accent);        /* legacy alias: rules, marks, outlines */
  --cyan-soft: var(--accent-soft);   /* legacy alias: tinted fills */

  --err:       #C0392B;   /* 5.44:1 on white */
  --err-line:  #C0392B;

  /* --display is the Tungsten role: H1, H2, the wordmark, the partner lockup.
     Nothing else. Labels, buttons and chips are Source Sans per the kit. */
  /* "Source Sans 3" sits in the display stack purely to supply arrow glyphs:
     Oswald has none, and without this a → inside an Oswald heading drops to Arial. */
  --display: "Oswald", "Source Sans 3", "Avenir Next Condensed", "Arial Narrow", system-ui, sans-serif;
  --sans:    "Source Sans 3", "Source Sans Pro", -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;

  --radius: 10px;
  --shadow: 0 1px 2px rgba(13,33,46,.06), 0 8px 24px rgba(13,33,46,.08);
}

* { box-sizing: border-box; }

body {
  overflow-wrap: break-word;
  margin: 0;
  font-family: var(--sans);
  font-size: 1.02rem;
  line-height: 1.6;
  color: var(--text);
  background: var(--canvas);
  text-rendering: optimizeLegibility;
  -webkit-font-smoothing: antialiased;
}

.wrap { max-width: 880px; margin: 0 auto; padding: 0 22px; }

/* ---------- header ---------- */
.site-header {
  position: sticky; top: 0; z-index: 50;
  background: rgba(7,22,30,.94);
  backdrop-filter: saturate(140%) blur(8px);
  border-bottom: 2px solid var(--accent);
}
.header-inner { display: flex; align-items: center; justify-content: space-between; gap: 16px; min-height: 72px; flex-wrap: wrap; padding-top: 10px; padding-bottom: 10px; }
.brand { display: inline-flex; align-items: center; gap: .68rem; color: var(--cobalt); cursor: pointer; }
.brand:hover .name { color: var(--cobalt-2); }
.brand:focus-visible { outline: 3px solid var(--cyan); outline-offset: 3px; border-radius: 6px; }
.brand .lockup { display: flex; flex-direction: column; line-height: 1; }
.brand .name { font-family: var(--display); font-weight: 800; font-size: 1.45rem; letter-spacing: -.005em; margin: 0; }
.brand .sub { font-family: var(--sans); font-weight: 500; font-size: .82rem; letter-spacing: .01em; margin: .22rem 0 0; color: var(--muted); white-space: nowrap; }
.header-chips { display: flex; gap: 8px; flex-wrap: wrap; }
.chip {
  font-family: var(--sans); font-size: .78rem; font-weight: 600; letter-spacing: .02em;
  color: var(--cobalt); background: var(--card); border: 1px solid var(--rule);
  padding: 5px 12px; border-radius: 999px; white-space: nowrap;
}
.chip-warn { background: var(--accent); color: var(--accent-ink); border-color: var(--accent); }
.session-progress { height: 3px; background: var(--rule); }
#session-progress-fill { height: 100%; width: 0%; background: var(--cyan); transition: width .3s ease; }

/* ---------- footer ---------- */
.site-footer { margin-top: 56px; padding: 24px 0 40px; border-top: 1px solid var(--rule); }
.site-footer p { color: var(--muted); font-size: .85rem; margin: 0; }
.footer-link { color: var(--cobalt); text-decoration: none; border-bottom: 2px solid var(--cyan); font-weight: 600; transition: border-color .15s ease; }
.footer-link:hover { border-bottom-color: var(--cobalt); }

/* ---------- layout blocks ---------- */
main { padding-top: 30px; }
.screen[hidden], [hidden] { display: none !important; }
@media (prefers-reduced-motion: no-preference) {
  .screen:not([hidden]) { animation: fadeUp .22s ease; }
  @keyframes fadeUp { from { opacity: 0; transform: translateY(5px); } to { opacity: 1; transform: none; } }
}
.card {
  background: var(--card); border: 1px solid var(--rule); border-radius: var(--radius);
  padding: 26px 28px; margin-bottom: 20px; box-shadow: var(--shadow);
}
.card h2 { font-family: var(--display); font-weight: 800; letter-spacing: -.005em; color: var(--cobalt); font-size: 1.55rem; line-height: 1.15; margin: 0 0 .6em; }
.center { text-align: center; }
.muted { color: var(--muted); }
.loading { text-align: center; color: var(--muted); padding: 64px 0; font-size: .95rem; letter-spacing: .04em; animation: pulse 1.2s ease-in-out infinite; }
@keyframes pulse { 0%,100% { opacity: .45; } 50% { opacity: 1; } }
.banner {
  background: var(--cyan-soft); border: 1px solid var(--cyan); color: var(--cobalt);
  border-radius: var(--radius); padding: 10px 16px; font-size: .9rem; margin-bottom: 18px;
}

/* ---------- badges / progress ---------- */
.progress-row { display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 14px; }
.badge {
  font-family: var(--sans); font-size: .82rem; font-weight: 600; letter-spacing: .01em;
  background: var(--card); color: var(--text); border: 1px solid var(--rule);
  padding: 5px 13px; border-radius: 999px;
}
.badge-cat { color: var(--cobalt); }
.badge-model { background: var(--cyan-soft); color: var(--cobalt); border-color: var(--cyan); }

/* ---------- steps / question ---------- */
.step-title { display: flex; align-items: center; gap: 11px; margin: 0 0 14px; font-family: var(--sans); font-weight: 700; letter-spacing: -.005em; color: var(--cobalt); font-size: 1.12rem; }
.step-num {
  font-family: var(--sans); font-weight: 700; font-size: .86rem;
  color: var(--accent-ink); background: var(--accent); border: 1.5px solid var(--accent);
  width: 27px; height: 27px; border-radius: 7px;
  display: inline-flex; align-items: center; justify-content: center; flex: none;
}
.question-text { font-size: 1.22rem; font-weight: 700; color: var(--cobalt); margin: 4px 0 12px; line-height: 1.4; }
.eyebrow { font-family: var(--sans); font-weight: 600; font-size: .88rem; letter-spacing: .02em; color: var(--muted); margin-bottom: 6px; }
.model-big { font-family: var(--display); font-size: 2.15rem; font-weight: 800; letter-spacing: -.005em; color: var(--cobalt); margin: 4px 0 10px; }
.assigned-why { font-size: .88rem; max-width: 560px; margin: 0 auto 20px; }
.tips { text-align: left; margin: 0 auto 24px; max-width: 580px; padding-left: 22px; }
.tips li { margin-bottom: 8px; }
.tips li::marker { color: var(--cyan); }

.reference { background: var(--accent-soft); border: 1.5px solid var(--cyan); border-radius: var(--radius); padding: 14px 18px; margin-top: 16px; }
.reference-label { font-family: var(--sans); font-weight: 700; font-size: .85rem; letter-spacing: .01em; color: var(--cobalt); margin: 0 0 7px; }
.reference-text { margin: 0; white-space: pre-wrap; overflow-wrap: anywhere; }
/* The reference answer is the thing a volunteer judges against — it should read
   as the authority on the screen, not as body copy. Weight only: no colour
   change (it sits in a tinted box already) and no size change (these answers run
   long, and scaling them up pushes the paste box below the fold). 13 Aug. */
#q-reference { font-weight: 600; }
/* Reference answers and volunteer sources contain long official URLs
   (e.g. sos.ga.gov/page/georgia-voter-identification-requirements). Without
   this they run past the card on a phone and the tail is unreadable —
   pre-wrap keeps the team's line breaks but will not break a long token. */
/* Secondary reference blocks (how to judge, follow-ups) — same shape as the
   reference box but quieter, so the required answer stays the loudest thing. */
.reference-note { background: var(--canvas); border-color: var(--rule); }
.reference-note .reference-label { color: var(--text); }
#q-reference-intro { margin-bottom: 8px; }
.disclose { margin-top: 12px; border: 1px solid var(--rule); border-radius: var(--radius); padding: 10px 14px; background: var(--card); }
.disclose > summary { cursor: pointer; font-family: var(--sans); font-weight: 600; font-size: .9rem; color: var(--cobalt); }
.disclose > summary:hover { text-decoration: underline; }
.disclose[open] > summary { margin-bottom: 8px; }
.field-label { font-family: var(--sans); font-weight: 600; margin: 0 0 8px; }

.hint { border-radius: 8px; padding: 10px 14px; font-size: .9rem; margin: 10px 0; }
.hint-amber { background: var(--canvas); border: 1px solid var(--rule); border-left: 3px solid var(--cobalt); color: var(--text); }

/* ---------- forms ---------- */
.field { margin: 18px 0; border: 0; padding: 0; }
fieldset.field { min-width: 0; }
label, legend { font-family: var(--sans); font-weight: 600; font-size: .92rem; color: var(--cobalt); }
legend { margin-bottom: 7px; padding: 0; }
.req { color: var(--err); }
input[type="text"], input[type="email"], input[type="tel"], input:not([type]), select, textarea {
  width: 100%; margin-top: 6px; padding: 11px 13px; font: inherit; color: var(--text);
  border: 1px solid var(--rule); border-radius: 8px; background: var(--card);
}
select { appearance: none; -webkit-appearance: none; background-image: linear-gradient(45deg, transparent 50%, var(--muted) 50%), linear-gradient(135deg, var(--muted) 50%, transparent 50%); background-position: calc(100% - 20px) 50%, calc(100% - 14px) 50%; background-size: 6px 6px; background-repeat: no-repeat; cursor: pointer; }
textarea { resize: vertical; }
/* FOCUS. This used to strip the UA ring from every control and replace it with
 * navy at 12% alpha — invisible on a navy card, measured at 1.0:1 on radios,
 * checkboxes and text inputs. A later `border-color` rule for text inputs also
 * out-specified `input:focus`, so those never changed at all. Keyboard users
 * could operate everything and never see where they were (WCAG 2.4.7).
 * Peach outline on every focusable thing, and no `outline: none` anywhere. */
/* Form controls use :focus, NOT :focus-visible, deliberately. :focus-visible does
 * not match a programmatic .focus(), and this app focuses fields itself — the
 * judgment form puts the volunteer on the first unanswered field, and assistive
 * tech moves focus the same way. Under :focus-visible that ring is invisible at
 * exactly the moment it matters most. A peach ring after a mouse click is a small
 * price. Buttons and links keep :focus-visible, where the mouse-click ring really
 * is just noise. */
input:focus, select:focus, textarea:focus {
  outline: 3px solid var(--accent);
  outline-offset: 2px;
  border-radius: 4px;
}
button:focus-visible, a:focus-visible, summary:focus-visible, [tabindex]:focus-visible {
  outline: 3px solid var(--accent);
  outline-offset: 2px;
  border-radius: 4px;
}
/* Text fields additionally brighten their own border, which the specificity bug
 * below used to swallow — hence :focus-visible here rather than :focus. */
input[type="text"]:focus, input[type="email"]:focus,
input[type="tel"]:focus, input:not([type]):focus,
select:focus, textarea:focus, .source-input:focus {
  border-color: var(--accent);
}
input::placeholder, textarea::placeholder { color: var(--muted-2); }
/* The transparent border is load-bearing: the checked state below sets only a
 * border-COLOR, and with no border declared here it painted nothing. The tint
 * alone composites to 1.25:1 over the card — invisible — on the verdict radios,
 * which are the single control this whole app exists to collect. */
.check { display: flex; align-items: flex-start; gap: 10px; font-family: var(--sans); font-weight: 400; font-size: .95rem; color: var(--text); padding: 7px 9px; border: 1px solid transparent; border-radius: 7px; cursor: pointer; }
.check input { margin-top: 3px; flex: none; accent-color: var(--cobalt); width: 17px; height: 17px; }
.check:has(input:checked) { border-color: var(--cyan); background: var(--cyan-soft); }
.check-standalone:has(input:checked) { border-color: var(--cyan); }
.check strong { color: var(--cobalt); }
.check-standalone { background: var(--canvas); border: 1px solid var(--rule); border-radius: 8px; padding: 11px 13px; margin-top: 6px; }
/* Dim the CONTROLS, not the group. `opacity` on the fieldset is group opacity, so
   a child cannot opt out of it however hard it tries — the unlock hint set
   `opacity: 1` and still rendered at 3.69:1, dimmer than the legend nobody tried
   to rescue. Dimming the parts leaves the one line that tells a stuck volunteer
   what to do at full contrast. */
fieldset:disabled { opacity: 1; }
fieldset:disabled legend, fieldset:disabled .check, fieldset:disabled #conditions-list { opacity: .5; }
fieldset:disabled .check { cursor: not-allowed; }

/* ---------- buttons ---------- */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: .5rem;
  font-family: var(--sans); font-size: .92rem; font-weight: 700; letter-spacing: .02em;
  padding: .72rem 1.3rem; border-radius: 8px; border: 1.5px solid transparent;
  text-decoration: none; cursor: pointer;
  transition: transform .12s ease, background .15s ease, border-color .15s ease, color .15s ease;
}
.btn:active { transform: translateY(1px); }
.btn-lg { padding: .85rem 1.6rem; font-size: 1rem; }
.btn-primary { background: var(--accent); color: var(--accent-ink); }
.btn-primary:hover { background: var(--accent-dk); }
.btn-secondary { background: transparent; color: var(--cobalt); border-color: var(--grey); }
.btn-secondary:hover { border-color: var(--cobalt); }
/* Underlined at rest, not only on hover.
 *
 * These are the escape hatches — skip, exclude, stop, switch model — and with
 * transparent borders, muted text and no underline they read as one caption
 * sentence rather than four controls. A volunteer who is stuck scans straight
 * past the very things built for them. Hover is not a discoverability
 * mechanism, and on touch there is no hover at all. */
.btn-ghost { background: transparent; color: var(--muted); border-color: transparent; padding-left: .6rem; padding-right: .6rem;
  text-decoration: underline; text-underline-offset: 3px; text-decoration-color: var(--cyan); text-decoration-thickness: 1px; }
.btn-ghost:hover { color: var(--cobalt); text-decoration-thickness: 2px; }
.btn:disabled { opacity: .55; cursor: wait; }
/* stretch, not center: btn-lg and a default btn sitting side by side measured
 * 51.2px and 46.0px, which reads as a misalignment rather than a hierarchy. */
.btn-row { display: flex; gap: 12px; align-items: stretch; flex-wrap: wrap; margin-top: 20px; }
.btn-row.center { justify-content: center; }
/* NOT margin-top. `.btn-row` is `align-items: stretch`, so a margin here pushes
   one button 12px down and stretches the other to compensate: measured 58.03px
   against 51.19px on the done screen while every other row was equal. Space the
   row, not the button. */
.card.center .btn-row { margin-top: 12px; }
.alt-actions { display: flex; gap: 4px 8px; align-items: center; flex-wrap: wrap; margin-top: 16px; padding-top: 13px; border-top: 1px solid var(--rule); font-size: .88rem; }
.alt-actions > .muted { font-size: .85rem; }
.alt-actions .btn-ghost { padding: 4px 8px; font-size: .85rem; }
.resume-note { font-size: .85rem; margin-top: 12px; }
.feedback-box { max-width: 560px; margin: 26px auto 0; text-align: left; border-top: 1px solid var(--rule); padding-top: 20px; }
.feedback-box .btn-row { margin-top: 12px; }
.btn-help { font-size: .82rem; }
.training-lede { font-size: 1.08rem; line-height: 1.65; }
.mission { border-left: 3px solid var(--cyan); padding-left: 14px; font-size: 1.06rem; font-weight: 500; color: var(--cobalt); }
.field-row { display: flex; gap: 14px; }
.field-row .field { flex: 1; }
@media (max-width: 560px) { .field-row { flex-direction: column; gap: 0; } }
.model-video-wrap { max-width: 620px; margin: 0 auto 18px; }
.model-video-wrap video { width: 100%; display: block; margin-top: 12px; border: 1px solid var(--rule); border-radius: 10px; box-shadow: var(--shadow); background: #000; }
.model-video-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 16px; margin-top: 12px; }
@media (max-width: 560px) { .model-video-grid { grid-template-columns: 1fr; } }
.model-video-grid figure { margin: 0; }
.model-video-grid figcaption { font-family: var(--sans); font-weight: 700; font-size: .9rem; color: var(--cobalt); margin-bottom: 6px; }
.model-video-grid video { width: 100%; display: block; border: 1px solid var(--rule); border-radius: 10px; background: #000; }
.video-wrap { margin: 18px 0 20px; }
.video-wrap video { width: 100%; display: block; border: 1px solid var(--rule); border-radius: 10px; box-shadow: var(--shadow); background: #000; }
.video-cap { font-size: .88rem; margin: 9px 0 0; }
.training-reassure { list-style: none; padding-left: 0; margin: 14px 0 4px; }
.training-reassure li { position: relative; padding-left: 28px; margin-bottom: 9px; font-size: 1.02rem; }
.training-reassure li::before { content: "✓"; position: absolute; left: 2px; color: var(--cobalt-2); font-weight: 800; }
.training-steps { padding-left: 22px; margin: 0; }
.training-steps li { margin-bottom: 20px; font-size: 1.02rem; line-height: 1.75; }
.training-steps li::marker { font-family: var(--sans); font-weight: 700; color: var(--cobalt); font-size: 1.05em; }
.training-faq { list-style: none; padding-left: 0; margin: 0 0 6px; }
.training-faq li { margin-bottom: 12px; font-size: 1rem; line-height: 1.6; }
.training-faq strong, .training-steps strong { color: var(--cobalt); }
.training-h3 { font-family: var(--sans); font-weight: 700; color: var(--cobalt); font-size: 1.18rem; margin: 0 0 12px; }
.example-response { margin-top: 14px; }
.example-judgment { margin: 12px 0 8px; }
.demo-btn {
  display: inline-block; font-family: var(--sans); font-weight: 700; font-size: .82rem;
  padding: .26rem .72rem; border-radius: 7px; border: 1.5px solid transparent;
  white-space: nowrap; vertical-align: baseline; cursor: default;
}
.demo-primary { background: var(--accent); color: var(--accent-ink); }
.demo-secondary { background: var(--card); color: var(--cobalt); border-color: var(--grey); }
kbd {
  font-family: var(--sans); font-size: .85em; color: var(--cobalt);
  background: var(--canvas); border: 1px solid var(--rule); border-bottom-width: 2px;
  border-radius: 5px; padding: 0 .4em; margin: 0 .06em;
}

/* ---------- feedback ---------- */
.error-text { color: var(--err); font-size: .92rem; font-weight: 600; }
#toast {
  position: fixed; left: 50%; bottom: 28px; transform: translateX(-50%);
  /* NOT var(--cobalt). That token was the navy heading ink in the light build and
   * became #FFFFFF when the app went dark, so `background: var(--cobalt); color: #fff`
   * has been rendering every toast as WHITE TEXT ON A WHITE PILL — including
   * "Saved ✓" after every single judgment, and "All queued responses delivered ✓",
   * the only signal that offline work finally went through. A legacy token name
   * that quietly inverted underneath its callers. */
  background: var(--card); color: var(--text); border: 1px solid var(--rule);
  padding: 10px 20px; border-radius: 999px;
  font-family: var(--sans); font-size: .88rem; font-weight: 600;
  box-shadow: 0 6px 20px rgba(13,33,46,.35); z-index: 60; white-space: nowrap;
}
/* Peach fill takes NAVY ink: white on peach measures 2.21:1. --accent-ink exists
 * for exactly this pairing and is 7.47:1. */
#toast.toast-warn { background: var(--accent); color: var(--accent-ink); border-color: var(--accent); }

/* Fingers need ~44px on any touch device, not only on a narrow portrait phone.
 * These were scoped to max-width:560px, so phone LANDSCAPE (667x375, 844x390) and
 * every tablet dropped back to 38px radios and 29px destructive buttons. Hover:none
 * catches touch regardless of width. */
@media (hover: none) and (pointer: coarse) {
  .check { padding-top: 11px; padding-bottom: 11px; }
  .check input { width: 20px; height: 20px; margin-top: 2px; }
  .alt-actions .btn-ghost { min-height: 44px; padding: 11px 10px; font-size: .9rem; }
  .site-footer a { display: inline-block; padding: 5px 0; }
}

@media (max-width: 560px) {
  .card { padding: 20px 17px; }
  .question-text { font-size: 1.1rem; }
  .btn-row .btn { width: 100%; }
  .brand .sub { white-space: normal; }
  #toast { white-space: normal; width: calc(100% - 48px); text-align: center; border-radius: 12px; }
  /* Fingers need ~44px. The judgment radios/checkboxes were 38px and the
     skip / switch / don't-ask-again buttons only 30px — the most-tapped and
     least-recoverable controls in the app. */
  .check { padding-top: 11px; padding-bottom: 11px; }
  .check input { width: 20px; height: 20px; margin-top: 2px; }
  .alt-actions .btn-ghost { min-height: 44px; padding: 11px 10px; font-size: .9rem; }
  .site-footer a { display: inline-block; padding: 5px 0; }
}
/* Landscape phone, or portrait with the keyboard open: the viewport is very
   short, so give the sticky header less of it while someone is typing. */
@media (max-height: 450px) {
  .header-inner { min-height: 52px; padding-top: 6px; padding-bottom: 6px; }
  main { padding-top: 16px; }
}

/* --- mid-session model switch --- */
.switch-confirm {
  margin-top: 14px;
  padding: 16px 18px;
  background: var(--cyan-soft);
  border: 1px solid var(--cyan);
  border-radius: 10px;
}
.switch-confirm p { margin: 0 0 12px; }

/* --- 1–5 experience rating --- */
.rate-label {
  margin: 22px 0 8px;
  font-weight: 600;
  color: var(--cobalt);
}
.rate-row { display: flex; gap: 10px; }
.rate-btn {
  width: 52px;
  height: 52px;
  font-size: 20px;
  font-weight: 600;
  font-family: inherit;
  color: var(--cobalt);
  background: var(--card);
  border: 1.5px solid var(--rule);
  border-radius: 10px;
  cursor: pointer;
  transition: border-color .12s, background .12s, color .12s;
}
.rate-btn:hover { border-color: var(--cobalt-2); }
.rate-btn.selected {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--accent-ink);
}
.rate-scale {
  display: flex;
  justify-content: space-between;
  /* Was a hard 300px, which pushed the whole page sideways at 320px and clipped
     "5 = great" off-screen. The scale should never be wider than its column. */
  width: 100%;
  max-width: 300px;
  margin: 6px 0 0;
  font-size: 13px;
  color: var(--muted);
}
.rate-followup {
  margin-top: 16px;
  padding: 16px 18px;
  background: var(--cyan-soft);
  border: 1px solid var(--cyan);
  border-radius: 10px;
  text-align: left;
}
.rate-followup p { margin: 0 0 10px; }
.rate-followup textarea { width: 100%; }
.contact-ask { margin-top: 12px; }
.contact-choices { display: flex; flex-wrap: wrap; gap: 10px; }

/* A 1–2 rating means a volunteer had a bad time. A name and a face reads as a
 * person owning it; a generic "we're sorry" reads as a form. */
.isabelle-note {
  display: flex;
  align-items: center;
  gap: 14px;
  margin: 0 0 14px;
  padding: 12px 14px;
  background: var(--card);
  border: 1px solid var(--cyan);
  border-radius: 10px;
}
.isabelle-photo {
  flex: 0 0 auto;
  width: 60px;
  height: 60px;
  border-radius: 50%;
  object-fit: cover;
  border: 2px solid var(--cyan);
}
/* Shown only if the photo is missing, so the card never renders a broken image. */
/* Same inverted-token trap as #toast: --cobalt is white in the dark build, so
 * these initials were white on a white circle. */
.isabelle-fallback {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  /* Peach fill takes navy ink. Swapping the background off white fixed half of
     this; white-on-peach is 2.21:1, and --accent-ink exists for the pairing. */
  background: var(--accent);
  color: var(--accent-ink) !important;
  
  font-family: var(--sans);
  font-weight: 700;
  font-size: 1.15rem;
  letter-spacing: .02em;
}
.isabelle-copy { margin: 0 !important; line-height: 1.45; }
@media (prefers-reduced-motion: no-preference) {
  #rate-neg:not([hidden]) .isabelle-note { animation: popIn .26s cubic-bezier(.2, .8, .3, 1.1); }
  @keyframes popIn { from { opacity: 0; transform: scale(.94) translateY(4px); } to { opacity: 1; transform: none; } }
}

.field-hint {
  margin: 6px 0 0;
  font-size: 13px;
  line-height: 1.45;
  color: var(--muted);
}

/* Sign-up privacy note. Collapsed by default so it doesn't push the form's
   primary action below the fold on a phone, but present before anyone submits
   personal details rather than buried in a policy page nobody opens. */
/* Currently unused: the sign-up privacy note was removed 10 Aug 2026 pending a
   real privacy policy. Kept because that policy will want the same styling. */
.privacy-note {
  margin: 4px 0 14px;
  border: 1px solid var(--rule);
  border-radius: 10px;
  background: var(--sunken);
  padding: 10px 14px;
  font-size: 13.5px;
  color: var(--muted);
}
.privacy-note summary {
  cursor: pointer;
  font-weight: 600;
  color: var(--cobalt-2);
  min-height: 24px;
}
.privacy-note p { margin: 8px 0 0; line-height: 1.55; }

/* Sources: one link per box, added on demand (13 Aug). Inputs stay at the
   app's normal 17px — anything under 16px makes iOS zoom the whole page on
   focus, which is the top "feels broken on my phone" complaint. */
#sources-list { display: flex; flex-direction: column; gap: 8px; }
#sources-list .source-input { width: 100%; }
#btn-add-source {
  margin-top: 8px;
  padding: 8px 14px;
  font-size: 15px;
  min-height: 44px;   /* tap target — matches the other phone-safe controls */
}

/* Invalid form fields. Paired with aria-invalid in markInvalid() so the cue is
   not colour-only — a red border alone fails anyone who cannot see it, and this
   form is the first thing every volunteer meets. */
.is-invalid,
input.is-invalid:focus {
  border-color: var(--err-line) !important;
  box-shadow: 0 0 0 3px rgba(192, 57, 43, .14);
}
/* A native radio computes border-width: 0, so .is-invalid's border-color painted
 * nothing and only its 14%-alpha glow remained — 1.08:1 over the navy card, the
 * same invisible-alpha-on-navy mistake the focus rules above call out. Radios and
 * checkboxes get a real outline instead, which does not depend on a border. */
input[type="radio"].is-invalid, input[type="checkbox"].is-invalid {
  /* --err (light pink) not --err-line (dark red): the dark one measured 2.61:1
     against the card, under the 3:1 non-text minimum, on precisely the controls
     this app exists to collect. --err clears it at 6.79:1.
     DASHED, and offset further than the focus ring, because markBadFields_ FOCUSES
     the first bad field — so on every failed save an invalid ring and a focus ring
     appear together, and solid peach against solid pink measured 1.05:1 apart:
     the same mark twice. Shape, not just colour, distinguishes them, which is also
     the rule that colour must never be the only signal. */
  outline: 3px dashed var(--err);
  outline-offset: 4px;
}

/* Collapsed field explanations.
   These two hints were ~250 characters each and sat between the question and
   the paste box — the single reason those two things felt far apart. The
   checkboxes stay ABOVE the box on purpose (they ask about something done
   before pasting, and below it they were being forgotten); only the prose
   folds away. */
details.hint-collapse { margin-top: 4px; }
details.hint-collapse > summary {
  cursor: pointer; list-style: none; display: inline-block;
  text-decoration: underline; text-underline-offset: 3px;
  text-decoration-color: var(--cyan); text-decoration-thickness: 1px;
}
details.hint-collapse > summary::-webkit-details-marker { display: none; }
details.hint-collapse > summary::after { content: " ▸"; text-decoration: none; }
details.hint-collapse[open] > summary::after { content: " ▾"; }
details.hint-collapse > p { margin: 6px 0 0; }

/* ---------- Option C: navy hero band ----------
   Full-bleed without 100vw (which overflows whenever a vertical scrollbar is
   present): box-shadow paints the colour out past the element and clip-path
   keeps it from creating scroll. inset(0 -100vmax) clips top/bottom to the
   element's own box, so the colour never floods the page vertically.
   The peach rule is a SEPARATE bled element: a border-bottom on .hero-band
   sits inside the border box, so the navy shadow paints over and past it and
   the rule stops at the content width. */
.hero-band {
  background: var(--wash);
  box-shadow: 0 0 0 100vmax var(--wash)  /* MUST match the band's own
     background, which is navy-deep. Bleeding navy made the shadow the same colour
     as the page ground, so the bleed did nothing and the band read as a hard-edged
     dark rectangle floating at the content width. */;
  clip-path: inset(0 -100vmax);
  margin: -30px 0 0;
  padding: 40px 0 34px;
}
.hero-rule {
  height: 4px;
  background: var(--accent);
  box-shadow: 0 0 0 100vmax var(--accent);
  clip-path: inset(0 -100vmax);
  margin: 0 0 26px;
}
/* A visible banner (test mode, offline queue, backend warning) sits directly
   above the hero. Without this the -30px pull-up slides the navy over it and
   eats the message. */
#banner:not([hidden]) ~ #screen-welcome .hero-band { margin-top: 8px; }
.hero-band h2 { color: var(--cobalt); font-family: var(--display); font-weight: 800; letter-spacing: -.01em;
  font-size: 1.9rem; line-height: 1.15; margin: 0 0 .55em; }
.hero-band .mission { color: var(--text); border-left-color: var(--accent); max-width: 60ch; }
@media (max-width: 560px) {
  .hero-band { margin-top: -30px; padding: 30px 0 26px; }
  .hero-rule { margin-bottom: 20px; }
  .hero-band h2 { font-size: 1.5rem; }
}

/* ---------- co-brand lockup (Ground Game Georgia + DPG) ----------
   Placeholder type until the kit arrives. Sized so the partner marks read as
   equal partners with Keep It Honest GA without out-shouting it, and so the whole
   group collapses gracefully on a phone rather than pushing the header two
   rows deep.
   The script word is the KIT peach, not the darker edge shade: it is a
   wordmark, not a rule or an edge, and the kit has exactly one peach. */
.header-lockup { display: flex; align-items: center; gap: 14px; min-width: 0; }
.cobrand { display: flex; align-items: center; gap: 13px; }
.cb-mark { font-family: var(--display); color: var(--cobalt); line-height: 1.02; }
.cb-gg { display: flex; flex-direction: column; }
/* The partner mark is the lead brand on this page, so it is sized to hold its
   own next to the app name rather than sit under it. */
.cb-gg b { font-weight: 700; font-size: 1.06rem; letter-spacing: .02em; }
/* The kit's script word is a fat brush script. Brush Script MT is the closest
   face that is actually present on Mac and on Windows-with-Office, and it
   falls back to the old Georgia italic everywhere else, so this can only be
   an improvement or a no-op. It sets small for its point size, hence the
   larger font-size and the tighter margin. Replace the whole span with the
   real supplied mark when the asset lands. */
.cb-gg i { font-style: italic; font-size: 1.32rem; color: var(--accent);
  font-family: "Brush Script MT", "Segoe Script", Georgia, "Times New Roman", serif;
  margin-top: -1px; line-height: 1.05; }
/* Both partner marks are WHITE. Setting DPG in --muted made one half of a
   co-brand visibly junior to the other, which is a claim about the
   relationship that neither partner agreed to. Size and letterspacing carry
   the hierarchy against Keep It Honest GA instead. */
/* 500, not 700. Oswald tops out at 700, so "GROUND GAME" (800) silently clamped
 * to the same weight as this and the two partner marks became indistinguishable.
 * Dropping this restores the hierarchy without touching the lead mark. */
.cb-dpg { font-size: .66rem; font-weight: 500; letter-spacing: .085em;
  text-transform: uppercase; color: var(--cobalt); max-width: 124px; }
/* Fixed height, not align-self:stretch — stretching gave the two dividers 30.0px
 * and 39.8px because the groups either side are different heights, which reads as
 * a mistake rather than a rhythm. */
.cb-div { width: 1px; height: 42px; align-self: center; background: var(--rule); flex: none; }
.brand { min-width: 0; }

@media (max-width: 720px) {
  /* Tighten before dropping anything: this app is going on DPG's own site, so
     their mark earns the extra 30px on a phone. */
  .cobrand { gap: 9px; }
  .cb-dpg { font-size: .53rem; letter-spacing: .06em; max-width: 78px; }
}
@media (max-width: 469.98px) {   /* 470 itself must still SHOW the DPG mark */
  /* Below this the row genuinely cannot hold three marks. DPG's is the longest
     and the last in, so it goes first. It stays in the footer, in the SMS
     consent line and on the thanks page either way. */
  .cb-dpg, .cobrand .cb-div { display: none; }
}
@media (max-width: 560px) {
  .header-lockup { gap: 10px; }
  .cb-gg b { font-size: .86rem; }
  .cb-gg i { font-size: 1.06rem; }
  .brand .name { font-size: 1.2rem; }
}


/* ============================ DARK THEME ============================
   Opt-in, set by the toggle as data-theme="dark" on <html>. These are the
   exact values the app shipped with on 1 Sep, so switching to dark returns
   the build that four rounds of QA were run against, unchanged. */
:root[data-theme="dark"] {
  --canvas:    var(--brand-navy);
  --card:      #172D3C;
  --sunken:    #0F232D;
  --island:    #FFFFFF;    /* the reading islands stay white on navy chrome */
  --wash:      var(--brand-navy-deep);   /* the hero band's navy, unchanged */
  --reference-bg: var(--island);   /* stays a white document on navy */
  --chrome:    rgba(7,22,30,.94);
  --chrome-ink: #FFFFFF;
  --rule:      #274154;
  --field-edge: #274154;   /* on a white island a hairline already clears 3:1 */

  --cobalt:    #FFFFFF;
  --cobalt-2:  var(--brand-peach);
  --text:      #E8EFF4;
  --muted:     #94A9B8;
  --muted-2:   #849AAA;
  --grey:      #3B5568;

  --edge-accent: var(--brand-peach);   /* peach reads at 6.45:1 on the dark card */
  --accent-soft: rgba(245,150,121,.13);

  --err:       #F49AA8;
  --err-line:  #C0392B;

  --shadow: 0 1px 2px rgba(0,0,0,.30), 0 10px 30px rgba(0,0,0,.38);
}

/* ---- chrome FOLLOWS the theme ----
   The first attempt pinned the header navy in both modes so the white co-brand
   lockup would survive. That is what produced the 16.5:1 seam. The lockup ink is
   a token now, so the marks flip with the page — which the brand sheet supports:
   it shows the logo dark-on-light as well as white-on-navy. */
.site-header { background: var(--chrome); }
.site-header .brand .name,
.site-header .cb-mark,
.site-header .cb-dpg { color: var(--chrome-ink); }
.site-header .brand .sub { color: var(--muted); }
.site-header .cb-div { background: var(--rule); }
.site-header .chip { color: var(--chrome-ink); background: var(--sunken); border-color: var(--rule); }
.site-header .chip-warn { background: var(--accent); color: var(--accent-ink); border-color: var(--accent); }
.site-header .btn-ghost { color: var(--chrome-ink); border-color: var(--field-edge); }
:root[data-theme="dark"] .site-header .chip { background: rgba(255,255,255,.10); border-color: #274154; }
:root[data-theme="dark"] .site-header .btn-ghost { border-color: #3B5568; }
:root[data-theme="dark"] .site-header .btn-ghost:hover { background: rgba(255,255,255,.08); }

/* ================= Option F: light reading islands =================
   The app runs dark, with two deliberate exceptions: the verified reference
   answer, and every box a volunteer types into. Those are the surfaces where
   long text is actually read and written, so they stay a white document.
   Everything else — panels, chrome, tables — is navy. */

/* Navy in BOTH themes — on a white page it becomes the one strong brand block,
   which is a better read than a white band on a white page. The full-bleed
   above is safe on a light page too: clip-path inset(0 -100vmax) clips the
   shadow to the band's own height, so it only ever extends sideways. */
.hero-band { background: var(--wash); }

/* ISLAND 1 — the reference answer. The one white block in the card, which is
   what makes it read as the authority a volunteer judges against. */
/* 2px, and the DARKER peach: kit peach on a white page is 2.21:1, under the
   3:1 a UI boundary needs. On the dark card the same token resolves to kit
   peach at 6.45:1. One rule, correct in both themes. */
/* Its own token, NOT --wash. In light mode the warm wash makes this the one
   tinted block on a white card. In dark mode the reading island must stay WHITE
   against navy chrome — --wash resolves to navy there, which would turn the
   verified reference answer into just another navy panel. */
.reference { background: var(--reference-bg); border: 2px solid var(--edge-accent); }
/* Navy, not peach. This is "What the answer must include", the label on the
 * verified reference every judgment is made against, and --accent-dk on white
 * measured 3.03:1 at 13.6px bold — under the 4.5:1 it needs. The palette comment
 * at the top of this file warns about exactly this: peach is for fills, rules and
 * marks, never for text on white. The peach border already carries the emphasis. */
.reference .reference-label { color: var(--brand-navy); }
.reference .reference-text { color: var(--brand-navy); }

/* The secondary blocks (how to judge this one, follow-ups, also-correct) stay
   DARK on purpose. If they were white too, the required answer would stop
   being the loudest thing in the card. Declared after .reference so they win. */
.reference-note { background: var(--sunken); border-color: var(--rule); }
.reference-note .reference-label { color: var(--muted); }
.reference-note .reference-text { color: var(--text); }
.disclose { background: var(--sunken); }

/* ISLAND 2 — everything a volunteer types into. */
/* background-COLOR, not the `background` shorthand: the shorthand resets
   background-image/position/size, and the select's arrow is drawn with two
   gradients. Using it here blew the arrow up to fill the whole control. */
input[type="text"], input[type="email"], input[type="tel"], input:not([type]),
select, textarea, .source-input {
  background-color: var(--island); color: var(--brand-navy); border-color: var(--field-edge);
}
input::placeholder, textarea::placeholder { color: #6B7885; }
/* the arrow was tuned for a light page; on a white control it needs the dark
   ink. Position/size/repeat are restated so they survive any shorthand above. */
select {
  background-image:
    linear-gradient(45deg, transparent 50%, var(--brand-navy) 50%),
    linear-gradient(135deg, var(--brand-navy) 50%, transparent 50%);
  background-position: calc(100% - 20px) 50%, calc(100% - 14px) 50%;
  background-size: 6px 6px;
  background-repeat: no-repeat;
}
.rate-btn { background: #FFFFFF; color: var(--brand-navy); }
.rate-btn.selected { background: var(--accent); border-color: var(--accent); color: var(--accent-ink); }

/* ---------- SMS consent note ----------
   Deliberately quiet but not hidden: it is a legal record, so it must be
   legible and adjacent to the control it governs, not buried in a disclosure. */
.consent-note {
  margin: 18px 0 0;
  font-size: .82rem;
  line-height: 1.5;
  color: var(--muted);
  border-left: 2px solid var(--rule);
  padding-left: 12px;
  max-width: 62ch;
}
.consent-note a { color: var(--cobalt-2); }


/* Why a fieldset is greyed out. Shown only while it is actually disabled — a
   standing explanation on an enabled control is just noise. */
/* Held OUT of the disabled group's 45% dim. This hint is the instruction that
   tells a stuck volunteer how to unlock the control, so it is the one thing in a
   disabled fieldset that must stay readable — it measured 2.33:1 inheriting the
   dim. */
.locked-hint { font-size: .85rem; margin: 0 0 10px; display: none; color: var(--text); }
fieldset:disabled .locked-hint { display: block; }
.training-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap; }
.btn-sm { padding: 6px 12px; font-size: .82rem; }


/* Touch targets on the pages that never got the touch rules: thanks and the three
   organizer gates carry no .check/.alt-actions markup, so their links stayed at
   18-22px. */
@media (hover: none) and (pointer: coarse) {
  details.field-hint > summary { padding: 13px 0; }   /* 18.84px line box + 26 = 44.84 */
  .site-footer a, .footer-link { display: inline-block; padding: 11px 0; }
}


/* ---------- theme toggle ----------
   Shows the theme it will SWITCH TO, not the one you are in: a control that
   reads "Light" while the page is already light is the classic version of this
   that everyone misreads. Sits in the header, which is navy in both themes, so
   its ink is pinned with the rest of the chrome above. */
.btn-theme { display: inline-flex; align-items: center; gap: 7px; }
.btn-theme .icon-sun { display: none; }
:root[data-theme="dark"] .btn-theme .icon-moon { display: none; }
:root[data-theme="dark"] .btn-theme .icon-sun { display: inline; }
@media (max-width: 560px) { .btn-theme span { display: none; } .btn-theme { gap: 0; } }


/* Privacy disclosure in the footer. Narrower than the footer's full width so a
   long paragraph does not run the whole page, and left-aligned with the rest of
   the footer text rather than centred on it. */
.footer-privacy { margin: 14px 0 10px; max-width: 62ch; text-align: left; }
.footer-privacy summary { font-weight: 600; }
