/* jobctl - a job board that remembers, styled like the ledger it keeps
   rather than like a landing page selling itself. Signature element: the
   ledger - a vertical timeline with a monospace gutter, echoed in miniature
   by the stat tiles' tick marks. Monospace is the default voice, not an
   accent applied to the data: nearly everything on this surface is an
   identifier, a count, a timestamp or a column, and the four blocks that
   are genuinely prose opt into --sans by name. Verdicts are never colour
   alone - every one pairs a glyph and a word with its colour.

   Dividers are a last resort here, not the default separator: a rule earns
   its place only where whitespace and weight cannot say what it says - the
   list's one column-header boundary, the chrome's top and bottom edges.
   Repeating a rule once per row (a posting, a source, a quality signal)
   was the loudest thing a dense page could do; a faint zebra tint carries
   that job instead. */

:root {
  --bg: #f7f7f5;
  --surface: #ffffff;
  --ink: #16181d;
  --ink-muted: #5b5f6b;
  --ink-faint: #8a8f9c;
  --rule: #dfe1e6;
  --rule-strong: #c3c7d1;
  --accent: #1f5fbf;
  --accent-ink: #ffffff;
  --good: #1e7a4c;
  --warn: #8a5c00;
  --bad: #b0311e;
  --focus: #1f5fbf;

  --sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica,
    Arial, sans-serif;
  --mono: "JetBrains Mono", "IBM Plex Mono", "Iosevka Term", ui-monospace,
    SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace;

  /* One scale, nine steps, and nothing off it. What was here before was
     twenty-one sizes each picked for the element in front of whoever was
     editing - 0.7, 0.72, 0.78, 0.8, 0.82, 0.85, 0.88, 0.9, 0.92 - which is
     how a page ends up with no hierarchy: every size is near every other,
     so none of them means anything. Steps are far enough apart to read as
     deliberate. Adding a tenth is a decision, not a nudge. */
  --t-3xs: 0.6875rem;
  --t-2xs: 0.75rem;
  --t-xs: 0.8125rem;
  --t-sm: 0.875rem;
  --t-base: 0.9375rem;
  --t-md: 1.0625rem;
  --t-lg: 1.375rem;
  --t-xl: 1.75rem;
  --t-2xl: 2.75rem;

  /* Uppercase micro-labels need tracking to stay legible; body text does
     not. One value, so they all look like the same decision. */
  --tracking-label: 0.06em;

  /* Spacing, on the same terms as the type scale above: nine steps at
     roughly 1.5x, and a gap is chosen from the list rather than typed. What
     was here before was thirty-odd literal values between 0.15rem and 4rem,
     picked one element at a time - which is how a page ends up where the
     distance between two blocks says nothing about how related they are.

     What each band is for, so a later editor picks by relationship rather
     than by eye:
       3xs..xs  inside one element - a label and its value, a chip's padding
       sm..md   between the elements of one block
       lg       between the blocks of one region
       xl       between regions of a page
       2xl,3xl  page-level - the top of main, the run-out above the footer

     The scale governs block-level gaps. Two kinds of value stay off it on
     purpose, and each says so where it is written: an intrinsic size (an
     icon, a column wide enough for a five-digit number, a marker straddling
     the ledger's spine), and the inside padding of something inline - a
     badge, a tag, the diff arrow - which is set in em so it tracks its own
     text rather than the page's rhythm. Anything else off the scale is an
     oversight. */
  --s-3xs: 0.25rem;
  --s-2xs: 0.375rem;
  --s-xs: 0.5rem;
  --s-sm: 0.75rem;
  --s-md: 1rem;
  --s-lg: 1.5rem;
  --s-xl: 2.25rem;
  --s-2xl: 3.5rem;
  --s-3xl: 5rem;

  color-scheme: light dark;
}

@media (prefers-color-scheme: dark) {
  :root {
    --bg: #0e1116;
    --surface: #161a21;
    --ink: #e7e9ee;
    --ink-muted: #9199a8;
    --ink-faint: #6b7280;
    --rule: #262b34;
    --rule-strong: #333a46;
    --accent: #6fa8ff;
    --accent-ink: #0e1116;
    --good: #4cc98a;
    --warn: #e3b341;
    --bad: #f2685a;
    --focus: #6fa8ff;
  }
}

* {
  box-sizing: border-box;
}

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

body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  /* Monospace is the default here, not the exception. This is a ledger:
     the page is mostly identifiers, counts, timestamps and columns, and
     setting those in a proportional face while switching to mono for the
     "data" bits is what produced a surface with two voices and no opinion.
     Prose - the blocks that are genuinely sentences - opts back into
     --sans explicitly, and there are four of them. */
  font-family: var(--mono);
  font-size: var(--t-base);
  line-height: 1.5;
  font-variant-numeric: tabular-nums;
  -webkit-font-smoothing: antialiased;
}

/* The blocks that are sentences rather than fields. Everything else
   inherits the monospace default above. */
.lede,
.detail-description,
.subscribe-note {
  font-family: var(--sans);
}

a {
  color: var(--accent);
  text-decoration-thickness: 1px;
  text-underline-offset: 2px;
}

a:hover {
  text-decoration-thickness: 2px;
}

:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 2px;
}

/* Kept for the markup that asks for it by name. Both are the body default
   now, so this only has to survive inside a block that opted into --sans. */
code,
.mono {
  font-family: var(--mono);
}

/* h1 is on the scale explicitly, not the browser's UA default - a bare
   <h1>Sources</h1> and a bare <h1>Admin</h1> were previously sized by
   whatever the browser ships, which is the same "no size means anything"
   problem the --t-* scale exists to end for everything else. The one page
   that wants a louder moment - the landing page's own name - opts into
   --t-2xl by name via .hero-title, rather than every h1 growing. */
h1,
h2,
h3 {
  font-weight: 650;
  line-height: 1.2;
  margin: 0 0 var(--s-sm);
}

/* A page's own h1 owns the space under it: what follows is the page, not
   another paragraph of the heading. */
h1 {
  font-size: var(--t-xl);
  margin-bottom: var(--s-lg);
}

.hero-title {
  font-size: var(--t-2xl);
}

p {
  margin: 0 0 var(--s-md);
}

/* Wider than a prose measure because the list is a table, not an article.
   The blocks that are prose - .lede, .detail-description - set their own
   max-width in ch and are unaffected.

   padding-inline, not the padding shorthand, because .site is on the
   header's row, the footer's row and on <main> itself, and each of those
   three sets its own block padding in its own rule. The shorthand won
   every one of those on specificity - a class beats a bare element - and
   silently reset it to zero, which is how <main> came to sit flush under
   the header rule with a padding-top nobody could see not applying. */
.site {
  max-width: 1280px;
  margin: 0 auto;
  padding-inline: var(--s-lg);
}

.site-header {
  border-bottom: 1px solid var(--rule);
  background: var(--surface);
}

/* The chrome is a band, and its padding is what makes it one: symmetric,
   on the scale, and small enough that the bar reads as a fixed strip rather
   than as the first region of the page. It is deliberately not the source
   of the space under it - see main's padding-top. */
.site-header .site {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-md);
  padding-top: var(--s-md);
  padding-bottom: var(--s-md);
}

.brand {
  font-family: var(--mono);
  font-weight: 700;
  font-size: var(--t-base);
  color: var(--ink);
  text-decoration: none;
  letter-spacing: -0.01em;
}

.brand:hover {
  text-decoration: none;
}

.brand-mark {
  color: var(--accent);
}

.nav {
  display: flex;
  gap: var(--s-lg);
  flex-wrap: wrap;
}

.nav a {
  color: var(--ink-muted);
  text-decoration: none;
  font-size: var(--t-sm);
}

.nav a:hover {
  color: var(--ink);
}

/* The gap between the header's rule and a page's h1 is set here, in one
   place, and it is a page-level step rather than a block-level one: the
   heading has to read as the start of a new region and not as a second line
   of the chrome. The header contributes only its own symmetric padding, so
   the two are not adding up to a distance nobody chose. */
main {
  padding: var(--s-2xl) 0 var(--s-3xl);
}

/* Links, not a tagline. What used to sit here was a sentence about what
   jobctl does, which every visitor had already read at the top of the page
   and which told nobody where anything is. */
.site-footer {
  border-top: 1px solid var(--rule);
  padding: var(--s-lg) 0;
  color: var(--ink-faint);
  font-size: var(--t-xs);
}

.site-footer .site {
  display: flex;
  gap: var(--s-lg);
  flex-wrap: wrap;
}

.lede {
  max-width: 62ch;
  font-size: var(--t-md);
  line-height: 1.55;
  color: var(--ink-muted);
  margin-bottom: var(--s-lg);
}

.stats {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  /* 1px is the hairline the grid background shows through as the cell
     borders, not a spacing decision. */
  gap: 1px;
  background: var(--rule);
  border: 1px solid var(--rule);
  margin: var(--s-xl) 0;
}

.stat {
  background: var(--surface);
  padding: var(--s-md);
  position: relative;
}

.stat::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0;
  bottom: 0;
  width: 2px;
  background: var(--rule-strong);
}

.stat-value {
  font-family: var(--mono);
  font-size: var(--t-xl);
  font-weight: 600;
  display: block;
}

.stat-label {
  color: var(--ink-muted);
  font-size: var(--t-xs);
  line-height: 1.45;
  margin-top: var(--s-3xs);
}

/* A rule used to sit under every one of these. Weight, tracking and colour
   already say "this is a label, not a sentence" on their own - the rule
   added nothing a reader could not already see, and on a page with three
   or four sections it was three or four more horizontal lines.

   Its margins are what a rule used to do: a region step above, a block step
   below, so the label sits nearer the thing it names than the thing before
   it. That asymmetry is the whole separator now. */
.section-title {
  font-size: var(--t-xs);
  font-weight: 650;
  text-transform: uppercase;
  letter-spacing: var(--tracking-label);
  color: var(--ink-muted);
  margin: var(--s-xl) 0 var(--s-sm);
}

/* The list: one posting per line, columns on a fixed grid, set in
   monospace so a reader scanning 200 of them scans columns rather than
   re-reading sentences. Every cell is a single line that ellipsises - a
   row that wraps breaks the column alignment that is the whole point, and
   the full value is one click away on the detail page.

   Rows carry no border of their own - a rule per row is a rule for every
   posting on the page, which is the loudest thing 200 of them could do.
   The heading row's own rule below is the list's one boundary; inside it, a
   faint zebra tint (.row:nth-child(even)) does the scanning work a line
   between every row used to. */
.list {
  font-family: var(--mono);
  font-size: var(--t-xs);
  font-variant-numeric: tabular-nums;
}

.row {
  display: grid;
  /* Widths come from what the columns actually hold: a "where" is a policy
     plus a place ("Hybrid - Valencia, Spain") and needs more room than a
     salary, which is bounded by its own format.

     Columns also fall into two groups, and the order says so: Organisation,
     Where and Salary are facts about the JOB; Source, Seen and Q are facts
     about OUR OBSERVATION of it - Source leads that second group rather
     than trailing the first, because it is an observation fact, not a job
     fact. Source ids are slugs (longest configured is 21 characters,
     "berlinstartupjobs-eng") and ellipsise at this width like every other
     cell here - the full value is one click away on the detail page, same
     as the rest of the row. The width for it comes off Posting and Where,
     the two columns with the most slack; Salary keeps its own width
     because its format bounds it already. */
  grid-template-columns:
    minmax(0, 2.0fr) minmax(0, 1.1fr) minmax(0, 1.3fr)
    minmax(0, 1fr) minmax(0, 0.9fr) 4.5rem 1.25rem;
  gap: 0 var(--s-lg);
  align-items: baseline;
  /* The zebra tint is what separates one row from the next, and a tint needs
     to be a band rather than a line: the vertical padding is a step up from
     what it was so the stripe has a shape. */
  padding: var(--s-xs) var(--s-sm);
}

.row > * {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* The faint stripe that replaces a rule between rows - present, but quiet
   enough that it reads as rhythm rather than as N more lines on the page. */
.row:nth-child(even) {
  background: color-mix(in srgb, var(--ink) 3%, transparent);
}

.row:not(.row-head):hover {
  background: color-mix(in srgb, var(--accent) 10%, var(--surface));
}

/* Sticky so the columns stay named while a long page scrolls - the
   heading is what lets a missing value be an em dash rather than the
   words "not stated" on every line. This is the list's one rule: it marks
   the boundary between "column names" and "data", which nothing else on
   the list does. */
.row-head {
  position: sticky;
  top: 0;
  z-index: 1;
  background: var(--bg);
  border-bottom: 1px solid var(--rule-strong);
  color: var(--ink-muted);
  font-size: var(--t-3xs);
  text-transform: uppercase;
  letter-spacing: var(--tracking-label);
}

.row-title a {
  color: var(--ink);
  text-decoration: none;
}

.row-title a:hover {
  color: var(--accent);
  text-decoration: underline;
}

.row-cell {
  color: var(--ink-muted);
}

.row-seen {
  text-align: right;
}

.row-q {
  text-align: center;
}

/* A secondary line under a primary one, outside the list grid: the source
   table's cadence line and the admin dashboard's kind/URL line. */
.meta-line {
  color: var(--ink-muted);
  font-size: var(--t-xs);
  line-height: 1.6;
  font-family: var(--mono);
  margin-top: var(--s-3xs);
}

.row-tag {
  color: var(--ink-faint);
  border: 1px solid var(--rule-strong);
  border-radius: 2px;
  font-size: var(--t-3xs);
  padding: 0 0.3em;
  margin-right: 0.4em;
}

.badge {
  display: inline-flex;
  align-items: center;
  gap: 0.35em;
  font-family: var(--mono);
  font-size: var(--t-2xs);
  padding: 0.15em 0.5em;
  border-radius: 3px;
  border: 1px solid currentColor;
  white-space: nowrap;
}

.verdict-fine {
  color: var(--good);
}

.verdict-concerning {
  color: var(--warn);
}

.verdict-bad {
  color: var(--bad);
}

.verdict-unknown {
  color: var(--ink-faint);
}

.status-open {
  color: var(--good);
}

.status-closed {
  color: var(--ink-faint);
}

/* Stands in for a list, so it takes a list's share of the page rather than
   a paragraph's. */
.empty {
  padding: var(--s-xl) 0;
  color: var(--ink-muted);
}

/* The link that follows a list - next page, older entries, browse them all.
   It was three different spacings for one role: a named margin on the two
   paging links and the paragraph default on the third. */
.after-list {
  margin: var(--s-lg) 0 0;
}

/* Filters are one line by default. The previous form was a grid of nine
   fields with 29 source checkboxes in it, which on /jobs left four
   postings above the fold - a filter panel taller than its own results.
   Everything past search, status and sort now lives behind <details>,
   which the server opens when one of those filters is actually set. */
.filters {
  margin-bottom: var(--s-xl);
}

.filters-bar {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-xs);
  align-items: center;
}

/* Sized by what it holds - a job title plus an organisation name - and
   allowed to take the whole line's slack; the selects beside it are sized
   by their longest option. */
.filters-search {
  flex: 1 1 22rem;
  min-width: 12rem;
}

.filters-bar input,
.filters-bar select {
  padding: var(--s-2xs) var(--s-xs);
  background: var(--surface);
  border: 1px solid var(--rule-strong);
  border-radius: 3px;
  color: var(--ink);
  font: inherit;
  font-size: var(--t-sm);
}

.filters-more {
  margin-top: var(--s-sm);
}

/* The subscribe panel sits between the filters and the results because
   that is what it is about: the search just built, handed to a reader.
   It used to be a collapsed <details> - a footnote on the page whose whole
   point is carrying a search away from it. Two chips are now visibly
   clickable beside the results, not folded behind a summary line.

   It is a region of its own, so it is spaced as one: the filters' bottom
   margin above it and its own below it are the same region step, since it
   belongs to neither neighbour more than the other. */
.subscribe {
  margin: 0 0 var(--s-xl);
  padding: var(--s-lg);
  background: var(--surface);
  border: 1px solid var(--rule);
  border-radius: 6px;
}

.subscribe > .section-title {
  margin-top: 0;
}

.subscribe-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-sm);
}

.subscribe-chip {
  display: inline-flex;
  align-items: center;
  gap: var(--s-xs);
  padding: var(--s-xs) var(--s-md);
  border: 1px solid var(--accent);
  border-radius: 5px;
  color: var(--accent);
  font-weight: 600;
  font-size: var(--t-sm);
  text-decoration: none;
}

.subscribe-chip:hover {
  background: color-mix(in srgb, var(--accent) 12%, transparent);
  text-decoration: none;
}

.subscribe-icon {
  width: 1rem;
  height: 1rem;
  flex-shrink: 0;
}

.subscribe-note {
  margin: var(--s-md) 0 0;
  font-size: var(--t-xs);
  line-height: 1.65;
  color: var(--ink-muted);
  max-width: 58ch;
}

/* Closer to the sentence that introduces it than that sentence is to the
   chips above it: the field and its note are one thing. */
.subscribe-url {
  display: block;
  width: 100%;
  margin-top: var(--s-xs);
  padding: var(--s-xs) var(--s-sm);
  background: var(--bg);
  color: var(--ink);
  border: 1px solid var(--rule-strong);
  border-radius: 3px;
  font-size: var(--t-2xs);
}

.filters-more > summary,
.sources-picker > summary {
  cursor: pointer;
  color: var(--ink-muted);
  font-size: var(--t-xs);
  font-family: var(--mono);
  width: fit-content;
}

.filters-more > summary:hover,
.sources-picker > summary:hover {
  color: var(--ink);
}

.filters-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: var(--s-md) var(--s-lg);
  background: var(--surface);
  border: 1px solid var(--rule);
  padding: var(--s-lg);
  margin-top: var(--s-xs);
}

.filters .full {
  grid-column: 1 / -1;
}

.sources-picker > div {
  margin-top: var(--s-xs);
  /* A scroll cap in rows of checkboxes, not a spacing step: about eight
     source names before the list starts scrolling. */
  max-height: 14rem;
  overflow-y: auto;
}

.field label {
  display: block;
  font-size: var(--t-2xs);
  line-height: 1.4;
  color: var(--ink-muted);
  margin-bottom: var(--s-3xs);
}

.field input[type="text"],
.field input[type="date"],
.field input[type="number"],
.field select,
.field textarea {
  width: 100%;
  padding: var(--s-2xs) var(--s-xs);
  background: var(--bg);
  border: 1px solid var(--rule-strong);
  border-radius: 3px;
  color: var(--ink);
  font: inherit;
}

.field textarea {
  resize: vertical;
}

.checkbox-group {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-xs) var(--s-md);
}

.checkbox-group label {
  display: inline-flex;
  align-items: center;
  gap: var(--s-3xs);
  font-size: var(--t-sm);
  color: var(--ink);
}

.btn {
  display: inline-block;
  font: inherit;
  font-weight: 600;
  padding: var(--s-xs) var(--s-md);
  border-radius: 3px;
  border: 1px solid var(--accent);
  background: var(--accent);
  color: var(--accent-ink);
  cursor: pointer;
}

.btn:hover {
  filter: brightness(1.08);
}

.btn-quiet {
  background: transparent;
  color: var(--ink);
  border-color: var(--rule-strong);
}

.btn-danger {
  background: transparent;
  color: var(--bad);
  border-color: var(--bad);
}

.btn-small {
  font-size: var(--t-xs);
  padding: var(--s-3xs) var(--s-sm);
}

/* The posting's identity block: title, organisation and the facts, held
   together by small gaps and set off from the sections below by a region
   step. The padding-bottom that used to sit here was left over from a rule
   that no longer exists, and only made the margin an unpickable number. */
.detail-header {
  margin-bottom: var(--s-xl);
}

.detail-title {
  font-size: var(--t-lg);
  margin-bottom: var(--s-3xs);
}

.detail-org {
  color: var(--ink-muted);
  font-size: var(--t-md);
  line-height: 1.5;
}

.detail-facts {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
  gap: var(--s-md) var(--s-lg);
  margin-top: var(--s-lg);
  font-size: var(--t-sm);
}

.detail-facts dt {
  color: var(--ink-muted);
  font-size: var(--t-2xs);
  line-height: 1.4;
  text-transform: uppercase;
  letter-spacing: var(--tracking-label);
}

.detail-facts dd {
  margin: var(--s-3xs) 0 0;
  font-family: var(--mono);
}

.callout {
  border: 1px solid var(--bad);
  color: var(--bad);
  background: color-mix(in srgb, var(--bad) 8%, var(--surface));
  padding: var(--s-sm) var(--s-md);
  margin: 0 0 var(--s-xl);
  border-radius: 4px;
  font-weight: 600;
}

.callout::before {
  content: "! ";
}

/* The posting's own text. It reaches here as plain paragraphs with every
   tag already removed (internal/ui/description.go), so nothing in this
   block styles source markup - there is none to style.

   It carries no top margin: the section title above it already set the gap
   between the label and the thing it labels, and a margin here would have
   put the heading as far from its own text as from the section before it.
   The measure is 66ch, inside the 45-75 a reader's eye can track without
   losing the line, rather than the page's 1280px. */
.detail-description {
  margin: 0 0 var(--s-xl);
  max-width: 66ch;
}

.detail-description p {
  margin: 0 0 var(--s-md);
  line-height: 1.65;
}

.detail-description p:last-child {
  margin-bottom: 0;
}

.quality-block {
  border: 1px solid var(--rule);
  padding: var(--s-lg);
  margin-bottom: var(--s-xl);
}

.quality-score {
  font-family: var(--mono);
  font-size: var(--t-lg);
  font-weight: 700;
}

/* The second score inside one block: a block step from the list above it,
   which is less than the region step between the block and the next
   section. */
.quality-score-spaced {
  margin-top: var(--s-lg);
}

.signal-list {
  list-style: none;
  margin: var(--s-sm) 0 0;
  padding: 0;
}

.signal-list li {
  display: flex;
  gap: var(--s-xs);
  padding: var(--s-2xs) 0;
  font-size: var(--t-sm);
}

.signal-glyph {
  font-family: var(--mono);
  width: 1.2em;
  flex-shrink: 0;
}

.signal-detail {
  color: var(--ink-muted);
  font-size: var(--t-xs);
  line-height: 1.6;
  display: block;
}

/* The ledger: the timeline, this product's signature element. A
   monospace gutter of sequence numbers and a connecting rule down the
   left, echoing a log a reader could tail with jq. */
.ledger {
  list-style: none;
  margin: 0;
  padding: 0;
  border-left: 2px solid var(--rule-strong);
}

.ledger-entry {
  position: relative;
  padding: 0 0 var(--s-lg) var(--s-lg);
}

.ledger-entry:last-child {
  padding-bottom: 0;
}

/* Sized and offset to straddle the ledger's 2px spine: left is half the
   marker's own width plus the border, not a spacing step. */
.ledger-marker {
  position: absolute;
  left: -0.72rem;
  top: 0.1rem;
  width: 1.3rem;
  height: 1.3rem;
  border-radius: 50%;
  background: var(--bg);
  border: 2px solid currentColor;
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: var(--mono);
  font-size: var(--t-3xs);
}

.ledger-seq {
  font-family: var(--mono);
  color: var(--ink-faint);
  font-size: var(--t-2xs);
}

.ledger-when {
  font-family: var(--mono);
  color: var(--ink-muted);
  font-size: var(--t-xs);
}

.ledger-label {
  font-weight: 650;
  margin-right: var(--s-xs);
}

.ledger-detail {
  color: var(--ink-muted);
  font-size: var(--t-sm);
  line-height: 1.55;
  margin-top: var(--s-3xs);
}

.field-diff {
  list-style: none;
  margin: var(--s-xs) 0 0;
  padding: 0;
  font-size: var(--t-xs);
  line-height: 1.6;
}

.field-diff li {
  padding: var(--s-3xs) 0;
}

.field-diff-label {
  color: var(--ink-faint);
  font-family: var(--mono);
  font-size: var(--t-2xs);
  text-transform: uppercase;
}

.diff-before {
  text-decoration: line-through;
  color: var(--ink-faint);
}

.diff-arrow {
  color: var(--ink-faint);
  margin: 0 0.35em;
}

/* Same shape as .list/.row above: one rule under the header row marks the
   column-names/data boundary, and a faint zebra tint on the body rows does
   the scanning work a border on every <tr> used to. */
table.sources {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--t-sm);
}

/* Cells hold up to three stacked lines, so they get a block step rather
   than the list row's tighter one - the same reason .row is denser: that
   one holds exactly one line. */
table.sources th,
table.sources td {
  text-align: left;
  padding: var(--s-sm);
  vertical-align: top;
}

table.sources thead tr {
  border-bottom: 1px solid var(--rule-strong);
}

table.sources tbody tr:nth-child(even) {
  background: color-mix(in srgb, var(--ink) 3%, transparent);
}

table.sources th {
  color: var(--ink-muted);
  font-size: var(--t-2xs);
  text-transform: uppercase;
  letter-spacing: var(--tracking-label);
  font-weight: 600;
}

table.sources td.mono {
  font-family: var(--mono);
  font-size: var(--t-xs);
}

.table-scroll {
  overflow-x: auto;
}

.login-form {
  /* One field wide, in the same units the field itself is measured in. */
  max-width: 26rem;
  margin: var(--s-2xl) auto;
  border: 1px solid var(--rule);
  padding: var(--s-xl);
  background: var(--surface);
}

.login-form h1 {
  font-size: var(--t-lg);
  margin-bottom: var(--s-lg);
}

.login-form .field {
  margin-bottom: var(--s-lg);
}

.login-form input[type="password"] {
  width: 100%;
  padding: var(--s-xs);
  border: 1px solid var(--rule-strong);
  border-radius: 3px;
  background: var(--bg);
  color: var(--ink);
  font: inherit;
}

.form-error {
  color: var(--bad);
  font-size: var(--t-sm);
  margin-bottom: var(--s-md);
}

.form-message {
  color: var(--good);
  font-size: var(--t-sm);
  margin-bottom: var(--s-md);
}

.inline-form {
  margin-bottom: var(--s-xl);
}

.admin-source-row {
  border: 1px solid var(--rule);
  padding: var(--s-md);
  margin-bottom: var(--s-sm);
}

.admin-source-row form {
  display: inline-flex;
  align-items: center;
  gap: var(--s-xs);
  flex-wrap: wrap;
  margin-top: var(--s-sm);
}

.admin-source-row input[type="number"] {
  /* Five digits of a poll interval in seconds, plus the spinner. */
  width: 6rem;
  padding: var(--s-3xs) var(--s-2xs);
  border: 1px solid var(--rule-strong);
  border-radius: 3px;
  background: var(--bg);
  color: var(--ink);
  font: inherit;
  font-family: var(--mono);
}

/* No top margin: the section title above it already set that gap. */
.admin-create-form {
  border: 1px solid var(--rule);
  padding: var(--s-lg);
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
  gap: var(--s-md) var(--s-lg);
}

/* Three lines centred in an otherwise empty page: the code, the sentence,
   the way out. They are one group, so they sit close to each other and far
   from everything else. */
.error-page {
  max-width: 46ch;
  margin: var(--s-2xl) auto;
  text-align: center;
}

.error-page h1 {
  margin-bottom: var(--s-md);
}

.error-page .lede {
  margin-left: auto;
  margin-right: auto;
}

.error-page .error-code {
  font-family: var(--mono);
  font-size: var(--t-2xl);
  line-height: 1;
  color: var(--ink-faint);
  margin-bottom: var(--s-xs);
}

/* Below this width seven columns stop fitting without every one of them
   ellipsising to nothing. The row becomes two lines - the posting, then
   its facts - which loses the column alignment on purpose rather than
   pretending to keep it. Source needs nothing added here: it is another
   .row-cell, so the existing .row-cell + .row-cell::before separator
   already gives it the "- " prefix every other stacked fact gets. */
@media (max-width: 860px) {
  .row {
    display: flex;
    flex-wrap: wrap;
    gap: var(--s-3xs) var(--s-sm);
    padding: var(--s-sm) var(--s-xs);
  }

  .row-title {
    flex: 1 1 100%;
    white-space: normal;
  }

  .row-seen,
  .row-q {
    text-align: left;
  }

  /* The heading names the columns; with no columns left it would name
     nothing, so the facts carry their own separators instead. */
  .row-head {
    display: none;
  }

  .row-cell + .row-cell::before,
  .row-cell + .row-q::before {
    content: "- ";
    color: var(--ink-faint);
  }
}

/* On a phone the page-level steps are a larger share of the screen than
   they are of a desktop window, so the two that are page-level - the gutter
   and the top of main - step down one. Everything below region level keeps
   its value: the relationships it encodes do not change with the viewport.
   The stacked header grows a row of nav links, so its own padding steps
   down too rather than adding to a band that is now twice as tall. */
@media (max-width: 640px) {
  .site {
    padding-inline: var(--s-md);
  }

  main {
    padding: var(--s-xl) 0 var(--s-2xl);
  }

  .site-header .site {
    flex-direction: column;
    align-items: flex-start;
    gap: var(--s-xs);
    padding-top: var(--s-sm);
    padding-bottom: var(--s-sm);
  }
}
