/* =============================================================================
   Spika — the design layer.

   This is the only stylesheet the site has. Bootstrap used to ship underneath
   it and was removed on 2026-08-14: 94.4% of it matched nothing these pages
   render, and the third of this file that dealt with it existed to suppress its
   defaults — system font stack, #0d6efd, 8px radii, 1.5 line height — which are
   the most recognisable signature on the web and would make this read as an
   admin panel with a serif bolted on. What it genuinely contributed is the
   reboot below.

   Three ideas carry the design.

   1. THE STATEMENT IS THE PAGE. On a quote page the first thing rendered is the
      quotation mark and the words. No kicker, no date, no name above it. News
      convention leads with attribution; this inverts it deliberately, because
      the claim the site makes is about the words.

   2. TYPE WEIGHT TRACKS EPISTEMIC STATUS. The verbatim quotation is the largest
      and darkest thing on the page. The machine-written context is smaller and
      set in muted ink. The disclosure that a machine wrote it is smaller still.
      A reader can tell how much to trust a passage from across the room,
      without reading a word of it. This is the one hierarchy rule; everything
      else follows from it.

   3. THE QUOTATION MARK IS LOCALISED, AND IT CLOSES. Slovene sets quotations
      in »guillemets«; Croatian, Bosnian and Serbian in „low-high quotes”. The
      opening mark hangs in the margin at display size in the accent; the
      closing mark is set inline at the end of the statement. A quotation that
      visibly closes is a quotation that has not been cropped — which is the
      site's whole integrity claim, made in punctuation.

   Palette note: the four countries' flags are all red, white and blue in some
   arrangement. Any palette built from those reads as taking a side. The accent
   is a deep petrol — stamp ink, archive, no flag.
   ========================================================================== */

:root {
  color-scheme: light dark;

  /* --- ink and ground ---------------------------------------------------- */
  /* Cool, faintly green-grey. Official-form paper, not the warm cream that
     every editorial template reaches for. */
  --paper:        #f3f3f1;
  --paper-sunk:   #eaeae7;
  --ink:          #17191b;
  --ink-rgb:      23, 25, 27;
  --ink-muted:    #555b60;
  --ink-faint:    #83898f;
  --rule:         #d8d8d4;
  --rule-strong:  #b9bab5;
  --accent:       #0e4f5b;
  --accent-rgb:   14, 79, 91;
  --accent-soft:  #d9e7ea;
  --flag:         #8a3324;      /* content warnings only. Never decoration. */
  --bad:          #a8321f;      /* a field the reader must fix. Not --flag: that one means
                                   'this content carries a warning' and says so. */

  /* --- type -------------------------------------------------------------- */
  --serif: "Source Serif 4", Georgia, "Times New Roman", serif;
  --sans:  "Inter", system-ui, -apple-system, "Segoe UI", sans-serif;
  /* Only the cookie table sets any, and it sets cookie names, so the stack is
     whatever the reader's platform already has. Nothing is downloaded for it. */
  --mono:  ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono",
           "Courier New", monospace;

  --fs-statement:      clamp(1.5rem, 1.05rem + 1.9vw, 2.35rem);
  --fs-statement-lead: clamp(1.75rem, 1.15rem + 2.6vw, 3rem);
  --fs-teaser:         clamp(1.0625rem, 1rem + 0.4vw, 1.25rem);
  --fs-body:           1.0625rem;
  --fs-meta:           0.8125rem;
  --fs-label:          0.6875rem;

  /* --- measure and rhythm ------------------------------------------------ */
  /* Two measures, because they are set at different sizes. 34rem is ~68
     characters of body prose; a statement set at display size needs a wider
     column to reach a readable line, not a narrower one. */
  --measure:           34rem;
  --measure-statement: 42rem;
  --measure-wide:      62rem;
  --s1: 0.25rem; --s2: 0.5rem;  --s3: 0.75rem; --s4: 1rem;
  --s5: 1.5rem;  --s6: 2rem;    --s7: 3rem;    --s8: 4.5rem; --s9: 7rem;

  /* --- the localised quotation marks ------------------------------------- */
  --quote-open:  "„";
  --quote-close: "”";
}

/* Slovene sets quotations in guillemets. Getting this wrong is the kind of
   detail a reader from the region notices before they read a sentence. */
html[lang^="sl"] { --quote-open: "»"; --quote-close: "«"; }

@media (prefers-color-scheme: dark) {
  :root {
    --paper:       #15181a;
    --paper-sunk:  #1b1f22;
    --ink:         #e9eae7;
    --ink-rgb:     233, 234, 231;
    --ink-muted:   #a2a8ad;
    --ink-faint:   #767c82;
    --rule:        #2b3033;
    --rule-strong: #3c4347;
    --accent:      #6fbfcc;
    --accent-rgb:  111, 191, 204;
    --accent-soft: #17343a;
    --flag:        #d9836f;
    --bad:         #f0907c;
  }
}

/* ============================================================ the reboot

   This replaces Bootstrap, which used to sit underneath this file. It is not a
   trimmed copy of it: it is the set of rules that were measured to be doing
   something. Every selector Bootstrap shipped was tested against 441 rendered
   pages — every URL in the sitemap plus search, sign-in, the chart and a 404 —
   and 94.4% of the stylesheet matched nothing at all. What matched was a
   reboot, `.table`, `.visually-hidden`, `.lead`, and the dropdown behind the
   language switcher, which is now a `<details>` and needs no library.

   The thing that made this cheap was already in this file. The block that used
   to be here set 33 `--bs-*` custom properties, and every one of them pointed
   at a token defined above — the site was not building on Bootstrap's design
   system, it was spending 33 lines suppressing it. Deleting Bootstrap deleted
   them rather than needing them replaced.

   Nothing here is a change of appearance. Each rule reproduces what was
   computed before, so that removing 31KB of render-blocking CSS and 24KB of
   JavaScript is provably invisible to a reader; the two places where that was
   *not* the right answer are marked below and were changed deliberately.
   ========================================================================== */

*, *::before, *::after { box-sizing: border-box; }

/* Headings the design layer does not name fall through to here. The size is
   Bootstrap's own responsive curve, kept because it is what these pages have
   been laid out against: `calc(1.375rem + 1.5vw)` capped at 2.5rem from 1200px
   up. `min()` states the cap in one declaration where Bootstrap needed a media
   query, and is exactly equivalent — the calc reaches the cap precisely at
   1200px. Legal pages and the sign-in screen are what this actually sets. */
h1, h2, h3, h4, h5, h6 {
  margin-top: 0;
  margin-bottom: 0.5rem;
  font-weight: 500;
  line-height: 1.2;
  color: var(--ink);
}
h1 { font-size: min(calc(1.375rem + 1.5vw), 2.5rem); }
h2 { font-size: min(calc(1.325rem + 0.9vw), 2rem); }
h3 { font-size: min(calc(1.3rem + 0.6vw), 1.75rem); }
h4 { font-size: min(calc(1.275rem + 0.3vw), 1.5rem); }
h5 { font-size: 1.25rem; }
h6 { font-size: 1rem; }

p            { margin-top: 0; margin-bottom: 1rem; }
ol, ul       { margin-top: 0; margin-bottom: 1rem; padding-left: 2rem; }
blockquote   { margin: 0 0 1rem; }
figure       { margin: 0 0 1rem; }
img, svg     { vertical-align: middle; }
[hidden]     { display: none !important; }
/* No template writes one; the consent manager and the ad slot both do, at
   runtime, which is why this was missed by a scan of the markup we render. */
iframe       { border: 0; }

table { caption-side: bottom; border-collapse: collapse; }
th    { text-align: inherit; text-align: -webkit-match-parent; }
thead, tbody, tr, th, td { border-color: inherit; border-style: solid; border-width: 0; }

/* Form controls do not inherit type from their page unless told to, and that
   is the one reboot rule whose absence is unmistakable. */
button, input, optgroup, select, textarea {
  margin: 0;
  font-family: inherit;
  font-size: inherit;
  line-height: inherit;
}
button { border-radius: 0; text-transform: none; }
button, [type="button"], [type="submit"] { -webkit-appearance: button; }
button:not(:disabled),
[type="button"]:not(:disabled),
[type="submit"]:not(:disabled) { cursor: pointer; }
/* A mouse click focuses a button in Safari and Firefox. Suppressing the ring
   only when it is *not* keyboard focus is what keeps `:focus-visible` honest. */
button:focus:not(:focus-visible) { outline: 0; }
textarea { resize: vertical; }
label { display: inline-block; }
[type="search"] { -webkit-appearance: textfield; outline-offset: -2px; }

/* `code` was Bootstrap's `#d63384` — a pink from its default palette, on a page
   whose accent is petrol. Nothing overrode it because nothing had noticed it;
   it renders in the cookie table, where a reader is matching names against what
   their browser shows them. This is the one colour that changes. */
code { font-family: var(--mono); font-size: 0.875em; color: var(--ink-muted); word-wrap: break-word; }

/* ============================================================ base */

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--sans);
  font-size: var(--fs-body);
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  /* Landscape on iOS inflates text in a way no other platform does, and it
     inflates it *after* the layout the statement sizing was tuned against. */
  -webkit-text-size-adjust: 100%;
  -webkit-tap-highlight-color: transparent;
  display: flex;
  flex-direction: column;
  min-height: 100vh;
}

a {
  color: var(--accent);
  text-decoration: underline;
  text-underline-offset: 0.18em;
  text-decoration-thickness: from-font;
}
a:hover { color: var(--ink); }
/* An anchor with neither href nor class is a placeholder, not a link. */
a:not([href]):not([class]), a:not([href]):not([class]):hover {
  color: inherit;
  text-decoration: none;
}

/* Hidden from the screen and not from a screen reader. The `!important`s and
   the 1px box are not stylistic: a zero-size or `display:none` element is
   dropped from the accessibility tree, and `clip` is what still works when an
   ancestor is a flex container. The `-focusable` variant gives all of it back
   the moment focus lands, which is the skip link. */
.visually-hidden,
.visually-hidden-focusable:not(:focus):not(:focus-within) {
  width: 1px !important;
  height: 1px !important;
  padding: 0 !important;
  margin: -1px !important;
  overflow: hidden !important;
  clip: rect(0, 0, 0, 0) !important;
  white-space: nowrap !important;
  border: 0 !important;
}
.visually-hidden:not(caption),
.visually-hidden-focusable:not(:focus):not(:focus-within):not(caption) {
  position: absolute !important;
}

/* The base every table on the site builds on — the media ledger and the review
   screens. `.legal-table` deliberately does not use it and sets its own.

   The cell background is `transparent` where Bootstrap painted it an opaque
   `--bs-table-bg`, and that is a fix rather than a simplification. Cells paint
   above rows, so an opaque cell hides anything a row sets behind it — which is
   exactly what `.review-candidates__flagged` does to mark a promotion the
   screening rule is not sure about. Measured: with the opaque cell the flag is
   painted over completely; with this it shows. Bootstrap needed the opaque
   cell for its own striping and hover, neither of which this site uses. */
.table { width: 100%; margin-bottom: 1rem; vertical-align: top; border-color: var(--rule); }
.table > :not(caption) > * > * {
  padding: 0.5rem;
  color: var(--ink);
  background-color: transparent;
  border-bottom-width: 1px;
}
.table > thead { vertical-align: bottom; }
.table > tbody { vertical-align: inherit; }

:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
  border-radius: 1px;
}

.skip {
  position: absolute; top: var(--s3); left: var(--s3); z-index: 20;
  background: var(--paper); color: var(--ink);
  padding: var(--s2) var(--s4); border: 1px solid var(--rule-strong);
}

/* One rail. Every page sits in the same centred 62rem box and its content
   hangs off that box's LEFT edge — so the wordmark, the statement, the
   attribution and the footer all share one vertical line, and the air
   collects on the right. Centring each column independently is what made the
   first draft look like two pages stacked on each other. */
.wrap {
  width: 100%;
  max-width: var(--measure-wide);
  margin-inline: auto;
  padding-inline: var(--s5);
}
.wrap--narrow, .wrap--wide { max-width: var(--measure-wide); }

.statement, .teaser, .list-header, .hub-header, .page-intro,
.prose, .index, .root, .error-page, .rail, .lead, .feed, .filter {
  max-width: var(--measure);
  margin-inline-end: auto;
}
.statement, .lead, .feed { max-width: var(--measure-statement); }
/* Bootstrap's `.lead`, carried over verbatim — and it is not inert, which is
   why it is still here rather than deleted.

   The collision is one of names: `.lead` here is a layout wrapper for the home
   page's lead statement and never asked for a type treatment, but Bootstrap
   shipped a `.lead` utility and it matched. `font-weight: 300` then inherits
   into everything under it that does not set its own — the occasion line, the
   attribution, the vote tally, the context body and note, the topic links —
   so the lead statement's furniture renders 100 units lighter than the
   identical furniture on every other statement on the site.

   That is an accident, and it is also what the home page has looked like for
   as long as there has been one. Reproducing it keeps the removal of Bootstrap
   invisible, which is the whole point of this pass; correcting it is a design
   decision about the home page and belongs to somebody who is making one.
   Recorded as gap 35 so it is a choice rather than an inheritance. */
.lead { font-size: 1.25rem; font-weight: 300; }

.site-main { flex: 1 0 auto; padding-block: var(--s7) var(--s9); }

/* A label, not a heading. Small, tracked, quiet — it names a region of the
   page without competing with anything in it. */
.section-label {
  font-family: var(--sans);
  font-size: var(--fs-label);
  font-weight: 600;
  letter-spacing: 0.11em;
  text-transform: uppercase;
  color: var(--ink-faint);
  margin: var(--s8) 0 var(--s4);
  padding-bottom: var(--s2);
  border-bottom: 1px solid var(--rule);
}

.empty { color: var(--ink-muted); font-style: italic; }

.more-link {
  font-family: var(--sans);
  font-size: var(--fs-meta);
  font-weight: 600;
  letter-spacing: 0.02em;
}

/* ============================================================ header */

.site-header {
  border-bottom: 1px solid var(--rule);
  background: var(--paper);
}
.site-header__inner {
  display: flex;
  align-items: baseline;
  gap: var(--s5);
  padding-block: var(--s4);
  /* The language menu falls back to hanging off this rail once the masthead
     wraps — see `.lang-switch` below. */
  position: relative;
}

.wordmark {
  font-family: var(--serif);
  font-size: 1.5rem;
  font-weight: 600;
  letter-spacing: -0.015em;
  color: var(--ink);
  text-decoration: none;
  /* The wordmark carries the tree's own opening mark. On /sl/ it is »Spika, on
     the rest „Spika — the identity is localised at the masthead, not just in
     the body copy. */
}
.wordmark::before {
  content: var(--quote-open);
  color: var(--accent);
  margin-right: 0.06em;
}

.site-nav {
  display: flex;
  gap: var(--s5);
  margin-right: auto;
  font-size: var(--fs-meta);
  font-weight: 500;
  letter-spacing: 0.02em;
}
.site-nav a { color: var(--ink-muted); text-decoration: none; }
.site-nav a:hover { color: var(--ink); text-decoration: underline; }

/* The language switcher: a `<details>`, which is why this page loads no
   JavaScript library. It used to be Bootstrap's dropdown, kept for the four
   fiddly behaviours — keyboard, escape, click-outside and `aria-expanded`.
   `<details>` does two of those in the browser and needs no script for them;
   `spika.js` adds escape and click-outside in twenty lines. The trade is 24KB
   of JavaScript and a positioning engine against a switcher that now also
   works with no JavaScript at all, which the Bootstrap one never did. */
.lang-switch { position: relative; }
.lang-switch > summary {
  font-size: var(--fs-meta);
  font-weight: 500;
  color: var(--ink-muted);
  cursor: pointer;
  white-space: nowrap;
  list-style: none;          /* Firefox draws a disclosure triangle. */
}
.lang-switch > summary::-webkit-details-marker { display: none; }
.lang-switch > summary:hover { color: var(--ink); }
/* The caret, at Bootstrap's own geometry so the masthead is unchanged. */
.lang-switch > summary::after {
  content: "";
  display: inline-block;
  margin-left: 0.255em;
  vertical-align: 0.255em;
  border-top: 0.3em solid;
  border-right: 0.3em solid transparent;
  border-bottom: 0;
  border-left: 0.3em solid transparent;
}
.lang-switch__menu {
  position: absolute;
  z-index: 1000;
  top: calc(100% + 0.125rem);
  right: 0;
  min-width: 10rem;
  margin: 0;
  padding: var(--s2) 0;
  list-style: none;
  font-size: var(--fs-meta);
  background: var(--paper);
  border: 1px solid var(--rule-strong);
  border-radius: 2px;
}
.lang-switch__item {
  display: block;
  width: 100%;
  padding: var(--s1) var(--s4);
  color: var(--ink-muted);
  text-decoration: none;
  white-space: nowrap;
}
.lang-switch__item:hover, .lang-switch__item:focus {
  background: var(--paper-sunk);
  color: var(--ink);
}
.lang-switch__item.is-current { color: var(--accent); }

/* Where the menu hangs from, and the one thing Popper was really buying.

   `right: 0` normally means the switcher's right edge, which is where it has
   always opened. Once the masthead wraps that stops being safe: the switcher's
   position on its new row is not stable — measured at the right of its line at
   320px and at the *left* at 360px, because `.site-nav`'s auto margin only
   pushes the line it is on — and right-aligning to it at 360px put the menu
   38px off the left edge of the screen. Bootstrap never showed this because
   Popper measured the viewport on every open and flipped the menu.

   Making `.lang-switch` static below the wrap hands the menu to the header rail
   instead, so `right: 0` becomes the rail's right edge: on screen at every
   width, by construction rather than by measurement. It is also not a
   compromise — against the old build it lands on the same pixel at 320, 412,
   768 and 1280, and differs only at the width where Popper had to intervene.
   The header's own layout is untouched, which the alternative was not. */
@media (max-width: 40rem) {
  .lang-switch { position: static; }
  /* `right: 0` against the rail would be its *padding* box, putting the menu
     flush against the edge of the screen. This lands it on the same line as
     every other thing on the page — the same `--s4` the wrap uses at this
     width — which is the column the whole design hangs off. */
  .lang-switch__menu { right: var(--s4); }
}

/* ============================================================ the statement */

.statement {
  margin-block: var(--s7);
  /* A 300-character statement set at 48px is a wall, and a six-word one set
     small is a wasted front page. Length picks the size; the template buckets
     it, because only the template knows how long the text is. */
  --fs-active: var(--fs-statement);
}
.statement[data-len="short"] { --fs-active: calc(var(--fs-statement) * 1.12); }
.statement[data-len="long"]  { --fs-active: calc(var(--fs-statement) * 0.8); }
.statement--page             { --fs-active: var(--fs-statement-lead); }
.statement--page[data-len="short"] { --fs-active: calc(var(--fs-statement-lead) * 1.05); }
.statement--page[data-len="long"]  { --fs-active: calc(var(--fs-statement-lead) * 0.66); }
.lead .statement { margin-top: 0; }

.statement__quote {
  margin: 0 0 var(--s5);
  position: relative;
  max-width: var(--measure-statement);
}

/* The opening mark hangs in the margin so the first line of the statement
   starts at the true left edge — hanging punctuation, done with an element
   because `hanging-punctuation` is still Safari-only. */
.statement__quote::before {
  content: var(--quote-open);
  position: absolute;
  left: -0.58em;
  top: -0.02em;
  font-family: var(--serif);
  font-size: calc(var(--fs-active) * 1.65);
  line-height: 1;
  color: var(--accent);
  pointer-events: none;
}

.statement__text {
  font-family: var(--serif);
  font-size: var(--fs-active);
  font-weight: 400;
  line-height: 1.28;
  letter-spacing: -0.005em;
  color: var(--ink);
  margin: 0;
  text-wrap: pretty;
}
/* The closing mark, inline, at text size. A quotation that visibly closes is
   a quotation that has not been cropped. */
.statement__text::after {
  content: var(--quote-close);
  color: var(--accent);
  margin-left: 0.04em;
}

@media (max-width: 44rem) {
  /* No margin to hang into. The mark leads the line instead of sitting beside
     it, which keeps the device and drops the indent. */
  .statement__quote::before { position: static; float: left; margin-right: 0.1em; }
}

/* The transliteration. Both are visible without JavaScript; the script script
   turns them into a toggle. */
.statement__text--alt {
  font-size: calc(var(--fs-active) * 0.72);
  color: var(--ink-muted);
  margin-top: var(--s4);
}
.script-pair[data-script-ready] .statement__text--alt { display: none; }
.script-pair[data-script-ready][data-showing="latin"] .statement__text { display: none; }
.script-pair[data-script-ready][data-showing="latin"] .statement__text--alt {
  display: block;
  font-size: var(--fs-active);
  color: var(--ink);
  margin-top: 0;
}

/* Deliberately smaller than the statement. The words outrank the speaker. */
.statement__attrib-heading { margin: 0; font-size: inherit; font-weight: inherit; }
.statement__speaker {
  font-family: var(--serif);
  font-size: 1.1875rem;
  font-weight: 600;
  margin: 0 0 var(--s2);
}
.statement__speaker a { color: var(--ink); text-decoration: none; }
.statement__speaker a:hover { text-decoration: underline; }
.statement__role {
  display: block;
  font-family: var(--sans);
  font-size: var(--fs-meta);
  font-weight: 400;
  color: var(--ink-muted);
  margin-top: var(--s1);
}

.statement__occasion,
.statement__more {
  font-size: var(--fs-meta);
  color: var(--ink-muted);
  margin: var(--s2) 0 0;
}
.statement__occasion > * + *::before { content: " · "; color: var(--ink-faint); }
.statement__more { margin-top: var(--s4); }

/* ============================================================ attribution */

/* Never decoration and never optional. This block is what makes the site
   quotation rather than substitution, so it is given a rule of its own. */
.attribution {
  margin-top: var(--s5);
  padding-top: var(--s3);
  border-top: 1px solid var(--rule);
  font-size: var(--fs-meta);
}
.attribution__primary { margin: 0; font-weight: 600; }
.attribution__primary a { color: var(--ink); }
/* A filled square marks the outlet the words are verbatim from, as against the
   others that also carried the statement. */
.attribution__primary a::before {
  content: "";
  display: inline-block;
  width: 0.42em; height: 0.42em;
  margin-right: 0.5em;
  vertical-align: 0.06em;
  background: var(--accent);
}
.attribution__date { color: var(--ink-muted); font-weight: 400; margin-left: var(--s2); }
.attribution__others { margin: var(--s2) 0 0; color: var(--ink-muted); }
.attribution__others a { color: var(--ink-muted); }
.attribution__label {
  font-size: var(--fs-label);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ink-faint);
  margin-right: var(--s2);
}
.attribution__primary--missing { color: var(--ink-faint); font-weight: 400; font-style: italic; }

/* ============================================================ context */

/* Muted on purpose. A machine wrote this and a human checked it; it is not the
   record, and it should not look like the record. */
.context { margin-top: var(--s6); }
.context .section-label { margin-top: 0; }
.context__body {
  font-family: var(--serif);
  font-size: var(--fs-body);
  line-height: 1.68;
  color: var(--ink-muted);
  margin: 0;
  max-width: 32rem;
  text-wrap: pretty;
}
.context__note {
  font-size: var(--fs-label);
  line-height: 1.5;
  color: var(--ink-faint);
  margin: var(--s3) 0 0;
  max-width: 32rem;
}

.content-warning {
  font-size: var(--fs-meta);
  font-weight: 600;
  color: var(--flag);
  border-left: 3px solid var(--flag);
  padding-left: var(--s3);
  margin: 0 0 var(--s4);
}

/* ============================================================ topics */

.topic-list, .chip-list {
  list-style: none;
  display: flex;
  flex-wrap: wrap;
  gap: var(--s2) var(--s3);
  padding: 0;
  margin: var(--s5) 0 0;
}
.topic-list a, .chip-list a {
  display: inline-block;
  font-size: var(--fs-meta);
  color: var(--ink-muted);
  text-decoration: none;
  border: 1px solid var(--rule);
  border-radius: 2px;
  padding: 0.2em 0.7em;
}
.topic-list a:hover, .chip-list a:hover {
  color: var(--ink);
  border-color: var(--rule-strong);
  background: var(--paper-sunk);
}
.chip__count { color: var(--ink-faint); margin-left: 0.5em; font-variant-numeric: tabular-nums; }

/* ============================================================ feed */

.page-intro {
  font-family: var(--serif);
  font-size: 1.125rem;
  line-height: 1.55;
  color: var(--ink-muted);
  max-width: 30rem;
  margin: 0 0 var(--s7);
}

.teaser { padding-block: var(--s5); border-bottom: 1px solid var(--rule); }
.teaser:first-of-type { padding-top: 0; }
.teaser__link { text-decoration: none; display: block; }
.teaser__text {
  font-family: var(--serif);
  font-size: var(--fs-teaser);
  line-height: 1.4;
  color: var(--ink);
  margin: 0;
  text-wrap: pretty;
}
.teaser__text::before { content: var(--quote-open); color: var(--accent); }
.teaser__text::after  { content: var(--quote-close); color: var(--accent); }
.teaser__link:hover .teaser__text { color: var(--accent); }
.teaser__meta {
  font-size: var(--fs-meta);
  color: var(--ink-muted);
  margin: var(--s3) 0 0;
}
.teaser__speaker { font-weight: 600; color: var(--ink); }
.teaser__meta > * + *::before { content: " · "; color: var(--ink-faint); font-weight: 400; }

.rail { margin-top: var(--s7); }

/* ============================================================ actions */

/* Written by spika.js, so absent rather than broken without it. */
.actions { display: flex; gap: var(--s3); margin-top: var(--s4); }
.actions button {
  font-family: var(--sans);
  font-size: var(--fs-meta);
  font-weight: 500;
  color: var(--ink-muted);
  background: none;
  border: 1px solid var(--rule);
  border-radius: 2px;
  padding: 0.3em 0.85em;
  cursor: pointer;
}
.actions button:hover { color: var(--ink); border-color: var(--rule-strong); }
.actions button[data-done] { color: var(--accent); border-color: var(--accent); }

.card-link { margin: var(--s3) 0 0; font-size: var(--fs-meta); }
.card-link a { color: var(--ink-muted); }
.card-link a:hover { color: var(--ink); }

/* ============================================================ accounts + votes

   Kept quiet on purpose. A tally set at the size of the statement would read as the site's
   own verdict on what somebody said, which is the opposite of what the rest of this design
   is for. It sits below the attribution, at metadata size, with a sentence saying that it
   changes nothing about what gets published. */

/* Search. Collapsed to its icon, opening leftward into the gap between the nav and the
   account link — rightward would push the language switcher off the edge on a phone.

   The field is always in the DOM and always submittable: `is-open` only changes its width, so
   with JavaScript off it is a visible search box rather than an icon that does nothing. The
   width transition is the whole animation; there is no fade, because a field that fades in is
   a field you cannot type into for 200ms. */
.site-search {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  margin-inline-start: auto;
  gap: var(--s2);
}
.site-search__field {
  width: 13rem;
  font-family: var(--sans);
  font-size: var(--fs-meta);
  color: var(--ink);
  background: var(--paper);
  border: 0;
  border-bottom: 1px solid var(--rule-strong);
  border-radius: 0;
  padding: var(--s1, 0.25rem) var(--s2);
  transition: width 160ms ease, opacity 120ms ease;
}
.site-search__field:focus { outline: none; border-bottom-color: var(--accent); }
.site-search__field::-webkit-search-cancel-button { cursor: pointer; }

.site-search__toggle {
  display: inline-flex;
  align-items: center;
  color: var(--ink-muted);
  background: none;
  border: 0;
  padding: var(--s1, 0.25rem);
  cursor: pointer;
}
.site-search__toggle:hover { color: var(--ink); }

/* Collapsed state, applied by the script once it has confirmed it can reopen it. */
.site-search[data-collapsed] .site-search__field {
  width: 0;
  padding-inline: 0;
  opacity: 0;
  border-bottom-color: transparent;
  pointer-events: none;
}
.site-search[data-collapsed].is-open .site-search__field {
  width: 13rem;
  padding-inline: var(--s2);
  opacity: 1;
  border-bottom-color: var(--rule-strong);
  pointer-events: auto;
}

@media (max-width: 40rem) {
  .site-search__field, .site-search[data-collapsed].is-open .site-search__field { width: 9rem; }
}

.search-page__form { display: flex; gap: var(--s3); margin-block: var(--s5) var(--s2); }
.search-page__form input {
  flex: 1 1 auto;
  font-family: var(--sans);
  font-size: 1rem;
  color: var(--ink);
  background: var(--paper);
  border: 1px solid var(--rule-strong);
  border-radius: 2px;
  padding: var(--s2) var(--s3);
}
.search-page__form input:focus { outline: none; border-color: var(--accent); }
.search-page__form button {
  font-family: var(--sans);
  font-size: var(--fs-meta);
  color: var(--paper);
  background: var(--accent);
  border: 0;
  border-radius: 2px;
  padding: var(--s2) var(--s5);
  cursor: pointer;
}
.search-page__note { font-size: var(--fs-meta); color: var(--ink-faint); margin: 0 0 var(--s6); }

.search-speakers { list-style: none; padding: 0; margin: 0 0 var(--s7); }
.search-speakers li {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--s3);
  padding-block: var(--s3);
  border-bottom: 1px solid var(--rule);
}
.search-speakers a { font-weight: 600; color: var(--ink); text-decoration: none; }
.search-speakers a:hover { color: var(--accent); text-decoration: underline; }
.search-speakers__role, .search-speakers__count {
  font-size: var(--fs-meta);
  color: var(--ink-muted);
}
.search-speakers__count { margin-inline-start: auto; color: var(--ink-faint); }

.site-account { font-size: var(--fs-meta); }
.site-account a { color: var(--ink-muted); text-decoration: none; }
.site-account a:hover { color: var(--ink); text-decoration: underline; }
.site-account button {
  font-family: var(--sans);
  font-size: var(--fs-meta);
  color: var(--ink-muted);
  background: none;
  border: 0;
  padding: 0;
  cursor: pointer;
}
.site-account button:hover { color: var(--ink); text-decoration: underline; }

.votes { margin-top: var(--s4); }
.votes__form { display: flex; gap: var(--s2); }
.votes__button {
  display: inline-flex;
  align-items: baseline;
  gap: 0.4em;
  font-family: var(--sans);
  font-size: var(--fs-meta);
  font-variant-numeric: tabular-nums;
  color: var(--ink-muted);
  background: none;
  border: 1px solid var(--rule);
  border-radius: 2px;
  padding: 0.3em 0.8em;
  cursor: pointer;
}
.votes__button:hover { color: var(--ink); border-color: var(--rule-strong); }
/* The reader's own vote, marked but not celebrated. */
.votes__button.is-mine { color: var(--accent); border-color: var(--accent); }
.votes__tally {
  display: flex;
  gap: var(--s4);
  font-size: var(--fs-meta);
  font-variant-numeric: tabular-nums;
  color: var(--ink-muted);
  margin: 0;
}
.votes__tally span[aria-hidden] { margin-right: 0.3em; }
.votes__signin {
  display: inline-block;
  font-size: var(--fs-meta);
  color: var(--ink-muted);
  margin-top: var(--s2);
}
.votes__note {
  font-size: var(--fs-label);
  line-height: 1.5;
  color: var(--ink-faint);
  margin: var(--s2) 0 0;
  max-width: 30rem;
}

/* The editorial takedown, which only a signed-in editor is ever served. It is set in the flag
   colour rather than the accent: the accent is the publication's own voice and belongs to
   quotation marks, and this control is the one thing on a statement that is not part of the
   record. Deliberately plain and slightly apart — it is meant to be found in a hurry by
   somebody holding a complaint, not to be pretty. */
.takedown {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s2);
  align-items: center;
  margin-top: var(--s4);
  padding-top: var(--s3);
  border-top: 1px dashed var(--rule);
}
.takedown__reason {
  flex: 1 1 14rem;
  font-family: var(--sans);
  font-size: var(--fs-label);
  color: var(--ink);
  background: transparent;
  border: 0;
  border-bottom: 1px solid var(--rule);
  border-radius: 0;
  padding: var(--s1) 0;
}
.takedown__reason::placeholder { color: var(--ink-faint); }
.takedown__reason:focus { outline: none; border-bottom-color: var(--flag); }
.takedown button {
  font-family: var(--sans);
  font-size: var(--fs-label);
  color: var(--flag);
  background: none;
  border: 1px solid var(--flag);
  border-radius: 2px;
  padding: 0.3em 0.9em;
  cursor: pointer;
}
.takedown button:hover { color: var(--paper); background: var(--flag); }

.signin { padding-block: var(--s8); }
.signin h1 {
  font-family: var(--serif);
  font-size: clamp(1.9rem, 1.4rem + 2vw, 2.5rem);
  font-weight: 600;
  letter-spacing: -0.02em;
  margin: 0 0 var(--s4);
}
.signin__intro {
  font-family: var(--serif);
  font-size: 1.1875rem;
  line-height: 1.6;
  color: var(--ink-muted);
  max-width: 32rem;
  margin: 0 0 var(--s6);
}
.signin__button {
  display: inline-block;
  font-family: var(--sans);
  font-size: var(--fs-body);
  font-weight: 600;
  color: var(--accent);
  background: none;
  border: 1px solid var(--accent);
  border-radius: 2px;
  padding: 0.6em 1.4em;
  text-decoration: none;
  cursor: pointer;
}
.signin__button:hover { color: var(--paper); background: var(--accent); }
.signin__done { font-size: var(--fs-meta); color: var(--ink-muted); margin-bottom: var(--s3); }
.signin__privacy {
  font-size: var(--fs-meta);
  line-height: 1.6;
  color: var(--ink-faint);
  max-width: 32rem;
  margin-top: var(--s6);
}

/* ============================================================ the chart

   Ranked, but not celebratory. The rank numeral is set in the metadata face at metadata size
   rather than as a big display figure, because the statement is still the thing on the page
   and a large numeral beside somebody's words turns the list into a scoreboard. */

.chart__intro {
  font-family: var(--serif);
  font-size: 1.0625rem;
  line-height: 1.6;
  color: var(--ink-muted);
  max-width: 34rem;
  margin: var(--s4) 0 0;
}
.chart__list { max-width: var(--measure-statement); }

.windows {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s4);
  margin: var(--s6) 0 0;
  padding-bottom: var(--s3);
  border-bottom: 1px solid var(--rule);
  max-width: var(--measure-statement);
}
.windows__option {
  font-size: var(--fs-meta);
  color: var(--ink-muted);
  text-decoration: none;
  padding-bottom: var(--s2);
  border-bottom: 2px solid transparent;
  margin-bottom: calc(-1 * var(--s3) - 1px);
}
.windows__option:hover { color: var(--ink); }
.windows__option.is-current { color: var(--ink); border-bottom-color: var(--accent); }
.chart__items { list-style: none; padding: 0; margin: 0; counter-reset: none; }

.chart-row {
  display: grid;
  grid-template-columns: 2.5rem minmax(0, 1fr);
  gap: var(--s4);
  padding-block: var(--s5);
  border-bottom: 1px solid var(--rule);
}
.chart-row__rank {
  font-family: var(--sans);
  font-size: var(--fs-meta);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  color: var(--ink-faint);
  padding-top: 0.35em;
}
.chart-row__text {
  font-family: var(--serif);
  font-size: var(--fs-teaser);
  line-height: 1.4;
  color: var(--ink);
  text-decoration: none;
  display: block;
  text-wrap: pretty;
}
.chart-row__text::before { content: var(--quote-open); color: var(--accent); }
.chart-row__text::after  { content: var(--quote-close); color: var(--accent); }
.chart-row__text:hover { color: var(--accent); }
.chart-row__meta {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s3);
  font-size: var(--fs-meta);
  color: var(--ink-muted);
  margin: var(--s3) 0 0;
}
.chart-row__speaker { font-weight: 600; color: var(--ink); }
.chart-row__votes { font-variant-numeric: tabular-nums; color: var(--ink-faint); }
.chart-row__votes span { margin-left: 0.4em; }

/* ============================================================ hub + lists */

.hub-header { margin-bottom: var(--s7); }
.hub-header__name {
  font-family: var(--serif);
  font-size: clamp(1.9rem, 1.4rem + 2vw, 2.6rem);
  font-weight: 600;
  letter-spacing: -0.02em;
  margin: 0;
}
.hub-header__role {
  font-family: var(--serif);
  font-size: 1.125rem;
  color: var(--ink-muted);
  margin: var(--s2) 0 0;
}
.hub-header__meta {
  font-size: var(--fs-meta);
  color: var(--ink-faint);
  margin: var(--s3) 0 0;
}
.hub-header__meta > * + *::before { content: " · "; }
.hub-header__bio {
  font-family: var(--serif);
  line-height: 1.65;
  color: var(--ink-muted);
  margin: var(--s4) 0 0;
  max-width: 32rem;
}
/* The QA gate does not read `people.verified`. The page says so rather than
   implying a check that does not run. */
.hub-header__unverified {
  font-size: var(--fs-label);
  line-height: 1.5;
  color: var(--ink-faint);
  border-left: 2px solid var(--rule-strong);
  padding-left: var(--s3);
  margin: var(--s4) 0 0;
  max-width: 30rem;
}

/* A hub carries everything one person ever said, and for the person quoted most that is 190
   statements: 7,162 elements and 219,473px of document on a phone, measured 2026-08-14. The
   page is correct — every statement is linked from its speaker's hub, and the ones no outlet
   but their own carried have no other page at all, so none of this may be truncated — but the
   browser was laying out all 240 screens of it before painting the first one.

   `content-visibility: auto` lets it skip the layout and paint of what is off screen and do
   that work as the reader arrives at it. The elements stay in the accessibility tree and
   find-in-page still matches inside them.

   **But it cannot be on when the page is opened at a fragment**, and that was measured rather
   than assumed. The browser scrolls to where it believes the target is, using the estimated
   height for every element above it; each of those is then laid out for real and the error
   accumulates. Opening `#izjava-47` on this hub landed the reader **12,256px** past the
   statement they had followed a link to. A hub anchor is the permanent address of a statement
   that never earned its own URL — it is the whole reason those statements are reachable — so
   arriving at the wrong one is not a rendering imperfection, it is a broken link.

   `:has(:target)` is exactly that condition: any page opened at a fragment renders in full and
   lands where it should, and every other view of the hub gets the optimisation. A browser too
   old for `:has()` drops the whole rule and renders in full always, which is the safe way round.

   `contain-intrinsic-size: auto 1000px` is the guess used only until an element has been
   rendered once, after which the browser remembers what it actually measured. 1000px is under
   the measured mobile median of 1,099px on purpose: guessing short makes the scrollbar grow as
   you go, and guessing long makes it jump back. */
html:not(:has(:target)) .hub-statements > .statement {
  content-visibility: auto;
  contain-intrinsic-size: auto 1000px;
}

/* Back to top. `position: fixed`, so it is out of flow and cannot contribute to CLS — which
   matters here, where every page type was measured and the worst is 0.057.

   Neutral rather than accent-coloured: accent means "the system established this" elsewhere in
   the UI, and this establishes nothing. It is furniture.

   Hidden until `spika.js` reveals it, and `[hidden]` is respected rather than overridden — an
   `opacity: 0` button stays in the tab order, so a keyboard reader would find an invisible
   control at the end of every page. */
.to-top {
  position: fixed;
  right: var(--s5);
  bottom: calc(var(--s5) + env(safe-area-inset-bottom, 0px));
  z-index: 20;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 2.75rem;
  height: 2.75rem;
  border: 1px solid var(--rule-strong);
  border-radius: 50%;
  background: var(--paper);
  color: var(--ink-muted);
  box-shadow: 0 1px 6px rgb(0 0 0 / 12%);
  transition: color 0.15s, border-color 0.15s, transform 0.15s;
}
.to-top:hover, .to-top:focus-visible { color: var(--accent); border-color: var(--accent); }
.to-top:active { transform: translateY(1px); }
/* Below the wrap, the footer's own controls sit near this corner; tuck it in a little. */
@media (max-width: 30rem) {
  .to-top { right: var(--s4); bottom: calc(var(--s4) + env(safe-area-inset-bottom, 0px)); }
}
@media (prefers-reduced-motion: reduce) { .to-top { transition: none; } }

/* Which slice of the hub is on screen. Only rendered when there is more than one page, so a
   hub of three statements does not explain itself. */
.hub-range {
  font-size: var(--fs-meta);
  color: var(--ink-faint);
  margin: 0 0 var(--s5);
}

/* Hub pagination.

   Plain links and no script, because this is the only route to a statement that never earned
   its own URL — it has to work for a crawler and with JavaScript off, like the anchors it
   leads to.

   Every page is listed rather than windowed with an ellipsis. The largest hub in the corpus is
   seven pages at `HUB_PAGE_SIZE`, so the whole set fits on one line on a phone, and a link a
   crawler can see is a page it can reach. Revisit if a hub ever runs to twenty pages. */
.pager {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: var(--s3);
  margin: var(--s7) 0 0;
  padding-top: var(--s6);
  border-top: 1px solid var(--rule);
}
.pager__list {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s2);
  list-style: none;
  margin: 0;
  padding: 0;
}
.pager__page, .pager__step {
  /* Flex rather than inline-block so the label centres inside the tap target below. */
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: var(--fs-meta);
  line-height: 1;
  padding: var(--s3) var(--s4);
  border: 1px solid var(--rule);
  border-radius: 2px;
  text-decoration: none;
  color: var(--ink-muted);
  /* Meets the 44px tap-target guidance without the number itself looking oversized. */
  min-width: 2.75rem;
  min-height: 2.75rem;
}
.pager__page:hover, .pager__step:hover { border-color: var(--accent); color: var(--accent); }
.pager__page--here {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--paper);
  font-weight: 600;
}
.pager__page--here:hover { color: var(--paper); }
/* The ends, when there is nowhere further to go. Present rather than removed, so the row does
   not change width as the reader moves through it. */
.pager__step--off { opacity: 0.4; }
.pager__count {
  font-size: var(--fs-meta);
  color: var(--ink-faint);
  text-align: center;
  margin: var(--s4) 0 0;
}

.list-header { margin-bottom: var(--s6); }
.list-header h1 {
  font-family: var(--serif);
  font-size: clamp(1.75rem, 1.4rem + 1.6vw, 2.35rem);
  font-weight: 600;
  letter-spacing: -0.02em;
  margin: 0;
}
.list-header__meta, .list-header__note {
  font-size: var(--fs-meta);
  color: var(--ink-muted);
  margin: var(--s3) 0 0;
  max-width: 32rem;
}
.list-header__note { color: var(--ink-faint); }

.index { list-style: none; padding: 0; margin: 0; }
.index__row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: var(--s1) var(--s4);
  align-items: baseline;
  padding-block: var(--s4);
  border-bottom: 1px solid var(--rule);
}
.index__name {
  font-family: var(--serif);
  font-size: 1.125rem;
  font-weight: 600;
  color: var(--ink);
  text-decoration: none;
}
.index__name:hover { color: var(--accent); }
.index__count {
  font-size: var(--fs-meta);
  color: var(--ink-faint);
  font-variant-numeric: tabular-nums;
  grid-row: 1;
  grid-column: 2;
}
.index__role, .index__country {
  font-size: var(--fs-meta);
  color: var(--ink-muted);
  grid-column: 1;
}
.index__country { color: var(--ink-faint); }

.ledger { font-size: var(--fs-meta); margin: 0; }
.ledger th, .ledger td { padding: var(--s3) var(--s3) var(--s3) 0; }
.ledger thead th {
  font-size: var(--fs-label);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ink-faint);
  font-weight: 600;
  border-bottom: 1px solid var(--rule-strong);
}
.ledger tbody th { font-weight: 600; }
.ledger tbody th a { color: var(--ink); text-decoration: none; }
.ledger tbody th a:hover { color: var(--accent); text-decoration: underline; }
.ledger .num { text-align: right; font-variant-numeric: tabular-nums; }

/* Wide content scrolls inside its own box; the page body never does. */
.ledger-scroll { overflow-x: auto; }

/* ============================================================ filter */

/* The box is empty in the markup: `spika.js` builds the input, so that with JavaScript off
   there is no control rather than a broken one. That means the list below it moves down the
   moment the script runs — measured as 0.049 of layout shift on the media ledger, which is
   half of Google's entire "good" budget spent on a control nobody asked for yet.

   So the space is reserved here. `line-height` is stated rather than inherited for the same
   reason the height is stated: the reservation has to be arithmetic somebody can check, and
   an inherited 1.6 that changes in the body rules would silently make it wrong again. */
.filter { margin-bottom: var(--s5); min-height: calc(var(--fs-meta) * 1.6 + var(--s2) * 2 + 1px); }
.filter input {
  width: 100%;
  max-width: 20rem;
  font-family: var(--sans);
  font-size: var(--fs-meta);
  line-height: 1.6;
  color: var(--ink);
  background: transparent;
  border: 0;
  border-bottom: 1px solid var(--rule-strong);
  border-radius: 0;
  padding: var(--s2) 0;
}
.filter input::placeholder { color: var(--ink-faint); }
.filter input:focus { outline: none; border-bottom-color: var(--accent); }
.filter__empty { font-size: var(--fs-meta); color: var(--ink-faint); margin-top: var(--s4); }

/* ============================================================ root selector */

.root { padding-block: var(--s9) var(--s8); text-align: left; }
.root__wordmark {
  font-family: var(--serif);
  font-size: clamp(2.5rem, 1.6rem + 3.6vw, 4rem);
  font-weight: 600;
  letter-spacing: -0.03em;
  margin: 0;
}
.root__wordmark::before { content: var(--quote-open); color: var(--accent); }
.root__wordmark::after  { content: var(--quote-close); color: var(--accent); }
.root__tagline {
  font-family: var(--serif);
  font-size: 1.1875rem;
  line-height: 1.55;
  color: var(--ink-muted);
  margin: var(--s4) 0 var(--s8);
  max-width: 30rem;
}
.root__languages { list-style: none; padding: 0; margin: 0; }
.root__languages li { border-top: 1px solid var(--rule); }
.root__languages li:last-child { border-bottom: 1px solid var(--rule); }
.root__languages a {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: var(--s4);
  padding: var(--s5) 0;
  text-decoration: none;
}
.root__language {
  font-family: var(--serif);
  font-size: 1.5rem;
  font-weight: 600;
  color: var(--ink);
}
.root__languages a:hover .root__language { color: var(--accent); }
.root__count { font-size: var(--fs-meta); color: var(--ink-faint); font-variant-numeric: tabular-nums; }
/* Naming an empty tree is more honest than a home page with nothing on it. */
.root__missing {
  font-size: var(--fs-meta);
  color: var(--ink-faint);
  margin-top: var(--s5);
}

/* ============================================================ prose */

.prose h1 {
  font-family: var(--serif);
  font-size: clamp(1.9rem, 1.4rem + 2vw, 2.5rem);
  font-weight: 600;
  letter-spacing: -0.02em;
  margin: 0 0 var(--s5);
}
.prose h2 {
  font-family: var(--serif);
  font-size: 1.25rem;
  font-weight: 600;
  margin: var(--s7) 0 var(--s3);
}
.prose p {
  font-family: var(--serif);
  font-size: var(--fs-body);
  line-height: 1.68;
  color: var(--ink-muted);
  margin: 0 0 var(--s4);
  text-wrap: pretty;
}
.prose__lede { font-size: 1.1875rem; color: var(--ink); }
.prose__contact a { font-family: var(--sans); font-size: var(--fs-meta); }

.error-page { padding-block: var(--s8); }
.error-page__code {
  font-family: var(--sans);
  font-size: var(--fs-label);
  font-weight: 600;
  letter-spacing: 0.14em;
  color: var(--ink-faint);
  margin: 0 0 var(--s3);
}
.error-page h1 {
  font-family: var(--serif);
  font-size: clamp(1.75rem, 1.4rem + 1.6vw, 2.25rem);
  font-weight: 600;
  margin: 0 0 var(--s4);
}
.error-page p { color: var(--ink-muted); }

/* The joke, set as a quotation because that is what this site does with quotations. Quiet
   enough that it reads as a consolation rather than as the page's subject — the reader came
   here looking for something else and the first thing they need is the way back. */
.error-page__quote {
  margin: var(--s7) 0 var(--s6);
  padding-left: var(--s4);
  border-left: 2px solid var(--rule-strong);
}
.error-page__quote blockquote { margin: 0; }
.error-page__quote p {
  font-family: var(--serif);
  font-size: 1.15rem;
  line-height: 1.5;
  color: var(--ink);
  margin: 0;
}
.error-page__quote figcaption {
  font-size: var(--fs-meta);
  color: var(--ink-faint);
  margin-top: var(--s3);
}

/* ============================================================ footer */

.site-footer {
  flex-shrink: 0;
  border-top: 1px solid var(--rule);
  padding-block: var(--s6);
  font-size: var(--fs-meta);
  color: var(--ink-muted);
}
/* Four grid units: the statement takes two, the two link lists take one each. Prose and a
   link list set to the same measure read alike, and they are not alike. */
.site-footer__cols {
  display: grid;
  grid-template-columns: 2fr 1fr 1fr;
  gap: var(--s5) var(--s6);
  align-items: start;
}
.site-footer__col--wide { max-width: 42rem; }
.site-footer__statement { margin: 0 0 var(--s3); line-height: 1.6; }
/* The method paragraph sits under the statement and behind it in weight: it is the detail a
   reader checks once, not the sentence they read on every page. */
.site-footer__method { margin: 0; line-height: 1.6; opacity: 0.72; }

.site-footer__h {
  margin: 0 0 var(--s3);
  font-size: var(--fs-meta);
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--ink);
}
.site-footer__list { margin: 0; padding: 0; list-style: none; }
.site-footer__list li + li { margin-top: var(--s2); }
.site-footer__list a,
.site-footer__legal a { color: var(--ink-muted); text-decoration: none; }
.site-footer__list a:hover,
.site-footer__legal a:hover { color: var(--ink); text-decoration: underline; }

/* The legal bar. Separated by a rule because withdrawing consent has to be findable without
   reading anything, and the row that already says "cookies" and "privacy" is where people
   look. */
.site-footer__bar {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--s3) var(--s5);
  margin-top: var(--s6);
  padding-top: var(--s4);
  border-top: 1px solid var(--rule);
}
.site-footer__copy { margin: 0; }
.site-footer__legal { display: flex; flex-wrap: wrap; gap: var(--s5); align-items: baseline; }

/* A button that has to sit in a row of links and not shout about being a button. */
.site-footer__consent {
  padding: 0;
  border: 0;
  background: none;
  font: inherit;
  color: var(--ink-muted);
  cursor: pointer;
  text-decoration: underline;
  text-underline-offset: 0.2em;
  text-decoration-style: dotted;
}
.site-footer__consent:hover { color: var(--ink); text-decoration-style: solid; }

/* ============================================================ motion, print */

/* ============================================================ legal pages

   The cookie table and the "last updated" line the three legal documents share. Deliberately
   plain: this is the part of the site a regulator reads, and it should look like a document
   rather than like a feature. */

.prose__updated { margin-top: var(--s6); font-size: var(--fs-meta); color: var(--ink-muted); }

/* Its own scroller, so a four-column table on a phone does not make the whole page scroll
   sideways. */
.table-scroll { overflow-x: auto; margin-block: var(--s4); }

.legal-table { width: 100%; border-collapse: collapse; font-size: var(--fs-meta); }
.legal-table th,
.legal-table td {
  padding: var(--s2) var(--s3);
  text-align: left;
  vertical-align: top;
  border-bottom: 1px solid var(--rule);
}
.legal-table th {
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--ink-muted);
  white-space: nowrap;
}
/* Never broken across a line: a reader matches these against what their browser shows them,
   and `spika_stat` / `e` is not a name they can find. The cell wraps between names instead —
   see `CookieRow.names` — and `.table-scroll` catches whatever still will not fit. */
.legal-table code { font-size: 0.95em; white-space: nowrap; }
.legal-table td:first-child { line-height: 1.8; }

/* ============================================================ the editorial team

   A thumbnail and three lines. Kept small on purpose: the page is about what the site does,
   and a masthead portrait at any size larger than this starts to be the subject of it. */

.editors { list-style: none; margin: var(--s5) 0 0; padding: 0; }
.editor { display: flex; gap: var(--s4); align-items: flex-start; }
.editor + .editor { margin-top: var(--s5); }

.editor__photo {
  flex: 0 0 auto;
  width: 80px;
  height: 80px;
  border-radius: 50%;
  object-fit: cover;
  /* The photo is greyscale and the ground is near-white or near-black depending on the
     theme, so a hairline keeps the head from bleeding into the page at either end. */
  border: 1px solid var(--rule);
  background: var(--paper-sunk);
}
.editor__photo--initials {
  display: grid;
  place-items: center;
  font-size: 1.5rem;
  font-weight: 600;
  color: var(--ink-muted);
}

.editor__who { min-width: 0; }
.editor__name { margin: 0; font-weight: 600; }
.editor__role {
  display: block;
  font-size: var(--fs-meta);
  font-weight: 400;
  color: var(--ink-muted);
}
.editor__bio { margin: var(--s2) 0 0; font-size: var(--fs-meta); }
.editor__link { margin: var(--s2) 0 0; font-size: var(--fs-meta); }

/* ============================================================ the contact form

   The only place a reader types prose into this site, so the field is set in the reading face
   at the reading size: a message somebody is composing should look like the thing they are
   composing, not like a search box. */

.form { margin-block: var(--s6); }
.form__field { margin-bottom: var(--s5); }
.form__field label {
  display: block;
  margin-bottom: var(--s2);
  font-size: var(--fs-meta);
  font-weight: 600;
}
.form__hint { font-weight: 400; color: var(--ink-muted); }
.form__hint::before { content: " — "; }

.form__field input,
.form__field textarea {
  width: 100%;
  padding: var(--s3);
  border: 1px solid var(--rule);
  border-radius: 2px;
  background: var(--paper);
  color: var(--ink);
  font: inherit;
  line-height: 1.5;
}
.form__field textarea { resize: vertical; min-height: 9rem; }
.form__field input:focus-visible,
.form__field textarea:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }

.form__field--bad input,
.form__field--bad textarea { border-color: var(--bad); }
.form__error { margin: var(--s2) 0 0; font-size: var(--fs-meta); color: var(--bad); }

.form__note { font-size: var(--fs-meta); color: var(--ink-muted); }

.form__submit {
  padding: var(--s3) var(--s6);
  border: 1px solid var(--accent);
  border-radius: 2px;
  background: var(--accent);
  color: var(--paper);
  font: inherit;
  font-weight: 600;
  cursor: pointer;
}
.form__submit:hover { filter: brightness(1.1); }

/* The honeypot. Off-screen rather than `display:none` or `hidden`: some bots skip fields that
   are explicitly hidden, and this one is meant to look ordinary to a parser while being
   unreachable by eye, by keyboard and by screen reader. */
.form__trap {
  position: absolute;
  left: -9999px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

.notice {
  padding: var(--s3) var(--s4);
  border-left: 3px solid var(--rule);
  margin-block: var(--s5);
}
.notice--ok { border-left-color: var(--accent); }
.notice--bad { border-left-color: var(--bad); }

.btn-consent {
  padding: var(--s2) var(--s4);
  border: 1px solid var(--rule);
  border-radius: 2px;
  background: none;
  font: inherit;
  color: var(--ink);
  cursor: pointer;
}
.btn-consent:hover { border-color: var(--accent); color: var(--accent); }

@media (max-width: 48rem) {
  /* The double column stops being double before the link lists stop fitting beside it. */
  .site-footer__cols { grid-template-columns: 1fr 1fr; }
  .site-footer__col--wide { grid-column: 1 / -1; }
}

@media (max-width: 40rem) {
  .site-header__inner { flex-wrap: wrap; gap: var(--s2) var(--s4); }
  .site-nav { gap: var(--s4); }
  .wrap { padding-inline: var(--s4); }
  .site-main { padding-block: var(--s5) var(--s7); }
  .section-label { margin-top: var(--s7); }
  .index__row { grid-template-columns: minmax(0, 1fr) auto; }
  .site-footer__cols { grid-template-columns: 1fr; gap: var(--s5); }
  .site-footer__bar { flex-direction: column; align-items: flex-start; }
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    transition-duration: 0.01ms !important;
  }
}

@media print {
  /* The site is a record. It should print like one. */
  .site-header, .site-footer, .actions, .filter, .skip, .to-top { display: none; }
  body { background: #fff; color: #000; font-size: 11pt; }
  .wrap { max-width: none; padding: 0; }
  .statement__text { font-size: 16pt; }
  /* A printed quotation with no source is an unsourced quotation. */
  .attribution a::after { content: " (" attr(href) ")"; font-size: 8pt; word-break: break-all; }
}

/* ============================================================ review UI

   Shares the site's type deliberately: a reviewer approving a statement should be looking at
   the same page a reader will get. What is added is the editorial furniture around it, and it
   is marked so nobody can mistake one for the other. Every review page is noindex, and the
   whole subtree is disallowed in robots.txt. */

.review { padding-bottom: var(--s8); }

.review__bar {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--s5);
  padding-bottom: var(--s4);
  margin-bottom: var(--s6);
  border-bottom: 2px solid var(--accent);
}
.review__nav { display: flex; gap: var(--s5); font-size: var(--fs-meta); font-weight: 500; }
.review__nav a { color: var(--ink-muted); text-decoration: none; }
.review__nav a:hover { color: var(--ink); text-decoration: underline; }
.review__signout button,
.review__publish button,
.review__decide button,
.review-candidates button,
.review-login button {
  font-family: var(--sans);
  font-size: var(--fs-meta);
  font-weight: 600;
  color: var(--ink);
  background: none;
  border: 1px solid var(--rule-strong);
  border-radius: 2px;
  padding: 0.35em 0.9em;
  cursor: pointer;
}
.review__signout button { font-weight: 400; color: var(--ink-faint); border-color: var(--rule); }

.review__title {
  font-family: var(--serif);
  font-size: 1.9rem;
  font-weight: 600;
  letter-spacing: -0.02em;
  margin: 0 0 var(--s5);
}
.review__intro, .review__hint {
  font-size: var(--fs-meta);
  color: var(--ink-muted);
  max-width: 34rem;
  line-height: 1.6;
}
.review__hint { color: var(--ink-faint); margin: var(--s3) 0 0; }

.review__counts { display: flex; flex-wrap: wrap; gap: var(--s6); margin: 0 0 var(--s6); }
.review__counts dt {
  font-size: var(--fs-label);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ink-faint);
  font-weight: 600;
}
.review__counts dd {
  font-family: var(--serif);
  font-size: 1.75rem;
  font-weight: 600;
  margin: var(--s1) 0 0;
}

/* The one irreversible control on the site. Given weight, and a sentence saying what it does. */
.review__publish {
  border: 1px solid var(--accent);
  border-radius: 2px;
  padding: var(--s4) var(--s5);
  margin-bottom: var(--s6);
}
.review__publish button { border-color: var(--accent); color: var(--accent); }

.review-row { padding-block: var(--s4); border-bottom: 1px solid var(--rule); }
.review-row__text {
  font-family: var(--serif);
  font-size: 1.1875rem;
  line-height: 1.4;
  color: var(--ink);
  text-decoration: none;
  display: block;
  max-width: var(--measure-statement);
}
.review-row__text:hover { color: var(--accent); }
.review-row__meta {
  font-size: var(--fs-meta);
  color: var(--ink-muted);
  margin: var(--s2) 0 0;
  display: flex;
  flex-wrap: wrap;
  gap: var(--s4);
}
.review-row__flag { color: var(--flag); font-weight: 600; }
/* "no ads" is information, not alarm: an ad-restricted statement is still publishable and often
   the most newsworthy thing in the queue. Muted so it does not read as a problem to fix. */
.review-row__flag--ads { color: var(--ink-faint); font-weight: 500; }

/* Quick decisions from the queue. Deliberately plain and small — the detail page is where a
   statement gets read, and these should not compete with it for a reviewer's attention. */
.review-row__actions {
  display: flex;
  gap: var(--s3);
  align-items: center;
  margin-top: var(--s2);
  font-size: var(--fs-meta);
}
.review-row__actions form { display: inline; }
.review-row__actions button {
  font-family: var(--sans);
  font-size: var(--fs-meta);
  padding: var(--s1, 0.25rem) var(--s3);
  border: 1px solid var(--rule-strong);
  border-radius: 2px;
  background: var(--paper);
  color: var(--ink);
  cursor: pointer;
}
.review-row__actions .is-approve { border-color: var(--accent); color: var(--accent); }
.review-row__actions .is-reject { border-color: var(--flag); color: var(--flag); }
.review-row__actions a { color: var(--ink-faint); }

/* The duplicate guard on the candidates page. Flagged rather than accented: a near match is a
   question for the reviewer, not something the system has established. */
.review-candidates__near { font-weight: 600; display: block; }
.review-candidates__near.is-identical { color: var(--flag); }
.review-candidates__note { color: var(--ink-faint); font-size: var(--fs-meta); display: block; }
.review-candidates__form .is-attach {
  border-color: var(--flag);
  color: var(--flag);
  white-space: nowrap;
}
/* The collapsed reasoning. Same treatment as the tier breakdown on the review queue — present,
   and not occupying the screen a list needs. */
.review__notes { margin: 0 0 var(--s5); font-size: var(--fs-meta); }
.review__notes summary { color: var(--ink-faint); cursor: pointer; }
.review__notes .review__intro { margin-top: var(--s3); }

.review__bulk button { border-color: var(--accent); color: var(--accent); }

/* The sweeps over the human tier. Deliberately *not* accent-coloured: accent means "the system
   established this is safe" everywhere else in the review UI, and these approve statements the
   system flagged for a person. Boxed and flag-coloured so the two bulk controls cannot be
   confused at a glance — they sit within a screen of each other and do opposite things. */
.review__sweeps {
  margin: var(--s5) 0;
  padding: var(--s4) var(--s4) var(--s2);
  border: 1px solid var(--rule-strong);
  border-left: 3px solid var(--flag);
  max-width: var(--measure);
}
.review__sweeps .section-label { margin-top: 0; }
.review__sweeps-note { display: block; margin: 0 0 var(--s4); }
.review__sweep {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--s3);
  margin-bottom: var(--s4);
}
.review__sweep button {
  font-family: var(--sans);
  padding: var(--s2) var(--s4);
  border: 1px solid var(--flag);
  border-radius: 2px;
  background: var(--paper);
  color: var(--flag);
  cursor: pointer;
  white-space: nowrap;
}
.review__sweep .review__hint { flex: 1 1 16rem; }
.review__tiers { margin: var(--s3) 0 var(--s5); font-size: var(--fs-meta); }
.review__tiers summary { color: var(--ink-faint); cursor: pointer; }

.review__reasons { list-style: none; padding: 0; margin: 0; font-size: var(--fs-meta); }
.review__reasons li {
  display: flex;
  justify-content: space-between;
  gap: var(--s4);
  padding-block: var(--s2);
  border-bottom: 1px solid var(--rule);
  color: var(--ink-muted);
}
.review__reasons li span:last-child { font-variant-numeric: tabular-nums; color: var(--ink); }

/* Preview and panel side by side on a wide screen; the preview keeps the reading measure. */
.review__preview-label {
  font-size: var(--fs-label);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--flag);
  font-weight: 600;
  margin: 0 0 var(--s4);
  display: flex;
  flex-wrap: wrap;
  gap: var(--s3);
}
.review__preview-label span { color: var(--ink-faint); letter-spacing: 0.02em; text-transform: none; }
.review__preview { padding-bottom: var(--s6); border-bottom: 1px solid var(--rule); }

.review__panel { margin-top: var(--s6); max-width: var(--measure); }
.review__verdict { font-family: var(--sans); font-weight: 600; margin: 0 0 var(--s3); }
.review__verdict.is-pass { color: var(--accent); }
.review__verdict.is-fail { color: var(--flag); }
.review__blockers { font-size: var(--fs-meta); color: var(--ink-muted); padding-left: 1.2em; }

.review__facts { margin: var(--s5) 0; font-size: var(--fs-meta); }
.review__facts > div {
  display: flex;
  justify-content: space-between;
  gap: var(--s4);
  padding-block: var(--s2);
  border-bottom: 1px solid var(--rule);
}
.review__facts dt { color: var(--ink-faint); }
.review__facts dd { margin: 0; color: var(--ink); text-align: right; }

.review__warning {
  font-size: var(--fs-meta);
  line-height: 1.55;
  color: var(--flag);
  border-left: 3px solid var(--flag);
  padding-left: var(--s3);
  margin: var(--s5) 0;
}
.review__done {
  font-size: var(--fs-meta);
  color: var(--accent);
  border-left: 3px solid var(--accent);
  padding-left: var(--s3);
  margin-bottom: var(--s5);
}

.review__decide { margin-top: var(--s5); }
.review__decide label {
  display: block;
  font-size: var(--fs-label);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ink-faint);
  font-weight: 600;
  margin-bottom: var(--s2);
}
.review__decide textarea,
.review-candidates input,
.review-login input {
  width: 100%;
  font-family: var(--sans);
  font-size: var(--fs-meta);
  color: var(--ink);
  background: var(--paper);
  border: 1px solid var(--rule-strong);
  border-radius: 2px;
  padding: var(--s2) var(--s3);
}
.review__decide-actions { display: flex; gap: var(--s3); margin-top: var(--s4); }
.review__decide .is-approve { border-color: var(--accent); color: var(--accent); }
.review__decide .is-reject { border-color: var(--flag); color: var(--flag); }

.review-candidates { font-size: var(--fs-meta); }
.review-candidates code { color: var(--ink-muted); }
.review-candidates__form { display: flex; gap: var(--s2); align-items: center; }
.review-candidates__form input[name="country"] { width: 4rem; }
.review-candidates__form input[name="canonical_name"] { min-width: 12rem; }

/* Screening verdicts. A flagged row is tinted rather than hidden: the reviewer is meant to see
   what the rule thinks and be able to overrule it, which is why the confirm box sits inside the
   promote form rather than behind a second page. */
.review-candidates__flagged { background: color-mix(in srgb, var(--flag) 6%, transparent); }
.review-candidates .ok { color: var(--accent); }
.review-candidates .warn { color: var(--flag); cursor: help; }
.review-candidates__confirm {
  display: flex;
  gap: var(--s1, 0.25rem);
  align-items: center;
  font-size: var(--fs-label);
  color: var(--ink-faint);
  white-space: nowrap;
}
.review-candidates__confirm input { width: auto; }
.review-candidates button.danger { border-color: var(--flag); color: var(--flag); }

/* RSS health. Two tints, not three: a feed that is not collecting is a fault, a feed that is
   collecting but losing articles is a warning, and everything else is unmarked. Row tints are
   on the row rather than the cell — see the note at the top of this file about Bootstrap's
   opaque table cells painting over exactly this. */
.review-rss { font-size: var(--fs-meta); }
.review-rss code { color: var(--ink-muted); }
.review-rss__feed { max-width: 26rem; }
.review-rss__note { font-style: italic; }
.review-rss__bad { background: color-mix(in srgb, var(--flag) 8%, transparent); }
.review-rss__warn { background: color-mix(in srgb, var(--flag) 4%, transparent); }
.review-rss .ok { color: var(--accent); }
.review-rss .warn { color: var(--flag); cursor: help; }
.review-rss .bad { color: var(--flag); font-weight: 600; }
.review-rss__past { color: var(--ink-faint); cursor: help; }
.review__counts .rss--bad { color: var(--flag); }
.review__counts .rss--warn { color: var(--flag); }

/* Reader accounts. Same weight and rhythm as the RSS table — both are dense diagnostic lists
   an editor scans rather than reads, and they should not look like two different products. */
.review-readers { font-size: var(--fs-meta); }
.review-readers code { color: var(--ink-muted); font-size: 0.92em; overflow-wrap: anywhere; }
.review-readers__note { color: var(--ink-faint); font-style: normal; }
.review-readers__none { color: var(--ink-faint); }
.review-readers__num { text-align: right; font-variant-numeric: tabular-nums; }
/* An account with no identity provider cannot have been created by signing in, so it is marked
   rather than left to be spotted by reading the provider column on every row. */
.review-readers__no-provider { background: color-mix(in srgb, var(--flag) 4%, transparent); }

.review-login { max-width: 22rem; padding-block: var(--s9); }
.review-login h1 { font-family: var(--serif); font-size: 1.9rem; font-weight: 600; margin: 0; }
.review-login__note { font-size: var(--fs-meta); color: var(--ink-muted); margin: var(--s3) 0 var(--s5); }
.review-login label {
  display: block;
  font-size: var(--fs-label);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ink-faint);
  font-weight: 600;
  margin-bottom: var(--s2);
}
.review-login button { margin-top: var(--s4); }
.review-login__error { font-size: var(--fs-meta); color: var(--flag); margin-top: var(--s3); }

@media (min-width: 62rem) {
  .review__preview { display: grid; grid-template-columns: minmax(0, 1fr); }
}

/* --- review: speakers and their roles ---------------------------------------------
   A role prints under a speaker's name on every hub, statement and card, so this screen
   shows the evidence beside the field: an LLM proposal without its source sentence is a
   thing a reviewer can only agree with. */
.review__back { font-size: var(--fs-meta); margin: 0 0 var(--s3); }
.review__section {
  font-family: var(--sans);
  font-size: var(--fs-label);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ink-faint);
  margin: var(--s7) 0 var(--s3);
}
.review__count { color: var(--ink-muted); letter-spacing: 0; }

.review-people td { vertical-align: baseline; }
.review-role { display: block; max-width: 34rem; margin: var(--s5) 0; }
.review-role label {
  display: block;
  font-family: var(--sans);
  font-size: var(--fs-label);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-faint);
  margin-bottom: var(--s2);
}
.review-role__actions { margin-top: var(--s3); }
.review-role__propose { margin: var(--s5) 0; font-size: var(--fs-meta); }
.review-role__propose button {
  font-family: var(--sans);
  font-size: var(--fs-meta);
  font-weight: 600;
  color: var(--paper);
  background: var(--accent);
  border: 1px solid var(--accent);
  border-radius: 2px;
  padding: var(--s2) var(--s3);
  cursor: pointer;
  margin-right: var(--s3);
}

.review-proposal {
  border-left: 3px solid var(--accent);
  padding-left: var(--s4);
  margin: var(--s6) 0;
  max-width: 40rem;
}
.review-proposal--refused { border-left-color: var(--rule); }
.review-proposal__role {
  font-family: var(--serif);
  font-size: 1.4rem;
  font-weight: 600;
  margin: 0 0 var(--s2);
}
.review-proposal__evidence {
  font-size: var(--fs-meta);
  line-height: 1.6;
  color: var(--ink-muted);
  border: 0;
  margin: var(--s3) 0 0;
  padding: 0;
}

.review-evidence { list-style: none; padding: 0; margin: var(--s4) 0; max-width: 44rem; }
.review-evidence li { margin-bottom: var(--s4); }
.review-evidence__source {
  display: block;
  font-family: var(--sans);
  font-size: var(--fs-label);
  letter-spacing: 0.06em;
  color: var(--ink-faint);
}
.review-evidence__text { font-size: var(--fs-meta); line-height: 1.6; color: var(--ink-muted); }
