/* ===================================================================
   Public-Programs — the index as an instrument.

   Pure black ground with three flat elevation steps above it, 1px rules, and
   one red accent that appears only where the reader can act. Green and amber
   are not brand colours: green means "this reaches you", amber means
   "read before relying on it", grey means neither.

   No glow, no gradient, no glass. The table is the product and the
   backdrop stays out of its way.
   =================================================================== */

:root {
    color-scheme: dark;

    --bg: oklch(0 0 0);
    --surface: oklch(.19 .008 25);
    --raised: oklch(.225 .012 25);
    --hover: oklch(.255 .015 25);

    --ink: oklch(.97 .005 25);
    --muted: oklch(.72 .015 25);
    /* .64 rather than .58: the quietest ink still has to clear 4.5:1 on the
       brightest surface it ever lands on, which is a hovered row, not the
       ground. Measured 3.67:1 at .58 there, 4.68:1 at .64. */
    --faint: oklch(.64 .015 25);

    --accent: oklch(.637 .237 25.3);
    --accent-hover: oklch(.69 .22 25.3);
    --focus: oklch(.74 .18 25);

    --border: oklch(1 0 0/.11);
    --border-strong: oklch(1 0 0/.18);

    --positive: oklch(.78 .12 155);
    --positive-dim: oklch(.78 .12 155/.10);
    --positive-edge: oklch(.78 .12 155/.22);
    --warn: oklch(.8 .14 80);
    --warn-dim: oklch(.8 .14 80/.10);
    --warn-edge: oklch(.8 .14 80/.24);
    --danger: oklch(.66 .19 25);

    --mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
    --sans: system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;

    --sp-1: 0.25rem;
    --sp-2: 0.5rem;
    --sp-3: 0.75rem;
    --sp-4: 1rem;
    --sp-5: 1.5rem;
    --sp-6: 2rem;
    --sp-7: 3rem;

    --radius: 8px;
    --radius-sm: 6px;
    --radius-xs: 4px;
    --container: 1400px;
    --rail: 244px;
}

* { box-sizing: border-box; }

body {
    margin: 0;
    min-width: 320px;
    min-height: 100vh;
    min-height: 100dvh;
    display: flex;
    flex-direction: column;
    background: var(--bg);
    color: var(--ink);
    font-family: var(--sans);
    font-size: 14px;
    line-height: 1.55;
    font-synthesis: none;
    -webkit-font-smoothing: antialiased;
}

main {
    flex: 1 0 auto;
    width: 100%;
    max-width: var(--container);
    margin: 0 auto;
    padding: 0 var(--sp-6);
}

button, input, select, textarea { font: inherit; color: inherit; }
button, select { cursor: pointer; }
button { background: none; border: 0; }
button:disabled { cursor: default; opacity: .3; }

button, a, input, select {
    transition: background-color .16s ease, color .16s ease, border-color .16s ease;
}

a { color: var(--accent-hover); text-decoration: none; }
a:hover { color: var(--accent); text-decoration: underline; text-underline-offset: 3px; }

/* Browser surfaces. These ship with defaults that belong to no design system,
   and they are the cheapest signal that a page was built rather than assembled. */
:focus-visible { outline: 2px solid var(--focus); outline-offset: 3px; border-radius: 2px; }
::selection { background: oklch(.637 .237 25.3/.3); color: var(--ink); }
input, textarea { caret-color: var(--accent); }
* { scrollbar-color: var(--border-strong) transparent; scrollbar-width: thin; }
::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: var(--border-strong); border-radius: 99px; border: 3px solid var(--bg); }
::-webkit-scrollbar-thumb:hover { background: var(--faint); }

[hidden] { display: none !important; }

h1, h2, h3 { text-wrap: balance; }
p { text-wrap: pretty; }
code { font-family: var(--mono); font-size: 0.9em; }

/* Icons are drawn, at one stroke weight, rather than borrowed from the
   character set: an arrow glyph inherits the text face's own idea of weight
   and never matches the one beside it. */
.icon {
    width: 16px;
    height: 16px;
    flex: none;
    fill: none;
    stroke: currentColor;
    stroke-width: 1.6;
    stroke-linecap: round;
    stroke-linejoin: round;
}

.sr-only {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
    border: 0;
}

.skip-link {
    position: absolute;
    left: -9999px;
    top: 0;
    z-index: 100;
    padding: var(--sp-2) var(--sp-4);
    background: var(--ink);
    color: var(--bg);
    font-weight: 600;
    border-radius: 0 0 var(--radius-sm) 0;
}

.skip-link:hover { color: var(--bg); }
.skip-link:focus { left: 0; }


/* ========================== Top bar ================================ */

.topbar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--sp-5);
    height: 64px;
    /* Full-bleed rule, but the brand lands on the content column's own left
       edge rather than 20px inside it. `100%` rather than `100vw` so a visible
       scrollbar does not push the bar wider than the page. */
    padding-inline: max(var(--sp-6), calc((100% - var(--container)) / 2 + var(--sp-6)));
    border-bottom: 1px solid var(--border);
}

.brand {
    display: flex;
    align-items: center;
    gap: var(--sp-3);
    color: var(--ink);
    font-size: 15px;
    font-weight: 600;
    letter-spacing: -.02em;
}

.brand:hover { color: var(--ink); text-decoration: none; }
.brand .logo { flex: none; }
/* The bar is a fixed 64px, so the name may not wrap into a second line. */
.brand-name { white-space: nowrap; }

.site-nav { display: flex; align-items: center; gap: var(--sp-1); }

.site-nav a {
    padding: var(--sp-2) var(--sp-3);
    border-radius: var(--radius-xs);
    border-bottom: 2px solid transparent;
    color: var(--muted);
    font-size: 11px;
    text-transform: uppercase;
    letter-spacing: .12em;
}

.site-nav a:hover,
.site-nav a:focus-visible { color: var(--ink); background: var(--raised); text-decoration: none; }

.site-nav a[aria-current="page"] {
    color: var(--ink);
    background: none;
    border-bottom-color: var(--accent);
    font-weight: 600;
}


/* ======================== The opening ==============================
   The pinned design opens on a statement and a copy of the dataset, then
   rules off into the working surface. The headline is the one place on the
   site with a display voice; everything below it is 11-14px.
   =================================================================== */

.intro {
    padding: var(--sp-7) 0 var(--sp-6);
}

.intro-text { max-width: 640px; min-width: 0; }

/* Two clauses of one sentence at two weights of the same voice, not a
   heading over a subheading — so the second line takes the muted tone and
   the same size rather than a smaller one. */
.intro h1 {
    margin: 0;
    font-size: clamp(34px, 5vw, 52px);
    font-weight: 680;
    line-height: 1.08;
    letter-spacing: -.035em;
}

.intro h1 span { color: var(--muted); }

.intro-copy {
    max-width: 56ch;
    margin: var(--sp-5) 0 0;
    color: var(--muted);
    font-size: 14px;
    line-height: 1.65;
}

/* The rule between the statement and the working surface. */

/* The crawl really does run on a schedule, so a live indicator is accurate
   rather than decorative. It is the only motion on the page. */
.observation { display: inline-flex; align-items: center; gap: 0.45rem; font-family: var(--mono); font-size: 11px; }

.live-dot {
    position: relative;
    flex: none;
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: var(--positive);
}

.live-dot::after {
    content: '';
    position: absolute;
    inset: -3px;
    border-radius: 50%;
    border: 1px solid var(--positive);
    opacity: 0;
    animation: livePulse 2.6s ease-out infinite;
}

@keyframes livePulse {
    0%   { transform: scale(.6); opacity: .8; }
    70%  { transform: scale(1.9); opacity: 0; }
    100% { transform: scale(1.9); opacity: 0; }
}

/* ========================= Workspace =============================== */

.workspace {
    display: block;
    scroll-margin-top: var(--sp-4);
}

/* The filters sit above the results, folded away. Open, they are a full-width
   grid of controls; closed, they are one line — so the table keeps the width
   and the height it had while the controls stay one click from the top. */


.rail-panel { border: 0; }

/* The rule line: the one control on the left, the one fact on the right, and
   no box around either. The rules above and below belong to the page, not to
   the toggle. */
.rail-summary {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: var(--sp-2) var(--sp-4);
    padding: var(--sp-3) 0;
    border-block: 1px solid var(--border);
    color: var(--muted);
    cursor: pointer;
    font-size: 12px;
    list-style: none;
}

.summary-label { display: inline-flex; align-items: center; gap: var(--sp-2); }

.rail-summary::-webkit-details-marker { display: none; }
.rail-summary:hover { color: var(--ink); }
.rail-panel[open] .rail-summary { color: var(--ink); }
.rail-summary .chevron { width: 13px; height: 13px; transition: transform .18s ease; }
.rail-panel[open] .rail-summary .chevron { transform: rotate(180deg); }

.rail-head {
    display: flex;
    align-items: center;
    /* Only Reset survives here — the summary names the panel — so it sits at
       the left edge under the toggle rather than orphaned across the page. */
    justify-content: flex-start;
    gap: var(--sp-3);
    margin-bottom: var(--sp-2);
}

/* The summary already names the panel. */
.rail-head h2 { display: none; }

/* The controls lay out across the width instead of stacking down a column. */
/* Scoped to the open state on purpose. An author `display` on a direct child
   of <details> overrides the collapse the element does natively, so an
   unconditional `display: grid` here left the whole panel rendered with
   `open` false — measured at 430px tall while the disclosure said closed. */
.rail-panel[open] > .rail-body {
    display: grid;
    padding: var(--sp-4) 0 var(--sp-5);
    grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
    gap: var(--sp-4) var(--sp-5);
    align-items: start;
}

.rail-body > .rail-head,
.rail-body > .rail-note { grid-column: 1 / -1; }

/* The hosting group carries three labelled rows with counts, so it needs
   about twice the width of a single select. */
.rail-body > fieldset.rail-group { grid-column: span 2; }

.rail-head h2 { margin: 0; font-size: 13px; font-weight: 600; }

.text-button {
    padding: var(--sp-1) var(--sp-2);
    margin: calc(var(--sp-1) * -1) calc(var(--sp-2) * -1);
    border-radius: var(--radius-xs);
    color: var(--muted);
    font-size: 11px;
}

.text-button:hover { color: var(--ink); background: var(--raised); }

.rail-group { margin: 0; padding: 0; border: 0; min-width: 0; }

.rail-group legend,
.rail-group > label {
    display: block;
    margin-bottom: var(--sp-2);
    padding: 0;
    color: var(--muted);
    font-size: 11px;
    font-weight: 600;
}

.rail-group select { width: 100%; }

/* An armed filter is one unambiguous physical change, not a tint: the row
   fills, its label goes to full ink, and the count comes forward. */
.hosting-options { display: flex; flex-direction: column; gap: 2px; }

.hosting-options label {
    display: flex;
    align-items: center;
    gap: var(--sp-3);
    padding: 0.5rem;
    border-radius: var(--radius-sm);
    cursor: pointer;
    font-size: 12px;
    color: var(--muted);
}

.hosting-options label:hover { background: var(--raised); }
.hosting-options label:has(:checked) { background: var(--raised); color: var(--ink); }
.hosting-options label:has(:focus-visible) { outline: 2px solid var(--focus); outline-offset: 2px; }

input[type="radio"], input[type="checkbox"] {
    accent-color: var(--accent);
    margin: 0;
    width: 13px;
    height: 13px;
    flex: none;
}

/* Named apart from the results `.count`. Both are counts and both were once
   called one, so the results figure inherited the rail's size and colour, and
   the mobile rule that hides the rail's counts hid the most important number
   on the page along with them. */
.rail-count {
    margin-left: auto;
    font-family: var(--mono);
    font-variant-numeric: tabular-nums;
    font-size: 11px;
    color: var(--faint);
}

.hosting-options label:has(:checked) .rail-count { color: var(--muted); }

/* The one thing on this page that is not a control: the sentence the whole
   dataset is qualified by, kept in the rail where the filtering happens. */
.rail-note { margin-top: var(--sp-2); padding-top: var(--sp-4); border-top: 1px solid var(--border); }
.rail-note h3 { margin: 0 0 var(--sp-2); font-size: 12px; font-weight: 600; }
.rail-note p { max-width: 78ch; margin: 0 0 var(--sp-3); color: var(--muted); font-size: 12px; line-height: 1.6; }
.rail-note a { font-size: 11px; }


/* ==================== Search, toolbar, chips ======================= */

.results { min-width: 0; }

.search-box {
    display: flex;
    align-items: center;
    gap: var(--sp-3);
    height: 46px;
    padding: 0 var(--sp-4);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    background: var(--surface);
}

.search-box:focus-within { border-color: var(--focus); }
.search-box .icon { width: 18px; height: 18px; color: var(--muted); }

.search-box input {
    width: 100%;
    height: 100%;
    min-width: 0;
    border: 0;
    background: none;
    outline: none;
    font-size: 13px;
}

.search-box input::placeholder { color: var(--muted); opacity: 1; }

kbd {
    flex: none;
    padding: 2px 6px;
    border: 1px solid var(--border);
    border-radius: var(--radius-xs);
    color: var(--muted);
    font-family: var(--mono);
    font-size: 11px;
}

input, select, textarea {
    padding: 0.5rem 0.6rem;
    background: var(--raised);
    color: var(--ink);
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    font-size: 12px;
}

select { padding-right: 1.7rem; }
input:hover, select:hover { border-color: var(--border-strong); }

/* The site's focus standard is a 2px ring; these controls had a 1px shadow,
   half that weight, and it fired on mouse as well as keyboard. The global
   :focus-visible rule draws the ring — this only tightens its offset and keeps
   the border in step. */
input:focus-visible, select:focus-visible, textarea:focus-visible {
    outline-offset: 1px;
    border-color: var(--focus);
}

.results-toolbar {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: var(--sp-3);
    padding: var(--sp-5) 0 var(--sp-3);
}

/* Scale maps to intensity: the number of programs a visitor just narrowed to
   is the most important thing on the page after the rows themselves. */
.count {
    margin: 0;
    color: var(--muted);
    font-size: 13px;
}

.count b {
    font-family: var(--mono);
    font-variant-numeric: tabular-nums;
    font-size: 19px;
    font-weight: 650;
    letter-spacing: -.02em;
    color: var(--ink);
}

.sort-label {
    display: inline-flex;
    align-items: center;
    gap: var(--sp-2);
    color: var(--muted);
    font-size: 11px;
}

.sort-label select { background: none; border-color: transparent; }
.sort-label select:hover { border-color: var(--border); background: var(--raised); }

.chips { display: flex; flex-wrap: wrap; gap: var(--sp-2); margin-bottom: var(--sp-3); }

.chip {
    display: inline-flex;
    align-items: center;
    gap: var(--sp-2);
    padding: 0.3rem 0.55rem;
    border: 1px solid var(--border);
    border-radius: var(--radius-xs);
    background: var(--raised);
    color: var(--ink);
    font-size: 11px;
}

.chip span { color: var(--muted); }
.chip:hover { border-color: var(--accent); background: var(--hover); }
.chip:hover span { color: var(--accent); }


/* =========================== The table ============================= */

.table-wrap {
    border: 1px solid var(--border);
    border-radius: var(--radius);
    overflow: hidden;
}

table {
    width: 100%;
    border-collapse: collapse;
    text-align: left;
    table-layout: fixed;
}

.c-program { width: 46%; }
.c-reward  { width: 16%; }
.c-harbour { width: 16%; }
.c-status  { width: 16%; }
.c-open    { width: 6%; }

thead { background: var(--surface); }

th {
    padding: 0.75rem var(--sp-4);
    border-bottom: 1px solid var(--border);
    color: var(--muted);
    font-size: 11px;
    font-weight: 500;
    text-transform: uppercase;
    letter-spacing: .07em;
}

td {
    padding: 0.6rem var(--sp-4);
    border-bottom: 1px solid var(--border);
    font-size: 12px;
    vertical-align: middle;
    overflow: hidden;
    text-overflow: ellipsis;
}

tbody tr { transition: background-color .16s ease; }
tbody tr:hover { background: var(--surface); }
tbody tr:last-child td { border-bottom: 0; }

td.empty {
    padding: var(--sp-7) var(--sp-4);
    text-align: center;
    color: var(--muted);
    font-size: 13px;
}

.program { display: flex; align-items: center; gap: var(--sp-3); min-width: 0; }

/* Holds the drawn mark. Quiet at rest and a step brighter on the row you are
   pointing at, so it reads as part of the row rather than as chrome. */
.avatar {
    display: grid;
    place-items: center;
    flex: none;
    width: 30px;
    height: 30px;
    padding: 5px;
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    background: var(--raised);
    color: var(--faint);
}

.mark {
    width: 100%;
    height: 100%;
    fill: none;
    stroke: currentColor;
    stroke-width: 1.6;
    stroke-linecap: round;
}
tbody tr:hover .avatar { color: var(--muted); border-color: var(--border-strong); }

.program-info { min-width: 0; }

/* The contact that matched the query, when the domain did not. */
.hit { color: var(--ink); font-family: var(--mono); }

.domain {
    display: block;
    max-width: 100%;
    padding: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    text-align: left;
    color: var(--ink);
    font-family: var(--mono);
    font-size: 13px;
    font-weight: 550;
}

.domain:hover { color: var(--accent-hover); }

.program-meta {
    display: block;
    margin-top: 2px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    color: var(--muted);
    font-size: 11px;
}

/* Reward is the one verdict that keeps a chip: it is what the reader is
   scanning for, and three chips in a row would compete with each other. */
.pill {
    display: inline-flex;
    align-items: center;
    padding: 0.18rem 0.45rem;
    border: 1px solid transparent;
    border-radius: var(--radius-xs);
    background: var(--raised);
    color: var(--muted);
    font-family: var(--mono);
    font-size: 11px;
    white-space: nowrap;
}

.pill.pay  { color: var(--positive); background: var(--positive-dim); border-color: var(--positive-edge); }
.pill.swag { color: var(--warn); background: var(--warn-dim); border-color: var(--warn-edge); }
.pill.rec  { color: var(--ink); }
.pill.none, .pill.unk { color: var(--muted); }
.pill.unk  { background: none; }

/* Safe harbour reads as a word, because "stated" and "not stated" are already
   distinct and a second chip beside the reward is noise. */
.verdict { display: inline-flex; align-items: center; gap: 5px; font-family: var(--mono); font-size: 11px; white-space: nowrap; }

.verdict .tick {
    width: 12px;
    height: 12px;
    flex: none;
    fill: none;
    stroke: currentColor;
    stroke-width: 2.2;
    stroke-linecap: round;
    stroke-linejoin: round;
}
.verdict.safe { color: var(--positive); }
.verdict.nosafe { color: var(--warn); }
.verdict.unk { color: var(--muted); }

/* Status carries a mark as well as a hue, so the state survives a
   monochrome display and a red-green colour deficiency alike. */
.state {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    font-family: var(--mono);
    font-size: 11px;
    color: var(--muted);
    white-space: nowrap;
}

.state::before {
    content: '';
    width: 6px;
    height: 6px;
    flex: none;
    border-radius: 50%;
    background: currentColor;
}

.state.active { color: var(--positive); }
.state.expired { color: var(--warn); }
.state.expired::before { border-radius: 0; }
.state.retired { color: var(--muted); }
.state.retired::before { border-radius: 0; height: 2px; width: 8px; }
.state[title] { cursor: help; }

/* The grounds for a status, under it: the date the file expired, or the date
   it stopped being served. Quiet — it qualifies rather than competes. */
.why {
    display: block;
    margin-top: 2px;
    color: var(--faint);
    font-family: var(--mono);
    font-variant-numeric: tabular-nums;
    font-size: 11px;
    white-space: nowrap;
}

/* A researcher said they were paid. It says "reported" and not "verified",
   because this index did not verify it; the tooltip names the source. */
.rep-badge {
    color: var(--positive);
    font-weight: 600;
    cursor: help;
}

td.open { text-align: right; padding-left: 0; }

.row-open {
    display: inline-grid;
    place-items: center;
    width: 30px;
    height: 30px;
    border-radius: var(--radius-sm);
    color: var(--faint);
}

.row-open:hover { background: var(--raised); color: var(--accent-hover); }
tbody tr:hover .row-open { color: var(--muted); }

.pager {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--sp-4);
    padding: var(--sp-4) 0 0;
    color: var(--muted);
    font-size: 11px;
    font-variant-numeric: tabular-nums;
}

.pager-controls { display: flex; align-items: center; gap: var(--sp-3); }

.pager-controls button {
    display: grid;
    place-items: center;
    width: 30px;
    height: 30px;
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    background: var(--surface);
    color: var(--muted);
}

.pager-controls button:hover:not(:disabled) { background: var(--hover); color: var(--ink); }


/* ======================== The record sheet =========================
   The one modal on the site. It earns the interruption: this is where a
   researcher decides whether a programme is worth their hours, so it
   carries every field the table has no room for and states, at the point
   of decision, that a listing is not permission.
   =================================================================== */

dialog {
    width: min(620px, calc(100% - 2rem));
    max-height: 86vh;
    padding: var(--sp-5);
    border: 1px solid var(--border-strong);
    border-radius: 12px;
    background: var(--surface);
    color: var(--ink);
    overflow: auto;
}

dialog::backdrop { background: oklch(.1 0 0/.72); }

.record-top {
    display: flex;
    align-items: center;
    justify-content: space-between;
    color: var(--muted);
    font-size: 11px;
    text-transform: uppercase;
    letter-spacing: .1em;
}

.record-top button {
    display: grid;
    place-items: center;
    width: 30px;
    height: 30px;
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    color: var(--muted);
}

.record-top button:hover { background: var(--raised); color: var(--ink); }

dialog h2 {
    margin: var(--sp-5) 0 var(--sp-1);
    font-family: var(--mono);
    font-size: 22px;
    font-weight: 600;
    letter-spacing: -.02em;
    overflow-wrap: anywhere;
}

.record-kind { margin: 0 0 var(--sp-5); color: var(--muted); font-size: 12px; }

.record-fields {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: var(--sp-4);
    margin: 0 0 var(--sp-5);
    padding-bottom: var(--sp-5);
    border-bottom: 1px solid var(--border);
}

.record-fields dt, .record-contacts dt {
    margin-bottom: 3px;
    color: var(--muted);
    font-size: 11px;
}

.record-fields dd, .record-contacts dd {
    margin: 0;
    font-size: 13px;
    overflow-wrap: anywhere;
}

.record-contacts { margin-bottom: var(--sp-5); }
.record-contacts dd { display: grid; gap: var(--sp-1); font-family: var(--mono); font-size: 12px; }

.record-why { margin: 0 0 var(--sp-4); color: var(--muted); font-size: 12px; line-height: 1.6; }

.record-warning {
    margin: 0 0 var(--sp-5);
    padding: var(--sp-3) var(--sp-4);
    border: 1px solid var(--warn-edge);
    border-radius: var(--radius-sm);
    background: var(--warn-dim);
    color: var(--ink);
    font-size: 12px;
    line-height: 1.6;
}

.record-actions { display: flex; flex-wrap: wrap; gap: var(--sp-3); }

.action {
    display: inline-flex;
    align-items: center;
    gap: var(--sp-2);
    padding: 0.55rem 0.9rem;
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    background: var(--raised);
    color: var(--ink);
    font-size: 12px;
}

.action:hover { background: var(--hover); border-color: var(--border-strong); color: var(--ink); text-decoration: none; }
.action .icon { width: 14px; height: 14px; color: var(--muted); }

.action.primary { background: var(--ink); border-color: var(--ink); color: var(--bg); font-weight: 600; }
.action.primary:hover { background: var(--muted); border-color: var(--muted); color: var(--bg); }
.action.primary .icon { color: var(--bg); }

.dead { color: var(--muted); text-decoration: line-through; text-decoration-color: var(--danger); cursor: help; }
.none-mark { color: var(--muted); }


/* ========================= Prose blocks ============================ */

/* What the columns mean, laid out on the page's own structure: the full
   content width, the same left edge as the table and the rule above it, and
   the same responsive column grid the filter panel uses. A single 70ch column
   centred under a 1,336px table read as an unrelated block; this reads as the
   table's own key. */
.notes {
    margin: var(--sp-7) 0 0;
    padding-top: var(--sp-5);
    border-top: 1px solid var(--border);
    color: var(--muted);
    font-size: 13px;
}

.notes h2 {
    margin: 0 0 var(--sp-4);
    font-size: 12px;
    font-weight: 600;
    color: var(--muted);
    text-transform: uppercase;
    letter-spacing: .07em;
}

.notes ul {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(290px, 1fr));
    gap: var(--sp-4) var(--sp-5);
    margin: 0;
    padding: 0;
    list-style: none;
}

.notes li { margin: 0; line-height: 1.65; }
/* The terms stay inline. These notes were written as continuous sentences —
   "Self-hosted means the program names no platform" — so putting the term on
   its own line left a lowercase fragment starting the next one. */
.notes strong, .notes b { color: var(--ink); font-weight: 600; }

.lede { max-width: 62ch; margin: 0 0 var(--sp-5); color: var(--muted); font-size: 13px; line-height: 1.65; }

.card {
    max-width: 860px;
    margin: 0 0 var(--sp-4);
    padding: var(--sp-5);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    background: var(--surface);
}

.card h2 { margin: 0 0 var(--sp-3); font-size: 14px; font-weight: 650; letter-spacing: -.01em; }
.card p { margin: 0 0 var(--sp-3); color: var(--muted); font-size: 13px; }
.card p:last-child { margin-bottom: 0; }

.card code {
    padding: 0.05rem 0.32rem;
    border-radius: 3px;
    background: var(--raised);
    color: var(--ink);
}

.card pre {
    margin: 0;
    padding: var(--sp-3);
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    background: var(--bg);
    overflow-x: auto;
    font-size: 11px;
}

.card pre code { padding: 0; background: none; }

.card.promo { border-left: 1px solid var(--accent); background: oklch(.637 .237 25.3/.05); }

.defs { display: grid; grid-template-columns: max-content 1fr; gap: var(--sp-3) var(--sp-4); margin: 0; align-items: baseline; }
.defs dt { margin: 0; }
.defs dd { margin: 0; color: var(--muted); font-size: 13px; line-height: 1.6; }
.defs dd b { color: var(--ink); font-weight: 600; }

ul.tight, ol.tight { margin: 0 0 var(--sp-3); padding-left: 1.2rem; }
ul.tight li, ol.tight li { margin-bottom: var(--sp-2); color: var(--muted); font-size: 13px; }
ul.tight li:last-child, ol.tight li:last-child { margin-bottom: 0; }

.dim { color: var(--muted); font-family: var(--mono); font-size: 12px; }
.note { margin: var(--sp-3) 0 0; color: var(--muted); font-size: 12px; line-height: 1.6; }

/* Standing context at the top of the docs, not a dismissible alert. Amber
   rather than red: the absence of a triage team is what these programs are,
   not a defect. */
.expect {
    max-width: 860px;
    margin: 0 0 var(--sp-5);
    padding: var(--sp-4);
    background: var(--warn-dim);
    border: 1px solid var(--warn-edge);
    border-radius: var(--radius-sm);
    color: var(--muted);
    font-size: 13px;
    line-height: 1.65;
}

.expect strong { color: var(--ink); font-weight: 600; }
.expect a { color: var(--warn); white-space: nowrap; }


/* ===================== Docs: the field reference =================== */

.card .table-wrap { border: 0; border-radius: 0; }
.card .table-wrap + .table-wrap { margin-top: var(--sp-5); }

/* Three columns of reference prose have a min-content width of roughly 460px —
   the option lists carry unbreakable tokens like `safe_harbour` — so below
   that the table scrolls inside its own card instead of pushing the page
   sideways. Measured at 81px of document overflow at 360px before this. */
@media (max-width: 560px) {
    .card .table-wrap { overflow-x: auto; }
    .doc-table { min-width: 460px; }
}

.doc-table { table-layout: auto; font-size: 12px; }
.doc-table thead { background: none; }
.doc-table th { padding-left: 0; }
.doc-table td:first-child { padding-left: 0; }

.doc-table th,
.doc-table td {
    text-align: left;
    white-space: normal;
    vertical-align: top;
    padding: 0.6rem var(--sp-3);
}

.doc-table td:first-child { white-space: nowrap; }
.doc-table td:nth-child(2) { color: var(--muted); }

/* These were `<col style="width:22%">` attributes, which the site's own CSP
   (style-src 'self', no 'unsafe-inline') silently refused — a style attribute
   is inline style whether a script wrote it or the HTML shipped with it. */
.doc-table col.c1 { width: 22%; }
.doc-table col.c2 { width: 26%; }


/* ===================== Contact: rows and form ====================== */

.link-row {
    display: flex;
    align-items: center;
    gap: var(--sp-3);
    padding: var(--sp-3) var(--sp-2);
    border-bottom: 1px solid var(--border);
}

.link-row:last-child { border-bottom: 0; }
.link-row:hover { background: var(--raised); text-decoration: none; }
.link-row:hover .link-text { color: var(--accent-hover); }

.link-icon {
    flex: none;
    display: grid;
    place-items: center;
    width: 22px;
    height: 22px;
    color: var(--muted);
}

/* All three marks sit in the body colour. LinkedIn's tile is solid where the
   other two are outlines, but a brand blue here would be the only hex in an
   oklch system and the only colour on the site that is not red, green, amber
   or grey — which is exactly what "recognisable by the single red mark" rules
   out. */
.link-icon svg { width: 17px; height: 17px; }
.link-text { color: var(--ink); font-size: 13px; }

.pf { margin-top: var(--sp-5); display: grid; gap: var(--sp-4); }
.pf-lead { margin: var(--sp-4) 0 0; color: var(--muted); font-size: 13px; line-height: 1.65; }
.pf-lead b { color: var(--ink); }
.pf-field { display: grid; gap: 0.35rem; }

.pf-field > span { font-size: 11px; color: var(--muted); font-weight: 600; }
.pf-field > span em { font-style: normal; font-weight: 400; opacity: .7; }

.pf-field input, .pf-field textarea, .pf-field select {
    width: 100%;
    font-family: var(--mono);
    font-size: 12px;
}

.pf-field textarea { resize: vertical; line-height: 1.55; }
.pf-row { display: flex; flex-wrap: wrap; gap: var(--sp-4); }
.pf-row .pf-field { flex: 1 1 12rem; }

/* Off-screen rather than display:none — some form fillers skip anything not
   rendered, and the point is that they find it. */
.pf-trap { position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden; }

.pf-actions { display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-4); }

.pf-actions button {
    padding: 0.6rem 1.4rem;
    border-radius: var(--radius-sm);
    background: var(--ink);
    color: var(--bg);
    font-size: 12px;
    font-weight: 600;
}

.pf-actions button:hover:not(:disabled) { background: var(--muted); }

.pf-status { margin: 0; font-size: 12px; color: var(--muted); }
.pf-status.is-good { color: var(--positive); }
.pf-status.is-bad  { color: var(--danger); }
.pf-derived { margin: calc(var(--sp-2) * -1) 0 0; color: var(--muted); font-size: 11px; line-height: 1.55; }


/* ===================== Stats: figures and bars ===================== */

.kpi-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
    gap: var(--sp-3);
    margin-bottom: var(--sp-6);
}

.kpi {
    display: flex;
    flex-direction: column;
    gap: 0.2rem;
    padding: var(--sp-4);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    background: var(--surface);
}

.kpi-num {
    font-family: var(--mono);
    font-variant-numeric: tabular-nums;
    font-size: 28px;
    font-weight: 650;
    line-height: 1.05;
    letter-spacing: -.03em;
}

.kpi-label { font-size: 11px; color: var(--muted); }
.kpi.pay .kpi-num  { color: var(--positive); }
.kpi.swag .kpi-num { color: var(--warn); }
.kpi.bad .kpi-num  { color: var(--warn); }

.chart-block { max-width: var(--container); margin-bottom: var(--sp-6); }
.chart-block h2 { margin: 0 0 var(--sp-3); font-size: 13px; font-weight: 600; }
.chart-note { margin: 0 0 var(--sp-3); color: var(--muted); font-size: 12px; line-height: 1.6; }
.chart { display: grid; gap: var(--sp-1); }

.bar-row {
    display: grid;
    grid-template-columns: 9.5rem 1fr 7.5rem;
    align-items: center;
    gap: var(--sp-3);
    font-family: var(--mono);
    font-variant-numeric: tabular-nums;
    font-size: 11px;
}

.bar-label { color: var(--muted); text-align: right; }
.bar-track { height: 16px; border-radius: 3px; background: var(--raised); overflow: hidden; }

.bar-fill {
    display: block;
    height: 100%;
    border-radius: 3px;
    /* The neutral is the default; the semantic classes below carry the meaning.
       The accent never paints a quantity — it marks what a reader can act on. */
    background: oklch(.72 .015 25/.35);
}

.bar-fill.pay  { background: var(--positive); }
.bar-fill.swag { background: var(--warn); }
.bar-fill.bad  { background: var(--warn); }
.bar-fill.rec  { background: oklch(.72 .015 25/.55); }
.bar-fill.none { background: oklch(.72 .015 25/.35); }
.bar-fill.unk  { background: oklch(.72 .015 25/.2); }
.bar-value { color: var(--ink); }


/* ========================== Footer ================================= */

footer {
    flex: none;
    width: 100%;
    max-width: var(--container);
    margin: var(--sp-7) auto 0;
    padding: var(--sp-4) var(--sp-6) var(--sp-6);
    border-top: 1px solid var(--border);
    color: var(--muted);
    font-size: 12px;
}

footer p { margin: 0 0 var(--sp-1); }
footer p:last-child { margin-bottom: 0; }


/* ===================== Content-page headers ========================
   Stats, Docs and Contact keep a page title where the index has its
   masthead; they use the same rules, one step quieter.
   =================================================================== */

/* Docs and Contact are read rather than scanned, so their column is capped
   for measure and centred in the page. That deliberately gives up the left
   edge the topbar and the index share — these two pages are prose, and a
   centred measure is the point of them. */
body:has(.card) .card,
body:has(.card) .expect,
body:has(.card) .lede,
body:has(.card) .page-head {
    max-width: 900px;
    margin-inline: auto;
}

body:has(.card) .page-head { text-align: center; }
body:has(.card) .page-head .tagline { max-width: 62ch; margin-inline: auto; }

.page-head { padding: var(--sp-5) 0 var(--sp-4); }

.page-head h1 {
    margin: 0;
    font-size: 30px;
    font-weight: 620;
    letter-spacing: -.032em;
    line-height: 1.1;
}

.page-head .tagline { margin: var(--sp-2) 0 0; color: var(--muted); font-size: 13px; }


/* ============================ 404 ================================== */

.page-404 { display: flex; flex-direction: column; min-height: 100vh; }

.page-404 #main {
    flex: 1;
    display: grid;
    place-content: center;
    width: 100%;
    padding: var(--sp-5);
}

.nf { max-width: 560px; margin-inline: auto; text-align: center; }
.nf-scope { position: relative; width: 168px; height: 168px; margin: 0 auto var(--sp-6); }

.nf-ring, .nf-ring-2, .nf-sweep, .nf-cross, .nf-dot { position: absolute; inset: 0; border-radius: 50%; }

.nf-ring { border: 2px solid var(--border-strong); }
.nf-ring-2 { inset: 34px; border: 2px solid oklch(.637 .237 25.3/.35); animation: nfPulse 3.2s ease-out infinite; }

.nf-sweep {
    background: conic-gradient(from 0deg,
        oklch(.637 .237 25.3/.55) 0deg,
        oklch(.637 .237 25.3/.16) 42deg,
        transparent 92deg,
        transparent 360deg);
    animation: nfSweep 4s linear infinite;
}

.nf-cross {
    background:
        linear-gradient(to right, transparent calc(50% - 1px), oklch(1 0 0/.22) calc(50% - 1px),
            oklch(1 0 0/.22) calc(50% + 1px), transparent calc(50% + 1px)),
        linear-gradient(to bottom, transparent calc(50% - 1px), oklch(1 0 0/.22) calc(50% - 1px),
            oklch(1 0 0/.22) calc(50% + 1px), transparent calc(50% + 1px));
    -webkit-mask: radial-gradient(circle, #000 0 84px, transparent 84px);
    mask: radial-gradient(circle, #000 0 84px, transparent 84px);
}

.nf-dot { inset: 78px; background: var(--muted); opacity: .55; animation: nfBlink 2.1s steps(1, end) infinite; }

@keyframes nfSweep { to { transform: rotate(360deg); } }
@keyframes nfPulse {
    0%   { transform: scale(1); opacity: .9; }
    70%  { transform: scale(1.28); opacity: 0; }
    100% { transform: scale(1.28); opacity: 0; }
}
@keyframes nfBlink { 0%, 60% { opacity: .55; } 61%, 100% { opacity: .12; } }

.nf-code {
    margin: 0;
    font-family: var(--mono);
    font-size: clamp(3rem, 12vw, 4.4rem);
    font-weight: 700;
    line-height: 1;
    letter-spacing: .06em;
    color: var(--ink);
}

.nf-title { margin: var(--sp-3) 0; font-size: clamp(1.1rem, 4vw, 1.4rem); }
.nf-sub { margin: 0 auto var(--sp-6); max-width: 46ch; color: var(--muted); font-size: 13px; line-height: 1.7; }
.nf-actions { display: flex; flex-wrap: wrap; gap: var(--sp-3); justify-content: center; }

.nf-btn {
    padding: 0.6rem 1.05rem;
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    background: var(--surface);
    color: var(--ink);
    font-size: 12px;
}

.nf-btn:hover { background: var(--hover); border-color: var(--border-strong); text-decoration: none; color: var(--ink); }
.nf-btn-primary { background: var(--ink); border-color: var(--ink); color: var(--bg); font-weight: 600; }
.nf-btn-primary:hover { background: var(--muted); border-color: var(--muted); color: var(--bg); }


/* ========================= Responsive ============================== */

@media (max-width: 1100px) {
    :root { --rail: 210px; }
    .workspace { gap: var(--sp-5); }
    .rail { padding-right: var(--sp-4); }
    th, td { padding-inline: var(--sp-3); }
    .avatar { display: none; }
    .c-program { width: 40%; }
}

@media (max-width: 860px) {
    main, .topbar, footer { padding-inline: var(--sp-4); }

    .intro { padding: var(--sp-6) 0 var(--sp-5); }
    .intro-copy { font-size: 13px; }

    /* The four nav items and the name have to share 390px. The mark keeps the
       site identifiable once the name is too wide to sit beside them. */
    .topbar { gap: var(--sp-3); }
    .brand { font-size: 13px; }
    .site-nav a { padding-inline: var(--sp-2); letter-spacing: .08em; }

    /* Two columns of controls rather than one very long rail above the
       table the visitor actually came for. */


    .hosting-options { flex-direction: row; flex-wrap: wrap; }
    .hosting-options label { flex: 1 1 auto; }
    .rail-count { display: none; }
    .rail-note { display: none; }
}

@media (max-width: 700px) {
    .record-fields { grid-template-columns: 1fr; gap: var(--sp-3); }
}

/* Below this the bar becomes two rows rather than one that overflows.
   Measured: the brand is ~148px and the four nav items ~280px, so the row
   needs 449px and never fitted a 360-390px phone. It was the only
   document-level overflow on the site, and it was on every page. */
@media (max-width: 520px) {
    .topbar {
        height: auto;
        flex-wrap: wrap;
        gap: var(--sp-2);
        padding-block: var(--sp-3);
    }

    .site-nav {
        width: 100%;
        justify-content: space-between;
        gap: 0;
    }

    .site-nav a { padding-inline: var(--sp-2); }
}

/* The table becomes cards. Five columns in 390px gave Program 180px and every
   verdict a truncated word; `table-layout: fixed` made it look fine because it
   clipped rather than overflowed, which is why a scroll-width check missed it.
   The labels come from data-label, so there is one row template rather than
   two that can drift. */
@media (max-width: 620px) {
    #results, #results tbody, #results tr, #results td { display: block; width: auto; }
    #results thead { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); }

    .table-wrap { border: 0; border-radius: 0; overflow: visible; }

    #results tr {
        position: relative;
        margin-bottom: var(--sp-3);
        padding: var(--sp-3);
        border: 1px solid var(--border);
        border-radius: var(--radius);
        background: var(--surface);
    }

    #results tbody tr:hover { background: var(--surface); }

    #results td {
        display: flex;
        justify-content: space-between;
        align-items: baseline;
        gap: var(--sp-3);
        padding: 0.3rem 0;
        border: 0;
        overflow: visible;
        text-align: right;
    }

    #results td::before {
        content: attr(data-label);
        flex: none;
        color: var(--muted);
        font-size: 11px;
        text-transform: uppercase;
        letter-spacing: .07em;
    }

    /* The domain is the row's identity, so it leads rather than sitting in a
       labelled pair like the rest. */
    #results td.program {
        display: flex;
        align-items: center;
        margin-bottom: var(--sp-2);
        padding-bottom: var(--sp-2);
        padding-right: 34px;
        border-bottom: 1px solid var(--border);
        text-align: left;
    }

    #results td.program::before { content: none; }
    #results td.why, #results .why { text-align: right; }

    /* The whole card is the target, so the action sits at its corner rather
       than in a row of its own. */
    #results td.open {
        position: absolute;
        top: var(--sp-3);
        right: var(--sp-3);
        padding: 0;
    }

    #results td.open::before { content: none; }

    .results-toolbar { padding-top: var(--sp-4); }
    .count b { font-size: 17px; }
}

/* Tap targets, keyed on the input device rather than viewport width: an iPad
   is touch at 768px and a narrow desktop window is not touch at 500px. 44px is
   the accessibility floor. */
@media (hover: none) and (pointer: coarse) {
    .site-nav a { display: inline-flex; align-items: center; min-height: 44px; }
    .rail-group select, .search-box input, .rail-summary { min-height: 44px; }
    .hosting-options label { min-height: 44px; }
    .row-open, .pager-controls button, .record-top button { width: 44px; height: 44px; }
    .domain { padding: 0.3rem 0; }
}

@media (prefers-reduced-motion: reduce) {
    *, *::before, *::after {
        animation: none !important;
        transition: none !important;
        scroll-behavior: auto !important;
    }

    .nf-sweep { transform: rotate(35deg); }
    .live-dot::after { opacity: .5; transform: scale(1); }
}
