/* ============================================================================
   Remote job board — design system
   Dark-first, compact, zero framework. ~9 KB.
   ========================================================================== */

:root {
  --bg: #0b0d12;
  --surface: #12151c;
  --surface-2: #171b24;
  --surface-hover: #1b2029;
  --border: rgba(255, 255, 255, 0.07);
  --border-strong: rgba(255, 255, 255, 0.13);
  --text: #e8eaed;
  --text-muted: #8b92a4;
  --text-dim: #656c7d;
  --accent: #c8f751;
  --accent-ink: #0b0d12;
  --accent-glow: rgba(200, 247, 81, 0.16);
  --warn: #ffb86b;
  --radius: 8px;
  --radius-lg: 12px;
  --max: 1140px;
  --row-h: 68px;
  --font: -apple-system, BlinkMacSystemFont, 'Segoe UI', Inter, Roboto, Helvetica, Arial, sans-serif;
  --mono: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, monospace;
}

/* ---------- Theme resolution ---------------------------------------------
   Three states, resolved with the cascade rather than JavaScript:

     auto (default)  no data-theme attribute -> follows prefers-color-scheme
     light           data-theme="light"      -> always light
     dark            data-theme="dark"       -> always dark

   Doing "auto" in CSS instead of JS means the correct theme paints on the very
   first frame with no flash, and it keeps working with JavaScript disabled.
   The tiny inline script in <head> only runs when the visitor has made an
   EXPLICIT choice, which is the only case CSS cannot express.
   ------------------------------------------------------------------------ */

/* auto -> light, when the visitor's OS asks for light and they have not chosen */
@media (prefers-color-scheme: light) {
  :root:not([data-theme]) {
    --bg: #fbfbfd;
    --surface: #ffffff;
    --surface-2: #f4f5f8;
    --surface-hover: #f0f2f6;
    --border: rgba(10, 12, 20, 0.09);
    --border-strong: rgba(10, 12, 20, 0.16);
    --text: #14171f;
    --text-muted: #5b6373;
    --text-dim: #7b8494;
    --accent: #2f6f2f;
    --accent-ink: #ffffff;
    --accent-glow: rgba(47, 111, 47, 0.1);
  }
}

/* explicit light, chosen via the toggle */
[data-theme='light'] {
  --bg: #fbfbfd;
  --surface: #ffffff;
  --surface-2: #f4f5f8;
  --surface-hover: #f0f2f6;
  --border: rgba(10, 12, 20, 0.09);
  --border-strong: rgba(10, 12, 20, 0.16);
  --text: #14171f;
  --text-muted: #5b6373;
  --text-dim: #7b8494;
  --accent: #2f6f2f;
  --accent-ink: #ffffff;
  --accent-glow: rgba(47, 111, 47, 0.1);
}

/* explicit dark wins over a light system preference */
[data-theme='dark'] {
  --bg: #0b0d12;
  --surface: #12151c;
  --surface-2: #171b24;
  --surface-hover: #1b2029;
  --border: rgba(255, 255, 255, 0.07);
  --border-strong: rgba(255, 255, 255, 0.13);
  --text: #e8eaed;
  --text-muted: #8b92a4;
  --text-dim: #656c7d;
  --accent: #c8f751;
  --accent-ink: #0b0d12;
  --accent-glow: rgba(200, 247, 81, 0.16);
}

/* ---------- The hero wash ------------------------------------------------
   Two companion colours for the glow behind the headline.

   They used to be a fixed cyan and a fixed violet, which was invisible on the
   shipped lime accent and obvious on anything else: a board themed rosewood
   still had a blue-and-purple haze over its own headline. So where the browser
   can compute a colour from another colour, these are the accent rotated
   either side of its own hue — an analogous set that follows whatever the
   owner picked. Where it cannot, they are exactly the two colours from before,
   so nothing about the default board changes.
   ------------------------------------------------------------------------ */
:root {
  --wash-a: #6ee7ff;
  --wash-b: #b98cff;
  --wash-a-soft: rgba(110, 231, 255, .12);
}
@supports (color: rgb(from red r g b)) {
  :root {
    --wash-a: oklch(from var(--accent) l c calc(h + 58));
    --wash-b: oklch(from var(--accent) l c calc(h - 58));
    --wash-a-soft: oklch(from var(--accent) l c calc(h + 58) / .12);
  }
}

/* ---------- Native controls ----------------------------------------------
   Checkboxes, radios, select popups, scrollbars, date pickers and focus rings
   are drawn by the browser, and the browser has no idea what the variables
   above say. Without this it uses the VISITOR's system preference: a visitor
   on a light machine reading this site in dark got white checkboxes and a
   white select popup on a black page, and vice versa.

   Same three states, same order — the :not() carries higher specificity than
   :root, so the auto case wins wherever it applies, and an explicit choice
   overrides both.
   ------------------------------------------------------------------------ */
:root { color-scheme: dark; }
[data-theme='light'] { color-scheme: light; }
[data-theme='dark'] { color-scheme: dark; }
@media (prefers-color-scheme: light) {
  :root:not([data-theme]) { color-scheme: light; }
}

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

html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }
@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: var(--font);
  font-size: 15px;
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
  font-feature-settings: 'tnum' 0;
}

/* An author `display` rule beats the user agent's `[hidden] { display: none }`,
   because the UA sheet is the weakest origin in the cascade. Any element that
   sets its own display AND gets hidden from script therefore stays on screen.
   That was not a hypothetical: `.job-row` is `display: grid`, so every filter on
   the browse page — search, category, region, type, salary — computed the right
   answer, updated the count to "4 of 18", and then hid nothing at all. */
[hidden] { display: none !important; }

a { color: inherit; text-decoration: none; }
a:focus-visible, button:focus-visible, input:focus-visible, select:focus-visible, summary:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: 4px;
}

.wrap { max-width: var(--max); margin: 0 auto; padding: 0 20px; }
.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}
.skip-link {
  position: absolute; left: 12px; top: -60px; z-index: 100;
  background: var(--accent); color: var(--accent-ink);
  padding: 10px 16px; border-radius: var(--radius); font-weight: 600;
  transition: top .16s ease;
}
.skip-link:focus { top: 12px; }

/* ---------- Header ------------------------------------------------------- */

.site-header {
  position: sticky; top: 0; z-index: 50;
  height: 56px; display: flex; align-items: center;
  background: color-mix(in srgb, var(--bg) 82%, transparent);
  border-bottom: 1px solid transparent;
  transition: border-color .2s ease, background .2s ease;
}
.site-header.is-stuck {
  backdrop-filter: saturate(180%) blur(14px);
  -webkit-backdrop-filter: saturate(180%) blur(14px);
  border-bottom-color: var(--border);
}
.site-header .wrap { display: flex; align-items: center; gap: 16px; width: 100%; }

.brand { display: flex; align-items: center; gap: 9px; font-weight: 700; font-size: 15.5px; letter-spacing: -0.02em; flex-shrink: 0; }
.brand-mark {
  width: 26px; height: 26px; border-radius: 7px; flex-shrink: 0;
  background: linear-gradient(135deg, var(--accent), color-mix(in srgb, var(--accent) 55%, var(--wash-a)));
  display: grid; place-items: center; color: var(--accent-ink); font-size: 13px; font-weight: 800;
}
.header-nav { display: flex; align-items: center; gap: 4px; margin-left: auto; }
.header-nav a.nav-link {
  padding: 7px 11px; border-radius: var(--radius); color: var(--text-muted);
  font-size: 13.5px; font-weight: 500; transition: color .15s ease, background .15s ease;
}
.header-nav a.nav-link:hover { color: var(--text); background: var(--surface-2); }

/* The optional employer sign-in link. It lives in the bar rather than in the
   collapsing panel, so on a narrow screen it competes for space with the theme
   toggle, the primary call to action and the menu button — and of those four it
   is the one that can go. The footer carries it, and the panel does not need a
   fifth item crowding the row. */
.header-nav a.nav-signin { color: var(--text-dim); }
.header-nav a.nav-signin:hover { color: var(--text); }
@media (max-width: 560px) {
  .header-nav a.nav-signin { display: none; }
}

/* The header is one row and cannot wrap — it is sticky and a fixed height — so
   an overflowing header widens the whole document and every page below it
   scrolls sideways, which is what makes a site feel broken on a phone.

   Until 1.4 the answer was to drop links: secondary ones below 700px, all of
   them below 460px. That kept the bar intact and made the menu disappear. The
   links now move into a panel instead — see "Mobile navigation" further down. */
@media (max-width: 460px) {
  .header-nav { gap: 6px; }
  .brand { font-size: 15px; }
  .header-nav .btn-primary { padding: 8px 12px; }
}
@media (max-width: 340px) {
  /* Wordmark goes, logo stays, so the CTA is never squeezed off the screen. */
  .brand > span:not(.brand-mark) { display: none; }
}

.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 7px;
  padding: 8px 15px; border-radius: var(--radius); border: 1px solid var(--border-strong);
  background: var(--surface-2); color: var(--text);
  font-size: 13.5px; font-weight: 600; font-family: inherit; cursor: pointer;
  transition: transform .12s ease, background .15s ease, border-color .15s ease;
}
.btn:hover { background: var(--surface-hover); border-color: var(--border-strong); }
.btn:active { transform: translateY(1px); }
.btn-primary { background: var(--accent); color: var(--accent-ink); border-color: transparent; font-weight: 700; }
.btn-primary:hover { background: color-mix(in srgb, var(--accent) 88%, white); }
.btn-lg { padding: 12px 22px; font-size: 15px; }
.btn-block { width: 100%; }
.btn-icon { padding: 8px; width: 34px; height: 34px; }

/* ---------- Hero --------------------------------------------------------- */

.hero { position: relative; overflow: hidden; border-bottom: 1px solid var(--border); }
/* padding-block, not the shorthand: `.hero-inner` sits on the same element as
   `.wrap`, and a `padding` shorthand there wipes out the 20px inline padding
   every other section on the page uses — which is why the headline sat 20px
   left of the job list below it. */
/* No max-height. `.hero` is overflow: hidden (it clips the aurora), so a height
   cap on the inner column does not shrink the content — it cuts the bottom off
   it. On a 700px-tall window, 38vh hid the tag chips and the stats line
   entirely, and the shorter the window the more of the hero disappeared. */
.hero-inner { position: relative; z-index: 2; padding-block: 36px 24px; }
@media (max-width: 760px) { .hero-inner { padding-block: 30px 22px; } }

/* Aurora background. transform/opacity only, so it stays off the main thread.
   Never the LCP element — the headline paints first and does not wait on this. */
.aurora { position: absolute; inset: -40% -10% auto -10%; height: 320px; z-index: 1; filter: blur(72px); opacity: .5; pointer-events: none; }
.aurora span { position: absolute; display: block; border-radius: 50%; will-change: transform; }
.aurora span:nth-child(1) { width: 460px; height: 300px; left: 4%;  background: radial-gradient(circle, var(--accent) 0%, transparent 68%); animation: drift1 calc(19s * var(--fx-speed, 1)) ease-in-out infinite; }
.aurora span:nth-child(2) { width: 380px; height: 280px; left: 38%; background: radial-gradient(circle, var(--wash-a) 0%, transparent 68%); animation: drift2 calc(23s * var(--fx-speed, 1)) ease-in-out infinite; }
.aurora span:nth-child(3) { width: 420px; height: 260px; right: 6%; background: radial-gradient(circle, var(--wash-b) 0%, transparent 68%); animation: drift3 calc(27s * var(--fx-speed, 1)) ease-in-out infinite; }
@keyframes drift1 { 0%,100% { transform: translate3d(0,0,0) scale(1); } 50% { transform: translate3d(60px,26px,0) scale(1.13); } }
@keyframes drift2 { 0%,100% { transform: translate3d(0,0,0) scale(1.05); } 50% { transform: translate3d(-50px,34px,0) scale(.92); } }
@keyframes drift3 { 0%,100% { transform: translate3d(0,0,0) scale(.95); } 50% { transform: translate3d(-38px,-22px,0) scale(1.1); } }
@media (prefers-color-scheme: light) { :root:not([data-theme]) .aurora { opacity: .3; } }
[data-theme='light'] .aurora { opacity: .3; }

h1.hero-title {
  margin: 0 0 11px; font-size: clamp(28px, 4.4vw, 44px); line-height: 1.08;
  letter-spacing: -0.035em; font-weight: 800; max-width: 17ch;
  /* Even out the lines instead of filling each one before wrapping. A headline
     is the one place on the page where the browser's greedy line breaking is
     plainly wrong: it produced "Remote jobs that / actually pay well." with the
     highlighted word alone at the start of line two, which is what the whole
     hero is judged on. Ignored by anything that does not support it, which
     leaves the greedy break — no worse than before. */
  text-wrap: balance;
}
.hero-title .accent { color: var(--accent); }
.hero-sub {
  margin: 0 0 16px; color: var(--text-muted); font-size: 16px; max-width: 56ch;
  text-wrap: pretty;   /* no single-word last line */
}

.hero-search { display: flex; gap: 8px; max-width: 540px; margin-bottom: 12px; }
.hero-search input {
  flex: 1; padding: 11px 14px; border-radius: var(--radius);
  border: 1px solid var(--border-strong); background: var(--surface);
  color: var(--text); font-size: 15px; font-family: inherit;
}
.hero-search input::placeholder { color: var(--text-dim); }

.chip-row { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 14px; }
.chip {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 5px 11px; border-radius: 999px; border: 1px solid var(--border);
  background: var(--surface); color: var(--text-muted);
  font-size: 12.5px; font-weight: 500; cursor: pointer; font-family: inherit;
  transition: transform .13s cubic-bezier(.34,1.56,.64,1), background .15s ease, color .15s ease, border-color .15s ease;
}
.chip:hover { color: var(--text); border-color: var(--border-strong); background: var(--surface-2); }
.chip[aria-pressed='true'], .chip.is-active {
  background: var(--accent); color: var(--accent-ink); border-color: transparent; font-weight: 650;
  transform: scale(1.04);
}
.hero-stats { color: var(--text-dim); font-size: 13px; }
.hero-stats strong { color: var(--text); font-weight: 650; font-variant-numeric: tabular-nums; }

/* ---------- Filter bar --------------------------------------------------- */

.filter-bar {
  position: sticky; top: var(--header-h, 56px); z-index: 40;
  background: color-mix(in srgb, var(--bg) 90%, transparent);
  backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px);
  border-bottom: 1px solid var(--border); padding: 10px 0;
}
.filter-bar .wrap { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.select {
  appearance: none; padding: 7px 30px 7px 11px; border-radius: var(--radius);
  border: 1px solid var(--border); background: var(--surface); color: var(--text-muted);
  font-size: 13px; font-family: inherit; cursor: pointer;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6' viewBox='0 0 10 6'%3E%3Cpath d='M1 1l4 4 4-4' stroke='%238b92a4' stroke-width='1.5' fill='none' stroke-linecap='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat; background-position: right 10px center;
}
.select:hover { border-color: var(--border-strong); color: var(--text); }
.result-count { margin-left: auto; color: var(--text-dim); font-size: 12.5px; font-variant-numeric: tabular-nums; }

/* ---------- Job rows ----------------------------------------------------- */

.job-list { margin: 0; padding: 0; list-style: none; }

/* Seven children: logo, main, tags, salary, region, age, apply.
   The column count must match exactly or items wrap onto a second implicit row. */
/*
 * The title column has a FLOOR, and the chip column can shrink.
 *
 * It used to be `minmax(0, 1fr)` for the title beside an `auto` chip column,
 * which reads as "give the chips whatever they need and the title whatever is
 * left". With three short tags that is fine. With a board whose chips say
 * "Level I trauma with replantation" and "Microvascular & replantation", in a
 * container narrower than the full page — the "similar jobs" list on a listing
 * page — the chips took 617 of 1078 pixels and the title was left with 38.
 * Titles were rendering as "Hand & E…" and, in one case, as a single letter.
 *
 * So the title gets a minimum it cannot be squeezed below, and the chip column
 * is allowed to shrink and clip instead. A clipped chip row still communicates
 * — the first two chips are the ones that matter — whereas a clipped title
 * communicates nothing at all.
 */
.job-row {
  position: relative; display: grid; align-items: center; gap: 14px;
  grid-template-columns: 40px minmax(210px, 1fr) minmax(0, auto) auto auto 38px 78px;
  min-height: var(--row-h); padding: 12px 14px;
  border-bottom: 1px solid var(--border);
  transition: background .14s ease, transform .14s ease;
}
/* The apply cell keeps its width whether or not the button is visible, so rows
   do not reflow on hover. */
.job-row > .job-apply { justify-self: end; }
.job-row:hover, .job-row:focus-within { background: var(--surface); transform: translateY(-1px); }
.job-row.is-featured { background: var(--feat, var(--surface-2)); border-left: 2px solid var(--feat-border, var(--accent)); padding-left: 12px; }
.job-row.is-expired { opacity: .55; }

.job-logo {
  width: 40px; height: 40px; border-radius: var(--radius); flex-shrink: 0;
  display: grid; place-items: center; font-weight: 700; font-size: 14px;
  background: var(--logo-bg, var(--surface-2)); color: #fff; letter-spacing: -0.02em;
}
.job-main { min-width: 0; }
.job-title {
  display: block; font-size: 15px; font-weight: 620; letter-spacing: -0.012em;
  color: var(--text); margin-bottom: 2px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.job-row:hover .job-title { color: var(--accent); }
.job-meta { display: flex; align-items: center; gap: 8px; font-size: 12.5px; color: var(--text-muted); min-width: 0; }
.job-company { font-weight: 500; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.job-company:hover { color: var(--text); text-decoration: underline; }

.tag-list { display: flex; gap: 5px; flex-wrap: nowrap; min-width: 0; overflow: hidden; }
.tag {
  padding: 3px 8px; border-radius: 5px; background: var(--surface-2);
  border: 1px solid var(--border); color: var(--text-muted);
  font-size: 11.5px; font-weight: 500; white-space: nowrap;
}
.tag:hover { color: var(--text); border-color: var(--border-strong); }

.job-salary { font-size: 13px; font-weight: 600; color: var(--text); white-space: nowrap; font-variant-numeric: tabular-nums; }
.job-salary.is-undisclosed { color: var(--text-dim); font-weight: 500; }
.job-region { font-size: 12.5px; color: var(--text-muted); white-space: nowrap; }
.job-age { font-size: 12px; color: var(--text-dim); white-space: nowrap; font-variant-numeric: tabular-nums; min-width: 34px; text-align: right; }

/* Apply reveals on hover AND focus-within. Hover does not exist on touch and is
   invisible to keyboard users, so the whole row is always a link underneath. */
.job-apply {
  opacity: 0; transform: translateX(6px);
  transition: opacity .16s ease, transform .16s ease;
  padding: 6px 13px; font-size: 12.5px; white-space: nowrap;
}
.job-row:hover .job-apply, .job-row:focus-within .job-apply { opacity: 1; transform: translateX(0); }
@media (hover: none) { .job-apply { display: none; } }

.badge {
  display: inline-block; padding: 2px 7px; border-radius: 4px;
  font-size: 10.5px; font-weight: 700; text-transform: uppercase; letter-spacing: .05em;
}
.badge-new { background: var(--accent-glow); color: var(--accent); }
.badge-featured { background: var(--accent); color: var(--accent-ink); }
.badge-expired { background: rgba(255, 184, 107, .14); color: var(--warn); }
/* Not a warning and not a promotion — a fact about who is hiring. Neutral
   weight on purpose: an individual posting a real job is not a lesser listing,
   the applicant just needs to know which kind of employer they are writing to. */
.badge-curated {
  /* Outlined rather than filled: it marks provenance, not promotion. A filled
     accent badge would read as a paid upgrade, which is the exact confusion
     this badge exists to prevent. */
  background: transparent; color: var(--text-muted);
  border: 1px dashed var(--border-strong);
}
/* .curated-note is gone with the full-width notice it styled — the disclosure
   now lives in a sidebar card. See .side-note below. */

.badge-individual {
  background: var(--surface-2); color: var(--text-muted);
  border: 1px solid var(--border);
}

/* Narrow screens get an explicit two-row layout rather than a narrower version
   of the same seven-column grid.
 *
 * This has to be spelled out. Hiding .tag-list and .job-region removes them
 * from the grid entirely, so the remaining children reflow into whatever
 * columns are left and land in the wrong places — the classic symptom being an
 * age stamp that drops onto its own line under the logo. Every remaining child
 * is therefore placed by hand.
 */
@media (max-width: 860px) {
  .job-row {
    grid-template-columns: 36px minmax(0, 1fr) auto;
    grid-template-rows: auto auto;
    align-items: start;
    column-gap: 11px; row-gap: 3px;
    padding: 11px 12px;
  }
  .tag-list, .job-region, .job-apply { display: none; }

  .job-row > .job-logo   { grid-column: 1; grid-row: 1 / span 2; align-self: start; }
  .job-row > .job-main   { grid-column: 2; grid-row: 1; }
  .job-row > .job-salary { grid-column: 2; grid-row: 2; font-size: 12.5px; }
  .job-row > .job-age    { grid-column: 3; grid-row: 1; }

  .job-logo { width: 36px; height: 36px; font-size: 13px; }
  .job-title { white-space: normal; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; }
  .job-meta { flex-wrap: wrap; row-gap: 2px; }
}

/* ---------- Sections ----------------------------------------------------- */

.section { padding: 34px 0; }
.section-head { display: flex; align-items: baseline; justify-content: space-between; gap: 16px; margin-bottom: 14px; }
h2.section-title { margin: 0; font-size: 19px; font-weight: 700; letter-spacing: -0.02em; }
.section-link { color: var(--text-muted); font-size: 13px; font-weight: 500; }
.section-link:hover { color: var(--accent); }

.empty-state { padding: 48px 20px; text-align: center; color: var(--text-muted); }
.empty-state p { margin: 0 0 14px; }

.link-cloud { display: flex; flex-wrap: wrap; gap: 7px; }
.link-cloud a {
  padding: 6px 12px; border-radius: var(--radius); border: 1px solid var(--border);
  background: var(--surface); color: var(--text-muted); font-size: 13px;
}
.link-cloud a:hover { color: var(--text); border-color: var(--border-strong); background: var(--surface-2); }

/* ---------- Job detail --------------------------------------------------- */

.breadcrumb { display: flex; align-items: center; gap: 7px; flex-wrap: wrap; font-size: 12.5px; color: var(--text-dim); padding: 18px 0 0; }
.breadcrumb a:hover { color: var(--text); }
.breadcrumb .sep { opacity: .5; }

.job-header { padding: 18px 0 24px; border-bottom: 1px solid var(--border); }
.job-header-top { display: flex; gap: 16px; align-items: flex-start; }
.job-header .job-logo { width: 60px; height: 60px; font-size: 20px; border-radius: var(--radius-lg); }
h1.job-h1 { margin: 0 0 5px; font-size: clamp(23px, 3.4vw, 31px); line-height: 1.16; letter-spacing: -0.03em; font-weight: 750; }
.job-header-company { color: var(--text-muted); font-size: 14.5px; }
.job-header-company a { color: var(--text); font-weight: 600; }
.job-header-company a:hover { color: var(--accent); }

.meta-chips { display: flex; flex-wrap: wrap; gap: 7px; margin: 16px 0 18px; }
.meta-chip {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 6px 12px; border-radius: var(--radius); border: 1px solid var(--border);
  background: var(--surface); font-size: 13px; color: var(--text-muted);
}
.meta-chip strong { color: var(--text); font-weight: 600; }

/* ---------- Job reference — v2.15 ---------------------------------------- */
/* Deliberately quieter than the chips above it. Tabular numerals and a wider
   letter-spacing because this is a number people read aloud and retype, and
   proportional digits in a body face make 6 and 8 easy to confuse at 13px. */

.job-ref { display: flex; align-items: center; gap: 8px; margin: -6px 0 18px; }
.jr-label {
  font-size: 11px; font-weight: 700; letter-spacing: .07em; text-transform: uppercase;
  color: var(--text-dim);
}
.jr-value {
  font-variant-numeric: tabular-nums; letter-spacing: .08em;
  font-size: 14px; font-weight: 650; color: var(--text);
  user-select: all;   /* one click selects the whole number, not one digit */
}
.jr-copy {
  border: 1px solid var(--border); background: var(--surface); color: var(--text-muted);
  border-radius: var(--radius); padding: 2px 8px; font-size: 11.5px; cursor: pointer;
  font-family: inherit; line-height: 1.6;
}
.jr-copy:hover { color: var(--text); border-color: var(--border-strong); }
.jr-copy.is-done { color: var(--accent); border-color: var(--accent); }

@media print { .jr-copy { display: none; } }

.notice {
  padding: 14px 16px; border-radius: var(--radius-lg); margin: 18px 0;
  border: 1px solid; font-size: 14px; line-height: 1.5;
}
.notice-warn { background: rgba(255, 184, 107, .08); border-color: rgba(255, 184, 107, .28); color: var(--warn); }
.notice-warn strong { color: var(--text); }

/* Validation summary at the top of a returned form. Sits above the fields it
   describes and takes focus, so a screen reader announces the problem rather
   than dropping the user back at the top of a form that looks unchanged. */
.form-errors {
  padding: 14px 16px; border-radius: var(--radius-lg); margin: 0 0 22px;
  background: rgba(255, 107, 107, .09); border: 1px solid rgba(255, 107, 107, .32);
  color: var(--text); font-size: 14px; line-height: 1.5;
}
.form-errors:focus { outline: 2px solid var(--accent); outline-offset: 3px; }
.form-errors ul { margin: 8px 0 0; padding-left: 20px; color: var(--text-muted); }
.form-errors li { margin-bottom: 3px; }

.job-layout { display: grid; grid-template-columns: minmax(0, 1fr) 300px; gap: 40px; padding: 28px 0 48px; align-items: start; }
@media (max-width: 900px) { .job-layout { grid-template-columns: 1fr; gap: 28px; } }

.prose { font-size: 15.5px; line-height: 1.68; color: #cfd4de; max-width: 68ch; }
@media (prefers-color-scheme: light) { :root:not([data-theme]) .prose { color: #303747; } }
[data-theme='light'] .prose { color: #303747; }
.prose h2 { margin: 30px 0 10px; font-size: 18px; font-weight: 700; color: var(--text); letter-spacing: -0.015em; }
.prose h3 { margin: 22px 0 8px; font-size: 15.5px; font-weight: 650; color: var(--text); }
.prose h2:first-child, .prose h3:first-child { margin-top: 0; }
.prose p { margin: 0 0 14px; }
.prose ul, .prose ol { margin: 0 0 16px; padding-left: 20px; }
.prose li { margin-bottom: 6px; }
.prose li::marker { color: var(--text-dim); }
.prose strong { color: var(--text); font-weight: 640; }
.prose a { color: var(--accent); text-decoration: underline; text-underline-offset: 2px; }
.prose code { font-family: var(--mono); font-size: .89em; background: var(--surface-2); padding: 2px 5px; border-radius: 4px; }
.prose blockquote { margin: 0 0 16px; padding-left: 14px; border-left: 2px solid var(--border-strong); color: var(--text-muted); }
.prose hr { border: 0; border-top: 1px solid var(--border); margin: 24px 0; }

.benefit-grid { display: flex; flex-wrap: wrap; gap: 7px; margin: 8px 0 0; }
.benefit { display: inline-flex; align-items: center; gap: 6px; padding: 6px 11px; border-radius: var(--radius); background: var(--surface-2); border: 1px solid var(--border); font-size: 12.5px; color: var(--text-muted); }

.card { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-lg); padding: 18px; margin-bottom: 16px; }
.card h3 { margin: 0 0 10px; font-size: 14px; font-weight: 650; letter-spacing: -0.01em; }
.card p { margin: 0 0 12px; font-size: 13.5px; color: var(--text-muted); line-height: 1.55; }
.sidebar { position: sticky; top: 78px; }
@media (max-width: 900px) { .sidebar { position: static; } }

.apply-card { border-color: var(--border-strong); }
.apply-note { font-size: 12px; color: var(--text-dim); margin: 10px 0 0; text-align: center; }

.share-row { display: flex; gap: 7px; flex-wrap: wrap; }

/* Sticky mobile apply bar */
.sticky-apply {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 60;
  padding: 10px 16px calc(10px + env(safe-area-inset-bottom));
  background: color-mix(in srgb, var(--bg) 94%, transparent);
  backdrop-filter: blur(14px); -webkit-backdrop-filter: blur(14px);
  border-top: 1px solid var(--border);
  display: none; gap: 12px; align-items: center;
  transform: translateY(100%); transition: transform .22s ease;
}
.sticky-apply.is-visible { transform: translateY(0); }
.sticky-apply .sa-title { flex: 1; min-width: 0; font-size: 13px; font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
@media (max-width: 900px) { .sticky-apply { display: flex; } body.has-sticky-apply { padding-bottom: 66px; } }

/* ---------- Company page ------------------------------------------------- */

.company-header { display: flex; gap: 18px; align-items: center; padding: 28px 0; border-bottom: 1px solid var(--border); }
.company-header .job-logo { width: 64px; height: 64px; font-size: 22px; border-radius: var(--radius-lg); }
h1.company-h1 { margin: 0 0 4px; font-size: 27px; letter-spacing: -0.03em; font-weight: 750; }

.company-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); gap: 12px; }
.company-card { display: block; padding: 16px; border: 1px solid var(--border); border-radius: var(--radius-lg); background: var(--surface); transition: transform .14s ease, border-color .15s ease; }
.company-card:hover { transform: translateY(-2px); border-color: var(--border-strong); }
.company-card .cc-head { display: flex; align-items: center; gap: 11px; margin-bottom: 9px; }
.company-card .job-logo { width: 34px; height: 34px; font-size: 12px; }
.company-card h3 { margin: 0; font-size: 14.5px; font-weight: 650; }
.company-card p { margin: 0; font-size: 12.5px; color: var(--text-muted); line-height: 1.5; }
.company-card .cc-count { margin-top: 9px; font-size: 12px; color: var(--accent); font-weight: 600; }

/* ---------- Salary page — v2.14 ------------------------------------------ */
/* Three figures, the median deliberately larger than the two that bracket it.
   Equal weight would make a reader compare them, and they are not comparable:
   the median is the answer to the question and the other two are its context.

   auto-fit rather than a fixed three columns, so a board reporting only a
   median (no spread worth showing) does not leave two empty cells. */

.salary-figures {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(210px, 1fr)); gap: 12px;
}
.salary-figure {
  padding: 20px; border: 1px solid var(--border); border-radius: var(--radius-lg);
  background: var(--surface); display: flex; flex-direction: column; gap: 6px;
}
.salary-figure.is-lead { border-color: var(--border-strong); }
.sf-label {
  font-size: 11px; font-weight: 700; letter-spacing: .07em; text-transform: uppercase;
  color: var(--text-dim);
}
.sf-value {
  font-size: 30px; font-weight: 780; letter-spacing: -.03em; line-height: 1.05;
  font-variant-numeric: tabular-nums;
}
.salary-figure.is-lead .sf-value { font-size: 40px; color: var(--accent); }
.sf-note { font-size: 12.5px; color: var(--text-muted); line-height: 1.45; }

/* Rates do not stretch. A board usually carries one, and one card allowed to
   fill a 1100px row is a card that looks like a failed layout. */
.salary-figures.is-rates { grid-template-columns: repeat(auto-fill, minmax(240px, 320px)); }
.salary-figures.is-rates .sf-value { font-size: 26px; }

/* ---------- Pagination --------------------------------------------------- */

.pagination { display: flex; align-items: center; justify-content: center; gap: 6px; padding: 28px 0; flex-wrap: wrap; }
.pagination a, .pagination span {
  min-width: 34px; height: 34px; padding: 0 10px; border-radius: var(--radius);
  display: inline-flex; align-items: center; justify-content: center;
  border: 1px solid var(--border); background: var(--surface);
  font-size: 13px; color: var(--text-muted); font-variant-numeric: tabular-nums;
}
.pagination a:hover { color: var(--text); border-color: var(--border-strong); }
.pagination .is-current { background: var(--accent); color: var(--accent-ink); border-color: transparent; font-weight: 700; }
.pagination .is-gap { border: 0; background: none; }

/* ---------- Alerts / forms ----------------------------------------------- */

.alert-box { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-lg); padding: 26px; text-align: center; }
.alert-box h2 { margin: 0 0 7px; font-size: 19px; letter-spacing: -0.02em; }
.alert-box p { margin: 0 0 16px; color: var(--text-muted); font-size: 14px; }
.alert-form { display: flex; gap: 8px; max-width: 420px; margin: 0 auto; }
.alert-form input {
  flex: 1; padding: 10px 13px; border-radius: var(--radius);
  border: 1px solid var(--border-strong); background: var(--bg);
  color: var(--text); font-size: 14.5px; font-family: inherit;
}
@media (max-width: 520px) { .alert-form { flex-direction: column; } }

.field { margin-bottom: 16px; }
.field label { display: block; font-size: 13.5px; font-weight: 600; margin-bottom: 5px; }
.field .hint { display: block; font-size: 12.5px; color: var(--text-dim); margin-bottom: 7px; font-weight: 400; }
/*
 * Text controls only.
 *
 * `.field input` used to mean every input, and a checkbox is an input. Inside
 * a .field the tick boxes on the alerts page were therefore stretched to the
 * full width of their chip with 10px of padding and a 1px border, which pushed
 * the label text onto a second line and turned a row of pills into a grid of
 * tall boxes with a stripe floating above each caption. The `label` rule above
 * did the other half of it: `.field label` (0,1,1) outranks `.chip` (0,1,0),
 * so every chip lost its `display: inline-flex` as well.
 *
 * :not() rather than listing the text types: a type this selector has not
 * heard of — a future one, or one a browser does not support and renders as
 * text — should get the text treatment, which is what it will look like.
 */
.field input:not([type='checkbox']):not([type='radio']):not([type='range']),
.field textarea, .field select {
  width: 100%; padding: 10px 13px; border-radius: var(--radius);
  border: 1px solid var(--border-strong); background: var(--surface);
  color: var(--text); font-size: 14.5px; font-family: inherit;
}
/* Put the chip back together, whatever it is nested inside. Specificity high
   enough to beat `.field label`, stated once here rather than as a scattering
   of overrides at each call site. */
.field label.chip, label.chip {
  display: inline-flex; align-items: center; gap: 7px;
  margin-bottom: 0; font-size: 12.5px; font-weight: 500;
}
.field label.chip input[type='checkbox'] { margin: 0; flex: 0 0 auto; }
.field textarea { min-height: 150px; resize: vertical; line-height: 1.55; }

/* ---------- FAQ ---------------------------------------------------------- */

.faq details { border: 1px solid var(--border); border-radius: var(--radius); background: var(--surface); margin-bottom: 8px; }
.faq summary { padding: 13px 16px; cursor: pointer; font-weight: 600; font-size: 14px; list-style: none; display: flex; justify-content: space-between; gap: 12px; }
.faq summary::-webkit-details-marker { display: none; }
.faq summary::after { content: '+'; color: var(--text-dim); font-size: 17px; line-height: 1; }
.faq details[open] summary::after { content: '−'; }
.faq .faq-body { padding: 0 16px 14px; color: var(--text-muted); font-size: 13.5px; line-height: 1.6; }
.faq .faq-body p { margin: 0 0 10px; }
.faq .faq-body p:last-child { margin: 0; }

/* ---------- Footer ------------------------------------------------------- */

.site-footer { border-top: 1px solid var(--border); padding: 34px 0 44px; margin-top: 20px; color: var(--text-muted); font-size: 13px; }
.footer-cols { display: grid; grid-template-columns: 1.6fr repeat(3, 1fr); gap: 28px; margin-bottom: 26px; }
@media (max-width: 760px) { .footer-cols { grid-template-columns: 1fr 1fr; gap: 22px; } }
.footer-cols h4 { margin: 0 0 9px; font-size: 12px; text-transform: uppercase; letter-spacing: .07em; color: var(--text-dim); font-weight: 650; }
.footer-cols ul { margin: 0; padding: 0; list-style: none; }
.footer-cols li { margin-bottom: 6px; }
.footer-cols a:hover { color: var(--text); }
.footer-bottom { display: flex; justify-content: space-between; gap: 16px; flex-wrap: wrap; padding-top: 18px; border-top: 1px solid var(--border); font-size: 12.5px; color: var(--text-dim); }

/* ---------- Motion preferences ------------------------------------------- */
/* Mandatory: this is an accessibility requirement, not a nicety. */

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .001ms !important;
  }
  .aurora span { animation: none !important; }
  .job-apply { opacity: 1; transform: none; }
}


/* ---------- Theme toggle (single button) --------------------------------- */

.theme-toggle {
  display: inline-grid; place-items: center;
  width: 34px; height: 34px; padding: 0;
  color: var(--text-muted);
}
.theme-toggle:hover { color: var(--text); }
.theme-toggle svg {
  width: 15px; height: 15px; fill: none; stroke: currentColor;
  stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round;
}
#theme-auto { margin-right: 10px; }
#theme-auto:hover { color: var(--text); }
/* The toggle stays visible at every width. Phones are where a light/dark switch
   matters most, so hiding it on small screens would remove the feature exactly
   where it is used. */

/* ============================================================================
   Utility classes.

   These exist so no template ever needs an inline style="" attribute. A strict
   `style-src 'self'` blocks inline style attributes, not just <style> elements,
   so an inline style anywhere would force either 'unsafe-inline' (which guts
   the policy) or per-page hashes (impossible to cache). Named classes are the
   clean way out.

   The only dynamic per-element value is a company's brand colour, handled by
   Templates::brandStyleSheet() which emits one <style> block whose SHA-256 hash
   is added to the CSP at request time.
   ========================================================================== */

.u-flush        { margin: 0; }
.u-mt-sm        { margin-top: 10px; }
.u-mt           { margin-top: 20px; }
.u-mt-lg        { margin-top: 30px; }
.u-mt-xl        { margin-top: 34px; }
.u-mb-sm        { margin-bottom: 8px; }
.u-mb           { margin-bottom: 16px; }
.u-mb-lg        { margin-bottom: 22px; }
.u-pt-0         { padding-top: 0; }
.u-pt-sm        { padding-top: 16px; }
.u-pt           { padding-top: 24px; }
.u-pad-page     { padding: 26px 0 12px; }
.u-pad-page-lg  { padding: 40px 0; }
.u-pad-hero     { padding: 34px 0 8px; }
.u-pad-empty    { padding: 90px 20px; }

/* Centred, and that is not a style preference.
   .u-narrow is used for whole PAGES — job alerts, the phone-reveal page, the
   employer area — inside a .wrap that is itself centred and up to 1200px wide.
   Capping the width without centring it pinned every one of those pages to the
   left edge with half the screen empty beside it, which reads as a layout that
   failed to load rather than as a deliberately narrow column.
   .u-readable and .u-wide are used INSIDE cards and columns that are already
   positioned, so they stay left-aligned where they sit. */
.u-narrow       { max-width: 620px; margin-inline: auto; }
.u-readable     { max-width: 680px; }
.u-wide         { max-width: 720px; }
.u-measure      { max-width: 62ch; }
.u-measure-sm   { max-width: 34ch; }
.u-min0         { min-width: 0; }
.u-grow         { flex: 1; }
.u-w-sm         { width: 100px; }

.u-muted        { color: var(--text-muted); }
.u-dim          { color: var(--text-dim); }
.u-body         { font-size: 15px; }
.u-body-lg      { font-size: 16px; }
.u-small        { font-size: 13px; }
.u-tiny         { font-size: 12px; }
.u-center       { text-align: center; }
.u-row          { display: flex; gap: 8px; }
.u-stack-sm > * { margin-bottom: 6px; }
.u-offscreen    { position: absolute; left: -9999px; }
.u-tall         { min-height: 110px; }
.u-inline-mr    { margin-right: 5px; }

.page-title     { margin: 0 0 8px; font-size: clamp(24px, 3.6vw, 32px); letter-spacing: -.03em; font-weight: 750; }
.page-title-lg  { margin: 0 0 10px; font-size: clamp(25px, 3.8vw, 34px); letter-spacing: -.03em; font-weight: 780; }
.page-title-xl  { margin: 12px 0 10px; font-size: clamp(26px, 4vw, 38px); letter-spacing: -.03em; font-weight: 800; }
.page-lede      { margin: 0; color: var(--text-muted); max-width: 62ch; font-size: 15px; }
.sub-heading    { font-size: 18px; margin: 0 0 10px; letter-spacing: -.015em; }
.form-heading   { font-size: 18px; margin: 28px 0 14px; letter-spacing: -.015em; }
.country-pill   { padding: 6px 12px; border-radius: 8px; border: 1px solid var(--border); background: var(--surface); color: var(--text-muted); font-size: 13px; }
.big-404        { font-size: 56px; margin: 0 0 6px; letter-spacing: -.04em; font-weight: 800; }

/* ---------- Uploaded brand image ----------------------------------------- */
/* A logo is an <img> only when one has been uploaded; otherwise the monogram
   markup above is used.

   Both dimensions come from the server, computed together by Settings::logoBox
   from the aspect ratio measured at upload: the logo is fitted to a 250px width
   budget and a 46px height ceiling, and whichever it meets first decides the
   size. A wordmark therefore comes out wide, which is the whole point of a
   wordmark — the previous version drew the widest logos SHORTEST and left a
   780x195 upload 120px wide in a header with room for 250.

   object-fit: contain is the safety net, and the reason both dimensions can be
   fixed at once. If the stored ratio is ever wrong — an SVG with no viewBox, a
   file replaced on disk behind the setting — the logo is letterboxed inside its
   box. It cannot be stretched. Setting a width and clamping with max-height,
   which is what this used to do, scales one axis only and distorts. */
.brand-img {
  display: block;
  width: var(--logo-w, 88px);
  height: var(--logo-h, 34px);
  max-width: min(var(--logo-w, 88px), 42vw);
  object-fit: contain;
  object-position: left center;
}
.site-footer .brand-img {
  width: calc(var(--logo-w, 88px) * .78);
  height: calc(var(--logo-h, 34px) * .78);
  max-width: min(calc(var(--logo-w, 88px) * .78), 60vw);
}

/* A phone has less width to give, so the whole box shrinks in step and keeps
   its proportions — scaling only the height would crop or letterbox instead. */
@media (max-width: 700px) {
  .brand-img {
    width: calc(var(--logo-w, 88px) * .84);
    height: calc(var(--logo-h, 34px) * .84);
    max-width: min(calc(var(--logo-w, 88px) * .84), 46vw);
  }
}
@media (max-width: 420px) {
  .brand-img {
    width: calc(var(--logo-w, 88px) * .72);
    height: calc(var(--logo-h, 34px) * .72);
    max-width: min(calc(var(--logo-w, 88px) * .72), 44vw);
  }
}

/* An employer's own unfinished submissions, on their dashboard. */
.unfinished-list { list-style: none; padding: 0; margin: 0; }
.unfinished-list li {
  display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap;
  padding: 8px 0; border-top: 1px solid var(--border);
}
.unfinished-list li:first-child { border-top: 0; }
.unfinished-list form { margin-left: auto; }

/* The way out of a form that is not working. Quiet by default — it is not the
   thing to read first — but never so quiet it cannot be found when it is. */
.support-note {
  color: var(--text-dim); font-size: 13px; line-height: 1.55;
  margin: 14px 0 0; text-align: center;
}
.support-note a { color: var(--text-muted); text-decoration: underline; text-underline-offset: 2px; }
.support-note a:hover { color: var(--accent); }

/* Sidebar cards that are ABOUT the listing rather than about the role — how it
   got here, and how to reach a person about it.

   Deliberately quieter than the cards above them: smaller type, dimmer heading,
   and a plain button rather than the accent one. They are for the one visitor
   in a hundred who has a stake in the listing itself, and nothing in them may
   compete with Apply. */
.side-note { padding: 15px 16px; }
.side-note h3 {
  display: flex; align-items: center; gap: 7px;
  font-size: 13px; font-weight: 650; color: var(--text-muted); margin: 0 0 7px;
}
.side-note p { font-size: 12.5px; line-height: 1.55; color: var(--text-dim); margin: 0 0 12px; }
.side-note p:last-child { margin-bottom: 0; }
.side-note .btn { font-size: 12.5px; padding: 8px 12px; }
.side-note .badge-curated { font-size: 10px; padding: 2px 7px; }

/* The contact form. Narrow on purpose — it is five fields and a paragraph,
   and a full-width column of them reads as a much bigger job than it is. */
.contact-form .field-row { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; }
@media (max-width: 620px) { .contact-form .field-row { grid-template-columns: 1fr; } }
.contact-form textarea { min-height: 170px; }
.contact-form .hirer-type .check-line + .check-line { margin-top: 10px; }
.contact-form .check-line .hint { display: block; margin: 2px 0 0; }
.form-errors .support-note { text-align: left; margin-top: 10px; }

/* ---------- Sign-in and registration ------------------------------------- */
/* Centred in the viewport rather than left-aligned in the page grid. These two
   pages have exactly one job each and nothing else competing for attention, so
   the layout says so: a narrow column, vertically centred, with the form and
   the way to the OTHER page as two clearly separate blocks. Half the people who
   land on a sign-in page do not have an account, and finding that out should
   not mean reading the small print under the button. */

.auth-page {
  max-width: 420px;
  margin-inline: auto;
  padding-block: clamp(28px, 7vh, 72px);
  display: flex; flex-direction: column; gap: 18px;
}
.auth-card {
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius-lg); padding: 26px 24px 24px;
}
.auth-title { font-size: 23px; font-weight: 700; letter-spacing: -0.02em; margin: 0 0 4px; }
.auth-sub { color: var(--text-muted); font-size: 14px; margin: 0 0 20px; }
.auth-card .field:last-of-type { margin-bottom: 18px; }

/* Deliberately not a card. It is an alternative, not a second form, and giving
   it the same surface would read as one long form split in two. */
.auth-alt {
  border: 1px dashed var(--border-strong); border-radius: var(--radius-lg);
  padding: 18px 20px 20px; text-align: center;
}
.auth-alt h2 { font-size: 15px; font-weight: 650; margin: 0 0 6px; }
.auth-alt p { color: var(--text-muted); font-size: 13.5px; margin: 0 0 14px; }
.auth-foot { text-align: center; color: var(--text-dim); font-size: 13px; margin: 0; }
.auth-foot a { color: var(--text-muted); text-decoration: underline; text-underline-offset: 2px; }
.auth-foot a:hover { color: var(--text); }

/* No sticky header on these two pages: nothing scrolls behind it, and a bar
   pinned above a centred card wastes the height the card is centred in. */
body.page-auth .site-header { position: static; }

/* ---------- Employer account area ---------------------------------------- */
/* The only tabular data on the public site. Deliberately quiet — this is a
   utility screen, not part of the board's front door. */

.page-head-row {
  display: flex; align-items: flex-start; justify-content: space-between;
  gap: 20px; flex-wrap: wrap; margin-bottom: 8px;
}

.data-table { width: 100%; border-collapse: collapse; font-size: 14px; }
.data-table th, .data-table td {
  padding: 12px 14px; text-align: left; border-bottom: 1px solid var(--border);
  vertical-align: middle;
}
.data-table thead th {
  font-size: 11px; text-transform: uppercase; letter-spacing: .06em;
  color: var(--text-dim); font-weight: 700;
}
.data-table tbody tr:last-child td { border-bottom: 0; }
.data-table tbody tr:hover { background: var(--surface); }
.data-table a { font-weight: 600; }
.data-table a:hover { color: var(--accent); }
.data-table .right { text-align: right; white-space: nowrap; }
.data-table .right .btn { margin-left: 6px; }

.u-inline { display: inline-block; }

/* Below this width the four columns stop being readable, so each row becomes
   a stacked card with its column name as a label. */
@media (max-width: 620px) {
  .data-table thead { display: none; }
  .data-table tbody tr {
    display: block; padding: 12px 0; border-bottom: 1px solid var(--border);
  }
  .data-table td { display: flex; justify-content: space-between; gap: 14px; padding: 4px 0; border: 0; }
  .data-table td::before {
    content: attr(data-label); color: var(--text-dim); font-size: 12.5px; flex-shrink: 0;
  }
  .data-table .right { text-align: left; }
  .data-table .right .btn { margin: 6px 6px 0 0; }
}

/* ============================================================================
   Post-a-job page — v1.0
   Two columns on a desktop: the form, and a live preview that follows you down
   the page. On a phone the preview moves above the form and collapses, because
   a sticky panel on a 700px-tall screen would eat the form it is describing.
   ========================================================================== */

.hire-hero {
  border-bottom: 1px solid var(--border);
  background:
    radial-gradient(900px 300px at 15% -20%, var(--accent-glow), transparent 60%),
    var(--surface);
}
.hire-hero-inner { padding-block: 40px 32px; }
.hire-trust {
  display: flex; flex-wrap: wrap; gap: 8px 22px;
  margin: 18px 0 0; padding: 0; list-style: none;
  font-size: 13.5px; color: var(--text-muted);
}
.hire-trust li { display: flex; align-items: center; gap: 7px; }
.hire-trust li::before {
  content: '✓'; color: var(--accent); font-weight: 800; font-size: 12px;
}

.hire-grid {
  display: grid; grid-template-columns: minmax(0, 1fr) 380px;
  gap: 36px; align-items: start; padding: 32px 0 64px;
}
.hire-side { position: sticky; top: 72px; display: grid; gap: 16px; }

.hire-step {
  padding: 24px; margin-bottom: 18px;
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius-lg);
}
.step-h {
  display: flex; align-items: center; gap: 11px;
  margin: 0 0 18px; font-size: 17px; letter-spacing: -.02em;
}
.step-n {
  display: grid; place-items: center; flex-shrink: 0;
  width: 26px; height: 26px; border-radius: 50%;
  background: var(--accent); color: var(--accent-ink);
  font-size: 13px; font-weight: 800;
}
.field-row { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; }

.salary-row { display: grid; grid-template-columns: 1fr auto 1fr 110px; gap: 8px; align-items: center; }
.salary-dash { color: var(--text-dim); }

.counter { font-variant-numeric: tabular-nums; font-weight: 600; }
.counter.short { color: var(--warn); }
.counter.ok { color: var(--accent); }

/* ---------- Package cards ------------------------------------------------ */

.pkg-grid { display: grid; gap: 12px; }
.pkg { display: block; cursor: pointer; position: relative; }
.pkg input {
  position: absolute; opacity: 0; width: 1px; height: 1px;
}
.pkg-body {
  display: block; padding: 16px 18px;
  border: 1.5px solid var(--border-strong); border-radius: var(--radius-lg);
  background: var(--bg); transition: border-color .15s ease, background .15s ease;
}
.pkg:hover .pkg-body { border-color: var(--text-dim); }
.pkg input:checked + .pkg-body {
  border-color: var(--accent);
  background: var(--accent-glow);
}
/* Focus has to be visible on the label, since the real radio is off-screen. */
.pkg input:focus-visible + .pkg-body { outline: 2px solid var(--accent); outline-offset: 2px; }
.pkg-flag {
  position: absolute; top: -9px; right: 14px; z-index: 1;
  padding: 2px 9px; border-radius: 999px;
  background: var(--accent); color: var(--accent-ink);
  font-size: 10.5px; font-weight: 800; text-transform: uppercase; letter-spacing: .05em;
}
.pkg-head { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; }
.pkg-name { font-weight: 700; font-size: 15.5px; }
.pkg-price { font-weight: 800; font-size: 19px; letter-spacing: -.02em; }
.pkg-blurb { display: block; margin-top: 4px; font-size: 13px; color: var(--text-muted); }
.pkg-perks {
  margin: 12px 0 0; padding: 0; list-style: none;
  display: grid; gap: 5px; font-size: 12.5px; color: var(--text-muted);
}
.pkg-perks li { display: flex; align-items: flex-start; gap: 7px; }
.pkg-perks li::before { content: '✓'; color: var(--accent); font-weight: 800; flex-shrink: 0; }

/* ---------- Preview ------------------------------------------------------ */

.preview-card, .order-card {
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius-lg); padding: 16px;
}
.preview-head {
  display: flex; align-items: center; justify-content: space-between;
  margin-bottom: 10px; font-size: 11px; font-weight: 700;
  text-transform: uppercase; letter-spacing: .08em; color: var(--text-dim);
}
.preview-toggle {
  background: none; border: 0; padding: 2px 6px; cursor: pointer;
  color: var(--text-muted); font: inherit; text-transform: none; letter-spacing: 0;
  border-radius: 5px;
}
.preview-toggle:hover { color: var(--text); background: var(--surface-2); }
.preview-note { margin: 0 0 10px; font-size: 12.5px; color: var(--text-dim); }

/* The preview reuses the real .job-row rules, so what an employer sees is the
   component the board actually renders — not a drawing of it. */
.preview-list { border: 1px solid var(--border); border-radius: var(--radius); overflow: hidden; }
.preview-list .job-row { grid-template-columns: 34px minmax(0, 1fr) auto; gap: 10px; padding: 10px 11px; }
.preview-list .job-row:hover { transform: none; background: none; }
.preview-list .job-logo { width: 34px; height: 34px; font-size: 12px; }
.preview-list .tag-list, .preview-list .job-region, .preview-list .job-apply { display: none; }
.preview-list .job-salary { grid-column: 2; font-size: 12px; }
.preview-list .job-age { grid-column: 3; grid-row: 1; }
.preview-list .job-title { font-size: 14px; }

.preview-page {
  margin-top: 14px; padding: 14px;
  border: 1px solid var(--border); border-radius: var(--radius); background: var(--bg);
}
.preview-page-head { display: flex; align-items: center; gap: 11px; margin-bottom: 10px; }
.job-logo.lg { width: 40px; height: 40px; font-size: 14px; }
.preview-page-head strong { display: block; font-size: 14.5px; letter-spacing: -.015em; }
.preview-chips { display: flex; flex-wrap: wrap; gap: 5px; margin-bottom: 10px; }
.preview-chips .meta-chip { font-size: 11.5px; padding: 3px 8px; }
.preview-body-text {
  font-size: 12.5px; color: var(--text-muted); line-height: 1.6;
  max-height: 190px; overflow: hidden; position: relative;
}
.preview-body-text.is-placeholder { color: var(--text-dim); font-style: italic; }
.preview-body-text p { margin: 0 0 7px; }
.preview-body-text strong { display: block; color: var(--text); margin: 10px 0 4px; }
/* Fades the cut-off rather than ending mid-word. */
.preview-body-text::after {
  content: ''; position: absolute; inset: auto 0 0 0; height: 40px;
  background: linear-gradient(transparent, var(--bg));
}

.order-card h3 { margin: 0 0 12px; font-size: 14px; letter-spacing: -.01em; }
.order-line {
  display: flex; align-items: baseline; justify-content: space-between; gap: 12px;
  padding-bottom: 10px; border-bottom: 1px solid var(--border);
}
.order-line strong { font-size: 19px; letter-spacing: -.02em; }
.order-note { margin: 10px 0 0; font-size: 12.5px; color: var(--text-muted); }
.order-pay { margin: 8px 0 0; font-size: 12px; color: var(--text-dim); }

/* ---------- Small screens ------------------------------------------------
   The preview goes first — an employer should see what they are buying before
   they start typing — but collapsed, so it costs one line rather than a
   screenful. Sticky is dropped entirely; on a short viewport a pinned panel
   just squeezes the form. */
@media (max-width: 940px) {
  .hire-grid { grid-template-columns: 1fr; gap: 20px; }
  /* display: contents dissolves the sidebar so its cards become grid items in
     their own right and can be ordered individually. Without it the preview is
     stuck to whatever the sidebar does, and the sidebar has to come first —
     which is how the preview ended up ABOVE the form on a phone, asking someone
     to look at an empty preview before they had typed anything. */
  .hire-side { display: contents; }
  .order-card { order: -1; }        /* the price stays visible from the start */
  .hire-main { order: 0; }
  .preview-card { order: 1; }       /* after the form, where it is worth looking */
  .hire-testimonials { order: 2; }
  .hire-preview-bottom { order: 1; }
}
@media (max-width: 620px) {
  .hire-hero-inner { padding-block: 28px 24px; }
  .hire-step { padding: 18px 16px; }
  .field-row { grid-template-columns: 1fr; gap: 0; }
  .salary-row { grid-template-columns: 1fr 1fr; gap: 8px; }
  .salary-dash { display: none; }
  .salary-row select { grid-column: 1 / -1; }
  .pkg-body { padding: 14px 15px; }
  .pkg-price { font-size: 17px; }
  .hire-trust { gap: 6px 16px; font-size: 12.5px; }
  /* A full-width action at the bottom of the viewport is the one control that
     should never require scrolling to find on a phone. */
  .page-hire #hire-submit {
    position: sticky; bottom: 12px; z-index: 20;
    box-shadow: 0 8px 28px rgba(0, 0, 0, .45);
  }
}

/* The preview column is 380px wide, so the row inside it needs tighter rules
   than the real board — the company name and employment type must not fight
   each other for a line. */
.preview-list .job-meta { font-size: 11.5px; gap: 5px; flex-wrap: nowrap; overflow: hidden; }
.preview-list .job-company { max-width: 42%; }
.preview-list .badge { font-size: 9px; padding: 1px 5px; flex-shrink: 0; }
.preview-list .job-age { font-size: 11px; }

/* A note under a URL field when the scheme was filled in for the visitor.
   Announced, never silent — quietly rewriting what someone typed is how a form
   loses trust, and this one is asking for money. */
.scheme-note {
  display: block; margin-top: 6px; font-size: 12.5px;
  color: var(--accent); font-weight: 600;
}

/* ============================================================================
   v1.3 — header width, background effects, password meter, share, print
   ========================================================================== */

/* ---------- Header: full width -------------------------------------------
   The header used to sit inside the same 1140px column as the content, which
   on a wide monitor left the logo floating a long way in from the left edge
   with nothing around it. A site header is a bar; it should span the window
   and let its contents sit near the edges. The reading column stays narrow,
   because line length is a readability constraint and header width is not. */

/* The bar spans the window; its CONTENTS line up with the page content.
   1.3 had the whole header inside the 1140px reading column, which on a wide
   monitor left the logo stranded far from the edge. 1.4 overcorrected and
   pushed it hard against the window edge, where it no longer lined up with
   anything below it — the logo sat at 56px while the first job row started at
   260px. Aligning to the same container fixes both: the bar still reads as
   full width because its background and border are, and the logo now sits
   directly above the content it belongs to. */
.site-header .wrap { max-width: var(--max); }

/* The bar grows to fit the logo rather than the logo shrinking to fit the bar.
   --header-h is emitted by Settings::themeCss only when a logo image is set, so
   a site using the text monogram keeps exactly the 62px it always had. */
.site-header { height: var(--header-h, 62px); }

/* The reading column widens on a large monitor, but only so far. 1140px was
   set for prose; a job row is a table — logo, title, tags, salary, region, age —
   and at 1140 those columns are cramped while a third of a 1920px screen sits
   empty. Text that should stay narrow says so itself (.u-narrow, .u-measure),
   so widening the container does not widen paragraphs. */
@media (min-width: 1280px) {
  :root { --max: 1320px; }
}
@media (min-width: 1600px) {
  :root { --max: 1440px; }
}
.brand { font-size: 16.5px; gap: 10px; }
.brand-mark { width: 30px; height: 30px; border-radius: 8px; font-size: 14px; }
.header-nav { gap: 6px; }
.header-nav a.nav-link { font-size: 14px; padding: 8px 13px; }
/* Whatever the header ends up being, the sticky filter bar sits directly under
   it. These two numbers drifting apart is how a filter bar ends up overlapping
   the logo or floating below a gap. */
.filter-bar { top: var(--header-h, 62px); }

@media (max-width: 700px) {
  .site-header { height: max(58px, calc(var(--logo-h, 34px) * .84 + 20px)); }
  .site-header .wrap { padding-inline: 16px; }
  .filter-bar { top: max(58px, calc(var(--logo-h, 34px) * .84 + 20px)); }
}

/* ---------- Optional background effects ----------------------------------
   Chosen in the admin theme editor and applied as a body class, so the CSS is
   static and cacheable and only the class name is dynamic. All are pure CSS —
   no canvas, no JavaScript, nothing that costs a frame budget on a phone.
   Every one is switched off entirely under prefers-reduced-motion. */

/* z-index: -1 rather than raising everything else above a z-index: 0 layer.
   `html` sets no background, so body's colour propagates to the canvas and a
   negative-z-index child of body paints above that canvas and below every
   in-flow element — no other rule has to change. Raising `body > *` instead
   would give <main> a stacking context, which would trap the fixed apply bar
   (z-index 60) underneath the sticky header (z-index 50). */
.bg-fx {
  position: fixed; inset: 0; z-index: -1; pointer-events: none;
  contain: layout paint;
}

/* Mesh: slow-moving colour fields. The default choice — visible enough to feel
   alive, dim enough to read over. */
.fx-mesh .bg-fx {
  background:
    radial-gradient(45vw 45vw at 12% 8%, var(--accent-glow), transparent 70%),
    radial-gradient(40vw 40vw at 88% 22%, rgba(110, 231, 255, .10), transparent 70%),
    radial-gradient(50vw 50vw at 55% 92%, rgba(185, 140, 255, .09), transparent 70%);
  animation: meshDrift calc(34s * var(--fx-speed, 1)) ease-in-out infinite alternate;
}
@keyframes meshDrift {
  0%   { transform: translate3d(0, 0, 0) scale(1); }
  100% { transform: translate3d(-2%, 2%, 0) scale(1.08); }
}

/* Grid: an engineering-drawing feel, drifting slowly upward. */
.fx-grid .bg-fx {
  background-image:
    linear-gradient(var(--border) 1px, transparent 1px),
    linear-gradient(90deg, var(--border) 1px, transparent 1px);
  background-size: 56px 56px;
  mask-image: radial-gradient(120vw 80vh at 50% 0%, #000 20%, transparent 78%);
  -webkit-mask-image: radial-gradient(120vw 80vh at 50% 0%, #000 20%, transparent 78%);
  animation: gridDrift calc(26s * var(--fx-speed, 1)) linear infinite;
}
@keyframes gridDrift { to { background-position: 0 -56px, -56px 0; } }

/* Dots: quieter than the grid, same idea. */
.fx-dots .bg-fx {
  background-image: radial-gradient(var(--border-strong) 1px, transparent 1px);
  background-size: 26px 26px;
  mask-image: radial-gradient(120vw 70vh at 50% 0%, #000 10%, transparent 72%);
  -webkit-mask-image: radial-gradient(120vw 70vh at 50% 0%, #000 10%, transparent 72%);
  animation: gridDrift calc(40s * var(--fx-speed, 1)) linear infinite;
}

/* Beams: diagonal light sweeps crossing the page. */
.fx-beams .bg-fx {
  background:
    linear-gradient(115deg, transparent 38%, var(--accent-glow) 47%, transparent 56%),
    linear-gradient(115deg, transparent 62%, rgba(110, 231, 255, .08) 70%, transparent 78%);
  background-size: 260% 260%;
  animation: beamSweep calc(22s * var(--fx-speed, 1)) ease-in-out infinite alternate;
}
@keyframes beamSweep {
  0%   { background-position: 0% 0%, 100% 100%; }
  100% { background-position: 100% 100%, 0% 0%; }
}

/* Hero effects sit inside .hero, above the page effect. */
.hero-fx { position: absolute; inset: 0; z-index: 1; pointer-events: none; overflow: hidden; }
.hero-beams .hero-fx {
  background: linear-gradient(105deg, transparent 40%, var(--accent-glow) 50%, transparent 60%);
  background-size: 300% 100%;
  animation: beamSweep calc(16s * var(--fx-speed, 1)) ease-in-out infinite alternate;
}
.hero-mesh .hero-fx {
  background:
    radial-gradient(60% 90% at 18% 0%, var(--accent-glow), transparent 72%),
    radial-gradient(50% 80% at 82% 30%, var(--wash-a-soft), transparent 72%);
  animation: meshDrift calc(28s * var(--fx-speed, 1)) ease-in-out infinite alternate;
}

@media (prefers-reduced-motion: reduce) {
  .bg-fx, .hero-fx { animation: none !important; }
}

/* ---------- Password meter ---------------------------------------------- */

.pw-meter { margin-top: 8px; }
.pw-bar { display: grid; grid-template-columns: repeat(4, 1fr); gap: 4px; }
.pw-bar i {
  height: 4px; border-radius: 2px; background: var(--border-strong);
  transition: background .18s ease;
}
.pw-meter.score-1 .pw-bar i.on { background: #ff6b6b; }
.pw-meter.score-2 .pw-bar i.on { background: #ffb86b; }
.pw-meter.score-3 .pw-bar i.on { background: #7fd8a0; }
.pw-meter.score-4 .pw-bar i.on { background: var(--accent); }
.pw-label { margin: 6px 0 0; font-size: 12.5px; font-weight: 700; color: var(--text-muted); }
.pw-meter.score-1 .pw-label { color: #ff6b6b; }
.pw-meter.score-2 .pw-label { color: #ffb86b; }
.pw-meter.score-3 .pw-label,
.pw-meter.score-4 .pw-label { color: var(--accent); }
.pw-problems { margin: 5px 0 0; padding-left: 16px; font-size: 12px; color: var(--text-dim); }
.pw-problems li { margin-bottom: 2px; }
.pw-meter:not(.is-active) { display: none; }
input.is-mismatch, input.is-weak { border-color: #ff6b6b; }
input.is-mismatch:focus, input.is-weak:focus { border-color: #ff6b6b; box-shadow: 0 0 0 3px rgba(255, 107, 107, .18); }

/* ---------- Share, print, testimonials ----------------------------------- */

.share-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 7px; }
.share-btn {
  display: flex; align-items: center; gap: 8px; justify-content: flex-start;
  padding: 9px 12px; border-radius: var(--radius);
  border: 1px solid var(--border-strong); background: var(--surface);
  color: var(--text-muted); font-size: 13px; font-weight: 600;
  font-family: inherit; cursor: pointer; text-align: left; width: 100%;
  transition: color .14s ease, border-color .14s ease, background .14s ease;
}
.share-btn:hover { color: var(--text); border-color: var(--text-dim); background: var(--surface-2); }
.share-btn .icon { flex-shrink: 0; opacity: .8; }
.share-btn.is-done { color: var(--accent); border-color: var(--accent); }
/* Hiding is handled by the global [hidden] rule near the top of this file. */
#share-native { grid-column: 1 / -1; justify-content: center; }

/* A checkbox chip that is ticked. The class is set server-side for the state
   the page arrives in; :has() keeps it right as the visitor clicks. */
.chip:has(input:checked) {
  background: var(--accent); color: var(--accent-ink); border-color: transparent; font-weight: 650;
}

.job-subscribe { margin-top: 14px; }
.job-subscribe form { display: flex; gap: 7px; margin-top: 9px; }
.job-subscribe input { flex: 1; min-width: 0; }
@media (max-width: 380px) { .job-subscribe form { flex-direction: column; } }

.testimonial {
  padding: 15px 16px; border-radius: var(--radius-lg);
  background: var(--surface); border: 1px solid var(--border);
  margin-bottom: 10px;
}
.testimonial blockquote { margin: 0 0 10px; font-size: 13.5px; line-height: 1.6; color: var(--text); }
.testimonial figcaption { display: flex; align-items: center; gap: 9px; font-size: 12.5px; color: var(--text-dim); }
.testimonial .t-mark {
  width: 28px; height: 28px; border-radius: 50%; flex-shrink: 0;
  display: grid; place-items: center; font-size: 11px; font-weight: 800;
  background: var(--surface-2); color: var(--text-muted);
}
.testimonial cite { font-style: normal; font-weight: 650; color: var(--text-muted); }
.hire-testimonials { margin-top: 14px; }
.side-h {
  margin: 0 0 10px; font-size: 12px; font-weight: 700;
  text-transform: uppercase; letter-spacing: .08em; color: var(--text-dim);
}

/* ---------- Print --------------------------------------------------------
   A candidate printing a listing wants the role, the pay and how to apply.
   Everything else is furniture. */

@media print {
  .site-header, .site-footer, .filter-bar, .sidebar, .sticky-apply,
  .share-grid, .job-subscribe, .skip-link, .bg-fx, .hero-fx, .aurora,
  .related-jobs, .job-apply { display: none !important; }
  body { background: #fff; color: #000; font-size: 11pt; }
  .wrap { max-width: 100%; padding: 0; }
  .job-layout { display: block; }
  a[href^="http"]::after { content: " (" attr(href) ")"; font-size: 9pt; color: #444; }
  .prose { color: #000; }
  .meta-chip { border: 1px solid #999; color: #000; }
  h1, h2, h3 { color: #000; page-break-after: avoid; }
  p, li { page-break-inside: avoid; }
}

/* ============================================================================
   v1.4 — mobile navigation, animated New badge
   ========================================================================== */

/* ---------- Mobile navigation ---------------------------------------------
   Above 780px the panel is not a panel: it is the row of links it always was,
   laid out inline, and the menu button is not rendered at all. Below 780px the
   links move behind the button.

   `html.js` gates the collapsing behaviour. The class is set by the inline
   script in <head>, before the first paint, so there is no jump. With scripting
   off the panel simply sits on a second row — every link reachable, the header
   a little taller. A button that does nothing is the one outcome worth ruling
   out. */

.nav-panel { display: flex; align-items: center; gap: 4px; }
.nav-burger { display: none; }

@media (max-width: 780px) {
  .nav-burger {
    display: grid; place-items: center;
    width: 36px; height: 36px; flex-shrink: 0;
    padding: 0; border-radius: var(--radius);
    border: 1px solid var(--border-strong); background: var(--surface-2);
    color: var(--text); cursor: pointer; font-family: inherit;
    transition: background .15s ease, border-color .15s ease;
  }
  .nav-burger:hover { background: var(--surface-hover); }

  .nb-lines { display: grid; gap: 4px; width: 16px; }
  .nb-lines i {
    display: block; height: 2px; border-radius: 2px; background: currentColor;
    transition: transform .22s ease, opacity .16s ease;
  }
  /* Three lines fold into a cross. The middle one fades; the outer two rotate
     about their own centres after being translated onto it. */
  [aria-expanded='true'] .nb-lines i:nth-child(1) { transform: translateY(6px) rotate(45deg); }
  [aria-expanded='true'] .nb-lines i:nth-child(2) { opacity: 0; }
  [aria-expanded='true'] .nb-lines i:nth-child(3) { transform: translateY(-6px) rotate(-45deg); }

  /* Anchored to the header, not to the nav, so the sheet spans the window.
     .site-header is position: sticky, which is a positioned value, so it is
     already the containing block for this. */
  .js .nav-panel {
    position: absolute; top: 100%; left: 0; right: 0;
    display: none; flex-direction: column; align-items: stretch; gap: 2px;
    padding: 10px 16px 16px;
    background: var(--bg);
    border-bottom: 1px solid var(--border);
    box-shadow: 0 20px 44px rgba(0, 0, 0, .34);
  }
  .js .site-header.nav-open .nav-panel { display: flex; }
  .js .site-header.nav-open { border-bottom-color: var(--border); }

  .nav-panel a.nav-link {
    padding: 12px 12px; border-radius: var(--radius);
    font-size: 15px; font-weight: 550; color: var(--text);
  }
  .nav-panel a.nav-link:hover { background: var(--surface-2); }

  /* Scripting off: the links get their own row rather than disappearing. The
     header stops being a fixed-height bar, which is fine — it is only sticky,
     not measured by anything else. */
  /* Three rows is a lot to pin to the top of a phone screen, so this one does
     not stick. The filter bar then has nothing above it to clear. */
  html:not(.js) .site-header { position: static; height: auto; min-height: 58px; padding: 8px 0 10px; }
  html:not(.js) .filter-bar { top: 0; }
  html:not(.js) .site-header .wrap { flex-wrap: wrap; row-gap: 8px; }
  html:not(.js) .header-nav { flex-wrap: wrap; row-gap: 8px; justify-content: flex-end; }
  html:not(.js) .nav-panel {
    order: 3; flex: 1 0 100%;
    flex-wrap: wrap; justify-content: flex-start; gap: 4px;
    padding-top: 8px; border-top: 1px solid var(--border);
  }
  html:not(.js) .nav-burger { display: none; }
}

/* The menu editor's second flag used to mean "hide this below 700px". With the
   panel there is nothing left to hide from — every link fits on a phone now —
   so it means what the class always said instead: secondary. Drawn a shade
   quieter in the inline row, full strength in the panel where a row of dimmed
   links would just look disabled. */
.header-nav .nav-panel > .nav-secondary { color: var(--text-dim); }
.header-nav .nav-panel > .nav-secondary:hover { color: var(--text); }
@media (max-width: 780px) {
  .header-nav .nav-panel > .nav-secondary { color: var(--text); }
}

@media (max-width: 360px) {
  .header-nav { gap: 5px; }
  .header-nav .btn-primary { padding: 8px 11px; font-size: 13px; }
}

/* ---------- Animated "New" badge -------------------------------------------
   The window is set in the admin panel; this is only the motion. Each variant
   animates something other than the badge's own box wherever it can, because a
   transform on the badge nudges the job title sitting next to it.

   All three stop under prefers-reduced-motion, and none of them is the only
   signal that a listing is new — the word "New" is there either way. */

.badge-new { position: relative; }
/* Clipping belongs to the shine alone — the glow draws a ring OUTSIDE the
   badge's box, and hiding overflow on every variant would cut it off. */
.nb-shine { overflow: hidden; }

/* Shine: a narrow highlight crosses the badge every few seconds, clipped to its
   rounded box. The closest thing to the animated tag the big boards use, drawn
   rather than shipped as a GIF — no extra request, no fixed colour, and it
   follows the accent. */
.nb-shine > i {
  position: absolute; inset: 0; pointer-events: none;
  background: linear-gradient(105deg,
    transparent 38%,
    color-mix(in srgb, var(--accent) 55%, transparent) 48%,
    transparent 58%);
  background-size: 260% 100%;
  /* Without this the gradient tiles, so the badge sits permanently half-lit
     between sweeps instead of resting flat. */
  background-repeat: no-repeat;
  animation: badgeShine calc(3.4s * var(--badge-speed, 1)) ease-in-out infinite;
}
@keyframes badgeShine {
  0%, 62% { background-position: 130% 0; }
  100%    { background-position: -30% 0; }
}

/* Glow: the badge breathes. Box-shadow and background only — nothing that
   changes the badge's size, so the line it sits on never reflows. */
.nb-glow {
  animation: badgeGlow calc(2.6s * var(--badge-speed, 1)) ease-in-out infinite;
}
@keyframes badgeGlow {
  0%, 100% { box-shadow: 0 0 0 0 transparent; background: var(--accent-glow); }
  50%      { box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 18%, transparent);
             background: color-mix(in srgb, var(--accent) 26%, transparent); }
}

/* Pop: a brief nudge, with a long pause. inline-block plus a transform-origin
   on the left keeps the growth away from the title beside it. */
.nb-pop {
  transform-origin: left center;
  animation: badgePop calc(4s * var(--badge-speed, 1)) ease-in-out infinite;
}
@keyframes badgePop {
  0%, 88%, 100% { transform: scale(1); }
  92%           { transform: scale(1.13); }
  96%           { transform: scale(.99); }
}

@media (prefers-reduced-motion: reduce) {
  .nb-shine > i, .nb-glow, .nb-pop, .nb-blink, .nb-slide > i, .nb-ring::after, .nb-rainbow { animation: none; }
  .nb-shine > i { display: none; }
}

/* ---------- Single listing: no dead column ---------------------------------
   `.prose` is capped at 68ch for readability, which is right. The bug was the
   column it sat in: the job layout gave the article every spare pixel, so on a
   1600px screen a 586px paragraph sat in a 1060px column and left roughly 475px
   of nothing between the text and the sidebar. Widening the container in 1.4
   made an existing 200px gap into a chasm.

   Two changes, together: the page gets its own narrower measure, and inside the
   article column the prose fills what it is given. 68ch stops being a cap and
   starts being what the column is sized to. */

body.page-job { --max: 1120px; }
.page-job .job-layout { grid-template-columns: minmax(0, 1fr) 320px; gap: 44px; }
.page-job .job-layout .prose { max-width: none; }
@media (max-width: 900px) {
  .page-job .job-layout { grid-template-columns: 1fr; }
  /* Stacked, there is no sidebar to sit beside, so the reading measure comes
     back — a full-width paragraph on a wide phone-in-landscape is unreadable. */
  .page-job .job-layout .prose { max-width: 68ch; }
}

/* ============================================================================
   v1.5 — more effects, speed control, featured tint, place suggestions
   ========================================================================== */

/* ---------- More background effects ---------------------------------------
   Same rules as the originals: pure CSS, keyed off a body class, every duration
   routed through --fx-speed so one control in the panel retunes all of them,
   and all of it switched off under prefers-reduced-motion. */

/* Waves: wide bands rolling upward. Reads as depth rather than as a pattern. */
.fx-waves .bg-fx {
  background:
    repeating-linear-gradient(0deg,
      transparent 0 60px,
      color-mix(in srgb, var(--accent) 6%, transparent) 60px 62px,
      transparent 62px 130px);
  mask-image: radial-gradient(130vw 90vh at 50% 30%, #000 10%, transparent 80%);
  -webkit-mask-image: radial-gradient(130vw 90vh at 50% 30%, #000 10%, transparent 80%);
  animation: waveRoll calc(30s * var(--fx-speed, 1)) linear infinite;
}
@keyframes waveRoll { to { background-position: 0 -130px; } }

/* Spotlight: one pool of light wandering the page. */
.fx-spotlight .bg-fx {
  background: radial-gradient(38vw 38vw at 50% 50%, var(--accent-glow), transparent 70%);
  animation: spotWander calc(38s * var(--fx-speed, 1)) ease-in-out infinite alternate;
}
@keyframes spotWander {
  0%   { background-position: -22% -18%; }
  50%  { background-position: 62% 30%; }
  100% { background-position: 18% 74%; }
}

/* Grain: a still texture, no animation at all. The SVG is a data URL rather
   than a file so it costs no request; feTurbulence is a filter primitive, not
   script, and needs no CSP allowance. */
.fx-grain .bg-fx {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='3'/%3E%3C/filter%3E%3Crect width='140' height='140' filter='url(%23n)' opacity='.42'/%3E%3C/svg%3E");
  opacity: .05;
}
[data-theme='light'] .fx-grain .bg-fx { opacity: .035; }

/* Orbits: two large soft rings turning behind everything. */
.fx-orbit .bg-fx {
  background:
    radial-gradient(closest-side circle at 22% 26%,
      transparent 58%, var(--accent-glow) 60%, transparent 63%),
    radial-gradient(closest-side circle at 78% 68%,
      transparent 62%, rgba(110, 231, 255, .10) 64%, transparent 67%);
  background-size: 62vw 62vw, 54vw 54vw;
  background-repeat: no-repeat;
  animation: orbitTurn calc(64s * var(--fx-speed, 1)) linear infinite;
}
@keyframes orbitTurn {
  to { transform: rotate(360deg) scale(1.06); }
}

/* Fanned rays across the hero. */
.hero-rays .hero-fx {
  background: repeating-conic-gradient(from 0deg at 12% -10%,
    var(--accent-glow) 0deg 3deg, transparent 3deg 14deg);
  opacity: .5;
  animation: rayTurn calc(52s * var(--fx-speed, 1)) linear infinite;
}
@keyframes rayTurn { to { transform: rotate(18deg); } }

.hero-pulse .hero-fx {
  background: radial-gradient(60% 100% at 30% 20%, var(--accent-glow), transparent 72%);
  animation: heroPulse calc(7s * var(--fx-speed, 1)) ease-in-out infinite;
}
@keyframes heroPulse {
  0%, 100% { opacity: .45; transform: scale(1); }
  50%      { opacity: 1;   transform: scale(1.08); }
}

.hero-scan .hero-fx {
  background: linear-gradient(180deg,
    transparent 0%,
    color-mix(in srgb, var(--accent) 40%, transparent) 50%,
    transparent 100%);
  background-size: 100% 90px;
  background-repeat: no-repeat;
  animation: heroScan calc(9s * var(--fx-speed, 1)) linear infinite;
}
@keyframes heroScan {
  0%   { background-position: 0 -120px; }
  100% { background-position: 0 130%; }
}

/* ---------- More badge movement ------------------------------------------- */

.nb-blink { animation: badgeBlink calc(2.2s * var(--badge-speed, 1)) ease-in-out infinite; }
@keyframes badgeBlink {
  /* Never to zero. A badge that disappears entirely reads as a rendering
     fault, and the word has to stay legible at every frame. */
  0%, 100% { opacity: 1; }
  50%      { opacity: .45; }
}

.nb-slide { overflow: hidden; }
.nb-slide > i {
  position: absolute; inset: 0; pointer-events: none;
  background: linear-gradient(90deg, var(--accent-glow), transparent 60%);
  animation: badgeSlide calc(2.8s * var(--badge-speed, 1)) ease-out infinite;
}
@keyframes badgeSlide {
  0%       { transform: translateX(-100%); }
  55%, 100% { transform: translateX(100%); }
}

/* The ring is drawn on a pseudo-element outside the badge box, so nothing on
   the line reflows while it expands. */
.nb-ring::after {
  content: ''; position: absolute; inset: 0; border-radius: inherit;
  border: 1px solid var(--accent); pointer-events: none;
  animation: badgeRing calc(2.8s * var(--badge-speed, 1)) ease-out infinite;
}
@keyframes badgeRing {
  0%       { transform: scale(1); opacity: .7; }
  70%, 100% { transform: scale(1.5); opacity: 0; }
}

.nb-rainbow { animation: badgeHue calc(6s * var(--badge-speed, 1)) linear infinite; }
@keyframes badgeHue {
  /* Rotating the filter, not the colour value, so it works whatever accent is
     configured and needs nothing generated server-side. */
  to { filter: hue-rotate(360deg); }
}

@media (prefers-reduced-motion: reduce) {
  .fx-waves .bg-fx, .fx-spotlight .bg-fx, .fx-orbit .bg-fx,
  .hero-rays .hero-fx, .hero-pulse .hero-fx, .hero-scan .hero-fx { animation: none; }
  .hero-scan .hero-fx, .nb-slide > i { display: none; }
}

/* ---------- Featured rows -------------------------------------------------
   "No tint" still keeps the coloured left edge and the badge, because a
   featured listing that looks identical to a free one is not worth paying for. */

.job-row.is-featured.feat-flat { background: transparent; }
.job-row.is-featured.feat-flat:hover { background: var(--surface); }

/* ---------- Place suggestions ---------------------------------------------
   Our own list, not Google's widget — see app/Places.php for why. That means
   it inherits the site's colours, radius and font for free, and needs no CSP
   allowance for a third party's inline styles. */

.places-wrap { position: relative; display: block; }
.filter-bar .places-wrap { display: inline-block; }
.places-list {
  position: absolute; top: calc(100% + 6px); left: 0; right: 0; z-index: 60;
  margin: 0; padding: 5px; list-style: none;
  max-height: 300px; overflow-y: auto;
  background: var(--surface); border: 1px solid var(--border-strong);
  border-radius: var(--radius-lg);
  box-shadow: 0 18px 44px rgba(0, 0, 0, .3);
}
.places-list li {
  padding: 8px 10px; border-radius: var(--radius); cursor: pointer;
  display: flex; flex-direction: column; gap: 1px; line-height: 1.35;
}
.places-list li strong { font-size: 13.5px; font-weight: 600; color: var(--text); }
.places-list li span { font-size: 12px; color: var(--text-dim); }
.places-list li:hover, .places-list li.is-active { background: var(--surface-2); }
.select-place { min-width: 170px; }
@media (max-width: 700px) { .places-list { right: auto; min-width: 260px; } }

/* A quiet "Optional" beside a label, so the label itself stays scannable. */
.hint-inline {
  font-size: 11.5px; font-weight: 500; color: var(--text-dim);
  text-transform: uppercase; letter-spacing: .06em; margin-left: 6px;
}
.check-line { display: flex; align-items: flex-start; gap: 9px; cursor: pointer; }
.check-line input { margin-top: 3px; flex-shrink: 0; }
.check-line span { font-size: 13.5px; line-height: 1.5; color: var(--text-muted); }
.check-line strong { color: var(--text); }

.contact-card h3 { display: flex; align-items: center; gap: 8px; }
.contact-card h3 .icon { color: var(--text-dim); flex-shrink: 0; }
.phone-reveal { font-variant-numeric: tabular-nums; letter-spacing: .02em; }

/* ============================================================================
   v1.6 — hero layouts, geo dropdowns
   ========================================================================== */

/* ---------- Hero layouts ---------------------------------------------------
   Three, chosen in the theme panel. The copy column is capped so the headline
   keeps a sane measure whichever layout is running — a 1440px-wide h1 is not a
   headline, it is a banner. */

.hero-inner { display: grid; gap: 40px; align-items: center; }
.hero-copy { min-width: 0; max-width: 620px; }
.hero-aside { min-width: 0; }

/* Live panel and image both make it two columns. The aside is given a fixed
   share rather than 1fr so the headline never gets squeezed by a tall panel. */
.hero-stats .hero-inner,
.hero-image .hero-inner { grid-template-columns: minmax(0, 1fr) minmax(0, 400px); }

/* Centred: one column, everything on the middle line. */
.hero-centred .hero-inner { grid-template-columns: 1fr; justify-items: center; text-align: center; }
/* Wider than the left-aligned layouts, because nothing sits beside it and the
   only thing capping the measure is this. The elements inside set their own
   limits — the headline is allowed most of it, the paragraph and the search
   box much less — so widening the column does not widen the reading line. */
.hero-centred .hero-copy { max-width: 900px; }
.hero-centred .hero-search { margin-inline: auto; }
.hero-centred .chip-row { justify-content: center; }
.hero-centred .hero-sub { margin-inline: auto; }

/* Centred text needs different measures from left-aligned text, and the same
   numbers for both was most of why the centred hero looked wrong.

   A 17ch headline centred in a 720px column breaks after three or four words
   whatever they are, and a centred two-line block whose lines are very
   different lengths reads as a mistake rather than as a choice. Widening the
   headline's own measure lets it take a full line before it wraps at all, and
   `text-wrap: balance` above then splits it evenly when it does.

   The paragraph goes the other way: 56ch is a comfortable measure to read
   ragged-right and an uncomfortable one to read centred, because every line
   starts in a different place and the eye has further to travel back. */
.hero-centred .hero-title,
.hero-cover .hero-title {
  max-width: 30ch; margin-inline: auto;
  font-size: clamp(30px, 5.2vw, 52px);
}
/* Balanced rather than pretty here. `pretty` only rescues the last line from a
   single orphaned word, which is a ragged-right problem; centred text wants
   every line about the same length or it reads as three unrelated fragments
   stacked up. Three ragged centred lines under a headline was most of what
   made the centred hero look unfinished. */
.hero-centred .hero-sub,
.hero-cover .hero-sub { max-width: 52ch; font-size: 16.5px; text-wrap: balance; }

/* Room to breathe. The centred layout has nothing beside it, so the vertical
   rhythm is the only thing giving the hero any presence at all — at the old
   padding it read as a cramped strip rather than as the top of the page. */
.hero-centred .hero-inner { padding-block: clamp(40px, 6.5vw, 76px) clamp(32px, 5vw, 60px); }
.hero-centred .hero-search { width: 100%; }

/* The hero had a max-height that assumed a single short column. A panel beside
   it is taller than that, and clipping it is worse than a taller hero. */
.hero-stats .hero-inner,
.hero-image .hero-inner { padding-block: 34px 30px; }

.hero-aside-image img {
  display: block; width: 100%; height: auto;
  border-radius: var(--radius-lg);
  /* A photo of unknown proportions should not decide the height of the hero. */
  max-height: 340px; object-fit: cover;
}

/* ---------- Cover-image hero --------------------------------------------
   The image fills the section and the copy sits on top of it, centred.

   Text over a photograph is only readable if something guarantees the contrast,
   and a photograph guarantees nothing — the same white headline is crisp on a
   dark skyline and invisible on a bright sky. Two things make it safe: a scrim
   whose opacity the owner controls, and white text with a soft shadow in BOTH
   colour schemes. Following the light theme here would put dark text on a
   dimmed photo, which is the one combination that cannot work. */

.hero-cover .hero-inner { grid-template-columns: 1fr; justify-items: center; text-align: center; }
.hero-cover .hero-copy { max-width: 760px; }
.hero-cover .hero-search { margin-inline: auto; }
.hero-cover .chip-row { justify-content: center; }
.hero-cover .hero-sub { margin-inline: auto; }
.hero-cover .hero-inner { padding-block: clamp(48px, 9vw, 96px) clamp(40px, 7vw, 76px); }

.hero-cover { position: relative; isolation: isolate; }
.hero-cover-media { position: absolute; inset: 0; z-index: -2; overflow: hidden; }
.hero-cover-media img { width: 100%; height: 100%; object-fit: cover; object-position: center; display: block; }

/* Two layers, not one. A flat wash dims the bright areas and the dark ones
   equally; the gradient underneath it deepens the bottom, where the search box
   and chips sit, and fades the section into the page below it. */
.hero-cover::after {
  content: ''; position: absolute; inset: 0; z-index: -1; pointer-events: none;
  background:
    linear-gradient(to bottom,
      rgba(0, 0, 0, .18) 0%,
      rgba(0, 0, 0, .05) 34%,
      rgba(0, 0, 0, .55) 100%),
    rgba(0, 0, 0, var(--hero-dim, .45));
}

.hero-cover .hero-title,
.hero-cover .hero-sub,
.hero-cover .hero-stats,
.hero-cover .hero-stats strong { color: #fff; }
.hero-cover .hero-title { text-shadow: 0 2px 18px rgba(0, 0, 0, .55); }

/* The accent word inside the headline. Left alone it uses whichever accent the
   current scheme picked, and the LIGHT one is chosen to be readable on white —
   a dark green over a dimmed photograph, which is the one place it cannot go.
   Mixed toward white it stays recognisably the site's colour and stays legible,
   whichever accent the owner set and whichever scheme the visitor is in. */
.hero-cover .hero-title .accent { color: color-mix(in srgb, var(--accent) 45%, #fff); }
.hero-cover .hero-sub,
.hero-cover .hero-stats { text-shadow: 0 1px 10px rgba(0, 0, 0, .6); }
.hero-cover .hero-sub { color: rgba(255, 255, 255, .93); }
.hero-cover .hero-stats { color: rgba(255, 255, 255, .82); }

/* The chips and the search field need their own surface over a photograph —
   the page's translucent defaults borrow a background that is no longer there. */
.hero-cover .chip-row a {
  background: rgba(12, 14, 20, .55); border-color: rgba(255, 255, 255, .22); color: #fff;
  backdrop-filter: blur(6px); -webkit-backdrop-filter: blur(6px);
}
.hero-cover .chip-row a:hover { background: var(--accent); color: var(--accent-ink); border-color: transparent; }
.hero-cover .hero-search input {
  background: rgba(12, 14, 20, .62); border-color: rgba(255, 255, 255, .26); color: #fff;
  backdrop-filter: blur(6px); -webkit-backdrop-filter: blur(6px);
}
.hero-cover .hero-search input::placeholder { color: rgba(255, 255, 255, .62); }

/* The decorative background effects sit behind the photo and are invisible
   there — painting them is work with no result. */
.hero-cover .aurora,
.hero-cover .hero-fx { display: none; }

.hp-card {
  background: color-mix(in srgb, var(--surface) 88%, transparent);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  padding: 16px;
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
}
.hp-stats {
  display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px;
  padding-bottom: 14px; margin-bottom: 12px; border-bottom: 1px solid var(--border);
}
.hp-stats span { display: flex; flex-direction: column; font-size: 11.5px; color: var(--text-dim); }
.hp-stats strong {
  font-size: 20px; font-weight: 700; color: var(--text);
  letter-spacing: -.02em; font-variant-numeric: tabular-nums;
}
.hp-head {
  font-size: 11px; font-weight: 700; text-transform: uppercase;
  letter-spacing: .08em; color: var(--text-dim); margin-bottom: 8px;
}
.hp-list { display: grid; gap: 2px; }
.hp-job {
  display: grid; grid-template-columns: 30px minmax(0, 1fr) auto;
  gap: 10px; align-items: center;
  padding: 8px; border-radius: var(--radius);
  transition: background .14s ease;
}
.hp-job:hover { background: var(--surface-2); }
.hp-job-main { min-width: 0; display: flex; flex-direction: column; }
.hp-job-title {
  font-size: 13.5px; font-weight: 600; color: var(--text);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.hp-job-meta {
  font-size: 11.5px; color: var(--text-dim);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.hp-job-age { font-size: 11px; color: var(--text-dim); font-variant-numeric: tabular-nums; }
.job-logo.sm { width: 30px; height: 30px; font-size: 11px; border-radius: 7px; }
.hp-more {
  display: block; margin-top: 10px; padding-top: 10px;
  border-top: 1px solid var(--border);
  font-size: 12.5px; font-weight: 600; color: var(--accent);
}

/* One column on anything narrow. The panel goes BELOW the search box, never
   above it — the search is why the page exists. */
@media (max-width: 940px) {
  .hero-stats .hero-inner,
  .hero-image .hero-inner { grid-template-columns: 1fr; gap: 24px; }
  .hero-copy { max-width: none; }
  .hero-aside-image { display: none; }   /* decorative, and a phone has no room */
}
@media (max-width: 560px) {
  .hp-stats strong { font-size: 17px; }
}

/* ---------- Location dropdowns --------------------------------------------
   Country, region and city. The country list is server-rendered so it works
   with scripting off; the other two are filled from /api/geo on demand. */

.geo-group { display: contents; }
.geo-select:disabled { opacity: .5; cursor: not-allowed; }
.filter-bar .geo-select { min-width: 130px; max-width: 190px; }

.geo-row { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 8px; }
.geo-row .geo-select {
  width: 100%; padding: 10px 12px; border-radius: var(--radius);
  border: 1px solid var(--border-strong); background: var(--surface);
  color: var(--text); font-size: 14px; font-family: inherit;
}
@media (max-width: 620px) { .geo-row { grid-template-columns: 1fr; } }

/* The one-line explanation under the filter bar. Not a footnote nobody reads:
   without it, picking "Berlin" and seeing every Germany-wide role looks broken
   rather than correct. */
.filter-note {
  flex-basis: 100%; font-size: 12px; color: var(--text-dim);
  margin-top: 2px; line-height: 1.5;
}

/* ---------- Markdown editor ------------------------------------------------
   A toolbar and a preview tab around an ordinary textarea. Not a WYSIWYG: the
   stored format stays Markdown, so the allowlist renderer stays the security
   boundary. See assets/editor.js. */

.md-editor {
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-lg);
  background: var(--surface);
  overflow: hidden;
}
.md-editor:focus-within { border-color: var(--text-dim); }

.md-bar {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  padding: 7px 8px; border-bottom: 1px solid var(--border);
  background: var(--surface-2);
}
.md-tools { display: flex; gap: 2px; flex-wrap: wrap; }
.md-btn {
  min-width: 30px; height: 28px; padding: 0 8px;
  border: 1px solid transparent; border-radius: 6px;
  background: transparent; color: var(--text-muted);
  font-size: 12.5px; font-family: inherit; cursor: pointer; line-height: 1;
  transition: background .12s ease, color .12s ease;
}
.md-btn:hover { background: var(--surface); color: var(--text); border-color: var(--border); }
.md-btn.is-bold { font-weight: 800; }
.md-btn.is-italic { font-style: italic; font-family: Georgia, serif; }

.md-tabs { display: flex; gap: 2px; margin-left: auto; }
.md-tab {
  padding: 5px 12px; border: 1px solid transparent; border-radius: 6px;
  background: transparent; color: var(--text-dim);
  font-size: 12.5px; font-weight: 600; font-family: inherit; cursor: pointer;
}
.md-tab:hover { color: var(--text); }
.md-tab.is-on { background: var(--surface); color: var(--text); border-color: var(--border); }

/* The textarea loses its own frame — the shell provides it now. */
.md-editor textarea {
  display: block; width: 100%; border: 0; border-radius: 0;
  background: transparent; resize: vertical; min-height: 220px;
}
.md-editor textarea:focus { outline: none; }

.md-preview {
  padding: 16px 18px; min-height: 220px;
  max-height: 60vh; overflow-y: auto;
}
.md-preview .preview-empty { color: var(--text-dim); font-style: italic; }
.md-preview > :first-child { margin-top: 0; }

/* ---------- Posting page: the preview below the form -----------------------
   The other placement for the live preview. Full width, so the listing-page
   half of the preview is drawn at close to its real proportions rather than
   squeezed into a 320px column. */

.hire-preview-bottom {
  grid-column: 1 / -1;
  margin-top: 28px;
  padding-top: 24px;
  border-top: 1px solid var(--border);
}
.hire-preview-bottom .step-h { margin-bottom: 14px; }
.hire-preview-bottom .preview-card { position: static; }
@media (min-width: 901px) {
  /* Inside the form but outside the grid, so it spans the whole column. */
  .hire-preview-bottom .preview-page { max-width: 820px; }
}

/* Destructive action. Quiet until hovered — a red button that shouts from a
   resting state gets clicked by accident more often, not less. */
.btn-danger { color: #d94a4a; border-color: color-mix(in srgb, #d94a4a 35%, transparent); }
.btn-danger:hover { background: color-mix(in srgb, #d94a4a 12%, transparent); border-color: #d94a4a; }
.hire-account.is-signed-in {
  padding: 10px 14px; border-radius: var(--radius);
  background: var(--accent-glow); border: 1px solid color-mix(in srgb, var(--accent) 30%, transparent);
  color: var(--text);
}

/* ---------- Trust badges -------------------------------------------------
   Off until switched on, and each one a claim the site owner has chosen to
   make. Quiet by design: a row of reassurances that shouts competes with the
   thing it is meant to be reassuring you about. */

.trust-row {
  list-style: none; margin: 20px 0 0; padding: 0;
  display: grid; gap: 12px 22px;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
}
.trust-badge { display: flex; align-items: flex-start; gap: 10px; }
.trust-icon { flex-shrink: 0; margin-top: 1px; color: var(--accent); }
.trust-text { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.trust-text strong { font-size: 13.5px; font-weight: 650; color: var(--text); }
.trust-detail { font-size: 12.5px; color: var(--text-dim); line-height: 1.5; }

/* Stacked, for a narrow column such as the checkout card. */
.trust-stack { grid-template-columns: 1fr; gap: 12px; }

.site-footer .trust-row {
  margin-top: 26px; padding-top: 22px; border-top: 1px solid var(--border);
}

/* ---------- Checkout -----------------------------------------------------
   One decision on the page and nothing else. Centred and narrow because a
   layout that invites the eye to wander invites it to wander away, and this is
   the one screen where that costs an order. */

.checkout { max-width: 460px; margin-inline: auto; padding-block: clamp(28px, 6vh, 60px); }
.checkout-title { font-size: 26px; font-weight: 700; letter-spacing: -.02em; margin: 0 0 6px; text-align: center; }
.checkout-sub { color: var(--text-muted); font-size: 14.5px; margin: 0 0 22px; text-align: center; }
.checkout-order { margin-bottom: 18px; }

.checkout-steps {
  list-style: none; display: flex; justify-content: center; gap: 8px;
  margin: 0 0 24px; padding: 0; font-size: 12px; color: var(--text-dim);
  flex-wrap: wrap;
}
.checkout-steps li { display: flex; align-items: center; gap: 8px; }
.checkout-steps li::before {
  content: ''; width: 7px; height: 7px; border-radius: 50%;
  background: var(--border-strong); flex-shrink: 0;
}
.checkout-steps li + li { margin-left: 6px; }
.checkout-steps .is-done { color: var(--text-muted); }
.checkout-steps .is-done::before { background: var(--accent); }
.checkout-steps .is-current { color: var(--text); font-weight: 650; }
.checkout-steps .is-current::before { background: var(--accent); box-shadow: 0 0 0 3px var(--accent-glow); }

.pay-buttons { display: flex; flex-direction: column; gap: 10px; }
.pay-form { margin: 0; }
.pay-btn { display: inline-flex; align-items: center; justify-content: center; gap: 10px; }
.pay-mark { flex-shrink: 0; }

.checkout-note {
  display: flex; align-items: flex-start; gap: 10px;
  margin: 18px 0 0; font-size: 12.5px; line-height: 1.55; color: var(--text-dim);
}
.checkout-note .trust-icon { color: var(--text-dim); }

/* ---------- Stepped posting form -----------------------------------------
   Added by assets/hire-steps.js, which is loaded only when the setting is on.
   Nothing here applies to the single-page form. */

/* A track, not a wrapped list.
   Each step is an equal column with its number above its name and a rule
   joining it to the next, so the eye reads distance-travelled at a glance
   instead of counting chips. The rule is drawn by the step it leaves, which
   is why it turns accent-coloured on .is-done and not on .is-current: the
   line behind you is done, the line ahead of you is not. */
.step-progress {
  list-style: none; display: flex; gap: 0;
  margin: 0 0 26px; padding: 2px 0 18px; border-bottom: 1px solid var(--border);
  font-size: 12px; color: var(--text-dim); counter-reset: step;
}
.step-progress li {
  flex: 1 1 0; min-width: 0; position: relative; counter-increment: step;
  display: flex; flex-direction: column; align-items: center; gap: 8px;
  text-align: center; line-height: 1.3; padding: 0 4px;
}
.step-progress li::before {
  content: counter(step);
  width: 26px; height: 26px; border-radius: 50%; flex-shrink: 0;
  display: grid; place-items: center; font-size: 11.5px; font-weight: 650;
  background: var(--surface-2); color: var(--text-dim);
  border: 1px solid var(--border-strong);
  transition: background .15s ease, color .15s ease, border-color .15s ease;
}
/* The joining rule. Absolutely positioned so it never becomes a flex item and
   never widens the column it belongs to. */
.step-progress li::after {
  content: ''; position: absolute; top: 13px; height: 2px; border-radius: 2px;
  left: calc(50% + 19px); right: calc(-50% + 19px);
  background: var(--border-strong);
}
.step-progress li:last-child::after { content: none; }
.step-progress .is-current { color: var(--text); font-weight: 650; }
.step-progress .is-current::before {
  background: var(--accent); color: var(--accent-ink); border-color: transparent;
  box-shadow: 0 0 0 4px var(--accent-glow);
}
.step-progress .is-done { color: var(--text-muted); }
.step-progress .is-done::before {
  content: '✓'; background: color-mix(in srgb, var(--accent) 22%, transparent);
  color: var(--accent); border-color: color-mix(in srgb, var(--accent) 40%, transparent);
}
.step-progress .is-done::after { background: color-mix(in srgb, var(--accent) 45%, transparent); }

/* Five labels do not fit a phone. The numbers and the joining rule still show
   the shape of the journey, and the step's own heading is directly below —
   so the names are the one part that can go. */
@media (max-width: 620px) {
  .step-progress li { font-size: 0; gap: 0; padding: 0; }
  .step-progress li::before { font-size: 11.5px; }
}

.step-nav {
  display: flex; align-items: center; gap: 12px;
  margin-top: 22px; padding-top: 18px; border-top: 1px solid var(--border);
}
.step-nav .btn { min-width: 104px; }
.step-count { margin-inline: auto; font-size: 12.5px; color: var(--text-dim); font-variant-numeric: tabular-nums; }
.step-nav > .btn:last-child { margin-left: 0; }

@media (max-width: 560px) {
  .step-nav { flex-wrap: wrap; }
  .step-count { order: -1; width: 100%; margin: 0 0 4px; text-align: center; }
  .step-nav .btn { flex: 1; }
}

/* One line under a step heading saying what this step wants. Cheap, and it is
   the difference between "another screen of fields" and "four answers". */
.step-lead { margin: -6px 0 20px; font-size: 13.5px; color: var(--text-muted); }

/* A question the browser cannot check for itself, asked and not answered.
   Written by assets/hire-steps.js; see the comment there for why it is an
   element rather than a custom validity. */
.group-error {
  margin: 10px 0 0; font-size: 13px; font-weight: 600; color: #ff8f8f;
}
[data-required-group].has-error {
  border-left: 2px solid rgba(255, 107, 107, .55);
  padding-left: 13px; margin-left: -15px;
}
/* "Tick at least one" carries an instruction, unlike "Optional" which carries
   permission — so it is not drawn in the same grey as the thing people skip. */
.hint-inline.is-needed { color: var(--warn); }

/* ============================================================================
   v2.3 — headline highlight styles
   ==========================================================================

   The owner's headline carries *asterisks* around the words to pick out, and
   Headline::html() turns each pair into <span class="hl">. Which of these five
   rules applies is decided by a class on the hero section, so the span itself
   is identical whichever style is running — switching style is a class change,
   never a re-render of different markup.

   Every one of them is drawn from --accent, the colour the owner already chose
   for the site, and every one has a defined appearance in both schemes. None
   loads an image or a font: this is the largest text on the page and the thing
   the Largest Contentful Paint is usually measured against, so it must be
   finished painting when the HTML arrives. */

/* No highlight. The span is still emitted so the style can be switched back
   without editing the headline, but it inherits everything. */
.hl-plain .hero-title .hl { color: inherit; }

/* Colour: the original behaviour, and still the default. */
.hl-colour .hero-title .hl { color: var(--accent); }

/* Underline. A stroke UNDER the letters rather than a text-decoration through
   them: at headline weight an underline that close to the baseline collides
   with every descender. The gradient is flat — it is a rectangle drawn as a
   background image, which is what lets it sit at a controlled distance and
   thickness that scale with the type. */
.hl-underline .hero-title .hl {
  background-image: linear-gradient(var(--accent), var(--accent));
  background-repeat: no-repeat;
  background-size: 100% .12em;
  background-position: 0 .96em;
  padding-bottom: .06em;
}

/* Marker pen. Drawn behind the words with a slight overhang so it reads as a
   swipe rather than as a table cell, and the text colour flips to the computed
   ink for the accent — the same pairing the buttons use, so a pale accent gets
   dark text and a dark accent gets light text without anybody choosing. */
.hl-highlight .hero-title .hl {
  background: var(--accent); color: var(--accent-ink);
  padding: 0 .18em .04em; margin: 0 -.06em;
  border-radius: .12em;
  box-decoration-break: clone;          /* survives a line break intact */
  -webkit-box-decoration-break: clone;
}

/* Gradient fill. background-clip: text is the only part of this file that
   cannot be relied on, so the flat accent is set FIRST as a plain colour and
   the clip is layered over it inside @supports. A browser without it shows the
   accent colour; nothing shows transparent text on a transparent background,
   which is the failure mode this ordering exists to rule out. */
.hl-gradient .hero-title .hl { color: var(--accent); }
@supports ((background-clip: text) or (-webkit-background-clip: text)) {
  .hl-gradient .hero-title .hl {
    background-image: linear-gradient(100deg,
      var(--accent) 0%,
      color-mix(in srgb, var(--accent) 60%, var(--text)) 100%);
    -webkit-background-clip: text;
    background-clip: text;
    color: transparent;
  }
}

/* Over a cover photograph the accent is mixed toward white for legibility (see
   the .hero-cover rules above). The highlight styles have to follow, or a dark
   green underline vanishes into a dimmed photo. */
.hero-cover.hl-colour .hero-title .hl,
.hero-cover.hl-gradient .hero-title .hl { color: color-mix(in srgb, var(--accent) 45%, #fff); }
.hero-cover.hl-underline .hero-title .hl {
  background-image: linear-gradient(
    color-mix(in srgb, var(--accent) 55%, #fff),
    color-mix(in srgb, var(--accent) 55%, #fff));
}
@supports ((background-clip: text) or (-webkit-background-clip: text)) {
  .hero-cover.hl-gradient .hero-title .hl {
    background-image: linear-gradient(100deg,
      color-mix(in srgb, var(--accent) 45%, #fff) 0%, #fff 100%);
    color: transparent;
  }
}
/* The marker swipe is opaque, so the text on it is back on a known colour and
   the shadow that helps white text over a photo would only muddy it. */
.hero-cover.hl-highlight .hero-title { text-shadow: none; }
.hero-cover.hl-highlight .hero-title .hl { color: var(--accent-ink); }

/* ============================================================================
   v2.3 — job alerts page
   ==========================================================================

   Two columns above 900px, one below. The form keeps its own measure rather
   than stretching: a single-column form 900px wide is harder to fill in than
   the same form at 560px, whatever the screen can hold. */

.alerts-page {
  display: grid; gap: 28px; align-items: start;
  grid-template-columns: minmax(0, 1.25fr) minmax(0, 1fr);
  max-width: 1040px; margin-inline: auto;
}
.alerts-main { min-width: 0; }
.alerts-side { min-width: 0; }
@media (max-width: 900px) {
  .alerts-page { grid-template-columns: 1fr; max-width: 620px; gap: 20px; }
  /* Below the form on a phone: the reassurance is read after the decision to
     subscribe has been made, not before the field it is reassuring about. */
  .alerts-side { order: 2; }
}

/* A fieldset, because Categories and Regions are groups of checkboxes and a
   <label> pointing at nothing was what the markup used to claim. The reset is
   the whole point of using one — browsers give fieldsets a border, padding and
   a legend inset that belong to 1996. */
.chip-field { border: 0; padding: 0; margin: 0 0 16px; min-width: 0; }
.chip-field legend {
  padding: 0; font-size: 13.5px; font-weight: 600; margin-bottom: 5px;
}

/* The globe marks sit optically low against a capital letter; nudge them. */
.region-mark { flex: 0 0 auto; vertical-align: -2px; }
.chip .region-mark { vertical-align: 0; opacity: .85; }
.chip:has(input:checked) .region-mark { opacity: 1; }
.job-region .region-mark, .meta-chip .region-mark { margin-right: 2px; }
.link-cloud .region-mark { margin-right: 4px; }

/* "What arrives": one icon per promise, aligned to the first line of each. */
.tick-list { list-style: none; margin: 0; padding: 0; display: grid; gap: 13px; }
.tick-list li { display: flex; gap: 10px; align-items: flex-start; font-size: 13.5px; line-height: 1.5; color: var(--text-muted); }
.tick-list li strong { color: var(--text); font-weight: 620; }
.tick-list .icon { flex: 0 0 auto; margin-top: 2px; color: var(--accent); }

/* ============================================================================
   v2.4 — employer account page
   ==========================================================================

   Four cards, two up on a wide screen. One column below 760px, where two
   password fields side by side is worse than two stacked. */

.account-grid {
  display: grid; gap: 16px; align-items: start;
  grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
  max-width: 900px;
}
.account-grid .card { margin: 0; }
.account-grid .side-h { margin-bottom: 12px; }
.account-grid .field:last-of-type { margin-bottom: 14px; }

/* ============================================================================
   v2.4 — company logos and their stand-ins
   ==========================================================================

   Three things can occupy the same square: an uploaded image, a drawn glyph,
   or the initials. They must all come out the same size, or a browse page with
   a mixture in it looks ragged — which is the whole reason the fallback is
   configurable in the first place. .job-logo already sets the box; these only
   change what happens inside it. */

.job-logo.is-image {
  /* contain, not cover: a wordmark cropped to a square is unreadable, and a
     logo with the sides cut off is worse than one with space around it. */
  object-fit: contain; background: var(--surface-2); padding: 3px;
}
.job-logo.is-mark {
  background: var(--surface-2); color: var(--text-dim);
  border: 1px solid var(--border);
}
.job-logo.is-mark svg { width: 62%; height: 62%; }

/* ============================================================================
   v2.5 — the country picker on the posting form
   ==========================================================================

   A scrolling box rather than a list that grows to two hundred rows: the
   sections below it are the ones with the money in them, and pushing them off
   the screen behind a list most employers will tick two entries in is the
   wrong trade. */

.country-field { border: 0; padding: 0; margin: 0 0 16px; min-width: 0; }
.country-field legend { padding: 0; font-size: 13.5px; font-weight: 600; margin-bottom: 5px; }

/* A long list of tick boxes — the languages a set of records might be in, the
   things somebody wants delivered. One column turns a form into a scroll: 25
   options is 25 rows and the field after it is off the screen. */
.check-grid {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(190px, 1fr));
  gap: 4px 16px; margin-top: 4px;
}
.check-grid .check-line { padding: 2px 0; }
@media (max-width: 520px) { .check-grid { grid-template-columns: 1fr 1fr; gap: 4px 10px; } }

/* The from/to pair on a span of years. */
.year-span { border: 0; padding: 0; margin: 0 0 16px; min-width: 0; }
.year-span legend { padding: 0; font-size: 13.5px; font-weight: 600; margin-bottom: 5px; }
.year-row { display: flex; align-items: center; gap: 10px; }
.year-row input { width: 8em; }
.year-row span { color: var(--text-muted); font-size: 13.5px; }

/* Company or individual. A real fieldset, because the two options are one
   question and a screen reader should hear it that way — and it is the answer
   the server reads to decide whether a website is required. */
.hirer-type { border: 0; padding: 0; margin: 0 0 18px; min-width: 0; }
.hirer-type legend { padding: 0; font-size: 13.5px; font-weight: 600; margin-bottom: 7px; }
.hirer-type .check-line + .check-line { margin-top: 9px; }
.country-pick { margin-top: 4px; }
.country-filter { margin-bottom: 8px; }

.country-list {
  max-height: 210px; overflow-y: auto; overscroll-behavior: contain;
  padding: 10px; border: 1px solid var(--border); border-radius: var(--radius);
  background: var(--surface);
  /* Same reason as the admin country table: `overflow` clips what is painted,
     but the full height is still counted towards the page's scrollable area
     unless containment says otherwise. */
  contain: paint;
}
.country-list.is-muted { opacity: .45; pointer-events: none; }
.country-count { margin: 7px 0 0; font-variant-numeric: tabular-nums; }
.country-note {
  margin: 7px 0 0; padding: 8px 10px; border-radius: var(--radius);
  background: var(--surface-2); font-size: 12.5px; color: var(--text-muted);
}

/* ============================================================================
   v2.6 — proof stats
   ==========================================================================

   A row of figures, sized to the space they land in: three across in the
   posting sidebar, as many as fit on the homepage. Measured and claimed rows
   look identical on purpose — a visitor has no use for the distinction, and it
   is the OWNER who needs to be reminded which is which, in the panel. */

.proof-row { margin-top: 14px; }
.proof-stats {
  display: grid; gap: 10px;
  grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
}
.proof-stat {
  padding: 11px 13px; border: 1px solid var(--border); border-radius: var(--radius);
  background: var(--surface);
}
.proof-stat b {
  display: block; font-size: 21px; font-weight: 750; letter-spacing: -.03em;
  line-height: 1.15; color: var(--text); font-variant-numeric: tabular-nums;
}
.proof-stat span {
  display: block; margin-top: 2px; font-size: 12px; line-height: 1.35;
  color: var(--text-dim);
}
/* On the homepage it sits between the hero and the listings, so it gets the
   section's own rhythm rather than the sidebar's. */
.wrap > .proof-row { margin: 22px 0 0; }

/* A testimonial photo occupies the same circle the initials do, so a column
   mixing the two lines up. */
.t-mark.is-photo { object-fit: cover; padding: 0; }

/* ---------- Accepted cards, at checkout ---------------------------------
   Names in text beside one neutral card glyph. The networks' own marks are
   their trademarks and are not ours to redistribute inside a template — see
   app/Trust.php. */

.card-row { margin-top: 16px; text-align: center; }
.card-row-label {
  display: block; font-size: 11px; font-weight: 700; letter-spacing: .07em;
  text-transform: uppercase; color: var(--text-dim); margin-bottom: 8px;
}
.card-chips { display: flex; flex-wrap: wrap; gap: 7px; justify-content: center; }
.card-chip {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 5px 10px; border: 1px solid var(--border); border-radius: var(--radius);
  background: var(--surface); font-size: 12.5px; color: var(--text-muted);
}
.card-mark { flex: 0 0 auto; opacity: .8; }
.card-row-note {
  margin: 9px auto 0; max-width: 46ch; font-size: 12px; line-height: 1.5;
  color: var(--text-dim);
}

/* ---------- Sign in, on the posting page ---------------------------------
   This was a link in the middle of a sentence, and it was reported as
   impossible to see. It was: the one action that saves an employer the most
   typing had exactly the weight of the prose around it. A card and a real
   button, with the "no account needed" reassurance underneath rather than
   inside — a person who does not want an account has to be told so before
   they start reading the form for a way past it. */
.hire-account {
  display: flex; align-items: center; justify-content: space-between;
  gap: 18px; flex-wrap: wrap;
  margin: 18px 0 0; padding: 14px 16px;
  border: 1px solid var(--border-strong); border-radius: var(--radius-lg);
  background: var(--surface);
}
.hire-account-body { min-width: min(100%, 22ch); flex: 1 1 30ch; }
.hire-account-lead { margin: 0; font-size: 15px; }
.hire-account-sub { margin: 3px 0 0; font-size: 13.5px; color: var(--text-muted); line-height: 1.5; }
.hire-account-actions { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.hire-account-actions .btn { white-space: nowrap; }
.btn-quiet { background: transparent; border-color: transparent; color: var(--text-muted); }
.btn-quiet:hover { background: var(--surface-hover); border-color: var(--border); color: var(--text); }
.hire-account-closed { font-size: 12.5px; color: var(--text-dim); }
.hire-account-guest { margin: 9px 0 0; font-size: 13px; color: var(--text-dim); }

@media (max-width: 560px) {
  .hire-account { align-items: stretch; }
  .hire-account-actions { width: 100%; }
  .hire-account-actions .btn { flex: 1; justify-content: center; }
}

/* ---------- Receipt ------------------------------------------------------
   Printable: the header, footer and buttons come off, and the card border is
   dropped so it reads as a document rather than a screenshot of one. */

.invoice-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 16px; margin-bottom: 16px; }
.invoice-parties { display: grid; gap: 20px; grid-template-columns: 1fr 1fr; margin-bottom: 18px; }
.invoice-parties p { margin: 0; font-size: 14px; line-height: 1.55; }
.invoice-lines th { width: 34%; color: var(--text-dim); font-weight: 500; }
.invoice-total th, .invoice-total td { border-top: 1px solid var(--border-strong); font-size: 15px; }
@media (max-width: 560px) { .invoice-parties { grid-template-columns: 1fr; } }

/* The optional billing block on the payment step. Closed by default — most
   buyers do not need it, and an open form of three fields between somebody and
   a Pay button is three reasons to hesitate. */
.checkout-billing {
  margin-top: 16px; text-align: left; border: 1px solid var(--border);
  border-radius: var(--radius); background: var(--surface); padding: 12px 14px;
}
.checkout-billing summary { cursor: pointer; font-size: 13.5px; font-weight: 600; }
.checkout-billing summary span {
  font-weight: 500; color: var(--text-dim); font-size: 12px; text-transform: uppercase;
  letter-spacing: .05em; margin-left: 6px;
}
.checkout-billing form { margin-top: 12px; }

@media print {
  .page-invoice .site-header, .page-invoice .site-footer, .page-invoice .js-print { display: none !important; }
  .page-invoice .card { border: 0; padding: 0; background: none; }
}

/* ---------- The directory ------------------------------------------------
   A grid of people rather than a table of rows. A directory is browsed by
   face and name — the thing somebody is deciding is "do I want to talk to this
   person", and that decision is not made from a table cell. */

.dir-controls {
  display: flex; gap: 10px; flex-wrap: wrap; align-items: center;
  margin: 0 0 20px; padding: 14px 16px;
  border: 1px solid var(--border); border-radius: var(--radius-lg);
  background: var(--surface);
}
.dir-controls input[type='search'] { flex: 1 1 260px; min-width: 0; }
.dir-controls select { flex: 0 1 auto; }
.dir-count { flex-basis: 100%; margin: 0; }

.dir-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(330px, 1fr)); gap: 14px; }
@media (max-width: 560px) { .dir-grid { grid-template-columns: 1fr; } }

.dir-card {
  display: flex; gap: 14px; align-items: flex-start;
  padding: 16px; border: 1px solid var(--border); border-radius: var(--radius-lg);
  background: var(--surface);
}
.dir-card.is-featured { border-color: color-mix(in srgb, var(--accent) 45%, var(--border)); }
.dir-photo {
  width: 64px; height: 64px; border-radius: 14px; flex-shrink: 0;
  object-fit: cover; background: var(--surface-2);
}
.dir-photo.is-initials {
  display: grid; place-items: center; font-weight: 800; font-size: 20px;
  color: #fff; background: var(--logo-bg, #2a3040); letter-spacing: -.02em;
}
.dir-name { margin: 0 0 2px; font-size: 16px; letter-spacing: -.015em; }
.dir-name a { color: var(--text); text-decoration: none; }
.dir-name a:hover { color: var(--accent); }
.dir-headline { margin: 0 0 4px; font-size: 13.5px; color: var(--text-muted); line-height: 1.45; }
.dir-meta { margin: 0 0 8px; font-size: 12.5px; color: var(--text-dim); }

.dir-header { display: flex; gap: 18px; align-items: flex-start; margin: 18px 0 26px; }
.dir-header .dir-photo { width: 88px; height: 88px; border-radius: 18px; font-size: 28px; }

.empty-note {
  padding: 30px; text-align: center; color: var(--text-dim);
  border: 1px dashed var(--border-strong); border-radius: var(--radius-lg);
}
