/* Application shell: sidebar + main + drawer, with the list and kanban views
   sharing one scroll container. */

.boot {
  position: fixed;
  inset: 0;
  display: grid;
  place-items: center;
  color: var(--faint);
}

.linkish {
  border: 0;
  background: none;
  padding: 0;
  font: inherit;
  color: var(--accent);
  cursor: pointer;
  text-decoration: underline;
  text-underline-offset: 3px;
}

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

.icon-btn {
  display: inline-grid;
  place-items: center;
  width: 2rem;
  height: 2rem;
  border: 0;
  border-radius: var(--r-sm);
  background: transparent;
  color: var(--muted);
  font-size: 1.35rem;
  line-height: 1;
  cursor: pointer;
}

.icon-btn:hover {
  background: var(--surface-3);
  color: var(--text);
}

/* --- wordmark (shared with landing) ------------------------------------- */

.wordmark {
  display: inline-flex;
  align-items: center;
  font-family: var(--font-sans);
  font-size: 1.0625rem;
  font-weight: 650;
  letter-spacing: -0.02em;
  text-decoration: none;
  color: var(--text);
}

.wordmark__owl {
  width: 1.5em;
  height: 1.5em;
  flex: none;
}

/* The owl carries the space before the name: a flex gap would also split
   "job-boards" from its ".io". Scoped, so the rail's lone owl stays centred. */
.wordmark > .wordmark__owl {
  margin-right: 0.55rem;
}

.wordmark__tld {
  color: var(--faint);
}

/* ============================== AUTH ==================================== */

.auth {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  min-height: 100vh;
}

.auth__pitch {
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  gap: 3rem;
  padding: clamp(2rem, 4vw, 3.5rem);
  /* The one tint, flat: the half of the screen that is not the form reads as
     the page's margin rather than as a second card competing with it. */
  background: var(--surface-2);
  border-right: 1px solid var(--line-soft);
  position: relative;
  overflow: hidden;
}

.auth__blurb {
  margin-top: 1.1rem;
  max-width: 38ch;
  color: var(--muted);
  font-size: 0.9375rem;
  position: relative;
}

.auth__blurb--tight {
  margin: -0.35rem 0 1.25rem;
}

.auth__foot {
  font-size: 0.75rem;
  color: var(--faint);
  position: relative;
}

.auth__panel {
  display: grid;
  place-items: center;
  padding: clamp(1.5rem, 4vw, 3rem);
}

.auth__card {
  width: 100%;
  max-width: 23rem;
}

.auth__title {
  margin: 0.7rem 0 1.6rem;
  font-size: 1.65rem;
}

.auth__switch {
  margin-top: 0.9rem;
  font-size: 0.875rem;
  color: var(--muted);
  text-align: center;
}

/* The line that says what signing up agrees to. Quiet, but a real link. */
.auth__legal {
  margin-top: 1rem;
  font-size: 0.75rem;
  line-height: 1.5;
  color: var(--faint);
  text-align: center;
}

.auth__legal a {
  color: var(--muted);
  text-decoration: underline;
  text-underline-offset: 0.2em;
}

/* "or", with a rule through it. A border on the p and a padded span over the
   middle, rather than two pseudo-elements and a flex row - fewer moving parts
   for a divider that has one job. */
.auth__or {
  position: relative;
  margin: 1rem 0 0.9rem;
  border-top: 1px solid var(--line);
  text-align: center;
}

.auth__or span {
  position: relative;
  top: -0.7rem;
  padding: 0 0.6rem;
  background: var(--surface);
  font-family: var(--font-mono);
  font-size: 0.75rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--faint);
}

/* A provider button is a neutral surface, never the accent: the primary
   action on this form is the one the person already chose by typing into it,
   and two cobalt blocks would make the page argue with itself about which. */
.btn--provider {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.6rem;
  background: var(--surface);
  border: 1px solid var(--line);
  color: var(--text);
  text-decoration: none;
}

.btn--provider:hover {
  background: var(--surface-2);
  border-color: var(--line);
}

/* Apple asks for its mark on plain black or plain white, not on a tinted
   surface, and for the two to swap with the surrounding scheme. So the button
   is the theme's own ink on its own paper - black on white in light mode,
   white on black in dark - which satisfies that and still reads as one of
   this form's controls rather than a decal stuck on it. */
.btn--apple {
  background: var(--text);
  border-color: var(--text);
  color: var(--ink);
}

.btn--apple:hover {
  background: var(--text);
  border-color: var(--text);
  opacity: 0.88;
}

/* A brand mark keeps its own colours where it has them, so it does not
   inherit the button's text colour in either theme. Apple's is a single path
   painted with currentColor, which is how it follows the swap above. */
.btn__glyph {
  flex: none;
}

.input--code {
  font-size: 1.4rem;
  letter-spacing: 0.28em;
  text-align: center;
  padding: 0.85rem;
}

@media (max-width: 880px) {
  .auth {
    grid-template-columns: 1fr;
  }
  .auth__pitch {
    border-right: 0;
    border-bottom: 1px solid var(--line-soft);
    gap: 1.75rem;
    padding-bottom: 2.5rem;
  }
  .auth__foot {
    display: none;
  }
}

/* ============================== SHELL =================================== */

.app {
  display: grid;
  grid-template-columns: 17.5rem minmax(0, 1fr);
  min-height: 100vh;
}

/* The nav is two pieces in one aside: a 3.5rem icon rail that is always
   there, and a 14rem panel showing ONE section's list at a time. */
.sidebar {
  display: flex;
  border-right: 1px solid var(--line-soft);
  background: var(--surface);
  position: sticky;
  top: 0;
  height: 100vh;
  overflow: hidden;
}

.rail {
  flex: none;
  width: 3.5rem;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.35rem;
  padding: 0.85rem 0;
  border-right: 1px solid var(--line-soft);
}

.rail__logo {
  display: grid;
  place-items: center;
  width: 2.5rem;
  height: 2.2rem;
  margin-bottom: 0.5rem;
}

.rail__btn {
  position: relative;
  display: grid;
  place-items: center;
  width: 2.5rem;
  height: 2.5rem;
  border: 0;
  border-radius: var(--r-sm);
  background: transparent;
  color: var(--muted);
  cursor: pointer;
}

.rail__btn:hover {
  background: var(--surface-2);
  color: var(--text);
}

/* The section showing wears the accent's wash and the accent itself - the
   same pair a selected row takes - rather than a stripe down one edge. */
.rail__btn.is-on {
  background: var(--accent-wash);
  color: var(--accent);
}

/* The live count, riding the icon's shoulder. */
.rail__count {
  position: absolute;
  top: 1px;
  right: 2px;
  font-size: 0.625rem;
  color: var(--accent);
  line-height: 1;
}

.rail__count:empty {
  display: none;
}

.rail__spring {
  flex: 1;
}

/* The signed-in chip, top right on the title row - the door to Account &
   security, where signing out lives too. */
.topbar__avatar {
  border: 0;
  background: transparent;
  padding: 0;
  cursor: pointer;
  display: inline-flex;
  flex: none;
}

/* "navpanel", never "panel": .panel is the app-wide CARD class, and naming
 * the nav's list panel the same thing once hid every card on the page the
 * moment the nav collapsed. */
/* The list panel is the one tint, beside a white rail: the row showing is
   then the only white thing in it, lifted by a hairline, which reads at a
   glance as "this one". */
.navpanel {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  padding: 0.85rem 0.6rem 0.75rem;
  background: var(--surface-2);
}

.navpanel__head {
  display: flex;
  align-items: center;
  gap: 0.45rem;
  padding: 0 0.25rem 0.35rem;
  min-height: 2rem;
}

.navpanel__title {
  font-weight: 700;
  font-size: 0.9375rem;
  white-space: nowrap;
}

.navpanel__count {
  font-size: 0.6875rem;
  color: var(--muted);
  background: var(--surface-3);
  border-radius: var(--r-pill);
  padding: 2px 7px;
}

.navpanel__count:empty {
  display: none;
}

.navpanel__spring {
  flex: 1;
}

.navpanel__new {
  font-size: 0.75rem;
}

.navpanel__new:disabled {
  opacity: 0.35;
  cursor: not-allowed;
  text-decoration: none;
}

.navpanel .board-list {
  flex: 1;
  overflow-y: auto;
}

.navpanel .board-list[hidden] {
  display: none;
}

/* The standing reminder that board rows are drop targets. */
.navpanel__foot {
  border-top: 1px solid var(--line-soft);
  padding: 0.6rem 0.25rem 0;
  font-size: 0.75rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--faint);
}

.sidebar__close {
  display: none;
}

/* Collapsing on a wide screen shrinks the aside to the rail alone - the rail
   IS the collapsed nav, so every section stays one click away. Narrow screens
   are handled further down: there the whole aside is an off-canvas drawer. */
.sidebar__collapse {
  display: none;
}

@media (min-width: 881px) {
  .sidebar__collapse {
    display: inline-grid;
  }

  .app {
    transition: grid-template-columns 0.22s var(--ease);
  }

  /* Two ways the panel folds down to the rail: the user's collapse, and a
     route with no list (Home, Profile, the map) - panel-off, set by the
     router, so the stale section list never lingers beside a page it has
     nothing to do with. */
  .app.sidebar-collapsed,
  .app.panel-off {
    grid-template-columns: 3.5rem minmax(0, 1fr);
  }

  /* visibility rather than display: a panel merely clipped by the shrinking
     column keeps its buttons in the tab order - so tabbing would walk
     through a list nobody can see. inert (set in JS) is the guarantee. */
  .app.sidebar-collapsed .navpanel,
  .app.panel-off .navpanel {
    visibility: hidden;
  }
}

@media (prefers-reduced-motion: reduce) {
  .app,
  .sidebar {
    transition: none;
  }
}

.board-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.board-item {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  width: 100%;
  padding: 0.55rem 0.6rem;
  border: 0;
  border-radius: var(--r-sm);
  background: transparent;
  color: var(--muted);
  font: inherit;
  font-size: 0.875rem;
  text-align: left;
  cursor: pointer;
}

.board-item:hover {
  background: var(--surface-3);
  color: var(--text);
}

/* The hairline is a shadow rather than a border, so the row showing is the
   same size as every other and nothing in the list moves when it changes. */
.board-item.is-on {
  background: var(--surface);
  box-shadow: 0 0 0 1px var(--line-soft), var(--shadow-sm);
  color: var(--text);
  font-weight: 600;
}

.board-item__name {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.board-item__count {
  font-family: var(--font-mono);
  font-size: 0.75rem;
  color: var(--faint);
}

.board-item.is-on .board-item__count {
  color: var(--accent);
}

/* A search row carries a close control a board row does not: a board's jobs
   are hand-picked, a search is just a sentence that can be run again. */
.search-item {
  position: relative;
  display: flex;
  align-items: center;
}

.search-item .board-item {
  padding-right: 1.8rem;
}

.search-item__del {
  position: absolute;
  right: 0.25rem;
  border: 0;
  background: transparent;
  color: var(--faint);
  font: inherit;
  font-size: 1.05rem;
  line-height: 1;
  padding: 0.25rem 0.4rem;
  border-radius: var(--r-sm);
  cursor: pointer;
}

.search-item__del:hover {
  color: var(--text);
  background: var(--surface-3);
}

.avatar {
  display: grid;
  place-items: center;
  width: 1.9rem;
  height: 1.9rem;
  border-radius: 50%;
  background: var(--accent-wash);
  color: var(--accent);
  font-size: 0.8125rem;
  font-weight: 650;
  flex: none;
}

.sidebar__scrim {
  display: none;
}

@media (max-width: 880px) {
  .app {
    grid-template-columns: 1fr;
  }
  .sidebar {
    position: fixed;
    z-index: 60;
    left: 0;
    top: 0;
    width: 17.5rem;
    transform: translateX(-100%);
    transition: transform 0.22s var(--ease);
  }
  .app.sidebar-open .sidebar {
    transform: none;
  }
  .sidebar__close {
    display: inline-grid;
  }
  .app.sidebar-open .sidebar__scrim {
    display: block;
    position: fixed;
    inset: 0;
    z-index: 55;
    background: var(--scrim);
  }
}

/* --- topbar ------------------------------------------------------------- */

.main {
  display: flex;
  flex-direction: column;
  min-width: 0;
}

.topbar {
  display: flex;
  align-items: center;
  gap: 1rem;
  padding: 0.9rem clamp(0.85rem, 2.5vw, 1.6rem);
  border-bottom: 1px solid var(--line-soft);
  background: var(--veil);
  backdrop-filter: blur(12px);
  position: sticky;
  top: 0;
  z-index: 40;
}

.topbar__menu {
  display: none;
}

.topbar__title {
  min-width: 0;
}

.topbar__title h1 {
  font-size: 1.15rem;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.topbar__stamp {
  font-size: 0.75rem;
  color: var(--faint);
  margin-top: 0.15rem;
}

/* Takes the free space, so it pins to the right of the title row and the
   tools that follow pack in beside it - the same order the bar had when the
   toggle lived inside them. */
.topbar__theme {
  margin-left: auto;
  flex: 0 0 auto;
}

.topbar__tools {
  display: flex;
  align-items: center;
  gap: 0.6rem;
}

/* The delete control (board or search): a real trash can, big enough to
   read as one - quiet until hovered, red when it means it. */
.topbar__delete {
  width: 2.25rem;
  height: 2.25rem;
}

.topbar__delete svg {
  width: 1.25rem;
  height: 1.25rem;
}

.topbar__delete:hover {
  color: var(--danger);
}

.input--search {
  width: 12rem;
  padding: 0.45rem 0.75rem;
  font-size: 0.875rem;
  border-radius: var(--r-sm);
}

/* One recessed track, the choice showing raised out of it - the landing
   panel's Search/Board tabs are the same control. */
.segmented {
  display: flex;
  gap: 2px;
  padding: 3px;
  border-radius: 9px;
  background: var(--surface-3);
}

.segmented__btn {
  padding: 0.32rem 0.85rem;
  border: 0;
  border-radius: 7px;
  background: transparent;
  color: var(--muted);
  font: inherit;
  font-size: 0.8125rem;
  font-weight: 600;
  cursor: pointer;
}

.segmented__btn.is-on {
  background: var(--surface);
  color: var(--text);
  box-shadow: var(--shadow-sm);
}

@media (max-width: 880px) {
  .topbar__menu {
    display: inline-grid;
  }
  .input--search {
    width: 8rem;
  }

  /* The tools row was 406px of controls on a 375px screen, so it wrapped onto
     a line of its own under the title. It no longer holds 406px of anything:
     the search box and the List/Board toggle moved into the board's own
     filter row, and what is left in here is a single 2.25rem trash can -
     board's or search's, never both. A full-width row for one icon left it
     stranded below the title as a floating glyph, which is exactly the
     complaint the theme toggle above it was moved out to fix.
     So it stays on the title row now, beside the toggle it belongs with. */
  .topbar {
    flex-wrap: wrap;
    row-gap: 0.5rem;
    /* Three controls now share the title row, and at 375px the 1rem desktop
       gap spent 64px of it on whitespace - enough to cut the search's name to
       "Untitled se...". Tightened here only; the controls are 44px targets
       and stay far apart enough to hit. */
    column-gap: 0.5rem;
  }
  .topbar__tools {
    flex: 0 0 auto;
  }
  /* Both delete buttons are hidden on views that own neither, and an empty
     flex item still draws the row's gap beside the toggle. Nothing to show,
     no gap. */
  .topbar__tools:not(:has(> :not([hidden]))) {
    display: none;
  }
  .topbar__title h1 {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }
  /* A zero basis: the title's own width never decides where the row
     breaks, so the theme toggle and the account chip stay on it and the
     title takes whatever is left, ellipsis and all. With basis auto the
     search view's title editor (which :has() gives basis auto further
     down, at higher specificity - hence the second selector) pushed both
     onto a row of their own at phone width. */
  .topbar__title,
  .topbar .topbar__title:has(.search-title) {
    flex: 1 1 0;
  }

  /* Touch sizing. 44px is the floor a thumb can hit reliably; the desktop
     sizes stay as they are because a cursor does not need the help. 16px on
     the inputs is not cosmetic - below it, iOS zooms the page on every focus
     and leaves it zoomed. */
  .btn,
  .btn--sm,
  .icon-btn,
  .chip,
  .select,
  .status-select,
  .segmented__btn,
  .theme-toggle {
    min-height: 2.75rem;
  }
  .icon-btn,
  .theme-toggle {
    min-width: 2.75rem;
  }
  /* The vote arrows and the flag on a tablet: a fingertip's target, sized
     so four of them still share the actions cell with Open, and never
     squashed by the flex row when they do not. Scoped to their cluster so
     this outranks the base .triage-btn size, which sits later in the file;
     the phone card, with a line of its own for them, goes to 44px below. */
  .row-actions .triage-btn {
    width: 2.25rem;
    height: 2.25rem;
    flex: none;
  }
  .jobs .jobs__actions-col {
    width: 11.5rem;
  }
  .chip {
    padding: 0.5rem 0.9rem;
  }
  .input,
  .select,
  .recency .select,
  textarea.input,
  select.status-select {
    /* Not cosmetic: below 16px, iOS zooms the page on every focus and leaves
       it zoomed. select.status-select outranks the 0.75rem rule later in the file, which a bare class at the same specificity could not. */
    font-size: 16px;
  }
}

@media (max-width: 620px) {
  .topbar__tools {
    gap: 0.4rem;
  }
  .input--search {
    width: 6.5rem;
  }
}

/* When the table's width runs out, the rows regroup into cards. Same DOM,
   same cells - the columns cannot be honestly read at that width, and
   scrolling a table sideways to see the salary is a chore nobody finishes.
   Keyed to the table's own width (see .table-wrap), at the point where even
   the narrowest table - salary and location dropped - would leave Role under
   its 15rem floor. That is 56.5rem for the search table's widths; the board
   table's columns sum within a rem of the same, so the two share it. The
   order in the card is the order someone triages in: what is it, where, how
   good, then what to do about it. */
@container (width <= 56.5rem) {
  .jobs thead {
    display: none;
  }
  /* A card has the whole row for the company line, so the table's cap is
     lifted. */
  .jobs:not(.jobs--search) .job__company {
    max-width: none;
  }
  .jobs,
  .jobs tbody {
    display: block;
  }
  .jobs tbody tr {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto auto;
    grid-template-areas:
      'title   title  title'
      'company company company'
      'place   place  place'
      'score   age    pay'
      'status  status actions';
    align-items: center;
    gap: 0.3rem 0.7rem;
    padding: 0.9rem 1rem;
  }
  /* tbody in the selector is load-bearing: the desktop cell padding rule
     (.jobs td, later in the file) wins a bare-class tie on source order, and
     0.8rem of dead space inside every card cell is what it looks like when
     it does. */
  .jobs tbody td {
    display: block;
    padding: 0;
  }
  .jobs td:nth-child(1) { grid-area: score; }
  .jobs td:nth-child(2) { grid-area: age; }
  .jobs td:nth-child(3) { grid-area: title; }
  .jobs td:nth-child(4) { grid-area: company; }
  .jobs td:nth-child(5) { grid-area: place; }
  .jobs td:nth-child(6) { grid-area: pay; text-align: right; white-space: nowrap; }
  .jobs td:nth-child(7) { grid-area: status; }
  .jobs td:nth-child(8) { grid-area: actions; justify-self: end; }

  .jobs .job__title {
    font-size: 1rem;
    margin-bottom: 0;
  }
  .jobs .job__company,
  .jobs .job__place {
    color: var(--muted);
    font-size: 0.875rem;
  }
  /* The score bar reads as a gauge on desktop; on the card the number does the
     work and the bar would just be a stray line. */
  .jobs .score__bar {
    display: none;
  }
  .jobs .status-select {
    width: 100%;
  }
}

/* --- banner ------------------------------------------------------------- */

.banner {
  margin: 0.9rem clamp(0.85rem, 2.5vw, 1.6rem) 0;
  padding: 0.7rem 0.9rem;
  border-radius: var(--r-md);
  border: 1px solid var(--line);
  background: var(--surface-2);
  font-size: 0.875rem;
  display: flex;
  align-items: center;
  gap: 0.7rem;
}

/* The standing while-under-construction strip, above everything the app
   shows. Static markup with static styling: a warning about bugs must not
   itself depend on scripts running cleanly. */
.devnote {
  padding: 0.35rem 0.9rem;
  text-align: center;
  font-family: var(--font-mono);
  font-size: 0.72rem;
  letter-spacing: 0.04em;
  color: var(--warn);
  background: color-mix(in srgb, var(--warn) 8%, transparent);
  border-bottom: 1px solid color-mix(in srgb, var(--warn) 25%, transparent);
}

/* The notice's one action: filing the bug it warns about. A real button,
   styled as part of the sentence so the static banner stays one line. */
.devnote__link {
  border: 0;
  padding: 0;
  background: none;
  font: inherit;
  letter-spacing: inherit;
  color: inherit;
  text-decoration: underline;
  text-underline-offset: 2px;
  cursor: pointer;
}

.devnote__link:hover {
  text-decoration-thickness: 2px;
}

/* Status colour for the words as well as the edge: both clear 5:1 on their
   own wash in each palette, as the notices in base.css do. */
.banner--warn {
  border-color: color-mix(in srgb, var(--warn) 35%, transparent);
  background: color-mix(in srgb, var(--warn) 7%, transparent);
  color: var(--warn);
}

.banner--error {
  border-color: color-mix(in srgb, var(--danger) 35%, transparent);
  background: color-mix(in srgb, var(--danger) 7%, transparent);
  color: var(--danger);
}

.banner button {
  margin-left: auto;
}

/* --- content ------------------------------------------------------------ */

.content {
  flex: 1;
  padding: clamp(0.85rem, 2.5vw, 1.6rem);
  min-width: 0;
}

.filters {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 1rem;
  margin-bottom: 1rem;
}

.chips {
  display: flex;
  flex-wrap: wrap;
  gap: 0.35rem;
}

.chip {
  padding: 0.32rem 0.8rem;
  border: 1px solid var(--line);
  border-radius: var(--r-pill);
  background: transparent;
  color: var(--muted);
  font: inherit;
  font-size: 0.8125rem;
  cursor: pointer;
  white-space: nowrap;
}

.chip:hover {
  background: var(--surface-2);
  color: var(--text);
}

/* A filter that is on is filled with ink: of a row of pills, the one doing
   the filtering is the one that should be unmistakable, and cobalt is kept
   for the things that act. */
.chip.is-on {
  background: var(--text);
  border-color: var(--text);
  color: var(--ink);
  font-weight: 600;
}

.chip__n {
  font-family: var(--font-mono);
  font-size: 0.75rem;
  opacity: 0.75;
  margin-left: 0.3rem;
}

.recency {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

/* Wide screens only - the mobile 16px rule sits earlier in the file, and an
   unscoped rule here would win the source-order tie and undo it. */
@media (min-width: 881px) {
  .recency .select {
    font-size: 0.8125rem;
  }
}

.minscore {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  margin-left: auto;
  font-size: 0.75rem;
  color: var(--muted);
}

/* Posting age. Colour only ever encodes the band, and the label always carries
   the value, so it stays readable without relying on hue. */
.age {
  display: inline-block;
  padding: 0.18rem 0.5rem;
  border-radius: var(--r-pill);
  border: 1px solid var(--line);
  font-family: var(--font-mono);
  font-size: 0.75rem;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  color: var(--muted);
}

.age--fresh {
  border-color: color-mix(in srgb, var(--accent) 35%, transparent);
  background: var(--accent-wash);
  color: var(--accent);
  font-weight: 600;
}

.age--recent {
  color: var(--text);
}

.age--stale {
  color: var(--faint);
  border-color: var(--line-soft);
}

.age--unknown {
  color: var(--faint);
  border-style: dashed;
  border-color: var(--line-soft);
}

/* Kept on narrow screens: recency is the point of the column, and it is the
   cheapest one to render. Details and status give way first. */
.jobs__age {
  width: 5.5rem;
}

.minscore input[type='range'] {
  width: 7rem;
  accent-color: var(--accent);
}

.minscore output {
  min-width: 1.5rem;
  color: var(--text);
  font-size: 0.8125rem;
}

/* --- list view ---------------------------------------------------------- */

.table-wrap {
  /* A containing block for the visually hidden header labels, which are
     absolutely positioned: without it they escaped the scroll frame and a
     wide table scrolled the whole page instead of only itself. */
  position: relative;
  border: 1px solid var(--line-soft);
  border-radius: var(--r-lg);
  /* auto, not hidden: hidden merely clips, while the table's min-content width
     still propagates and inflates the layout viewport - on a phone the entire
     app rendered zoomed out to 681px because of this one table. auto contains
     the width and gives mid-size screens a scrollbar inside the frame. */
  overflow-x: auto;
  background: var(--surface);
  /* The wrap is the container the table's layout is keyed to. Which columns
     show, and whether the rows are a table at all, depends on the width the
     TABLE has - and that is the viewport less the sidebar (17.5rem open,
     3.5rem folded, none below 880px) less the content padding. Keyed to the
     viewport, as these rules were, the same 1280px window gave Role 14rem
     with the panel folded and nothing with it open. The thresholds and how
     they are derived sit with the drop rules below. */
  container-type: inline-size;
}

.jobs {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.9375rem;
}

/* Bumping the size twice did not fix this, because the size was not really the
   problem: uppercase monospace with tracking reads optically smaller than its
   own px value, and every cue here was working against legibility at once.
   Now sans, sentence case, body scale, full-strength colour - an ordinary
   table header. The mono micro-label device still carries the rest of the UI;
   it just has no business labelling a dense table someone has to scan. */
.jobs thead th {
  padding: 0.8rem 1rem;
  border-bottom: 1px solid var(--line);
  background: var(--surface-2);
  font-family: var(--font-sans);
  font-size: 0.9375rem;
  font-weight: 600;
  letter-spacing: -0.005em;
  text-transform: none;
  color: var(--text);
  text-align: left;
}

.jobs__score {
  width: 5rem;
}

.jobs__status-col {
  width: 9.5rem;
}
/* The search table's columns: each fixed one gets room to breathe - a
   status chip on one line, a company name and a city without a break, the
   four action icons with air between them - and Role takes what is left,
   which is still the widest column on any screen the table fits. */
.jobs--search .jobs__status-col {
  width: 9.5rem;
}
.jobs--search .jobs__score {
  width: 4.5rem;
}
.jobs--search .jobs__age {
  width: 5rem;
}
.jobs--search .jobs__company-col {
  width: 10.5rem;
}
.jobs--search .jobs__place-col {
  width: 10.5rem;
}
.jobs--search .jobs__pay-col {
  width: 7rem;
}
.jobs--search .jobs__actions-col {
  width: 10rem;
}
/* Fixed layout, so those widths are what the columns get and Role - the
   one column without one - takes everything left, instead of every column
   being held open by its longest unbreakable word. */
.jobs--search {
  table-layout: fixed;
  width: 100%;
}
.jobs--search .job__title {
  overflow-wrap: anywhere;
}
/* Every table: a title word longer than its cell breaks rather than pushing
   the row wide. break-word, not anywhere, on the board table: it leaves the
   column's min-content alone, so ordinary titles never split mid-word. */
.jobs .job__title {
  overflow-wrap: break-word;
}
/* The board table sizes its columns to their content (only the search table
   is fixed layout), and a company line that does not wrap made the Company
   column as wide as the longest employer's whole name - crushing Role to a
   word a line and Status to a single letter. The column's own width is
   11rem; the name is held to it and ellipses. The search table fits its
   Company column itself and keeps the name whole where it has room. */
.jobs:not(.jobs--search) .job__company {
  max-width: 11rem;
}
/* No visible row states a salary: the column stands down, header and all. */
.jobs--nopay .jobs__pay-col,
.jobs--nopay td.job__pay {
  display: none;
}

/* Header sort buttons wear the th's own type, so a sortable column looks like
   any other header until pointed at. The direction cell keeps its width while
   empty, so an arrow appearing does not nudge the label sideways. */
.jobs thead .th-sort {
  display: inline-flex;
  align-items: baseline;
  gap: 0.25rem;
  padding: 0;
  border: 0;
  background: none;
  font: inherit;
  letter-spacing: inherit;
  color: inherit;
  cursor: pointer;
}

.jobs thead .th-sort:hover,
.jobs thead th[aria-sort] .th-sort {
  color: var(--accent);
}

.jobs thead .th-sort__dir {
  width: 0.8em;
  font-size: 0.75rem;
}

.jobs tbody tr {
  border-bottom: 1px solid var(--line-soft);
  cursor: pointer;
  transition: background 0.12s var(--ease);
}

.jobs tbody tr:last-child {
  border-bottom: 0;
}

.jobs tbody tr:hover {
  background: var(--surface-2);
}

.jobs tbody tr.is-archived {
  opacity: 0.45;
}

/* The employer has pulled the listing. Red, but a tinted row rather than red
   text - the row still has to be readable, since it is kept precisely so the
   user can see what happened. The left border carries the signal for anyone
   who cannot distinguish the tint. */
.jobs tbody tr.is-delisted {
  background: color-mix(in srgb, var(--danger) 7%, transparent);
  box-shadow: inset 3px 0 0 var(--danger);
}

.jobs tbody tr.is-delisted:hover {
  background: color-mix(in srgb, var(--danger) 12%, transparent);
}

.jobs tbody tr.is-delisted .job__title {
  color: var(--danger);
}

/* Same treatment on the kanban card, so the two views agree. */
.kcard.is-delisted {
  border-color: color-mix(in srgb, var(--danger) 45%, transparent);
  background: color-mix(in srgb, var(--danger) 7%, var(--surface));
}

.kcard.is-delisted .kcard__company {
  color: var(--danger);
}

.jobs td {
  padding: 0.8rem 1rem;
  vertical-align: middle;
}

.job__title {
  font-weight: 600;
  letter-spacing: -0.01em;
  display: block;
  margin-bottom: 0.15rem;
}

/* Company gets its own column and full text weight. Buried in a run-on meta
   line it was the hardest thing on the row to find, and it is the first thing
   anyone actually looks for. */
.job__company {
  display: block;
  font-weight: 600;
  font-size: 0.875rem;
  letter-spacing: -0.01em;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.job__via {
  display: block;
  font-family: var(--font-mono);
  font-size: 0.75rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--faint);
}

.job__place,
.job__pay {
  font-size: 0.8125rem;
  color: var(--muted);
}

.job__pay {
  font-family: var(--font-mono);
  font-size: 0.75rem;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* Pay is green wherever it appears - the table's own column, a kanban card,
   a drawer's tag row. The colour is the point of recognition: a figure the
   eye can find without reading the row.
   
   On the SPAN, never the cell. A salary column with no salary holds an em
   dash, and an em dash is an absence rather than a number: colouring the
   cell would paint that dash green too and make "no pay" look like pay. */
.pay {
  color: var(--pay);
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* A tag that happens to be money: the tag's shape, the pay colour, and a
   border tinted to match so the chip does not read as a green word sitting
   in a grey box. */
.tag--pay {
  color: var(--pay);
  border-color: color-mix(in srgb, var(--pay) 40%, var(--line));
}
/* The search table's Salary column is widened to fit its data (app.js
   fitSearchColumns). This is what holds when it cannot be: a fixed-layout
   cell does not clip on its own, and nowrap text longer than the column
   paints straight across the vote arrows beside it. The whole figure is on
   the cell's title. */
.jobs--search td.job__pay {
  overflow: hidden;
  text-overflow: ellipsis;
}

/* Where the job is, not how good it is, so it takes the quiet tag of the
   grid rather than the accent a score or a new posting wears. */
.chip-remote {
  display: inline-block;
  margin-right: 0.35rem;
  padding: 0.1rem 0.4rem;
  border-radius: 5px;
  background: var(--surface-3);
  border: 1px solid transparent;
  color: var(--muted);
  font-family: var(--font-mono);
  font-size: 0.625rem;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  vertical-align: 1px;
}

.jobs .score {
  width: 3.4rem;
}

.status-select {
  padding: 0.3rem 1.7rem 0.3rem 0.6rem;
  font-size: 0.75rem;
  border-radius: var(--r-pill);
  background-position: right 0.7rem center, right 0.45rem center;
}

.row-actions {
  display: flex;
  gap: 0.3rem;
  justify-content: flex-end;
}

.jobs__meta-col {
  width: 15rem;
}

/* Column order: 1 score, 2 posted, 3 role, 4 company, 5 location, 6 salary,
   7 status, 8 actions. These indices must track that order - getting them
   stale once already hid the role title instead of the details column.
   Things drop in order of least use: salary, then location, then status.
   Company never drops. */
.jobs__company-col {
  width: 11rem;
}
.jobs__place-col {
  width: 10rem;
}
.jobs__pay-col {
  width: 8rem;
}

/* While the row is still a table, columns drop as width runs out - a squeezed
   eight-column grid helps nobody. The card takes over below 56.5rem and keeps
   every cell, so these stop exactly where it begins rather than fighting it.

   The thresholds are sums, not taste. The search table is fixed-layout with
   every column but Role sized in the stylesheet: pick 2 + score 4.5 + posted
   5 + status 9.5 + company 10.5 + location 10.5 + salary 7 + actions 10 =
   59rem before Role gets a pixel. Role keeps 15rem (ROLE_MIN_REM in app.js,
   the same floor the salary fit honours), so:

     full table            needs 59 + 15 = 74rem
     salary dropped        needs 52 + 15 = 67rem
     location dropped too  needs 41.5 + 15 = 56.5rem, and below that, cards

   Widths are the table's own, which is what makes them true: measured in
   Chromium, the viewport-keyed rules these replace left Role 10rem at 1440px
   and nothing at 1280px with the section panel open. test/table-container
   derives these numbers from the widths above, so a width that moves has to
   move them. */
/* The search table has its own order (1 pick, 2 score, 3 posted, 4 role,
   5 status, 6 company, 7 location, 8 salary, 9 actions), so the board's
   indices would drop its company and status cells under a salary and
   location header. Same headers, that table's own cells. */
@container (56.5rem < width <= 74rem) {
  .jobs__pay-col,
  .jobs:not(.jobs--search) tbody td:nth-child(6),
  .jobs--search tbody td:nth-child(8) {
    display: none;
  }
}

@container (56.5rem < width <= 67rem) {
  .jobs__place-col,
  .jobs:not(.jobs--search) tbody td:nth-child(5),
  .jobs--search tbody td:nth-child(7) {
    display: none;
  }
}

/* --- kanban ------------------------------------------------------------- */

.kanban {
  display: grid;
  grid-auto-flow: column;
  /* 16rem, not 15: beside the logo and the score, a card's text had 84px,
     and once long words were made to break rather than run off the card,
     ordinary ones like "Infrastructure" split mid-word. 100px holds them. */
  grid-auto-columns: minmax(16rem, 1fr);
  gap: 0.8rem;
  overflow-x: auto;
  padding-bottom: 0.8rem;
  align-items: start;
}

/* A column is a tray of the one tint and its cards lie on it in white, so
   the stage reads as a place and the job as a thing in it. The edge is
   there to light up as a drop target and is invisible until then. */
.column {
  background: var(--surface-2);
  border: 1px solid transparent;
  border-radius: var(--r-lg);
  display: flex;
  flex-direction: column;
  min-height: 8rem;
  max-height: calc(100vh - 14rem);
}

.column.is-target {
  border-color: var(--accent);
  background: var(--accent-wash);
}

.column__head {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.8rem 0.9rem 0.45rem;
  position: sticky;
  top: 0;
  background: inherit;
  border-radius: var(--r-lg) var(--r-lg) 0 0;
}

.column__dot {
  width: 0.5rem;
  height: 0.5rem;
  border-radius: 50%;
  background: var(--col, var(--muted));
  flex: none;
}

/* The stage's name in the sans, sentence case: it is a heading over a list,
   and the mono capitals are kept for labels on figures. */
.column__name {
  font-size: 0.875rem;
  font-weight: 650;
  letter-spacing: -0.005em;
  color: var(--text);
}

.column__n {
  margin-left: auto;
  font-family: var(--font-mono);
  font-size: 0.75rem;
  color: var(--muted);
}

.column__body {
  padding: 0.6rem;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  overflow-y: auto;
}

.kcard {
  padding: 0.75rem;
  border: 1px solid var(--line-soft);
  border-radius: var(--r-md);
  background: var(--surface);
  box-shadow: var(--shadow-sm);
  cursor: grab;
  display: grid;
  /* One column, held to the card's own width. Left implicit, the track is
     auto, and an auto track cannot shrink below its content's min-content -
     which, for a company line that does not wrap, is the whole name. A long
     employer made the track wider than the card, so the name and the title
     ran off its edge and the column scrolled sideways under them, while a
     short name like GitLab's looked fine. minmax(0, 1fr) is the cap. */
  grid-template-columns: minmax(0, 1fr);
  gap: 0.4rem;
}

.kcard:hover {
  border-color: var(--line);
}

.kcard.is-dragging {
  opacity: 0.4;
}

.kcard__top {
  display: flex;
  align-items: flex-start;
  gap: 0.6rem;
}

/* --- resumes as personas ------------------------------------------------- */
/* Each card is one resume and the role it is for. The one driving search is
   outlined in the accent, because which one that is, is the only question
   this list exists to answer. */
/* Tiles rather than rows: a resume is a document, and five of them read
   faster as five pages than as five lines of metadata. auto-fill so the row
   wraps on its own at any width instead of at breakpoints we guessed. */
.resumes {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(168px, 1fr));
  gap: 0.75rem;
  align-items: start;
}

/* The empty note is a sentence, not a tile - it spans whatever the row is. */
#resumes-empty {
  grid-column: 1 / -1;
}

.resume {
  display: flex;
  flex-direction: column;
  gap: 0.45rem;
  padding: 0.55rem;
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: var(--surface);
  min-width: 0;
}

/* The page itself. A fixed 8.5:11 box whatever the render came back as, so a
   row of tiles is a row of pages rather than a ragged edge - and so the box
   is the same size before the image arrives as after, which is what stops the
   row reflowing as five of them load. */
.resume__page {
  position: relative;
  display: block;
  width: 100%;
  aspect-ratio: 8.5 / 11;
  padding: 0;
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  background: #fff;
  overflow: hidden;
  cursor: pointer;
  /* The tile is a button; it must not inherit the button reset's centering. */
  font: inherit;
  color: inherit;
}

.resume__page img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: top center;
}

.resume__page:hover,
.resume__page:focus-visible {
  border-color: var(--accent);
  box-shadow: 0 0 0 2px var(--accent-wash);
}

/* No page image: a PDF too large to keep, or a format nothing renders. The
   tile still has to look like a document rather than a hole, so it says what
   it is in the space the page would have occupied. */
.resume__page--none {
  display: grid;
  place-content: center;
  gap: 0.3rem;
  padding: 0.6rem;
  background: var(--surface-2);
  text-align: center;
  color: var(--faint);
  font-size: 0.75rem;
  line-height: 1.35;
  cursor: default;
}

.resume__pages {
  position: absolute;
  right: 0.3rem;
  bottom: 0.3rem;
  padding: 0.1rem 0.35rem;
  border-radius: var(--r-sm);
  background: rgba(0, 0, 0, 0.62);
  color: #fff;
  font-family: var(--font-mono);
  font-size: 0.75rem;
  line-height: 1.5;
}

.resume--on {
  border-color: var(--accent);
  background: var(--accent-wash);
}

/* Under the page. The name takes a line of its own and the controls sit
   beneath it: at 168px a flex row squeezed the name input to a sliver, since
   "Using for search" and the × will not shrink and it was the only thing that
   could. */
.resume__head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.3rem;
}

.resume__head .resume__name {
  flex: 1 0 100%;
}

/* The tag or the button takes the rest of the row, with the × beside it. */
.resume__head .tag,
.resume__head .btn {
  flex: 1;
  min-width: 0;
  text-align: center;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* The name is editable in place: a file is called resume-final-v3.pdf and a
   toggle should not be. */
.resume__name {
  flex: 1;
  min-width: 0;
  padding: 0.3rem 0.45rem;
  font-weight: 600;
}

/* A flex item, unlike .search-item__del which is absolutely positioned for a
   different layout - reusing that class here put the × outside the card. */
.resume__del {
  flex: none;
  border: 0;
  background: transparent;
  color: var(--faint);
  font: inherit;
  font-size: 1.05rem;
  line-height: 1;
  padding: 0.25rem 0.4rem;
  border-radius: var(--r-sm);
  cursor: pointer;
}

.resume__del:hover {
  color: var(--danger);
  background: var(--surface-3);
}

/* Two lines at most: at tile width the full list wrapped to three and turned
   a page of documents into a page of text. */
.resume__meta {
  margin: 0;
  font-size: 0.75rem;
  line-height: 1.35;
  color: var(--faint);
  overflow-wrap: anywhere;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

/* --- the viewer ----------------------------------------------------------
   A resume opened full size. Not the browser's PDF viewer - object-src and
   frame-src are 'none', so pages are rasterised to a canvas by pdf.js and
   this is the frame around them. */
.docview {
  position: fixed;
  inset: 0;
  z-index: 70;
  display: flex;
  flex-direction: column;
  background: rgba(8, 10, 14, 0.82);
  backdrop-filter: blur(2px);
}

.docview__bar {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  flex-wrap: wrap;
  padding: 0.55rem 0.75rem;
  padding-top: calc(0.55rem + env(safe-area-inset-top, 0px));
  background: var(--surface);
  border-bottom: 1px solid var(--line);
}

.docview__name {
  flex: 1;
  min-width: 8rem;
  font-weight: 600;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.docview__count {
  font-family: var(--font-mono);
  font-size: 0.75rem;
  color: var(--faint);
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}

/* The scroller. Pages are laid out in one column and the whole document is
   scrollable, which is how every document reader behaves and means page
   navigation is a scroll rather than a re-render. */
.docview__pages {
  flex: 1;
  overflow: auto;
  padding: 1rem 0.75rem calc(1rem + env(safe-area-inset-bottom, 0px));
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.75rem;
  -webkit-overflow-scrolling: touch;
}

.docview__page {
  display: block;
  max-width: 100%;
  background: #fff;
  border-radius: 2px;
  box-shadow: 0 2px 14px rgba(0, 0, 0, 0.35);
}

.docview__note {
  margin: 2rem auto;
  max-width: 28rem;
  padding: 1rem;
  border-radius: var(--r-md);
  background: var(--surface);
  color: var(--text);
  text-align: center;
}

.tag--on {
  border-color: var(--accent);
  color: var(--accent);
  white-space: nowrap;
}

@media (max-width: 560px) {
  .resume__head {
    flex-wrap: wrap;
  }
  .resume__name {
    flex-basis: 100%;
  }
}

/* --- company logos ------------------------------------------------------- */
/* One fixed box whatever is in it. The monogram and the image are the same
   size, so the logo arriving after the row is painted swaps one for the other
   without moving a pixel of the row around it - which is the whole reason the
   monogram exists rather than an empty space that fills in later. */
.clogo {
  flex: none;
  display: inline-grid;
  place-items: center;
  width: 3rem;
  height: 3rem;
  border-radius: 12px;
  overflow: hidden;
  vertical-align: middle;
}

/* A name we could not key at all - no letters in it. Holds the space so a
   row with one is still aligned with every row that has a logo. */
.clogo--none {
  visibility: hidden;
}

.clogo__img {
  width: 100%;
  height: 100%;
  /* contain, not cover: a wordmark cropped to a square is unreadable, and
     these come from whatever favicon an employer happens to serve. */
  object-fit: contain;
  display: block;
}

/* An icon smaller than the tile. The script sets the image to its own pixel
   size - a 32px favicon is drawn at 32px, never scaled up to 48 and blurred -
   and the tile takes a quiet ground so the small mark sits in it rather than
   floating beside the row. Same box as every other state, so nothing moves. */
.clogo--small {
  background: var(--surface-2);
}

.clogo--small .clogo__img {
  /* Sized inline by the script; this only stops a size larger than the box
     from ever getting through. */
  max-width: 100%;
  max-height: 100%;
}

/* The monogram. Hue is per employer and stable, so the same company is the
   same colour on every screen without anything being stored about it. */
.clogo--mono {
  background: hsl(var(--clogo-hue, 0) 40% 88%);
  color: hsl(var(--clogo-hue, 0) 55% 24%);
}

:root[data-theme='dark'] .clogo--mono {
  background: hsl(var(--clogo-hue, 0) 26% 22%);
  color: hsl(var(--clogo-hue, 0) 55% 78%);
}

.clogo__mono {
  font-family: var(--font-mono);
  /* Scales with the box. The monogram and the image occupy the same square,
     so a monogram left at the old size would sit in a puddle of background
     and the swap to a real logo would read as a size change. */
  font-size: 1.125rem;
  font-weight: 700;
  letter-spacing: 0.02em;
  line-height: 1;
}

/* The Role cell: logo at the left edge, the title and its reason beside it.
   This is what a column between Posted and Role would have looked like,
   without being a column - see the note at the call site. */
.job__role {
  display: flex;
  align-items: flex-start;
  gap: 0.5rem;
  min-width: 0;
}

.job__role-text {
  min-width: 0;
  flex: 1;
}

/* The title is block-level inside its own wrapper, as it was when it was the
   cell's direct child. The reason used to sit under it and no longer does -
   it is in the drawer, where a sentence has room. */
.job__role-text .job__title {
  display: block;
}

.kcard__company {
  /* Two lines, then an ellipsis. Beside the logo and the score the name has
     about ten capitals of room, so a single line cut most employers down to
     "TAILWIND SY..." - the name is what people scan a column for, and two
     lines hold nearly all of them whole. The trial board on the landing page
     already wraps its company line the same way. */
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  overflow: hidden;
  overflow-wrap: anywhere;
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  font-weight: 500;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  /* Muted, not the accent: on a board of white cards the accent is the
     score, and a column of blue employer names drowned it. */
  color: var(--muted);
  margin-bottom: 0.2rem;
}

.kcard__title {
  display: block;
  font-size: 0.875rem;
  font-weight: 600;
  letter-spacing: -0.01em;
  line-height: 1.35;
  /* A word longer than the column is broken rather than pushed past the
     card's edge. Ordinary titles still break between words. */
  overflow-wrap: anywhere;
}

.kcard .score {
  width: 2.6rem;
  flex: none;
}

.kcard__meta {
  font-family: var(--font-mono);
  font-size: 0.75rem;
  color: var(--faint);
  overflow-wrap: anywhere;
}

.kcard__move {
  display: flex;
  gap: 0.25rem;
}

/* The keyboard path to reorder: buttons appear on focus so drag-and-drop is
   never the only way to move a card. */
.kcard__move button {
  padding: 0.15rem 0.45rem;
  font-size: 0.625rem;
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  background: transparent;
  color: var(--muted);
  cursor: pointer;
  opacity: 0;
  transition: opacity 0.15s var(--ease);
}

.kcard:hover .kcard__move button,
.kcard:focus-within .kcard__move button {
  opacity: 1;
}

/* A finger cannot hover. Where there is no hover at all, the reveal device
   has nothing to attach to and the arrows simply never appeared - leaving
   drag-and-drop, which touch cannot do either, as the only visible way to
   move a card. */
@media (hover: none) {
  .kcard__move button {
    opacity: 1;
    padding: 0.4rem 0.7rem;
  }
}

.kcard__move button:hover {
  background: var(--surface-3);
  color: var(--text);
}

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

.empty {
  display: grid;
  place-items: center;
  gap: 0.6rem;
  padding: clamp(2.5rem, 8vw, 5rem) 1rem;
  text-align: center;
  border: 1px dashed var(--line);
  border-radius: var(--r-lg);
  margin-top: 1rem;
}

.empty h3 {
  font-size: 1.15rem;
}

.empty p {
  color: var(--muted);
  font-size: 0.9375rem;
  max-width: 34rem;
}

/* --- drawer ------------------------------------------------------------- */

.drawer {
  position: fixed;
  top: 0;
  right: 0;
  z-index: 70;
  width: min(30rem, 100vw);
  height: 100vh;
  /* Dynamic viewport units where they exist: on iOS, 100vh sits under the
     URL bar and the drawer's foot is unreachable. */
  height: 100dvh;
  background: var(--surface);
  border-left: 1px solid var(--line);
  overflow-y: auto;
  box-shadow: var(--shadow-edge);
}

.drawer__scrim {
  position: fixed;
  inset: 0;
  z-index: 65;
  background: var(--scrim);
}

.drawer__inner {
  padding: clamp(1.1rem, 3vw, 1.75rem);
  display: grid;
  /* Held to the drawer's width, for the reason the board card is (.kcard):
     an implicit auto track grows to its widest content, and a long board
     name in the move picker or one long word in a title pushed the drawer
     past its own edge and its Close button off the screen. */
  grid-template-columns: minmax(0, 1fr);
  gap: 1.1rem;
}

.drawer__head {
  display: flex;
  align-items: flex-start;
  gap: 1rem;
}

.drawer__title {
  font-size: 1.3rem;
  flex: 1;
}

.drawer__title,
.drawer__head .muted {
  overflow-wrap: anywhere;
}

.drawer__meta {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
}

.tag {
  padding: 0.22rem 0.6rem;
  border-radius: var(--r-pill);
  border: 1px solid var(--line);
  background: var(--surface-2);
  font-family: var(--font-mono);
  font-size: 0.625rem;
  color: var(--muted);
}

/* The employer has pulled the listing. Only ever appears on jobs the user
   already acted on - everything else is archived out of view. */
/* The listing came down, but the user is already in process - a note rather
   than a warning, so it stays neutral. */
.tag--noted {
  margin-left: 0.5rem;
  border-color: var(--line);
  background: var(--surface-3);
  color: var(--muted);
  white-space: nowrap;
}

/* We fetched the posting and it answered. */
.tag--live {
  border-color: color-mix(in srgb, var(--ok) 40%, transparent);
  background: color-mix(in srgb, var(--ok) 9%, transparent);
  color: var(--ok);
}

.tag--closed {
  margin-left: 0.5rem;
  border-color: color-mix(in srgb, var(--danger) 40%, transparent);
  background: color-mix(in srgb, var(--danger) 9%, transparent);
  color: var(--danger);
  white-space: nowrap;
}

.drawer__why {
  padding: 0.85rem 1rem;
  border-radius: var(--r-md);
  border: 1px solid color-mix(in srgb, var(--accent) 25%, transparent);
  background: var(--accent-wash);
  font-size: 0.875rem;
  line-height: 1.55;
}

.drawer__why .label {
  color: var(--accent);
  margin-bottom: 0.3rem;
}

.drawer__desc {
  font-size: 0.875rem;
  line-height: 1.65;
  color: var(--muted);
  white-space: pre-wrap;
  max-height: 22rem;
  overflow-y: auto;
  padding-right: 0.5rem;
}

.drawer__actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
}

/* The move and send pickers size to their longest option, which is a board
   name of up to 80 characters. */
.drawer__actions .select {
  max-width: 100%;
}

/* --- modal -------------------------------------------------------------- */

.modal {
  position: fixed;
  inset: 0;
  z-index: 80;
  display: grid;
  place-items: center;
  padding: 1rem;
}

.modal__scrim {
  position: absolute;
  inset: 0;
  background: var(--scrim);
  backdrop-filter: blur(3px);
}

.modal__panel {
  position: relative;
  width: min(34rem, 100%);
  max-height: min(85vh, 46rem);
  display: flex;
  flex-direction: column;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  box-shadow: var(--shadow);
}

.modal__panel--wide {
  width: min(46rem, 100%);
}

.modal__head {
  display: flex;
  align-items: center;
  gap: 1rem;
  padding: 1.1rem 1.25rem;
  border-bottom: 1px solid var(--line-soft);
}

.modal__head h2 {
  font-size: 1.1rem;
  flex: 1;
}

.modal__body {
  padding: 1.25rem;
  overflow-y: auto;
}

.modal__foot {
  display: flex;
  justify-content: flex-end;
  gap: 0.6rem;
  margin-top: 1.25rem;
  padding-top: 1.1rem;
  border-top: 1px solid var(--line-soft);
}

.row {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 0.9rem;
}

/* --- required fields ---------------------------------------------------- */

.req {
  margin-left: 0.5rem;
  padding: 0.1rem 0.4rem;
  border-radius: var(--r-pill);
  background: var(--accent-wash);
  color: var(--accent);
  font-size: 0.625rem;
  letter-spacing: 0.1em;
}

.input.is-invalid,
.textarea.is-invalid {
  border-color: var(--danger);
  background: color-mix(in srgb, var(--danger) 6%, var(--surface));
}

/* --- advanced disclosure ------------------------------------------------ */

.advanced {
  margin-top: 1.75rem;
  border: 1px solid var(--line-soft);
  border-radius: var(--r-md);
  background: var(--surface-2);
}

.advanced__summary {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  padding: 0.85rem 1rem;
  cursor: pointer;
  font-size: 0.9375rem;
  font-weight: 600;
  list-style: none;
  user-select: none;
}

/* Replace the default marker with a small chevron in the accent, and rotate
   it on open. */
.advanced__summary::-webkit-details-marker {
  display: none;
}

.advanced__summary::before {
  content: '';
  width: 0.4rem;
  height: 0.4rem;
  border-right: 2px solid var(--accent);
  border-bottom: 2px solid var(--accent);
  transform: rotate(-45deg);
  transition: transform 0.18s var(--ease);
  flex: none;
  margin-left: 0.15rem;
}

.advanced[open] .advanced__summary::before {
  transform: rotate(45deg);
}

.advanced__summary:hover {
  color: var(--text);
}

.advanced__hint {
  margin-left: auto;
  font-size: 0.75rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--faint);
}

.advanced__body {
  padding: 0 1rem 1rem;
  border-top: 1px solid var(--line-soft);
  padding-top: 1rem;
}

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

/* --- settings sections -------------------------------------------------- */

.panel {
  border: 1px solid var(--line-soft);
  border-radius: var(--r-md);
  padding: 1.1rem;
  margin-bottom: 1rem;
}

.panel > h3 {
  font-size: 0.9375rem;
  margin-bottom: 0.3rem;
}

.panel > p {
  font-size: 0.875rem;
  color: var(--muted);
  margin-bottom: 1rem;
}

.list-rows {
  display: grid;
  gap: 0.5rem;
}

.list-row {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  padding: 0.7rem 0.85rem;
  border: 1px solid var(--line-soft);
  border-radius: var(--r-md);
  background: var(--surface-2);
  font-size: 0.875rem;
}

.list-row__main {
  flex: 1;
  min-width: 0;
}

.list-row__title {
  font-weight: 600;
  display: block;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.list-row__sub {
  font-family: var(--font-mono);
  font-size: 0.75rem;
  color: var(--faint);
}

.dot {
  width: 0.5rem;
  height: 0.5rem;
  border-radius: 50%;
  flex: none;
  background: var(--faint);
}

.dot--ok {
  background: var(--ok);
}
.dot--error {
  background: var(--danger);
}

/* --- MFA enrollment ----------------------------------------------------- */

.qr {
  display: grid;
  place-items: center;
  padding: 1rem;
  border-radius: var(--r-md);
  background: #fff;
  width: fit-content;
  margin: 0 auto 1rem;
}

.qr svg {
  display: block;
}

.secret {
  padding: 0.7rem 0.9rem;
  border-radius: var(--r-md);
  border: 1px dashed var(--line);
  background: var(--surface-2);
  font-family: var(--font-mono);
  font-size: 0.9375rem;
  letter-spacing: 0.08em;
  text-align: center;
  word-break: break-all;
}

.codes {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(7rem, 1fr));
  gap: 0.4rem;
  margin: 1rem 0;
}

.codes code {
  padding: 0.5rem;
  border-radius: var(--r-sm);
  background: var(--surface-2);
  border: 1px solid var(--line-soft);
  font-family: var(--font-mono);
  font-size: 0.875rem;
  text-align: center;
  letter-spacing: 0.06em;
}

/* --- skill gap rows ----------------------------------------------------- */
/* A bar chart rather than a list: the whole point is which gaps are bigger
   than the others, and a column of numbers makes that comparison work the
   reader has to do. */

.gap-row {
  display: grid;
  grid-template-columns: 9rem 1fr 8rem;
  align-items: center;
  gap: 0.75rem;
  padding: 0.4rem 0;
}

.gap-row__name {
  font-size: 0.875rem;
  font-weight: 600;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.gap-row__bar {
  display: block;
  height: 8px;
  border-radius: 4px;
  background: var(--surface-3);
  overflow: hidden;
}

.gap-row__bar i {
  display: block;
  height: 100%;
  border-radius: 4px;
  background: var(--warn);
}

.gap-row__n {
  font-size: 0.75rem;
  color: var(--muted);
  text-align: right;
  white-space: nowrap;
}

@media (max-width: 560px) {
  .gap-row {
    grid-template-columns: 7rem 1fr;
  }
  .gap-row__n {
    display: none;
  }
}

/* --- toast -------------------------------------------------------------- */

.toast {
  position: fixed;
  left: 50%;
  bottom: 1.5rem;
  transform: translateX(-50%);
  z-index: 100;
  padding: 0.7rem 1.1rem;
  border-radius: var(--r-md);
  background: var(--surface);
  border: 1px solid var(--line);
  color: var(--text);
  font-size: 0.875rem;
  box-shadow: var(--shadow);
  max-width: calc(100vw - 2rem);
}

.toast--error {
  border-color: color-mix(in srgb, var(--danger) 50%, transparent);
  color: var(--danger);
}

/* --- CV dropzone -------------------------------------------------------- */
/* The file is parsed in the browser and never uploaded, which is worth saying
   on the control itself rather than burying in a privacy page. */

.dropzone {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.4rem;
  padding: 1.4rem 1rem;
  margin-bottom: 0.75rem;
  border: 1px dashed var(--line);
  border-radius: var(--r-md);
  background: var(--surface-2);
  text-align: center;
  font-size: 0.875rem;
  color: var(--muted);
  transition: border-color 0.15s var(--ease), background 0.15s var(--ease);
}

.dropzone.is-over {
  border-color: var(--accent);
  border-style: solid;
  background: var(--accent-wash);
}

.dropzone__icon {
  font-size: 1.25rem;
  line-height: 1;
  color: var(--accent);
}

.dropzone__hint {
  font-size: 0.75rem;
  letter-spacing: 0.04em;
  color: var(--faint);
}

/* --- search view & drag-to-board -------------------------------------- */

.search-form {
  max-width: 46rem;
  /* Tight: the results are the point, and the run button now shares the
     filters row, so nothing below the form earns extra air. */
  margin: 0 0 0.75rem;
}

/* Where a search came from, worn above the sentence. A profile-built slot
   wears the accent and lists its borrowed pieces as chips; a typed one is a
   basic search and says so quietly. */
.search-provenance {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  flex-wrap: wrap;
  margin-bottom: 0.5rem;
}

.search-kind {
  display: inline-flex;
  align-items: center;
  padding: 0.18rem 0.55rem;
  border: 1px solid var(--line);
  border-radius: var(--r-pill);
  font-family: var(--font-mono);
  font-size: 0.625rem;
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--muted);
  white-space: nowrap;
}

.search-kind--profile {
  border-color: transparent;
  background: var(--accent-wash);
  color: var(--accent);
}

/* The panel row's compact form of the same fact. */
.search-item__kind {
  display: inline-flex;
  flex: none;
  color: var(--accent);
}
.search-item__kind--ai {
  color: var(--ai, #7c3aed);
}

/* The other three kinds' tags: advanced wears the line a little darker,
   the AI one its own hue, so the four read as four at a glance. */
.search-kind--advanced {
  border-color: var(--text);
  color: var(--text);
}
.search-kind--ai {
  gap: 0.35rem;
  border-color: transparent;
  background: color-mix(in srgb, var(--ai, #7c3aed) 12%, transparent);
  color: var(--ai, #7c3aed);
}
.search-kind--ai .beta-tag {
  margin-left: 0;
}

/* Beta / New: two small marks worn by the newer searches, in the tab and
   the tag. Same shape as the profile's Optional tag. */
.beta-tag,
.new-tag {
  display: inline-block;
  margin-left: 0.4rem;
  padding: 0.12rem 0.4rem;
  border-radius: var(--r-pill);
  font-family: var(--font-mono);
  font-size: 0.5625rem;
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  vertical-align: 0.12em;
  white-space: nowrap;
}
.beta-tag {
  background: var(--ai, #7c3aed);
  color: #fff;
}
.new-tag {
  background: var(--accent-wash);
  color: var(--accent);
}

/* --- the four ways to search ------------------------------------------- */

/* The tabs over the form. One row: the lede, then the segmented control;
   under it, one line saying what the picked kind does. */
.search-modes {
  max-width: 46rem;
  margin: 0 0 0.9rem;
}
.search-modes__row {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  flex-wrap: wrap;
}
.search-modes__lede {
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--faint);
  white-space: nowrap;
}
.search-modes__tabs {
  flex-wrap: wrap;
}
.search-modes__tab {
  display: inline-flex;
  align-items: center;
}
.search-modes__tab .beta-tag,
.search-modes__tab .new-tag {
  margin-left: 0.35rem;
}
.search-modes__blurb {
  margin: 0.45rem 0 0;
  font-size: 0.8125rem;
  color: var(--muted);
}

/* The Profile search's builder: the resume's titles as one-tap searches. */
.psearch-panel {
  max-width: 46rem;
  margin: 0 0 0.9rem;
  padding: 0.9rem 1rem;
  border: 1px solid var(--line);
  border-radius: var(--r-md, 10px);
  background: var(--surface-2);
}
.psearch-panel h3 {
  margin: 0 0 0.3rem;
  font-size: 1rem;
}
.psearch-panel p.muted {
  margin: 0 0 0.6rem;
  font-size: 0.875rem;
}
.psearch-panel__titles {
  display: flex;
  flex-wrap: wrap;
  gap: 0.35rem;
  margin-bottom: 0.7rem;
}
.psearch-panel__actions {
  display: flex;
  align-items: center;
  gap: 0.9rem;
  flex-wrap: wrap;
}
.psearch-panel--folded {
  padding: 0.55rem 1rem;
}
.psearch-panel--folded[open] {
  padding-bottom: 0.9rem;
}
.psearch-panel__summary {
  cursor: pointer;
  font-size: 0.8125rem;
  color: var(--muted);
}
.psearch-panel--folded[open] .psearch-panel__summary {
  margin-bottom: 0.6rem;
}

/* The AI search: the brief, its button, and what the model understood. */
.ai-panel {
  max-width: 46rem;
  margin: 0 0 0.9rem;
  padding: 0.9rem 1rem;
  border: 1px solid color-mix(in srgb, var(--ai, #7c3aed) 35%, var(--line));
  border-radius: var(--r-md, 10px);
  background: color-mix(in srgb, var(--ai, #7c3aed) 5%, var(--surface));
}
.ai-panel__head {
  display: flex;
  align-items: center;
  gap: 0.3rem;
  margin-bottom: 0.3rem;
}
.ai-panel__head h3 {
  margin: 0;
  font-size: 1rem;
}
.ai-panel > p.muted {
  margin: 0 0 0.6rem;
  font-size: 0.875rem;
}
.ai-brief {
  min-height: 6rem;
  font-size: 1rem;
}
.ai-panel__row {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  flex-wrap: wrap;
  margin-top: 0.6rem;
  font-size: 0.875rem;
  color: var(--muted);
}
.ai-panel__hint {
  margin: 0;
}
.ai-panel__row .btn {
  margin-left: auto;
}
.ai-panel .search-progress {
  margin-top: 0.6rem;
}
.ai-panel__error {
  margin-top: 0.6rem;
}
.ai-plan {
  margin-top: 0.8rem;
  padding: 0.7rem 0.85rem;
  border-left: 3px solid var(--ai, #7c3aed);
  background: var(--surface-2);
  border-radius: 0 var(--r-sm) var(--r-sm) 0;
}
.ai-plan__head {
  margin: 0 0 0.3rem;
  font-size: 0.6875rem;
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ai, #7c3aed);
}
.ai-plan__summary {
  margin: 0 0 0.4rem;
  font-size: 0.9375rem;
}
.ai-plan__notes {
  margin: 0 0 0.4rem;
  padding-left: 1.1rem;
  font-size: 0.8125rem;
  color: var(--muted);
}
.ai-plan > .hint {
  margin: 0;
}

/* The advanced form's second row: labelled controls, wrapping, the run
   button at the end. */
.search-form__adv {
  display: flex;
  align-items: flex-end;
  gap: 0.6rem 0.9rem;
  flex-wrap: wrap;
  margin-top: 0.6rem;
  padding-top: 0.6rem;
  border-top: 1px dashed var(--line-soft);
  font-size: 0.875rem;
  color: var(--muted);
}
.search-adv__field {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
  min-width: 0;
}
.search-adv__label {
  font-family: var(--font-mono);
  font-size: 0.625rem;
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--faint);
}
.search-adv__select {
  /* base.css sets a select full-width; here it sizes to its words, so a box
     and its select share a line. */
  width: auto;
  min-width: 9rem;
  max-width: 14rem;
  font-size: 0.8125rem;
}
.search-adv__input {
  width: 14rem;
  max-width: 100%;
  font-size: 0.8125rem;
}
/* A box and the select that says how it presses, side by side under one label. */
.search-adv__pair {
  display: flex;
  gap: 0.4rem;
}
.search-form__adv > .btn {
  margin-left: auto;
}
.search-prompt {
  min-height: 5.5rem;
  font-size: 1.05rem;
}
/* The pre-search limits: hard constraints the gate enforces, sitting with
   the prompt because they change what the search reads, not how the loaded
   results are narrowed. */
.search-form__opts {
  display: flex;
  align-items: center;
  /* Tight enough that all three toggles share one row on a phone - the
     wrap stays as the fallback for the very narrowest screens. */
  gap: 0.75rem;
  flex-wrap: wrap;
  margin-top: 0.6rem;
  font-size: 0.875rem;
  color: var(--muted);
}
.search-form__opts .checkbox {
  white-space: nowrap;
}
/* The run button sits on the filters row, pushed to its right edge. */
.search-form__opts .btn {
  margin-left: auto;
}
/* The search's own history: one muted line under the form, opening into the
   full trail on request. */
.search-audit {
  display: flex;
  align-items: baseline;
  gap: 0.6rem;
  flex-wrap: wrap;
  margin-top: 0.45rem;
  font-size: 0.75rem;
  color: var(--faint);
}
.search-audit__toggle {
  border: 0;
  background: none;
  padding: 0;
  font: inherit;
  color: var(--muted);
  cursor: pointer;
  text-decoration: underline;
  text-underline-offset: 2px;
}
.search-audit__toggle:hover {
  color: var(--text);
}

/* What the limits held back, under the run line, and one tap to lift each.
   The offers are plain chips: they search again, they do not select. */
.held-back {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.35rem 0.4rem;
  margin-top: 0.6rem;
  font-size: 0.8125rem;
  color: var(--muted);
}
.held-back__lead {
  margin-right: 0.2rem;
}
.held-back__done,
.held-back__off {
  flex: 1 0 100%;
  margin: 0 0 0.15rem;
}
.held-back__done {
  font-size: 0.875rem;
  color: var(--text);
}
.held-back__done:focus {
  outline: none;
}
.held-back > .hint {
  flex: 1 0 100%;
  margin-top: 0.1rem;
}
/* A note inside an offer ("the page holds 100"): sans and muted, not the mono
   digit style of .chip__n - it is a phrase, not a count. */
.chip__note {
  margin-left: 0.3rem;
  font-weight: 400;
  color: var(--muted);
}
.empty .held-back {
  justify-content: center;
  max-width: 46rem;
  margin: 0.2rem auto 0;
}
/* The small print stays small inside the empty box, whose own paragraphs
   are set larger. */
.empty .held-back > .hint {
  max-width: 34rem;
  margin: 0.35rem auto 0;
  font-size: 0.8125rem;
  color: var(--faint);
  text-align: center;
}
@media (max-width: 880px) {
  /* Undo and Use it: a 44px target for a thumb, with an equal negative
     margin so the line they sit on does not move. */
  .held-back__done .linkish,
  .held-back__off .linkish {
    padding: 0.8rem 0.35rem;
    margin: -0.8rem -0.35rem;
  }
}
@media (max-width: 640px) {
  /* Long offers wrap inside their chip rather than widening the page, and
     the note takes a line of its own. */
  .held-back .chip {
    white-space: normal;
    text-align: left;
    max-width: 100%;
  }
  .held-back .chip__note {
    display: block;
    margin-left: 0;
  }
}
.search-audit__panel {
  flex: 1 1 100%;
  margin: 0.35rem 0 0;
  padding: 0.6rem 0.75rem;
  border: 1px solid var(--line-soft);
  border-radius: 8px;
  background: var(--surface-2);
  display: grid;
  gap: 0.3rem;
}
.search-audit__row {
  display: grid;
  grid-template-columns: 6.5rem 1fr;
  gap: 0.6rem;
}
.search-audit__row dt {
  color: var(--faint);
}
.search-audit__row dd {
  margin: 0;
  color: var(--muted);
}
/* The optional place limit rides the same row as the toggles; compact, and
   full-width only when the row has already wrapped on a narrow screen.
   Small basis, capped growth - not a fixed width: at full width the row
   totals ~49rem against the form's 46rem cap, and in a wrapping flex row
   items wrap BEFORE they shrink (measured in a real render, not assumed),
   which put the search button back on its own line - the exact layout
   moving it to this row was meant to end. A 7rem basis always fits the
   line; leftover space then grows the inputs toward their comfortable
   widths. */
.input--place {
  flex: 1 1 6.5rem;
  max-width: 12.5rem;
  padding: 0.3rem 0.55rem;
  font-size: 0.8125rem;
  /* On a desktop the shared row can squeeze this box below its placeholder's
     width, and an input clips overflow at the border - the text ended mid
     glyph, reading as spill past the box. Ellipsis covers placeholder and
     value alike. */
  text-overflow: ellipsis;
}
.input--salary {
  flex: 1 1 5rem;
  max-width: 7.5rem;
  padding: 0.3rem 0.55rem;
  font-size: 0.8125rem;
}
/* Sized like the place box it sits beside, and for the same reason: a
   comma-separated list of employers is longer than a salary and shorter than
   a sentence, and the row has to keep the search button on its own line. */
.input--company {
  flex: 1 1 6.5rem;
  max-width: 12.5rem;
  padding: 0.3rem 0.55rem;
  font-size: 0.8125rem;
  text-overflow: ellipsis;
}
@media (max-width: 640px) {
  .input--place,
  .input--company,
  .input--salary {
    width: 100%;
    flex: 1 1 100%;
  }
}
/* Below 375px the two inputs stop fitting on one line at their comfortable
   caps and take a row each, which is where the taller toggle row would have
   cost a 320px screen more height than grouping the toggles saved it. Pulled
   in just far enough to pair again: measured, the placeholders still read in
   full at this width. */
@media (max-width: 374px) {
  .input--place {
    max-width: 10.5rem;
  }
  .input--salary {
    max-width: 6.25rem;
  }
}

/* The in-flight bar. A search reads thirty-odd employer boards; the fill is
   a CSS animation sized to the typical fifteen seconds, resumed rather than
   restarted on re-render (negative animation-delay from the caller). */
.search-progress {
  max-width: 46rem;
}
.progress {
  height: 6px;
  border-radius: 999px;
  background: var(--surface-3);
  overflow: hidden;
  margin: 1.75rem 0 0.6rem;
}
.progress__fill {
  height: 100%;
  width: 0;
  border-radius: inherit;
  background: var(--accent);
  /* Paced to the index-first search: the index answers in a few seconds and
     hydration a few more, so the bar eases over that. The curve leaves slow
     headroom at the top for the rare cold-start live read. */
  animation: progress-fill 25s cubic-bezier(0.2, 0.6, 0.3, 1) forwards;
}
@keyframes progress-fill {
  0% { width: 2%; }
  70% { width: 82%; }
  100% { width: 96%; }
}

/* Sticky sits on the host div rather than the bar itself: the bar's parent is
   exactly bar-sized, and a sticky element cannot leave its containing block -
   on the bar it had zero travel and scrolled away with the page. */
.send-bar-host {
  position: sticky;
  /* Below the topbar, which is sticky itself and wins the z-order: at top: 0
     the bar stuck invisibly behind the topbar's blur. The height is measured
     into --topbar-h by wireApp, because it varies by route and viewport. */
  top: calc(var(--topbar-h, 3.5rem) + 0.4rem);
  z-index: 5;
}

.send-bar {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.5rem 0.75rem;
  margin-bottom: 0.5rem;
  border: 1px solid var(--line);
  border-radius: 10px;
  background: var(--surface-2);
}
.send-bar__count {
  font-size: 0.8rem;
  color: var(--muted);
}
/* The picker is as wide as its longest option - a board name, up to 80
   characters - and one long name pushed Send off the right edge and made
   the whole page scroll sideways. A cap rather than min-width: 0, which on
   a phone would split the row with the spacer and cut the picker's own
   "Select jobs to send" down to "Select j". */
.send-bar .select {
  max-width: 16rem;
}
.send-bar__name {
  width: 12rem;
}

.jobs--search tr {
  cursor: grab;
}
.jobs--search tr.is-selected {
  background: var(--accent-wash);
}
.jobs__pick {
  width: 2rem;
}

tr.is-dragging,
.kcard.is-dragging {
  opacity: 0.5;
}

/* While a drag is live, every board in the sidebar is a target; the one under
   the pointer says so. */
.is-dragging-jobs .board-item {
  outline: 1px dashed var(--line);
  outline-offset: -1px;
}
.board-item.is-target {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
}

/* Results toolbar: the board list's filter idiom, client-side over what one
   search already returned. */
.search-filters {
  margin: 0 0 0.6rem;
  display: flex;
  align-items: center;
  gap: 0.9rem;
  flex-wrap: wrap;
}
.search-filters .input--search {
  flex: 1 1 12rem;
  max-width: 18rem;
}

/* The seniority chips take a line of their own inside the filter row: there
   are up to seven of them with counts, and inline they pushed the min-score
   slider onto a second line at every width below a wide desktop. */
.level-chips {
  flex: 1 0 100%;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.35rem;
}
.level-chips__label {
  font-size: 0.75rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted);
  margin-right: 0.25rem;
}
/* The levels picker on the search form: one summary in the filter row that
   says the selection ("Any level", "Senior, Staff / Lead", "3 levels"),
   opening into the two ladders as rows of chips. A native <details>, so it
   folds with the keyboard and needs no script to open or close. */
.levels-pick {
  position: relative;
}
.levels-pick__summary {
  list-style: none;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0.45rem 0.7rem;
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: var(--surface);
  color: var(--text);
  font-size: 0.875rem;
  white-space: nowrap;
  max-width: 16rem;
  overflow: hidden;
  text-overflow: ellipsis;
}
.levels-pick__summary::-webkit-details-marker {
  display: none;
}
.levels-pick__summary::after {
  content: '\25BE';
  color: var(--muted);
}
.levels-pick[open] .levels-pick__summary {
  border-color: var(--accent-dim);
}
.levels-pick__panel {
  position: absolute;
  z-index: 30;
  top: calc(100% + 0.35rem);
  left: 0;
  width: min(36rem, calc(100vw - 2rem));
  padding: 0.7rem 0.8rem;
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  background: var(--surface);
  box-shadow: var(--shadow);
  display: grid;
  gap: 0.55rem;
}
.levels-pick__track {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.35rem;
}
.levels-pick__track .level-chips__label {
  flex: 0 0 100%;
  margin: 0;
}
.levels-pick__foot .level-chips__label {
  flex: 0 0 auto;
  margin-right: 0.25rem;
}
/* Says how many rows the level chips are holding back, and undoes it. Not a
   chip: it is the way out of a narrowing, not another narrowing. */
.level-chips__clear {
  border: 0;
  background: transparent;
  color: var(--accent);
  font: inherit;
  font-size: 0.75rem;
  cursor: pointer;
  padding: 0.32rem 0.4rem;
  text-decoration: underline;
  text-underline-offset: 2px;
}
.level-chips__clear:hover {
  color: var(--text);
}
.jobs__none {
  padding: 1.2rem 0.8rem;
  color: var(--muted);
}
.jobs__none > .hint {
  margin-top: 0.5rem;
}
@media (max-width: 880px) {
  /* On a touch screen each way out is its own 44px row, and the separators
     between them go. */
  .jobs__none-offers {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    margin-top: 0.25rem;
  }
  .jobs__none-offers .linkish {
    min-height: 2.75rem;
    text-align: left;
  }
  .jobs__none-sep {
    display: none;
  }
}

/* A row the daily run brought in that the previous set did not contain. */
/* First appearance in this search: the accent, caps, same weight as the
   sent chip it shares the Status column with - sending the row replaces it.
   The grid's tag corner rather than a pill, like every label on a row. */
.chip-new {
  display: inline-block;
  margin-right: 0.35rem;
  padding: 0.1rem 0.4rem;
  border-radius: 5px;
  background: var(--accent-wash);
  border: 1px solid color-mix(in srgb, var(--accent) 30%, transparent);
  color: var(--accent);
  font-family: var(--font-mono);
  font-size: 0.625rem;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

/* Already on a board: quieter than NEW on purpose - it marks work done, not
   work waiting - with the full board list in the tooltip. */
.chip-sent {
  display: inline-block;
  margin-right: 0.35rem;
  padding: 0.1rem 0.4rem;
  border-radius: 5px;
  background: color-mix(in srgb, var(--info) 10%, transparent);
  border: 1px solid color-mix(in srgb, var(--info) 30%, transparent);
  color: var(--info);
  font-family: var(--font-mono);
  font-size: 0.625rem;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  cursor: help;
}
.jobs--search tr.is-sent .job__title {
  color: var(--muted);
}
/* The Status column: one chip or nothing. A long board name truncates
   inside the chip - the tooltip already carries the full list. */
.job__status .chip-sent {
  margin-right: 0;
  display: inline-block;
  max-width: 11rem;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  vertical-align: middle;
}
.jobs--search tr.is-sent {
  background: color-mix(in srgb, var(--info) 4%, transparent);
}
.jobs--search tr.is-sent.is-selected {
  background: var(--accent-wash);
}

/* --- result triage ------------------------------------------------------ */

/* Ten columns at a rem of side padding each is a fifth of the table gone
   to gutters; the search table keeps a narrower one. Doubled class, so
   this outranks the base .jobs td rule wherever it sits; the phone card's
   own reset (padding 0, tbody in the selector) still wins below 640px. */
.jobs.jobs--search thead th,
.jobs.jobs--search tbody td {
  padding-left: 0.7rem;
  padding-right: 0.7rem;
}
/* Open: an icon at desktop width, the word on the phone card. */
.row-open {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
}
.jobs--search .row-open {
  padding: 0.3rem 0.4rem;
}
.jobs--search .row-open__text {
  display: none;
}
.row-open__icon {
  display: inline-flex;
}

/* The Status cell holds one chip at most - on a board, previously viewed,
   or new - and nothing when there is none. The chips may break into two
   lines rather than hold the column open for their longest label. */
.job__status .chip-new,
.job__status .chip-viewed {
  margin-right: 0;
}
.jobs--search .job__status .chip-viewed,
.jobs--search .job__status .chip-sent {
  white-space: normal;
  max-width: none;
  line-height: 1.3;
}

/* Seen before, in this or any search: a quiet grey - it marks memory, not
   work waiting. Sending the row to a board replaces it. */
.chip-viewed {
  display: inline-block;
  padding: 0.1rem 0.4rem;
  border-radius: 5px;
  background: var(--surface-3);
  border: 1px solid transparent;
  color: var(--muted);
  font-family: var(--font-mono);
  font-size: 0.625rem;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  white-space: nowrap;
  cursor: help;
}

/* The verdict on the row itself: interested takes a wash of the accent its
   lit arrow wears, dismissed rows fade - they only show under the wider
   status filters. */
.jobs--search tr.is-interested {
  background: color-mix(in srgb, var(--accent) 5%, transparent);
}
.jobs--search tr.is-interested.is-selected {
  background: var(--accent-wash);
}
/* Faded, but only so far: under the Dismissed and Every-status filters
   these rows are the ones being read, to reconsider, and their text has to
   keep AA contrast on both grounds. */
.jobs--search tr.is-dismissed {
  opacity: 0.8;
}
.jobs--search tr.is-dismissed .job__title {
  color: var(--muted);
}
/* A row the active filter is about to hide fades before the table rebuilds,
   so a line does not simply vanish under the pointer. */
.jobs--search tr.is-leaving {
  opacity: 0;
  transition: opacity 0.2s var(--ease);
}

/* The desktop's swipe: a thumb each way in the actions cell. */
.jobs--search .row-actions {
  gap: 0.4rem;
  flex-wrap: nowrap;
  align-items: center;
}
.triage-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.6rem;
  height: 1.6rem;
  padding: 0;
  border-radius: 7px;
  border: 1px solid var(--line);
  background: transparent;
  color: var(--muted);
  cursor: pointer;
  transition: color 0.12s var(--ease), border-color 0.12s var(--ease), background 0.12s var(--ease);
}
.triage-btn:hover {
  color: var(--text);
  border-color: var(--text);
}
/* A lit arrow is filled, the way the landing page's are: kept in the
   accent - an upvote puts the job on a board, the one move on the row that
   does something - and passed in ink, a verdict on a row that already steps
   back. Hue is not the only cue: the fill itself says "on". */
.triage-btn--up.is-on {
  color: var(--accent-ink);
  border-color: var(--accent);
  background: var(--accent);
}
.triage-btn--down.is-on {
  color: var(--ink);
  border-color: var(--text);
  background: var(--text);
}
/* Reported: amber, so a flagged row never reads as merely not interested. */
.triage-btn--flag.is-on {
  color: var(--warn);
  border-color: color-mix(in srgb, var(--warn) 50%, transparent);
  background: color-mix(in srgb, var(--warn) 12%, transparent);
}

@container (width <= 56.5rem) {
  /* Thumb triage: the card follows a sideways drag and the verdict it is
     heading for shows in the gap it leaves. touch-action keeps vertical
     scrolling the browser's; only the sideways move is ours. The tbody
     clips, so a card sliding out never widens the page. */
  .jobs--search tbody {
    overflow: hidden;
  }
  .jobs--search tbody tr {
    position: relative;
    /* pinch-zoom stays the page's: pan-y alone would stop a two-finger
       zoom that happens to start on a card, which is most of the screen. */
    touch-action: pan-y pinch-zoom;
    transition: transform 0.18s var(--ease), background 0.12s var(--ease), opacity 0.2s var(--ease);
  }
  .jobs--search tbody tr.is-swiping {
    transition: none;
  }
  /* A dismissed card finishes the slide the swipe started - the same
     leaving class that only fades a desktop row carries the card off, and
     names transform in its own transition, which the desktop rule's
     specificity would otherwise drop. */
  .jobs--search tbody tr.is-leaving {
    transform: translateX(-110%);
    opacity: 0;
    transition: transform 0.2s var(--ease), opacity 0.2s var(--ease);
  }
  .jobs--search tbody tr.swipe-right {
    background: color-mix(in srgb, var(--accent) 9%, transparent);
  }
  .jobs--search tbody tr.swipe-left {
    background: color-mix(in srgb, var(--text) 6%, transparent);
  }
  .jobs--search tbody tr.swipe-commit.swipe-right {
    background: color-mix(in srgb, var(--accent) 20%, transparent);
  }
  .jobs--search tbody tr.swipe-commit.swipe-left {
    background: color-mix(in srgb, var(--text) 14%, transparent);
  }
  /* Absolutely positioned, so the label is not a grid item: it sits just
     outside the card's edge and comes into view as the card moves away. */
  .jobs--search tbody tr.swipe-right::after,
  .jobs--search tbody tr.swipe-left::after {
    position: absolute;
    top: 50%;
    transform: translateY(-50%);
    width: 7rem;
    font-family: var(--font-mono);
    font-size: 0.75rem;
    font-weight: 600;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    white-space: nowrap;
    pointer-events: none;
  }
  .jobs--search tbody tr.swipe-right::after {
    content: 'Upvote';
    right: 100%;
    text-align: right;
    padding-right: 0.8rem;
    color: var(--accent);
  }
  .jobs--search tbody tr.swipe-left::after {
    content: 'Downvote';
    left: 100%;
    text-align: left;
    padding-left: 0.8rem;
    color: var(--muted);
  }
  /* The arrows stay on the card: with no other control for the vote, the
     lit arrow is how a card says what it holds, and a tap is the way in
     for anyone the swipe does not reach. They and the flag are fingertip
     sized here, on an actions line of their own. */
  .jobs--search .row-actions {
    gap: 0.5rem;
  }
  .jobs--search .triage-btn {
    width: 2.75rem;
    height: 2.75rem;
  }
  /* The card has room for the word. */
  .jobs--search .row-open {
    padding: 0.45rem 0.9rem;
  }
  .jobs--search .row-open__text {
    display: inline;
  }
  .jobs--search .row-open__icon {
    display: none;
  }
  /* The card shows salary in its own slot; the column stand-down is a
     table matter. */
  .jobs--nopay td.job__pay {
    display: block;
  }
}

/* Below the same width the search results become cards like a board's rows
   do, but their columns differ - the checkbox and Open link where a board has
   status and notes - and the generic nth-child mapping filed the checkbox
   under score and the title under company. Same card philosophy, this
   table's own geography: the checkbox rides top-right under the thumb
   (tapping the row itself opens the drawer), Open sits at the foot, and the
   location line takes back the right-aligned nowrap that belongs to the pay
   cell. */
@container (width <= 56.5rem) {
  .jobs--search tbody tr {
    grid-template-columns: minmax(0, 1fr) auto auto auto;
    grid-template-areas:
      'title   title   title  pick'
      'company company company pick'
      'status  status  status status'
      'place   place   place  place'
      'score   age     pay    pay'
      'actions actions actions actions';
    gap: 0.2rem 0.6rem;
    padding: 0.75rem 0.85rem;
  }
  .jobs--search td:nth-child(1) { grid-area: pick; justify-self: end; }
  .jobs--search td:nth-child(2) { grid-area: score; }
  .jobs--search td:nth-child(3) { grid-area: age; }
  .jobs--search td:nth-child(4) { grid-area: title; }
  .jobs--search td:nth-child(5) { grid-area: status; }
  /* No status chip means no card line - an empty grid item would still
     claim its gap as a blank strip. */
  .jobs--search td:nth-child(5):empty { display: none; }
  /* Sixth cell on a board is its pay, right-aligned and nowrap by the rule
     above; on this table it is the company, which reads under the title. */
  .jobs--search td:nth-child(6) { grid-area: company; text-align: left; white-space: normal; }
  .jobs--search td:nth-child(7) { grid-area: place; text-align: left; white-space: normal; }
  .jobs--search td:nth-child(8) { grid-area: pay; text-align: right; white-space: nowrap; }
  .jobs--search td:nth-child(9) { grid-area: actions; justify-self: end; }
  /* A fingertip needs more checkbox than a pointer does. */
  .jobs--search td:nth-child(1) input[type='checkbox'] {
    width: 1.35rem;
    height: 1.35rem;
  }
  /* The no-matches row is a message, not a card. */
  .jobs td.jobs__none {
    grid-area: title;
  }
}

/* Phone form ergonomics. Keyed to the viewport, unlike the table above: these
   are about the page a thumb is holding, not about how wide one table is. */
@media (max-width: 640px) {
  /* The three toggles belong together. In DOM order Run daily comes after the
     place and salary inputs - correct on a desktop, where the whole lot is
     one row - but on a phone those inputs are full-width, so Run daily was
     marooned two rows below the toggles it belongs with and pushed the search
     button down a row for nothing. Ordering is presentational here: the DOM
     keeps the reading order the form was written in.

       Remote only  Based in US  Run daily
       [ place                            ]
       [ salary                           ]
       [ Search                           ]  */
  .search-form__opts > .checkbox {
    order: 1;
  }
  .search-form__opts > .input--place {
    order: 2;
  }
  .search-form__opts > .input--salary {
    order: 3;
  }
  /* The levels picker takes a full line of its own between the salary and
     the button; its panel opens in flow rather than floating, because a
     floating panel wider than a phone is a horizontal scroll. */
  .search-form__opts > .levels-pick {
    order: 4;
    flex: 1 1 100%;
  }
  .levels-pick__panel {
    position: static;
    width: auto;
    margin-top: 0.4rem;
    box-shadow: none;
  }
  .search-form__opts > .btn {
    order: 5;
  }


  /* Three labels on one 375px line only fits with the gaps and the type
     pulled in. The 44px floor the rest of this block sets is then bought back
     with min-height, so the row is thumb-sized even though the text is not -
     which it was not before either, at two rows of 22px. */
  .search-form__opts {
    gap: 0.5rem 0.6rem;
  }
  .search-form__opts .checkbox {
    gap: 0.4rem;
    font-size: 0.8125rem;
    align-items: center;
    min-height: 2.75rem;
  }
  .search-form__opts .checkbox input {
    margin-top: 0;
  }

  /* The filters and the search button do not share 375px - on a phone the
     button takes its own full-width line at the end of the row. */
  .search-form__opts .btn {
    flex: 1 1 100%;
    margin-left: 0;
  }

  /* The four tabs and the advanced row on a phone: the tabs take the whole
     line under their lede, every advanced control its own line, and the
     run button its own at the end. */
  .search-modes__lede {
    flex-basis: 100%;
  }
  .search-modes__tabs {
    width: 100%;
  }
  .search-modes__tab {
    flex: 1 1 auto;
    justify-content: center;
    padding-inline: 0.5rem;
  }
  .search-adv__field,
  .search-adv__pair,
  .search-adv__select,
  .search-adv__input {
    width: 100%;
    max-width: none;
  }
  .search-form__adv > .btn,
  .ai-panel__row .btn {
    flex: 1 1 100%;
    margin-left: 0;
  }

  /* Once a search has run, the form is reference rather than the task: the
     prompt folds to a single line (opening back up on focus), and the results
     start on screen instead of below the fold. */
  .search-form--ran {
    margin-bottom: 0.75rem;
  }
  .search-form--ran .search-prompt {
    min-height: 0;
    height: 3.1rem;
    font-size: 16px;
  }
  .search-form--ran .search-prompt:focus {
    height: 6rem;
  }

  /* One control per row where a shared row would crush them: the free-text
     filter and the score slider stretch, the presets keep their own line. */
  .search-filters .input--search {
    flex-basis: 100%;
    max-width: none;
  }
  .minscore {
    margin-left: 0;
    flex: 1 1 100%;
  }
  .minscore input[type='range'] {
    flex: 1;
  }

  .send-bar {
    flex-wrap: wrap;
  }
  /* The count gets its own line; the destination picker and Send share the
     next, instead of Send wrapping alone into a stray third row. */
  .send-bar__count {
    flex: 1 1 100%;
  }
  .send-bar .select {
    flex: 1;
  }
  .send-bar__name {
    flex: 1 1 100%;
    width: auto;
  }
}

/* -------------------------------------------------------------- dark mode --
   Every colour above comes from a token, so the dark palette in base.css
   repaints it without a rule here. What is left is the two chart ramps on
   Home, which change direction as well as colour - see there. */

/* --- the jobs map ------------------------------------------------------- */

.modal-section {
  margin: 0 0 0.6rem;
  font-size: 0.95rem;
}

.map-panel {
  margin-bottom: 1.5rem;
}

/* The breadcrumb is the only way back up: the modal has no browser history
   behind it, so a drill-down without this is a trap. */
.map-crumbs {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  flex-wrap: wrap;
  margin-bottom: 0.6rem;
  font-size: 0.875rem;
}
.map-crumb {
  padding: 0.2rem 0.5rem;
  border: 0;
  border-radius: var(--r-pill);
  background: var(--surface-2);
  color: var(--muted);
  font: inherit;
  cursor: pointer;
}
.map-crumb:hover {
  color: var(--text);
}
.map-crumb--on {
  background: transparent;
  color: var(--text);
  font-weight: 600;
  cursor: default;
}
.map-crumb-sep {
  color: var(--faint);
}

/* The map view is a page now, not a panel in a dialog: it gets the width. */
.map-page {
  max-width: 68rem;
}
.map-frame {
  border: 1px solid var(--line-soft);
  border-radius: var(--r-md, 10px);
  background: var(--surface-2);
  overflow: hidden;
}
.map-svg {
  display: block;
  width: 100%;
  height: auto;
}

/* The land. Dark enough to read as ground rather than as a value - the
   bubbles carry the counts, and a filled shape competing with them would be
   read as a second, contradictory scale. */
.map-shape path {
  fill: var(--surface-3);
  stroke: var(--line);
  stroke-width: 0.5;
  vector-effect: non-scaling-stroke;
}
/* A country the index has jobs in, lifted just enough to separate it from
   one it has none in. */
.map-shape--has path {
  fill: var(--line);
}
.map-shape--drill {
  cursor: pointer;
}
.map-shape--drill:hover path {
  fill: var(--surface-3);
  stroke: var(--accent);
}

/* Faint enough to read as a projection's frame rather than as data. */
.map-grid {
  stroke: var(--line-soft);
  stroke-width: 1;
  opacity: 0.5;
}

.map-bubble circle {
  fill: var(--accent);
  fill-opacity: 0.35;
  stroke: var(--accent);
  stroke-width: 1;
}
.map-bubble--drill {
  cursor: pointer;
}
.map-bubble--drill:hover circle,
.map-bubble:focus-visible circle {
  fill-opacity: 0.6;
}
.map-bubble:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
.map-label {
  fill: var(--text);
  font-size: 11px;
  font-weight: 600;
  pointer-events: none;
  paint-order: stroke;
  stroke: var(--surface-2);
  stroke-width: 3px;
  stroke-linejoin: round;
}
/* A code on a small bubble: the mark is barely wider than the text, so it
   sits above the fill rather than inside it. */
.map-label--code {
  font-size: 9px;
  font-family: var(--font-mono);
}

.map-list {
  margin-top: 0.75rem;
  display: grid;
  gap: 2px;
  max-height: 16rem;
  overflow-y: auto;
}
/* Each row is its own bar: the count is the bar's width, so the list reads as
   a ranking without a second chart beside it. */
.map-row {
  position: relative;
  display: flex;
  align-items: center;
  gap: 0.6rem;
  width: 100%;
  padding: 0.35rem 0.6rem;
  border: 0;
  border-radius: 6px;
  background: transparent;
  color: var(--text);
  font: inherit;
  font-size: 0.875rem;
  text-align: left;
  cursor: pointer;
}
.map-row:hover {
  background: var(--surface-2);
}
.map-row__bar {
  position: absolute;
  left: 0;
  top: 0;
  bottom: 0;
  border-radius: 6px;
  background: var(--accent);
  opacity: 0.14;
  pointer-events: none;
}
.map-row__name {
  position: relative;
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.map-row__n {
  position: relative;
  color: var(--muted);
  font-size: 0.8125rem;
}
.map-row__nopin {
  position: relative;
  color: var(--faint);
}
.map-note {
  margin-top: 0.6rem;
  font-size: 0.75rem;
}

@media (max-width: 640px) {
  .map-list {
    max-height: 12rem;
  }
  .map-row {
    min-height: 2.75rem;
  }
}

/* The search's name, edited in the topbar title itself - the one place the
   name is shown. It lives inside the h1 and inherits its type wholesale, so
   an unnamed search reads as a quiet placeholder and a named one is simply
   the page's title; no visible chrome until hover or focus says it can be
   edited. The h1's own font size is over the 16px iOS zoom threshold at
   every width, so no phone override is needed. */
/* The h1 clips overflow for its text ellipsis; with the input inside, that
   clipping would shave the input's focus ring and the negative margin that
   keeps an unnamed title aligned with a plain one. The input carries its own
   ellipsis, so the h1 has nothing left to clip. */
.topbar__title h1:has(.search-title) {
  overflow: visible;
}
/* A width-100% input inside a content-sized flex item is circular, and the
   browser resolves it to the input's ~170px default - measured, the title
   truncated at "Aerospace ...". Holding the editor, the title takes the
   row's free space instead; the cap keeps a click on the empty right half
   of a wide topbar from reading as "edit the name". */
.topbar__title:has(.search-title) {
  flex: 1 1 auto;
}
.topbar__title .search-title {
  max-width: 34rem;
}
.search-title {
  display: block;
  width: 100%;
  margin: -0.15rem -0.3rem;
  padding: 0.15rem 0.3rem;
  border: 1px solid transparent;
  border-radius: 6px;
  background: transparent;
  color: inherit;
  font: inherit;
  text-overflow: ellipsis;
}
.search-title::placeholder {
  color: var(--faint);
  font-weight: 500;
}
.search-title:hover {
  border-color: var(--line-soft);
}
.search-title:focus {
  border-color: var(--line);
  background: var(--surface-2);
  outline: none;
}

/* The board's audit line sits between the topbar and the view, in the
   content's own padding rhythm. */
#board-audit .search-audit {
  margin: 0 0 0.75rem;
}

/* --- curated gigs -------------------------------------------------------- */
/* The allowlisted corpus view. Cards borrow the board drawer's vocabulary
   (tag, chip-remote, chip-sent) so a gig reads like a job, not a brochure. */

.gigs-page {
  display: flex;
  flex-direction: column;
  gap: 1.25rem;
  padding-bottom: 3rem;
}

.gigs-chips {
  flex-wrap: wrap;
}

.gigs-head h2 {
  font-size: 1.35rem;
  margin-bottom: 0.35rem;
}

.gigs-head p {
  max-width: 46rem;
  font-size: 0.9375rem;
}

/* Section overview pages: one card per search slot or board, plus the
   card that starts the next one. */
.hub-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 19rem), 1fr));
  gap: 1rem;
  align-items: stretch;
}

.hub-card {
  display: flex;
  flex-direction: column;
  gap: 0.55rem;
  padding: 1.05rem 1.15rem;
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  background: var(--surface);
  color: var(--text);
  font: inherit;
  text-align: left;
  cursor: pointer;
  transition: background 0.15s var(--ease), border-color 0.15s var(--ease);
}

.hub-card:hover {
  background: var(--surface-2);
}

.hub-card.is-target {
  border-color: var(--accent);
  background: var(--surface-2);
}

.hub-card__top {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 0.6rem;
}

.hub-card__top h3 {
  /* The title takes the room; the origin tag and the close hug the right. */
  flex: 1;
  min-width: 0;
  font-size: 1rem;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.hub-card__count {
  font-size: 0.75rem;
  color: var(--faint);
  white-space: nowrap;
}

.hub-card__del {
  position: static;
  flex-shrink: 0;
}

.hub-card__body {
  font-size: 0.875rem;
  color: var(--muted);
}

.hub-card__tags {
  display: flex;
  flex-wrap: wrap;
  gap: 0.3rem;
}

.hub-card__meta {
  margin-top: auto;
  font-size: 0.72rem;
  color: var(--faint);
}

.hub-card--new {
  align-items: center;
  justify-content: center;
  gap: 0.35rem;
  border-style: dashed;
  background: transparent;
  color: var(--muted);
  min-height: 7.5rem;
}

.hub-card--new:hover {
  color: var(--text);
  border-color: var(--accent-dim);
}

.hub-card--new .hub-card__meta {
  margin-top: 0.15rem;
}

.hub-card__plus {
  font-size: 1.6rem;
  line-height: 1;
  color: var(--faint);
}

.hub-card--note {
  cursor: default;
  justify-content: center;
}

.hub-empty {
  max-width: 34rem;
  padding: 1.4rem 0 0;
}

.hub-empty h3 {
  margin-bottom: 0.4rem;
}

.hub-empty__actions {
  display: flex;
  gap: 0.6rem;
  margin-top: 0.9rem;
  flex-wrap: wrap;
}

/* The live web-search panel that sits above the curated corpus. */
.gigs-scout {
  padding: 1.1rem 1.2rem;
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  background: var(--surface);
  margin-bottom: 1.5rem;
}

.gigs-scout h3 {
  font-size: 1.02rem;
  margin-bottom: 0.3rem;
}

.gigs-scout__lede {
  font-size: 0.875rem;
  max-width: 46rem;
}

.gigs-scout__row {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin-top: 0.7rem;
}

.gigs-scout__row .input {
  flex: 1 1 16rem;
}

.gigs-scout__row .gigs-scout__loc {
  flex: 0 1 11rem;
}

.gigs-scout__past {
  flex-wrap: wrap;
  margin-top: 0.75rem;
}

.gigs-scout__results {
  margin-bottom: 1.5rem;
}

.gigs-scout__results .gig-grid {
  margin-top: 0.75rem;
}

.chip-x {
  margin-left: 0.45rem;
  opacity: 0.55;
}

.chip-x:hover {
  opacity: 1;
}

.gig-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 21rem), 1fr));
  gap: 1rem;
  align-items: start;
}

.gig-card {
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
  padding: 1.1rem 1.2rem;
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  background: var(--surface);
  cursor: pointer;
  transition: background 0.15s var(--ease), border-color 0.15s var(--ease);
}

.gig-card:hover {
  background: var(--surface-2);
}

.gig-card--info {
  cursor: default;
}

.gig-card__top {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 0.6rem;
}

.gig-card__top h3 {
  font-size: 1.02rem;
}

.gig-card__role {
  font-size: 0.9rem;
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
  align-items: baseline;
}

.gig-card__loc {
  font-family: var(--font-mono);
  font-size: 0.72rem;
  color: var(--faint);
}

.gig-card__needs {
  font-size: 0.875rem;
  color: var(--muted);
}

.gig-card__needs strong {
  color: var(--text);
}

.gig-card__pay {
  font-size: 0.8rem;
  color: var(--ok);
}

.gig-card__apply {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem 0.75rem;
  align-items: center;
  padding: 0.55rem 0.7rem;
  border: 1px solid var(--line-soft);
  border-radius: var(--r-md);
  background: var(--ink);
}

.gig-card__contact {
  font-family: var(--font-mono);
  font-size: 0.78rem;
  color: var(--accent);
  text-decoration: none;
  word-break: break-all;
}

.gig-card__contact:hover {
  text-decoration: underline;
}

.gig-card__how {
  font-size: 0.78rem;
  color: var(--muted);
}

.gig-card__advice {
  font-size: 0.84rem;
  color: var(--muted);
  border-left: 2px solid var(--accent-dim);
  padding-left: 0.7rem;
}

.gig-card__advice em {
  font-style: normal;
  font-weight: 600;
  color: var(--text);
}

.gig-card__caution {
  font-size: 0.84rem;
  color: var(--warn);
  border-left: 2px solid var(--warn);
  padding-left: 0.7rem;
}

.gig-card__caution em {
  font-style: normal;
  font-weight: 600;
}

.gig-card__track {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  align-items: center;
}

.gig-card__source {
  margin-top: auto;
  font-size: 0.75rem;
  color: var(--faint);
}

.gig-card__source a {
  color: var(--faint);
}

.gigs-sub {
  font-size: 1.05rem;
  margin-top: 0.75rem;
}

.gigs-watch {
  padding: 1.1rem 1.3rem;
  border: 1px solid var(--line-soft);
  border-radius: var(--r-lg);
  background: var(--surface);
}

.gigs-watch h3 {
  font-size: 0.9375rem;
  margin-bottom: 0.7rem;
}

.gigs-watch ul {
  margin: 0;
  padding: 0;
  list-style: none;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 19rem), 1fr));
  gap: 0.6rem 1.4rem;
}

.gigs-watch li {
  font-size: 0.84rem;
  color: var(--muted);
  line-height: 1.5;
}

.gigs-watch li > a {
  color: var(--text);
  text-decoration: none;
}

.gigs-watch li > a:hover {
  text-decoration: underline;
}

.gigs-watch__cost {
  font-size: 0.75rem;
  color: var(--accent);
  margin-left: 0.35rem;
}

.gigs-watch__what {
  display: block;
}

.gigs-strategy {
  max-width: 65ch;
  font-size: 0.9rem;
  color: var(--muted);
  line-height: 1.65;
  border-left: 2px solid var(--line);
  padding-left: 0.9rem;
}

/* --- home ----------------------------------------------------------------- */
/* The landing view: hero + stats, then the profile panel beside orientation. */

.home-page {
  display: flex;
  flex-direction: column;
  gap: 1.25rem;
  padding-bottom: 3rem;
}

.home-hero {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  justify-content: space-between;
  gap: 1rem 2rem;
}

.home-hero__copy h2 {
  margin: 0.5rem 0 0.4rem;
  font-size: 1.45rem;
}

.home-hero__lede {
  max-width: 44rem;
  font-size: 0.9375rem;
}

/* The profile must never read as homework. This tag rides every heading that
   leads to it — the flow step, the dashboard card, the page itself — so the
   guarantee is visible before any copy is read. */
.optional-tag {
  display: inline-block;
  margin-left: 0.5rem;
  padding: 0.18rem 0.5rem;
  border-radius: var(--r-pill);
  background: var(--accent-wash);
  color: var(--accent);
  font-family: var(--font-mono);
  font-size: 0.625rem;
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  vertical-align: 0.16em;
  white-space: nowrap;
}

/* A feature that exists but is not switched on yet wears the same tag
   muted - a statement, not an invitation. */
.optional-tag--soon {
  background: var(--surface-2);
  color: var(--muted);
  border: 1px solid var(--line);
}

/* The process strip: four steps drawn as doors, arrows between them - and the
   control that puts it away, so the dashboard numbers can be the first thing
   on the page once somebody knows how the product works. */
.home-flow-wrap {
  position: relative;
}

.home-flow {
  display: flex;
  align-items: stretch;
  gap: 0.5rem;
  margin: 1.4rem 0 1.6rem;
}

/* Sits over the strip's top-right corner rather than taking a row of its own:
   the point of the control is to reclaim vertical space. */
.home-flow__hide {
  position: absolute;
  top: 0.45rem;
  right: 0;
  padding: 0.15rem 0.5rem;
  border: 0;
  border-radius: var(--r-pill);
  background: transparent;
  color: var(--faint);
  font: inherit;
  font-size: 0.72rem;
  cursor: pointer;
}

.home-flow__hide:hover {
  color: var(--text);
  background: var(--surface-2);
}

/* What is left behind. One quiet line: enough to find again, small enough
   that the numbers below it are what the page is about. */
.home-flow__show {
  display: inline-block;
  margin: 0.9rem 0 1.1rem;
  padding: 0.2rem 0;
  border: 0;
  background: transparent;
  color: var(--muted);
  font: inherit;
  font-size: 0.78rem;
  text-decoration: underline;
  text-underline-offset: 3px;
  cursor: pointer;
}

.home-flow__show:hover {
  color: var(--text);
}

.home-flow__step {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 0.45rem;
  padding: 0.9rem 0.95rem;
  border: 1px solid var(--line-soft);
  border-radius: var(--r-md);
  background: var(--surface);
  color: var(--text);
  font: inherit;
  text-align: left;
  cursor: pointer;
}

.home-flow__step:hover {
  border-color: var(--accent);
}

.home-flow__step strong {
  font-size: 0.9rem;
}

.home-flow__step > span:last-child {
  font-size: 0.78rem;
  color: var(--muted);
  line-height: 1.5;
}

.home-flow__icon {
  display: grid;
  place-items: center;
  width: 2.4rem;
  height: 2.4rem;
  border-radius: var(--r-pill);
  background: var(--accent-wash);
  color: var(--accent);
}

.home-flow__arrow {
  align-self: center;
  color: var(--faint);
  flex: none;
}

@media (max-width: 980px) {
  .home-flow {
    flex-direction: column;
  }
  .home-flow__arrow {
    transform: rotate(90deg);
    align-self: flex-start;
    margin-left: 1.5rem;
  }
}

/* The dashboard: one card per surface, each the door to that surface. */
/* --- Home metrics ------------------------------------------------------- */

/* The board pipeline as an ordered scale: one hue in lightness steps, and
   two neutral grays for the ways out. Cobalt, stepped in OKLCH at the
   accent's own hue (266) so every step is the same blue made lighter or
   darker, 0.08 of lightness apart. On paper further along is darker; on
   the dark page, lighter. Every step clears 3:1 against the bar's track
   and the card on both grounds - New, the status every sent job starts in,
   included - which leaves neighbouring steps closer than a categorical
   palette would allow; the 2px surface gaps and a legend that names and
   counts every segment carry that. */
:root {
  --stage-1: #4e7bfd;
  --stage-2: #3861e1;
  --stage-3: #2547c5;
  --stage-4: #142baa;
  --stage-5: #0a038f;
  --stage-off: #7d8494;
  --stage-gone: #5a6170;
  /* The calendar's own ramp, climbing through the accent: "more" is
     darker on paper and never borrows a stage colour, so on this page the
     stage ramp means where a job stands and nothing else. Stepped 0.065
     apart, the first step clearing 3:1 on the empty day it sits beside.
     Both ramps validated as ordinal ramps on both grounds. */
  --heat-1: #5884ff;
  --heat-2: #3b68ff;
  --heat-3: #2950ee;
  --heat-4: #1b37d7;
  --heat-5: #121ac0;
}

/* The same ramps for the dark page, climbing the other way: further along,
   and more, are lighter - toward the lifted cobalt the accent is there. */
:root[data-theme='dark'] {
  --stage-1: #5162e7;
  --stage-2: #677dff;
  --stage-3: #869dff;
  --stage-4: #a8baff;
  --stage-5: #cad6ff;
  --stage-off: #8a91a0;
  --stage-gone: #666e7c;
  --heat-1: #5162e7;
  --heat-2: #6277fe;
  --heat-3: #7a91ff;
  --heat-4: #95aaff;
  --heat-5: #b0c1ff;
}

.home-metrics {
  display: flex;
  flex-direction: column;
  gap: 0.9rem;
  margin-bottom: 0.9rem;
}

/* Two tiles, side by side at every width: they fit a phone as a pair. */
.home-metrics__row {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 0.9rem;
}

/* A stat tile: the name, the number, and beneath it the change and the
   trend when the tile has a past. A door, like every card on Home. */
.stat {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0.25rem;
  min-width: 0;
  padding: 0.85rem 1rem;
  border: 1px solid var(--line-soft);
  border-radius: var(--r-md);
  background: var(--surface);
  color: var(--text);
  font: inherit;
  text-align: left;
  cursor: pointer;
}

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

.stat__label,
.home-status__head strong {
  font-family: var(--font-mono);
  font-size: 0.75rem;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  /* Muted, not faint: at this size faint falls under AA, and the label is
     the only name the number has. */
  color: var(--muted);
}

.stat__value {
  font-size: 1.75rem;
  font-weight: 600;
  line-height: 1.1;
  letter-spacing: -0.02em;
}

.stat--pending .stat__value {
  color: var(--faint);
}

.stat__delta,
.stat__note {
  font-size: 0.75rem;
  color: var(--muted);
}

.stat__delta {
  font-family: var(--font-mono);
}

/* More applications is the good direction; fewer is not an error, so it
   stays muted rather than red. */
.stat__delta--up {
  color: var(--ok);
}

.stat__trend {
  display: block;
  width: 100%;
  max-width: 6rem;
  height: 28px;
  margin-top: 0.15rem;
}

.stat__trend svg {
  display: block;
  width: 100%;
  height: 100%;
}

.stat__trend rect {
  /* Recessive, but there: surface-3 on the card was 1.2:1 and the trend
     read as two bars and a blank. */
  fill: var(--faint);
}

.stat__trend rect.is-current {
  fill: var(--accent);
}

/* --- Home charts -------------------------------------------------------- */

/* The figures under the three counts: a grid of cards, each a figure with
   its table behind a toggle. Three columns on a desk, the wide figures
   taking two; one column on anything narrower. */
.home-charts {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 0.9rem;
}

.chart-card--span2 {
  grid-column: span 2;
}

/* Until the wire answers: one quiet line where the figures will be, tall
   enough that their arrival does not shove the door cards down the page. */
.home-charts--pending {
  display: flex;
  align-items: center;
  min-height: 12rem;
  border: 1px dashed var(--line-soft);
  border-radius: var(--r-md);
}

.home-charts__wait {
  margin: 0 auto;
  font-size: 0.875rem;
}

@media (max-width: 1100px) {
  .home-charts {
    grid-template-columns: 1fr;
  }
  .chart-card--span2 {
    grid-column: auto;
  }
}

.chart-card {
  display: flex;
  flex-direction: column;
  gap: 0.7rem;
  min-width: 0;
  padding: 0.85rem 1rem 0.95rem;
  border: 1px solid var(--line-soft);
  border-radius: var(--r-md);
  background: var(--surface);
  color: var(--text);
}

.chart-card__head {
  display: flex;
  align-items: baseline;
  gap: 0.3rem 0.8rem;
}

.chart-card__head strong {
  font-family: var(--font-mono);
  font-size: 0.75rem;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--muted);
}

.chart-card__lede,
.chart-card__empty,
.chart-card__foot {
  font-size: 0.8rem;
  color: var(--muted);
  line-height: 1.5;
}

.chart-card__lede {
  margin-top: -0.35rem;
}

.chart-card__tools {
  display: flex;
  gap: 0.9rem;
  margin-left: auto;
  font-size: 0.78rem;
}

.chart-card__toggle {
  color: var(--muted);
}

.chart-card__toggle:hover,
.chart-card__toggle[aria-pressed='true'] {
  color: var(--text);
}

/* The body takes the height the grid row gives the card, so a figure
   that can grow (the columns) grows into it rather than leaving a blank. */
.chart-card__body {
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: 0.7rem;
  min-width: 0;
  min-height: 0;
}

.chart-card__empty {
  margin: 0.2rem 0 0.3rem;
}

.chart-card__foot {
  padding-top: 0.55rem;
  border-top: 1px solid var(--line-soft);
}

.chart-card__foot strong {
  color: var(--text);
  font-weight: 600;
}

.viz-host {
  width: 100%;
  min-width: 0;
}

.viz {
  display: block;
  max-width: 100%;
  height: auto;
  overflow: visible;
}

/* The chart's own text: never the series colour, always a text token,
   in the mono the tiles use for numbers. */
.viz__tick,
.viz__axis,
.viz__sub,
.viz__goal-label {
  font-family: var(--font-mono);
  font-size: 11px;
  fill: var(--muted);
}

.viz__axis.is-current {
  fill: var(--text);
}

.viz__hero {
  font-family: var(--font-sans);
  font-size: 26px;
  font-weight: 600;
  letter-spacing: -0.02em;
  fill: var(--text);
}

/* Hairline and solid - a dashed grid reads as a projection. */
.viz__grid {
  stroke: var(--line-soft);
  stroke-width: 1;
}

/* The goal: one solid reference line a step darker than the grid, its
   number at the end in muted text. Never the accent - the accent is the
   week now running, and the line is what it is measured against. */
.viz__goal {
  stroke: var(--muted);
  stroke-width: 1;
}

/* Columns: the week now running in the accent, the rest recessive - the
   one-series-is-the-point form. The hit area is the whole band. */
.viz__hit {
  fill: transparent;
}

.viz__col {
  cursor: default;
}

.viz__bar {
  fill: var(--faint);
  transition: fill 0.15s var(--ease);
}

.viz__bar--zero {
  fill: var(--faint);
}

.viz__col.is-current .viz__bar {
  fill: var(--accent);
}

.viz__col:hover .viz__bar,
.viz__col:focus-visible .viz__bar {
  fill: var(--text);
}

.viz__col.is-current:hover .viz__bar,
.viz__col.is-current:focus-visible .viz__bar {
  fill: var(--accent-hover);
}

.viz__col:focus-visible .viz__hit {
  fill: var(--accent-wash);
}

/* The ring and the donut: the track is a step off the surface, so an
   empty ring is still a ring. */
.viz__track {
  stroke: var(--surface-3);
}

.viz--ring .viz__fill {
  stroke: var(--accent);
  stroke-linecap: round;
  transition: stroke-dasharray 0.6s var(--ease);
}

/* The calendar: six steps from the empty track up the heat ramp. The
   day is a group - a hit square the size of the cell plus its gap, and
   the painted square inside it - so the group takes the focus and the
   hover, and the paint answers. */
.viz__day {
  cursor: default;
}

/* Today's ring, and the hover ring, are drawn on the hit square - which
   sits in the gap around the painted one - so they never have to be told
   apart from the paint they would otherwise sit on. */
.viz__day .viz__hit {
  stroke: transparent;
  stroke-width: 2;
}

.viz__day.is-today .viz__hit {
  stroke: var(--text);
}

.viz__day:hover .viz__hit,
.viz__day:focus-visible .viz__hit {
  stroke: var(--accent);
}

/* An empty day is the one tint on paper, so the first ramp step clears 3:1
   against it; on the dark page it takes the next step up, where --surface-2
   would all but vanish into the card. */
.viz__cell {
  fill: var(--surface-2);
  transition: fill 0.15s var(--ease);
}

:root[data-theme='dark'] .viz__cell--0,
:root[data-theme='dark'] .viz-legend__cell.viz__cell--0 {
  fill: var(--surface-3);
  background: var(--surface-3);
}

.viz__cell--1 {
  fill: var(--heat-1);
}
.viz__cell--2 {
  fill: var(--heat-2);
}
.viz__cell--3 {
  fill: var(--heat-3);
}
.viz__cell--4 {
  fill: var(--heat-4);
}
.viz__cell--5 {
  fill: var(--heat-5);
}

/* Legends: the table a figure needs, or the ramp a calendar climbs. */
.viz-legend {
  display: flex;
  flex-wrap: wrap;
  gap: 0.35rem 1rem;
  margin: 0;
  padding: 0;
  list-style: none;
  font-size: 0.8rem;
  color: var(--muted);
}

.viz-legend__key {
  display: flex;
  align-items: center;
  gap: 0.4rem;
}

.viz-legend__swatch {
  width: 0.6rem;
  height: 0.6rem;
  border-radius: 2px;
  flex: none;
}

.viz-legend__n {
  font-family: var(--font-mono);
  color: var(--text);
}

.viz-legend__note {
  color: var(--faint);
}

.viz-legend--ramp {
  align-items: center;
  gap: 0.3rem 0.7rem;
  font-family: var(--font-mono);
  font-size: 0.7rem;
  color: var(--muted);
}

.viz-legend__step {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
}

.viz-legend__unit {
  color: var(--faint);
}

.viz-legend__cell {
  width: 0.7rem;
  height: 0.7rem;
  border-radius: 2px;
  background: var(--surface-2);
}

.viz-legend__cell.viz__cell--1 {
  background: var(--heat-1);
}
.viz-legend__cell.viz__cell--2 {
  background: var(--heat-2);
}
.viz-legend__cell.viz__cell--3 {
  background: var(--heat-3);
}
.viz-legend__cell.viz__cell--4 {
  background: var(--heat-4);
}
.viz-legend__cell.viz__cell--5 {
  background: var(--heat-5);
}

/* The donut sits beside its legend where there is room, above it where
   there is not. */
.viz-split {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.6rem 1.2rem;
}

.viz-split > .viz-legend {
  flex: 1 1 9rem;
  flex-direction: column;
  gap: 0.35rem;
}

/* The week: the ring and its words beside the columns where there is
   room, above them where there is not. */
.week {
  flex: 1;
  display: flex;
  flex-wrap: wrap;
  align-items: stretch;
  gap: 0.8rem 1.4rem;
}

.week__goal {
  flex: 0 1 17rem;
  min-width: 14rem;
  align-self: flex-start;
}

.week__columns {
  flex: 1 1 18rem;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
}

.week__columns > .viz-host {
  flex: 1;
  min-height: 0;
  /* The chart draws to the height it is given; what is left over sits
     under it, above the caption, rather than stretching the drawing. */
  display: flex;
  flex-direction: column;
  justify-content: flex-start;
}

.week__caption {
  font-family: var(--font-mono);
  font-size: 0.7rem;
  color: var(--faint);
}

/* The ring meter and its words, side by side. */
.goal {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.6rem 1.1rem;
}

.goal__copy {
  flex: 1 1 9rem;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
  font-size: 0.8rem;
  color: var(--muted);
}

.goal__copy strong {
  color: var(--text);
  font-size: 0.9rem;
}

.goal__set {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.35rem;
}

.goal__set .chip {
  padding: 0.22rem 0.6rem;
  font-size: 0.75rem;
}

.goal__input {
  width: 3.6rem;
  padding: 0.22rem 0.45rem;
  border: 1px solid var(--line);
  border-radius: var(--r-pill);
  background: var(--surface-2);
  color: var(--text);
  font: inherit;
  font-size: 0.8rem;
  text-align: center;
}

.goal__input:focus {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
}

@media (max-width: 880px) {
  .goal__input {
    font-size: 16px;
  }
}

/* The calendar and, beside it where the card is wide, its streak figures
   stacked; beneath it where it is not. */
.showing {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  gap: 0.8rem 2rem;
}

.showing__grid {
  flex: 1 1 22rem;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
}

.showing__grid > .viz-host {
  max-width: 30rem;
}

.showing__stats {
  flex: 0 1 11rem;
}

/* Streak figures: small stats, in a row or a stack. */
.streaks {
  display: flex;
  flex-wrap: wrap;
  gap: 0.3rem 1.4rem;
}

.showing__stats.streaks {
  flex-direction: column;
  gap: 0.8rem;
}

.showing__stats .streaks__n {
  font-size: 1.6rem;
}

.streaks__stat {
  display: flex;
  flex-direction: column;
  gap: 0.05rem;
}

.streaks__n {
  font-size: 1.25rem;
  font-weight: 600;
  line-height: 1.15;
  letter-spacing: -0.02em;
}

.streaks__label {
  font-size: 0.75rem;
  color: var(--muted);
}

/* The funnel: label, bar, count per stage; the carried rate between. */
.funnel {
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
}

.funnel__row {
  display: grid;
  grid-template-columns: 6rem minmax(0, 1fr) 2.6rem;
  align-items: center;
  gap: 0.6rem;
  padding: 0.15rem 0.2rem;
  border-radius: var(--r-sm);
}

.funnel__row:hover,
.funnel__row:focus-visible {
  background: var(--surface-2);
}

.funnel__label {
  font-size: 0.8rem;
  color: var(--muted);
}

.funnel__track {
  display: block;
  height: 14px;
  border-radius: 4px;
  background: var(--surface-2);
  overflow: hidden;
}

.funnel__fill {
  display: block;
  height: 100%;
  border-radius: 0 4px 4px 0;
  transition: width 0.6s var(--ease);
}

.funnel__n {
  text-align: right;
  font-size: 0.85rem;
  color: var(--text);
}

.funnel__rate {
  padding-left: 6.8rem;
  font-size: 0.72rem;
  color: var(--faint);
}

/* Next up: two short lists, each row a door to its job. */
.nudge__group {
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
}

.nudge__group + .nudge__group {
  margin-top: 0.35rem;
}

.nudge__head {
  display: flex;
  align-items: baseline;
  gap: 0.5rem;
  font-size: 0.8rem;
  color: var(--text);
  font-weight: 600;
}

.nudge__caption,
.nudge__more {
  font-size: 0.75rem;
  color: var(--muted);
}

.nudge__more {
  align-self: flex-start;
}

.nudge__act {
  flex: none;
  padding: 0.22rem 0.55rem;
  font-size: 0.72rem;
}

/* Quiet applications: a short list of rows, each a door to its board. */
.nudge {
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
}

.nudge__row {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  padding: 0.35rem 0.55rem;
  border: 1px solid var(--line-soft);
  border-radius: var(--r-sm);
  background: var(--surface-2);
  color: var(--text);
  font-size: 0.8rem;
}

/* The row's body is the door; the action beside it is its own button. */
.nudge__door {
  flex: 1;
  min-width: 0;
  display: flex;
  align-items: center;
  gap: 0.6rem;
  padding: 0;
  border: 0;
  background: none;
  color: inherit;
  font: inherit;
  text-align: left;
  cursor: pointer;
}

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

.nudge__main {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 0.05rem;
}

.nudge__title {
  font-weight: 600;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.nudge__sub {
  color: var(--muted);
  font-size: 0.75rem;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.nudge__days {
  flex: none;
  font-family: var(--font-mono);
  font-size: 0.75rem;
  color: var(--warn);
}

/* The figures' tables. */
.viz-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.8rem;
}

.viz-table th,
.viz-table td {
  padding: 0.3rem 0.5rem;
  border-bottom: 1px solid var(--line-soft);
  text-align: left;
}

.viz-table th {
  font-family: var(--font-mono);
  font-size: 0.7rem;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--muted);
}

.viz-table tbody th {
  font-family: var(--font-sans);
  font-size: 0.8rem;
  font-weight: 400;
  letter-spacing: 0;
  text-transform: none;
  color: var(--text);
}

.viz-table td {
  text-align: right;
  font-variant-numeric: tabular-nums;
}

/* The one tooltip every figure shares: fixed to the viewport, above the
   mark, and never in the way of the pointer. */
.viz-tip {
  position: fixed;
  z-index: 60;
  max-width: 18rem;
  padding: 0.4rem 0.6rem;
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  background: var(--surface-2);
  color: var(--text);
  font-size: 0.78rem;
  line-height: 1.45;
  white-space: pre-line;
  box-shadow: var(--shadow);
  pointer-events: none;
}

@media (prefers-reduced-motion: reduce) {
  .viz--ring .viz__fill,
  .funnel__fill,
  .viz__bar,
  .viz__cell {
    transition: none;
  }
}

/* Where the board jobs stand: the bar, and the legend that is its table. */
.home-status {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  width: 100%;
  padding: 0.85rem 1rem;
  border: 1px solid var(--line-soft);
  border-radius: var(--r-md);
  background: var(--surface);
  color: var(--text);
}

.home-status__head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.4rem 1rem;
}

.home-status__open {
  margin-left: auto;
  font-size: 0.8rem;
}

.home-status__total,
.home-status__empty {
  font-size: 0.8rem;
  color: var(--muted);
}

/* Segments are separated by the surface showing through a 2px gap, never
   by a stroke; the bar's own ground is the empty track. */
.home-status__bar {
  display: flex;
  gap: 2px;
  height: 10px;
  overflow: hidden;
  border-radius: 5px;
  background: var(--surface-2);
}

.home-status__seg {
  display: block;
  min-width: 4px;
}

.home-status__legend {
  display: flex;
  flex-wrap: wrap;
  gap: 0.35rem 1rem;
  margin: 0;
  padding: 0;
  list-style: none;
}

.home-status__key {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  font-size: 0.8rem;
  color: var(--muted);
}

.home-status__swatch {
  width: 0.6rem;
  height: 0.6rem;
  border-radius: 2px;
}

.home-status__n {
  font-family: var(--font-mono);
  color: var(--text);
}

.home-dash {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 0.9rem;
}

@media (max-width: 1100px) {
  .home-dash {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

@media (max-width: 640px) {
  .home-dash {
    grid-template-columns: 1fr;
  }
}

.home-card {
  display: flex;
  align-items: flex-start;
  gap: 0.8rem;
  padding: 0.95rem 1rem;
  border: 1px solid var(--line-soft);
  border-radius: var(--r-md);
  background: var(--surface);
  color: var(--text);
  font: inherit;
  text-align: left;
  cursor: pointer;
}

.home-card:hover {
  border-color: var(--accent);
}

.home-card__icon {
  display: grid;
  place-items: center;
  width: 2.2rem;
  height: 2.2rem;
  flex: none;
  border-radius: var(--r-sm);
  background: var(--surface-2);
  color: var(--muted);
}

.home-card:hover .home-card__icon {
  color: var(--accent);
}

.home-card__body {
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 0.2rem;
}

.home-card__body strong {
  font-size: 0.8125rem;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  font-family: var(--font-mono);
  color: var(--faint);
  font-weight: 600;
}

.home-card__value {
  font-size: 0.9375rem;
  font-weight: 600;
  color: var(--accent);
}

.home-card__note {
  font-size: 0.78rem;
  color: var(--muted);
}

/* The profile editor's page: the card grows into the desktop instead of
 * hugging a narrow column beside a field of nothing. */
#view-profile .home-profile {
  max-width: 72rem;
}

/* The changed-elsewhere offer, sitting above the editor it refuses to
   repaint over. */
.profile-stale {
  max-width: 72rem;
  margin-bottom: 0.75rem;
}

.profile-cols {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 0 2.4rem;
  align-items: start;
}

.profile-col {
  min-width: 0;
}

/* Half a page is not a whole one: inside a column the quad pairs up. */
.profile-cols .home-cols--quad {
  grid-template-columns: repeat(2, minmax(0, 1fr));
}

@media (max-width: 980px) {
  .profile-cols {
    grid-template-columns: 1fr;
  }
}

/* What is on file, said quietly under the dropzone. */
.home-resumenote {
  margin: 0.5rem 0 0.85rem;
  font-size: 0.72rem;
  color: var(--faint);
}

.home-cols {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 0 1rem;
}

@media (max-width: 640px) {
  .home-cols {
    grid-template-columns: 1fr;
  }
}

/* Where / Remote / Years / Salary as one row - four short answers were
 * eating four rows of the panel. minmax(0, …) lets the number inputs
 * shrink below their intrinsic width instead of blowing the grid open. */
.home-cols--quad {
  grid-template-columns: repeat(4, minmax(0, 1fr));
}

@media (max-width: 900px) {
  .home-cols--quad {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

@media (max-width: 480px) {
  .home-cols--quad {
    grid-template-columns: 1fr;
  }
}

/* Which company list is in play, and its New / Rename / Delete row. */
.home-listbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.4rem;
  margin-bottom: 0.5rem;
}

.home-listpick {
  flex: 1;
  min-width: 10rem;
}

.home-searchhint {
  margin: 0.6rem 0 0;
  font-size: 0.75rem;
  color: var(--faint);
}

/* The profile-search chooser: one row per dimension, its strictness select
 * on the same line as the label, the tickable pieces wrapping beneath. */
.psearch__row {
  padding: 0.65rem 0;
  border-top: 1px solid var(--line-soft);
}

.psearch__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  margin-bottom: 0.4rem;
}

.psearch__mode {
  max-width: 14rem;
  font-size: 0.8125rem;
}

.psearch__picks {
  display: flex;
  flex-wrap: wrap;
  gap: 0.25rem 1.1rem;
}

.psearch__pick {
  font-size: 0.875rem;
  color: var(--muted);
}

.psearch__warn {
  margin: 0.75rem 0 0;
  color: var(--warn, #b58900);
}

.pchips {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
  margin-bottom: 0.5rem;
  font-size: 0.8125rem;
}

.pchip {
  display: inline-flex;
  align-items: center;
  gap: 0.15rem;
  padding: 0.22rem 0.3rem 0.22rem 0.65rem;
  border: 1px solid var(--line);
  border-radius: var(--r-pill);
  background: var(--surface-2);
  font-size: 0.8125rem;
}

/* A company the index has no board for: a wish, drawn as one. The dashed
   edge marks it apart from catalogue picks; the note row under the chips
   says why and offers the request. */
.pchip--unindexed {
  border-style: dashed;
  background: transparent;
  color: var(--muted);
}

.pchips__note {
  flex-basis: 100%;
  display: flex;
  align-items: baseline;
  gap: 0.5rem;
  flex-wrap: wrap;
}

.pchips__note .hint {
  margin: 0;
}

.pchips__ask {
  font-size: 0.8125rem;
  white-space: nowrap;
}

.pchips__asked {
  color: var(--accent);
}

.pchip--action .pchip__body {
  border: 0;
  background: transparent;
  color: var(--accent);
  font: inherit;
  cursor: pointer;
  padding: 0;
}

.pchip--action .pchip__body:hover {
  text-decoration: underline;
}

.pchip__x {
  border: 0;
  background: transparent;
  color: var(--faint);
  font-size: 0.9rem;
  line-height: 1;
  cursor: pointer;
  padding: 0.1rem 0.35rem;
}

.pchip__x:hover {
  color: var(--danger);
}

.home-addrow {
  display: flex;
  gap: 0.5rem;
}

.home-addrow .input {
  flex: 1;
  min-width: 0;
}

.cosuggest {
  position: relative;
}

.cosuggest__panel {
  position: absolute;
  z-index: 30;
  top: calc(100% + 4px);
  left: 0;
  right: 0;
  max-height: 16rem;
  overflow-y: auto;
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: var(--surface-2);
  box-shadow: var(--shadow);
  padding: 0.25rem;
  display: grid;
  gap: 2px;
}

.cosuggest__row {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 0.75rem;
  width: 100%;
  padding: 0.45rem 0.6rem;
  border: 0;
  border-radius: var(--r-sm);
  background: transparent;
  color: var(--text);
  font: inherit;
  font-size: 0.875rem;
  text-align: left;
  cursor: pointer;
}

.cosuggest__row:hover {
  background: var(--surface-3);
}

.cosuggest__row--custom {
  color: var(--accent);
}

.cosuggest__count {
  font-size: 0.75rem;
  color: var(--faint);
  white-space: nowrap;
}

.home-enable {
  margin: 0.5rem 0 1rem;
  font-size: 0.875rem;
  color: var(--muted);
}

.home-profile__actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.6rem;
}

/* A curated gig whose posting the employer's board stopped listing. */
.gig-card--closed {
  opacity: 0.62;
}

.gig-card--closed:hover {
  opacity: 1;
}

/* Alerts. The cards reuse the hub card the Searches and Boards pages use;
   these are the three things that page adds - a stacked form inside a card,
   its labelled fields, and the preview list of what an alert is holding. */
.alert-editor {
  display: grid;
  gap: 0.6rem;
  margin-top: 0.75rem;
  padding-top: 0.75rem;
  border-top: 1px solid var(--line);
}
.alert-field {
  display: grid;
  gap: 0.25rem;
  font-size: 0.85rem;
  color: var(--ink-soft);
}
/* No control styling here on purpose. The fields ARE .input / .select /
   .textarea, the same primitives every other form on the site uses, because
   the parallel rule that used to live here is how this page ended up with
   black text on a near-black ground in dark mode: it reached for --ink, which
   is the page's own ground colour, where the primitives use --text. A
   second set of rules for the same widget only ever drifts. */
.alert-field .textarea { min-height: 4.5rem; }
.hub-card__list {
  margin: 0.4rem 0 0;
  padding-left: 1.1rem;
  font-size: 0.875rem;
  line-height: 1.55;
}
.hub-card__list a { color: inherit; }
/* The page is a stack of sections, and a heading flush against the block
   above it reads as part of that block. */
/* The Alerts page head: the title and its one factual line on the left, the
   button that opens the create form on the right. */
.alerts-head {
  display: flex;
  align-items: flex-start;
  gap: 1rem;
  flex-wrap: wrap;
}
.alerts-head__text { flex: 1 1 18rem; min-width: 0; }
.alerts-head > .btn { flex: 0 0 auto; }

/* Three tabs. Underline rather than a segmented pill, because one of them
   carries a live count and another a fault mark, and a pill row with badges
   in it reads as a row of chips rather than as navigation. */
.vtabs {
  display: flex;
  gap: 0.125rem;
  margin-top: 1.25rem;
  border-bottom: 1px solid var(--line);
  overflow-x: auto;
  scrollbar-width: none;
}
.vtabs::-webkit-scrollbar { display: none; }
.vtabs__btn {
  appearance: none;
  background: none;
  border: 0;
  border-bottom: 2px solid transparent;
  color: var(--muted);
  font: inherit;
  font-size: 0.9375rem;
  padding: 0.7rem 1rem;
  min-height: 44px;
  display: flex;
  align-items: center;
  gap: 0.4rem;
  cursor: pointer;
  white-space: nowrap;
  transition: color 0.15s var(--ease), border-color 0.15s var(--ease);
}
.vtabs__btn:hover { color: var(--text); }
.vtabs__btn.is-on {
  color: var(--text);
  font-weight: 600;
  border-bottom-color: var(--accent);
}
.vtabs__btn:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.vtabs__count {
  font-family: var(--font-mono);
  font-size: 0.75rem;
  line-height: 1;
  padding: 0.2rem 0.45rem;
  border-radius: var(--r-pill);
  border: 1px solid var(--line);
  color: var(--muted);
}
/* Unread is the one count that is about something waiting for you, so it is
   the one that is filled rather than outlined. */
.vtabs__count--live {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--accent-ink);
  font-weight: 600;
}
.vtabs__dot {
  width: 0.4375rem;
  height: 0.4375rem;
  border-radius: var(--r-pill);
  background: var(--warn);
}
.vtabs__panel { margin-top: 0.25rem; }
/* The panel's first section sits under the tabs, which already separate it
   from the head; a second top margin double-spaces it. */
.vtabs__panel > .alerts-section:first-child { margin-top: 1.25rem; }

.alerts-create {
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  padding: 1rem 1.15rem 1.25rem;
  background: var(--surface-2);
}
.alerts-create .alerts-head { align-items: center; }

.alerts-section { margin-top: 1.75rem; }
.alerts-section .gigs-head { margin-bottom: 0.5rem; }
/* The create form is a form, not a layout: at full page width its inputs run
   a thousand pixels for a two-digit number. */
.alert-editor--new {
  max-width: 34rem;
  border-top: 0;
  padding-top: 0;
}
.alert-editor--new button[type='submit'] { justify-self: start; }
#alerts-history th,
#alerts-history td { padding: 0.45rem 0.9rem 0.45rem 0; vertical-align: top; }
#alerts-history th:first-child,
#alerts-history td:first-child { min-width: 14rem; }

/* Where one notification goes. A fieldset rather than a row of chips: these
   are checkboxes that can both be on, and a chip row reads as a choice. */
.alert-channels {
  display: grid;
  gap: 0.35rem;
  margin: 0;
  padding: 0.6rem 0.75rem 0.7rem;
  border: 1px solid var(--line);
  border-radius: 10px;
}
.alert-channels legend {
  padding: 0 0.35rem;
  font-size: 0.85rem;
  color: var(--ink-soft);
}
.alert-channel {
  display: flex;
  align-items: center;
  gap: 0.45rem;
  font-size: 0.9rem;
}
.alert-channel input { margin: 0; }
.alert-channels .muted { margin: 0.15rem 0 0; font-size: 0.8rem; }
/* The destination on a card, distinguished from the settings beside it: the
   other tags say what the rule does, this one says where it lands. */
.tag--channel {
  border-color: var(--accent, var(--line));
  color: var(--ink-soft);
}
/* Activity as a dated line.
   A day at a time, each event at the clock time it happened and named by the
   alert behind it, because every question asked of this list is a question
   about when and about which alert. */
.timeline {
  display: flex;
  flex-direction: column;
  gap: 1.35rem;
  /* Off the filter bar above it: the first day heading is a heading, not a
     fourth button. */
  margin-top: 1.1rem;
}

.timeline__date {
  margin: 0 0 0.55rem;
  font-family: var(--font-mono);
  font-size: 0.75rem;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  /* Muted, not faint: at this size faint falls under AA, and this is the
     only place the actual date is written. */
  color: var(--muted);
}

.timeline__events {
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
  /* The line itself, with the day's events strung along it. */
  margin: 0;
  padding: 0 0 0 0.6rem;
  border-left: 1px solid var(--line-soft);
  list-style: none;
}

.timeline__event {
  position: relative;
  display: grid;
  /* The clock times form a column - the same fixed width on every event, so
     a glance down the left edge answers "when" without reading a word of the
     right. Wide enough for a locale that writes 12:34 PM as well as 12:34. */
  grid-template-columns: 4.4rem minmax(0, 1fr);
  gap: 0.2rem 0.7rem;
  padding: 0.55rem 0.75rem;
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: var(--surface);
}

/* The bead on the line. Decorative, and off the accessibility tree by being
   a pseudo-element at all. */
.timeline__event::before {
  content: '';
  position: absolute;
  top: 0.95rem;
  left: -0.95rem;
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--line);
}

/* Unread is a left edge and a lit bead, not a fill: the line is read by
   scanning down it, and a tinted row makes the text harder to read for
   exactly the rows that most want reading. */
.timeline__event.is-unread {
  border-left: 3px solid var(--accent);
}

.timeline__event.is-unread::before {
  background: var(--accent);
}

.timeline__at {
  font-family: var(--font-mono);
  font-size: 0.78rem;
  color: var(--muted);
  white-space: nowrap;
}

.timeline__what {
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
  min-width: 0;
}

.timeline__who {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.35rem;
}

/* The alert's own number, worn the same way on its card in Your alerts so the
   two can be matched by eye. */
.timeline__id {
  flex-shrink: 0;
  padding: 0.12rem 0.4rem;
  border: 1px solid var(--accent);
  border-radius: var(--r-sm);
  font-family: var(--font-mono);
  font-size: 0.75rem;
  font-weight: 600;
  letter-spacing: 0.04em;
  /* Full text weight, not muted: this is a name, and the whole use of it is
     being able to match it against the one on the alert's own card. */
  color: var(--text);
}

/* A service notice is nobody's alert, so its chip does not pretend to be a
   number. */
.timeline__id--service {
  border-color: var(--line);
  color: var(--muted);
}

.timeline__rule {
  min-width: 0;
  overflow: hidden;
  font-size: 0.78rem;
  color: var(--muted);
  text-overflow: ellipsis;
  white-space: nowrap;
}

.timeline__rule--gone {
  font-style: italic;
  color: var(--faint);
}

.timeline__title {
  margin: 0;
  font-size: 0.95rem;
  font-weight: 600;
}

.timeline__body {
  margin: 0;
  font-size: 0.85rem;
  color: var(--muted);
}

.timeline__acts {
  display: flex;
  flex-wrap: wrap;
  gap: 0.3rem;
  margin-top: 0.1rem;
}

/* On a phone the time gives up its column and sits above the event rather
   than squeezing the title into nothing. */
@media (max-width: 560px) {
  .timeline__event {
    grid-template-columns: minmax(0, 1fr);
  }
}

/* --- resume review ---------------------------------------------------------
   Feedback on a resume, and proposed edits shown change by change before
   anything is saved. Two colours carry the whole thing: the same green and red
   a diff has always used, on a wash rather than a fill so the text stays the
   page's own. Everything else is the panel's existing type and tags. */
.rr {
  margin: 1.25rem 0 0.5rem;
  padding: 1rem 1rem 1.1rem;
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: var(--surface);
}
.rr h3 {
  margin: 0 0 0.35rem;
}
.rr h4 {
  margin: 0.1rem 0 0;
  font-size: 1.05rem;
}
.rr h5 {
  margin: 1rem 0 0.35rem;
  font-size: 0.8125rem;
  font-weight: 600;
  letter-spacing: 0.01em;
  color: var(--muted);
  text-transform: uppercase;
}
.rr__controls {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 2fr);
  gap: 0.75rem;
  margin-top: 0.75rem;
}
.rr__field {
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
  min-width: 0;
}
.rr__field .select {
  width: 100%;
  min-width: 0;
}
.rr__otherjob {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  margin-top: 0.6rem;
}
.rr__otherrow {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 0.5rem;
}
.rr__otherdesc {
  width: 100%;
  min-height: 7rem;
  font: inherit;
  font-size: 0.875rem;
  resize: vertical;
}
.rr__actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin-top: 0.75rem;
}
.rr__hint {
  margin-top: 0.4rem;
}
.rr__out {
  margin-top: 0.9rem;
}
.rr__card {
  padding: 0.9rem 1rem 1rem;
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: var(--surface);
}
.rr__card--proposal {
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-wash);
}
.rr__cardhead {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: 1rem;
}
.rr__stats {
  margin: 0.3rem 0 0;
  font-size: 0.75rem;
  color: var(--faint);
}
.rr__fit {
  display: flex;
  flex-direction: column;
  align-items: center;
  min-width: 3.6rem;
  padding: 0.35rem 0.5rem;
  border: 1px solid var(--line);
  border-radius: var(--r-md);
}
.rr__fitnum {
  font-size: 1.5rem;
  font-weight: 600;
  line-height: 1;
}
.rr__fitlabel {
  font-size: 0.6875rem;
  color: var(--faint);
  text-transform: uppercase;
  letter-spacing: 0.06em;
}
.rr__overall {
  margin: 0.75rem 0 0;
  font-size: 0.95rem;
  line-height: 1.5;
}
.rr__list {
  margin: 0;
  padding-left: 1.2rem;
  font-size: 0.875rem;
  line-height: 1.5;
}
.rr__issues,
.rr__changes {
  margin: 0;
  padding: 0;
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
}
.rr__issue,
.rr__change {
  padding: 0.6rem 0.75rem;
  border: 1px solid var(--line-soft);
  border-left-width: 3px;
  border-radius: var(--r-sm);
  font-size: 0.875rem;
}
.rr__issue--high {
  border-left-color: var(--danger);
}
.rr__issue--medium {
  border-left-color: var(--warn);
}
.rr__issue--low {
  border-left-color: var(--line);
}
.rr__issuehead,
.rr__changehead {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem;
  margin-bottom: 0.35rem;
}
.rr__sev--high {
  color: var(--danger);
  border-color: currentColor;
}
.rr__sev--medium {
  color: var(--warn);
  border-color: currentColor;
}
.rr__where {
  font-size: 0.75rem;
  color: var(--faint);
}
.rr__finding {
  margin: 0;
}
.rr__fix {
  margin: 0.3rem 0 0;
  color: var(--muted);
}
.rr__terms {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: 1rem;
}
.rr__chips {
  display: flex;
  flex-wrap: wrap;
  gap: 0.35rem;
}
.rr__tabs {
  margin-top: 1rem;
  width: max-content;
  max-width: 100%;
}
.rr__view {
  margin-top: 0.75rem;
}
.rr__change {
  border-left-color: var(--accent);
}
.rr__before,
.rr__after {
  display: grid;
  grid-template-columns: 2.4rem minmax(0, 1fr);
  gap: 0.4rem;
  padding: 0.35rem 0.5rem;
  border-radius: var(--r-sm);
  line-height: 1.45;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}
.rr__before {
  background: rgba(180, 35, 24, 0.08);
  text-decoration: line-through;
  text-decoration-color: rgba(180, 35, 24, 0.5);
}
.rr__after {
  margin-top: 0.25rem;
  background: rgba(6, 118, 71, 0.1);
}
.rr__lbl {
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--faint);
  text-decoration: none;
  padding-top: 0.15rem;
}
.rr__reason {
  margin: 0.4rem 0 0;
  color: var(--muted);
}
.rr__claim {
  margin-top: 0.5rem;
  padding: 0.45rem 0.6rem;
  border: 1px dashed var(--line);
  border-radius: var(--r-sm);
  background: var(--accent-wash);
}
.rr__diff {
  max-height: 32rem;
  overflow: auto;
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  font-family: var(--font-mono);
  font-size: 0.78rem;
  line-height: 1.5;
}
.rr__line {
  display: grid;
  grid-template-columns: 1.4rem minmax(0, 1fr);
  padding: 0 0.5rem 0 0;
}
.rr__mark {
  text-align: center;
  color: var(--faint);
  user-select: none;
}
.rr__linetext {
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}
.rr__line--add {
  background: rgba(6, 118, 71, 0.12);
}
.rr__line--add .rr__mark {
  color: var(--ok);
}
.rr__line--del {
  background: rgba(180, 35, 24, 0.1);
  color: var(--muted);
}
.rr__line--del .rr__mark {
  color: var(--danger);
}
.rr__pre {
  margin: 0;
  padding: 0.75rem;
  max-height: 32rem;
  overflow: auto;
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  font-family: var(--font-mono);
  font-size: 0.78rem;
  line-height: 1.5;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}
.rr__novelterm {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  padding: 0.2rem 0.55rem 0.2rem 0.4rem;
  border: 1px solid var(--warn);
  border-radius: var(--r-pill);
  font-size: 0.8125rem;
  cursor: pointer;
}
.rr__novelterm--number {
  border-color: var(--danger);
}
.rr__novelterm input {
  margin: 0;
}
.rr__approve {
  margin-top: 1.1rem;
  padding: 0.9rem 1rem 1rem;
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: var(--accent-wash);
}
.rr__approve h5 {
  margin-top: 0;
}
.rr__mode {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  font-size: 0.875rem;
}
.rr__mode .input {
  width: min(100%, 18rem);
  margin-left: 0.25rem;
  vertical-align: middle;
}
.rr__attest {
  margin-top: 0.8rem;
  padding: 0.6rem 0.75rem;
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  background: var(--surface);
  font-size: 0.875rem;
  line-height: 1.5;
}
.rr__gate {
  margin-top: 0.5rem;
}
.rr__history {
  margin-top: 1rem;
}
.rr__hist {
  margin: 0;
  padding: 0;
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
  font-size: 0.8125rem;
}
.rr__histrow {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.5rem 0.75rem;
  padding: 0.3rem 0;
  border-bottom: 1px solid var(--line-soft);
}
.rr__histwhat {
  flex: 1;
  min-width: 12rem;
}
.rr__histmeta {
  font-size: 0.7rem;
  color: var(--faint);
}
.rr__histrow--proposed .rr__histmeta {
  color: var(--accent);
}
@media (max-width: 720px) {
  .rr__controls,
  .rr__otherrow {
    grid-template-columns: minmax(0, 1fr);
  }
  .rr__cardhead {
    flex-direction: column;
  }
}

/* ============================ APPLICATIONS ============================== */
/* Every application on one page. The overview is a bar of the stages over
   four stat tiles; the toolbar is the search, the chips and the controls;
   the rows each carry a track - three marks on a line, filled as far as
   the application has reached - beside how long it has waited and the
   controls that move it. Colours are the stage tokens the kanban's column
   dots already wear (--st-*) for the marks, and Home's ordered stage scale
   (--stage-*) for the bar, so nothing here means a new thing. */

.apps-page {
  display: flex;
  flex-direction: column;
  gap: 0.9rem;
  max-width: 88rem;
}

.apps-wait {
  font-size: 0.875rem;
}

.apps-overview__inner {
  display: flex;
  flex-direction: column;
  gap: 0.9rem;
}

/* The bar: part to whole, segments parted by the surface showing through. */
.apps-bar {
  display: flex;
  flex-direction: column;
  gap: 0.55rem;
  padding: 0.85rem 1rem;
  border: 1px solid var(--line-soft);
  border-radius: var(--r-md);
  background: var(--surface);
}

.apps-bar__track {
  display: flex;
  gap: 2px;
  height: 10px;
  overflow: hidden;
  border-radius: 5px;
  background: var(--surface-2);
}

.apps-bar__seg {
  display: block;
  min-width: 4px;
}

.apps-bar__legend {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.3rem 0.5rem;
}

/* A key is a filter: the one pressed is filled with ink like a chip. */
.apps-bar__key {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0.2rem 0.6rem;
  border: 1px solid transparent;
  border-radius: var(--r-pill);
  background: transparent;
  color: var(--muted);
  font: inherit;
  font-size: 0.8rem;
  cursor: pointer;
}

.apps-bar__key:hover {
  background: var(--surface-2);
  color: var(--text);
}

.apps-bar__key.is-on {
  background: var(--text);
  border-color: var(--text);
  color: var(--ink);
}

.apps-bar__swatch {
  width: 0.6rem;
  height: 0.6rem;
  border-radius: 2px;
  flex: none;
}

.apps-bar__n {
  font-size: 0.75rem;
}

.apps-bar__key.is-on .apps-bar__n {
  color: inherit;
}

.apps-bar__total {
  margin-left: auto;
  font-size: 0.75rem;
  color: var(--faint);
}

/* Four tiles in a row on a desk, two by two on a phone. */
.apps-tiles {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 0.9rem;
}

.apps-tile.is-on {
  border-color: var(--accent);
  background: var(--accent-wash);
}

.apps-tile--still {
  cursor: default;
}

.apps-tile--still:hover {
  border-color: var(--line-soft);
}

/* The toolbar wraps: the search first, the chips, then the controls. */
.apps-toolbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.6rem 1rem;
}

.apps-search .input--search {
  width: 14rem;
}

.apps-chips {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.35rem;
}

/* A hairline between the stage chips and the toggles. */
.apps-chips__gap {
  width: 1px;
  height: 1.4rem;
  margin: 0 0.3rem;
  background: var(--line);
}

/* A toggle chip is dashed until it is on: it adds a condition rather than
   choosing one of a set. */
.chip--toggle {
  border-style: dashed;
}

.chip--toggle.is-on {
  border-style: solid;
}

.apps-controls {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem;
  margin-left: auto;
}

.apps-select {
  width: auto;
  padding: 0.32rem 1.9rem 0.32rem 0.7rem;
}

@media (min-width: 881px) {
  .apps-select {
    font-size: 0.8125rem;
  }
}

.apps-tidy .chip__n {
  color: var(--accent);
  opacity: 1;
}

.apps-clear {
  font-size: 0.8rem;
}

/* The selection bar sticks under the topbar while rows are picked. */
.apps-bulk {
  position: sticky;
  top: var(--topbar-h, 0);
  z-index: 5;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem 0.75rem;
  padding: 0.55rem 0.85rem;
  border: 1px solid var(--accent);
  border-radius: var(--r-md);
  background: var(--accent-wash);
  backdrop-filter: blur(8px);
  font-size: 0.875rem;
}

.apps-bulk[hidden] {
  display: none;
}

.apps-bulk__n {
  font-variant-numeric: tabular-nums;
}

.apps-bulk__spring {
  flex: 1;
}

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

.approws {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}

/* Pick, who, track, wait, controls - one row, the track given the room.
   The controls column is a fixed width rather than auto: a "Chased 2d ago"
   button is wider than "Followed up", and a row with no chase button at
   all is narrower, and an auto track let every such row nudge its track
   a few pixels off its neighbours'. The marks must line up down the page. */
.approw {
  display: grid;
  grid-template-columns: auto minmax(12rem, 1.4fr) minmax(15rem, 2fr) minmax(8rem, 0.9fr) 21rem;
  align-items: center;
  gap: 0.6rem 1rem;
  padding: 0.7rem 0.85rem;
  border: 1px solid var(--line-soft);
  border-radius: var(--r-md);
  background: var(--surface);
  cursor: pointer;
}

.approw:hover {
  border-color: var(--line);
}

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

.approw.is-picked {
  border-color: var(--accent);
  background: var(--accent-wash);
}

/* A row that is waiting on the person wears the signal at its edge - the
   marker colour, kept to a mark. */
.approw.is-quiet {
  box-shadow: inset 3px 0 0 var(--signal);
}

.approw.is-off .approw__title,
.approw.is-archived .approw__title {
  color: var(--muted);
}

.approw.is-archived {
  opacity: 0.7;
}

.approw__pick {
  display: grid;
  place-items: center;
  padding: 0.3rem;
  cursor: pointer;
}

.approw__pick input {
  width: 1rem;
  height: 1rem;
  margin: 0;
  accent-color: var(--accent);
  cursor: pointer;
}

.approw__who {
  display: flex;
  align-items: flex-start;
  gap: 0.6rem;
  min-width: 0;
}

.approw__text {
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
}

.approw__company {
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  font-weight: 500;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--muted);
  overflow-wrap: anywhere;
}

.approw__title {
  font-size: 0.9375rem;
  font-weight: 600;
  letter-spacing: -0.01em;
  line-height: 1.3;
  overflow-wrap: anywhere;
}

.approw__meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.25rem 0.5rem;
  font-family: var(--font-mono);
  font-size: 0.75rem;
  color: var(--faint);
  overflow-wrap: anywhere;
}

.approw__meta .tag {
  margin-left: 0;
}

/* --- the track ---------------------------------------------------------- */

.track {
  display: flex;
  align-items: flex-start;
  min-width: 0;
  padding-top: 0.15rem;
}

.track__step {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.2rem;
  flex: none;
  width: 4.6rem;
  text-align: center;
}

.track__dot {
  display: grid;
  place-items: center;
  width: 0.9rem;
  height: 0.9rem;
  border-radius: 50%;
  border: 2px solid var(--line);
  background: var(--surface);
  font-size: 0.7rem;
  line-height: 1;
  color: var(--ink);
}

.track__step.is-done .track__dot {
  border-color: var(--col);
  background: var(--col);
}

/* The mark it is at now, ringed so the eye finds it first. */
.track__step.is-current .track__dot {
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--col) 22%, transparent);
}

.track__step.is-off .track__dot {
  border-color: var(--col);
  background: var(--col);
  font-weight: 700;
}

.track__name {
  font-size: 0.7rem;
  color: var(--faint);
  line-height: 1.2;
}

.track__step.is-done .track__name,
.track__step.is-off .track__name {
  color: var(--text);
  font-weight: 600;
}

.track__date {
  font-size: 0.6875rem;
  color: var(--muted);
  min-height: 0.9rem;
  line-height: 1.2;
}

/* The line between marks, filled behind the ones reached. Set to the dot's
   height so it runs through the dots' centres. */
.track__line {
  flex: 1;
  min-width: 0.6rem;
  height: 2px;
  margin-top: calc(0.45rem - 1px);
  background: var(--line);
}

.track__line.is-done {
  background: var(--text);
}

.track__line.is-off {
  background: var(--st-rejected);
}

.approw.is-archived .track {
  opacity: 0.75;
}

/* --- the wait and the controls ----------------------------------------- */

.approw__wait {
  display: flex;
  flex-direction: column;
  gap: 0.1rem;
  min-width: 0;
}

.approw__lead {
  font-size: 0.875rem;
  font-weight: 600;
}

.approw.is-quiet .approw__lead {
  color: var(--warn);
}

.approw__sub {
  font-size: 0.75rem;
  color: var(--muted);
}

.approw__act {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 0.4rem;
  cursor: default;
}

.approw__act .status-select {
  width: auto;
}

.apps-chase.is-done {
  color: var(--muted);
  font-weight: 400;
}

/* --- the pipeline face -------------------------------------------------- */

/* Four columns, five with the way out - a card carries the board it is
   from and how long it has sat, which a single board never needed. */
.kanban--apps .kcard__foot {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.25rem 0.5rem;
  font-size: 0.6875rem;
  color: var(--faint);
}

.kcard__board {
  font-family: var(--font-mono);
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

.kcard__quiet {
  color: var(--warn);
  font-weight: 600;
}

.kcard__receipt {
  color: var(--ok);
}

.kanban--apps .kcard.is-quiet {
  box-shadow: inset 3px 0 0 var(--signal), var(--shadow-sm);
}

/* --- tidy up ------------------------------------------------------------ */

.tidy {
  display: flex;
  flex-direction: column;
  gap: 0.8rem;
}

.tidy__lede,
.tidy__foot {
  font-size: 0.875rem;
  color: var(--muted);
  line-height: 1.5;
}

.tidy__row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem 1rem;
  padding: 0.75rem 0.85rem;
  border: 1px solid var(--line-soft);
  border-radius: var(--r-md);
  background: var(--surface-2);
}

.tidy__text {
  flex: 1;
  min-width: 14rem;
  display: flex;
  flex-direction: column;
  gap: 0.2rem;
  font-size: 0.875rem;
}

.tidy__why {
  color: var(--muted);
  font-size: 0.8rem;
  line-height: 1.45;
}

.tidy__names {
  font-size: 0.7rem;
  color: var(--faint);
}

.tidy__actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.4rem;
}

.tidy__confirm {
  font-size: 0.8125rem;
  font-weight: 600;
}

/* --- the rest ----------------------------------------------------------- */

.apps-gone:empty {
  display: none;
}

.apps-gone__box {
  border: 1px dashed var(--line);
  border-radius: var(--r-md);
  padding: 0.6rem 0.85rem;
}

.apps-gone__sum {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.4rem 0.75rem;
  cursor: pointer;
  font-size: 0.875rem;
  font-weight: 600;
}

.apps-gone__hint {
  font-family: var(--font-mono);
  font-size: 0.7rem;
  font-weight: 400;
  color: var(--faint);
}

.apps-gone__box[open] .apps-gone__sum {
  margin-bottom: 0.6rem;
}

.apps-empty__doors {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 0.6rem;
}

.apps-nomatch {
  margin-top: 0;
}

/* The drawer's record: the trail, newest first, and the posting on request. */
.apprec {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}

.apprec__trail {
  margin: 0;
  padding: 0;
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
  font-size: 0.8125rem;
}

.apprec__event {
  display: flex;
  gap: 0.75rem;
  align-items: baseline;
}

.apprec__when {
  flex: none;
  width: 6.5rem;
  font-size: 0.7rem;
  color: var(--faint);
}

.apprec__note {
  color: var(--muted);
}

.apprec__receipt {
  font-size: 0.8rem;
  color: var(--ok);
}

.apprec .btn {
  align-self: flex-start;
}

/* --- narrower ----------------------------------------------------------- */

@media (max-width: 1100px) {
  .approw {
    grid-template-columns: auto minmax(0, 1fr) minmax(14rem, 1.4fr);
    grid-template-areas:
      'pick who track'
      'pick wait act';
  }
  .approw__pick {
    grid-area: pick;
    align-self: start;
  }
  .approw__who {
    grid-area: who;
  }
  .track {
    grid-area: track;
  }
  .approw__wait {
    grid-area: wait;
  }
  .approw__act {
    grid-area: act;
  }
}

@media (max-width: 880px) {
  .apps-tiles {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
  .apps-controls {
    margin-left: 0;
  }
  .apps-search .input--search {
    width: 100%;
  }
  .apps-search {
    width: 100%;
  }
}

@media (max-width: 640px) {
  .approw {
    grid-template-columns: auto minmax(0, 1fr);
    grid-template-areas:
      'pick who'
      'track track'
      'wait wait'
      'act act';
    gap: 0.5rem 0.6rem;
  }
  .track {
    justify-content: space-between;
  }
  .track__step {
    width: 4rem;
  }
  .approw__act {
    justify-content: flex-start;
  }
  .apps-bulk {
    position: static;
  }
}

@media (prefers-reduced-motion: no-preference) {
  .track__step.is-done .track__dot,
  .track__line.is-done {
    transition: background 0.3s var(--ease), border-color 0.3s var(--ease);
  }
}
