/* Utility Belt — Ambush design system. Dark-first (the hero look), light kept
   functional. Self-contained: no CDN, no web fonts beyond the self-hosted faces
   in fonts.css. Palette and typography are the real Ambush brand, taken from
   getambush.com. Later UI inherits these tokens; existing class names are kept
   working so out-of-scope screens do not regress. */

:root {
  color-scheme: light dark;

  /* Brand — light (functional) */
  --ub-bg: #faf9f7;
  --ub-bg-deep: #f2eee6;
  --ub-surface: #ffffff;
  --ub-surface-2: #f6f2ea;
  --ub-border: #e5e1d8;
  --ub-border-strong: #d8d2c4;
  --ub-ink: #18122b;
  --ub-ink-soft: #5b5768;
  --ub-ink-faint: #8a8598;
  --ub-accent: #9a7d23;
  --ub-accent-hover: #806619;
  --ub-accent-ink: #ffffff;
  --ub-accent-quiet: color-mix(in srgb, var(--ub-accent) 12%, transparent);
  --ub-primary: #49338d;
  --ub-star: #6a63b8;
  --ub-star-ghost: color-mix(in srgb, var(--ub-star) 32%, transparent);

  /* Skill-bar levels (0–5) */
  --ub-lvl-5: #3f9f74;
  --ub-lvl-4: #58a765;
  --ub-lvl-3: #4f7fe0;
  --ub-lvl-2: #7a6dc7;
  --ub-lvl-1: #9a94ad;

  --ub-ok: #2f8f57;
  --ub-danger: #c0392f;

  /* Type */
  --ub-font: "Open Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  --ub-font-display: "Recurse", "Playfair Display", "Iowan Old Style", Georgia, "Times New Roman", serif;
  --ub-font-mono: "JetBrains Mono", ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
  --ub-text-display: clamp(2.1rem, 1.4rem + 3vw, 3.25rem);
  --ub-text-h1: 1.75rem;
  --ub-text-h2: 1.3rem;
  --ub-text-h3: 1.05rem;
  --ub-text-body: 1rem;
  --ub-text-small: 0.85rem;
  --ub-text-label: 0.72rem;

  /* Space + form */
  --ub-radius: 14px;
  --ub-radius-sm: 9px;
  --ub-radius-pill: 999px;
  --ub-shadow: 0 1px 2px rgba(24, 18, 43, 0.05), 0 12px 32px rgba(24, 18, 43, 0.06);
  --ub-shadow-lift: 0 2px 6px rgba(24, 18, 43, 0.08), 0 20px 48px rgba(24, 18, 43, 0.12);
  /* A focus indicator has to clear 3:1 against what it sits on (WCAG 2.2
     SC 1.4.11). The accent could not: gold at 45% composites to rgb(237,224,181)
     on white, which is 1.32:1, and even at full opacity it only reaches 1.92:1 —
     so this is a change of hue, not of opacity. The primary at 60% is 3.34:1. */
  --ub-ring: 0 0 0 3px color-mix(in srgb, var(--ub-primary) 60%, transparent);
}

@media (prefers-color-scheme: dark) {
  :root {
    /* Brand — dark (the hero look) */
    --ub-bg: #0c0a15;
    --ub-bg-deep: #08060f;
    --ub-surface: #16122a;
    --ub-surface-2: #1f1a38;
    --ub-border: #2c2747;
    --ub-border-strong: #3b3559;
    --ub-ink: #f3f1ec;
    --ub-ink-soft: #a7a1b8;
    --ub-ink-faint: #6f6a83;
    --ub-accent: #d8b95a;
    --ub-accent-hover: #e7cd76;
    --ub-accent-ink: #1a1405;
    --ub-accent-quiet: color-mix(in srgb, var(--ub-accent) 15%, transparent);
    --ub-primary: #7b62d6;
    --ub-star: #8f8ad6;
    --ub-star-ghost: color-mix(in srgb, var(--ub-star) 34%, transparent);

    --ub-lvl-5: #4bb587;
    --ub-lvl-4: #57ab6d;
    --ub-lvl-3: #5b8def;
    --ub-lvl-2: #8b7fd0;
    --ub-lvl-1: #6b6780;

    --ub-ok: #4bb587;
    --ub-danger: #e5786f;

    --ub-shadow: 0 1px 2px rgba(0, 0, 0, 0.5), 0 14px 36px rgba(0, 0, 0, 0.45);
    --ub-shadow-lift: 0 2px 8px rgba(0, 0, 0, 0.55), 0 26px 60px rgba(0, 0, 0, 0.55);

    /* Full strength here: 60% of this purple over the dark surface is 2.23:1,
       under the 3:1 the indicator owes. Undiluted it is 3.95:1. */
    --ub-ring: 0 0 0 3px var(--ub-primary);
  }
}

/* The recruiting shell paints a light field whatever the OS scheme is, so it
   takes the light field's ring rather than whichever one `:root` is currently
   answering with. */
.rec-color {
  --ub-ring: 0 0 0 3px color-mix(in srgb, var(--ub-primary) 60%, transparent);
}

* {
  box-sizing: border-box;
}

/* The `hidden` attribute is UA-stylesheet `display: none`, same specificity
   as any single class selector (e.g. `.button { display: inline-flex }`) --
   author rules beat UA rules regardless of load order, so a hidden `.button`
   would otherwise stay visible. Force it to win everywhere. */
[hidden] {
  display: none !important;
}

html {
  -webkit-text-size-adjust: 100%;
}

body {
  margin: 0;
  font-family: var(--ub-font);
  font-size: var(--ub-text-body);
  color: var(--ub-ink);
  background: var(--ub-bg);
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

a {
  color: var(--ub-accent);
}

:focus-visible {
  outline: none;
  box-shadow: var(--ub-ring);
  border-radius: var(--ub-radius-sm);
}

/* ---- Layout ---- */

.app-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding: 0.85rem 1.5rem;
  border-bottom: 1px solid var(--ub-border);
  background: color-mix(in srgb, var(--ub-surface) 75%, transparent);
  backdrop-filter: saturate(140%) blur(8px);
  position: sticky;
  top: 0;
  z-index: 20;
}

.app-mark {
  display: inline-flex;
  align-items: baseline;
  gap: 0.55rem;
  color: var(--ub-ink);
  text-decoration: none;
}

.app-mark__word {
  font-weight: 700;
  font-size: 1.15rem;
  letter-spacing: -0.02em;
}

.app-mark__tag {
  font-family: var(--ub-font-mono);
  font-size: 0.62rem;
  font-weight: 500;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--ub-accent);
  padding-bottom: 0.1rem;
}

.app-main {
  max-width: 44rem;
  margin: 0 auto;
  padding: 2rem 1.25rem 5rem;
}

.app-main--wide {
  max-width: 76rem;
}

.app-main--full {
  max-width: none;
  padding: 0;
}

/* ---- Typography helpers ---- */

.eyebrow {
  font-family: var(--ub-font-mono);
  font-size: var(--ub-text-label);
  font-weight: 500;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--ub-ink-faint);
  margin: 0 0 0.65rem;
}

.hero {
  padding: 1rem 0 1.75rem;
}

.hero__title {
  margin: 0.5rem 0 0.6rem;
  font-size: var(--ub-text-display);
  font-weight: 700;
  letter-spacing: -0.028em;
  line-height: 1.02;
}

.hero__lede {
  margin: 0;
  max-width: 42rem;
  color: var(--ub-ink-soft);
  font-size: 1.1rem;
}

.muted {
  color: var(--ub-ink-soft);
}

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

/* ---- Card ---- */

.card {
  background: var(--ub-surface);
  border: 1px solid var(--ub-border);
  border-radius: var(--ub-radius);
  box-shadow: var(--ub-shadow);
  padding: 1.75rem;
}

.card--centered {
  max-width: 26rem;
  margin: 3rem auto 0;
  text-align: center;
}

.card__title {
  margin: 0 0 0.5rem;
  font-size: var(--ub-text-h1);
  font-weight: 700;
  letter-spacing: -0.02em;
}

.card__subtitle {
  margin: 1.5rem 0 0.5rem;
  font-size: var(--ub-text-h3);
}

.card__lede {
  margin: 0 0 1.5rem;
  color: var(--ub-ink-soft);
}

.card + .card {
  margin-top: 1.5rem;
}

/* ---- Buttons ---- */

.button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  padding: 0.7rem 1.15rem;
  border-radius: var(--ub-radius-sm);
  border: 1px solid var(--ub-border-strong);
  background: var(--ub-surface);
  color: var(--ub-ink);
  font: inherit;
  font-weight: 600;
  text-decoration: none;
  cursor: pointer;
  transition: transform 0.06s ease, background 0.15s ease, border-color 0.15s ease;
}

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

.button:active {
  transform: translateY(1px);
}

.button:disabled {
  cursor: default;
  opacity: 0.6;
  transform: none;
}

.button--primary {
  background: var(--ub-accent);
  border-color: var(--ub-accent);
  color: var(--ub-accent-ink);
}

.button--primary:hover {
  background: var(--ub-accent-hover);
  border-color: var(--ub-accent-hover);
}

.button--ghost {
  background: transparent;
  border-color: var(--ub-border);
}

.button--sm {
  padding: 0.42rem 0.7rem;
  font-size: var(--ub-text-small);
}

/* ---- Pill / badge ---- */

.badge {
  display: inline-flex;
  align-items: center;
  padding: 0.15rem 0.6rem;
  border-radius: var(--ub-radius-pill);
  font-size: 0.8rem;
  font-weight: 700;
}

.badge--pill {
  font-family: var(--ub-font-mono);
  font-size: var(--ub-text-label);
  font-weight: 500;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--ub-accent);
  background: var(--ub-accent-quiet);
  border: 1px solid color-mix(in srgb, var(--ub-accent) 45%, transparent);
  padding: 0.3rem 0.7rem;
}

.badge--allowed {
  background: color-mix(in srgb, var(--ub-ok) 20%, transparent);
  color: var(--ub-ok);
}

.badge--denied {
  background: color-mix(in srgb, var(--ub-danger) 20%, transparent);
  color: var(--ub-danger);
}

.badge--team {
  font-size: var(--ub-text-small);
  font-weight: 600;
  color: var(--ub-accent);
  background: var(--ub-accent-quiet);
  border: 1px solid color-mix(in srgb, var(--ub-accent) 38%, transparent);
}

/* ---- Fields ---- */

.field {
  display: block;
  margin-bottom: 1rem;
  text-align: left;
}

.field__label {
  display: block;
  margin-bottom: 0.4rem;
  font-size: var(--ub-text-small);
  font-weight: 600;
  color: var(--ub-ink-soft);
}

.field__control,
.input {
  width: 100%;
  padding: 0.62rem 0.8rem;
  border-radius: var(--ub-radius-sm);
  border: 1px solid var(--ub-border-strong);
  background: var(--ub-bg);
  color: var(--ub-ink);
  font: inherit;
}

.field__control::placeholder,
.input::placeholder {
  color: var(--ub-ink-faint);
}

/* ---- Messages + banners ---- */

.messages {
  list-style: none;
  margin: 0 0 1.5rem;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}

.message {
  padding: 0.7rem 1rem;
  border-radius: var(--ub-radius-sm);
  border: 1px solid var(--ub-border);
  background: var(--ub-surface);
}

.message--error {
  border-color: color-mix(in srgb, var(--ub-danger) 40%, transparent);
  color: var(--ub-danger);
}

.message--success {
  border-color: color-mix(in srgb, var(--ub-ok) 40%, transparent);
  color: var(--ub-ok);
}

.banner {
  margin-bottom: 1.5rem;
  padding: 0.85rem 1.1rem;
  border-radius: var(--ub-radius);
  font-weight: 600;
}

.banner--stale {
  background: color-mix(in srgb, var(--ub-danger) 14%, transparent);
  color: var(--ub-danger);
  border: 1px solid color-mix(in srgb, var(--ub-danger) 35%, transparent);
}

/* ---- Tables ---- */

.table-wrap {
  overflow-x: auto;
}

.worklist {
  width: 100%;
  border-collapse: collapse;
  margin-top: 0.5rem;
}

.worklist th,
.worklist td {
  padding: 0.65rem 0.55rem;
  border-bottom: 1px solid var(--ub-border);
  text-align: left;
  font-size: 0.9rem;
}

.worklist th {
  font-family: var(--ub-font-mono);
  font-size: var(--ub-text-label);
  font-weight: 500;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ub-ink-faint);
}

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

/* A quarantine row with its decision in flight: dim, and stop taking a second
   press, the same way a console row says it. The button is already out of
   action through hx-disabled-elt; this makes that visible for the whole row the
   answer will replace. */
.worklist tbody tr.htmx-request {
  opacity: 0.55;
  pointer-events: none;
}

/* The row is focused after it is swapped in (rule 6), so the move has to be
   visible to anyone watching rather than only to a screen reader. */
.worklist tbody tr:focus-visible {
  outline: 2px solid var(--ub-primary);
  outline-offset: -2px;
}

/* A worklist whose rows carry controls stops being a table below 1100px, which
   is where the shell's sidebar leaves six columns too little room to reach the
   last one. docs/architecture/utility-belt-decisions.md requires every
   interactive element to stay reachable at every supported viewport, 1024 among
   them (it named 390 too until 2026-09-20) — a control you
   have to find by scrolling a table sideways does not qualify, so the row
   becomes a stack of labelled lines instead. Tables with no controls in them
   keep scrolling inside .table-wrap. */
@media (max-width: 1100px) {
  .worklist--actions,
  .worklist--actions tbody,
  .worklist--actions tr,
  .worklist--actions td {
    display: block;
    width: auto;
  }

  .worklist--actions thead {
    display: none;
  }

  .worklist--actions tr {
    padding: 0.75rem 0;
    border-bottom: 1px solid var(--ub-border);
  }

  .worklist--actions td {
    padding: 0.2rem 0;
    border: 0;
  }

  .worklist--actions td[data-label]::before {
    content: attr(data-label);
    display: block;
    font-family: var(--ub-font-mono);
    font-size: 0.62rem;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--ub-ink-faint);
  }

  .worklist--actions .worklist__actions,
  .worklist--actions .worklist__actions form {
    flex-wrap: wrap;
  }

  .worklist--actions .worklist__actions input[type="text"] {
    width: 100%;
  }
}

/* A refusal beside the control that caused it, never a banner at the top of a
   worklist that may be a hundred rows long. */
.worklist__error {
  margin: 0.3rem 0 0;
  color: var(--ub-danger);
  font-size: var(--ub-text-small);
}

/* The cockpit's sync-control panel: dim while its own change is in flight, and
   show where focus landed once the swap replaced the button that submitted. */
#sync-control.htmx-request {
  opacity: 0.55;
  pointer-events: none;
}

#sync-control:focus-visible {
  outline: 2px solid var(--ub-primary);
  outline-offset: 6px;
  border-radius: var(--ub-radius-sm);
}

.control-panel__error {
  margin: 0.3rem 0 0;
  color: var(--ub-danger);
  font-size: var(--ub-text-small);
}

.worklist__actions form {
  display: flex;
  gap: 0.35rem;
  align-items: center;
}

.worklist__actions input[type="text"] {
  padding: 0.35rem 0.5rem;
  border-radius: var(--ub-radius-sm);
  border: 1px solid var(--ub-border-strong);
  background: var(--ub-bg);
  color: var(--ub-ink);
  font: inherit;
  font-size: 0.85rem;
  width: 8rem;
}

/* ---- Misc existing helpers ---- */

.result {
  margin-top: 1.5rem;
  border-top: 1px solid var(--ub-border);
  padding-top: 1.25rem;
  text-align: left;
}

.reason-code {
  font-family: var(--ub-font-mono);
  font-size: 0.95rem;
}

.surface-links {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem;
}

.status-note {
  display: block;
  margin-top: 0.35rem;
  color: var(--ub-ink-soft);
  font-size: 0.82rem;
}

.status-note--inactive,
.status-note--expired {
  color: var(--ub-danger);
  font-weight: 650;
}

.worklist-detail textarea,
.worklist-detail input[type="date"] {
  width: 100%;
  min-width: 8.5rem;
  padding: 0.5rem;
  border: 1px solid var(--ub-border-strong);
  border-radius: var(--ub-radius-sm);
  background: var(--ub-bg);
  color: var(--ub-ink);
  font: inherit;
}

.worklist-detail textarea {
  min-width: 18rem;
  resize: vertical;
}

.worklist-detail__inactive {
  background: color-mix(in srgb, var(--ub-danger) 8%, transparent);
}

.form-actions {
  margin-top: 1rem;
  text-align: right;
}

/* ---- Rating scale + progress ---- */

.scale {
  display: grid;
  gap: 0.55rem;
}

.scale__row {
  display: grid;
  grid-template-columns: 6.5rem 1fr;
  gap: 0.9rem;
  align-items: start;
  font-size: var(--ub-text-small);
}

.scale__key {
  font-family: var(--ub-font-mono);
  color: var(--ub-ink-soft);
}

.scale__key .star-on {
  color: var(--ub-star);
}

.scale__key .star-off {
  color: var(--ub-border-strong);
}

.scale__def strong {
  color: var(--ub-ink);
}

.scale__def {
  color: var(--ub-ink-soft);
}

.scale__row--ghost {
  margin-top: 0.35rem;
  padding-top: 0.7rem;
  border-top: 1px solid var(--ub-border);
}

.scale__key .star-ghost {
  color: var(--ub-star-ghost);
}

.scale__na {
  font-family: var(--ub-font-mono);
  font-size: 0.72em;
  letter-spacing: 0.06em;
  padding: 0.05rem 0.35rem;
  border-radius: 5px;
  border: 1px dashed var(--ub-border-strong);
  color: var(--ub-ink-faint);
}

.scale__na--ghost {
  border-color: var(--ub-star-ghost);
  color: color-mix(in srgb, var(--ub-star) 65%, transparent);
}

.recall {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  flex-wrap: wrap;
  margin: 1.25rem 0 0.5rem;
  padding: 0.85rem 1.1rem;
  border-radius: var(--ub-radius);
  background: var(--ub-surface-2);
  border-left: 3px solid var(--ub-primary);
}

.recall__text {
  margin: 0;
  color: var(--ub-ink-soft);
}

.recall__text strong {
  color: var(--ub-ink);
}

.recall__feedback {
  margin: 0;
  width: 100%;
  color: var(--ub-ok);
  font-weight: 600;
}

.progress {
  display: flex;
  align-items: center;
  gap: 0.9rem;
  margin: 1.5rem 0 0.5rem;
}

.progress__track {
  flex: 1;
  height: 6px;
  border-radius: var(--ub-radius-pill);
  background: var(--ub-border);
  overflow: hidden;
}

.progress__bar {
  height: 100%;
  width: 0;
  border-radius: inherit;
  background: linear-gradient(90deg, var(--ub-accent), var(--ub-accent-hover));
  transition: width 0.25s ease;
}

.progress__count {
  font-family: var(--ub-font-mono);
  font-size: var(--ub-text-small);
  color: var(--ub-ink-soft);
  white-space: nowrap;
}

/* ---- Accordion ---- */

.accordion {
  border: 1px solid var(--ub-border);
  border-radius: var(--ub-radius);
  background: var(--ub-surface);
  margin-top: 1rem;
  overflow: hidden;
}

.accordion__head {
  width: 100%;
  display: flex;
  align-items: center;
  gap: 0.8rem;
  padding: 1.1rem 1.35rem;
  background: none;
  border: none;
  color: inherit;
  font: inherit;
  font-weight: 700;
  cursor: pointer;
  text-align: left;
}

.accordion__diamond {
  width: 0.75rem;
  height: 0.75rem;
  flex: none;
  transform: rotate(45deg);
  border: 1.5px solid var(--ub-accent);
  border-radius: 2px;
}

.accordion__name {
  font-size: var(--ub-text-h3);
}

.accordion__count {
  margin-left: auto;
  font-family: var(--ub-font-mono);
  font-size: var(--ub-text-label);
  letter-spacing: 0.1em;
  color: var(--ub-ink-soft);
}

.accordion__chevron {
  flex: none;
  width: 1rem;
  height: 1rem;
  color: var(--ub-ink-faint);
  transition: transform 0.18s ease;
}

.accordion__body {
  padding: 0.4rem 1.35rem 1.35rem;
  border-top: 1px solid var(--ub-border);
}

.accordion:not(.accordion--open) .accordion__chevron {
  transform: rotate(-90deg);
}

.accordion.js-ready:not(.accordion--open) .accordion__body {
  display: none;
}

/* ---- Skill card + stars ---- */

.skillgrid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr));
  gap: 0.8rem;
  margin-top: 0.9rem;
}

.skillcard {
  border: 1px solid var(--ub-border);
  border-radius: var(--ub-radius-sm);
  background: var(--ub-bg);
  padding: 0.95rem 1rem;
}

.skillcard__label {
  font-weight: 600;
  margin-bottom: 0.7rem;
}

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

.stars__na {
  font-family: var(--ub-font-mono);
  font-size: 0.62rem;
  letter-spacing: 0.08em;
  padding: 0.2rem 0.45rem;
  border-radius: 6px;
  border: 1px dashed var(--ub-border-strong);
  background: transparent;
  color: var(--ub-ink-faint);
  cursor: pointer;
}

.stars__na--on {
  border-style: solid;
  border-color: var(--ub-accent);
  color: var(--ub-accent);
  background: var(--ub-accent-quiet);
}

/* Same faded-purple language as the ghost stars: what you answered last time,
   not an answer yet. */
.stars__na--ghost {
  border-color: var(--ub-star-ghost);
  color: color-mix(in srgb, var(--ub-star) 65%, transparent);
}

.stars__row {
  display: inline-flex;
  gap: 0.15rem;
}

.stars__star {
  padding: 0.1rem;
  background: none;
  border: none;
  cursor: pointer;
  color: var(--ub-border-strong);
  line-height: 0;
}

.stars__star svg {
  width: 1.15rem;
  height: 1.15rem;
  fill: currentColor;
}

.stars__star--on {
  color: var(--ub-star);
}

.stars__star--ghost {
  color: var(--ub-star-ghost);
}

/* Native controls: the no-JS fallback. Hidden once the island enhances. */
.stars--enhanced .stars__state,
.stars--enhanced .stars__value {
  display: none;
}

.stars__state,
.stars__value {
  font: inherit;
  padding: 0.3rem 0.4rem;
  border-radius: 6px;
  border: 1px solid var(--ub-border-strong);
  background: var(--ub-surface);
  color: var(--ub-ink);
}

.stars__value {
  width: 3.5rem;
  margin-left: 0.4rem;
}

/* ---- Sticky action bar ---- */

.actionbar {
  position: sticky;
  bottom: 0;
  display: flex;
  align-items: center;
  justify-content: flex-end;
  flex-wrap: wrap;
  gap: 0.75rem;
  margin-top: 1.5rem;
  padding: 0.9rem 1.1rem;
  border: 1px solid var(--ub-border);
  border-radius: var(--ub-radius);
  background: color-mix(in srgb, var(--ub-surface) 88%, transparent);
  backdrop-filter: saturate(140%) blur(8px);
  box-shadow: var(--ub-shadow-lift);
}

/* A save in flight. The bar is sticky and holds both controls, so dimming it
   says a press landed without moving anything the contractor is reading. */
.actionbar.htmx-request {
  opacity: 0.55;
  pointer-events: none;
}

/* Sticky is right at a desk, where the bar follows a long list of ratings. In a
   short window it covers whatever is under it, and a control hidden behind a
   floating bar is not reachable.

   **Its stated width lost its checker on 2026-09-20**, when 390x844 stopped
   being a supported viewport: this breakpoint is 640px and nothing measures it
   any more. Kept rather than deleted — the rule it encodes is still true of any
   short window, and removing a live rule because its test went is a separate
   decision from removing the test. */
@media (max-width: 640px) {
  .actionbar {
    position: static;
  }
}

/* The conflict a stale revision produces, beside the control that produced it. */
.actionbar .message--error {
  flex: 1 1 100%;
  margin: 0;
}

/* ---- Home launcher ---- */

.tool-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(15.5rem, 1fr));
  gap: 1rem;
  margin-top: 0.5rem;
}

.tool-card {
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
  padding: 1.35rem;
  border: 1px solid var(--ub-border);
  border-radius: var(--ub-radius);
  background: var(--ub-surface);
  box-shadow: var(--ub-shadow);
  color: inherit;
  text-decoration: none;
  transition: transform 0.08s ease, border-color 0.15s ease, box-shadow 0.15s ease;
}

.tool-card:hover {
  transform: translateY(-2px);
  border-color: color-mix(in srgb, var(--ub-accent) 55%, var(--ub-border));
  box-shadow: var(--ub-shadow-lift);
}

.tool-card__icon {
  width: 2.4rem;
  height: 2.4rem;
  display: grid;
  place-items: center;
  border-radius: 10px;
  color: var(--ub-accent);
  background: var(--ub-accent-quiet);
  border: 1px solid color-mix(in srgb, var(--ub-accent) 30%, transparent);
}

.tool-card__icon svg {
  width: 1.25rem;
  height: 1.25rem;
}

.tool-card__name {
  margin: 0.15rem 0 0;
  font-size: var(--ub-text-h3);
  font-weight: 700;
}

.tool-card__desc {
  margin: 0;
  color: var(--ub-ink-soft);
  font-size: var(--ub-text-small);
}

.tool-card__go {
  margin-top: auto;
  font-family: var(--ub-font-mono);
  font-size: var(--ub-text-label);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ub-accent);
}

.empty-note {
  padding: 2rem;
  text-align: center;
  border: 1px dashed var(--ub-border-strong);
  border-radius: var(--ub-radius);
  color: var(--ub-ink-soft);
}

/* ---- Talent Search (conversational surface) ---- */

/* The shell pins the --hs-* palette to a committed-light theme; --ub-ok and
   --ub-danger do flip with the system theme, so this surface keeps its own. */
.ts-workspace {
  --ts-ok: #2f8f57;
  --ts-danger: #c0392f;
  /* The hairline and hover of a dense purple-field table, the two values
     `recruiting-color.css` already states literally for `.rec-table` rows.
     Named here rather than repeated: the comparison table is the same kind of
     surface and must not drift from it by a hex. */
  --ts-row-line: #efecf7;
  --ts-row-hover: #faf9fd;

  /* Five steps, and no component picks a size of its own. This screen carried
     eighteen distinct sizes, fifteen of them between 8.96px and 14px — the
     difference between 13.12px and 13.28px is not a hierarchy anybody reads, it
     is each component having chosen its own 0.82rem. The steps are named for
     what they carry, so a new component asks what it is rather than what it
     should measure. */
  --ts-text-label: 0.7rem;
  --ts-text-small: 0.82rem;
  --ts-text-name: 0.95rem;
  --ts-text-card: 1.05rem;
  --ts-text-count: 1.5rem;

  display: grid;
  /* A rail, not a column: the conversation is prose about the result and the
     result is what the reviewer came for, so the width goes to the table. */
  grid-template-columns: 21.25rem minmax(0, 1fr);
  grid-template-areas: "convo rail";
  gap: 1.25rem;
  align-items: start;
  padding-bottom: 3rem;
}

.ts-workspace > * {
  min-width: 0;
}

/* Inside a bounded page both columns are the full height and neither adds to
   it: `min-height: 0` is what lets a grid child be shorter than its content and
   scroll, and without it the table pushes the page open again. */
.hs-page--bounded .ts-workspace {
  height: 100%;
  align-items: stretch;
  padding-bottom: 0;
}

.hs-page--bounded .ts-workspace > *,
.hs-page--bounded .ts-rail {
  min-height: 0;
}

/* The swapped element places the rail into the grid above without the swap
   boundary having to move. */
.ts-state {
  display: contents;
}

.ts-notice {
  grid-column: 1 / -1;
}

/* ---- the conversation ---- */

/* One card that stays where it is while the work beside it scrolls, with the
   thread scrolling inside it and the composer pinned to its foot. That is the
   shape the surface was missing: the answer used to appear in a third band
   between the thread and the composer, so a reply landed below the question and
   above the box the next one is typed into. */
.ts-convo {
  grid-area: convo;
  position: sticky;
  top: calc(56px + 1rem);
  display: flex;
  flex-direction: column;
  /* Tall enough to read, never taller than the window. `height` would make the
     card a full viewport starting below the page header, which pushes its own
     composer off the bottom of the screen before anything has been scrolled —
     and the floor is capped against the viewport for that same reason: the card
     starts below the page title, so a fixed 44rem is off the bottom of a short
     window before anything has been scrolled. Whichever is smaller wins. */
  min-height: min(44rem, calc(100dvh - 18rem));
  max-height: calc(100dvh - 56px - 2rem);
  background: var(--hs-surface);
  border: 1px solid var(--hs-line);
  border-radius: var(--ub-radius);
  box-shadow: var(--ub-shadow);
  overflow: hidden;
}

/* The composer is first in the document and last in the picture: a requester
   arrives to write, so it is the first thing focus reaches, and a thread puts
   its input at the bottom. */
.ts-convo__head {
  order: 1;
}

/* At the foot before the first turn as well as after it. It used to open at
   the top of an empty card and drop to the foot the moment a turn existed,
   which moved the one control on the screen out from under the pointer about
   to press it — and gave the first visit a composer of a different size and
   place from every visit after it. */
.ts-convo__scroll {
  order: 2;
}

.ts-convo .ts-composer {
  order: 3;
}

/* Full height of the column and nothing sticky to arrange: inside a bounded
   page the card is the column. The floor and the cap both go — `min-height`
   beats `height` in CSS, so the tall floor the scrolling layout sets would win
   over the bound outright. */
.hs-page--bounded .ts-convo {
  position: static;
  top: auto;
  height: 100%;
  min-height: 0;
  max-height: none;
}

.ts-convo__head {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  flex: none;
  padding: 0.7rem 0.875rem;
  border-bottom: 1px solid var(--hs-line);
}

.ts-convo__mark {
  flex: none;
  display: grid;
  place-items: center;
  width: 1.625rem;
  height: 1.625rem;
  border-radius: var(--ub-radius-sm);
  background: var(--hs-ink);
  color: var(--hs-surface);
  font-family: var(--ub-font-display);
  font-size: 0.81rem;
  font-weight: 700;
}

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

.ts-convo__name {
  font-size: var(--ts-text-name, 0.95rem);
  font-weight: 600;
}

.ts-convo__meta {
  font-family: var(--ub-font-mono);
  font-size: var(--ts-text-label, 0.7rem);
  color: var(--hs-ink-faint);
}

.ts-convo__scroll {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  padding: 0.875rem;
  /* No bar of its own. A second scrollbar halfway across the screen reads as a
     frame inside the page; the only one that should be visible is the window's,
     at the right edge. The fade below is the affordance instead. */
  scrollbar-width: none;
  -ms-overflow-style: none;
}

.ts-convo__scroll::-webkit-scrollbar {
  display: none;
}

/* Content running under the header, so a thread with more above it says so
   without a bar. */
.ts-convo__scroll::before {
  content: "";
  position: sticky;
  top: -0.875rem;
  z-index: 1;
  display: block;
  height: 1.6rem;
  margin: -0.875rem -0.875rem -0.5rem;
  pointer-events: none;
  background: linear-gradient(to bottom, var(--hs-surface) 35%, transparent);
}

/* ---- what the agent is for, before there is anything to read ---- */


/* ---- the thread ---- */

.ts-thread {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 1rem;
}

.ts-thread:not(:empty) {
  margin-top: 0.2rem;
}

/* A turn is the pair — what was asked and what came back — and it is drawn as
   a conversation rather than as a card: the requester's side to the right in
   the ink, the agent's to the left on the page ground. It used to be one
   bordered box holding both, which read as a log entry. */
.conversation__turn {
  display: flex;
  flex-direction: column;
  gap: 0.9rem;
}

.ts-turn__row {
  display: flex;
  gap: 0.55rem;
  align-items: flex-start;
}

.ts-turn__row:first-child {
  flex-direction: row-reverse;
}

.ts-turn__row:first-child .ts-turn__body {
  align-items: flex-end;
}

.ts-turn__avatar {
  flex: none;
  width: 1.5rem;
  height: 1.5rem;
  display: grid;
  place-items: center;
  border-radius: var(--ub-radius-sm);
  background: var(--hs-gold-tint);
  border: 1px solid var(--hs-gold-border);
  color: var(--hs-ink);
  font-size: 0.62rem;
  font-weight: 700;
}

.ts-turn__avatar--reply {
  background: var(--hs-ink);
  border-color: var(--hs-ink);
  color: var(--hs-surface);
  font-family: var(--ub-font-display);
  font-size: 0.8rem;
}

.ts-turn__avatar--refused {
  background: var(--hs-bg);
  border-color: var(--hs-line-strong);
  color: var(--ts-danger);
  font-weight: 400;
}

.ts-turn__body {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0.3rem;
  min-width: 0;
  flex: 1;
}

.ts-turn__who {
  font-size: var(--ts-text-label, 0.7rem);
  color: var(--hs-ink-faint);
}

.ts-turn__who--refused {
  color: var(--ts-danger);
}

.conversation__message {
  max-width: 88%;
  margin: 0;
  padding: 0.56rem 0.75rem;
  background: var(--hs-ink);
  border-radius: var(--ub-radius);
  border-bottom-right-radius: 0.25rem;
  color: var(--hs-surface);
  font-size: 0.81rem;
  line-height: 1.6;
  overflow-wrap: anywhere;
}

/* The agent's side of the pair, whichever of the two it is: the prose the
   server sent, or the one sentence a turn says when there is no answer to
   move. */
.ts-turn__body > .conversation__note,
.ts-turn__body > .ts-reply {
  max-width: 94%;
  background: var(--hs-bg);
  border: 1px solid var(--hs-line);
  border-radius: var(--ub-radius);
  border-bottom-left-radius: 0.3rem;
}

/* Padding is set per speaker rather than shared, because the two carry
   different amounts of writing: the note is one sentence, and the reply is the
   model's own paragraphs. A single value tuned for the note leaves the reply's
   prose against its own border. */
.ts-turn__body > .conversation__note {
  margin: 0;
  padding: 0.7rem 0.9rem;
  font-size: var(--ts-text-small, 0.82rem);
  line-height: 1.6;
  color: var(--hs-ink-soft);
}

.conversation__turn--refused .ts-turn__body > .conversation__note {
  background: color-mix(in srgb, var(--ts-danger) 4%, var(--hs-surface));
  border-color: color-mix(in srgb, var(--ts-danger) 22%, transparent);
  color: var(--hs-ink);
}

/* A manual search in the thread. Nobody said anything, so it is a rule across
   the column rather than a bubble: the criteria stopped being the
   conversation's at this point, and the agent's account of the result it
   produced hangs under it like any other answer. */
.ts-manual {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  margin: 0;
}

.ts-manual::before,
.ts-manual::after {
  content: "";
  flex: 1;
  height: 1px;
  background: var(--hs-line);
}

.ts-manual__label {
  flex: none;
  font-family: var(--ub-font-mono);
  font-size: var(--ts-text-label, 0.7rem);
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--hs-ink-faint);
}

/* What the turn did to the count, in the reply's own byline. */
.ts-turn__count {
  color: var(--hs-ink-faint);
  letter-spacing: 0;
  text-transform: none;
}

/* The polite announcement, at the foot of the thread — and normally not drawn at
   all. The element carries `visually-hidden` from the template, which is
   declared later in this file and therefore wins the properties they share; do
   not move that utility above this rule. Everything the conversation island
   announces is already on screen in the turn, so painting it read the same
   sentence twice.

   These are the styles for the one message that has nowhere else to go: a review
   decision the server refused swaps nothing and opens no turn, so
   talent-review.js drops the class and this is what the reviewer then sees. */
.ts-status {
  margin: 1rem 0 0;
  padding: 0.5rem 0.7rem;
  border-radius: var(--ub-radius-sm);
  background: var(--hs-bg);
  font-family: var(--ub-font-mono);
  font-size: 0.7rem;
  line-height: 1.5;
  color: var(--hs-ink-soft);
}

.ts-status:empty {
  display: none;
}

/* ---- the composer ---- */

.ts-composer {
  flex: none;
  display: flex;
  flex-direction: column;
  gap: 0.55rem;
  padding: 0.75rem 0.875rem 0.875rem;
  background: var(--hs-surface);
  border-top: 1px solid var(--hs-line);
}

.ts-composer__label {
  font-size: var(--ts-text-small, 0.82rem);
  font-weight: 600;
  color: var(--hs-ink);
}

.ts-composer textarea {
  width: 100%;
  padding: 0.56rem 0.7rem;
  border: 1px solid var(--hs-line-strong);
  border-radius: var(--ub-radius-sm);
  background: var(--hs-bg);
  color: var(--hs-ink);
  font: inherit;
  font-size: 0.81rem;
  line-height: 1.6;
  resize: vertical;
}

.ts-composer textarea:focus-visible {
  outline: none;
  border-color: var(--hs-gold);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--hs-gold) 45%, transparent);
}

.ts-composer--refused textarea {
  border-color: var(--ts-danger);
}

.ts-composer__foot {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 0.9rem;
  flex-wrap: wrap;
}

/* htmx ships `.htmx-indicator { opacity: 0 }`, which still reserves the row and
   leaves a gap under the composer for the whole of a page's life. This one is
   out of flow until the turn is in flight. */
.ts-thinking {
  display: none;
  align-items: center;
  gap: 0.6rem;
  margin: 0;
}

.ts-thinking.htmx-request {
  display: flex;
}

/* The same element, borrowed by the turn in flight: there it is the agent's
   half of the pair and is drawn as one, in the bubble the answer replaces it
   with. Its label stays behind — the status line a few rows under the thread
   already says the same sentence, and two copies that close together read as
   two different things happening. */
.ts-turn__body > .ts-thinking.htmx-request {
  display: inline-flex;
  padding: 0.7rem 0.9rem;
  background: var(--hs-bg);
  border: 1px solid var(--hs-line);
  border-radius: var(--ub-radius);
  border-bottom-left-radius: 0.3rem;
}

.ts-turn__body > .ts-thinking .ts-thinking__label {
  display: none;
}

/* Except under a manual search, which is the one wait watched from the other
   column: the requester pressed Search in the editor and nothing there says why
   it has gone quiet, so the sentence rides the dots rather than being left to
   the message above them — there is no message above them. */
.conversation__turn--manual .ts-turn__body > .ts-thinking .ts-thinking__label {
  display: inline;
}

.ts-thinking__dots {
  display: inline-flex;
  gap: 0.22rem;
}

.ts-thinking__dots i {
  width: 0.36rem;
  height: 0.36rem;
  border-radius: 999px;
  background: var(--hs-gold);
  animation: ts-thinking-pulse 1.15s ease-in-out infinite;
}

.ts-thinking__dots i:nth-child(2) {
  animation-delay: 0.16s;
}

.ts-thinking__dots i:nth-child(3) {
  animation-delay: 0.32s;
}

/* The dots are the only thing on this surface that moves. Reduced motion gets
   the same row, held at the visible end of the cycle. */
@keyframes ts-thinking-pulse {
  0%,
  100% {
    opacity: 0.25;
    transform: translateY(0);
  }

  50% {
    opacity: 1;
    transform: translateY(-2px);
  }
}

@media (prefers-reduced-motion: reduce) {
  .ts-thinking__dots i {
    animation: none;
    opacity: 1;
  }
}

.ts-thinking__label {
  font-family: var(--ub-font-mono);
  font-size: var(--ts-text-small, 0.82rem);
  color: var(--hs-ink-soft);
}

/* A review decision in flight: the shortlist it is editing stops accepting a
   second press, and says so by dimming rather than by moving anything. */
#review-form.htmx-request {
  opacity: 0.55;
  pointer-events: none;
}

/* A manual search in flight, said the same way and for the same reason. The
   wait itself is drawn once, in the thread — this is only the form saying it is
   no longer taking input, which matters because the two are in different
   columns and the editor is where the click happened. */
.criteria.htmx-request {
  opacity: 0.55;
  pointer-events: none;
}

/* ---- the criteria rail ---- */

.ts-rail {
  grid-area: rail;
  display: flex;
  flex-direction: column;
  gap: 1rem;
  min-width: 0;
}

/* The result column owns its own scrollbar inside the bounded page, so the
   window keeps none. */
.hs-page--bounded .ts-rail {
  height: 100%;
  overflow-y: auto;
}

/* `flex-shrink: 0` is what stops the criteria card being squeezed by the
   ledger under it. The rail is a flex column and both cards are items in it, so
   a long result took the height out of the criteria above it — and because the
   card clips, what was squeezed out was the editor strip at its foot, left
   sliced in half across its own rounded corner. The rail scrolls; nothing in it
   has to give up height. */
.ts-rail .card {
  margin: 0;
  padding: 0;
  overflow: hidden;
  flex-shrink: 0;
}

/* Criteria, what could not be scored, and the manual editor are one card in
   three bands. They were three separate cards, which gave the editor — a way
   out of the conversation — the same weight as the criteria the conversation
   produced. */
.ts-rail__crit {
  padding: 0.7rem 1.1rem 0.75rem;
}

/* A title and nothing else, so it is set as a label for the screen rather than
   as a page-opening statement — the eyebrow, the rule and the lede under it all
   said what the composer beside them already asks for. */
.hs-page__head--slim {
  margin-bottom: 0.9rem;
}

.hs-page__head--slim .hs-page__title {
  font-size: 1.6rem;
}

.ts-rail__title {
  margin: 0;
  padding-bottom: 0.2rem;
  font-family: var(--ub-font-display);
  font-weight: 700;
  font-size: var(--ts-text-card, 1.05rem);
  letter-spacing: -0.01em;
}

.ts-crit__empty {
  margin: 0.75rem 0 0;
  font-size: 0.85rem;
  line-height: 1.6;
  color: var(--hs-ink-soft);
}

.ts-rail__legend {
  margin: 0;
  font-family: var(--ub-font-mono);
  font-size: 0.62rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--hs-ink);
}

/* ---- chips: one requirement, one shape ---- */

.ts-chips {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  gap: 0.35rem;
  flex: 1;
  min-width: 0;
}

.ts-crit__line {
  display: flex;
  align-items: baseline;
  gap: 0.6rem;
  flex-wrap: wrap;
}

.ts-crit__line + .ts-crit__line {
  margin-top: 0.35rem;
}

/* An empty band is one short sentence and carries the paragraph margins the UA
   gives it, which is where most of the card's air came from: two of these put
   32px of nothing between "Core" and "Supporting". */
.ts-crit__line .ts-chips > .muted {
  margin: 0;
  font-size: 0.78rem;
  color: var(--hs-ink-faint);
}

.ts-crit__legend {
  flex: none;
  width: 4.5rem;
  margin: 0;
  padding-top: 0.15rem;
  font-size: var(--ts-text-small, 0.82rem);
  color: var(--hs-ink-faint);
}

/* A requirement and the parts of it nothing verifies are one item, stacked:
   a flat row of dashed chips would say three things are unverified without
   saying what they are part of. */
.ts-crit__item {
  display: inline-flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0.1rem;
  max-width: 100%;
}

/* The requester's own words under the requirement they produced. Quiet and
   under the chip rather than inside it: the label is what a reviewer scans, and
   the phrase is what they reach for when a label surprises them. */
.ts-crit__phrase {
  padding-left: 0.6rem;
  font-size: 0.68rem;
  line-height: 1.35;
  color: var(--hs-ink-faint);
}

.ts-crit__facets {
  display: flex;
  flex-wrap: wrap;
  gap: 0.25rem;
  margin-left: 0.55rem;
  padding-left: 0.5rem;
  border-left: 1px dotted var(--hs-line-strong);
}

.ts-chip {
  display: inline-flex;
  align-items: baseline;
  gap: 0.35rem;
  max-width: 100%;
  padding: 0.16rem 0.5rem;
  border: 1px solid var(--hs-line-strong);
  border-radius: 999px;
  background: var(--hs-surface);
  font-size: var(--ts-text-small, 0.82rem);
  line-height: 1.5;
  overflow-wrap: anywhere;
}

.ts-chip--core {
  border-color: color-mix(in srgb, var(--hs-gold) 42%, transparent);
  background: color-mix(in srgb, var(--hs-gold) 9%, var(--hs-surface));
}

.ts-chip__dot {
  flex: none;
  align-self: center;
  width: 0.3rem;
  height: 0.3rem;
  border-radius: 999px;
  background: var(--hs-gold);
}

/* An any-of group is one requirement met by any one of its members, and the
   chip says so by joining them with "or" — so the marker is a border, not a
   second label competing with the members themselves. */
.ts-chip--anyof {
  border-style: double;
  border-width: 3px;
}

.ts-chip__origin {
  font-size: var(--ts-text-label, 0.7rem);
  color: var(--hs-ink-faint);
}

/* The parts of a requirement nothing here verifies: dashed, because the state
   is an absence of evidence rather than a measured shortfall. */
.ts-chip--gap {
  border-style: dashed;
  background: transparent;
  color: var(--hs-ink-soft);
  font-size: 0.72rem;
}

/* Its own band inside the criteria card: what the search was asked for and
   could not score is part of what this search *is*, and is read where the
   criteria are read. */
/* Under the header and above the rows, which is where "nobody below was checked
   against this" has to be read. A band of the card, never behind the disclosure
   the rest of the criteria live in. */
.ts-rail__unresolved {
  padding: 0.6rem 1.25rem 0.7rem;
  border-bottom: 1px solid var(--hs-line);
  background: color-mix(in srgb, var(--hs-gold) 5%, var(--hs-surface));
}

.ts-rail__unresolved p {
  margin: 0.2rem 0 0;
  font-size: 0.85rem;
  overflow-wrap: anywhere;
}

.ts-rail__unresolved .muted {
  font-size: 0.72rem;
}

/* A foot strip, not a section: the manual editor is the way out of the
   conversation, and as its own card beside the criteria it read as an equal
   offer to use the form instead.

   It keeps the card's own surface rather than the page ground it used to take:
   filled with `--hs-bg` it was the same colour as the page behind the card, so
   the closed strip looked like a second, misaligned slab hanging off the
   bottom instead of the card's own foot. The hairline above it is the whole of
   the separation it needs. */
.ts-rail__editor {
  border-top: 1px solid var(--hs-line);
  background: var(--hs-surface);
}

.ts-rail__editor[open] {
  padding-bottom: 0.9rem;
  background: var(--hs-bg);
}

/* Label left, chevron right, the way a disclosure row reads. It was a bare
   inline label with the marker tucked against it in the middle of an otherwise
   empty strip. */
.ts-rail__editor > summary {
  list-style: none;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.6rem;
  padding: 0.55rem 1.1rem 0.6rem;
  font-size: var(--ts-text-small, 0.82rem);
  font-weight: 600;
  color: var(--hs-ink-soft);
  transition: background 0.15s ease, color 0.15s ease;
}

.ts-rail__editor > p,
.ts-rail__editor > .criteria {
  padding-inline: 1.1rem;
}

.ts-rail__editor > p {
  margin: 0;
  font-size: var(--ts-text-small, 0.82rem);
  color: var(--hs-ink-faint);
}

.ts-rail__editor > summary::-webkit-details-marker {
  display: none;
}

.ts-rail__editor > summary:hover {
  color: var(--hs-gold);
  background: var(--hs-hover);
}

/* The editor is a dense strip, not a page. In the narrow rail it used to live
   in, one control per row was the only option; at this width a stack of
   full-bleed inputs reads as the main event and buries the criteria it is
   attached to. Two columns, capped controls, small labels. */
.ts-rail__editor .criteria {
  /* `.criteria__label`: 0.45rem above, 0.62rem × 1.5 of text, 0.25rem below. */
  --ts-field-offset: 1.63rem;

  margin-top: 0.7rem;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
  /* The row gap carries the whole separation between five groups of very
     different heights, and at 0.6rem the second row read as a continuation of
     the first rather than a row of its own. */
  gap: 1rem 1.4rem;
  align-items: start;
}

.ts-rail__editor fieldset {
  border: 0;
  margin: 0;
  padding: 0;
  min-width: 0;
}

/* The skills list is the tall one and takes the full width; everything else
   pairs up beside its neighbour. */
.criteria__skills,
.ts-rail__editor .form-actions {
  grid-column: 1 / -1;
}

.ts-rail__editor legend {
  padding: 0;
  font-family: var(--ub-font-mono);
  font-size: var(--ts-text-label, 0.7rem);
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--hs-ink-faint);
}

/* A stated line-height, not an inherited one: the height of this block is what
   every column of the row is aligned on, and `--ts-field-offset` is the sum of
   its three numbers. */
.criteria__label {
  display: block;
  margin: 0.45rem 0 0.25rem;
  font-family: var(--ub-font-mono);
  font-size: var(--ts-text-label, 0.7rem);
  line-height: 1.5;
  letter-spacing: 0.06em;
  font-weight: 400;
  color: var(--hs-ink-soft);
}

/* One control, so the column is as wide as the control: at `1fr` a select
   holding a single digit stretched across a third of the editor. */
.criteria__rating {
  max-width: 9rem;
}

/* The column's own heading, in the register the legends beside it use: it is
   the first thing in its column, where the labels it shares a class with sit
   under a legend that has already named their group. */
.criteria__rating > .criteria__label {
  margin-top: 0;
  font-size: 0.6rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--hs-ink-faint);
}

/* Across the two columns the row has left, and for the reason the skills box
   takes all of them: the sentence explaining what the AI does and does not
   decide was four lines tall in a column meant for a select, and it set the
   height of everything beside it. Two columns make it two lines and close the
   hole the row ended on.

   The narrow media query below puts it back to one, because `span 2` in a grid
   that has resolved to a single track adds an implicit second one and takes the
   whole editor to two columns. */
.criteria__analysis {
  grid-column: span 2;
}

.criteria__analysis .muted {
  margin-top: 0.35rem;
  font-size: 0.72rem;
  line-height: 1.45;
}

/* An action bar rather than a button adrift: `margin-top: 1rem` and nothing
   else left it floating in the gap below whichever column happened to be
   shortest, with no line tying it to the form it submits. */
.ts-rail__editor .form-actions {
  margin-top: 0.2rem;
  padding-top: 0.7rem;
  border-top: 1px solid var(--hs-line);
}

/* Two or three columns of checkboxes rather than one long list: at this width a
   single column is a column of mostly empty row. */
.criteria__options {
  list-style: none;
  margin: 0.3rem 0 0;
  padding: 0.3rem;
  max-height: 11rem;
  overflow-y: auto;
  border: 1px solid var(--hs-line);
  border-radius: var(--ub-radius-sm);
  background: var(--hs-surface);
  columns: 3 13rem;
  column-gap: 0.6rem;
}

/* One plain column, no height cap, no scroll. The capped multi-column box this
   replaces fragmented sideways rather than down — a multicol box out of height
   grows its next column to the right — so a handful of lists that fit the box
   whole were still served behind a horizontal scrollbar, which ate the bottom
   of the box and left the spacing under the legend looking wrong. There are
   few enough working lists that the list can simply be as tall as it is. */
/* Every column of this row names its control under the legend except this one,
   whose checkboxes are the control. Without that line the box started 1.3rem
   above the selects beside it, and the row read as three fields at three
   different heights — so the space the label would have taken is kept. */
.criteria__working-lists .criteria__options {
  margin-top: var(--ts-field-offset);
  max-height: none;
  overflow: visible;
  columns: auto;
}

.criteria__option {
  break-inside: avoid;
  padding: 0.2rem 0.3rem;
  border-radius: 0.3rem;
  font-size: var(--ts-text-small, 0.82rem);
}

.criteria__option:hover {
  background: var(--hs-hover);
}

.criteria__option + .criteria__option {
  border-top: 0;
}

.criteria__option label {
  display: flex;
  align-items: baseline;
  gap: 0.35rem;
}

.ts-rail__editor input[type="search"],
.ts-rail__editor input[type="text"] {
  width: 100%;
  max-width: 22rem;
  padding: 0.35rem 0.55rem;
  border: 1px solid var(--hs-line-strong);
  border-radius: var(--ub-radius-sm);
  background: var(--hs-surface);
  color: var(--hs-ink);
  font: inherit;
  font-size: 0.82rem;
}

.ts-rail__editor .ub-select {
  max-width: 13rem;
}

.ts-rail__editor .criteria__analysis label {
  display: flex;
  align-items: baseline;
  gap: 0.4rem;
  margin-top: 0.35rem;
  font-size: var(--ts-text-small, 0.82rem);
}

.ts-rail__editor .criteria__analysis .muted {
  margin: 0.3rem 0 0;
  font-size: var(--ts-text-label, 0.7rem);
  line-height: 1.5;
}

.ts-rail__editor .form-actions {
  margin: 0.2rem 0 0;
}

/* ---- the ledger ---- */

.ts-ledger {
  background: var(--hs-surface);
  border: 1px solid var(--hs-line);
  border-radius: var(--ub-radius);
  box-shadow: var(--ub-shadow);
  overflow: hidden;
}

/* Inside the bounded page the card is the column, in bands: the header, what
   the save said, what could not be scored, the table, and the foot. Only the
   table scrolls — every other band is a fact about the whole result and stays
   where the reviewer left it. */
/* The remaining height of the rail, not all of it: `height: 100%` resolved
   against the rail while the criteria card sat above it, so the two together
   were always taller than the column they share and the rail scrolled to make
   up the difference — taking the card's foot with it. The table inside is what
   is meant to scroll. */
/* A floor, not `min-height: 0`. The manual editor stays open across a swap on
   purpose (static/js/rail-disclosure.js), and open it is taller than the whole
   rail — at 0 the ledger took what was left, which was nothing: measured at 2px
   with its four rows and its Save button unreachable, at 1280x720 and 1440x900
   alike. With a floor the rail scrolls down to a table a reviewer can use.
   Capped at the rail's own height so a short window never gets a ledger taller
   than the column it scrolls in; with the editor closed the ledger already has
   more than this and nothing moves. */
.hs-page--bounded .ts-ledger {
  display: flex;
  flex-direction: column;
  flex: 1;
  min-height: min(24rem, 100%);
}

.hs-page--bounded .ts-ledger > *,
.hs-page--bounded .review {
  flex: none;
}

.hs-page--bounded .review {
  display: flex;
  flex-direction: column;
  flex: 1;
  min-height: 0;
}

/* ---- the comparison table ---- */

/* One grid, chosen by how many requirements the search compiled and shared
   verbatim by the header and every row — a column that is 4.6rem in one and
   4.7rem in the other is a table nobody can read down. Past six the remainder
   is in the evidence cell, so six is the widest this ever gets.

   The requirement columns were cut for a 9.6px header; on the scale's label
   step they carry 11.2px, and a two-word requirement wrapped onto a second
   line — a header band taller than the rows it governs, in a table whose whole
   point is that every row is visible.

   The contractor column has a floor rather than `minmax(0, 1fr)`. Between the
   68rem breakpoint below and roughly 1350px the rail is narrower than the fixed
   columns already need, and a `0` minimum let the name be resolved to zero
   width: every candidate rendered one letter per line under a header whose own
   labels overlapped. Sideways scrolling is the failure this table already
   accepts past six requirements; a nameless row is not one it should. */
.ts-table {
  --ts-grid: 1.9rem minmax(7rem, 1fr) 7.4rem 13.1rem;
  --ts-evidence-column: 3;

  flex: 1;
  min-height: 0;
  overflow-y: auto;
}

.ts-table[data-columns="1"] {
  --ts-grid: 1.9rem minmax(7rem, 1fr) 5.3rem 7.4rem 13.1rem;
  --ts-evidence-column: 4;
}

.ts-table[data-columns="2"] {
  --ts-grid: 1.9rem minmax(7rem, 1fr) 5.3rem 5.3rem 7.4rem 13.1rem;
  --ts-evidence-column: 5;
}

.ts-table[data-columns="3"] {
  --ts-grid: 1.9rem minmax(7rem, 1fr) 5.3rem 5.3rem 5.3rem 7.4rem 13.1rem;
  --ts-evidence-column: 6;
}

.ts-table[data-columns="4"] {
  --ts-grid: 1.9rem minmax(7rem, 1fr) 5.3rem 5.3rem 5.3rem 5.3rem 7.4rem 13.1rem;
  --ts-evidence-column: 7;
}

.ts-table[data-columns="5"] {
  --ts-grid: 1.9rem minmax(7rem, 1fr) 5.3rem 5.3rem 5.3rem 5.3rem 5.3rem 7.4rem 13.1rem;
  --ts-evidence-column: 8;
}

.ts-table[data-columns="6"] {
  --ts-grid: 1.9rem minmax(7rem, 1fr) 5.3rem 5.3rem 5.3rem 5.3rem 5.3rem 5.3rem 7.4rem 13.1rem;
  --ts-evidence-column: 9;
}

.ts-row {
  display: grid;
  grid-template-columns: var(--ts-grid);
  gap: 0.35rem 0.75rem;
  align-items: center;
  padding: 0.4rem 1.25rem;
}

/* The rank, the name and the requirement cells auto-place along the first row;
   the evidence summary and the review controls are placed, because between them
   in the document sits the disclosure's own panel, which is a row of its own. */
.ts-row > .review__controls {
  grid-row: 1;
  grid-column: -2 / -1;
}

/* Sticky rather than fixed above the body: it is a row of the same grid, so it
   cannot drift out of line with the cells under it. */
.ts-table__head {
  position: sticky;
  top: 0;
  z-index: 2;
  background: var(--hs-hover);
  border-bottom: 1px solid var(--hs-line);
  font-family: var(--ub-font-mono);
  font-size: var(--ts-text-label, 0.7rem);
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--hs-ink-soft);
}

/* A grid item sized by its track, not by its own content: `justify-self` on a
   cell shrink-wraps it, and a shrink-wrapped box at 0.16em letter-spacing runs
   straight over the header beside it. */
.ts-table__target {
  min-width: 0;
  line-height: 1.25;
  overflow-wrap: anywhere;
}

/* The gold dot distinguishes requirements that determine the comparison from
   supporting context. */
.ts-table__target--core::before {
  content: "";
  display: inline-block;
  width: 0.3rem;
  height: 0.3rem;
  margin-right: 0.3rem;
  vertical-align: middle;
  border-radius: 999px;
  background: var(--hs-gold);
}

.ts-row__rank {
  font-family: var(--ub-font-mono);
  font-size: var(--ts-text-label, 0.7rem);
  color: var(--hs-ink-faint);
  font-variant-numeric: tabular-nums;
}

/* The rank cell of a ranked row is also its drag handle (`talent-review.js`).
   The cursor is the whole affordance and the number is the whole target — a
   grip glyph beside it would move the number, and the column is 1.9rem wide
   precisely so it does not shift between the ninth row and the tenth. It comes
   forward on hover so the row says the handle is there. */
.ts-row__rank[data-ts-grip] {
  cursor: grab;
  user-select: none;
}

.review__row:hover .ts-row__rank[data-ts-grip] {
  color: var(--hs-ink);
}

.ts-row__who {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0.1rem;
  min-width: 0;
  justify-self: start;
}

.ts-row__cell {
  justify-self: start;
}

.ts-row__evidence {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.3rem;
  min-width: 0;
}

.ts-row__provenance {
  font-family: var(--ub-font-mono);
  font-size: 0.62rem;
  color: var(--hs-ink-soft);
}

.ts-row__review {
  justify-self: end;
  text-align: right;
}

/* ---- the group bands ---- */

/* Heading and summary in one band and on one line where it fits: as an `h3`
   over a paragraph the pair cost two bands of height in a table whose point is
   that every row is on screen at once. */
.ts-group {
  /* A grid and not a wrapping flex row, because the disclosure beside the
     heading is a control: opening it used to give it `flex-basis: 100%`, which
     moved the summary onto a line of its own and out from under the pointer
     that had just pressed it — so closing it again was a second aim at a
     control that had walked away. A cell keeps it where it was; the reason
     grows downward inside it. */
  display: grid;
  grid-template-columns: minmax(0, auto) minmax(0, 1fr);
  align-items: baseline;
  gap: 0.45rem;
  margin: 0;
  padding: 0.45rem 1.25rem;
  background: color-mix(in srgb, var(--ub-accent) 4%, var(--hs-surface));
  border-bottom: 1px solid var(--hs-line);
  box-shadow: inset 3px 0 0 var(--hs-gold-border);
  font-weight: 400;
}

.ts-group__heading {
  margin: 0;
  font-weight: 400;
  font-family: var(--ub-font-mono);
  font-size: var(--ts-text-label, 0.7rem);
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--hs-ink);
}

/* The model's reason for cutting the run where it did. Never truncated — §14
   invariant 6 puts the model's own text on staying readable — but folded, so
   five lines of it no longer sit between the rows a reviewer is scanning. */
.ts-group__reason > summary {
  list-style: none;
  cursor: pointer;
  /* The words are the control. The disclosure is a grid cell now and stretches
     across the rest of the band, and a summary that stretched with it toggled
     from anywhere in that empty stretch. */
  width: max-content;
  max-width: 100%;
  font-family: var(--ub-font-mono);
  font-size: var(--ts-text-label, 0.7rem);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--hs-ink-faint);
}

.ts-group__reason > summary::-webkit-details-marker {
  display: none;
}

.ts-group__reason > summary::after {
  content: "▾";
  margin-left: 0.3rem;
  display: inline-block;
  transition: transform 0.18s ease;
}

.ts-group__reason[open] > summary::after {
  transform: rotate(180deg);
}

.ts-group__summary {
  margin: 0.4rem 0 0.15rem;
  font-size: var(--ts-text-small, 0.82rem);
  line-height: 1.6;
  color: var(--hs-ink-faint);
}

/* ---- the foot band ---- */

/* A band, and when the disclosure inside it is open, a band with a panel under
   it. The band itself never scrolls: it used to carry `max-height: 50%` and its
   own scrollbar, which put the disclosure's 28 rows inside a strip half the
   ledger tall and left the summary line scrolling away from them. What is
   capped now is the panel, one level down. */
.ts-ledger__foot {
  display: flex;
  flex-direction: column;
  min-height: 0;
  border-top: 1px solid var(--hs-line);
}

.ts-ledger__tally {
  font-family: var(--ub-font-mono);
  font-size: 0.66rem;
  letter-spacing: 0.02em;
  color: var(--hs-ink-faint);
}

/* The nothing-set-aside case, which is the band's whole content. */
.ts-ledger__foot > .ts-ledger__tally {
  padding: 0.6rem 1.25rem;
  text-align: right;
}

/* Analyzed and set aside, collapsed rather than dropped: the model may judge
   somebody unsuitable and may not make them disappear. */
.review__considered {
  min-height: 0;
}

/* The summary is the band's line: the label where the disclosure marker leaves
   it, the tally hard right. The tally is floated rather than pushed there by a
   flex rule, and that is the whole reason this is not a flex row — a `summary`
   is a list item, and any other `display` takes its marker away, leaving a
   disclosure with nothing to say it is one. */
.review__considered-line {
  padding: 0.6rem 1.25rem;
  cursor: pointer;
  font-size: 0.81rem;
  color: var(--hs-ink);
}

.review__considered-line .ts-ledger__tally {
  float: right;
}

.review__considered-line:hover {
  background: var(--hs-hover);
}

/* The rows, on the same grid as the order above — that grid is a custom
   property declared on `.ts-table`, and outside one these rows had no columns
   at all — and scrolling inside a third of the window rather than lengthening
   what holds them.

   The cap is on the panel and not on the `details` around it: Chrome puts the
   non-summary children of a `details` inside a slot of its own, so a flex rule
   on the element reaches the slot and never the panel, and the panel went on
   growing behind a box that said it was 21rem tall.

   Capped in `vh` rather than rems because what it must not outgrow is the
   window: inside the bounded page the foot does not shrink, so every pixel it
   takes comes out of the order above it, and a fixed cap that is comfortable on
   a large screen leaves a laptop with a two-row table. */
.review__considered > .ts-table {
  max-height: 34vh;
  border-top: 1px solid var(--hs-line);
}

/* The foot keeps its height once the panel is open, so the order above gives
   way to it and scrolls — rather than the panel being squeezed and clipping the
   rows at the bottom of a list whose whole claim is that none is hidden. */
.hs-page--bounded .ts-ledger__foot {
  flex: none;
}

/* What this result is, the two things a reviewer does to it as a whole, and
   under both, where it came from.

   Two lines, not three: the counts and the actions share the first, and the
   provenance line is the second. The row gap is what keeps them one band —
   `0.35rem` between the count and a wrapped provenance line, against the
   `0.875rem` that separates the count from the meta beside it. */
.ts-ledger__head {
  display: flex;
  align-items: baseline;
  gap: 0.3rem 0.875rem;
  flex-wrap: wrap;
  padding: 0.7rem 1.25rem 0.6rem;
  border-bottom: 1px solid var(--hs-line);
}

/* One step up from the rail's own scale: with the page's head band gone this is
   the largest thing on the screen, which is the whole point of the redesign —
   how many people were found is what the search produced. */
.ts-ledger__count {
  font-family: var(--ub-font-display);
  font-weight: 700;
  font-size: var(--ts-text-count, 1.5rem);
  letter-spacing: -0.01em;
  color: var(--hs-ink);
}

.ts-ledger__meta {
  order: 1;
  font-size: var(--ts-text-small, 0.82rem);
  line-height: 1.6;
  color: var(--hs-ink-faint);
}

.ts-ledger__actions {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin-left: auto;
  flex: none;
  /* Centred on the count's line rather than sitting on its baseline: the count
     is 1.5rem and the buttons are 0.82rem, and a shared baseline hung them
     off the bottom of it. */
  align-self: center;
  order: 2;
}

/* The provenance line: the whole width of the band, on the row under the count.
   `order` rather than document position, because the actions must stay to the
   right of the count in the source for a keyboard to reach them in the order
   they are read. */
.ts-ledger__id {
  flex: 1 0 100%;
  order: 3;
  font-family: var(--ub-font-mono);
  font-size: var(--ts-text-label, 0.7rem);
  line-height: 1.5;
  color: var(--hs-ink-faint);
  overflow-wrap: anywhere;
}

.ts-ledger__save {
  margin: 0;
}

/* The band's own scale, not the application's: a header this dense cannot carry
   a 44px button, and above 68rem it does not have to (the media query below
   restores the floor where a finger is the pointer). */
.ts-ledger__save button,
.ts-ledger__export {
  padding: 0.56rem 0.94rem;
  border-radius: var(--ub-radius-sm);
  font-size: 0.81rem;
  font-weight: 600;
  line-height: 1.2;
}

/* What the save did, in the band under the button that did it. Its own band
   rather than a badge in the header: a refusal is a sentence to read, and the
   two tones must occupy the same place — a reviewer should not have to find out
   where the answer went. */
.ts-ledger__note {
  margin: 0;
  padding: 0.5rem 1.25rem;
  border-bottom: 1px solid var(--hs-line);
  font-size: 0.82rem;
  line-height: 1.5;
  overflow-wrap: anywhere;
}

/* `--ts-ok`, not `--ub-ok`: this surface keeps its own pair, for the reason the
   `.ts-workspace` block above records. */
.ts-ledger__note--ok {
  background: color-mix(in srgb, var(--ts-ok) 8%, var(--hs-surface));
  color: var(--hs-ink);
}

.ts-ledger__note--bad {
  background: color-mix(in srgb, var(--ts-danger) 6%, var(--hs-surface));
  color: var(--hs-ink);
}

/* Which requirements the rows are compared across: the header for the chips
   every row carries, so it takes its own line under the counts rather than
   trailing them. Stronger than the meta beside it — it names requirements, not
   quantities. */
/* ---- what the agent said ---- */

/* Rendered into the result and moved into the thread by the conversation
   script, so it is styled for both places at once: a block of the agent's own
   prose, with the model's words held apart from the compiler's. */
.ts-reply {
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
  padding: 0.7rem 0.8rem;
  border-bottom: 1px solid var(--hs-line);
}

/* Moved out of the rail and into the thread, the reply keeps its own padding —
   it is the same block of prose in both places — and drops only what belonged
   to the rail: the hairline that separated it from the ledger below it. It used
   to drop the padding too, which left the paragraph flush against the bubble
   this element is now inside. */
.ts-turn__body > .ts-reply {
  border-bottom: 0;
}

/* The model's sentences, and the whole of the bubble.

   The counts, the compared-on line, the provenance aside and the label over
   this paragraph were all removed: each of them either restated the ledger
   header a few rows down or said the answer is a model's, which the turn's own
   byline and that header already say. What was a panel inside a block is now
   the block, so it needs neither the tint that held it apart from the counts
   above it nor the label that named it. */
.ts-reply__text {
  margin: 0;
  font-size: 0.81rem;
  line-height: 1.65;
  color: var(--hs-ink);
}

/* A long answer, folded. The model routinely writes six or seven paragraphs
   about a ranking, and unfolded they push the message that asked for them, and
   every turn before it, out of a rail that is the whole conversation. Set by
   `talent-conversation.js` only on a block that actually overflows, so a short
   answer is never given a control that does nothing.

   Four lines, not eleven rems of them: the fold's job is to leave the answer as
   an abstract of itself — enough to see what it is about and decide to open
   it — and at the height it was, a folded answer still filled the rail and the
   turn that asked for it was still off the top of it. `6rem` is the bubble's own
   top padding and four lines of its prose, so the cut lands on a line rather
   than through one.

   `max-height` rather than `-webkit-line-clamp`, because the block being
   folded is a stack of paragraphs and an aside, not one run of text. */
.ts-long {
  position: relative;
  max-height: 6rem;
  overflow: hidden;
}

/* The cut is the bubble's own bottom padding taken away, so it needs saying
   that there is more rather than that the bubble ends here. Short enough to
   veil the tail of the last line and no more: over 3rem it washed out half of
   what the fold was keeping. */
.ts-long::after {
  content: "";
  position: absolute;
  inset: auto 0 0 0;
  height: 1.5rem;
  background: linear-gradient(to bottom, transparent, var(--hs-bg));
  pointer-events: none;
}

.ts-long--open {
  max-height: none;
  overflow: visible;
}

.ts-long--open::after {
  display: none;
}

/* Outside the bubble rather than in it: inside, it is the one thing the fold
   would hide. */
.ts-long__more {
  margin: 0;
  padding: 0.1rem 0.2rem;
  border: 0;
  background: none;
  cursor: pointer;
  font-family: var(--ub-font-mono);
  font-size: 0.62rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--hs-gold);
}

.ts-long__more:hover {
  text-decoration: underline;
}

/* A caveat about the answer, held apart from the answer by a gold hairline
   rather than by being smaller: it is the one thing in the bubble a reader must
   not skim past. */
.ts-reply__aside {
  margin: 0;
  padding-left: 0.7rem;
  box-shadow: inset 2px 0 0 var(--hs-gold-border);
  font-size: 0.75rem;
  line-height: 1.5;
  color: var(--hs-ink-soft);
}

.ts-reply__aside--warn {
  color: var(--ts-danger);
}

.ts-ledger__notice {
  margin: 0;
  padding: 0.7rem 1.1rem;
  border-bottom: 1px solid var(--hs-line);
  font-size: 0.82rem;
  line-height: 1.55;
  color: var(--hs-ink-soft);
}

.ts-ledger__notice--warn {
  color: var(--ts-danger);
}

/* The three vocabularies, chosen once above the rows. A grid rather than a
   flex row: three equal tracks keep the labels on one line each, where wrapping
   them made the strip taller than the row it governs. */
/* The rail is a dense column: the component's own scale, one step down. */
.ts-rail__editor .ub-select select {
  padding: 0.45rem 2.2rem 0.45rem 0.6rem;
  background: var(--hs-surface);
  font-size: 0.83rem;
}

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

.review__order > li.muted {
  padding: 0.6rem 1.1rem;
  border-bottom: 1px solid var(--hs-line);
  font-size: 0.85rem;
}

.review__row {
  border-bottom: 1px solid var(--ts-row-line);
}

.review__row:hover {
  background: var(--ts-row-hover);
}

.review__head {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  flex-wrap: wrap;
}

/* A basis rather than `min-width: 0`: beside the controls in a narrow column
   the name would otherwise collapse to nothing and `anywhere` would break a
   long one a character at a time. */
/* The name is the link into the hub. Marked with a quiet rule rather than left
   bare — an unmarked clickable name is a control nobody finds — and inheriting
   its colour, because a coloured name in a scored list reads as a rank. Matches
   `.hub-facts__link`, the same move on the hub's own Delivery Principal row. */
.review__name {
  max-width: 100%;
  font-size: var(--ts-text-name, 0.95rem);
  font-weight: 600;
  overflow-wrap: anywhere;
  color: inherit;
  /* A border rather than an underline: at 0.875rem in a row 2.4rem tall an
     underline sits on the descenders, and the baseline is what makes a column
     of names read as a column. */
  text-decoration: none;
  border-bottom: 1px solid var(--hs-line-strong);
}

.review__name:hover {
  border-bottom-color: currentColor;
}

.review__flag {
  flex: none;
  font-family: var(--ub-font-mono);
  font-size: 0.6rem;
  font-weight: 400;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--hs-gold);
}

/* Hard right, in the last column of the grid. */
.review__controls {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 0.25rem;
  justify-self: end;
}

/* Keep and Exclude are the decisions and stay; reordering is reached by pointer
   or by keyboard. `opacity` rather than `visibility`, which would take the two
   buttons out of the tab order and leave a keyboard with no way to them. */
.review__controls [data-move] {
  opacity: 0;
}

.ts-row:hover .review__controls [data-move],
.ts-row:focus-within .review__controls [data-move] {
  opacity: 1;
}

/* Nothing hovers on a touch pointer, so there they are simply shown. */
@media (hover: none) {
  .review__controls [data-move] {
    opacity: 1;
  }
}

.review__controls .button {
  padding: 0.19rem 0.5rem;
  border-radius: 0.5rem;
  font-size: var(--ts-text-small, 0.82rem);
  font-weight: 600;
  line-height: 1.3;
}

/* One chip per requirement, wrapping over as many lines as it takes. This was
   a single nowrap line ending in an ellipsis, which cut the requirements off at
   whatever the column width happened to be — reliably hiding the unrecorded
   ones, since they are the longest. */
.ts-ev {
  list-style: none;
  display: flex;
  flex-wrap: wrap;
  gap: 0.3rem;
  margin: 0.15rem 0 0;
  padding: 0;
}

.ts-ev__chip {
  display: inline-flex;
  align-items: baseline;
  gap: 0.35rem;
  padding: 0.06rem 0.44rem;
  border: 1px solid transparent;
  border-radius: var(--ub-radius-sm);
  font-family: var(--ub-font-mono);
  font-size: var(--ts-text-label, 0.7rem);
  line-height: 1.6;
  overflow-wrap: anywhere;
}

.ts-ev__state {
  font-weight: 600;
}

/* Four states, four treatments, and the two gaps are the pair that must never
   look alike (§14 invariant 4): `unmet` was measured and fell short, `unknown`
   was never recorded and says nothing either way. Solid versus dashed carries
   that before the words are read. */
.ts-ev__chip--met {
  border-color: color-mix(in srgb, var(--ts-ok) 26%, transparent);
  background: color-mix(in srgb, var(--ts-ok) 9%, var(--hs-surface));
  color: #1f6b40;
}

.ts-ev__chip--partial {
  border-color: color-mix(in srgb, var(--hs-gold) 32%, transparent);
  background: color-mix(in srgb, var(--hs-gold) 10%, var(--hs-surface));
  color: #6f5a17;
}

.ts-ev__chip--unmet {
  border-color: var(--hs-line-strong);
  background: var(--hs-bg);
  color: var(--hs-ink-soft);
}

.ts-ev__chip--unknown {
  border-style: dashed;
  border-color: var(--hs-line-strong);
  color: var(--hs-ink-faint);
}

/* One element in two places: `display: contents` drops its own box, so the
   summary is a cell of the row — the evidence column — and the panel it opens
   is a sub-row spanning every column under it. A disclosure that was a cell of
   its own could only open by overlapping the row below or by taking a second
   line in every row, closed or not. */
.candidate {
  display: contents;
}

/* And the box the browser puts around the disclosure's own content. Chrome
   generates one (`::details-content`); Firefox and Safari lay the children out
   directly, where the rule above is already the whole of it and this selector
   is unknown and dropped. Without this the panel is a block inside that UA box
   rather than a row of the grid, and it renders one column wide — read in a
   browser, because no stylesheet says the box is there. */
.candidate::details-content {
  display: contents;
}

.candidate__panel {
  grid-column: 1 / -1;
  display: none;
}

.candidate[open] > .candidate__panel {
  display: block;
  margin: 0.15rem 0 0.35rem;
  padding: 0.7rem 0.85rem;
  background: var(--hs-bg);
  border: 1px solid var(--hs-line);
  border-radius: var(--ub-radius-sm);
}

/* The evidence cell of the row: where this candidate was read from, in two or
   three words, and the control that opens all of it. Set in the row's own
   sentence case rather than the eyebrow it used to be — it is content now, and
   0.16em of letter-spacing does not fit a 7.4rem column. */
.candidate > summary {
  grid-row: 1;
  grid-column: var(--ts-evidence-column);
  min-width: 0;
  list-style: none;
  cursor: pointer;
  font-size: var(--ts-text-small, 0.82rem);
  line-height: 1.4;
  color: var(--hs-ink-soft);
}

.candidate > summary::-webkit-details-marker {
  display: none;
}

.candidate > summary:hover {
  color: var(--hs-ink);
}

.candidate > summary::after,
.ts-rail__editor > summary::after {
  content: "▾";
  margin-left: 0.25rem;
  display: inline-block;
  transition: transform 0.18s ease;
}

.candidate[open] > summary::after,
.ts-rail__editor[open] > summary::after {
  transform: rotate(180deg);
}

/* The row is `space-between`, so the marker is already at the far edge and the
   nudge that separates it from an inline label only pushes it off the end. */
.ts-rail__editor > summary::after {
  margin-left: 0;
  color: var(--hs-ink-faint);
}

.candidate ul {
  list-style: none;
  margin: 0.6rem 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
  font-size: 0.82rem;
  line-height: 1.5;
}

/* Source evidence and generated prose are two kinds of sentence, and a reader
   has to tell them apart without reading them (§14 invariant 6): each gets its
   own labelled block, and only the generated one is set in italic. */
.candidate__lists,
.candidate__claim {
  margin-top: 0.7rem;
  padding-top: 0.6rem;
  border-top: 1px solid var(--hs-line);
}

.candidate__section-label {
  margin: 0 0 0.35rem;
  font-family: var(--ub-font-mono);
  font-size: var(--ts-text-label, 0.7rem);
  letter-spacing: 0.16em;
  text-transform: uppercase;
}

.candidate__lists > ul {
  margin: 0;
}

.candidate__lists li {
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
}

.candidate__list-name,
.candidate__list-field,
.candidate__list-note {
  overflow-wrap: anywhere;
}

.candidate__list-note {
  color: var(--hs-ink-soft);
}

.candidate__claim-text {
  margin: 0.35rem 0 0;
  font-size: 0.85rem;
  line-height: 1.6;
  font-style: italic;
}

.review__exclusions {
  flex: none;
  min-height: 0;
  display: flex;
  flex-direction: column;
}

.review__exclusions > summary {
  list-style: none;
  cursor: pointer;
}

.review__exclusions > summary::-webkit-details-marker {
  display: none;
}

.review__exclusions > summary::after {
  content: "▾";
  margin-left: 0.35rem;
  display: inline-block;
  transition: transform 0.18s ease;
}

.review__exclusions[open] > summary::after {
  transform: rotate(180deg);
}

.review__excluded {
  max-height: 14rem;
  overflow-y: auto;
  list-style: none;
  margin: 0;
  padding: 0.7rem 1.1rem;
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
  border-top: 1px solid var(--hs-line);
  font-size: 0.85rem;
}

.review__excluded li {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 0.35rem;
}

.review__restore {
  margin-left: auto;
}

.ts-ledger__empty {
  margin: 0;
  padding: 1.4rem 1.1rem;
  text-align: center;
  color: var(--hs-ink-soft);
}

/* ---- one column: the shell has already dropped the sidebar here ---- */

@media (max-width: 68rem) {
  .ts-workspace {
    grid-template-columns: minmax(0, 1fr);
    grid-template-areas:
      "convo"
      "rail";
  }

  /* One column above the other is a page, not a workspace: the bound belongs to
     the side-by-side arrangement, and holding it here would give a stacked
     screen two nested scrollbars inside one viewport height. */
  .hs-page--bounded {
    height: auto;
    padding: clamp(1.75rem, 3vw, 2.6rem) clamp(1.25rem, 3vw, 2.75rem) 0;
    overflow: visible;
  }

  .hs-page--bounded .ts-workspace {
    height: auto;
    padding-bottom: 3rem;
  }

  .hs-page--bounded .ts-rail {
    height: auto;
    overflow: visible;
  }

  .hs-page--bounded .ts-ledger {
    height: auto;
  }

  .hs-page--bounded .review {
    flex: none;
  }

  .ts-table {
    overflow: visible;
  }

  /* The cap belongs to the bound, like the two above it: on a stacked page the
     panel is a section of a document and scrolling it inside a window that is
     already scrolling is two scrollbars for one list. */
  .review__considered > .ts-table {
    max-height: none;
  }

  /* One column of controls here, and `span 2` against a single resolved track
     would add an implicit second one. */
  .criteria__analysis {
    grid-column: auto;
  }

  .ts-table__head {
    display: none;
  }

  .ts-row {
    grid-template-columns: 2rem minmax(0, 1fr);
    gap: 0.55rem 0.75rem;
    padding: 0.75rem 1rem;
  }

  .ts-row__rank {
    grid-column: 1;
    grid-row: 1;
  }

  .ts-row__who {
    grid-column: 2;
    grid-row: 1;
  }

  .ts-row__cell {
    grid-column: 1 / -1;
    justify-self: stretch;
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    align-items: center;
    gap: 0.75rem;
  }

  .ts-row__cell::before {
    content: attr(data-target);
    min-width: 0;
    overflow-wrap: anywhere;
    color: var(--hs-ink-soft);
    font-weight: 400;
  }

  .candidate > summary,
  .ts-row > .review__controls {
    grid-column: 1 / -1;
    grid-row: auto;
    justify-self: start;
  }

  .ts-row > .review__controls {
    justify-content: flex-start;
    flex-wrap: wrap;
  }

  .ts-ledger__actions {
    flex: 1 1 100%;
    min-width: 0;
  }

  /* Nothing to stay beside, and a sticky column of viewport height would push
     the whole rail below the fold. */
  .ts-convo,
  .hs-page--bounded .ts-convo {
    position: static;
    height: auto;
    /* Restated, not inherited: `min-height` beats `max-height` in CSS, so the
       tall floor the two-column layout sets would win over this cap outright. */
    min-height: 22rem;
    max-height: 34rem;
  }

  .ts-composer button,
  .ts-ledger__save button,
  .ts-ledger__export,
  .review__controls .button {
    min-height: 44px;
  }

  .review__controls .button {
    min-width: 44px;
  }
}

/* ---- Sign-in ---- */

/* ---- Sign in (editorial, committed-light regardless of system theme) ---- */

.page-login .app-header {
  display: none;
}

.login {
  /* Local palette, fixed so the paper look survives system dark mode. */
  --paper: #f5f1ea;
  --ink: #1a1330;
  --ink-soft: #5f5a6e;
  --ink-faint: #6d6880;
  --gold: #9a7d23;

  position: relative;
  min-height: 100vh;
  min-height: 100dvh;
  display: flex;
  flex-direction: column;
  gap: 1rem;
  padding: clamp(1.75rem, 4vw, 3.25rem) clamp(1.5rem, 6vw, 6rem);
  color: var(--ink);
  background:
    radial-gradient(130% 90% at 100% 0%, color-mix(in srgb, var(--gold) 9%, transparent), transparent 58%),
    var(--paper);
  overflow: hidden;
}

.login__mark {
  font-family: var(--ub-font-display);
  font-weight: 700;
  font-size: 1.4rem;
  letter-spacing: -0.01em;
  color: var(--ink);
  text-decoration: none;
}

.login__body {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  justify-content: center;
  max-width: 36rem;
  padding: 2rem 0;
}

.login__eyebrow {
  font-family: var(--ub-font-mono);
  font-size: 0.72rem;
  font-weight: 500;
  letter-spacing: 0.3em;
  text-transform: uppercase;
  color: var(--ink-faint);
  margin: 0 0 1.35rem;
}

.login__headline {
  font-family: var(--ub-font-display);
  font-weight: 680;
  font-size: clamp(2rem, 1.15rem + 4.4vw, 5.25rem);
  line-height: 1.03;
  letter-spacing: -0.015em;
  margin: 0;
}

.login__line {
  display: block;
}

.login__headline em {
  font-style: italic;
  font-weight: 560;
}

.login__rule {
  display: block;
  width: 3.5rem;
  height: 2px;
  margin: 2rem 0 1.75rem;
  background: var(--gold);
  transform-origin: left center;
}

.login__lede {
  max-width: 27rem;
  margin: 0 0 2.25rem;
  font-size: 1.05rem;
  line-height: 1.5;
  color: var(--ink-soft);
}

.login__cta {
  display: inline-flex;
  align-items: center;
  gap: 0.7rem;
  padding: 0.9rem 1.5rem;
  border-radius: var(--ub-radius-pill);
  border: 1px solid var(--ink);
  background: var(--ink);
  color: var(--paper);
  font-family: var(--ub-font);
  font-weight: 600;
  font-size: 0.98rem;
  text-decoration: none;
  box-shadow: 0 12px 30px -14px rgba(26, 19, 48, 0.55);
  transition: transform 0.14s ease, box-shadow 0.2s ease;
}

.login__cta:hover {
  transform: translateY(-2px);
  box-shadow: 0 20px 42px -16px rgba(26, 19, 48, 0.6);
}

.login__cta:active {
  transform: translateY(0);
}

.login__cta:focus-visible {
  outline: none;
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--gold) 55%, transparent);
}

.login__gmark {
  width: 1.15rem;
  height: 1.15rem;
  flex: none;
}

.login__note {
  margin: 1.1rem 0 0;
  font-size: 0.8rem;
  color: var(--ink-faint);
}

.login__foot {
  margin: 0;
  font-family: var(--ub-font-mono);
  font-size: 0.72rem;
  letter-spacing: 0.24em;
  text-transform: uppercase;
  color: var(--ink-faint);
}

@media (prefers-reduced-motion: no-preference) {
  .login__eyebrow,
  .login__line,
  .login__lede,
  .login__cta,
  .login__note {
    animation: login-rise 0.72s cubic-bezier(0.2, 0.7, 0.2, 1) both;
  }
  .login__eyebrow { animation-delay: 0.05s; }
  .login__line:nth-child(1) { animation-delay: 0.13s; }
  .login__line:nth-child(2) { animation-delay: 0.24s; }
  .login__lede { animation-delay: 0.46s; }
  .login__cta { animation-delay: 0.56s; }
  .login__note { animation-delay: 0.64s; }

  .login__rule {
    animation: login-rule 0.7s cubic-bezier(0.2, 0.7, 0.2, 1) 0.36s both;
  }
}

@keyframes login-rise {
  from { opacity: 0; transform: translateY(14px); }
  to { opacity: 1; transform: translateY(0); }
}

@keyframes login-rule {
  from { opacity: 0; transform: scaleX(0); }
  to { opacity: 1; transform: scaleX(1); }
}

@media (prefers-reduced-motion: reduce) {
  * {
    transition: none !important;
    animation: none !important;
  }
}

/* ---- Taxonomy editor ---- */

.taxo-versions {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  flex-wrap: wrap;
  margin: 1.25rem 0 0.25rem;
}

.vchip {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.4rem 0.75rem;
  border-radius: var(--ub-radius-pill);
  border: 1px solid var(--ub-border);
  background: var(--ub-surface);
  font-family: var(--ub-font-mono);
  font-size: var(--ub-text-small);
  color: var(--ub-ink-soft);
}

.vchip strong {
  color: var(--ub-ink);
}

.vchip--live {
  border-color: color-mix(in srgb, var(--ub-ok) 45%, transparent);
}

.vchip--live .vchip__dot {
  background: var(--ub-ok);
}

.vchip--draft {
  border-style: dashed;
  border-color: var(--ub-primary);
  color: var(--ub-ink);
}

.vchip--draft .vchip__dot {
  background: var(--ub-primary);
}

.vchip__dot {
  width: 0.55rem;
  height: 0.55rem;
  border-radius: 50%;
  flex: none;
}

.vchip__arrow {
  color: var(--ub-ink-faint);
}

.taxo-toolbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  flex-wrap: wrap;
  margin: 1.5rem 0 0.25rem;
}

.taxo-toolbar__title {
  font-size: var(--ub-text-h3);
  font-weight: 700;
  margin: 0;
}

.taxo-addcat,
.taxo-addskill-form {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

.taxo-addcat__input,
.taxo-addskill__input {
  font: inherit;
  padding: 0.45rem 0.65rem;
  border-radius: var(--ub-radius-sm);
  border: 1px solid var(--ub-border-strong);
  background: var(--ub-bg);
  color: var(--ub-ink);
}

.taxo-addskill-form {
  width: 100%;
}

.taxo-addskill__input {
  flex: 1 1 auto;
}

/* The category header reuses .accordion__head but hosts an editable name, so its
   rename form must not break the flex row. */
.taxo-cat__head {
  display: flex;
  align-items: center;
  gap: 0.8rem;
  padding: 1rem 1.25rem;
}

.taxo-cat__rename {
  display: contents;
}

.taxo-cat__name {
  font: inherit;
  font-size: var(--ub-text-h3);
  font-weight: 700;
  color: var(--ub-ink);
  background: transparent;
  border: 1px solid transparent;
  border-radius: var(--ub-radius-sm);
  padding: 0.2rem 0.45rem;
  margin-left: -0.45rem;
  min-width: 6rem;
}

.taxo-cat__name:hover {
  border-color: var(--ub-border);
}

.taxo-cat__name:focus {
  outline: none;
  border-color: var(--ub-accent);
  background: var(--ub-surface-2);
}

.taxo-cat__spacer {
  margin-left: auto;
}

.taxo-chevron {
  flex: none;
  width: 1rem;
  height: 1rem;
  color: var(--ub-ink-faint);
  background: none;
  border: none;
  cursor: pointer;
  transition: transform 0.18s ease;
}

.accordion:not(.accordion--open) .taxo-chevron {
  transform: rotate(-90deg);
}

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

.taxo-skill {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  padding: 0.6rem 0.75rem;
  border: 1px solid var(--ub-border);
  border-radius: var(--ub-radius-sm);
  background: var(--ub-bg);
}

/* Reordering is JS-only, so the grip appears once taxonomy.js marks the list
   enhanced -- a visible handle that does nothing reads as a broken control. */
.taxo-skill__grip {
  display: none;
  flex: none;
  width: 0.6rem;
  height: 1rem;
  padding: 0;
  border: none;
  border-radius: 2px;
  cursor: grab;
  background-color: transparent;
  background-image: radial-gradient(currentColor 1px, transparent 1px);
  background-size: 4px 4px;
  color: var(--ub-ink-faint);
  opacity: 0.6;
}

.js-reorder .taxo-skill__grip {
  display: block;
}

.taxo-skill__grip:hover,
.taxo-skill__grip:focus-visible {
  opacity: 1;
  color: var(--ub-ink-soft);
}

.taxo-skill__grip:active {
  cursor: grabbing;
}

/* A row saving itself. The field being typed into is deliberately still live,
   so this says a request is in flight without taking anything away. */
.taxo-skill.htmx-request,
.accordion.htmx-request .taxo-cat__name {
  border-color: var(--ub-accent);
}

.taxo-skill--dragging {
  opacity: 0.45;
  border-color: var(--ub-border-strong);
}

.taxo-skill__label {
  font: inherit;
  font-weight: 600;
  color: var(--ub-ink);
  background: transparent;
  border: 1px solid transparent;
  border-radius: var(--ub-radius-sm);
  padding: 0.3rem 0.5rem;
  flex: 1 1 auto;
  min-width: 4rem;
}

.taxo-skill__label:hover {
  border-color: var(--ub-border);
}

.taxo-skill__label:focus {
  outline: none;
  border-color: var(--ub-accent);
  background: var(--ub-surface);
}

.taxo-skill__label--ro {
  border-color: transparent;
  cursor: default;
}

.taxo-skill__controls {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  flex: none;
}

.taxo-retire {
  display: inline-flex;
  align-items: center;
  font-family: var(--ub-font-mono);
  font-size: 0.62rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  padding: 0.32rem 0.55rem;
  border-radius: var(--ub-radius-pill);
  border: 1px dashed var(--ub-border-strong);
  background: transparent;
  color: var(--ub-ink-faint);
  cursor: pointer;
  user-select: none;
}

.taxo-retire__cb {
  position: absolute;
  width: 1px;
  height: 1px;
  opacity: 0;
}

.taxo-retire:has(.taxo-retire__cb:checked) {
  border-style: solid;
  border-color: var(--ub-danger);
  color: var(--ub-danger);
  background: color-mix(in srgb, var(--ub-danger) 12%, transparent);
}

.taxo-retire:focus-within {
  box-shadow: var(--ub-ring);
}

.taxo-skill[data-retired="true"] .taxo-skill__label {
  color: var(--ub-ink-faint);
  text-decoration: line-through;
}

.taxo-skill[data-retired="true"] {
  opacity: 0.72;
}

.taxo-badge {
  font-family: var(--ub-font-mono);
  font-size: 0.6rem;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  padding: 0.18rem 0.45rem;
  border-radius: var(--ub-radius-pill);
  flex: none;
}

.taxo-badge--new {
  color: var(--ub-accent);
  background: var(--ub-accent-quiet);
}

.taxo-badge--moved {
  color: var(--ub-lvl-3);
  background: color-mix(in srgb, var(--ub-lvl-3) 15%, transparent);
}

.taxo-badge--renamed {
  color: var(--ub-primary);
  background: color-mix(in srgb, var(--ub-primary) 16%, transparent);
}

.taxo-badge--retired {
  color: var(--ub-ink-faint);
  background: var(--ub-surface-2);
}

.taxo-addskill {
  font-family: var(--ub-font-mono);
  font-size: var(--ub-text-small);
  color: var(--ub-ink-soft);
  background: transparent;
  border: 1px dashed var(--ub-border-strong);
  border-radius: var(--ub-radius-sm);
  padding: 0.5rem 0.8rem;
  cursor: pointer;
  flex: none;
}

.taxo-addskill:hover {
  border-color: var(--ub-accent);
  color: var(--ub-accent);
}

.diffgrid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(8.5rem, 1fr));
  gap: 0.7rem;
  margin-top: 0.5rem;
}

.diffstat {
  padding: 0.85rem 1rem;
  border: 1px solid var(--ub-border);
  border-radius: var(--ub-radius-sm);
  background: var(--ub-surface-2);
}

.diffstat__n {
  font-family: var(--ub-font-mono);
  font-size: 1.6rem;
  font-weight: 700;
  color: var(--ub-ink);
  line-height: 1;
  font-variant-numeric: tabular-nums;
}

.diffstat__label {
  display: block;
  margin-top: 0.35rem;
  font-size: var(--ub-text-small);
  color: var(--ub-ink-soft);
}

.diffstat--accent .diffstat__n {
  color: var(--ub-accent);
}

.diffstat--danger .diffstat__n {
  color: var(--ub-danger);
}

.alias-note {
  margin-top: 0.9rem;
  font-size: var(--ub-text-small);
  color: var(--ub-ink-soft);
}

.alias-note code {
  font-family: var(--ub-font-mono);
  background: var(--ub-surface-2);
  padding: 0.1rem 0.35rem;
  border-radius: 5px;
  color: var(--ub-ink);
}

.publishbar {
  display: flex;
  align-items: center;
  gap: 0.9rem;
  flex-wrap: wrap;
  margin-top: 1.25rem;
  padding: 1rem 1.15rem;
  border: 1px solid var(--ub-border);
  border-radius: var(--ub-radius);
  background: var(--ub-surface);
  box-shadow: var(--ub-shadow);
}

.publishbar__publish {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  flex: 1 1 20rem;
}

.publishbar__note {
  flex: 1 1 auto;
  font: inherit;
  padding: 0.55rem 0.7rem;
  border-radius: var(--ub-radius-sm);
  border: 1px solid var(--ub-border-strong);
  background: var(--ub-bg);
  color: var(--ub-ink);
}

.publishbar__spacer {
  flex: 1 1 auto;
}

@media (prefers-reduced-motion: reduce) {
  .taxo-chevron {
    transition: none !important;
  }
}

/* ---- Home shell (product launcher, committed-light like the sign-in) ----
   Replaces the block that starts at `.page-home .app-header` and runs to the
   end of app.css. Tokens, sidebar and topbar geometry are unchanged; new rules
   cover the topbar search + account menu, the editorial hero, and the grouped
   tool cards. */

.page-shell .app-header {
  display: none;
}

/* The shell is committed-light, but the page canvas is painted from `body`,
   whose `--ub-bg` flips under system dark mode. Anything the shell does not
   cover — overscroll, and any strip below a short shell — came out near-black
   under a paper screen. Pinned to the shell's own ground so the two agree. */
html:has(.home-shell),
body:has(.home-shell) {
  background: #f6f5f2;
}

.home-shell {
  --hs-bg: #f6f5f2;
  --hs-surface: #ffffff;
  --hs-line: #eae7e1;
  --hs-line-strong: #ddd8cf;
  --hs-ink: #1a1330;
  --hs-ink-soft: #57536a;
  --hs-ink-faint: #6d6880;
  --hs-gold: #9a7d23;
  --hs-gold-tint: color-mix(in srgb, var(--hs-gold) 10%, #fff);
  --hs-gold-border: color-mix(in srgb, var(--hs-gold) 28%, transparent);
  --hs-hover: #f3f1ec;
  --hs-active: color-mix(in srgb, var(--hs-gold) 9%, #fff);

  /* This shell is committed-light, so the UA must not paint form controls from
     the visitor's dark system preference: unchecked checkboxes would come out
     black on white. */
  color-scheme: light;
  accent-color: var(--hs-gold);

  display: grid;
  grid-template-columns: 250px 1fr;
  min-height: 100vh;
  min-height: 100dvh;
  color: var(--hs-ink);
  background: var(--hs-bg);
  font-family: var(--ub-font);
}

/* Collapsed keeps a narrow icon rail rather than closing the sidebar outright,
   so the tools stay one click away instead of two. */
.home-shell--collapsed {
  grid-template-columns: 3.75rem 1fr;
}

.hs-side {
  display: flex;
  flex-direction: column;
  padding: 0.9rem 0.7rem 0.7rem;
  background: var(--hs-surface);
  border-right: 1px solid var(--hs-line);
  /* Sticky, so the nav stays in reach on long screens like the directory.
     top:0 holds because shell pages hide .app-header. */
  position: sticky;
  top: 0;
  height: 100vh;
  height: 100dvh;
  overflow-y: auto;
  overflow-x: hidden;
}

.home-shell--collapsed .hs-side {
  padding: 0.9rem 0.35rem 0.7rem;
}

.home-shell--collapsed .hs-side {
  padding: 0.9rem 0.35rem 0.7rem;
}

.home-shell--collapsed .hs-side {
  padding: 0.9rem 0.35rem 0.7rem;
}

.home-shell--collapsed .hs-brand {
  justify-content: center;
  padding: 0.4rem 0 0.9rem;
}

.home-shell--collapsed .hs-brand__id {
  display: none;
}

.home-shell--collapsed .hs-nav__label {
  display: none;
}

.home-shell--collapsed .hs-nav__item {
  justify-content: center;
  gap: 0;
  padding: 0.55rem;
}

/* Kept in the accessibility tree (clipped, not display:none) so the item's
   name still reads on a screen reader with the label hidden from sight. */
.home-shell--collapsed .hs-nav__txt {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

.hs-brand {
  display: flex;
  align-items: center;
  gap: 0.65rem;
  padding: 0.4rem 0.5rem 0.9rem;
}

.hs-brand__mark {
  width: 2rem;
  height: 2rem;
  border-radius: 9px;
  flex: none;
  display: block;
}

.hs-brand__id {
  display: flex;
  flex-direction: column;
  line-height: 1.15;
  min-width: 0;
  flex: 1;
}

.hs-brand__name {
  font-weight: 700;
  font-size: 0.95rem;
  letter-spacing: -0.01em;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.hs-brand__tag {
  font-family: var(--ub-font-mono);
  font-size: 0.56rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--hs-gold);
}

.hs-nav {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.hs-nav__label {
  font-family: var(--ub-font-mono);
  font-size: 0.58rem;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--hs-ink-faint);
  padding: 1rem 0.6rem 0.4rem;
}

.hs-nav__item {
  display: flex;
  align-items: center;
  gap: 0.65rem;
  padding: 0.5rem 0.6rem;
  border-radius: 8px;
  color: var(--hs-ink-soft);
  font-size: 0.875rem;
  font-weight: 500;
  text-decoration: none;
  white-space: nowrap;
  transition: background 0.12s ease, color 0.12s ease;
}

.hs-nav__ic {
  display: inline-flex;
  color: var(--hs-ink-faint);
  flex: none;
}

.hs-nav__ic svg {
  width: 1.05rem;
  height: 1.05rem;
}

.hs-nav__item:hover {
  background: var(--hs-hover);
  color: var(--hs-ink);
}

.hs-nav__item--active {
  background: var(--hs-active);
  color: var(--hs-ink);
  font-weight: 600;
  box-shadow: inset 2px 0 0 var(--hs-gold);
}

.hs-nav__item--active .hs-nav__ic {
  color: var(--hs-gold);
}

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

/* ---- Topbar ---- */

.hs-topbar {
  position: sticky;
  top: 0;
  z-index: 5;
  height: 56px;
  display: flex;
  align-items: center;
  gap: 0.85rem;
  padding: 0 1.1rem;
  background: var(--hs-surface);
  border-bottom: 1px solid var(--hs-line);
}

/* Edge handle, Notion-style: a small circle straddling the sidebar border,
   always visible. Anchored to the topbar, which is already positioned, so
   the sidebar's own overflow never clips it. Points left expanded, right
   collapsed. */
.hs-edge {
  position: absolute;
  left: -0.8rem;
  top: 0.85rem;
  width: 1.625rem;
  height: 1.625rem;
  border-radius: 50%;
  background: var(--hs-surface);
  border: 1px solid var(--hs-line);
  color: var(--hs-ink-faint);
  display: grid;
  place-items: center;
  cursor: pointer;
  padding: 0;
  z-index: 6;
  box-shadow: 0 2px 8px rgba(26, 19, 48, 0.12);
}

.hs-edge:hover {
  color: var(--hs-ink);
  border-color: var(--hs-gold);
}

.hs-edge:focus-visible {
  outline: 2px solid var(--hs-gold);
  outline-offset: 2px;
}

.hs-edge svg {
  width: 0.75rem;
  height: 0.75rem;
}

.home-shell--collapsed .hs-edge svg {
  transform: scaleX(-1);
}

.hs-crumb {
  font-size: 0.9rem;
  font-weight: 600;
}

.hs-user__avatar {
  width: 1.9rem;
  height: 1.9rem;
  border-radius: 50%;
  background: linear-gradient(150deg, var(--hs-gold), #b9962f);
  color: #fff;
  display: grid;
  place-items: center;
  font-weight: 700;
  font-size: 0.72rem;
  flex: none;
}

/* ---- Sidebar footer: the account lives here now, pinned to the bottom. ---- */

.hs-foot {
  margin-top: auto;
  display: flex;
  align-items: center;
  gap: 0.55rem;
  padding: 0.7rem 0.35rem 0.2rem;
  border-top: 1px solid var(--hs-line);
}

.hs-foot__avatar {
  text-decoration: none;
}

a.hs-foot__avatar:hover {
  outline: 2px solid var(--hs-gold);
  outline-offset: 2px;
}

.hs-foot__who {
  min-width: 0;
  flex: 1;
  display: flex;
}

.hs-foot__name {
  font-size: 0.8rem;
  font-weight: 600;
  color: var(--hs-ink-soft);
  text-decoration: none;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.hs-foot__name:hover {
  color: var(--hs-ink);
  text-decoration: underline;
}

.hs-foot__signout {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  padding: 0.35rem 0.4rem;
  border-radius: 7px;
  font-size: 0.78rem;
  font-weight: 600;
  color: var(--hs-ink-faint);
  text-decoration: none;
  flex: none;
}

.hs-foot__signout:hover {
  background: var(--hs-hover);
  color: var(--hs-ink);
}

.hs-foot__signout svg {
  display: none;
  width: 1rem;
  height: 1rem;
}

/* Collapsed rail: option B folds into option A — avatar plus an icon-only
   sign-out, stacked and centred. The text stays in the accessibility tree
   (clipped, not display:none) so the link keeps its name. */
.home-shell--collapsed .hs-foot {
  flex-direction: column;
  gap: 0.45rem;
  padding: 0.7rem 0 0.2rem;
}

.home-shell--collapsed .hs-foot__who {
  display: none;
}

.home-shell--collapsed .hs-foot__signout svg {
  display: block;
}

.home-shell--collapsed .hs-foot__signout-txt {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

/* ---- Profile: an editorial identity sheet, not another admin table. ---- */

.pf-wrap {
  max-width: 56rem;
  margin: 0 auto;
  padding: 2.5rem 1.5rem 4rem;
}

.pf-id {
  display: flex;
  align-items: center;
  gap: 1.4rem;
  padding-bottom: 1.8rem;
  border-bottom: 1px solid var(--hs-line);
}

.pf-mark {
  width: 4.5rem;
  height: 4.5rem;
  border-radius: 50%;
  background: linear-gradient(150deg, var(--hs-gold), #b9962f);
  color: #fff;
  display: grid;
  place-items: center;
  font-weight: 700;
  font-size: 1.7rem;
  flex: none;
}

.pf-id__txt {
  min-width: 0;
  flex: 1;
}

.pf-name {
  margin: 0;
  font-size: 1.65rem;
  font-weight: 700;
  letter-spacing: -0.02em;
  line-height: 1.15;
}

.pf-email {
  margin: 0.25rem 0 0;
  font-size: 0.85rem;
  color: var(--hs-ink-soft);
  overflow-wrap: anywhere;
}

.pf-flags {
  margin: 0.7rem 0 0;
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
}

.pf-flag {
  font-family: var(--ub-font-mono);
  font-size: 0.68rem;
  letter-spacing: 0.04em;
  color: var(--hs-ink-soft);
  border: 1px solid var(--hs-line);
  border-radius: 20px;
  padding: 0.2rem 0.65rem;
  background: var(--hs-surface);
}

.pf-signout {
  flex: none;
}

.pf-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 2.5rem;
  padding-top: 1.8rem;
}

@media (max-width: 800px) {
  .pf-grid {
    grid-template-columns: 1fr;
  }
  .pf-id {
    flex-wrap: wrap;
  }
}

.pf-sec__title {
  margin: 0 0 0.4rem;
  font-family: var(--ub-font-mono);
  font-size: 0.68rem;
  font-weight: 500;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--hs-ink-faint);
}

.pf-rows {
  margin: 0;
}

.pf-row {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 1rem;
  padding: 0.65rem 0;
  border-bottom: 1px solid var(--hs-line);
}

.pf-row:last-child {
  border-bottom: 0;
}

.pf-row dt {
  font-size: 0.83rem;
  color: var(--hs-ink-faint);
  flex: none;
}

.pf-row dd {
  margin: 0;
  font-size: 0.88rem;
  font-weight: 600;
  text-align: right;
  overflow-wrap: anywhere;
}

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

.pf-link {
  display: block;
  padding: 0.65rem 0;
  border-bottom: 1px solid var(--hs-line);
  font-size: 0.88rem;
  font-weight: 600;
  color: var(--hs-ink-soft);
  text-decoration: none;
}

.pf-links li:last-child .pf-link {
  border-bottom: 0;
}

.pf-link:hover {
  color: var(--hs-ink);
  text-decoration: underline;
}

.pf-empty {
  padding: 0.65rem 0;
  font-size: 0.88rem;
  color: var(--hs-ink-faint);
}

/* ---- Content: editorial hero ---- */

.hs-content {
  flex: 1;
  padding: 0 0 4rem;
}

/* Room under a document, and nothing under a screen that is exactly the height
   below the topbar: this 4rem is the whole of what the bounded page still had
   to scroll past. */
.hs-content:has(.hs-page--bounded) {
  padding-bottom: 0;
}

.hs-hero {
  display: flex;
  flex-direction: column;
  gap: 1.1rem;
  padding: clamp(1.75rem, 3vw, 2.9rem) clamp(1.25rem, 3vw, 2.75rem) clamp(1.5rem, 2.5vw, 2.1rem);
  background: var(--hs-surface);
  border-bottom: 1px solid var(--hs-line);
}

.hs-eyebrow {
  margin: 0;
  font-family: var(--ub-font-mono);
  font-size: 0.66rem;
  letter-spacing: 0.28em;
  text-transform: uppercase;
  color: var(--hs-gold);
}

.hs-hero__title {
  margin: 0;
  max-width: 39rem;
  font-family: var(--ub-font-display);
  font-weight: 700;
  font-size: clamp(2rem, 1.1rem + 2.4vw, 3.25rem);
  line-height: 1.08;
  letter-spacing: -0.02em;
  text-wrap: pretty;
}

.hs-hero__title em {
  font-style: italic;
}

.hs-rule {
  width: 4rem;
  height: 2px;
  background: var(--hs-gold);
}

.hs-lede {
  margin: 0;
  max-width: 33rem;
  font-size: 0.94rem;
  line-height: 1.6;
  color: var(--hs-ink-soft);
}

/* ---- Content: grouped tools ---- */

.hs-groups {
  display: flex;
  flex-direction: column;
  gap: 1.9rem;
  padding: 2rem clamp(1.25rem, 3vw, 2.75rem) 0;
}

.hs-group {
  display: flex;
  flex-direction: column;
  gap: 0.9rem;
}

.hs-group__head {
  display: flex;
  align-items: center;
  gap: 0.9rem;
}

.hs-group__label {
  font-family: var(--ub-font-mono);
  font-size: 0.66rem;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--hs-ink-faint);
  white-space: nowrap;
}

.hs-group__rule {
  flex: 1;
  height: 1px;
  background: var(--hs-line);
}

.hs-tools {
  display: grid;
  gap: 1rem;
}

.hs-tools--pair,
.hs-tools--row {
  grid-template-columns: repeat(auto-fit, minmax(21rem, 1fr));
}

/* `quad` and `quad-primary` share a density and differ in prominence.
   `quad-primary` opts out of the two quiet treatments below — it keeps the gold
   icon chip and the description, because Talent's four are the tools the
   product is for and their labels alone do not say what they open. */
.hs-tools--quad,
.hs-tools--quad-primary {
  grid-template-columns: repeat(auto-fit, minmax(13.5rem, 1fr));
  gap: 0.875rem;
}

/* A group of exactly four is the one count `auto-fit` cannot lay out evenly.
   The column count is floor((container + gap) / (track + gap)), monotonic in
   container width, so every track width passes through three on the way from
   two to four — and three columns is what leaves the fourth card alone on a
   second line. Measured against this shell: a 13.5rem track orphans at 1024
   and 1180 CSS px, 15rem at 1180 and 1280, 21rem at 1440 and 1512. There is no
   value that does not, so the four-card case is pinned instead and never
   offered three. One, two and three cards stay on `auto-fit` above, which is
   even for them at every width this shell produces. */
.hs-tools--quad[data-count="4"],
.hs-tools--quad-primary[data-count="4"] {
  grid-template-columns: minmax(0, 1fr);
}

@media (min-width: 620px) {
  .hs-tools--quad[data-count="4"],
  .hs-tools--quad-primary[data-count="4"] {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

/* 1280 px is where the content column first clears 4 x 13.5rem plus its gaps
   (953 px against the 906 px that needs), so four across never squeezes a card
   under its own minimum. */
@media (min-width: 1280px) {
  .hs-tools--quad[data-count="4"],
  .hs-tools--quad-primary[data-count="4"] {
    grid-template-columns: repeat(4, minmax(0, 1fr));
  }
}

.hs-tool {
  display: flex;
  flex-direction: column;
  gap: 0.55rem;
  padding: 1.25rem 1.35rem 1.2rem;
  background: var(--hs-surface);
  border: 1px solid var(--hs-line);
  border-radius: 12px;
  color: inherit;
  text-decoration: none;
  transition: border-color 0.14s ease, box-shadow 0.14s ease, transform 0.14s ease;
}

.hs-tool:hover {
  border-color: var(--hs-line-strong);
  box-shadow: 0 12px 26px -18px rgba(26, 19, 48, 0.45);
  transform: translateY(-2px);
}

.hs-tool--row {
  flex-direction: row;
  align-items: flex-start;
  gap: 0.875rem;
  padding: 1.15rem 1.25rem;
}

.hs-tool--quad,
.hs-tool--quad-primary {
  gap: 0.5rem;
  padding: 1rem 1.15rem;
  border-radius: 11px;
}

.hs-tool__top {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
}

.hs-tool--row .hs-tool__top {
  flex: none;
}

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

.hs-tool__icon {
  width: 2.15rem;
  height: 2.15rem;
  border-radius: 8px;
  background: var(--hs-gold-tint);
  border: 1px solid var(--hs-gold-border);
  color: var(--hs-gold);
  display: grid;
  place-items: center;
  flex: none;
}

.hs-tool--quad .hs-tool__icon {
  width: auto;
  height: auto;
  background: none;
  border: 0;
  place-items: start;
}

.hs-tool__icon svg {
  width: 1.1rem;
  height: 1.1rem;
}

.hs-tool__name {
  margin: 0.15rem 0 0;
  font-family: var(--ub-font-display);
  font-weight: 700;
  font-size: 1.19rem;
  letter-spacing: -0.01em;
}

.hs-tool--quad .hs-tool__name,
.hs-tool--quad-primary .hs-tool__name {
  font-size: 1rem;
  margin: 0;
}

.hs-tool__desc {
  margin: 0;
  font-size: 0.85rem;
  line-height: 1.5;
  color: var(--hs-ink-soft);
}

.hs-tool--quad .hs-tool__desc {
  display: none;
}

.hs-badge {
  font-family: var(--ub-font-mono);
  font-size: 0.69rem;
  letter-spacing: 0.1em;
  padding: 0.19rem 0.5rem;
  border-radius: 5px;
  background: #faf9f7;
  border: 1px solid var(--hs-line);
  color: var(--hs-ink-soft);
  white-space: nowrap;
}

.hs-badge--accent {
  background: var(--hs-active);
  border-color: var(--hs-gold-border);
  color: #847332;
}

.hs-meta {
  display: flex;
  align-items: center;
  gap: 0.45rem;
  font-family: var(--ub-font-mono);
  font-size: 0.72rem;
  color: var(--hs-ink-soft);
}

.hs-meta--danger {
  color: var(--ub-danger, #c0392f);
}

.hs-dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: currentColor;
  flex: none;
}

.hs-meta--ok .hs-dot {
  background: var(--ub-ok, #2f8f57);
}

@media (max-width: 860px) {
  .home-shell {
    grid-template-columns: 1fr;
  }
  .hs-side {
    display: none;
  }
  .hs-tool--row {
    flex-direction: column;
  }
}

/* ---- Tool screens inside the product shell (skills self-assessment, taxonomy)
   The shell is committed-light; pin the theme-flipping --ub-* content tokens to
   the shell's light palette so reused components (scale, stars, accordion, taxo
   editor, action/publish bars) stay consistent instead of flipping to dark
   inside the always-light shell. */

.home-shell {
  --ub-bg: var(--hs-bg);
  --ub-surface: var(--hs-surface);
  --ub-surface-2: #f2eee6;
  --ub-border: var(--hs-line);
  --ub-border-strong: var(--hs-line-strong);
  --ub-ink: var(--hs-ink);
  --ub-ink-soft: var(--hs-ink-soft);
  --ub-ink-faint: var(--hs-ink-faint);
  --ub-accent: var(--hs-gold);
  --ub-accent-hover: #806619;
  --ub-accent-ink: #ffffff;
  --ub-accent-quiet: color-mix(in srgb, var(--hs-gold) 12%, transparent);
  --ub-star: #6a63b8;
  --ub-star-ghost: color-mix(in srgb, #6a63b8 32%, transparent);
  --ub-primary: #49338d;
  --ub-ok: #2f8f57;
  --ub-danger: #c0392f;
  --ub-shadow: 0 1px 2px rgba(24, 18, 43, 0.05), 0 12px 32px rgba(24, 18, 43, 0.06);
  --ub-shadow-lift: 0 2px 6px rgba(24, 18, 43, 0.08), 0 20px 48px rgba(24, 18, 43, 0.12);
}

.hs-page {
  padding: clamp(1.75rem, 3vw, 2.6rem) clamp(1.25rem, 3vw, 2.75rem) 0;
  max-width: 74rem;
  margin-inline: auto;
}

/* A workspace rather than a document: two columns of real content, and rows
   that carry a chip per requirement. At 74rem the rail is too narrow to read
   either of them. */
.hs-page--wide {
  max-width: 96rem;
}

/* Talent Search alone is uncapped: a workspace, not a centered document. The
   shared cap binds only past 1786px viewports, so every narrower width —
   including the 68rem single-column layout — renders exactly as before. */
#talent-search-page {
  max-width: none;
}

/* Once there is a result to review, the window stops scrolling and each column
   scrolls inside itself instead. A reviewer compares rows against each other,
   which a page scroll makes impossible: the column header leaves with the rows
   above it. The page is therefore exactly the height under the topbar, and the
   padding is inside that height rather than added to it.

   Set by the server on a full page and by `talent-conversation.js` on the first
   turn, for the reason the head band above is: this element sits outside the
   state every answer replaces. */
.hs-page--bounded {
  box-sizing: border-box;
  height: calc(100dvh - 56px);
  padding: 1.25rem 1.75rem 1.5rem;
  overflow: hidden;
}

.hs-page__head {
  margin-bottom: 1.6rem;
}

.hs-page__head .eyebrow {
  color: var(--hs-gold);
}

.hs-page .badge--pill {
  margin-bottom: 0.85rem;
}

.hs-page__title {
  margin: 0.1rem 0 0;
  font-family: var(--ub-font-display);
  font-weight: 700;
  font-size: clamp(1.8rem, 1.1rem + 1.7vw, 2.5rem);
  line-height: 1.08;
  letter-spacing: -0.02em;
  text-wrap: balance;
}

.hs-page__title em {
  font-style: italic;
  color: var(--hs-ink-soft);
}

.hs-page__lede {
  margin: 0.9rem 0 0;
  max-width: 44rem;
  color: var(--hs-ink-soft);
  line-height: 1.6;
}

/* Summary stat row (self-assessment) */
.hs-summary {
  display: grid;
  grid-template-columns: 1.6fr 1fr 1fr;
  gap: 0.9rem;
  margin-bottom: 1.5rem;
}

.hs-stat {
  background: var(--hs-surface);
  border: 1px solid var(--hs-line);
  border-radius: var(--ub-radius);
  padding: 1rem 1.15rem;
}

.hs-stat__label {
  margin: 0 0 0.5rem;
  font-family: var(--ub-font-mono);
  font-size: 0.62rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--hs-ink-faint);
}

.hs-stat__big {
  font-family: var(--ub-font-display);
  font-weight: 700;
  font-size: 1.45rem;
  line-height: 1.1;
  color: var(--hs-ink);
}

.hs-stat__sub {
  margin: 0.45rem 0 0;
  font-size: 0.8rem;
  color: var(--hs-ink-soft);
}

.hs-stat .progress {
  margin: 0.7rem 0 0.35rem;
}

/* Collapsible reference block (rating scale) */
.hs-reveal {
  background: var(--hs-surface);
  border: 1px solid var(--hs-line);
  border-radius: var(--ub-radius);
  margin-bottom: 1rem;
}

.hs-reveal > summary {
  list-style: none;
  cursor: pointer;
  display: flex;
  align-items: center;
  gap: 0.55rem;
  padding: 1rem 1.35rem;
}

.hs-reveal > summary::-webkit-details-marker {
  display: none;
}

.hs-reveal > summary .eyebrow {
  margin: 0;
}

.hs-reveal__hint {
  font-size: 0.8rem;
  color: var(--hs-ink-faint);
}

.hs-reveal__chev {
  margin-left: auto;
  width: 1rem;
  height: 1rem;
  color: var(--hs-ink-faint);
  transition: transform 0.18s ease;
}

.hs-reveal[open] .hs-reveal__chev {
  transform: rotate(180deg);
}

.hs-reveal__body {
  padding: 0 1.35rem 1.35rem;
}

@media (prefers-reduced-motion: reduce) {
  .hs-reveal__chev {
    transition: none;
  }
}

@media (max-width: 860px) {
  .hs-summary {
    grid-template-columns: 1fr;
  }
}

/* ---- Administration console (inside the product shell) ------------------- */

.console__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  flex-wrap: wrap;
  margin-bottom: 1.4rem;
}

.console__title {
  margin: 0;
  font-family: var(--ub-font-display);
  font-weight: 700;
  font-size: clamp(1.7rem, 1.1rem + 1.5vw, 2.3rem);
  line-height: 1.1;
  letter-spacing: -0.02em;
}

.console__head .console__title {
  margin: 0;
}

.console__title + .console__mono,
.console__title + p {
  margin-top: 0.4rem;
}

.console__h2 {
  margin: 1.9rem 0 0.7rem;
  font-size: var(--ub-text-h3);
  font-weight: 700;
}

.console__mono {
  font-family: var(--ub-font-mono);
  font-size: var(--ub-text-small);
  color: var(--ub-ink-faint);
  word-break: break-all;
}

.console__sub {
  display: block;
  font-size: var(--ub-text-small);
  color: var(--ub-ink-faint);
}

/* Landing tiles */
.console__tiles {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr));
  gap: 0.9rem;
  margin-top: 1.25rem;
}

.console__tile {
  display: block;
  padding: 1.2rem 1.3rem;
  text-decoration: none;
  color: inherit;
  transition: border-color 0.15s ease, transform 0.06s ease, box-shadow 0.15s ease;
}

.console__tile:hover {
  border-color: var(--ub-accent);
  box-shadow: var(--ub-shadow-lift);
}

.console__tile + .console__tile {
  /* Overrides .card + .card's stacking margin (line 249): that rule assumes
     cards stack vertically, but these tiles sit side by side in a grid, where
     the same DOM-order sibling margin instead stretches the first tile taller
     than the rest under align-items: stretch. */
  margin-top: 0;
}

.console__tile .card__title {
  font-family: var(--ub-font-display);
  font-size: var(--ub-text-h3);
  margin: 0 0 0.35rem;
}

.console__tile .card__desc {
  margin: 0;
  color: var(--ub-ink-soft);
  font-size: var(--ub-text-small);
}

/* Data tables */
.console__tablewrap {
  overflow-x: auto;
  border: 1px solid var(--ub-border);
  border-radius: var(--ub-radius);
  background: var(--ub-surface);
}

.console__table {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--ub-text-small);
}

.console__table th {
  text-align: left;
  padding: 0.7rem 0.9rem;
  font-family: var(--ub-font-mono);
  font-size: 0.62rem;
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ub-ink-faint);
  border-bottom: 1px solid var(--ub-border);
  white-space: nowrap;
}

.console__table td {
  padding: 0.75rem 0.9rem;
  border-bottom: 1px solid var(--ub-border);
  vertical-align: middle;
  font-variant-numeric: tabular-nums;
}

.console__table tbody tr:last-child td {
  border-bottom: none;
}

.console__table tbody tr:hover {
  background: var(--hs-hover, var(--ub-surface-2));
}

/* A row with a mutation in flight, the same way #review-form says it on the
   Talent Search shortlist: dim, and stop taking a second press. The button is
   already out of action through hx-disabled-elt; this is what makes that
   visible for the whole row the answer will replace. */
.console__table tbody tr.htmx-request {
  opacity: 0.55;
  pointer-events: none;
}

/* The row is focused after it is swapped in (rule 6), so the move has to be
   visible to anyone watching rather than only to a screen reader. The rows
   region carries the same outline for a mutation whose row does not survive it,
   which is where focus goes on Roles. */
.console__table tbody tr:focus-visible,
.console__table tbody:focus-visible {
  outline: 2px solid var(--ub-primary);
  outline-offset: -2px;
}

.console__table a {
  color: var(--ub-ink);
  font-weight: 600;
  text-decoration: none;
}

.console__table a:hover {
  color: var(--ub-accent);
}

.console__table form {
  margin: 0;
}

/* Forms */
.console__form {
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
  max-width: 34rem;
  margin-top: 1.25rem;
}

.console__form label,
.console__form .console__form-label {
  font-family: var(--ub-font-mono);
  font-size: 0.62rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ub-ink-soft);
  margin-top: 0.85rem;
}

.console__form input[type="text"],
.console__form input[type="email"],
.console__form input[type="search"],
.console__form input[type="datetime-local"],
.console__form select,
.console__form textarea {
  width: 100%;
  padding: 0.62rem 0.8rem;
  border-radius: var(--ub-radius-sm);
  border: 1px solid var(--ub-border-strong);
  background: var(--ub-bg);
  color: var(--ub-ink);
  font: inherit;
}

/* A console form with its own submission in flight, the same way a row says it
   above: dim, and stop taking a second press. It is also the region focus moves
   to when a rejected submission swaps it back in, so the move has to be visible
   to anyone watching and not only to a screen reader. */
.console__form.htmx-request {
  opacity: 0.55;
  pointer-events: none;
}

.console__form:focus-visible {
  outline: 2px solid var(--ub-primary);
  outline-offset: 6px;
  border-radius: var(--ub-radius-sm);
}

.console__form .errorlist {
  list-style: none;
  margin: 0.3rem 0 0;
  padding: 0;
  color: var(--ub-danger);
  font-size: var(--ub-text-small);
}

.console__actions {
  display: flex;
  justify-content: flex-end;
  gap: 0.7rem;
  margin-top: 1.5rem;
}

/* Person picker (typeahead) */
.picker {
  position: relative;
  max-width: 100%;
}

.picker__input {
  width: 100%;
  padding: 0.62rem 0.8rem;
  border-radius: var(--ub-radius-sm);
  border: 1px solid var(--ub-border-strong);
  background: var(--ub-bg);
  color: var(--ub-ink);
  font: inherit;
}

.picker__results {
  position: relative;
}

.picker__options {
  list-style: none;
  margin: 0.3rem 0 0;
  padding: 0.3rem;
  border: 1px solid var(--ub-border);
  border-radius: var(--ub-radius-sm);
  background: var(--ub-surface);
  box-shadow: var(--ub-shadow);
  max-height: 16rem;
  overflow-y: auto;
}

.picker__option {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  padding: 0.5rem 0.6rem;
  border-radius: 7px;
  cursor: pointer;
}

.picker__option:hover {
  background: var(--ub-accent-quiet);
}

.picker__name {
  font-weight: 600;
}

.picker__email {
  font-size: var(--ub-text-small);
  color: var(--ub-ink-faint);
}

.picker__empty {
  padding: 0.6rem;
  color: var(--ub-ink-faint);
  font-size: var(--ub-text-small);
}

.picker__chosen {
  margin: 0.5rem 0 0;
  font-size: var(--ub-text-small);
  color: var(--ub-ok);
  font-weight: 600;
}

/* Status badges (grants, admissions) */
.badge--active {
  background: color-mix(in srgb, var(--ub-ok) 16%, transparent);
  color: var(--ub-ok);
}

.badge--scheduled {
  background: var(--ub-accent-quiet);
  color: var(--ub-accent);
}

.badge--expired {
  background: color-mix(in srgb, var(--ub-danger) 14%, transparent);
  color: var(--ub-danger);
}

.badge--quiet {
  background: color-mix(in srgb, var(--ub-ink-faint) 16%, transparent);
  color: var(--ub-ink-faint);
}

.picker__input:focus-visible,
.console__table .button:focus-visible {
  outline: none;
  box-shadow: var(--ub-ring);
}

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

/* ---- Select ----------------------------------------------------------------
   One select treatment for the whole product: the box of a text input, our own
   chevron in place of the platform's arrows, and the accent on hover and focus.
   The native control is kept — appearance: none restyles the closed field and
   leaves keyboard behaviour, screen-reader semantics and the mobile picker
   alone. The open option list stays the platform's own; it is not reliably
   styleable, and the closed field is what a person sees almost all the time.
   Markup: <span class="ub-select"><select>...</select>{% include
   "partials/select_chevron.html" %}</span>. */
.ub-select {
  position: relative;
  display: inline-flex;
  align-items: center;
  width: 100%;
}

.ub-select select {
  appearance: none;
  width: 100%;
  padding: 0.62rem 2.5rem 0.62rem 0.8rem;
  border: 1px solid var(--ub-border-strong);
  border-radius: var(--ub-radius-sm);
  background: var(--ub-bg);
  color: var(--ub-ink);
  font: inherit;
  cursor: pointer;
}

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

.ub-select select:focus-visible {
  outline: none;
  border-color: var(--ub-accent);
  box-shadow: var(--ub-ring);
}

.ub-select select:disabled {
  border-color: var(--ub-border);
  background: var(--ub-surface-2);
  color: var(--ub-ink-faint);
  cursor: not-allowed;
}

/* Reads as unset until something is chosen: a required select with the empty
   option selected is :invalid, which is exactly that state. */
.ub-select select:required:invalid {
  color: var(--ub-ink-faint);
}

.ub-select__chev {
  position: absolute;
  right: 0.8rem;
  width: 0.95rem;
  height: 0.95rem;
  color: var(--ub-ink-faint);
  pointer-events: none;
  transition: color 0.12s ease;
}

.ub-select select:hover ~ .ub-select__chev,
.ub-select select:focus-visible ~ .ub-select__chev {
  color: var(--ub-accent);
}

/* Toolbar and table-row scale, where the value is a short machine-ish token
   rather than prose. */
.ub-select--mono select {
  padding-block: 0.5rem;
  font-family: var(--ub-font-mono);
  font-size: 0.8rem;
}

/* ---- Role capabilities editor ---- */
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

/* The editor is a card per role rather than a role-by-capability table: the
   read-only column alone is 20-odd dotted tokens on the widest role, and no
   table narrow enough to read is wide enough to hold it. */
.rolecap {
  margin-top: 1.5rem;
}

.rolecap__grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(19rem, 1fr));
  align-items: start;
  gap: 1rem;
}

.rolecap__card {
  display: flex;
  flex-direction: column;
  border: 1px solid var(--ub-border);
  border-radius: var(--ub-radius);
  background: var(--ub-surface);
  overflow: hidden;
}

/* The house treatment from home.html: a mono identifier, the accent hairline
   under it, and a mono uppercase caption -- the accent is the only brand colour
   this screen spends. */
.rolecap__cardhead {
  padding: 1rem 1.05rem 0.85rem;
  border-bottom: 1px solid var(--ub-border);
}

.rolecap__role {
  margin: 0;
  font-family: var(--ub-font-mono);
  font-size: 0.95rem;
  font-weight: 500;
  overflow-wrap: anywhere;
}

.rolecap__role::after {
  content: "";
  display: block;
  width: 2rem;
  height: 2px;
  margin-top: 0.55rem;
  background: var(--ub-accent);
}

.rolecap__reach {
  margin: 0.5rem 0 0;
  font-family: var(--ub-font-mono);
  font-size: var(--ub-text-label);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ub-ink-faint);
}

.rolecap__reach strong {
  color: var(--ub-accent);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}

.rolecap__caps {
  list-style: none;
  margin: 0;
  padding: 0.4rem;
  display: flex;
  flex-direction: column;
  gap: 0.1rem;
}

.rolecap__cap {
  display: flex;
  align-items: center;
  gap: 0.65rem;
  padding: 0.5rem 0.6rem;
  border-left: 2px solid transparent;
  border-radius: 0 var(--ub-radius-sm) var(--ub-radius-sm) 0;
  cursor: pointer;
  font-family: var(--ub-font-mono);
  font-size: 0.8rem;
  color: var(--ub-ink-faint);
  overflow-wrap: anywhere;
}

.rolecap__cap:hover {
  background: var(--ub-surface-2);
}

.rolecap__cap:has(input:checked) {
  border-left-color: var(--ub-accent);
  background: var(--ub-accent-quiet);
  color: var(--ub-ink);
}

.rolecap__cap:focus-within {
  outline: none;
  box-shadow: var(--ub-ring);
}

.rolecap__cap input[type="checkbox"] {
  flex: none;
  width: 1.05rem;
  height: 1.05rem;
  margin: 0;
  accent-color: var(--ub-accent);
}

.rolecap__locked {
  margin: auto 0 0;
  padding: 0.7rem 1.05rem;
  border-top: 1px solid var(--ub-border);
  font-size: var(--ub-text-label);
  color: var(--ub-ink-faint);
}

.rolecap__locked summary {
  cursor: pointer;
  border-radius: var(--ub-radius-sm);
}

.rolecap__locked summary:focus-visible {
  outline: none;
  box-shadow: var(--ub-ring);
}

.rolecap__lockedlist {
  list-style: none;
  margin: 0.6rem 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
  font-family: var(--ub-font-mono);
  overflow-wrap: anywhere;
}

/* ---- Working lists (product shell) ---------------------------------------- */

.wl-toolbar {
  display: flex;
  flex-wrap: wrap;
  gap: 1rem 1.5rem;
  align-items: flex-end;
  margin: 1.1rem 0 1.5rem;
  padding: 1rem 1.15rem;
  background: var(--ub-surface-2);
  border: 1px solid var(--ub-border);
  border-radius: var(--ub-radius);
}

.wl-toolbar form {
  display: flex;
  gap: 0.6rem;
  align-items: flex-end;
}

.wl-toolbar .field {
  margin-bottom: 0;
  min-width: 14rem;
}

/* Someone hand-enters ~24 contractors in one sitting here, so the entry table
   favors a wide, legible note column and a tight row height over the console
   table's default uniform columns (person_detail.html, roles.html). */
.console__table--worklist td {
  vertical-align: top;
  padding: 0.55rem 0.9rem;
}

.console__table--worklist .wl-col-note {
  min-width: 22rem;
}

/* A viewer has no inputs to size, so below 640px the note column wraps inside
   the viewport instead of forcing a horizontal scroll nobody signals. Edit
   mode keeps the 22rem floor its textareas need. */
@media (max-width: 640px) {
  .console__table--reader .wl-col-note {
    min-width: 0;
  }
}

/* The note column is 22rem by design, so this table runs past the page below
   roughly 1200px and the row's Remove control would sit outside it. Pinned so it
   stays reachable without a horizontal scroll nothing signals. `:not(.wl-col-note)`
   because a viewer without edit access has no control column, and the note is
   their last cell. */
/* `tr:not(.wl-group)` because a band heading spans the table on a row of its
   own, so its single cell is the last child as well as the first: without the
   exclusion it is styled as the pinned action column and loses the band's own
   ground to the surface colour. */
.console__table--worklist tr:not(.wl-group) > th:last-child:not(.wl-col-note),
.console__table--worklist td:last-child:not(.wl-col-note) {
  position: sticky;
  right: 0;
  background: var(--ub-surface);
  box-shadow: -10px 0 10px -10px rgba(24, 18, 43, 0.25);
}

.console__table--worklist textarea,
.console__table--worklist input[type="date"] {
  width: 100%;
  padding: 0.5rem 0.6rem;
  border: 1px solid var(--ub-border-strong);
  border-radius: var(--ub-radius-sm);
  background: var(--ub-bg);
  color: var(--ub-ink);
  font: inherit;
  font-size: var(--ub-text-small);
}

.console__table--worklist textarea {
  min-height: 3.2rem;
  resize: vertical;
}

/* One team's band inside the one table. A heading row rather than a table per
   team: the rows under it are a single bound formset, and a manager reading down
   the list keeps one set of columns the whole way. Only the by-team order emits
   these; the other two render the same rows with no heading above them. */
.console__table--worklist .wl-group > th {
  padding: 1.05rem 0.9rem 0.5rem;
  border-bottom: 1px solid var(--ub-border-strong);
  background: var(--ub-surface-2);
  box-shadow: inset 3px 0 0 var(--wl-rail, transparent);
  font-family: var(--ub-font);
  font-size: var(--ub-text-h3);
  font-weight: 700;
  letter-spacing: normal;
  text-transform: none;
  color: var(--ub-ink);
  white-space: normal;
}

/* The first band opens the table, so it needs none of the space that separates
   a later band from the rows above it. */
.console__table--worklist tbody > .wl-group:first-child > th {
  padding-top: 0.7rem;
}

.wl-group__row {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.6rem;
}

.wl-group__name {
  font-family: var(--ub-font-display);
}

/* The same treatment "No longer active" gives its count, for the same reason:
   a number beside a heading is a caption and must not compete with it. */
.wl-group__count {
  font-family: var(--ub-font-mono);
  font-size: 0.62rem;
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ub-ink-soft);
}

/* Two tones of the brand purple, alternating band by band. The heading names
   the team; this is what keeps consecutive bands apart once a heading has
   scrolled out of view, and it repeats the heading rather than carrying
   anything of its own — a reader who does not perceive it loses nothing. */
.wl-band--a {
  --wl-rail: var(--ub-primary);
}

.wl-band--b {
  --wl-rail: var(--ub-star);
}

.console__table--worklist .wl-member > td:first-child {
  box-shadow: inset 3px 0 0 var(--wl-rail, transparent);
}

/* The order control. A segmented row of links rather than a <select>, because
   there are two options and the one in use has to be readable at a glance from
   across the table it reorders. */
.wl-sort {
  display: flex;
  align-items: center;
  gap: 0;
  margin: 0 0 0.8rem;
}

.wl-sort__label {
  margin-right: 0.65rem;
  color: var(--ub-ink-faint);
  font-size: var(--ub-text-label);
  letter-spacing: 0.09em;
  text-transform: uppercase;
}

.wl-sort__option {
  padding: 0.28rem 0.75rem;
  border: 1px solid var(--ub-border-strong);
  border-radius: 0;
  background: var(--ub-surface);
  color: var(--ub-ink-soft);
  font-size: var(--ub-text-small);
  text-decoration: none;
}

/* One joined control rather than two pills: the options are sibling links, so
   squaring the inner corners and overlapping the shared border is enough. */
.wl-sort__option:first-of-type {
  border-radius: var(--ub-radius-pill) 0 0 var(--ub-radius-pill);
}
.wl-sort__option:last-of-type {
  border-radius: 0 var(--ub-radius-pill) var(--ub-radius-pill) 0;
}
.wl-sort__option + .wl-sort__option {
  margin-left: -1px;
}

.wl-sort__option:hover {
  border-color: var(--ub-accent);
  color: var(--ub-ink);
}

.wl-sort__option--on {
  border-color: transparent;
  background: var(--ub-accent);
  color: var(--ub-accent-ink);
  font-weight: 600;
}

/* `.rec-color a:not(.button)` (0,2,1) beats any single-class state above, so a
   filled pill on a link renders ink-on-accent — measured purple on purple on
   both this control and the People filter chips. Scoped to win outright rather
   than by source order, since app.css loads before recruiting-color.css. */
.rec-color .wl-sort a.wl-sort__option--on {
  border-color: var(--ub-accent);
  background: var(--ub-accent);
  color: var(--ub-accent-ink);
}

/* A row nobody has written anything about. It carries a word and not only a
   tint: the flag has to survive a monochrome screen and a reader who does not
   perceive the colour, and the note column is where a manager decides whether
   this person still needs a decision. */
.wl-note-flag {
  display: none;
  align-items: center;
  margin-top: 0.35rem;
  padding: 0.12rem 0.5rem;
  border-radius: var(--ub-radius-pill);
  background: var(--ub-accent-quiet);
  color: var(--ub-accent-hover);
  font-size: var(--ub-text-label);
  font-weight: 600;
  letter-spacing: 0.04em;
}

/* Shown while the box beside it is still empty, and withdrawn the moment
   someone types — `:placeholder-shown` is true only for an empty field, so the
   flag never contradicts what the editor is looking at while they wait to save.
   The static variant is the viewer's cell, which holds no field to ask. */
.console__table--worklist textarea:placeholder-shown + .wl-note-flag,
.wl-note-flag--static {
  display: inline-flex;
}

/* .worklist-detail__inactive (the row tint) is defined above, next to the
   older .worklist-detail rules it was introduced alongside — kept as one
   definition rather than duplicated here. */

/* Sticky, so "Save all" stays reachable while scrolling a long entry table —
   the same sticky-on-scroll technique .hs-topbar already uses, one tier below
   it (z-index 5) so the two never compete. */
.wl-savebar {
  position: sticky;
  bottom: 0;
  z-index: 4;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  margin-top: 1rem;
  padding: 0.85rem 0.25rem;
  background: var(--ub-surface);
  border-top: 1px solid var(--ub-border);
}

.wl-savebar__count {
  margin: 0;
  color: var(--ub-ink-faint);
  font-size: var(--ub-text-small);
}

.wl-checkbox-field {
  display: flex;
  align-items: center;
  gap: 0.55rem;
  margin: 0.9rem 0;
  font-weight: 600;
}

/* Sits directly under the block it saves rather than .console__actions's
   full-width, right-justified row, which strands the button far from — and
   right of — the single checkbox it belongs to. */
.wl-field-actions {
  margin-top: 0.9rem;
}

.wl-settings {
  margin-top: 2.25rem;
  border: 1px solid var(--ub-border);
  border-radius: var(--ub-radius);
  background: var(--ub-surface);
}

.wl-settings > *:not(summary) {
  margin-inline: 1.75rem;
}

.wl-settings[open] {
  padding-bottom: 1.75rem;
}

.wl-settings__summary {
  display: flex;
  align-items: baseline;
  gap: 0.7rem;
  padding: 1.05rem 1.25rem;
  border-radius: var(--ub-radius);
  font-size: var(--ub-text-h3);
  font-weight: 700;
  cursor: pointer;
}

.wl-settings[open] .wl-settings__summary {
  border-bottom: 1px solid var(--ub-border);
  border-end-start-radius: 0;
  border-end-end-radius: 0;
}

.wl-settings__hint {
  color: var(--ub-ink-faint);
  font-size: var(--ub-text-small);
  font-weight: 400;
}

/* The access table otherwise butts directly against the "Give someone
   access" panel below it — every other adjacent pair in this settings panel
   gets at least a heading's own margin worth of breathing room. */
/* A Worklists panel with its own mutation in flight: dim, and stop taking a
   second press. Rule 4 asks for both halves — the control is already out of
   action through hx-disabled-elt, and this is what makes that visible for the
   region the answer will replace. */
#worklist-editor.htmx-request,
#wl-availability.htmx-request,
#wl-shares.htmx-request,
#wl-rename.htmx-request {
  opacity: 0.55;
  pointer-events: none;
}

.wl-shares-tablewrap {
  margin-bottom: 1.25rem;
}

.wl-panel--flush {
  max-width: 30rem;
  padding: 0;
  border: 0;
  border-radius: 0;
  background: none;
  box-shadow: none;
}

/* Last inside the settings, on its own ground, so it is never the button beside
   a Save. */
.wl-archive {
  margin-top: 1.9rem;
  padding: 1.25rem 1.4rem;
  background: var(--ub-surface-2);
  border: 1px solid var(--ub-border);
  border-radius: var(--ub-radius);
}

.wl-archive__title {
  margin: 0;
  font-size: var(--ub-text-h3);
  font-weight: 700;
}

.wl-archive__lede {
  margin: 0.4rem 0 1.1rem;
  max-width: 36rem;
  color: var(--ub-ink-soft);
  font-size: var(--ub-text-small);
  line-height: 1.55;
}

.wl-archive__button {
  border-color: color-mix(in srgb, var(--ub-danger) 35%, transparent);
  color: var(--ub-danger);
}

/* A departed contractor is kept on the list and never auto-removed, so they need
   somewhere to be that is not a row inviting an edit — and not a red tint, which
   read as an error rather than as a fact about the person. */
.wl-departed {
  margin-top: 2rem;
  padding: 1.25rem 1.4rem;
  background: var(--ub-surface-2);
  border: 1px solid var(--ub-border);
  border-radius: var(--ub-radius);
}

.wl-departed__title {
  display: flex;
  align-items: baseline;
  gap: 0.6rem;
  margin: 0;
  font-size: var(--ub-text-h3);
  font-weight: 700;
  color: var(--ub-ink-soft);
}

.wl-departed__count {
  font-family: var(--ub-font-mono);
  font-size: 0.62rem;
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ub-ink-faint);
}

.wl-departed__lede {
  margin: 0.4rem 0 1.1rem;
  max-width: 44rem;
  color: var(--ub-ink-soft);
  font-size: var(--ub-text-small);
  line-height: 1.55;
}

.wl-departed__list {
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  margin: 0;
  padding: 0;
}

.wl-departed__row {
  display: flex;
  align-items: center;
  gap: 1rem;
  padding: 0.7rem 0.9rem;
  background: var(--ub-surface);
  border: 1px solid var(--ub-border);
  border-radius: var(--ub-radius-sm);
}

.wl-departed__name {
  flex: 1;
  font-size: 0.9rem;
  font-weight: 600;
}

.wl-departed__fact {
  color: var(--ub-ink-soft);
  font-size: var(--ub-text-small);
}

.wl-lede {
  margin: 0.7rem 0 0;
  max-width: 44rem;
  color: var(--ub-ink-soft);
  line-height: 1.6;
}

/* auto-fit, not auto-fill: with fewer lists than columns, auto-fill still
   reserves the extra empty tracks, so the cards stop partway across the
   centred page column with dead space beside them instead of filling it —
   the same auto-fit .hs-tools already uses on the home screen. */
.wl-cards {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(14rem, 1fr));
  gap: 0.75rem;
  margin-bottom: 2rem;
}

/* Two lines of text tall, on the scale of home.html's tool tiles: a list card
   carries a name and the date column's label, which never justified the height
   of a full content card. */
.wl-card {
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
  padding: 0.9rem 1rem;
  background: var(--ub-surface);
  border: 1px solid var(--ub-border);
  border-radius: var(--ub-radius-sm);
  color: var(--ub-ink);
  text-decoration: none;
}

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

.wl-card__name {
  display: flex;
  align-items: center;
  gap: 0.45rem;
  font-size: 0.95rem;
  font-weight: 600;
  letter-spacing: -0.01em;
}

/* The date column's name is what distinguishes two lists whose names do not. */
.wl-card__meta {
  font-family: var(--ub-font-mono);
  font-size: 0.68rem;
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ub-ink-faint);
}

/* The roster's scale, so a reader can tell which list to open. */
.wl-card__counts {
  font-size: var(--ub-text-small);
  color: var(--ub-ink-soft);
}

/* Creating a list is the rarer act on this screen, so it opens on demand rather
   than standing open below the lists it dwarfs. Kept in the DOM either way, and
   forced open by the template when the submission came back with errors. */
.wl-new {
  max-width: 40rem;
  margin-inline: auto;
  border: 1px solid var(--ub-border);
  border-radius: var(--ub-radius);
  background: var(--ub-surface);
}

.wl-new__summary {
  padding: 0.8rem 1.1rem;
  cursor: pointer;
  font-weight: 600;
  font-size: 0.92rem;
}

.wl-new__summary::marker {
  color: var(--ub-accent);
}

.wl-new__summary:focus-visible {
  outline: none;
  box-shadow: var(--ub-ring);
  border-radius: var(--ub-radius);
}

.wl-new .wl-panel {
  max-width: none;
  border: 0;
  border-top: 1px solid var(--ub-border);
  border-radius: 0;
  box-shadow: none;
}

/* A form panel with its own labels rather than .console__form's, which renders
   every label as a mono uppercase caption — unreadable for a label that carries
   a sentence of help beneath it. */
.wl-panel {
  max-width: 40rem;
  padding: 1.6rem 1.75rem;
  background: var(--ub-surface);
  border: 1px solid var(--ub-border);
  border-radius: var(--ub-radius);
  box-shadow: var(--ub-shadow);
}

.wl-panel__title {
  margin: 0 0 0.4rem;
  font-size: var(--ub-text-h3);
  font-weight: 700;
}

.wl-panel__lede {
  margin: 0 0 1.6rem;
  color: var(--ub-ink-soft);
  font-size: var(--ub-text-small);
  line-height: 1.55;
}

.wl-panel__actions {
  display: flex;
  justify-content: flex-end;
  margin-top: 1.75rem;
  padding-top: 1.4rem;
  border-top: 1px solid var(--ub-border);
}

.wl-field {
  margin-bottom: 1.5rem;
}

.wl-field--group {
  min-width: 0;
  margin-bottom: 0;
  padding: 0;
  border: 0;
}

.wl-field__label {
  display: block;
  padding: 0;
  font-size: var(--ub-text-small);
  font-weight: 600;
  color: var(--ub-ink);
}

.wl-field__help {
  margin: 0.3rem 0 0.55rem;
  color: var(--ub-ink-soft);
  font-size: var(--ub-text-small);
  line-height: 1.5;
}

.wl-field input[type="text"] {
  width: 100%;
  padding: 0.62rem 0.8rem;
  border-radius: var(--ub-radius-sm);
  border: 1px solid var(--ub-border-strong);
  background: var(--ub-bg);
  color: var(--ub-ink);
  font: inherit;
}

.wl-field .errorlist {
  list-style: none;
  margin: 0.3rem 0 0;
  padding: 0;
  color: var(--ub-danger);
  font-size: var(--ub-text-small);
}

.wl-choices {
  display: flex;
  flex-direction: column;
  gap: 0.1rem;
}

.wl-choice {
  display: flex;
  align-items: flex-start;
  gap: 0.6rem;
  padding: 0.5rem 0.6rem;
  border-radius: var(--ub-radius-sm);
  cursor: pointer;
}

.wl-choice:hover {
  background: var(--ub-surface-2);
}

.wl-choice input[type="checkbox"] {
  margin: 0.2rem 0 0;
  width: 1rem;
  height: 1rem;
  accent-color: var(--ub-accent);
}

.wl-choice__text {
  display: flex;
  flex-direction: column;
  gap: 0.1rem;
}

.wl-choice__name {
  font-size: 0.92rem;
  font-weight: 600;
}

.wl-choice__help {
  font-size: 0.82rem;
  color: var(--ub-ink-faint);
}

.field-help {
  margin: 0.5rem 0 1.25rem;
  color: var(--ub-ink-faint);
  font-size: var(--ub-text-small);
}

@media (max-width: 640px) {
  .wl-toolbar {
    flex-direction: column;
    align-items: stretch;
  }

  .wl-savebar {
    flex-direction: column;
    align-items: stretch;
  }
}

/* ---- the announcement region ---- */

/* One per page, in base.html. Fixed rather than in flow because it is written
   by a swap that may have happened anywhere on the page, and a message that
   pushes the layout down as it appears moves the control that was just
   clicked. Hidden while empty, so it costs nothing until something fails. */
.app-status {
  position: fixed;
  z-index: 60;
  right: 1rem;
  bottom: 1rem;
  left: auto;
  max-width: min(28rem, calc(100vw - 2rem));
  margin: 0;
  padding: 0.7rem 0.9rem;
  border: 1px solid var(--ub-border-strong);
  border-left: 3px solid var(--ub-border-strong);
  border-radius: var(--ub-radius-sm);
  background: var(--ub-surface);
  box-shadow: var(--ub-shadow-lift);
  color: var(--ub-ink);
  font-size: var(--ub-text-small);
  line-height: 1.5;
}

.app-status:empty {
  display: none;
}

/* ---- the confirmation dialog ---- */

/* Every `hx-confirm`, asked in the application's own voice instead of the
   browser's. A native `<dialog>`, so the backdrop, the focus trap and Escape
   are the platform's; everything below is only what it looks like. */
.confirm {
  width: min(30rem, calc(100vw - 2rem));
  padding: 0;
  border: 1px solid var(--ub-border-strong);
  border-radius: var(--ub-radius);
  background: var(--ub-surface);
  box-shadow: var(--ub-shadow-lift);
  color: var(--ub-ink);
}

.confirm::backdrop {
  background: rgba(24, 18, 43, 0.42);
}

.confirm__body {
  display: flex;
  flex-direction: column;
  gap: 1.15rem;
  margin: 0;
  padding: 1.35rem 1.4rem 1.2rem;
}

/* The question is the content of this dialog, so it is set as content and not as
   a caption: one step up, at the reading measure the rest of the application
   uses for a sentence somebody has to weigh. */
.confirm__question {
  margin: 0;
  font-size: 1rem;
  line-height: 1.6;
  color: var(--ub-ink);
}

.confirm__actions {
  display: flex;
  justify-content: flex-end;
  gap: 0.6rem;
  flex-wrap: wrap;
}

/* Cancel is first in the DOM, where the keyboard needs it, and second on screen,
   where the eye expects the affirmative last. */
.confirm__actions .button--primary {
  order: 1;
}

/* Tone is carried on the region rather than in the words, so the same sentence
   reads the same to a screen reader whichever way it arrived. */
.app-status--ok {
  border-left-color: var(--ub-ok);
}

.app-status--bad {
  border-left-color: var(--ub-danger);
}

@media (max-width: 640px) {
  .app-status {
    right: 0.75rem;
    left: 0.75rem;
    max-width: none;
  }
}

/* A refusal beside the control that caused it, rather than a banner at the top
   of a page whose editor is nine categories long. */
.taxo-row__error {
  flex: 1 1 100%;
  margin: 0.35rem 0 0;
  padding: 0.4rem 0.6rem;
  border-left: 3px solid var(--ub-danger);
  border-radius: var(--ub-radius-sm);
  background: var(--ub-surface-2);
  font-size: var(--ub-text-small);
  color: var(--ub-ink);
}

/* No box of its own. It kept one from the screen it was written for, where it
   stood on plain ground; in the hub it lives inside the compose rail, and a
   tinted card inside a tinted card is two edges saying the same thing. */
.dpform { margin: 0; }

/* The way into one contractor's file from a search result. Quiet: it sits beside
   a name in a list a reviewer is scanning, and it must not compete with the
   review controls that act on the shortlist. */
/* ---------------------------------------------------------------------------
   The contractor hub. One page, three tabs, one panel underneath: identity, the
   at-a-glance strip and the before-staffing warnings stay on screen whichever
   section is open, which is the whole reason for the shape.
   --------------------------------------------------------------------------- */

.hub {
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
  width: 100%;
  max-width: 74rem;
  margin: 0 auto;
  padding: 2rem 2.5rem 3rem;
}

/* The way back to a shortlist, and only ever that: it renders when the reviewer
   arrived from one. `align-self` because `.hub` is a column flex container and
   a stretched link would take the click across the whole page width. Quiet, in
   the label voice — it names where you came from, and the contractor's name is
   what this page is about. */
.hub__back {
  align-self: flex-start;
  margin-bottom: 0.35rem;
  font-family: var(--ub-font-mono);
  font-size: var(--ub-text-label);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ub-ink-soft);
  text-decoration: none;
}
.hub__back:hover { color: var(--ub-ink); }

.hub__eyebrow {
  margin: 0;
  font-family: var(--ub-font-mono);
  font-size: var(--ub-text-label);
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--ub-accent);
}
.hub__name {
  margin: 0.4rem 0 0;
  font-family: var(--ub-font-display);
  font-size: 2.25rem;
  line-height: 1.06;
  letter-spacing: -0.02em;
}

/* The reference gives Identity slightly more room, with At a glance and the
   staffing warnings sharing the rest. Below 1100px the connected surface
   becomes one divided stack. */
.hub__columns {
  display: grid;
  grid-template-columns: 1fr;
  margin-top: 0.55rem;
  overflow: hidden;
  background: var(--ub-surface);
  border: 1px solid var(--ub-border);
  border-radius: var(--ub-radius);
}
@media (min-width: 1100px) {
  .hub__columns { grid-template-columns: minmax(0, 1.1fr) minmax(0, 1fr) minmax(0, 1.05fr); }
}

.hub-card {
  padding: 1.35rem 1.5rem;
  background: var(--ub-surface);
  border: 0;
  border-bottom: 1px solid var(--ub-border);
  border-radius: 0;
  box-shadow: none;
}
.hub__columns > .hub-card:last-child { border-bottom: 0; }
@media (min-width: 1100px) {
  .hub__columns > .hub-card {
    border-right: 1px solid var(--ub-border);
    border-bottom: 0;
  }
  .hub__columns > .hub-card:last-child { border-right: 0; }
}
/* An `h2` for the document outline, but an eyebrow to read: the heading level
   is what carries the weight here, and the browser's bold `h2` default made
   three labels compete with the card contents under them. Matched to
   `.hub__eyebrow`, which is a `<p>` and so was already light. */
.hub-card__title {
  margin: 0 0 0.8rem;
  font-family: var(--ub-font-mono);
  font-size: 0.66rem;
  font-weight: 500;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--ub-ink-faint);
}
.hub-card__sub {
  margin: 1rem 0 0.5rem;
  padding-top: 0.9rem;
  border-top: 1px solid var(--ub-border);
  font-family: var(--ub-font-mono);
  font-size: var(--ub-text-label);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ub-ink-faint);
}

.hub-identity__title { margin: 0; font-size: var(--ub-text-h3); font-weight: 600; }
.hub-identity__team { margin: 0.15rem 0 0; font-size: 0.95rem; color: var(--ub-ink-soft); }

.hub-facts {
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
  margin: 0.95rem 0 0;
  padding-top: 0.85rem;
  border-top: 1px solid var(--ub-border);
}
.hub-facts__row { display: flex; gap: 0.75rem; font-size: var(--ub-text-small); }
.hub-facts__row dt { width: 5.75rem; flex: none; color: var(--ub-ink-faint); }
.hub-facts__row dd { margin: 0; text-align: left; overflow-wrap: anywhere; }
.hub-facts__mono { font-family: var(--ub-font-mono); }
/* Underlined rather than coloured: this row sits in a list of four facts, and a
   coloured name would read as a status about the DP instead of a way to reach
   them. The offset keeps the rule clear of the descenders in a person's name. */
.hub-facts__link {
  color: inherit;
  text-decoration: underline;
  text-decoration-color: var(--ub-border-strong);
  text-underline-offset: 0.18em;
}
.hub-facts__link:hover { text-decoration-color: currentColor; }

.hub-lists {
  display: flex;
  flex-wrap: wrap;
  gap: 0.45rem;
  margin: 0.85rem 0 0;
  padding: 0.85rem 0 0;
  border-top: 1px solid var(--ub-border);
  list-style: none;
}
.hub-lists__chip {
  padding: 0.2rem 0.55rem;
  border: 1px solid var(--ub-border-strong);
  border-radius: 2px;
  font-size: var(--ub-text-label);
  color: var(--ub-ink-soft);
}
.hub-lists__chip--roster {
  border-color: color-mix(in srgb, var(--ub-accent) 35%, var(--ub-border));
  background: var(--ub-accent-quiet);
  color: var(--ub-ink);
}
.hub-lists__chip--roster::before { content: "•"; margin-right: 0.35rem; color: var(--ub-ok); }

.hub-stats { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0.8rem 1rem; }
.hub-stat { padding: 0; }
.hub-stat__value {
  margin: 0;
  font-family: var(--ub-font-display);
  font-size: 1.75rem;
  font-weight: 700;
  line-height: 1;
}
.hub-stat__of { font-size: var(--ub-text-small); font-weight: 400; color: var(--ub-ink-faint); }
.hub-stat__label { margin: 0.1rem 0 0; font-size: var(--ub-text-label); color: var(--ub-ink-faint); }

.hub-strengths { display: flex; flex-wrap: wrap; gap: 0.35rem; margin: 0; padding: 0; list-style: none; }
.hub-strengths__chip {
  padding: 0.2rem 0.6rem;
  background: color-mix(in srgb, var(--ub-ok) 10%, var(--ub-surface));
  border-radius: 2px;
  font-size: var(--ub-text-small);
  color: var(--ub-ok);
  font-weight: 600;
}
.hub-strengths__count { font-family: var(--ub-font-mono); font-size: var(--ub-text-label); font-weight: 400; }

.hub-warnings { background: color-mix(in srgb, var(--ub-surface-2) 60%, var(--ub-surface)); }
/* The card heading no longer carries the alarm. It did while it read "Read this
   before staffing" and held only computed warnings; now it also quotes ordinary
   working-list notes, and a red label above "Nothing to flag." was reading as an
   alert about nothing. Urgency belongs on the individual item's dot below. */

.hub-warnings__list { display: flex; flex-direction: column; gap: 0.65rem; margin: 0; padding: 0; list-style: none; }
/* A dot, as design 3b marks these, rather than a left rule: a stack of ruled
   boxes reads as four quotations, and these are four sentences in one list. The
   dot is drawn on the flex line so it stays beside the first line of a wrapped
   item instead of centring against all three. */
.hub-warnings__item {
  display: flex;
  gap: 0.65rem;
  font-size: var(--ub-text-small);
  line-height: 1.5;
}
.hub-warnings__item::before {
  content: "";
  flex: none;
  width: 6px;
  height: 6px;
  margin-top: 0.45rem;
  border-radius: 50%;
  background: var(--ub-border-strong);
}
/* Tone by kind, so a disagreement is not read at the same weight as an ageing
   assessment. Both are worth acting on; only one is somebody's judgement of a
   person. */
.hub-warnings__item--disagreement::before { background: var(--ub-danger); }
.hub-warnings__item--stale_self_assessment::before,
.hub-warnings__item--note_review_due::before { background: var(--ub-accent); }

/* Quoted, not derived. No dot: the marker column above belongs to computed
   warnings, and giving somebody's own sentence the same bullet would file it as
   one more finding. A rule on the inline edge is the ordinary quotation mark. */
.hub-warnings__notes {
  display: flex;
  flex-direction: column;
  gap: 0.7rem;
  margin-top: 0.9rem;
  padding-top: 0.9rem;
  border-top: 1px solid var(--ub-border);
}
.hub-warnings__note { border-inline-start: 2px solid var(--ub-border-strong); padding-inline-start: 0.65rem; }
.hub-warnings__note-list {
  display: block;
  color: var(--ub-ink-faint);
  font-family: var(--ub-font-mono);
  font-size: var(--ub-text-label);
  text-transform: uppercase;
  letter-spacing: 0.04em;
}
/* Wrapping is load-bearing: a note is truncated at 280 characters, not at a
   width, so a long unbroken token must not push the card's column open. */
.hub-warnings__note-body {
  margin: 0.15rem 0 0;
  font-size: var(--ub-text-small);
  line-height: 1.5;
  overflow-wrap: anywhere;
}

/* 1.35fr / 1fr, the ratio design 3b gives this row: the control that takes a
   name and a decision needs the width, and the profile card is one line. */
.hub__fixtures { display: grid; align-items: stretch; gap: 0.75rem; grid-template-columns: 1fr; }
@media (min-width: 1100px) {
  .hub__fixtures { grid-template-columns: minmax(0, 1.35fr) minmax(0, 1fr); }
}
/* Card 10 and card 11 fill these; an empty slot draws no box of its own. */
.hub-fixture:empty { display: none; }
.hub-fixture > .validation-issue { height: 100%; }
.hub-tabs {
  display: flex;
  flex-wrap: wrap;
  gap: 0.25rem;
  margin-top: 1rem;
  border-bottom: 1px solid var(--ub-border);
}
.hub-tabs__tab {
  padding: 0.75rem 1rem;
  border-bottom: 2px solid transparent;
  color: var(--ub-ink-soft);
  font-size: 0.92rem;
  text-decoration: none;
}
.hub-tabs__tab:hover { color: var(--ub-ink); }
.hub-tabs__tab--active { border-bottom-color: var(--ub-accent); color: var(--ub-ink); font-weight: 700; }
.hub-tabs__count { color: var(--ub-ink-faint); font-family: var(--ub-font-mono); font-size: var(--ub-text-label); font-weight: 400; }
/* The one count on this strip that changes what somebody does next. */
.hub-tabs__count--alert { color: var(--ub-danger); }

.hub-panel__body { padding-top: 1.4rem; }

/* ---------------------------------------------------------------------------
   The People directory. The way into a contractor's hub, and the only screen
   that renders the whole roster: 366 rows in one list, as the console directory
   already does, because a page control buys nothing at this size.
   --------------------------------------------------------------------------- */

/* `.hs-content` is full-bleed, so a screen that draws no measure of its own runs
   the whole width of the window — 366 rows stretched edge to edge, with the
   staffing marks pushed off the right of a wide display. The same measure and the
   same gutter as `.hub`, deliberately: the list and the file it opens are one
   surface, and a different width here would shift the page on every row click. */
.people {
  display: flex;
  flex-direction: column;
  gap: 1.1rem;
  width: 100%;
  max-width: 74rem;
  margin: 0 auto;
  padding: 2rem 2.5rem 3rem;
}
.people__eyebrow {
  margin: 0;
  font-family: var(--ub-font-mono);
  font-size: var(--ub-text-label);
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--ub-accent);
}
.people__title {
  margin: 0.4rem 0 0;
  font-family: var(--ub-font-display);
  font-size: 2.25rem;
  line-height: 1.06;
  letter-spacing: -0.02em;
}
.people__lede { margin: 0.35rem 0 0; color: var(--ub-ink-soft); font-size: var(--ub-text-small); }

.people-search { display: flex; gap: 0.5rem; align-items: center; }
.people-search__field { flex: 1 1 auto; }
.people-search__field .field__control { width: 100%; }

.people-chips { display: flex; flex-wrap: wrap; gap: 0.35rem; margin-bottom: 0.85rem; }
.people-chips__chip {
  padding: 0.3rem 0.75rem;
  border: 1px solid var(--ub-border-strong);
  border-radius: var(--ub-radius-pill);
  color: var(--ub-ink-soft);
  font-size: var(--ub-text-small);
  text-decoration: none;
}
.people-chips__chip:hover { border-color: var(--ub-accent); color: var(--ub-ink); }
.people-chips__chip--active { background: var(--ub-accent); border-color: var(--ub-accent); color: var(--ub-accent-ink); }
/* Same `.rec-color a:not(.button)` override as the worklist sort control. */
.rec-color .people-chips a.people-chips__chip--active {
  background: var(--ub-accent);
  border-color: var(--ub-accent);
  color: var(--ub-accent-ink);
}
.people-chips__count { font-family: var(--ub-font-mono); font-size: var(--ub-text-label); }

/* The heading strip shares the row's grid and its horizontal padding, plus the
   row border it does not draw, so every label sits over its own column. The
   avatar column is deliberately unlabelled. */
.people-cols {
  display: grid;
  align-items: end;
  gap: 0.75rem;
  grid-template-columns: 2rem minmax(0, 2fr) minmax(0, 1fr) minmax(0, 1fr) 5rem minmax(0, 1fr);
  padding: 0 calc(0.75rem + 1px) 0.4rem;
  border-bottom: 1px solid var(--ub-border);
}
.people-cols__label {
  font-family: var(--ub-font-mono);
  font-size: var(--ub-text-label);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ub-ink-faint);
}
.people-cols__label--name { grid-column: 2; }

.people-rows { display: flex; flex-direction: column; gap: 0.35rem; margin: 0; padding: 0; list-style: none; }
.people-row__link {
  display: grid;
  align-items: center;
  gap: 0.75rem;
  grid-template-columns: 2rem minmax(0, 2fr) minmax(0, 1fr) minmax(0, 1fr) 5rem minmax(0, 1fr);
  padding: 0.6rem 0.75rem;
  background: var(--ub-surface);
  border: 1px solid var(--ub-border);
  border-radius: var(--ub-radius-sm);
  color: inherit;
  text-decoration: none;
}
.people-row__link:hover { border-color: var(--ub-border-strong); box-shadow: var(--ub-shadow); }

/* Read out but never drawn while the heading strip is on screen; the stack
   breakpoint below turns the same words back into visible text. */
.people-row__key {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

/* Below 1100px the row stops being a grid and becomes a labelled stack: a
   control found only by scrolling a table sideways is the case the reach rule
   is about. */
@media (max-width: 1100px) {
  .people-cols { display: none; }
  .people-row__link { grid-template-columns: 2rem minmax(0, 1fr); }
  .people-row__team,
  .people-row__dp,
  .people-row__tenure,
  .people-row__marks { grid-column: 2; }
  .people-row__key {
    position: static;
    width: auto;
    height: auto;
    margin: 0 0.4rem 0 0;
    overflow: visible;
    clip: auto;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--ub-ink-faint);
  }
}

.people-row__avatar {
  display: grid;
  place-items: center;
  width: 2rem;
  height: 2rem;
  background: var(--ub-ink);
  border-radius: var(--ub-radius-sm);
  color: var(--ub-surface);
  font-family: var(--ub-font-display);
}
.people-row__identity { display: flex; flex-direction: column; min-width: 0; }
.people-row__name { font-weight: 600; }
.people-row__title,
.people-row__team,
.people-row__dp,
.people-row__tenure { color: var(--ub-ink-soft); font-size: var(--ub-text-small); }
/* Tenure is a code, and the mono face is what this system already sets counts
   in — it also lines the digits up down 366 rows. */
.people-row__tenure { font-family: var(--ub-font-mono); font-size: var(--ub-text-label); }
.people-row__marks { display: flex; flex-wrap: wrap; gap: 0.3rem; justify-content: flex-end; }

.people-mark {
  padding: 0.1rem 0.5rem;
  border-radius: var(--ub-radius-pill);
  font-size: var(--ub-text-label);
}
.people-mark--roster { background: var(--ub-accent-quiet); color: var(--ub-accent); }
.people-mark--stale { border: 1px solid var(--ub-border-strong); color: var(--ub-ink-faint); }

.people-empty { padding: 1.5rem 0; }

/* ---- The hub's self-assessment panel ---- */

/* Reuses .accordion, .skillgrid, .skillcard and .stars from the contractor's
   own screen. Only the read-only differences live here: nothing is clickable,
   and a validation is marked beside the rating it agrees or disagrees with. */

/* One control row: what narrows the list on the left, what dates it on the
   right, as design 3b arranges it. The meta wraps under the chips rather than
   squeezing them when the row runs out of width. */
.skillpanel__controls {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem 1rem;
  margin-bottom: 0.9rem;
}
.skillpanel__controls .skillchips { margin-bottom: 0; }
.skillpanel__meta {
  margin: 0 0 0 auto;
  color: var(--ub-ink-faint);
  font-size: var(--ub-text-small);
}
/* Stale is a word, not a badge. A pill here competes with the filter chips
   beside it for the same glance, and this is the smaller news of the two. */
.skillpanel__stale { color: var(--ub-danger); font-weight: 600; }

.skillchips { display: flex; flex-wrap: wrap; gap: 0.35rem; margin-bottom: 1rem; }
/* Square, not pill: 2px is the radius design 3b gives every chip and button
   that is an action, and it is what separates them from the rounded surfaces
   they sit on. Active is a tint and a weight, not a fill — a solid gold chip
   outshouts the panel it is filtering. */
.skillchip {
  padding: 0.42rem 0.8rem;
  border: 1px solid var(--ub-border-strong);
  border-radius: 2px;
  background: var(--ub-surface);
  color: var(--ub-ink-soft);
  font: inherit;
  font-size: var(--ub-text-small);
  cursor: pointer;
}
.skillchip:hover { border-color: var(--ub-accent); color: var(--ub-ink); }
.skillchip--active {
  background: var(--ub-accent-quiet);
  border-color: var(--ub-accent);
  color: var(--ub-ink);
  font-weight: 600;
}
.skillchip--alert { border-color: var(--ub-danger); color: var(--ub-danger); font-weight: 600; }
.skillchip--alert.skillchip--active { background: color-mix(in srgb, var(--ub-danger) 8%, var(--ub-surface)); }
.skillchip__count { font-family: var(--ub-font-mono); font-size: var(--ub-text-label); }

/* The stars are spans here, not buttons: nothing on this panel is pressable,
   and a pointer cursor over a rating would promise an edit that is not on
   offer. */
.stars--readonly .stars__star,
.stars--readonly .stars__na { cursor: default; }
/* "No experience" is an answer, but it is not a rating and it is not an action:
   on the contractor's own screen the gold says "this is what you picked", and
   here there is nothing to pick. Design 3b draws it as a dashed neutral tag. */
.stars--readonly .stars__na--on {
  border-style: dashed;
  border-color: var(--ub-border-strong);
  background: none;
  color: var(--ub-ink-faint);
}

/* ---- Category rows, collapsed (design 3b) ---- */

/* Tighter than the contractor's own accordion: nine of these stack on one
   screen, and 3b sets them 8px apart rather than 16. */
.skillcat { margin-top: 0.5rem; }
.skillcat .accordion__head { gap: 0.85rem; padding: 0.85rem 1.2rem; }
.skillcat .accordion__name { font-size: 1rem; }

/* The five-band bar. `overflow: hidden` against the track's own radius is what
   squares the bands' ends; each band's width is a percentage of the whole
   category, so a thin one draws short. */
.skillcat__bar {
  display: flex;
  flex: none;
  width: 9.4rem;
  height: 6px;
  overflow: hidden;
  border-radius: var(--ub-radius-pill);
  background: var(--ub-border);
}
.skillcat__band--5 { background: var(--ub-lvl-5); }
.skillcat__band--4 { background: var(--ub-lvl-4); }
.skillcat__band--3 { background: var(--ub-lvl-3); }
.skillcat__band--2 { background: var(--ub-lvl-2); }
.skillcat__band--1 { background: var(--ub-lvl-1); }

.skillcat .accordion__count { margin-left: 0; letter-spacing: 0.04em; font-weight: 400; }
/* A category nobody finished says so in the count itself. */
.skillcat .accordion__count--short { color: var(--ub-danger); }

.skillcat__mark {
  margin-left: auto;
  padding: 0.15rem 0.5rem;
  border-radius: 2px;
  font-size: var(--ub-text-label);
  font-weight: 600;
  white-space: nowrap;
}
.skillcat__mark--validated { background: color-mix(in srgb, var(--ub-ok) 12%, var(--ub-surface)); color: var(--ub-ok); }
.skillcat__mark--differs { background: color-mix(in srgb, var(--ub-danger) 12%, var(--ub-surface)); color: var(--ub-danger); }
.skillcat__mark--gap { border: 1px dashed var(--ub-border-strong); color: var(--ub-ink-faint); font-weight: 400; }

.skillcat--differs { border-color: color-mix(in srgb, var(--ub-danger) 35%, var(--ub-border)); }
.skillcat--differs .accordion__diamond { border-color: var(--ub-danger); }

/* ---- Skill rows, expanded (design 3b) ---- */

/* Two columns of thin rows separated by rules, not a grid of cards: this panel
   is read and never answered, so no row needs a target of its own. */
/* Two columns exactly, not `auto-fit`: the rule below is drawn on the odd
   children, which only means "the left column" when there are two of them. */
.skillgrid--rows {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 0;
  margin-top: 0;
}
@media (min-width: 55rem) {
  .skillgrid--rows { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
.skillgrid--rows .skillcard {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  padding: 0.5rem 0.15rem;
  background: none;
  border: 0;
  border-bottom: 1px solid var(--ub-border);
  border-radius: 0;
  font-size: var(--ub-text-small);
}
.skillgrid--rows .skillcard__label { margin: 0; font-weight: 400; }
.skillgrid--rows .stars { margin-left: auto; gap: 0.45rem; }
.skillgrid--rows .stars__star svg { width: 0.85rem; height: 0.85rem; }
/* The dividing rule between the two columns, drawn on the odd children only so
   the right-hand column keeps a clean edge. */
@media (min-width: 55rem) {
  .skillgrid--rows .skillcard:nth-child(odd) { padding-right: 1.35rem; margin-right: 1.35rem; border-right: 1px solid var(--ub-border); }
}

.skillcard__unanswered { font-size: var(--ub-text-small); }
/* Square and green, like every other "somebody looked at this and agreed" mark
   on the page. It was a gold pill, which is the accent this design spends on
   actions rather than on evidence. */
.skillcard__mark {
  padding: 0.05rem 0.45rem;
  border-radius: 2px;
  background: color-mix(in srgb, var(--ub-ok) 12%, var(--ub-surface));
  color: var(--ub-ok);
  font-size: var(--ub-text-label);
  font-weight: 600;
}
.skillcard__mark--differs { background: color-mix(in srgb, var(--ub-danger) 12%, var(--ub-surface)); color: var(--ub-danger); }

/* ---- The hub's validations panel ---- */

.valpanel { display: flex; flex-direction: column; }
.valcards { display: flex; flex-direction: column; gap: 0.75rem; }

/* A card, not a ruled paragraph: 3c opens each disagreement as its own object
   because it is one interviewer's argument, and the red is on the edge of the
   whole card rather than a bar down its left side. */
.valcard {
  padding: 1.25rem 1.35rem;
  background: var(--ub-surface);
  border: 1px solid color-mix(in srgb, var(--ub-danger) 40%, var(--ub-border));
  border-radius: var(--ub-radius);
}
.valcard__head { display: flex; flex-wrap: wrap; align-items: center; gap: 0.6rem; }
.valcard__skill { margin: 0; font-family: var(--ub-font-display); font-size: 1.2rem; font-weight: 700; }
.valcard__flag {
  padding: 0.1rem 0.5rem;
  border-radius: 2px;
  background: color-mix(in srgb, var(--ub-danger) 14%, var(--ub-surface));
  color: var(--ub-danger);
  font-size: var(--ub-text-label);
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}
.valcard__category { margin-left: auto; color: var(--ub-ink-soft); font-size: var(--ub-text-small); }

/* The two readings, facing each other across an arrow. Tinted, because it is
   the one line on the card that is the disagreement rather than evidence for
   it. */
.valcard__verdict {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.7rem;
  margin: 0.8rem 0 0;
  padding: 0.55rem 0.85rem;
  border-radius: var(--ub-radius-sm);
  background: color-mix(in srgb, var(--ub-danger) 7%, var(--ub-surface));
  font-size: var(--ub-text-small);
}
.valcard__verdict-label { color: var(--ub-ink-soft); }
.valcard__verdict .stars { gap: 0.1rem; }
.valcard__verdict .stars__star svg { width: 0.9rem; height: 0.9rem; }
.valcard__self { font-weight: 600; }
.valcard__arrow { flex: none; width: 1.1rem; height: 1.1rem; color: var(--ub-danger); }
.valcard__read {
  padding: 0.15rem 0.6rem;
  border-radius: 2px;
  background: var(--ub-danger);
  color: #fff;
  font-weight: 700;
}
.valcard__gap { margin-left: auto; color: var(--ub-ink-faint); font-size: var(--ub-text-label); }

/* Four columns, label above answer, no box: 3c leaves these on the card's own
   ground so the tinted verdict line above stays the only tinted thing. */
.valcard__scales {
  display: grid;
  gap: 0.9rem 1rem;
  grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr));
  margin: 1.1rem 0 0;
}
.valcard__scale dt {
  margin-bottom: 0.2rem;
  color: var(--ub-ink-faint);
  font-family: var(--ub-font-mono);
  font-size: 0.62rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
}
.valcard__scale dd { margin: 0; font-size: var(--ub-text-small); }

.valcard__quote {
  margin: 1.1rem 0 0;
  padding-left: 1rem;
  border-left: 2px solid var(--ub-border-strong);
  font-family: var(--ub-font-display);
  font-size: 1.02rem;
  font-style: italic;
  font-weight: 700;
  line-height: 1.5;
}
.valcard__byline { margin: 1rem 0 0; color: var(--ub-ink-soft); font-size: var(--ub-text-small); }

/* The rule that separates the arguments above from the agreements below, with
   the count set into it. */
.valpanel__divider {
  display: flex;
  align-items: center;
  gap: 0.9rem;
  margin: 1.6rem 0 0.7rem;
  color: var(--ub-ink-faint);
  font-family: var(--ub-font-mono);
  font-size: var(--ub-text-label);
  letter-spacing: 0.14em;
  text-transform: uppercase;
}
.valpanel__divider::after { content: ""; flex: 1; height: 1px; background: var(--ub-border); }

.valtable__frame {
  overflow: hidden;
  overflow-x: auto;
  background: var(--ub-surface);
  border: 1px solid var(--ub-border);
  border-radius: var(--ub-radius);
}
.valtable { width: 100%; border-collapse: collapse; font-size: var(--ub-text-small); }
.valtable th,
.valtable td { padding: 0.7rem 0.9rem; border-bottom: 1px solid var(--ub-border); text-align: left; vertical-align: top; }
.valtable tbody tr:last-child th,
.valtable tbody tr:last-child td { border-bottom: 0; }
.valtable thead th {
  background: var(--ub-bg);
  color: var(--ub-ink-faint);
  font-family: var(--ub-font-mono);
  font-size: 0.62rem;
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
}
.valtable tbody th { font-weight: 600; }
.valtable .stars__star svg { width: 0.85rem; height: 0.85rem; }
.valtable__level {
  display: inline-block;
  padding: 0.1rem 0.5rem;
  border-radius: 2px;
  background: color-mix(in srgb, var(--ub-ok) 12%, var(--ub-surface));
  color: var(--ub-ok);
  font-weight: 600;
}
.valtable__who { color: var(--ub-ink-soft); white-space: nowrap; }
/* Set as a quotation like the cards above, one size down: the same words doing
   the same job, in less room. */
.valtable__quote {
  font-family: var(--ub-font-display);
  font-style: italic;
  font-weight: 700;
  line-height: 1.45;
}
.valtable__redacted { color: var(--ub-ink-faint); font-style: italic; }

/* ---- The hub's Delivery Principal assessment panel ---- */

/* Reading is the common case, so the notes take the width and the compose form
   is a rail beside them. The old screen put two form boxes and a permanently
   open six-field form above the notes. */

/* 3d's proportions: the notes take the width, the compose form is a fixed rail
   beside them. Fixed rather than fractional, because a form's fields do not get
   more useful as the window grows and the notes do. */
.dppanel { display: grid; gap: 1.25rem; grid-template-columns: 1fr; }
@media (min-width: 1100px) {
  .dppanel { grid-template-columns: minmax(0, 1fr) 22.5rem; }
}

.dppanel__privacy { margin: 0 0 0.9rem; color: var(--ub-ink-faint); font-size: var(--ub-text-small); }
.dppanel__none { margin: 0; }

.dpnote {
  padding: 1rem 1.25rem;
  margin-bottom: 0.75rem;
  background: var(--ub-surface);
  border: 1px solid var(--ub-border-strong);
  border-radius: var(--ub-radius-sm);
}
.dpnote--past { margin-bottom: 0; padding: 0.9rem 0 0; background: none; border: 0; border-top: 1px solid var(--ub-border); border-radius: 0; }
.dppanel__pastbody [data-skill-category] > .dpnote--past:first-child { padding-top: 0; border-top: 0; }

/* Facts left, the one action right, on one line — 3d's arrangement. The action
   is a link rather than a button: it opens the form in the rail with a note
   already chosen, and nothing is written by clicking it. */
.dpnote__head { display: flex; align-items: flex-start; gap: 1rem; }
.dpnote__meta { display: flex; flex-wrap: wrap; align-items: center; gap: 0.45rem; margin: 0 0 0.6rem; }
.dpnote__when { color: var(--ub-ink-soft); font-size: var(--ub-text-small); }
.dpnote__chip {
  padding: 0.1rem 0.6rem;
  border: 1px solid var(--ub-border-strong);
  border-radius: var(--ub-radius-pill);
  background: none;
  color: var(--ub-ink-soft);
  font-size: var(--ub-text-small);
  text-transform: capitalize;
}
.dpnote__chip--current {
  border-color: color-mix(in srgb, var(--ub-ok) 40%, transparent);
  background: color-mix(in srgb, var(--ub-ok) 10%, var(--ub-surface));
  color: var(--ub-ok);
  font-weight: 600;
}
.dpnote__chip--redacted {
  border-color: color-mix(in srgb, var(--ub-danger) 40%, transparent);
  background: color-mix(in srgb, var(--ub-danger) 8%, var(--ub-surface));
  color: var(--ub-danger);
  font-weight: 600;
}
.dpnote__body { margin: 0; line-height: 1.6; white-space: pre-wrap; }
.dpnote__replace { flex: none; margin-left: auto; font-size: var(--ub-text-small); white-space: nowrap; }

/* The history, shaped like the card it sits under rather than a bare
   disclosure triangle: same surface, same radius, and a chevron that turns. */
.dppanel__past {
  margin-top: 0.75rem;
  overflow: hidden;
  background: var(--ub-surface);
  border: 1px solid var(--ub-border);
  border-radius: var(--ub-radius);
}
.dppanel__pastsummary {
  display: flex;
  align-items: center;
  gap: 0.85rem;
  padding: 0.9rem 1.25rem;
  cursor: pointer;
  list-style: none;
}
.dppanel__pastsummary::-webkit-details-marker { display: none; }
.dppanel__pastcount { font-weight: 600; font-size: var(--ub-text-small); }
.dppanel__pastbreak { color: var(--ub-ink-soft); font-size: var(--ub-text-small); }
.dppanel__pastchevron {
  flex: none;
  width: 1rem;
  height: 1rem;
  margin-left: auto;
  color: var(--ub-ink-faint);
  transform: rotate(-90deg);
  transition: transform 0.18s ease;
}
.dppanel__past[open] .dppanel__pastchevron { transform: none; }
.dppanel__pastbody { padding: 1rem 1.25rem 1.25rem; border-top: 1px solid var(--ub-border); }
.dppanel__pastbody .skillchips { margin-bottom: 0.9rem; }

.dppanel__rail {
  align-self: start;
  padding: 1.25rem;
  background: var(--ub-surface-2);
  border: 1px solid var(--ub-border-strong);
  border-radius: var(--ub-radius);
}
.dppanel__railhead { margin: 0 0 0.9rem; font-size: 0.95rem; font-weight: 700; }
.dppanel__rail .field { display: block; margin-bottom: 0.7rem; }
.dppanel__rail .field__control { width: 100%; }
/* Mono, uppercase, small — the label voice this design uses everywhere a field
   is named, and what keeps six stacked fields from reading as six headings. */
.dppanel__rail .field__label {
  color: var(--ub-ink-soft);
  font-family: var(--ub-font-mono);
  font-size: 0.62rem;
  font-weight: 400;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}
.dppanel__rail .button { width: 100%; justify-content: center; }
/* Review and expiry are one decision in two halves, so they share a line. */
.dpform__dates { display: flex; gap: 0.6rem; }
.dpform__dates .field { flex: 1; min-width: 0; }
.dpform__hint { margin: 0 0 0.9rem; color: var(--ub-ink-faint); font-size: var(--ub-text-label); line-height: 1.4; }
.dpform__cancel { margin: 0.6rem 0 0; font-size: var(--ub-text-small); text-align: center; }

.dppanel__railhead--second { margin-top: 1.5rem; padding-top: 1.25rem; border-top: 1px solid var(--ub-border-strong); }
.dppanel__unassessed { margin: -0.4rem 0 0.8rem; font-size: var(--ub-text-small); }
.dpform--structured { padding-bottom: 0.9rem; margin-bottom: 0.9rem; border-bottom: 1px dashed var(--ub-border-strong); }
.dpform--structured:last-child { border-bottom: none; margin-bottom: 0; }

/* ---- The hub's client-facing profile card ---- */

.profilecard {
  padding: 1.1rem 1.25rem;
  background: var(--ub-surface);
  border: 1px solid var(--ub-border);
  border-radius: var(--ub-radius);
}
/* Design 3b: icon, what it is, what state it is in, and the one action, on a
   single vertically-centred row. This card names itself in the row rather than
   under a mono eyebrow, which is what keeps it one line tall. */
.profilecard__row { display: flex; align-items: center; gap: 1rem; }
.profilecard__doc {
  display: grid;
  place-items: center;
  flex: none;
  width: 36px;
  height: 46px;
  border: 1px solid var(--ub-border-strong);
  border-radius: 2px;
  background: var(--ub-surface-2);
  color: var(--ub-ink-soft);
}
.profilecard__doc svg { width: 17px; height: 17px; max-width: 100%; }
.profilecard__what { flex: 1; min-width: 0; }
.profilecard__title { margin: 0; font-size: var(--ub-text-body); font-weight: 600; }
.profilecard__state { margin: 0.25rem 0 0; font-size: var(--ub-text-small); color: var(--ub-ink-soft); }
/* Nothing recorded is a gap, not a state to be proud of: 3b marks its own empty
   card with a dashed edge, and the icon follows it. */
.profilecard--empty { border-color: var(--ub-border-strong); }
.profilecard--empty .profilecard__doc { color: var(--ub-ink-faint); }
/* The recorded card is one line, as 3b draws it; the empty one cannot be,
   because it carries a field. So it keeps 3b's row on top — icon, what it is,
   what state it is in — and puts the field on a second line at full width,
   rather than squeezing a URL field into a third column where nobody can read
   what they pasted. */
.profilecard--empty {
  display: grid;
  grid-template-columns: 36px minmax(0, 1fr);
  align-items: center;
  gap: 0.7rem 1rem;
}
.profilecard--empty .profilecard__row { display: contents; }
.profilecard--empty .profilecard__doc { grid-column: 1; grid-row: 1; }
.profilecard--empty .profilecard__what { grid-column: 2; grid-row: 1; padding-right: 10.5rem; }
.profilecard__state--empty { color: var(--ub-ink-faint); }
.profilecard--empty .profilecard__form { grid-column: 1 / -1; grid-row: 2; }
/* The placeholder carries the shape of what to paste and the state line above
   already says nothing is recorded, so the label is doing its work for screen
   readers only. */
.profilecard--empty .profilecard__paste .field__label {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}
.profilecard__unrecorded {
  color: var(--ub-ink);
  font-weight: 700;
}
.profilecard--empty .field__label { font-weight: 400; }
.profilecard__actions { display: flex; flex-wrap: wrap; align-items: center; gap: 0.75rem; margin: 0 0 0.75rem; }
.profilecard__create { display: flex; flex-wrap: wrap; align-items: center; gap: 0.6rem; margin: 0 0 0.85rem; }
.profilecard__create .muted { font-size: var(--ub-text-label); }
.profilecard__folder,
.profilecard__folder-only a { font-size: var(--ub-text-small); }
.profilecard__form .field { display: block; }
.profilecard__form .field__control { width: 100%; }
/* The paste line, which design 3b has no state for — it shows an approved
   profile, and its empty state offers an in-app editor this branch deleted.
   Bottom-aligned so Record meets the input, not the label above it. */
.profilecard__paste { display: flex; flex-wrap: wrap; align-items: flex-end; gap: 0.6rem; margin-top: 0.75rem; }
.profilecard__paste .field { flex: 1 1 12rem; margin: 0; }
.profilecard__paste .button { flex: 0 0 auto; }
/* The how-to, closed. Read once, furniture thereafter — and the card sits beside
   another, so its resting height is what matters. */
.profilecard__how { margin: 0.55rem 0 0; font-size: var(--ub-text-label); }
.profilecard__how > summary { cursor: pointer; color: var(--ub-ink-soft); }
.profilecard__how > summary:hover { color: var(--ub-ink); }
.profilecard__how[open] > summary { margin-bottom: 0.6rem; }
.profilecard__how--create { margin-top: 0.7rem; }
.profilecard--empty .profilecard__how--create {
  grid-column: 2;
  grid-row: 1;
  justify-self: end;
  margin: 0;
}
.profilecard--empty .profilecard__how--create[open] {
  grid-column: 1 / -1;
  grid-row: 3;
  justify-self: stretch;
}
.hub__fixtures:has(.profilecard__how--create[open]) { align-items: start; }
.hub__fixtures:has(.profilecard__how--create[open]) .hub-fixture > .validation-issue { height: auto; }
/* One line, never two: the field and Record share the card's last column, and
   letting them wrap there makes the card taller than the one beside it — the
   thing this layout exists to avoid. */
.profilecard--empty .profilecard__paste { flex-wrap: nowrap; margin-top: 0; }
.profilecard--empty .profilecard__paste .field { flex: 1 1 8rem; }
.profilecard__how .profilecard__create { margin: 0.75rem 0 0.6rem; }
.profilecard__how .profilecard__form { margin-top: 0.75rem; }
.profilecard__how .profilecard__steps { margin: 0; }
.profilecard__refusal {
  margin: 0.4rem 0;
  padding: 0.4rem 0.6rem;
  border-radius: var(--ub-radius-sm);
  background: color-mix(in srgb, var(--ub-danger) 10%, transparent);
  color: var(--ub-danger);
  font-size: var(--ub-text-small);
}
.profilecard__steps { margin: 0 0 0.9rem; padding-left: 1.1rem; font-size: var(--ub-text-small); line-height: 1.6; }
.profilecard__steps li { margin-bottom: 0.15rem; }
.profilecard__name { font-family: var(--ub-font-mono); font-size: var(--ub-text-label); overflow-wrap: anywhere; }

/* ---- The hub's validation-form control ---- */

/* Warm, unlike its neighbour, but only one surface step away from white. The
   control should read as a quiet action strip rather than a highlighted alert. */
.validation-issue {
  padding: 0.95rem 1.25rem;
  background: var(--ub-surface-2);
  border: 1px solid var(--ub-border);
  border-radius: var(--ub-radius);
}
/* The tint costs the accent its contrast, and 3b already answers that: its
   label on this card is muted ink, while the gold eyebrow is only ever worn by
   cards on plain surface. `--ub-accent` on this ground is 3.18:1 and axe refuses
   it; ink-soft is 6.2:1 and the hover accent 4.8:1, so neither needs an
   ACCEPTED_CONTRAST entry. */
.validation-issue .hub-card__title { color: var(--ub-ink-soft); }
.validation-issue a { color: var(--ub-accent-hover); }
.validation-issue .hub-card__title { margin-bottom: 0.45rem; }
.validation-issue .field { display: block; margin-bottom: 0; }
.validation-issue .field__control { width: 100%; }
/* Field and submit on one line, tops aligned so the button sits level with the
   input and the caption hangs below it — design 3b's arrangement. */
.validation-issue__row { display: flex; flex-wrap: wrap; align-items: stretch; gap: 0.6rem; }
.validation-issue__field { flex: 1 1 12rem; min-width: 0; }
.validation-issue__submit {
  flex: 0 0 auto;
  padding: 0.62rem 1.15rem;
  font-size: var(--ub-text-small);
}
/* Caption and the Slack box share one line under the row: what the control
   costs on the left, the one option it takes on the right. */
.validation-issue__caption {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem 1rem;
  margin-top: 0.5rem;
}
.validation-issue__hint {
  margin: 0;
  font-size: var(--ub-text-label);
  line-height: 1.35;
  color: var(--ub-ink-faint);
}
.validation-issue__caption .validation-issue__notify { margin-left: auto; }
/* Scoped to this control, not the shared picker: here the results sit beside a
   submit button, and an in-flow list would push it down as somebody types. */
.validation-issue__field .picker__results { position: absolute; left: 0; right: 0; z-index: 3; }
.validation-issue__url { font-family: var(--ub-font-mono); font-size: var(--ub-text-small); }
.validation-issue__actions { margin: 0 0 0.6rem; }
.validation-issue__done { margin: 0 0 0.6rem; font-weight: 600; }
.validation-issue__notice { margin: 0 0 0.4rem; font-size: var(--ub-text-small); color: var(--ub-ink-soft); }
.validation-issue__notice--warn { color: var(--ub-danger); }
.validation-issue__again,
.validation-issue__cancel { font-size: var(--ub-text-small); }
.validation-issue__cancel { margin-left: 0.75rem; }
.validation-issue__warn { margin: 0 0 0.5rem; font-weight: 600; }
.validation-issue__refusal {
  margin: 0 0 0.6rem;
  padding: 0.4rem 0.6rem;
  border-radius: var(--ub-radius-sm);
  background: color-mix(in srgb, var(--ub-danger) 10%, transparent);
  color: var(--ub-danger);
  font-size: var(--ub-text-small);
}
.validation-issue__live,
.validation-issue__outstanding {
  margin: 0 0 0.75rem;
  padding-left: 1.1rem;
  font-size: var(--ub-text-small);
  color: var(--ub-ink-soft);
}
.validation-issue__notify {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  margin: 0;
  font-size: var(--ub-text-label);
  color: var(--ub-ink-soft);
  white-space: nowrap;
  cursor: pointer;
}
.validation-issue__notify input {
  margin: 0;
  width: 0.8rem;
  height: 0.8rem;
  accent-color: var(--ub-accent);
}

@media (max-width: 760px) {
  .hub { padding: 1.5rem 1.25rem 2.5rem; }
  .hub__name { font-size: 2rem; }
  .people { padding: 1.5rem 1.25rem 2.5rem; }
  .people__title { font-size: 2rem; }
  .validation-issue__submit { width: 100%; align-items: flex-start; }
  .profilecard--empty { display: block; }
  .profilecard--empty .profilecard__row { display: flex; }
  .profilecard--empty .profilecard__what { padding-right: 0; }
  .profilecard--empty .profilecard__form,
  .profilecard--empty .profilecard__how--create { margin-top: 0.7rem; }
}

/* ---------------------------------------------------------------------------
   The interviewer's validation form (skills/validation_form.html)

   A standalone page: the person answering followed a link and is not navigating
   the app, so there is no shell to inherit a ground from. `.login` sets the
   precedent for what that means here — a local palette, pinned, so the surface
   does not flip to the dark hero look under system dark mode while every screen
   this form reports into stays paper.
   --------------------------------------------------------------------------- */

/* On the body rather than on `.vform`: the page's own header sits outside that
   element, and `color-scheme` is what decides how the browser paints a native
   select, checkbox and date picker. Left to the system it renders dark controls
   on a paper card, which is what this form looked like before. */
.page-validation {
  color-scheme: light;
  background: #f6f5f2;

  /* The app's light tokens, fixed. Shared components — `.field__control`,
     `.button`, the focus ring — are then correct inside this page without
     knowing anything about it. */
  --ub-bg: #faf9f7;
  --ub-bg-deep: #f2eee6;
  --ub-surface: #ffffff;
  --ub-surface-2: #f6f2ea;
  --ub-border: #e5e1d8;
  --ub-border-strong: #d8d2c4;
  --ub-ink: #18122b;
  --ub-ink-soft: #5b5768;
  --ub-ink-faint: #8a8598;
  --ub-accent: #9a7d23;
  --ub-accent-hover: #806619;
  --ub-accent-ink: #ffffff;
}

.vform {
  width: 100%;
  max-width: 60rem;
  margin: 0 auto;
  padding: clamp(1.75rem, 3vw, 2.75rem) clamp(1.25rem, 3vw, 2.5rem) 8rem;
  color: var(--ub-ink);
}

.vform__head {
  margin-bottom: 1.5rem;
}
.vform__eyebrow {
  margin: 0;
  font-family: var(--ub-font-mono);
  font-size: var(--ub-text-label);
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--ub-accent);
}
.vform__title {
  margin: 0.4rem 0 0;
  font-family: var(--ub-font-display);
  font-size: clamp(1.9rem, 1.2rem + 1.8vw, 2.5rem);
  line-height: 1.06;
  letter-spacing: -0.02em;
}
.vform__lede {
  margin: 0.5rem 0 0;
  max-width: 46rem;
  color: var(--ub-ink-soft);
  font-size: var(--ub-text-small);
}

/* ---- Step one: choosing the skills ---- */

/* The filter and the running count on one line: what narrows the list on the
   left, what the form will post on the right. */
.vform__tools {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.6rem 1rem;
  margin-bottom: 1rem;
}
.vform__search {
  flex: 1 1 16rem;
  min-width: 0;
}
.vform__tally {
  margin: 0;
  font-family: var(--ub-font-mono);
  font-size: var(--ub-text-label);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ub-ink-faint);
}

.vform__skills {
  margin: 0;
  padding: 0;
  border: 0;
}
.vform__group + .vform__group {
  margin-top: 1.5rem;
}
/* The category name is a label on the set below it, not a headline, so it is set
   in the face this system labels things in — and the rule under it is what makes
   two hundred chips read as a dozen groups. */
.vform__grouptitle {
  margin: 0 0 0.6rem;
  padding-bottom: 0.35rem;
  border-bottom: 1px solid var(--ub-border);
  font-family: var(--ub-font-mono);
  font-size: var(--ub-text-label);
  font-weight: 500;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--ub-ink-soft);
}
.vform__options {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
}

/* A checkbox and its name, worn as one chip. The native box stays in the markup
   and is what the keyboard drives; `:has()` tints the chip around it, so the
   whole pill reads as chosen rather than a 13px tick doing all the work. */
.vform__option {
  display: inline-flex;
  align-items: center;
  gap: 0.45rem;
  padding: 0.35rem 0.8rem 0.35rem 0.6rem;
  border: 1px solid var(--ub-border-strong);
  border-radius: var(--ub-radius-pill);
  background: var(--ub-surface);
  color: var(--ub-ink-soft);
  font-size: var(--ub-text-small);
  cursor: pointer;
}
.vform__option:hover {
  border-color: var(--ub-accent);
  color: var(--ub-ink);
}
.vform__option input {
  margin: 0;
  width: 0.85rem;
  height: 0.85rem;
  accent-color: var(--ub-accent);
  cursor: pointer;
}
.vform__option:has(input:checked) {
  border-color: var(--ub-accent);
  background: var(--ub-accent-quiet);
  color: var(--ub-ink);
  font-weight: 600;
}
.vform__option:has(input:focus-visible) {
  outline: none;
  box-shadow: var(--ub-ring);
}

.vform__nomatch,
.vform__empty {
  margin: 1rem 0 0;
  color: var(--ub-ink-faint);
  font-size: var(--ub-text-small);
}

/* ---- Step two: one card per skill ---- */

.vform__card {
  margin-bottom: 0.9rem;
  padding: 1.15rem 1.25rem 0.5rem;
  background: var(--ub-surface);
  border: 1px solid var(--ub-border);
  border-radius: var(--ub-radius);
}
.vform__cardtitle {
  margin: 0 0 1rem;
  font-family: var(--ub-font-mono);
  font-size: var(--ub-text-label);
  font-weight: 500;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--ub-ink-soft);
}
/* The skill names the block, so it is set as the subject it is rather than as
   another field label. */
.vform__cardtitle--skill {
  font-family: var(--ub-font-display);
  font-size: var(--ub-text-h3);
  font-weight: 700;
  letter-spacing: normal;
  text-transform: none;
  color: var(--ub-ink);
}

.vform__grid {
  display: grid;
  gap: 0 1rem;
  grid-template-columns: 1fr;
}
@media (min-width: 720px) {
  .vform__grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  /* Two questions on a row, and the date and duration are a pair rather than a
     row that happens to hold two. */
  .vform__grid--pair { grid-template-columns: minmax(0, 14rem) minmax(0, 10rem); }
}
.vform__notes .vform__field {
  max-width: 46rem;
}
.vform__field {
  margin-bottom: 1rem;
}
.vform__field textarea {
  resize: vertical;
}
.vform__help {
  margin: 0.35rem 0 0;
  color: var(--ub-ink-faint);
  font-size: var(--ub-text-label);
}
.vform__error {
  margin: 0.35rem 0 0;
  color: var(--ub-danger);
  font-size: var(--ub-text-label);
  font-weight: 600;
}
.vform__field--wrong .field__control {
  border-color: var(--ub-danger);
}

.vform__refusal {
  margin: 0 0 1rem;
  padding: 0.6rem 0.85rem;
  border-radius: var(--ub-radius-sm);
  background: color-mix(in srgb, var(--ub-danger) 10%, transparent);
  color: var(--ub-danger);
  font-size: var(--ub-text-small);
  font-weight: 600;
}
.vform__errors {
  margin: 0 0 1rem;
  padding-left: 1.1rem;
  color: var(--ub-danger);
  font-size: var(--ub-text-small);
}

/* ---- The one action, always reachable ---- */

/* Both steps are long enough to scroll past the button — two hundred chips on
   one, five questions per chosen skill on the other. It rides the bottom of the
   viewport instead, so the answer to "what do I do now" never scrolls away.

   A bar rather than a fade: the cards it floats over are white, and any gradient
   off the page ground leaves their text showing through it. */
.vform__foot {
  position: sticky;
  bottom: 1rem;
  z-index: 2;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem 1rem;
  margin-top: 1.25rem;
  padding: 0.8rem 1rem;
  background: var(--ub-surface);
  border: 1px solid var(--ub-border);
  border-radius: var(--ub-radius);
  box-shadow: var(--ub-shadow-lift);
}
.vform__footnote {
  margin: 0;
  color: var(--ub-ink-faint);
  font-size: var(--ub-text-label);
}

@media (prefers-reduced-motion: reduce) {
  .vform__option { transition: none; }
}
/* A recruiting region with its own mutation in flight: dim, and stop taking a
   second press. Rule 4 asks for both halves — the control is already out of
   action through hx-disabled-elt, and this is what makes that visible for the
   region the answer will replace. */
#rc-board.htmx-request,
#rc-process.htmx-request,
#rc-notes.htmx-request,
#rc-outcome.htmx-request,
#rc-scorecards.htmx-request {
  opacity: 0.55;
  pointer-events: none;
}

/* The scorecard region is focused after it is swapped in (rule 6), so the move
   is visible to anyone watching rather than only to a screen reader. */
#rc-scorecards:focus-visible {
  outline: 2px solid var(--ub-primary);
  outline-offset: 6px;
  border-radius: var(--ub-radius-sm);
}

/* --- Recruiting -----------------------------------------------------------
   Colour-by-stage-kind is the one visual idea taken from OpenATS's pipeline
   board (screening amber, interview blue, offer green). The Kanban drag is
   not: a <select> plus a button is testable, keyboard-reachable, and works at
   390 px, which a drag is none of without real work. */

/* Stage-kind ink. The tints below still come from the level swatches, but the
   *text* may not: `--ub-lvl-3` and `--ub-lvl-5` are swatch colours, sized to
   read as a solid band (`.skillcat__band--3`), and a column head used one
   token for both its 16% tint and its own label. A colour against 16% of
   itself is legible only if the colour is already dark, and these are not —
   offer green measured 2.38:1 and interview blue 2.81:1, the two worst pairs
   in the application, with screening amber at 4.00:1 behind them.

   These are those three hues at the same hue and saturation, darkened until
   each clears 4.5:1 on the tint it actually sits on, with margin: 4.62, 4.60
   and 4.63:1. The tints are untouched, so the kinds separate exactly as
   before, and the stage name is text in the head either way — the colour was
   never the only thing saying which column this is.

   Literal, not derived from `--ub-lvl-*`, for a second reason: those tokens
   flip in a dark-mode browser and the product shell is committed-light, so a
   head reading them was taking a dark-mode blue onto a light tint. */
:root {
  --rc-ink-screening: #745d17;
  --rc-ink-interview: #2359c7;
  --rc-ink-offer: #2a6b4e;
}

.rc-notice {
  margin-bottom: 1rem;
}

.rc-source {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  flex-wrap: wrap;
  margin-bottom: 1.25rem;
}

.rc-source__lede {
  font-size: 0.85rem;
  color: var(--ub-ink-soft);
}

/* The two configuration screens for one role. Ordinary links dressed as tabs,
   so the strip needs no state of its own and no island to keep in sync. */
.rc-tabs {
  display: flex;
  gap: 0.4rem;
  flex-wrap: wrap;
  margin-bottom: 1.25rem;
  border-bottom: 1px solid var(--ub-border);
  padding-bottom: 0.6rem;
}

.rc-tabs__tab {
  padding: 0.4rem 0.8rem;
  border-radius: 0.45rem;
  font-size: 0.9rem;
  font-weight: 600;
  color: var(--ub-ink-soft);
  text-decoration: none;
  border: 1px solid transparent;
}

.rc-tabs__tab:hover {
  color: var(--ub-ink);
  border-color: var(--ub-border);
}

.rc-tabs__tab--current {
  color: var(--ub-ink);
  background: var(--ub-surface-2);
  border-color: var(--ub-border);
}

.rc-team {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
}

.rc-team__row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  flex-wrap: wrap;
  padding: 0.55rem 0.7rem;
  border: 1px solid var(--ub-border);
  border-radius: 0.5rem;
  background: var(--ub-surface);
}

.rc-team__who {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  flex-wrap: wrap;
  min-width: 0;
}

.rc-team__name {
  font-weight: 600;
  color: var(--ub-ink);
}

/* The team panel dims under its own mutation, the way every other recruiting
   region does — rule 4 asks for the press to be visibly out of action. */
#rc-team.htmx-request {
  opacity: 0.55;
  pointer-events: none;
}

.rc-interviews {
  list-style: none;
  margin: 0 0 1rem;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}

.rc-interview {
  padding: 0.7rem 0.8rem;
  border: 1px solid var(--ub-border);
  border-radius: 0.5rem;
  background: var(--ub-surface);
}

.rc-interview__head {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  flex-wrap: wrap;
}

.rc-interview__title {
  font-weight: 600;
  color: var(--ub-ink);
}

.rc-interview__when {
  font-variant-numeric: tabular-nums;
  color: var(--ub-ink-soft);
  font-size: 0.9rem;
}

.rc-interview__stage {
  font-size: 0.8rem;
  color: var(--ub-ink-faint);
}

.rc-interview__note,
.rc-interview__feedback {
  margin: 0.4rem 0 0;
  font-size: 0.88rem;
  color: var(--ub-ink-soft);
}

.rc-interview__outcome,
.rc-interview__new {
  margin-top: 0.6rem;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr));
  gap: 0.6rem;
  align-items: end;
}

.rc-interview__outcome button,
.rc-interview__new button {
  justify-self: start;
}

#rc-interviews.htmx-request {
  opacity: 0.55;
  pointer-events: none;
}

.rc-interview__panel {
  margin: 0.4rem 0 0;
  display: flex;
  gap: 0.35rem;
  flex-wrap: wrap;
  align-items: center;
}

.rc-interview__panelform {
  margin-top: 0.6rem;
}

.rc-recognised {
  margin-bottom: 1.25rem;
  padding: 0.8rem 0.9rem;
  border: 1px solid var(--ub-border-strong);
  border-left: 3px solid var(--ub-accent);
  border-radius: 0.5rem;
  background: var(--ub-surface);
}

.rc-recognised__lede {
  margin: 0;
  display: flex;
  gap: 0.5rem;
  flex-wrap: wrap;
  align-items: baseline;
}

.rc-recognised__list {
  margin: 0.4rem 0 0;
  padding-left: 1.1rem;
  font-size: 0.9rem;
  color: var(--ub-ink-soft);
}

.rc-recognised__hidden {
  margin: 0.4rem 0 0;
  font-size: 0.9rem;
  color: var(--ub-ink-soft);
}

.rc-note--pinned {
  border-left: 3px solid var(--ub-accent);
  padding-left: 0.6rem;
}

.rc-choice {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  margin-right: 0.9rem;
  font-size: 0.9rem;
}

.rc-search {
  display: flex;
  align-items: flex-end;
  gap: 0.6rem;
  flex-wrap: wrap;
  margin-bottom: 1.25rem;
}

.rc-search__label {
  flex: 0 0 100%;
  font-size: 0.8rem;
  color: var(--ub-ink-soft);
}

.rc-search__input {
  flex: 1 1 18rem;
  min-width: 0;
  padding: 0.45rem 0.55rem;
  border: 1px solid var(--ub-border-strong);
  border-radius: 0.4rem;
  background: var(--ub-surface);
  color: var(--ub-ink);
}

.rc-candidates {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}

.rc-candidate {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 1rem;
  flex-wrap: wrap;
  padding: 0.75rem 0.9rem;
  border: 1px solid var(--ub-border);
  border-radius: 0.6rem;
  background: var(--ub-surface);
}

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

.rc-candidate__name {
  font-weight: 600;
  color: var(--ub-ink);
}

.rc-candidate__email {
  font-size: 0.85rem;
  color: var(--ub-ink-soft);
}

/* The roles a candidate is on, right-aligned so a person on three reads as one
   row with three lines rather than three rows. */
.rc-candidate__roles {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 0.25rem;
  font-size: 0.9rem;
}

.rc-candidate__role {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
}


@media (max-width: 620px) {
  .rc-candidate__roles {
    align-items: flex-start;
  }
}

.rc-add {
  background: var(--ub-surface);
  border: 1px solid var(--ub-border);
  border-radius: 0.75rem;
  padding: 1rem;
  margin-bottom: 1.25rem;
}

.rc-add__title {
  margin: 0 0 0.75rem;
  font-weight: 600;
  color: var(--ub-ink);
}

.rc-add__fields {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(12rem, 1fr));
  gap: 0.75rem;
}

.rc-add__actions {
  margin-top: 0.75rem;
}

.rc-field {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
}

.rc-field__label {
  font-size: 0.8rem;
  color: var(--ub-ink-soft);
}

.rc-field input,
.rc-field select {
  padding: 0.45rem 0.55rem;
  border: 1px solid var(--ub-border-strong);
  border-radius: 0.4rem;
  background: var(--ub-surface);
  color: var(--ub-ink);
  min-width: 0;
}

/* Horizontal only above the phone breakpoint, and with a visible scrollbar
   when it is: docs/STATUS.md records the worklists table failing at 390 px by
   hiding its controls inside an unsignalled overflow-x container, and a
   horizontal board is the same trap. */
.rc-board {
  display: flex;
  gap: 0.75rem;
  overflow-x: auto;
  scrollbar-color: var(--ub-border-strong) transparent;
  padding-bottom: 0.5rem;
  align-items: flex-start;
}

.rc-col {
  /* The supplied design's width. A five-stage board still fits 1440px beside
     the app sidebar; a sixth stage scrolls, and the scrollbar stays visible. */
  flex: 0 0 16.5rem;
  background: var(--ub-bg-deep);
  border: 1px solid var(--ub-border);
  border-radius: 12px;
  padding: 0.6rem;
  min-width: 0;
}

/* Marked only while a card is held over it, by `pipeline-drag.js`. */
.rc-col--over {
  border-color: var(--ub-accent);
  background: color-mix(in srgb, var(--ub-accent) 8%, var(--ub-bg-deep));
}

.rc-col__head {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.4rem 0.5rem 0.6rem;
  font-weight: 700;
  font-size: 0.85rem;
}

/* The stage's kind, as a dot rather than a tinted band behind the name: the
   design carries the colour at 7px, which is enough to group the interview
   stages without three header colours competing with the cards under them. */
.rc-col__dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  flex: none;
  background: var(--ub-ink-faint);
}

.rc-col--screening .rc-col__dot { background: var(--ub-accent); }
.rc-col--interview .rc-col__dot { background: var(--ub-lvl-3); }
.rc-col--offer .rc-col__dot { background: var(--ub-lvl-5); }

.rc-col__count {
  font-family: var(--ub-font-mono);
  font-size: 0.7rem;
  color: var(--ub-ink-faint);
  margin-left: auto;
  font-variant-numeric: tabular-nums;
}

.rc-col__aged {
  margin: 0 0 0.5rem;
  display: flex;
  align-items: center;
  gap: 0.35rem;
  padding: 0.3rem 0.5rem;
  border-radius: 6px;
  background: color-mix(in srgb, var(--ub-danger) 10%, transparent);
  /* Darkened against its own tint rather than left at `--ub-danger`, which
     is 4.08:1 there and fails WCAG AA. 80% of the token on black is 5.65:1
     and reads as the same red. */
  color: color-mix(in srgb, var(--ub-danger) 80%, #000);
  font-size: 0.72rem;
  font-weight: 600;
}

.rc-dot {
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: currentColor;
  flex: none;
}

.rc-col__cards {
  list-style: none;
  margin: 0.6rem 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}

.rc-col__empty {
  color: var(--ub-ink-faint);
  font-size: 0.85rem;
  padding: 0.4rem 0.45rem;
}

.rc-card {
  background: var(--ub-surface);
  border: 1px solid var(--ub-border);
  border-radius: 9px;
  padding: 0.65rem 0.7rem;
  display: flex;
  flex-direction: column;
  gap: 0.45rem;
  align-items: stretch;
}

.rc-card[draggable="true"] { cursor: grab; }

.rc-card--dragging {
  opacity: 0.45;
  cursor: grabbing;
}

.rc-card--stalled { box-shadow: inset 3px 0 0 var(--ub-danger); }

.rc-card__who {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  min-width: 0;
}

.rc-card__avatar {
  width: 1.75rem;
  height: 1.75rem;
  border-radius: 50%;
  background: var(--ub-bg-deep);
  color: var(--ub-ink-soft);
  display: grid;
  place-items: center;
  font-size: 0.68rem;
  font-weight: 700;
  flex: none;
}

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

.rc-card__name {
  font-size: 0.88rem;
  font-weight: 600;
  color: var(--ub-ink);
}

/* Still the meta line on the notes, scorecard and candidate-record cards; the
   board's own card stopped using it when it took the design's two-line head. */
.rc-card__meta {
  font-size: 0.8rem;
  color: var(--ub-ink-soft);
  overflow-wrap: anywhere;
}

.rc-card__email {
  font-size: 0.74rem;
  color: var(--ub-ink-faint);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

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

.rc-card__source {
  font-family: var(--ub-font-mono);
  font-size: 0.62rem;
  letter-spacing: 0.06em;
  padding: 0.12rem 0.4rem;
  border-radius: 4px;
  background: var(--ub-bg);
  border: 1px solid var(--ub-border);
  color: var(--ub-ink-faint);
}

.rc-card__age {
  font-size: 0.74rem;
  color: var(--ub-ink-soft);
  margin-left: auto;
}

.rc-card__stalled {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  font-size: 0.72rem;
  color: var(--ub-danger);
}

/* Inside the card, never off to the right of the board.

   The supplied design's card carries no visible move control — dragging is the
   whole mechanism there. This one stays, at full contrast, because it is the
   only way to move a candidate without a pointer. Dimming it to 0.35 opacity
   was tried and is what `tests/e2e/harness/axe.py` refuses: the select rendered
   #afacb7 on white, about 2.2:1, and AA contrast is not a matter of taste.
   Hiding it until hover fails worse, because an element that is not rendered
   cannot be tabbed to at all. */
.rc-card__move {
  display: flex;
  gap: 0.4rem;
  align-items: center;
  width: 100%;
  margin-top: 0.2rem;
}

.rc-card__movelabel {
  flex: 1 1 auto;
  min-width: 0;
}

.rc-card__move select {
  width: 100%;
  padding: 0.3rem 0.4rem;
  border: 1px solid var(--ub-border-strong);
  border-radius: 0.35rem;
  background: var(--ub-surface);
  color: var(--ub-ink);
}

.rc-panel {
  background: var(--ub-surface);
  border: 1px solid var(--ub-border);
  border-radius: 0.75rem;
  padding: 1rem;
  margin-bottom: 1.25rem;
}

.rc-panel__title {
  margin: 0 0 0.75rem;
  font-size: 1rem;
  color: var(--ub-ink);
}

.rc-timeline,
.rc-applications {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
}

.rc-timeline__entry,
.rc-application {
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
  border-left: 2px solid var(--ub-border-strong);
  padding-left: 0.7rem;
}

.rc-timeline__move {
  color: var(--ub-ink);
}

.rc-timeline__meta {
  font-size: 0.8rem;
  color: var(--ub-ink-soft);
}

/* Stack rather than scroll below the desktop width. Five 16rem columns do not
   fit 1024 px beside the app sidebar, and a board that scrolls horizontally
   puts each card's Move button outside the viewport — the same defect
   docs/STATUS.md already records for the worklists table. The E2E visual check
   at 1024x768 is what holds this; it read "at 1024x768 and 390x844" until
   2026-09-20, when the second width went. */
@media (max-width: 1100px) {
  .rc-board {
    flex-direction: column;
    overflow-x: visible;
  }

  .rc-col {
    flex: 1 1 auto;
    width: 100%;
  }
}

.rc-panel__lede {
  margin: -0.4rem 0 0.9rem;
  color: var(--ub-ink-soft);
  font-size: 0.85rem;
}

.rc-criterion {
  border: 0;
  border-top: 1px solid var(--ub-border);
  margin: 0;
  padding: 0.8rem 0 0;
}

.rc-criterion__prompt {
  padding: 0;
  font-weight: 600;
  color: var(--ub-ink);
}

.rc-scale,
.rc-recommendations {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin: 0.5rem 0;
}

.rc-scale__option {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  padding: 0.3rem 0.6rem;
  border: 1px solid var(--ub-border-strong);
  border-radius: 0.4rem;
  background: var(--ub-surface);
  cursor: pointer;
}

.rc-criterion textarea {
  padding: 0.45rem 0.55rem;
  border: 1px solid var(--ub-border-strong);
  border-radius: 0.4rem;
  background: var(--ub-surface);
  color: var(--ub-ink);
  width: 100%;
}

.rc-evaluation {
  border-top: 1px solid var(--ub-border);
  padding-top: 0.8rem;
  margin-top: 0.8rem;
}

.rc-evaluation:first-of-type {
  border-top: 0;
  padding-top: 0;
  margin-top: 0;
}

.rc-evaluation__head {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 0.5rem;
  flex-wrap: wrap;
}

.rc-evaluation__who {
  font-weight: 600;
  color: var(--ub-ink);
}

.rc-evaluation__ratings {
  list-style: none;
  margin: 0.5rem 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
}

.rc-evaluation__ratings li {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
  align-items: baseline;
}

.rc-evaluation__prompt {
  flex: 1 1 14rem;
  color: var(--ub-ink-soft);
  font-size: 0.85rem;
}

.rc-evaluation__value {
  font-variant-numeric: tabular-nums;
  font-weight: 600;
}

/* Reuse the existing badge tones rather than inventing a palette. */
.badge--strong_hire,
.badge--hire {
  background: color-mix(in srgb, var(--ub-lvl-5) 16%, transparent);
  color: var(--rc-ink-offer);
}

.badge--no_hire,
.badge--strong_no_hire {
  background: color-mix(in srgb, var(--ub-danger) 14%, transparent);
  color: var(--ub-danger);
}

.rc-outside {
  margin: 0 0 0.9rem;
  padding: 0.5rem 0.65rem;
  border-left: 3px solid var(--ub-accent);
  background: var(--ub-accent-quiet);
  color: var(--ub-ink-soft);
  font-size: 0.85rem;
}

.rc-panel--restricted {
  border-color: var(--ub-border-strong);
  background: var(--ub-surface-2);
}

.rc-notes {
  list-style: none;
  margin: 0 0 0.9rem;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
}

.rc-note {
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
  border-left: 2px solid var(--ub-border-strong);
  padding-left: 0.7rem;
}

.rc-note__body {
  color: var(--ub-ink);
  overflow-wrap: anywhere;
}

.rc-note-form {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  align-items: flex-start;
}

.rc-note-form textarea {
  padding: 0.45rem 0.55rem;
  border: 1px solid var(--ub-border-strong);
  border-radius: 0.4rem;
  background: var(--ub-surface);
  color: var(--ub-ink);
  width: 100%;
}

/* --- Recruiting: the hiring-process editor -------------------------------- */

.rc-stages {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
  counter-reset: none;
}

.rc-stage {
  border: 1px solid var(--ub-border);
  border-radius: 0.5rem;
  background: var(--ub-surface);
  border-left-width: 3px;
}

.rc-stage--screening {
  border-left-color: var(--ub-accent);
}

.rc-stage--interview {
  border-left-color: var(--ub-lvl-3);
}

.rc-stage--offer {
  border-left-color: var(--ub-lvl-5);
}

/* Wraps rather than scrolls: the row carries seven controls and the narrow
   viewport must not put any of them outside it. */
.rc-stage__row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem;
  padding: 0.6rem 0.7rem;
}

.rc-stage__position {
  font-variant-numeric: tabular-nums;
  color: var(--ub-ink-faint);
  min-width: 1.2rem;
}

/* Each "name" input gets its own class rather than borrowing a neighbour's.
   `.rc-stage__name` means "an existing stage's name" and nothing else; a
   locator or a style that cannot tell two of these apart reaches the wrong
   input, which has cost three test failures on this screen alone. */
.rc-newscorecard__name,
.rc-newstage__name,
.rc-stage__name {
  flex: 1 1 12rem;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
}

.rc-newstage__kind,
.rc-stage__scorecard {
  flex: 0 1 14rem;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
}

.rc-newscorecard__name input,
.rc-newstage__name input,
.rc-newstage__kind select,
.rc-stage__name input,
.rc-stage__scorecard select {
  width: 100%;
  padding: 0.35rem 0.45rem;
  border: 1px solid var(--ub-border-strong);
  border-radius: 0.35rem;
  background: var(--ub-surface);
  color: var(--ub-ink);
}

.rc-stage__held {
  font-size: 0.8rem;
  color: var(--ub-ink-faint);
  white-space: nowrap;
}

.rc-stage__row .button[disabled] {
  opacity: 0.4;
  cursor: not-allowed;
}

/* Django renders RadioSelect as a <ul>; the recruiting scale and recommendation
   rows lay it out inline rather than as a bulleted list. */
.rc-scale ul,
.rc-recommendations ul {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
}

.rc-scale li label,
.rc-recommendations li label {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  padding: 0.3rem 0.6rem;
  border: 1px solid var(--ub-border-strong);
  border-radius: 0.4rem;
  background: var(--ub-surface);
  cursor: pointer;
}

.rc-scorecard {
  border-top: 1px solid var(--ub-border);
  padding-top: 0.8rem;
  margin-top: 0.8rem;
}

.rc-scorecard:first-of-type {
  border-top: 0;
  padding-top: 0;
  margin-top: 0;
}

.rc-scorecard__name {
  margin: 0 0 0.5rem;
  font-size: 0.95rem;
  color: var(--ub-ink);
}

.rc-questions {
  list-style: none;
  margin: 0 0 0.5rem;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
}

.rc-question__row {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: 0.5rem;
}

.rc-question__row--new {
  border-top: 1px solid var(--ub-border);
  padding-top: 0.8rem;
  margin-top: 0.8rem;
}

.rc-question__prompt {
  flex: 1 1 16rem;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
}

.rc-question__prompt input {
  width: 100%;
  padding: 0.35rem 0.45rem;
  border: 1px solid var(--ub-border-strong);
  border-radius: 0.35rem;
  background: var(--ub-surface);
  color: var(--ub-ink);
}

.rc-panel--closed {
  background: var(--ub-surface-2);
  border-left: 3px solid var(--ub-ink-faint);
}

.badge--rejected,
.badge--withdrawn {
  background: color-mix(in srgb, var(--ub-ink-faint) 18%, transparent);
  color: var(--ub-ink-soft);
}

.badge--hired {
  background: color-mix(in srgb, var(--ub-lvl-5) 18%, transparent);
  color: var(--rc-ink-offer);
}

.rc-outcome {
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
}

.rc-outcome textarea {
  width: 100%;
  padding: 0.45rem 0.55rem;
  border: 1px solid var(--ub-border-strong);
  border-radius: 0.4rem;
  background: var(--ub-surface);
  color: var(--ub-ink);
}

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

/* ---------------------------------------------------------------------------
   RECRUITING SECTION — the supplied redesign
   ---------------------------------------------------------------------------
   The design ships its own neutral ramp, a few percent off the app tokens, and
   `.page-validation` above sets the precedent for what that means here: pin the
   values locally so shared components are correct inside without knowing about
   it. Scoped to the section's own content, so the shell chrome outside keeps
   the `--hs-*` palette and no other tool shifts.

   The accent (#9a7d23) and the danger red (#c0392f) are already the app's, so
   only the neutrals are restated.
   --------------------------------------------------------------------------- */
.rc-shell {
  --ub-bg: #f6f5f2;
  --ub-bg-deep: #f2eee6;
  --ub-surface: #ffffff;
  --ub-border: #eae7e1;
  --ub-border-strong: #ddd8cf;
  --ub-ink: #1a1330;
  --ub-ink-soft: #57536a;
  --ub-ink-faint: #6d6880;

  color-scheme: light;
  accent-color: var(--ub-accent);
  color: var(--ub-ink);
  background: var(--ub-bg);
  min-height: 100%;
}

/* The section bar. Sticky under the 56px topbar, which is what makes it the
   answer to "everything is hidden": it stays on screen inside a job as well. */
.rc-bar {
  position: sticky;
  top: 56px;
  z-index: 5;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  column-gap: 1.25rem;
  padding: 0 clamp(1.25rem, 3vw, 2.75rem);
  background: var(--ub-surface);
  border-bottom: 1px solid var(--ub-border);
}

.rc-bar__tab {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0.85rem 0.5rem;
  font-size: 0.86rem;
  font-weight: 600;
  color: var(--ub-ink-soft);
  text-decoration: none;
  white-space: nowrap;
}

.rc-bar__tab:hover { color: var(--ub-ink); }

.rc-bar__tab--on {
  color: var(--ub-ink);
  box-shadow: inset 0 -2px 0 var(--ub-accent);
}

.rc-bar__n {
  font-family: var(--ub-font-mono);
  font-size: 0.66rem;
  padding: 0.1rem 0.35rem;
  border-radius: 4px;
  background: var(--ub-bg-deep);
  color: var(--ub-ink-faint);
}

/* The only badge that means somebody is waiting on this reader personally. */
.rc-bar__n--due {
  background: color-mix(in srgb, var(--ub-danger) 12%, transparent);
  color: var(--ub-danger);
}

.rc-bar__action {
  margin-left: auto;
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  align-self: center;
  flex: 0 0 auto;
  white-space: nowrap;
  font-size: 0.82rem;
}

.rc-body { background: var(--ub-bg); }

.rc-page {
  padding: clamp(1.5rem, 3vw, 2.2rem) clamp(1.25rem, 3vw, 2.75rem) 0;
  max-width: 82rem;
  margin-inline: auto;
}

.rc-hello { margin-bottom: 1.6rem; }

.rc-hello__title {
  margin: 0;
  font-family: var(--ub-font-display);
  font-weight: 700;
  font-size: 2.3rem;
  line-height: 1.1;
  letter-spacing: -0.02em;
}

.rc-hello__lede {
  margin: 0.6rem 0 0;
  max-width: 44rem;
  color: var(--ub-ink-soft);
  line-height: 1.6;
}

.rc-tiles {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(13rem, 1fr));
  gap: 0.875rem;
  margin-bottom: 1.8rem;
}

.rc-tile {
  background: var(--ub-surface);
  border: 1px solid var(--ub-border);
  border-radius: 12px;
  padding: 1rem 1.15rem;
}

.rc-tile--due { box-shadow: inset 3px 0 0 var(--ub-danger); }
.rc-tile--due .rc-tile__n { color: var(--ub-danger); }

.rc-tile__label {
  margin: 0 0 0.5rem;
  font-family: var(--ub-font-mono);
  font-size: 0.62rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--ub-ink-faint);
}

.rc-tile__n {
  font-family: var(--ub-font-display);
  font-weight: 700;
  font-size: 1.9rem;
  line-height: 1;
}

.rc-tile__foot {
  margin: 0.35rem 0 0;
  font-size: 0.8rem;
  color: var(--ub-ink-soft);
}

.rc-duo {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(24rem, 1fr));
  gap: 1rem;
  margin-bottom: 1.8rem;
}

.rc-duo > .rc-panel { margin-bottom: 0; }

.rc-panel__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  margin-bottom: 0.9rem;
}

.rc-panel__more { font-size: 0.82rem; font-weight: 600; }

.rc-rows {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.55rem;
}

.rc-rows--day { margin-bottom: 1.25rem; }

.rc-row {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  padding: 0.65rem 0.75rem;
  border: 1px solid var(--ub-border);
  border-radius: 9px;
  background: var(--ub-surface);
}

.rc-avatar {
  width: 2rem;
  height: 2rem;
  border-radius: 50%;
  background: var(--ub-bg-deep);
  color: var(--ub-ink-soft);
  display: grid;
  place-items: center;
  font-size: 0.75rem;
  font-weight: 700;
  flex: none;
}

.rc-when {
  display: flex;
  flex-direction: column;
  align-items: center;
  width: 2.6rem;
  flex: none;
  line-height: 1.1;
}

.rc-when__dow {
  font-family: var(--ub-font-mono);
  font-size: 0.6rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ub-ink-faint);
}

.rc-when__day { font-family: var(--ub-font-display); font-size: 1.15rem; font-weight: 700; }

.rc-time {
  font-family: var(--ub-font-mono);
  font-size: 0.78rem;
  color: var(--ub-ink-soft);
  width: 3rem;
  flex: none;
}

.rc-row__body { display: flex; flex-direction: column; min-width: 0; flex: 1; }

.rc-row__name {
  font-size: 0.9rem;
  font-weight: 600;
  color: var(--ub-ink);
  text-decoration: none;
}

a.rc-row__name:hover { text-decoration: underline; }

.rc-row__meta { font-size: 0.78rem; color: var(--ub-ink-soft); }

.rc-age {
  font-family: var(--ub-font-mono);
  font-size: 0.68rem;
  color: var(--ub-ink-faint);
  white-space: nowrap;
}

.rc-age--old { color: var(--ub-danger); }

.rc-row__act {
  padding: 0.35rem 0.7rem;
  border-radius: 7px;
  border: 1px solid var(--ub-border-strong);
  background: var(--ub-surface);
  color: var(--ub-ink);
  font-size: 0.8rem;
  font-weight: 600;
  text-decoration: none;
  white-space: nowrap;
}

.rc-row__act:hover { background: var(--ub-bg); }

.rc-day {
  margin: 0 0 0.6rem;
  font-family: var(--ub-font-mono);
  font-size: 0.66rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--ub-ink-faint);
}

.rc-jobs {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(21rem, 1fr));
  gap: 1rem;
}

.rc-job {
  display: flex;
  flex-direction: column;
  gap: 0.8rem;
  padding: 1.1rem 1.25rem;
  background: var(--ub-surface);
  border: 1px solid var(--ub-border);
  border-radius: 12px;
  color: inherit;
  text-decoration: none;
}

.rc-job:hover { border-color: var(--ub-border-strong); }

.rc-job__head { display: flex; align-items: flex-start; justify-content: space-between; gap: 0.75rem; }
.rc-job__id { display: flex; flex-direction: column; gap: 0.15rem; min-width: 0; }
.rc-job__title { font-family: var(--ub-font-display); font-size: 1.15rem; font-weight: 700; }
.rc-job__meta { font-size: 0.8rem; color: var(--ub-ink-soft); }

.rc-job__role {
  font-family: var(--ub-font-mono);
  font-size: 0.66rem;
  padding: 0.19rem 0.5rem;
  border-radius: 5px;
  background: #fdf9ef;
  border: 1px solid color-mix(in srgb, var(--ub-accent) 28%, transparent);
  color: #847332;
  white-space: nowrap;
}

/* One segment per stage, in order, coloured by that stage's kind — the same
   distinction the board's column dots carry, so the two pictures of one board
   agree. The design specifies the screening and interview tints; offer has no
   occupied stage in it, so that one is mixed from the level token the dot uses.
   A stage nobody is standing in is grey whatever its kind. */
.rc-strip { display: flex; gap: 3px; height: 6px; }
.rc-strip__seg { flex: 1; border-radius: 3px; }
.rc-strip__seg--screening { background: #d9c88c; }
.rc-strip__seg--interview { background: #9fb6e8; }
.rc-strip__seg--offer { background: color-mix(in srgb, var(--ub-lvl-5) 55%, #ffffff); }
.rc-strip__seg--empty { background: var(--ub-border); }

.rc-job__foot {
  display: flex;
  justify-content: space-between;
  font-size: 0.78rem;
  color: var(--ub-ink-soft);
}

@media (max-width: 640px) {
  /* The bar wraps rather than scrolling. A single scrolling row is shorter and
     is what the design shows, but it puts the last two tabs and the action past
     the right edge, and `tests/e2e/harness/visual.py` counts those as clipped —
     the same rule the card's move control already obeys. Height is the cheaper
     cost. */
  .rc-bar { column-gap: 0.75rem; }
  .rc-bar__action { margin-left: 0; }
  .rc-hello__title { font-size: 1.8rem; }
}

/* --- Candidate detail: header, rail, activity tabs ---------------------- */

.rc-cand__head {
  display: flex;
  align-items: center;
  gap: 0.9rem;
  margin-bottom: 1.4rem;
}

.rc-cand__avatar {
  width: 3rem;
  height: 3rem;
  border-radius: 50%;
  background: var(--ub-bg-deep);
  color: var(--ub-ink-soft);
  display: grid;
  place-items: center;
  font-size: 1rem;
  font-weight: 700;
  flex: none;
}

.rc-cand__id { min-width: 0; }

.rc-cand__crumb {
  margin: 0 0 0.3rem;
  font-size: 0.82rem;
  color: var(--ub-ink-soft);
}

.rc-cand {
  display: grid;
  grid-template-columns: minmax(16rem, 20rem) minmax(0, 1fr);
  gap: 1rem;
  align-items: start;
}

.rc-rail { min-width: 0; }

.rc-facts {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.45rem;
  font-size: 0.85rem;
}

.rc-facts li {
  display: flex;
  gap: 0.75rem;
  align-items: baseline;
}

.rc-facts__key {
  font-family: var(--ub-font-mono);
  font-size: 0.62rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ub-ink-faint);
  flex: 0 0 4.5rem;
}

.rc-facts__links {
  margin: 0.8rem 0 0;
  display: flex;
  gap: 0.75rem;
  font-size: 0.82rem;
  font-weight: 600;
}

.rc-activity { min-width: 0; }

.rc-activity__strip {
  display: flex;
  gap: 0.25rem;
  flex-wrap: wrap;
  border-bottom: 1px solid var(--ub-border);
  margin-bottom: 1rem;
}

.rc-atab {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0.6rem 0.6rem;
  border: 0;
  background: transparent;
  font: inherit;
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--ub-ink-soft);
  cursor: pointer;
}

.rc-atab:hover { color: var(--ub-ink); }

.rc-atab--on {
  color: var(--ub-ink);
  box-shadow: inset 0 -2px 0 var(--ub-accent);
}

/* Panels carry their own `.rc-panel` cards, so the wrapper adds nothing but a
   hiding boundary. */
.rc-activity__panel[hidden] { display: none; }

@media (max-width: 900px) {
  .rc-cand { grid-template-columns: minmax(0, 1fr); }
}

/* "Log an interview" on the interview log: a disclosure, because the list of
   candidates is long enough to bury the log it sits above. */
.rc-log {
  background: var(--ub-surface);
  border: 1px solid var(--ub-border);
  border-radius: 12px;
  padding: 0.9rem 1.1rem;
  margin-bottom: 1.4rem;
}

.rc-log__summary {
  font-family: var(--ub-font-display);
  font-size: 1.05rem;
  font-weight: 700;
  cursor: pointer;
}

.rc-log[open] .rc-log__summary { margin-bottom: 0.5rem; }

/* --- Email template library --------------------------------------------- */

.rc-lib {
  display: grid;
  grid-template-columns: minmax(16rem, 22rem) minmax(0, 1fr);
  gap: 1rem;
  align-items: start;
}

.rc-lib__new { margin-bottom: 0.75rem; display: inline-flex; }

.rc-row--on {
  border-color: var(--ub-accent);
  background: color-mix(in srgb, var(--ub-accent) 6%, var(--ub-surface));
}

.rc-lib__actions {
  display: flex;
  gap: 0.5rem;
  margin-top: 1rem;
}

.rc-fields {
  display: flex;
  flex-wrap: wrap;
  gap: 0.35rem;
  margin: 0 0 0.4rem;
}

.rc-fields__token {
  font-family: var(--ub-font-mono);
  font-size: 0.68rem;
  padding: 0.2rem 0.45rem;
  border: 1px solid var(--ub-border-strong);
  border-radius: 5px;
  background: var(--ub-bg);
  color: var(--ub-ink-soft);
  cursor: pointer;
}

.rc-fields__token:hover {
  border-color: var(--ub-accent);
  color: var(--ub-ink);
}

.rc-preview {
  border: 1px solid var(--ub-border);
  border-radius: 9px;
  padding: 0.9rem 1rem;
  background: var(--ub-bg);
  margin-bottom: 0.75rem;
}

.rc-preview__subject {
  margin: 0 0 0.6rem;
  font-weight: 700;
  font-size: 0.92rem;
}

.rc-preview__body {
  margin: 0;
  font-size: 0.88rem;
  line-height: 1.6;
  color: var(--ub-ink-soft);
  /* The live preview is rewritten with `textContent`, which carries newlines
     and no markup; without this they collapse and a message written in
     paragraphs previews as one run-on line the moment it is edited. */
  white-space: pre-wrap;
}

@media (max-width: 900px) {
  .rc-lib { grid-template-columns: minmax(0, 1fr); }
}

/* Moving a candidate from their own screen, beside their name. */
.rc-cand__act {
  margin-left: auto;
  display: flex;
  align-items: center;
  gap: 0.5rem;
  flex-wrap: wrap;
}

.rc-cand__actlabel { min-width: 0; }

.rc-cand__act select {
  padding: 0.4rem 0.5rem;
  border: 1px solid var(--ub-border-strong);
  border-radius: 7px;
  background: var(--ub-surface);
  color: var(--ub-ink);
  font: inherit;
  font-size: 0.85rem;
}

/* The rail's summary of the recruiter-only notes. The panel it summarises lives
   in the Notes tab and stays the place they are written. */
.rc-restricted__body {
  margin: 0 0 0.4rem;
  font-size: 0.85rem;
  line-height: 1.5;
}

.rc-restricted__who {
  margin: 0;
  font-family: var(--ub-font-mono);
  font-size: 0.66rem;
  color: var(--ub-ink-faint);
}

/* The restricted panel sits on `--ub-surface-2`, and the accent at its normal
   value is 3.4:1 there — under AA, which `tests/e2e/harness/axe.py` enforces.
   The hover value is 4.74:1 and is already a brand token, so nothing new is
   invented to clear it. */
.rc-panel--restricted a {
  color: var(--ub-accent-hover);
}

/* --- Jobs table, candidate filters, application chips ------------------- */

.rc-hello--split {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 1rem;
  flex-wrap: wrap;
}

/* Four columns on a grid rather than a `<table>`: each row is one link, and a
   link cannot wrap table cells. The head is `aria-hidden` because every cell
   below already reads as a sentence on its own. */
.rc-table {
  background: var(--ub-surface);
  border: 1px solid var(--ub-border);
  border-radius: 12px;
  overflow: hidden;
}

.rc-table__head,
.rc-table__row {
  display: grid;
  grid-template-columns: minmax(0, 2.2fr) minmax(0, 1.6fr) minmax(6rem, 0.7fr) 5rem;
  gap: 1rem;
  align-items: center;
  padding: 0.85rem 1.15rem;
}

.rc-table__head {
  font-family: var(--ub-font-mono);
  font-size: 0.62rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--ub-ink-faint);
  border-bottom: 1px solid var(--ub-border);
  padding-block: 0.6rem;
}

.rc-table__row {
  border-top: 1px solid var(--ub-border);
  color: inherit;
  text-decoration: none;
}

.rc-table__row:first-of-type { border-top: 0; }
.rc-table__row:hover { background: var(--ub-bg); }

.rc-table__job { display: flex; flex-direction: column; gap: 0.15rem; min-width: 0; }
.rc-table__pipeline { display: flex; flex-direction: column; gap: 0.35rem; min-width: 0; }

.rc-table__counts {
  font-family: var(--ub-font-mono);
  font-size: 0.7rem;
  color: var(--ub-ink-faint);
}

.rc-table__team { display: flex; gap: 0.25rem; flex-wrap: wrap; }

.rc-table__count {
  font-family: var(--ub-font-display);
  font-size: 1.15rem;
  font-weight: 700;
  text-align: right;
}

.rc-avatar--small {
  width: 1.6rem;
  height: 1.6rem;
  font-size: 0.6rem;
}

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

.rc-filters__search { flex: 1 1 22rem; min-width: 0; }
.rc-filters__search .rc-search__input { width: 100%; }
.rc-filters__pick { flex: 0 0 auto; }

.rc-filters select {
  padding: 0.45rem 0.55rem;
  border: 1px solid var(--ub-border-strong);
  border-radius: 8px;
  background: var(--ub-surface);
  color: var(--ub-ink);
  font: inherit;
  font-size: 0.85rem;
}

/* A row whose right-hand side is a stack of application chips. */
.rc-row--tall { align-items: flex-start; }
.rc-row--tall .rc-avatar,
.rc-row--tall .rc-row__body { margin-top: 0.15rem; }

.rc-chips {
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
  flex: 1 1 22rem;
  min-width: 0;
}

.rc-chip {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  padding: 0.35rem 0.6rem;
  border: 1px solid var(--ub-border);
  border-radius: 7px;
  background: var(--ub-surface);
  color: inherit;
  text-decoration: none;
  font-size: 0.82rem;
}

.rc-chip:hover { border-color: var(--ub-border-strong); }
.rc-chip__job { font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.rc-chip__stage {
  font-family: var(--ub-font-mono);
  font-size: 0.66rem;
  color: var(--ub-ink-faint);
  white-space: nowrap;
}

.rc-chip__stage--closed { color: var(--ub-danger); }

@media (max-width: 900px) {
  .rc-table__head { display: none; }
  .rc-table__row { grid-template-columns: minmax(0, 1fr); gap: 0.5rem; }
  .rc-table__count { text-align: left; }
}

/* Two links where a panel head offers both an act and a way to see everything. */
.rc-panel__links { display: flex; gap: 0.75rem; }

/* A hiring-team row: who, their rank, and the way off the team. */
.rc-team__rank { margin-left: auto; }

.rc-team__rank select {
  padding: 0.35rem 0.5rem;
  border: 1px solid var(--ub-border-strong);
  border-radius: 7px;
  background: var(--ub-surface);
  color: var(--ub-ink);
  font: inherit;
  font-size: 0.82rem;
}

/* --- The recruiting modals ---------------------------------------------- */

/* Native `<dialog>`, so the backdrop, focus trap and Escape come with it. The
   server can render one `open`, which is what makes these work with no
   JavaScript — then it is an ordinary block in the page rather than a modal,
   and the rules below still dress it. */
.rc-modal {
  width: min(38rem, calc(100vw - 2rem));
  max-height: calc(100dvh - 4rem);
  padding: 0;
  border: 1px solid var(--ub-border-strong);
  border-radius: 14px;
  background: var(--ub-surface);
  color: var(--ub-ink);
  overflow: hidden;
}

/* Head and foot keep their height, the body takes what is left and scrolls
   only when there is nothing left to take. Guarded on [open] because the UA
   sheet hides a closed dialog with `display: none`, and any author `display`
   here would override that and leave all six on the page. */
.rc-modal[open] {
  display: flex;
  flex-direction: column;
}

.rc-modal::backdrop { background: rgba(26, 19, 48, 0.45); }

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

.rc-modal__title {
  margin: 0;
  font-family: var(--ub-font-display);
  font-size: 1.15rem;
  font-weight: 700;
}

.rc-modal__x {
  margin-left: auto;
  display: grid;
  place-items: center;
  width: 2rem;
  height: 2rem;
  border: 0;
  border-radius: 7px;
  background: transparent;
  color: var(--ub-ink-soft);
  cursor: pointer;
}

.rc-modal__x:hover { background: var(--ub-bg); color: var(--ub-ink); }

.rc-modal__body {
  padding: 1.15rem 1.25rem;
  display: flex;
  flex-direction: column;
  gap: 0.9rem;
  overflow-y: auto;
  /* No max-height of its own. It carried `min(60vh, 34rem)`, which on a 13"
     laptop resolves to 468px while the dialog above it is allowed 716px — so
     `Add a candidate` hid 108px behind a scrollbar with 102px of screen still
     empty. The dialog's own cap is the only bound that should exist; this
     shrinks inside it, and `min-height: 0` is what lets a flex item do that. */
  flex: 1 1 auto;
  min-height: 0;
}

/* Every dialog wraps body and foot in its `<form>`, so without this the body
   is no flex item of the dialog, never shrinks, and the dialog's `overflow:
   hidden` clips the foot out of reach at 1280×720. */
.rc-modal[open] > form {
  display: flex;
  flex-direction: column;
  flex: 1 1 auto;
  min-height: 0;
}

/* A refused save focuses its field, which can sit further down than the body
   is tall; the sentence saying why stays pinned above it. The offset cancels
   the body's own top padding. */
.rc-modal__body > .rec-partial {
  position: sticky;
  top: -1.15rem;
  z-index: 1;
}

.rc-modal__lede { margin: 0; font-size: 0.88rem; color: var(--ub-ink-soft); line-height: 1.55; }

.rc-modal__foot {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  padding: 0.9rem 1.25rem;
  border-top: 1px solid var(--ub-border);
  background: var(--ub-bg);
}

.rc-modal__note { font-size: 0.8rem; color: var(--ub-ink-soft); margin-right: auto; }

/* A row of two fields that collapses rather than squeezing. */
.rc-pair {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr));
  gap: 0.9rem;
}

/* One choice per line, the whole line clickable. */
.rc-choices { display: flex; flex-direction: column; gap: 0.4rem; }

.rc-choices label {
  display: flex;
  align-items: center;
  gap: 0.55rem;
  padding: 0.5rem 0.65rem;
  border: 1px solid var(--ub-border);
  border-radius: 8px;
  font-size: 0.88rem;
  cursor: pointer;
}

.rc-choices label:hover { border-color: var(--ub-border-strong); background: var(--ub-bg); }
.rc-choices input:focus-visible { outline: 2px solid var(--ub-accent); outline-offset: 2px; }

/* The wizard's step rail. */
.rc-steps { display: flex; gap: 0.75rem; padding: 0 1.25rem 0.9rem; }
.rc-step { flex: 1; display: flex; flex-direction: column; gap: 0.35rem; }

.rc-step__bar { height: 3px; border-radius: 2px; background: var(--ub-border); }
.rc-step--on .rc-step__bar { background: var(--ub-accent); }

.rc-step__name {
  font-family: var(--ub-font-mono);
  font-size: 0.62rem;
  letter-spacing: 0.1em;
  color: var(--ub-ink-faint);
}

.rc-step--on .rc-step__name { color: var(--ub-ink); }

.rc-modal .rc-field { display: flex; flex-direction: column; gap: 0.3rem; }

.rc-modal input[type="text"],
.rc-modal input[type="email"],
.rc-modal input[type="url"],
.rc-modal input[type="datetime-local"],
.rc-modal select,
.rc-modal textarea {
  width: 100%;
  padding: 0.45rem 0.55rem;
  border: 1px solid var(--ub-border-strong);
  border-radius: 8px;
  background: var(--ub-surface);
  color: var(--ub-ink);
  font: inherit;
  font-size: 0.9rem;
}

/* Where an interview is being held, when somebody recorded it. */
.rc-interview__where {
  font-size: 0.8rem;
  color: var(--ub-ink-soft);
  overflow-wrap: anywhere;
}

/* The way into the log-an-interview dialog, above the log it adds to. */
.rc-log-open { margin: 0 0 1.4rem; }

/* A pencil beside the links it edits, rather than a row of its own. */
.rc-pencil {
  display: inline-grid;
  place-items: center;
  width: 1.6rem;
  height: 1.6rem;
  border-radius: 6px;
  color: var(--ub-ink-faint);
}

.rc-pencil:hover { background: var(--ub-bg-deep); color: var(--ub-ink); }

/* The second scope in a dialog that edits two things at once. */
.rc-scope {
  border: 1px solid var(--ub-border);
  border-radius: 9px;
  padding: 0.85rem 1rem;
  background: var(--ub-bg);
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}

.rc-scope__title {
  margin: 0;
  font-family: var(--ub-font-mono);
  font-size: 0.62rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ub-ink-faint);
}

.rc-scope__lede { margin: 0; font-size: 0.82rem; color: var(--ub-ink-soft); }

/* The locked-URL note under a job's title. */
.rc-locked { display: flex; align-items: center; gap: 0.35rem; }
.rc-locked code { font-size: 0.92em; }

/* A scope block whose control sits beside its explanation rather than under it. */
.rc-scope--row { flex-direction: row; align-items: center; gap: 1rem; }
.rc-scope--row p { margin: 0; }

/* ---- Careers page template editor (Settings) ----
   Rules for one screen, scoped under `.rec-adtpl`. This sheet loads before
   the recruiting sheets, so the scope's extra class is what lets each rule
   beat the installed one it adjusts, without editing that sheet. */

.rec-adtpl .rec-panel--quiet > .rec-panel__body { padding-block: 0.85rem; }

/* The name is one row: field and both buttons on one line, the buttons
   aligned to the input rather than to the label above it. */
.rec-adtpl .rec-form--oneline { flex-direction: row; align-items: flex-end; gap: 0.75rem; }
.rec-adtpl .rec-form--oneline > .rec-form__field { flex: 1 1 auto; min-width: 0; }
.rec-adtpl .rec-form--oneline > .rec-form__acts { padding-top: 0; flex: none; }
.rec-adtpl .rec-form--oneline .button { height: 2.6rem; }
.rec-adtpl .rec-input--lg {
  height: 2.6rem; padding-block: 0;
  font-size: 1.05rem; font-weight: 600; letter-spacing: -0.01em;
}

/* Every child of a row aligns to the top of one control-height line. The
   installed rule centres them on the whole row, and a live row is an input
   over a sub line, so the chip and acts sat about 8px below the input. */
.rec-adtpl .rec-reorder,
.rec-adtpl .rec-addrow { --rec-rowctl: 2.3rem; --rec-statecol: 7rem; --rec-actscol: 20rem; }
.rec-adtpl .rec-reorder > li { align-items: flex-start; }
.rec-adtpl .rec-reorder .rec-input,
.rec-adtpl .rec-addrow .rec-input,
.rec-adtpl .rec-addrow .rec-select { height: var(--rec-rowctl); padding-block: 0; }
.rec-adtpl .rec-reorder__pos,
.rec-adtpl .rec-reorder__grip,
.rec-adtpl .rec-reorder > li > .rec-rowstate,
.rec-adtpl .rec-reorder > li > .rec-rowacts,
.rec-adtpl .rec-reorder__main > .rec-reorder__name {
  display: inline-flex; align-items: center; min-height: var(--rec-rowctl);
}
.rec-adtpl .rec-reorder__grip { height: var(--rec-rowctl); }
/* A box around the chip, never the chip itself: sizing `.rec-pill` directly
   stretches it to the column. */
.rec-adtpl .rec-rowstate { display: inline-flex; align-items: center; justify-content: center; flex: none; }

/* Chips and buttons at the row's scale. Row buttons take the input's height
   so field and acts share both edges; the chip grows but stays shorter, a
   state rather than something to press. */
.rec-adtpl .rec-rowacts { gap: 0.35rem; }
.rec-adtpl .rec-reorder .rec-textbtn,
.rec-adtpl .rec-addrow .button,
.rec-adtpl .rec-reorder__newanswer .button {
  display: inline-flex; align-items: center; height: var(--rec-rowctl);
  padding: 0 0.8rem; border-radius: 7px; font-size: 0.82rem;
}
.rec-adtpl .rec-reorder .rec-pill { height: 1.8rem; padding: 0 0.7rem; gap: 0.4rem; font-size: 0.8rem; }
.rec-adtpl .rec-reorder .rec-pill__dot { width: 7px; height: 7px; }

/* One grid for every field. The field column starts after the list's 1rem,
   the ordinal's 1.6rem, the grip's 1.4rem and two 0.6rem gaps; the state and
   acts columns are fixed, so every field ends on one line whatever its row
   offers. Widest content: Disqualifies at 109px, `Close answers · Mark
   required · Retire` at 313px. */
@media (min-width: 900px) {
  .rec-adtpl .rec-reorder > li > .rec-rowstate { width: var(--rec-statecol); }
  .rec-adtpl .rec-reorder > li > .rec-rowacts { width: var(--rec-actscol); flex: none; justify-content: flex-end; }
  /* An add row has no chip, so its button takes both columns and the gap. */
  .rec-adtpl .rec-addrow > .rec-rowacts,
  .rec-adtpl .rec-reorder__newanswer > .rec-rowacts {
    width: calc(var(--rec-statecol) + 0.6rem + var(--rec-actscol)); flex: none; justify-content: flex-end;
  }
}
/* Narrower, the chip and the acts drop under the field, which keeps the whole
   line; they start on the field column. */
@media (max-width: 899px) {
  .rec-adtpl .rec-reorder > li,
  .rec-adtpl .rec-addrow,
  .rec-adtpl .rec-reorder__newanswer,
  .rec-adtpl .rec-addrow > .rec-rowacts,
  .rec-adtpl .rec-form--oneline { flex-wrap: wrap; }
  .rec-adtpl .rec-reorder > li > .rec-reorder__main { flex: 1 0 calc(100% - 4.25rem); }
  .rec-adtpl .rec-reorder > li > .rec-rowstate { margin-left: 4.2rem; }
  .rec-adtpl .rec-reorder > li > .rec-rowacts { flex: 1 0 100%; flex-wrap: wrap; row-gap: 0.35rem; padding-left: 4.2rem; }
  .rec-adtpl .rec-reorder > li > .rec-rowacts:empty { display: none; }
  .rec-adtpl .rec-addrow > .rec-rowacts,
  .rec-adtpl .rec-reorder__newanswer > .rec-rowacts { flex: 1 0 100%; }
  .rec-adtpl .rec-reorder__newanswer { padding-left: 0; }
}

/* The fixed fields draw no grip, so the grip and its gap come back as margin. */
.rec-adtpl .rec-reorder--fixed .rec-reorder__main { margin-left: 2rem; }

/* The add rows are the row that comes next, on the list's grid. */
.rec-adtpl .rec-addrow {
  display: flex; align-items: flex-start; gap: 0.6rem;
  padding: 0.85rem 1rem 1rem 5.2rem; border-top: 1px solid #efecf7;
}
/* The kind sits beside Add, in the acts columns, so the new question's field
   keeps the whole field column like every row above it. */
.rec-adtpl .rec-addrow .rec-select { width: 10.5rem; flex: none; }
/* The add-answer field starts where the answers' fields start. */
.rec-adtpl .rec-reorder__newanswer { gap: 0.6rem; padding-left: 4.8rem; align-items: flex-start; }
.rec-adtpl .rec-reorder__newanswer .rec-input { height: var(--rec-rowctl); padding-block: 0; }

.rec-adtpl .rec-panel__foot--save { justify-content: flex-end; gap: 0.75rem; }
.rec-adtpl .rec-panel__foot--save .rec-panel__hint { margin-right: auto; }

/* A left edge names the consequence: purple for a required row, red at 60%
   for a disqualifying answer. The sub line still says it in words. */
.rec-adtpl .rec-reorder > li.is-required { box-shadow: inset 2px 0 0 var(--pp-mid); }
.rec-adtpl .rec-reorder > li.is-disqualifying {
  box-shadow: inset 2px 0 0 color-mix(in srgb, var(--ub-danger) 60%, transparent);
}
/* With the edge carrying the red, the chip keeps the neutral shape and a
   single red dot, the way `--open` marks a live row with a green one. */
.rec-adtpl .rec-reorder .rec-pill--rejected { border-color: var(--hs-line-strong); color: var(--hs-ink-soft); }
.rec-adtpl .rec-reorder .rec-pill--rejected::before {
  content: ""; width: 7px; height: 7px; border-radius: 50%; flex: none;
  background: var(--ub-danger);
}

/* `.rec-color .rec-textbtn:hover` ties the base `--danger:hover` on
   specificity and wins on sheet order, so Retire hovered purple on every
   palette screen. Here it hovers red; --ub-danger on this ground is 4.78:1. */
.rec-color .rec-adtpl .rec-textbtn--danger:hover {
  color: var(--ub-danger);
  border-color: color-mix(in srgb, var(--ub-danger) 45%, transparent);
  background: color-mix(in srgb, var(--ub-danger) 6%, #fff);
}

/* The answers read as one block with the question above them. The tint keeps
   a 0.6rem right inset, given back by the nest row, so nested acts end on the
   outer rows' edge. The nested rows regain the edge's width on the left. */
.rec-color .rec-adtpl .rec-reorder__nest {
  background: var(--pp-tint); border-left-color: var(--pp-mid);
  border-radius: 0 10px 10px 0;
  padding: 0.7rem 0.6rem 0.85rem 1rem;
}
.rec-color .rec-adtpl .rec-reorder > li.rec-reorder__nestrow { padding-right: 0.4rem; }
.rec-color .rec-adtpl .rec-reorder__nest > .rec-reorder > li { background: transparent; padding-left: 0.6rem; }
.rec-color .rec-adtpl .rec-reorder__nest > .rec-reorder > li:hover { background: #fff; }

/* 45a's week grid: a click picks the half hour under the pointer, so the
   pointer shows that half hour before it is picked. Drawn in JS at the
   chosen duration, dashed so the chosen block and the busy blocks under it
   stay legible. Not in the design package: the artboards draw no hover. */
.rec-slideover .rec-week__col { cursor: pointer; }
.rec-week__hover {
  position: absolute; left: 1px; right: 1px; z-index: 2; pointer-events: none;
  padding: 0.15rem 0.3rem; border-radius: 5px;
  border: 2px dashed var(--pp-mid, #49338d);
  background: color-mix(in srgb, var(--pp-mid, #49338d) 12%, transparent);
  font-size: 0.64rem; font-weight: 600; line-height: 1.25; white-space: nowrap;
  color: var(--pp-ink, #18122b);
}

/* ---- Recruiting tab swaps ----
   `canvas.html` swaps a tab in with a View Transition: the page cross-fades
   rather than flashing, and the lit tab's underline slides to the new tab.
   The underline is its own element so that only it moves — naming the tab
   instead carries its label along, overprinting the tabs it passes. */
.rec-sectionbar__tab--on .rec-sectionbar__ink { view-transition-name: rec-tab-ink; }

::view-transition-old(root),
::view-transition-new(root) {
  animation-duration: 0.16s;
  animation-timing-function: ease-out;
}
::view-transition-group(rec-tab-ink) {
  animation-duration: 0.26s;
  animation-timing-function: cubic-bezier(0.2, 0, 0, 1);
}
::view-transition-old(rec-tab-ink),
::view-transition-new(rec-tab-ink) {
  height: 100%;
}

/* Job labels switch at once; their separate ink slides as the section's does. */
::view-transition-group(rec-job-tab-ink) {
  animation-duration: 0.26s;
  animation-timing-function: cubic-bezier(0.2, 0, 0, 1);
}
::view-transition-old(rec-job-tab-ink),
::view-transition-new(rec-job-tab-ink) {
  height: 100%;
}
::view-transition-old(rec-job-strip) {
  animation: none;
  opacity: 0;
}
::view-transition-new(rec-job-strip) {
  animation: none;
}

/* Interviews' switch and tabs swap the whole body (13, 35). The list panel
   glides to its new place and height instead of jumping when *Nothing
   arranged* appears or Held is longer; its two pictures stay top-anchored and
   unscaled, so rows never stretch while the box resizes. */
.rec-ivx-tabbed { view-transition-name: rec-iv-panel; }
.rec-ivx-nothing { view-transition-name: rec-iv-nothing; }
::view-transition-group(rec-iv-panel),
::view-transition-group(rec-iv-nothing) {
  animation-duration: 0.26s;
  animation-timing-function: cubic-bezier(0.2, 0, 0, 1);
}
::view-transition-image-pair(rec-iv-panel) { overflow: clip; border-radius: 14px; }
::view-transition-old(rec-iv-panel),
::view-transition-new(rec-iv-panel) {
  height: auto;
  object-fit: none;
  object-position: top;
  animation-duration: 0.2s;
}

/* Interviews' pill and underline slide between options (REVISION-04 §4.131).
   Each is drawn on its own `__ink` span rather than on the option, as the
   section bar's is, so only the marker moves and the labels stay put. The
   installed rules paint the option itself; these move that paint onto the
   span, inside Interviews only. */
.rec-ivx .rec-seg__opt,
.rec-ivx .rec-jobtabs__tab { position: relative; isolation: isolate; }
.rec-seg__ink,
.rec-jobtabs__ink { position: absolute; z-index: -1; pointer-events: none; }
.rec-seg__ink { inset: 0; border-radius: inherit; }
.rec-jobtabs__ink { left: 0; right: 0; bottom: -2px; height: 2px; }
.rec-workspace .rec-ivx .rec-seg a[aria-current="true"] { background: transparent; box-shadow: none; }
.rec-workspace .rec-ivx .rec-seg a[aria-current="true"] > .rec-seg__ink {
  background: #fff; box-shadow: 0 1px 3px rgba(24, 18, 43, 0.13);
}
.rec-workspace .rec-ivx .rec-ivx-seg2 a[aria-current="true"] > .rec-seg__ink { background: var(--hs-hover); box-shadow: none; }
.rec-ivx .rec-jobtabs__tab--on { border-bottom-color: transparent; }
.rec-ivx .rec-jobtabs__tab--on > .rec-jobtabs__ink { background: var(--hs-ink); }
.rec-ivx-seg2 a[aria-current="true"] > .rec-seg__ink { view-transition-name: rec-iv-scope-ink; }
.rec-ivx-heldbar a[aria-current="true"] > .rec-seg__ink { view-transition-name: rec-iv-held-ink; }
.rec-ivx-tabs .rec-jobtabs__tab--on > .rec-jobtabs__ink { view-transition-name: rec-iv-tab-ink; }
::view-transition-group(rec-iv-scope-ink),
::view-transition-group(rec-iv-held-ink),
::view-transition-group(rec-iv-tab-ink) {
  animation-duration: 0.26s;
  animation-timing-function: cubic-bezier(0.2, 0, 0, 1);
}
/* Past & feedback's two row acts share one height (REVISION-04 §4.132): the
   one owed keeps its box, reading loses it. Three classes, because the palette
   sheet's `.rec-color .rec-textbtn` loads later and paints the border back. */
.rec-ivx .rec-ivx-act .rec-textbtn--plain,
.rec-ivx .rec-ivx-act .rec-textbtn--plain:hover { border-color: transparent; background: transparent; }
.rec-ivx .rec-ivx-act .rec-textbtn--plain:hover { text-decoration: underline; }

/* A named element is lifted above the page while the transition runs, so a
   filled pill would cover the label it sits behind. Each option is named too,
   and its layer is stacked above the pills: the words stay on top. */
.rec-ivx-seg2 .rec-seg__opt:nth-child(1) { view-transition-name: rec-iv-scope-1; }
.rec-ivx-seg2 .rec-seg__opt:nth-child(2) { view-transition-name: rec-iv-scope-2; }
.rec-ivx-heldbar .rec-seg__opt:nth-child(1) { view-transition-name: rec-iv-held-1; }
.rec-ivx-heldbar .rec-seg__opt:nth-child(2) { view-transition-name: rec-iv-held-2; }
.rec-ivx-heldbar .rec-seg__opt:nth-child(3) { view-transition-name: rec-iv-held-3; }
.rec-ivx-heldbar .rec-seg__opt:nth-child(4) { view-transition-name: rec-iv-held-4; }
::view-transition-group(rec-iv-scope-1),
::view-transition-group(rec-iv-scope-2),
::view-transition-group(rec-iv-held-1),
::view-transition-group(rec-iv-held-2),
::view-transition-group(rec-iv-held-3),
::view-transition-group(rec-iv-held-4) {
  z-index: 2;
  animation-duration: 0.26s;
  animation-timing-function: cubic-bezier(0.2, 0, 0, 1);
}
::view-transition-old(rec-iv-scope-ink),
::view-transition-new(rec-iv-scope-ink),
::view-transition-old(rec-iv-held-ink),
::view-transition-new(rec-iv-held-ink),
::view-transition-old(rec-iv-tab-ink),
::view-transition-new(rec-iv-tab-ink) {
  height: 100%;
}

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

/* ---- Recruiting review drawer motion ----
   Not in the design package: 10g draws the drawer at rest. rec-review-drawer.js
   adds --enter when a drawer opens with none showing, --leaving while Close
   slides it away before the region is swapped empty, and --step when
   Previous, Next or another row replaces an open one. A tab switches still:
   it is the same person seen another way, and a fade there read as a stall.
   A reload with the drawer in the address draws it still, and so does a save. */
@keyframes rec-drawer-in { from { transform: translateX(calc(100% + 48px)); } }
@keyframes rec-drawer-out { to { transform: translateX(calc(100% + 48px)); } }
@keyframes rec-drawer-fade-in { from { opacity: 0; } }
@keyframes rec-drawer-fade-out { to { opacity: 0; } }
@keyframes rec-drawer-step { from { opacity: 0.55; transform: translateX(var(--rec-drawer-step, 0)); } }
.rec-drawer--enter { animation: rec-drawer-in 280ms cubic-bezier(0.2, 0.7, 0.2, 1) both; }
.rec-scrim:has(+ .rec-drawer--enter) { animation: rec-drawer-fade-in 220ms ease both; }
.rec-drawer--leaving { animation: rec-drawer-out 180ms cubic-bezier(0.4, 0, 1, 1) forwards; pointer-events: none; }
.rec-scrim:has(+ .rec-drawer--leaving) { animation: rec-drawer-fade-out 180ms ease forwards; pointer-events: none; }
.rec-drawer--step > :not(.rec-drawer__band),
.rec-drawer--step .rec-drawer__head { animation: rec-drawer-step 150ms cubic-bezier(0.2, 0.7, 0.2, 1) both; }

@media (prefers-reduced-motion: reduce) {
  .rec-drawer--enter, .rec-drawer--leaving,
  .rec-scrim:has(+ .rec-drawer--enter), .rec-scrim:has(+ .rec-drawer--leaving),
  .rec-drawer--step > *, .rec-drawer--step .rec-drawer__head { animation: none; }
}

/* ---- Recruiting motion beyond the review drawer ----
   Dialogs, the intake drawer and the slide-overs a press reveals enter and
   leave; whatever the page load draws open appears still (shell.js). The
   global [hidden] rule's `!important` does not stop the exit: a transition
   outranks it, and `allow-discrete` keeps the panel displayed until it ends. */
@media (prefers-reduced-motion: no-preference) {
  .ub-ready :is(.rc-modal, .confirm) {
    transition: opacity 180ms ease, transform 240ms cubic-bezier(0.2, 0.7, 0.2, 1),
      overlay 240ms allow-discrete, display 240ms allow-discrete;
  }
  .ub-ready :is(.rc-modal, .confirm):not([open]) { opacity: 0; transform: translateY(8px) scale(0.985); transition-duration: 140ms; }
  .ub-ready :is(.rc-modal, .confirm)::backdrop {
    transition: opacity 200ms ease, overlay 200ms allow-discrete, display 200ms allow-discrete;
  }
  .ub-ready :is(.rc-modal, .confirm):not([open])::backdrop { opacity: 0; }
  @starting-style {
    .ub-ready :is(.rc-modal, .confirm)[open] { opacity: 0; transform: translateY(12px) scale(0.985); }
    .ub-ready :is(.rc-modal, .confirm)[open]::backdrop { opacity: 0; }
  }

  .ub-ready :is(form.rec-drawer[data-rec-intake], .rec-slideover) {
    transition: transform 280ms cubic-bezier(0.2, 0.7, 0.2, 1), display 280ms allow-discrete;
  }
  .ub-ready :is(form.rec-drawer[data-rec-intake], .rec-slideover)[hidden] {
    transform: translateX(calc(100% + 48px));
    transition-duration: 180ms; transition-timing-function: cubic-bezier(0.4, 0, 1, 1);
  }
  .ub-ready .rec-scrim:has(+ :is(form.rec-drawer[data-rec-intake], .rec-slideover)) { transition: opacity 220ms ease, display 220ms allow-discrete; }
  .ub-ready .rec-scrim[hidden]:has(+ :is(form.rec-drawer[data-rec-intake], .rec-slideover)) { opacity: 0; transition-duration: 180ms; }
  @starting-style {
    .ub-ready :is(form.rec-drawer[data-rec-intake], .rec-slideover) { transform: translateX(calc(100% + 48px)); }
    .ub-ready .rec-scrim:has(+ :is(form.rec-drawer[data-rec-intake], .rec-slideover)) { opacity: 0; }
  }
}

/* A list being refiltered dims under its own controls, after a beat so a
   quick answer never flickers. Only requests aimed at the list: a drawer or
   review link inside it targets its own region. */
#rec-candidates:has(.htmx-request[hx-target="#rec-candidates"]) .rec-table > tbody,
#rec-jobs-region:has(.htmx-request[hx-target="#rec-jobs-region"]) :is(.rec-ov__table, .rec-jobs__tile),
#rec-ov-ivpanel:has(.htmx-request[hx-target="#rec-ov-ivpanel"]) .rec-ov__ivs {
  opacity: 0.45; transition: opacity 160ms ease 120ms;
}

/* Screen 36 leaves as it arrived (rec-review-overlay.js). The leaving rules
   name .rec-review twice to outrank recruiting.css's --enter, which loads
   later and still holds the panel. */
@keyframes rec-review-sink { to { opacity: 0; transform: translateY(16px) scale(0.985); } }
.rec-review--leaving { pointer-events: none; }
.rec-review.rec-review--leaving .rec-review__backdrop { animation: rec-drawer-fade-out 180ms ease forwards; }
.rec-review.rec-review--leaving .rec-review__panel { animation: rec-review-sink 180ms cubic-bezier(0.4, 0, 1, 1) forwards; }
@media (prefers-reduced-motion: reduce) {
  .rec-review.rec-review--leaving .rec-review__backdrop, .rec-review.rec-review--leaving .rec-review__panel { animation: none; }
}

/* The row whose drawer is open (rec-review-drawer.js), in the gold every
   other Recruiting selection uses. On cells, because the lists paint their
   own hover on the row. */
tr.rec-open-row > td,
.rec-open-row:not(tr) { background: var(--hs-active); }
tr.rec-open-row > td:first-child,
.rec-open-row:not(tr) { box-shadow: inset 3px 0 0 var(--hs-gold); }
