@charset "utf-8";
/* ============================================================================
   Circle Lite Web — app.css
   The complete design system, ported from reference/spec-design.md and the ten
   .dc.html artboards. Owner: css. Nothing else in the repo owns any styling.

   HOW TO USE THIS FILE
   --------------------
   Screen modules build DOM with dom.js `h()` and attach the class names listed
   in the CLASS INVENTORY below. Semantic HTML is styled out of the box, so
   `h('h1', …)` / `h('p', …)` / `h('button', …)` already look right with no
   class at all. Add a class only to move off the default.

   FIVE RULES THIS FILE ENFORCES STRUCTURALLY — do not undo them
   ------------------------------------------------------------
   1. TINTED PANELS NEVER CARRY A SHADOW. `--surface` CARDS ALWAYS DO.
      True in all ten comps with no exception. `.panel` hard-sets
      `box-shadow: none`; `.card` hard-sets `var(--shadow-card)`.
      Elevation means "this is a thing". Tint means "this is a note about a thing".
   2. ONLY FONT-WEIGHTS 400 AND 600 EXIST. All 111 weight declarations across the
      comps are 600. There is no 500 and no 700 in this file. Do not add one.
   3. NO ALARM RED. `#B94A2E` is not in this file and must not enter it. The
      ONE warm-red is --clay (#BE6F62, a dusty terracotta), and it has one
      job: the disc of iconDidntVote — on the voting record (Haashim,
      2026-09-14) and, since 2026-09-17, in the search list's status column,
      which is the SAME mark for the SAME outcome in a second place, not a
      second job. It is never a chip, a panel, text or an error. For
      warnings, the strongest colour is still `--amber-16`, and it means
      "stop and don't conclude", not "something broke".
   4. THE GREY RING MEANS KNOWN-NEGATIVE. THE TAN ALERT + BANG MEANS UNKNOWN.
      `.icon--rest` (ring) is only ever "we know, and the answer is no".
      `.icon--unknown` (alert) is "we do not know, and we are not going to guess".
      `.icon--yes` (check) is only ever a confirmed positive.
      Using the ring for an unknown is exactly how the nine shipped defects looked.
      THE WHITE ✕ IN A CLAY DISC (icons.js `iconDidntVote`, 2026-09-13, clay
      since 2026-09-14) means one thing: history outcome `noBallotRecorded`, or
      "Didn't vote". A --clay disc with white strokes. Never for registered-later (tan), unknown
      (alert or nothing), not-yet-held (nothing), or a null status.
   5. LIGHT ONLY. `color-scheme: light` on :root is load-bearing — without it
      Android Chrome's "Auto dark theme" inverts this warm-paper palette into
      muddy grey-green during the demo. No dark palette exists to port. Do not
      add a `prefers-color-scheme: dark` block.

   CONTRAST — every text/background pair in this file, measured
   -----------------------------------------------------------
   WCAG AA for normal text is 4.5:1. Nothing here is large text under the AA
   definition (>=24px regular / >=18.66px semibold) except the Literata numerals,
   and those clear 4.5:1 anyway. Measured with the WCAG 2.x relative-luminance
   formula, tints composited over their real backgrounds:

     --ink      #152A1D  on ground 13.75  surface 14.48  stone-22 12.62
                          amber-16 12.90  leaf-14 11.88  tan-34    9.84
     --muted    #616160  on ground  5.61  surface  5.90  leaf-14   4.84
                          stone-22   5.15  stone-36 4.87  stone-50  4.60
                          amber-16   5.26  tan-14   4.83
     --green-deep #505B2D on ground  6.60  surface  6.95  leaf-14   5.98
                          leaf-15    5.64  leaf-16  5.58  stone-36  5.94
     --roots-deep #6A5238 on ground  6.60  surface  6.95  amber-16  6.48
                          tan-14     5.69  tan-34   4.72  stone-36  5.93
     --on-green #ffffff  on --green #66753A            5.04
                          on --green-hover #5C6A34      5.89

   Deliberately NOT used as text anywhere in this file, and why:
     --green  #66753A on a tint = 3.85–4.48  -> fails AA. Fills and >=20px
              Literata numerals only; `--green-deep` carries every green word.
     --roots  #9D7A53 on ground = 3.55       -> fails AA. Marks and fills only.
              spec-behaviour §6.1 names the same failure at 3.7:1 for the 12px
              status headline and specifies rootsDeep instead.
     #7A5F3C  (the comps' one-use "Needs fixing" chip ink) on --tan-34 = 3.85
              -> fails AA. `--roots-deep` replaces it at 4.72.
     --stone  #C9C9BC on ground = 1.51       -> not text, ever. Dividers, dashed
              rules, the candidate dot, large decorative glyphs. For a mark that
              carries meaning on its own use `--stone-mark` (3.19 on ground,
              clearing the 3:1 non-text-contrast bar).

   CLASS INVENTORY
   ---------------
   layout     .screen .screen--tabs .screen--plain .screen--doc
              .col .row-h .meta-row .divider .divider--tint .spacer-top
              .g-1 .g-2 .g-3 .g-4 .g-5 .g-6 .g-8 .g-10 .g-12 .g-14 .g-16 .g-18 .g-22
   type       .t-title .t-title-xl .t-detail .t-sheet-title .t-card-title
              .t-section .t-row .t-body .t-body-lg .t-emph .t-meta .t-meta-em
              .t-sub .t-input .eyebrow .eyebrow--tan .eyebrow--green .eyebrow--sm
              .num-hero .num-summary .num-compact .stat-value .stat-lift
              .link .scope-note .center .sr-only
   surfaces   .card .card--roomy .card--avatar .card--hero .card--row
              .card--inline .card-tappable .inset
              .panel (= .tint) .panel--green .panel--neutral .panel--amber
              .panel--tan .panel--roomy .panel--note .qr-plate
   controls   .btn-primary .btn-primary--icon .btn-primary--compact .btn-text
              .btn-pill .btn-pill--quiet .field .searchfield
              .searchfield__input .searchfield__echo .searchfield__clear .backrow .iconbtn .tap-44
              .faq-toprow .sharebtn .sharebtn__label .faq-share-status
              .brand__end .brand__status .titlerow
   path       .path .path__node .path__icon .path__card .path__heading
              .path__head .path__title .path__chev .path__body .is-open
   parts      .tabbar .tab .tab__label .chip .chip--yes/--registered
              .chip--in/--voted .chip--no/--none .chip--fix/--needsfix
              .chip--attention/--unknown .chip--mark .chips
              .avatar .avatar--48 .avatar--40 .avatar--36 .avatar--34
              .avatar--quiet .row .row__body .row__name .row__where
              .status-row .status-row--top .status-row__text .status-line
              .status-sub .unknown-card .results .bullets .bullet .bullet__text
              .accordion .acc-card .acc-head .acc-titles .acc-kicker .acc-body
              .stat-block .stat-row .empty .empty--inline .empty--roomy
              .empty__glyph .empty__headline .empty__body .skeleton .skel-row
              .sheet-scrim .sheet .qr-block .qr-link .qr-caption .doc
              .qr-share .qr-share__button .qr-share__status
              .bar .bar__voted .bar__counted .bar__unknown .pips .pip
   icons      .icon + one size (.icon--17/--18/--18-chip/--status/--20/--22/
              --34/--40/--42) + one colour (.icon--yes/--rest/--unknown/
              --muted/--quiet/--green/--leaf/--onbrand) .dot .dot--candidate

   FONT FILES
   ----------
   Self-hosted under ./fonts/ — never Google Fonts. A webfont request would leak
   who is being looked up to a third party, and venue wifi on demo day is not to
   be trusted. If the downloaded files are named differently from the candidates
   in the @font-face `src:` lists below, edit ONLY those two lists — the browser
   walks a src list in order and uses the first URL that loads, so extra
   candidates cost one 404 each and nothing else.
   ============================================================================ */


/* ---------------------------------------------------------------------------
   1. FONTS — self-hosted under ./fonts/, woff2, swap, real fallbacks
   One family since the 2026-09-26 unified look: Libre Franklin, for Circle Lite
   and the website guide alike. Its @font-face is in section 35 with the rest of
   that look. Literata, Atkinson Hyperlegible Next and Bricolage Grotesque were
   removed that day; their files and provenance are in git history.
--------------------------------------------------------------------------- */


/* ---------------------------------------------------------------------------
   2. TOKENS
--------------------------------------------------------------------------- */
:root {
  /* Load-bearing on Android: opts this page out of Chrome's force-dark.
     Pair it with <meta name="color-scheme" content="light"> in index.html. */
  color-scheme: light;

  /* -- solid colours, with the role each one actually plays in the comps ---- */
  --ground:      #F7F3EC;   /* the page. every artboard's root. warm paper.     */
  --surface:     #FCF9F4;   /* cards, input fills, the sheet. one step lighter. */
  --hairline:    #F0ECE3;   /* 1px input borders; dividers INSIDE a card.       */
  --ink:         #152A1D;   /* primary text. dark green-black, not neutral.     */
  --muted:       #616160;   /* secondary text, sub-labels, captions, inactive.  */
  --green:       #66753A;   /* PRIMARY FILL + big Literata numerals. see below. */
  --leaf:        #798844;   /* check-mark stroke, bullet dot, focus ring.       */
  --roots:       #9D7A53;   /* the ALERT mark. tan fills. NEVER text (3.55:1).  */
  --stone:       #C9C9BC;   /* "we assert nothing": dividers, dots, big glyphs. */
  --on-green:    #ffffff;   /* text/icon on --green. only ever on --green.      */
  --clay:        #BE6F62;   /* ONLY the "Didn't vote" disc (rule 3). Not text.  */
  /* The disc colours live in icons.js, not here: both are baked into the glyph
     (iconDidntVote fills --clay, iconVoted fills --green) so a mark cannot lose
     its meaning to an inherited colour. Listed for the record, not for use. */

  /* -- accessible text derivatives ---------------------------------------- */
  /* --green is 3.85–4.48:1 on every tint in the system, so it cannot carry a
     word there. --green-deep is --green taken to 78% and clears 4.5:1 on
     ground, surface and every tint (worst case leaf-16 at 5.58). Use it for
     ALL green text: chips, avatar initials, the active tab, green eyebrows,
     links. --green stays the fill and the >=20px Literata numeral. */
  --green-deep:  #505B2D;
  /* Primary-button hover. The comps hover the button to --leaf, but white on
     --leaf is 3.88:1 — below AA. Darkening instead keeps the affordance and
     puts white at 5.89:1. */
  --green-hover: #5C6A34;
  /* The brown that carries words. Grove ships rootsDeep #7A5B39 for the ballot
     status headline (spec-behaviour §6.1) and it clears everything except
     --tan-34 at 4.02:1; this is that brown one step deeper so ONE token is safe
     on every background in the system, including the "Needs fixing" chip. Two
     near-identical browns read as a mistake, so there is only this one. */
  --roots-deep:  #6A5238;
  /* A mark that carries meaning on its own needs 3:1 (WCAG 1.4.11). --stone is
     1.51:1 on ground — invisible as a signal. This is --stone at 68%, 3.19:1.
     Status marks only; --stone keeps every decorative and divider job. */
  --stone-mark:  #898980;

  /* -- amber exists only as a tint. never a solid, in any comp. ------------ */
  --amber:       #EFC463;

  /* -- tints (RGB bases in the comments) ---------------------------------- */
  --leaf-14:     rgba(121,136,68,0.14);   /* green tint panel; positive disc    */
  --leaf-15:     rgba(121,136,68,0.15);   /* ONLY the active tab pill           */
  --leaf-16:     rgba(121,136,68,0.16);   /* "Voted" / "Ballot in" chip         */
  --stone-22:    rgba(201,201,188,0.22);  /* quiet explainer panel, no claim    */
  --stone-36:    rgba(201,201,188,0.36);  /* neutral chip; not-yet-voted disc   */
  --stone-50:    rgba(201,201,188,0.50);  /* progress track; unfilled pip       */
  --amber-16:    rgba(239,196,99,0.16);   /* "stop and don't conclude" panel    */
  --tan-14:      rgba(157,122,83,0.14);   /* a boundary set by the person       */
  --tan-34:      rgba(157,122,83,0.34);   /* the "Needs fixing" chip            */
  --ink-06:      rgba(21,42,29,0.06);     /* the tab bar's 0.5px edge           */
  --ink-08:      rgba(21,42,29,0.08);     /* 1px divider ON A TINT              */
  --scrim:       rgba(21,42,29,0.42);     /* full-screen scrim behind a sheet   */
  --surface-92:  rgba(252,249,244,0.92);  /* the tab bar's own background       */
  --surface-60:  rgba(252,249,244,0.60);  /* the quiet pill button              */

  /* -- elevation. two shadows, and only two. ------------------------------ */
  --shadow-card: 0 2px 10px rgba(0,0,0,0.05);   /* every --surface card        */
  --shadow-bar:  0 6px 18px rgba(0,0,0,0.14);   /* the floating tab bar ONLY   */
  --shadow-card-hover: 0 3px 14px rgba(0,0,0,0.07);

  /* -- radii -------------------------------------------------------------- */
  --r-pill:  999px;   /* tab bar, chips, discs, pills                          */
  --r-card:  22px;    /* THE card radius. cards, tint panels, primary buttons  */
  --r-tile:  18px;    /* picker tiles, secondary blocks                        */
  --r-field: 16px;    /* text inputs, small inline blocks                      */
  --r-sheet: 28px;    /* bottom sheet, top corners only                        */

  /* -- stroke ladder for icons (spec-design §1.6) -------------------------- */
  --sw-sm:   2;       /* <=18px: tab bar, field icons, chevrons               */
  --sw-chip: 1.8;     /* 18px in a chip context (the calendar)                */
  --sw-tile: 1.9;     /* 20px decorative (picker tiles)                       */
  --sw-status: 2.2;   /* 20px STATUS marks: check, ring, calendar, alert      */
  --sw-lg:   1.6;     /* 34px inline glyph                                    */
  --sw-xl:   1.5;     /* >=40px full-screen glyph                             */

  /* -- gap ladder, named by meaning (spec-design §1.3) --------------------- */
  --gap-tight:   2px;   /* two lines that read as one thing                   */
  --gap-hair:    3px;   /* icon -> label; name -> note                        */
  --gap-xs:      4px;   /* detail name -> location                           */
  --gap-sm:      6px;   /* title -> subtitle; candidate rows                 */
  --gap-md:      8px;   /* within-panel paragraphs; chip rows                */
  --gap-lg:     10px;   /* THE default gap between siblings in a stack       */
  --gap-xl:     12px;   /* card-to-card; avatar -> text                      */
  --gap-2xl:    14px;   /* between status rows inside a card                 */
  --gap-section:16px;   /* between top-level sections of a screen            */

  /* -- type scale as named roles ------------------------------------------ */
  --fs-title-xl: 30px;  /* day-after title (out of scope, kept for parity)    */
  --fs-title:    26px;  /* screen title                                       */
  --fs-detail:   24px;  /* person name on a detail screen                     */
  --fs-sheet:    22px;  /* sheet title                                        */
  --fs-card:     20px;  /* card title / full-screen empty headline            */
  --fs-section:  18px;  /* section title, election heading                    */
  --fs-lead:     16px;  /* primary button label; "Nobody yet"                 */
  --fs-row:      15px;  /* row title, status line, input text, body-large     */
  --fs-emph:     14px;  /* body emphasis                                      */
  --fs-body:     13px;  /* body copy, list items, sub-headers                 */
  --fs-meta:     12px;  /* captions, eyebrows, panel body                     */
  --fs-sub:      11px;  /* sub-labels: city, county, freshness                */
  --fs-chip:     10px;  /* chip label                                         */
  --fs-tab:       9px;  /* tab label                                          */

  --lh-tight:  1.2;     /* screen + detail titles                             */
  --lh-snug:   1.25;    /* accordion card title                               */
  --lh-body:   1.45;    /* sub-headers, sub-lines                             */
  --lh-copy:   1.5;     /* the most common body line-height                   */
  --lh-loose:  1.55;    /* the long explanatory paragraphs                    */
  --lh-flat:   1;       /* big numerals                                       */

  --track-eyebrow: 0.4px;
  --track-kicker:  0.6px;
  --track-title:  -0.2px;   /* Main's screen title only                       */

  --font-display: 'Libre Franklin', system-ui, -apple-system, 'Segoe UI', Roboto, Arial, sans-serif;

  /* -- the voter-guide list (section 28, 2026-09-25) ------------------------
     AMAC's three positions, as chips. Look A's values; `[data-look="guide"]`
     swaps them for look C's. Measured on their own fills:
       endorsed    #3E4A22 on leaf-16 over surface   8.9
       recommended #505B2D on surface                6.95
       dual        #2F4C8A on #EAF0FA                 7.2
     --body2 is the prototype's second text colour (#3B4A40, 9.0 on ground). */
  --pick-end-bg:     var(--leaf-16);
  --pick-end-border: var(--green);
  --pick-end-ink:    #3E4A22;
  --pick-rec-bg:     var(--surface);
  --pick-rec-ink:    var(--green-deep);
  --pick-dual-bg:    #EAF0FA;
  --pick-dual-ink:   #2F4C8A;
  --body2:           #3B4A40;
  --place-chip-bg:   var(--stone-36);
  --quiet-bg:        var(--stone-22);
  --font-body:    'Libre Franklin', system-ui, -apple-system, 'Segoe UI',
                  Roboto, 'Helvetica Neue', Arial, sans-serif;

  /* -- geometry ----------------------------------------------------------- */
  --gutter:  20px;    /* the universal side gutter. every screen, no exception */
  --col-max: 560px;   /* the column stops growing here; ground fills the rest  */
  --tap:     44px;    /* minimum touch target, everywhere, no exception        */
  /* The comps' 62px top padding is an iOS status-bar inset baked into a
     402x874 artboard. In a browser tab there is no status bar — the URL bar
     already occupies that space and a literal 62px reads as a mistake. This
     gives ~24px in Chrome on Android and Safari, and the real inset in a
     home-screen PWA. Requires viewport-fit=cover in the viewport meta. */
  --pad-top:      calc(env(safe-area-inset-top, 0px) + 24px);
  --pad-bottom-tabs:  calc(108px + env(safe-area-inset-bottom, 0px));
  --pad-bottom-plain: calc(28px + env(safe-area-inset-bottom, 0px));
  --tabbar-bottom:    calc(8px + env(safe-area-inset-bottom, 0px));

  --dur-fast: 120ms;
  --dur:      180ms;
  --ease:     cubic-bezier(0.2, 0, 0.2, 1);
}


/* ---------------------------------------------------------------------------
   3. RESET AND BASE
--------------------------------------------------------------------------- */
*, *::before, *::after { box-sizing: border-box; }

html {
  background: var(--ground);
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
  /* Nothing may put a horizontal scrollbar on the body. `clip` does not create
     a scroll container (so position: sticky keeps working); `hidden` is the
     fallback for anything that does not know `clip`. */
  overflow-x: hidden;
  overflow-x: clip;
}

body {
  margin: 0;
  background: var(--ground);
  color: var(--ink);
  font-family: var(--font-body);
  font-size: var(--fs-row);
  font-weight: 400;
  line-height: normal;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  overflow-x: hidden;
  overflow-x: clip;
  /* Do not add a device frame, a border or a shadow around the column.
     The ground is meant to be edge-to-edge. */
}

/* Voter names come off a government file and some of them are long. A single
   unbroken token must wrap rather than widen the page. */
body, .t-row, .t-detail, .t-card-title, .row__body, .doc { overflow-wrap: break-word; }

h1, h2, h3, h4, p, ul, ol, figure, blockquote { margin: 0; }
ul, ol { padding: 0; list-style: none; }

/* Almost everything here is display:flex or display:grid, and both beat the UA
   sheet's [hidden] rule. Toggle visibility with el.hidden and this makes it
   stick — otherwise a "hidden" accordion body or error block stays on screen. */
[hidden] { display: none !important; }

/* Semantic defaults, so unclassed markup already looks like the comps. */
h1 {
  font-family: var(--font-display);
  font-size: var(--fs-title); font-weight: 600;
  line-height: var(--lh-tight); color: var(--ink);
}
h2 {
  font-family: var(--font-display);
  font-size: var(--fs-section); font-weight: 600;
  line-height: var(--lh-snug); color: var(--ink);
}
h3 {
  font-family: var(--font-body);
  font-size: var(--fs-row); font-weight: 600; color: var(--ink);
}
p {
  font-size: var(--fs-body); font-weight: 400;
  line-height: var(--lh-body); color: var(--muted);
}

strong, b { font-weight: 600; }        /* 600, never 700 */

svg { display: block; flex-shrink: 0; }

::selection { background: var(--leaf-16); color: var(--ink); }


/* ---------------------------------------------------------------------------
   4. CONTROL RESETS — every cursor:pointer div in the comps becomes a real
   control. Reset the UA chrome once, here, so screen modules can use
   <button> and <a> without fighting the browser.
--------------------------------------------------------------------------- */
button, [role="button"] {
  appearance: none; -webkit-appearance: none;
  margin: 0; border: 0; background: none;
  font: inherit; color: inherit; text-align: inherit;
  cursor: pointer;
}
button:disabled, [aria-disabled="true"] { cursor: default; opacity: 0.55; }

a { color: var(--green-deep); text-decoration: none; }   /* 6.60:1 on ground   */
@media (hover: hover) {                                   /* gate hover so a   */
  a:hover { color: var(--green); }                        /* tap on Android    */
}                                                         /* leaves no state   */
.link { color: var(--green-deep); text-decoration: none; font: inherit; }
@media (hover: hover) { .link:hover { color: var(--green); } }

/* One focus ring for everything. --leaf on ground is 3.50:1, clearing the
   3:1 bar for a non-text indicator. */
:where(a, button, input, select, textarea, summary, [tabindex]):focus-visible {
  outline: 2px solid var(--leaf);
  outline-offset: 1px;
  border-radius: inherit;
}
/* Keyboard users get the ring; pointer users do not get a stuck one. */
:where(a, button, input, summary, [tabindex]):focus:not(:focus-visible) { outline: none; }
/* An element with tabindex="-1" is focused only by code -- a result landing moves focus there so a
   screen reader reads it. Tab can never reach it, so it has no ring to show. Pressing Enter in the
   address box made Chrome treat that move as keyboard focus and drew a ring round the whole result
   until the reader clicked away (Haashim, 2026-09-25). */
[tabindex="-1"]:focus, [tabindex="-1"]:focus-visible { outline: none; }

/* When a control is an <a>, the global a:hover link colour must not repaint it.
   Specificity: a:hover is (0,1,1), so these (0,2,1) rules win outright. */
@media (hover: hover) {
  a.btn-primary:hover,
  a.btn-primary--compact:hover { color: var(--on-green); }
  a.row:hover,
  a.card-tappable:hover,
  a.card:hover,
  a.tab:hover,
  a.qr-link:hover { color: inherit; }
}


/* ---------------------------------------------------------------------------
   5. LAYOUT — the 402px artboard becomes a centred fluid column
   At 402px this is pixel-identical to the comps except the top inset. Above
   560px the column stops and the ground fills the rest: on an iPad it reads as
   a page, not a stretched phone app.
--------------------------------------------------------------------------- */
.screen {
  width: 100%;
  max-width: var(--col-max);
  margin-inline: auto;
  padding-inline: var(--gutter);
  padding-top: var(--pad-top);
  padding-bottom: var(--pad-bottom-plain);
  display: flex;
  flex-direction: column;
  gap: var(--gap-section);
}
.screen > * { min-width: 0; }              /* nothing widens the page          */

.screen--tabs  { padding-bottom: var(--pad-bottom-tabs); }   /* clears the bar */
.screen--plain { padding-bottom: var(--pad-bottom-plain); }  /* pushed screens */
.screen--doc   { gap: var(--gap-section); padding-bottom: var(--pad-bottom-plain); }

/* Stacks. `.col` is the default 10px stack; the .g-* modifiers walk the ladder
   in §1.3 so a screen never needs an inline style. */
.col   { display: flex; flex-direction: column; gap: var(--gap-lg); }
.col > *, .row-h > * { min-width: 0; }
.row-h { display: flex; align-items: center; gap: var(--gap-lg); }
.row-h--top      { align-items: flex-start; }
.row-h--baseline { align-items: baseline; }
.row-h--wrap     { flex-wrap: wrap; }

.g-1  { gap: 1px; }
.g-2  { gap: var(--gap-tight); }
.g-3  { gap: var(--gap-hair); }
.g-4  { gap: var(--gap-xs); }
.g-5  { gap: 5px; }
.g-6  { gap: var(--gap-sm); }
.g-8  { gap: var(--gap-md); }
.g-10 { gap: var(--gap-lg); }
.g-12 { gap: var(--gap-xl); }
.g-14 { gap: var(--gap-2xl); }
.g-16 { gap: var(--gap-section); }
.g-18 { gap: 18px; }
.g-22 { gap: 22px; }

.grow  { flex-grow: 1; }
.shrink-0 { flex-shrink: 0; }
.center { text-align: center; }
.spacer-top { padding-top: 4px; }          /* the election heading + footer     */

/* A meta row: eyebrow left, scope note right. Baseline-aligned as drawn. */
.meta-row {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: var(--gap-lg);
}
.scope-note { font-size: var(--fs-sub); color: var(--muted); }

/* Dividers. --hairline inside a --surface card; --ink-08 inside a tint.
   Using the wrong one is invisible on the wrong background. */
.divider       { height: 1px; background: var(--hairline); border: 0; margin: 0; }
.divider--tint { height: 1px; background: var(--ink-08);  border: 0; margin: 0; }

.sr-only {
  position: absolute; width: 1px; height: 1px;
  padding: 0; margin: -1px; overflow: hidden;
  clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap; border: 0;
}


/* ---------------------------------------------------------------------------
   6. TYPE ROLES
   Line-height is set per role, exactly as the comps do. There is deliberately
   no global line-height: setting one would change every single-line label.
--------------------------------------------------------------------------- */
.t-title-xl   { font-family: var(--font-display); font-size: var(--fs-title-xl);
                font-weight: 600; line-height: 1.15; color: var(--ink); }
.t-title      { font-family: var(--font-display); font-size: var(--fs-title);
                font-weight: 600; line-height: var(--lh-tight); color: var(--ink); }
.t-title--tight { letter-spacing: var(--track-title); }  /* the Ballot H1 only */
.t-detail     { font-family: var(--font-display); font-size: var(--fs-detail);
                font-weight: 600; line-height: var(--lh-tight); color: var(--ink); }
.t-sheet-title{ font-family: var(--font-display); font-size: var(--fs-sheet);
                font-weight: 600; color: var(--ink); }
.t-card-title { font-family: var(--font-display); font-size: var(--fs-card);
                font-weight: 600; color: var(--ink); }
.t-section    { font-family: var(--font-display); font-size: var(--fs-section);
                font-weight: 600; line-height: var(--lh-snug); color: var(--ink); }

.num-hero     { font-family: var(--font-display); font-size: 44px; font-weight: 600;
                line-height: var(--lh-flat); color: var(--green); }
.num-summary  { font-family: var(--font-display); font-size: 40px; font-weight: 600;
                line-height: var(--lh-flat); color: var(--green); }
.num-compact  { font-family: var(--font-display); font-size: 32px; font-weight: 600;
                line-height: var(--lh-flat); color: var(--green); }
.stat-value   { font-family: var(--font-display); font-size: var(--fs-card);
                font-weight: 600; color: var(--ink); }
.stat-lift    { font-family: var(--font-display); font-size: var(--fs-row);
                font-weight: 600; color: var(--ink); }

.t-lead   { font-size: var(--fs-lead);  font-weight: 600; color: var(--ink); }
.t-row    { font-size: var(--fs-row);   font-weight: 600; color: var(--ink); }
.t-body-lg{ font-size: var(--fs-row);   font-weight: 400; line-height: var(--lh-copy);
            color: var(--ink); }
.t-emph   { font-size: var(--fs-emph);  font-weight: 600; color: var(--ink); }
.t-body   { font-size: var(--fs-body);  font-weight: 400; line-height: var(--lh-body);
            color: var(--muted); }
.t-body--copy  { line-height: var(--lh-copy); }
.t-body--loose { line-height: var(--lh-loose); }
.t-body--ink   { color: var(--ink); }         /* Learn bodies, empty bullets   */
.t-meta   { font-size: var(--fs-meta); font-weight: 400; line-height: var(--lh-copy);
            color: var(--muted); }
.t-meta-em{ font-size: var(--fs-meta); font-weight: 600; color: var(--ink); }
.t-sub    { font-size: var(--fs-sub);  font-weight: 400; line-height: var(--lh-body);
            color: var(--muted); }
.t-input  { font-size: var(--fs-row);  font-weight: 400; color: var(--ink); }

/* Eyebrows. Neutral is --muted; attention is --roots-deep (NOT --roots, which
   is 3.55:1); positive is --green-deep (NOT --green, which fails on a tint). */
.eyebrow {
  font-size: var(--fs-meta); font-weight: 600;
  letter-spacing: var(--track-eyebrow); text-transform: uppercase;
  color: var(--muted);
}
.eyebrow--tan   { color: var(--roots-deep); }
.eyebrow--green { color: var(--green-deep); }
.eyebrow--sm    { font-size: var(--fs-sub); letter-spacing: var(--track-kicker); }

/* The Learn bodies carry \n\n paragraph breaks and are set as one text node. */
.t-prewrap { white-space: pre-line; }


/* ---------------------------------------------------------------------------
   7. SURFACES
   RULE 1, enforced: .card ALWAYS carries --shadow-card. .panel NEVER carries a
   shadow. Elevation = "this is a thing". Tint = "this is a note about a thing".
--------------------------------------------------------------------------- */
.card {
  padding: 16px var(--gutter);
  background: var(--surface);
  border-radius: var(--r-card);
  box-shadow: var(--shadow-card);
  display: flex; flex-direction: column; gap: var(--gap-lg);
}
.card--roomy  { padding: 18px var(--gutter); }         /* the roomier default  */
.card--avatar { padding: var(--gutter); }              /* when it holds a disc */
.card--hero   { padding: 22px var(--gutter); }
.card--row    { padding: var(--gap-xl); }              /* a list row           */
.card--inline { padding: 14px 18px; border-radius: var(--r-card); }

/* NOT a card, deliberately. The comps have exactly one --surface shape that
   carries no elevation: a small 16px-radius inset block (the Settings
   resolution card, Shells' captain rows). It is not a `.card` variant, because
   `.card` must be true to RULE 1 without exception — an unshadowed thing with a
   `card` in its class name is how that rule gets eroded. */
.inset {
  padding: 14px 16px;
  background: var(--surface);
  border-radius: var(--r-field);
  display: flex; gap: var(--gap-lg);
  box-shadow: none;
}

/* A card that is itself a control. Reset the button chrome, keep the card. */
.card-tappable {
  width: 100%; text-align: left; cursor: pointer;
  transition: box-shadow var(--dur) var(--ease), transform var(--dur-fast) var(--ease);
}
@media (hover: hover) {
  .card-tappable:hover { box-shadow: var(--shadow-card-hover); }
}
.card-tappable:active { transform: scale(0.995); }

/* Tint panels — four variants, NEVER shadowed. */
.panel {
  padding: 16px var(--gutter);
  border-radius: var(--r-card);
  display: flex; flex-direction: column; gap: var(--gap-md);
  box-shadow: none;              /* RULE 1. do not override this anywhere.     */
  background: var(--stone-22);   /* neutral is the default: it claims nothing. */
}
.panel--green   { background: var(--leaf-14);  }  /* confirmed, good news      */
.panel--neutral { background: var(--stone-22); }  /* a quiet explainer         */
.panel--amber   { background: var(--amber-16); }  /* STOP AND DON'T CONCLUDE   */
.panel--tan     { background: var(--tan-14);   }  /* a boundary the person set */
.panel--roomy   { padding: 18px var(--gutter); }
.panel--note    { padding: 14px var(--gutter); border-radius: var(--r-field); }
.panel--tile    { padding: 14px 16px; border-radius: var(--r-field); }

/* Belt and braces on RULE 1: a shadow on a tint is a bug in this system, so
   make it non-overridable on the panel itself. (Scoped to the panel, not its
   descendants — a --surface card nested inside a panel still keeps its own
   elevation, because it is still a thing.) */
.panel { box-shadow: none !important; }

/* Alias: some screens read more naturally as ".tint". Same rules, same rule 1. */
.tint          { padding: 16px var(--gutter); border-radius: var(--r-card);
                 display: flex; flex-direction: column; gap: var(--gap-md);
                 background: var(--stone-22); box-shadow: none !important; }
.tint--green   { background: var(--leaf-14);  }
.tint--neutral { background: var(--stone-22); }
.tint--amber   { background: var(--amber-16); }
.tint--tan     { background: var(--tan-14);   }


/* ---------------------------------------------------------------------------
   8. CONTROLS
--------------------------------------------------------------------------- */
/* Primary button. The green button shares the card's 22px radius on purpose —
   it reads as a card that acts. ~52px tall, already over the 44px minimum. */
.btn-primary {
  display: flex; align-items: center; justify-content: center;
  gap: var(--gap-md);
  width: 100%;
  min-height: var(--tap);
  padding: 16px;
  background: var(--green);
  border-radius: var(--r-card);
  color: var(--on-green);                     /* 5.04:1 on --green            */
  font-family: var(--font-body);
  font-size: var(--fs-lead); font-weight: 600;
  cursor: pointer;
  transition: background var(--dur) var(--ease), filter var(--dur-fast) var(--ease);
}
.btn-primary--icon { gap: var(--gap-lg); }    /* a 20px white glyph before it  */
.btn-primary--compact {
  width: auto; align-self: center;
  padding: 14px 24px; font-size: var(--fs-row);
}
@media (hover: hover) {
  /* Darker, not lighter: white on --leaf would be 3.88:1. See --green-hover. */
  .btn-primary:hover { background: var(--green-hover); }
}
.btn-primary:active { filter: brightness(0.95); }
.btn-primary svg { stroke: var(--on-green); }

/* Tertiary text button — "Not now", "Not me — search again".
   Drawn at padding 6px/8px, i.e. ~25px and ~32px tall: two of the four comp
   elements that fail the 44px minimum. min-height fixes both while leaving the
   label optically where it was. */
.btn-text {
  display: flex; align-items: center; justify-content: center;
  width: 100%;
  min-height: var(--tap);
  padding: var(--gap-md);
  background: none;
  font-size: var(--fs-body); font-weight: 600;
  color: var(--muted);                        /* 5.61:1 on ground             */
  cursor: pointer;
  transition: color var(--dur) var(--ease);
}
@media (hover: hover) { .btn-text:hover { color: var(--ink); } }

/* Paired pill actions. ~46px as drawn, already over the minimum; pinned. */
.btn-pill {
  flex-grow: 1;
  display: flex; align-items: center; justify-content: center; gap: var(--gap-sm);
  min-height: var(--tap);
  padding: 15px var(--gap-xl);
  background: var(--surface);
  border-radius: var(--r-pill);
  font-size: var(--fs-body); font-weight: 600; color: var(--ink);
  cursor: pointer;
}
.btn-pill--quiet { background: var(--surface-60); color: var(--muted); }
/* A pressed pill in a toggle pair. `.btn-pill` vs `.btn-pill--quiet` is
   `--surface` vs `--surface-60` -- two near-whites -- and on the Look-up
   screen's name/address toggle that left both pills reading as unselected
   (Haashim, 2026-09-10: "that box should be highlighted so we know which
   section we're on"). Same fill and ink the tab bar uses for the current tab,
   so "chosen" means one thing on this app. Keyed on aria-pressed, which the
   toggle already sets: the state that assistive tech announces is the state
   that is drawn, and a class alone could drift from it. */
.btn-pill[aria-pressed="true"] { background: var(--leaf-15); color: var(--green-deep); }

/* Back row — "‹ Look up", "‹ Back". 18px tall as drawn: the third failing
   element. The negative inline margin keeps the chevron on the 20px gutter
   line while the hit box grows to 44px. */
/* ⚠️ THIS WAS NOT A TOUCH-TARGET PROBLEM AND ENLARGING IT WOULD HAVE CHANGED
   NOTHING VISIBLE. Measured before touching it: the hit box was already
   `min-height: var(--tap)` (44px) and the label already cleared AA at 5.61:1 on
   --ground. It was tappable and legible and still got missed on a real phone,
   because 13px of --muted text beside a hairline chevron reads as a CAPTION.
   The reader was not failing to hit it; they were failing to recognise it as a
   control at all ("some of these things are a little bit hidden").

   So the change is weight, not geometry: a bordered pill, --ink instead of
   --muted, and the row font size rather than body. The 44px box is untouched
   because it was never the defect. */
.backrow {
  display: inline-flex; align-items: center; gap: var(--gap-sm);
  align-self: flex-start;
  min-height: var(--tap);
  padding-inline: 14px;
  border: 1px solid var(--hairline);
  border-radius: 999px;
  background: var(--surface);
  color: var(--ink);
  font-size: var(--fs-row); font-weight: 500;
  cursor: pointer;
}
.backrow svg { stroke: var(--ink); }
@media (hover: hover) { .backrow:hover { color: var(--ink); } }
@media (hover: hover) { .backrow:hover svg { stroke: var(--ink); } }

/* Any bare icon affordance — the fourth failing element. The glyph stays at
   its drawn size; the box around it reaches 44px. */
.iconbtn, .tap-44 {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: var(--tap); min-height: var(--tap);
}
.tabbar > * { min-height: var(--tap); }   /* a short label never shrinks a tab */

/* The FAQ's top row: Back on the left, Share on the right (2026-09-12). */
.faq-toprow {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--gap-lg);
}
.faq-toprow > .backrow { align-self: auto; }
/* Share wears the Back pill's border and height so the two read as one row of
   controls. On a touch screen it is a 44px circle holding only the glyph; a
   fine pointer (a computer) has room for the word, so the label appears. */
/* `.backrow` is weight 500, which the two-weight rule forbids; Share resets it
   so the visible "Share" word on FAQ, Home and the recommendations screen is
   600 (redesign PR5 review). */
.sharebtn {
  justify-content: center;
  min-width: var(--tap);
  padding-inline: 0;
  font-weight: 600;
}
.sharebtn__label { display: none; }
@media (hover: hover) and (pointer: fine) {
  .sharebtn { padding-inline: 14px; }
  .sharebtn__label { display: inline; }
}
.faq-share-status {
  align-self: flex-end;
  font-size: var(--fs-meta); font-weight: 600;
  color: var(--green-deep);
}

/* Text field. 50px tall, already over the minimum. */
.field, input[type="text"], input[type="search"], input[type="email"], input[type="tel"] {
  width: 100%;
  height: 50px;
  padding: 0 16px;
  font-family: var(--font-body);
  font-size: var(--fs-row);
  font-weight: 400;
  color: var(--ink);
  background: var(--surface);
  border: 1px solid var(--hairline);
  border-radius: var(--r-field);
  appearance: none; -webkit-appearance: none;
}
input::placeholder { color: var(--muted); opacity: 1; }   /* 5.90:1 on surface */
input[type="search"]::-webkit-search-decoration,
input[type="search"]::-webkit-search-cancel-button { -webkit-appearance: none; }

/* Search-field variant: the box becomes a flex row holding an 18px magnifier
   and a chromeless input. */
.searchfield {
  display: flex; align-items: center; gap: var(--gap-lg);
  width: 100%;
  height: 50px;
  padding: 0 16px;
  background: var(--surface);
  border: 1px solid var(--hairline);
  border-radius: var(--r-field);
}
.searchfield svg { stroke: var(--muted); }
.searchfield__input,
.searchfield input {
  flex-grow: 1; min-width: 0;
  height: 100%;
  padding: 0;
  border: 0; background: transparent;
  font-family: var(--font-body);
  font-size: var(--fs-row); font-weight: 400; color: var(--ink);
  appearance: none; -webkit-appearance: none;
}
.searchfield:focus-within { outline: 2px solid var(--leaf); outline-offset: 1px; }
.searchfield__input:focus, .searchfield input:focus { outline: none; }
/* The x that clears the field. 44px to tap, but pulled into the 16px padding
   so the row stays 50px tall and the glyph sits where the padding ends. */
.searchfield__clear {
  flex-shrink: 0;
  margin: 0 -13px 0 -8px;
  padding: 0;
  border: 0; background: transparent;
  color: var(--muted);
  cursor: pointer;
}
.searchfield__clear[hidden] { display: none; }
@media (hover: hover) { .searchfield__clear:hover { color: var(--ink); } }
.searchfield__clear:focus-visible { outline: 2px solid var(--leaf); border-radius: 50%; }
/* The failed-query echo on the full-screen no-match board is static text in the
   same box, not an input. */
.searchfield__echo { flex-grow: 1; font-size: var(--fs-row); color: var(--ink); }

/* City combobox (lite/js/citypick.js). A field with a floating listbox under it.

   ⚠️ UNREFERENCED SINCE 2026-09-09, AND KEPT ON PURPOSE. `citypick.js` was
   deleted with the find-self screen, so nothing in the app builds a `.citypick`
   any more and none of these rules can match. They are kept for two reasons and
   neither is sentiment: the Z-INDEX SCALE below is the app's only written record
   of how three fixed layers relate, and a future city control would otherwise
   have to rediscover both the layering and the `[hidden]` trap the hard way.
   If a city control never comes back, this whole block can go — but delete the
   scale comment into `.tabbar` and `.sheet-scrim` first, not with it.

   THE Z-INDEX SCALE, WHICH NOW HAS THREE LEVELS AND ONLY THREE:
     10  .tabbar        fixed, always over the page
     11  .citypick__menu  ABOVE the tab bar, because a city field low in the
                          viewport with the keyboard up is unusable if the bar
                          covers its suggestions — and BELOW the scrim, because
     20  .sheet-scrim   the public-data notice must always win.
   Do not add a fourth without adding it here.

   The wrapper carries `position: relative` and NO display utility: a flex class
   on an element out-specifies the UA's `[hidden]` rule, which is the trap
   findself.js:1835-1838 documents. The <ul> is what gets hidden. */
.citypick { position: relative; }

/* A THING, so it is elevated — RULE 1 makes `.panel` unshadowable, and a
   floating popup with no elevation reads as page content. Only .card's padding
   and gap are overridden. */
.citypick__menu {
  position: absolute;
  top: calc(100% + 6px);
  left: 0; right: 0;
  z-index: 11;
  padding: 6px;
  gap: 0;
  margin: 0;
  list-style: none;
  /* The cap on suggestions is this box, never a slice() over the list: a cap on
     the ROWS can hide the reader's real city behind a plausible near-miss. */
  max-height: 232px;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  overscroll-behavior: contain;   /* the page must not scroll under the thumb */
}
.citypick__option {
  display: flex; align-items: center;
  min-height: var(--tap);
  padding: 0 12px;
  border-radius: var(--r-field);
  font-size: var(--fs-row);
  color: var(--ink);
  cursor: pointer;                /* required for a synthesised click on iOS */
}
.citypick__option[aria-selected="true"] { background: var(--stone-22); }
@media (hover: hover) {
  .citypick__option:hover { background: var(--stone-22); }
}
/* The disclosure row. Not an option: nothing to select, nothing to announce as
   selectable, and it never receives the active highlight. */
.citypick__nomatch {
  display: flex; align-items: center;
  min-height: 40px;
  padding: 0 12px;
  font-size: var(--fs-meta);
  color: var(--muted);
}


/* ---------------------------------------------------------------------------
   9. TAB BAR — fixed, centred, safe-area aware
   520px = 560 - 2x20, so the gutter survives at max column width and the bar
   stays thumb-reachable on an iPad instead of stretching across the slate.
--------------------------------------------------------------------------- */
.tabbar {
  position: fixed;
  left: 50%;
  transform: translateX(-50%);
  width: min(100% - 40px, 520px);
  bottom: var(--tabbar-bottom);
  z-index: 10;                         /* level 1 of 3 — the scale is documented
                                          above .citypick__menu in section 8 */
  display: flex; gap: var(--gap-tight);
  padding: 6px;
  background: var(--surface-92);
  border: 0.5px solid var(--ink-06);   /* hairline at DPR>=2; 1px on a desktop
                                          monitor, which is acceptable — do not
                                          swap it for 1px. */
  border-radius: var(--r-pill);
  box-shadow: var(--shadow-bar);
  /* The 0.92 alpha over scrolling content is an iOS blurred material. Without
     a blur the translucency just looks like a smudge. Fallback is the opaque
     --surface fill above. */
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
}

.tab {
  flex-grow: 1; flex-basis: 0;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: var(--gap-hair);
  padding: 7px 0;
  border-radius: var(--r-pill);
  background: transparent;
  color: var(--muted);
  text-decoration: none;
  cursor: pointer;
}
.tab svg { stroke: var(--muted); }
.tab__label,
.tab > span {
  font-size: var(--fs-tab); font-weight: 600; color: var(--muted);
  letter-spacing: 0;
}
/* Active. The pill is --leaf-15 and only the tab bar ever uses that tint.
   Ink is --green-deep, not --green: --green on --leaf-15 is 4.07:1 and the
   label is 9px. */
.tab.is-active,
.tab[aria-current="page"] { background: var(--leaf-15); }
.tab.is-active svg,
.tab[aria-current="page"] svg { stroke: var(--green-deep); }
.tab.is-active .tab__label, .tab.is-active > span,
.tab[aria-current="page"] .tab__label, .tab[aria-current="page"] > span {
  color: var(--green-deep);
}

/* Out of the way while the reader types on a phone. The bar is fixed to the
   bottom of the layout viewport, and iOS draws the keyboard over that, so the
   bar ends up floating on top of the keyboard — over the City field and the
   first result, in the screenshot that asked for this (2026-09-12). Touch
   screens only: on a laptop there is no keyboard to sit on. #app, so the
   invisible keyboard-hold box on <body> (dom.js) never hides it. */
@media (pointer: coarse) {
  body:has(#app input:focus) .tabbar { display: none; }
}


/* ---------------------------------------------------------------------------
   10. CHIPS
   Three meanings and only three. There is no red chip and there never will be.
--------------------------------------------------------------------------- */
.chip {
  display: inline-flex; align-items: center;
  padding: 2px 7px;
  border-radius: var(--r-pill);
  font-size: var(--fs-chip); font-weight: 600;
  white-space: nowrap;
  background: var(--stone-36);
  color: var(--muted);                     /* 4.87:1 on stone-36 over ground   */
}
.chip--roomy { padding: 3px 9px; }         /* the list-row variant             */

/* Confirmed positive: registered. */
.chip--yes,
.chip--registered { background: var(--leaf-14); color: var(--green-deep); } /* 5.98 */
/* Confirmed positive: the county says the ballot is in. */
.chip--in,
.chip--voted      { background: var(--leaf-16); color: var(--green-deep); } /* 5.58 */

/* A known negative the file actually STATED. Neutral grey, never red.
   ⚠️ Do not reach for this chip for a silence. "The county said no ballot is
   back" and "the county said nothing" are different facts; only the first one
   may wear this. The second wears .chip--attention. */
.chip--no,
.chip--none       { background: var(--stone-36); color: var(--muted); }     /* 4.87 */

/* "Don't conclude" — needs fixing, or a county that has not reported.
   --roots-deep, NOT the comps' #7A5F3C, which is 3.85:1 on --tan-34. */
.chip--fix,
.chip--needsfix   { background: var(--tan-34);  color: var(--roots-deep); } /* 4.72 */
.chip--attention,
.chip--unknown    { background: var(--amber-16); color: var(--roots-deep); }/* 6.48 */

/* A chip whose VERB is a glyph. `.chip` is already inline-flex, so this adds
   nothing but the gap between the mark and the words.
   ⚠️ THE MARK IS NOT DECORATION. It carries "Voted"; the text beside it carries
   only the election name, and the whole sentence rides along in an .sr-only
   span (screens/lookup.js `chipNode`). So this modifier may never be added to a
   chip whose label already reads as a complete fact, and a chip that wears it
   may never be drawn without its icon — half of it would be an election name
   sitting beside somebody's name, saying nothing and implying something.
   The icon strokes in `currentColor`: the comps' ICON_COLOR.leaf (#798844) is
   the colour for a white card and does not clear 4.5:1 on --leaf-16. */
.chip--mark { gap: 3px; }

.chips { display: flex; flex-wrap: wrap; gap: var(--gap-sm); }


/* ---------------------------------------------------------------------------
   11. AVATAR DISCS
   Initials are first and last only — never one per word. "MAVDB" in a 40pt
   circle is not a monogram, it is a smudge. That is a copy.js/screen concern;
   this file just sizes the disc.
--------------------------------------------------------------------------- */
.avatar {
  display: flex; align-items: center; justify-content: center;
  flex-shrink: 0;
  width: 40px; height: 40px;
  border-radius: var(--r-pill);
  background: var(--leaf-14);
  font-family: var(--font-display);
  font-size: var(--fs-row); font-weight: 600;
  color: var(--green-deep);                /* 5.98:1 — --green would be 4.12   */
}
.avatar--48 { width: 48px; height: 48px; font-size: 17px; }
.avatar--40 { width: 40px; height: 40px; font-size: 15px; }
.avatar--36 { width: 36px; height: 36px; font-size: 13px; }
.avatar--34 { width: 34px; height: 34px; font-size: 14px; }
/* The quiet disc. Used when there is nothing positive to say — which includes
   "the county has not reported". It is grey because grey claims nothing; it is
   never used to mean "this person did not vote". */
.avatar--quiet { background: var(--stone-36); color: var(--muted); }


/* ---------------------------------------------------------------------------
   12. LIST ROWS AND RESULTS
--------------------------------------------------------------------------- */
.row {
  display: flex; align-items: center; gap: var(--gap-xl);
  width: 100%;
  padding: var(--gap-xl);
  background: var(--surface);
  border-radius: var(--r-card);
  box-shadow: var(--shadow-card);
  text-align: left;
  color: inherit;
  min-height: 64px;                        /* already over the 44px minimum    */
}
.row__body { flex-grow: 1; min-width: 0; display: flex; flex-direction: column; gap: 5px; }
/* The status column: ONE mark per row, at the right edge, so a reader answers
   "did they vote" by running an eye down the list instead of reading the same
   election name on every line (Haashim, 2026-09-17). `flex-shrink: 0` because a
   long name must take the space from `.row__body`, never from the answer. */
.row__status { flex-shrink: 0; display: flex; align-items: center; }
.row__status .icon { width: 22px; height: 22px; }
.row__name { font-size: var(--fs-row); font-weight: 600; color: var(--ink); }
.row__where{ font-size: var(--fs-sub); color: var(--muted); }
.row--tappable { cursor: pointer; transition: box-shadow var(--dur) var(--ease); }
@media (hover: hover) { .row--tappable:hover { box-shadow: var(--shadow-card-hover); } }

/* The results list. ONE COLUMN AT EVERY WIDTH, exactly as drawn.
   The 2-up grid that used to live here above 700px has been removed:
   spec-design §10.11 says "do not add a two-column layout at any width", and
   design fidelity to Grove's Circle Lite is a stated hard requirement, so the
   design reference outranks the build brief that asked for it (the brief was
   ours; the spec is the reference). The column still breathes to --col-max on
   a wide screen, which is the responsive allowance CONTRACT §7 actually pins.
   Kept as a grid rather than a flex column so the `grid-column: 1 / -1` rule
   below keeps working for anything that must never be columned. */
.results { display: grid; grid-template-columns: 1fr; gap: var(--gap-lg); }
/* Anything that is not a result row spans the grid — an empty block, a
   skeleton, a failure card. Never let a message get columned. */
.results > .empty,
.results > .skeleton,
.results > .card,
.results > .results__head,
.span-all { grid-column: 1 / -1; }

/* The line above the rows: the election named ONCE on the left, the key to the
   marks on the right. Padding matches `.row`'s own --gap-xl so the name sits
   over the avatars and the key sits over the marks it explains; it is not a
   card, so it carries no surface, no shadow and no radius of its own.
   It wraps to two lines rather than squeezing either half on a narrow phone. */
.results__head {
  display: flex; align-items: center; justify-content: space-between;
  flex-wrap: wrap; gap: 2px var(--gap-md);
  padding: 0 var(--gap-xl) 2px;
  font-size: var(--fs-sub); color: var(--muted);
}
.results__election { font-weight: 600; color: var(--ink); }
.results__key { display: flex; flex-wrap: wrap; align-items: center; gap: 2px var(--gap-md); }
.results__key > span { display: inline-flex; align-items: center; gap: 4px; }
.results__key .icon { width: 14px; height: 14px; }


/* ---------------------------------------------------------------------------
   13. STATUS ROWS — the shape that carries the whole product's honesty
   A status row is: a 20px mark, then a line, then an optional sub-line.
   RULE 4 lives in the icon modifiers, §16.
   A missing sub-line is DELETED, not filled. Never "In  County", never
   "Updated ", never "Accepted ". This file gives .status-sub no empty-state
   styling on purpose — an empty one must not be rendered at all.
--------------------------------------------------------------------------- */
.status-row {
  display: flex; align-items: center; gap: var(--gap-lg);
}
.status-row--top { align-items: flex-start; }       /* when the sub wraps      */
.status-row__text { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.status-row--top .status-row__text { gap: var(--gap-hair); }
.status-line { font-size: var(--fs-row); font-weight: 600; color: var(--ink); }
.status-line--sm { font-size: var(--fs-emph); }     /* the Find-yourself card  */
.status-sub  { font-size: var(--fs-sub); font-weight: 400;
               line-height: var(--lh-body); color: var(--muted); }

/* The "we were told nothing" card — the canonical pattern from EmptyStates
   board 2, reused for every unknown in the app. A --surface card (it IS a
   thing), a tan alert mark, a heading that describes OUR RECORD, and a body
   that names the possibilities and refuses to pick one. */
.unknown-card { gap: var(--gap-xl); }               /* use with .card--roomy   */

/* OUR failure. Not a fact about anybody. Same shape as .unknown-card because
   the two say the same kind of thing — "we could not read this", never "the
   answer is no". Named separately so it is greppable at review time, and so a
   screen cannot reach for the empty-state block by accident: the empty state
   is a claim about the voter file and only a 200 with an empty array earns it.
   Pair with .icon--unknown (never .icon--rest) and a "Try again" .btn-primary. */
.failure { gap: var(--gap-xl); }                    /* use with .card--roomy   */


/* ---------------------------------------------------------------------------
   14. ACCORDION (Learn)
--------------------------------------------------------------------------- */
.accordion { display: flex; flex-direction: column; gap: var(--gap-xl); }
.acc-card {
  padding: 18px var(--gutter);
  background: var(--surface);
  border-radius: var(--r-card);
  box-shadow: var(--shadow-card);
  display: flex; flex-direction: column; gap: var(--gap-lg);
}
.acc-head {
  display: flex; align-items: flex-start; gap: var(--gap-xl);
  width: 100%; padding: 0; text-align: left; cursor: pointer;
  min-height: var(--tap);
}
.acc-titles { flex-grow: 1; min-width: 0; display: flex; flex-direction: column; gap: var(--gap-xs); }
.acc-kicker { font-size: var(--fs-meta); color: var(--muted); }
.acc-head svg { stroke: var(--stone); margin-top: 4px; }
.acc-body {
  padding-left: 46px;                      /* 34px disc + 12px gap            */
  display: flex; flex-direction: column; gap: var(--gap-lg);
}
.acc-body[hidden] { display: none; }
.stat-block {
  display: flex; flex-direction: column; gap: var(--gap-md);
  padding: 14px 16px;
  background: var(--amber-16);
  border-radius: var(--r-field);
  box-shadow: none;
}
.stat-row {
  display: flex; align-items: baseline; justify-content: space-between; gap: var(--gap-lg);
}
.stat-row__label { font-size: var(--fs-meta); color: var(--muted); }  /* 5.26  */


/* ---------------------------------------------------------------------------
   15. EMPTY STATES
   `.empty` is the full-screen board-1 treatment and it is the one that ships:
   it echoes the query so the reader can see the typo, and it is followed by
   "Things worth trying". `.empty--inline` is the smaller earlier draft, kept
   because a screen may want it under a field.

   ⚠️ Neither of these may be rendered for a request that failed, was still in
   flight, or was never made. "The Washington voter list has no match" is a
   claim, and only a 200 with an empty array licenses it.
--------------------------------------------------------------------------- */
.empty {
  padding: 40px var(--gap-xl);
  display: flex; flex-direction: column; align-items: center;
  gap: var(--gap-xl);
  text-align: center;
}
.empty__glyph { stroke: var(--stone); }         /* decorative; the headline
                                                   carries the meaning        */
.empty__headline {
  font-family: var(--font-display); font-size: var(--fs-card); font-weight: 600;
  color: var(--ink);
}
.empty__body {
  font-size: var(--fs-body); line-height: var(--lh-loose);
  color: var(--muted); max-width: 280px;
}
.empty--inline { padding: 28px var(--gutter); gap: var(--gap-lg); }
.empty--inline .empty__headline {
  font-family: var(--font-body); font-size: var(--fs-row);
}
.empty--inline .empty__body {
  font-size: var(--fs-meta); line-height: var(--lh-copy); max-width: 260px;
}
/* The tab-less "Nobody yet" style block: 44px padding, 16/600 headline. */
.empty--roomy { padding: 44px var(--gutter); }
.empty--roomy .empty__headline { font-family: var(--font-body); font-size: var(--fs-lead); }
.empty--roomy .empty__body { font-size: var(--fs-body); line-height: var(--lh-copy); max-width: 270px; }

/* Bullet list — "Things worth trying". */
.bullets { display: flex; flex-direction: column; gap: var(--gap-lg); }
.bullet  { display: flex; gap: var(--gap-lg); }
/* The dot is drawn by ::before so a screen can write a bullet as one element.
   If a screen renders an explicit <span class="dot"> instead — which is what
   the comps do — :has() suppresses the pseudo-element so there is only one. */
.bullet::before,
.bullet > .dot {
  content: "";
  display: block;
  width: 5px; height: 5px; border-radius: var(--r-pill);
  background: var(--leaf);
  margin-top: 7px; flex-shrink: 0;
}
.bullet:has(> .dot)::before { display: none; }
.bullet__text { font-size: var(--fs-body); line-height: var(--lh-copy); color: var(--ink); }

/* The 6px dot before a candidate name. Neutral: it asserts nothing about them. */
.dot--candidate {
  width: 6px; height: 6px; border-radius: var(--r-pill);
  background: var(--stone); flex-shrink: 0;
}

/* Loading. A skeleton is the ONLY thing that may occupy the results region
   while a request is in flight — never the no-match block. */
.skeleton { display: flex; flex-direction: column; gap: var(--gap-lg); }
.skel-row {
  height: 64px;
  border-radius: var(--r-card);
  background: var(--surface);
  box-shadow: var(--shadow-card);
  position: relative;
  overflow: hidden;
}
.skel-row::after {
  content: "";
  position: absolute; inset: 0;
  background: linear-gradient(90deg, transparent, var(--stone-22), transparent);
  transform: translateX(-100%);
  animation: skel 1.4s var(--ease) infinite;
}
@keyframes skel { to { transform: translateX(100%); } }


/* ---------------------------------------------------------------------------
   16. ICONS
   Every icon is viewBox="0 0 24 24" fill="none" with round caps and joins.
   Stroke weight scales inversely with glyph size: <=18px -> 2 ·
   20px status -> 2.2 · 20px decorative -> 1.9 · >=34px -> 1.5.
   Only the star is ever filled.

   RULE 4, and it is the difference between this app and the nine defects:
     .icon--yes     check, --leaf        = we know, and the answer is YES
     .icon--rest    ring,  --stone-mark  = we know, and the answer is NO
     .icon--unknown alert, --roots       = we DO NOT KNOW, and will not guess
   Never draw the ring for an unknown. Never draw a check for anything but a
   confirmed positive.
--------------------------------------------------------------------------- */
.icon { display: block; flex-shrink: 0; fill: none;
        stroke-linecap: round; stroke-linejoin: round; }

/* Size + stroke together, so the ladder cannot be got wrong by hand.
   Apply one of these alongside a colour modifier, e.g.
   class="icon icon--status icon--unknown". */
.icon--17     { width: 17px; height: 17px; stroke-width: var(--sw-sm);     }
.icon--18     { width: 18px; height: 18px; stroke-width: var(--sw-sm);     }
.icon--18-chip{ width: 18px; height: 18px; stroke-width: var(--sw-chip);   }
.icon--status { width: 20px; height: 20px; stroke-width: var(--sw-status); }
.icon--20     { width: 20px; height: 20px; stroke-width: var(--sw-tile);   }
.icon--22     { width: 22px; height: 22px; stroke-width: var(--sw-sm);     }
.icon--34     { width: 34px; height: 34px; stroke-width: var(--sw-lg);     }
.icon--40     { width: 40px; height: 40px; stroke-width: var(--sw-xl);     }
.icon--42     { width: 42px; height: 42px; stroke-width: var(--sw-xl);     }

.icon--yes      { stroke: var(--leaf);       }  /* 3.50:1 on ground, >=3:1 ok  */
.icon--rest     { stroke: var(--stone-mark); }  /* 3.19:1 — --stone is 1.51    */
.icon--unknown  { stroke: var(--roots);      }  /* 3.55:1 as a mark, >=3:1 ok  */
.icon--muted    { stroke: var(--muted);      }
.icon--quiet    { stroke: var(--stone);      }  /* decorative only: chevrons,
                                                   dividers, big empty glyphs  */
.icon--onbrand  { stroke: var(--on-green);   }
.icon--leaf     { stroke: var(--leaf);       }
.icon--green    { stroke: var(--green-deep); }

/* The filled star. Two places on iOS; Lite web draws no star at all today
   (stars are out of scope — no backend), so this exists only for the tab
   glyph if a future shell needs it. */
.icon--filled-leaf  { fill: var(--leaf);  stroke: var(--leaf);  }
.icon--filled-green { fill: var(--green); stroke: var(--green); }


/* ---------------------------------------------------------------------------
   17. BOTTOM SHEET — the public-data notice gate
   The notice GATES the first search; it does not accompany it. That is
   behaviour, not styling, but this is the surface it is raised on.
--------------------------------------------------------------------------- */
.sheet-scrim {
  position: fixed; inset: 0; z-index: 20;
  background: var(--scrim);
  display: flex; align-items: flex-end; justify-content: center;
  animation: fade var(--dur) var(--ease);
}
.sheet {
  width: 100%;
  max-width: var(--col-max);
  max-height: 88vh;
  max-height: 88svh;   /* svh, never vh, on mobile — see spec-design §10.2 */
  /* ⚠️ hidden, NOT auto. The sheet used to be its own scroller, which put the
     agree control below several screens of legal text — findable only by
     scrolling to the end of the document it is asking about. The document
     scrolls (.sheet-scroll); the actions do not (.sheet-actions). */
  overflow: hidden;
  padding: 28px 24px calc(34px + env(safe-area-inset-bottom, 0px)) 24px;
  background: var(--surface);
  border-radius: var(--r-sheet) var(--r-sheet) 0 0;
  display: flex; flex-direction: column; gap: var(--gap-section);
  animation: sheet-up 240ms var(--ease);
}

/* The document. `min-height: 0` is load-bearing: a flex item's default
   `min-height: auto` refuses to shrink below its content, so without it the
   sheet grows past 88svh and the actions leave the screen — which is the whole
   defect this split exists to fix. */
.sheet-scroll {
  min-height: 0;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  /* Scrolling past the end of a long legal document must not chain out to the
     page behind the sheet. Without this, a flick that runs off the end of the
     notice scrolls the screen the reader was on — measured at 500px — and
     dismissing then returns them to a position they never chose. */
  overscroll-behavior: contain;
  display: flex; flex-direction: column; gap: var(--gap-section);
}

/* The two exits. `flex-shrink: 0` so a long document can never squeeze them.
   The hairline is what stops the primary button reading as the next paragraph
   of the document above it. Outside a .sheet this is an ordinary stack: the
   #/terms route renders the same fragment and must be unaffected. */
.sheet-actions {
  flex-shrink: 0;
  display: flex; flex-direction: column; gap: var(--gap-md);
  padding-top: var(--gap-section);
  border-top: 1px solid var(--hairline);
}

/* The sheet's own gutter is 24px, not the screen's 20px. */
@keyframes fade    { from { opacity: 0; } }
@keyframes sheet-up{ from { transform: translateY(24px); opacity: 0; } }

/* Native <dialog>, if a screen prefers it to a div. */
dialog.sheet { border: 0; margin: 0 auto auto; inset: auto 0 0; position: fixed; }
dialog.sheet::backdrop { background: var(--scrim); }


/* ---------------------------------------------------------------------------
   18. QR PLATE — a handoff, never a verdict
   ⚠️ The modules are BLACK and whatever sits behind them must be light and
   UNTINTED. The plate itself stopped being a white box on 2026-09-10 (it read
   as a second surface inside a card); the code now sits on the card's own warm
   paper, which is ~20:1 against black — far above what a scanner needs — and
   the plate's padding is kept as the QR quiet zone. A TINTED background (leaf,
   amber, tan) is still forbidden and still fails intermittently rather than
   obviously. See js/qr.js `buildPlate` for the full reasoning.
   The library also hard-codes width/height attributes; the rules below let the
   viewBox scale instead.
   The block must be tappable as well as scannable — the demo runs on a phone
   and an iPad and a reader may be holding the screen themselves.
--------------------------------------------------------------------------- */
/* ⚠️ THE GAP IS SMALL BECAUSE THE CODE BRINGS ITS OWN (2026-09-10). The svg
   carries a four-module quiet zone inside its viewBox — 16px of empty paper on
   every side — so a 10px flex gap above and below it measured as 26px of
   optical space and read as a card that had come apart (Haashim: "shorten the
   box a lot so less empty white space"). The gap is 8px and `.qr-plate` pulls
   back 6px of the zone it is already carrying. The quiet zone itself is NOT
   touched: it is inside the svg, it is what a scanner needs, and qr.js TRAP 1
   is the note about it. */
.qr-block {
  display: flex; flex-direction: column; align-items: center; gap: var(--gap-md);
  text-align: center;
}
.qr-block > .qr-link,
.qr-block > .qr-handoff { margin-block: -6px; }
/* `.qr-block` is `align-items: center`, so an auto-width button keeps its own
   width — but `.btn-pill` carries `flex-grow: 1` for the paired rows it was
   drawn for, and there is nothing to pair with here. */
.qr-block > .btn-pill { flex-grow: 0; }
/* A step smaller again (2026-09-10, Haashim: "does the button/text in it need to
   be that big?"). The card's action is a step below the screen's own primary
   buttons — "Show recommendations", "Find all voters at this address" — because
   it leaves the app, and it sits under an 18px title rather than at the head of
   a screen. ⚠️ `.btn-primary` keeps `min-height: var(--tap)`, so this trims the
   type and the horizontal padding and the 44px floor still holds: measured 44px
   exactly, which is the minimum and not a pixel under it. */
.qr-block > .btn-primary {
  padding: 10px 20px;
  font-size: var(--fs-emph);
}
.qr-link {
  display: block;
  min-width: var(--tap); min-height: var(--tap);
  border-radius: var(--r-field);
}
/* ⚠️ NO PADDING, AND THAT IS NOT THE QUIET ZONE GOING MISSING (2026-09-10).
   `js/qr.js` asks the library for `createSvgTag(CELL_SIZE_PX, MARGIN_PX)` with
   MARGIN_PX derived from a FOUR-MODULE quiet zone, so the margin is already
   inside the svg's own viewBox — 132px of modules inside a 164px square. The
   12px of plate padding on top of it was a SECOND quiet zone, and with the
   white box gone it read as the code floating in dead space (Haashim: "why is
   there so much empty space between title and QR code"). Read js/qr.js TRAP 1
   before changing MARGIN_PX; that one IS the quiet zone and removing it breaks
   scanning intermittently.
   `max-content` shrinks the plate to the code rather than scaling the code to
   the plate: the svg is pinned to its natural 164px by qr.js so the modules
   land on whole pixels, and a plate wider than that left the code adrift on
   whichever screen forgot to call `fitPlate` (this one did). */
.qr-plate {
  background: transparent;           /* was #ffffff; see the note above.      */
  padding: 0;
  width: max-content;
  max-width: 100%;
  display: block;
}
/* ⚠️ THE LIBRARY PAINTS ITS OWN WHITE BACKGROUND, AND THAT IS THE SECOND HALF
   OF REMOVING THE PLATE. `js/vendor/qrcode.js` emits
   `<rect width="100%" height="100%" fill="white">` before the module path, so
   clearing `.qr-plate`'s background alone left a white square exactly where the
   plate used to be (measured 2026-09-10 — the computed style said transparent
   and the screenshot still showed white). The modules are ONE `<path>`; the
   only `<rect>` in the markup is that background, which is why this selector
   cannot touch a module. Cleared here rather than in the vendored file so the
   library stays a verbatim copy, and in CSS rather than in JS so a stylesheet
   that fails to load falls back to the white square rather than to nothing. */
.qr-plate svg rect { fill: transparent; }
.qr-plate svg {
  display: block;
  width: 100%;
  height: auto;                      /* overrides the library's width="164px" */
  /* ⚠️ qr.js pins the svg to its own module width in an INLINE style, which beats
     the 100% above — so inside a plate wider than the code, the code sat against
     the left edge (measured 2026-09-10 on the live page: plate 250px, svg 164px
     at x+12). This centres it. Do not "fix" it by removing the inline width;
     that is what keeps the modules on whole pixels. */
  margin-inline: auto;
  shape-rendering: crispEdges;       /* no antialiased module edges           */
}
.qr-caption { font-size: var(--fs-meta); line-height: var(--lh-copy); color: var(--muted); }
/* The control on a handoff card (2026-09-10). Haashim: "the tap part is what's
   more important, so people know it's an option." It is a real button now — the
   green primary where the card owns the action, the quiet pill where the card
   is a signpost — and it sits ABOVE the code, because the person holding the
   phone cannot scan the screen they are holding.
   ⚠️ THE PRIMARY BUTTON IS FULL WIDTH, AND PINNING IT NARROWER MADE THE CARD
   TALLER. `.btn-primary` is width:100% everywhere else in this app; held to
   240px, "How to register in Washington" wrapped to two lines and the button
   went from 52px to 73px — a card 57px TALLER than the one it was meant to
   tighten. Measured 2026-09-10. The quiet pill keeps its own width, because it
   carries `flex-grow` for the paired rows it was drawn for and has nothing to
   pair with here. */
/* The line that says who the code is for. Sub-label size: it is an aside about
   the code above it, not a second instruction. */
.qr-block > .qr-caption { font-size: var(--fs-sub); }
/* Nothing else: `.qr-block`'s own 10px gap now sits directly between the title,
   the button, the caption, the code and the share control, because the plate no
   longer carries 12px of its own. */

/* The share control under a handoff — send the same URL instead of holding it
   up. The button and its one-line confirmation are a pair, so they sit in their
   own column with the tighter gap: the confirmation belongs to the button, not
   to the stack of captions above it.
   `.btn-pill` carries `flex-grow: 1` for the PAIRED rows it was drawn for.
   There is nothing to pair with here, so it is pinned back to its own width —
   the same reason householdButton() wraps its pill in a bare `.col`.
   The status line starts `hidden` (the `hidden` property, set from JS, never
   `display`), because `.qr-block` is a gapped flex column and an empty but
   visible paragraph would still eat a row. */
.qr-share {
  display: flex; flex-direction: column; align-items: center; gap: var(--gap-sm);
  max-width: 100%;
}
.qr-share > .btn-pill { flex-grow: 0; }
.qr-share__status { max-width: 32ch; }


/* ---------------------------------------------------------------------------
   19. LEGAL / NOTICE DOCUMENT (#/terms)
--------------------------------------------------------------------------- */
.doc { display: flex; flex-direction: column; gap: var(--gap-section); }
.doc h1 { font-size: var(--fs-title); }
.doc section { display: flex; flex-direction: column; gap: var(--gap-md); }
.doc h2 {
  font-family: var(--font-body);
  font-size: var(--fs-row); font-weight: 600;
  line-height: normal; color: var(--ink);
}
.doc p, .doc li {
  font-size: var(--fs-body); font-weight: 400;
  line-height: var(--lh-loose); color: var(--muted);
}
.doc ul { display: flex; flex-direction: column; gap: var(--gap-md); }
.doc li { display: flex; gap: var(--gap-lg); }
.doc li::before {
  content: ""; width: 5px; height: 5px; border-radius: var(--r-pill);
  background: var(--leaf); margin-top: 8px; flex-shrink: 0;
}
.doc .divider { margin-block: var(--gap-sm); }


/* ---------------------------------------------------------------------------
   20. PROGRESS AND PIPS
   Kept because the vocabulary is part of the system, but note the rule:
   a two-tone bar is a lie whenever anyone is unknown. Three zones or nothing.
   `.bar__unknown` is striped so it is visibly not a claim.
--------------------------------------------------------------------------- */
.bar {
  width: 100%; height: 10px;
  border-radius: var(--r-pill);
  background: var(--stone-50);
  display: flex; overflow: hidden;
}
.bar__voted   { background: var(--leaf); }
.bar__counted { background: var(--stone-50); }
.bar__unknown {
  background: repeating-linear-gradient(135deg,
              var(--stone-50) 0 4px, transparent 4px 8px);
}
.pips { display: flex; gap: 5px; }
.pip  { flex-grow: 1; height: 8px; border-radius: var(--r-pill); background: var(--stone-50); }
.pip--voted   { background: var(--leaf); }
.pip--unknown {
  background: repeating-linear-gradient(135deg,
              var(--stone-50) 0 4px, transparent 4px 8px);
}


/* ---------------------------------------------------------------------------
   21. RESPONSIVE
   Faithful at 402px. The column caps at 560px and the ground fills the rest.
   No device frame, no border, no shadow around the column.
--------------------------------------------------------------------------- */
@media (min-width: 600px) {
  /* Slightly more air above the fold once there is room for it, without
     touching the 20px gutter — that value is constant at every width. */
  .screen { padding-top: calc(env(safe-area-inset-top, 0px) + 32px); }
}

/* Phone landscape is ~390px tall and the fixed bar eats ~65px of it. That is
   accepted, and better than an inner scroller — but tighten the top inset so
   the title is not pushed off. */
@media (max-height: 460px) and (orientation: landscape) {
  .screen { padding-top: calc(env(safe-area-inset-top, 0px) + 12px); }
}

/* Very narrow phones (320px): keep everything inside the gutter. */
@media (max-width: 360px) {
  .card, .panel { padding-inline: 16px; }
  .screen { padding-inline: 16px; }
}


/* ---------------------------------------------------------------------------
   21b. THE BOOT NOTICE, LATE (2026-09-10)
   index.html ships "Circle Lite is starting" inside #app so a failed or slow
   module load is never a blank page. It was visible on EVERY open, because it
   was on screen from the first byte until main.js painted. Now it is held
   invisible for 1.5s: a normal load replaces it before it is ever seen, and a
   load that genuinely stalls still gets the sentence. The delay is an
   animation-delay, so section 22's reduced-motion rule (which shortens
   durations, not delays) leaves it intact. Painted from app.css rather than an
   inline style because the CSP admits no inline <style> element.
--------------------------------------------------------------------------- */
[data-boot-fallback] { opacity: 0; animation: boot-reveal 0s linear 1.5s forwards; }
@keyframes boot-reveal { to { opacity: 1; } }


/* ---------------------------------------------------------------------------
   22. MOTION
   Every transition and animation in this file is listed above and every one of
   them is suppressed here. Nothing in this app depends on motion to be
   understood.
--------------------------------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
  .skel-row::after { animation: none; opacity: 0.5; transform: none; }
}


/* ---------------------------------------------------------------------------
   23. PRINT — the demo may be screenshotted or printed to PDF for the deck.
   Drop the fixed bar so it does not stamp itself over the content.
--------------------------------------------------------------------------- */
@media print {
  .tabbar, .sheet-scrim { display: none !important; }
  .screen { max-width: none; padding-bottom: 24px; }
  .card, .row { box-shadow: none; border: 1px solid var(--hairline); }
}

/* ==========================================================================
   cityfilter — one button, and a sheet of cities behind it.

   This was a horizontally scrolling chip row. It failed on a real phone: four
   cities visible of 137, the rest off the right edge with no cue, and no reader
   swipes sideways on a vertical page. The replacement is one control that says
   what it does and a sheet that scrolls the way everything else does.

   RULE 1 still holds — no box-shadow anywhere in here.
   ========================================================================== */
.cityfilter {
  align-self: flex-start;
  min-height: var(--tap);
  padding-inline: 16px;
  border: 1px solid var(--hairline);
  border-radius: 999px;
  background: var(--surface);
  color: var(--ink);
  font-size: var(--fs-row); font-weight: 500;
  cursor: pointer;
}
/* A filter that is ON is the primary fill, so "something is narrowing this" is
   legible at a glance rather than by reading the label. */
.cityfilter--on {
  background: var(--green);
  border-color: var(--green);
  color: var(--on-green);
}

/* The filter and its escape, side by side. The escape is only ever drawn when
   something is actually narrowing the screen, so it never sits there as a
   control that does nothing. */
.filterbar {
  display: flex; align-items: center; gap: var(--gap-md);
  flex-wrap: wrap;
}
.filterbar__clear {
  min-height: var(--tap);
  padding-inline: 4px;
  border: 0;
  background: none;
  color: var(--green-deep);
  font-size: var(--fs-row); font-weight: 500;
  text-decoration: underline;
  text-underline-offset: 3px;
  cursor: pointer;
}

.sheet__title {
  margin: 0;
  font-family: var(--font-display);
  font-size: var(--fs-sheet);
  color: var(--ink);
}

.cityopts { display: flex; flex-direction: column; }

/* Sits above the list and scrolls with it. 16px comes from the form-control rule
   at the end of this file — below that iOS zooms the page on focus, which is
   exactly what a sheet must not do. */
.cityopts__search { width: 100%; margin-bottom: var(--gap-md); }

/* Full-width rows, not chips. A city is a line to run a thumb down, and the
   whole width is the target. */
.cityopt {
  display: flex; align-items: center;
  width: 100%;
  min-height: var(--tap);
  padding: 10px 4px;
  border: 0;
  border-bottom: 1px solid var(--hairline);
  background: none;
  color: var(--ink);
  font-size: var(--fs-row); font-weight: 400;
  text-align: start;
  cursor: pointer;
}
/* ⚠️ THE NAME TAKES THE SLACK AND THE COUNT KEEPS ITS PLACE. Every row carries a number now
   that the breakdown is counted rather than sampled, and the longest city string in the file is
   26 characters — without this the numbers wander in and out down the right edge and stop being
   comparable, which is the only thing they are there for. */
.cityopt__name  { flex: 1 1 auto; min-width: 0; }
.cityopt__count {
  flex: none;
  margin-inline-start: var(--gap-lg);
  font-size: var(--fs-sub);
  font-variant-numeric: tabular-nums;
  color: var(--muted);
}
/* The selected row goes bold; its number must not, or the count reads as the emphasised thing. */
.cityopt--on .cityopt__count { font-weight: 400; }

.cityopt:last-child { border-bottom: 0; }
.cityopt--on { font-weight: 600; }
.cityopt--clear { color: var(--green-deep); font-weight: 500; }
@media (hover: hover) { .cityopt:hover:not(:disabled) { background: var(--stone-22); } }

/* The tick box (2026-09-12, several cities). Bold alone said "chosen" when a tap
   closed the sheet; now a tap toggles and nothing closes, so the row needs a mark
   that reads as "ticked, and you can tick more". Drawn in CSS, not an icon, because
   cityfilter.js may import only dom.js and copy.js. */
.cityopt__box {
  flex: none;
  position: relative;
  width: 20px; height: 20px;
  margin-inline-end: var(--gap-md);
  border: 1.5px solid var(--muted);
  border-radius: 5px;
}
.cityopt--on .cityopt__box { background: var(--green); border-color: var(--green); }
.cityopt--on .cityopt__box::after {
  content: '';
  position: absolute;
  left: 5.5px; top: 1.5px;
  width: 5px; height: 10px;
  border: solid var(--on-green);
  border-width: 0 2px 2px 0;
  transform: rotate(45deg);
}
/* Past the ceiling an unticked row is inert; it dims so it does not read as broken. */
.cityopt:disabled { color: var(--muted); cursor: default; }
.cityopt:disabled .cityopt__box { opacity: 0.5; }

.cityopts__more {
  margin: var(--gap-sm) 0 0;
  font-size: var(--fs-body);
  color: var(--muted);
}

/* ==========================================================================
   THE VOTING RECORD — one row per election, all of them at once.

   ⚠️ THERE IS NO TOGGLE AND THERE MUST NOT BE ONE AGAIN. This card carried a
   collapsed pill over a hidden list; reported from a phone, looking at a real
   record, as saying the same thing as the VOTING RECORD heading above it and
   then costing a second tap to show what the first tap had already asked for.
   ========================================================================== */
.historylist { margin-top: var(--gap-sm); }

/* ⚠️ ALIGNED TO THE TOP, NOT CENTRED. These sentences wrap on a narrow phone
   and a centred glyph drifts to the middle of a two-line sentence, which reads
   as marking the second line. */
.historyrow { align-items: flex-start; }

/* The blank slot `notYetHeld` gets instead of a mark. It reserves the glyph's
   width so the sentences stay in one column — an unmarked row that shuffles
   left reads as a different KIND of row rather than as an unmarked one. */
.historyrow__blank { display: inline-block; width: 20px; height: 20px; }

/* ⚠️ 500, NOT the .status-line 600. One bold line under a heading is emphasis;
   seven bold lines in a stack is a wall, and the reader is scanning for the
   glyphs, not the prose.

   ⚠️ AND --muted, NOT --ink: this is the DEFAULT, and a confirmed vote opts out
   of it below. See the note on .historyrow__text--yes. */
.historyrow__text {
  font-weight: 500;
  line-height: var(--lh-body);
  color: var(--muted);
}

/* ⚠️ THE ONE EMPHASISED ROW SHAPE, AND IT MEANS "CONFIRMED", NOT "GOOD".
   Asked for on a real record: with every sentence at one weight, finding the
   elections somebody actually voted in meant reading seven lines and decoding a
   20px glyph on each. The request was a grey X inside the ring — but a cross
   asserts "this person did not vote", and `noBallotRecorded` only ever means a
   county has no ballot on file. Darkening the YES instead of marking the NO
   gets the same scan for free and makes no claim the data cannot carry.

   ⚠️ SO THE CONTRAST IS CARRIED BY THE POSITIVE ROW. Do not "balance" this by
   fading the others further or tinting them: --muted is 5.61:1 on ground and
   these rows are a real person's civic record, not chrome. The pairing a reader
   learns is leaf check + ink = confirmed; anything lighter = we were not told
   yes, with the glyph saying which kind of not-told-yes it is. */
.historyrow__text--yes {
  font-weight: 600;
  color: var(--ink);
}

/* ==========================================================================
   "DID YOU MEAN" — other names the reader's letters could have meant.

   ⚠️ ROWS, NOT A SCROLLING CHIP STRIP. The city filter started life as one and
   failed on a real phone: the row ran off the right edge with no cue anything
   was there, and "the idea of the scrolling toggles is smart, but it's not
   intuitive for most people". These wrap instead, so every name is reachable
   without a gesture anybody has to guess.
   ========================================================================== */
.suggest { margin-top: var(--gap-xl); }
.suggest__heading { margin: 0 0 var(--gap-md); }
.suggest__rows {
  display: flex; flex-wrap: wrap; gap: var(--gap-md);
}
.suggest__row {
  /* 44px, like every other tappable thing here. A name is a small target and
     these sit close together. */
  min-height: var(--tap);
  padding-inline: 14px;
  border: 1px solid var(--hairline);
  border-radius: 999px;
  background: var(--surface);
  color: var(--ink);
  font-size: var(--fs-row); font-weight: 500;
  cursor: pointer;
}
@media (hover: hover) { .suggest__row:hover { background: var(--stone-22); } }

/* ==========================================================================
   ⚠️ 16px ON ANYTHING A THUMB CAN FOCUS. iOS Safari ZOOMS THE PAGE when a
   focused form control computes to less than 16px, and there is no way to
   decline it from CSS — it is a reading aid, not a bug, and it fires on every
   tap into the search field. What the reader then gets is a page scrolled off
   both edges, a heading cut in half, and a name list they have to pinch out of
   before they can read it. Reported from a real phone with a screen recording,
   which is the only way this surfaces: no desktop browser does it and no test
   in this repo can see it.

   --fs-row is 15px and is right for ROWS. It is one pixel under the threshold,
   which is the whole defect. These override to --fs-lead (16px) rather than
   moving the token, so nothing else in the type scale shifts.

   ⚠️ DO NOT "FIX" THIS WITH `maximum-scale=1` OR `user-scalable=no` IN THE
   VIEWPORT META. That disables pinch-zoom for everybody, including readers who
   need it to read at all, to spare us one pixel — and iOS has ignored
   user-scalable since iOS 10, so it would not even work. The font size is the
   fix.
   ========================================================================== */
/* ⚠️ THE ATTRIBUTE SELECTORS ARE REPEATED ON PURPOSE, NOT BY COPY-PASTE. A bare
   `input` is specificity (0,0,1); the rule above sets 15px through
   `input[type="search"]`, which is (0,1,1) and beats it. Overriding only `input`
   here looks right, reads right, and leaves the search field at 15px — which is
   the one control this whole block exists for. Matched at equal specificity and
   placed last, so the cascade resolves on order. */
input, select, textarea,
input[type="text"], input[type="search"], input[type="email"], input[type="tel"],
.field,
.searchfield input,
.searchfield__input,
.t-input,
.citypick__input {
  font-size: var(--fs-lead);
}


/* ---------------------------------------------------------------------------
   23. HOME AND ABOUT — the 2026-09-10 redesign
   Home went from nine elements to five: a brand row, a seasonal headline, a
   search bar that IS the call to action, one countdown card, and one list card
   of secondary rows. About is a new fourth tab. Everything below is additive;
   nothing earlier in this file changed for it.
--------------------------------------------------------------------------- */
/* Brand row: the wordmark left, AMAC's logo right (owner feedback 2026-09-10:
   "There is no branding. Add AMAC logo on the top right"). The logo is a PNG
   with alpha under ./img/, so it sits on the paper without a white plate. */
.brand { display: flex; align-items: center; justify-content: space-between; gap: var(--gap-xl); }
/* The wordmark is a product name, not an eyebrow (2026-09-10): the display face
   at the card-title size, bold, in ink, with the mark in green beside it. Not
   as big as the headline under it — it names the product; the headline does
   the talking. The mark is all-filled, so it takes the green as fill. */
.wordmark {
  display: inline-flex; align-items: center; gap: 8px;
  font-family: var(--font-display);
  font-size: var(--fs-card); font-weight: 600; line-height: 1;
  color: var(--ink);
}
.wordmark svg { fill: var(--green-deep); stroke: var(--green-deep); }
/* The Home tab draws the same mark inline with fill="currentColor". Section 9
   recolours an active tab's svg STROKE and its label, but `.tab` itself keeps
   --muted as its colour, so a filled glyph stayed grey when lit. This paints
   the dots to match the label. */
.tab.is-active svg circle,
.tab[aria-current="page"] svg circle { fill: var(--green-deep); }

/* A link that looks like one (2026-09-10). The FAQ's "How to register" and
   "Find your county elections office", and the Picks page's ballot link, were
   13px muted text centred in a card — the caption register — and Haashim's
   review said nobody could tell they were tappable. This is a full-width
   tinted row, 44px, green words, chevron on the right: the same reading as the
   list rows on Home, without a card's elevation, because it sits INSIDE one. */
.btn-link {
  display: flex; align-items: center; justify-content: space-between; gap: var(--gap-lg);
  width: 100%; min-height: var(--tap);
  padding: 10px 14px 10px 16px;
  background: var(--leaf-14);
  border-radius: var(--r-field);
  color: var(--green-deep);
  font-size: var(--fs-body); font-weight: 600; line-height: var(--lh-body);
  transition: background var(--dur) var(--ease);
}
.btn-link > span { min-width: 0; }
.btn-link svg { stroke: var(--green-deep); }
@media (hover: hover) { a.btn-link:hover { background: var(--leaf-16); color: var(--green-deep); } }
.btn-link:active { transform: scale(0.995); }

/* The person card's recommendations box (2026-09-10). The Picks page draws one
   card per contest; here they are rows inside ONE card, separated by hairlines,
   because this card is itself one item in a column of cards about a person. */
.picks-list > li + li { border-top: 1px solid var(--hairline); padding-top: var(--gap-md); }

/* ---------------------------------------------------------------------------
   24. THE COUNTY LIST (2026-09-10)
   Thirty-nine counties were thirty-nine elevated cards — a page of boxes with
   two words in each, and a phone number that looked like a caption rather than
   something to press. Haashim: "I don't know if each county needs a full box
   background — find a way to make it more UX friendly, and also very clear you
   can actually press the numbers to directly call."
   So: ONE card, hairline rows, and the number is a call BUTTON with a handset.
--------------------------------------------------------------------------- */
.countylist { padding-block: 4px; gap: 0; }   /* the hairlines come from .card--list */
.countyrow {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--gap-lg); min-height: 52px; padding: 8px 0;
}
.countyrow__name { font-size: var(--fs-row); font-weight: 600; color: var(--ink); min-width: 0; }
/* A tel: link that reads as a control: the leaf tint, the handset, the number
   in green at tabular width so a column of them lines up. */
.callchip {
  display: inline-flex; align-items: center; gap: var(--gap-sm);
  flex-shrink: 0;
  min-height: var(--tap); padding: 0 14px;
  background: var(--leaf-14);
  border-radius: var(--r-pill);
  color: var(--green-deep);
  font-size: var(--fs-body); font-weight: 600;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  transition: background var(--dur) var(--ease);
}
.callchip svg { stroke: var(--green-deep); }
@media (hover: hover) { a.callchip:hover { background: var(--leaf-16); color: var(--green-deep); } }
.callchip:active { transform: scale(0.98); }
/* An extension pushes the longest chip past a 360px screen. Below that the row
   stacks: the name on its own line, the chip under it, still full-height. */
@media (max-width: 380px) {
  .countyrow { flex-direction: column; align-items: stretch; gap: var(--gap-sm); }
  .countyrow .callchip { justify-content: center; }
}
.brand__logo { display: block; height: 34px; width: auto; max-width: 40%; object-fit: contain; }

/* E1 (redesign PR5, 2026-09-13): Home's share icon, between the wordmark and
   the logo. A PLAIN grey glyph — no pill, no disc, no border — because it sits
   in the brand row and must not read as a second button competing with the
   search bar. The box stays 44px for the thumb (--tap); the negative inline
   margin lets the row lay it out at its ~30px visual width, so the icon costs
   the row 30px, not 44, and the logo keeps its 34px height at 320px wide.
   `.brand__end` groups it with the logo; the logo's 40% cap now applies inside
   the group, so the group itself carries no max-width. */
.brand { position: relative; }
.brand__end { display: inline-flex; align-items: center; gap: var(--gap-md); flex-shrink: 0; }
.brand__end > .brand__logo { max-width: none; }
.brand .sharebtn {
  align-self: auto;
  min-width: var(--tap); min-height: var(--tap);
  margin-inline: -7px;
  border: 0; border-radius: 999px;
  background: transparent;
  color: var(--muted);
}
.brand .sharebtn svg { stroke: var(--muted); }
@media (hover: hover) { .brand .sharebtn:hover svg { stroke: var(--ink); } }
/* The word would push the logo off a narrow row. Icon only below 420px, even
   with a mouse (a narrowed desktop window). */
@media (max-width: 420px) {
  .brand .sharebtn { padding-inline: 0; }
  .brand .sharebtn .sharebtn__label { display: none; }
}
@media (max-width: 360px) { .brand { gap: var(--gap-md); } }
/* "Link copied." hangs under the row, right-aligned beneath the icon, OUT of the
   flow: the row's height never changes when it shows, and nothing under it
   moves. It sits in the 16px section gap above the headline. */
.brand__status {
  position: absolute; top: 100%; right: 0;
  margin-top: 1px;
  white-space: nowrap;
  font-size: var(--fs-meta); font-weight: 600; line-height: 1.2;
  color: var(--green-deep);
}

/* F1 (redesign PR5): the recommendations title with its share icon top-right,
   the same `.sharebtn` the FAQ wears. The title wraps; the icon stays at the top
   of the row, level with the first line. */
.titlerow { display: flex; align-items: flex-start; justify-content: space-between; gap: var(--gap-lg); }
.titlerow > h1 { min-width: 0; }
.titlerow > .sharebtn { flex-shrink: 0; align-self: flex-start; }

/* The seasonal headline. 30px, two lines at most on a 360px phone. */
.t-hero {
  font-family: var(--font-display);
  font-size: 30px; font-weight: 600;
  line-height: 1.15; letter-spacing: -0.2px; color: var(--ink);
  text-wrap: balance;
}

/* The search bar as a call to action: a field-shaped LINK to #/look-up with the
   app's one green fill attached. It is not an input — Home fetches nothing and
   types nothing; tapping anywhere on it lands on Look up with the field focused
   and the keyboard up (dom.js `holdKeyboard`). The ellipsis keeps the placeholder on one line beside the
   button on the narrowest phones. */
.cta-search {
  display: flex; align-items: stretch;
  background: var(--surface);
  border: 1px solid var(--hairline);
  border-radius: var(--r-card);
  box-shadow: var(--shadow-card);
  overflow: hidden;
  color: inherit;
  transition: box-shadow var(--dur) var(--ease), transform var(--dur-fast) var(--ease);
}
.cta-search__field {
  flex: 1 1 auto; min-width: 0;
  display: flex; align-items: center; gap: var(--gap-lg);
  padding: 0 16px; min-height: 56px;
  color: var(--muted); font-size: var(--fs-row);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.cta-search__field svg { stroke: var(--stone-mark); }
.cta-search__go {
  flex: 0 0 auto;
  display: flex; align-items: center; gap: var(--gap-xs);
  padding: 0 18px; margin: 6px;
  border-radius: var(--r-field);
  background: var(--green); color: var(--on-green);
  font-size: var(--fs-row); font-weight: 600;
  transition: background var(--dur) var(--ease);
}
.cta-search__go svg { stroke: var(--on-green); }
@media (hover: hover) {
  .cta-search:hover { box-shadow: var(--shadow-card-hover); color: inherit; }
  .cta-search:hover .cta-search__go { background: var(--green-hover); }
}
.cta-search:active { transform: scale(0.995); }

/* The countdown: the numeral is the design's big green Literata figure, the
   word beside it sits on its baseline, and the election-day sentence gets its
   own row under them in ink (it read as a caption when it was muted). */
.count { display: flex; align-items: baseline; gap: var(--gap-lg); }
.count__n {
  font-family: var(--font-display);
  font-size: 54px; font-weight: 600; line-height: 1;
  color: var(--green); letter-spacing: -1px;
  font-variant-numeric: tabular-nums;
}
.count__w { font-size: var(--fs-row); font-weight: 600; color: var(--ink); }
.eday {
  display: flex; align-items: flex-start; gap: var(--gap-md);
  font-size: var(--fs-emph); line-height: var(--lh-body); color: var(--ink);
}
.eday svg { stroke: var(--green-deep); margin-top: 2px; }
.eday b { font-weight: 600; }

/* The countdown bar: TODAY on the left, election day on the right, ticks at the
   milestones still ahead, placed to scale.

   ⚠️ REDRAWN 2026-09-11, AND THE ANCHOR IS THE CHANGE. It used to run from the
   PREVIOUS election to this one, which pinned the ticks at fixed percentages for
   the whole season (80.2 / 91.2 / 100 for the November 2026 dates) and moved
   only the marker — into them. By late October the marker covered the
   registration tick and the "Today" tag had clamped over a column it did not
   belong to. Anchored at today the span SHRINKS as the season runs, so the ticks
   slide left and pull apart and the bar is clearest in the last fortnight.

   There is no fill: the whole track is time REMAINING, so there is no elapsed
   part to colour. There is no tag either — the numeral above already says how
   many days are left, and the tag was what collided with the row beneath.
   The bar carries no labels and is aria-hidden decoration; the rows below are
   the content. The track vocabulary is section 20's (--stone-50 rail).

   The 7px side padding is load-bearing: the marker is 14px and sits at left:0,
   and the election tick sits at 100%, so both would otherwise be clipped in
   half by the card's edge. */
.tl { position: relative; padding-left: 7px; padding-right: 7px; padding-bottom: 6px; }
.tl__track { position: relative; height: 6px; border-radius: var(--r-pill); background: var(--stone-50); }
.tl__mark {
  position: absolute; top: 50%;
  width: 8px; height: 8px; border-radius: var(--r-pill);
  background: var(--surface); border: 2px solid var(--stone-mark);
  transform: translate(-50%, -50%);
}
.tl__today {
  position: absolute; top: 50%; left: 0;
  width: 14px; height: 14px; border-radius: var(--r-pill);
  background: var(--green); border: 3px solid var(--surface);
  box-shadow: 0 0 0 1px var(--leaf);
  transform: translate(-50%, -50%);
}
/* Listed in section 22 by reference: the ticks slide when the clock moves, and
   collapse to 0.01ms under prefers-reduced-motion like every other transition
   in this file. */
.tl__mark { transition: left 600ms var(--ease); }

/* The dates rows: one line each — label, the date, and how far off it is.

   ⚠️ REDRAWN 2026-09-11 FROM A THREE-COLUMN GRID, and the reason is the whole
   point of the change. The columns were evenly spaced thirds while the ticks
   above them were placed to scale, so the two never lined up: on 2026-09-11 the
   "Today" marker sat at 41.8% of the track, which is where the middle column
   happened to start, and readers read down from "Today" into "Register by" and
   believed they had to register that day. A row cannot be misaligned with
   itself.

   `.dates__in` ("in 35 days") is the scale of time the bar was being asked to
   carry alone. A reader parses it faster than a dot's position, and it cannot
   be misread against anything. */
.dates { display: flex; flex-direction: column; }
.dates__row {
  display: grid;
  grid-template-columns: 1fr auto auto;
  align-items: baseline;
  column-gap: 12px;
  padding-block: 9px;
  border-top: 1px solid var(--hairline);
}
.dates__row:first-child { border-top: 0; }
.dates__label { font-size: var(--fs-body); line-height: 1.3; color: var(--muted); }
.dates__day   { font-size: var(--fs-emph); font-weight: 600; color: var(--ink); white-space: nowrap; font-variant-numeric: tabular-nums; }
.dates__in    {
  font-size: var(--fs-sub); color: var(--muted); white-space: nowrap;
  font-variant-numeric: tabular-nums; min-width: 62px; text-align: right;
}
.dates .is-past .dates__label,
.dates .is-past .dates__day,
.dates .is-past .dates__in { color: var(--green-deep); }

/* The two registration routes under one heading (2026-09-11). The heading is a
   row's label with no date of its own; the two routes indent under it so the
   METHOD is what separates them, which is the distinction that decides which
   deadline a reader actually gets. The group carries the hairline; its rows do
   not, so the pair reads as one block rather than three unrelated lines. */
.dates__group { border-top: 1px solid var(--hairline); }
.dates__grouphead {
  margin: 0; padding-block: 9px 0;
  font-size: var(--fs-body); line-height: 1.3; color: var(--muted);
}
.dates__row--sub { border-top: 0; padding-block: 4px; padding-left: 14px; }
.dates__row--sub:last-child { padding-bottom: 9px; }
.dates .is-past.dates__row--sub .dates__label { color: var(--green-deep); }

/* A list card: rows that navigate, one hairline between them, the row IS the
   control (spec-behaviour §10). The disc is a tinted 36px circle around an icon
   from icons.js — tan for the recommendations, leaf for questions and lookup. */
.card--list { padding-block: 2px; gap: 0; }
.lrow {
  display: flex; align-items: center; gap: var(--gap-xl);
  min-height: 52px; padding: 10px 0;
  color: inherit;
}
.card--list > li + li { border-top: 1px solid var(--hairline); }
.lrow__body { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
/* A row title is one line on a 360px phone beside a disc and a chevron, so it is
   the lead size (16px), not the section size the card titles use. */
.lrow h2 { font-size: var(--fs-lead); }
.lrow__note { font-size: var(--fs-meta); line-height: 1.4; color: var(--muted); }
.lrow > svg:last-child { stroke: var(--stone-mark); }
@media (hover: hover) { a.lrow:hover { color: inherit; } a.lrow:hover .t-section { color: var(--green-deep); } }
.disc {
  width: 36px; height: 36px; border-radius: var(--r-pill);
  display: flex; align-items: center; justify-content: center; flex-shrink: 0;
  background: var(--tan-14);
}
.disc svg { stroke: var(--roots-deep); }
.disc--leaf { background: var(--leaf-14); }
.disc--leaf svg { stroke: var(--green-deep); }

/* The one bold line About draws (`copy.about.sections[].emphasis`). `.doc p`
   is (0,1,1) and sets weight 400 and --muted on every paragraph in a document,
   so a bare `.t-emph` (0,1,0) loses to it; this (0,2,1) wins on specificity
   rather than on order. */
.doc p.t-emph { font-size: var(--fs-emph); font-weight: 600; color: var(--ink); }

/* ---------------------------------------------------------------------------
   25. DROP BOXES (2026-09-10)
   Where a ballot goes, under what is on it. Three rows, hairline-separated, in
   the same card shape `.picks-list` uses — this sits directly beneath the picks
   card on a person's page and the two must read as one column, not as two
   competing boxes.

   ⚠️ NOTHING HERE IS A LINK OR A TAP TARGET, and that is deliberate rather than
   unfinished. A drop box row is an address to read or copy, not a destination:
   a map link would hand the reader's location to a third party on a page whose
   whole promise is that it does not do that, and no map provider is in the
   Content-Security-Policy `connect-src` anyway. The one link in this section is
   the county elections office, which is the authority on the boxes themselves.
   --------------------------------------------------------------------------- */
.dropboxes__list > li + li {
  border-top: 1px solid var(--hairline);
  padding-top: var(--gap-md);
}

/* The place is what a reader scans for; the address is what they act on, so the
   place takes the ink and the address stays at --muted via `.t-meta`. */
.dropbox__place { font-weight: 600; }

/* The county's own note about its own box — the only thing in this data that can
   say "this one is in a lobby and shuts at five". Same muted weight as the
   address it sits under: it is context, not a warning, and 39 counties write it
   39 different ways, so it must not be styled as if we had classified it. */
.dropbox__note { font-style: italic; }


/* ---------------------------------------------------------------------------
   26. CONVERSATION PATH (redesign A4, 2026-09-13) — js/path.js
   An accordion drawn as a dotted vertical line of small icons, one per
   section. Each icon is a 22px disc with a 13px glyph on the line; the line
   runs from the bottom of one disc to the top of the next, through the disc
   centres (x = 11px into the 32px rail).

   ⚠️ OPEN STATE IS `.path__node.is-open`, SET BY JS. Never derive it with
   :has() — see path.js. `aria-expanded` and `[hidden]` move with the class.

   The card is a --surface card, so it carries the card shadow (rule 1). The
   icon disc is a tint mark, no shadow. Weights 600 and 400 only (rule 2).
--------------------------------------------------------------------------- */
.path {
  display: flex; flex-direction: column; gap: 12px;
  padding-left: 32px;
  margin: 0;
}
.path__node { position: relative; }

.path__icon {
  position: absolute; left: -32px; top: 17px;
  width: 22px; height: 22px; box-sizing: border-box;
  display: grid; place-items: center;
  border-radius: var(--r-pill);
  background: var(--ground);
  border: 1.5px solid var(--stone);
  z-index: 1;
  transition: background var(--dur) var(--ease), border-color var(--dur) var(--ease);
}
.path__icon svg { width: 13px; height: 13px; stroke: var(--green-deep); }
.path__node.is-open .path__icon {
  background: var(--leaf-14);
  border-color: var(--leaf);
}

/* The dotted line: from under this node's disc (17 + 22 = 39px) to the top of
   the next node's disc (the 12px gap plus that node's 17px offset = 29px below
   this node's bottom edge). Centred on the disc: -32 + 11 - 1 = -22px. */
.path__node:not(:last-child)::after {
  content: "";
  position: absolute; left: -22px; top: 39px; bottom: -29px;
  width: 2px;
  background: repeating-linear-gradient(to bottom, var(--stone) 0 3px, transparent 3px 7px);
}

.path__card { padding: 0; gap: 0; overflow: hidden; }
.path__heading { margin: 0; font-size: inherit; font-weight: inherit; }
.path__head {
  display: flex; align-items: center; gap: var(--gap-xl);
  width: 100%; min-height: 56px;
  padding: 14px 16px;
  background: none; border: 0;
  text-align: left; color: var(--ink);
  cursor: pointer;
}
.path__title {
  flex-grow: 1; min-width: 0;
  font-family: var(--font-display);
  font-size: 16px; font-weight: 600; line-height: 1.25;
}
.path__chev {
  stroke: var(--stone);
  transition: transform var(--dur) var(--ease);
}
.path__node.is-open .path__chev { transform: rotate(180deg); }
.path__body {
  padding: 0 16px 16px;
  display: flex; flex-direction: column; gap: var(--gap-lg);
}
.path__body[hidden] { display: none; }

/* Section 22 already collapses every transition; this names the path's own so
   a later edit to that blanket rule cannot quietly bring them back. */
@media (prefers-reduced-motion: reduce) {
  .path__icon, .path__chev { transition: none; }
}


/* ---------------------------------------------------------------------------
   27. THE PERSON PAGE REDESIGN (2026-09-13) — screens/person.js
   B4 voting grid, C1 recommendation tiers, D2 register row + sheet, G3
   questions card. Rules 1–5 hold: weights 400/600 only, no red, light only,
   tinted rows and chips carry no shadow, the questions card (a --surface
   card) keeps its shadow.
--------------------------------------------------------------------------- */

/* --- B4: the voting grid. One tinted row per year, headings once. -------- */
.vgrid { display: flex; flex-direction: column; gap: 5px; }
.vgrid__row {
  display: grid;
  grid-template-columns: 48px repeat(3, minmax(0, 1fr));
  align-items: center; justify-items: center;
  padding: 9px 10px;
  border-radius: 14px;
  background: var(--stone-22);           /* a tint: no shadow (rule 1)      */
}
/* No voted special on this record: the Special column is dropped (2026-09-14). */
.vgrid--two .vgrid__row { grid-template-columns: 48px repeat(2, minmax(0, 1fr)); }
.vgrid__row--head {
  background: none;
  padding: 0 10px 2px;
  font-size: var(--fs-sub); font-weight: 600; color: var(--muted);
}
.vgrid__row > :first-child { justify-self: start; }
.vgrid__colhead { display: flex; flex-direction: column; align-items: center; line-height: 1.2; }
.vgrid__colmonth { font-size: var(--fs-chip); font-weight: 400; min-height: 12px; }
.vgrid__year {
  font-size: var(--fs-body); font-weight: 600; color: var(--ink);
  font-variant-numeric: tabular-nums;
}
.vgrid__cell { display: flex; align-items: center; justify-content: center; gap: 4px; min-height: 22px; }
/* "Upcoming": an election on the calendar that has not happened. Muted text,
   not a mark, so it cannot be read as a result. */
.vgrid__upcoming { font-size: var(--fs-sub); color: var(--muted); }
/* The folded pre-registration row: year range, then one sentence across the
   remaining columns instead of a line of glyphs. */
.vgrid__row--folded { grid-template-columns: 48px minmax(0, 1fr); }
.vgrid--two .vgrid__row--folded { grid-template-columns: 48px minmax(0, 1fr); }
.vgrid__folded { display: flex; align-items: center; min-height: 22px;
  font-size: var(--fs-sub); color: var(--muted); }
.vgrid__legend {
  display: flex; flex-wrap: wrap; gap: 4px 12px;
  font-size: var(--fs-sub); color: var(--muted);
}
.vgrid__legend > span { display: inline-flex; align-items: center; gap: 4px; }
.vgrid__legend .icon { width: 14px; height: 14px; }

/* --- C1's tier groups were replaced by the voter-guide list (section 28). -- */
.races-near {
  font-size: var(--fs-sub); color: var(--muted);
  border-top: 1px solid var(--hairline); padding-top: var(--gap-lg);
}

/* --- D2: the register row under the household list. ---------------------- */
.qr-row { display: flex; align-items: center; gap: var(--gap-xl); }
.qr-thumb {
  flex: 0 0 auto;
  width: 58px; height: 58px;
  min-width: var(--tap); min-height: var(--tap);
  display: grid; place-items: center;
  border-radius: 8px;
  background: var(--surface);             /* untinted: a code sits on paper */
}
.qr-thumb .qr-plate, .qr-thumb svg { width: 58px; }
.qr-row__text { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.qr-row__title { font-size: var(--fs-emph); font-weight: 600; color: var(--ink); }
.qr-row__link { font-size: var(--fs-meta); font-weight: 600; color: var(--green-deep); }
.qr-sheet { align-items: center; text-align: center; gap: var(--gap-lg); }
.qr-sheet > .btn-primary { align-self: stretch; }
/* The row's share button (2026-09-14): a 44px disc on the paper colour, so it
   reads as a control against the card without a border or a shadow. */
.qr-row__sharebtn {
  flex: 0 0 auto;
  width: var(--tap); height: var(--tap);
  display: grid; place-items: center;
  border-radius: var(--r-pill);
  background: var(--ground);
  color: var(--green-deep);
  cursor: pointer;
}
.qr-row__sharebtn svg { stroke: currentColor; }
.qr-row__status {
  align-self: flex-end; text-align: right;
  font-size: var(--fs-meta); font-weight: 600;
  color: var(--green-deep);
}
/* The sheet's share pill sits on --surface, so it takes the ground colour. */
.qr-sheet__sharewrap { align-items: stretch; }
.qr-sheet__share { background: var(--ground); }
.qr-sheet__share svg { stroke: var(--green-deep); }

/* --- G3: questions about this record. ------------------------------------ */
.qcard { padding-block: 2px; gap: 0; }
.qitem + .qitem { border-top: 1px solid var(--hairline); }
.qitem__heading { margin: 0; font-size: inherit; font-weight: inherit; }
.qitem__head {
  display: flex; align-items: center; gap: var(--gap-xl);
  width: 100%; min-height: 52px; padding: 12px 0;
  text-align: left; color: var(--ink);
}
/* A step up (Haashim, 2026-09-14: "increase the size slightly"): questions 16px,
   answers 15px. `.qitem__body p` outranks `.t-body` (13px) on specificity. */
.qitem__q { flex: 1 1 auto; min-width: 0; font-size: var(--fs-lead); font-weight: 600; line-height: 1.35; }
.qitem__body p { font-size: var(--fs-row); line-height: var(--lh-copy); }
.qitem__head svg { stroke: var(--stone); transition: transform var(--dur) var(--ease); }
.qitem__head[aria-expanded="true"] svg { transform: rotate(180deg); }
.qitem__body { display: flex; flex-direction: column; gap: var(--gap-md); padding-bottom: 12px; }
.qitem__body[hidden] { display: none; }
.qlink {
  display: inline-flex; align-items: center; gap: var(--gap-sm);
  align-self: flex-start;
  min-height: var(--tap); padding: 0 4px;
  font-size: var(--fs-emph); font-weight: 600; color: var(--green-deep);
}
.qlink svg { stroke: var(--green-deep); }
@media (prefers-reduced-motion: reduce) {
  .qitem__head svg { transition: none; }
}

/* A candidate's statement and campaign website, set in ACT (migration 0086; slate.js candidateExtras).
   Stacked under the name; the link keeps this page's own link colour and a 44px-tall hit area. */
.candidate-statement { display: block; line-height: 1.4; }
.candidate-website {
  display: inline-flex; align-items: center; min-height: 44px;
  font-weight: 600; text-decoration: underline; text-underline-offset: 2px;
}
.race-line__names .candidate-statement,
.race-line__names .candidate-website { display: block; min-height: 0; padding: 2px 0; }


/* ---------------------------------------------------------------------------
   28. THE VOTER-GUIDE LIST (2026-09-25) — js/slate.js, screens/ballot.js,
   screens/person.js. The clickable prototypes Haashim approved: a summary
   card, races grouped by level, a chip per candidate naming AMAC's position,
   the completeness panel, Share. Sizes are the prototype's (larger than the
   2026-09-10 card type, on purpose: this is the page people read on a phone
   in a lobby). Weights stay 400/600 (rule 2): the prototype's 700s clamp.
--------------------------------------------------------------------------- */
.picks-subtitle { font-size: var(--fs-lead); line-height: var(--lh-copy); color: var(--body2); }

.picks-layout { display: flex; flex-direction: column; gap: 18px; }
.picks-layout > * { min-width: 0; }

.picks-summary { gap: var(--gap-lg); }
.picks-summary__eyebrow {
  font-size: var(--fs-meta); font-weight: 600; letter-spacing: 0.06em;
  text-transform: uppercase; color: var(--muted);
}
.picks-summary__count {
  font-family: var(--font-display); font-size: 22px; font-weight: 600;
  line-height: 1.2; color: var(--ink);
}
.picks-summary__addr { font-size: var(--fs-emph); color: var(--muted); overflow-wrap: anywhere; }
.picks-meta { font-size: var(--fs-emph); color: var(--muted); overflow-wrap: anywhere; }

.places__label { font-size: var(--fs-emph); font-weight: 600; color: var(--ink); }
.places__chips { display: flex; flex-wrap: wrap; gap: 6px; }
.place-chip {
  font-size: var(--fs-body); line-height: 1.3; color: var(--ink);
  padding: 4px 10px; border-radius: var(--r-pill); background: var(--place-chip-bg);
}

.legend { display: flex; flex-direction: column; gap: var(--gap-md); }
.legend__toggle {
  align-self: flex-start; min-height: 36px; padding: 0;
  background: none; font-size: var(--fs-emph); font-weight: 600; color: var(--green-deep);
  cursor: pointer;
}
.legend__title { display: none; font-size: var(--fs-emph); font-weight: 600; color: var(--ink); }
.legend__rows { display: flex; flex-direction: column; gap: var(--gap-md); }
.legend__row { display: flex; align-items: center; gap: var(--gap-md); font-size: var(--fs-emph); color: var(--body2); }

/* The chips. Pills with a 1.5px edge, as drawn. */
.pick-chip {
  flex-shrink: 0; align-self: center;
  font-size: var(--fs-body); font-weight: 600; line-height: 1.2; white-space: nowrap;
  padding: 4px 10px; border-radius: var(--r-pill); border: 1.5px solid;
}
.pick-chip--endorsed    { background: var(--pick-end-bg);  border-color: var(--pick-end-border); color: var(--pick-end-ink); }
.pick-chip--recommended { background: var(--pick-rec-bg);  border-color: var(--pick-end-border); color: var(--pick-rec-ink); }
.pick-chip--dual        { background: var(--pick-dual-bg); border-color: var(--pick-dual-ink);   color: var(--pick-dual-ink); }

.picks-groups { display: flex; flex-direction: column; gap: 18px; }
.picks-group { display: flex; flex-direction: column; gap: var(--gap-md); }
.picks-group__head { display: flex; flex-direction: column; gap: 2px; }
.picks-group__title { font-family: var(--font-display); font-size: var(--fs-card); font-weight: 600; line-height: 1.2; color: var(--ink); }
.picks-group__note { font-size: var(--fs-emph); color: var(--muted); }
.picks-group__card {
  display: flex; flex-direction: column;
  background: var(--surface); border: 1px solid var(--hairline);
  border-radius: 14px; box-shadow: var(--shadow-card);
}

.race-row { display: flex; flex-direction: column; gap: var(--gap-lg); padding: 14px 16px; }
.race-row + .race-row { border-top: 1px solid var(--hairline); }
.race-row__head { display: flex; flex-direction: column; gap: 2px; }
.race-row__office { font-family: var(--font-body); font-size: var(--fs-emph); font-weight: 500; line-height: 1.35; color: var(--muted); }
.race-row__seat { font-size: var(--fs-emph); color: var(--muted); line-height: 1.35; }
.race-row__picks { display: flex; flex-direction: column; gap: var(--gap-lg); }
.race-row__note { font-size: var(--fs-emph); line-height: 1.4; color: var(--body2); }

.pick-lines { display: flex; flex-direction: column; gap: var(--gap-lg); }
.pick-line { display: flex; align-items: center; gap: var(--gap-lg); }
.pick-line__ini { display: none; }
.pick-line__who { display: flex; flex-direction: column; gap: 2px; flex-grow: 1; min-width: 0; }
.pick-line__name { font-size: 17px; font-weight: 600; line-height: 1.3; color: var(--ink); }

/* The completeness panel (slate.js completenessPanel): mandated first and last. */
.picks-complete { background: var(--quiet-bg); }
.picks-complete__title { font-size: var(--fs-row); font-weight: 600; color: var(--ink); }
.picks-complete__body { font-size: var(--fs-row); line-height: var(--lh-body); color: var(--body2); }

.picks-share__button { border-radius: var(--r-pill); font-size: 17px; }
.picks-share__status { font-size: var(--fs-emph); color: var(--muted); text-align: center; }

.picks-notyet { padding: 20px; }
.picks-notyet .t-card-title, .picks-notyet__title {
  font-family: var(--font-display); font-size: 22px; font-weight: 600; line-height: 1.2; color: var(--ink);
}
.picks-notyet__body { font-size: var(--fs-lead); line-height: var(--lh-copy); color: var(--ink); }

.picks-tips { margin: 4px 0 0; padding-left: 20px; list-style: disc; font-size: var(--fs-body); line-height: 1.6; color: var(--body2); }

/* The person card's compact list: a level label over its names and chips. */
.compact-picks__meta { font-size: var(--fs-body); color: var(--muted); }
.compact-picks__group { display: flex; flex-direction: column; gap: var(--gap-sm); padding-top: var(--gap-lg); border-top: 1px solid var(--hairline); }
.compact-picks__label {
  font-size: var(--fs-sub); font-weight: 600; letter-spacing: 0.06em;
  text-transform: uppercase; color: var(--muted);
}
.compact-picks .pick-lines { gap: var(--gap-md); }
.compact-picks .pick-line__name { font-size: var(--fs-row); }
.compact-picks .pick-chip { font-size: var(--fs-meta); padding: 3px 8px; }
.picks-notyet__title { font-size: var(--fs-card); }
.picks .picks-notyet { padding: 16px; }
.picks .picks-notyet__body { font-size: var(--fs-row); }


/* ---------------------------------------------------------------------------
   29. THE WEBSITE GUIDE — look C (2026-09-25)
   `?guide=1` puts data-look="guide" on <html> (js/guide.js, main.js). This
   block is the whole re-skin: tokens, the header that replaces the tab bar,
   and the two-column desktop layout. No screen knows which look it is in.

   Look C contrast, measured:
     --ink   #18203A on ground #EEF1F6 14.4   surface 16.0
     --muted #5A6275 on ground         5.6    surface 6.2
     --body2 #3A4258 on ground         9.2
     primary #22408C: white on it 9.3; as text on ground 8.3, on the chip tint 8.0
     endorsed #1A5A4B on #E3F1EC 7.2; dual #22408C on #E7ECF7 7.6
--------------------------------------------------------------------------- */
:root[data-look="guide"] {
  --ground:      #EEF1F6;
  --surface:     #FFFFFF;
  --hairline:    #DDE3EE;
  --ink:         #18203A;
  --muted:       #5A6275;
  --body2:       #3A4258;
  --green:       #22408C;
  --green-deep:  #22408C;
  --green-hover: #182F6A;
  --leaf:        #22408C;
  --leaf-14:     rgba(34,64,140,0.08);
  --leaf-15:     rgba(34,64,140,0.10);
  --leaf-16:     rgba(34,64,140,0.12);
  --stone-22:    rgba(34,64,140,0.06);
  --stone-36:    rgba(34,64,140,0.08);
  --amber-16:    rgba(138,91,6,0.10);
  --surface-92:  rgba(255,255,255,0.92);
  --place-chip-bg: rgba(34,64,140,0.08);
  --quiet-bg:      rgba(34,64,140,0.06);
  --pick-end-bg:     #E3F1EC;
  --pick-end-border: #237563;
  --pick-end-ink:    #1A5A4B;
  --pick-rec-bg:     #FFFFFF;
  --pick-rec-ink:    #1A5A4B;
  --pick-dual-bg:    #E7ECF7;
  --pick-dual-ink:   #22408C;
  --r-card:  16px;
  --r-field: 12px;
  --font-display: 'Libre Franklin', system-ui, -apple-system, 'Segoe UI', Roboto, Arial, sans-serif;
}
[data-look="guide"] .card { border: 1px solid var(--hairline); }
[data-look="guide"] .btn-primary,
[data-look="guide"] .picks-share__button { border-radius: 14px; }
[data-look="guide"] .field { border: 2px solid #C4CCDA; height: 56px; font-size: 18px; }
[data-look="guide"] .screen--plain,
[data-look="guide"] .screen--tabs { padding-bottom: var(--pad-bottom-plain); }

/* The header that stands where Circle Lite's tab bar would. */
.guidebar { background: var(--surface); border-bottom: 1px solid var(--hairline); }
.guidebar__inner {
  max-width: var(--col-max); margin-inline: auto;
  display: flex; align-items: center; justify-content: space-between; gap: var(--gap-xl);
  min-height: 56px; padding: 10px var(--gutter);
  padding-top: calc(env(safe-area-inset-top, 0px) + 10px);
}
.guidebar__title { font-family: var(--font-display); font-size: 19px; font-weight: 600; color: var(--ink); }
.guidebar__logo { height: 28px; width: auto; }
[data-look="guide"] .screen { padding-top: 20px; }

/* Wide screens (design A2, Haashim 2026-09-25): ONE centred column. The strip, the note and the
   picks run straight down under the search -- the picks are what people came for, so nothing sits
   beside them for the eye to land on first. Same DOM as the phone. */
@media (min-width: 1024px) {
  :root[data-look="guide"] { --col-max: 1040px; --gutter: 40px; }
  [data-look="guide"] .picks-screen > form {
    flex-direction: row; align-items: center; gap: var(--gap-xl);
  }
  [data-look="guide"] .picks-screen > form .field { flex: 1 1 auto; height: 52px; }
  [data-look="guide"] .picks-screen > form .btn-primary { width: auto; flex-shrink: 0; white-space: nowrap; padding: 0 26px; min-height: 52px; }
  [data-look="guide"] .picks-subtitle { max-width: 640px; }
  [data-look="guide"] .t-title--tight { font-size: 34px; }

  [data-look="guide"] .picks-layout { gap: 16px; }
  [data-look="guide"] .picks-groups { gap: 26px; padding-top: 6px; }
  [data-look="guide"] .picks-layout > .picks-share { align-self: center; width: 340px; padding-top: 6px; }

  /* The strip: election and count | address and places | the tag. */
  [data-look="guide"] .picks-strip {
    grid-template-columns: auto minmax(0, 1fr) auto;
    grid-template-areas: "eyebrow where tag" "count where tag";
    column-gap: 22px; padding: 14px 22px;
  }
  [data-look="guide"] .picks-strip__where {
    border-top: 0; padding-top: 0; border-left: 1px solid var(--hairline); padding-left: 22px; align-self: stretch;
    justify-content: center;
  }
  [data-look="guide"] .picks-strip__tag { align-self: center; }

  /* The note: what it says on the left, the key on the right. */
  [data-look="guide"] .picks-intro {
    display: grid; grid-template-columns: minmax(0, 1fr) auto; column-gap: 24px; row-gap: 6px;
    padding: 16px 22px;
  }
  [data-look="guide"] .picks-intro > * { grid-column: 1; }
  [data-look="guide"] .picks-intro > .picks-intro__key {
    grid-column: 2; grid-row: 1 / span 3; flex-direction: column; align-items: flex-end; align-self: center;
  }
  [data-look="guide"] .picks-intro > .picks-intro__more { grid-column: 1 / -1; }
  [data-look="guide"] .picks-summary { padding: 22px; }
  [data-look="guide"] .picks-summary__count { font-size: 26px; }
  [data-look="guide"] .legend__toggle { display: none; }
  [data-look="guide"] .legend__body[hidden] { display: flex; }
  [data-look="guide"] .legend__body { display: flex; flex-direction: column; gap: var(--gap-lg); padding-top: 14px; border-top: 1px solid var(--hairline); }
  [data-look="guide"] .legend__title { display: block; }

  [data-look="guide"] .picks-group__head { flex-direction: row; align-items: baseline; gap: var(--gap-xl); }
  [data-look="guide"] .picks-group__title { font-size: 24px; }
  [data-look="guide"] .picks-group__card { border-radius: 16px; }
  [data-look="guide"] .race-row {
    display: grid; grid-template-columns: 260px minmax(0, 1fr); gap: 20px; padding: 16px 22px;
  }
  [data-look="guide"] .race-row__head { flex-direction: column; align-items: flex-start; gap: 3px; }
  [data-look="guide"] .race-row__office { font-size: var(--fs-row); }
  [data-look="guide"] .race-row__seat { font-size: var(--fs-row); }
  [data-look="guide"] .pick-line { gap: var(--gap-xl); }
  [data-look="guide"] .pick-line__ini {
    display: flex; align-items: center; justify-content: center; flex-shrink: 0;
    width: 36px; height: 36px; border-radius: 50%;
    background: #E7ECF7; color: #182F6A; font-size: var(--fs-body); font-weight: 600;
  }
  [data-look="guide"] .pick-line__name { font-size: 18px; }
  [data-look="guide"] .race-row__note { padding-left: 48px; }
}

/* Person card, 2026-09-25: each race's ballot wording over its candidates, the two-candidate note
   under them, and a QUIET share button that does not outweigh the picks. */
.pick-race { font-size: var(--fs-emph); font-weight: 500; color: var(--muted); padding-top: 8px; }
.pick-race:first-child { padding-top: 0; }
.pick-race__note { font-size: var(--fs-emph); color: var(--muted); }
.picks-share__button--quiet {
  display: flex; align-items: center; justify-content: center; gap: var(--gap-md);
  width: 100%; min-height: var(--tap); padding: 12px 16px;
  background: transparent; border: 1.5px solid var(--green); border-radius: var(--r-pill);
  color: var(--green-deep); font-family: var(--font-body); font-size: var(--fs-row); font-weight: 600;
  cursor: pointer;
}
.picks-share__button--quiet svg { stroke: var(--green-deep); width: 20px; height: 20px; }
@media (hover: hover) { .picks-share__button--quiet:hover { background: var(--leaf-14); } }
[data-look="guide"] .picks-share__button--quiet { border-color: var(--green); }

/* 2026-09-25: the COMPACT note above every list of picks (picksIntro). */
.picks-intro { background: var(--quiet-bg); padding: 14px 16px; }
.picks-intro__title { font-size: var(--fs-row); font-weight: 700; color: var(--ink); }
.picks-intro__link { display: inline-flex; align-items: center; gap: 4px; align-self: flex-start;
  min-height: 32px; font-size: var(--fs-emph); font-weight: 600; color: var(--green-deep); }
.picks-intro__link svg { width: 16px; height: 16px; stroke: currentColor; }
.picks-intro__body { font-size: var(--fs-emph); line-height: 1.4; color: var(--body2); }
.picks-intro__key { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 14px; padding-top: 2px; }
.picks-intro__chips { display: flex; flex-wrap: wrap; gap: 8px 6px; }
/* Each li is as tall as its pill, so a wrapped row never touches the one above (Haashim,
   2026-09-25: the li kept a 20px line box and the 26px pills overlapped on the website). A
   notch smaller than a race's pills, so all three fit on one line in the guide's 308px column. */
.picks-intro__chips > li { display: flex; }
.picks-intro .pick-chip { font-size: var(--fs-meta); padding: 3px 8px; }
.picks-intro__toggle { align-self: flex-start; min-height: 32px; padding: 0; background: none; border: 0; font: inherit; font-size: var(--fs-emph); font-weight: 600; color: var(--green-deep); cursor: pointer; }
.picks-intro__more { display: flex; flex-direction: column; gap: 6px; font-size: var(--fs-emph); line-height: 1.4; color: var(--body2); }
.picks-intro__more[hidden] { display: none; }

/* 2026-09-25: section headings as the ballot's bands (Federal, State, County, State Supreme Court...),
   SOFTENED (Haashim: the solid dark band was harsh on the warm paper): a light tint of the ink with
   ink text, so they still anchor the eye without shouting. */
.picks-group__head { background: var(--band-bg); padding: 9px 14px; border-radius: 10px; }
.picks-group__title { color: var(--ink); font-size: 19px; }
.picks-group__note { color: var(--muted); }
.compact-picks__label {
  background: var(--band-bg); color: var(--ink); padding: 6px 10px; border-radius: 8px;
  letter-spacing: 0.04em;
}
:root { --band-bg: rgba(21, 42, 29, 0.07); }
[data-look="guide"] { --band-bg: rgba(34, 64, 140, 0.09); }

/* Person card, "Who should they vote for?": the notice, the top note and the section bands reach
   10px into the card's 16px padding with 10px inside, so their text starts on the same line as
   the race titles and names under them (Haashim, 2026-09-25: "STATE" and "This election has
   already been held" sat 10-20px right of "Justice Position No. 1"). */
.path__body .picks .picks-notice,
.path__body .picks .picks-intro,
.path__body .compact-picks__label { margin-inline: -10px; padding-inline: 10px; }

/* The website guide's summary strip (slate.js picksStrip, design A2). On a phone, a small card: the
   election and the tag on one line, the count, then a hairline and where the address votes. The
   wide layout (the 1024px block above) turns it into one row. */
.picks-strip {
  display: grid; grid-template-columns: minmax(0, 1fr) auto;
  grid-template-areas: "eyebrow tag" "count count" "where where";
  column-gap: 10px; row-gap: 6px; padding: 18px 20px; align-items: center;
}
.picks-strip__eyebrow { grid-area: eyebrow; }
.picks-strip__tag {
  grid-area: tag; justify-self: end; white-space: nowrap;
  font-size: var(--fs-meta); font-weight: 600; padding: 3px 10px; border-radius: 6px;
  background: #F6EBD3; color: #7A4F05;
}
.picks-strip__count { grid-area: count; font-size: 24px; }
.picks-strip__where {
  grid-area: where; display: flex; flex-direction: column; gap: 4px;
  border-top: 1px solid var(--hairline); padding-top: 12px; margin-top: 6px;
}
.picks-strip__addr { font-size: var(--fs-row); font-weight: 600; color: var(--ink); overflow-wrap: anywhere; }
.picks-strip__places { font-size: var(--fs-emph); line-height: 1.55; color: var(--muted); }

/* The website guide on a phone (design A2): more room between the blocks and inside each one, so
   the page reads as separate pieces rather than one run. */
@media (max-width: 1023.98px) {
  :root[data-look="guide"] { --gutter: 20px; }
  [data-look="guide"] .picks-layout { gap: 24px; }
  [data-look="guide"] .picks-groups { gap: 28px; }
  [data-look="guide"] .picks-intro { padding: 18px 20px; gap: 12px; }
  [data-look="guide"] .picks-intro__chips { gap: 8px; }
  [data-look="guide"] .race-row { padding: 18px; gap: 10px; }
  [data-look="guide"] .picks-group__head { padding: 10px 16px; border-radius: 12px; }
}

/* ============================================================================
   35. UNIFIED LOOK — Circle Lite in amacouncil.org's colours (Haashim, 2026-09-26)
   ----------------------------------------------------------------------------
   One look for Circle Lite and the website guide: AMAC navy frames every screen
   (the app bar and a title band), crimson is reserved for the ONE main action on
   a screen, the ground is a cool light grey with white cards, and Libre Franklin
   is the only typeface. Approved in the clickable prototype
   (claude.ai/artifact/W9JfYvsfmR7UYLdePpQoRa): header "Left mark", label style
   "Solid", "Make sure your vote counts".

   ⚠️ THIS SECTION DELIBERATELY BREAKS TWO OLD RULES FROM THE FILE HEADER, AND
   ONLY THESE: rule 2 (weights 400/600 only) — titles are 700/800 now, as on the
   website — and the olive palette. Rules 1, 3, 4 and 5 still hold: tints carry
   no shadow, there is no alarm red, the ring/alert/check meanings are unchanged,
   and the page is light only.
   ============================================================================ */
@font-face {
  font-family: 'Libre Franklin';
  font-style: normal;
  font-weight: 400 800;
  font-display: swap;
  src: url('./fonts/LibreFranklin-Variable-latin.woff2') format('woff2');
}

:root,
:root[data-look="guide"] {
  --navy:        #0E2A75;
  --navy-deep:   #0A1F57;
  --navy-tint:   #E4E8F3;
  --navy-line:   #B7C1DA;
  --crimson:     #B31942;
  --crimson-hover: #9A1538;
  --voted:       #1E7A4C;   /* the "Voted" check. green is status, never brand. */

  --ground:      #F2F3F6;
  --surface:     #FFFFFF;
  --hairline:    #E3E3E8;
  --ink:         #14172B;   /* 16.9:1 on --ground */
  --muted:       #4B5064;   /* 7.3:1 on --ground, 7.9:1 on white */
  --body2:       #3A4258;
  --green:       #0E2A75;   /* the old "primary fill" token now means AMAC navy */
  --green-deep:  #0E2A75;
  --green-hover: #0A1F57;
  --leaf:        #1E7A4C;
  --on-green:    #FFFFFF;
  --clay:        #B4480F;   /* "Not voted": burnt orange, never read as AMAC crimson */
  --stone:       #C9CDD8;
  --stone-mark:  #6B7186;
  --leaf-14:     rgba(30,122,76,0.10);
  --leaf-15:     rgba(14,42,117,0.10);
  --leaf-16:     rgba(30,122,76,0.12);
  --stone-22:    rgba(75,80,100,0.07);
  --stone-36:    rgba(75,80,100,0.10);
  --stone-50:    rgba(75,80,100,0.16);
  --ink-06:      rgba(20,23,43,0.06);
  --ink-08:      rgba(20,23,43,0.08);
  --scrim:       rgba(10,20,50,0.45);
  --surface-92:  rgba(255,255,255,0.96);
  --surface-60:  rgba(255,255,255,0.70);
  --shadow-card: 0 1px 2px rgba(20,23,43,0.05);
  --shadow-card-hover: 0 2px 8px rgba(20,23,43,0.08);
  --shadow-bar:  0 8px 24px rgba(10,31,87,0.16);
  --r-card:  16px;
  --r-tile:  14px;
  --r-field: 12px;
  --r-sheet: 22px;
  --place-chip-bg: rgba(14,42,117,0.07);
  --quiet-bg:      rgba(14,42,117,0.05);
  --font-display: 'Libre Franklin', system-ui, -apple-system, 'Segoe UI', Roboto, Arial, sans-serif;
  --font-body:    'Libre Franklin', system-ui, -apple-system, 'Segoe UI', Roboto, Arial, sans-serif;
  --pad-top: 0px;           /* the app bar and the title band own the top now */
}
html, body { background: var(--ground); }
:focus-visible { outline-color: var(--navy); }

/* ---- the app bar (main.js installAppbar) ----------------------------------- */
.appbar {
  background: var(--navy); color: #fff;
  padding-top: env(safe-area-inset-top, 0px);
}
.appbar__inner {
  max-width: var(--col-max); margin-inline: auto;
  padding-inline: var(--gutter);
  min-height: 52px;
  display: flex; align-items: center; justify-content: space-between; gap: var(--gap-lg);
}
.appbar__start:empty { display: none; }
.appbar__mark { display: inline-flex; align-items: center; gap: 10px; color: #fff; text-decoration: none; min-height: var(--tap); }
.appbar__logo { height: 22px; width: auto; display: block; filter: brightness(0) invert(1); }
.appbar__rule { width: 1px; height: 20px; background: rgba(255,255,255,0.4); }
.appbar__name { font-size: 16px; font-weight: 700; letter-spacing: 0.01em; }
.appbar__end { display: inline-flex; align-items: center; }
.appbar__end:empty { display: none; }
/* Back screens: Back on the left, the AMAC logo alone on the right. */
.appbar--back .appbar__mark { order: 2; }
.appbar--back .appbar__rule,
.appbar--back .appbar__name { display: none; }
.appbar--back .appbar__logo { height: 20px; }
.appbar .backrow {
  min-height: 36px; padding: 0 14px 0 10px; margin: 0;
  border-radius: var(--r-pill);
  background: rgba(255,255,255,0.14); color: #fff;
  font-size: 14px; font-weight: 700;
}
.appbar .backrow { border: 0; box-shadow: none; padding: 0 14px; }
.appbar .backrow:not(.sharebtn) { gap: 4px; padding: 0 14px 0 10px; }
.appbar__backicon { width: 16px; height: 16px; stroke: #fff; }
.appbar .sharebtn {
  min-width: var(--tap); min-height: var(--tap); padding: 0; margin-right: -8px;
  background: none; border: 0; box-shadow: none; color: #fff;
  display: inline-flex; align-items: center; justify-content: center;
}
.appbar .sharebtn svg { stroke: #fff; }
.appbar .sharebtn .sharebtn__label { display: none; }
.brand--appbar { display: contents; }
.guidebar { display: none; }

/* ---- the title band: navy, full-bleed, first thing on every screen --------- */
.band {
  background: var(--navy); color: #fff;
  box-shadow: 0 0 0 100vmax var(--navy);
  clip-path: inset(0 -100vmax);
  padding: 4px 0 22px;
  margin-bottom: 2px;
}
.band h1, .band .t-title, .band .t-title--tight, .band .t-hero, .band .t-detail {
  font-family: var(--font-display);
  color: #fff; font-weight: 800; letter-spacing: -0.015em;
  font-size: 25px; line-height: 1.12; text-wrap: balance;
}
.band .t-hero { font-size: 27px; }
.band p, .band .t-body, .band .t-body-lg, .band .t-meta, .band .picks-subtitle {
  color: rgba(255,255,255,0.86); font-size: 14.5px; line-height: 1.45;
}
.band a { color: #fff; }
.band .sharebtn { display: none; }          /* only Home shares from the top */
.faq-toprow:not(:has(h1)) { display: none; }
.band .cta-search { margin-top: 6px; border: 0; box-shadow: none; border-radius: 14px; }
.band .cta-search__field { min-height: 52px; font-size: 15px; }
.cta-search__go { background: var(--crimson); border-radius: 10px; font-weight: 700; }
@media (hover: hover) { .cta-search:hover .cta-search__go { background: var(--crimson-hover); } }

/* ---- type ---------------------------------------------------------------- */
.t-title, .t-title--tight, .t-detail, .t-hero, .t-title-xl { font-weight: 800; }
.eyebrow { color: var(--muted); font-weight: 700; letter-spacing: 0.1em; }

/* ---- buttons: crimson is the one action; navy is selection ------------------ */
.btn-primary { background: var(--crimson); color: #fff; font-weight: 700; border-radius: var(--r-field); }
@media (hover: hover) { .btn-primary:hover { background: var(--crimson-hover); } }
.btn-pill[aria-pressed="true"] { background: var(--navy); color: #fff; }
.btn-pill--quiet { background: var(--surface); color: var(--muted); }

/* ---- the tab bar ------------------------------------------------------------ */
.tabbar { border-radius: 22px; padding: 5px; border: 0; box-shadow: var(--shadow-bar); }
.tab { border-radius: 16px; }
.tab__label, .tab > span { font-size: 11px; font-weight: 700; }
.tab.is-active, .tab[aria-current="page"] { background: var(--navy); }
.tab.is-active svg, .tab[aria-current="page"] svg { stroke: #fff; }
.tab.is-active svg circle, .tab[aria-current="page"] svg circle { fill: #fff; }
.tab.is-active .tab__label, .tab.is-active > span,
.tab[aria-current="page"] .tab__label, .tab[aria-current="page"] > span { color: #fff; }

/* ---- cards and panels: no borders, white on grey ---------------------------- */
.card { border: 0; }

/* ---- quieter details (the "less dense" pass, 2026-09-26) ------------------- */
.tl { display: none; }                         /* the countdown's dots: decoration */
.disc, .disc--leaf { background: none; width: 26px; height: 26px; }
.disc svg, .disc--leaf svg { stroke: var(--navy); }
.lrow h2 { color: var(--ink); font-weight: 700; }
.avatar { background: var(--navy-tint); color: var(--navy); font-weight: 700; }
.avatar--quiet { background: var(--stone-36); color: var(--muted); }
.path__icon { background: #fff; border-color: var(--navy-line); }
.path__icon svg { stroke: var(--navy); }
.path__icon svg circle { fill: var(--navy); }
.path__node.is-open .path__icon { background: var(--navy); border-color: var(--navy); }
.path__node.is-open .path__icon svg { stroke: #fff; }
.path__node.is-open .path__icon svg circle { fill: #fff; }
.path__node:not(:last-child)::after { background: repeating-linear-gradient(to bottom, var(--navy-line) 0 3px, transparent 3px 7px); }
.path__title { color: var(--navy); font-weight: 800; }
.path__chev { stroke: var(--navy); }
.row__status { gap: 6px; }
.row__status .icon { width: 18px; height: 18px; }
.row__word { font-size: 13px; font-weight: 700; white-space: nowrap; }
.row__status--voted .row__word { color: var(--voted); }
.row__status--didntVote .row__word { color: var(--clay); }

.backrow--inbar { display: none !important; }

/* ---- picks: Solid labels, the key, the election line, the aside, the sheet --- */
.pick-chip {
  display: inline-flex; align-items: center; justify-content: center;
  height: 26px; padding: 0 11px; border-radius: var(--r-pill);
  font-family: var(--font-body); font-size: 12.5px; font-weight: 700; line-height: 1;
  white-space: nowrap; border: 0; box-shadow: none; cursor: pointer;
}
button.pick-chip:focus-visible { outline: 2px solid var(--navy); outline-offset: 2px; }
.pick-chip--endorsed    { background: #2F8A57; color: #fff; }
.pick-chip--dual        { background: #3A62C4; color: #fff; }
.pick-chip--recommended { background: #E4F3EA; color: #1B6B41; box-shadow: inset 0 0 0 1.5px #9ACDAF; }
.pick-key { display: flex; align-items: center; flex-wrap: wrap; gap: 6px; }
.pick-key__info {
  width: 32px; height: 32px; border: 0; background: none; color: var(--muted);
  display: inline-flex; align-items: center; justify-content: center; border-radius: 50%; cursor: pointer;
}
.pick-key__info svg { stroke: currentColor; }
.pick-election { display: flex; align-items: center; justify-content: space-between; gap: 10px; margin: 0; }
.pick-election b { font-size: 17px; font-weight: 800; color: var(--navy); }
.pick-election__tag {
  font-size: 12px; font-weight: 700; padding: 3px 9px; border-radius: 6px; white-space: nowrap;
  background: var(--navy-tint); color: var(--navy);
}
.pick-election__tag.is-past { background: #F6EBD3; color: #7A4F05; }
.pick-aside {
  margin: 0; border: 1.5px dashed var(--navy-line); border-radius: 14px;
  padding: 12px 14px; font-size: 13.5px; line-height: 1.5; color: var(--muted);
}
.pick-aside a { color: var(--navy); font-weight: 700; text-decoration: none; white-space: nowrap; }
.pick-aside a:hover { text-decoration: underline; }

/* the section headings: name on the left, the district quietly on the right */
.picks-group__head {
  background: none !important; padding: 0 2px 6px !important; border-radius: 0 !important;
  display: flex; flex-direction: row; align-items: baseline; justify-content: space-between; gap: 10px;
  border-bottom: 2px solid var(--navy);
}
.picks-group__title { font-size: 12.5px !important; font-weight: 800; letter-spacing: 0.08em; text-transform: uppercase; color: var(--navy) !important; font-family: var(--font-body) !important; }
.picks-group__note { font-size: 12px; font-weight: 600; color: var(--muted) !important; text-align: right; }
.compact-picks__label {
  background: none !important; padding: 0 0 5px !important; border-radius: 0 !important;
  border-bottom: 2px solid var(--navy); color: var(--navy) !important; font-weight: 800;
}

/* the sheet a label opens */
.labels-sheet { position: fixed; inset: 0; z-index: 40; display: flex; align-items: flex-end; justify-content: center; }
.labels-sheet__scrim { position: absolute; inset: 0; background: var(--scrim); }
.labels-sheet__panel {
  position: relative; width: 100%; max-width: 520px; max-height: 85vh; overflow: auto;
  background: #fff; border-radius: var(--r-sheet) var(--r-sheet) 0 0;
  padding: 20px var(--gutter) calc(18px + env(safe-area-inset-bottom, 0px));
  display: flex; flex-direction: column; gap: 12px;
}
.labels-sheet__title { margin: 0; font-size: 20px; font-weight: 800; color: var(--navy); }
.labels-sheet__list { margin: 0; padding: 0; list-style: none; display: flex; flex-direction: column; gap: 4px; }
.labels-sheet__row { display: flex; flex-direction: column; align-items: flex-start; gap: 6px; padding: 10px 12px; border-radius: 12px; }
.labels-sheet__row.is-current { background: var(--navy-tint); }
.labels-sheet__row p { margin: 0; font-size: 14px; line-height: 1.45; color: var(--muted); }
.labels-sheet__row .pick-chip { cursor: default; align-self: flex-start; margin: 0; }
.labels-sheet__panel { text-align: left; }
.labels-sheet__done { background: var(--navy); }
@media (min-width: 700px) { .labels-sheet { align-items: center; } .labels-sheet__panel { border-radius: var(--r-sheet); } }

/* the share button at the end of a picks list: outlined navy, never competing with crimson */
.picks-share__button--quiet, .picks-share__button { border: 1.5px solid var(--navy); color: var(--navy); background: #fff; font-weight: 700; }
.picks-share__button--quiet svg, .picks-share__button svg { stroke: var(--navy); }

.pick-key { flex-wrap: wrap; gap: 5px; row-gap: 6px; }  /* fits one line from ~360px; wraps below rather than clip the ⓘ */
.pick-key .pick-chip { font-size: 11.5px; padding: 0 9px; height: 25px; }
.pick-key__info { flex: none; width: 28px; }
[data-look="guide"] .screen { padding-top: 0; }

/* Link buttons and call chips: navy tint, not the old leaf-green wash. */
.btn-link, .callchip { background: var(--leaf-15); }
@media (hover: hover) { a.btn-link:hover, a.callchip:hover { background: rgba(14,42,117,0.16); } }

/* Person card: the section rules sit flush with the race rows (the old -10px bleed was for the
   tinted band, which the unified look dropped). */
.path__body .compact-picks__label { margin-inline: 0 !important; }


/* ============================================================================
   36. REFINEMENT PASS — match the approved prototype (Haashim, 2026-09-26:
   "doesn't look as refined as the model"). Values are the prototype's own
   (~/.claude-amac/lite-unified/src4.html, v4 block).
   ========================================================================== */

/* The bar and the title band are one navy block at every width: the older
   responsive rules added 12-32px of ground above the band (the grey stripe). */
.screen, .screen--tabs { padding-top: 0 !important; }

/* Title band: breathing room under the bar, a 25px title, a 14px lead. */
.band { padding-top: 14px; padding-bottom: 24px; gap: 6px; }
.band h1, .band .t-hero { font-size: 25px; line-height: 1.12; letter-spacing: -0.015em; text-wrap: balance; }
.band p { font-size: 14px; line-height: 1.45; color: rgba(255,255,255,0.86); }
.band form, .band .searchfield, .band .cta-search { margin-top: 8px; }

/* Cards: tighter, softer — 16px radius, 14/16 padding, a hairline shadow. */
.screen .card { border-radius: 16px; box-shadow: 0 1px 2px rgba(20,23,43,0.05); }
.screen .card:not(.card--list):not(.qcard):not(.path__card) { padding: 14px 16px; }

/* Countdown: a 40px heavy numeral, ink labels at 14px, flat rows. */
.count__n { font-size: 40px; font-weight: 800; letter-spacing: -0.03em; line-height: 0.95; }
.count__w { font-size: 15px; font-weight: 700; }
.dates__row { padding-block: 8px; }
.dates__label { font-size: 14px; color: var(--ink); }
.dates__day { font-size: 14px; font-weight: 700; }
.dates__in { font-size: 12px; }

/* List rows (Next steps, Help): 15.5/700 ink title, 13px note. */
.lrow h2, .lrow .t-section { font-size: 15.5px; }
.lrow__note { font-size: 13px; line-height: 1.35; }

/* Person timeline: 34px circles, white with a navy ring; open = solid navy. */
.path { padding-left: 44px; gap: 10px; }
.path__icon { left: -44px; top: 10px; width: 34px; height: 34px; border-width: 1.5px; }
.path__icon svg { width: 16px; height: 16px; }
.path__node:not(:last-child)::after { left: -28px; top: 48px; bottom: -14px; }
.path__title { font-size: 16px; }

/* Vote history: a tile per cell, not a pill per row. */
.vgrid__row { background: none; padding: 0; gap: 5px; justify-items: stretch; }
.vgrid__row--head { padding-bottom: 2px; }
.vgrid__row:not(.vgrid__row--head) .vgrid__cell { background: var(--ground); border-radius: 7px; min-height: 32px; }
.vgrid__year { font-weight: 800; }

/* Questions: 15px/700 ink with a navy chevron. */
.qitem__q { font-size: 15px; font-weight: 700; color: var(--ink); }
.qitem__head svg { stroke: var(--navy); }

/* Household inside the person card: flat divided rows, not a card per person
   inside a card (the prototype's .l-hh). */
.path__body .results { gap: 0; }
.path__body .results .row { background: none; box-shadow: none; border-radius: 0; padding-inline: 0; border-top: 1px solid var(--hairline); }
.path__body .results .row:first-child { border-top: 0; }

/* The app bar's mark is a link: the global a:hover link colour (--green, now navy)
   painted "Circle Lite" navy on navy on hover. It stays white in every state. */
.appbar__mark, .appbar__mark:hover, .appbar__mark:focus, .appbar__mark:active, .appbar__mark:visited { color: #fff; text-decoration: none; }
@media (hover: hover) { .appbar__mark:hover { opacity: 0.85; } }

/* "Link copied." after a share from the app bar (Home, Help): one small white note
   just under the share icon, on every screen, instead of text inside the band. */
.brand__status, .faq-share-status, .band .faq-share-status {
  position: fixed; z-index: 30;
  top: calc(env(safe-area-inset-top, 0px) + 50px);
  right: max(var(--gutter), calc((100vw - var(--col-max)) / 2 + var(--gutter)));
  margin: 0; padding: 6px 12px; border-radius: 999px;
  background: #fff; color: var(--navy); font-size: 13px; font-weight: 700;
  box-shadow: 0 4px 14px rgba(20,23,43,0.18);
  text-align: right; white-space: nowrap;
}
.brand__status:empty, .faq-share-status:empty { display: none; }
