/* OpenMarketIL. Swiss grid, modernized. System rules, don't break them
   ad hoc elsewhere in this file:
   1. Warm off-white page background (--white); forest-green ink is a
      component color (buttons, rules), never on <body> directly.
   2. Green marks exactly one "this matters" element, never decoration.
   3. 4px radius on every discrete bordered box (buttons, chips, badges,
      inputs, floating panels); sharp (0) only for what isn't really a
      box -- grid-tile cards, the table, dividers. No box-shadow depth
      anywhere either way. See DESIGN.md's Shapes section for the full
      list of what stays sharp and why.
   4. Two voices: --font (Helvetica) for everything dense; --font-display
      (Overused Grotesk, bold/extrabold) only for the topbar and the two section
      titles.
   5. Job Board and Statistics are permanent, side by side above the
      mobile breakpoint (where they stack), never a drawer or toggle.
   6. Light/dark both read var(--white)/var(--black), never a literal
      hex, but the two palettes are tuned independently rather than one
      being a straight hex-invert of the other. */

@font-face {
  font-family: "Overused Grotesk";
  src: url("fonts/OverusedGrotesk-VF.woff2") format("woff2-variations");
  font-weight: 300 900;
  font-display: swap;
}

/* Source Sans 3 for controls (buttons, inputs, the view switch, chips,
   badges), per request. Self-hosted for the same reason as Overused
   Grotesk: no third-party font request on every page view. The variable
   files cover weights 200 to 900; latin-ext carries the accented letters
   company and city names need. OFL, licence beside the files. */
@font-face {
  font-family: "Source Sans 3";
  src: url("fonts/SourceSans3-VF-latin.woff2") format("woff2-variations");
  font-weight: 200 900;
  font-display: swap;
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Source Sans 3";
  src: url("fonts/SourceSans3-VF-latin-ext.woff2") format("woff2-variations");
  font-weight: 200 900;
  font-display: swap;
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

:root {
  --white: #f2f0ef;
  /* Ink that stays ink in dark mode, for the one section that keeps a
     light ground in both themes (see .hero-showcase). */
  --ink-fixed: #40513b;
  --black: #40513b;
  --green: #609966;
  --green-bright: #80ad85;
  /* The loading sweep runs along the topbar's dark rule, not on paper, so
     it is measured against #40513b: --green was 2.5:1 there and hard to
     see. This is 5.1:1. */
  --grey: #40513b;
  /* Neutral, not green. The structure (row dividers, panel and tile
     borders, bar tracks) used to be a sage tint, which made the whole
     page read green before any of the green that means something. Green
     is now kept for signals only. 1.37:1 on paper, a touch clearer than
     the old #c7d9b3 at 1.32:1. */
  --grey-line: #d2cfcb;
  --red: #b8362c;
  --hover-bg: #e8e5e2; /* neutral too; was the sage #e9efdb at 1.04:1 */
  /* Board panel outlines, and nothing else. Darker than --grey-line
     because it has to read against the tinted ground the panels sit on,
     where the hairline used between two things on paper disappears. */
  --panel-line: #a7ad9f;
  /* Behind a salary the employer published, and nothing else. An
     estimate gets the same green outline with no fill, so the two are
     told apart by the fill rather than by reading the word "Est.". */
  --salary-fill: #e4ece0;
  /* Row states, apart from --hover-bg (which still tints menu options)
     so hovering a row and having it open never look the same. Both are
     neutral now; the open one is a shade deeper and carries an ink bar
     down its left edge, the way LinkedIn marks the job you are reading.
     --row-selected keeps its green for the two things that still point
     at something: a search match (mark) and a checked filter's tile. The
     old shared tint was 1.04:1 against paper and barely visible; hover is
     1.13:1 and open is 1.22:1. */
  --row-hover: #e6e3df;
  --row-open: #dedad5;
  --row-selected: #dce8d4;
  /* Green for text, and for fills that text sits on. --green is 2.96:1
     on paper, under the 4.5:1 body text needs; this is 5.18:1, and still
     4.6:1 on --row-hover. --green stays for marks with no text in them:
     bars, chart lines, focus rings, checkboxes. */
  --green-text: #3f6f45;
  /* Dims the board behind the job sheet. Its own token
     because it has to darken in both themes, and --black is a light
     colour in dark mode -- reusing it there would brighten the page
     behind the sheet instead of pushing it back. */
  --scrim: rgba(38, 36, 34, 0.32);

  /* AWS service-health status colours, used only for pipeline state.
     Kept apart from --green/--red, which are brand and carry the One
     Voice Rule; these say "operational/degraded/disrupted" and nothing
     else. --aws-maintenance is unused so far and is here so a scheduled
     pause has somewhere to go rather than borrowing the warning colour. */
  --aws-operational: #248823;
  --aws-degraded: #ff9900;
  --aws-outage: #d13212;
  --aws-maintenance: #0073bb;
  --font: "Helvetica Neue", Helvetica, Arial, sans-serif;
  --font-display: "Overused Grotesk", "Helvetica Neue", Helvetica, Arial, sans-serif;
  --font-ui: "Source Sans 3", "Helvetica Neue", Helvetica, Arial, sans-serif;

  /* Side padding for .workspace/.container. Scales with viewport
     (20px floor, 64px ceiling) instead of a fixed max-width box. */
  --gutter: clamp(20px, 4vw, 64px);
  /* The gutter, or the screen's own cutout inset where that is wider.
     Used by anything whose content runs to the page edge. */
  --edge: max(var(--gutter), env(safe-area-inset-left), env(safe-area-inset-right));
  /* The ground the logo row stands on, light whatever the theme: company
     marks are drawn for a light page and a third of them are white. */
  --logo-band: #edf0ec;
  --logo-band-rule: #d6ded6;
  --rule: 2px;

  /* Forces native chrome (input caret, scrollbars) to follow OUR toggle
     instead of the OS's own dark-mode setting, which would otherwise
     paint them dark against our light surfaces. */
  color-scheme: light;
}

:root[data-theme="dark"] {
  /* AWS's own light-background status colours go muddy on #17181c,
     particularly the green, which reads closer to black than to a
     signal. Lifted for contrast, same hues. */
  --aws-operational: #3fb950;
  --aws-degraded: #ffab33;
  --aws-outage: #ff5d47;
  --aws-maintenance: #539fe5;
  --white: #17181c;
  --black: #ededec;
  --green: #2fae60;
  --green-bright: #4ee585;
  --grey: #9a9a9a;
  --grey-line: #2b2c31;
  --hover-bg: #1e1f24;
  --panel-line: #4a4c52;
  --salary-fill: #1a2a20;
  --row-hover: #23252b;
  --row-open: #2b2e35;
  --row-selected: #1d3024;
  --green-text: #2fae60; /* same as --green: already 6.2:1 on #17181c */
  /* The light red is 3.0:1 on #17181c, under the 4.5:1 text needs, so
     every red message and the red Reset were faint in dark mode. 6.4:1. */
  --red: #ff6b5e;
  --scrim: rgba(0, 0, 0, 0.58);
  color-scheme: dark;
  /* The landing page honours the theme now, and a light band across it
     read as a stripe of daylight. The marks keep their own light square
     on the board, where each is 44px; here the row is the page's. */
  --logo-band: #1b1c21;
  --logo-band-rule: #2b2c31;
}

* { box-sizing: border-box; }

html, body {
  margin: 0;
  padding: 0;
  background: var(--white);
  color: var(--black);
}

body {
  font-family: var(--font);
  font-size: 14px;
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
}

/* iOS-only behaviour, so the iPhone site matches the Android one.
   text-size-adjust: Safari enlarges text on its own when a phone turns
   landscape; this keeps the sizes the stylesheet sets. */
html { -webkit-text-size-adjust: 100%; text-size-adjust: 100%; }
/* Safari draws text inputs with its own inner shadow and rounding
   underneath the borders set here. */
input[type="text"], input[type="search"], input[type="email"], textarea {
  -webkit-appearance: none;
  appearance: none;
}
/* Safari zooms the whole page when a field under 16px takes focus, and
   leaves it zoomed. Every field here is 12.5 to 13px, so tapping Search on
   an iPhone zoomed the board in; Android does not do this. 16px on touch
   screens only, desktop keeps its sizes. !important because the fields'
   own rules are more specific, and this must win over all of them. */
@media (pointer: coarse) {
  input:not([type="checkbox"]):not([type="radio"]), select, textarea { font-size: 16px !important; }
}

::selection { background: var(--black); color: var(--white); }

/* Custom scrollbars, theme-tokened like the rest of the page. The thumb's
   border uses the track color, the standard trick for an inset look. */
html { scrollbar-width: thin; scrollbar-color: var(--black) var(--white); }
*::-webkit-scrollbar { width: 12px; height: 12px; }
*::-webkit-scrollbar-track { background: var(--white); }
*::-webkit-scrollbar-thumb {
  background: var(--black);
  border: 3px solid var(--white);
  background-clip: padding-box;
}
*::-webkit-scrollbar-thumb:hover { background: var(--green); }
*::-webkit-scrollbar-corner { background: var(--white); }

a { color: inherit; }
a.link { color: var(--green-text); text-decoration: none; border-bottom: 1px solid var(--green-text); }
a.link:hover { background: var(--green-text); color: var(--white); }

.icon-link { display: inline-flex; align-items: center; color: var(--black); }
.icon-link:hover { color: var(--green-text); }

button, input, select {
  font-family: var(--font-ui);
  font-size: 13px;
  color: var(--black);
  caret-color: var(--black); /* belt-and-suspenders on top of color-scheme: light above */
}

.container {
  /* viewport-fit=cover lets the page reach the true screen edge, so the
     gutter has to clear the cutout itself: on a notched phone in
     landscape the inset is larger than the gutter, and in portrait it is
     zero and this is just the gutter. Any phone with a cutout, not only
     an iPhone. */
  padding-left: max(var(--gutter), env(safe-area-inset-left));
  padding-right: max(var(--gutter), env(safe-area-inset-right));
}

.section {
  border-bottom: var(--rule) solid var(--black);
  padding: 12px 0 40px;
}

.section-title {
  font-family: var(--font-display);
  font-size: 34px;
  font-weight: 400;
  line-height: 1;
  margin: 0 0 12px;
}
/* Inside the Statistics column the title sits beside 13px filter
   controls, and at 34px it was the loudest thing on the page. 24px
   still reads as the column's label. */
.stats-board .section-title { font-size: 24px; line-height: 1.1; }

/* Topbar */

.topbar {
  font-family: var(--font-display);
  position: sticky;
  top: 0;
  background: var(--white);
  z-index: 10;
}

/* The board only. The same hairline the listing rows are divided by,
   not the 2px ink rule: the bar is navigation, not a content boundary,
   and a faint line is enough to hold it off the table. Nowhere else has
   a grid starting directly under the bar, and a line across the top of
   a page that opens with a title was dividing it from nothing. */
.topbar-board { border-bottom: 1px solid var(--grey-line); }

.topbar .container {
  display: flex;
  align-items: center;
  gap: 16px;
  /* nowrap, deliberately: .topbar-search has min-width:0 so it shrinks
     to fit instead. flex-wrap here wrapped the whole topbar onto 3 lines
     at any width short of "everything fits." */
  flex-wrap: nowrap;
  min-height: 56px;
  padding-top: 10px;
  padding-bottom: 10px;
}
.topbar .container > * { min-width: 0; } /* same min-width:auto issue as everywhere else in this file */
.topnav { flex: 0 0 auto; } /* natural width; the search is the one flexible element beside it */

/* The board's search box, when it is in the topbar. Above 800px this is
   its home; below it the same node sits in the filter bar (the rules at
   the bottom of this file) and this element is only the carrier, so the
   field's chrome is on the wrapper and the input inside is bare. */

.topbar-search {
  width: 560px;
  min-width: 0;
  display: flex;
  align-items: center;
  gap: 8px;
  height: 36px;
  padding: 0 6px 0 14px;
  border: 1.5px solid var(--black);
  border-radius: 999px;
  background: var(--white);
}
/* The ring goes on the wrapper, not the input: the input has no edge of
   its own to draw one on. */
.topbar-search:focus-within { outline: 2px solid var(--green); outline-offset: -2px; }
.topbar-search-icon { flex: 0 0 auto; color: var(--grey); }
.topbar-search:focus-within .topbar-search-icon { color: var(--green-text); }

.topbar-search input {
  flex: 1 1 auto;
  min-width: 0;
  height: 100%;
  padding: 0;
  border: 0;
  background: none;
  color: var(--black);
  font: 400 13px var(--font-ui);
}
.topbar-search input:focus { outline: none; }
.topbar-search input::placeholder { color: var(--grey); opacity: 0.75; }

.topbar-search-clear {
  flex: 0 0 auto;
  display: grid;
  place-items: center;
  width: 24px;
  height: 24px;
  padding: 0;
  border: 0;
  border-radius: 999px;
  background: none;
  color: var(--grey);
  line-height: 0;
  cursor: pointer;
}
.topbar-search-clear:hover { background: var(--hover-bg); color: var(--black); }
.topbar-search-clear:focus-visible { outline: 2px solid var(--green); outline-offset: 1px; }
.topbar-search-clear[hidden] { display: none; }

.topnav {
  /* Keeps the nav on the right on the pages with nothing else in the
     bar (account, stats, privacy, 404). The wordmark used to hold the
     left edge; the bar is empty there now. */
  margin-left: auto;
  display: flex;
  align-items: center;
  gap: 28px;
  font-size: 12px;
}

/* The theme control, a 64x32 switch: a knob that sits over the moon on
   the dark theme and slides 32px to sit over the sun on the light one.
   The state is read straight off :root[data-theme], so the CSS is the
   only thing that knows which side is which; the script sets
   aria-checked and nothing else. Built here rather than pulled in as a
   React component, because this site has no React, no build step and no
   Tailwind, and a 64px control is not a reason to grow all three. */
.theme-toggle {
  position: relative;
  flex: none;
  width: 64px;
  height: 32px;
  padding: 4px;
  box-sizing: border-box;
  border: 1px solid var(--grey-line);
  border-radius: 999px;
  background: var(--white);
  cursor: pointer;
  transition: background-color 0.3s ease, border-color 0.3s ease;
}
.theme-toggle:focus-visible { outline: 2px solid var(--green); outline-offset: 2px; }
.theme-knob {
  position: absolute;
  top: 3px;
  left: 3px;
  width: 24px;
  height: 24px;
  border-radius: 50%;
  background: var(--row-hover);
  transform: translateX(32px); /* over the sun: light is the default theme */
  transition: transform 0.3s ease, background-color 0.3s ease;
}
.theme-ico {
  position: absolute;
  top: 7px;
  transition: color 0.3s ease;
}
.theme-ico-moon { left: 7px; color: var(--grey); }
.theme-ico-sun { left: 39px; color: var(--black); }
:root[data-theme="dark"] .theme-toggle { border-color: var(--panel-line); }
:root[data-theme="dark"] .theme-knob { transform: translateX(0); }
:root[data-theme="dark"] .theme-ico-moon { color: var(--black); }
:root[data-theme="dark"] .theme-ico-sun { color: var(--grey); }
.theme-toggle:hover { border-color: var(--green); }
@media (prefers-reduced-motion: reduce) {
  .theme-knob, .theme-toggle, .theme-ico { transition: none; }
}

/* Auth. Same dropdown shape as .ms-menu (position:relative parent,
   absolute panel, 2px ink border, paper background) -- one hand-built
   pattern, not a second one invented for this. */
/* display:flex here is only for the signed-in case's two triggers
   (+ Alert, email) sitting side by side -- the signed-out case's single
   trigger and the (always position:absolute, out of flow) panel are
   unaffected either way. */
.auth-area { position: relative; display: flex; align-items: center; gap: 12px; }
/* Worn by a button when signed out and by the account link when signed
   in, so it has to strip a link's underline as well as a button's chrome.
   The transparent bottom border below is what hover and .active colour
   in, and text-decoration would sit under it as a second line. */
.auth-trigger {
  background: none;
  border: none;
  font-family: var(--font-ui);
  font-size: 13px;
  font-weight: 700;
  color: inherit;
  padding: 0;
  cursor: pointer;
  text-decoration: none;
  border-bottom: 1px solid transparent;
}
.auth-trigger:hover, .auth-trigger.active { color: var(--green-text); border-color: var(--green-text); }

/* The signed-in trigger is a glyph rather than text, so it drops the
   underline the text version uses to show hover and lets the icon's own
   currentColor carry that instead. 24px box, not 16: the ring in the
   #account symbol is 18 units wide in a 24-unit viewBox, so at 16px it
   drew a 12px circle beside the GitHub mark's 18px, and read as
   miniscule. Reported live. At 24px the ring is 18px across, the same as
   the GitHub mark next to it. */
.auth-account {
  display: inline-flex;
  align-items: center;
  border-bottom: none;
}
.account-icon {
  width: 24px;
  height: 24px;
  display: block;
}

.auth-signed-in {
  display: flex;
  align-items: center;
  gap: 10px;
  font-size: 12px;
  text-transform: none;
  letter-spacing: normal;
}
.auth-signed-in .auth-email {
  color: var(--grey);
  max-width: 160px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.auth-signout {
  background: none;
  border: none;
  font-family: var(--font-ui);
  font-size: 13px;
  font-weight: 700;
  color: inherit;
  padding: 0;
  cursor: pointer;
}
.auth-signout:hover { color: var(--red); }

.alerts-header {
  font-family: var(--font-display);
  font-size: 12px;
  font-weight: 700;
  margin-bottom: 10px;
}

.alerts-empty {
  font-size: 12px;
  color: var(--grey);
  margin: 0 0 12px;
}

.alert-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 8px 0;
  border-bottom: 1px solid var(--grey-line);
}
.alert-row:last-child { border-bottom: none; }

/* A saved listing reuses .alert-row, because it is the same object on
   this page: one line, a label on the left, one control on the right.
   The title is a link out to the posting rather than a button, since
   there is nothing to edit here, only somewhere to go. */
.saved-main { min-width: 0; }
.saved-title {
  font-weight: 600;
  color: var(--black);
  text-decoration: none;
}
.saved-title:hover { color: var(--green-text); text-decoration: underline; }
/* The title is the link out to the listing, so on a touch screen it
   needs a box rather than a line of text to hit. */
@media (pointer: coarse) {
  .saved-title { display: inline-block; padding: 5px 0; }
}
.saved-meta {
  color: var(--grey);
  font-size: 12px;
  margin-top: 2px;
  overflow-wrap: anywhere;
}

/* A button, not a span: clicking the summary is how you edit the alert,
   so it has to be reachable by keyboard and announce itself as a
   control. Styled back down to look like the line of text it replaced,
   with the underline on hover as the only affordance it needs at this
   size. */
.alert-summary {
  font-size: 12px;
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  appearance: none;
  border: none;
  background: none;
  padding: 0;
  margin: 0;
  font-family: var(--font);
  color: inherit;
  text-align: left;
  cursor: pointer;
}
.alert-summary:hover { text-decoration: underline; }
.alert-summary:focus-visible { outline: 2px solid var(--green); outline-offset: 2px; }
.alert-row.paused .alert-summary { color: var(--grey); }
/* Which row the form below is currently holding. */
.alert-row.editing .alert-summary { font-weight: 700; text-decoration: underline; }

.alert-actions { display: flex; align-items: center; gap: 8px; flex: 0 0 auto; }

.alert-toggle {
  background: none;
  border: 1px solid var(--black);
  border-radius: 4px;
  padding: 3px 8px;
  font-family: var(--font-ui);
  font-size: 13px;
  color: var(--black);
  cursor: pointer;
}
.alert-toggle:hover { background: var(--black); color: var(--white); }
/* The whole row is three controls on one line, and on a touch screen
   all three were between 14px and 26px tall. Padding only, so the row
   grows once and the three stay on their line. */
@media (pointer: coarse) {
  .alert-summary { padding: 11px 0; }
  .alert-toggle { padding: 8px 12px; }
}

.alert-delete {
  background: none;
  border: none;
  color: var(--grey);
  font-size: 13px;
  line-height: 1;
  cursor: pointer;
  padding: 2px;
}
.alert-delete:hover { color: var(--red); }
/* A 17px glyph is not a thumb target. The padding grows the tap box
   without moving the glyph, so the row keeps its height. */
@media (pointer: coarse) {
  .alert-delete { padding: 12px; margin: -12px 0; }
}

.create-alert-feedback {
  margin: 8px 0 0;
  font-size: 12px;
  color: var(--green-text);
}
.create-alert-feedback.error { color: var(--red); }

/* New-alert form, inside the My Alerts panel (app.js's wireAlertCreateForm) --
   its own filter pickers, independent of the board's .filters above. */
.alert-create {
  margin-top: 12px;
  padding-top: 12px;
  border-top: 1px solid var(--grey-line);
}
.alert-create-fields {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin-bottom: 10px;
}
.alert-create-fields input[type="text"] {
  border: var(--rule) solid var(--black);
  border-radius: 4px;
  background: var(--white);
  padding: 9px 10px;
  height: 38px;
  width: 100%;
  font-family: var(--font-ui);
}
.alert-create-fields input[type="text"]:focus { outline: 2px solid var(--green); outline-offset: -2px; }
.alert-create-fields label.toggle {
  display: flex;
  align-items: center;
  gap: 8px;
  height: 38px;
  padding: 0 10px;
  border: var(--rule) solid var(--black);
  border-radius: 4px;
  font-size: 12px;
  cursor: pointer;
}
.alert-create-fields label.toggle input { accent-color: var(--green); }
.alert-form-actions { display: flex; gap: 8px; }
.alert-create .btn { width: 100%; }
/* Cancel sits beside Save, and must not compete with it. */
.alert-create .btn-quiet {
  width: auto;
  flex: 0 0 auto;
  background: none;
  color: var(--black);
  border: var(--rule) solid var(--black);
}
.alert-create .btn-quiet:hover { background: var(--black); color: var(--white); }

/* 4px radius -- same scoped exception as .job-detail/.ms-menu above. */
.auth-panel {
  position: absolute;
  top: calc(100% + 4px);
  right: 0;
  width: 260px;
  max-height: calc(100vh - 80px);
  max-height: calc(100dvh - 80px);
  overflow-y: auto;
  background: var(--white);
  border: var(--rule) solid var(--black);
  border-radius: 4px;
  z-index: 40;
  padding: 14px;
  padding-bottom: max(14px, env(safe-area-inset-bottom));
  text-transform: none;
  letter-spacing: normal;
  font-family: var(--font);
}
.auth-panel.alerts-panel { width: 320px; } /* alert summaries + action buttons, plus the new-alert form, need more room than the sign-in form */

.auth-provider-btn {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  height: 38px;
  padding: 0 12px;
  margin-bottom: 8px;
  border: var(--rule) solid var(--black);
  border-radius: 4px;
  background: var(--white);
  color: var(--black);
  font-family: var(--font-ui);
  font-size: 13px; /* Body, matching .filters input/.ms-toggle -- 12.5px sat in the gap between Label and Body for no reason */
  font-weight: 700;
  text-align: left;
  cursor: pointer;
}
.auth-provider-btn svg { flex: 0 0 auto; } /* Google's mark keeps its own brand colors on hover (fill="currentColor" only on GitHub's, which does follow) */
.auth-provider-btn:hover { background: var(--black); color: var(--white); }

.auth-divider {
  text-align: center;
  font-size: 12px;
  color: var(--grey);
  margin: 10px 0;
}

.auth-email-form { display: flex; gap: 6px; }
.auth-email-form[hidden] { display: none; } /* same fix as .job-detail[hidden]: a class rule's display beats the UA [hidden] stylesheet without this */
.auth-email-form input {
  flex: 1 1 auto;
  min-width: 0;
  height: 38px;
  padding: 9px 10px;
  border: var(--rule) solid var(--black);
  border-radius: 4px;
  background: var(--white);
  color: var(--black);
  font-family: var(--font-ui);
  font-size: 13px;
}
.auth-email-form input:focus { outline: 2px solid var(--green); outline-offset: -2px; }
.auth-email-form .btn { flex: 0 0 auto; padding: 0 12px; font-size: 12px; }

.auth-error {
  margin: 8px 0 0;
  font-size: 12px;
  color: var(--red);
}

/* A square block, not a circle, per the sharp-corners rule. Reported
   live: the flicker animation this used to have read as distracting,
   not lively -- a flat, static color communicates the same LIVE/OFFLINE
   state without asking for attention every 1.6s. */
/* Status glyphs, on AWS's own service-health vocabulary rather than a
   colour alone. Three states, because two wastes the middle: the write
   cycle is 5 minutes, so a snapshot 10 minutes old has missed two of
   them and is worth saying so about, while 20 minutes is the point the
   whole board stops claiming to be current.

   The shape is what carries the state. Colour on its own fails in
   greyscale, fails for a red-green colourblind reader, and fails at the
   13px the topbar renders this. A tick, a bang and a cross do not. */
.status-icon {
  width: 13px;
  height: 13px;
  display: block;
  margin-right: 6px;
  color: var(--aws-operational);
  flex: 0 0 auto;
}
.status-icon.degraded { color: var(--aws-degraded); }
.status-icon.outage { color: var(--aws-outage); }
/* Updating: the data is older than it should be, but the merge reported
   in within the last few minutes, so an update is on its way rather than
   the pipeline being down. Ink, not a status colour, because it is not a
   warning. */
.status-icon.updating {
  color: var(--grey);
  transform-origin: 50% 50%;
  animation: status-spin 0.9s linear infinite;
}
@keyframes status-spin { to { transform: rotate(360deg); } }

/* The Data Health tile shows the same state next to a 30px number, so
   the glyph is sized to that line rather than the topbar's. */
.metric-card .value .status-icon {
  width: 24px;
  height: 24px;
  display: inline-block;
  vertical-align: -3px;
  margin-right: 8px;
}

/* Workspace: Job Board (main) + Market Stats (a permanent right-hand
   board, not a drawer/overlay). Stacks vertically under the responsive
   breakpoint at the end of this file. */

.workspace {
  display: grid;
  grid-template-columns: 1fr clamp(360px, 28vw, 460px);
  align-items: start;
  padding: 0 var(--gutter);
}

/* min-width:0. Without it a grid item can't shrink below its content's
   natural width, so one long unbreakable value (a table row, a bar-list
   name) forces the whole page wider instead of wrapping locally. */
.workspace > #board,
.workspace > .stats-board,
/* Every other page puts a plain .section in this same grid and none of
   them were given the same treatment: the API reference, privacy, 404,
   account and the board itself all match. It went unnoticed while their
   content had nothing unbreakable in it. A full curl example is one
   574px line, and the API reference's code blocks are already
   overflow-x:auto so they can scroll on their own; without this the
   grid item refuses to shrink and the document goes 634px wide on a
   390px phone instead. Measured on all five before and after: doc
   width, grid columns and every panel width are identical, so this
   only ever releases a constraint nothing was relying on. */
.workspace > .section {
  min-width: 0;
}

/* Container, not just a grid item: #board's own width is what the
   filter bar actually has to fit in, and that's decoupled from the
   viewport once the two-column workspace grid is active (Market Stats'
   clamp(360px, 28vw, 460px) column eats a variable, viewport-independent
   chunk of the row). A viewport-width media query for the filter-wrap
   breakpoint below got this wrong: it never fired above 640px viewport
   width even though #board itself could be narrower than that, cutting
   the filter row off instead of wrapping it. See the @container rule in
   the responsive section for the actual fix. */
#board { container-type: inline-size; }

/* No border at all, per request -- the muted row separators inside
   #board are the only divider system now; nothing artificially walls
   the board off from Market Stats beside it. Padding stays, for
   breathing room between the two columns' content. */
.workspace > #board {
  padding-right: var(--gutter);
}
.workspace > #board .container { padding: 0; }

.stats-board {
  position: sticky;
  top: 57px; /* topbar height + its 2px bottom rule */
  max-height: calc(100vh - 57px);
  max-height: calc(100dvh - 57px);
  overflow-y: auto;
  /* container-type: this panel's real width is clamp(360px, 28vw, 460px)
     (see .workspace), decoupled from the viewport the same way #board's
     own width is -- a plain viewport media query for the single-column
     fallback below would get the breakpoint wrong for the same reason
     the filter-wrap one originally did (see #board's own comment). */
  container-type: inline-size;
  /* Right padding is half the left gutter, floored at 16px -- not zero.
     Reported live: at --gutter's own 20px floor, half of that (10px) is
     narrower than the actual scrollbar (12px, see *::-webkit-scrollbar
     above), so the scrollbar overlapped the last couple pixels of
     content -- and because overflow-y:auto alone forces overflow-x to
     auto too (the CSS spec computes a bare "visible" as "auto" once the
     other axis isn't visible), that overlap clipped text at the box's
     right edge instead of just sitting flush against it.
  */
  padding: 12px max(calc(var(--gutter) / 2), 16px) 40px var(--gutter);
}

.stats-board .metrics-grid { grid-template-columns: repeat(2, 1fr); }
.stats-board .panel-grid { grid-template-columns: 1fr; }

/* Collapsing the Statistics column, per request. The toggle folds the
   column away to the right and the board takes the width, the grid column
   easing between its normal width and a 62px rail. Desktop only: below
   960px the column already stacks under the board. The ease runs with
   reduced motion on too, because it was asked for and it moves once, when
   clicked; a collapse saved from an earlier visit is applied by the head
   script in index.html before first paint, so a return does not animate. */
.stats-toggle { display: none; }
@media (min-width: 961px) {
  .workspace { transition: grid-template-columns 0.35s ease; }
  .stats-board > :not(.stats-toggle) { transition: opacity 0.2s ease, visibility 0.2s; }
  .stats-toggle {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 6px;
    position: absolute;
    top: 12px;
    right: max(calc(var(--gutter) / 2), 16px);
    height: 28px;
    min-width: 28px;
    padding: 0 8px;
    border: 1.5px solid var(--black);
    border-radius: 4px;
    background: var(--white);
    color: var(--black);
    font-family: var(--font-ui);
    font-size: 13px;
    cursor: pointer;
  }
  .stats-toggle:hover { background: var(--black); color: var(--white); }
  .stats-toggle:focus-visible { outline: 2px solid var(--green); outline-offset: 2px; }
  .stats-toggle-icon { font-size: 16px; line-height: 1; transition: transform 0.35s ease; }
  .stats-toggle-label { display: none; }

  html.stats-collapsed .workspace { grid-template-columns: 1fr 62px; }
  /* 24px, not the open column's 12px: the column sticks at 58px, so this puts
     the tab's top edge on the filter row's (82px), and the 62px rail
     leaves 12px between the tab and Reset. Reported live: the tab sat
     flush against the filter row, 12px higher and 55px taller than it. */
  html.stats-collapsed .stats-board { overflow: hidden; padding: 24px 0 0; }
  html.stats-collapsed .stats-board > :not(.stats-toggle) { opacity: 0; visibility: hidden; }
  /* The same 38px as the filter controls beside it, so the two read as one row. */
  html.stats-collapsed .stats-toggle {
    position: static;
    display: flex; /* block-level, or margin: auto cannot centre it in the rail */
    width: 38px;
    height: 38px;
    margin: 0 auto;
    padding: 0;
  }
  html.stats-collapsed .stats-toggle-icon { transform: rotate(180deg); }
}

/* The topnav's API link is a control like the buttons beside it. */
.topnav a.link { font-family: var(--font-ui); }

/* The three labelled blocks of the Statistics column (see index.html for
   why they exist). The heading is the Label step DESIGN.md already
   documents (700, 10-12px, uppercase, 0.06-0.1em), the same one
   .panel-title and .metric-card .label use: a heading here introduces a
   block of labels, so a new size would compete with the tiles instead of
   ranking above them. Full ink rather than --grey is what separates it
   from the tile labels underneath. */
.stats-section { margin-top: 18px; }
.stats-section + .stats-section { margin-top: 26px; }

.stats-section-title {
  font-size: 12px;
  font-weight: 700;
  line-height: 1.3;
  color: var(--black);
  margin: 0 0 4px;
}

/* The scope line: which filters the block above is counting, or a plain
   statement that none are. Same 11.5px as .metric-card .sub, so it adds
   no step to the ramp. */
.stats-scope {
  font-size: 11.5px;
  line-height: 1.35;
  color: var(--grey);
  margin: 0 0 10px;
}

/* A grid standing on its own rather than as one half of the
   metrics-grid/panel-grid pair. It keeps neither the -2px seam overlap
   nor the half-rounded corners those two share. Two class names deep so
   it outranks .panel-grid/.metrics-grid wherever they land in the file,
   rather than depending on sitting below them. */
.stats-board .stats-grid-solo {
  margin-top: 0;
  border-radius: 4px;
}

/* Five scoped tiles in a two-column grid leave a sixth cell empty, and
   an empty cell here is not whitespace. It is the hairline background
   showing through where a tile should be. The odd one out takes the
   whole row instead. */
.stats-board .metrics-grid .metric-card:last-of-type:nth-of-type(odd) {
  grid-column: 1 / -1;
}

/* Pipeline status is one tile and always will be. */
#pipeline-grid { grid-template-columns: 1fr; }

/* Same root cause as the padding fix above, one layer up: two columns
   of metric tiles need real width each for their own label/value text,
   and this panel's width can get narrow enough (a smaller window, not
   just a phone) that two columns starts squeezing tile content into
   the same clipped-not-wrapped failure mode. Drop to one column before
   that happens rather than after. */
@container (max-width: 300px) {
  .stats-board .metrics-grid { grid-template-columns: 1fr; }
}

/* Metrics grid */

/* Same shape as .panel-grid/.panel below: the black background is just
   the hairline showing through the gaps. The cells are the light
   surface, matching the (correctly white) panels beside them. */
.metrics-grid {
  display: grid;
  grid-template-columns: repeat(6, 1fr);
  gap: var(--rule);
  /* --grey-line, not --black -- reported live, the dividers read too
     bold/high-contrast (this is the same "quiet divider" token already
     used for table.jobs row separators, same reasoning: a full-strength
     ink line is a section-level rule, not what a dense grid of small
     tiles needs between every cell). */
  background: var(--grey-line);
  border: var(--rule) solid var(--grey-line);
  /* Scoped to just the Market Stats box, per request -- same 4px radius
     as the filter bar, top corners only (the seam it shares with
     panel-grid below stays square). overflow:hidden so the corner
     .metric-card's square white background actually follows the curve
     instead of poking a square corner past it. */
  border-radius: 4px 4px 0 0;
  overflow: hidden;
}

.metric-card {
  background: var(--white);
  color: var(--black);
  padding: 18px 16px;
  min-height: 116px;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
}

.metric-card .label {
  font-size: 12px;
  color: var(--grey);
}

.metric-card .value {
  font-size: 30px;
  font-weight: 800;
  font-variant-numeric: tabular-nums;
  margin-top: 10px;
  color: var(--black);
}

/* Data Health is the one tile whose value is a word rather than a
   figure, and DEGRADED is eight characters where the numbers beside it
   are at most seven digits. At the shared 30px step it wanted 185px in
   a box that offers 136, so it rendered as DEGRADE, clipped mid-word by
   the panel's overflow. Reported live from a screenshot.

   So this tile gets its own step, sized to the longest of the three
   words at the narrowest the tile ever gets, which is 102px at 1440
   wide, where the panel's grid turns three columns. Measured, not
   guessed: every other width gives more room, so a size that holds
   DEGRADED there holds it everywhere. One size for all three, not
   a per-word shrink: LIVE and DEGRADED belong on the same baseline at
   the same weight, or the tile changes shape every time the state does.
   nowrap says out loud that the glyph and its word are one line. */
#metric-api-status .value { font-size: 13px; white-space: nowrap; }
/* The glyph comes down with the word it belongs to, and gives back the
   margin the word needs to stay beside it. */
#metric-api-status .value .status-icon {
  width: 14px;
  height: 14px;
  margin-right: 4px;
  vertical-align: -2px;
}

.metric-card.highlight .value { color: var(--green-text); }
/* Data Health only. The word and its glyph are one statement, so a
   warning bang beside a green LIVE is a contradiction rather than a
   colour choice. */
.metric-card.degraded .value { color: var(--aws-degraded); }
.metric-card.outage .value { color: var(--aws-outage); }
.metric-card.updating .value { color: var(--black); }
.metric-card .sub {
  font-size: 11.5px;
  color: var(--grey);
  margin-top: 4px;
}
/* API Status card only, third line -- same size/color as .sub above,
   just a second one, so it introduces no new type-ramp step.
   tabular-nums keeps the countdown's digits from jittering in width
   as it ticks down every second. */
/* The pipeline activity line. Was a countdown to the next sync, which
   is why the class is named for one; it now carries whatever stage the
   pipeline reports it is in. Renaming the class would touch three files
   to say the same thing, so the comment carries it instead.

   tabular-nums stays because most of these read "sweeping 1,100 of
   3,434 companies due now" and the digits should not jitter as the
   counts change. overflow-wrap because a stage detail is a sentence,
   not a timer, and the tile is narrow. */
.metric-card .sync-countdown {
  font-size: 11.5px;
  color: var(--grey);
  margin-top: 2px;
  font-variant-numeric: tabular-nums;
  overflow-wrap: anywhere;
  line-height: 1.35;
}

/* A tile waiting on its own scoped answer. A scoped /stats was measured
   at up to 2.9s, and for that long the previous filter's number sitting
   there reads as this filter's number, so the tile shows bones instead.
   Same 26px as .sk-tile's own value bone; the card's own min-height
   holds its shape either way, so nothing around it moves. */
.metric-card.pending .value .skeleton {
  width: 52%;
  height: 26px;
  margin-top: 2px;
}

/* Market-insight panels: who's hiring, what for, where. No ATS-vendor
   breakdown here; that's plumbing, not a market signal. */

.panel-grid {
  margin-top: calc(var(--rule) * -1); /* overlaps metrics-grid's bottom border into one shared line, no visible gap */
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: var(--rule);
  background: var(--grey-line); /* same reasoning as metrics-grid above */
  border: var(--rule) solid var(--grey-line);
  /* Bottom corners only -- see metrics-grid above, this is the other
     half of the same visually-seamless box. */
  border-radius: 0 0 4px 4px;
  overflow: hidden;
}

.panel {
  background: var(--white);
  padding: 18px 20px 20px;
}

.panel-title {
  font-size: 12px;
  color: var(--grey);
  margin-bottom: 12px;
  font-weight: 700;
}

.bar-row {
  display: grid;
  grid-template-columns: 1fr 100px 44px;
  align-items: center;
  gap: 12px;
  font-size: 13px;
  padding: 6px 0;
}
.bar-row .name {
  min-width: 0; /* the ellipsis below can't engage without this */
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.bar-row .bar-track { background: var(--grey-line); height: 9px; position: relative; }
.bar-row .bar-fill { background: var(--black); height: 100%; }
.bar-row:first-child .bar-fill { background: var(--green); }
.bar-row .n { text-align: right; font-variant-numeric: tabular-nums; }
.bar-row.clickable { cursor: pointer; }
.bar-row.clickable .name:hover { color: var(--green-text); text-decoration: underline; }
/* The company list renders all 10 and shows 7. The rest are already in
   the page, so View all expands in place with no request. */
.panel:not(.expanded) .bar-row-extra { display: none; }
.bar-more {
  margin-top: 8px;
  padding: 4px 0;
  background: none;
  border: none;
  color: var(--green-text);
  font-family: var(--font-ui);
  font-size: 13px;
  font-weight: 700;
  cursor: pointer;
}
.bar-more:hover { text-decoration: underline; }
.bar-more:focus-visible { outline: 2px solid var(--green); outline-offset: 2px; }

.trend-chart { width: 100%; height: 64px; display: block; overflow: visible; }

/* Shared by every trend line: a single smooth stroke, no fill, no
   per-point markers. .closed overrides the color for the second line
   sharing a chart (new-listings dark, closed the signal green). */
.trend-line { fill: none; stroke: var(--black); stroke-width: 1.5; }
.trend-line.closed { stroke: var(--green); }

.trend-legend {
  display: flex;
  gap: 14px;
  font-size: 12px;
  color: var(--grey);
  margin-top: 6px;
}
.trend-legend .dot { display: inline-block; width: 7px; height: 7px; margin-right: 4px; }
.trend-legend .dot.new { background: var(--black); }
.trend-legend .dot.closed { background: var(--green); }

.trend-axis {
  display: flex;
  justify-content: space-between;
  font-size: 12px;
  color: var(--grey);
  margin-top: 4px;
}

/* One big headline number, for a panel whose whole point is a single
   stat (the dormant-listing rate), not a list of several. */
.ghost-pct {
  font-size: 34px;
  font-weight: 800;
  font-variant-numeric: tabular-nums;
  line-height: 1;
}
.ghost-sub { font-size: 11.5px; color: var(--grey); margin-top: 8px; }

@media (max-width: 720px) {
  .panel-grid { grid-template-columns: 1fr; }
}

/* Job board */

table.jobs tbody tr[data-id] { cursor: pointer; }

/* Multi-select filter. A native <select multiple> needs ctrl/cmd-click
   to pick more than one, not discoverable, so this is a checkbox-dropdown. */
.ms { position: relative; }
.ms-toggle {
  width: 100%;
  height: 38px;
  padding: 9px 10px;
  border: 1.5px solid var(--black);
  border-radius: 4px;
  background: var(--white);
  color: var(--black);
  font-family: var(--font-ui);
  font-size: 13px;
  text-align: left;
  cursor: pointer;
  /* A long label otherwise wraps and spills past this fixed-height box.
     Truncate with an ellipsis instead, same reasoning as .bar-row .name. */
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.ms-toggle.active { border-color: var(--green-text); color: var(--green-text); font-weight: 700; }
.ms-toggle:focus { outline: 2px solid var(--green); outline-offset: -2px; }

/* 4px radius -- same scoped exception as .job-detail above. */
.ms-menu {
  position: absolute;
  top: calc(100% + 4px);
  left: 0;
  width: max(100%, 220px);
  max-height: 320px;
  overflow-y: auto;
  background: var(--white);
  border: var(--rule) solid var(--black);
  border-radius: 4px;
  z-index: 40;
  padding: 8px;
}

.ms-search {
  width: 100%;
  height: 32px;
  padding: 6px 8px;
  margin-bottom: 6px;
  border: 1px solid var(--grey-line);
  border-radius: 4px;
  background: var(--white);
  color: var(--black);
  font-family: var(--font-ui);
  font-size: 12px;
}

.ms-pinned { font-weight: 700; }
.ms-pinned-divider { border-top: 1px solid var(--grey-line); margin: 4px 0; }

.ms-options { display: flex; flex-direction: column; }
.ms-option {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 6px 4px;
  font-size: 12.5px;
  cursor: pointer;
}
.ms-option:hover { background: var(--hover-bg); }
.ms-option input { accent-color: var(--green); flex-shrink: 0; }
.ms-empty { padding: 10px 4px; color: var(--grey); font-size: 12px; }

/* Locations, the one dropdown with two levels: countries, and each
   country's cities folded under it. The chevron is a real button beside
   the <label>, never inside it, because a click anywhere in a label
   toggles that label's checkbox and expanding a country is a different
   act from filtering by it. */
.ms-group-row { display: flex; align-items: center; }
.ms-group-row .ms-option { flex: 1 1 auto; min-width: 0; }
/* Country names plus their count outran the menu (Bosnia and Herzegovina
   (1,204) does it on its own), and .ms-option is a flex row with a
   fixed-size checkbox in it, so something has to give. The name gives; the
   count sits in its own span that never shrinks, because a truncated count
   is the one part of the row that tells the reader nothing. The menu is
   also wider here than the other dropdowns: it is the only one with a
   chevron taking 28px of every row. */
.ms-option-text { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ms-option-count { flex: 0 0 auto; margin-left: -4px; color: var(--grey); white-space: nowrap; font-variant-numeric: tabular-nums; }
.ms-menu:has(.ms-group-row) { width: max(100%, 280px); }
.ms-chevron {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: center;
  /* The arrow itself is 10x6. The rest is hit area: at the drawing's own
     size this is a target you aim at rather than one you hit. */
  width: 28px;
  height: 28px;
  padding: 0;
  border: none;
  background: none;
  color: var(--black);
  cursor: pointer;
}
.ms-chevron svg { transform: rotate(-90deg); transition: transform 120ms ease; }
.ms-chevron[aria-expanded="true"] svg { transform: rotate(0deg); }
.ms-chevron:hover { background: var(--hover-bg); }
.ms-chevron:focus-visible { outline: 2px solid var(--green); outline-offset: -2px; }
/* Collapsed, with cities picked underneath. Without this the board is
   filtered by something the open dropdown gives no sign of at all. */
.ms-chevron.has-picks { color: var(--green-text); }
.ms-cities { display: flex; flex-direction: column; }
/* One checkbox plus .ms-option's own 8px gap, so a city's box starts
   where its country's name does instead of at an invented indent. */
.ms-city { padding-left: 24px; }

.ms-clear {
  width: 100%;
  margin-top: 6px;
  padding: 6px;
  border: 1px solid var(--grey-line);
  border-radius: 4px;
  background: none;
  color: var(--grey);
  font-family: var(--font-ui);
  font-size: 13px;
  cursor: pointer;
}
.ms-clear:hover { border-color: var(--black); color: var(--black); }

/* #f-reset (Reset), not .btn generally -- .btn is used well outside the
   filter bar (Create Alert, Sign Out, etc.), which stays sharp-cornered
   and keeps its uppercase/bold Label-style treatment. This one drops
   back to sentence case/regular weight, same reasoning as label.toggle
   above -- it's still `.btn.ghost` otherwise (border, hover, colors). */
#f-reset {
  border-width: 1.5px;
  border-radius: 4px;
  text-transform: none;
  letter-spacing: normal;
  font-weight: 400;
  /* Red text, per request: Reset clears every filter at once, and the
     colour marks it as the undoing control in a row of choosing ones.
     The border keeps the ghost button's ink. */
  color: var(--red);
}
/* The ghost hover fills the button with ink; red on that fill is
   unreadable, so the text goes to paper like every other ghost button. */
#f-reset:hover { color: var(--white); }

.btn {
  font-family: var(--font-ui);
  border: var(--rule) solid var(--black);
  border-radius: 4px;
  background: var(--black);
  color: var(--white);
  height: 38px;
  padding: 0 16px;
  font-size: 13px;
  font-weight: 700;
  cursor: pointer;
}
/* A link dressed as a button. .btn is written for <button>, which centres its
   own text; an <a> needs it said, and loses the link underline. Reported
   twice: on the 404 page and on /account's See matches. */
a.btn { display: inline-flex; align-items: center; justify-content: center; text-decoration: none; }
.btn:hover { background: var(--green-text); border-color: var(--green-text); }
.btn.ghost { background: var(--white); color: var(--black); }
.btn.ghost:hover { background: var(--black); color: var(--white); border-color: var(--black); }

.chip {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  background: var(--green-text);
  color: var(--white);
  border: var(--rule) solid var(--green-text);
  border-radius: 4px;
  font-family: var(--font-ui);
  font-size: 13px;
  font-weight: 700;
  padding: 6px 10px;
  margin-bottom: 12px;
  cursor: pointer;
}
.chip:hover { background: var(--black); border-color: var(--black); }

.search-notice {
  font-size: 12px;
  color: var(--grey);
  margin-bottom: 8px;
}
.search-notice code, .empty-state code {
  font-family: var(--font-mono, monospace);
  font-size: 12px;
  color: var(--black);
}
/* A button that reads as part of the sentence it sits in. */
.link-inline {
  border: 0;
  padding: 0;
  background: none;
  font: inherit;
  color: var(--green-text);
  border-bottom: 1px solid var(--green-text);
  cursor: pointer;
}
.empty-actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 8px;
  margin-top: 14px;
}
/* The search words, where a row matched them. */
mark {
  background: var(--row-selected);
  color: inherit;
  padding: 0 1px;
}

.result-count {
  font-size: 12px;
  color: var(--grey);
  margin-bottom: 12px;
  /* One line tall while empty too: the count arrives with the rows, and
     an empty div here moved the whole table down 18px when it did. */
  min-height: 1.5em;
}
.result-count b { color: var(--black); }

/* New listings a background refresh held back because the reader was
   scrolled into the list. Sticks under the topbar so it is visible from
   wherever they are; inserting the rows above them would have moved what
   they were reading. */
/* A zero-height sticky strip, so showing the button moves nothing: taking
   room in the flow would itself shift the rows it is there to keep still. */
.new-listings-anchor {
  position: sticky;
  top: 70px; /* holdForReader sets the real value from the topbar's height */
  z-index: 5;
  height: 0;
  display: flex;
  justify-content: center;
}
.new-listings {
  height: max-content;
  padding: 7px 14px;
  /* The system's own shape for a bordered box: 4px, a 2px rule, paper
     behind it. It sits over the rows, and the separation comes from the
     border and the solid background, never a shadow (see DESIGN.md). */
  border: var(--rule) solid var(--green-text);
  border-radius: 4px;
  background: var(--white);
  color: var(--green-text);
  font: inherit;
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
}
.new-listings[hidden] { display: none; }
.new-listings:hover { background: var(--green-text); color: var(--white); }
.new-listings:focus-visible { outline: 2px solid var(--green); outline-offset: 2px; }

/* #board's content padding leaves a gutter-wide gap between the table's
   own right edge and the divider. Widen by --gutter and pull back with a
   matching negative margin to bleed the table out to the true box edge;
   the last column gets that width back as padding to keep its own inset. */
table.jobs {
  width: 100%;
  border-collapse: collapse;
  font-size: 13px;
  /* Fixed, with the star and Age columns given widths below. Left to
     auto layout, the browser sized columns from their content, and the
     Best matches rows (whose skill lines ask for more room) squeezed the
     star column, so the logo, title, salary and Age all sat about 15px
     further left than on Show all. Reported live. Fixed layout takes the
     widths from the header, so no row's content can move a column. */
  table-layout: fixed;
}
/* The rule under the header, on the row rather than on each cell. The
   header is a grid of two headings over four columns, so a border per
   cell left a 14px break in the line where the gap between them falls. */
table.jobs thead tr { border-bottom: var(--rule) solid var(--black); }
table.jobs thead th {
  text-align: left;
  font-size: 12px;
  color: var(--grey);
  padding: 0 10px 10px;
  user-select: none;
  white-space: nowrap;
}
/* Only sortable columns get the pointer cursor. */
table.jobs thead th[data-sort] { cursor: pointer; }
table.jobs thead th[data-sort]:hover { color: var(--black); }
table.jobs thead th.active { color: var(--black); }
table.jobs thead th.active::after { content: attr(data-dir); margin-left: 4px; color: var(--green-text); }

/* --grey-line, not --black -- DESIGN.md's own color spec names "table
   row separators" as this token's job ("the quiet dividers"). The
   --black version read as a bold, high-contrast white line in dark
   mode against the near-black background, much louder than a 1px rule
   should be. */
table.jobs tbody tr { border-bottom: 1px solid var(--grey-line); }
table.jobs tbody tr:hover,
table.jobs tbody tr:focus-within { background: var(--row-hover); }
table.jobs tbody tr.selected { background: var(--row-open); }
table.jobs tbody td { padding: 14px 10px; vertical-align: top; }

/* Card layout: logo, title/meta/actions, salary, skills -- four real
   grid columns, not flex items. Flexbox was tried through several
   rounds here (a fixed flex-basis, a centered auto-margin, a shared
   wrapping box) and kept drifting: flexbox is fundamentally a
   content-driven, one-axis model, so a flex item's own position still
   depends on its siblings' content length even with a fixed basis on
   itself. Grid's column tracks are genuinely independent of content --
   this is what actually guarantees the same x-position on every row
   regardless of title/company/location length or how many skills a
   given job has. 40px matches the logo; 1fr lets the card body absorb
   all the variable slack; salary and skills get fixed percentages of
   the cell's own width (15%/30%) rather than pixel widths, so they
   still scale sanely across viewport widths. Auto-placement fills the
   columns in DOM order (logo, body, salary, skills) below -- no
   explicit grid-column needed as long as that order holds.
   min-width:0 on the body is load-bearing (DESIGN.md's own rule):
   without it a long unbroken domain/title stops the grid item from
   ever shrinking below its content width. */
/* Third column (was 15%/30% split for salary+skills) is just salary now
   -- skills pulled from the UI per request, kept in the codebase but
   unused (see app.js). 20% gives it a bit more room now it's not
   sharing the row with a second column.
   First column is 70px, wider than the 40px logo itself (see
   .company-logo.listing's justify-self:center below) -- reported live,
   a column sized exactly to the logo left it sitting flush against the
   star with all the breathing room dumped on the far side, in front of
   the title text instead. */
/* height:100% -- reported live, the logo's align-self:center (see
   .company-logo.listing below) wasn't reliably centering against the
   row's actual rendered height. A <td> with display:grid is unusual:
   it still participates in normal table row-height layout (sized to
   match its siblings/row), but that outer box and the grid's own
   content-driven height are two different things unless the grid is
   told explicitly to fill the box it's given -- without this, align-
   self only had the grid's auto height to center within, which isn't
   necessarily the same as the row's full height. */
table.jobs .title-cell {
  display: grid;
  grid-template-columns: 74px 1fr 20%;
  column-gap: 12px;
  align-items: start;
  height: 100%;
}
.job-card-body { min-width: 0; }

/* overflow-wrap:anywhere. A company domain has no spaces to break at,
   and some job titles arrive as one long hyphenated/slashed word too. */
/* The row title, at the one step between body text and the detail
   panel's own title. It used to inherit the table's 13px body size, so
   the thing a reader is actually looking for was set at the same size as
   the department and the location under it. Documented in DESIGN.md as
   Row Title rather than left as another undeclared literal. */
.job-card-title { font-size: 15px; line-height: 1.35; }

/* :not(.apply-link): this out-ranked .job-links a.apply-link, so every
   row's Apply button had ink text on a green fill. */
table.jobs .title-cell a:not(.apply-link) {
  font-weight: 600;
  text-decoration: none;
  color: var(--black);
  overflow-wrap: anywhere;
}
table.jobs .title-cell a:not(.apply-link):hover { color: var(--green-text); }
/* Sentence case, not the system's usual uppercase Label treatment --
   deliberately closer to a normal reading line (company/location names
   read oddly shouted in all-caps) than to a table-header-style tag. */
.job-meta { color: var(--grey); font-size: 13px; margin-top: 3px; overflow-wrap: anywhere; }
/* The employer, at full ink while the rest of the line stays grey. Same
   size and weight as its neighbours, so this is contrast doing the work
   rather than scale: the row already has one thing set larger and bolder
   than everything else, and a second would just compete with it. */
.job-company { color: var(--black); }

/* Salary (real disclosed or an estimate, see app.js's jobSalaryHtml)
   plus skill chips -- confirmed live: plain text, no border/background
   at all, same weight as .job-meta, not a boxed/badge treatment. Two
   genuinely separate grid columns now (see .title-cell above), each
   with its own top padding so both start level with .job-meta,
   regardless of how many lines a wide skills column ends up wrapping to
   -- a row with more skills is legitimately taller than one without any,
   same as a row with a longer title already was; that's content-driven
   height, not a bug. */
.job-salary-col,
.job-skills-col {
  padding-top: 24px; /* clears .job-card-title's line so this starts level with .job-meta */
  font-size: 12px;
}
.job-skills-col {
  display: flex;
  flex-wrap: wrap;
  align-content: flex-start;
  gap: 4px 10px;
}
.job-salary { color: var(--black); font-weight: 700; }
/* Filled green box per a live reference (a rounded pill on another
   board) -- reusing .chip's solid-green/paper-text treatment. Still
   visually distinct from a real disclosed figure (plain bold black, no
   box) -- deliberately bold/eye-catching regardless, so the rows that DO
   have a number are easy to spot scanning down a column mostly full of
   "Undisclosed." */
.job-salary.estimate {
  display: inline-block;
  background: var(--green-text);
  color: var(--white);
  border-radius: 4px;
  font-weight: 700;
  padding: 2px 8px;
}
/* The learned estimate (salary_model.py) gets the outline, not the fill.
   Not a downgrade: it has more evidence behind it than the table does,
   since every band is the measured spread of real disclosed listings.
   It is a question of how many rows carry it. The filled chip works on
   the Israeli table's ~1% of the board; the learned model answers about
   two thirds of it, and two thirds of a column painted Signal Green
   breaks the One Voice Rule outright (see DESIGN.md: green marks one
   thing at a time, and if everything is green nothing is). Same green,
   same shape, one step quieter. */
.job-salary.estimate.estimated {
  background: none;
  color: var(--green-text);
  border: 1px solid var(--green-text);
  padding: 1px 7px; /* 1px off the filled chip's padding, so the border doesn't grow the box */
}
.job-salary.undisclosed { color: var(--grey); font-weight: 400; }
.skill-chip {
  background: none;
  border: none;
  padding: 0;
  color: var(--grey);
  font-family: var(--font-ui);
  font-size: 12px;
  cursor: pointer;
}
.skill-chip:hover { color: var(--green-text); text-decoration: underline; }
/* The two-line matched/missing markup this styled is gone: Best matches
   is one line now (see jobMatchLine in app.js), and .job-match-summary
   beside the chip rules above is what carries it. */

/* All listings / Best matches / Saved: the Explore page's segmented
   control, a touch taller to sit with the filter row above it. */
.view-switch { margin-bottom: 12px; }
/* Separate pills rather than a joined segmented control. Four views are
   four choices, not four halves of one switch, and the phone already
   drew them this way: one shape for both. */
.board-bar .view-switch {
  display: flex;
  gap: 8px;
  border: 0;
  border-radius: 0;
  overflow: visible;
}
.board-bar .view-switch .seg-btn {
  height: 32px;
  padding: 0 14px;
  border: 1px solid var(--grey-line);
  border-radius: 999px;
  background: var(--white);
  white-space: nowrap;
}
.board-bar .view-switch .seg-btn:hover { background: var(--hover-bg); }
.board-bar .view-switch .seg-btn.active {
  border-color: var(--black);
  background: var(--black);
  color: var(--white);
}

/* What Best matches is ordered by, each skill removable. The skills fold
   down under "My skills"; grid rows animate to the content's own
   height, which a max-height guess cannot. */
.match-panel {
  margin-bottom: 12px;
  font-size: 13px;
}
.match-panel[hidden] { display: none; }
.match-panel-head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px 12px;
}
.match-panel-toggle {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-family: var(--font-ui);
  font-size: 13px;
  font-weight: 600;
  color: var(--black);
  background: none;
  border: 1px solid var(--grey-line);
  border-radius: 4px;
  height: 26px;
  padding: 0 8px;
  cursor: pointer;
}
.match-panel-toggle:hover { background: var(--hover-bg); }
.match-panel-toggle:focus-visible { outline: 2px solid var(--green); outline-offset: 2px; }
.match-panel-count { color: var(--grey); font-weight: 400; font-variant-numeric: tabular-nums; }
.match-panel-toggle svg { transition: transform 0.25s ease; }
.match-panel-toggle[aria-expanded="true"] svg { transform: rotate(180deg); }
.match-skills-wrap {
  display: grid;
  grid-template-rows: 0fr;
  transition: grid-template-rows 0.25s ease;
}
.match-skills-wrap.open { grid-template-rows: 1fr; }
.match-skills {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px 8px;
  min-height: 0;
  overflow: hidden;
  visibility: hidden;
  transition: visibility 0.25s;
}
.match-skills-wrap.open .match-skills { visibility: visible; padding-top: 8px; }
.match-panel-label { color: var(--grey); }
.match-skill {
  font-family: var(--font-ui);
  font-size: 12px;
  color: var(--green-text);
  background: none;
  border: 1px solid var(--green-text);
  border-radius: 4px;
  padding: 2px 6px;
  /* Fixed, because "normal" is 22px in the fallback face and 24px once
     Source Sans 3 arrives, and the rows under the open panel moved by
     the difference on every load. */
  line-height: 18px;
  cursor: pointer;
}
.match-skill:hover { background: var(--green-text); color: var(--white); }
.match-skill:focus-visible { outline: 2px solid var(--green); outline-offset: 2px; }
.match-panel-edit { font-size: 12px; }

/* The row's own explanation: how many of your skills, which, and what
   the listing asks for that the CV does not have. */
.job-match { align-items: center; }
.job-match-count { font-size: 12px; font-weight: 700; color: var(--black); margin-right: 2px; }
.job-match-asks { display: flex; align-items: center; gap: 4px; margin-top: 4px; font-size: 12px; color: var(--grey); opacity: 0.7; }
.job-match-asks-label { flex: none; }
/* Plain text, faded back behind the matched chips on purpose. */
.job-match-asks [data-fit] { flex: none; }
.job-match-asks [data-fit] + [data-fit]::before { content: ", "; margin-left: -4px; }
.job-match-count, .match-chip { flex: none; }

.skill-bracket { color: var(--grey); }
.skill-bracket:first-child { margin-right: -6px; }
.skill-bracket:last-child { margin-left: -6px; }

/* Google's favicon endpoint (see app.js's companyLogoUrl) -- no border
   radius, matching DESIGN.md's rounded:none everywhere else in this
   Swiss-grid layout, not a soft app-icon treatment. object-fit:contain
   keeps a non-square favicon from stretching. */
.company-logo {
  width: 16px;
  height: 16px;
  object-fit: contain;
  vertical-align: -3px;
  margin-right: 6px;
  flex-shrink: 0;
}
/* The row's own logo is a flex sibling of .job-card-body (gap above
   handles spacing), not inline text -- sized up to actually read as a
   company mark, the way it does on LinkedIn's job cards. */
/* align-self overrides .title-cell's own align-items:start (grid,
   see above) just for the logo -- everything else in the row still
   top-aligns, but the logo centers against the row's full height,
   which varies with how much the card body wraps to. */
/* Asymmetric on purpose: confirmed via DevTools' own box-model panel
   that 15px/15px (the geometric center of the 70px column) was
   landing correctly, but the star-side path has an extra ~8px the
   text-side doesn't (the star cell's own padding stacks with the
   title cell's), so equal margins here still read visually off-center
   against the row as a whole. 11px/19px compensates for that -- both
   real gaps (star-to-logo, logo-to-text) come out to ~31px. */
/* 52px, up from 40. A job board row is scanned by logo first and title
   second, and at 40 the mark was smaller than the company name beside
   it, which inverts that order. LinkedIn runs 48 for the same job;
   this sits just above it because our rows carry more text per line. */
.company-logo.listing { width: 52px; height: 52px; margin: 0 14px 0 8px; align-self: center; }
.job-detail-company .company-logo.detail { width: 32px; height: 32px; vertical-align: -8px; margin-right: 8px; }
.bar-row .company-logo { width: 14px; height: 14px; vertical-align: -2px; margin-right: 5px; }

.job-links {
  display: flex;
  align-items: center;
  flex-wrap: wrap; /* individual links can drop to a new row if truly out of width; their own text never should (see white-space: nowrap below) */
  gap: 12px;
  margin-top: 8px;
}

/* The one action this row exists for, green per rule 2 above. Filled,
   not just colored text, so it reads as the row's one button, not a
   same-weight sibling to Save link. Hover inverts to ink, like .chip/.btn. */
.job-links a.apply-link {
  font-family: var(--font-ui);
  display: inline-flex;
  align-items: center;
  font-size: 13px;
  font-weight: 700;
  color: var(--white);
  background: var(--green-text);
  border: var(--rule) solid var(--green-text);
  border-radius: 4px;
  padding: 4px 10px;
  text-decoration: none;
  white-space: nowrap;
}
.job-links a.apply-link:hover { background: var(--black); border-color: var(--black); }

.copy-link-btn {
  font-size: 13px;
  font-weight: 400;
  color: var(--grey);
  text-decoration: none;
  border: none;
  border-bottom: 1px solid var(--grey-line);
  background: none;
  padding: 0;
  cursor: pointer;
  white-space: nowrap;
}
.copy-link-btn:hover { color: var(--black); border-color: var(--black); }
.copy-link-btn.copied { color: var(--green-text); font-weight: 700; border-color: var(--green-text); }

.badge {
  font-family: var(--font-ui);
  display: inline-block;
  font-size: 12px;
  border: 1px solid var(--black);
  border-radius: 4px;
  padding: 2px 6px;
  font-weight: 700;
}
.badge.best-effort { border-color: var(--grey); color: var(--grey); }
/* A listing that has closed. Only ever seen in the Saved view, since the
   board hides closed rows by default, and that is exactly where it
   matters: the point of saving something is to come back to it, and
   coming back to find it gone without being told is worse than not
   having saved it. Red rather than grey, because this is the one badge
   that changes what a reader should do next. */
.badge.closed { border-color: var(--aws-outage); color: var(--aws-outage); }
.title-cell .badge { margin-left: 6px; vertical-align: middle; }

/* Reported live: left-aligned (the table default), a short "Age"
   header sitting above widely variable-width durations ("41M" vs "3H
   58M") reads as off-center even though the text edges genuinely do
   line up -- the column's visual weight drifts right of the header as
   values get longer. Centering both keeps the header sitting where the
   eye actually expects the numbers to cluster. Reset back to left
   inside the mobile stacked-card breakpoint below, where every other
   field (location, salary, ...) stays left-aligned next to its own
   label -- centering just this one there would look inconsistent, not
   fixed. */
table.jobs thead th[data-sort="age"] { text-align: center; }
.age-cell { text-align: center; font-variant-numeric: tabular-nums; white-space: nowrap; }
.age-cell.fresh { color: var(--green-text); font-weight: 700; }
/* The age again, at the end of the details line, for phone and tablet
   widths only (see the 960px block). Hidden here, where the Age column
   does the job. */
.meta-age { display: none; }

.star-btn {
  border: none;
  background: none;
  cursor: pointer;
  font-size: 15px;
  color: var(--black);
  padding: 0;
  line-height: 1;
}
.star-btn.on { color: var(--green-text); }
.star-btn:hover { color: var(--green-text); }

.pagination {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap; /* page buttons + result-count text won't both fit one row on a phone */
  gap: 10px;
  /* No rule of its own. The last row already ends in the same hairline,
     so this drew a second line 20px under it, and a shorter one, because
     the table bleeds into the gutter and this does not. Reported live. */
  margin-top: 20px;
  font-size: 12px;
}
.pagination .pages { display: flex; gap: 6px; }
.pagination .pages button {
  border: 1px solid var(--black);
  border-radius: 4px;
  background: var(--white);
  width: 32px;
  height: 32px;
  cursor: pointer;
}
.pagination .pages button.active { background: var(--black); color: var(--white); }
.pagination .pages button:disabled { opacity: 0.3; cursor: default; }

.empty-state, .error-state, .loading-state {
  border: var(--rule) dashed var(--grey-line);
  border-radius: 4px;
  padding: 40px;
  text-align: center;
  color: var(--grey);
  font-size: 13px;
}
.empty-state strong { display: block; color: var(--black); font-weight: 700; }
.empty-state span { display: block; margin-top: 8px; text-transform: none; letter-spacing: 0; font-size: 12.5px; }
.error-state { border-color: var(--red); color: var(--red); }
/* The job sheet's description placeholder turns the Updating ring beside
   its text, so a slow description reads as on its way, not stuck. Keeps
   turning with reduced motion, like the status ring, since a stopped
   spinner reads as a hung one. */
.loading-with-spinner {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
}
.loading-spinner {
  width: 18px;
  height: 18px;
  flex: none;
  color: var(--grey);
  animation: status-spin 0.9s linear infinite;
}

/* Run query, while it runs. The ring rides in the button rather than
   beside it, so the button keeps its width and nothing on the row shifts:
   the label fades out under it. Driven by body.explore-busy, which run()
   sets on entry and clears in its finally, so a timeout or a failure stops
   the ring as surely as an answer does. Same turning ring as the board's
   status glyph, same keyframe. */
#explore-run {
  position: relative;
}
.btn-spinner {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 19px;
  height: 19px;
  margin: -9.5px 0 0 -9.5px;
  opacity: 0;
  color: currentColor;
  pointer-events: none;
}
.explore-busy #explore-run .btn-label { opacity: 0; }
.explore-busy #explore-run .btn-spinner {
  opacity: 1;
  animation: status-spin 0.9s linear infinite;
}
/* A button mid-query should not invite a second click. */
.explore-busy #explore-run { cursor: progress; }

/* Footer */

footer {
  padding: 40px 0 60px;
}
/* min-width:0. A curl command or long param row is an unbreakable-width
   child that would otherwise force the whole footer wider than the
   viewport instead of scrolling locally. */
.api-endpoint {
  min-width: 0;
  padding: 16px 0;
  border-top: 1px solid var(--grey-line);
}
.api-endpoint:first-of-type { border-top: none; padding-top: 0; }
.api-endpoint-head {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 8px;
}
.api-method {
  flex: 0 0 auto;
  background: var(--black);
  color: var(--white);
  font-size: 10px;
  font-weight: bold;
  letter-spacing: 0.06em;
  padding: 2px 6px;
}
.api-path {
  font-family: "Courier New", monospace;
  font-size: 13px;
  font-weight: bold;
}
.api-endpoint > p {
  font-size: 12px;
  color: var(--grey);
  margin: 0 0 10px;
  max-width: 720px;
}
.api-params {
  width: 100%;
  border-collapse: collapse;
  margin-bottom: 10px;
  font-size: 12px;
}
.api-params td {
  padding: 4px 12px 4px 0;
  vertical-align: top;
  color: var(--grey);
}
.api-params td:first-child { white-space: nowrap; width: 1%; }
.api-params .param {
  font-family: "Courier New", monospace;
  font-size: 11.5px;
  font-weight: bold;
  color: var(--black);
}
/* API reference page (api/help_page.py, served at /api/help by the
   Lambda itself -- moved off this footer 2026-09-08, see index.html's
   own footer-meta link) reuses .api-endpoint/.api-params/.api-method
   above plus these three, its only page-specific rules. */
/* Section headings on the API reference. The page divides into what
   the API is, how to call it, and the routes themselves, so each one
   has to read as a break. They were 12px uppercase labels, and at that
   size "Quick start" and "Authentication" vanished into the run of
   paragraphs and code blocks. Now the display face at the size the
   Statistics column's title uses (24px, sentence case), under the 34px
   page title and clearly above the 12px endpoint rows. */
.api-help h3 {
  font-family: var(--font-display);
  font-size: 24px;
  font-weight: 400;
  line-height: 1.1;
  letter-spacing: -0.01em;
  margin: 44px 0 14px;
  padding-bottom: 10px;
  border-bottom: 1px solid var(--grey-line);
}
.api-help h3:first-of-type { margin-top: 32px; }
/* The heading already drew a rule, and .api-endpoint draws its own on
   top. Without this the first endpoint under every heading sits under
   two lines 8px apart. */
.api-help h3 + .api-endpoint { border-top: none; padding-top: 0; }
.api-help-intro { font-size: 12.5px; color: var(--grey); max-width: 720px; margin: 0 0 28px; }
/* Deliberately fixed, not var(--black)/var(--white). A terminal/code
   block stays dark regardless of the page's own light/dark state. */
.api-help code {
  display: block;
  background: #0a0a0a;
  color: #2fae60;
  text-shadow: 0 0 1px var(--white);
  padding: 10px 12px;
  font-size: 11.5px;
  margin-bottom: 8px;
  overflow-x: auto;
  white-space: pre;
}
.api-help p { color: var(--grey); margin: 0 0 8px; }
.footer-meta {
  display: flex;
  flex-wrap: wrap;
  gap: 16px;
  align-items: center;
  margin-top: 32px;
  padding-top: 16px;
  border-top: 1px solid var(--grey-line);
  color: var(--grey);
  font-size: 12.5px;
}

/* Buy Me a Coffee's own row, under the footer links.
   The widget's script writes its own button and owns every pixel of it:
   their yellow, their Poppins, their radius. Nothing here tries to pull
   it onto the palette or the type ramp, because a half-matched
   third-party button reads worse than an obviously foreign one. All this
   does is give it room and stop it sitting on the links. */
.footer-support {
  margin-top: 14px;
  /* Half of the widget's own 60px, because the scale below is a paint
     transform and leaves the original box behind in layout. Without
     this the footer keeps a button-sized gap under a half-sized
     button. */
  height: 30px;
}
/* Their script writes the markup, so there is no class of ours to hang
   this on and no width to set. transform is the one lever that works on
   an element you do not control, and it scales the whole thing (image,
   text, radius) evenly rather than squashing one part of it. */
.footer-support > * {
  transform: scale(0.5);
  transform-origin: left top;
}
.footer-support img {
  vertical-align: middle;
}

/* privacy.html only -- a plain reading column, not the board/stats
   two-up layout .workspace otherwise implies. */
.legal .container { max-width: 640px; padding-top: 32px; }
.legal h3 { font-size: 15px; margin: 28px 0 8px; }
.legal p { margin: 0 0 12px; }
.legal-updated { color: var(--grey); font-size: 12.5px; margin-top: -4px; }
.legal-back { margin-top: 32px; }
/* A link on its own line is a target, not prose. The text is 17px tall;
   the padding takes the tappable box to 33px without moving anything. */
.legal-back .link { display: inline-block; padding: 8px 0; }

/* 404.html. The privacy page's reading column, with the three things a
   reader needs to get back to the board: search, the listings, and the
   page they came from. */
.not-found .container { max-width: 640px; padding-top: 48px; padding-bottom: 64px; }
/* The numeral is the page's one display moment, in the grotesque face.
   96px at phone width up to 160px, so it stays at least 2.8 times the
   34px "Page not found" beneath it at every width. It was a 12px label,
   asked to be made much bigger. */
.not-found-code {
  font-family: var(--font-display);
  font-size: clamp(96px, 22vw, 160px);
  font-weight: 400;
  line-height: 0.85;
  letter-spacing: -0.02em;
  color: var(--black);
  margin: 0 0 20px;
}
.job-gone-label {
  font-size: 12px;
  font-weight: 700;
  color: var(--grey);
  margin: 0 0 8px;
}
.not-found-lede { margin: 0 0 24px; }
.not-found-search { display: flex; gap: 8px; margin-bottom: 16px; }
.not-found-search input {
  flex: 1 1 auto;
  min-width: 0;
  height: 38px;
  padding: 0 12px;
  border: 1.5px solid var(--black);
  border-radius: 4px;
  background: var(--white);
  color: var(--black);
  font: inherit;
}
.not-found-search input:focus { outline: 2px solid var(--green); outline-offset: -2px; }
.not-found-actions { display: flex; flex-wrap: wrap; gap: 8px; }
/* Links styled as buttons. .btn is written for <button>, which centres its
   own text; an <a> needs it said, and loses the link underline. */
.not-found-actions a.btn { display: inline-flex; align-items: center; text-decoration: none; }
.not-found-actions [hidden] { display: none; }

/* The sheet's version, for a ?job= link to a listing that has gone. */
.job-gone { padding: 24px 0; }
.job-gone p { margin: 0 0 16px; }

/* Responsive */

@media (max-width: 960px) {
  .workspace { grid-template-columns: 1fr; }
  .workspace > #board { border-right: none; padding-right: 0; }
  .stats-board {
    position: static;
    max-height: none;
    padding: 24px 0;
    border-top: var(--rule) solid var(--black);
    overflow-y: visible;
  }

  /* Above this width the panel is a right-hand sheet (see
     .job-detail). At phone width a 380px sheet beside a
     ~360px viewport is the whole screen anyway, so it goes full-screen
     and slides up from the bottom instead of in from the side, which is
     what a phone reader expects and what the swipe-to-close gesture in
     app.js is written against. The scrim stays off here: the sheet
     covers the viewport, so there is nothing behind it left to dim. */
  .job-detail {
    inset: 0;
    /* Full screen here, so it meets the left edge as well and clears a
       cutout on that side too (the desktop rule above covers the other
       three). */
    padding-left: max(20px, env(safe-area-inset-left));
    width: 100vw;
    height: 100vh;
    /* dvh: the visible viewport. 100vh on iPhone includes the space under
       Safari's toolbar, which put the bottom of the sheet behind it. */
    height: 100dvh;
    border-width: var(--rule) 0 0 0;
    transform: translateY(100%);
  }
  #job-scrim { display: none; }
}

@media (max-width: 640px) {
  /* The utility row on a phone. It was 10.5px text in controls 17 to 19px
     tall, bunched into the left 160px of a 390px screen, right under the
     browser's address bar: reported live as too tight. The wordmark hides
     at this width and the search box drops into the filter bar, so the
     row has the whole width to itself. The links go back to the 13px UI size and
     every control is 44px tall to tap, which is the height Apple's
     guidance asks for and what makes the bar taller. They stay grouped on
     the left: spread across the width, the gaps read as too much. Each
     control's own 6px side padding widens its tap area, so the visible
     space between words is the 10px gap plus 12px. */
  .topbar .container { min-height: 64px; padding-top: 6px; padding-bottom: 6px; }
  .topnav { gap: 10px; font-size: 13px; }
  .topnav > *, .topnav .auth-area > button, .topnav .auth-area > a { min-height: 44px; }
  .topnav > a, .topnav > button, .topnav .auth-area > button, .topnav .auth-area > a {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding-left: 6px;
    padding-right: 6px;
  }
  /* a.link draws its underline as a bottom border, which on a 44px tap
     box lands at the bottom of the bar, a long way under "API". An
     underline sits with the word instead. */
  .topnav > a.link {
    border-bottom: 0;
    text-decoration: underline;
    text-decoration-thickness: 1px;
    text-underline-offset: 4px;
  }
  .topbar .container { flex-wrap: nowrap; }

  /* Reported live: cut off. .auth-panel is `right: 0` relative to its
     trigger button, at a fixed 260/320px width -- fine when the trigger
     sits near the topbar's own right edge on desktop, but .topnav wraps
     at this width (see .topbar .container above), so the trigger can
     land anywhere, and a 320px-wide panel anchored to it routinely needs
     to extend further left than the 360-390px viewport has room for.
     Pinned to the viewport as a bottom sheet instead, same fixed-sheet
     language the job-detail drawer already uses below 960px -- width is
     never in question when it's the viewport's own width. */
  .auth-panel, .auth-panel.alerts-panel {
    position: fixed;
    top: auto;
    bottom: 0;
    left: 0;
    right: 0;
    width: auto;
    max-height: 75vh;
    max-height: 75dvh;
    border-radius: 4px 4px 0 0;
    border-width: var(--rule) var(--rule) 0 var(--rule);
  }
}

/* Loading states */

/* Skeleton bones. --grey-line is already this system's "quiet divider"
   weight (see DESIGN.md's color spec), which is exactly what a
   placeholder should be: present, structural, not competing with real
   content. No new gray token, and no #2A2A2A-style neutral, which would
   read as a foreign object against the paper.
   Sharp corners, not 4px: these live inside the table and the stat grid,
   both of which DESIGN.md keeps at radius 0. */
.skeleton {
  display: block;
  background-color: var(--grey-line);
  background-image: linear-gradient(90deg, transparent 0%, var(--hover-bg) 50%, transparent 100%);
  background-size: 55% 100%;
  background-repeat: no-repeat;
  /* steps(), not a smooth glide. The status dot established this
     system's motion voice as mechanical and quantized, deliberately
     "closer to a Minecraft torch than a smooth pulse" (DESIGN.md), and
     a silky gradient sweep here would be the only smoothly-interpolated
     motion on the page. Reads as a terminal repainting, which is the
     right register for a market-terminal board. */
  animation: skeleton-sweep 1.15s steps(11, end) infinite;
}
@keyframes skeleton-sweep {
  from { background-position: -60% 0; }
  to   { background-position: 160% 0; }
}

/* The skeleton row is the real row's markup (see board.html's
   #skeleton-row template) with bones inside the title, meta and links
   wrappers, so every line box is the real line box and the row is the
   real row's height at every width. Measured live before this: 79px of
   bone against 114px of row on desktop, 157 against 177 on a phone,
   fifty rows at a time. Only the links line needs a stated height: its
   real height comes from the Apply button's padding, not from text. */
.sk-star { width: 14px; height: 14px; }
.sk-logo { display: block; }
.sk-line { height: 11px; }
.sk-title { width: 62%; height: 14px; }
.sk-meta  { width: 84%; }
.sk-meta2 { width: 46%; display: none; } /* the phone's second meta line, see the phone block */
.sk-links { width: 38%; }
.sk-salary { width: 72px; }
.skeleton-row .job-links { min-height: 32px; }
.sk-age   { width: 44px; height: 11px; }
/* A bone inside a text wrapper sits in the wrapper's line box, so the
   wrapper is one line of its own font tall, exactly as it is with text
   in it. As a block the wrapper collapsed to the bone's 11px, and the
   skeleton was one line short per line. */
.skeleton-row .sk-line,
.metric-card.pending .skeleton,
.sk-panel .skeleton { display: inline-block; vertical-align: middle; }
/* Uneven widths so a column of rows reads as text, not a barcode. */
.skeleton-row:nth-child(3n+2) .sk-title { width: 47%; }
.skeleton-row:nth-child(3n+2) .sk-meta  { width: 71%; }
.skeleton-row:nth-child(3n)   .sk-title { width: 73%; }
.skeleton-row:nth-child(3n)   .sk-meta  { width: 58%; }

/* Stat tiles and panels are bones inside the real tile and panel
   markup (.metric-card.pending, .panel with .bar-row children), for the
   same reason as the rows above: the tile's own line boxes set the
   height, so the column does not grow when the numbers land. The value
   bone is 26px inside the 30px value line; the sub bone one line of
   the sub's own size. */
.metric-card.pending .sub .sk-sub { width: 62%; height: 9px; }
.sk-panel .sk-label { width: 44%; height: 9px; }
.sk-panel .sk-name { width: 70%; height: 10px; }
.sk-panel .sk-n { width: 60%; height: 10px; }
.sk-panel .bar-row:nth-child(3n) .sk-name { width: 52%; }
.sk-panel .bar-row:nth-child(3n+1) .sk-name { width: 84%; }
.sk-panel .sk-more .sk-name { width: 64px; }

/* Inline button busy state. color:transparent keeps the label in flow,
   so the button never changes size mid-click. */
.btn-busy {
  color: transparent;
  pointer-events: none;
  position: relative;
}
.btn-busy::after {
  content: "";
  position: absolute;
  top: 50%;
  left: 50%;
  width: 13px;
  height: 13px;
  margin: -7px 0 0 -7px;
  border: 2px solid var(--grey-line);
  border-top-color: var(--white);
  border-radius: 50%;
  animation: btn-spin 0.72s steps(8, end) infinite;
}
@keyframes btn-spin {
  to { transform: rotate(360deg); }
}

/* Everything above still communicates with the animation off: the bones
   remain, the spinner holds one frame, the sheet arrives already open.
   Nothing depends on movement to be legible. */
@media (prefers-reduced-motion: reduce) {
  .skeleton { animation: none; background-image: none; }
  .btn-busy::after { animation: none; }
  /* The sheet still opens and closes, it just arrives already there. */
  .job-detail, #job-scrim { transition: none; }
  /* Still turning, at under half the speed. A stopped spinner reads as a
     hung one, which is the exact worry this state exists to remove. */
  .status-icon.updating,
  .loading-spinner,
  .btn-spinner { animation-duration: 2.4s; }
}

/* Screen-reader-only announcement for the skeleton states above. The
   bones themselves are aria-hidden decoration, so without this a
   non-visual user gets silence where a sighted one sees "something is
   coming". */
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* Theme crossfade */

/* The whole page, crossfaded once by the compositor. See wireThemeToggle
   in app.js for what this replaced and why: a transition on every
   element, which repainted ~2,430 elements a frame on a full board and,
   measured, never actually started.

   Only the root snapshot is animated, so this is two images fading into
   each other rather than thousands of colour interpolations. 280ms:
   long enough to read as a fade, short enough that the page is not
   frozen while someone is trying to click something. */
::view-transition-old(root),
::view-transition-new(root) {
  animation-duration: 280ms;
  animation-timing-function: ease;
}

@media (prefers-reduced-motion: reduce) {
  ::view-transition-old(root),
  ::view-transition-new(root) {
    animation: none;
  }
}

/* Explore (stats.html). Questions over every listing, answered in the
   browser against a database file on the CDN.

   Laid out as a workbench: a sidebar of schema and starter questions,
   and a workspace of two panels, the question above the answer. Every
   panel is the same object, a 2px rule with a hairline-ruled header,
   so the eye reads controls, results, and reference as one instrument
   rather than a page of sections. Dense on purpose: 32px controls,
   10.5px micro-labels, 12px body, and nothing floats in dead space.

   No native-looking control anywhere. The builder is the board's own
   filter-bar vocabulary (ink borders, the hand-drawn chevron, the
   hand-built multi-select) at the workbench's smaller scale. Mode and
   shape switches are a segmented pair of the system's buttons; the
   schema tree is a hand-built disclosure, not <details>.

   Monospace is the second scoped use of that voice, after the API
   reference's parameter chips: the SQL editor, column names, and the
   axis numbers a reader lines up by eye. Same family, one exception. */

.explore-page { display: block; padding-bottom: 40px; }
.explore-page .section { padding-top: 28px; }
.explore-intro { color: var(--grey); margin: 0 0 16px; max-width: 72ch; line-height: 1.5; font-size: 12.5px; }

.explore-layout {
  display: grid;
  grid-template-columns: 300px minmax(0, 1fr);
  gap: 14px;
  align-items: start;
}
.explore-side { display: flex; flex-direction: column; gap: 14px; }
/* Same fix as .job-detail[hidden]: the display a class rule sets beats
   the UA stylesheet's [hidden], so mode panels and the Reset button
   need it said again. */
.explore-page [hidden] { display: none !important; }
.explore-main { display: flex; flex-direction: column; gap: 14px; min-width: 0; }

/* Panel: the one container on this page. */
.panel { border: var(--rule) solid var(--grey-line); border-radius: 4px; background: var(--white); min-width: 0; }
.panel-head {
  display: flex;
  align-items: center;
  gap: 10px;
  min-height: 40px;
  padding: 4px 10px;
  border-bottom: 1px solid var(--grey-line);
}
.panel-title {
  font-size: 12px;
  color: var(--grey);
  font-weight: 700;
}
.panel-actions { margin-left: auto; display: flex; gap: 6px; }
.panel-body { padding: 10px; }
.panel-body-flush { padding: 0; }
.panel-foot {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px;
  padding: 8px 10px;
  border-top: 1px solid var(--grey-line);
}

/* Schema explorer. */
.schema-search { border: 0; border-bottom: 1px solid var(--grey-line); border-radius: 0; height: 32px; }
.schema-search:focus { outline: 2px solid var(--green); outline-offset: -2px; }
.sx { max-height: 46vh; overflow-y: auto; }
.sx-table { border-bottom: 1px solid var(--grey-line); }
.sx-table:last-child { border-bottom: 0; }
.sx-toggle {
  width: 100%;
  font: inherit;
  font-size: 12px;
  text-align: left;
  background: none;
  border: 0;
  padding: 6px 10px 6px 24px;
  color: var(--black);
  cursor: pointer;
  display: flex;
  gap: 8px;
  align-items: baseline;
  position: relative;
  min-width: 0;
}
.sx-toggle:hover { background: var(--hover-bg); }
.sx-toggle::before { content: "+"; color: var(--grey); position: absolute; left: 10px; top: 6px; width: 10px; }
.sx-table.open .sx-toggle::before { content: "\2013"; }
.sx-toggle code, .sx li code { font-family: "Courier New", monospace; font-size: 11.5px; font-weight: 700; }
.sx li code { font-weight: 400; }
.sx-count { color: var(--grey); font-size: 10.5px; font-variant-numeric: tabular-nums; }
.sx-note { color: var(--grey); font-size: 12px; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.sx-toggle .sx-note { flex: 1 1 auto; }
.sx ul { list-style: none; margin: 0; padding: 0 0 4px; }
.sx li {
  font-size: 11.5px;
  padding: 2px 10px 2px 24px;
  display: grid;
  grid-template-columns: minmax(0, auto) 44px minmax(0, 1fr);
  gap: 8px;
  align-items: baseline;
}
.sx-type { color: var(--grey); font-size: 12px; }

/* Starter queries. */
.explore-templates { display: flex; flex-direction: column; padding: 4px 0; }
.explore-template {
  font: inherit;
  font-size: 12px;
  text-align: left;
  background: none;
  border: 0;
  border-left: 2px solid transparent;
  padding: 5px 10px 5px 22px;
  color: var(--black);
  cursor: pointer;
  line-height: 1.3;
}
.explore-template:hover { background: var(--hover-bg); }
/* The mark is the rule, not the text: green on the light ground is
   under 3:1 at this size, and the board's One Voice is one mark anyway. */
.explore-template.active { border-left-color: var(--green); font-weight: 700; }

/* Segmented switch: the system's ghost buttons joined into one control,
   the active half filled ink. Used for Builder/SQL and for Auto/Table/
   Bars/Line. */
.seg { display: inline-flex; border: 1.5px solid var(--black); border-radius: 4px; overflow: hidden; }
.seg-btn {
  font-family: var(--font-ui);
  font-size: 13px;
  font-weight: 700;
  height: 28px;
  padding: 0 10px;
  border: 0;
  border-right: 1.5px solid var(--black);
  background: var(--white);
  color: var(--black);
  cursor: pointer;
}
.seg-btn:last-child { border-right: 0; }
.seg-btn:hover { background: var(--hover-bg); }
.seg-btn.active { background: var(--black); color: var(--white); }
.seg-btn:focus-visible { outline: 2px solid var(--green); outline-offset: -2px; }
.btn-small { height: 30px; padding: 0 12px; font-size: 12px; white-space: nowrap; }

.explore-status { margin-left: auto; color: var(--grey); font-size: 11.5px; font-variant-numeric: tabular-nums; text-align: right; }
.explore-hint { color: var(--grey); font-size: 12px; }
/* The last run's measurement. Green while a query is running: the one
   thing on the page that is happening right now. */
.explore-metric {
  margin-left: auto;
  font-family: "Courier New", monospace;
  font-size: 11.5px;
  color: var(--grey);
  font-variant-numeric: tabular-nums;
  text-align: right;
}
body.explore-busy .explore-metric { color: var(--green-text); }

/* Builder. */
.qb { display: flex; flex-direction: column; gap: 10px; }
.qb-row { display: flex; gap: 8px; align-items: flex-end; flex-wrap: wrap; }
.qb-field { display: flex; flex-direction: column; gap: 4px; flex: 1 1 150px; min-width: 0; }
.qb-field-limit { flex: 0 0 72px; }
.qb-label {
  font-size: 12px;
  color: var(--grey);
  font-weight: 700;
}
.qb-select, .qb-input {
  font-family: var(--font-ui);
  font-size: 12.5px;
  color: var(--black);
  border: 1.5px solid var(--black);
  border-radius: 4px;
  background: var(--white);
  padding: 5px 8px;
  height: 32px;
  width: 100%;
  box-sizing: border-box;
}
.qb-select:focus, .qb-input:focus { outline: 2px solid var(--green); outline-offset: -2px; }
/* Same hand-drawn chevron as .filters select, hardcoded per theme for
   the same reason recorded there: a data URI cannot read a token. */
.qb-select {
  appearance: none;
  -webkit-appearance: none;
  -moz-appearance: none;
  padding-right: 26px;
  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' fill='none' stroke='%2340513b' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 10px center;
  background-size: 10px 6px;
}
:root[data-theme="dark"] .qb-select {
  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' fill='none' stroke='%23ededec' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
}
.qb-select-inline { width: auto; min-width: 150px; }

/* The filters, ruled off from the row above and laid out as label,
   rows, add. The multi-select is the board's control at this scale. */
.qb-where { display: flex; flex-direction: column; gap: 6px; padding-top: 10px; border-top: 1px solid var(--grey-line); }
.qb-filters { display: flex; flex-direction: column; gap: 6px; }
.qb-filter {
  display: grid;
  grid-template-columns: 100px 70px minmax(0, 1fr) 30px;
  gap: 6px;
  align-items: center;
}
.qb-filter-name { font-size: 12px; font-weight: 700; }
.qb-filter-op { font-size: 12px; color: var(--grey); }
.qb-filter .ms { min-width: 0; }
.qb .ms-toggle { height: 32px; padding: 5px 8px; font-size: 12.5px; }
.qb-date { flex: 1 1 130px; }
.qb-filter:has(.qb-date) { grid-template-columns: 100px 70px minmax(0, 1fr) minmax(0, 1fr) 30px; }
.qb-remove {
  height: 30px;
  width: 30px;
  border: 1.5px solid var(--grey-line);
  border-radius: 4px;
  background: var(--white);
  color: var(--grey);
  font-size: 15px;
  line-height: 1;
  cursor: pointer;
}
.qb-remove:hover { border-color: var(--black); color: var(--black); }

/* SQL mode: the editor is the panel body edge to edge. */
.explore-editor {
  display: block;
  width: 100%;
  box-sizing: border-box;
  font-family: "Courier New", monospace;
  font-size: 12.5px;
  line-height: 1.5;
  padding: 10px 12px;
  border: 0;
  border-radius: 0;
  background: var(--white);
  color: var(--black);
  resize: vertical;
  min-height: 150px;
  tab-size: 2;
}
.explore-editor:focus { outline: 2px solid var(--green); outline-offset: -2px; }

/* Results. */
.explore-error {
  margin-bottom: 10px;
  padding: 8px 10px;
  border: 1px solid var(--red);
  border-radius: 4px;
  color: var(--red);
  font-family: "Courier New", monospace;
  font-size: 12px;
  white-space: pre-wrap;
}
.explore-table-wrap { overflow-x: auto; border: 1px solid var(--grey-line); border-radius: 4px; max-height: 70vh; overflow-y: auto; }
.explore-table { border-collapse: collapse; width: 100%; font-size: 12px; font-variant-numeric: tabular-nums; }
.explore-table th, .explore-table td {
  text-align: left;
  padding: 4px 8px;
  border-bottom: 1px solid var(--grey-line);
  white-space: nowrap;
  max-width: 420px;
  overflow: hidden;
  text-overflow: ellipsis;
}
.explore-table th {
  font-size: 12px;
  color: var(--grey);
  font-weight: 700;
  position: sticky;
  top: 0;
  background: var(--white);
  box-shadow: inset 0 -1px 0 var(--grey-line); /* the border-bottom scrolls away under sticky; this does not */
}
.explore-table tbody tr:last-child td { border-bottom: 0; }
.explore-table tbody tr:hover td { background: var(--hover-bg); }
.explore-table td.num { text-align: right; font-family: "Courier New", monospace; }
.explore-table td.null { color: var(--grey); font-style: italic; }

/* Bars. One three-column grid per row, the same columns for the axis
   row, so labels, tracks, and values line up down the whole chart. The
   track carries the scale: a baseline on the left, gridlines at the
   quarters, the far edge ruled. */
.xbars { display: flex; flex-direction: column; font-size: 12px; }
.xbar {
  display: grid;
  grid-template-columns: minmax(90px, 200px) minmax(0, 1fr) 64px;
  gap: 10px;
  align-items: center;
  padding: 2px 0;
}
.xbar-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.xbar-track {
  height: 14px;
  position: relative;
  border-left: 1.5px solid var(--black);
  border-right: 1px solid var(--grey-line);
  background-image: linear-gradient(to right, var(--grey-line) 1px, transparent 1px);
  background-size: 25% 100%;
  background-position: 0 0;
}
.xbar-fill { background: var(--black); height: 100%; }
.xbar:first-child .xbar-fill { background: var(--green); }
.xbar-n { text-align: right; font-family: "Courier New", monospace; font-variant-numeric: tabular-nums; font-size: 11.5px; }
.xbar-axis { padding-top: 4px; margin-top: 2px; align-items: start; }
.xbar-axis .xbar-name, .xbar-axis .xbar-n {
  font-family: var(--font);
  font-size: 12px;
  color: var(--grey);
  font-weight: 700;
}
.xbar-ticks { position: relative; height: 14px; font-family: "Courier New", monospace; font-size: 10.5px; color: var(--grey); }
.xbar-ticks span { position: absolute; top: 0; transform: translateX(-50%); white-space: nowrap; }
.xbar-ticks span:first-child { transform: none; }
.xbar-ticks span:last-child { transform: translateX(-100%); }

/* Line. Y ticks in a column beside the svg, x ticks under it; the svg
   itself stretches to the panel, which keeps the stroke and gridlines
   proportional and leaves the text crisp. */
.xline { display: grid; grid-template-columns: auto minmax(0, 1fr); grid-template-rows: 160px auto; column-gap: 8px; }
.xline-y {
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  text-align: right;
  font-family: "Courier New", monospace;
  font-size: 10.5px;
  color: var(--grey);
  line-height: 1;
}
.xline-svg { width: 100%; height: 160px; display: block; }
.xline-grid { stroke: var(--grey-line); stroke-width: 1; vector-effect: non-scaling-stroke; }
.xline-base { stroke: var(--black); stroke-width: 1.5; vector-effect: non-scaling-stroke; }
.xline-path { fill: none; stroke: var(--black); stroke-width: 1.5; vector-effect: non-scaling-stroke; }
.xline-x {
  grid-column: 2;
  display: flex;
  justify-content: space-between;
  font-family: "Courier New", monospace;
  font-size: 10.5px;
  color: var(--grey);
  margin-top: 4px;
}

@media (max-width: 960px) {
  .explore-layout { grid-template-columns: minmax(0, 1fr); }
  .explore-side { order: 2; position: static; }
  .explore-main { order: 1; }
  .panel-head { flex-wrap: wrap; }
  .explore-status { margin-left: 0; width: 100%; text-align: left; }
  /* Name and the remove button on the first line, the control full
     width under them. Reported live: the picker was landing in the
     30px column meant for the remove button, cut to "A...". */
  .qb-filter, .qb-filter:has(.qb-date) { grid-template-columns: minmax(0, 1fr) 36px; }
  .qb-filter-op { display: none; }
  /* Density is for a desk. Under a thumb every control grows to a
     target you can hit: 38px inputs, 36px buttons, 36px list rows. */
  .qb-select, .qb-input, .qb .ms-toggle { height: 38px; padding-top: 8px; padding-bottom: 8px; }
  .seg-btn { height: 36px; padding: 0 12px; }
  .btn-small, .qb-remove { height: 36px; }
  .qb-remove { width: 36px; }
  .explore-template { padding: 9px 10px 9px 22px; }
  .sx-toggle { padding: 9px 10px 9px 24px; }
  .sx-toggle::before { top: 9px; }
  .sx li { padding: 4px 10px 4px 24px; }
  .qb-filter-name { grid-column: 1; grid-row: 1; }
  .qb-remove { grid-column: 2; grid-row: 1; }
  .qb-filter .ms, .qb-filter .qb-input, .qb-filter .qb-date { grid-column: 1 / -1; }
  .explore-metric { margin-left: 0; width: 100%; text-align: left; }
  .xbar { grid-template-columns: minmax(72px, 36%) minmax(0, 1fr) 52px; }
  /* Five ticks do not fit a phone-width track; keep the two ends. */
  .xbar-ticks span:nth-child(2), .xbar-ticks span:nth-child(3), .xbar-ticks span:nth-child(4) { display: none; }
}

/* Account page.

   The nav opens a section rather than scrolling to one, so exactly one
   is on screen at a time and the page has no length to get lost in.
   Above 960px the nav and the open section are one group, centred in
   the window rather than spread to the gutters: a settings screen that
   ran the full width of an ultra-wide monitor put its nav and its form
   a metre apart. Below 960px the nav is the whole screen and opening a
   section replaces it, with Back to come out again.

   Before this the page was a 720px column pinned to the left of a
   1600px window, 45% of the width, with four sections under four
   identical 13px headings. */
/* .workspace is itself the board's two-column grid, so a page that puts
   one .section in it gets the Market Stats column reserved beside it
   and never filled. Without this the group below centres inside that
   leftover column rather than inside the window. */
.workspace-solo { grid-template-columns: 1fr; }
/* .section ends with a rule across the whole window, which is right on a
   page whose content is also that wide. Here it drew a full-width line
   under a 1120px group and then nothing, so the page looked like it had
   lost its second half. There is no footer under it to divide from. */
.section.account-section { border-bottom: 0; padding-bottom: 64px; }
/* Two columns, a hairline between them, and no banner above them. The
   avatar, the name and the email moved into the sidebar beside the nav:
   as a centred header they were a third of the first screen saying
   something the reader already knew. */
/* The account page, on the board's panel system: two boxes on the same
   tinted ground, each with the board's own 64px head. The two pages are
   the same product and were drawn as if they were not. */
.account-shell {
  background: var(--hover-bg);
  height: calc(100vh - 57px);
  height: calc(100dvh - 57px);
  display: flex;
  flex-direction: column;
}
.account-layout {
  display: grid;
  grid-template-columns: 260px minmax(0, 1fr);
  gap: 10px;
  align-items: stretch;
  flex: 1;
  min-height: 0;
  padding: 12px 28px 28px;
}
.account-layout[hidden] { display: none; }
.account-layout > * { min-width: 0; }

.acct-box {
  display: flex;
  flex-direction: column;
  min-height: 0;
  background: var(--white);
  border: 1px solid var(--panel-line);
  border-radius: 10px;
  overflow: hidden;
}
.acct-box-head {
  flex: none;
  height: 64px;
  box-sizing: border-box;
  padding: 0 20px;
  display: flex;
  align-items: center;
  gap: 12px;
  border-bottom: 1px solid var(--grey-line);
}
.acct-box-body {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
  scrollbar-width: thin;
  scrollbar-color: var(--grey-line) transparent;
  padding: 24px;
  display: flex;
  flex-direction: column;
  gap: 32px;
}

/* Sidebar */
.acct-id-mark { width: 32px; height: 32px; flex: 0 0 auto; }
.acct-id-mark .hero-avatar,
.acct-id-mark img { width: 32px; height: 32px; font-size: 14px; }
.acct-id-text { display: flex; flex-direction: column; min-width: 0; }
.acct-id-name { font: 400 15px var(--font-display); color: var(--black); }
.acct-id-email {
  font-size: 12px;
  color: var(--grey);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.account-nav {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  padding: 12px;
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.account-nav-link {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  height: 36px;
  padding: 0 10px;
  border-radius: 4px;
  color: var(--black);
  font-size: 14px;
  text-decoration: none;
}
.account-nav-link:hover:not(.active) { background: var(--row-hover); }
.account-nav-link:focus-visible { outline: 2px solid var(--green); outline-offset: -2px; }
/* The same mark an open row carries on the board. */
.account-nav-link.active {
  background: var(--row-open);
  box-shadow: inset 3px 0 0 var(--black);
  font-weight: 700;
}
/* A count of nothing is not information. */
.account-nav-count {
  flex: 0 0 auto;
  font-size: 12px;
  color: var(--grey);
  font-variant-numeric: tabular-nums;
}
.account-nav-count[data-zero="1"] { display: none; }
.account-nav-link.active .account-nav-count { color: var(--green-text); }

.acct-box-foot {
  flex: none;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 12px 16px;
  border-top: 1px solid var(--grey-line);
  font-size: 12px;
  color: var(--grey);
}
.acct-provider { display: inline-flex; align-items: center; gap: 6px; min-width: 0; }
.acct-provider[hidden] { display: none; }
.acct-provider svg { flex: none; }
.acct-signout {
  /* A button element, so it needs the chrome taken off it explicitly.
     margin-left:auto keeps it at the right edge when the provider line
     beside it is empty, which it is unless the token names one. */
  flex: 0 0 auto;
  margin-left: auto;
  padding: 0;
  border: 0;
  background: none;
  font: 400 12px var(--font);
  cursor: pointer;
}

/* One best match: title and where over a count of the skills it hit. */
.acct-match {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  height: 104px;
  box-sizing: border-box;
  padding: 0 4px;
  border-bottom: 1px solid var(--grey-line);
  color: var(--black);
  text-decoration: none;
}
.acct-match:last-child { border-bottom: 0; }
.acct-match:hover { background: var(--row-hover); }
.acct-match:hover .acct-match-title { color: var(--green-text); }
.acct-match .company-logo {
  width: 40px;
  height: 40px;
  flex: 0 0 auto;
  margin-right: 12px;
}
.acct-match-text { display: flex; flex-direction: column; gap: 4px; min-width: 0; flex: 1; }
.acct-match-title {
  font: 600 15px var(--font);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.acct-match-meta {
  font-size: 13px;
  color: var(--grey);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.acct-match-score { flex: 0 0 auto; font-size: 12px; color: var(--green-text); font-variant-numeric: tabular-nums; }

/* Main head */
.acct-head-text { display: flex; flex-direction: column; gap: 2px; min-width: 0; flex: 1; }
.acct-head-title { font: 500 17px var(--font-display); color: var(--black); }
.acct-head-status { font-size: 12px; color: var(--grey); }
.acct-head-action { flex: 0 0 auto; }

/* Section panels inside the main box */
.acct-panel { display: flex; flex-direction: column; gap: 12px; min-width: 0; }
.acct-panel-title {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  margin: 0;
  padding-bottom: 8px;
  border-bottom: 1px solid var(--grey-line);
  font: 500 14px var(--font-display);
  color: var(--black);
}
.acct-panel-title .link { font-size: 12px; font-weight: 400; }
.acct-row-two { display: grid; grid-template-columns: 1fr 1fr; gap: 24px; align-items: start; }
/* Its own separation from the row above rather than relying on the
   body's gap: that gap is measured from the taller of the two panels up
   there, so under the shorter one it collapsed to a few pixels. */
.acct-row-lead {
  display: grid;
  grid-template-columns: 1.6fr 1fr;
  gap: 24px;
  margin-top: 16px;
}
.acct-row-two > [hidden] { display: none; }

/* Finish setup */
.acct-check { list-style: none; margin: 0; padding: 0; }
.acct-check-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 0;
  border-bottom: 1px solid var(--grey-line);
  font-size: 14px;
}
.acct-check-row:last-child { border-bottom: 0; }
.acct-check-mark {
  width: 16px;
  height: 16px;
  flex: 0 0 auto;
  border: 1.5px solid var(--grey-line);
  border-radius: 4px;
}
.acct-check-label { flex: 1; min-width: 0; }
.acct-check-row.done { opacity: 0.6; }
.acct-check-row.done .acct-check-label { text-decoration: line-through; }
.acct-check-row.done .acct-check-mark {
  display: grid;
  place-items: center;
  border-color: var(--black);
  background: var(--black);
  color: var(--white);
  font-size: 11px;
  font-weight: 700;
}
.acct-check-row.done .acct-check-mark::after { content: "\2713"; }
.acct-check-do[hidden] { display: none; }

/* At a glance */
.acct-tiles {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  border: 1px solid var(--grey-line);
  border-radius: 4px;
  overflow: hidden;
}
.acct-tile {
  display: flex;
  flex-direction: column;
  gap: 3px;
  padding: 14px 14px 16px;
  border-right: 1px solid var(--grey-line);
  color: var(--black);
  text-decoration: none;
}
.acct-tile:last-child { border-right: 0; }
.acct-tile:hover { background: var(--hover-bg); }
.acct-tile:hover .acct-tile-value { color: var(--green-text); }
.acct-tile:focus-visible { outline: 2px solid var(--green); outline-offset: -2px; }
.acct-tile-value { font: 400 28px/1 var(--font-display); font-variant-numeric: tabular-nums; }
.acct-tile-label { font-size: 13px; }
.acct-tile-sub { font-size: 12px; color: var(--grey); }
.acct-tile-sub:empty { display: none; }

/* Skills */
.acct-tags { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; }
.acct-tag {
  display: inline-flex;
  align-items: center;
  height: 22px;
  padding: 0 7px;
  border: 1px solid var(--grey-line);
  border-radius: 4px;
  font: 400 12px var(--font-ui);
  color: var(--black);
}
.acct-more { font-size: 12px; color: var(--green-text); text-decoration: none; }
.acct-more:hover { text-decoration: underline; }
.acct-tags .acct-none { color: var(--grey); font-size: 13px; }
.acct-note { margin: 0; font-size: 12px; color: var(--grey); }
.acct-note[hidden] { display: none; }
.acct-matches-empty { margin: 0; font-size: 13px; color: var(--grey); }

@media (max-width: 960px) {
  .acct-row-two,
  .acct-row-lead { grid-template-columns: minmax(0, 1fr); }
}

.account-main .alert-create-fields {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 8px;
}
.account-main .alert-create-fields > input[type="text"] { grid-column: 1 / -1; }
.account-main .alert-create .btn { width: auto; min-width: 180px; }

/* One section at a time. */
.account-block { padding: 0 0 8px; border-top: 0; }
.account-block:not(.is-active) { display: none; }
.account-back { display: none; }

/* Rounder corners across this page, by request.

   14px, which is what the sign-in dialog and the account menu already
   use for a control on a surface that floats rather than sits in the
   grid. Reusing that value rather than inventing a third one, and
   scoped to this page so the board keeps the system's flat 4px. */
.account-page .btn,
.account-page .cv-drop,
.account-page .cv-chip,
.account-page .account-back,
.account-page .badge,
.account-page .alert-toggle,
.account-page .ms-toggle,
.account-page .ms-menu,
.account-page .alert-create-fields input[type="text"],
.account-main .alert-create-fields label.toggle { border-radius: 4px; }
/* Opening a section, above the breakpoint only. On a phone a section is
   a different screen rather than a panel changing beside a nav that
   stays put, and sliding one screen over another is a transition this
   page has no other use for. */
@media (min-width: 961px) {
  @keyframes account-panel-in {
    from { opacity: 0; transform: translateY(10px); }
    to { opacity: 1; transform: none; }
  }
  /* The class is only ever toggled on a change, so this runs when a
     section opens and not again on a resize or a repaint. */
  .account-block.is-active { animation: account-panel-in 260ms cubic-bezier(0.22, 1, 0.36, 1); }
}
@media (prefers-reduced-motion: reduce) {
  .account-block.is-active { animation: none; }
}

/* Phone and small tablet. The nav stops being a screen of its own and
   becomes a strip of tabs above the content, which is why
   .account-layout:not(.section-open) .account-main used to hide
   everything: with no hash in the URL nothing was open, so the page
   rendered a menu and nothing under it. Reported as blank content under
   the tabs, and it was.  */
@media (max-width: 800px) {
  .account-shell { height: auto; min-height: 0; background: var(--hover-bg); }
  .account-layout {
    display: flex;
    flex-direction: column;
    /* 16 and 12, not 10 and 8. The 8px gutter came from the board, where
       the content is one dense list and every pixel of width is a job
       title. This page is a column of cards, and at 8px they sat almost
       on the edge of the screen with less air between them than there is
       inside them, which reads as one crowded thing rather than four
       separate ones. Reported live 2026-09-24. */
    gap: 16px;
    padding: 16px 12px;
    height: auto;
  }

  /* The sidebar box unwraps: identity as a row, nav as the tab strip,
     and the foot moves to the end of the page. */
  /* A grid so the identity and the way out share the top row and the
     tabs run the width under both. As a flex column the foot landed
     between the tabs and the content, which is the one place a Sign out
     link should never be. */
  /* display: contents, so the three parts of the sidebar become items of
     the page column itself and can be ordered against .account-main. As
     its own grid the foot could only ever land under the tabs, above the
     content. The box styling this drops is already off at this width. */
  .account-side {
    display: contents;
  }
  .account-side .acct-box-head { order: 1; }
  .account-side .account-nav { order: 2; }
  .account-main { order: 3; }
  .account-side .acct-box-foot { order: 4; }
  .account-side .acct-box-head {
    height: auto;
    padding: 2px 4px 0;
    border-bottom: 0;
  }
  .acct-id-mark { width: 40px; height: 40px; }
  .acct-id-mark .hero-avatar,
  .acct-id-mark img { width: 40px; height: 40px; font-size: 17px; }
  .acct-id-name { font-size: 17px; }

  /* Underline tabs, scrolled sideways. No boxes: a row of outlined
     buttons at this width reads as five separate things to decide
     between rather than one place you are in. */
  .account-nav {
    flex: none;
    flex-direction: row;
    gap: 0;
    padding: 0;
    overflow-x: auto;
    overflow-y: hidden;
    scrollbar-width: none;
    border-bottom: 1px solid var(--grey-line);
  }
  .account-nav::-webkit-scrollbar { display: none; }
  .account-nav-link {
    flex: 0 0 auto;
    height: 44px;
    gap: 6px;
    padding: 0 14px;
    border: 0;
    border-radius: 0;
    background: none;
    font-size: 15px;
    white-space: nowrap;
  }
  .account-nav-link:hover:not(.active) { background: none; color: var(--green-text); }
  .account-nav-link.active {
    background: none;
    box-shadow: inset 0 -2px 0 var(--green-text);
    font-weight: 700;
  }

  .acct-box-foot { border: 0; padding: 0 4px 8px; }

  /* Every section is a box on the ground, and they are all present:
     the tab decides which one, not whether there is one. */
  .account-main {
    border: 0;
    border-radius: 0;
    background: none;
    overflow: visible;
  }
  .account-main > .acct-box-head { display: none; }
  .acct-box-body {
    overflow: visible;
    padding: 0;
    gap: 16px;
  }
  .account-block {
    display: none;
    padding: 0;
    background: var(--white);
    border: 1px solid var(--panel-line);
    border-radius: 10px;
    overflow: hidden;
  }
  .account-block.is-active { display: block; }
  .acct-row-two,
  .acct-row-lead { grid-template-columns: minmax(0, 1fr); gap: 16px; margin-top: 0; }

  /* A panel inside a section becomes its own box, with the 48px header
     row the spec asks for. */
  .acct-panel {
    gap: 0;
    background: var(--white);
    border-top: 1px solid var(--grey-line);
  }
  .acct-panel:first-child { border-top: 0; }
  .acct-panel-title {
    height: 48px;
    padding: 0 16px;
    align-items: center;
    border-bottom: 1px solid var(--grey-line);
  }
  /* The content of a panel started on the header's own bottom rule: the
     skill chips touched the line above them, and so did "Looking..." and
     the checklist. A 48px header bar with its text centred reads as
     roomy and made the rest look squeezed by comparison. */
  /* Not :last-child, which is what this was: the Skills panel ends with
     a hidden .acct-note, so the chips above it were the last thing you
     could see and the last thing with no padding under them. They ran
     into the bottom of the card. Every content child carries its own
     padding; each of these panels has exactly one. */
  .acct-panel > :not(.acct-panel-title) {
    padding: 14px 16px 18px;
  }
  /* Except the tile grid, whose dividers are meant to run the full
     height of the row. It takes its air inside each tile instead. */
  .acct-tiles { padding-top: 0; padding-bottom: 0; }
  /* Sections that are not built from .acct-panel -- Skills, Alerts,
     Saved, Settings -- put their content straight into the block, so it
     sat against the box edge with nothing between the two. The panel
     rule above only ever covered Overview. */
  .account-block > :not(.acct-panel):not(.acct-row-two):not(.acct-row-lead) {
    padding: 18px 16px;
  }
  /* A status line says nothing until something happens to it, and an
     empty <p> with this padding was 36px of blank band under the CV drop
     zone for the whole time nothing had. */
  .account-block > .cv-status:empty,
  .account-block > .account-status:empty { display: none; }
  .acct-check-row { padding: 13px 0; }
  .account-block > .acct-rows { padding: 0 16px; }
  .account-block > .acct-rows .acct-row:first-child { border-top: 0; }
  /* The chips wrap to five or six rows on a phone, so they need the
     breathing room more than anything else on the page. */
  .cv-chips { row-gap: 10px; }
  .acct-tiles { border: 0; border-radius: 0; }
  .acct-tile { padding: 18px 12px 20px; }
  .acct-tile-value { font-size: 24px; }

  .account-back { display: none; }
  .account-main .alert-create-fields { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .account-main .alert-create .btn { width: 100%; }
}

.account-block-title {
  margin: 0 0 6px;
  /* Display / Section Title at its 24px step, the one the Statistics
     column already takes when a display title has to sit above 13px
     controls. At the old 13px these were the same size as the labels
     inside them, which is why the page read as four equal things. */
  font-family: var(--font-display);
  font-size: 24px;
  font-weight: 500;
  line-height: 1.1;
  letter-spacing: -0.02em;
}
.account-block-note {
  margin: 0 0 18px;
  max-width: 68ch;
  color: var(--grey);
  font-size: 13px;
  line-height: 1.5;
}

@media (max-width: 640px) {
  .account-main .alert-create-fields { grid-template-columns: 1fr; }
  .cv-drop { padding: 24px 16px; }
}

/* The account bar carries one link out and the theme, so it is the
   landing bar's shape rather than its bare, right-aligned variant. */
.account-bar { justify-content: space-between; }
.account-bar .hero-bar-name { display: inline-flex; align-items: center; }
.account-bar .hero-bar-name:hover { color: var(--green-text); }

/* One line saying what the page is for, under the block that names it. */
.account-lede {
  grid-area: lede;
  margin: 0 0 32px;
  color: var(--grey);
  font-size: 13px;
  text-align: center;
}
/* Which provider signed this reader in, when the token says so. Not a
   claim the page makes on its own: see account.js. */
.acct-provider {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  margin: 3px 0 0;
  padding: 2px 10px 2px 6px;
  border: 1px solid var(--grey-line);
  border-radius: 999px;
  color: var(--grey);
  font-family: var(--font-ui);
  font-size: 12px;
}
.acct-provider[hidden] { display: none; }
.acct-provider svg { flex: none; }

/* The overview's three counts, built the way the board's Market Stats
   tiles are: paper cells over a hairline ground, so the 2px gaps are
   the grid showing through rather than a border each. */

.acct-profile { margin-bottom: 32px; }
.acct-sub {
  margin: 0 0 10px;
  font-family: var(--font-display);
  font-size: 17px;
  font-weight: 500;
}
.acct-tags { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
.acct-more {
  color: var(--green-text);
  font-family: var(--font-ui);
  font-size: 12.5px;
}
.acct-more:hover { text-decoration: underline; }
/* Read-only here. Removing one is the scanner's job, where the list
   being edited is the one about to be saved. */
.acct-tag {
  padding: 5px 12px;
  border: 1px solid var(--grey-line);
  border-radius: 999px;
  font-family: var(--font-ui);
  font-size: 12.5px;
}
.acct-tags .acct-none { color: var(--grey); font-size: 13px; }

/* A phone's chips. 22px is a desktop size, and here they are the main
   thing in their own panel with a thumb for a pointer. This has to come
   after the rules above rather than with the rest of the phone layout:
   those set gap and padding later in the file and would win on order. */
@media (max-width: 800px) {
  .acct-tags { row-gap: 10px; }
  .acct-tag { height: 28px; padding: 0 12px; font-size: 13px; }
}

/* The last thing on the page, behind a hairline in the one reserved
   colour. A bordered red card here was nearly as loud as the workspace
   itself, which is the wrong weight for something a reader should meet
   only when they are looking for it. */
.account-danger {
  margin-top: 40px;
  padding-top: 18px;
  border-top: 1px solid var(--red);
}
.acct-danger-title {
  margin: 0 0 4px;
  color: var(--red);
  font-size: 13px;
  font-weight: 700;
}
.acct-danger-note { margin: 0; max-width: 62ch; color: var(--grey); font-size: 13px; }
.account-danger .account-actions { margin-top: 12px; }

@media (max-width: 640px) {
}

/* Signed out, the sign-in is a dialog in the middle of the window
   rather than a card at the top of an otherwise empty page. The page
   behind it drops its section rule and title, which had nothing under
   them to introduce. */
.signin-scrim {
  position: fixed;
  inset: 0;
  z-index: 60;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
  background: var(--scrim);
}
.signin-scrim[hidden] { display: none; }
.signin-dialog {
  width: 100%;
  max-width: 420px;
  padding: 28px;
  border: var(--rule) solid var(--black);
  /* Card / Preview, the same 24px the showcase card on the landing
     page is rounded to. This is that card's shape, not a new one. */
  border-radius: 24px;
  background: var(--white);
  /* The display face, not the dense UI one. This is the only dialog on
     the site a reader meets before they have seen anything else, so it
     speaks in the landing page's voice rather than the board's. */
  font-family: var(--font-display);
}
/* The fields and buttons inside name --font-ui themselves, so each one
   has to be told again here. */
.signin-dialog .auth-provider-btn,
.signin-dialog .auth-email-form input,
.signin-dialog .auth-divider,
.signin-dialog .btn {
  font-family: var(--font-display);
}
.signin-dialog .auth-provider-btn,
.signin-dialog .auth-email-form input {
  height: 46px;
  font-size: 14px;
}
.signin-dialog .auth-provider-btn { margin-bottom: 10px; }
.signin-dialog .auth-email-form .btn { height: 46px; font-size: 13px; padding: 0 18px; }
/* The controls inside the dialog, all four shaped alike.
   
   They used to be the system's 2px ink rule on a 4px corner, on the
   argument that a soft outer card holding hard-cornered controls is the
   contrast that makes the card read. That was the showcase card's
   reasoning borrowed wholesale, and it does not survive here: this card
   floats over the page rather than sitting in it, and four hard boxes
   inside a floating card read as a form pasted into a dialog.
   
   So the same corner the card and the landing button use, and an edge
   made of shadow rather than ink. The hairline stays for the same
   reason the account menu keeps one: on a dark ground a shadow is
   invisible and the control still needs a boundary. */
.signin-dialog .auth-provider-btn,
.signin-dialog .auth-email-form input,
.signin-dialog .auth-email-form .btn {
  border-radius: 10px;
  border: 1px solid var(--grey-line);
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05), 0 4px 12px rgba(0, 0, 0, 0.07);
}
/* The one filled control here, so its edge comes from its own fill. */
.signin-dialog .auth-email-form .btn { border-color: transparent; }
.signin-dialog .auth-provider-btn:hover { border-color: var(--black); }
.signin-dialog .auth-email-form input:focus { outline-offset: -1px; }
.signin-dialog .auth-divider { font-size: 13px; margin: 14px 0; }
.signin-dialog .account-block-title {
  margin: 0;
  /* Display / Section Title at its 24px step, the one the Statistics
     column uses for exactly this reason: a display title sitting beside
     13px controls, where the full 34px would be the loudest thing on
     screen. */
  font-size: 24px;
  font-weight: 400;
  letter-spacing: -0.02em;
}
.signin-dialog-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 20px; }
.signin-close {
  border: 0;
  background: none;
  color: var(--grey);
  font-size: 24px;
  line-height: 1;
  padding: 0 4px;
  cursor: pointer;
}
.signin-close:hover { color: var(--black); }
.signin-close:focus-visible { outline: 2px solid var(--green); outline-offset: 2px; }
body.signin-open .account-page > .section-title { display: none; }
body.signin-open .workspace > .section { border-bottom: 0; }
.account-note { color: var(--grey); font-size: 11.5px; margin: 10px 0 0; max-width: 60ch; line-height: 1.5; }
.account-fields { display: flex; flex-direction: column; gap: 10px; max-width: 420px; }
.account-field { display: flex; flex-direction: column; gap: 4px; }
.account-label {
  font-size: 12px;
  color: var(--grey);
}
.account-toggle { align-self: flex-start; }
.account-actions { display: flex; align-items: center; gap: 10px; margin-top: 14px; flex-wrap: wrap; }
.account-status { font-size: 11.5px; color: var(--grey); }
.account-status.error { color: var(--red); }
/* The alert rows here are read-only summaries; editing lives in the
   board's own panel, which already has the filter pickers. */
.alert-summary-static { font-size: 12px; flex: 1 1 auto; min-width: 0; }
.alerts-header-row { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; }
.account-link { font-size: 11.5px; }
/* Destructive, and the only place on the site that is. */
.btn.btn-danger { border-color: var(--red); color: var(--red); }
.btn.btn-danger:hover { background: var(--red); border-color: var(--red); color: var(--white); }
/* The confirm half of the pair: filled, because by the time it appears
   the reader has already said what they want and this is the click that
   does it. */
.btn.btn-danger-solid {
  background: var(--red);
  border-color: var(--red);
  color: var(--white);
}
.btn.btn-danger-solid:hover { background: var(--black); border-color: var(--black); }

/* CV analyser. The file is read in the page and never uploaded, so
   there is no upload affordance to draw: a button and the filename is
   the whole interaction. */
.cv-drop {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  padding: 28px 20px;
  border: var(--rule) solid var(--grey-line);
  border-radius: 4px;
  text-align: center;
}
/* The one green moment on this page, and it lasts as long as a file is
   over the zone. */
.cv-drop.dragging { border-color: var(--green); }
.cv-drop-lead { margin: 0; font-size: 15px; font-weight: 600; }
.cv-drop-sub { margin: 0 0 10px; color: var(--grey); font-size: 12px; }
.cv-filename { color: var(--grey); font-size: 11.5px; }
.cv-status { font-size: 11.5px; color: var(--grey); margin: 8px 0 0; min-height: 1.2em; }
.cv-status.error { color: var(--red); }
.cv-chips { display: flex; flex-wrap: wrap; gap: 6px; margin: 6px 0 4px; }
/* Each chip is a remove button, so the found list is a starting point
   rather than a verdict. */
.cv-chip {
  appearance: none;
  border: var(--rule) solid var(--black);
  border-radius: 4px;
  background: var(--white);
  color: var(--black);
  font-family: var(--font-ui);
  font-size: 11.5px;
  padding: 4px 8px;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  cursor: pointer;
}
.cv-chip span { color: var(--grey); }
.cv-chip:hover { background: var(--black); color: var(--white); }
.cv-chip:hover span { color: var(--white); }
.cv-chip:focus-visible { outline: 2px solid var(--green); outline-offset: 2px; }
/* Each chip is a remove button and at 29px it was a small one for a
   thumb. Height only: the row still wraps the same way. */
@media (pointer: coarse) {
  .cv-chip { min-height: 36px; padding: 6px 10px; font-size: 12.5px; }
}

/* /hero. A draft landing page: a giant wordmark over a full-width block of
   live board numbers on two tickers, then the features, each rising into
   place as it scrolls in. Overused Grotesk throughout, a named exception to
   the Three-Voice Rule (see DESIGN.md), because this page is the brand
   moment the display face exists for. Colours are the board's own tokens,
   so both themes work. */
.hero-page { background: var(--white); color: var(--black); }

.hero-bar {
  display: flex;
  align-items: stretch;
  justify-content: space-between;
  gap: 16px;
  padding: 14px var(--edge) 0;
  font-family: var(--font-display);
  /* The footer's size, because they are the same bar: the header was
     13px against the footer's 26px and read as a caption above a page
     that ends in full voice. */
  font-size: 26px;
  line-height: 1.3;
  /* One line of this bar. Written out rather than as 1.3em, because a
     custom property holding em resolves against whoever uses it, and
     the theme button next door is 17px. */
  --bar-line: calc(26px * 1.3);
}
/* The landing page's bar carries the account corner and nothing else.
   The site's name there linked to the page you were already on, and the
   repository has a home in the menu and the mobile list.
   
   Top spacing equals side spacing, asked for and measured against it:
   --edge is what holds the bar off the right edge, so the same value
   holds it off the top and the corner sits in a square of space. */
.hero-bar-bare {
  justify-content: space-between;
  align-items: center;
  padding-top: var(--edge);
}

/* The brand, Island-style: the mark and the name in regular weight. */
.hero-brand {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  font: 400 22px/1 var(--font-display);
  letter-spacing: -0.01em;
  color: var(--black);
}
.hero-brand img { display: block; width: 28px; height: 28px; }
/* Focus shows as an underline on the name, the way the menus show it,
   rather than the green box the bar's links draw: that box read as a
   frame around the logo, not as a focus mark. */
.hero-bar .hero-brand:focus-visible { outline: none; }
.hero-brand:focus-visible span { text-decoration: underline; text-underline-offset: 5px; text-decoration-thickness: 1px; }

/* The menus: plain text, a small chevron, no boxes. The bar's own 26px
   is the footer's voice; the menus speak at nav size. */
.hero-bar-head .hero-bar-right { align-items: center; gap: 28px; }
.hero-bar-head .hero-bar-right > * { min-height: 0; }
.site-menus { display: flex; align-items: center; gap: 28px; }
.site-menu { position: relative; }
.site-menu-link,
.site-menu-btn,
.hero-bar-head .hero-bar-name {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 6px 0;
  border: 0;
  background: none;
  color: var(--black);
  font: 400 16px/1.2 var(--font-display);
  cursor: pointer;
}
.site-menu-link:hover,
.site-menu-btn:hover,
.site-menu.open .site-menu-btn,
.hero-bar-head .hero-bar-name:hover { text-decoration: underline; text-underline-offset: 4px; }
.site-menu-btn:focus-visible,
.site-menu-link:focus-visible { outline: 2px solid var(--green); outline-offset: 3px; border-radius: 2px; }
.site-menu-chev { transition: transform 150ms cubic-bezier(0.2, 0, 0, 1); }
.site-menu.open .site-menu-chev { transform: rotate(180deg); }
@media (prefers-reduced-motion: reduce) { .site-menu-chev { transition: none; } }
.site-menu-panel {
  position: absolute;
  top: calc(100% + 10px);
  right: -12px;
  z-index: 40;
  min-width: 180px;
  display: flex;
  flex-direction: column;
  padding: 6px;
  border: 1px solid var(--grey-line);
  border-radius: 10px;
  background: var(--white);
  box-shadow: 0 12px 32px -12px rgba(0, 0, 0, 0.25);
}
.site-menu-panel[hidden] { display: none; }
.site-menu-item {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 10px 12px;
  border-radius: 6px;
  color: var(--black);
}
.site-menu-item:hover,
.site-menu-item:focus-visible { background: var(--row-hover); outline: none; }
.site-menu-name { font: 500 15px var(--font-display); }
/* The theme pill at nav size beside the menus. */
.hero-bar-head .hero-theme { font-size: 14px; }
.hero-bar-head .hero-account-btn { font: 400 16px/1.2 var(--font-display); }
/* One line box for every item in the row, links and buttons alike, so
   Jobs and Log in share a baseline with the two menus. */
.hero-bar-head .site-menu-link,
.hero-bar-head .site-menu-btn,
.hero-bar-head .hero-bar-name,
.hero-bar-head .hero-account-btn { height: 32px; padding-top: 0; padding-bottom: 0; line-height: 1; align-items: center; }
.hero-bar-head .hero-account { align-items: center; }
/* A flex box, not a block: a block wraps the button in a line box with
   room for descenders under it, which sat Resources and About 2px low. */
.site-menu { display: flex; }
.hero-bar-head .hero-account-btn { box-sizing: border-box; border: 0; }
/* On a phone the right side becomes mobile_nav.js's list, and its menu
   button is inserted first in the bar; the brand takes the left edge
   and the button the right. */
@media (max-width: 640px) {
  .hero-brand { order: -1; font-size: 19px; }
  .hero-brand img { width: 24px; height: 24px; }
}
.hero-bar a { color: inherit; text-decoration: none; }
/* The name over the repository link, one column at the left. */
.hero-bar-stack { display: flex; flex-direction: column; gap: 4px; }
.hero-foot-links { display: inline-flex; gap: 16px; }

/* The account corner. Signed out it is a word; signed in, a button with
   the reader's initial that opens a menu in the system's own shapes:
   paper, an ink rule, the 4px field radius, no shadow. */
/* Level with the first line on the other side, not centred against the
   whole two-line stack. Centring put the account a half-line lower than
   the site's own name and the bar read as two rows that missed each
   other. 1.3em is the bar's line-height, so one line here is exactly one
   line there. */
.hero-bar-right { display: flex; align-items: flex-start; gap: 20px; }
.hero-bar-right > * { min-height: var(--bar-line); }
.hero-theme { display: inline-flex; align-items: center; }
/* Signed in, the theme is a row in the account menu and this button
   is hidden. The line above sets display, which beats the browser's
   own [hidden] rule, so without this it stayed on screen: the same
   trap .auth-email-form[hidden] documents further up this file. */
.hero-theme[hidden] { display: none; }

/* The phone menu, on every page.
   
   The bar was four or five controls in a row, and on the board a status
   light and a ticker besides. At 390px that is a wall before a reader
   has seen a word of the page. So on a phone the bar is three stepped
   rules at the left edge and everything else opens downward from them.
   
   mobile_nav.js builds the list. Nothing in it owns any behaviour: the
   theme row forwards a click to the page's own theme button, and
   sign-in opens the same dialog the bar always opened. */
.hero-nav-toggle {
  display: none;
  align-items: center;
  padding: 4px 0;
  border: 0;
  background: none;
  color: inherit;
  cursor: pointer;
}
.hero-nav-toggle:focus-visible { outline: 2px solid var(--green); outline-offset: 4px; border-radius: 4px; }
.hero-nav-bars { display: flex; flex-direction: column; align-items: flex-start; gap: 5px; }
/* Stepped, longest on top, all flush left, which is what puts the short
   rule over the page's own left margin rather than floating in the
   middle of a square. Three equal rules is every other site's menu; the
   step is the one place this bar gets a voice. */
.hero-nav-bars i {
  display: block;
  width: 26px;
  height: 2px;
  background: currentColor;
  /* Scaled rather than resized. All three rules are the same 26px and
     the short ones are squeezed, so the step animates on the compositor
     instead of asking the browser to lay the bar out again on every
     frame of a 240ms transition. */
  transform-origin: left center;
  transition: transform 0.24s cubic-bezier(0.2, 0.7, 0.2, 1);
}
.hero-nav-bars i:nth-child(2) { transform: scaleX(0.73); }
.hero-nav-bars i:nth-child(3) { transform: scaleX(0.46); }
/* Open, the step evens out. It says "everything is showing" without
   turning into an X, which would be a different idea in a different
   visual language. */
.nav-open .hero-nav-bars i { transform: scaleX(1); }

.hero-nav-panel {
  display: none;
  flex-direction: column;
  /* Hung off the bar, so it covers the page rather than pushing it
     down. The bar is the positioned ancestor and, on the board, the
     sticky element that already paints over the listings. */
  position: absolute;
  top: 100%;
  left: 0;
  right: 0;
  z-index: 50;
  /* The window the list slides through. It keeps its full height at all
     times and is simply invisible when shut, which is what lets the
     slide be a transform: animating a height instead would make the
     browser lay the page out again on every frame of the ease. Absolute,
     so holding that height costs the page nothing. */
  overflow: hidden;
  visibility: hidden;
  pointer-events: none;
  transition: visibility 0s linear 0.26s;
  /* The display face at a size a thumb can aim at. The dense UI face is
     for tables; this is nine words on an otherwise empty screen. */
  font-family: var(--font-display);
  font-size: 24px;
  font-weight: 400;
}
.hero-nav-list {
  padding: 6px 0 14px;
  background: var(--white);
  border-bottom: var(--rule) solid var(--black);
  /* Parked directly above the window, so it arrives from under the bar
     rather than fading in where it lands. */
  transform: translateY(-100%);
  transition: transform 0.26s ease-out;
}
.nav-open .hero-nav-panel {
  visibility: visible;
  pointer-events: auto;
  transition: visibility 0s;
}
.nav-open .hero-nav-list { transform: translateY(0); }
.hero-nav-item {
  display: flex;
  align-items: center;
  gap: 12px;
  width: 100%;
  padding: 15px 0;
  border: 0;
  border-bottom: 1px solid var(--grey-line);
  background: none;
  color: var(--black);
  font: inherit;
  text-align: left;
  text-decoration: none;
  cursor: pointer;
}
.hero-nav-item:last-child { border-bottom: 0; }
.hero-nav-item:active { color: var(--green-text); }
/* Grown with the text beside them, or a 16px mark against 24px type
   reads as an afterthought. */
.hero-nav-item svg, .hero-nav-who svg { flex: none; width: 21px; height: 21px; }
.hero-nav-out { color: var(--red); }
.hero-nav-who {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 14px 0 16px;
  border-bottom: 1px solid var(--grey-line);
  font-size: 19px;
}
.hero-nav-email { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

@media (max-width: 640px) {
  .hero-nav-toggle { display: inline-flex; }
  .hero-nav-panel { display: flex; }
  /* Everything the menu now carries, taken out of the bar itself. */
  .hero-bar-head .hero-bar-stack,
  .hero-bar-head .hero-bar-right,
  /* The account bar's way back to the board. The menu below already
     carries Jobs, and the rule on a phone is that the bar is the three
     rules and nothing else. */
  .account-bar .hero-bar-name,
  /* The account and contact bars carry their links, sign-in and theme
     button loose in the row rather than inside .topnav, so each has to
     be named. Left in, they sat on top of whatever else the bar held
     once the menu icon took the width. */
  .topbar-account .topbar-nav,
  .topbar-board .topbar-left .topbar-nav,
  .topbar-account .auth-area,
  .topbar-account .theme-toggle,
  .topbar .topnav { display: none; }
  /* The panel is a child of the bar, and the bar is a flex row, so
     without this it lands beside the icon instead of under it. */
  .hero-bar-head {
    align-items: center;
    padding-top: 12px;
    padding-bottom: 10px;
  }
  /* The stepped rules stay at the left edge, so the bare bar goes
     back to pushing its two ends apart on a phone. */
  .hero-bar-bare { justify-content: space-between; padding-top: 12px; }
  .hero-nav-panel { display: flex; }
  /* The panel is absolute against these, so they have to be what it is
     absolute against. The board's is already sticky, which positions
     it; the hero header is not. */
  .hero-bar-head { position: relative; }
  /* It no longer sits inside the padded row, so it carries the page
     margin itself. Two rules because the two bars measure that margin
     differently, and the board's clears a notch. */
  .hero-bar-head .hero-nav-list { padding-left: var(--edge); padding-right: var(--edge); }
  .topbar .hero-nav-list {
    padding-left: max(var(--gutter), env(safe-area-inset-left));
    padding-right: max(var(--gutter), env(safe-area-inset-right));
  }
  .topbar > .container { align-items: center; gap: 12px; }
}

/* Deliberately no prefers-reduced-motion exception for either menu.
   Asked for on 2026-09-22: these are to animate whatever the operating
   system is set to. The stepped rules still honour it, since a width
   stepping under a thumb is the part of this that could bother anyone
   who asked for less movement. */
@media (prefers-reduced-motion: reduce) {
  .hero-nav-bars i { transition: none; }
}

/* Only ever on screen signed out, where there is no account menu to
   carry the theme row. A pill rather than the bare word it used to be:
   it stands next to a round avatar and a menu with a 14px corner, and a
   naked word between them read as unfinished. */
.hero-theme {
  border: 1px solid var(--grey-line);
  border-radius: 999px;
  background: none;
  padding: 2px 18px;
  color: inherit;
  /* The bar's face and size, as the account control beside it. */
  font: inherit;
  cursor: pointer;
}
.hero-theme:hover { border-color: var(--green); }
.hero-theme:hover { color: var(--green-text); }
.hero-theme:focus-visible { outline: 2px solid var(--green); outline-offset: 3px; border-radius: 4px; }
.hero-account { position: relative; display: flex; align-items: flex-start; }
.hero-account > .hero-bar-name { display: inline-flex; align-items: center; }
a.hero-account-btn { text-decoration: none; }
/* The same menu on the topbar, where the bar's own uppercase tracking
   would otherwise reach into it. */
/* The board's topbar keeps its own smaller copy: that bar is dense and
   full of controls, where the landing page's is two words and a door. */
.auth-area .hero-account-btn { height: 30px; min-height: 0; font-size: 13px; padding: 0; }
.auth-area .hero-avatar { width: 22px; height: 22px; font-size: 12px; }
.auth-area .hero-menu { text-transform: none; letter-spacing: normal; }
/* Signed out, the two links the menu carries when signed in. */
.auth-panel-links {
  display: flex;
  gap: 16px;
  margin-top: 12px;
  padding-top: 12px;
  border-top: 1px solid var(--grey-line);
  font-family: var(--font-ui);
  font-size: 13px;
  font-weight: 600;
}
.auth-panel-links a { color: var(--black); text-decoration: none; }
.auth-panel-links a:hover { color: var(--green-text); }
.hero-account-btn {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  /* One line of the bar, so the word sits level with the site's name
     across the way. */
  min-height: var(--bar-line);
  /* No box: the bar's two ends are words, and a bordered control on one
     of them read as the only button on a page that has none. The mark
     and the word carry it; hover colours them. */
  padding: 0 4px;
  border: 0;
  background: none;
  color: var(--black);
  /* The bar's own face and size, inherited rather than restated. It used
     to be 17px of the dense UI face, which made the two ends of the bar
     two different voices: the site's name on the left at 26px display,
     and this answering it a third smaller. */
  font: inherit;
  cursor: pointer;
}
.hero-account-btn:hover { color: var(--green-text); }
.hero-account-btn:focus-visible { outline: 2px solid var(--green); outline-offset: 2px; }
.hero-avatar {
  position: relative;
  overflow: hidden;
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 30px;
  height: 30px;
  border-radius: 50%;
  background: var(--green-text);
  color: var(--white);
  font-family: var(--font-ui);
  font-size: 15px;
  font-weight: 700;
}
.hero-menu {
  position: absolute;
  top: calc(100% + 8px);
  right: 0;
  width: 248px;
  padding: 6px;
  background: var(--white);
  border-radius: 14px;
  /* A shadow instead of the 2px ink rule. This is the one element on
     the site that floats over the page rather than sitting in it, and a
     hard border drew it as another box in the layout. The rule is kept
     as a hairline so the card still has an edge on a dark background,
     where a shadow alone is invisible. */
  border: 1px solid var(--grey-line);
  box-shadow: 0 12px 28px rgba(0, 0, 0, 0.16), 0 2px 6px rgba(0, 0, 0, 0.08);
  z-index: 40;
  /* The display face, as the bar above it uses. */
  font-family: var(--font-display);
  font-size: 15px;
  /* Eased down, the way the phone menu is. Opacity and a transform, so
     it costs the compositor and not the layout. */
  opacity: 0;
  transform: translateY(-8px);
  visibility: hidden;
  transition: opacity 0.18s ease-out, transform 0.22s ease-out, visibility 0s linear 0.22s;
}
/* The UA stylesheet's display:none cannot be animated, and the script
   still uses [hidden] to say which state it is in. visibility below is
   what actually takes it out of the tab order and off a screen reader. */
.hero-menu[hidden] { display: block; }
.hero-menu:not([hidden]) {
  opacity: 1;
  transform: translateY(0);
  visibility: visible;
  transition: opacity 0.18s ease-out, transform 0.22s ease-out, visibility 0s;
}
.hero-menu-head {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 10px 12px;
  margin-bottom: 4px;
  border-bottom: 1px solid var(--grey-line);
  font-weight: 400;
}
.hero-menu-head .hero-avatar { width: 30px; height: 30px; font-size: 14px; }
/* Grown with the word beside it. At 26px text a 30px mark read as a
   button from a smaller bar that had wandered in. */
.hero-bar-right .hero-avatar { width: 36px; height: 36px; font-size: 16px; }
/* Over the letter, not instead of it: an image that fails removes
   itself and the letter is already there underneath. */
.hero-avatar-img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  border-radius: 50%;
}
.hero-menu-email { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.hero-menu a, .hero-menu button {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  padding: 10px 10px;
  border: 0;
  /* The system's own 4px, as the sign-in dialog does: a soft outer
     card holding hard-cornered rows. Only the card's own radius grew. */
  border-radius: 4px;
  background: none;
  color: var(--black);
  font: inherit;
  font-weight: 400;
  text-align: left;
  text-decoration: none;
  cursor: pointer;
}
.hero-menu a:hover, .hero-menu button:hover { background: var(--hover-bg); color: var(--black); }
.hero-menu a:focus-visible, .hero-menu button:focus-visible { outline: 2px solid var(--green); outline-offset: -2px; }
.hero-menu-out { color: var(--red) !important; margin-top: 4px; border-top: 1px solid var(--grey-line) !important; border-radius: 0 !important; }
.hero-bar a:hover { color: var(--green-text); }
.hero-bar a:focus-visible { outline: 2px solid var(--green); outline-offset: 3px; border-radius: 4px; }

.hero {
  /* The space above used to belong to the wordmark's own margin, so taking
     the wordmark out left the claim sitting against the nav row. It lives
     on the section now, where it survives whatever the first child happens
     to be. The range matches what that margin actually resolved to: 0.5em
     of a clamp(34px, 6.4vw, 82px) face, so 17px on a phone and 41px on a
     desktop. */
  padding: clamp(18px, 4vw, 44px) var(--edge) 0;
  container-type: inline-size;
  /* Centred: the claim and the two doors read as one column down the
     middle of the page rather than a left-hung stack. */
  text-align: center;
}
/* The claim carries the page, and now it is the only thing up here. The
   brand used to sit above it and the visitor met the name before the
   promise; the bar at the top of the page says who we are, which is
   enough. */
.hero-claim {
  font-family: var(--font-display);
  font-weight: 400;
  font-size: clamp(30px, 4.4vw, 4rem);
  line-height: 1.02;
  letter-spacing: -0.03em;
  margin: 0;
  /* Wide enough to keep the sentence on one line on a desktop: it is 27
     characters, against the 25 of the claim this replaced, and the old
     18ch measure broke it in two. Narrow screens still wrap it, where the
     viewport is the real constraint rather than this. */
  max-width: 30ch;
  margin-inline: auto;
}
.hero-lede {
  /* One line on a desktop: the sentence is 78 characters, which needs the
     wider measure. It still wraps on anything narrower. */
  max-width: min(100%, 760px);
  margin: 0.8em auto 0;
  font-size: clamp(16px, 1.2vw, 19px);
  line-height: 1.45;
  color: var(--black);
}
/* Stacked, not side by side. The door and the side door were a row
   under a line of prose; with the prose gone they are the only thing
   under the claim, and a column puts the API link where an afterthought
   belongs rather than level with the main action. */
.hero-actions {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 18px;
  margin-top: clamp(24px, 3vw, 38px);
}
/* The one door on the page, so it is not the 13px dense-face button the
   rest of the site shares. The display face at 16px, a taller box and
   the same 14px corner the account menu floats on. */
.hero-primary.btn {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  height: 46px;
  padding: 0 24px;
  border-radius: 14px;
  font-family: var(--font-display);
  /* 400, not the 700 every other button on the site carries. The display
     face is already the loud one, and bold on top of it read as shouting
     under a claim set in the same face at 400. Bumped a step, because a
     regular weight at 16px looked thin inside a box this size, and 19 is
     the documented step above it: the hero lede's own upper bound. */
  font-size: 19px;
  font-weight: 400;
}
.hero-primary.btn .cta-arrow { width: 16px; height: 16px; }
.cta-arrow { flex: none; }
.hero-secondary { font-size: 15px; color: var(--black); text-underline-offset: 3px; }
.hero-secondary:hover { color: var(--green-text); }
/* A line of live numbers over a row of company logos, on the page's own
   paper. It was a green band for a while and behaved like a second
   headline; as proof of scale it belongs quietly on the ground the rest of
   the page stands on. */
/* The band lives in its own section now. The block inside still steps out
   of a gutter to reach the screen edge, so the section has to have that
   gutter for it to step out of, or it steps off the page instead. */
.hero-band { padding: 0 var(--edge); }

.hero-block {
  --ticker-size: clamp(26px, 3.4vw, 3.4rem);
  --band-pad: clamp(16px, 2.6vw, 36px);
  position: relative;
  display: flex;
  flex-direction: column;
  --band-top: calc(var(--band-pad) - 0.16 * var(--ticker-size));
  gap: var(--band-top);
  padding: var(--band-top) 0 var(--band-pad);
  /* Edge to edge of the screen: the rows step out of the hero's gutter and
     run off both sides, where the fade below takes them. */
  margin-inline: calc(-1 * var(--edge));
  color: var(--black);
  overflow: hidden;
}
/* Both rows fade out at the window's edges rather than being cut off by
   it, so they read as something passing through rather than a strip that
   stops. */
.hero-ticker {
  overflow: hidden;
  /* A stencil, not a colour: a mask reads only the alpha channel, so black
     means "keep this" and transparent means "fade out". Nothing here is
     painted black. */
  -webkit-mask-image: linear-gradient(90deg, transparent, black 7%, black 93%, transparent);
  mask-image: linear-gradient(90deg, transparent, black 7%, black 93%, transparent);
}
.hero-ticker-track {
  display: flex;
  width: max-content;
  font-family: var(--font-display);
  font-weight: 400;
  font-size: var(--ticker-size);
  line-height: 1.04;
  letter-spacing: -0.03em;
  white-space: nowrap;
  will-change: transform;
}
/* Opposite directions, different speeds: the top row drifts right over a
   minute, the bottom row runs left faster. Each track holds its content
   twice, so moving exactly half its width loops without a seam. */
.hero-ticker.to-right .hero-ticker-track { animation: hero-ticker-right 60s linear infinite; }
.hero-ticker.to-left .hero-ticker-track { animation: hero-ticker-left 38s linear infinite; }
/* The logos keep their own size rather than tracking the numbers: at this
   type size half a line is too small to recognise a mark. The height is set
   on the track, not the row, so the band can pad around it without the
   rows jumping when the logos arrive. */
.hero-logos {
  padding-block: clamp(7px, 0.9vw, 12px);
  background: var(--logo-band);
  border-block: 1px solid var(--logo-band-rule);
}
.hero-logos .hero-ticker-track { height: clamp(30px, 3.4vw, 46px); }
.hero-logo {
  flex: none;
  display: grid;
  place-items: center;
  height: 100%;
  aspect-ratio: 1;
  margin-right: clamp(26px, 3vw, 54px);
}
/* Grey, the way a row of marks on a page is grey: they say who is hiring,
   not who paid. Desaturated, never faded: measured on the live row, the
   old grayscale-plus-0.55-opacity pair left half the marks under 3:1 on
   paper, and a company mark nobody can make out is not restraint. */
.hero-logo img {
  width: 100%;
  height: 100%;
  object-fit: contain;
  filter: grayscale(1) contrast(1.3);
  pointer-events: none;
  -webkit-user-drag: none;
}
/* No theme-specific filter: the band is light in both, so a mark that
   reads in one reads in the other. Inverting or brightening was only ever
   compensating for a dark ground. */
/* Once the page script moves the rows, the CSS drift stops and each row
   can be grabbed and thrown (hero.html). pan-y leaves vertical swipes to
   the page, so a phone still scrolls past the band. */
.hero-block.is-live .hero-ticker-track { animation: none; }
.hero-block.is-live .hero-ticker {
  cursor: grab;
  touch-action: pan-y;
  user-select: none;
  -webkit-user-select: none;
}
.hero-block.is-live .hero-ticker.is-dragging { cursor: grabbing; }
@keyframes hero-ticker-right { from { transform: translateX(-50%); } to { transform: translateX(0); } }
@keyframes hero-ticker-left { from { transform: translateX(0); } to { transform: translateX(-50%); } }
.hero-ticker-item { display: inline-flex; align-items: center; }
.hero-num { font-variant-numeric: tabular-nums; margin-right: 0.22em; color: var(--green-text); }
/* A drawn dot between items, sized to the type. */
.hero-ticker-item::after {
  content: "";
  width: 0.14em;
  height: 0.14em;
  margin: 0 0.42em;
  border-radius: 50%;
  background: currentColor;
}

/* The product shot: a photographed device with the board inside it,
   standing on a sunlit field. --showcase-bg is the art behind it, a
   photograph under a veil; point it at another file to change the picture.
   The device itself is one image per screen size (see .showcase-device),
   and the board in it is always the light one, whatever the reader's
   theme: this is a picture of the product, not a mirror of their
   settings. */
.hero-showcase {
  /* The photograph under a paper veil, the same one in both themes: the
     hills are a bright, sunlit thing and turning them to night to match a
     dark page made them someone else's picture. Measured against the sky
     itself, ink needs 0.51 at the heading and 0.42 at the copy to clear
     4.5:1, so the veil starts a hair above that and thins to almost
     nothing at the foot, where the laptop sits and nothing is written. */
  --showcase-veil: linear-gradient(180deg,
    rgba(242, 240, 239, 0.54) 0%,
    rgba(242, 240, 239, 0.52) 30%,
    rgba(242, 240, 239, 0.34) 62%,
    rgba(242, 240, 239, 0.16) 100%);
  --showcase-bg: var(--showcase-veil), url("/img/hills.webp");
  position: relative;
  overflow: hidden;
  /* A card, not a full-bleed band: the photograph is held away from the
     window on both sides and rounded, so it reads as a picture of the
     product rather than the page's own ground. Closer to the window than
     the text margin on desktop, so the picture gets the width. */
  margin: clamp(24px, 3vw, 48px) clamp(16px, 2.4vw, 36px) 0;
  border-radius: 24px;
  text-align: center;
  /* Desktop is one finished picture: the sky, the hills and the board on
     a screen cut off at the foot, at its own 5:2. Nothing is written on
     it, so it needs no veil. The phone layout below still builds the
     scene from the hills and the phone photograph. */
  aspect-ratio: 5 / 2;
  background: url("/img/showcase-desktop.webp") bottom center / cover no-repeat;
}
/* The API block. Dark in both themes, because the thing in it is a
   terminal and a terminal that follows the page theme stops reading as
   one. The values are literal rather than tokens for the same reason:
   these are not the page's colours, they are the block's. */
.hero-api {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.3fr);
  gap: 40px;
  align-items: center;
  margin: 0 var(--edge) clamp(40px, 6vw, 88px);
  padding: 28px 32px;
  border: 0;
  border-radius: 4px;
  background: #17181c;
  color: #ededec;
}
.hero-api-text { display: flex; flex-direction: column; gap: 12px; min-width: 0; }
.hero-api h2 {
  margin: 0;
  font: 400 36px/1 var(--font-display);
  letter-spacing: -0.03em;
  color: #ededec;
}
.hero-api p {
  margin: 0;
  max-width: 62ch;
  font: 400 15px/1.5 var(--font);
  color: #ededec;
}
/* The secondary button inverted: the ground is dark, so the outline is
   the light thing and filling it is what hover does. */
.hero-api-link {
  align-self: flex-start;
  display: inline-flex;
  align-items: center;
  height: 38px;
  padding: 0 16px;
  border: 2px solid #ededec;
  border-radius: 4px;
  background: transparent;
  color: #ededec;
  font: 600 13px var(--font-ui);
  text-decoration: none;
}
.hero-api-link:hover { background: #ededec; color: #17181c; }

.hero-api-code { position: relative; min-width: 0; }
.hero-api-code pre {
  margin: 0;
  overflow-x: auto;
  background: none;
}
.hero-api-code code {
  display: block;
  white-space: pre;
  font: 12.5px/1.7 ui-monospace, Menlo, monospace;
  color: #2fae60;
}
.api-cmd { color: #ededec; }
.api-key { color: #9a9a9a; }
/* Over the code rather than above it: the block is already the last
   thing on the page and a row of its own for one word is a row wasted. */
.hero-api-copy {
  position: absolute;
  top: 0;
  right: 0;
  padding: 2px 4px;
  border: 0;
  background: none;
  color: #9a9a9a;
  font: 400 12px var(--font-ui);
  cursor: pointer;
}
.hero-api-copy:hover { color: #ededec; }

@media (max-width: 900px) {
  .hero-api { grid-template-columns: minmax(0, 1fr); gap: 24px; padding: 24px; }
  .hero-api h2 { font-size: 30px; }
}
/* The reason the board exists, so it is the largest of the four. */
.hero-feature.feature-lead h2 { font-size: clamp(36px, 5.4vw, 5rem); }
/* Phones only: the desktop picture is the section's own background. */
.showcase-device { display: none; }
@media (max-width: 720px) {
  .hero-showcase {
    margin-inline: var(--edge);
    padding: clamp(44px, 6vw, 96px) clamp(20px, 3vw, 48px) 0;
    aspect-ratio: auto;
    background-image: var(--showcase-bg);
    background-size: auto, cover;
    background-position: center, bottom center;
  }
  /* A phone mockup with the board's own phone layout composited into its
     screen, exported as one image. Drawn frames came first and never
     stopped looking drawn. */
  .showcase-device {
    display: block;
    width: min(300px, 78%);
    height: clamp(300px, 80vw, 420px);
    margin: clamp(36px, 5vw, 72px) auto 0;
    /* The query string is bumped whenever the composite is re-exported.
       The deploy stamps only the .js and .css the HTML names, so without
       it the CDN and the reader's browser keep serving the old picture. */
    background: url("/img/device-iphone.webp?v=2") top center / 100% auto no-repeat;
  }
}

.hero-features {
  padding: clamp(56px, 9vw, 140px) var(--edge) clamp(24px, 4vw, 56px);
}
.hero-feature {
  display: grid;
  grid-template-columns: minmax(0, 1.25fr) minmax(0, 1fr);
  gap: 16px 56px;
  align-items: start;
  padding: clamp(28px, 4vw, 52px) 0;
  border-top: var(--rule) solid var(--black);
}
.hero-feature h2 {
  font-family: var(--font-display);
  font-weight: 400;
  font-size: clamp(32px, 4.6vw, 4.5rem);
  line-height: 1;
  letter-spacing: -0.03em;
  margin: 0;
  text-wrap: balance;
}
.hero-feature p {
  margin: 0.35em 0 0;
  max-width: 44ch;
  font-size: 16px;
  line-height: 1.55;
  color: var(--grey);
}

.hero-cta {
  margin: 0 var(--edge);
  padding-block: clamp(24px, 4vw, 56px) clamp(56px, 8vw, 120px);
  border-top: var(--rule) solid var(--black);
}
.hero-cta-link {
  font-family: var(--font-display);
  font-weight: 400;
  font-size: clamp(52px, 9vw, 10rem);
  line-height: 1;
  letter-spacing: -0.03em;
  color: var(--green-text);
  text-decoration: none;
}
.hero-cta-link:hover { text-decoration: underline; text-decoration-thickness: 0.04em; text-underline-offset: 0.12em; }
.hero-cta-link:focus-visible { outline: 2px solid var(--green); outline-offset: 6px; border-radius: 4px; }
.hero-foot { padding-bottom: 18px; color: var(--grey); }
.hero-foot .hero-foot-links { gap: 28px; }

/* The contact page. Two columns: what this is and how else to reach me
   on the left, the form on the right in a block of its own. The form is
   the wider column because writing is what most people came to do. */
.contact-page { background: var(--white); color: var(--black); }

.contact-shell {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.15fr);
  gap: clamp(40px, 6vw, 96px);
  align-items: start;
  padding: clamp(40px, 6vw, 80px) var(--edge) clamp(48px, 7vw, 96px);
  max-width: 1320px;
  margin: 0 auto;
}
.contact-intro { display: flex; flex-direction: column; gap: 40px; }
.contact-claim h1 {
  margin: 0 0 16px;
  font: 400 clamp(40px, 6vw, 72px)/0.95 var(--font-display);
  letter-spacing: -0.04em;
}
.contact-claim p { margin: 0; max-width: 460px; font-size: 18px; line-height: 1.45; }

/* Four rows under one rule. The heading's rule is ink and the rows'
   are the line grey, so the list reads as one block with a header
   rather than four separate things. */
.contact-direct h2 {
  margin: 0;
  padding: 0 8px 10px;
  border-bottom: 1px solid var(--black);
  font: 500 14px var(--font-display);
}
.contact-way {
  display: grid;
  grid-template-columns: 20px minmax(0, 1fr) auto;
  gap: 14px;
  align-items: center;
  padding: 14px 8px;
  border-bottom: 1px solid var(--grey-line);
  color: var(--black);
  text-decoration: none;
}
.contact-way:hover { background: var(--hover-bg); }
.contact-way:hover .contact-way-name { color: var(--green-text); }
.contact-way-icon { width: 20px; height: 20px; color: var(--black); }
/* LinkedIn's mark is a word, so it is set rather than drawn. */
.contact-way-in {
  display: grid;
  place-items: center;
  border-radius: 3px;
  background: var(--black);
  color: var(--white);
  font: 700 11px var(--font-ui);
}
.contact-way-text { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.contact-way-name { font-size: 14px; font-weight: 700; }
.contact-way-value { font-size: 14px; overflow-wrap: anywhere; }
.contact-way-go { font-size: 15px; }

.contact-panel {
  border: 1px solid var(--grey-line);
  border-radius: 14px;
  padding: clamp(20px, 3vw, 32px);
}
/* display:flex beats the browser's own [hidden] rule, so the sent panel
   has to say so itself or the form stays under it. */
.contact-form { display: flex; flex-direction: column; gap: 20px; }
.contact-form[hidden] { display: none; }

.contact-topics { margin: 0; padding: 0; border: 0; }
.contact-topics legend { padding: 0 0 8px; font: 600 14px var(--font-ui); }
.contact-topic-row { display: flex; flex-wrap: wrap; gap: 8px; }
.contact-topic {
  height: 36px;
  padding: 0 14px;
  border: 1.5px solid var(--black);
  border-radius: 4px;
  background: transparent;
  color: var(--black);
  font: 600 14px var(--font-ui);
  cursor: pointer;
}
.contact-topic:hover { background: var(--hover-bg); }
.contact-topic.on { background: var(--black); color: var(--white); }

.contact-bugnote {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 12px 14px;
  border-radius: 4px;
  background: var(--hover-bg);
  font-size: 14px;
  line-height: 1.4;
}
.contact-bugnote[hidden] { display: none; }
.contact-bugnote svg { width: 20px; height: 20px; flex: 0 0 auto; }
.contact-bugnote span { flex: 1; }
.contact-bugnote a { color: var(--black); font: 600 14px var(--font-ui); white-space: nowrap; }

.contact-pair { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; }
.contact-field { display: flex; flex-direction: column; gap: 6px; min-width: 0; }
.contact-field label {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 8px;
  font: 600 14px var(--font-ui);
}
.contact-hint { font: 400 13px var(--font); color: var(--grey); opacity: 0.75; }
.contact-hint.near { opacity: 1; color: var(--red); }
.contact-field input,
.contact-field textarea {
  width: 100%;
  box-sizing: border-box;
  border: 1.5px solid var(--black);
  border-radius: 4px;
  background: var(--white);
  color: var(--black);
  padding: 0 14px;
  font: 400 15px var(--font-ui);
}
.contact-field input { height: 44px; }
.contact-field textarea {
  height: 220px;
  padding: 12px 14px;
  line-height: 1.45;
  resize: vertical;
}
.contact-field input:focus,
.contact-field textarea:focus { outline: 2px solid var(--green); outline-offset: -2px; }
.contact-trap { position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden; }

.contact-actions { display: flex; align-items: center; gap: 16px; flex-wrap: wrap; }
.contact-send {
  height: 44px;
  padding: 0 22px;
  border: 0;
  border-radius: 4px;
  background: var(--black);
  color: var(--white);
  font: 600 15px var(--font-ui);
  cursor: pointer;
}
.contact-send:hover { background: var(--green-text); }
.contact-send:disabled { opacity: 0.6; cursor: default; }
.contact-status { margin: 0; font-size: 13px; color: var(--grey); opacity: 0.8; }
.contact-status.bad { color: var(--red); opacity: 1; }

/* Sent. The same height the form had, so the panel does not jump. */
.contact-sent {
  min-height: 520px;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: flex-start;
  gap: 14px;
}
.contact-sent[hidden] { display: none; }
.contact-sent-mark {
  width: 44px;
  height: 44px;
  display: grid;
  place-items: center;
  border-radius: 50%;
  background: var(--row-selected);
  color: var(--green-text);
  font: 600 20px var(--font-ui);
}
.contact-sent h2 { margin: 0; font: 400 34px/1 var(--font-display); letter-spacing: -0.02em; }
.contact-sent p { margin: 0; max-width: 420px; font-size: 15px; line-height: 1.45; }
.contact-again {
  margin-top: 8px;
  padding: 0;
  border: 0;
  background: none;
  color: var(--black);
  font: 400 14px var(--font);
  text-decoration: underline;
  cursor: pointer;
}
.contact-again:hover { color: var(--green-text); }

/* One column, form first: the reason to be on this page is to write. */
@media (max-width: 900px) {
  .contact-shell { grid-template-columns: 1fr; gap: 40px; }
  .contact-panel { order: -1; }
  .contact-sent { min-height: 340px; }
}
@media (max-width: 560px) {
  .contact-pair { grid-template-columns: 1fr; }
  .contact-bugnote { flex-wrap: wrap; }
  .contact-bugnote span { flex: 1 1 100%; order: 3; }
}

/* The one authored motion on the page: each feature row rises and fades in
   once, on an exponential ease-out, as it scrolls into view. Only when the
   page script added .hero-motion, so without it everything is simply there. */
.hero-motion .hero-feature > *,
.hero-motion .hero-cta > * {
  opacity: 0;
  transform: translateY(28px);
  transition: opacity 0.7s cubic-bezier(0.16, 1, 0.3, 1), transform 0.9s cubic-bezier(0.16, 1, 0.3, 1);
}
.hero-motion .hero-feature > p { transition-delay: 0.08s; }
.hero-motion .hero-feature.in > *,
.hero-motion .hero-cta.in > * { opacity: 1; transform: none; }

@media (max-width: 720px) {
  .hero-bar { font-size: 12px; flex-wrap: wrap; row-gap: 4px; }
  .hero-feature { grid-template-columns: 1fr; }
}
/* Slower, not still: a reader who asked for less motion still wants to
   know the row is a marquee and not a static list. */
@media (prefers-reduced-motion: reduce) {
  .hero-ticker.to-right .hero-ticker-track { animation-duration: 180s; }
  .hero-ticker.to-left .hero-ticker-track { animation-duration: 120s; }
}
.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; }

/* A listing's own page, /job/<id> (api/job_page.py). Built on the detail
   sheet's rules for the meta rows and badges, laid out as a document
   rather than a sheet: one column at a reading measure, the actions
   between the title and the facts, the description as real paragraphs
   and lists instead of pre-wrapped text. */
.job-page { max-width: 72ch; padding-top: 8px; }
.job-page-company {
  display: flex;
  align-items: center;
  gap: 10px;
  color: var(--grey);
  font-size: 13px;
  margin-bottom: 12px;
}
.job-page-logo {
  width: 40px;
  height: 40px;
  border-radius: 4px;
  object-fit: contain;
  background: var(--white);
  border: 1px solid var(--grey-line);
}
.job-page-title {
  font-size: 30px;
  line-height: 1.2;
  margin: 0 0 10px;
  text-wrap: balance;
  overflow-wrap: anywhere;
}
.job-page-actions { display: flex; flex-wrap: wrap; gap: 8px; margin: 18px 0 22px; }
.job-page-notice {
  border: 1px solid var(--grey-line);
  border-radius: 4px;
  padding: 10px 12px;
  margin-bottom: 18px;
  font-size: 13px;
}
.job-page .job-detail-meta-row {
  display: grid;
  grid-template-columns: 9.5rem minmax(0, 1fr);
  gap: 12px;
  padding: 7px 0;
  border-bottom: 1px solid var(--grey-line);
  font-size: 13px;
}
.job-page .job-detail-meta-row .label { font-size: 13px; color: var(--grey); }
@media (max-width: 640px) { .job-page .job-detail-meta-row { grid-template-columns: 7rem minmax(0, 1fr); } }
.job-page .job-detail-description-title { font-size: 13px; margin-top: 8px; }
.job-page-description { white-space: normal; font-size: 14px; line-height: 1.6; }
.job-page-description h3 { font-size: 14px; margin: 18px 0 6px; }
.job-page-description p { margin: 0 0 10px; }
.job-page-description ul { padding-left: 20px; margin: 6px 0 12px; }
.job-page-description li { margin: 0 0 4px; }
.topbar-nav { margin-left: auto; }
.topbar-nav a { font-size: 13px; }
.job-page-footer { padding: 24px 0 40px; font-size: 13px; color: var(--grey); }

/* /company/<domain>: the employer's open roles as a plain numbered
   list, each a link to the listing's own page. Same type steps as the
   listing page around it. */
.company-summary { font-size: 14px; color: var(--grey); margin: -6px 0 0; }
.company-jobs { padding-left: 22px; margin: 8px 0 16px; font-size: 14px; line-height: 1.5; }
.company-jobs li { margin: 0 0 8px; }
.company-job-meta { display: block; font-size: 13px; color: var(--grey); }
.company-more { font-size: 13px; }

/* ===== Board: three columns ======================================= */

/* What to narrow by on the left, the listings in the middle, the open
   one on the right. It replaced a single list under a row of dropdowns,
   where the row ran out of width around six controls and hid the rest
   behind a button, and where opening a listing threw a sheet over the
   board so reading one cost your place in the others.

   The page still scrolls as one page. The rail and the pane are sticky
   inside their columns rather than fixed panels with their own
   scrollbars, which is the same thing the Statistics column did before
   it: the list is what grows, and the footer stays reachable under it. */

/* The board fills the window and does not scroll as a page: the bar
   takes what it needs, the three columns take the rest, and each of them
   scrolls on its own. Subtracting a fixed topbar height was guesswork
   that broke every time something in the bar changed height, so the
   topbar simply takes its own height out of a flex column. */
.board-page {
  background: var(--hover-bg);
  height: 100vh;
  height: 100dvh;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}
.board-page > .topbar { flex: none; }
/* The page footer goes with it. The board is now a full-height app view
   whose three columns are asked to fill the viewport, and a footer can
   only take that height from them. Its three links live on every other
   page on the site. */
.board-page > .hero-foot,
.board-page > footer,
.board-page > .site-footer { display: none; }
.board-shell {
  padding: 0;
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
}

/* --- the top bar ------------------------------------------------- */

/* Only what applies to the whole result set. Sticky under the topbar so
   the search box and the count stay put while the list runs past them:
   the count is the one number that says whether a filter did anything,
   and it was scrolling away exactly when someone changed one. */
.board-bar {
  flex: none;
  z-index: 9; /* under .topbar's 10 */
  /* The same ground as the boxes below, so the two do not meet in a
     seam across the page. */
  background: var(--hover-bg);
  /* No bottom rule: the boxes below start their own edge. */
  padding: 16px var(--gutter) 14px;
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.board-bar-row {
  display: flex;
  align-items: center;
  gap: 10px;
  min-width: 0;
}

.board-bar-row > * { min-width: 0; } /* form controls have a real intrinsic width too */

.board-bar #f-date-posted,
.board-bar #f-sort { flex: 0 0 150px; }

.board-bar select {
  height: 38px;
  padding: 0 10px;
  border: 1.5px solid var(--black);
  border-radius: 4px;
  background: var(--white);
  color: var(--black);
  font: 400 13px var(--font-ui);
}
.board-bar select:focus { outline: 2px solid var(--green); outline-offset: -2px; }

/* The same chevron the rail's carets use, so the two arrows sitting a
   few pixels apart match. */
.board-bar select {
  appearance: none;
  padding-right: 28px;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 10 6'%3E%3Cpath d='M1 1l4 4 4-4' fill='none' stroke='%2340513b' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 10px center;
  background-size: 10px 6px;
}
:root[data-theme="dark"] .board-bar select {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 10 6'%3E%3Cpath d='M1 1l4 4 4-4' fill='none' stroke='%23ededec' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
}

.board-bar-row-2 { flex-wrap: wrap; row-gap: 8px; }
.board-bar .view-switch { margin: 0; flex: 0 0 auto; }
.board-bar #f-reset { flex: 0 0 auto; height: 30px; padding: 0 12px; color: var(--red); }
.board-bar #f-reset[hidden] { display: none; }

/* Pushed to the far end, and the only thing on the row that is allowed
   to sit on its own. */
.board-bar .result-count {
  margin: 0 0 0 auto;
  flex: 0 0 auto;
  white-space: nowrap;
}

.rail-toggle { display: none; } /* a column above 800px; see the sheet rules below */

/* --- active filter chips ----------------------------------------- */

.active-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  min-width: 0;
}

/* Outlined, not filled. The chip is a thing to remove, and fifty of the
   board's own signal green across one row would say "these matter most"
   about the reader's own filters. */
.active-chips .chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  margin: 0;
  height: 30px;
  padding: 0 9px;
  /* Ink on nothing: a hairline and text in the theme's own ink, black
     on light and white on dark, no fill and no green. */
  border: 1px solid var(--black);
  border-radius: 4px;
  background: none;
  color: var(--black);
  font: 400 13px var(--font-ui);
  text-transform: none;
  letter-spacing: normal;
  cursor: pointer;
}
.active-chips .chip:hover .chip-label { text-decoration: underline; text-underline-offset: 3px; }
.active-chips .chip:focus-visible { outline: 2px solid var(--green); outline-offset: 1px; }
.active-chips .chip-label { max-width: 20ch; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.chip-x { flex: 0 0 auto; opacity: 0.6; }
.active-chips .chip:hover .chip-x { opacity: 1; }

/* --- the grid ----------------------------------------------------- */

/* Three columns that fill what is left of the viewport, each scrolling
   on its own. The page itself does not scroll here: the board is a tool
   you work inside, and losing the filters because you read to the
   bottom of the list was the thing the old single scroll kept doing. */
/* Two boxes on a tinted ground. The tint is what separates them, which
   is why the filter column gave up the tint it used to carry: two
   tinted surfaces touching read as one, and the box edge had nothing
   left to do. The same gutter as the bar above, so the boxes line up
   with the search box. */
.board-grid {
  display: grid;
  /* The pane grows with the window and the list gives the width up.
     420px fixed was fine at 1280 and mean at 1920: a description column
     narrower than a phone on a screen five times as wide, with the list
     beside it spending the difference on ellipsised titles. 34vw is 490
     at 1440 and 600 at 1770 and beyond. The floor is the old fixed
     width, so from 1100 to 1235 nothing moves: that end was already as
     tight as the list can stand, and this takes width only where there
     is width to take. */
  grid-template-columns: minmax(0, 1fr) clamp(420px, 34vw, 600px);
  gap: 10px;
  align-items: stretch;
  flex: 1;
  min-height: 0;
  padding: 0 var(--gutter) 28px;
  background: var(--hover-bg);
}

.board-box {
  min-height: 0;
  background: var(--white);
  border: 1px solid var(--panel-line);
  border-radius: 10px;
  /* So each column's header row and its scroller are cut to the corner
     rather than squaring it off. */
  overflow: hidden;
}
/* Filters and listings share a box: one border around the pair and a
   hairline between them, no gap. */
.board-box-main {
  display: grid;
  grid-template-columns: 260px minmax(0, 1fr);
  align-items: stretch;
}
.board-box-main > * { min-width: 0; }
.board-grid > * { min-width: 0; }

/* One height for every head, so the two that remain read as the top of
   the board rather than as lids of their own. The listings column gave
   its head up above 1100px, where the panel beside it says the same
   number: the rows start at the top of the box instead and the first one
   sits level with the two titles. The heads stay put; only the bodies
   under them scroll. */
.col-head {
  flex: none;
  height: 64px;
  box-sizing: border-box;
  padding: 0 20px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  /* A light line inside a darker box edge. Ink here made two heavy
     lines a few pixels apart. */
  border-bottom: 1px solid var(--grey-line);
  background: var(--white);
}
.col-head-title { font: 500 17px var(--font-display); color: var(--black); }
.col-head-title b { font-weight: 500; font-variant-numeric: tabular-nums; }
.col-head-sub { font: 400 12px var(--font); color: var(--black); opacity: 0.75; }
.list-head-text { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.list-head-sort {
  flex: none;
  border: 0;
  background: none;
  padding: 0;
  font: 500 13px var(--font-display);
  color: var(--black);
  cursor: pointer;
}
.list-head-sort::after { content: " " attr(data-dir); }
.list-head-sort:hover { color: var(--green-text); }

.rail-reset {
  border: 0;
  background: none;
  padding: 0;
  font: 500 13px var(--font-display);
  color: var(--red);
  cursor: pointer;
}
.rail-reset[hidden] { display: none; }
/* Only the sheet needs a way out; above 800px the rail is a column. */
.rail-close { display: none; }

/* --- the filter rail ---------------------------------------------- */

.filter-rail {
  display: flex;
  flex-direction: column;
  min-height: 0;
  background: var(--white);
  /* The divider inside box 1, and the only column border left: the
     other two were replaced by the box edges. */
  border-right: 1px solid var(--grey-line);
}

.rail-groups {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
  scrollbar-width: thin;
  scrollbar-color: var(--grey-line) transparent;
  padding: 20px 20px 28px;
  display: flex;
  flex-direction: column;
  gap: 28px;
}
/* A hairline between groups rather than around each one: the gap does
   most of the separating and a box around every group turned a quiet
   column into six panels. */
.rail-group { display: flex; flex-direction: column; gap: 8px; }
.rail-group + .rail-group { border-top: 1px solid var(--grey-line); padding-top: 28px; }

/* The accordion. Four groups, a hairline between them, each a header
   button over the existing option rows. */
.rail-groups:has(.rail-acc) { gap: 0; padding-top: 8px; }
.rail-acc + .rail-acc { border-top: 1px solid var(--grey-line); }
.rail-acc-head {
  display: grid;
  grid-template-columns: 18px 1fr 14px;
  align-items: center;
  column-gap: 12px;
  width: 100%;
  min-height: 56px;
  padding: 8px;
  border: 0;
  border-radius: 4px;
  background: none;
  color: var(--black);
  text-align: left;
  font: inherit;
  cursor: pointer;
}
.rail-acc-head:hover { background: var(--row-hover); }
.rail-acc-head:focus-visible { outline: 2px solid var(--green); outline-offset: -2px; }
.rail-acc-icon { color: var(--black); }
.rail-acc-text { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.rail-acc-name { font: 500 15px var(--font-display); }
/* The one green in the sidebar: what a closed group is filtering by. */
.rail-acc-sum {
  font: 400 12px var(--font);
  color: var(--green-text);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.rail-acc-chev { color: var(--black); transition: transform 150ms cubic-bezier(0.2, 0, 0, 1); }
.rail-acc.open .rail-acc-chev { transform: rotate(180deg); }
@media (prefers-reduced-motion: reduce) { .rail-acc-chev { transition: none; } }
.rail-acc-body { display: flex; flex-direction: column; gap: 18px; padding: 2px 8px 14px 38px; }
.rail-acc-body[hidden] { display: none; }
/* Inside a group the old group heads become sub-section labels, and
   the hairline and spacing between groups belong to the accordion. */
.rail-acc .rail-group + .rail-group { border-top: 0; padding-top: 0; }
.rail-acc .rail-title { font: 400 12px var(--font); color: var(--grey); }
.rail-acc.single .rail-title { display: none; }

.rail-title {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 8px;
  margin: 0;
  font: 700 14px var(--font);
  color: var(--black);
}

.rail-body { display: flex; flex-direction: column; gap: 2px; }

.rail-search {
  height: 30px;
  margin-bottom: 4px;
  padding: 0 8px 0 26px;
  border: 1px solid var(--grey-line);
  border-radius: 4px;
  background: var(--white) url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 14 14'%3E%3Ccircle cx='6' cy='6' r='4.2' fill='none' stroke='%2340513b' stroke-width='1.5'/%3E%3Cpath d='M9.2 9.2L12.5 12.5' stroke='%2340513b' stroke-width='1.5' stroke-linecap='round'/%3E%3C/svg%3E") no-repeat 8px center;
  background-size: 12px 12px;
  color: var(--black);
  font: 400 13px var(--font-ui);
}
:root[data-theme="dark"] .rail-search {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 14 14'%3E%3Ccircle cx='6' cy='6' r='4.2' fill='none' stroke='%23ededec' stroke-width='1.5'/%3E%3Cpath d='M9.2 9.2L12.5 12.5' stroke='%23ededec' stroke-width='1.5' stroke-linecap='round'/%3E%3C/svg%3E");
}
.rail-search:focus { outline: 2px solid var(--green); outline-offset: -2px; }

/* An option row is the whole line, not the 14px box on it: a checkbox
   is a small target and the label beside it is the thing being read. */
.rail-option {
  display: flex;
  align-items: center;
  gap: 10px;
  min-height: 30px;
  /* The negative margin is what makes the whole row clickable and the
     checked fill run the width of the column, rather than a pill
     floating inside it. */
  padding: 0 8px;
  margin: 0 -8px;
  border-radius: 4px;
  cursor: pointer;
  font: 400 14px var(--font-ui);
  color: var(--black);
}
.rail-option:hover { background: var(--row-hover); }
.rail-option.on { background: var(--row-selected); }
.rail-option input { position: absolute; opacity: 0; width: 0; height: 0; }
.rail-option input:focus-visible + .rail-box { outline: 2px solid var(--green); outline-offset: 1px; }

.rail-box {
  flex: 0 0 auto;
  width: 16px;
  height: 16px;
  border: 1.5px solid var(--black);
  border-radius: 4px;
  background: var(--white);
  color: var(--white);
  display: grid;
  place-items: center;
}
.rail-option.on .rail-box { background: var(--black); border-color: var(--black); }
.rail-tick { width: 12px; height: 12px; opacity: 0; }
.rail-option.on .rail-tick { opacity: 1; }

.rail-option-label {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* Ticked reads a step heavier, so the picked options are findable in a
   list without reading every box. */
.rail-option.on .rail-option-label { font-weight: 700; }

/* Tabular, and its own element, so a long name is what truncates and
   never the number. */
.rail-count {
  flex: 0 0 auto;
  font-variant-numeric: tabular-nums;
  font-size: 12px;
  color: var(--grey);
}

.rail-more {
  align-self: flex-start;
  margin-top: 2px;
  padding: 2px 4px;
  border: 0;
  background: none;
  color: var(--green-text);
  font: 400 12.5px var(--font-ui);
  text-decoration: underline;
  text-underline-offset: 2px;
  cursor: pointer;
}
.rail-more:hover { color: var(--black); }
.rail-more:focus-visible { outline: 2px solid var(--green); outline-offset: 1px; }

.rail-empty { padding: 2px 4px; font: 400 12.5px var(--font-ui); color: var(--grey); }

/* A group whose counts are still being fetched, at the height of the
   rows that are coming, so the rail does not resize when they land. */
.rail-bone { pointer-events: none; }
.rail-bone .sk-line { width: 100%; height: 11px; border-radius: 2px; }
.rail-group[aria-busy="true"] .rail-bone:nth-child(2) .sk-line { width: 82%; }
.rail-group[aria-busy="true"] .rail-bone:nth-child(3) .sk-line { width: 64%; }
.rail-group[aria-busy="true"] .rail-bone:nth-child(4) .sk-line { width: 73%; }

/* --- location: countries, cities under them ----------------------- */

.rail-country + .rail-country { margin-top: 6px; }
.rail-country-row { display: flex; align-items: center; gap: 2px; }
/* No negative margin on a tree row. A plain option pulls itself 8px out
   on each side so its checked fill spans the column; here that dragged
   the country's checkbox back over the caret beside it, and moved every
   city 8px off the connector lines drawn from its own left edge. */
.rail-country-row .rail-option,
.rail-option.rail-city { margin-left: 0; margin-right: 0; }
.rail-country-row .rail-option { flex: 1 1 auto; min-width: 0; }

.rail-caret {
  flex: 0 0 auto;
  width: 18px;
  height: 22px;
  display: grid;
  place-items: center;
  border: 0;
  background: none;
  color: var(--grey);
  cursor: pointer;
  transition: transform 0.15s ease;
}
.rail-caret svg { width: 9px; height: 6px; }
.rail-country:not(.open) .rail-caret { transform: rotate(-90deg); }
.rail-caret:hover { color: var(--black); }
.rail-caret:focus-visible { outline: 2px solid var(--green); outline-offset: -2px; border-radius: 3px; }

/* The connector lines. A vertical run down the left with a short tick
   into each row, and the last row ends the run at its own middle, so
   the group closes itself instead of trailing off. Drawn rather than
   indented alone, because four cities indented under a country read as
   four more countries at a glance. */
.rail-cities { margin-left: 25px; position: relative; }
.rail-city { position: relative; padding-left: 16px; }
.rail-city::before,
.rail-city::after {
  content: "";
  position: absolute;
  left: 0;
  background: var(--grey-line);
}
.rail-city::before { top: 0; bottom: 0; width: 1px; }
.rail-city.last::before { bottom: 50%; }
.rail-city::after { top: 50%; width: 9px; height: 1px; }

.rail-country-note {
  margin: 2px 0 0 25px;
  font: 400 12px var(--font-ui);
  color: var(--grey);
}
.rail-more-city { margin-left: 25px; }

/* --- salary range ------------------------------------------------- */

/* Two native range inputs stacked over one track. Native because a
   hand-rolled pair of handles is a keyboard trap and a screen-reader
   silence, and these arrive tabbable, arrow-key steppable and labelled
   for free. Only the thumbs take pointer events, so the input lying
   over the other one cannot swallow its drag. */
.rail-range {
  position: relative;
  height: 34px;
  margin: 6px 2px 0;
}
.rail-range-track {
  position: absolute;
  left: 0;
  right: 0;
  top: 10px;
  height: 4px;
  border-radius: 4px;
  background: var(--grey-line);
}
.rail-range-fill {
  position: absolute;
  top: 0;
  bottom: 0;
  background: var(--green);
  border-radius: 4px;
}
.rail-range-input {
  position: absolute;
  left: 0;
  top: 4px;
  width: 100%;
  height: 16px;
  margin: 0;
  appearance: none;
  background: none;
  pointer-events: none;
}
.rail-range-input:focus { outline: none; }
.rail-range-input::-webkit-slider-thumb {
  appearance: none;
  width: 14px;
  height: 14px;
  border: 1.5px solid var(--green);
  border-radius: 50%;
  background: var(--white);
  pointer-events: auto;
  cursor: grab;
}
.rail-range-input::-moz-range-thumb {
  width: 14px;
  height: 14px;
  border: 1.5px solid var(--green);
  border-radius: 50%;
  background: var(--white);
  pointer-events: auto;
  cursor: grab;
}
.rail-range-input:focus-visible::-webkit-slider-thumb { outline: 2px solid var(--green); outline-offset: 2px; }
.rail-range-input:focus-visible::-moz-range-thumb { outline: 2px solid var(--green); outline-offset: 2px; }

.rail-range-ends {
  position: absolute;
  left: 0;
  right: 0;
  top: 20px;
  display: flex;
  justify-content: space-between;
  font: 400 12px var(--font-ui);
  font-variant-numeric: tabular-nums;
  color: var(--grey);
}

/* --- the list ----------------------------------------------------- */

/* The list column: a head that stays and a body that scrolls. The
   padding moved off the column and onto the rows, so a row's hover and
   selection fills span the whole column width. */
.board-list {
  display: flex;
  flex-direction: column;
  min-width: 0;
  min-height: 0;
}
.board-list {
  overflow-y: auto;
  overscroll-behavior: contain;
  scrollbar-width: thin;
  scrollbar-color: var(--grey-line) transparent;
}
/* 4px top and bottom on every scroll area, so the scrollbar's thumb
   clears the box's rounded corners instead of being clipped by them. */
.board-list > table.jobs { margin: 4px 0; }
.rail-groups { padding-top: 24px; padding-bottom: 32px; }
/* Sticky rather than a sibling of a scroller: the list column is one
   scroll and the head rides at its top, which keeps the count and the
   sort in reach fifty rows down. */
.board-list .list-head { position: sticky; top: 0; z-index: 2; }
/* Only where the overview panel is on screen to say it instead. Above
   1100px that panel is always beside the list, and once a listing is
   open the pane head carries "N of TOTAL", so the count was written
   twice a column apart. Below 1100px the panel is a drawer and this head
   is the only place the number lives, so it stays. */
@media (min-width: 1101px) {
  .board-list .list-head { display: none; }
}

table.jobs tbody tr[data-id],
table.jobs tbody tr.skeleton-row {
  display: grid;
  grid-template-columns: 56px minmax(0, 1fr) auto;
  gap: 14px;
  align-items: start;
}
/* Each cell names its column rather than trusting auto-placement. The
   star is display:none at this width and a hidden grid item does not
   advance the cursor, so the three that remain would land correctly by
   luck; naming them is what stops the next change to this grid quietly
   shifting all of them one column left, which is exactly what happened
   to the phone row. */
.logo-cell { grid-column: 1; }
.main-cell { grid-column: 2; }
.age-cell { grid-column: 3; }

table.jobs tbody td { padding: 0; vertical-align: top; }
/* One height for every row, so fifty of them make a rhythm a reader can
   scan rather than a ragged column that reflows as titles wrap. Each of
   the three lines gets exactly one line: 76px of content inside 28px of
   padding is title (19) + meta (19) + chips (22) + two 8px gaps. */
table.jobs tbody tr[data-id],
table.jobs tbody tr.skeleton-row {
  padding: 16px 20px;
  position: relative;
  height: 124px;
  align-content: start;
  cursor: pointer;
  outline-offset: -3px;
}
table.jobs tbody tr[data-id]:hover { background: var(--row-hover); }
table.jobs tbody tr[data-id]:focus-visible { outline: 2px solid var(--green); }
/* Nothing may push the row taller, which is what a wrapped title, a
   second meta line or a fourth chip row would each do. */
.main-cell { overflow: hidden; }

table.jobs tbody tr.selected { background: var(--row-open); }
/* An inset bar rather than a border, so a selected row is the same
   height as every other one and the list does not shuffle as the
   selection moves. */
table.jobs tbody tr.selected::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0;
  bottom: 0;
  width: 3px;
  /* Ink, not green: black on paper, white on the dark ground. Green
     here said "this one is good"; the bar only means "this one is open". */
  background: var(--black);
}

/* Not on the desktop row. It was an 18px column of outlines down the
   left edge of every listing, ahead of the logo, and the pane that opens
   on a click carries Save as a labelled button. The phone keeps it: there
   the row is the only place it can live, and it sits on the right. */
.star-cell { display: none; justify-content: center; padding-top: 2px; }
.star-btn {
  width: 18px;
  height: 18px;
  display: grid;
  place-items: center;
  border: 0;
  background: none;
  color: var(--grey);
  opacity: 0.55;
  cursor: pointer;
}
.star-btn:hover { opacity: 1; color: var(--green-text); }
.star-btn.on { opacity: 1; color: var(--green-text); }
.star-btn.on .star-mark { fill: currentColor; }
.star-mark { width: 16px; height: 16px; }
.star-btn:focus-visible { outline: 2px solid var(--green); outline-offset: 1px; border-radius: 3px; }

/* 56px, the same mark the open listing's pane carries, so the logo does
   not shrink between the row and the thing the row opens. */
.logo-cell .company-logo.listing { width: 56px; height: 56px; border-radius: 4px; }

/* 8px, and the three lines below are 19 + 19 + 22, which is the row's
   104 less its 28 of padding exactly. The gap used to be 5 while
   .job-meta still carried a 3px top margin of its own from the old
   layout, so the title sat 8px above the company and the company 5px
   above the chips: one row, two rhythms. */
.main-cell { display: flex; flex-direction: column; gap: 6px; min-width: 0; }

/* Fixed at one text line. The seniority badge is 25px tall against the
   title's 19, and as a baseline-aligned flex item it dragged the whole
   line 6px taller, so a row with a badge spaced differently from a row
   without one. It is centred on the line and overhangs it by 3px each
   side, which the gap absorbs. */
.job-card-title {
  display: flex;
  align-items: center;
  flex-wrap: nowrap;
  gap: 6px;
  min-width: 0;
  height: 19px;
  font: 600 15px var(--font);
  line-height: 19px;
  color: var(--black);
}
/* The title is what gives way. min-width:0 lets it shrink below its
   content, which a flex item will not do by default, and the badge
   beside it keeps its size instead of being the thing that disappears:
   "Senior" is three characters of information and a truncated title
   still reads. The full string is in the title attribute (see
   renderJobRows). */
.job-title-text {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* Exactly the height of the line it sits on: 15 of line, 1 of padding
   and 1 of border, each doubled, is 19. At the badge's usual 25 it stood
   3px proud of the title at each end, and .main-cell clips, so the top
   edge of every seniority badge on the board was shaved off. The row has
   no slack to give it (the chip line already ends flush with the cell),
   so the badge is what gives. */
.job-card-title .badge {
  flex: 0 0 auto;
  line-height: 15px;
  padding: 1px 6px;
}
tr:hover .job-title-text { color: var(--green-text); }

.job-meta {
  margin: 0; /* .main-cell's gap owns the spacing now */
  font: 400 13px var(--font);
  line-height: 19px;
  color: var(--grey);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* The employer as a link to its own page. Underlined only on hover: a
   permanent rule under one word in the middle of the meta line would
   compete with the title above it for the first thing the eye lands on. */
a.job-company { color: var(--black); text-decoration: none; }
a.job-company:hover { color: var(--green-text); text-decoration: underline; }
/* Who is hiring reads at full ink, where the job is reads muted under
   it, so a column of fifty rows sorts itself by employer first. */
.job-meta { color: var(--black); }
.job-where {
  font: 400 13px var(--font);
  line-height: 18px;
  color: var(--black);
  opacity: 0.75;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* Three things only a phone shows. The column, the detail pane and the
   chip line carry them at every other width. */
.meta-age,
.job-links,
.job-salary-line { display: none; }
.meta-age-value.fresh { color: var(--black); } /* the phone's age, same rule */

/* One row, and it is the skills that fall off the end of it. The salary
   chip is first in the markup and never shrinks, so a row that only has
   space for one thing shows the number rather than a skill tag. */
.job-chips {
  display: flex;
  flex-wrap: nowrap;
  gap: 6px;
  overflow: hidden;
  height: 22px;
}
.job-chips > * { flex: 0 0 auto; }
/* The strip clips, so the chip that does not fit was being sliced down
   the middle of a word. It fades out instead. The gradient only covers
   the last 24px, which on a row whose chips end before that is empty
   background and shows no change at all. */
.job-chips {
  -webkit-mask-image: linear-gradient(90deg, #000 calc(100% - 24px), transparent);
  mask-image: linear-gradient(90deg, #000 calc(100% - 24px), transparent);
}

/* Best matches. A matched skill is the same chip as any other, with a
   tick in front, so the eye reads one vocabulary down the column rather
   than learning a second one for this view. */
.job-chip.skill.matched {
  gap: 5px;
  border-color: var(--green-text);
  color: var(--green-text);
}
.match-tick { flex: 0 0 auto; }

/* The only thing on the line allowed to shrink. The chips are the
   specifics and keep their size; this is the summary, and a summary
   that ends in an ellipsis still says what it is. The whole of it,
   untruncated, is in its own title attribute. */
.job-match-summary {
  flex: 0 1 auto;
  min-width: 0;
  align-self: center;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font: 400 12px var(--font);
  color: var(--grey);
}
.job-chip {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  height: 22px;
  padding: 0 7px;
  border: 1px solid var(--grey-line);
  border-radius: 4px;
  background: none;
  color: var(--grey);
  font: 400 12px var(--font-ui);
  white-space: nowrap;
}
.job-chip.skill { cursor: pointer; }
.job-chip.skill:hover { border-color: var(--black); color: var(--black); }
.job-chip.skill:focus-visible { outline: 2px solid var(--green); outline-offset: 1px; }
/* The salary reads as text in the theme's ink, black on light and white
   on dark, with no outline and no fill: weight is what sets it apart
   from the skills beside it. No left padding, since without a border it
   would read as an indent. 4px to the right on top of the chip line's
   6px gap, so it stands off the skills. */
.job-chip.salary {
  border-color: transparent;
  padding-left: 0;
  background: none;
  color: var(--black);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  margin-right: 4px;
}
.job-chip.salary .salary-range { color: var(--black); font-variant-numeric: tabular-nums; }
/* "Est." is the qualifier, not part of the figure, so it stays in the
   row's own text colour at the row's own weight. */
.job-chip.salary .salary-est-label { color: var(--black); font-weight: 400; }
.salary-est-label { color: var(--grey); }

.age-cell {
  font: 400 12.5px var(--font);
  font-variant-numeric: tabular-nums;
  color: var(--black);
  text-align: right;
  white-space: nowrap;
  padding-top: 1px;
}
/* Ink, not green. A fresh row still reads heavier than the rest, but
   the colour belongs to the salary now. */
.age-cell.fresh { color: var(--black); font-weight: 600; }

/* --- the detail pane ---------------------------------------------- */

.job-detail {
  display: flex;
  flex-direction: column;
  min-height: 0;
  background: var(--white);
}
.pane-body {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
  scrollbar-width: thin;
  scrollbar-color: var(--grey-line) transparent;
  padding: 24px 20px 32px;
  scroll-padding-block: 4px;
  display: flex;
  flex-direction: column;
  gap: 18px;
}
.pane-close {
  width: 32px;
  height: 32px;
  margin-right: -8px;
  display: grid;
  place-items: center;
  border: 0;
  border-radius: 4px;
  background: none;
  color: var(--black);
  font-size: 15px;
  cursor: pointer;
}
.pane-close:hover { background: var(--hover-bg); }
.job-detail[hidden] { display: none; }

.job-detail-close {
  position: absolute;
  top: 18px;
  right: 18px;
  width: 28px;
  height: 28px;
  display: grid;
  place-items: center;
  border: 1.5px solid var(--grey-line);
  border-radius: 4px;
  background: var(--white);
  color: var(--grey);
  cursor: pointer;
}
.job-detail-close:hover { background: var(--black); border-color: var(--black); color: var(--white); }
.job-detail-close:focus-visible { outline: 2px solid var(--green); outline-offset: 1px; }

.job-detail-company { display: flex; align-items: center; gap: 12px; padding-right: 36px; }
.job-detail-company .company-logo.detail { width: 56px; height: 56px; border-radius: 4px; flex: 0 0 auto; }
.job-detail-company-text { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.job-detail-company-name { font: 700 14px var(--font); color: var(--black); }
.job-detail-company-place { font: 400 13px var(--font); color: var(--grey); opacity: 0.9; }

.job-detail-title {
  margin: 0;
  font: 400 24px/1.15 var(--font-display);
  letter-spacing: -0.01em;
  color: var(--black);
  text-wrap: balance;
}

.job-detail-actions { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.job-detail-star {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  height: 38px;
  padding: 0 14px;
  border: 1.5px solid var(--black);
  border-radius: 4px;
  background: var(--white);
  color: var(--black);
  font: 400 13px var(--font-ui);
  cursor: pointer;
}
.job-detail-star:hover { border-color: var(--green-text); color: var(--green-text); }
.job-detail-star.on { color: var(--green-text); border-color: var(--green-text); }
.job-detail-star.on .star-mark { fill: currentColor; }
.job-detail-permalink {
  border: 0;
  background: none;
  padding: 0;
  font: 400 13px var(--font-ui);
  cursor: pointer;
}

/* Four facts in a square, divided by hairlines drawn as gaps in the
   grid's own ground rather than as borders on each cell, so the two
   inner lines meet instead of doubling up where they cross. */
.job-detail-facts {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 1px;
  background: var(--grey-line);
  border: 1px solid var(--grey-line);
  border-radius: 4px;
  overflow: hidden;
}
.fact { background: var(--white); padding: 12px 14px; display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.fact-label { font: 700 11.5px var(--font); color: var(--grey); opacity: 0.85; }
.fact-value { font: 400 13.5px var(--font); color: var(--black); overflow-wrap: anywhere; }
.fact-value .salary-range { color: var(--green-text); font-variant-numeric: tabular-nums; }
/* An absence, said as an absence rather than left blank. Quieter than
   a fact by a step of weight, not by an opacity that took it under the
   4.5:1 body text needs (measured at 3.60:1 before this). */
.fact-absent { color: var(--grey); }

.job-detail-section-title { font: 700 12px var(--font); color: var(--grey); }
.job-detail-description { font: 400 13.5px/1.6 var(--font); color: var(--black); white-space: pre-wrap; }
.job-detail-description.empty { color: var(--grey); }
.skeleton-desc { display: flex; flex-direction: column; gap: 9px; }
.skeleton-desc .sk-line { height: 11px; width: 100%; border-radius: 2px; }
.skeleton-desc .sk-line.short { width: 62%; }

/* --- the empty state ---------------------------------------------- */

/* What the filters add up to, in the space the open listing would take.
   This is where the Statistics column went. */
.detail-empty { display: flex; flex-direction: column; gap: 24px; min-height: 100%; }

/* Value first, label under it, in a 2x2 that shares its rules. A tile
   whose number we do not have is left out rather than shown holding a
   dash, so the grid can be 3 or 2 wide as easily as 4. */
/* Four tiles, each its own box with a gap between, rather than one
   box ruled into quarters. */
.ov-tiles {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 10px;
}
.ov-tile {
  padding: 14px 16px;
  display: flex;
  flex-direction: column;
  gap: 4px;
  border: 1px solid var(--grey-line);
  border-radius: 4px;
}
.ov-value {
  font: 400 28px/1 var(--font-display);
  color: var(--black);
  font-variant-numeric: tabular-nums;
}
.ov-value.ov-new { color: var(--green-text); }
.ov-value .sk-line { display: block; width: 70%; height: 22px; border-radius: 3px; }
.ov-label { font: 400 13px var(--font); color: var(--black); }

.ov-block { display: flex; flex-direction: column; }
.ov-block-title { font: 500 14px var(--font-display); color: var(--black); padding-bottom: 8px; }
.ov-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 9px 0;
  border: 0;
  border-top: 1px solid var(--grey-line);
  background: none;
  color: var(--black);
  font: 400 14px var(--font);
  text-align: left;
  cursor: pointer;
}
.ov-row.static { cursor: default; }
.ov-row:not(.static):hover { color: var(--green-text); }
.ov-row:focus-visible { outline: 2px solid var(--green); outline-offset: -2px; }
.ov-row > span:first-child { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ov-row-n { flex: 0 0 auto; font-variant-numeric: tabular-nums; opacity: 0.75; }
/* The company rows carry a 32px mark, the same tile as the listing
   rows at a smaller size, so the row's padding comes down to keep the
   list from doubling in height. */
.ov-row-co { padding: 6px 0; }
.ov-co { display: flex; align-items: center; gap: 10px; min-width: 0; }
.ov-co-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ov-row .company-logo.ov-logo { width: 32px; height: 32px; margin: 0; border-radius: 4px; flex: 0 0 auto; }
.ov-dot-row { display: flex; align-items: center; gap: 7px; flex: 0 0 auto; }
.ov-dot { width: 7px; height: 7px; border-radius: 50%; background: var(--aws-operational); }
.ov-dot.stale { background: var(--aws-degraded); }

.ov-hint { margin-top: auto; font: 400 13px var(--font); color: var(--black); opacity: 0.7; }

/* Recovered with the three-column rewrite: the geolocation prompt, the
   Apply button and the description's own headings all sat in the block
   that held the old board layout, and none of the three is about
   layout. */

/* The country prompt, shown once before the board's first load. Same
   scrim and the same flat surface as the job sheet, so the one overlay
   language this app has covers both rather than growing a second.
   z-index above the sheet's 20 because it is answered before anything
   else can open. */
.geo-scrim {
  position: fixed;
  inset: 0;
  z-index: 29;
  background: var(--scrim);
}
.geo-prompt {
  position: fixed;
  z-index: 30;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: min(420px, calc(100vw - 2 * var(--edge)));
  background: var(--white);
  color: var(--black);
  border: var(--rule) solid var(--black);
  border-radius: 4px;
  padding: 20px;
}
.geo-prompt h2 {
  font-family: var(--font-display);
  font-size: 24px;
  line-height: 1.1;
  margin: 0 0 8px;
}
.geo-prompt p { margin: 0 0 16px; font-size: 14px; line-height: 1.5; }
/* What was actually read, said plainly. The country header is the only
   thing this asks Cloudflare for, and naming it is the difference
   between a helpful guess and a site that seems to know too much. */
.geo-evidence {
  border: 1px solid var(--grey-line);
  border-radius: 4px;
  padding: 10px 12px;
  margin: 0 0 16px;
  font-family: "Courier New", monospace;
  font-size: 11.5px;
  line-height: 1.6;
  color: var(--grey);
}
.geo-evidence b { color: var(--black); font-weight: 700; }
.geo-actions { display: flex; align-items: center; gap: 12px; }
/* Neither button shouts. .btn is 700 everywhere else, which on a dialog
   this small made the accept button the loudest thing on the screen,
   ahead of the question it answers. The fill still carries the emphasis. */
.geo-prompt .btn { font-weight: 400; }
/* Skip is the quiet one. .btn-quiet is scoped to .alert-create, so it
   does not reach here, and without this both buttons render as the
   primary and Skip reads as the thing to press. Widening that selector
   instead would tie the alert form's styling to this prompt. */
.geo-prompt .btn-quiet {
  background: none;
  border-color: transparent;
  color: var(--grey);
  font-weight: 400;
}
.geo-prompt .btn-quiet:hover { color: var(--black); background: var(--hover-bg); }
.job-detail-apply {
  font-family: var(--font-ui);
  display: inline-flex;
  align-items: center;
  height: 38px;
  padding: 0 16px;
  background: var(--green-text);
  color: var(--white);
  border: var(--rule) solid var(--green-text);
  border-radius: 4px;
  font-size: 13px;
  font-weight: 700;
  text-decoration: none;
  white-space: nowrap;
}
.job-detail-apply:hover { background: var(--black); border-color: var(--black); }
/* The drawn arrow on both Apply buttons (see EXTERNAL_ARROW_SVG in
   app.js). Both buttons are already inline-flex and centred, so the gap
   is all it needs. */
.job-detail-apply { gap: 5px; }
.external-arrow { flex: none; display: block; }
.job-detail-desc-heading {
  display: block;
  font-weight: 700;
  font-size: 12px;
  color: var(--grey);
  margin-top: 12px;
}

/* ===== Board: narrower ============================================ */

/* Under about 1100px the pane has no column to sit in and comes over
   the list as a sheet, which is what it did at every width before this
   layout. Under about 800px the rail goes the same way, behind the
   Filters button, and the chips stay on the bar so what is on is still
   readable with the rail shut. */

@media (max-width: 1100px) {
  /* Box 2 leaves the grid and becomes the sheet. Box 1 keeps its
     border, its radius and the inset. */
  .board-grid { grid-template-columns: minmax(0, 1fr); }
  .board-box-main { grid-template-columns: 236px minmax(0, 1fr); }
  .job-detail.board-box { border-radius: 0; border: 0; }

  .job-detail {
    position: fixed;
    top: 0;
    right: 0;
    bottom: 0;
    left: auto;
    width: min(440px, 100%);
    max-height: none;
    z-index: 20; /* above .topbar's 10 */
    border-left: var(--rule) solid var(--black);
    padding-right: max(32px, env(safe-area-inset-right));
    padding-bottom: max(28px, env(safe-area-inset-bottom));
    transform: translateX(100%);
    transition: transform 0.25s ease;
  }
  .job-detail.open { transform: none; }
  /* Closed, the pane is off-screen, so the empty state has nothing to
     say and nothing to say it to. */
  .job-detail:not(.open) { visibility: hidden; }

  #job-scrim {
    position: fixed;
    inset: 0;
    z-index: 19;
    background: var(--scrim);
    opacity: 0;
    visibility: hidden;
    transition: opacity 0.25s ease, visibility 0.25s;
  }
  #job-scrim.open { opacity: 1; visibility: visible; }
}

/* Above it there is no sheet, so nothing to dim and nothing to catch. */
@media (min-width: 1101px) {
  #job-scrim { display: none; }
}

@media (max-width: 800px) {
  /* No box treatment at all: full bleed, and the filters are a sheet
     too, so there is nothing left for a border to hold. */
  .board-grid { grid-template-columns: minmax(0, 1fr); padding: 0; background: var(--white); }
  .board-box { border: 0; border-radius: 0; }
  .board-box-main { grid-template-columns: minmax(0, 1fr); }
  .board-list { padding-left: 0; }
  .rail-toggle { display: inline-flex; }

  /* Up from the bottom, not in from the side. A side sheet on a phone
     is a desktop drawer shrunk: it leaves a dead strip of board beside
     it, it opens away from the thumb, and the Filters button that
     summons it is at the top of the screen while the sheet arrives from
     the left. A bottom sheet comes from where the hand already is and
     takes the full width, which is what the rail's rows want anyway. */
  .filter-rail {
    position: fixed;
    top: auto;
    right: 0;
    bottom: 0;
    left: 0;
    width: 100%;
    height: auto;
    max-height: 85vh;
    max-height: 85dvh;
    z-index: 20;
    border: 0;
    border-top: var(--rule) solid var(--black);
    border-radius: 4px 4px 0 0;
    padding: 12px 16px max(28px, env(safe-area-inset-bottom));
    transform: translateY(100%);
    transition: transform 0.25s ease;
  }
  body.rail-open .filter-rail { transform: none; }
  /* The grab handle every bottom sheet has, so it reads as one. */
  .filter-rail::before {
    content: "";
    display: block;
    width: 36px;
    height: 4px;
    margin: 0 auto 10px;
    border-radius: 999px;
    background: var(--grey-line);
  }
  /* The board behind must not scroll while the sheet is up: on a phone
     the sheet covers most of it and a stray drag moved the list instead
     of the sheet. */
  body.rail-open { overflow: hidden; }
  body.rail-open #job-scrim { opacity: 1; visibility: visible; }

  .rail-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    margin-bottom: 14px;
  }
  .rail-head-title { font: 700 14px var(--font); color: var(--black); }
  .rail-close {
    width: 28px;
    height: 28px;
    display: grid;
    place-items: center;
    border: 1.5px solid var(--grey-line);
    border-radius: 4px;
    background: var(--white);
    color: var(--grey);
    cursor: pointer;
  }
  .rail-close::before { content: "\2715"; font-size: 12px; }
  .rail-close:hover { background: var(--black); border-color: var(--black); color: var(--white); }

  /* Each control takes a row of its own, full width, which is what
     this bar was before the three-column layout. The chips stay behind
     the Filters button's own count: two rows of them on a 390px screen
     is most of the first screen, and the count says the same thing in
     one line. */
  .board-bar-row-2 { gap: 10px; }
  .board-bar-row-2 .rail-toggle-unused {
    order: 1;
    width: 100%;
    height: 42px;
    display: flex;
    align-items: center;
    justify-content: center;
  }
  .board-bar-row-2 .view-switch { order: 2; width: 100%; }
  /* Four labels across 390px. They shrink to fit rather than wrap:
     "Best matches" on two lines made its segment taller than the three
     beside it and the whole control lost its baseline. */
  .board-bar-row-2 .view-switch .seg-btn {
    flex: 1 1 0;
    min-width: 0;
    padding: 0 4px;
    font-size: 12px;
    white-space: nowrap;
  }
  .board-bar-row-2 .result-count { order: 3; margin: 0; white-space: normal; }
  .board-bar-row-2 .active-chips { display: none; }
  .board-bar-row-2 #f-reset { display: none; }
}

@media (max-width: 640px) {
  .board-bar { position: static; padding: 12px 8px; gap: 10px; }
  .board-bar-row { flex-wrap: nowrap; gap: 8px; }
  /* Date posted moves into the filter sheet and the sort moves into the
     list head, so neither takes a row here. Search plus Filters is the
     whole of the first row. */
  .board-bar #f-date-posted,
  .board-bar #f-sort,
  .board-bar .search-go { display: none; }
  .rail-toggle {
    height: 44px;
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    padding: 0 14px;
    border-radius: 999px;
    line-height: 1;
  }
  /* The count on the Filters button, so the sheet is not the only place
     that says anything is on. */
  .rail-toggle[data-count]:not([data-count="0"])::after {
    content: attr(data-count);
    display: inline-grid;
    place-items: center;
    flex: 0 0 auto;
    min-width: 18px;
    height: 18px;
    margin-left: 7px;
    padding: 0 5px;
    border-radius: 999px;
    background: var(--green-text);
    color: var(--white);
    font: 700 11px var(--font-ui);
  }

  /* The view switch stops being a segmented control and becomes chips
     that scroll: four segments across 390px gives each of them nine
     characters and no room for a thumb. */
  .board-bar-row-2 { flex-direction: row; gap: 8px; overflow: visible; }
  .board-bar-row-2 .view-switch {
    order: 0;
    width: auto;
    overflow-x: auto;
    overflow-y: hidden;
    scrollbar-width: none;
  }
  .board-bar-row-2 .view-switch::-webkit-scrollbar { display: none; }
  /* Roomier than the desktop's 14px. The labels are the same length on
     a third of the width, so the same padding reads as the text being
     pressed against the border. The row scrolls, so the extra width
     costs nothing. */
  .board-bar-row-2 .view-switch .seg-btn {
    flex: 0 0 auto;
    height: 34px;
    padding: 0 18px;
  }

  .job-detail {
    /* A phone has no room for a side sheet, so it is the whole screen
       and swipes down to close (see wireJobDetailSwipe). */
    width: 100%;
    top: 0;
    left: 0;
    border-left: 0;
    border-top: var(--rule) solid var(--black);
    padding: 22px 20px;
    transform: translateY(100%);
  }

  table.jobs thead { display: none; }

  /* The list is a box with a lid and no floor: it runs off the bottom of
     the screen, because there is always more of it. */
  .board-list {
    margin: 0 8px;
    background: var(--white);
    border: 1px solid var(--panel-line);
    border-bottom: 0;
    border-radius: 10px 10px 0 0;
  }
  .board-list .list-head { height: 52px; padding: 0 14px; }
  .list-head .col-head-title { font-size: 15px; }
  .list-head .col-head-sub { font-size: 11px; }

  /* 112px, and the row carries only what can be read at a glance: the
     title, one meta line and one chip line. Apply, Save link and the
     salary line are gone from it -- tapping the row opens the listing,
     which is where acting on it belongs. */
  table.jobs tbody tr[data-id],
  table.jobs tbody tr.skeleton-row {
    grid-template-columns: 40px minmax(0, 1fr) 28px;
    gap: 12px;
    padding: 14px;
    height: 112px;
    align-items: start;
    align-content: start;
  }
  .age-cell,
  .job-where,
  .job-links,
  .job-salary-line { display: none; }
  /* The age rides at the end of this line on a phone, and the line
     truncates, so it was the first thing to go: a row read
     "mobius.org - Engineering Solutions - 29d..." with the one word that
     said how old it is cut in half. The company and department give up
     their characters instead. */
  .job-meta { display: flex; align-items: baseline; }
  .job-meta .job-who {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }
  /* padding, because the separator's leading space is the first thing in
     a flex item and flex collapses it: the line read "Engineering- 29d
     ago" with the gap only on one side of the dot. */
  .meta-age { display: inline; flex: none; white-space: nowrap; padding-left: 4px; }
  /* Every cell placed explicitly. The star is first in the DOM and the
     phone grid wants it last, and naming only its column left the other
     two to auto-placement: the cursor had already passed column 3, so
     the logo and the text wrapped onto a second row and the star sat
     alone above them. Reported as the rows being a mess, and they were. */
  .logo-cell { grid-column: 1; grid-row: 1; }
  .main-cell { grid-column: 2; grid-row: 1; }
  .star-cell { display: flex; grid-column: 3; grid-row: 1; justify-self: end; }
  .star-btn { width: 36px; height: 36px; margin: -8px -8px 0 0; }
  .job-chips { display: flex; height: 22px; }
  /* Two lines, then the ellipsis. A phone title wraps where a desktop
     one truncates: the width is half and the words are the same. */
  .job-card-title { height: auto; align-items: flex-start; }
  .job-title-text {
    white-space: normal;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    line-clamp: 2;
  }
  table.jobs tbody tr.selected::before { left: -8px; }
  .logo-cell .company-logo.listing { width: 40px; height: 40px; }
  .logo-cell { padding-top: 4px; }

  .main-cell { gap: 6px; }
  .job-card-title { font-size: 15px; line-height: 1.25; }
  .job-meta { font-size: 13px; line-height: 1.35; }

  /* Apply is the one filled control on the row, the way it was before
     this layout. Save link beside it stays a plain link: they are not
     the same weight of action. */
  .job-links { align-items: center; gap: 14px; margin-top: 2px; }
  .job-links .apply-link {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    height: 38px;
    padding: 0 16px;
    border: 1.5px solid var(--green-text);
    border-radius: 4px;
    background: var(--green-text);
    color: var(--white);
    font: 700 14px var(--font-ui);
    text-decoration: none;
  }
  .job-links .copy-link-btn {
    border: 0;
    background: none;
    padding: 0;
    color: var(--grey);
    font: 400 14px var(--font-ui);
    text-decoration: underline;
    text-underline-offset: 3px;
    cursor: pointer;
  }
  /* The figure in green with a quiet "Est." in front, or the absence
     said out loud. Plain text, not the desktop chip: there is one of
     these per row here and it does not need a box to be found. */
  .job-salary-line { font: 700 14px var(--font); color: var(--green-text); }
  /* .job-salary.estimate is a filled green box elsewhere. Here it is a
     line of text under a green Apply button, and two filled greens
     stacked read as one control split in half. */
  .job-salary-line .job-salary.estimate {
    display: inline;
    background: none;
    border: 0;
    padding: 0;
    color: var(--green-text);
  }
  .job-salary-line .job-salary.undisclosed { color: var(--grey); font-weight: 400; }
  .job-salary-line .salary-est-label { color: var(--grey); font-weight: 400; }
}

/* The sheets slide, and that is the one moment of motion on this page.
   With reduced motion on they arrive already open rather than not at
   all: a sheet that cannot be seen to move still has to be seen. */
@media (prefers-reduced-motion: reduce) {
  .job-detail,
  .filter-rail,
  #job-scrim { transition: none; }
}

/* ===== Account: the redesigned shell ============================== */

.topbar-account .container { display: flex; align-items: center; gap: 24px; min-height: 56px; }
.topbar-word { font: 400 15px var(--font-display); letter-spacing: 0.02em; color: var(--black); text-decoration: none; }
.topbar-word span { color: var(--green); }
.topbar-nav { display: flex; gap: 20px; font-size: 14px; }
.topbar-nav a { color: var(--black); text-decoration: none; }
.topbar-nav a:hover { color: var(--green-text); }
/* The page you are on. An inset rule under the word rather than a
   colour, so it reads as a position in the bar and not as a link in a
   different state. */
/* Bold and nothing else: the ask was plain text, the page you are on
   in bold, and the underline that used to sit under it was a second
   mark saying the same thing. */
.topbar-nav a.current { font-weight: 700; }

/* The mark and the destinations as one group, on every bar that has
   them. The nav's own margin-left:auto exists for bars where it is the
   only thing on the left; inside this group it would push the links to
   the far end of the column, away from the mark. */
.topbar-left { display: flex; align-items: center; gap: 18px; min-width: 0; }
.topbar-left .topbar-nav { margin-left: 0; }
.topbar-mark { display: inline-flex; align-items: center; gap: 8px; flex: none; line-height: 0; color: var(--black); text-decoration: none; }
/* On a phone the menu button (mobile_nav.js) lists Jobs, Statistics and
   API, so the inline links give up their place beside the brand. Pages
   without that menu (a listing, an employer) keep them. */
@media (max-width: 640px) {
  .topbar .container:has(.hero-nav-toggle) .topbar-left .topbar-nav { display: none; }
  /* Each page's bar spaced its items its own way (12, 16 or 24px), so
     the brand started somewhere different on every page. One gap. */
  .topbar .container:has(.hero-nav-toggle) { column-gap: 14px; }
}
.topbar-mark img { display: block; width: 22px; height: 22px; border-radius: 5px; }
.topbar-mark:focus-visible { outline: 2px solid var(--green); outline-offset: 3px; border-radius: 5px; }
.topbar-gap { flex: 1; }

/* The nav's own rules live with the rest of the sidebar higher up. A
   second copy sat here, outside any media query and after the phone
   block, so it won at every width: it is why the tabs measured 40px
   with a left-hand bar instead of 44 with an underline, and why an
   active row on the desktop was tinted rather than filled. */
/* A count of nothing is not information. */
.account-nav-count:empty,
.account-nav-count[data-zero="1"] { display: none; }

/* One header pattern for every section. */
.account-head { display: flex; flex-direction: column; gap: 6px; }
.account-head-title {
  margin: 0;
  font: 400 34px/1 var(--font-display);
  letter-spacing: -0.02em;
  color: var(--black);
}

/* display on a class beats the user agent's [hidden], which is how a
   hidden panel would otherwise still show. The same trap
   .account-layout[hidden] above already documents. */
.acct-panel[hidden],
.acct-row-two[hidden],
.acct-row-lead[hidden] { display: none; }


/* Settings: label and description on the left, control on the right. */
.acct-rows { display: flex; flex-direction: column; }
.acct-row {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 24px;
  padding: 18px 0;
  border-bottom: 1px solid var(--grey-line);
}
.acct-row-text { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
.acct-row-label { font: 700 15px var(--font); color: var(--black); }
.acct-row-note { font: 400 14px var(--font); color: var(--grey); }

/* Preferences. Groups under a heading, and in each a row per value:
   label, the value in a line, a chevron. Opening a row turns the
   chevron down and shows that value's control and a Save under it.
   The rows are the account page's own hairline rows; nothing is boxed. */
.pref-group + .pref-group { margin-top: 28px; }
.pref-group-title { margin: 0 0 2px; font: 500 16px var(--font-display); color: var(--black); }
.pref-item { border-bottom: 1px solid var(--grey-line); }
.pref-head {
  display: flex;
  width: 100%;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding: 16px 0;
  border: 0;
  background: none;
  color: var(--black);
  text-align: left;
  font: inherit;
  cursor: pointer;
}
.pref-head:hover .pref-label { color: var(--green-text); }
.pref-head:focus-visible { outline: 2px solid var(--green); outline-offset: -2px; }
.pref-head-text { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
.pref-label { font: 700 15px var(--font); }
.pref-summary { font: 400 14px var(--font); color: var(--grey); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pref-chevron { flex: 0 0 auto; color: var(--grey); transition: transform 160ms cubic-bezier(0.2, 0, 0, 1); }
.pref-item.open .pref-chevron { transform: rotate(90deg); }
.pref-editor { display: flex; flex-direction: column; gap: 12px; padding: 0 0 18px; }
.pref-editor[hidden] { display: none; }
.pref-editor > .ms { max-width: 320px; }
.pref-actions { display: flex; align-items: center; gap: 10px; }
.pref-radios { display: flex; flex-direction: column; gap: 12px; }
.pref-radio { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; }
.pref-radio input[type="radio"] { margin: 0; accent-color: var(--green); flex: 0 0 auto; }
.pref-radio-label { font: 500 14px var(--font); color: var(--black); cursor: pointer; }
/* The controls that go with a row: a time, a zone, a day. Greyed, not
   hidden, when another row is chosen, so the row still reads as a
   sentence and the value is there to come back to. */
.pref-when { display: inline-flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.pref-when.off { opacity: 0.45; }
.pref-time, .pref-tz, .pref-day {
  height: 32px;
  padding: 0 8px;
  border: 1px solid var(--grey-line);
  border-radius: 4px;
  background: var(--white);
  color: var(--black);
  font: 400 14px var(--font);
}
.pref-tz { max-width: 260px; }
.pref-time:focus-visible, .pref-tz:focus-visible, .pref-day:focus-visible { outline: 2px solid var(--green); outline-offset: 1px; }

/* Its own block, bordered in the one colour this system reserves for
   something that cannot be undone. */
.account-danger {
  border: 1px solid var(--red);
  border-radius: 14px;
  padding: 22px 24px;
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 24px;
  flex-wrap: wrap;
}
.acct-danger-title { font: 700 15px var(--font); color: var(--red); }

@media (max-width: 800px) {
  /* The account page's own phone rules are in the max-width: 800px
     block above, which is where the tab strip is defined. This one used
     to redefine the same four selectors a second time and win on source
     order, which is how the tabs ended up 40px tall with a 3px
     underline instead of the 44 and 2 they are drawn at. */
}

/* Value over label in the listing's facts, the same way round as the
   overview's tiles, so the two states of one pane read alike. */
.job-detail-facts .fact { display: flex; flex-direction: column; gap: 4px; }
.job-detail-skills { display: flex; flex-wrap: wrap; gap: 6px; }

/* One tile everywhere a company mark appears: white, hairline, 4px, and
   the image inset inside it rather than bleeding to the edge. A logo is
   someone else's artwork at someone else's aspect ratio, so it is
   contained and centred and never cropped. */
.company-logo {
  box-sizing: border-box;
  background: var(--white);
  border: 1px solid var(--grey-line);
  border-radius: 4px;
  object-fit: contain;
  padding: 6px;
}

/* The pane's head is a position and a way through the list, not a word.
   The count is tabular so the buttons do not shuffle as it changes. */
.pane-nav { display: flex; align-items: center; gap: 4px; min-width: 0; }
.pane-pos { font-variant-numeric: tabular-nums; }
.pane-step {
  width: 26px;
  height: 26px;
  flex: 0 0 auto;
  display: grid;
  place-items: center;
  border: 0;
  border-radius: 4px;
  background: none;
  color: var(--black);
  font-size: 17px;
  line-height: 1;
  cursor: pointer;
}
.pane-step:hover:not(:disabled) { background: var(--hover-bg); }
.pane-step:disabled { opacity: 0.35; cursor: default; }
.pane-step:focus-visible { outline: 2px solid var(--green); outline-offset: -2px; }

/* Apply and Save again at the foot of the pane, once the real pair has
   scrolled out of it. A rule above rather than a shadow: the board draws
   with lines, not with depth. */
.pane-sticky {
  flex: none;
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 12px 20px;
  border-top: 1px solid var(--grey-line);
  background: var(--white);
}
.pane-sticky[hidden] { display: none; }

/* The last thing in the pane: the version that is actually current. */
.job-detail-source {
  align-self: flex-start;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font: 600 13px var(--font-ui);
  color: var(--green-text);
  text-decoration: none;
}
.job-detail-source:hover { text-decoration: underline; }

/* The way back out of an empty result set. */
.empty-undo { margin-top: 14px; }

/* The part of a filter option the reader typed. */
.rail-hit { font-weight: 700; color: var(--green-text); }

/* Above the phone breakpoint the button says the number in words; the
   badge is a phone treatment. */
@media (min-width: 801px) {
  .rail-toggle[data-count]:not([data-count="0"])::after {
    content: " (" attr(data-count) ")";
  }
}

/* The end of the list. Centred and quiet: it is a place the reader
   arrives at, not a control they go looking for. */
.jobs-more {
  display: flex;
  justify-content: center;
  padding: 18px 20px 28px;
}
.jobs-more[hidden] { display: none; }

/* The pane's action bar. Two square targets and then Apply, which takes
   the rest of the row: the two are things you might do and Apply is the
   thing you came for. */
.pane-sticky .pane-act {
  width: 48px;
  height: 48px;
  flex: 0 0 auto;
  display: grid;
  place-items: center;
  border: 1.5px solid var(--black);
  border-radius: 4px;
  background: var(--white);
  color: var(--black);
  cursor: pointer;
}
.pane-sticky .pane-act:hover { background: var(--hover-bg); }
.pane-sticky .pane-act.on { color: var(--green-text); border-color: var(--green-text); }
.pane-sticky .pane-apply {
  flex: 1;
  height: 48px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  min-width: 0;
  padding: 0 14px;
  border-radius: 4px;
  background: var(--green-text);
  color: var(--white);
  font: 700 14px var(--font-ui);
  text-decoration: none;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

@media (max-width: 800px) {
  /* The whole screen, not a 440px drawer with the board showing beside
     it: at 390px that drawer was wider than the phone and every line in
     it ran off the right edge. */
  .job-detail {
    inset: 0;
    width: 100%;
    max-width: none;
    border-radius: 0;
  }
  .job-detail .pane-head { height: 52px; padding: 0 8px; }
  .pane-step { width: 44px; height: 44px; }
  .pane-close { width: 44px; height: 44px; }
  .pane-body { padding: 16px 16px 24px; }
  .job-detail-title { font-size: 24px; }
  .job-detail-description { font-size: 15px; line-height: 1.55; }
  /* Always there, not only once the header actions scroll away: on a
     phone the header is off screen the moment you start reading. */
  .pane-sticky {
    padding: 10px 16px calc(10px + env(safe-area-inset-bottom));
  }
  .pane-sticky[hidden] { display: flex; }
}

/* The filter bar's controls take the pill radius the view chips under
   them already have, so the two rows read as one bar rather than a
   square row above a round one. The extra side padding is not
   decoration: at 999px the corner eats into the text box, and a
   placeholder starting under the curve looks clipped. */
.board-bar select,
.board-bar .search-go,
.board-bar .rail-toggle {
  border-radius: 999px;
}
.board-bar select { padding-left: 14px; }
.board-bar .search-go { padding-left: 20px; padding-right: 20px; }
/* The active-filter chips share that row with the view pills, so they
   take the same shape. A 4px chip beside a 999px one reads as a
   different kind of control rather than the same row. */
.active-chips .chip { border-radius: 999px; }

/* Date posted and Newest live in the top bar. Neither narrows by a
   value, which is what the rail is for: one bounds the result set and
   the other orders it, so they belong beside the search rather than on
   a row with the views. */
/* Search and the two selects are one group, centred on the bar rather
   than parked at its two ends. Three explicit columns, not flex with
   auto margins: the status line on the left and the nav on the right are
   nowhere near the same width, so splitting the free space between them
   leaves the group looking centred on nothing. Equal 1fr cheeks put it
   on the middle of the window.

   Each item names its column. The status line hides below 640px, and
   auto-placement would then walk everything else one column left, which
   is the same trap the mobile job row fell into. */
.topbar-board .container {
  display: grid;
  /* min-content, not the 1fr shorthand: .topbar .container > * sets
     min-width: 0 on every child, which zeroes a 1fr track's automatic
     minimum, and the nav collapsed its own column and drew over the
     selects. */
  grid-template-columns: minmax(min-content, 1fr) auto minmax(min-content, 1fr);
  align-items: center;
  column-gap: 16px;
}
.topbar-board .topbar-left { grid-column: 1; justify-self: start; }
.topbar-center {
  grid-column: 2;
  min-width: 0;
  display: flex;
  align-items: center;
  gap: 10px;
}
/* nowrap so the track's min-content is the nav's real width. Without it
   "Sign in" broke across two lines and took the bar's height with it. */
.topbar-board .topnav { grid-column: 3; justify-self: end; margin-left: 0; flex: none; white-space: nowrap; }

.topbar-sorts { display: flex; align-items: center; gap: 8px; flex: none; }
.topbar-sorts select {
  height: 36px;
  /* The chevron the rail's carets use, not the browser's own. These two
     wore the native arrow from the moment they moved up here, jammed
     against a 999px corner that curves away under it. 34px of right
     padding and 16px of inset clears the curve. */
  appearance: none;
  padding: 0 34px 0 16px;
  border: 1.5px solid var(--black);
  border-radius: 999px;
  background-color: var(--white);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 10 6'%3E%3Cpath d='M1 1l4 4 4-4' fill='none' stroke='%2340513b' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 16px center;
  background-size: 10px 6px;
  color: var(--black);
  font: 400 13px var(--font-ui);
}
:root[data-theme="dark"] .topbar-sorts select {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 10 6'%3E%3Cpath d='M1 1l4 4 4-4' fill='none' stroke='%23ededec' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
}
.topbar-sorts select:focus-visible { outline: 2px solid var(--green); outline-offset: -2px; }

/* Below 800px the group is empty: the selects hide and the search box
   moves into the filter bar. Back to the flex row the other pages use,
   so the nav reaches the right edge on its own. */
@media (max-width: 800px) {
  .topbar-board .container { display: flex; }
  .topbar-board .topnav { margin-left: auto; }
}

/* Nothing is left on the bar's first row above 800px: the search box is
   in the topbar and the Filters button is a phone control. */
.board-bar-row-1 { display: none; }

@media (max-width: 800px) {
  /* No room for any of it up there. The selects go back to the sheet and
     the list head; the search box goes back to the filter bar, where
     wireSearchHome() moves the node. The display rules cover the moment
     before that runs, and the case where it never does. */
  .topbar-sorts { display: none; }
  .topbar .topbar-search { display: none; }
  .board-bar-row-1 { display: flex; }
  .board-bar .topbar-search {
    display: flex;
    /* Basis 0, not the 560px width it carries upstairs: the box takes
       what the Search and Filters buttons leave, rather than shrinking
       alongside them and taking 2px off each. */
    flex: 1 1 0;
    width: auto;
    height: 38px;
    padding-left: 16px;
    order: -1;
  }
  /* The box goes back to being a plain field down here. The magnifier
     costs 23px of a placeholder that is already cut off at 390px, and a
     phone has no / to press. The clear cross stays: backspacing a search
     out by hand is worst on the device with no Escape key. */
  .board-bar .topbar-search-icon { display: none; }
}

/* The phone's 44px tap target, the height this box has had there since
   the mobile pass. */
@media (max-width: 640px) {
  .board-bar .topbar-search { height: 44px; }
}

/* The top bar, one size up on desktop: 4px taller, and every control in
   it scaled with it rather than left floating in a bigger box. Controls
   go 36 -> 40, text one pixel up, the mark, avatar and icons two. The
   theme switch keeps its geometry: 72 x 36 with a 28px knob travels
   72 - 2 (border) - 6 (inset) - 28 = 36px, and each icon sits centred
   under the knob's two stops. The phone bar is its own layout and is
   left alone. */
@media (min-width: 801px) {
  .topbar .container { min-height: 60px; }
  .topbar-search { height: 40px; }
  .topbar-search input { font-size: 14px; }
  .topbar-search-icon { width: 16px; height: 16px; }
  .topbar-sorts select { height: 40px; font-size: 14px; }
  .topbar-nav { font-size: 15px; }
  .topbar-mark img { width: 24px; height: 24px; }
  .auth-area .hero-account-btn { height: 34px; font-size: 14px; }
  .auth-area .hero-avatar { width: 24px; height: 24px; font-size: 13px; }
  .auth-trigger { font-size: 14px; }
  .topnav .icon-link svg { width: 20px; height: 20px; }
  .topbar .theme-toggle { width: 72px; height: 36px; }
  .topbar .theme-knob { width: 28px; height: 28px; transform: translateX(36px); }
  :root[data-theme="dark"] .topbar .theme-knob { transform: translateX(0); }
  .topbar .theme-ico { top: 8px; width: 18px; height: 18px; }
  .topbar .theme-ico-moon { left: 8px; }
  .topbar .theme-ico-sun { left: 44px; }
}
