:root {
  --ink: #17211B;
  --ink-soft: #4C5B53;
  --muted: #8A97A3;
  --accent: #10B981;
  --accent-soft: #E7F8F1;
  /* Split from --accent-soft when dark mode arrived: the same pale tint
     cannot be both a *subtle* chip background and a *visible* focus halo
     once the page goes dark. Light mode keeps the value it always had. */
  --focus-ring: #E7F8F1;
  --accent-line: #BFEBDA;
  --warn: #B4831B;
  --card: #FFFFFF;
  --line: #E4E9EF;
  /* pill palette (complements the emerald accent) */
  --pill-level: #7C6FDE;      --pill-level-bg: #F2F0FD;  --pill-level-line: #E0DCF9;
  --pill-loc: #0E8CC3;        --pill-loc-bg: #EDF7FC;    --pill-loc-line: #D3ECF7;
  --pill-src: #64748B;        --pill-src-bg: #F3F5F8;    --pill-src-line: #E4E9EF;
  --radius: 12px;
  /* Colours below were inline until dark mode needed them switchable. Each
     name states the role rather than the shade, so the dark block can give a
     different value to the same job. */
  --bg-top: #FFFFFF;          --bg-bottom: #F7F9FC;
  --on-accent: #fff;          --accent-hover: #0DA271;
  --logo-bg: #FFFFFF;         /* employer logos assume a light backing */
  --ink-muted: #64717E;       /* 4.99:1 on white — chrome meant to be clicked */
  --ink-muted-tint: #5A6673;  /* 5.37:1, the same role on a tinted chip */
  --warn-ink: #7C3F0D;        --warn-bg: #FEF3E2;   --warn-line: #E9C08A;
  --warn-bg-soft: #FFFCF7;    --mark-bg: #FDECCF;
  --good-ink: #0B6B47;        --good-bg: #EAF6F0;   --good-line: #C7E7D8;
  /* The only red on the site, and it marks exactly one thing: a listing open
     more than a year. 6.47:1 on a card, so it clears AA at the 13px the age
     line runs at — `--warn` (#B4831B) reaches 3.4:1 there and could not. */
  --aged-ink: #B91C1C;
  --neutral-bg: #F3F5F8;
  /* Links in running prose, which is a different job from the accent.
     `--accent` is 2.54:1 on white — recorded at the Clear button below as a
     deliberate exception for one small control, and not defensible for a page
     of body text. This is the same green a shade deeper: 6.55:1 on the page's
     top and 6.21:1 on its bottom, so it clears AA on both ends of the
     gradient. Prose links are the one place where the accent has to give way
     to being readable, and having a token for it is what stops the next one
     reaching for `--accent` again. */
  --link: #0B6B47;            --link-hover: #07603F;
  --est-ink: #5C6660;         --est-bg: #F1F2F1;    --est-line: #DCDFDD;
  --flag-ink: #9A5B1A;        --flag-bg: #FDF2E4;   --flag-line: #F0D8B8;
  --fresh-ink: #8A6410;
}

/* Dark theme. One block, keyed on an attribute the inline script in _head.html
   sets before first paint — it reads the stored preference and falls back to
   `prefers-color-scheme`, so the OS setting is honoured without a second copy
   of these thirty tokens inside a media query. Two copies would drift, and the
   drift would only ever show up in the theme nobody is looking at.
   Consequence worth knowing: with JS off the site stays light, which is the
   safe direction to fail.

   Every value here is contrast-checked against the surface it lands on rather
   than picked by eye. Body text is 14.7:1 on a card, the muted register 5.3:1,
   the accent 10.9:1 — all comfortably past AA, which makes dark mode the more
   accessible of the two themes.

   Elevation runs the same way as light mode: the card sits *above* the page
   rather than below it. The brief asked for black cards on grey, which inverts
   that and leaves the card edge at 1.26:1; keeping the relationship means a
   card reads as a card without depending entirely on its border. */
html[data-theme="dark"] {
  --ink: #EDF2EF;
  --ink-soft: #B7C2BB;
  --muted: #87948D;
  /* Yellow, per the brief. Note what it forces: white on yellow is 1.5:1, so
     every filled control has to take dark text instead — that is what
     --on-accent is for, and why it is a token rather than a literal #fff. */
  --accent: #FACC15;
  --accent-soft: #2A2410;
  /* A translucent glow rather than a flat tint: on a dark ground any
     colour dark enough to work as a chip background is invisible as a
     ring, which would leave keyboard users with no focus indicator. */
  --focus-ring: rgba(250, 204, 21, .40);
  --accent-line: #4A3F14;
  --on-accent: #14180F;
  /* Filled controls lighten on hover here rather than darkening: on a dark
     ground, "brighter" is what reads as raised. */
  --accent-hover: #FFD84D;
  --warn: #E3B071;
  --card: #1E2320;
  --line: #333A36;
  --bg-top: #141715;
  --bg-bottom: #0D0F0E;
  /* Employer logos are supplied by the boards and are overwhelmingly dark
     marks on transparent or white. They keep a light backing in both themes —
     inverting this is what turns a company's logo into a black square. */
  --logo-bg: #FFFFFF;
  --ink-muted: #A6B0AA;
  --ink-muted-tint: #AEB8B2;
  --warn-ink: #F0C078;        --warn-bg: #2A2117;   --warn-line: #5A4425;
  --warn-bg-soft: #1F1B14;    --mark-bg: #4A3A16;
  --aged-ink: #F87171;        /* 5.77:1 on the dark card */
  --good-ink: #7BD9AE;        --good-bg: #12241C;   --good-line: #264535;
  --neutral-bg: #232825;
  /* Dark mode has the opposite problem: the accent is the most readable thing
     on the page here (11.8:1), so a prose link takes it rather than needing a
     quieter substitute — which also keeps links the same yellow they are in
     every other dark-theme rule. The hover lifts rather than deepens, for the
     reason `--accent-hover` does. */
  --link: #FACC15;            --link-hover: #FFD84D;
  --est-ink: #A9B2AC;         --est-bg: #1E211F;    --est-line: #333836;
  --flag-ink: #E3B071;        --flag-bg: #271E14;   --flag-line: #4E3B22;
  --fresh-ink: #E8C36A;
  --pill-level: #B3A9F5;      --pill-level-bg: #221F35;  --pill-level-line: #37325A;
  --pill-loc: #7CC7EC;        --pill-loc-bg: #12242E;    --pill-loc-line: #24455A;
  --pill-src: #A3B0C0;        --pill-src-bg: #202528;    --pill-src-line: #363D42;
}

* { box-sizing: border-box; }
body {
  margin: 0;
  min-height: 100vh;
  background: linear-gradient(180deg, var(--bg-top), var(--bg-bottom)) fixed;
  color: var(--ink);
  font: 15.5px/1.55 "Manrope", system-ui, -apple-system, "Segoe UI", sans-serif;
}
.wrap { max-width: 880px; margin: 0 auto; padding: 0 20px; }

/* Centred as a block, not just the wordmark. Centring the logo alone would
   leave a 282px mark floating over a left-aligned tagline and nav, which reads
   as a mistake rather than a choice — and the tally and footer below are
   centred too, so the whole column now shares one axis. */
.masthead { border-bottom: 1px solid var(--line); padding: 30px 0 24px;
            text-align: center; }
/* The wordmark, one SVG per theme. Its viewBox is cropped to the artwork — the
   file as supplied sat on a 1248x832 canvas with the art in the middle third,
   which made any size a guess.

   The light file still carries the opaque white background panel this comment
   used to warn about. `stillopen_logo_dark.svg` is generated from it with that
   full-canvas path *removed* — exactly the fix predicted here — plus the fills
   remapped by role: the nine near-black wordmark paths to --ink, the three
   greens to the yellow accent, and eight white inner highlights to --bg-top so
   they keep reading as holes rather than becoming white blobs. The source is
   auto-traced and has no ids or classes, so fill colour is the only handle on
   it; regenerate rather than hand-edit if the artwork ever changes. */
.logo { margin: 0; line-height: 0; }
/* Text that exists for screen readers and crawlers only. The wordmark is a
   background image, so without this the masthead <h1> would be empty. */
.vh {
  position: absolute; width: 1px; height: 1px; overflow: hidden;
  clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap;
}
/* Sized by aspect-ratio rather than a fixed height, so `max-width` still clamps
   it on a narrow phone without distorting — the same reason the old <img> set
   width and left height auto. 1044x282 is the SVG's viewBox.

   `inline-block`, not `block`, and it matters: `.masthead` centres its
   contents with `text-align: center`, which moves an inline-block but does
   nothing to a block — that would silently left-align the wordmark. This was
   previously set by a separate `.logo a` rule, which outranked this one on
   specificity and made the declaration here dead. One rule owns it now. */
.logo-mark {
  display: inline-block; width: 282px; max-width: 100%; aspect-ratio: 1044 / 282;
  background: var(--logo-light) no-repeat center / contain;
}
html[data-theme="dark"] .logo-mark { background-image: var(--logo-dark); }
.logo a:focus-visible { outline: 2px solid var(--accent); outline-offset: 4px; border-radius: 4px; }
/* Wide enough that the second sentence (58 characters) is not wrapped again by
   the column after the explicit break has already placed it. */
/* 40px above and below, so the tagline is a band of its own rather than
   something crowding the wordmark. It owns both gaps — `.mast-nav` sets no top
   margin — because two rules would collapse into one anyway and the survivor
   is not obvious from either. */
.tagline { margin: 40px auto; max-width: 62ch; color: var(--ink-soft); font-size: 15px; }

/* Legend for the card vocabulary, between the tally and the list.
   Samples reuse the real classes (.vbadge) so restyling a card restyles its
   explanation too. `.card-ats` was the other one until 2026-08-29, when the
   blue border went with the aggregator tier: it distinguished a listing from
   the employer's own board, and every listing is one now.

   It is a <details> so narrow screens can collapse it: expanded it runs ~295px
   on a 375px phone, which buried the first listing below the fold. Desktop has
   the room, so there the summary is hidden and the content forced open — see
   the min-width query below. */
.legend {
  margin: 0 0 14px;
  padding: 12px 16px;
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--radius);
}
/* --muted (#8A97A3) sits at 2.98:1 on white — fine for the incidental ages on a
   card, but too faint for chrome the reader is meant to act on. #64717E keeps
   the muted register at 4.99:1, clearing WCAG AA. The sample terms themselves
   do not use it at all: they take the bold --ink-soft that dt already carries,
   so every term in the legend reads with the same weight. */
.legend-title {
  font-size: 11.5px; font-weight: 700; letter-spacing: .07em; text-transform: uppercase;
  color: var(--ink-muted); cursor: pointer; list-style: none;
}
.legend-title::-webkit-details-marker { display: none; }
/* Own marker, so the affordance survives list-style:none on both engines. */
.legend-title::after {
  content: ""; display: inline-block; vertical-align: 1px; margin-left: 7px;
  width: 6px; height: 6px; border-right: 2px solid currentColor;
  border-bottom: 2px solid currentColor; transform: rotate(45deg) translate(-2px, -2px);
  transition: transform .15s ease;
}
.legend[open] .legend-title::after { transform: rotate(-135deg) translate(-3px, -3px); }
.legend[open] .legend-title { margin-bottom: 11px; }
.legend-title:focus-visible {
  outline: 2px solid var(--accent); outline-offset: 3px; border-radius: 4px;
}
@media (prefers-reduced-motion: reduce) { .legend-title::after { transition: none; } }
.legend-items {
  margin: 0; display: grid; gap: 11px 24px;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
}
/* Sample and explanation share one wrapping paragraph rather than stacking as
   two blocks. Stacked, the four items ran 370px on a 375px screen and pushed
   the search field to the fold with no listing visible behind it; inline the
   same content costs a third less height and reads as a sentence. */
.legend-item { font-size: 12.5px; line-height: 1.6; color: var(--ink-soft); }
.legend-item dt { display: inline; font-weight: 700; }
.legend-item dd { display: inline; margin: 0; }
.legend-item dd::before { content: " — "; color: var(--ink-muted); }
/* .vbadge carries a top margin for its position on a card; not wanted here. */
.legend .vbadge { margin-top: 0; vertical-align: -1px; }
.legend-static { margin: 0 0 6px; }

/* Desktop has the vertical room, so the legend is always open there: drop the
   summary and override the UA's hiding of a closed <details>. Both properties
   are needed — engines that predate ::details-content hide the children with
   display, newer Chrome hides them with content-visibility on that pseudo. */
@media (min-width: 640px) {
  .legend > .legend-title { display: none; }
  .legend > .legend-items { display: grid; content-visibility: visible; }
  .legend::details-content { content-visibility: visible; block-size: auto; }
}

/* Landing pages only: the job list itself has no <h1> because the masthead
   carries the site name there. */
.landing-head { margin: 22px 0 4px; text-align: center; }
.landing-h1 {
  margin: 0 0 6px; font-size: 30px; font-weight: 800; letter-spacing: -0.02em;
  color: var(--ink);
}
/* `margin: 0 auto` and not just `text-align`, because the measure is capped at
   62ch: left-aligned inside a wider box, centred text would sit off-centre by
   whatever the box has spare. The inherited `text-align` from `.landing-head`
   centres the words; this centres the column they sit in. */
.landing-intro {
  margin: 0 auto; max-width: 62ch; color: var(--ink-muted); font-size: 15px;
}
/* A second intro paragraph, on `/saved` only. `.landing-intro` has no bottom
   margin — it was written as the single line under a heading — so two of them
   run together into one block. This spaces the pair without touching the ~85
   landing pages that render the first one alone. */
.saved-limits { margin-top: 10px; line-height: 1.6; }

.filters { display: flex; gap: 10px; margin: 18px 0 18px; flex-wrap: wrap; }
.filters input[type="search"] { flex: 1 1 260px; }
.filters input, .filters select {
  font: inherit; color: var(--ink);
  background: var(--card); border: 1px solid var(--line); border-radius: var(--radius);
  padding: 10px 14px;
  transition: border-color .15s ease, box-shadow .15s ease;
}
/* `appearance: none` is what removes the native dropdown arrow the icon
   replaces. It also removes the platform's own padding and text colour, so
   those are restated above and here rather than inherited — and the right
   padding has to clear the mark, or a long option ("North America (7,283)")
   runs under it.

   Keyed on the wrapper, so a select gets the reset exactly when it gets the
   mark — the two are one change and cannot be applied to one page and not the
   other. Both containers are named rather than writing a bare
   `.filter-field select`, and that is the cascade rather than tidiness:
   `.match-controls select` further down sets the `padding` *shorthand* at the
   same specificity (0,1,1) and later in the file, so it would expand to
   `padding-right: 14px` and win — the mark would then sit on top of the option
   text with nothing erroring. Qualifying takes it to (0,2,1). */
.filters .filter-field select,
.match-controls .filter-field select {
  appearance: none; -webkit-appearance: none;
  padding-right: 38px; cursor: pointer;
}
/* The mark sits on the wrapper because a <select> renders no ::after of its
   own. Masked rather than drawn: the accent differs per theme, and an SVG
   loaded as a background-image cannot be recoloured by CSS — so the file
   supplies the *shape* through the mask and `background` supplies the colour,
   which keeps web/static/icons/Filter.svg the single copy. `pointer-events:
   none` so the mark does not swallow the click that opens the menu. */
.filter-field { position: relative; display: inline-flex; }
.filter-field::after {
  content: ""; position: absolute; right: 13px; top: 50%;
  width: 16px; height: 16px; margin-top: -8px; pointer-events: none;
  background: var(--accent);
  -webkit-mask: var(--icon-filter) center / contain no-repeat;
          mask: var(--icon-filter) center / contain no-repeat;
}
.filters input:hover, .filters select:hover { border-color: var(--accent-line); }
.filters input:focus-visible, .filters select:focus-visible {
  outline: none; border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--focus-ring);
}
/* Desktop puts the search on its own row and the three dropdowns on the next.
   The box now searches skills and the ad body, not just titles, so it takes
   phrases rather than a word — and sharing a row with three selects left it
   too narrow to read one back. The selects then split the second row evenly
   (`flex: 1 1 0`) rather than sizing to their labels, which otherwise left
   "Any level" a third the width of "Any location" and read as an accident.
   Below 640px nothing changes: the controls already stack one per row. */
/* Sized to its word rather than sharing the selects' `flex: 1 1 0`, so the
   row reads as three filters and an action rather than four filters.

   Filled to match `.pg-now`, the current-page chip — white on --accent. It was
   deliberately quiet before, on the reasoning that clearing is an escape hatch
   rather than the thing to click first; that was overruled on 2026-08-17 in
   favour of matching the pager, and the two must now be changed together or
   they stop matching.

   **Recorded rather than discovered later: white on --accent is 2.54:1**, below
   the 4.5:1 AA minimum for text this size, and this is the only control on the
   site that sits under it — the greens elsewhere are AA-checked (#64717E at
   4.99:1, which is what this button used to use). #047857 is the same hue at
   5.48:1 if it is ever revisited, and `.pg-now` would move with it. A
   deliberate call, not an oversight.

   **Which criterion that 2.54:1 is measured against changed when the word
   "Clear" became an icon.** The contents are now a graphical object, so the
   bar is 1.4.11's 3:1 rather than 1.4.3's 4.5:1 — the ratio did not move, the
   shortfall narrowed, and it is still a shortfall. Dark mode is unaffected:
   --on-accent is #14180F there, near-black on yellow. Recorded because an
   exception taken for text should not be assumed to carry over silently to a
   mark, and because #047857 above would clear this bar outright. */
.filters-clear {
  display: inline-flex; align-items: center;
  font: inherit; color: var(--on-accent); text-decoration: none;
  background: var(--accent); border: 1px solid var(--accent);
  border-radius: var(--radius); padding: 10px 14px;
  transition: background .15s ease, border-color .15s ease;
}
/* Matches the masthead marks at 18px. It is not `.nav-icon` because it takes
   its colour from the button (`--on-accent`) rather than the accent, and
   sharing the rule would mean overriding half of it back. */
.filters-clear-icon { display: block; flex: none; width: 18px; height: 18px; }
/* `.pg-now` needs no hover — it is a marker, not a control. This is a link, so
   it needs to answer the pointer; darkening the fill keeps the resting state
   pixel-identical to the chip it is matching. */
.filters-clear:hover { background: var(--accent-hover); border-color: var(--accent-hover); }
/* The old ring was `box-shadow: 0 0 0 3px var(--accent-soft)`, which read well
   as a halo around a white button and would all but disappear around a filled
   green one — near-white on white. Same outline pattern as `.title` and the
   wordmark instead: offset, so the white gap separates ring from fill. */
.filters-clear:focus-visible {
  outline: 2px solid var(--accent); outline-offset: 2px;
}
@media (min-width: 640px) {
  .filters input[type="search"] { flex: 1 1 100%; }
  /* `min-width: 0` is what lets them give the row back. A flex item defaults
     to `min-width: auto`, so each select refused to shrink below its longest
     option ("North America (7,283)"), the four filled the row exactly, and
     `clear` wrapped onto a line of its own.

     This moved onto the wrapper when the selects gained one — the flex item is
     now `.filter-field`, and the select has to be told to fill it, or it keeps
     its own content width and the icon detaches from its right edge. */
  .filters .filter-field { flex: 1 1 0; min-width: 0; }
  .filters .filter-field select { width: 100%; min-width: 0; }
  .filters-clear { flex: 0 0 auto; }
}
.title:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 4px; }

/* ------------------------------------------------------------------ hero --
   The method, above the list. Everything this project is actually good at —
   ground-truth verification, honest ageing, closing a listing the hour its
   employer drops it — was legible only on /how-it-works, which is precisely
   the page a first-time reader has not yet decided to click.

   Deliberately quiet: no panel, no accent fill, no headline. The list is what
   people came for and this is the caption above it, so it is sized and
   coloured as a caption. A hero that competed with the job cards would be
   trading the thing that works for the thing that explains it. */
/* Ruled off top and bottom, so the block reads as one statement rather than
   as loose text above the filters. `var(--accent)` rather than a literal, so
   it follows the theme — the yellow in dark mode, the green in light. Padding
   comes with the rules: without it the first tick and the last sentence sit
   flush against the lines. */
/* Full `.wrap` width, so the rules line up with the edges of the cards below
   rather than floating as a narrower centred band. No `max-width` at all: the
   measure that matters is inside it — `.evkey` is a three-column grid and
   `.trust` a wrapping row, both of which read fine at the card width and would
   be cramped at a prose measure. Unchanged on a phone, where `.wrap` was
   already the narrower constraint. */
.hero {
  margin: 0 0 18px;
  border-top: 3px solid var(--accent);
  border-bottom: 3px solid var(--accent);
  padding: 20px 0;
}

/* Four claims, as a row that wraps rather than a bulleted list: they are of a
   kind, and a vertical list of four ticks reads as terms and conditions. The
   tick is a pseudo-element so the text can wrap without breaking under it. */
.trust {
  display: flex; flex-wrap: wrap; justify-content: center;
  gap: 6px 18px; margin: 0 0 14px; padding: 0; list-style: none;
  font-size: 13.5px; font-weight: 600; color: var(--ink-soft);
}
.trust li { display: flex; align-items: baseline; gap: 6px; }
.trust li::before {
  content: "✓"; flex: none;
  color: var(--accent); font-weight: 800;
}

/* The badge key. A grid so the three definitions line up in one row on a wide
   screen and stack on a phone, and `align-items: start` so a badge sits on the
   first line of its own sentence rather than centred against it. */
.evkey {
  display: grid; gap: 10px 20px; margin: 0;
  grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
}
.evkey-item { display: flex; flex-direction: column; gap: 4px; }
/* The badge sits centred over its own description rather than pinned to the
   left edge. Done with `text-align` on the <dt> rather than `align-items` on
   the item: the badge is an inline-flex pill, so centring the text inside a
   full-width <dt> moves it without letting the <dd> shrink-wrap — which would
   have made each column break its lines at a different width and left the
   three descriptions visibly ragged against each other. */
.evkey dt { margin: 0; text-align: center; }
/* The badges carry their own top margin for the card's stacked layout, which
   is wrong in a key where each is the first thing in its own cell. */
.evkey .vbadge, .evkey .status { margin-top: 0; }
/* The weaker kinds, as text rather than a pill: a badge reads as a stamp, and
   these are the entries the key exists to rank *below* the badge, so they get
   the same weight and none of the chrome. Accent-coloured like "still open"
   beside it — the three are one vocabulary, and the ranking is carried by
   having a pill or not rather than by fading the words out. */
.evkey-plain {
  display: inline-flex; align-items: center; gap: 5px;
  font-size: 14px; font-weight: 700; letter-spacing: -0.01em;
  color: var(--accent); white-space: nowrap;
}
/* `currentColor` in the markup, so the mark follows the text. */
.evkey-icon { width: 14px; height: 14px; flex-shrink: 0; }
/* All three terms at one size. The badge is 12px on a card, where it sits in a
   dense right-hand column; here it is the subject of a legend and has to read
   as the same word the reader will look for. */
.evkey .vbadge { font-size: 14px; padding: 2px 10px 2px 8px; }
.evkey .vbadge-icon { width: 14px; height: 14px; }
.evkey dd {
  margin: 0; font-size: 12.5px; line-height: 1.5; color: var(--ink-muted);
  text-align: center;
}

@media (max-width: 560px) {
  /* Left-aligned on a phone: centred, four wrapped claims of different lengths
     read as ragged prose rather than as a list of four things. */
  .trust { justify-content: flex-start; gap: 4px 14px; font-size: 13px; }
  .evkey { gap: 12px; }
}

.tally {
  margin: 0 auto 18px; max-width: 62ch; text-align: center;
  font-size: 15px; font-weight: 600; line-height: 1.6; color: var(--ink);
}
.tally strong {
  font-size: 22px; font-weight: 800; letter-spacing: -0.02em;
  color: var(--accent);
}

/* Forwarding address for the legend, which now lives on /how-it-works. Quiet
   and centred under the tally: it is a pointer, not a call to action, and the
   list should open on listings rather than on chrome. */
.tally-note { margin: -8px auto 16px; text-align: center; font-size: 13px; }
.tally-note a { color: var(--muted); text-decoration: underline;
                text-underline-offset: 2px; }
.tally-note a:hover { color: var(--accent); }

.joblist { list-style: none; margin: 0; padding: 0; display: grid; gap: 12px; }

.pager {
  display: flex; flex-wrap: wrap; align-items: center; gap: 6px;
  margin: 22px 0 4px; justify-content: center;
}
.pg {
  min-width: 34px; padding: 6px 10px; text-align: center;
  font-size: 13.5px; font-weight: 700; text-decoration: none;
  color: var(--ink-soft); background: var(--card);
  border: 1px solid var(--line); border-radius: 9px;
  transition: border-color .15s ease, color .15s ease, background .15s ease;
}
a.pg:hover { color: var(--accent); border-color: var(--accent); }
/* The match pager posts rather than links, so its controls are buttons. A
   button does not inherit the page font and carries a UA line-height, so
   without these it renders a different size from the identical control on the
   job list. */
button.pg { font-family: inherit; line-height: 1.35; cursor: pointer; }
button.pg:hover { color: var(--accent); border-color: var(--accent); }
.pg-now { color: var(--on-accent); background: var(--accent); border-color: var(--accent); cursor: default; }
.pg-off { opacity: .38; }
/* Only the step controls hold an icon, so only they become flex — `.pg` stays
   as it is because the numbered pages rely on `text-align: center` inside a
   34px minimum, which a flex container ignores. `inline-flex` on the steps is
   safe: they are wider than the minimum either way.

   The disabled ends are <span>s carrying the same classes, so they take the
   icon and the layout too — the arrow is part of the control, not of its
   enabled state, and `.pg-off` fades the whole thing including the mark. */
.pg-step { display: inline-flex; align-items: center; gap: 6px; }
/* `flex: none` for the reason the masthead marks carry it: a fixed-ratio mark
   has nowhere to go when the row is tight, and the pager wraps on a phone. */
.pg-icon { display: block; flex: none; width: 16px; height: 16px; }
.pg-gap { padding: 0 2px; color: var(--muted); }

.pg-jump { display: flex; align-items: center; gap: 6px; margin-left: 10px; }
.pg-jump label { font-size: 12.5px; font-weight: 600; color: var(--muted); }
.pg-jump input {
  width: 68px; padding: 6px 8px; font: inherit; font-size: 13.5px;
  font-weight: 700; color: var(--ink); text-align: center;
  background: var(--card); border: 1px solid var(--line); border-radius: 9px;
}
.pg-jump input:focus-visible {
  outline: none; border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--focus-ring);
}
.pg-jump button {
  padding: 6px 12px; font: inherit; font-size: 13.5px; font-weight: 700;
  color: var(--accent); background: var(--accent-soft); cursor: pointer;
  border: 1px solid var(--accent-line); border-radius: 9px;
  transition: background .15s ease, color .15s ease;
}
.pg-jump button:hover { color: var(--on-accent); background: var(--accent); border-color: var(--accent); }

@media (prefers-reduced-motion: reduce) {
  .pg, .pg-jump button { transition: none; }
}

/* A listing's card, and the job page's header, which is the same object: logo,
   the listing's own details, and `_status.html` in a `.card-side` beside them.
   **One rule for both**, for the reason that template is shared — a reader who
   clicked a card should land on the same thing, and two copies of "what a
   listing looks like" drift. They had: the header carried no shadow, 20px
   padding against 16/18, and a 16px gap against 14, all invisible until the
   two are seen one after the other.

   The differences that remain are below and each is a consequence of the
   header being a *page heading* rather than a link. */
.card, .job-head, .co-head {
  /* The brand column's width, named because the mobile rule below has to line
     the status block up with the text and would otherwise repeat the number.
     One value, two readers — the same reason `.filters-clear`'s rule is shared
     rather than restated. */
  --brand-w: 120px;
  display: flex; align-items: center; gap: 14px;
  background: var(--card); border: 1px solid var(--line); border-radius: var(--radius);
  padding: 16px 18px;
  box-shadow: 0 1px 2px rgba(23, 33, 27, .04);
  transition: transform .15s ease, box-shadow .15s ease, border-color .15s ease;
}
.card:hover {
  transform: translateY(-2px);
  border-color: var(--accent);
  box-shadow: 0 6px 18px rgba(23, 33, 27, .08);
}
@media (prefers-reduced-motion: reduce) {
  .card, .filters input, .filters select { transition: none; }
  .card:hover { transform: none; }
}


.clogo {
  width: 42px; height: 42px; flex-shrink: 0;
  border-radius: 10px; object-fit: contain;
  background: var(--logo-bg); border: 1px solid var(--line);
}
.clogo-fallback {
  display: inline-flex; align-items: center; justify-content: center;
  font-weight: 700; font-size: 18px; color: var(--accent);
  background: var(--accent-soft); border-color: var(--accent-line);
}

/* `min-width: 0` lets the flex item shrink; `overflow-wrap` is what lets its
   text actually fit once it has. Without the second, a title with no space in
   it — "SeniorFullStackEngineerPlatformInfrastructure", or a company name run
   together — has no break opportunity, so it sets the item's minimum width and
   drags the whole card past the screen edge. Measured on a 320px phone: a card
   went from 280px to 516px wide and the page scrolled sideways. `anywhere`
   rather than `break-word` because only `anywhere` counts for min-content
   sizing, which is the calculation flexbox is doing here. */
/* Logo, employer, and the boards carrying the listing — one identity column.
   Centred on the mark rather than left-aligned: the logo is a fixed 42px in a
   120px column, so aligning left would leave it floating against a ragged
   name. `align-items: center` on `.card` then centres the whole column against
   the title block beside it. */
.card-brand {
  display: flex; flex-direction: column; align-items: center; gap: 6px;
  width: var(--brand-w); flex: none; text-align: center;
}
/* Long employer names wrap rather than widening the column or overflowing it.
   "Precision Medicine Group" is three lines at this width and that is the
   trade: a fixed column keeps every card's title starting at the same x. */
.card-brand .company { overflow-wrap: anywhere; line-height: 1.3; }
.card-main { flex: 1 1 auto; min-width: 0; overflow-wrap: anywhere; }
.title {
  font-weight: 700; font-size: 17.5px; letter-spacing: -0.01em;
  color: var(--ink); text-decoration: none;
}
.title:hover { color: var(--accent); }
/* 10px, not 5: the title is 17.5px semibold and the chips are 12px, so a
   5px gap read as one block rather than a heading and its details. */
.meta { display: flex; align-items: center; gap: 7px; margin-top: 10px; flex-wrap: wrap; }
.company { color: var(--ink-soft); font-size: 14px; font-weight: 600; }
.chip {
  font-size: 13px; font-weight: 600; color: var(--ink-soft);
  border: 1px solid var(--line); border-radius: 999px; padding: 1.5px 10px;
  text-transform: lowercase; white-space: nowrap;
}
/* Levels are single words from a closed vocabulary (junior … head), so
   `capitalize` reads them correctly and needs no help from the template. */
.chip-level { color: var(--pill-level); background: var(--pill-level-bg);
              border-color: var(--pill-level-line); text-transform: capitalize; }
.chip-loc   { color: var(--pill-loc);   background: var(--pill-loc-bg);   border-color: var(--pill-loc-line);
              text-transform: none; }
/* `none`, not `capitalize`: the chip reads "via greenhouse" and title-casing
   the lot would give "Via Greenhouse". The board's name is capitalised in the
   template instead, where the literal "via" can be left alone. */
.chip-src   { color: var(--pill-src);   background: var(--pill-src-bg);
              border-color: var(--pill-src-line); text-transform: none; }
/* text-transform:none, like .chip-loc: .chip lowercases everything by default,
   which is right for source and level pills but not for a place name. */
.chip-globe { color: var(--accent); background: var(--accent-soft); border-color: var(--accent-line);
              text-transform: none; }
/* "+N more" — same family as the location pills it summarises, but unfilled so
   it reads as a count rather than another place. The full list is in its
   title attribute. */
.chip-more  { color: var(--pill-loc); border-color: var(--pill-loc-line);
              background: transparent; text-transform: none; cursor: help; }
/* One card standing for several requisitions of the same role. Same unfilled
   treatment as `.chip-more` and for the same reason — it is a count, not a
   property of the job — but on the neutral line rather than the location
   colour, because it is not a place and sitting it in the location family
   would read as one more country. `cursor: help` because the title carries
   the explanation. */
.chip-fold  { color: var(--ink-soft); border-color: var(--line);
              background: transparent; text-transform: none; cursor: help; }
/* Absence of a location, not a location. Dashed and grey so it never reads as
   somewhere you could work — it pairs with the "Not specified" facet, so a card
   showing this is reachable through that filter rather than through none.
   #5A6673 rather than the #64717E used elsewhere, giving 5.37:1 on the chip's
   own background. */
.chip-none  { color: var(--ink-muted-tint); background: var(--neutral-bg); border-color: var(--line);
              border-style: dashed; text-transform: none; }
/* Why a listing is normally hidden — shown only when the toggle is on, so
   "include these" never means "quietly mix them back in". */
.chip-flag { color: var(--flag-ink); background: var(--flag-bg); border-color: var(--flag-line); }
/* Pay. Green because it belongs to the same family as the open-green the board
   is built around, and because it is good news rather than a warning — it must
   not read as a sibling of .chip-warn sitting a few pills along. Filled and
   tabular-figure so a column of cards can be scanned down without the digits
   shifting. 5.9:1 on its own background. text-transform:none keeps "CA$88.4k"
   and "SEK 45k" intact, which .chip would otherwise lowercase into nonsense. */
.chip-pay { color: var(--good-ink); background: var(--good-bg); border-color: var(--good-line);
            text-transform: none; font-variant-numeric: tabular-nums;
            cursor: help; }

/* A board's estimate of market rate, not a figure any employer published.
   Drained of the open-green the published pills carry, because that colour is
   the page's signal for "this is real" and an estimate has not earned it. Grey
   rather than amber: it is not a warning, it is a weaker claim, and the amber
   family already means "these two statements contradict each other" on this
   card. The "est." suffix comes from shared.salary.display; this is what makes
   it legible to someone scanning a column of pay pills without hovering any.
   5.31:1 on its own background, against 5.90:1 for the published chip — muted
   enough to read as the weaker claim, still clear of AA. These are also
   excluded from the pay filter — see salary.FILTER_SOURCES. */
.chip-pay-est { color: var(--est-ink); background: var(--est-bg); border-color: var(--est-line);
                font-style: italic; }

/* The listing's location tag and its own ad text disagree about who may apply.
   Deliberately the loudest chip on the card: it contradicts the pill sitting
   next to it, and a reader who takes the pill at face value wastes an
   application. Same amber family as .chip-flag but darker and solid-bordered,
   so the two are not mistaken for each other — 7.43:1 on its own background,
   the strongest contrast of any chip, because this is the one that must not be
   skimmed past. text-transform:none for the place names, as .chip-loc. */
/* The one chip allowed to wrap. `.chip` is nowrap, which is right for a level
   or a country but wrong here: this badge joins every disputed place with
   " / ", so "⚠ ad says United Kingdom / Europe / United States / Hong Kong /
   Vietnam" is a single 326px pill that cannot break. On a 320px phone it
   dragged the whole card to 384px and off the side of the screen. Wrapping
   rather than truncating, because this is the most important claim on the card
   and a shortened list is the *narrower* finding problem all over again. */
.chip-warn { color: var(--warn-ink); background: var(--warn-bg); border-color: var(--warn-line);
             font-weight: 700; text-transform: none; cursor: help;
             white-space: normal; }


/* Four stacked claims, in the order a reader weighs them: is it open, was
   that confirmed, when was it posted, when did we last look. They used to flow
   inline and wrap wherever the column ran out, which put "still open" and the
   verified badge on one line and the two dates on another — a grouping that
   came from the width rather than from any relationship between them.

   Scoped to `.card-side` rather than styling `.age` directly, because
   `_status.html` is shared: the job page's copy keeps its single line. */
.card-side {
  display: flex; flex-direction: column; align-items: flex-end; gap: 4px;
  flex-shrink: 0; text-align: right;
  /* Matches `.card`'s own flex gap, so the column sits off the right edge by
     about as much as the brand column sits off the left. Without it the dates
     ran up against the card border while the logo had 18px of air. */
  padding-right: 14px;
}
.card-side .age { display: flex; flex-direction: column; align-items: flex-end; }
/* Margins that spaced these when they were inline would now add to the gap. */
.card-side .vbadge, .card-side .age { margin-top: 0; }
.card-side .age-sep { display: none; }
.status {
  display: inline-flex; align-items: center; gap: 6px;
  font-size: 14px; font-weight: 700; color: var(--accent);
}
/* A listing that has closed. Muted rather than alarming: it is a state,
   not a warning — the reader is being told the outcome of a check. Only
   `/saved` renders this on a card; the job page shows it above its own
   banner. (`--warn` used to be spoken for by "likely filled", removed
   2026-09-06 — see `web/detail.py`.) */
.status-closed { color: var(--ink-muted); }
/* The four status icons. Inlined in `_status.html` rather than loaded as
   `<img>`, for the reason recorded at the masthead marks: the shipped files
   carry `stroke="white"` and page CSS cannot reach inside an embedded SVG, so
   `currentColor` is the only way these follow the theme.

   Two sizes. The status line is 13px bold and carries a 14px mark; the two
   ages are 12px and carry 11px marks, matching what the old lightning bolt
   used so the line height does not shift. */
.status-icon { width: 14px; height: 14px; flex-shrink: 0; }
.vbadge-icon { width: 12px; height: 12px; flex-shrink: 0; }
.age-icon {
  width: 11px; height: 11px; margin-right: 3px; vertical-align: -1px;
  flex-shrink: 0;
}
/* "checked" is about *our* freshness rather than the employer's, and the
   accent is what the site uses for its own claims — the same colour as
   "still open" and the verified badge. */
.age-checked { color: var(--accent); }
.vbadge {
  display: inline-flex; align-items: center; gap: 4px;
  margin-top: 5px;
  font-size: 12px; font-weight: 700; letter-spacing: -0.01em;
  color: var(--accent); background: var(--accent-soft);
  border: 1px solid var(--accent-line); border-radius: 999px;
  padding: 1.5px 9px 1.5px 7px; white-space: nowrap;
}
.vbadge svg { width: 11px; height: 11px; flex-shrink: 0; }
.age { display: block; font-size: 13px; color: var(--muted); margin-top: 4px; }
/* Posted within 48h. Darker than --warn (#B4831B), which only reaches 3.4:1 on
   white — a caution colour, not a highlight. This is 12px and needs to clear
   AA on its own terms. */
.age-fresh { color: var(--fresh-ink); font-weight: 700; }
/* Open more than a year. The number is what looks wrong to a reader — "posted
   3,136d ago" beside "still open" reads as broken data — so the number is what
   gets marked, and the icon follows through `currentColor` because these marks
   are inlined rather than `<img>`-ed.

   It cannot collide with `.age-fresh`: that fires inside 48 hours and this
   past 365 days. `cursor: help` because the card carries the explanation as a
   `title`; the listing's own page states it in text below the header. */
.age-long { color: var(--aged-ink); font-weight: 700; cursor: help; }

.empty {
  border: 1px dashed var(--line); border-radius: var(--radius);
  padding: 40px; text-align: center; color: var(--ink-soft); background: var(--card);
}
/* Centred to sit under the tally, which is the other centred line on the page.
   max-width keeps the sentence from running the full 880px column, where a
   centred line that wide reads as ragged rather than composed. */
.foot { padding: 26px 20px 40px; color: var(--muted); font-size: 13px;
        text-align: center; }
.foot p { max-width: 62ch; margin: 0 auto; line-height: 1.6; }
.foot a { color: var(--muted); text-decoration: underline; text-underline-offset: 2px; }
.foot a:hover { color: var(--accent); }
/* Both of these are qualified as `.foot p.x` rather than `.x`, and that is
   load-bearing rather than stylistic: `.foot p` above is a class *and* a type
   selector, so it outranks a bare class and its `margin: 0 auto` silently wins.
   `.foot-links` carried `margin-top: 12px` for exactly that reason and it never
   applied once — the links row has been sitting flush at 0. Anything adding
   vertical spacing to a footer paragraph has to clear that bar or it does
   nothing, with no error and no visual clue beyond the gap looking tight. */

/* 10px: the two statements are both about how the site works and read as one
   block. */
.foot p.foot-note { margin-top: 10px; }
/* 20px: the links are a different kind of thing from the two statements above
   them, so the gap that separates them is the larger one.
   Spaced by layout rather than by punctuation: a "·" between each pair is read
   aloud by a screen reader, turning four links into one run-on sentence. */
.foot p.foot-links { margin-top: 20px; display: flex; flex-wrap: wrap;
                     gap: 8px 20px; justify-content: center; }

/* Analytics consent. Accept and Decline are the same size and weight — a
   reject that is harder to reach than accept is not a free choice. */
.consent {
  position: fixed; left: 16px; right: 16px; bottom: 16px; z-index: 50;
  max-width: 620px; margin: 0 auto;
  display: flex; align-items: center; gap: 16px; flex-wrap: wrap;
  padding: 14px 18px;
  background: var(--card); border: 1px solid var(--line); border-radius: var(--radius);
  box-shadow: 0 8px 28px rgba(23, 33, 27, .14);
}
.consent[hidden] { display: none; }
.consent-text { margin: 0; flex: 1 1 300px; font-size: 13.5px; line-height: 1.5; color: var(--ink); }
.consent-actions { display: flex; gap: 8px; flex-shrink: 0; }
.consent-btn {
  padding: 8px 16px; font: inherit; font-size: 13.5px; font-weight: 700;
  color: var(--ink-soft); background: var(--card); cursor: pointer;
  border: 1px solid var(--line); border-radius: 9px;
  transition: border-color .15s ease, color .15s ease, background .15s ease;
}
.consent-btn:hover { border-color: var(--accent); color: var(--accent); }
.consent-yes { color: var(--on-accent); background: var(--accent); border-color: var(--accent); }
.consent-yes:hover { color: var(--on-accent); background: var(--accent-hover); border-color: var(--accent-hover); }
@media (prefers-reduced-motion: reduce) { .consent-btn { transition: none; } }

@media (max-width: 560px) {
  /* Both wrap into stacked bands at this width — see the note on the shared
     `.card, .job-head` rule. The header used to restate `padding: 16px` and
     `gap: 12px` here, so the two ended a phone layout at different sizes.

     `flex-wrap` is load-bearing on the header, not decoration: without it the
     status block keeps the `width: 100%` it takes from `.card-side` below and
     squeezes the title column to zero, so the <h1> vanishes rather than
     merely tightening. */
  .card, .job-head { flex-wrap: wrap; }
  /* The brand column turns into a brand *row* on a phone, and the whole card
     becomes three stacked bands: who is hiring, then the title, then the
     status.

     As a column it took `--brand-w` off the width, leaving the title 120px
     less than the screen — and job titles are long ("Senior Software Engineer,
     Platform Infrastructure"), so almost every card wrapped its title to three
     or four lines against a mostly-empty logo column. Horizontal, the title
     gets the full width and needs one or two lines.

     `flex-wrap` on the row itself so the "via" pill drops below a long
     employer name rather than squeezing it. */
  .card-brand {
    flex-direction: row; flex-wrap: wrap; justify-content: center;
    width: 100%; gap: 8px 10px; text-align: center;
  }
  .card-main { flex: 1 1 100%; }
  /* Centred, and the indent that used to line this up with the title is gone
     with the left column it was clearing. That indent is worth remembering:
     at `calc(var(--brand-w) + 14px)` it pushed the block 134px in on a 375px
     screen, which reads as right-aligned rather than as an indent. */
  /* A row, not a column: the card is already three stacked bands, and four
     more single-line rows would make the status taller than the listing it
     describes. "still open" and the verified badge share the first line —
     they are one claim and its confirmation — and `.age` is pushed onto its
     own by a full-width basis rather than by hoping it does not fit. */
  .card-side {
    flex-direction: row; flex-wrap: wrap; justify-content: center;
    align-items: center; gap: 5px; text-align: center;
    width: 100%; padding-left: 0; padding-right: 0;
    /* On top of `.card`'s own 14px row gap. The status band is a different
       kind of claim from the chips above it — those describe the role, these
       describe whether it is still there — so it needs more air than the gap
       that merely separates wrapped rows. */
    margin-top: 10px;
  }
  .card-side .age { display: block; flex: 0 0 100%; }
  /* Undoing the `display: none` the stacked desktop layout gives it. */
  .card-side .age-sep { display: inline; }
  /* Unscoped, so the job page's status block gets it too: "posted 2d ago ·
     checked just now" had only collapsed whitespace either side of the dot,
     which made the gap a consequence of how the template happens to be
     indented rather than a number. */
  .age-sep { margin: 0 4px; }
}

/* ------------------------------------------------------------ CV matching --
   The match page reuses .card, .chip and .wrap so a result reads as the same
   kind of object as a job listing. Only what is genuinely new is defined. */
/* Wraps as whole items, and the labels do not wrap at all. The two go together:
   without `nowrap` a flex item shrinks to its longest word before the row ever
   breaks, so at 320px the four labels each broke across three lines rather than
   moving one item onto a second row. That was already true of the old nav at
   two lines; the icons cost the width that made it visible. Row gap is tighter
   than the column gap because 18px between stacked rows reads as two navs. */
.mast-nav { display: flex; justify-content: center; flex-wrap: wrap;
            gap: 8px 18px; font-size: 15px; font-weight: 600; }
.mast-nav a, .theme-toggle { white-space: nowrap; }
/* The nav read as three grey captions under the tagline — nothing said these
   were links until a cursor happened to land on one. A leading icon carries
   that now; it replaced a trailing accent chevron, and the job it inherited is
   the *accent*, not the shape. Left grey it would be decoration and the nav
   would read as captions again, which is the state this rule exists to fix. */
.mast-nav a { color: var(--ink-soft); text-decoration: none;
              display: inline-flex; align-items: center; gap: 7px; }
.mast-nav a:hover { color: var(--accent); }
/* Matches the nav links in colour, size and weight, and now in icon colour too.
   The chevron used to mark the difference — links got one, this did not, because
   it stays on the page — and that distinction is deliberately not carried over.
   It costs the button its hover feedback: the label is visually hidden, so
   `:hover { color }` has no visible text left to recolour and the accent icon no
   longer changes. Hence the opacity shift below, which is the only thing left
   that can answer the cursor. */
.theme-toggle {
  display: inline-flex; align-items: center; gap: 7px;
  font: inherit; font-size: 15px; font-weight: 600;
  color: var(--ink-soft); background: none; border: 0; padding: 0;
  cursor: pointer;
}
.theme-toggle:hover { color: var(--accent); opacity: .7; }
.theme-toggle:focus-visible {
  outline: 2px solid var(--accent); outline-offset: 3px; border-radius: 4px;
}
/* Both labels ship in the markup and CSS shows one, so the button is drawn
   correctly by the first paint — a script writing the label would run after it
   and flicker on every navigation. Each label names the theme you would get by
   clicking, not the one you are in. */
/* One rule for every masthead icon — the three nav links and both toggle
   states — so they cannot drift apart in size. Sized here rather than by the
   SVG's own width/height attributes, which are dropped when it is inlined: the
   marks are a 24-unit viewBox drawn at 18px so they sit on the nav's 15px cap
   height without towering over the text beside them. `display: block` because
   an inline SVG otherwise sits on the text baseline and carries descender space
   the flex centring cannot see.

   `flex: none` is load-bearing, not tidiness. These are flex items inside a
   flex item, so on a 320px screen the nav shrinks each link and each link then
   shrinks its own icon — measured at 12.2px, 9.4px and 10px, three sizes with
   three apparent stroke weights, because `width` is only a basis to shrink
   from. The text has somewhere to go when space runs out; a fixed-ratio mark
   does not.

   The accent is set here rather than on each icon's own rule so all five marks
   take it from one place — and on the SVG itself rather than inherited from the
   link, whose colour is `--ink-soft` and flips on hover. `currentColor` in the
   markup is what makes that reach the stroke. */
.nav-icon, .theme-icon {
  display: block; flex: none; width: 18px; height: 18px;
  color: var(--accent);
}
.theme-state { display: inline-flex; align-items: center; gap: 7px; }
.theme-state-light { display: none; }
html[data-theme="dark"] .theme-state-dark { display: none; }
html[data-theme="dark"] .theme-state-light { display: inline-flex; }
/* Centred, matching the masthead above it — the heading and its intro are the
   page's opening address, not the start of the results. Everything below the
   form (the listings themselves) stays left-aligned, because that is a list to
   scan rather than something to read. */
.match-title { margin: 22px 0 8px; font-size: 26px; letter-spacing: -0.02em;
               text-align: center; }
/* Same centred column as /how-it-works, for the same reason: the intro and
   the results were capped inside the 880px wrap and hugged the left edge. */
.matchpage { max-width: 720px; margin: 0 auto; }
/* Narrower than the 720px column and centred within it: centred text is read by
   returning to a ragged left edge, so a full-width measure is where the pattern
   stops being legible. */
.match-intro { margin: 0 auto 18px; max-width: 60ch; color: var(--ink-soft);
               line-height: 1.6; text-align: center; }
.match-intro strong { color: var(--ink); }

.match-form textarea {
  width: 100%; font: inherit; color: var(--ink); line-height: 1.55;
  background: var(--card); border: 1px solid var(--line);
  border-radius: var(--radius); padding: 12px 14px; resize: vertical;
}
.match-form textarea:focus-visible {
  outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px var(--focus-ring);
}
.match-controls { display: flex; gap: 10px; margin: 10px 0 6px; flex-wrap: wrap; align-items: center; }
.match-controls select {
  font: inherit; color: var(--ink); background: var(--card);
  border: 1px solid var(--line); border-radius: var(--radius); padding: 10px 14px;
}
.match-controls button {
  font: inherit; font-weight: 700; color: var(--on-accent); cursor: pointer;
  background: var(--accent); border: 1px solid var(--accent);
  border-radius: var(--radius); padding: 10px 20px;
}
.match-controls button:hover { filter: brightness(1.06); }

.match-error {
  max-width: 68ch; margin: 14px 0; padding: 12px 14px;
  color: var(--warn-ink); background: var(--warn-bg); border: 1px solid var(--warn-line);
  border-radius: var(--radius); line-height: 1.55;
}
.match-summary { margin: 24px 0 10px; line-height: 1.6; }
.match-skills { display: flex; flex-wrap: wrap; gap: 6px; margin: 10px 0 0; }

.matchlist { list-style: none; margin: 14px 0 0; padding: 0; display: grid; gap: 12px; }
.match-card { display: block; }
/* The headline figure. Larger than the surrounding text because it is the
   answer the page exists to give — and deliberately a sentence, not a number
   in a badge, so it cannot be mistaken for a score out of a hundred. */
.match-count { margin: 8px 0 6px; font-size: 15px; font-weight: 700; color: var(--ink); }
.match-detail { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; margin: 0 0 6px; }
.match-have, .match-gap { font-size: 13px; font-weight: 600; color: var(--ink-soft); }
.chip-skill { color: var(--good-ink); background: var(--good-bg); border-color: var(--good-line); text-transform: none; }
/* Missing skills are grey, not red. A gap is information, not a failure, and
   colouring it like an error would tell people not to apply. */
.chip-gap { color: var(--ink-muted-tint); background: var(--neutral-bg); border-color: var(--line);
            text-transform: none; }


/* ------------------------------------------------------- job detail page --
   Reuses .wrap, .card, .chip and the pill palette so a listing's own page
   reads as the same object as its card. Only what is genuinely new is defined
   here.

   The page's spine is .ev — one claim, its answer, and how the answer was
   arrived at. That third line is the entire reason these pages exist, so it is
   a first-class element rather than a tooltip. */

.jobpage { padding-bottom: 10px; }
.crumb { margin: 18px 0 14px; font-size: 13.5px; font-weight: 600; }
.crumb a { color: var(--ink-soft); text-decoration: none; }
.crumb a:hover { color: var(--accent); }
/* Prose links that carry an arrow, as opposed to the pager's buttons: the link
   itself becomes the flex container, because these sit inside running text
   rather than in a flex row of their own. `inline-flex` keeps them inline-level
   so the closed-banner sentence still flows around one — the trade is that the
   label can no longer break across lines mid-phrase, which is why it is used
   only on the two short ones. 15px against 13.5–14px prose, a touch under the
   16px the pager uses, so the mark reads as part of the sentence. */
.crumb a, .closed-banner a, .notfound a { display: inline-flex; align-items: center; gap: 5px; }
.link-icon { display: block; flex: none; width: 15px; height: 15px; }

/* A closed listing keeps its page, so the fact it is closed has to be the
   first thing read — above the title, not a chip beside it. Amber rather than
   red: the job being filled is not an error, and the page is still useful. */
.closed-banner {
  margin: 0 0 18px; padding: 13px 16px; line-height: 1.55; font-size: 14px;
  color: var(--warn-ink); background: var(--warn-bg); border: 1px solid var(--warn-line);
  border-radius: var(--radius);
}
.closed-banner a { color: var(--warn-ink); font-weight: 700; }

/* The header is a card: `.card-brand`, `.card-main` and `.card-side` in the
   shared `.card, .job-head` rule above, ATS border included. It carries no
   rules of its own any more — `align-items: flex-start` went with the bare
   logo it existed for, now that the identity column is the card's own and
   centres against the title exactly as it does on the list.

   **The one thing not shared is the hover lift.** `.card:hover` raises and
   accents the whole card because the whole card is a link; on a page heading
   that would promise a click that does nothing. */
.job-title {
  margin: 0; font-size: 25px; font-weight: 800; line-height: 1.25;
  letter-spacing: -0.02em; overflow-wrap: anywhere;
}
/* The employer's own site, linked from the card's `.company` in the brand
   column. Inherits that class's colour and weight so the link reads as the
   company name rather than as a call to action — the apply button is the only
   thing on this page that should. */
.card-brand .company a { color: inherit; text-decoration: none; }
.card-brand .company a:hover { color: var(--accent); text-decoration: underline; }

.apply-row { margin: 16px 0 6px; }
.apply-row-end { margin: 22px 0 8px; }
/* `inline-flex` rather than `inline-block` so the mark centres against the
   label instead of sitting on its baseline. It stays inline-level, so the row
   around it is unchanged. */
.apply-primary {
  display: inline-flex; align-items: center; gap: 8px; padding: 11px 22px;
  font-size: 15px; font-weight: 700; color: var(--on-accent); text-decoration: none;
  background: var(--accent); border: 1px solid var(--accent);
  border-radius: var(--radius);
}
/* Takes `--on-accent` off the button through `currentColor`, so it flips with
   the theme exactly as the label does — white on green, near-black on yellow. */
.apply-icon { display: block; flex: none; width: 17px; height: 17px; }
.apply-primary:hover { filter: brightness(1.06); }
.apply-primary:focus-visible { outline: 2px solid var(--ink); outline-offset: 2px; }
.apply-note { margin: 8px 0 0; font-size: 13px; color: var(--ink-soft); }

/* The long-open disclosure on the listing's own page: one line the width of
   the card, directly under the header it explains.

   **It sits outside `<header>` rather than inside it**, and that is layout
   rather than taste. `.job-head` is a centred flex row of three columns; a
   fourth child at `flex-basis: 100%` would need `flex-wrap: wrap` on the
   header at desktop, which today is a mobile-only rule and is load-bearing
   there. Adding it above the breakpoint would let the three columns wrap on
   any width that squeezed them — a real regression for a cosmetic gain.

   It began life inside `.card-side`, which is right-aligned and narrow, so the
   sentence stacked into a 34ch column offset to the right of a wide card.

   The mark is red and the sentence is not. The red belongs on the *number*,
   which is the thing that looks wrong; a whole red sentence reads as an error,
   and this is an observation — the listing really is open and the employer
   really is still publishing it. */
.long-open {
  margin: 10px 0 0; font-size: 13px; line-height: 1.5;
  color: var(--ink-soft);
}

.section-h {
  margin: 30px 0 12px; font-size: 22px; font-weight: 700;
  letter-spacing: .08em; text-transform: uppercase; color: var(--ink-muted);
}

.ev-grid {
  display: grid; gap: 12px;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
}
.ev {
  padding: 15px 17px; background: var(--card);
  border: 1px solid var(--line); border-radius: var(--radius);
}
.ev-wide { grid-column: 1 / -1; }
/* The mismatch is the sharpest thing this site says. On the card it is a chip;
   here it gets the whole panel, in the same amber family, because the page is
   where a reader decides whether to trust it. */
.ev-warn { border-color: var(--warn-line); background: var(--warn-bg-soft); }
.ev h3 {
  margin: 0 0 6px; font-size: 13px; font-weight: 700; color: var(--ink-soft);
}
.ev-answer { margin: 0 0 6px; font-size: 17px; font-weight: 700; line-height: 1.35; }
.ev-note { margin: 0; font-size: 13px; line-height: 1.55; color: var(--ink-soft); }
.ev-note-warn { color: var(--warn-ink); }
.ev-note + .ev-note { margin-top: 6px; }
.ev .meta { margin-bottom: 8px; }

.ev-quotes { margin-top: 9px; font-size: 13px; }
.ev-quotes summary { cursor: pointer; font-weight: 700; color: var(--ink-muted); }
/* The same claim as JSON. Muted rather than accented: it is an affordance for
   the few readers who want to check the working, not a call to action
   competing with the apply button. */
.ev-api { color: var(--ink-muted); text-decoration: underline; }
.ev-api:hover { color: var(--accent); }
/* The evidence panel's one prose link — "…what CV matching compares against".
   It had no rule at all, so it rendered in the browser's default blue, and
   visited-purple, in both themes.
   The `:not()` is load-bearing: `.ev-api` above is *also* an <a> inside an
   `.ev-note`, and a bare `.ev-note a` is (0,1,1) against its (0,1,0), so it
   would quietly take over the status-JSON link too. This is now the only
   `:not()` guard of its kind left in the file — `.howto p` carried the other
   until its footer paragraph was removed. */
.ev-note a:not(.ev-api) {
  color: var(--link); text-decoration: underline; text-underline-offset: 2px;
}
.ev-note a:not(.ev-api):hover { color: var(--link-hover); }
.ev-quote {
  margin: 8px 0 0; padding: 8px 12px; line-height: 1.55;
  color: var(--ink); background: var(--card); border-left: 3px solid var(--warn-line);
  border-radius: 0 6px 6px 0;
}
/* The matched cue, not a highlight for emphasis: this is the phrase the
   extractor keyed on, and seeing it is what lets a reader judge the finding. */
.ev-quote mark { background: var(--mark-bg); color: inherit; font-weight: 700; padding: 0 2px; }

.srclist { list-style: none; margin: 0; padding: 0; display: grid; gap: 8px; }
.src {
  padding: 11px 15px; background: var(--card);
  border: 1px solid var(--line); border-radius: var(--radius);
}
.src-head { display: flex; align-items: center; gap: 9px; flex-wrap: wrap; }
.src-name { font-size: 14px; font-weight: 700; }
.src-age { font-size: 12px; color: var(--muted); }
/* What the audit measured about this board. Same amber family as the
   eligibility mismatch badge, because it is the same kind of statement — a
   thing the reader should weigh before trusting what is above it — but set as
   a sentence rather than a chip: it is a claim about a source's record, and a
   pill would compress it into something that reads like a label. */
.src-warn {
  margin: 8px 0 0; padding: 7px 11px;
  font-size: 12.5px; line-height: 1.5; font-weight: 600;
  color: var(--warn-ink); background: var(--warn-bg);
  border: 1px solid var(--warn-line); border-radius: 8px;
  cursor: help;
}
.src-warn + .src-warn { margin-top: 6px; }

.src-claims { margin: 8px 0 0; padding: 0 0 0 2px; list-style: none;
              font-size: 13px; line-height: 1.6; color: var(--ink-soft); }
/* What this source claims that the page does not show. Labelled rather than
   tabulated: there are rarely more than two, and a table for two rows is
   furniture. */
.src-field {
  display: inline-block; min-width: 62px;
  font-size: 11px; font-weight: 700; letter-spacing: .05em;
  text-transform: uppercase; color: var(--ink-muted);
}

/* The ad itself, sanitized upstream (shared/sanitize.py). Everything here is
   defensive: the markup is written by ~330 boards and employers, so it has to
   look reasonable whatever arrives — including a description that is one
   enormous div, or a table wider than the column. */
.jobdesc { margin-top: 4px; }
.desc-body {
  padding: 20px 22px; background: var(--card);
  border: 1px solid var(--line); border-radius: var(--radius);
  line-height: 1.65; overflow-wrap: break-word;
}
.desc-body > :first-child { margin-top: 0; }
.desc-body > :last-child { margin-bottom: 0; }
.desc-body h2, .desc-body h3, .desc-body h4, .desc-body h5, .desc-body h6 {
  margin: 22px 0 8px; font-size: 16px; font-weight: 700; line-height: 1.35;
}
.desc-body p, .desc-body ul, .desc-body ol { margin: 0 0 13px; }
.desc-body ul, .desc-body ol { padding-left: 22px; }
.desc-body li { margin: 4px 0; }
/* `--link`, not `--accent`: this is the employer's own prose, the longest
   stretch of running text on the site, and the accent is 2.54:1 on white. */
.desc-body a { color: var(--link); text-decoration: underline; text-underline-offset: 2px; }
.desc-body a:hover { color: var(--link-hover); }
.desc-body pre { overflow-x: auto; padding: 10px 12px; background: var(--neutral-bg); border-radius: 8px; }
.desc-body blockquote {
  margin: 0 0 13px; padding-left: 14px; color: var(--ink-soft);
  border-left: 3px solid var(--line);
}
.desc-body table { width: 100%; border-collapse: collapse; display: block; overflow-x: auto; }
.desc-body th, .desc-body td { padding: 6px 10px; border: 1px solid var(--line); text-align: left; }
.desc-body hr { border: 0; border-top: 1px solid var(--line); margin: 18px 0; }

.notfound { margin-top: 28px; line-height: 1.6; }
.notfound p { margin: 0 0 10px; }
/* Bold rather than underlined, which is this link's own affordance — only the
   colour moves onto `--link`. */
.notfound a { color: var(--link); font-weight: 700; }
.notfound a:hover { color: var(--link-hover); }

@media (max-width: 560px) {
  /* `.job-head-side` carries `.card-side` too, so the status block becomes
     the same centred full-width row a card's does — "still open" and its
     badge on one line, the ages on the next. It used to be pinned
     `flex-start` here, which left the job page stacking them left while every
     card on the list centred them. */
  .job-title { font-size: 21px; }
  /* Full width on a phone, but still a *flex* row. `display: block` was the
     obvious way to widen it and quietly broke the layout: it overrode the base
     rule's `inline-flex`, so the icon — which is `display: block` itself —
     became a block-level sibling of the label and wrapped onto its own line,
     stretching the button to two rows. `text-align: center` never did anything
     either, because it does not centre flex items.

     `flex` is block-level, so it fills the row the same way, while keeping
     `align-items: center` and `gap` from the base rule. `justify-content` is
     what actually centres the pair. */
  .apply-primary { display: flex; justify-content: center; }
  .desc-body { padding: 16px; }
}


/* ----------------------------------------------------------- how it works --
   Prose plus one table. Wider measure than the job list would take, but the
   text is capped at ~68ch inside it: a reading column and a data column have
   different ideal widths, and the table is the thing that needs the room. */
/* One centred reading column rather than 68ch of prose pinned to the left
   of an 880px container — that left a wide gap down the right-hand side and
   read as a layout mistake. The cap lives on the container now and children
   fill it, so headings, prose and the table share one measure. */
.howto { max-width: 720px; margin: 0 auto; padding-bottom: 24px; }
/* The title and its lede are centred; everything below them is not. Centred
   text is for a masthead, not for reading — the same split the front page
   already makes, where the tally is centred and the listings are not. The lede
   takes the tagline's 62ch measure so the two centred blocks on the site share
   a width rather than each picking their own. */
.howto-title {
  margin: 26px 0 10px; font-size: 30px; font-weight: 800;
  letter-spacing: -0.02em; line-height: 1.2; text-align: center;
}
.howto-lede {
  max-width: 62ch; margin: 0 auto 20px; text-align: center;
  font-size: 17px; line-height: 1.6; color: var(--ink-soft);
}
/* The contents bar. `/privacy` is its only user since 2026-09-03, when
   /how-it-works folded its four method sections behind <details> and dropped
   the nav that linked them.

   **Never let two asterisks sit immediately before a slash in a CSS comment.**
   This comment opened by bolding a path — two asterisks, then the slash of the
   /privacy URL — which is ordinary markdown emphasis and the way the rest of
   this file is written, and that sequence is a comment terminator. Everything
   after it parsed as garbage, and the parser's error recovery swallowed the
   `.howto-toc` rule below it whole. The bar then rendered as inline anchors in
   a block: no flex, no gap, rows overlapping by 4px, and labels breaking
   mid-pill. It looked exactly like a spacing bug, which is why it was reported
   as one. Same family as the two dead selectors this file already records —
   a rule that does nothing is indistinguishable from one that works.

   These rules were deleted in that change and restored the same day: the class
   was grepped for in the template being edited and nowhere else, so a second
   page silently lost its styling and rendered a row of browser-default blue
   links — the `.howto` classes are shared by three templates, and this file
   already records that /privacy and /about ride on them. **Deleting a rule
   needs the same enumeration as changing a shared function**: ask who else
   names the class, not whether the page in front of you still does.

   Kept under the `howto-` prefix rather than renamed to `privacy-`: the whole
   block of prose-page rules is prefixed this way, and renaming one member of
   it for the page that happens to be its last user trades a slightly odd name
   for an inconsistent one. /about and /privacy already use `.howto`.

   Restored byte-for-byte from 6c7e826, not rewritten from memory — and it is
   byte-for-byte deliberately. A `white-space: nowrap` was briefly added here
   to stop "How long we keep it" splitting across a line end, and reported as a
   pre-existing flaw on the evidence that the restored rules matched the commit
   exactly. They did; they were simply never *applying*, because of the comment
   bug above. As flex items the pills size to max-content and do not break, so
   the property was treating a symptom of that. **Verifying that a rule matches
   the source it came from says nothing about whether the browser is using
   it** — the screenshot showed both faults at once and only one was real. */
.howto-toc {
  display: flex; flex-wrap: wrap; gap: 8px; margin: 0 0 8px;
  padding: 12px 0; border-top: 1px solid var(--line);
  border-bottom: 1px solid var(--line);
}
.howto-toc a {
  font-size: 13px; font-weight: 700; text-decoration: none;
  color: var(--ink-soft); background: var(--card);
  border: 1px solid var(--line); border-radius: 999px; padding: 4px 12px;
}
.howto-toc a:hover { color: var(--accent); border-color: var(--accent); }

/* Each method section on /how-it-works is now a plain-language paragraph that always shows, and
   a <details> holding the working. Two typographic jobs, so two rules: the
   plain answer reads at the lede's size in full-strength ink, the folded detail
   reads at body size — the reader who opens it has chosen to read closely.

   `.howto-toc` above is what /privacy still uses.

   `<summary>` is a control and is styled as one, on `--link` rather than
   `--accent` for the reason recorded at that token: the accent is 2.54:1 in
   light mode, and this sits in running prose. `list-style: none` plus the
   WebKit pseudo drops the UA marker on both engines, so the chevron below is
   the single affordance in both themes. */
.howto-plain { font-size: 16.5px; line-height: 1.65; }
.howto-more { margin: 0 0 13px; }
.howto-more > summary {
  display: inline-block; cursor: pointer; list-style: none;
  font-size: 14px; font-weight: 700; color: var(--link);
}
.howto-more > summary::-webkit-details-marker { display: none; }
.howto-more > summary::after {
  content: ""; display: inline-block; vertical-align: 2px; margin-left: 7px;
  width: 6px; height: 6px; border-right: 2px solid currentColor;
  border-bottom: 2px solid currentColor; transform: rotate(45deg) translate(-2px, -2px);
  transition: transform .15s ease;
}
.howto-more[open] > summary::after { transform: rotate(-135deg) translate(-3px, -3px); }
.howto-more[open] > summary { margin-bottom: 13px; }
.howto-more > summary:hover { color: var(--link-hover); }
.howto-more > summary:focus-visible {
  outline: 2px solid var(--accent); outline-offset: 3px; border-radius: 4px;
}
@media (prefers-reduced-motion: reduce) {
  .howto-more > summary::after { transition: none; }
}

.howto-section { margin: 30px 0 0; scroll-margin-top: 20px; }
.howto-section h2 {
  margin: 0 0 12px; font-size: 21px; font-weight: 800; letter-spacing: -0.01em;
}
.howto-section p { margin: 0 0 13px; line-height: 1.7; }
/* Sub-headings inside a fold, which only the features section needs: the four
   method sections each answer one question and their detail runs as prose,
   while that one covers four unrelated features and a reader opening it is
   usually after one of them.

   Deliberately quieter than the `h2` above it — 15px against 21px, and a top
   margin large enough to group each heading with the paragraphs under it
   rather than float between them. A sub-heading at the section heading's
   weight would read as four new sections that happen to be indented. */
.howto-more h3 {
  margin: 22px 0 8px; font-size: 15px; font-weight: 800;
  letter-spacing: -0.01em; color: var(--ink);
}
.howto-more > summary + h3 { margin-top: 0; }
.howto-list { margin: 0 0 13px; padding-left: 20px; line-height: 1.7; }
.howto-list li { margin: 0 0 9px; }
.howto-list strong, .howto-section strong { color: var(--ink); }
.howto-note { font-size: 13.5px; color: var(--ink-soft); }
/* The mismatch badge quoted as an *example* — in the legend and in a
   sentence on /how-it-works — which is a different job from the badge on a
   card, and wrong in two ways if it inherits the card's rule.

   `.chip-warn` is `white-space: normal` so a real badge can wrap a long list
   of countries down the side of a card. In running prose that breaks
   "⚠ ad says …" across two lines mid-badge, which reads as a rendering fault
   rather than as a specimen of a badge.

   And it is `cursor: help` because a card's badge carries a `title` naming the
   countries the ad demands. These copies carry no `title` and should not: the
   paragraph they sit in, and the `<dd>` beside the legend entry, already say
   more than a tooltip could. A help cursor over an element with nothing to
   show promises a tooltip that never arrives.

   Scoped to `p` and the legend rather than to `.howto-section`, which would
   also catch the scorecard's restriction chips — those are in table cells and
   genuinely need to wrap. */
.howto-section p .chip-warn,
.legend-item dt .chip-warn { white-space: nowrap; cursor: default; }
/* Prose links on /about and /privacy, which had no rule at all and were
   therefore browser-default blue — and visited-purple, on a site with two
   hand-built palettes. /how-it-works shares these classes and has no prose
   links of its own; its <details> toggles are <summary> elements rather than
   anchors, so this does not reach them — they take `--link` from their own
   rule above, which is the same token, so the page reads as one system either
   way. (It used to say the same about a table of contents, which was a <nav>;
   that was removed on 2026-09-03.)

   **This carried a `:not(.howto-foot)` guard until 2026-08-27**, because
   /how-it-works ended in a footer paragraph whose link was deliberately bold
   and not underlined, and (0,2,2) would have silently overridden its (0,1,1).
   That paragraph was removed with the scorecard caveats, so the guard was
   guarding nothing — and a `:not()` for a class that exists nowhere is the
   same failure it was written to prevent, a rule that does nothing reading
   exactly like one that works. Removed together, deliberately.

   Underlined, like `.desc-body a`: in running text, colour alone is not a
   link affordance, and these paragraphs are long. */
.howto p a,
.howto li a {
  color: var(--link); text-decoration: underline; text-underline-offset: 2px;
}
.howto p a:hover,
.howto li a:hover { color: var(--link-hover); }

/* The state the page spends its first three weeks in. Styled as a considered
   answer rather than an error, because that is what it is: a source needs 30
   checks on a field across 3 separate weeks before a number is publishable. */
.howto-pending {
  margin: 0 0 18px; padding: 14px 17px;
  background: var(--accent-soft); border: 1px solid var(--accent-line);
  border-radius: var(--radius);
}
.howto-pending p { margin: 0 0 8px; font-size: 14px; line-height: 1.6; }
.howto-pending p:last-child { margin-bottom: 0; }

/* Scrolls inside its own box rather than pushing the page sideways — the
   column is 880px and this table is not. */
.scorecard-wrap { overflow-x: auto; margin: 0 0 14px; }
.scorecard {
  width: 100%; border-collapse: collapse; font-size: 13.5px;
  background: var(--card); border: 1px solid var(--line);
  border-radius: var(--radius);
}
.scorecard th, .scorecard td {
  padding: 10px 13px; text-align: left; border-bottom: 1px solid var(--line);
  vertical-align: top;
}
.scorecard thead th {
  font-size: 11.5px; font-weight: 700; letter-spacing: .06em;
  text-transform: uppercase; color: var(--ink-muted); white-space: nowrap;
}
.scorecard tbody tr:last-child th, .scorecard tbody tr:last-child td {
  border-bottom: 0;
}
.scorecard .num { text-align: right; font-variant-numeric: tabular-nums; }
.scorecard thead .num { text-align: right; }
.src-cell { font-weight: 700; white-space: nowrap; }
.src-kind {
  display: block; font-size: 11.5px; font-weight: 600; color: var(--muted);
  text-transform: none; letter-spacing: 0;
}
/* A published floor. Green because it is the number the page exists to give,
   and because it is a claim we are standing behind. */
.acc { display: block; font-weight: 700; color: var(--good-ink); }
.acc-n { display: block; font-size: 11px; color: var(--muted); }
/* Measured but not yet publishable, and not-applicable. Both deliberately
   quiet: neither is a score, and neither should read as one. */
.acc-pending { color: var(--muted); cursor: help; }
.acc-none { color: var(--muted); font-size: 12px; cursor: help; }
.scorecard .chip { display: inline-block; margin: 0 4px 4px 0; }

@media (max-width: 560px) {
  .howto-title { font-size: 24px; }
  .howto-section h2 { font-size: 19px; }
  .scorecard th, .scorecard td { padding: 8px 10px; }
}

/* Related listings on a job page. A reader feature and the internal link
   graph: ~14k job pages had no inbound link at all, so most of the catalogue
   was reachable only from the sitemap. Every colour is a token — see the
   Theming note in CLAUDE.md and tests/test_theme.py. */
.related { margin-top: 34px; }
.related-h3 {
  margin: 18px 0 8px; font-size: 20px; font-weight: 600;
  color: var(--accent);
}
/* The company heading is a link where the employer has a page. `inherit`
   rather than a colour of its own, so the heading is pixel-identical whether
   it links or not — the affordance is the underline on hover, exactly as
   `.related-list a` does it a few lines down. Reaching for `--link` here would
   make one of the two headings in this block a different colour from the
   other for no reason a reader could name. */
.related-h3 a { color: inherit; text-decoration: none; }
.related-h3 a:hover { text-decoration: underline; }
.related-list { list-style: none; margin: 0; padding: 0; }
.related-list li {
  padding: 7px 0; border-bottom: 1px solid var(--line);
}
.related-list li:last-child { border-bottom: 0; }
.related-list a { color: var(--ink); text-decoration: none; }
.related-list a:hover { text-decoration: underline; }
.related-co { color: var(--ink-muted); font-size: 13px; margin-left: 6px; }

/* Pipeline listings — an always-open talent pool, not a dated vacancy.
   Uses the existing --warn-* tokens: this is a caution, not an error, and
   adding a token would need a dark counterpart (tests/test_theme.py).
   `text-transform: none` because .chip lowercases by default, which is right
   for a source or level pill and wrong for a label that reads as a sentence. */
.chip-pipeline {
  color: var(--warn-ink); background: var(--warn-bg);
  border-color: var(--warn-line); text-transform: none;
  cursor: help;
}
.pipeline-note {
  margin: 10px 0 0; padding: 10px 12px;
  background: var(--warn-bg-soft); border: 1px solid var(--warn-line);
  border-radius: var(--radius); color: var(--ink-soft);
  font-size: 14px; line-height: 1.5; max-width: 60ch;
}
.pipeline-note .chip-pipeline { margin-right: 6px; cursor: default; }

/* Masthead search, on every page type except the job list (which has its own
   live-search input in the filter bar). A plain GET form: there is no list to
   update in place on a job page, so htmx would buy nothing and this works with
   JavaScript off. */
.mast-search {
  display: flex; gap: 6px; justify-content: center;
  /* 40px matches the tagline's own margin, which is what sets the gap
     above the nav — so tagline -> nav -> search reads as one rhythm
     rather than two. */
  margin: 40px auto 0; max-width: 420px;
}
.mast-search input {
  flex: 1; min-width: 0; padding: 8px 12px; font: inherit; font-size: 14px;
  color: var(--ink); background: var(--card);
  border: 1px solid var(--line); border-radius: var(--radius);
}
.mast-search input:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 1px; }
/* Same padding and fill as `.filters-clear`, so the two filled icon-buttons on
   the site match.

   There is deliberately no counterpart in the job list's filter bar: that input
   searches as you type through htmx, so a submit button there would be a
   control that does nothing a reader has not already had done for them. This
   one is needed because the masthead form is a plain GET — Enter or this
   button are the only ways to submit it. */
.mast-search button {
  display: inline-flex; align-items: center; padding: 10px 14px;
  color: var(--on-accent); background: var(--accent);
  border: 1px solid var(--accent); border-radius: var(--radius); cursor: pointer;
}
.mast-search button:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 1px; }
/* The chevron-less icon inherits the button's colour, not the masthead accent
   the other .nav-icon rules set — on a filled control that would be 1.5:1. */
.mast-search .nav-icon { display: block; flex: none; width: 18px; height: 18px; color: currentColor; }

/* Empty results. The suggestion names the filter measured to be most
   restrictive, rather than telling the reader to guess. */
.empty-relax { color: var(--ink-soft); font-size: 14px; margin-top: 6px; }
/* Underlined, unlike the two bold links above: this one had neither bold nor
   an underline, so the bright accent was its only affordance — and a deeper,
   quieter green would have left it reading as ordinary emphasis. */
.empty-relax a { color: var(--link); text-decoration: underline; text-underline-offset: 2px; }
.empty-relax a:hover { color: var(--link-hover); }

/* ---- Company pages ------------------------------------------------------ */
/* The header reuses `.card` so an employer's page opens with the same frame a
   reader has just clicked from — one rule, the reason `.card, .job-head` share
   theirs. Only the type scale differs, because this is an <h1> and not a row. */
/* **It joins the shared frame by name rather than by taking `.card`**, which
   is what `.job-head` does and for the identical reason: `.card:hover` lifts
   and accents the whole card because on the list the whole card is a link, and
   on a page heading that promises a click that does nothing. This header
   carried `class="card co-head"` until 2026-08-27 and hovered — the one
   difference the job page had been careful to avoid, reintroduced by reaching
   for the class instead of the rule. Nothing else was lost: every other
   `.card` rule is either the shared frame above or scoped to a child class
   (`.card-brand`, `.card-main`) that this header still carries.

   Centred, like a landing page's heading and for the same reason: this is a
   page about one subject rather than a row in a list, so the identity block
   reads as a title rather than as the first card. `.card-brand` keeps its
   fixed `--brand-w` column in the flex row, so centring the text alone would
   leave it off-centre by half that width — the whole header goes to a stacked,
   centred block instead. */
.co-head { display: block; text-align: center; }
.co-head .card-brand { display: block; width: auto; margin: 0 0 10px; }
.co-name { font-size: 24px; line-height: 1.25; margin: 0; }
.co-sub {
  color: var(--ink-soft); font-size: 14px; margin: 6px auto 0;
  line-height: 1.55; max-width: 62ch;
}

/* Facts before listings. Auto-fit rather than a fixed column count: a company
   with pay data shows three blocks and one without shows two, and a fixed grid
   would leave a hole where the absent metric would have been — which reads as
   something failing to load rather than as a metric we decline to publish. */
.co-facts {
  display: grid; gap: 14px; margin: 18px 0 26px;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
}
.co-fact {
  background: var(--card); border: 1px solid var(--line);
  border-radius: 10px; padding: 14px 16px;
}
.co-fact h2 { font-size: 13px; letter-spacing: .04em; text-transform: uppercase;
              color: var(--ink-soft); margin: 0 0 10px; }
.co-chips { list-style: none; display: flex; flex-wrap: wrap; gap: 6px; margin: 0; padding: 0; }
/* `.chip` lowercases by default and every variant opts back out — `.chip-loc`,
   `.chip-src`, `.chip-pay` all carry `text-transform: none`. The function chips
   here use the bare class, so without this they rendered "sales & marketing"
   beside "United States". (0,1,1) against `.chip`'s (0,1,0), so this wins. */
.co-chips a { text-decoration: none; text-transform: none; }
/* The count inside a chip. Dimmer than the label so the place reads first and
   the number qualifies it, rather than the two competing at one weight. */
.co-n { opacity: .65; font-variant-numeric: tabular-nums; }
.co-note { color: var(--ink-soft); font-size: 12.5px; line-height: 1.5; margin: 10px 0 0; }
.co-bands { list-style: none; margin: 0; padding: 0; }
.co-bands li { margin: 0 0 8px; }
.co-bands strong { font-size: 17px; display: block; }
.co-bands .co-note { margin-top: 2px; }

.co-listings-head { font-size: 15px; letter-spacing: .04em; text-transform: uppercase;
                    color: var(--ink-soft); margin: 0 0 12px; }
.co-more { margin-top: 14px; }
.co-evidence { color: var(--ink-soft); font-size: 13.5px; line-height: 1.6;
               margin: 26px 0 0; padding-top: 16px; border-top: 1px solid var(--line); }

/* `--link`, not `--accent`: these sit in running text, and the accent is
   2.54:1 on the light theme's ground. The same rule every prose link on the
   site follows, and the reason the token exists at all. */
.co-note a, .co-evidence a { color: var(--link); text-decoration: underline; text-underline-offset: 2px; }
.co-note a:hover, .co-evidence a:hover { color: var(--link-hover); }

@media (max-width: 700px) {
  .co-name { font-size: 20px; }
  .co-facts { grid-template-columns: 1fr; }
}

/* The link from a listing to its employer's page. Bold rather than underlined
   is this link's own affordance, like `.notfound a`; only the colour comes
   from the token, because `--accent` is 2.54:1 on the light theme's ground. */

/* ---- Browse companies --------------------------------------------------- */
/* Centred, and the same 420px measure as `.mast-search` — the two boxes sit on
   the same page and reading as one control is the point. */
.cobrowse-find { margin: 18px auto 10px; max-width: 420px; text-align: center; }
/* The masthead input's rule, restated. A bare `<input type="search">` matched
   neither `.filters input` (scoped to the job list's filter bar) nor
   `.mast-search input`, so it rendered as the browser's default white box on a
   dark page — visible only in a screenshot, which is why one was taken.
   Every value here is `.mast-search input`'s, so the two cannot look different.

   **No submit button, unlike the masthead**, and the stylesheet already argues
   the case at `.mast-search button`: that form is a plain GET, so Enter or the
   button are the only ways to send it, while the job list's live-search input
   deliberately has none because "a submit button there would be a control that
   does nothing a reader has not already had done for them". This filters as
   you type, so it is the second case. */
.cobrowse-find input[type="search"] {
  width: 100%; padding: 8px 12px; font: inherit; font-size: 14px;
  color: var(--ink); background: var(--card);
  border: 1px solid var(--line); border-radius: var(--radius);
}
.cobrowse-find input[type="search"]:focus-visible {
  outline: 2px solid var(--focus-ring); outline-offset: 1px;
}
.cobrowse-count { color: var(--ink-soft); font-size: 13px; margin: 6px 0 0; min-height: 1em; }

/* All 27 entries render whether or not they hold anything, so the bar keeps its
   shape as the catalogue moves. `.az-off` is set both server-side (a letter with
   no companies) and by the filter script (a letter with no matches) — one class,
   so the two cannot look different. */
.cobrowse-az {
  display: flex; flex-wrap: wrap; gap: 4px; margin: 0 0 22px;
  padding: 10px 12px; background: var(--card);
  border: 1px solid var(--line); border-radius: 10px;
}
.cobrowse-az a, .cobrowse-az span {
  min-width: 26px; text-align: center; padding: 3px 4px; border-radius: 6px;
  font-size: 14px; font-weight: 600; text-decoration: none; color: var(--ink);
}
.cobrowse-az a:hover { background: var(--accent); color: var(--on-accent); }
.cobrowse-az .az-off { color: var(--ink-soft); opacity: .45; pointer-events: none; }

.cobrowse-group { margin: 0 0 22px; }
.cobrowse-letter {
  font-size: 13px; letter-spacing: .08em; color: var(--ink-soft);
  margin: 0 0 8px; padding-bottom: 6px; border-bottom: 1px solid var(--line);
}
.cobrowse-list {
  list-style: none; margin: 0; padding: 0;
  display: grid; gap: 2px 18px;
  grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
}
.cobrowse-list li { display: flex; align-items: baseline; gap: 8px; padding: 3px 0; }
/* **`[hidden]` is a UA-stylesheet `display: none`, so any author `display`
   beats it.** The filter sets `li.hidden = true` and the row went on showing,
   because the rule above wins the cascade — typing "canonical" left every
   company under C on screen. `.consent[hidden]` exists a few hundred lines up
   for exactly this reason; the group carries one too so that adding a
   `display` to `.cobrowse-group` later cannot reintroduce it. */
.cobrowse-list li[hidden], .cobrowse-group[hidden] { display: none; }
.cobrowse-list a { text-decoration: none; color: var(--ink); font-weight: 600; }
.cobrowse-list a:hover { color: var(--accent); }
/* A company with nothing open keeps its listing here — the page is `noindex`
   but served, and this index is what makes it reachable at all. */
.cobrowse-list .co-none { font-weight: 400; font-size: 12px; }
.cobrowse-empty { color: var(--ink-soft); margin: 8px 0 0; }
.cobrowse-empty a { color: var(--link); text-decoration: underline; text-underline-offset: 2px; }
.cobrowse-empty a:hover { color: var(--link-hover); }

/* ---- Salary evidence band -------------------------------------------------
   What comparable listings disclose, on a job whose employer stated nothing.

   **It must not be mistakable for a disclosed figure**, which is the whole
   design constraint: it is dashed rather than solid, and the number takes
   `--ink-soft` rather than the confident ink a real salary gets. Same reasoning
   as `.chip-pay-est` — a board's estimate is greyed because it is a guess, and
   this is an inference over other people's listings, which is further still
   from a figure this employer offered. */
.ev-band { border-style: dashed; }
.ev-answer-band { color: var(--ink-soft); font-weight: 600; }
.ev-band .ev-note strong { color: var(--ink); }

/* ---- Saved jobs and saved searches ---------------------------------------
   The control is icon-only and ships *unmarked*: every page is byte-identical
   for every visitor and cached accordingly, so saved state is not knowable at
   render time — `saved.js` marks it after load, the same constraint the theme
   solves with its pre-paint script.

   No new tokens. The resting state is `--ink-muted`, which is the chrome ink
   for things meant to be clicked, and the saved state is `--link` rather than
   `--accent`: the accent is 2.54:1 in light mode, recorded at the Clear button
   as a deliberate exception for one filled control, and a 20px outline glyph
   is not that. `--link` is the deeper green in light and the accent yellow in
   dark, so it reads in both without a second definition. */
.savebtn {
  display: inline-flex; align-items: center; justify-content: center;
  flex: none; width: 28px; height: 28px; padding: 0;
  background: none; border: 0; border-radius: 8px;
  color: var(--ink-muted); cursor: pointer;
  transition: color .15s ease, background .15s ease;
}
.savebtn-icon { display: block; width: 20px; height: 20px; }
.savebtn:hover { color: var(--link); background: var(--neutral-bg); }
.savebtn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* Saved: the mark fills. A mark that only changes colour reads as a hover
   state, and this control has no label to say which way it went — the
   `aria-label` swaps for the same reason. */
/* **`--accent`, not `--link`** (2026-09-13). The saved state had the prose-link
   green, which in the light theme is `#0B6B47` — correct for a sentence and
   wrong for a 20px filled mark, where it reads as a muddy near-black rather
   than as the site's green. It now matches the filled controls around it: the
   search button, Clear filters, the nav badge.

   Known and accepted: `#10B981` is 2.54:1 on white, under the 3:1 WCAG asks of
   a meaningful graphic. The same number is recorded at the light theme's Clear
   button as a deliberate exception; `--accent-hover` (`#0DA271`, 3.27:1) is the
   same green a shade deeper if that bar is ever the one to meet. The mark is
   never the only carrier of this state — the `aria-label` and `title` both say
   "Remove from saved". */
.savebtn.is-saved { color: var(--accent); }
/* Or hovering a saved control would return it to the green above. */
.savebtn.is-saved:hover { color: var(--accent); }
.savebtn.is-saved .savebtn-icon path { fill: currentColor; }
/* `[hidden]` is a UA-stylesheet `display: none` and the rule above is an
   author `display`, which beats it — see tests/test_hidden_display.py, and the
   two times this exact omission shipped. */
.savebtn[hidden] { display: none; }

/* The search control sits in the filter row beside Clear, so it matches that
   button's height rather than the card's compact 28px. */
.savebtn-search {
  width: auto; height: auto; padding: 10px 12px;
  border: 1px solid var(--line); background: var(--card);
}
.savebtn-search:hover { border-color: var(--accent-line); }

/* Masthead count. Sits against the label rather than on the icon: the icons in
   this nav are 20px marks that carry no badge anywhere else, and a number on
   one would read as part of the mark. */
.nav-count {
  display: inline-block; margin-left: 6px; padding: 0 6px;
  border-radius: 999px; font-size: 11px; font-weight: 700; line-height: 18px;
  background: var(--accent); color: var(--on-accent);
}
.nav-count[hidden] { display: none; }

.saved-block { margin: 28px 0 0; }
.saved-h {
  margin: 0 0 10px; font-size: 15px; font-weight: 700; color: var(--ink);
  padding-bottom: 8px; border-bottom: 1px solid var(--line);
}
.saved-empty, .saved-note { color: var(--ink-soft); margin: 8px 0 0; }
.saved-empty[hidden], .saved-note[hidden] { display: none; }
.saved-empty a, .saved-note a {
  color: var(--link); text-decoration: underline; text-underline-offset: 2px;
}
.saved-empty a:hover, .saved-note a:hover { color: var(--link-hover); }

.ssearch-list { list-style: none; margin: 0; padding: 0; }
.ssearch {
  display: flex; align-items: center; gap: 10px;
  padding: 10px 12px; margin-bottom: 6px;
  background: var(--card); border: 1px solid var(--line);
  border-radius: var(--radius);
}
/* `flex: 0 1 auto` rather than `1 1 auto`, since 2026-09-13: the label may be
   followed by a state line ("(Weekly alert active)") and a growing anchor would
   push that away from the search it describes, to the far side of the row. The
   slack goes to the icon buttons instead, via `margin-left: auto` on the first
   of them, so the row still reads label-left / controls-right. */
.ssearch a {
  flex: 0 1 auto; color: var(--link); font-weight: 600;
  text-decoration: none; overflow-wrap: anywhere;
}
/* Not a link and not a button: a fact about the subscription, said in words
   because a mark can say "something is here" and only text can say what. */
.ssearch-state { flex: 0 1 auto; font-size: 13px; color: var(--ink-muted); }
.ssearch a:hover { color: var(--link-hover); text-decoration: underline; }
.ssearch-drop {
  display: inline-flex; align-items: center; justify-content: center;
  flex: none; width: 28px; height: 28px; padding: 0;
  background: none; border: 0; border-radius: 6px;
  color: var(--accent); cursor: pointer;
}
/* Sized here rather than by the SVG's own width/height, which are dropped when
   it is inlined — the same reason `.nav-icon` carries its size. `flex: none`
   for the same reason too: this is a flex item inside a flex item, so without
   it the row shrinks the mark before it shrinks the label beside it. */
.ssearch-drop-icon { display: block; flex: none; width: 16px; height: 16px; }
.ssearch-drop:hover { background: var(--neutral-bg); }
.ssearch-drop:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* The alert control, and the row it sits in had to learn to wrap.
   `.ssearch` is a one-line flex row, so the signup form — which is a whole
   second line — needs `flex-wrap` on the row and `width: 100%` on itself, or
   it is squeezed into the gap beside a truncated label. `order` is deliberately
   not used: the form follows its own toggle in the markup, and a visual order
   that disagreed with the DOM order is what makes a keyboard tab sequence jump
   about. */
.ssearch { flex-wrap: wrap; }
/* The bell shares `.ssearch-drop`'s box, so the two marks on a row are the
   same size and sit on the same baseline — a legend that redraws its subject
   at a different size teaches the wrong shape, and so does a row whose two
   controls disagree about what an icon button is.

   **The active state is a different glyph and nothing else.** A mark that only
   changed colour would read as a hover state, which `_savebtn.html` already
   records — the crossed bell carries the whole difference, so `is-on` sets no
   colour at all and the control stays the accent in both states, like every
   other icon button on the row. It was briefly muted grey on the reasoning
   that a settled state should be quieter than an invitation; on the page that
   read as disabled, which is the opposite of what it means. */
.ssearch-bell { margin-left: auto; }
/* The line the bell-off state opens. No `display` of its own, so the `hidden`
   attribute works through the UA stylesheet — the trap
   `tests/test_hidden_display.py` exists for. */
.ssearch-stop { flex: 1 1 100%; padding-top: 10px; border-top: 1px solid var(--line); }
/* `[hidden]` is honoured through the UA stylesheet's `display: none`, which any
   author `display` beats — so a rule giving this a `display` must never be
   unqualified. It is `:not([hidden])`, and `tests/test_hidden_display.py` holds
   the registry of every element on this site toggled that way. */
.ssearch-alert:not([hidden]) {
  display: flex; flex-wrap: wrap; align-items: center; gap: 8px;
  width: 100%; margin: 4px 0 0; padding-top: 10px;
  border-top: 1px solid var(--line);
}
.ssearch-alert input[type="email"] {
  flex: 1 1 220px; font: inherit; color: var(--ink); background: var(--card);
  border: 1px solid var(--line); border-radius: var(--radius); padding: 8px 12px;
}
.ssearch-alert input[type="email"]:focus-visible {
  outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px var(--focus-ring);
}
.ssearch-alert button {
  flex: none; font: inherit; font-weight: 700; cursor: pointer;
  color: var(--on-accent); background: var(--accent);
  border: 1px solid var(--accent); border-radius: var(--radius); padding: 8px 16px;
}
.ssearch-alert button:hover { filter: brightness(1.06); }
.ssearch-alert-note {
  flex: 1 1 100%; margin: 0; font-size: 13px; color: var(--ink-soft);
  line-height: 1.5;
}

/* The manage page. A definition list rather than a table: four pairs are not
   tabular data, and a `<dl>` keeps the label attached to its value when the
   two stack on a phone. */
.alert-detail { margin: 0 0 14px; }
.alert-detail dt {
  font-size: 13px; font-weight: 700; color: var(--ink-soft);
  text-transform: uppercase; letter-spacing: 0.04em; margin-top: 12px;
}
.alert-detail dd { margin: 2px 0 0; line-height: 1.6; overflow-wrap: anywhere; }
.alert-detail dd a { color: var(--link); font-weight: 600; }
.alert-detail dd a:hover { color: var(--link-hover); text-decoration: underline; }
/* Outlined rather than filled, and in the one red this site has. A destructive
   control drawn in the accent would read as the page's primary action, which on
   a page a reader reached from their own email is exactly backwards — they are
   far more often here to look than to stop. */
.alert-stop {
  font: inherit; font-weight: 700; cursor: pointer;
  color: var(--aged-ink); background: none;
  border: 1px solid var(--aged-ink); border-radius: var(--radius);
  padding: 10px 20px;
}
.alert-stop:hover { color: var(--on-accent); background: var(--aged-ink); }
.alert-stop:focus-visible { outline: 2px solid var(--aged-ink); outline-offset: 2px; }

/* The confirmation button, which is the one press the whole double opt-in
   rests on. Filled, because here it genuinely is the page's only action. */
.btn-primary {
  font: inherit; font-weight: 700; cursor: pointer;
  color: var(--on-accent); background: var(--accent);
  border: 1px solid var(--accent); border-radius: var(--radius);
  padding: 12px 24px; margin: 6px 0 10px;
}
.btn-primary:hover { filter: brightness(1.06); }
.btn-primary:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* The page's two actions, drawn as the masthead nav rather than as prose links.
   They are chrome — things you can do to this page — and the nav is the shape
   this site already uses to say so; underlined green text would have read as a
   sentence with two links in it.

   Real `<button>`s, not `<a href="#">`: neither goes anywhere, and a link that
   does not link is what a screen reader announces wrongly. Centred because,
   unlike the nav, they are the only thing on their line.

   `font: inherit` first and then the nav's own size and weight, because a
   button does not inherit typography from the page the way an anchor does. */
.saved-actions {
  margin: 16px 0 0; display: flex; flex-wrap: wrap;
  justify-content: center; gap: 8px 18px;
  font-size: 15px; font-weight: 600;
}
/* `[hidden]` is a UA-stylesheet `display: none` and the rule above is an author
   `display: flex`, which beats it. Registered in
   tests/test_hidden_display.py — this is the third element in this feature to
   need the guard and the trap has shipped twice before. */
.saved-actions[hidden] { display: none; }
.pageact {
  display: inline-flex; align-items: center; gap: 7px; white-space: nowrap;
  font: inherit; font-size: 15px; font-weight: 600;
  color: var(--ink-soft); background: none; border: 0; padding: 0;
  cursor: pointer;
}
.pageact:hover { color: var(--accent); }
.pageact:focus-visible {
  outline: 2px solid var(--accent); outline-offset: 3px; border-radius: 4px;
}
/* The accent sits on the mark rather than being inherited from the button,
   whose colour is `--ink-soft` and flips on hover — exactly the split
   `.nav-icon` makes, and for the same reason: left grey these read as
   decoration and the row reads as a caption. */
.pageact-icon {
  display: block; flex: none; width: 18px; height: 18px;
  color: var(--accent);
}

/* The one action that genuinely is part of a sentence: "…are no longer in our
   catalogue. Remove them." It keeps prose-link styling for that reason, and
   `--link` rather than `--accent` for the reason recorded at that token —
   `--accent` is 2.54:1 on white and this sits in running text. Still a real
   `<button>`, since it goes nowhere. */
.linkish {
  font: inherit; padding: 0; background: none; border: 0; cursor: pointer;
  color: var(--link); text-decoration: underline; text-underline-offset: 2px;
}
.linkish:hover { color: var(--link-hover); }
.linkish:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* The country page's link to the wider regional set. `--link`, not `--accent`:
   this is running prose, and the accent green is 2.54:1 on the light
   background — see the note at that token. */
.landing-wider { margin: 6px 0 0; font-size: 14px; }
.landing-wider a { color: var(--link); text-decoration: underline; }
