/* card.css — the printed artefact itself.
 *
 * Everything here is sized in physical units (in) or in `em` off a base that is
 * derived from the card's own width, so the same markup renders identically at
 * 25% on screen and at 100% on a press sheet. Nothing in this file may depend on
 * viewport units or device pixels.
 *
 * Palette and proportions are taken from the Texas Democratic Women — Fort Bend
 * push card (colours read out of the Illustrator source, not eyeballed).
 */

.card-bleed {
  --bleed: 0.125in;
  position: relative;
  width: calc(var(--trim-w) + var(--bleed) * 2);
  height: calc(var(--trim-h) + var(--bleed) * 2);
  background: var(--c-bleed, var(--c-brand));
  overflow: hidden;
  box-shadow: 0 2px 10px rgba(15, 23, 42, .18), 0 20px 40px -20px rgba(15, 23, 42, .35);
}

.card {
  /* The card fills the whole bleed box, not just the trim. Every element that
     touches an edge absorbs `--bleed` into its own padding (see below), so the
     artwork runs past the trim line the way a press expects — a card that stops
     at the trim leaves a white sliver the moment the guillotine drifts.
     Setting --bleed to 0 collapses this back to a trim-only page. */
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  overflow: hidden;
  background: #fff;
  color: var(--c-ink);
  /* Base type size scales with the card so a 4x6 and an 8.5x11 look like the
     same design rather than the same design with bigger margins. */
  /* Barlow is the card's base face, standing in for the original's Gotham on the
     SLATE side: same double-storey 'a' and tall x-height, and it was the closest
     free face measured on a 22-character candidate name.

     ⚠ That measurement was taken against ONE cut of Gotham. The artwork uses at
     least two, ~20% apart in width, and the info side proved it: its live text
     names Gotham-Medium/Gotham-Bold (wide — Montserrat 600/700 matches to
     within 0.6%) alongside GothamBold (narrow — Barlow matches to 2%). Barlow
     against the WIDE cut is 0.84, sixteen percent short. Side B is set per
     element accordingly; the slate side has not had that pass — see §8d of
     HANDOFF.md. Do not read this note as "Barlow is the Gotham substitute". */
  font-family: Barlow, -apple-system, "Segoe UI", sans-serif;
  font-size: calc(var(--trim-w) / 46);
  letter-spacing: -.006em;
  line-height: 1.25;
  -webkit-font-smoothing: antialiased;
  text-rendering: geometricPrecision;
}

.card * { box-sizing: border-box; }

/* Default palette — overridden per theme by theme.js */
.card {
  --c-ink:        #231F20;
  --c-brand:      #223368;
  --c-brand-deep: #191346;
  --c-brand-mid:  #2C2A6B;
  --c-royal:      #263E8A;
  --c-accent:     #1496CC;
  --c-accent-lt:  #32C5F4;
  --c-hot:        #D5137A;
  --c-warm:       #F16022;
  --c-sub:        #F89520;
  --c-sub2:       #F16022;
  --c-rule:       #89BDCB;
  --c-band:       #E9F6FD;
  --c-muted:      #6D84C2;
  --density: 1;
}

/* =========================================================================
   SIDE A — the slate
   -------------------------------------------------------------------------
   Proportions below are measured off the printed original
   (Political/TDW 0826 push card.ai **page 2**, a 6.25 x 9.25 artboard = a 6 x 9
   trim with 0.125 bleed) and expressed
   as fractions of the trim, so they hold at any card size:

     header bar bottom      9.30% of card height
     rule under it          0.23% of card height, colour #89BDCB
     column split          51.16% of card width
     text inset            3.91% left, 2.96% right
     arrow            x 44.88%-57.43%, y 7.85%-93.78%
     body type             2.14% of card width (9.26pt on a 6in card)
     row leading            1.20
     title type            4.61% of card width (19.9pt on a 6in card)

   Two things about the bands matter and were wrong before: they run the full
   width of their column, edge to edge into the bleed, and consecutive sections
   touch — no gaps, no rounded corners. The white "gaps" in the original are
   simply the unbanded sections.
   ========================================================================= */
.side-a {
  display: grid;
  grid-template-rows: auto auto minmax(0, 1fr) auto;
  position: relative;
}

/* ---- header ---- */
.slate-head {
  background: var(--c-brand);
  color: #fff;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  height: calc(var(--bleed) + var(--trim-h) * .0930);
  /* Fixed height: anything over-long inside must clip, not push the card. */
  overflow: hidden;
}
.slate-h1, .slate-sub {
  padding: 0 calc(var(--bleed) + var(--trim-w) * .0394);
}
/* The original's headline is Gotham Ultra Condensed: it fills the measure and
   still stands nearly as tall as the header bar. Setting it in a normal-width
   face means the fit pass has to shrink it to the same measure, which costs
   about a quarter of its cap height — the single most visible difference on the
   card. Barlow Condensed 800 holds the width and the cap height together. */
.slate-h1 {
  margin: 0;
  text-align: center;
  font-family: 'Barlow Condensed', Montserrat, Barlow, sans-serif;
  font-weight: 800;
  font-size: calc(var(--trim-w) * .0640 * var(--line-fit, 1));
  line-height: 1.00;
  white-space: nowrap;
  letter-spacing: -.004em;
  text-transform: uppercase;
}
.slate-h1 .hl { color: var(--c-accent-lt); }
.slate-sub {
  margin: .1em 0 0;
  text-align: center;
  font-family: Montserrat, Barlow, sans-serif;
  font-weight: 700;
  font-size: calc(var(--trim-w) * .0268);
  line-height: 1.12;
  /* The cap height already matched the artwork to 2%; the line ran 6.4% wide,
     which is the substitute face and not the size. Tracking is the lever that
     puts the measure back — tools/align-slate.js measured this value. */
  letter-spacing: -.0359em;
  color: var(--c-sub, var(--c-warm));
}
.slate-sub .sub-b { color: var(--c-sub2, var(--c-warm)); }
.slate-sub { overflow-wrap: anywhere; }

.slate-colheads {
  display: grid;
  grid-template-columns: var(--split) 1fr;
  margin-top: .45em;
  padding-bottom: .3em;
}
.slate-colheads > div {
  display: grid;
  grid-template-columns: var(--label-w) 1fr;
  font-family: Barlow, sans-serif;
  font-weight: 700;
  font-size: calc(var(--trim-w) * .0232);
  color: #fff;
}
.slate-colheads > div:first-child { padding: 0 var(--pad-1r) 0 var(--pad-1l); }
.slate-colheads > div:last-child  { padding: 0 var(--pad-2r) 0 var(--pad-2l); }

.slate-rule {
  height: calc(var(--trim-h) * .0023);
  min-height: .6px;
  background: var(--c-rule, #89BDCB);
}

/* ---- body: two explicit columns, filled first-come ----
   Measured off the original (as % of card width):
     column 1 text  3.94% .. 44.11%     arrow  44.88% .. 57.43%
     column 2 text 58.28% .. 96.83%     label column 19.34% wide
   Both columns clear the arrow, which is why the paddings look lopsided. */
.side-a {
  --split:   calc(var(--bleed) + var(--trim-w) * .5116);
  --label-w: calc(var(--trim-w) * .1934);
  --pad-1l:  calc(var(--bleed) + var(--trim-w) * .0394);
  --pad-1r:  calc(var(--trim-w) * .0705);
  --pad-2l:  calc(var(--trim-w) * .0712);
  --pad-2r:  calc(var(--bleed) + var(--trim-w) * .0300);
}
.slate-body {
  --sec-air: 0;
  letter-spacing: -.011em;
  display: grid;
  grid-template-columns: var(--split) 1fr;
  font-size: calc(var(--trim-w) * .0214 * var(--density));
  overflow: hidden;
  min-height: 0;
}

.slate-col {
  display: flex;
  flex-direction: column;
  min-width: 0;
  min-height: 0;
  overflow: hidden;
}
.slate-col-1 .sec, .slate-col-1 .col-tail { padding-left: var(--pad-1l); padding-right: var(--pad-1r); }
.slate-col-2 .sec, .slate-col-2 .col-tail { padding-left: var(--pad-2l); padding-right: var(--pad-2r); }
.slate-col-mid .sec, .slate-col-mid .col-tail {
  padding-left: calc(var(--trim-w) * .022);
  padding-right: calc(var(--trim-w) * .022);
}

/* Three columns for the two counties long enough to need them (Dallas, Harris).
   Equal thirds, a narrower label column, and no arrow — the arrow is a
   two-column device and would sit on top of the middle column's text. */
.card[data-cols="3"] .slate-body,
.card[data-cols="3"] .slate-colheads {
  grid-template-columns: repeat(3, minmax(0, 1fr));
}
.card[data-cols="3"] {
  --label-w: calc(var(--trim-w) * .1250);
}
.card[data-cols="3"] .slate-col-1 .sec, .card[data-cols="3"] .slate-col-1 .col-tail {
  padding-left: calc(var(--bleed) + var(--trim-w) * .026);
  padding-right: calc(var(--trim-w) * .014);
}
.card[data-cols="3"] .slate-col-2 .sec, .card[data-cols="3"] .slate-col-2 .col-tail {
  padding-left: calc(var(--trim-w) * .014);
  padding-right: calc(var(--bleed) + var(--trim-w) * .026);
}
.card[data-cols="3"] .slate-colheads > div:first-child { padding: 0 calc(var(--trim-w) * .014) 0 calc(var(--bleed) + var(--trim-w) * .026); }
.card[data-cols="3"] .slate-colheads > div:nth-child(2) { padding: 0 calc(var(--trim-w) * .022); }
.card[data-cols="3"] .slate-colheads > div:last-child  { padding: 0 calc(var(--bleed) + var(--trim-w) * .026) 0 calc(var(--trim-w) * .014); }
.card[data-cols="3"] .slate-bottom { grid-template-columns: 62% 1fr; }

.sec {
  --sec-scale: 1;
  break-inside: avoid;
  /* --col-scale is set per column by the fill pass in card.js; --sec-scale is
     the per-section emphasis. Only the sections scale — a column's tail (the
     footnotes, the early-voting block) keeps the card's base size. */
  font-size: calc(1em * var(--sec-scale) * var(--col-scale, 1));
  padding-top: calc(.19em * (1 + var(--sec-air)));
  padding-bottom: calc(.19em * (1 + var(--sec-air)));
}
/* Banding is decided in card.js, in reading order across the whole slate, so it
   does not restart at the top of column 2. */
.zebra .slate-col .sec.band { background: var(--c-band); }

/* The original sets headings in the same family as the rows (Gotham Bold).
   Montserrat here was 14% wider than Barlow and became the single constraint
   that held the whole card's type size ~11% below the original's. */
.sec-title {
  white-space: nowrap;
  overflow: hidden;
  font-family: Barlow, sans-serif;
  font-weight: 700;
  font-size: calc(1em * var(--line-fit, 1));
  line-height: 1.28;
  color: var(--c-ink);
  margin: 0;
}
.sec-title .star { color: var(--c-hot); }

.slate-row {
  display: grid;
  grid-template-columns: var(--label-w) 1fr;
  column-gap: 0;
  font-size: 1em;
  line-height: 1.20;
}
/* Every row in the original is exactly one line — long names are clipped at the
   column edge rather than wrapped. Same here, except an ellipsis marks it so a
   truncated name is visible rather than silently shortened, and the status line
   names the offenders. */
.slate-row > * {
  min-width: 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* Rows carry their own --line-fit, like section headings do: a name a few
   percent too wide for its column is condensed to fit rather than cut off. The
   original is set in Gotham, which is about 9% narrower than Barlow, so a
   handful of the longest names need it on any given card. */
.slate-row .lab, .slate-row .nom { font-size: calc(1em * var(--line-fit, 1)); }
.slate-row .lab { color: var(--c-ink); }
.slate-row .nom { color: var(--c-ink); }
.slate-row .inc {
  color: var(--c-accent);
  /* Set as a margin rather than a literal space in the markup: the space was
     being all but swallowed by the row's negative letter-spacing, and the
     printed original leaves a clear gap before the incumbent mark. */
  margin-left: .26em;
}
.slate-row .none { color: var(--c-hot); }

/* ---- the arrow, positioned exactly where the original puts it ---- */
.slate-arrow {
  position: absolute;
  left: calc(var(--bleed) + var(--trim-w) * .4488);
  width: calc(var(--trim-w) * .1254);
  top: calc(var(--bleed) + var(--trim-h) * .0785);
  height: calc(var(--trim-h) * .8593);
  pointer-events: none;
  z-index: 2;
}
.slate-arrow svg { width: 100%; height: 100%; display: block; }

/* ---- column tails: notes + paid-for under column 1,
        early voting + credit under column 2 ---- */
.col-tail { margin-top: auto; padding-top: .5em; }

/* Measured off the original: the footnotes sit at about three quarters of the
   row type, not just under it. At .92em they read as a third column of content
   rather than as a footnote. */
.foot-notes { font-size: .76em; line-height: 1.30; color: var(--c-ink); }
.foot-notes .star { font-size: 1.15em; line-height: 1; }
.foot-notes .star { color: var(--c-hot); font-weight: 700; }
/* The drawn asterisk (see STAR_SVG in card.js) sits on the text baseline at the
   size the glyph would have been. */
.mark-svg {
  display: inline-block;
  width: .82em;
  height: .82em;
  vertical-align: -.06em;
}
.foot-notes .inc { color: var(--c-accent); }

.hotline { margin: 0 0 .45em; font-size: 1em; line-height: 1.25; }
.hotline strong {
  display: block;
  font-family: Montserrat, Barlow, sans-serif;
  font-weight: 700;
  font-size: 1.3em;
  color: var(--c-ink);
  margin-bottom: .08em;
}
.hotline .tel { color: var(--c-warm); font-weight: 700; }

/* The paid-for line and the credit sit on one strip across the very bottom of
   the card, outside both columns — the original runs the paid-for line wider
   than column one. */
.slate-bottom {
  /* ⚠ Do not try to close the last ~0.5% of trim height between our bottom stack
     (credit, paid-for, and the bottom-anchored early-voting block) and the
     artwork's. Three routes were measured and all three made the card WORSE,
     because this strip shares a grid row with the slate's `1fr` body and the
     body runs its own shrink-to-fit:
       - growing this padding + max-height   -> col1 mean 0.25% -> 0.42%
       - padding-bottom on .col-tail         -> col1 lost a row entirely
       - relative offset on the strip's cells -> credit re-wrapped, 17% narrower
     The residual is 3.4pt on a 9in card and everything else is inside 0.3%.
     Leave it. */
  padding-bottom: calc(var(--bleed) + var(--trim-h) * .006);
  display: grid;
  /* The paid-for line runs wider than column one in the original, so this strip
     is split further right than the columns above it. */
  grid-template-columns: 56% 1fr;
  align-items: end;
  /* An `auto` grid row that can grow would push the card off its own page, so
     this strip is capped and clips instead. The cap is tight — on the original
     it is two lines of credit deep — because every point it takes comes
     straight off the slate above it. --density-f shrinks the text to fit. */
  /* The strip absorbs the bleed into its own padding, like every other element
     that touches an edge, so the cap has to allow for it — capping at the trim
     figure alone left barely a third of the strip usable once the bleed padding
     was taken out, and the fit pass ran to its floor and still reported a clip. */
  max-height: calc(var(--bleed) + var(--trim-h) * .042);
  overflow: hidden;
  /* --density-f is this strip's own fit pass: a long credit line shrinks itself
     instead of stealing height from the slate above it. */
  font-size: calc(var(--trim-w) * .0214 * var(--density) * var(--density-f, 1));
}
/* The strip sits below the arrow, so its right-hand cell does not need the
   column's arrow clearance — inheriting --pad-2l cost the credit line about a
   twentieth of the card's width and pushed it from the original's two lines onto
   three. The original starts its credit at 58.4% of the trim. */
.slate-bottom > :first-child { padding-left: var(--pad-1l); padding-right: 0; }
.slate-bottom > :last-child  {
  padding-left: calc(var(--trim-w) * .024);
  padding-right: var(--pad-2r);
}

/* Its grid track is a definite 56%, so a nowrap line longer than the track does
   not widen anything — it simply paints out over the credit beside it and off
   the card, with every box in the strip still measuring as containing its
   contents. It clips, and card.js reports the clip. */
.paidfor {
  font-size: calc(.84em * var(--line-fit, 1));
  white-space: nowrap;
  color: #808285;
  display: flex;
  align-items: center;
  gap: .5em;
  min-width: 0;
  overflow: hidden;
}
.paidfor > span {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
}
.paidfor .orn { flex: none; opacity: .55; }
.paidfor .orn svg { display: block; height: 1.5em; width: auto; }

.foot-ev { padding-bottom: .25em; }
.foot-ev .ev-k {
  font-family: Montserrat, Barlow, sans-serif;
  font-weight: 800;
  font-size: 1.22em;
  line-height: 1.3;
  color: var(--c-accent-lt);
  text-transform: uppercase;
}
.foot-ev .ev-v {
  font-family: Montserrat, Barlow, sans-serif;
  font-weight: 800;
  font-size: 1.3em;
  line-height: 1.22;
  color: var(--c-brand);
  text-transform: uppercase;
  margin-bottom: .12em;
}
.foot-ev .ev-v .to { color: var(--c-hot); font-size: .66em; }
.foot-ev .ed-v {
  font-family: Montserrat, Barlow, sans-serif;
  font-weight: 800;
  white-space: nowrap;
  font-size: calc(2.05em * var(--line-fit, 1));
  line-height: 1.06;
  color: var(--c-brand);
  text-transform: uppercase;
}
.foot-ev .ed-v .bar { color: var(--c-hot); font-weight: 500; margin: 0 .06em; }
.foot-ev sup { font-size: .5em; vertical-align: super; }

/* Both sides carry a credit line, and both used to be styled by a bare
   `.foot-credit` — so side B's rule, being later in the file at equal
   specificity, was setting side A's credit too. It sized the slate's credit off
   the card width instead of the bottom strip's own type, which is why the
   strip's fit pass ran all the way to its floor and still reported a clip. Each
   side owns its own rule now. */
.side-a .foot-credit {
  font-size: .74em;
  line-height: 1.18;
  color: var(--c-accent);
  min-width: 0;
  overflow-wrap: anywhere;
  overflow: hidden;
}

/* =========================================================================
   SIDE B — voting information
   -------------------------------------------------------------------------
   Block geometry measured off the printed original
   (Political/TDW 1124 push card.ai page 1, 5.7 x 8.7 artboard = a 5.5 x 8.5
   trim with 0.1in bleed, which the royal block's -1.82%..101.82% span confirms).
   All figures are % of the trim:

     dates block      0      .. 44.81   height
     voting times    44.81   .. 54.06
     locations       54.06   .. 91.54
     footer          91.54   .. 100
     logo        x 70.09..96.62   y  2.35..19.52
     QR          x 79.1 ..96.0    y 25.0 ..42.5
     badge       x 81.64..95.36   y 81.24..91.41
     footer top  91.53

   Type sizes are taken from `Political/TDW 0826 push card.ai` **page 4** — the
   2026 back, 6.25 x 9.25 artboard = a 6 x 9 trim with 0.125 bleed. That is the
   card in front of us, and it is NOT the 2024 back (TDW 1124): the two are
   within about 5% of each other but they are not the same piece, and measuring
   the wrong one puts every size out by that much.

   Five of its strings are still live text, so those sizes and faces are read,
   not inferred:

     IMPORTANT DATES        20.31pt  Montserrat-Bold
     "Last day to ..."      12.84pt  Gotham-Medium
     "Monday, October 5..." 14.98pt  Gotham-Bold
     QR blurb                9.51pt  Gotham-Bold
     badge                   7.13pt  Montserrat-Bold

   The rest is outlined and measured from the render. Cap height is 0.70 of the
   point size in every face used here, so a measured cap converts straight to a
   point size; dividing by the trim width in points (432 at 6in) gives a
   fraction that holds at any card size:

     IMPORTANT DATES        20.31pt  .04701 of trim width
     "Last day to ..."      12.84pt  .02972   <- the block's em base
     "Monday, October 5..." 14.98pt  .03468
     EARLY VOTING DATES     20.06pt  .04643
     MON/FRI early-voting   27.77pt  .06428
     ELECTION DAY IS        24.69pt  .05715   (larger than EARLY VOTING DATES)
     TUE | NOV              40.11pt  .09286
     voting-times line 1     9.77pt  .02262   (smaller than the two under it)
     voting-times lines 2-3 11.31pt  .02618
     POPULAR VOTING CENTERS 20.57pt  .04761   (fixed, NOT scaled by the list)
     location row           ~9.9pt   line pitch 2.28% of trim height
     footer URL             18.51pt  .04285
     badge                   7.13pt  .01650, leading 1.00

   Everything inside the dates block is set in em of one base, so the block's
   shrink-to-fit scales all of it together. Sizing some of it off the trim
   instead made the fit crush the body text to 60% while the headings above it
   stayed full size, which is worse than either being wrong on its own.

   Left insets: the muted headings HANG at 4.9% of the trim while the text under
   them sits at 7.25%. Losing that hang is most of why the card read as though
   it had a wider left margin than the original.

     dates band     0     .. 45.44   times 45.44 .. 54.72
     locations     54.72  .. 92.11   footer 92.11 .. 100
     logo      x 61.08..95.08   y  3.11..24.94
     badge     x 80.58..93.92   y 81.83..91.94
   ========================================================================= */
.side-b {
  display: grid;
  grid-template-rows:
    calc(var(--bleed) + var(--trim-h) * .4544)
    calc(var(--trim-h) * .0928)
    minmax(0, 1fr)
    auto;
  background: var(--c-royal);
  color: #fff;
  position: relative;
}

/* Balanced: the locations block takes only the height its list needs and the
   dates block absorbs the slack. The printed original is a fixed three-band
   design, so this is opt-in — but a county with five polling places wants it,
   because the alternative is a third of the card left empty. */
.side-b.balance {
  grid-template-rows:
    minmax(calc(var(--bleed) + var(--trim-h) * .4544), 1fr)
    calc(var(--trim-h) * .0928)
    auto
    auto;
}
.side-b.balance .info-centers { min-height: calc(var(--trim-h) * .18); }
/* The dates block is handed the height the list did not need, so its contents
   spread into it. Left top-aligned it just grows a gap above the times band,
   which reads as a mistake rather than as a bigger dates block. */
.side-b.balance .info-top { display: flex; }
.side-b.balance .info-main {
  flex: 1 1 auto;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  min-height: 0;
  padding-bottom: calc(var(--trim-h) * .012);
}

.info-top {
  min-height: 0;
  overflow: hidden;
  /* The block's own size carries the fit scale; everything inside is in em of
     it, otherwise --density-t would have no effect at all. */
  font-size: calc(var(--trim-w) * .02972 * var(--density-t, 1));
  background: var(--c-royal);
  padding: calc(var(--bleed) + var(--trim-h) * .022)
           calc(var(--bleed) + var(--trim-w) * .038) 0
           calc(var(--bleed) + var(--trim-w) * .0685);
  display: block;
}

/* The muted headings hang left of the text beneath them — 3.45% of the trim
   against 5.7% — which is a large part of why the original reads wider. */
.info-h2 {
  margin: 0 0 .35em calc(var(--trim-w) * -.0195);
  /* Montserrat-Bold — read from the artwork, which still has this string as
     live text. Bold is 700; 800 was a guess. */
  font-family: Montserrat, Barlow, sans-serif;
  font-weight: 700;
  font-size: calc(1.582em * var(--line-fit, 1));
  line-height: 1;
  /* Tracking is the lever that puts a substitute face back on the original's
     measure once its cap height already matches. Every value here is the one
     tools/align-reference.js measured as closing the gap, not a preference. */
  letter-spacing: .004em;
  text-transform: uppercase;
  color: var(--c-muted);
  white-space: nowrap;
}

/* The original's longest dates line — the indented TUE | NOV — reaches 67.2%
   of the trim, just clear of the blurb column. Anything wider paints over it;
   these lines are `nowrap`, so a max-width alone does not stop them, which is
   why each one carries a shrink-to-fit pass in card.js. */
.info-main { max-width: 70%; }

/* The artwork uses TWO cuts of Gotham and they are nowhere near the same width.
   Its own text engine gives the advance of every live string, so this is
   measured rather than argued:

     Gotham-Medium / Gotham-Bold   the wide cut  -> Montserrat 600 / 700
                                   (ratio 1.006 / 0.997 — near identical)
     GothamBold                    a narrow cut  -> Barlow 400/500 (1.02)

   Barlow against the wide cut is 0.84 — 16% narrow, which is what made the
   dates block's body text read small next to the original however well its
   point size matched. Both are "Gotham" and an earlier pass on this project
   measured only the narrow one, which is why the note in this file said Barlow
   was the closest substitute: true of that cut, wrong for this text. */
.datepair { margin-bottom: .62em; }
.datepair .k {
  display: block;
  font-family: Montserrat, Barlow, sans-serif;
  font-weight: 600;
  letter-spacing: -.002em;
  font-size: 1em;
  color: var(--c-accent-lt);
  /* Leading, not size: ours carried more of it than the artwork's and pushed
     the block past its band, which made the fit shrink every size in the block
     by 4% — including the ones measured off the artwork. Tuned back up to 1.28
     because 1.22 then put the second date pair 0.5% of the trim high. */
  line-height: 1.28;
}
.datepair .v {
  display: block;
  font-family: Montserrat, Barlow, sans-serif;
  font-weight: 700;
  font-size: 1.167em;
  line-height: 1.28;
  letter-spacing: -.004em;
  line-height: 1.3;
  color: #fff;
}

/* Two sizes, not one: the original sets ELECTION DAY IS a fifth larger than
   EARLY VOTING DATES. Setting them from one rule flattened the hierarchy of the
   whole block. */
.ev-title, .ed-title {
  font-family: Montserrat, Barlow, sans-serif;
  font-weight: 800;
  line-height: 1.16;
  color: var(--c-accent-lt);
  text-transform: uppercase;
  letter-spacing: -.014em;
  margin: .46em 0 .02em;
  white-space: nowrap;
}
.ev-title { font-size: calc(1.562em * var(--line-fit, 1)); }
/* The artwork leaves 2.27% of the trim between the early-voting pair and this
   line; ours left 2.94%, which put this line and the TUE | NOV under it 1.1%
   low and pushed the whole lower half of the block down with them. */
.ed-title { font-size: calc(1.923em * var(--line-fit, 1)); margin-top: .12em;
             letter-spacing: -.019em; }
/* Barlow, not Montserrat — and not Barlow Condensed either, which was the
   first guess off a bad arithmetic slip and looks obviously squeezed beside the
   original.
   Measured on the same substring: the artwork sets "MON, OCTOBER 19" in 49.05%
   of the trim at 28.29pt; Montserrat 800 needs 60.7% for it, 26% more. Barlow
   is 20% narrower than Montserrat and lands within ~5% of the artwork. The
   card's own note already says Barlow stands in for the original's Gotham —
   these two display lines are the Gotham ones. The accent titles above them
   really are Montserrat: the artwork still carries "IMPORTANT DATES" as live
   text and names the face.
   Set in Montserrat, these lines had to be condensed ~20% to hold the measure,
   which is where the whole block lost its scale. */
.ev-line {
  font-family: Barlow, Montserrat, sans-serif;
  font-weight: 700;
  font-size: calc(2.163em * var(--line-fit, 1));
  /* The artwork sets these two lines on a 4.39% pitch — leading 1.024. At 1.14
     the second line sat a full percent of the trim low and carried the rest of
     the block down with it. */
  line-height: 1.024;
  letter-spacing: -.001em;
  text-transform: uppercase;
  color: #fff;
  white-space: nowrap;
}
.ev-line .to { color: var(--c-hot); font-size: .58em; vertical-align: .18em; margin: 0 .1em; }
/* `vertical-align: super` raises the baseline by a third of the em, which puts
   the ordinal's top well above the cap line — on the original the TH sits with
   its top level with the caps. Cap height is 0.70em, so a 0.40em ordinal has to
   sit 0.42em of the parent (1.05 of its own em) above the baseline. */
.ev-line sup, .ed-line sup {
  font-size: .40em;
  vertical-align: baseline;
  position: relative;
  /* 1.05em put the ordinal's top 0.075em above the cap line, which made the
     line's ink 0.3% of the trim taller than the artwork's and shifted
     everything below it down. */
  bottom: 0.86em;
}
.ed-line {
  font-family: Barlow, Montserrat, sans-serif;
  font-weight: 700;
  white-space: nowrap;
  /* No indent. A first reading put this line 1.75% of the trim in from the rest
     of the block; the window that produced it had clipped the 'T'. Measured
     again on a window wide enough to be sure, the artwork starts it at 7.00% —
     flush with every other line, and slightly left of them only because a 'T'
     has almost no left bearing. */
  font-size: calc(3.125em * var(--line-fit, 1));
  line-height: 1.02;
  letter-spacing: .038em;
  text-transform: uppercase;
  color: #fff;
}
/* The artwork sets the divider with almost half an em of air on each side
   (measured: 0.494em left, 0.476em right at its 40.11pt). A 0.04em margin
   closed that up and the line read as one word. */
.ed-line .bar { color: var(--c-hot); font-weight: 400; margin: 0 .30em; }

/* logo and QR sit in their own measured boxes, not in the text flow.
   The box stays put and the artwork scales inside it, so growing the logo can
   never move the QR or push into the dates block. */
.info-aside { position: static; }
.org-logo, .org-logo-fallback {
  position: absolute;
  /* The artwork's roundel sits y 3.11%..20.28% of the trim — 17.17% tall, and
     circular, so 25.75% of a 6in width. Its foot has to stay clear of the QR
     blurb, whose first line starts at 22.28%; sizing it off a colour-detection
     pass that ran into the blurb put the two on top of each other. */
  left: calc(var(--bleed) + var(--trim-w) * .6933 -
             var(--trim-w) * .2575 * (var(--logo-scale, 1) - 1) / 2);
  width: calc(var(--trim-w) * .2575 * var(--logo-scale, 1));
  top: calc(var(--bleed) + var(--trim-h) * .0311 -
            var(--trim-h) * .1717 * (var(--logo-scale, 1) - 1) / 2);
  height: calc(var(--trim-h) * .1717 * var(--logo-scale, 1));
  object-fit: contain;
  z-index: 2;
}
/* A supplied logo usually arrives with its own background baked in. `cover`
   fills the box, `circle` clips it to the disc the original uses — which is
   what rescues a mark that ships as white-on-navy in a hard-edged rectangle and
   would otherwise read as a sticker stuck on the card. */
.org-logo.fit-cover { object-fit: cover; }
.org-logo.fit-circle { object-fit: cover; border-radius: 50%; }
.org-logo.bg-white { background: #fff; padding: calc(var(--trim-w) * .006); }
.org-logo.bg-circle {
  background: #fff;
  border-radius: 50%;
  padding: calc(var(--trim-w) * .010);
  object-fit: contain;
}
.org-logo-fallback {
  border-radius: 50%;
  border: calc(var(--trim-w) * .004) solid var(--c-accent-lt);
  display: grid;
  place-items: center;
  text-align: center;
  padding: 6%;
  overflow: hidden;
  overflow-wrap: break-word;
  hyphens: none;
  font-family: Montserrat, Barlow, sans-serif;
  font-weight: 700;
  font-size: calc(var(--trim-w) * .0185);
  line-height: 1.14;
  color: #fff;
  text-transform: uppercase;
}
.qr-blurb {
  position: absolute;
  left: calc(var(--bleed) + var(--trim-w) * .7430);
  width: calc(var(--trim-w) * .1920);
  top: calc(var(--bleed) + var(--trim-h) * .2228);
  /* Sits between the logo and the QR, so its height is fixed by both. The blurb
     is free text and five lines of it only just fit at 6 x 9 — at another aspect
     ratio it does not, so it carries its own shrink-to-fit rather than clipping
     the line that tells the reader what the QR is for. */
  /* Bottoms out just above the QR (top 31.15%), so a five-line blurb cannot
     print across the code. The blurb carries its own shrink-to-fit for that
     height rather than clipping — the last line is the one that tells the
     reader to scan. */
  max-height: calc(var(--trim-h) * .0835);
  overflow: hidden;
  font-size: calc(var(--trim-w) * .02201 * var(--line-fit, 1));
  line-height: 1.05;
  text-align: center;
  color: #fff;
  z-index: 2;
}
/* The QR grows about its own centre so enlarging it does not walk it off the
   right edge or into the blurb above. */
.qr-img {
  position: absolute;
  left: calc(var(--bleed) + var(--trim-w) * .7560 -
             var(--trim-w) * .1800 * (var(--qr-scale, 1) - 1) / 2);
  width: calc(var(--trim-w) * .1800 * var(--qr-scale, 1));
  top: calc(var(--bleed) + var(--trim-h) * .3115 -
            var(--trim-w) * .1800 * (var(--qr-scale, 1) - 1) / 2);
  aspect-ratio: 1;
  background: #fff;
  padding: calc(var(--trim-w) * .004);
  z-index: 2;
}
/* Knocked out of the card instead of sitting on a white panel — what the
   printed original does. No panel, no padding: the quiet zone is the card. */
.qr-img.knockout { background: none; padding: 0; }
.qr-img svg { width: 100%; height: 100%; display: block; }

.info-times {
  background: var(--c-brand-deep);
  padding: 0 calc(var(--bleed) + var(--trim-w) * .0675) 0
              calc(var(--bleed) + var(--trim-w) * .0742);
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: center;
  gap: .8em;
}
/* Measured both ways: Barlow at the original weights ran 7.9% short of the
   artwork's measure, Montserrat 6.9% long. Barlow with the weight up and a
   little tracking lands between them — the times band is the artwork's narrow
   Gotham cut, and Montserrat is not that cut. */
.times-lines {
  min-width: 0;
  overflow-wrap: anywhere;
  letter-spacing: .027em;
  font-size: calc(var(--trim-w) * .02618);
  /* The artwork sets these three lines on a 1.89% pitch — leading about 1.14.
     At 1.45 our block ran a fifth taller and its first line sat high. */
  line-height: 1.14;
}
/* The original sets the first line smaller as well as lighter — it is the
   caption, the two under it are the times themselves. */
.times-lines div:first-child { font-weight: 500; font-size: calc(var(--trim-w) * .02262); }
.times-lines div + div { font-weight: 700; }
.emblem { width: calc(var(--trim-w) * .0950); height: calc(var(--trim-w) * .0950); flex: none; }
.emblem svg { width: 100%; height: 100%; }

.info-centers {
  background: var(--c-brand-mid);
  /* The artwork leaves 1.5% of the trim below its last entry. Ours filled that
     space, which stretched the row pitch 3.5% and let the list drift a row and
     a half out of register by the bottom of the card. */
  padding: calc(var(--trim-h) * .0065) calc(var(--bleed) + var(--trim-w) * .038)
           calc(var(--trim-h) * .024) calc(var(--bleed) + var(--trim-w) * .0685);
  position: relative;
  overflow: hidden;
  min-height: 0;
  /* The list is given a definite height rather than left to size itself. A
     multi-column box with `height: auto` balances its columns to whatever the
     content needs and simply grows — so padding-bottom reserved nothing, it
     just made the box taller, and the last entry still printed under the badge.
     As a flex child with a definite height the columns are confined, and an
     over-long list overflows honestly where the fit pass can see it. */
  display: flex;
  flex-direction: column;
  font-size: calc(var(--trim-w) * .0270 * var(--density-b, 1));
}
.centers-h { flex: none; }
.centers-list {
  flex: 1 1 auto;
  min-height: 0;
  /* A definite-height multicol that runs out of room lays out a further column
     off to the side. Clipping means the stub never paints — the alternative is
     a sliver of a third column at the trim edge on the printed card. What is
     lost this way is counted and named by droppedCenters(); nothing here is
     allowed to disappear quietly. */
  overflow: hidden;
}
/* Hangs left of the rows beneath it, like the dates heading, and is sized off
   the TRIM rather than off the list's fitted density — the printed heading is
   the same size whether a county lists three polling places or twenty-six, and
   riding --density-b is what made it shrink with the list. */
.centers-h {
  /* .3em put the first entry 0.45% of the trim below where the artwork has it,
     and every entry under it inherited the offset. */
  margin: 0 0 .157em calc(var(--trim-w) * -.0235);
  font-family: Montserrat, Barlow, sans-serif;
  font-weight: 700;
  /* One line, like the original. The heading is free text, so it is condensed
     to the measure by fitLineToWidth rather than allowed to wrap — a wrapped
     heading still "contains its contents", so no overflow test can catch it. */
  white-space: nowrap;
  font-size: calc(var(--trim-w) * .04761 * var(--line-fit, 1));
  line-height: 1;
  letter-spacing: -.002em;
  text-transform: uppercase;
  color: var(--c-muted);
}
.centers-list {
  margin: 0;
  padding: 0;
  list-style: none;
  counter-reset: ctr;
  font-size: 1em;
  /* Measured off the original: 9.90pt type on a 14.04pt pitch. The 1.62 that
     was here spent the block's height on leading instead of on type, which is
     why the rows read a size smaller than the printed card's.
     The block still fills its height with leading before type size — a
     three-location card should not be set at twice the design size just because
     there is room — but it starts from the original's rhythm. */
  line-height: calc(1.470 * var(--cen-lead, 1));
}
/* Two columns for a county with more locations than a single column can hold at
   a readable size. The original is one column and stays one column.

   Two real elements, not CSS multi-column: multicol's columns are one box, so a
   badge sitting over the right-hand column reserved its band across the full
   width and cost the left-hand column the same space for nothing. */
.centers-cols {
  flex: 1 1 auto;
  min-height: 0;
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  column-gap: calc(var(--trim-w) * .030);
  align-items: start;
}
.centers-cols > .centers-list { height: 100%; }
/* Space given up to a corner badge that a per-row gutter cannot clear — set by
   reserveBadgeBand once the badge's own height is settled. */
.centers-list {
  padding-top: var(--band-pad-top, 0px);
  padding-bottom: var(--band-pad-bottom, 0px);
}
.centers-list li {
  counter-increment: ctr;
  display: grid;
  grid-template-columns: 1.365em minmax(0, 1fr);
  align-items: baseline;
}
.centers-list li::before {
  content: counter(ctr) ".";
  font-weight: 600;
  text-align: right;
  padding-right: .5em;
}
.centers-list.mk-bullet li::before { content: "•"; text-align: center; padding-right: .45em; }
.centers-list.mk-none li { grid-template-columns: minmax(0, 1fr); }
.centers-list.mk-none li::before { content: none; }

/* Every row is one line and clips, exactly like the printed card — over-long
   entries are condensed first (--line-fit) and named in the status line if even
   that will not hold them. `--gutter` is what the badge reserves for itself on
   the rows it sits over; without it a row simply prints underneath the badge,
   and nothing in the box model ever reports that. */
.centers-list .row {
  display: block;
  min-width: 0;
  /* MARGIN, not padding. `overflow: hidden` clips at the padding box, so a
     padding-right gutter does not clip anything — the text paints straight
     across it. A row too long to condense above the legibility floor is marked
     as cut, and with padding it was then painted over the badge anyway: the
     one case the gutter exists to prevent. A margin narrows the box itself, so
     the clip and the ellipsis land where the gutter says. */
  margin-right: var(--gutter, 0px);
  white-space: nowrap;
  overflow: hidden;
  /* A row that will not fit even condensed is cut — and says so. A hard clip
     mid-word ("1000 Ve") reads as a printing fault; an ellipsis reads as an
     abbreviation, and the status line names every row this happens to. */
  text-overflow: ellipsis;
  font-size: calc(1em * var(--line-fit, 1));
}
.centers-list li { padding-right: 0; }
/* Same wide Gotham cut as the dates body: Barlow ran the rows ~16% short of
   the original's measure at a matching point size. */
.centers-list .nm { font-family: Montserrat, Barlow, sans-serif; font-weight: 700; }
/* 600, not 500: only 600/700/800 of Montserrat are bundled, and a 500 request
   silently resolves to 600 anyway. Saying 500 was the CSS claiming an intent it
   could not honour — and a renderer that synthesised a lighter weight instead
   would have changed the measure this side is tuned to. */
.centers-list .ad { font-family: Montserrat, Barlow, sans-serif; font-weight: 600; }
/* Address on its own line under the name. Two lines by design, so the row is
   allowed its second one. Each half stays on its own single line so a row is exactly two lines high and
   the fit pass can still predict the block's height. */
.centers-list.stacked .row { white-space: normal; }
.centers-list.stacked .nm,
.centers-list.stacked .ad {
  display: block;
  white-space: nowrap;
  overflow: hidden;
}
.centers-list.stacked .ad {
  font-size: .88em;
  line-height: 1.25;
  color: var(--c-accent-lt);
}

/* The banner badge is a strip across the foot of the block, so the list has to
   give up that height — a gutter cannot reserve it. Its height is fixed in the
   trim so the reservation and the strip can never disagree; the badge's own fit
   pass sets the type into it. */
.info-centers.has-badge.badge-banner .centers-list {
  padding-bottom: calc(var(--trim-h) * .085);
}

/* ---------------------------------------------------------------- badge
   A fixed corner element. Its size comes off the trim, never off the locations
   list's fitted density — riding --density-b is what turned a five-line corner
   label into fourteen lines of single words running a third of the way down the
   card whenever the list was short. */
.badge {
  position: absolute;
  width: calc(var(--trim-w) * var(--badge-w, .1373));
  max-height: calc(var(--trim-h) * .16);
  padding: calc(var(--trim-h) * .014) calc(var(--trim-w) * .008);
  background: var(--c-brand-deep);
  color: #fff;
  text-align: center;
  /* Montserrat-Bold 7.13pt — the badge is the one part of the artwork still
     stored as live text, so the face, the weight and the size are all read
     rather than inferred. */
  font-family: Montserrat, Barlow, sans-serif;
  font-weight: 700;
  font-size: calc(var(--trim-w) * .01650 * var(--badge-fit, 1));
  line-height: 1.00;
  letter-spacing: -.01em;
  text-transform: uppercase;
  overflow: hidden;
  display: grid;
  place-items: center;
}
.badge-in { display: block; max-width: 100%; }
.badge.pos-br { right: calc(var(--bleed) + var(--trim-w) * .0608); bottom: calc(var(--trim-h) * .0017); }
.badge.pos-bl { left:  calc(var(--bleed) + var(--trim-w) * .0725); bottom: calc(var(--trim-h) * .0017); }
.badge.pos-tr { right: calc(var(--bleed) + var(--trim-w) * .0608); top:    calc(var(--trim-h) * .0120); }
.badge.pos-tl { left:  calc(var(--bleed) + var(--trim-w) * .0725); top:    calc(var(--trim-h) * .0120); }
/* A full-width strip across the foot of the block instead of a corner box. */
.badge.pos-banner {
  left: 0; right: 0; bottom: 0;
  width: auto;
  /* Height is fixed, not max-height: the list reserves exactly this much and
     the two must not be able to disagree. */
  height: calc(var(--trim-h) * .070);
  max-height: none;
  padding: calc(var(--trim-h) * .006) calc(var(--bleed) + var(--trim-w) * .050);
}
.badge.pos-banner .badge-in { display: inline; }
.badge.sty-outline {
  background: none;
  border: calc(var(--trim-w) * .0035) solid var(--c-accent-lt);
  color: var(--c-accent-lt);
}
.badge.sty-accent { background: var(--c-hot); color: #fff; }
.badge .hot { color: var(--c-accent-lt); }
.badge.sty-accent .hot { color: #fff; }

.info-foot {
  background: var(--c-brand-deep);
  /* The credit inside is set nowrap so it holds one line like the original.
     Without min-width: 0 its intrinsic width propagates up through the grid and
     widens the whole card — which then shrinks the dates block, three passes
     away from the cause. */
  min-width: 0;
  overflow: hidden;
  /* The artwork's footer band runs 92.11%..100% of the trim — 7.89% deep, with
     the URL sitting 2.28% below the band's top edge. The depth is padding, not
     type, so it is set here and asserted in tools/audit-info.js. */
  padding: calc(var(--trim-h) * .0090) calc(var(--bleed) + var(--trim-w) * .0610)
           calc(var(--bleed) + var(--trim-h) * .0050)
           calc(var(--bleed) + var(--trim-w) * .0610);
  text-align: center;
}
.foot-url {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: .45em;
  font-family: Montserrat, Barlow, sans-serif;
  font-weight: 800;
  font-size: calc(var(--trim-w) * .04285);
  line-height: 1.1;
  text-transform: uppercase;
  color: #fff;
  letter-spacing: -.022em;
  white-space: nowrap;
}
/* The `www.` and the TLD: smaller, and lower-case against the block's uppercase
   transform, exactly as the printed card sets them. */
.foot-url .lo { font-weight: 600; font-size: .68em; text-transform: lowercase; }
/* An optional line between the URL and the locked credit — a phone number, an
   office address, a "paid for by". One line, condensed to fit like the credit. */
.side-b .foot-note {
  margin-top: .3em;
  white-space: nowrap;
  overflow: hidden;
  min-width: 0;
  font-weight: 500;
  font-size: calc(var(--trim-w) * .0235 * var(--line-fit, 1));
  color: var(--c-accent-lt);
}
.socials { display: inline-flex; gap: .22em; }
.socials svg { width: calc(var(--trim-w) * .0449); height: calc(var(--trim-w) * .0449); }
.side-b .foot-credit {
  margin-top: .35em;
  /* One line, like the original. A second line pushes the block past the foot of
     the card, and the credit is the last thing anyone would look at to explain
     why the card grew. */
  white-space: nowrap;
  overflow: hidden;
  min-width: 0;
  font-size: calc(var(--trim-w) * .0205 * var(--line-fit, 1));
  letter-spacing: -.001em;
  color: var(--c-muted);
}

/* =========================================================================
   Guides + crop marks
   ========================================================================= */
.guides {
  position: absolute;
  inset: 0;
  pointer-events: none;
  display: none;
}
.show-guides .guides { display: block; }
.guides::before {
  content: "";
  position: absolute;
  inset: var(--bleed);
  outline: 1px dashed rgba(255, 45, 85, .85);
}
.guides::after {
  content: "";
  position: absolute;
  inset: calc(var(--bleed) + var(--safe, .25in));
  outline: 1px dashed rgba(0, 200, 255, .8);
}

.cropmarks { position: absolute; inset: 0; pointer-events: none; }
.cropmarks span {
  position: absolute;
  background: #000;
}
