/* Editorial data-desk, dark. One accent, serif figures, sans labels.
   Blue/red rather than red/green: the common colour-vision deficiencies are
   red-green, and party colour is the one encoding on this site a reader cannot
   afford to misread. Every party encoding is paired with a label or a position,
   never carried by hue alone. */

:root {
  --bg:        #0c0e11;
  --surface:   #14171c;
  --surface-2: #1b1f26;
  --line:      #262b33;
  --line-soft: #1d222a;
  --ink:       #e9e7e2;
  --ink-dim:   #b6bcc4;
  --muted:     #8b939d;
  --faint:     #5c646e;

  --dem:       #5b8fd9;
  --dem-deep:  #2f5fa8;
  --rep:       #e0574a;
  --rep-deep:  #a8342a;
  --accent:    #d8a657;
  --warn:      #d8a657;

  --serif: "Iowan Old Style", "Palatino Linotype", Palatino, Georgia, serif;
  --sans: system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  --mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;

  --measure: 68ch;
  --gutter: clamp(1rem, 4vw, 3rem);
}

* { box-sizing: border-box; }

html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font-family: var(--sans);
  font-size: 16px;
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
}

.wrap { max-width: 1180px; margin: 0 auto; padding: 0 var(--gutter); }

/* ---- masthead ---------------------------------------------------------- */
.masthead {
  border-bottom: 1px solid var(--line);
  /* padding-block, not the shorthand: `padding: 1.4rem 0 1.1rem` zeroed the side
     gutter .wrap sets on this same element, and the masthead sat flush against
     the screen edge at every width under 1180px. */
  padding-block: 1.4rem 1.1rem;
  display: flex; flex-wrap: wrap; gap: 1rem;
  align-items: baseline; justify-content: space-between;
}
.masthead h1 {
  font: 500 0.94rem/1.2 var(--sans);
  letter-spacing: 0.13em; text-transform: uppercase;
  margin: 0; color: var(--ink);
}
.masthead .asof { color: var(--muted); font-size: 0.85rem; font-variant-numeric: tabular-nums; }

/* ---- tabs --------------------------------------------------------------- */
/* Sticky, because it is the only way back out of a long panel, and a reader who
   has scrolled to the bottom of the race table should not have to scroll back up
   to leave it. The masthead is not sticky: it says what this is, once. */
.tabbar {
  position: sticky; top: 0; z-index: 30;
  background: var(--bg); border-bottom: 1px solid var(--line);
}
.tabbar-inner {
  display: flex; gap: .2rem;
  /* Six labels do not fit a phone. They scroll sideways here rather than
     wrapping to a second row that would double the height of a sticky bar. */
  overflow-x: auto; scrollbar-width: none;
}
.tabbar-inner::-webkit-scrollbar { display: none; }
/* A flex scroll container drops its trailing padding from the scroll extent, so
   the last tab ended flush against the screen edge once the strip was scrolled
   to the end. A spacer is a flex item, and flex items are not dropped. */
.tabbar-inner::after { content: ''; flex: 0 0 var(--gutter); }
.tab {
  flex: 0 0 auto; background: none; border: 0; cursor: pointer;
  padding: .95rem .05rem; margin-right: 1.5rem;
  font: 500 .78rem/1 var(--sans); letter-spacing: .11em; text-transform: uppercase;
  color: var(--muted); white-space: nowrap;
  border-bottom: 2px solid transparent; margin-bottom: -1px;
  /* scrollIntoView scrolls the least it can get away with, which lands the tab
     it reveals flush against the edge of the strip. This is how much daylight it
     has to leave instead. */
  scroll-margin-inline: var(--gutter);
}
.tab:last-child { margin-right: 0; }
.tab:hover { color: var(--ink-dim); }
.tab.on { color: var(--ink); border-bottom-color: var(--accent); }
.tab:focus-visible { outline: 2px solid var(--accent); outline-offset: -3px; }

/* ---- conditioning bar ---------------------------------------------------- */
/* Off: one muted line of instruction, which scrolls away like any other prose.
   On: the chips, a colour that says the numbers under it are no longer the
   unconditional forecast, and -- because the way OUT of that mode must not be
   somewhere the reader has to scroll back to find -- sticky, under the strip.
   --tabbar-h is measured in js/tabs.js rather than assumed here. */
.cond-bar { border-bottom: 1px solid var(--line-soft); padding: .7rem 0; }
.cond-bar.on {
  position: sticky; top: var(--tabbar-h, 3.2rem); z-index: 29;
  background: var(--surface); border-bottom-color: var(--line);
}
.cond-bar .flash { margin-bottom: .6rem; }
.pins-label {
  font: 500 .72rem/1 var(--sans); letter-spacing: .13em; text-transform: uppercase;
  color: var(--accent); margin-right: .3rem;
}

/* Scope on the left, conditioning on the right; they stack on a phone. Two
   questions about the same thing -- which races am I looking at, and under what
   assumption -- so they share a bar rather than each claiming a row. */
.viewrow { display: flex; flex-wrap: wrap; gap: .6rem 1.6rem; align-items: center; }
.viewrow > .pins { flex: 1 1 22rem; }
/* `min-width:0` so the control may shrink rather than pushing the page wider
   than the screen; the toggle scrolls inside itself if it ever has to. */
.scope-ctl { display: flex; align-items: center; gap: .55rem; flex: 0 1 auto; min-width: 0; }
.scope-ctl .toggle { max-width: 100%; overflow-x: auto; scrollbar-width: none; }
.scope-ctl .toggle::-webkit-scrollbar { display: none; }
.scope-label {
  font: 500 .72rem/1 var(--sans); letter-spacing: .13em; text-transform: uppercase;
  color: var(--muted);
}
/* Four labels plus the word SHOWING are 397px on a 390px phone. The word is the
   droppable part: the control is self-evident, and every section below carries a
   badge naming what it settled on. */
@media (max-width: 560px) {
  .scope-label { display: none; }
  .scope-ctl .toggle button { padding: .55rem .6rem; }
}

/* ---- section scope badges ----------------------------------------------- */
/* Sits at the end of every section head and names the races that section is
   about. Recessive by default -- it is a caption, not a claim -- and warm when
   the section cannot give the scope what it asked for, because that is the one
   case where the reader is looking at something other than what they selected. */
.head-right { display: flex; flex-wrap: wrap; gap: .6rem; align-items: baseline;
              justify-content: flex-end; margin-left: auto; }
.scope {
  font: 500 .7rem/1.35 var(--sans); letter-spacing: .1em; text-transform: uppercase;
  color: var(--muted); border: 1px solid var(--line); border-radius: 2px;
  padding: .3rem .5rem; white-space: nowrap; background: var(--surface);
}
.scope.off { color: var(--warn); border-color: var(--warn); }
.scope-note {
  font-size: .84rem; color: var(--ink-dim); max-width: 70ch;
  border-left: 2px solid var(--warn); padding-left: .8rem; margin: 0 0 1.2rem;
}
.scope-note b { color: var(--ink); font-weight: 600; }

/* ---- one instance per chamber -------------------------------------------- */
/* Under the `all` scope a chart that can only speak about one chamber draws
   three, stacked and each named. The rule is divided rather than spaced: three
   seat histograms with nothing between them read as one chart with three humps. */
.per-chamber + .per-chamber {
  margin-top: 2rem; padding-top: 1.6rem; border-top: 1px solid var(--line-soft);
}
.pc-h {
  font: 500 .72rem/1 var(--sans); letter-spacing: .14em; text-transform: uppercase;
  color: var(--accent); margin: 0 0 1rem;
}

/* The chamber the scope is on, in the card row that always shows all three. */
.topline.on { background: var(--surface-2); box-shadow: inset 2px 0 0 var(--accent); }

/* ---- calibration source lines -------------------------------------------- */
/* Three charts under one heading, drawn from two different populations. */
.cal-source {
  font: 400 .78rem/1.4 var(--sans); color: var(--faint);
  margin: -.2rem 0 .7rem; max-width: none;
}
.cov-split, #coverage-split { margin-top: 1.8rem; }

/* ---- sections ---------------------------------------------------------- */
/* scroll-margin: a cross-tab link would otherwise land under the sticky strip. */
section { padding: 2.6rem 0; border-bottom: 1px solid var(--line-soft);
          scroll-margin-top: calc(var(--tabbar-h, 3.2rem) + var(--condbar-h, 0px) + 1rem); }
section:last-of-type { border-bottom: 0; }

h2 {
  font: 500 0.8rem/1.2 var(--sans);
  letter-spacing: 0.15em; text-transform: uppercase;
  color: var(--muted); margin: 0 0 1.4rem;
}
h3 { font: 600 1rem/1.3 var(--sans); margin: 0 0 .5rem; color: var(--ink); }

/* The question a section answers, under its eyebrow label. h2 is a small
   letterspaced cap here, which a whole sentence cannot survive, so the sentence
   gets its own line in the serif the page uses for figures. */
.q {
  font: 400 clamp(1.15rem, 2.2vw, 1.5rem)/1.35 var(--serif);
  color: var(--ink); max-width: 36ch; margin: 0 0 .9rem;
}
.q em { font-style: italic; color: var(--ink-dim); }

/* A section's answer where there is no eyebrow+question pair to hang it on --
   the headline, which already opens with a serif lede of its own. */
.read { margin: 1.8rem 0 0; max-width: 52ch; }

p { max-width: var(--measure); color: var(--ink-dim); }
a { color: var(--dem); text-decoration-thickness: 1px; text-underline-offset: 2px; }

/* A definition, a rule, or a set of cases -- as a list, because that is what it
   is. Prose was doing this job in paragraphs that ran past nine hundred
   characters: a reader looking for the one case that applies to them had to read
   all of them in sequence to find it. Marker is an en dash rather than a bullet,
   which sits better beside the letterspaced caps this page sets headings in. */
ul.pts { list-style: none; padding: 0; margin: .9rem 0 0; max-width: var(--measure); }
ul.pts li {
  position: relative; padding-left: 1.1rem; margin: 0 0 .45rem;
  color: var(--ink-dim); font-size: .92rem; line-height: 1.5;
}
ul.pts li::before {
  content: "\2013"; position: absolute; left: 0; color: var(--faint);
}
ul.pts li b, ul.pts li strong { color: var(--ink); font-weight: 600; }
ul.pts li:last-child { margin-bottom: 0; }
/* Inside a smaller block -- a chart note, an alarm, a drawer -- the list
   inherits that block's size rather than imposing its own. */
.chart-note ul.pts, .caveat-why ul.pts, .caveat-major ul.pts,
.limit .limit-body ul.pts, .dt-note-block ul.pts, .dt-chart-cap ul.pts,
.dt-cond-note ul.pts, .mv-foot ul.pts, .wt-copy ul.pts {
  margin-top: .5rem; margin-bottom: .5rem; font-size: 1em; max-width: none;
}
.chart-note ul.pts li, .caveat-major ul.pts li, .limit .limit-body ul.pts li,
.dt-chart-cap ul.pts li, .dt-cond-note ul.pts li, .wt-copy ul.pts li { font-size: 1em; }
/* Last list in a block does not push the block's padding open. */
.caveat-major ul.pts:last-child, .limit .limit-body ul.pts:last-child { margin-bottom: 0; }

/* A formula is the definition. Prose paraphrases of `n / (n + 3)` cost a reader
   a sentence and tell them less, so the formula is set where the sentence was.
   `python -m engine.dashboard.build` is longer than a phone is wide, so this
   wraps rather than forcing the page to scroll sideways. */
code {
  font: 500 .88em/1.4 var(--mono); background: var(--surface-2);
  color: var(--ink); padding: .08em .34em; border-radius: 2px;
  overflow-wrap: break-word;
}
/* A formula on its own line, where it is the answer and not an aside. */
p.fml-block, div.fml-block {
  margin: .9rem 0 0; font: 500 .95rem/1.5 var(--mono);
  color: var(--ink); max-width: var(--measure);
}
p.fml-block code, div.fml-block code { background: none; padding: 0; }

/* ---- headline ---------------------------------------------------------- */
.lede {
  font: 400 clamp(1.5rem, 3.4vw, 2.15rem)/1.28 var(--serif);
  color: var(--ink); max-width: 20ch; margin: 0 0 2rem;
}
.lede em { font-style: normal; color: var(--dem); }
.lede em.r { color: var(--rep); }
/* Three chambers in one sentence. Set smaller and to a wider measure than the
   single-chamber headline: at 20ch and 2.15rem it ran to eight lines and stopped
   reading as a lede at all. */
.lede.multi {
  font-size: clamp(1.15rem, 2.1vw, 1.55rem);
  max-width: 46ch; line-height: 1.4;
}

/* One card per chamber, stacked. This was `auto-fit, minmax(260px, 1fr)`, which
   packed House and Senate side by side in the half-width column `.split` gives
   it. A third chamber turned that into a 2x2 with an empty cell, and an empty
   cell in a bordered grid reads as a rendering fault rather than as a layout.
   Stacking has no such failure mode at any card count or any width. */
.toplines { display: grid; gap: 1px; background: var(--line);
            grid-template-columns: 1fr; border: 1px solid var(--line);
            align-self: start; }
.topline { background: var(--surface); padding: 1.5rem 1.6rem; transition: background .12s; }
.topline:hover { background: var(--surface-2); }
.topline .chamber {
  font: 500 .72rem/1 var(--sans); letter-spacing: .14em;
  text-transform: uppercase; color: var(--muted);
}
.topline .prob {
  font: 400 3.3rem/1 var(--serif); margin: .55rem 0 .1rem;
  font-variant-numeric: tabular-nums; letter-spacing: -0.01em;
}
.topline .prob.d { color: var(--dem); }
.topline .prob.r { color: var(--rep); }
.topline .who { font-size: .9rem; color: var(--ink-dim); }
.topline .seats {
  margin-top: .9rem; padding-top: .9rem; border-top: 1px solid var(--line);
  font-size: .88rem; color: var(--muted); font-variant-numeric: tabular-nums;
}
.topline .seats b { color: var(--ink); font-weight: 600; }

/* A chamber whose headline number is a COUNT, not a probability -- governors.
   The disclosure line under it is the one thing on this card that is not a
   forecast: it says the number's width rests on an asserted sigma. Warm rather
   than red; it is a caveat, not an error. */
.topline .seats.prior-fit {
  margin-top: .5rem; padding-top: .5rem;
  border-top: 1px dashed var(--line);
  color: var(--ink-dim);
}
.topline .seats.prior-fit b { color: var(--ink-dim); font-weight: 600; }
.topline .seats.prior-fit a { color: inherit; text-decoration: underline dotted; }
.topline .seats .muted { color: var(--muted); }

/* The Senate's third outcome. Not a caveat (dashed, dim) and not a forecast line
   (muted, ruled above): it is a slice of the headline that exists only under an
   assumption, so it takes the warm left rule the page already uses for "read this
   before you read the number". On the chamber the page is scoped to it is set a
   step larger, because there it carries the mechanism and not just the figure. */
.topline .seats.third {
  margin-top: .9rem; padding: .1rem 0 .1rem .8rem;
  border-top: 0; border-left: 2px solid var(--warn);
  color: var(--ink-dim); line-height: 1.5;
}
.topline .seats.third b { color: var(--ink); }
.topline .seats.third.open { font-size: .92rem; }
.topline .seats.third .tie { display: block; margin-top: .45rem; color: var(--muted); }

/* ---- placeholder panels ------------------------------------------------ */
.panel {
  border: 1px dashed var(--line); border-radius: 3px;
  padding: 1.6rem; background: var(--surface);
}
.panel.pending { color: var(--faint); }
.panel.pending h3 { color: var(--muted); }
.grid { display: grid; gap: 1rem; grid-template-columns: repeat(auto-fit, minmax(min(280px, 100%), 1fr)); }

/* ---- caveat strip ------------------------------------------------------ */
.caveats { display: flex; flex-wrap: wrap; gap: .5rem; margin-top: 1rem; }
.caveat {
  font: 500 .76rem/1 var(--sans); letter-spacing: .02em;
  border: 1px solid var(--line); border-left: 2px solid var(--warn);
  background: var(--surface); color: var(--ink-dim);
  padding: .5rem .7rem; border-radius: 2px;
}

/* ---- diagnostics ------------------------------------------------------- */
.scroll-x { overflow-x: auto; max-width: 100%; }
.diag { font-family: var(--mono); font-size: .8rem; border-collapse: collapse; width: 100%; min-width: 30rem; }
.diag td { padding: .38rem .6rem; border-bottom: 1px solid var(--line-soft); vertical-align: top; }
.diag td:first-child { color: var(--muted); white-space: nowrap; }
.diag td:last-child { color: var(--ink); }
.pass { color: #6fbf8b; } .fail { color: var(--rep); }

.stat-row { display: flex; flex-wrap: wrap; gap: 2rem 3rem; margin-top: .5rem; }
.stat { }
.stat .n { font: 400 1.7rem/1 var(--serif); font-variant-numeric: tabular-nums; }
/* Capped, because these labels now say what the number means rather than naming
   it, and an uncapped one stretches to the width of the row it sits in. */
.stat .l { font-size: .8rem; color: var(--muted); margin-top: .25rem; max-width: 26ch; }

footer { padding: 2.5rem 0 4rem; color: var(--faint); font-size: .82rem; }
footer a { color: var(--muted); }

@media (prefers-reduced-motion: reduce) {
  * { animation-duration: .01ms !important; transition-duration: .01ms !important; }
}

/* ---- chart furniture ---------------------------------------------------- */
.head { display:flex; flex-wrap:wrap; gap:.6rem 1rem; align-items:baseline;
        justify-content:space-between; }
.head h2 { margin-bottom:1rem; }
.head > .head-right { margin-bottom:1rem; }
.split { display:grid; gap:1.6rem; grid-template-columns:minmax(300px,1fr) minmax(280px,1fr); align-items:start; }
.two-col { display:grid; gap:2rem; grid-template-columns:minmax(320px,1.6fr) minmax(280px,1fr); align-items:start; }
@media (max-width:820px){ .split,.two-col{ grid-template-columns:1fr; } }

.iconwrap svg { max-width:440px; }
.icon-cap { font-size:.95rem; color:var(--ink-dim); margin:.9rem 0 0; max-width:44ch; }
.icon-cap b { color:var(--ink); font-weight:600; }
.cond { color:var(--accent); }
.chart-note { font-size:.85rem; color:var(--muted); margin:.85rem 0 0; max-width:70ch; }
.chart-note b { color:var(--ink-dim); font-weight:600; }
.frozen { color:var(--accent); }

.toggle { display:inline-flex; border:1px solid var(--line); border-radius:3px; overflow:hidden; }
.toggle button, .chip {
  background:var(--surface); color:var(--muted); border:0; cursor:pointer;
  font:500 .8rem/1 var(--sans); padding:.55rem .8rem; letter-spacing:.02em;
}
.toggle button + button { border-left:1px solid var(--line); }
.toggle button.on { background:var(--surface-2); color:var(--ink); }
.toggle button:hover, .chip:hover { color:var(--ink); }
.chip { border:1px solid var(--line); border-radius:3px; margin-left:.6rem; }
.chip.on { background:var(--surface-2); color:var(--ink); border-color:var(--faint); }

.keys { display:flex; flex-wrap:wrap; gap:1.1rem; margin-top:1rem; }
.key { display:inline-flex; align-items:center; gap:.45rem; font-size:.8rem; color:var(--ink-dim); }
.key i { width:11px; height:11px; border-radius:2px; display:inline-block; }
.key em { font-style:normal; color:var(--faint); }

.tip {
  position:fixed; z-index:50; display:none; pointer-events:none;
  background:var(--surface-2); border:1px solid var(--line); border-radius:3px;
  padding:.5rem .65rem; font:400 .82rem/1.4 var(--sans); color:var(--ink);
  box-shadow:0 6px 22px rgba(0,0,0,.55); max-width:250px;
}
.tip b { font-weight:600; }
.tip-dim { color:var(--muted); }

/* ---- conditioning ------------------------------------------------------- */
.pins { display:flex; flex-wrap:wrap; gap:.5rem; align-items:center; }
.pin {
  font:500 .82rem/1 var(--mono); background:var(--surface-2); color:var(--ink);
  border:1px solid var(--faint); border-radius:3px; padding:.5rem .7rem; cursor:pointer;
}
.pin:hover { border-color:var(--accent); }
.pin.clear { color:var(--muted); border-color:var(--line); font-family:var(--sans); }
.hint { flex-basis:100%; font-size:.85rem; color:var(--muted); margin:.4rem 0 0; }
/* With nothing pinned the hint is the bar's only content: no row above it to sit
   under, and no reason to hold it to the prose measure and wrap it onto two
   lines in a strip that is otherwise one line tall. */
.cond-bar:not(.on) .hint { margin:0; max-width:none; }
.flash {
  border-left:2px solid var(--warn); background:var(--surface); color:var(--ink-dim);
  padding:.6rem .8rem; font-size:.85rem; margin-bottom:.9rem; border-radius:2px;
}

/* ---- table -------------------------------------------------------------- */
.table-bar { display:flex; flex-wrap:wrap; gap:.6rem; align-items:center; margin:1.6rem 0 .8rem; }
.table-bar input, .table-bar select {
  background:var(--surface); border:1px solid var(--line); color:var(--ink);
  padding:.5rem .6rem; border-radius:3px; font:400 .85rem var(--sans);
}
.table-count { color:var(--muted); font-size:.8rem; }
.table-wrap { max-height:460px; overflow:auto; border:1px solid var(--line); border-radius:3px; }
table.races { width:100%; border-collapse:collapse; font-size:.85rem; }
table.races th {
  position:sticky; top:0; background:var(--surface-2); text-align:left; cursor:pointer;
  font:500 .74rem/1 var(--sans); letter-spacing:.08em; text-transform:uppercase;
  color:var(--muted); padding:.6rem .7rem; border-bottom:1px solid var(--line); white-space:nowrap;
}
table.races th[data-sort]::after { content:' ▲'; color:var(--accent); }
table.races th[data-sort=desc]::after { content:' ▼'; }
table.races td { padding:.5rem .7rem; border-bottom:1px solid var(--line-soft); color:var(--ink-dim); }
table.races td.num { text-align:right; font-variant-numeric:tabular-nums; font-family:var(--mono); }
table.races tbody tr { cursor:pointer; }
table.races tbody tr:hover { background:var(--surface); }
table.races tbody tr:focus { outline:1px solid var(--accent); outline-offset:-1px; }

svg :focus { outline:none; }
svg :focus-visible { outline:2px solid var(--accent); }

/* ---- scenario slider ---------------------------------------------------- */
.scenario { display:grid; gap:1.4rem; grid-template-columns:minmax(300px,1.2fr) minmax(230px,.8fr);
            align-items:start; }
.scenario > div:first-child { grid-row:1/3; }
@media (max-width:820px){ .scenario{ grid-template-columns:1fr; } .scenario > div:first-child{ grid-row:auto; } }
.scenario-ctl { display:flex; gap:.6rem; align-items:center; flex-wrap:wrap; }
.scenario-ctl input[type=range] { flex:1 1 160px; accent-color:var(--accent); }
.scenario-read { border-left:2px solid var(--line); padding-left:1rem; }
.sc-row { display:flex; flex-wrap:wrap; align-items:baseline; gap:.5rem; padding:.3rem 0; }
.sc-k { color:var(--muted); font-size:.8rem; min-width:8.5rem; }
.sc-v { color:var(--ink); font-variant-numeric:tabular-nums; }
.sc-v em { font-style:normal; color:var(--accent); font-size:.75rem; }
.sc-row.big .sc-v { font:400 1.5rem/1 var(--serif); }
.sc-sub { flex-basis:100%; color:var(--faint); font-size:.78rem; padding-left:8.5rem; }
.sc-delta { margin-top:.7rem; color:var(--accent); font-size:.85rem; }
.hops-read { min-height:1.4em; }

/* ---- drawer ------------------------------------------------------------- */
.drawer {
  position:fixed; top:0; right:0; bottom:0; width:min(420px,92vw); z-index:40;
  background:var(--surface); border-left:1px solid var(--line);
  box-shadow:-16px 0 40px rgba(0,0,0,.5); overflow-y:auto;
}
.drawer-inner { padding:1.6rem 1.6rem 3rem; }
.drawer-close {
  position:absolute; top:.6rem; right:.8rem; background:none; border:0;
  color:var(--muted); font-size:1.6rem; line-height:1; cursor:pointer;
}
.drawer-close:hover { color:var(--ink); }
.drawer h3 { font-size:1.15rem; }
.dt-state { color:var(--muted); font-weight:400; font-size:.85rem; margin-left:.4rem; }
/* The ballot. A 2px party rule rather than a dot, so the colour reads as the
   same signal the conditioning callout uses, and every row is labelled in words
   as well -- party is never carried by hue alone anywhere on this site. */
.dt-ballot { list-style:none; margin:.9rem 0 0; padding:0; }
.dt-ballot li { display:flex; align-items:baseline; gap:.6rem; padding:.34rem 0; }
.dt-chip { width:2px; flex:0 0 2px; align-self:stretch; border-radius:1px; }
.dt-name { flex:1; font-size:.95rem; color:var(--ink); }
/* Fixed width so the party words line up down the list whether or not a row
   also carries the incumbent pill; the pill is then the only thing that breaks
   the right edge, which is the emphasis it is there for. */
.dt-party { flex:0 0 5.4rem; text-align:right; color:var(--muted); font-size:.75rem; }
/* Neutral, not gold: on this page gold is the alarm colour (--warn aliases
   --accent), and an incumbency is a fact about the race, not a warning about
   it. The pill reads by its form and its word. */
.dt-inc {
  font:500 .64rem/1 var(--sans); letter-spacing:.11em; text-transform:uppercase;
  color:var(--ink); background:var(--surface-2); border:1px solid var(--line);
  border-radius:2px; padding:.22rem .36rem; white-space:nowrap;
}
.dt-holder { margin:.6rem 0 0; color:var(--faint); font-size:.8rem; line-height:1.45; }
.dt-head { display:flex; gap:2rem; margin:1.4rem 0 1.4rem; }
.dt-big { font:400 1.9rem/1 var(--serif); display:block; font-variant-numeric:tabular-nums; }
.dt-sub { color:var(--muted); font-size:.78rem; }
.drawer h4 {
  font:500 .72rem/1 var(--sans); letter-spacing:.13em; text-transform:uppercase;
  color:var(--muted); margin:1.5rem 0 .5rem; padding-top:.8rem; border-top:1px solid var(--line-soft);
}
.dt-row { display:flex; justify-content:space-between; gap:1rem; padding:.32rem 0; font-size:.85rem; }
.dt-k { color:var(--muted); }
.dt-v { color:var(--ink-dim); text-align:right; font-variant-numeric:tabular-nums; }
.dt-row.dt-sum { border-top:1px solid var(--line); margin-top:.3rem; padding-top:.5rem; }
.dt-row.dt-sum .dt-k, .dt-row.dt-sum .dt-v { color:var(--ink); font-weight:600; }
.dt-note { display:block; color:var(--faint); font-size:.75rem; font-weight:400; }
.dt-note-block { color:var(--faint); font-size:.8rem; margin-top:1.2rem; }
.dt-lead { font-size:.9rem; margin:.8rem 0 1.2rem; }
.dt-cond { background:var(--surface-2); border-left:2px solid var(--accent);
           padding:.6rem .8rem; font-size:.85rem; margin-bottom:1rem; color:var(--ink-dim); }
.dt-flags { margin-top:1.4rem; color:var(--warn); font-size:.8rem; }
.dt-pin { margin-top:1.5rem; margin-left:0; }

/* ---- calibration + limits ---------------------------------------------- */
.three-col { display:grid; gap:2rem; grid-template-columns:repeat(auto-fit,minmax(min(290px,100%),1fr)); align-items:start; }
.three-col h3 { font-size:.95rem; margin-bottom:.7rem; }

.limits { display:grid; gap:.5rem; margin:.8rem 0 2rem; grid-template-columns:repeat(auto-fit,minmax(min(320px,100%),1fr)); }
.limit { background:var(--surface); border:1px solid var(--line); border-left:2px solid var(--line);
         border-radius:2px; }
.limit.alarm { border-left-color:var(--warn); }
.limit.structural { border-left-color:var(--faint); }
.limit summary {
  cursor:pointer; padding:.7rem .9rem; font-size:.88rem; color:var(--ink);
  list-style:none; display:flex; justify-content:space-between; gap:.6rem; align-items:baseline;
}
.limit summary::-webkit-details-marker { display:none; }
.limit summary::after { content:'+'; color:var(--muted); font-size:1rem; line-height:1; }
.limit[open] summary::after { content:'−'; }
.limit summary:hover { color:var(--accent); }
/* The body opens with a bare sentence and then carries block children -- a fact
   table, a list, sometimes a closing paragraph. No `> :first-child { margin-top }`
   reset here: the first ELEMENT child is usually the table, and zeroing its top
   margin jams it against the sentence above it, which is a text node. */
.limit .limit-body { padding:0 .9rem .9rem; font-size:.83rem; color:var(--ink-dim); }
.limit .limit-body > :last-child { margin-bottom:0; }
.limit .limit-body p { margin:.6rem 0 0; font-size:1em; color:inherit; max-width:none; }
.limit em { color:var(--ink); font-style:italic; }
#s-limits h3 {
  font:500 .74rem/1 var(--sans); letter-spacing:.12em; text-transform:uppercase;
  color:var(--muted); margin:1.4rem 0 0;
}

/* ---- walkthrough -------------------------------------------------------- */
.wt-bar { display:flex; gap:.5rem; align-items:center; flex-wrap:wrap; margin-bottom:1rem; }
.wt-bar select {
  background:var(--surface); border:1px solid var(--line); color:var(--ink);
  padding:.5rem .6rem; border-radius:3px; font:400 .85rem var(--sans);
}
.wt-bar .chip { margin-left:0; }
.wt-bar .chip:disabled { opacity:.4; cursor:default; }
.wt-chart svg { max-width:760px; }
.wt-dots { display:flex; gap:.4rem; margin:.6rem 0 1.1rem; }
.wt-dot {
  width:34px; height:4px; border:0; border-radius:2px; padding:0; cursor:pointer;
  background:var(--line);
}
.wt-dot.done { background:var(--faint); }
.wt-dot.on { background:var(--accent); }
.wt-copy { max-width:64ch; min-height:8.5rem; }
.wt-step {
  font:500 .72rem/1 var(--sans); letter-spacing:.13em; text-transform:uppercase;
  color:var(--faint); margin-bottom:.4rem;
}
.wt-copy h3 { font-size:1.05rem; margin-bottom:.4rem; }
.wt-copy p { font-size:.92rem; color:var(--ink-dim); margin:0; }
.wt-copy b { color:var(--ink); font-weight:600; }
.wt-copy em { color:var(--ink); font-style:italic; }

/* ---- the playing field --------------------------------------------------- */
/* Tables here are read, not sorted or clicked, so they share none of the
   interaction affordances of table.races. */
.pf-h { margin: 2.2rem 0 .6rem; }
.pf-stats { margin-bottom: 2.2rem; }
/* The field name and its map are the row's identity; only the provenance column
   is allowed to wrap. */
table.plain td:first-child, table.plain td:nth-child(2) { white-space: nowrap; }
/* Held to a readable measure rather than the full page: these are six-row
   tables, and stretching them to 1200px puts the label and its number at
   opposite ends of the screen. */
table.plain { border-collapse: collapse; font-size: .85rem; margin: .9rem 0 0;
              width: 100%; max-width: 48rem; }
table.plain th, table.plain td { padding: .45rem 1.1rem; }
table.plain th:first-child, table.plain td:first-child { padding-left: 0; }
table.plain th:last-child, table.plain td:last-child { padding-right: 0; }
table.plain th {
  text-align: left; white-space: nowrap;
  font: 600 .72rem/1.2 var(--sans); letter-spacing: .04em; text-transform: uppercase;
  color: var(--muted); border-bottom: 1px solid var(--line);
}
table.plain td { border-bottom: 1px solid var(--line-soft); color: var(--ink-dim); }
table.plain td b { color: var(--ink); font-weight: 600; }
table.plain th.num, table.plain td.num { text-align: right; }
table.plain td.num { font-variant-numeric: tabular-nums; font-family: var(--mono); }
/* A vintage change is a break in what the rows MEAN, so it is drawn as a break
   and never as a gridline you could read a trend across. */
table.plain tr.brk td { border-top: 2px solid var(--line); padding-top: .8rem; }
.pf-vint { color: var(--faint); font-size: .78rem; }

/* ---- front page: the card as a door ------------------------------------- */
/* The three cards were divs. They have always BEHAVED like a control -- the row
   marks whichever chamber the scope is on -- while being unclickable, which is
   the worst of both. The face of the card is now the control that changes the
   page's subject, and the link row along the bottom is the three questions a
   reader has after reading the number. */
.tl-main {
  display: block; width: 100%; text-align: left;
  background: none; border: 0; padding: 0; margin: 0;
  color: inherit; font: inherit; cursor: pointer;
}
.tl-main:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }

/* Movement since a run the provenance will actually reach back to. Direction is
   an arrow as well as a colour; nothing on this page carries party in hue alone. */
.tl-move {
  display: flex; align-items: center; gap: .5rem; flex-wrap: wrap;
  margin-top: .7rem; min-height: 30px;
}
.tl-delta {
  font: 600 .82rem/1 var(--sans); font-variant-numeric: tabular-nums;
  letter-spacing: .01em; white-space: nowrap;
}
.tl-delta.up { color: var(--dem); }
.tl-delta.down { color: var(--rep); }
.tl-delta.flat, .tl-nomove { color: var(--muted); font-weight: 500; }
.tl-nomove { font: 500 .8rem/1 var(--sans); }
.tl-since { font-size: .78rem; color: var(--faint); white-space: nowrap; }
.tl-spark { margin-left: auto; display: block; line-height: 0; }

/* A number inside a sentence that leads to the chart it came from. Underlined
   rather than coloured: the sentence around it is already carrying party colour
   and a blue 218 would read as a Democratic 218. */
.tl-inline {
  background: none; border: 0; padding: 0; cursor: pointer;
  font: inherit; font-weight: 600; color: var(--ink);
  text-decoration: underline dotted var(--faint); text-underline-offset: 3px;
}
.tl-inline:hover { color: var(--accent); text-decoration-color: var(--accent); }
.tl-inline:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

.tl-links { display: flex; flex-wrap: wrap; gap: .4rem; margin-top: 1rem; }
.tl-link {
  background: none; border: 1px solid var(--line); border-radius: 2px;
  color: var(--muted); cursor: pointer;
  font: 500 .72rem/1 var(--sans); letter-spacing: .06em; text-transform: uppercase;
  padding: .42rem .6rem;
}
.tl-link:hover { color: var(--ink); border-color: var(--faint); background: var(--bg); }
.tl-link:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }

/* Where the comparability rule is spelled out. Every "since" above is measured
   inside the window this sentence describes, and where the window stops it says
   what stopped it. */
.tl-note {
  font-size: .8rem; color: var(--faint); margin: .8rem 0 0; max-width: none;
  line-height: 1.5;
}
.tl-note b { color: var(--muted); font-weight: 600; }
.tl-note a { color: var(--muted); }

.icon-why { display: block; margin-top: .5rem; color: var(--faint); font-size: .85rem; }

/* ---- the environment strip ---------------------------------------------- */
/* In the half of the headline split that used to be empty. The generic ballot is
   the input every number in this block is downstream of and it was reported
   nowhere on the front page. */
.env-strip {
  display: grid; grid-template-columns: 1fr 1fr; gap: 1px;
  background: var(--line); border: 1px solid var(--line);
  margin-top: 1.8rem;
}
.env-stat { background: var(--surface); padding: .85rem .95rem; }
.env-n {
  font: 400 1.55rem/1 var(--serif); color: var(--ink);
  font-variant-numeric: tabular-nums;
}
.env-u { font-size: .9rem; color: var(--muted); margin-left: .15rem; }
.env-l { font-size: .74rem; color: var(--faint); margin-top: .4rem; line-height: 1.45; }
.env-l b { color: var(--ink-dim); font-weight: 600; }
/* The one tile that is a door rather than a fact. */
.env-go { cursor: pointer; }
.env-go:hover { background: var(--surface-2); }
.env-go:hover .env-n { color: var(--accent); }
.env-go:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.env-go .env-l::after { content: ' \2192'; color: var(--accent); }

/* ---- caveats, ranked ----------------------------------------------------- */
/* Eight chips of identical weight said that "17 polls may be counted twice" and
   "the Senate number assumes three independents lose" are the same size of
   problem. The first is worth nothing you can see; the second is worth forty
   points of Senate control. */
.caveat-block { margin-top: 1.6rem; }
.caveat-major {
  border: 1px solid var(--line); border-left: 2px solid var(--warn);
  background: var(--surface); border-radius: 2px;
  padding: .85rem 1rem; margin-bottom: .6rem;
  font-size: .88rem; color: var(--ink-dim); max-width: var(--measure); line-height: 1.5;
}
.caveat-major b { color: var(--ink); font-weight: 600; }
.caveat-major .cm-t { display: block; margin-bottom: .25rem; }

.caveats-more { margin-top: .9rem; }
.caveats-more > summary {
  cursor: pointer; list-style: none; display: inline-flex; align-items: center; gap: .4rem;
  font: 500 .78rem/1 var(--sans); color: var(--muted);
  border: 1px solid var(--line); border-radius: 2px; padding: .5rem .7rem;
  background: var(--surface);
}
.caveats-more > summary::-webkit-details-marker { display: none; }
.caveats-more > summary::before { content: '\25b8'; color: var(--faint); }
.caveats-more[open] > summary::before { content: '\25be'; }
.caveats-more > summary:hover { color: var(--ink); border-color: var(--faint); }
.caveats-more > summary:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.caveat-group { margin-top: 1.2rem; }
.caveat-group h4 {
  font: 500 .7rem/1 var(--sans); letter-spacing: .13em; text-transform: uppercase;
  color: var(--accent); margin: 0 0 .4rem;
}
.caveat-why { font-size: .8rem; color: var(--faint); margin: 0 0 .7rem; max-width: 62ch; }
.caveat-why a { color: var(--muted); }

/* ---- what to watch ------------------------------------------------------- */
/* The list of races the answer rests on, and each row opens that race. */
.wl-h {
  font: 500 .72rem/1 var(--sans); letter-spacing: .13em; text-transform: uppercase;
  color: var(--muted); margin: 0 0 .7rem;
}
.wl { border: 1px solid var(--line); border-radius: 2px; overflow: hidden; }
.wl-row {
  display: grid; align-items: center; gap: .9rem;
  grid-template-columns: 4.6rem minmax(0, 1fr) 3.2rem 6rem 5.4rem;
  width: 100%; text-align: left; cursor: pointer;
  background: var(--surface); border: 0; border-top: 1px solid var(--line-soft);
  padding: .6rem .9rem; color: var(--ink-dim); font: inherit;
}
.wl-row:first-child { border-top: 0; }
.wl-row:hover { background: var(--surface-2); }
.wl-row:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.wl-id {
  font: 600 .82rem/1 var(--sans); color: var(--ink);
  font-variant-numeric: tabular-nums; letter-spacing: .02em;
}
.wl-who {
  font-size: .82rem; color: var(--muted);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.wl-cand { font-weight: 500; }
.wl-v { color: var(--faint); margin: 0 .35rem; font-style: italic; }
.wl-noballot { color: var(--faint); font-style: italic; }
.wl-p {
  font: 600 .84rem/1 var(--sans); text-align: right; font-variant-numeric: tabular-nums;
}
.wl-bar { position: relative; display: block; height: 6px;
          background: var(--line-soft); border-radius: 1px; }
.wl-bar i { display: block; height: 100%; border-radius: 1px; opacity: .75; }
/* Diverging about a centre line, for the mode whose quantity is a signed margin
   rather than a share. The tick is the zero, and it sits proud of the track so
   it reads as an axis rather than as a gap in the bar. */
.wl-bar.div::before {
  content: ''; position: absolute; left: 50%; top: -3px; bottom: -3px;
  width: 1px; background: var(--faint);
}
.wl-bar.div i { position: absolute; top: 0; bottom: 0; height: 100%; }
.wl-v2 {
  font-size: .78rem; color: var(--muted); text-align: right;
  font-variant-numeric: tabular-nums;
}
.wl-key {
  display: grid; gap: .9rem; margin: .45rem 0 0;
  grid-template-columns: 4.6rem minmax(0, 1fr) 3.2rem 6rem 5.4rem;
  font-size: .68rem; color: var(--faint); letter-spacing: .04em; text-transform: uppercase;
  max-width: none; padding: 0 .9rem;
}
.wl-key span:first-child { grid-column: 3; text-align: right; }
.wl-key span:last-child { grid-column: 4 / span 2; text-align: right; }

/* The dotplot is laid out at 720x220 and `svg()` gives it width:100%, so in a
   1080px column it scaled to 330px tall -- 110px of which is the empty air above
   a stack that is only ever a few dots high. Held at the width it was drawn for;
   three of these are on the page under the `all` scope. */
#dotplot svg { max-width: 720px; }

/* ---- why the forecast moved -------------------------------------------- */
.mv-lede { margin: 0 0 .7rem; color: var(--ink); }
.mv-none { margin: 0; color: var(--faint); font-style: italic; }
.mv { border: 1px solid var(--line); border-radius: 2px; overflow: hidden; }
.mv-row {
  display: grid;
  grid-template-columns: 13rem 1fr 5rem;
  grid-template-areas: "k bar v" ". note note";
  align-items: center;
  gap: .1rem .8rem;
  padding: .5rem .7rem;
  border-top: 1px solid var(--line);
}
.mv-row:first-child { border-top: 0; }
.mv-k { grid-area: k; font-size: .85rem; color: var(--ink); }
.mv-v { grid-area: v; text-align: right; font-variant-numeric: tabular-nums;
        font-size: .9rem; font-weight: 500; color: var(--faint); }
.mv-bar { grid-area: bar; position: relative; display: block; height: 6px;
          background: var(--surface-2); border-radius: 1px; }
/* The centre line is the zero a contribution is signed about, and it is the only
   rule in this panel -- an axis a reader has to find is worse than none. */
.mv-bar::before {
  content: ""; position: absolute; left: 50%; top: -2px; bottom: -2px;
  border-left: 1px solid var(--line);
}
.mv-bar i { position: absolute; top: 0; bottom: 0; height: 100%;
            border-radius: 1px; opacity: .8; }
.mv-note { grid-area: note; font-size: .75rem; color: var(--faint); }
.mv-wide { font-style: italic; }
.mv-foot { margin: .6rem 0 0; font-size: .78rem; color: var(--faint); }

/* A section the current scope cannot ask for. Set in js/main.js, alongside the
   badge, and deliberately not `hidden` -- that attribute belongs to the payload.
   `!important` because the panel-level rules that lay these out are more
   specific, and a section that is meant to be gone must actually be gone. */
.scope-hidden { display: none !important; }

/* ---- narrow viewports ---------------------------------------------------- */
/* This page is read on a phone now that it has an address, and the thing that
   broke there was never the prose -- it was the charts. Every chart is laid out
   in viewBox units at a fixed design width and then given `width:100%`, which
   scales the LABELS along with everything else. Measured at 390px: the race
   swarm rendered at 0.40 of its design width and its axis text at 4.4px, the
   seat dotplot at 0.50 and 5.5px, the trend at 0.51 and 5.1px. Axis text on this
   page is 9.4 to 11.1 viewBox units, so the scale at which the smallest of it
   still clears 8.5px is about 0.9 -- and that is the floor below which a chart
   scrolls instead of shrinking.
   
   Shrinking further does not produce a smaller readable chart. The marks
   collide as well: 426 dots across 358px is a smudge whatever size its labels
   are, and zooming a raster of it recovers nothing. */

/* `min-width: 0` on both, and it is load-bearing rather than defensive. A flex
   or grid item takes its automatic minimum size from its content, and the
   content here now declares a min-width of up to 0.9 x the design width -- so
   without this the scroller does not scroll, it INFLATES every ancestor it sits
   in and the whole page scrolls sideways instead. Measured: adding the chart
   floor without this took the page from 395px wide to 485px. */
.xscroll-frame { position: relative; min-width: 0; max-width: 100%; }
/* `min-width: 0` is not enough on its own: it fixes the scroller's own automatic
   minimum size but the frame still CONTRIBUTES the content's min-content width
   upward, so any ancestor that sizes to its contents -- an auto grid track, a
   plain block inside one -- grows to 468px and takes the page with it. This
   makes the scroller's inline size independent of what is inside it, which is
   the whole claim a scroll container is supposed to be making. */
.xscroll { overflow-x: auto; overscroll-behavior-x: contain;
           min-width: 0; max-width: 100%; contain: inline-size; }

/* The fade is the affordance, and it hangs off a class set from a measurement
   (js/charts/util.js) because no CSS can know whether a given scroller actually
   overflows -- that depends on the payload, the scope and the viewport. It sits
   on the FRAME, not the scroller, so it does not slide away on the first swipe.
   `is-scrollable` is cleared at the right-hand end, so the fade is never a
   promise of content that is not there. */
.xscroll-frame {
  /* What the fade fades TO. The default is the page, which is what almost every
     chart sits on; the race table sits on a lighter surface and says so. A fade
     to the wrong colour reads as a band drawn across the content rather than as
     an edge. */
  --fade-to: var(--bg);
}
.xscroll-frame::after {
  content: ''; position: absolute; inset-block: 0; right: 0; width: 2.25rem;
  pointer-events: none; opacity: 0; transition: opacity .18s ease;
  /* A smooth ramp the whole width. The first version went fully opaque at 78%
     and the last fifth of it was a flat dark block sitting on the chart. */
  background: linear-gradient(to right, transparent, var(--fade-to));
}
.xscroll-frame.is-scrollable::after { opacity: 1; }
@media (prefers-reduced-motion: reduce) {
  .xscroll-frame::after { transition: none; }
}

/* The chart scroller carries its own design width, so a chart that was already
   narrow never reaches the floor and never scrolls. Only the wide ones do. */
/* --legible-w is measured per chart in js/charts/util.js: the width at which
   that chart's own smallest label reaches 9px, capped at its design width. A
   chart whose labels are large is still free to shrink.
   
   NOT INSIDE A VIEWPORT MEDIA QUERY, and it was. Keyed to `max-width: 760px`
   this protected phones and did nothing for the race drawer, which is 420px wide
   on a 1440px screen: every chart in it rendered at 0.54 of its design width with
   labels at about 5px, on the widest monitor available. The floor is a fact about
   the CONTAINER, not the window. Applied unconditionally it costs nothing in a
   wide column -- min-width below the available width never binds -- and it
   catches every narrow box, whatever made it narrow. */
.chart-scroll > svg { min-width: var(--legible-w, 0px); }

@media (max-width: 760px) {
  /* The table's scroll box is capped at 460px tall for the desktop layout, which
     on a phone is most of the screen given to a list you are scrolling past. */
  .table-wrap { max-height: 60vh; }
}

/* ---- touch targets ------------------------------------------------------- */
/* 44px is the usual floor and several controls sat at 22 to 30. Inline controls
   inside running prose cannot simply grow -- a 44px-tall button in a paragraph
   sets its own line height and opens a gap in the text -- so those take the hit
   area from a pseudo-element instead, which is invisible to layout. */
@media (pointer: coarse) {
  .toggle button, .chip { padding: 1rem .9rem; }
  /* The scope strip has its own narrow-width rule at 560px which is the more
     specific selector, so it has to be named here or it keeps its 30px. Only the
     vertical padding grows: the horizontal padding is small at that width
     because four labels have to fit across a phone, and that has not changed. */
  .scope-ctl .toggle button { padding: 1rem .6rem; }
  .tab { padding: 1.15rem .05rem; }

  .tl-inline, .tl-link, .wl-row, .mv-row {
    position: relative;
  }
  .tl-inline::before, .tl-link::before {
    content: ''; position: absolute; inset: -11px -8px;
  }
  .tl-link { padding: .5rem .75rem; }

  /* The walkthrough's step dots were 34x4: a 4px-tall control is a target you
     hit by luck. The bar stays 4px because it reads as a progress rule rather
     than as a row of buttons; only the hit area grows. */
  .wt-dots { gap: .55rem; }
  .wt-dot { position: relative; }
  .wt-dot::before { content: ''; position: absolute; inset: -20px -4px; }

  /* A tooltip that must be dismissed by tapping elsewhere should not itself be
     the thing under the finger. */
  .tip { max-width: min(17rem, calc(100vw - 2rem)); }
}

/* ---- the watchlist row --------------------------------------------------- */
/* Five columns at 4.6 + 3.2 + 6 + 5.4rem of fixed track, four .9rem gaps and
   1.8rem of padding need 393px before the name column gets anything at all,
   which is why 390px was the width at which this page scrolled sideways. The
   row becomes two lines rather than five squeezed columns: what the race is on
   the first, how it is moving on the second. */
@media (max-width: 560px) {
  /* `13rem 1fr 5rem` plus its gaps and padding needs 336px before the bar gets
     anything, which is why the Method tab scrolled sideways on a 320px phone.
     Same treatment as the watchlist row: the cause and its size on one line, the
     bar it is drawn on beneath. */
  .mv-row {
    grid-template-columns: minmax(0, 1fr) 4.5rem;
    grid-template-areas: 'k    v'
                         'bar  bar'
                         'note note';
    gap: .35rem .7rem; padding: .6rem .7rem;
  }
  .mv-bar { margin-top: .1rem; }

  .wl-row {
    grid-template-columns: minmax(0, 1fr) 3.4rem;
    grid-template-areas: 'id  p'
                         'who p'
                         'bar v2';
    gap: .15rem .8rem; padding: .7rem .8rem;
  }
  .wl-id  { grid-area: id; }
  .wl-who { grid-area: who; white-space: normal; }
  .wl-p   { grid-area: p; align-self: center; font-size: .95rem; }
  .wl-bar { grid-area: bar; margin-top: .45rem; }
  .wl-v2  { grid-area: v2; text-align: right; margin-top: .4rem; }
  /* The key names columns that no longer exist in that arrangement. */
  .wl-key { display: none; }
}

/* ---- the race chart ------------------------------------------------------ */
/* Sits at the top of the drawer, above the arithmetic it illustrates. Its own
   block rather than a bare svg, because it carries a heading and a caption and
   they have to travel together when the drawer scrolls. */
.dt-chart { margin: .2rem 0 1.4rem; }
.dt-chart .dt-h {
  font: 500 .72rem/1 var(--sans); letter-spacing: .13em; text-transform: uppercase;
  color: var(--muted); margin: 0 0 .8rem;
}
.dt-chart-wait { font-size: .82rem; color: var(--faint); padding: 1.2rem 0; }
.dt-chart-cap {
  font-size: .78rem; line-height: 1.5; color: var(--muted);
  margin: .7rem 0 0; max-width: none;
}
.dt-chart-cap b { color: var(--ink-dim); font-weight: 600; }
/* The variance bar sits inside the uncertainty block, directly under the total
   it decomposes, so the rows and the picture cannot drift apart on screen. */
.dt-var { margin: .8rem 0 .4rem; }

/* ---- race drawer: the deeper panels -------------------------------------- */
.dt-cap-warn { border-left: 2px solid var(--accent); padding-left: .7rem; }

.dt-pollsters {
  width: 100%; border-collapse: collapse; font-size: .78rem; margin: .2rem 0 1.2rem;
}
.dt-pollsters th {
  text-align: left; font: 500 .68rem/1 var(--sans); letter-spacing: .09em;
  text-transform: uppercase; color: var(--muted); padding: 0 .5rem .45rem 0;
  border-bottom: 1px solid var(--line);
}
.dt-pollsters td { padding: .4rem .5rem .4rem 0; border-bottom: 1px solid var(--line-soft);
                   color: var(--ink-dim); vertical-align: baseline; }
.dt-pollsters td.num { text-align: right; font-variant-numeric: tabular-nums;
                       font-family: var(--mono); font-size: .74rem; white-space: nowrap; }
.dt-tag { font-size: .66rem; color: var(--faint); text-transform: uppercase;
          letter-spacing: .06em; }
/* The bar sits behind its own number rather than in a column of its own: a share
   and a picture of the share are one quantity and should not be two cells. */
.dt-wbar { display: inline-block; height: .62em; background: var(--dem-deep);
           opacity: .5; margin-right: .4rem; vertical-align: baseline;
           max-width: 4.5rem; }

.dt-cond { margin: 1.4rem 0 .6rem; border-top: 1px solid var(--line); padding-top: .9rem; }
.dt-cond-h { font: 500 .72rem/1 var(--sans); letter-spacing: .13em; text-transform: uppercase;
             color: var(--muted); margin-bottom: .7rem; }
.dt-cond-row { display: grid; grid-template-columns: 1fr auto; gap: 0 .8rem;
               align-items: baseline; padding: .4rem 0;
               border-bottom: 1px solid var(--line-soft); }
.dt-cond-k { font-size: .84rem; color: var(--ink); }
.dt-cond-v { font: 600 1rem/1 var(--sans); font-variant-numeric: tabular-nums;
             text-align: right; }
.dt-cond-se { font-size: .7rem; font-weight: 400; color: var(--faint); }
.dt-cond-n { grid-column: 1 / -1; font-size: .72rem; color: var(--faint); }
.dt-cond-note { font-size: .76rem; line-height: 1.5; color: var(--muted);
                margin: .7rem 0 0; max-width: none; }
@media (max-width: 560px) {
  .dt-pollsters { font-size: .72rem; }
  .dt-pollsters td.num { font-size: .68rem; }

  /* `table.plain` carries 1.1rem of padding a side, which is ~140px of pure
     gutter across four columns and pushed the Method tab to 389px on a 320px
     screen. The cells are short -- a label and a number -- so the padding is
     what does not fit, not the content. It scrolls as a last resort rather than
     clipping, but at these widths it no longer needs to. */
  /* Below this the numbers themselves stop fitting, and a number that wraps is
     worse than one you scroll to. The table becomes its own scroll container --
     the same treatment the race table and every wide chart already get, and the
     one case my own rule allows a horizontal scroll: a table inside its own box,
     never the page. */
  table.plain { font-size: .8rem; display: block; overflow-x: auto;
                overscroll-behavior-x: contain; max-width: 100%; }
  table.plain th, table.plain td { padding: .4rem .45rem; }
  table.plain th:first-child, table.plain td:first-child { padding-left: 0; }
  table.plain th:last-child, table.plain td:last-child { padding-right: 0; }
  table.plain td.num, table.plain th.num { font-size: .74rem; }
  .scroll-x, .table-wrap { overscroll-behavior-x: contain; }
}


/* ---- drawer: width toggle, key, range control ---------------------------- */
.drawer.wide { width: min(1040px, 96vw); }
/* The charts inside are the reason this exists, so give them the room rather
   than letting the prose stretch to a 1000px measure nobody can read. */
.drawer.wide .drawer-inner { padding: 1.6rem 2.2rem 3rem; }
.drawer.wide .dt-chart-cap, .drawer.wide .dt-cond-note { max-width: 68ch; }
.drawer-grow {
  position: absolute; top: .6rem; right: 2.6rem; background: none; border: 0;
  color: var(--muted); font-size: 1.15rem; line-height: 1; cursor: pointer;
  font-family: var(--sans);
}
.drawer-grow:hover { color: var(--ink); }
.drawer-grow:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* The caption, as a key rather than a paragraph: one line per thing on the
   chart, so a reader who wants to know what a hollow dot means can find that
   without reading the other three. */
.dt-key { margin: .8rem 0 0; font-size: .78rem; line-height: 1.5; }
.dt-key dt {
  font: 500 .66rem/1.4 var(--sans); letter-spacing: .08em; text-transform: uppercase;
  color: var(--muted); margin: .55rem 0 .1rem;
}
.dt-key dd { margin: 0; color: var(--ink-dim); }
.dt-key dd b { color: var(--ink); font-weight: 600; }

.dt-range {
  display: flex; align-items: center; justify-content: space-between; gap: .8rem;
  flex-wrap: wrap; margin: .9rem 0 .2rem; font-size: .76rem; color: var(--faint);
}
.dt-range .chip { margin-left: 0; font-size: .72rem; padding: .35rem .6rem; }
@media (pointer: coarse) { .dt-range .chip { padding: .6rem .7rem; } }

/* ---- defined terms ------------------------------------------------------- */
/* A dotted underline rather than a solid one: it has to read as "there is more
   here" without reading as "this navigates somewhere", because it does not. */
.term {
  color: inherit; border-bottom: 1px dotted var(--faint); cursor: help;
  background: none; padding: 0;
}
.term:hover, .term:focus-visible { color: var(--ink); border-bottom-color: var(--accent); }
.term:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 1px; }
@media (pointer: coarse) {
  /* A term inside running prose cannot grow without opening a gap in the line,
     so the hit area comes from a pseudo-element, as the other inline controls do. */
  .term { position: relative; }
  .term::before { content: ''; position: absolute; inset: -10px -4px; }
}

/* ---- fact tables inside prose -------------------------------------------- */
/* Label left, value right, no header row: these carry four or five figures, and
   a <thead> saying "quantity / value" is a row of text that tells a reader
   nothing they cannot see. */
.fx { width: 100%; border-collapse: collapse; margin: .7rem 0; font-size: .8rem; }
.fx th {
  text-align: left; font: 500 .74rem/1.4 var(--sans); color: var(--muted);
  padding: .3rem .9rem .3rem 0; vertical-align: baseline; white-space: nowrap;
  border-bottom: 1px solid var(--line-soft); width: 1%;
}
.fx td { padding: .3rem 0; color: var(--ink-dim); vertical-align: baseline;
         border-bottom: 1px solid var(--line-soft); }
.fx tr:last-child th, .fx tr:last-child td { border-bottom: 0; }
.fx b { color: var(--ink); font-weight: 600; }
@media (max-width: 560px) {
  /* Stack rather than squeeze: a 1%-width label column and a long value make a
     two-character-wide cell on a phone. */
  .fx th, .fx td { display: block; width: auto; white-space: normal; }
  .fx th { padding: .45rem 0 .05rem; border-bottom: 0; }
  .fx td { padding: 0 0 .45rem; }
}

/* ---- seat-total reading modes -------------------------------------------- */
.seat-modes { display: flex; align-items: baseline; gap: 1rem; flex-wrap: wrap;
              margin: .2rem 0 1rem; }
.seat-mode-note { font-size: .8rem; color: var(--muted); margin: 0; max-width: 46ch; }

/* ---- what the trailing side needs ---------------------------------------- */
.cheap { display: grid; gap: 1px; margin: .6rem 0 0; }
.cheap-row {
  display: grid; grid-template-columns: 5rem minmax(0, 1fr) 3rem; align-items: center;
  gap: .8rem; width: 100%; text-align: left; cursor: pointer; font: inherit;
  background: var(--surface); border: 0; border-top: 1px solid var(--line-soft);
  padding: .5rem .7rem; color: var(--ink-dim);
}
.cheap-row:first-child { border-top: 0; }
.cheap-row:hover { background: var(--surface-2); }
.cheap-row:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.cheap-id { font: 600 .84rem/1 var(--sans); color: var(--ink); font-variant-numeric: tabular-nums; }
.cheap-bar { position: relative; display: block; height: 7px; background: var(--line-soft);
             border-radius: 1px; }
.cheap-bar i { display: block; height: 100%; background: var(--dem-deep); opacity: .75;
               border-radius: 1px; }
.cheap-p { font: 600 .84rem/1 var(--sans); text-align: right;
           font-variant-numeric: tabular-nums; }
@media (max-width: 560px) {
  .cheap-row { grid-template-columns: 4.4rem minmax(0, 1fr) 2.6rem; gap: .55rem; }
}

/* ---- small multiples: which races moved ---------------------------------- */
.mv-grid { display: grid; gap: .6rem; grid-template-columns: repeat(auto-fit, minmax(min(168px, 100%), 1fr)); }
.mv-card {
  background: var(--surface); border: 1px solid var(--line); border-radius: 3px;
  padding: .5rem .55rem .45rem; cursor: pointer; font: inherit; text-align: left;
  color: var(--ink-dim); display: block; width: 100%;
}
.mv-card:hover { background: var(--surface-2); border-color: var(--faint); }
.mv-card:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.mv-head { display: flex; justify-content: space-between; align-items: baseline; gap: .5rem; }
.mv-rid { font: 600 .82rem/1 var(--sans); color: var(--ink); font-variant-numeric: tabular-nums; }
.mv-now { font: 500 .8rem/1 var(--sans); font-variant-numeric: tabular-nums; color: var(--ink-dim); }
.mv-foot-row { display: flex; justify-content: space-between; gap: .5rem;
               font-size: .7rem; color: var(--faint); margin-top: .1rem; }
.mv-move.d { color: var(--dem); }
.mv-move.r { color: var(--rep); }


/* ---- the section rail ------------------------------------------------------ */
/* A column of its own beside the panels on a wide screen, so the charts keep the
   1180px they were laid out for rather than giving up 14rem to it: the page grows
   outward instead. Below that width there is no room for a column, and the rail
   becomes one closed line above the panel. */
.page-grid { display: block; }
.toc { font-size: .84rem; }
.toc-title { display: none; }
.toc-list { list-style: none; margin: 0; padding: 0; }
.toc-item {
  display: block; width: 100%; text-align: left; cursor: pointer;
  background: none; border: 0; border-left: 2px solid var(--line);
  color: var(--muted); font: 400 .84rem/1.35 var(--sans);
  padding: .42rem .2rem .42rem .85rem;
}
.toc-item:hover { color: var(--ink); border-left-color: var(--faint); }
.toc-item.on { color: var(--ink); border-left-color: var(--accent); }
.toc-item:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }

.toc-toggle {
  display: flex; align-items: baseline; gap: .7rem; width: 100%; cursor: pointer;
  background: none; border: 0; border-bottom: 1px solid var(--line-soft);
  padding: .8rem 0; color: var(--ink-dim); font: 400 .88rem/1.2 var(--sans); text-align: left;
}
.toc-toggle::after { content: '\25be'; margin-left: auto; color: var(--faint); }
.toc.open .toc-toggle::after { content: '\25b4'; }
.toc-k {
  font: 500 .7rem/1 var(--sans); letter-spacing: .13em; text-transform: uppercase;
  color: var(--muted); white-space: nowrap;
}
.toc-cur { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
.toc .toc-list { display: none; padding: .6rem 0 .4rem; border-bottom: 1px solid var(--line-soft); }
.toc.open .toc-list { display: block; }
.toc.single .toc-toggle { display: none; }

@media (pointer: coarse) { .toc-item { padding-block: .75rem; } }

@media (min-width: 1240px) {
  .page-grid {
    display: grid; gap: 2.6rem; align-items: start;
    grid-template-columns: 12.5rem minmax(0, 1fr);
    max-width: calc(1180px + 12.5rem + 2.6rem);
  }
  .toc {
    position: sticky; align-self: start;
    top: calc(var(--tabbar-h, 3.2rem) + var(--condbar-h, 0px));
    max-height: calc(100vh - var(--tabbar-h, 3.2rem) - var(--condbar-h, 0px));
    overflow-y: auto; padding-block: 2.7rem 1rem;
  }
  .toc-toggle { display: none; }
  .toc-title {
    display: block; margin: 0 0 .8rem .85rem;
    font: 500 .7rem/1 var(--sans); letter-spacing: .14em; text-transform: uppercase; color: var(--faint);
  }
  .toc .toc-list { display: block; padding: 0; border: 0; }
  /* The masthead and tab strip keep to the same outer edge as the grid, so the
     rail does not hang off the left of everything above it. */
  .masthead.wrap, .tabbar .wrap, .cond-bar .wrap { max-width: calc(1180px + 12.5rem + 2.6rem); }
}

/* ---- the seat strip -------------------------------------------------------- */
.ss { position: relative; margin-top: 1.2rem; }
.ss-top, .ss-bottom { position: relative; height: 1.5rem; }
.ss-plot .chart-scroll > svg { min-width: 0; }
.ss-lab {
  position: absolute; bottom: .25rem; white-space: nowrap;
  font: 500 .74rem/1 var(--sans); color: var(--muted); font-variant-numeric: tabular-nums;
}
.ss-bottom .ss-lab { bottom: auto; top: .35rem; color: var(--faint); }
.ss-lab[data-align="end"] { transform: translateX(-100%); }
.ss-lab[data-align="mid"] { transform: translateX(-50%); }
.ss-lab.pivot { color: var(--accent); }
.ss-lab.pivot b { color: var(--ink); font-weight: 600; }
/* Two majority lines (a Senate with an independent's seat on one side) put a label
   either side of the middle, and at phone width those collide with the end labels.
   The hatching still marks the seats not up; the note below names both lines. */
@media (max-width: 600px) {
  .ss.ss-split .ss-lab.held, .ss.ss-split .ss-bottom .ss-lab.end { display: none; }
}
.ss-fh {
  font: 500 .72rem/1 var(--sans); letter-spacing: .13em; text-transform: uppercase;
  color: var(--muted); margin: 1.6rem 0 .7rem;
}
.ss-grid {
  display: grid; gap: 3px;
  grid-template-columns: repeat(auto-fill, minmax(5.6rem, 1fr));
}
.ss-cell {
  position: relative; display: flex; flex-direction: column; gap: .2rem; text-align: left;
  background: var(--surface); border: 1px solid var(--line-soft); border-top: 4px solid var(--seat);
  border-radius: 2px; padding: .4rem .45rem .45rem; cursor: pointer; color: var(--ink-dim); font: inherit;
}
.ss-cell:hover { background: var(--surface-2); }
.ss-cell:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.ss-cell.pivot { border-color: var(--accent); border-top-color: var(--seat); box-shadow: 0 0 0 1px var(--accent); }
.ss-line { display: flex; align-items: baseline; justify-content: space-between; gap: .3rem; }
.ss-rank { font: 400 .64rem/1 var(--sans); color: var(--faint); font-variant-numeric: tabular-nums; }
.ss-id { font: 600 .78rem/1 var(--sans); color: var(--ink); letter-spacing: .01em; white-space: nowrap; }
.ss-p { font: 500 .74rem/1 var(--sans); font-variant-numeric: tabular-nums; }

/* ---- flips ------------------------------------------------------------------ */
.fl-sum {
  display: grid; gap: 1px; background: var(--line); border: 1px solid var(--line);
  grid-template-columns: repeat(auto-fit, minmax(min(15rem, 100%), 1fr)); margin-top: 1.6rem;
}
.fl-card { background: var(--surface); padding: 1.1rem 1.2rem .6rem; }
.fl-ch { font: 500 .72rem/1 var(--sans); letter-spacing: .14em; text-transform: uppercase; color: var(--muted); }
.fl-n { font: 400 2.4rem/1 var(--serif); margin: .5rem 0 .15rem; font-variant-numeric: tabular-nums; color: var(--ink); }
.fl-l { font-size: .82rem; color: var(--ink-dim); }
.fl-fx td { text-align: right; font-variant-numeric: tabular-nums; font-weight: 600; }
.fl-bar.table-bar { margin-bottom: .4rem; }

.fl-tier { margin-top: 1rem; }
.fl-tier > summary {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: .4rem .9rem; cursor: pointer;
  padding: .55rem 0; list-style: none;
}
.fl-tier > summary::-webkit-details-marker { display: none; }
.fl-tier > summary::before { content: '\25b8'; color: var(--faint); width: .8rem; }
.fl-tier[open] > summary::before { content: '\25be'; }
.fl-tl { font: 500 .78rem/1 var(--sans); letter-spacing: .12em; text-transform: uppercase; color: var(--ink); }
.fl-rule { font-size: .74rem; color: var(--muted); }
.fl-tc { font-size: .78rem; color: var(--faint); margin-left: auto; }

.fl-list { border: 1px solid var(--line); border-radius: 2px; overflow: hidden; }
.fl-row, .fl-key {
  display: grid; align-items: center; gap: .9rem;
  grid-template-columns: 8.6rem minmax(0, 1fr) 3rem 7rem 4.6rem;
}
.fl-row {
  width: 100%; text-align: left; cursor: pointer; font: inherit; color: var(--ink-dim);
  background: var(--surface); border: 0; border-top: 1px solid var(--line-soft); padding: .55rem .9rem;
}
.fl-row:first-child { border-top: 0; }
.fl-row:hover { background: var(--surface-2); }
.fl-row:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.fl-id { font: 600 .82rem/1 var(--sans); color: var(--ink); display: flex; align-items: center; gap: .45rem; white-space: nowrap; }
.fl-chip {
  font: 500 .6rem/1 var(--sans); letter-spacing: .08em; text-transform: uppercase; color: var(--muted);
  border: 1px solid var(--line); border-radius: 2px; padding: .18rem .28rem; min-width: 3.1rem; text-align: center;
}
.fl-who { display: flex; align-items: baseline; gap: .4rem; min-width: 0; font-size: .82rem; }
.fl-from, .fl-to { font-weight: 700; }
.fl-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; color: var(--ink-dim); }
.fl-arrow { color: var(--faint); }
.fl-tag {
  font: 500 .62rem/1 var(--sans); letter-spacing: .06em; text-transform: uppercase; color: var(--warn);
  border: 1px solid var(--line); border-radius: 2px; padding: .15rem .3rem; white-space: nowrap;
}
.fl-p { font: 600 .84rem/1 var(--sans); text-align: right; color: var(--ink); font-variant-numeric: tabular-nums; }
.fl-bar { position: relative; display: block; height: 6px; background: var(--line-soft); border-radius: 1px; }
.fl-bar i { display: block; height: 100%; border-radius: 1px; opacity: .8; }
.fl-m { font-size: .78rem; color: var(--muted); text-align: right; font-variant-numeric: tabular-nums; }
.fl-key {
  margin: .8rem 0 -.4rem; padding: 0 .9rem; max-width: none;
  font-size: .66rem; color: var(--faint); letter-spacing: .05em; text-transform: uppercase;
}
.fl-key span:nth-child(3), .fl-key span:nth-child(5) { text-align: right; }

@media (pointer: coarse) { .fl-row { padding-block: .8rem; } }

@media (max-width: 560px) {
  .fl-key { display: none; }
  .fl-row {
    grid-template-columns: minmax(0, 1fr) 3rem;
    grid-template-areas: 'id  p'
                         'who m'
                         'bar bar';
    gap: .3rem .8rem; padding: .7rem .8rem;
  }
  .fl-id { grid-area: id; } .fl-who { grid-area: who; } .fl-p { grid-area: p; }
  .fl-m { grid-area: m; } .fl-bar { grid-area: bar; }
  .ss-grid { grid-template-columns: repeat(auto-fill, minmax(5.2rem, 1fr)); }
}

/* The tipping bars under the watchlist: the same share, for more races. */
.wl-tip { margin-top: 1.8rem; max-width: 34rem; }
.diag-more #diagnostics { margin-top: 1rem; }

/* How it works: the error curves' chamber headings sit under a list, not a chart. */
#errorcurve .three-col { margin-top: 1.2rem; }
