/* Forts Tools — one stylesheet, no framework, no external fonts.
   Dark by default with a light-scheme variant; both are defined as tokens so nothing
   depends on a media query alone. */

:root {
  --bg: #10131a;
  --panel: #171b24;
  --panel-2: #1e2430;
  --line: #2b3342;
  --ink: #e6e9ef;
  --ink-dim: #9aa3b2;
  --accent: #d98324;
  --accent-ink: #12161d;
  --warn-bg: #2a2113;
  --warn-line: #6b5220;
  --ok: #4caf7d;
  --ok-bg: #132a1f;
  --ok-line: #2f6b4c;
  --bad: #e0655f;
  /* Recent-form arrows on the leaderboard: win green, loss red. Dedicated tokens (not a bare
     colour) so the hue follows the palette; redefined in the light block below. */
  --form-win: #4caf7d;
  --form-loss: #e0655f;
  --radius: 10px;
  --measure: min(100%, 96rem);
}

@media (prefers-color-scheme: light) {
  :root:not([data-theme="dark"]) {
    --bg: #f4f5f7;
    --panel: #ffffff;
    --panel-2: #eef0f4;
    --line: #d6dae2;
    --ink: #1b2029;
    --ink-dim: #5b6472;
    --accent: #b5651d;
    --accent-ink: #ffffff;
    --warn-bg: #fdf3e0;
    --warn-line: #e0b877;
    --ok: #1e7a4d;
    --ok-bg: #e8f6ee;
    --ok-line: #7fbf9d;
    --bad: #b3312b;
    --form-win: #1e7a4d;
    --form-loss: #b3312b;
  }
}

* { box-sizing: border-box; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font: 16px/1.6 system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
}

header {
  border-bottom: 1px solid var(--line);
  background: var(--panel);
  padding: 0.75rem 1.25rem;
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem 1.5rem;
  align-items: center;
}

.brand {
  font-weight: 700;
  letter-spacing: 0.02em;
  color: var(--ink);
  text-decoration: none;
  font-size: 1.05rem;
}

nav { display: flex; flex-wrap: wrap; gap: 0.25rem 1rem; }
nav a { color: var(--ink-dim); text-decoration: none; font-size: 0.92rem; }
nav a:hover, nav a:focus-visible { color: var(--ink); text-decoration: underline; }
nav a.on { color: var(--accent); }

.sr-only {
  position: absolute;
  width: 1px; height: 1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
}

/* Far right of the header, on its own line if the header wraps on a narrow screen. */
.lang-switch {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  margin-left: auto;
}
.lang-switch select {
  font-size: 0.85rem;
  padding: 0.2rem 0.4rem;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--panel-2);
  color: var(--ink);
}
.lang-switch button {
  font-size: 0.85rem;
  padding: 0.2rem 0.6rem;
  min-width: 0;
}

/* The timezone selector sits immediately beside the language switcher. It is a plain
   client-side control (no submit button), so it needs no `margin-left: auto` of its own —
   the language switcher already pushed the pair to the right. */
.tz-switch {
  display: flex;
  align-items: center;
  gap: 0.4rem;
}
.tz-switch select {
  font-size: 0.85rem;
  padding: 0.2rem 0.4rem;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--panel-2);
  color: var(--ink);
  max-width: 14rem;
}

main {
  max-width: var(--measure);
  margin: 0 auto;
  padding: 2rem 1.25rem 3rem;
}

footer {
  max-width: var(--measure);
  margin: 0 auto;
  padding: 1.5rem 1.25rem 3rem;
  color: var(--ink-dim);
  font-size: 0.85rem;
  border-top: 1px solid var(--line);
}
footer a { color: var(--ink-dim); }
footer p { margin: 0 0 0.4rem; }
footer p:last-child { margin: 0; }
footer .footer-discord a { text-decoration-thickness: from-font; }

h1 { font-size: 1.7rem; line-height: 1.25; margin: 0 0 0.75rem; }
h2 { font-size: 1.15rem; margin: 2rem 0 0.6rem; }
p { margin: 0 0 1rem; }
.lede { color: var(--ink-dim); max-width: 44rem; }
.note { color: var(--ink-dim); font-size: 0.9rem; }
.empty { color: var(--ink-dim); font-style: italic; }
.error { font-size: 1.05rem; }
a { color: var(--accent); }

/* cards ------------------------------------------------------------------ */
.cards {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(17rem, 1fr));
  gap: 1rem;
  margin: 1.5rem 0 2rem;
}
.card {
  display: block;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 1.1rem 1.2rem;
  text-decoration: none;
  color: inherit;
}
.card:hover, .card:focus-visible { border-color: var(--accent); }
.card h2 { margin: 0 0 0.4rem; font-size: 1.05rem; color: var(--accent); }
.card p { margin: 0; color: var(--ink-dim); font-size: 0.92rem; }

.privacy {
  background: var(--panel-2);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 0.9rem 1.1rem;
  font-size: 0.92rem;
}

/* forms ------------------------------------------------------------------ */
.tool {
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 1.25rem;
  margin: 1.25rem 0;
}

.drop {
  display: block;
  border: 2px dashed var(--line);
  border-radius: var(--radius);
  padding: 1.5rem 1rem;
  text-align: center;
  cursor: pointer;
  background: var(--panel-2);
}
.drop:hover, .drop:focus-within { border-color: var(--accent); }
.drop.dragover {
  border-color: var(--accent);
  border-style: solid;
  background: var(--panel);
}
.drop-hint { display: block; color: var(--ink-dim); font-size: 0.85rem; margin-top: 0.3rem; }
.drop input[type="file"] {
  position: absolute;
  width: 1px; height: 1px;
  opacity: 0;
}
.drop-label { display: block; font-weight: 600; }
.drop-name { display: block; color: var(--ink-dim); font-size: 0.9rem; margin-top: 0.35rem; }
.limit { color: var(--ink-dim); font-size: 0.85rem; margin: 0.5rem 0 1rem; }

fieldset.opts {
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 0.75rem 1rem 1rem;
  margin: 0 0 1rem;
}
legend { color: var(--ink-dim); font-size: 0.85rem; padding: 0 0.35rem; }
.radio, .check { display: flex; align-items: center; gap: 0.5rem; margin: 0.45rem 0; flex-wrap: wrap; }

label { display: block; margin-bottom: 0.35rem; }

/* workbench: one operation per block, its own inputs indented under its checkbox */
.op { padding: 0.6rem 0; border-bottom: 1px solid var(--line); }
.op:last-of-type { border-bottom: 0; }
.op .check span { font-weight: 600; }
.op .note { margin: 0.15rem 0 0.35rem 1.6rem; }
.sub {
  margin: 0.35rem 0 0.2rem 1.6rem;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.4rem 0.6rem;
}
.sub label { margin: 0; color: var(--ink-dim); font-size: 0.9rem; }
.sub .radio { margin: 0; width: 100%; }
.sub .radio label { display: inline; }

input[type="text"] {
  background: var(--panel-2);
  color: var(--ink);
  border: 1px solid var(--line);
  border-radius: 6px;
  padding: 0.35rem 0.5rem;
  font: inherit;
  max-width: 100%;
}

/* /premium — one control per row, each full width up to a comfortable measure, so the
   whole gate is usable on a phone without pinching. `:disabled` here rather than the
   generic `button[disabled]` below, which means "submitting" and shows a progress cursor;
   these controls are locked, not busy. */
.prem { max-width: 26rem; }
.prem .field { margin: 0 0 1.15rem; }
.prem label { display: block; font-weight: 650; margin-bottom: 0.3rem; }
.prem input[type="text"],
.prem select,
.prem button { display: block; width: 100%; max-width: 100%; }
.prem input[type="text"] { padding: 0.55rem 0.6rem; }
.prem select { padding: 0.55rem 0.6rem; }
.prem .note { display: block; margin: 0.3rem 0 0; }
.prem select:disabled,
.prem button:disabled { opacity: 0.5; cursor: not-allowed; filter: none; }
.prem button:disabled:hover { filter: none; }
.prem .privacy { margin-top: 1.4rem; }

/* workbench result: the chain, in the order it ran */
ol.pipeline { margin: 0.75rem 0 1.5rem; padding-left: 1.4rem; }
ol.pipeline > li { margin: 0 0 1.1rem; }
ol.pipeline .did { margin: 0 0 0.4rem; }
ol.pipeline table.kv { margin-bottom: 0; }

select {
  background: var(--panel-2);
  color: var(--ink);
  border: 1px solid var(--line);
  border-radius: 6px;
  padding: 0.4rem 0.5rem;
  font: inherit;
  max-width: 100%;
}

button {
  background: var(--accent);
  color: var(--accent-ink);
  border: 0;
  border-radius: 6px;
  padding: 0.6rem 1.4rem;
  font: inherit;
  font-weight: 650;
  cursor: pointer;
}
button:hover { filter: brightness(1.08); }
button[disabled] { opacity: 0.6; cursor: progress; }

.working { color: var(--ink-dim); margin: 0.75rem 0 0; font-size: 0.9rem; }

/* results ---------------------------------------------------------------- */
table.kv {
  border-collapse: collapse;
  margin: 0 0 1rem;
  width: 100%;
  max-width: 46rem;
}
table.kv.wide { max-width: none; }
table.kv th, table.kv td {
  text-align: left;
  padding: 0.5rem 0.9rem 0.5rem 0;
  border-bottom: 1px solid var(--line);
  vertical-align: top;
}
table.kv th { color: var(--ink-dim); font-weight: 500; white-space: nowrap; }
table.kv thead th { color: var(--ink); }

/* A group row in the API reference field table: the JSON path the rows under it belong to
   (`summary`, `matches[]`, `downloads[]`). It spans the table, so it must not read as a
   field name. render.py `_api_fields`. */
table.kv tr.api-group th {
  color: var(--ink);
  font-weight: 600;
  padding-top: 1.1rem;
  white-space: normal;
}

pre.cmd {
  background: var(--panel-2);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 0.9rem 1rem;
  overflow-x: auto;
  font-size: 0.88rem;
}
pre.cmd code { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; }

/* A 64-character digest has no spaces in it, so it will push a narrow page sideways unless it
   is told it may break anywhere. Shown under every download (render.py `_download`). */
code.sha { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
           font-size: 0.85em; word-break: break-all; overflow-wrap: anywhere; }

.warn {
  background: var(--warn-bg);
  border: 1px solid var(--warn-line);
  border-radius: var(--radius);
  padding: 0.8rem 1.1rem;
  margin: 1.25rem 0;
}
.warn p:last-child, .warn ul:last-child { margin-bottom: 0; }
.warn ul { margin: 0; padding-left: 1.1rem; }

/* The INFERRED marker (ask 449). Deliberately loud and deliberately UNTRANSLATED — it is
   the same literal the Pi panel carries, and ask 301 names the failure mode it exists to
   stop: "a derived guess presented as a match record". It must read as a marker rather
   than as prose, in every locale and in both palettes, so it borrows the warning ink
   rather than the body ink. */
.inferred {
  display: inline-block;
  font-size: 0.72rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  padding: 0.1rem 0.4rem;
  border: 1px solid var(--warn-line);
  border-radius: 4px;
  background: var(--warn-bg);
  color: var(--ink);
  white-space: nowrap;
}
h2 .inferred { vertical-align: 0.18em; margin-left: 0.5rem; }
td.mark { white-space: nowrap; vertical-align: top; }
ul.claims { margin: 0; padding-left: 1.1rem; }
ul.claims li { margin: 0.1rem 0; }

/* The RECORD marker (ask 449) — `.inferred`'s counterpart, and GREEN where that one is
   gold, exactly as the Pi panel colours them. The two provenances on this page are not two
   confidence levels of one claim, and they must never be able to be mistaken for each other
   at a glance, in either palette. Same geometry as `.inferred` so a column of them lines up.
   The colour is carried by the BORDER and the ink, never by the ink alone: a reader who
   cannot distinguish the two hues still has the word, which is why the marker is a word. */
.record {
  display: inline-block;
  font-size: 0.72rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  padding: 0.1rem 0.4rem;
  border: 1px solid var(--ok-line);
  border-radius: 4px;
  background: var(--ok-bg);
  color: var(--ink);
  white-space: nowrap;
}
h2 .record { vertical-align: 0.18em; margin-left: 0.5rem; }

/* The per-side verdict bands. Deliberately quieter than the provenance markers above —
   what a row IS outranks what happened in it — but still a bordered word rather than
   coloured prose, for the same reason. */
.band {
  display: inline-block;
  min-width: 5.5rem;
  text-align: center;
  font-size: 0.68rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  padding: 0.05rem 0.35rem;
  margin-right: 0.35rem;
  border: 1px solid var(--line);
  border-radius: 4px;
  color: var(--ink-dim);
  white-space: nowrap;
}
.band.won { border-color: var(--ok-line); color: var(--ok); }
.band.lost, .band.docked { border-color: var(--bad); color: var(--bad); }
/* A match still in progress is neither a win nor a loss and must not be coloured as one:
   both players carry the start-of-match dock at this point, and the win lands later. */
.band.playing { border-color: var(--accent); color: var(--accent); }
/* `DOCKED AT START` is the longest band and the one whose meaning depends on its whole
   phrase, so it gets the room to say it rather than being clipped to `DOCKED AT…`. */
.band.docked { min-width: 8.5rem; }
.band.unknown { min-width: 8.5rem; }

/* The honest "outcome pending" label an UNDECIDED/unpaired RECORD row wears where a
   resolved row shows its points (see `_rec_side(..., pending=True)` and `lb_rec_pending`).
   Deliberately muted — it qualifies the row, it is not a verdict — and themed only through
   `--ink-dim`, which both palettes already define, so no new colour is introduced. */
.rec-pending { color: var(--ink-dim); font-size: 0.85rem; font-style: italic; }

/* Recent-form arrows beside a standings name: up to three ▲/▼, most-recent first. The
   DIRECTION carries the result (win ▲ / loss ▼) so the row is readable with no colour at all,
   and each arrow also carries a translated win/loss word in its title/aria-label; the colour
   below is an enhancement on top, themed in both palettes. */
.form { margin-left: 0.45rem; white-space: nowrap; }
.form-arrow {
  font-size: 0.8rem;
  line-height: 1;
  margin-left: 0.18rem;
}
.form-arrow:first-child { margin-left: 0; }
.form-arrow.win { color: var(--form-win); }
.form-arrow.loss { color: var(--form-loss); }

/* The ONE expand control on a standings row (`render._row_expand`): a native `<details>`,
   closed by default, inline after the form arrows. The summary is a visible button-like
   target (chevron + word, border, hover/focus state); the chevron turns and the word swaps
   from "expand" to "collapse" when open. The opened body drops below as a block. Themed
   only through variables both palettes define. */
details.row-expand { display: inline; margin-left: 0.45rem; }
details.row-expand > summary {
  display: inline-flex;
  align-items: center;
  gap: 0.25rem;
  cursor: pointer;
  list-style: none;
  font-size: 0.8rem;
  line-height: 1.2;
  padding: 0.05rem 0.45rem;
  border: 1px solid var(--ok-line);
  border-radius: var(--radius);
  color: var(--ink-dim);
  white-space: nowrap;
  user-select: none;
}
details.row-expand > summary::-webkit-details-marker { display: none; }
details.row-expand > summary::marker { content: ""; }
details.row-expand > summary:hover,
details.row-expand > summary:focus-visible { color: inherit; border-color: currentColor; }
details.row-expand > summary:focus-visible { outline: 2px solid currentColor; }
.rx-chev { display: inline-block; transition: transform 0.15s ease; }
details.row-expand[open] > summary .rx-chev { transform: rotate(90deg); }
.rx-close { display: none; }
details.row-expand[open] > summary .rx-open { display: none; }
details.row-expand[open] > summary .rx-close { display: inline; }
details.row-expand .prec-body .form { margin-left: 0; white-space: normal; }
@media (prefers-reduced-motion: reduce) { .rx-chev { transition: none; } }

/* The season chooser on /leaderboard (`render._season_chooser`): one link per season, the
   shown one marked. Wraps on a narrow screen. */
.season-pick { display: flex; flex-wrap: wrap; align-items: center; gap: 0.35rem 0.6rem;
               margin: 0.5rem 0; }
.season-pick ul { display: flex; flex-wrap: wrap; gap: 0.35rem; margin: 0; padding: 0;
                  list-style: none; }
.season-pick a { display: inline-block; padding: 0.15rem 0.6rem; border: 1px solid var(--ok-line);
                 border-radius: var(--radius); text-decoration: none; }
.season-pick a.on { font-weight: 600; border-color: currentColor; }
.prec-body {
  display: block;
  margin: 0.4rem 0 0.2rem;
  padding: 0.5rem 0.75rem;
  border: 1px solid var(--ok-line);
  border-radius: var(--radius);
  font-size: 0.9rem;
}
.prec-body p { margin: 0; }
dl.prec-facts {
  display: grid;
  grid-template-columns: max-content minmax(0, 1fr);
  gap: 0.25rem 0.75rem;
  margin: 0;
}
dl.prec-facts dt { color: var(--ink-dim); }
dl.prec-facts dd { margin: 0; overflow-wrap: anywhere; }
@media (max-width: 40rem) {
  dl.prec-facts { grid-template-columns: minmax(0, 1fr); }
  dl.prec-facts dd { margin-bottom: 0.3rem; }
}

/* The record block's own note, alongside `.warn` for the inferred one. Same shape, green
   ground, so the two blocks read as a matched pair with a different provenance each. */
.ok-note {
  border: 1px solid var(--ok-line);
  background: var(--ok-bg);
  border-radius: var(--radius);
  padding: 0.75rem 1rem;
  margin: 1.25rem 0;
}
.ok-note p:last-child { margin-bottom: 0; }

/* The hand-curated marker beside a name on the leaderboard (ask 217). Deliberately quiet:
   a small glyph next to the name, not a badge. The footnote under the table carries the
   whole of what it claims, and the two only ever appear together. */
.pmark { font-weight: 700; margin-left: 0.15rem; color: var(--ink); }
.marknote { margin: 0.6rem 0 1.2rem; }
ul.marks { list-style: none; margin: 0 0 0.3rem; padding: 0; font-weight: 600; }
ul.marks li { margin: 0.1rem 0; }

ul.downloads { list-style: none; padding: 0; margin: 1rem 0; }
ul.downloads li {
  padding: 0.55rem 0;
  border-bottom: 1px solid var(--line);
}
.meta { color: var(--ink-dim); font-size: 0.87rem; }

.dl { font-weight: 650; }

ul.checks { list-style: none; padding: 0; margin: 0.5rem 0 1rem; }
ul.checks li { padding: 0.2rem 0 0.2rem 1.6rem; position: relative; }
ul.checks li::before { position: absolute; left: 0; }
ul.checks li.pass::before { content: "\2713"; color: var(--ok); }
ul.checks li.fail::before { content: "\2717"; color: var(--bad); }

table.activity tr.activity-fail td:nth-child(3) { color: var(--bad); }
table.activity tr.activity-ok td:nth-child(3) { color: var(--ok); }

@media (max-width: 40rem) {
  main { padding: 1.25rem 1rem 2rem; }
  h1 { font-size: 1.4rem; }
}

/* Narrow screens: a table wider than the viewport scrolls inside itself instead of pushing the
   page sideways, and header and band cells may wrap. */
@media (max-width: 64rem) {
  table.kv { display: block; overflow-x: auto; max-width: 100%; }
  table.kv th { white-space: normal; }
  .band, .band.docked, .band.unknown { min-width: 0; }
}

/* --- lobby-census charts (added 2026-09-06) ---------------------------------------
   Server-rendered inline SVG. The series colours are duplicated here as `.k0`-`.k7`
   swatches because the site's CSP has no 'unsafe-inline' for style-src: a legend square
   painted with a style attribute would silently render blank. Keep these in step with
   `_SERIES_INK` in render.py — they are the same eight values in the same order. */

.chart {
  margin: 0.6rem 0 0.4rem;
  padding: 0.6rem 0.4rem 0.2rem;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  overflow-x: auto;
}
.chart svg { display: block; width: 100%; height: auto; min-width: 30rem; }
.chart.heat svg { min-width: 44rem; }

.chart .ax { stroke: var(--line); stroke-width: 1; }
.chart .ay { fill: var(--ink-dim); font-size: 10px; text-anchor: end; }
/* The right-hand (players) scale reads outward from the frame, so it anchors at
   its start where the left-hand one anchors at its end. */
.chart .ay-r { fill: #c0509b; text-anchor: start; }
.chart .ln { fill: none; stroke-width: 2; stroke-linejoin: round; stroke-linecap: round;
             vector-effect: non-scaling-stroke; }
.chart .ax-t { fill: var(--ink-dim); font-size: 10px; }
.chart .ax-x { text-anchor: middle; }
/* Day boundaries. Fainter than the value grid on purpose: they locate a spike in the
   week, they are not a second scale competing with the y axis. */
.chart .axv { stroke: var(--line); stroke-width: 1; stroke-opacity: 0.55;
              stroke-dasharray: 2 4; }
.chart .ln.off, .chart .ln-dot.off { display: none; }
.chart .hh { fill: var(--ink-dim); font-size: 10px; text-anchor: middle; }
.chart .hr { fill: var(--ink); font-size: 11px; }
.chart .hp { fill: var(--ink-dim); font-size: 11px; }

.legend { display: flex; flex-wrap: wrap; align-items: center; gap: 0.4rem;
          margin: 0 0 0.4rem; color: var(--ink); font-size: 0.85rem; }
.legend-hint { margin: 0.2rem 0 0.35rem; }
/* Each series is a toggle CHIP: a bordered pill with a pointer cursor, so it reads as a
   control rather than as a caption. The swatch doubles as the checkbox — filled with a
   tick while the line is shown, hollow while it is hidden. */
.legend .key, .legend .bulk {
  display: inline-flex; align-items: center; gap: 0.4rem;
  font: inherit; color: inherit; background: var(--panel);
  border: 1px solid var(--line); border-radius: 999px;
  padding: 0.2rem 0.65rem 0.2rem 0.45rem; cursor: pointer; min-height: 1.9rem;
}
.legend .key:hover, .legend .bulk:hover { border-color: var(--ink-dim); }
/* `outline` alone, with no companion property naming a displacement: the disclosure
   gate (`tests/test_webtools.py` FORBIDDEN) refuses that word anywhere a visitor can
   fetch, and a CSS ring is not worth an exception to a guard about struct layouts. */
.legend .key:focus-visible, .legend .bulk:focus-visible { outline: 2px solid var(--accent); }
.legend .key[aria-pressed="true"] { border-color: var(--ink-dim); }
/* A hidden series reads as struck through, dimmed AND hollow-swatched, so the state
   survives for a reader who cannot separate the swatch shades. */
.legend .key[aria-pressed="false"] { text-decoration: line-through; color: var(--ink-dim);
                                     border-style: dashed; }
.legend i { width: 0.85rem; height: 0.85rem; border-radius: 3px; display: inline-flex;
            align-items: center; justify-content: center; flex: none;
            font-style: normal; font-size: 0.7rem; line-height: 1; color: #fff;
            border: 2px solid transparent; box-sizing: border-box; }
.legend .key[aria-pressed="true"] i::after { content: "\2713"; font-weight: 700; }
.legend .key[aria-pressed="false"] i { background: none; border-color: var(--ink-dim); }
.legend .key-bulk { display: inline-flex; gap: 0.4rem; margin-left: 0.3rem; }
.legend .key-bulk[hidden] { display: none; }
.legend .bulk { padding: 0.2rem 0.65rem; color: var(--ink-dim); font-size: 0.8rem; }
.legend .k0 { background: #d98324; }
.legend .k1 { background: #4caf7d; }
.legend .k2 { background: #5b9bd5; }
.legend .k3 { background: #e0655f; }
.legend .k4 { background: #b07cc6; }
.legend .k5 { background: #e0b93c; }
.legend .k6 { background: #3fb8b0; }
.legend .k7 { background: #8c93a3; }
/* Players in game. Its own colour because it is its own quantity on its own (right-hand)
   axis, and a dashed swatch to match the dashed line — the two scales must be tellable
   apart without relying on hue. */
.legend .kp { background: #c0509b; }
.ln-players { stroke-dasharray: 5 3; stroke-width: 1.6; }
