/* ============================================================
   JORK V2 — the stylesheet.

   Every portal page is styled from this one file: design tokens,
   primitive components, the app shell, and each page's own rules.
   It is linked once from Components/App.razor.

   It replaces what used to be three sources — jork-design.css in
   Jork.Shared.UI, this app.css, and eight scoped .razor.css files
   bundled into Jork.Web.styles.css. Scoped CSS gave isolation for
   free; without it every class here shares one namespace, so page
   rules keep their prefix (cli-, dash-, cand-, cnd-, sb-, nav-,
   set-) and the one block that uses generic names is nested under
   .landing.
   Two of those are a letter apart on purpose: cand- is the candidate
   portal shell, cnd- is the recruiter list of candidates.

   The public marketing site keeps its own wwwroot/website/site.css:
   it is a separate visual language (--ink, --gold-*) loaded only by
   WebsiteLayout, and folding it in here would leak that palette
   into the portals.

   Order matters — tokens first, primitives next, pages last, so a
   page rule wins over a primitive at equal specificity.
   ============================================================ */


/* ============================================================
   1. Design system: tokens, reset, primitives
   Was _content/Jork.Shared.UI/css/jork-design.css.
   ============================================================ */

/* ============================================================
   JORK V2 — Design System (consolidated)
   Tokens + reset + primitive components.
   This is the single CSS source of truth shared across all
   three Blazor hosts via the Jork.Shared.UI Razor Class Library.
   Ported verbatim from jork-design-files/tokens.css; per-app
   overrides live in each host's wwwroot/app.css (kept minimal).
   ============================================================ */

:root {
  /* ---------- COLOR: Neutral scale ---------- */
  --n-0:   #ffffff;
  --n-25:  #fcfcfd;
  --n-50:  #f9fafb;
  --n-75:  #f4f5f7;
  --n-100: #eef0f3;
  --n-150: #e5e8ed;
  --n-200: #d9dde4;
  --n-300: #bac1cc;
  --n-400: #8d95a3;
  --n-500: #666e7d;
  --n-600: #4a5161;
  --n-700: #343a47;
  --n-800: #1f2430;
  --n-900: #0f1320;
  --n-950: #070a13;

  /* ---------- COLOR: Primary (Deep Navy) ---------- */
  /* The lightest primary wash. Two notification surfaces already reached for
     it through var(--p-25, var(--p-50)), so it was meant to exist and never
     got written; the unread row is the third. --p-50 behind four or five
     unread rows at once turns the whole panel blue. */
  --p-25:  #f6f8ff;
  --p-50:  #eef2ff;
  --p-100: #dae3ff;
  --p-200: #b8c7ff;
  --p-300: #8ba3f7;
  --p-400: #5b78e8;
  --p-500: #3955d1;
  --p-600: #2a3fb0;
  --p-700: #1e3a8a;
  --p-800: #182e6b;
  --p-900: #0f1f4d;
  --p-950: #0a1535;

  /* ---------- COLOR: Semantic ---------- */
  --success-50:  #ecfdf5;
  --success-100: #d1fae5;
  --success-500: #10b981;
  --success-600: #059669;
  --success-700: #047857;

  --warning-50:  #fffbeb;
  --warning-100: #fef3c7;
  --warning-500: #f59e0b;
  --warning-600: #d97706;
  --warning-700: #b45309;

  --danger-50:  #fef2f2;
  --danger-100: #fee2e2;
  /* The border tint for danger panels. Four screens reached for it and three
     carried the same literal fallback, so the family was incomplete rather than
     the uses being wrong; the value is the one they were all falling back to. */
  --danger-200: #fecaca;
  --danger-500: #ef4444;
  --danger-600: #dc2626;
  --danger-700: #b91c1c;

  --info-50:  #eff6ff;
  --info-100: #dbeafe;
  --info-500: #3b82f6;
  --info-600: #2563eb;
  --info-700: #1d4ed8;

  /* ---------- COLOR: Gold (JORK brand accent) ---------- */
  --gold-50:  #fffaeb;
  --gold-100: #fef0c7;
  --gold-200: #fedf89;
  --gold-300: #fec84b;
  --gold-400: #fdb022;
  --gold-500: #f5b800;
  --gold-600: #dc9b00;
  --gold-700: #b07900;
  --gold-800: #855a00;
  --gold-900: #5a3d00;

  /* ---------- Surfaces ---------- */
  --surface-page:    var(--n-50);
  --surface-raised:  var(--n-0);
  --surface-sunken:  var(--n-75);
  --surface-hover:   var(--n-75);
  --surface-active:  var(--n-100);
  --surface-overlay: rgba(15, 19, 32, 0.48);

  /* ---------- Borders ---------- */
  --border-subtle:  #e8ebf3;  /* brand navy at 10% over white */
  --border-default: #dbe0ec;  /* brand navy at 16% over white */
  --border-strong:  #bcc4dc;  /* brand navy at 30% over white */
  --border-focus:   var(--p-700);

  /* ---------- Text ---------- */
  --text-primary:   var(--n-900);
  --text-secondary: var(--n-600);
  --text-tertiary:  var(--n-500);
  --text-disabled:  var(--n-400);
  --text-inverse:   var(--n-0);
  --text-brand:     var(--p-700);

  /* ---------- Typography ---------- */
  --font-sans: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
  --font-display: 'Inter Display', 'Inter', sans-serif;
  --font-mono: 'JetBrains Mono', 'SF Mono', Consolas, monospace;
  --font-myanmar: 'Noto Sans Myanmar', 'Padauk', var(--font-sans);

  --fs-2xs: 10.5px;
  --fs-xs:  11.5px;
  --fs-sm:  12.5px;
  --fs-md:  13.5px;
  --fs-base: 14px;
  --fs-lg:  15px;
  --fs-xl:  17px;
  --fs-2xl: 20px;
  --fs-3xl: 24px;
  --fs-4xl: 30px;
  --fs-5xl: 38px;

  --fw-regular: 400;
  --fw-medium: 500;
  --fw-semibold: 600;
  --fw-bold: 700;

  --lh-tight: 1.2;
  --lh-snug: 1.35;
  --lh-normal: 1.5;
  --lh-relaxed: 1.65;

  /* ---------- Spacing (4px base) ---------- */
  --sp-0:  0px;
  --sp-1:  2px;
  --sp-2:  4px;
  --sp-3:  6px;
  --sp-4:  8px;
  --sp-5:  10px;
  --sp-6:  12px;
  --sp-8:  16px;
  --sp-10: 20px;
  --sp-12: 24px;
  --sp-14: 28px;
  --sp-16: 32px;
  --sp-20: 40px;
  --sp-24: 48px;
  --sp-32: 64px;

  /* ---------- Radii ---------- */
  --r-xs: 3px;
  --r-sm: 5px;
  --r-md: 7px;
  --r-lg: 10px;
  --r-xl: 14px;
  --r-2xl: 20px;
  --r-full: 9999px;

  /* ---------- Shadows ---------- */
  --shadow-xs:  0 1px 2px rgba(15, 19, 32, 0.04);
  --shadow-sm:  0 1px 2px rgba(15, 19, 32, 0.06), 0 1px 3px rgba(15, 19, 32, 0.04);
  --shadow-md:  0 4px 8px -2px rgba(15, 19, 32, 0.06), 0 2px 4px -1px rgba(15, 19, 32, 0.04);
  --shadow-lg:  0 12px 24px -6px rgba(15, 19, 32, 0.08), 0 4px 8px -2px rgba(15, 19, 32, 0.04);
  --shadow-xl:  0 24px 48px -12px rgba(15, 19, 32, 0.12), 0 8px 16px -4px rgba(15, 19, 32, 0.06);
  --shadow-focus: 0 0 0 3px rgba(30, 58, 138, 0.16);
  /* The only horizontally-offset shadow in the family: the sidebar casts to
     its inline end, where every other surface casts downward. */
  --shadow-sidebar: 4px 0 12px -4px rgba(15, 19, 32, 0.08), 1px 0 3px -1px rgba(15, 19, 32, 0.05);
  /* The same weight rotated a quarter turn, so the two edges of the shell
     read as one surface lifted off the page rather than two. */
  --shadow-topbar: 0 4px 12px -4px rgba(15, 19, 32, 0.08), 0 1px 3px -1px rgba(15, 19, 32, 0.05);

  /* ---------- Heights (form controls) ---------- */
  --h-xs: 24px;
  --h-sm: 28px;
  --h-md: 32px;
  --h-lg: 36px;
  --h-xl: 40px;
  --h-2xl: 44px;

  /* ---------- Motion ---------- */
  --ease-out: cubic-bezier(0.16, 1, 0.3, 1);
  --ease-in-out: cubic-bezier(0.65, 0, 0.35, 1);
  --dur-fast: 120ms;
  --dur-mid: 220ms;
  --dur-slow: 360ms;

  /* ---------- Z ---------- */
  --z-dropdown: 100;
  --z-sticky: 200;
  /* The shell chrome: sidebar and topbar. Above every sticky bar a page can pin
     (--z-sticky), because the topbar hangs its own panels - menu search, the
     bell, the language switcher - down over the page and they have to clear
     whatever the page pinned under them. Below the overlay scale, which covers
     the whole viewport, chrome included. */
  --z-chrome: 300;
  --z-drawer: 900;
  --z-modal: 1000;
  --z-toast: 1100;
}

/* ============================================================
   Reset / base
   ============================================================ */
*, *::before, *::after { box-sizing: border-box; }
html, body { margin: 0; padding: 0; }
body {
  font-family: var(--font-sans);
  font-size: var(--fs-base);
  line-height: var(--lh-normal);
  color: var(--text-primary);
  background: var(--surface-page);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  font-feature-settings: 'cv11', 'ss01', 'ss03';
}
button, input, select, textarea { font: inherit; color: inherit; }
button { cursor: pointer; border: none; background: none; padding: 0; }
a { color: inherit; text-decoration: none; }
svg { display: block; }
[hidden] { display: none !important; }

/* Scrollbars */
::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: var(--n-200); border-radius: 10px; border: 2px solid transparent; background-clip: padding-box; }
::-webkit-scrollbar-thumb:hover { background: var(--n-300); background-clip: padding-box; border: 2px solid transparent; }

/* ---------- Quiet scrollbar ----------
   For a scroll container nested inside the chrome rather than the page
   itself: the two navigation rails, and anything else where the global
   10px thumb would run down the inside of a panel as a second vertical
   line beside its own content. On the collapsed sidebar it was 10px of
   a 72px rail.

   Thinner, in a lane of its own so it never crosses a label, and on a
   pointer device painted only while the pointer is in the container or
   something inside it has keyboard focus. The lane is reserved either
   way, so revealing the thumb never reflows what is under it.

   Hidden-until-hover only under (hover: hover). On a touch device there
   is no hover to reveal it with, and the bar the platform overlays is
   the only thing saying the list continues. */

.scroll-quiet {
  scrollbar-width: thin;
  scrollbar-color: var(--n-300) transparent;
}

.scroll-quiet::-webkit-scrollbar { width: 8px; height: 8px; }
.scroll-quiet::-webkit-scrollbar-track { background: transparent; }

/* The transparent border plus a padding-box clip is what puts a 4px
   thumb in an 8px lane with air on both sides - the same trick the
   global scrollbar above uses, one size down. */
.scroll-quiet::-webkit-scrollbar-thumb {
  background: var(--n-300);
  background-clip: padding-box;
  border: 2px solid transparent;
  border-radius: var(--r-full);
}

@media (hover: hover) {
  .scroll-quiet { scrollbar-color: transparent transparent; }
  .scroll-quiet::-webkit-scrollbar-thumb { background: transparent; }

  .scroll-quiet:hover,
  .scroll-quiet:focus-within { scrollbar-color: var(--n-300) transparent; }

  .scroll-quiet:hover::-webkit-scrollbar-thumb,
  .scroll-quiet:focus-within::-webkit-scrollbar-thumb {
    background: var(--n-300);
    background-clip: padding-box;
  }

  .scroll-quiet:hover::-webkit-scrollbar-thumb:hover {
    background: var(--n-400);
    background-clip: padding-box;
  }
}

/* ============================================================
   Primitive components
   ============================================================ */

/* ---------- Button ---------- */
/* Every variant carries the same 1px border, so a filled button, an outlined
   one and a ghost all occupy the identical box. With border-box sizing the
   border is drawn inside the height token, so --h-lg stays 36px whether the
   border is coloured or transparent, and a row of mixed variants lines up. */
.btn {
  display: inline-flex; align-items: center; justify-content: center;
  gap: 6px;
  height: var(--h-lg);
  padding: 0 14px;
  border: 1px solid transparent;
  border-radius: var(--r-md);
  font-size: var(--fs-md);
  font-weight: var(--fw-medium);
  line-height: 1;
  letter-spacing: -0.005em;
  transition: all var(--dur-fast) var(--ease-out);
  white-space: nowrap;
  user-select: none;
}
.btn:focus-visible { outline: none; box-shadow: var(--shadow-focus); }
/* A disabled button must read as disabled. Without this it keeps
   cursor:pointer and full opacity, so "coming soon" actions look broken
   rather than unavailable; the title tooltip still explains why on hover. */
.btn:disabled { cursor: not-allowed; opacity: 0.5; }
.btn-primary {
  background: var(--p-700); color: var(--text-inverse);
  border-color: var(--p-800);
  box-shadow: inset 0 1px 0 rgba(255,255,255,0.08), 0 1px 2px rgba(15,19,32,0.12);
}
.btn-primary:hover { background: var(--p-800); border-color: var(--p-900); }
.btn-primary:active { background: var(--p-900); border-color: var(--p-900); }
/* Gold call to action. One per screen — the brand accent loses its pull the
   moment a second one appears beside it. Navy text, because gold carries far
   too little contrast against white. */
.btn-accent {
  background: var(--gold-500); color: var(--p-700);
  border-color: var(--gold-600);
  box-shadow: inset 0 1px 0 rgba(255,255,255,0.24), 0 1px 2px rgba(15,19,32,0.12);
}
.btn-accent:hover { background: var(--gold-600); border-color: var(--gold-700); }
.btn-accent:active { background: var(--gold-700); border-color: var(--gold-700); color: var(--text-inverse); }
.btn-secondary {
  background: var(--surface-raised); color: var(--text-primary);
  border-color: var(--border-strong);
  box-shadow: var(--shadow-xs);
}
.btn-secondary:hover { background: var(--p-50); border-color: var(--p-700); color: var(--text-brand); }
.btn-ghost { background: transparent; color: var(--text-secondary); border-color: var(--border-subtle); }
.btn-ghost:hover { background: var(--p-50); color: var(--text-brand); border-color: var(--border-default); }
.btn-danger { background: var(--danger-600); color: var(--text-inverse); border-color: var(--danger-700); }
.btn-danger:hover { background: var(--danger-700); border-color: var(--danger-700); }
.btn-sm { height: var(--h-md); padding: 0 10px; font-size: var(--fs-sm); border-radius: var(--r-sm); }
.btn-xs { height: var(--h-sm); padding: 0 8px; font-size: var(--fs-xs); border-radius: var(--r-sm); }
.btn-lg { height: var(--h-xl); padding: 0 18px; font-size: var(--fs-base); }
.btn-icon { width: var(--h-lg); padding: 0; }
.btn-icon.btn-sm { width: var(--h-md); }
.btn-icon.btn-xs { width: var(--h-sm); }

/* ---------- Wide tables ---------- */
/* A data table is as wide as its columns need; the card holding it is as wide
   as the layout allows. When the first exceeds the second the table used to
   spill outside the card, and because the page itself does not scroll
   sideways, the right-hand action column became unreachable — the edit and
   delete buttons were simply off-screen at narrower widths or any browser
   zoom.

   Applied per table rather than to every card body. A scroll container clips
   whatever is absolutely positioned inside it, and the Clients, Candidates and
   Jobs tables open a row action menu that has to escape the card — making every
   card body scroll cut those menus off at the card's edge. Wrap the tables that
   are genuinely too wide; leave the ones carrying menus alone.

   The class that does it is .tbl-wrap, in "12. Shared page chrome". This used
   to declare a .table-scroll of its own saying the same thing, which is how
   one table ended up outside everything .tbl-wrap knows - including, once the
   header pinned, which box it pins to. */

/* ---------- Row action menu ---------- */
/* Used by the Clients, Candidates and Jobs tables. These rules lived in a
   <style> block inside Clients.razor, so they only reached the DOM when that
   one page was rendered — on Candidates and Jobs the menu had no panel, no
   absolute positioning and no nowrap, so it pushed the row open and broke every
   label onto its own line. */
.ctx-menu {
  position: absolute;
  right: 8px;
  top: 38px;
  min-width: 188px;
  background: var(--n-0);
  border: 1px solid var(--border-default);
  border-radius: var(--r-md);
  box-shadow: 0 12px 32px rgba(20, 18, 12, 0.18);
  padding: 5px;
  display: flex;
  flex-direction: column;
  gap: 1px;
  z-index: 70;
}
/* A menu on one of the last rows would otherwise open past the bottom of the
   card and be clipped by the table's scroll container. */
.ctx-menu-up { top: auto; bottom: 38px; }

/* Jobs, whose rows scroll inside their card: the action cell lives in that
   scrollport, and a menu positioned inside it is clipped at the card's edge on
   every row but the last few. Fixed escapes, because nothing between the cell
   and the viewport sets a transform. C# parks it from the trigger's rect and
   does not track it afterwards, which is what the scrim is for - it takes the
   scroll that would otherwise leave the menu pointing at nothing. The inline
   style carries top and right; the cap keeps the panel on screen when the flip
   estimate is off. */
.ctx-menu-anchored {
    position: fixed;
    max-block-size: calc(100dvh - 16px);
    overflow-y: auto;
}
.ctx-item {
  display: flex;
  align-items: center;
  gap: 9px;
  width: 100%;
  padding: 7px 9px;
  border: none;
  background: transparent;
  border-radius: var(--r-sm);
  font-size: 12.5px;
  color: var(--text-primary);
  cursor: pointer;
  text-align: left;
  white-space: nowrap;
}
.ctx-item:hover:not(:disabled) { background: var(--n-50); }
.ctx-item:disabled { color: var(--text-tertiary); cursor: default; }
.ctx-item-danger { color: var(--danger-700); }
.ctx-item-danger:hover:not(:disabled) { background: var(--danger-50); }
.ctx-sep { height: 1px; background: var(--border-subtle); margin: 4px 6px; }


/* ---------- Focus ---------- */
/* Routes.razor uses <FocusOnNavigate Selector="h1">, which moves focus to the
   page heading after every navigation so screen readers announce it. The side
   effect was a focus ring drawn around the heading on every page load, which
   looked like a stray box around the title. Suppress the ring only where the
   browser itself judges it unwarranted (programmatic focus, mouse clicks);
   keyboard users still get :focus-visible everywhere. */
:focus:not(:focus-visible) { outline: none; }
/* FocusOnNavigate marks the heading tabindex="-1" and focuses it, and Chromium
   reports that as :focus-visible, so the rule above does not catch it. An
   element made focusable only so focus can be moved to it is never a keyboard
   stop, so suppressing its ring costs no accessibility. */
[tabindex="-1"]:focus { outline: none; }



/* ---------- Input ---------- */
.input {
  display: inline-flex; align-items: center;
  width: 100%;
  height: var(--h-lg);
  padding: 0 12px;
  background: var(--surface-raised);
  border: 1px solid var(--border-default);
  border-radius: var(--r-md);
  font-size: var(--fs-md);
  color: var(--text-primary);
  transition: border-color var(--dur-fast), box-shadow var(--dur-fast);
}
.input::placeholder { color: var(--text-tertiary); }
.input:hover { border-color: var(--border-strong); }
.input:focus { outline: none; border-color: var(--border-focus); box-shadow: var(--shadow-focus); }
.input.error { border-color: var(--danger-500); }
.input.error:focus { box-shadow: 0 0 0 3px rgba(239, 68, 68, 0.16); }
textarea.input { height: auto; padding: 10px 12px; line-height: var(--lh-normal); resize: vertical; min-height: 80px; }
select.input { padding-right: 32px; appearance: none; background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20' fill='%23666e7d'%3E%3Cpath d='M5.23 7.21a.75.75 0 011.06.02L10 11.06l3.71-3.83a.75.75 0 011.08 1.04l-4.25 4.39a.75.75 0 01-1.08 0L5.21 8.27a.75.75 0 01.02-1.06z'/%3E%3C/svg%3E"); background-repeat: no-repeat; background-position: right 8px center; background-size: 16px; }

.input-wrap { position: relative; display: block; }
.input-wrap .input-prefix,
.input-wrap .input-suffix {
  position: absolute; top: 50%; transform: translateY(-50%);
  display: flex; align-items: center; justify-content: center;
  color: var(--text-tertiary);
  pointer-events: none;
}
.input-wrap .input-prefix { left: 10px; }
.input-wrap .input-suffix { right: 10px; }
.input-wrap .input.has-prefix { padding-left: 34px; }
.input-wrap .input.has-suffix { padding-right: 34px; }

/* ---------- Form Field ---------- */
.form-field { display: flex; flex-direction: column; gap: 6px; }
.form-field .label {
  font-size: var(--fs-sm);
  font-weight: var(--fw-medium);
  color: var(--text-primary);
  letter-spacing: -0.005em;
  display: flex;
  align-items: center;
  gap: 4px;
}
.form-field .label .required { color: var(--danger-600); }
.form-field .label .optional { color: var(--text-tertiary); font-weight: var(--fw-regular); font-size: var(--fs-xs); margin-left: 4px; }
.form-field .hint { font-size: var(--fs-xs); color: var(--text-tertiary); }
.form-field .error-msg { font-size: var(--fs-xs); color: var(--danger-600); display: flex; align-items: center; gap: 4px; }

/* ---------- Badge / Chip ---------- */
.badge {
  display: inline-flex; align-items: center; gap: 4px;
  height: 20px;
  padding: 0 7px;
  border-radius: var(--r-full);
  font-size: var(--fs-2xs);
  font-weight: var(--fw-semibold);
  letter-spacing: 0.01em;
  text-transform: uppercase;
  border: 1px solid transparent;
}
.badge.dot::before { content: ''; width: 5px; height: 5px; border-radius: 50%; background: currentColor; }
.badge-neutral  { background: var(--n-100); color: var(--n-700); }
.badge-success  { background: var(--success-50); color: var(--success-700); }
.badge-warning  { background: var(--warning-50); color: var(--warning-700); }
.badge-danger   { background: var(--danger-50); color: var(--danger-700); }
.badge-info     { background: var(--info-50); color: var(--info-700); }
.badge-brand    { background: var(--p-50); color: var(--p-700); }
.badge-gold     { background: var(--gold-50); color: var(--gold-700); }
.badge-outline  { background: transparent; border-color: var(--border-default); color: var(--text-secondary); }

/* ---------- Avatar ---------- */
.avatar {
  display: inline-flex; align-items: center; justify-content: center;
  width: 32px; height: 32px;
  border-radius: var(--r-full);
  font-size: var(--fs-sm);
  font-weight: var(--fw-semibold);
  color: var(--n-0);
  text-transform: uppercase;
  letter-spacing: -0.01em;
  flex-shrink: 0;
}
.avatar-sm { width: 24px; height: 24px; font-size: var(--fs-2xs); }
.avatar-lg { width: 40px; height: 40px; font-size: var(--fs-base); }
.avatar-xl { width: 64px; height: 64px; font-size: var(--fs-xl); }

/* ---------- Card ---------- */
/* --border-default, not --border-subtle: QCard - which is what nearly every
   card on the product actually is - drew itself at default, so a hand-built
   .card sitting next to one was visibly lighter. The redesigned pages are the
   reference, so the primitive moved to match them rather than the other way
   round. */
.card {
  background: var(--surface-raised);
  border: 1px solid var(--border-default);
  border-radius: var(--r-lg);
  box-shadow: var(--shadow-xs);
}

/* ---------- Divider ---------- */
.divider { height: 1px; background: var(--border-subtle); border: none; margin: 0; }
.divider-v { width: 1px; background: var(--border-subtle); align-self: stretch; }

/* ---------- Kbd ---------- */
.kbd {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 20px; height: 20px; padding: 0 5px;
  background: var(--surface-sunken);
  border: 1px solid var(--border-default);
  border-bottom-width: 2px;
  border-radius: var(--r-xs);
  font-family: var(--font-mono);
  font-size: var(--fs-2xs);
  color: var(--text-secondary);
}

/* ---------- Checkbox ---------- */
.checkbox {
  appearance: none;
  width: 16px; height: 16px;
  border: 1.5px solid var(--border-strong);
  border-radius: var(--r-xs);
  background: var(--surface-raised);
  position: relative;
  cursor: pointer;
  transition: all var(--dur-fast);
  flex-shrink: 0;
}
.checkbox:hover { border-color: var(--p-500); }
.checkbox:checked { background: var(--p-700); border-color: var(--p-700); }
.checkbox:checked::after {
  content: '';
  position: absolute; inset: 0;
  background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='white' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M3 8l3.5 3.5L13 5'/%3E%3C/svg%3E") center / 12px no-repeat;
}
.checkbox:focus-visible { outline: none; box-shadow: var(--shadow-focus); }
.checkbox.indeterminate { background: var(--p-700); border-color: var(--p-700); }
.checkbox.indeterminate::after {
  content: '';
  position: absolute; left: 3px; right: 3px; top: 50%;
  height: 2px; background: white; border-radius: 1px;
  transform: translateY(-50%);
}

/* ---------- Segmented control ---------- */
.segmented {
  display: inline-flex;
  padding: 3px;
  background: var(--n-100);
  border-radius: var(--r-md);
  gap: 2px;
}
.segmented button {
  height: 26px;
  padding: 0 10px;
  border-radius: var(--r-sm);
  font-size: var(--fs-sm);
  font-weight: var(--fw-medium);
  color: var(--text-secondary);
  transition: all var(--dur-fast);
}
.segmented button:hover { color: var(--text-primary); }
.segmented button.active {
  background: var(--surface-raised);
  color: var(--text-primary);
  box-shadow: var(--shadow-xs);
}

/* ---------- Utility ---------- */
.sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0,0,0,0); white-space: nowrap; border: 0; }
.row { display: flex; align-items: center; }
.col { display: flex; flex-direction: column; }
.gap-1 { gap: 4px; } .gap-2 { gap: 8px; } .gap-3 { gap: 12px; } .gap-4 { gap: 16px; } .gap-6 { gap: 24px; }
.mono { font-family: var(--font-mono); font-variant-numeric: tabular-nums; }
.tnum { font-variant-numeric: tabular-nums; }
.truncate { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

/* Myanmar text */
.myanmar { font-family: var(--font-myanmar); line-height: var(--lh-relaxed); }

/* ============================================================
   2. Host globals: validation, error UI, responsive shell
   Was Jork.Web/wwwroot/app.css.
   ============================================================ */


.valid.modified:not([type=checkbox]) { outline: 1px solid var(--success-600); }
.invalid { outline: 1px solid var(--danger-600); }
.validation-message { color: var(--danger-600); font-size: var(--fs-xs); }

.blazor-error-boundary {
    background: var(--danger-600);
    color: var(--text-inverse);
    padding: var(--sp-8) var(--sp-12);
    border-radius: var(--r-md);
    font-size: var(--fs-md);
}
.blazor-error-boundary::after { content: "An error has occurred."; }

/* Pipeline kanban drag-and-drop (Phase 7) */
[data-pipeline-card] { cursor: grab; transition: box-shadow 0.12s ease, opacity 0.12s ease; }
[data-pipeline-card]:active { cursor: grabbing; }
.pipeline-card--dragging { opacity: 0.45; box-shadow: 0 6px 18px rgba(0,0,0,0.14); }
.pipeline-col--over { background: var(--p-50) !important; outline: 2px dashed var(--p-300); outline-offset: -3px; }
.pipeline-col--over .pipeline-col__body { background: transparent; }

/* Platform Console — responsive shell (icon-rail sidebar on narrow viewports) */
@media (max-width: 800px) {
    .app-shell { overflow-x: hidden; }
    /* !important because every shell sets this padding inline, which outranks a
       plain class rule. Without it this line has never done anything - the
       platform console kept its 32px side padding on a phone too. Matches the
       shells' own 8px: the point is to never exceed it on a narrow screen, and
       at 16px this rule would now be adding padding rather than removing it. */
    .app-shell > div:last-child main { padding: 8px !important; }
    /* The sidebar rail below 800px is driven from the --sb-* tokens in
       section 4; the old width/label overrides here would outrank those
       tokens, so they are deliberately gone. */

    .schema-layout { grid-template-columns: 1fr !important; }
    .schema-field-form-grid { grid-template-columns: 1fr !important; }
    /* .integ-catalog-grid / .integ-provider-grid were the integrations page's
       two fixed grids. It is auto-fit now ("65b."), which reflows on its own
       measurement rather than at a breakpoint stated here. */
    .ops-kpi-grid-3 { grid-template-columns: 1fr !important; }
    .ops-kpi-grid-4 { grid-template-columns: 1fr 1fr !important; }
    /* .settings-layout / .settings-subnav were the platform hub's own
       two-column frame and rail. It is built from .set now - "18. Settings
       hub (set-*)" - which carries its own breakpoints, so the overrides had
       nothing left to override. */
    .settings-form-grid { grid-template-columns: 1fr !important; }
    .tenant-support-layout { grid-template-columns: 1fr !important; }

    /* Candidate portal. Its shell is a separate component from AppShell and had
       none of these hooks, so everything above skipped it. The rail rules apply
       to it now via .app-shell / .app-sidebar; these are the pieces the platform
       console does not have. */
    /* Shared topbar. A keyboard hint means nothing on a touch device, and the
       search box is worth more than the pixels the chip costs; the AI entry
       point keeps its mark and drops its word for the same reason. */
    .app-topbar { padding-inline: var(--sp-6) !important; gap: var(--sp-3) !important; }
    .tb-search { flex-basis: 180px !important; }
    .tb-kbd { display: none !important; }
    .tb-ai { padding-inline: 0 !important; width: var(--h-md); justify-content: center; }
    .tb-ai-label { display: none !important; }
    .tb-sep { display: none !important; }

    .candidate-topbar { padding-inline: 12px !important; gap: 8px !important; }
    .candidate-kbd-hint { display: none !important; }
    .candidate-strength-label { display: none !important; }
    .candidate-strength { display: none !important; }

    /* Results before filters on the candidate job board used to live here as
       two ordering utilities. The board carries a rail now - navigation as
       well as filters - and it orders its own three pieces around the results
       from section 23, where the rest of that rail is. */

    /* A column of actions that has been given a full line: lay it across that
       line instead of down it, so the card does not grow a tall empty strip. */
    .fluid-sm[style*="flex-direction:column"],
    .fluid-sm[style*="flex-direction: column"] {
        flex-direction: row !important;
        justify-content: flex-start !important;
        align-items: center !important;
    }

    /* Narrow-viewport utilities.
       !important throughout because every one of these sites declares its
       layout inline, and an inline style outranks a class rule. Measured at
       390px: the portal fits the screen but nine pages pushed content into a
       sideways scroll inside <main>, where it looks fine and is unreachable.
       These are the three shapes that caused it. */

    /* Any fixed multi-column grid becomes one column. */
    .stack-sm { grid-template-columns: 1fr !important; }

    /* One column is not enough on its own: a grid item defaults to
       min-width:auto, so the track refuses to go narrower than the item's
       min-content width and the card sticks out of a pane that is technically
       1fr wide. The same defaulting that makes flex children overflow. */
    .stack-sm > * { min-width: 0; }

    /* A table cannot be narrowed, so it scrolls inside its own box rather than
       dragging the page - and the rest of the page stays put while it does. */
    .scroll-sm { overflow-x: auto !important; }

    /* A row of controls that would otherwise run off the edge. */
    .wrap-sm { flex-wrap: wrap !important; }

    /* A fixed-width column in a wrapping row, told to take the whole line.
       width:auto is not enough: the column shrinks to its content and the
       next one sits beside it, so both end up half-width and the row still
       overflows. Claiming the full basis is what forces the wrap. */
    .fluid-sm {
        width: 100% !important;
        min-width: 0 !important;
        flex-basis: 100% !important;
        flex-shrink: 1 !important;
    }

    /* Rows inside the candidate portal wrap rather than run off the edge.
       A blanket rule rather than a class per row, because wrapping is a no-op
       until a row actually runs out of room - it changes nothing that already
       fits, and rescues everything that does not. Rows that must stay on one
       line opt out with .nowrap-sm.
       Scoped to <main> so the sidebar and topbar, which are laid out
       deliberately above, are left alone. */
    .app-shell main [style*="display:flex"]:not(.nowrap-sm),
    .app-shell main [style*="display: flex"]:not(.nowrap-sm) { flex-wrap: wrap; }

    /* A min-width on a child defeats the wrap above by refusing to narrow. */
    .app-shell main [style*="min-width:2"]:not(.nowrap-sm),
    .app-shell main [style*="min-width: 2"]:not(.nowrap-sm) { min-width: 0 !important; }

    /* The single most common cause of a card wider than its column: a flex or
       grid child defaults to min-width:auto - its content's min-content width -
       and no track sizing can push it below that. Measured: a card 290px wide
       inside a 228px column, on a page whose grid had already collapsed to one
       track correctly.

       Applied to elements rather than to "children of a flex container", because
       CSS cannot select the latter and the containers here get their display
       from components as often as from an inline style. It removes an implicit
       floor only: anything with an explicit width or min-width keeps it, and
       .nowrap-sm opts out. */
    .app-shell main div:not(.nowrap-sm),
    .app-shell main section:not(.nowrap-sm),
    .app-shell main button:not(.nowrap-sm),
    .app-shell main a:not(.nowrap-sm) { min-width: 0; }

    /* min-width:0 is not enough on its own. In a column-direction flex container
       the items are sized on the cross axis, where min-width has no say - a card
       measured 290px inside a 228px parent with the floor already removed. A
       ceiling is what that case needs: nothing in the content area may be wider
       than the box it sits in. Images and tables are covered separately, and
       .nowrap-sm still opts out. */
    .app-shell main div:not(.nowrap-sm),
    .app-shell main section:not(.nowrap-sm),
    .app-shell main img,
    .app-shell main pre { max-width: 100%; }

    /* A long unbroken string - a pasted URL, or a headline saved without spaces
       after a bad résumé import - has a minimum width equal to its own length,
       and no amount of layout can make its container narrower than that. Letting
       it break mid-word keeps one bad field from widening the whole page. */
    .app-shell main { overflow-wrap: anywhere; }

    /* A hero banner whose avatar, name and buttons hang off it absolutely.
       Absolute boxes are out of flow, so nothing below them moves when their
       text wraps - on a phone the name simply landed on top of the next
       section. Putting all three back into flow costs the overhang effect and
       buys a page that can be read. */
    .hero-sm { height: auto !important; margin-bottom: 18px !important; padding: 16px !important; }
    .hero-float-sm {
        position: static !important;
        margin-top: 12px;
        flex-wrap: wrap;
        align-items: flex-start !important;
    }
}


/* ============================================================
   3. Layout: Blazor error UI and reconnect modal
   Was MainLayout.razor.css + ReconnectModal.razor.css.
   ============================================================ */

#blazor-error-ui {
    color-scheme: light only;
    background: var(--warning-50);
    color: var(--warning-700);
    bottom: 0;
    box-shadow: var(--shadow-md);
    box-sizing: border-box;
    display: none;
    left: 0;
    padding: var(--sp-6) var(--sp-12);
    position: fixed;
    width: 100%;
    z-index: var(--z-toast);
    font-family: var(--font-sans);
    font-size: var(--fs-md);
}

#blazor-error-ui .reload {
    color: inherit;
    font-weight: var(--fw-semibold);
    text-decoration: underline;
    margin-left: var(--sp-4);
}

#blazor-error-ui .dismiss {
    cursor: pointer;
    position: absolute;
    right: var(--sp-8);
    top: var(--sp-4);
}
.components-reconnect-first-attempt-visible,
.components-reconnect-repeated-attempt-visible,
.components-reconnect-failed-visible,
.components-pause-visible,
.components-resume-failed-visible,
.components-rejoining-animation {
    display: none;
}

#components-reconnect-modal.components-reconnect-show .components-reconnect-first-attempt-visible,
#components-reconnect-modal.components-reconnect-show .components-rejoining-animation,
#components-reconnect-modal.components-reconnect-paused .components-pause-visible,
#components-reconnect-modal.components-reconnect-resume-failed .components-resume-failed-visible,
#components-reconnect-modal.components-reconnect-retrying,
#components-reconnect-modal.components-reconnect-retrying .components-reconnect-repeated-attempt-visible,
#components-reconnect-modal.components-reconnect-retrying .components-rejoining-animation,
#components-reconnect-modal.components-reconnect-failed,
#components-reconnect-modal.components-reconnect-failed .components-reconnect-failed-visible {
    display: block;
}


#components-reconnect-modal {
    background-color: white;
    width: 20rem;
    margin: 20vh auto;
    padding: 2rem;
    border: 0;
    border-radius: 0.5rem;
    box-shadow: 0 3px 6px 2px rgba(0, 0, 0, 0.3);
    opacity: 0;
    transition: display 0.5s allow-discrete, overlay 0.5s allow-discrete;
    animation: components-reconnect-modal-fadeOutOpacity 0.5s both;
    &[open]

{
    animation: components-reconnect-modal-slideUp 1.5s cubic-bezier(.05, .89, .25, 1.02) 0.3s, components-reconnect-modal-fadeInOpacity 0.5s ease-in-out 0.3s;
    animation-fill-mode: both;
}

}

#components-reconnect-modal::backdrop {
    background-color: rgba(0, 0, 0, 0.4);
    animation: components-reconnect-modal-fadeInOpacity 0.5s ease-in-out;
    opacity: 1;
}

@keyframes components-reconnect-modal-slideUp {
    0% {
        transform: translateY(30px) scale(0.95);
    }

    100% {
        transform: translateY(0);
    }
}

@keyframes components-reconnect-modal-fadeInOpacity {
    0% {
        opacity: 0;
    }

    100% {
        opacity: 1;
    }
}

@keyframes components-reconnect-modal-fadeOutOpacity {
    0% {
        opacity: 1;
    }

    100% {
        opacity: 0;
    }
}

.components-reconnect-container {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 1rem;
}

#components-reconnect-modal p {
    margin: 0;
    text-align: center;
}

#components-reconnect-modal button {
    border: 0;
    background-color: #6b9ed2;
    color: white;
    padding: 4px 24px;
    border-radius: 4px;
}

    #components-reconnect-modal button:hover {
        background-color: #3b6ea2;
    }

    #components-reconnect-modal button:active {
        background-color: #6b9ed2;
    }

.components-rejoining-animation {
    position: relative;
    width: 80px;
    height: 80px;
}

    .components-rejoining-animation div {
        position: absolute;
        border: 3px solid #0087ff;
        opacity: 1;
        border-radius: 50%;
        animation: components-rejoining-animation 1.5s cubic-bezier(0, 0.2, 0.8, 1) infinite;
    }

        .components-rejoining-animation div:nth-child(2) {
            animation-delay: -0.5s;
        }

@keyframes components-rejoining-animation {
    0% {
        top: 40px;
        left: 40px;
        width: 0;
        height: 0;
        opacity: 0;
    }

    4.9% {
        top: 40px;
        left: 40px;
        width: 0;
        height: 0;
        opacity: 0;
    }

    5% {
        top: 40px;
        left: 40px;
        width: 0;
        height: 0;
        opacity: 1;
    }

    100% {
        top: 0px;
        left: 0px;
        width: 80px;
        height: 80px;
        opacity: 0;
    }
}


/* ============================================================
   4. Shell: recruiter/admin sidebar
   Was Jork.Shared.UI Components/Shell/AppSidebar.razor.css.
   ============================================================ */

/* Scoped styles for AppSidebar.

   Rail (collapsed) state is driven entirely by custom properties so the three
   things that can trigger it — the JS toggle on <html>, the Collapsed
   parameter, and the narrow-viewport media query — each set the same handful
   of variables instead of restating every rule. `--sb-x` is the display value
   for expanded-only chrome: unset by default, so each element falls back to
   its own natural display; `none` in the rail.

   The violet accent is deliberately local to this file. The product's primary
   is navy (`--p-700`) and every button, chart and pill outside the sidebar
   still uses it; changing the global token would restyle all four portals. */

.app-sidebar {
    --sb-w: 264px;
    --sb-pad: 16px;
    --sb-item-pad: 0 12px;
    --sb-item-gap: 12px;
    --sb-item-justify: flex-start;
    --sb-mini: none;
    --sb-brand-dir: row;

    --sb-accent: #6d5ae0;
    --sb-accent-strong: #5a46d4;
    --sb-accent-soft: #f1eeff;

    width: var(--sb-w);
    flex-shrink: 0;
    display: flex;
    flex-direction: column;
    background: var(--n-0);
    border-inline-end: 1px solid var(--border-subtle);
    /* The topbar and the content column come later in the DOM and paint their
       own backgrounds, so without a stacking context of its own the shadow
       lands underneath them and only shows against the page gutter. One above
       the topbar, which sits on --z-chrome, so the shadow still casts across
       the bar rather than stopping at the corner. */
    position: relative;
    z-index: calc(var(--z-chrome) + 1);
    box-shadow: var(--shadow-sidebar);
    transition: width var(--dur-mid) var(--ease-out);
}

/* ---------- Rail ---------- */

html.jork-sidebar-collapsed .app-sidebar,
.app-sidebar.is-collapsed {
    --sb-w: 72px;
    --sb-x: none;
    --sb-pad: 12px;
    --sb-item-pad: 0;
    --sb-item-gap: 0;
    --sb-item-justify: center;
    --sb-mini: flex;
    --sb-brand-dir: column;
}

/* ---------- Rail and drawer below 800px ----------

   Below this there is no rail. A 72px strip of icons is the worst of both
   readings of a menu: too narrow to say where any of them go, and still 72px
   off a 390px screen on every page, for a column that already has to carry a
   table. So the sidebar leaves the flex row, becomes a drawer the full width
   of a real menu, and parks itself off the side of the screen. The page gets
   the whole viewport; the menu, when it is asked for, gets 280px of it.

   Asking for it is `.tb-nav-toggle`, the arrow at the start of both topbars
   (AppTopbar, CandidateShell) - it only exists at this breakpoint, and it is
   the control the in-drawer chevron hands back to when the drawer closes. The
   two carry the same `data-jork-sidebar-toggle`, so js/jork-sidebar.js already
   drives both without knowing there are two.

   Off-canvas is a negative `margin-inline-start` rather than a transform: the
   logical property flips with `dir` on its own, so RTL parks the drawer off
   the right-hand edge with no second rule, and a fixed-position box adds
   nothing to the document's scrollable overflow in either direction. */

@media (max-width: 800px) {
    /* No rail here, so no gutter reserved for one either - the shell is left
       with its own padding, which is none, and the page spans the viewport.

       The drawer is the expanded menu at every moment - it is hidden by being
       off-screen, not by being narrow, so opening it slides one width rather
       than animating eight variables.

       `html.jork-sidebar-collapsed .app-sidebar` (the stored desktop
       preference) is (0,2,1) and would otherwise re-rail this, so it is named
       here at equal specificity and later source order. On a phone the stored
       preference is not what the control means. */
    .app-sidebar,
    html.jork-sidebar-collapsed .app-sidebar {
        /* `initial` on a custom property makes it guaranteed-invalid, so every
           `var(--sb-x, flex)` in the sidebar falls back to its own natural
           display - the expanded values are the absence of the rail's, not a
           second set to keep in step with them. */
        --sb-w: min(280px, 84vw);
        --sb-x: initial;
        --sb-pad: 16px;
        --sb-item-pad: 0 12px;
        --sb-item-gap: 12px;
        --sb-item-justify: flex-start;
        --sb-mini: none;
        --sb-brand-dir: row;

        position: fixed;
        inset-block: 0;
        inset-inline-start: 0;
        margin-inline-start: calc(0px - var(--sb-w));

        z-index: var(--z-drawer);
        box-shadow: 0 24px 60px rgba(15, 27, 45, 0.28);

        /* visibility, not display: a shut drawer must not hold tab stops, and
           `display:none` cannot be transitioned back in. The 0s delay holds it
           visible for the length of the slide out, then drops it. */
        visibility: hidden;
        transition: margin var(--dur-mid) var(--ease-out),
                    visibility 0s linear var(--dur-mid);
    }

    html.jork-sidebar-open .app-sidebar {
        margin-inline-start: 0;
        visibility: visible;
        transition: margin var(--dur-mid) var(--ease-out), visibility 0s;
    }

    /* The scrim. A pseudo-element rather than a node, because the shells are
       static-SSR on most pages and there is no circuit to render one; the tap
       that dismisses it is handled by js/jork-sidebar.js, which closes on any
       pointer outside the sidebar. */
    html.jork-sidebar-open .app-shell::after {
        content: "";
        position: fixed;
        inset: 0;
        z-index: calc(var(--z-drawer) - 1);
        background: rgba(15, 27, 45, 0.45);
    }
}

/* ---------- Brand ---------- */

.sb-brand {
    display: flex;
    flex-direction: var(--sb-brand-dir);
    align-items: center;
    gap: 12px;
    padding: 8px var(--sb-pad) 8px;
    border-bottom: 1px solid var(--border-subtle);
    margin-bottom: 12px;
}

.sb-brand-text {
    display: var(--sb-x, flex);
    flex-direction: column;
    flex: 1;
    min-width: 0;
    line-height: 1.2;
}

.sb-brand-name {
    font-size: 16px;
    font-weight: var(--fw-bold);
    letter-spacing: -0.02em;
    color: var(--text-primary);
}

.sb-brand-portal {
    font-size: 10px;
    font-weight: var(--fw-semibold);
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--n-400);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.sb-collapse {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 32px;
    height: 32px;
    flex-shrink: 0;
    border: 1px solid var(--border-default);
    border-radius: var(--r-lg);
    color: var(--n-400);
    background: var(--n-0);
    transition: background-color var(--dur-fast), color var(--dur-fast), border-color var(--dur-fast);
}

.sb-collapse:hover {
    background: var(--sb-accent-soft);
    border-color: var(--sb-accent);
    color: var(--sb-accent);
}

/* The chevron points the way the sidebar will move, so which of the two is
   drawn flips with the rail. Reusing --sb-x / --sb-mini keeps that to one
   variable each rather than a fourth rail-state selector. */
.sb-collapse .sb-chev-in  { display: var(--sb-x, block); }
.sb-collapse .sb-chev-out { display: var(--sb-mini); }

[dir="rtl"] .sb-collapse svg { transform: scaleX(-1); }

/* ---------- Nav ---------- */

.sb-nav {
    flex: 1;
    min-height: 0;
    padding: 4px var(--sb-pad) 14px;
    overflow-y: auto;
    overscroll-behavior: contain;
}

.sb-group + .sb-group { margin-top: 6px; }

/* Label and hairline are one row: the rule runs out to the sidebar edge beside
   the name, and on the rail — where the name is gone — the rule is what keeps
   the groups apart, with no second element to hide or show. */
.sb-group-head {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 16px 4px 8px;
}

.sb-group:first-child .sb-group-head { padding-top: 6px; }

.sb-group-label {
    display: var(--sb-x, block);
    font-size: 10px;
    font-weight: var(--fw-bold);
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--n-400);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.sb-group-rule {
    flex: 1;
    height: 1px;
    background: var(--border-subtle);
}

.nav-item {
    position: relative;
    display: flex;
    align-items: center;
    justify-content: var(--sb-item-justify);
    gap: var(--sb-item-gap);
    width: 100%;
    height: 44px;
    padding: var(--sb-item-pad);
    margin-bottom: 4px;
    border-radius: var(--r-xl);
    color: var(--n-700);
    background: transparent;
    font-size: 14px;
    font-weight: var(--fw-medium);
    text-align: start;
    text-decoration: none;
    transition: background-color var(--dur-fast), color var(--dur-fast);
}

/* Each row carries its own hue, rotating by position down the whole nav (see
   NavHues in AppSidebar.razor). The menu is built from the database and holds
   no colour of its own. Tile and badge share the pair, which is what makes the
   count read as belonging to its row.

   The pairs are a primitive rather than part of .nav-item, because the My Jobs
   rail (section 23) wears the same row and has to be able to wear the same
   tile. A row picks one by adding hue-N; nothing else in the product sets
   --tile-bg or --tile-fg. */
.hue-0 { --tile-bg: #eceefe; --tile-fg: #4f56d3; }
.hue-1 { --tile-bg: #e6f6ee; --tile-fg: #0f9d58; }
.hue-2 { --tile-bg: #fff1e0; --tile-fg: #e07c00; }
.hue-3 { --tile-bg: #f2ecfe; --tile-fg: #7c4ddb; }
.hue-4 { --tile-bg: #e6f2fd; --tile-fg: #0d7fd0; }
.hue-5 { --tile-bg: #e2f6f3; --tile-fg: #0d9488; }
.hue-6 { --tile-bg: #fdeaf3; --tile-fg: #db2777; }
.hue-7 { --tile-bg: #fff5da; --tile-fg: #b07900; }

.nav-tile {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 32px;
    height: 32px;
    flex-shrink: 0;
    border-radius: var(--r-lg);
    background: var(--tile-bg, var(--n-75));
    color: var(--tile-fg, var(--n-500));
    transition: background-color var(--dur-fast), color var(--dur-fast), box-shadow var(--dur-fast);
}

.nav-tile svg { color: inherit; }

.nav-item-label {
    display: var(--sb-x, block);
    flex: 1;
    min-width: 0;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.nav-item:hover { background: var(--n-50); }

.nav-item.active {
    background: linear-gradient(90deg, var(--sb-accent-soft) 0%, #faf9ff 100%);
    color: var(--sb-accent-strong);
    font-weight: var(--fw-semibold);
}

/* The active row inverts its tile: white on the tinted band, so the row reads
   as lifted rather than as one more coloured chip in the column. */
.nav-item.active .nav-tile {
    background: var(--n-0);
    color: var(--sb-accent);
    box-shadow: var(--shadow-xs);
}

/* Flush against the sidebar edge, so it reads as a marker on the rail rather
   than a border on the pill — hence the negative inset that cancels --sb-pad. */
.active-indicator {
    position: absolute;
    inset-inline-start: calc(-1 * var(--sb-pad));
    top: 50%;
    transform: translateY(-50%);
    width: 4px;
    height: 28px;
    background: var(--sb-accent);
    border-start-end-radius: 4px;
    border-end-end-radius: 4px;
    display: none;
}

.nav-item.active .active-indicator { display: block; }

/* Only on the row you are on: a chevron on every row would read as "these all
   expand", which none of them do. */
.nav-item .nav-go {
    display: none;
    flex-shrink: 0;
    color: var(--sb-accent);
}

.nav-item.active .nav-go { display: var(--sb-x, block); }

[dir="rtl"] .nav-item .nav-go { transform: scaleX(-1); }

.nav-badge {
    display: var(--sb-x, inline-flex);
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    min-width: 24px;
    height: 22px;
    padding: 0 7px;
    border-radius: var(--r-full);
    background: var(--tile-bg, var(--n-100));
    color: var(--tile-fg, var(--text-tertiary));
    font-size: 11px;
    font-weight: var(--fw-bold);
    font-variant-numeric: tabular-nums;
    line-height: 1;
}

/* ---------- User footer ---------- */

.sb-footer {
    position: relative;
    padding: 8px var(--sb-pad) 14px;
}

.sb-user {
    display: flex;
    align-items: center;
    justify-content: var(--sb-item-justify);
    gap: 10px;
    padding: 8px;
    border: 1px solid var(--border-subtle);
    border-radius: var(--r-xl);
    transition: border-color var(--dur-fast), background-color var(--dur-fast);
}

.sb-user:hover {
    background: var(--n-25);
    border-color: var(--border-default);
}

.sb-user .avatar {
    width: 36px;
    height: 36px;
    flex-shrink: 0;
    font-size: 12px;
}

.sb-user-avatar {
    width: 36px;
    height: 36px;
    flex-shrink: 0;
    border-radius: var(--r-full);
    object-fit: cover;
    border: 1px solid var(--border-subtle);
}

.sb-user-meta {
    display: var(--sb-x, block);
    flex: 1;
    min-width: 0;
}

.sb-user-name {
    font-size: 13.5px;
    font-weight: var(--fw-semibold);
    line-height: 1.3;
    color: var(--text-primary);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.sb-user-role {
    font-size: 11.5px;
    color: var(--n-500);
    line-height: 1.3;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.user-more {
    display: var(--sb-x, flex);
    align-items: center;
    justify-content: center;
    width: 30px;
    height: 30px;
    flex-shrink: 0;
    border: 1px solid var(--border-subtle);
    border-radius: var(--r-lg);
    color: var(--n-400);
    background: var(--n-0);
    transition: background-color var(--dur-fast), color var(--dur-fast), border-color var(--dur-fast);
}

.user-more:hover {
    background: var(--sb-accent-soft);
    border-color: var(--sb-accent);
    color: var(--sb-accent);
}

/* ---------- Account menu ---------- */

.sb-menu-backdrop {
    position: fixed;
    inset: 0;
    background: transparent;
    z-index: 60;
}

.sb-menu {
    position: absolute;
    inset-inline: var(--sb-pad);
    bottom: calc(100% - 4px);
    min-width: 210px;
    padding: 6px;
    background: var(--n-0);
    border: 1px solid var(--border-subtle);
    border-radius: var(--r-xl);
    box-shadow: var(--shadow-lg);
    z-index: 70;
}

.sb-menu-head {
    padding: 10px 12px 8px;
    margin-bottom: 4px;
    border-bottom: 1px solid var(--border-subtle);
}

.sb-menu-name {
    font-size: 12.5px;
    font-weight: var(--fw-semibold);
    color: var(--text-primary);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.sb-menu-email {
    font-size: 11px;
    color: var(--text-tertiary);
    font-family: var(--font-mono);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.sb-menu-rule {
    height: 1px;
    margin: 4px 0;
    background: var(--border-subtle);
}

.sb-menu-form { margin: 0; }

.sb-menu-signout {
    width: 100%;
    background: none;
    border: none;
    text-align: start;
}

.user-menu-item {
    display: flex;
    align-items: center;
    gap: 9px;
    padding: 8px 10px;
    border-radius: var(--r-md);
    font-size: 12.5px;
    font-weight: var(--fw-medium);
    line-height: 1;
    color: var(--text-primary);
    text-decoration: none;
    transition: background-color var(--dur-fast), color var(--dur-fast);
}

.user-menu-item:hover { background: var(--sb-accent-soft); color: var(--sb-accent-strong); }
.user-menu-item--danger { color: var(--danger-600); }
.user-menu-item--danger:hover { background: var(--danger-50); color: var(--danger-700); }


/* ============================================================
   4b. Shell: recruiter/admin topbar (AppTopbar)
   ============================================================ */

/* The bar holds two things now: a search of the whole portal on the left, and
   the actions on the right. The breadcrumb trail that used to open it is gone
   (PageHeader prints the section a page belongs to, inside the page), and the
   search box moved up out of the sidebar, where the rail could take it away.

   It was written in inline styles, which is why the box read as a grey smudge
   against a white bar: an inline style cannot express a hover, a focus ring or
   a breakpoint, so it had none of the three. Everything below is the same
   56px bar with those three restored. */

.app-topbar {
    display: flex;
    align-items: center;
    gap: var(--sp-4);
    height: 56px;
    flex-shrink: 0;
    padding-inline: var(--sp-12) var(--sp-10);
    background: var(--n-0);
    border-bottom: 1px solid var(--border-subtle);
    /* <main> scrolls its own content and comes later in the DOM, so a card
       scrolling up would pass over the bar's shadow without this.

       The level has to be --z-chrome, not the 1 it was. z-index on the header
       makes it a stacking context, so every panel the bar hangs below itself -
       menu search, the bell, the language switcher - is trapped at the bar's
       own level however high it sets its own z-index. At 1 that put all three
       under any positioned page content they opened over: .pg-head pins at 30
       and .jnw-head at --z-sticky, so a page header painted straight through
       an open dropdown. Page content cannot reach the bar itself - <main>
       clips it - so nothing is lost by putting the chrome above all of it.
       One below the sidebar, which casts across the full height of the shell. */
    position: relative;
    z-index: var(--z-chrome);
    box-shadow: var(--shadow-topbar);
}

/* ---------- Search ---------- */

/* A basis rather than a width, and an auto end margin instead of a spacer: the
   box gives its space back before the header can overflow a narrow screen, and
   the actions stay against the far edge whatever it does. */
/* ---------- The drawer's arrow ----------

   The one control that opens the menu below 800px, and the only reason the
   sidebar can park itself off-screen there: the chevron in the brand row goes
   with the drawer, so something outside it has to bring it back. Both topbars
   render one, at the start of the bar, where a phone user's thumb already
   looks for it.

   `display: none` by default rather than shown-and-hidden, so the desktop bar
   is untouched: above the breakpoint the sidebar is in the flex row and its
   own chevron is on screen, and a second control for the same thing would only
   disagree with it. Sized to the 44px touch target the public site's burger
   already uses, not to the 32px of the chevron it stands in for - that one is
   a mouse control on a desktop bar. */

.tb-nav-toggle {
    display: none;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    width: 40px;
    height: 40px;
    margin-inline-start: calc(0px - var(--sp-2));
    border: 1px solid var(--border-default);
    border-radius: var(--r-lg);
    background: var(--n-0);
    color: var(--text-secondary);
    cursor: pointer;
    transition: background-color var(--dur-fast), color var(--dur-fast), border-color var(--dur-fast);
}

.tb-nav-toggle:hover {
    background: var(--p-50);
    border-color: var(--p-700);
    color: var(--p-700);
}

/* The arrow points the way the drawer will travel, so it turns with `dir`
   for the same reason .sb-collapse does. */
[dir="rtl"] .tb-nav-toggle svg { transform: scaleX(-1); }

@media (max-width: 800px) {
    .tb-nav-toggle { display: inline-flex; }

    /* First on the bar on a phone too. The two-row rule at 560px gives the
       search `order: 2` and the actions `order: 1`; without a value of its own
       the arrow would take the default 0 and lead the row, which is right, but
       saying so keeps it there if either of those numbers ever moves. */
    .tb-nav-toggle { order: 0; }
}

.tb-search {
    /* Positioned so the menu-search dropdown can hang off the box itself: the
       panel has to line up with the field's own edges, and nothing else on the
       bar knows where those are. */
    position: relative;
    display: flex;
    align-items: center;
    gap: var(--sp-4);
    flex: 0 1 380px;
    min-width: 0;
    margin-inline-end: auto;
    height: var(--h-lg);
    padding-inline: var(--sp-5);
    background: var(--n-50);
    border: 1px solid var(--border-subtle);
    border-radius: var(--r-lg);
    transition: background-color var(--dur-fast), border-color var(--dur-fast), box-shadow var(--dur-fast);
}

/* Filled at rest so it reads as a field on the white bar; white on hover and
   focus, so typing in it feels like the box opening rather than staying shut. */
.tb-search:hover {
    background: var(--n-0);
    border-color: var(--border-default);
}

.tb-search:focus-within {
    background: var(--n-0);
    border-color: var(--border-focus);
    box-shadow: var(--shadow-focus);
}

.tb-search svg {
    color: var(--n-400);
    flex-shrink: 0;
    transition: color var(--dur-fast);
}

/* The magnifier takes the brand colour while the box has the caret — the one
   piece of the bar that says which control the keyboard is talking to. */
.tb-search:focus-within svg { color: var(--p-700); }

.tb-search-input {
    flex: 1;
    min-width: 0;
    border: none;
    outline: none;
    background: transparent;
    font-family: inherit;
    font-size: var(--fs-md);
    color: var(--text-primary);
    text-overflow: ellipsis;
}

.tb-search-input::placeholder { color: var(--text-disabled); }

/* Chrome/Safari draw their own clear button, which crowds the Cmd-K chip. */
.tb-search-input::-webkit-search-cancel-button { display: none; }

/* .kbd carries the shape; only the two things that differ inside a field are
   restated — it sits on a filled box, so it is white rather than sunken, and
   the doubled bottom border is too heavy at this size. */
.tb-kbd {
    flex-shrink: 0;
    background: var(--n-0);
    border-bottom-width: 1px;
    color: var(--n-400);
}

/* ---------- Menu search results ---------- */

/* Hangs below the box, matching its width exactly, so the panel reads as the
   box opening rather than as a second control that appeared beside it. */
.tb-menu {
    position: absolute;
    top: calc(100% + 4px);
    /* -1px, not 0: an absolutely positioned child is laid out against the
       padding box, so `0` tucks the panel inside the box's 1px border and it
       lands 2px narrow with its own border a pixel in from the field's. Pulling
       each edge out by that border width lines the two up. */
    inset-inline: -1px;
    z-index: 60;
    max-height: 340px;
    overflow-y: auto;
    padding: 4px;
    background: var(--n-0);
    border: 1px solid var(--border-subtle);
    border-radius: var(--r-lg);
    box-shadow: 0 10px 30px rgba(0, 0, 0, 0.14);
}

.tb-menu-item {
    display: flex;
    align-items: center;
    gap: var(--sp-4);
    padding: 7px 9px;
    border-radius: var(--r-md);
    color: var(--text-primary);
    text-decoration: none;
}

/* Hover and keyboard resolve to the same look, so moving between the mouse and
   the arrow keys never changes what "the selected row" looks like. */
.tb-menu-item:hover,
.tb-menu-item.is-active {
    background: var(--n-50);
}

.tb-menu-icon {
    display: flex;
    flex-shrink: 0;
    color: var(--n-400);
}

.tb-menu-item.is-active .tb-menu-icon { color: var(--p-700); }

.tb-menu-text {
    display: flex;
    flex-direction: column;
    min-width: 0;
}

.tb-menu-label {
    font-size: var(--fs-md);
    line-height: 1.3;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* The trail is the only thing telling two rows with the same name apart (three
   portals have a "Settings"), so it is quiet but never dropped. */
.tb-menu-trail {
    font-size: var(--fs-2xs);
    line-height: 1.4;
    color: var(--text-tertiary);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* ---------- Actions ---------- */

.tb-actions {
    display: flex;
    align-items: center;
    gap: var(--sp-3);
    flex-shrink: 0;
}

/* The bell is a 32px circle and cannot be anything else, so the rest of the
   row follows it rather than sitting beside it in a different shape: the
   language switcher's ghost button and the fallback bell are pills here. The
   search box stays a field — 10px, not a pill — because it is the one control
   on the bar you type into. */
.tb-actions .btn { border-radius: var(--r-full); }

.tb-sep {
    width: 1px;
    height: 20px;
    flex-shrink: 0;
    /* --border-subtle disappears against the white bar at one pixel wide; the
       rule has to be a shade stronger than the bar's own edge to read. */
    background: var(--border-default);
}

/* The AI entry point. Every other control on this bar is a ghost icon; this
   one is an offer, and the brand gold is what the product uses to say so. */
.tb-ai {
    display: inline-flex;
    align-items: center;
    gap: var(--sp-3);
    height: var(--h-md);
    padding-inline: var(--sp-5);
    background: var(--gold-50);
    border: 1px solid var(--gold-200);
    border-radius: var(--r-full);
    color: var(--gold-700);
    font-size: var(--fs-sm);
    font-weight: var(--fw-semibold);
    text-decoration: none;
    white-space: nowrap;
    transition: background-color var(--dur-fast), border-color var(--dur-fast);
}

.tb-ai:hover {
    background: var(--gold-100);
    border-color: var(--gold-300);
}

/* ---------- Topbar popovers (language, notifications, activity) ----------

   Three controls on the right of the bar hang a panel below themselves, and
   all three were written the same way in inline styles: `position: absolute;
   right: 0; width: 360px` (240 for the language list). An inline style cannot
   express a breakpoint, so the width was 360px at every screen size, and the
   panel was pinned to the right edge of a 32px button sitting near the right
   edge of the bar. On a 390px phone that put its left edge at 18px — behind
   the sidebar, which sits a level above the topbar and painted straight over
   it — and at 320px the panel started 52px off the left of the screen.

   The fix is one vocabulary for all three rather than three sets of inline
   numbers, and the piece that does the work on a phone is `.tb-pop-anchor`
   going `position: static`: the panel's containing block becomes the header
   itself, so "hang below and fill the width" needs no arithmetic and no
   hard-coded bar height. The bar is 56px on one row and about 96px on two,
   and the panel follows it either way. Because the header is the containing
   block, the panel also cannot reach back under the sidebar: the header
   starts where the sidebar ends. */

.tb-pop-anchor {
    position: relative;
}

.tb-pop {
    position: absolute;
    inset-block-start: calc(100% + 8px);
    inset-inline-end: 0;
    /* Above a page's own sticky chrome (--z-sticky) for the shells whose
       header is not itself a stacking context; inside .app-topbar, which is
       one, any positive value would do and this one is simply consistent. */
    z-index: var(--z-chrome);
    inline-size: 360px;
    max-inline-size: calc(100vw - 2 * var(--sp-4));
    overflow: hidden;
    background: var(--n-0);
    border: 1px solid var(--border-subtle);
    border-radius: var(--r-lg);
    box-shadow: 0 12px 32px rgba(15, 27, 45, 0.14);
}

/* The language list is a short column of names, not a feed. */
.tb-pop--sm { inline-size: 240px; }

/* The scrolling region inside a panel. A flat 380px is fine standing up and
   taller than the whole viewport lying down, which is how the notification
   list ran off the bottom of a landscape phone with no way back to the
   "View all" link under it. */
.tb-pop-scroll {
    max-block-size: min(380px, 50dvh);
    overflow-y: auto;
    overscroll-behavior: contain;
}

/* The click-away backdrop the interactive bells render under their panel.
   Below the panel, above everything else. */
.tb-pop-backdrop {
    position: fixed;
    inset: 0;
    z-index: calc(var(--z-chrome) - 1);
}

/* The transient "something arrived" card. It was pinned at top: 64px, which
   is the height of a one-row header; below 560px the bar wraps to two rows
   and the card landed on top of it.

   Same corner, card and entrance as the toast stack ("78. Shared: toasts"),
   so a notification and a save confirmation read as one family. */
.tb-toast {
    position: fixed;
    inset-block-start: 68px;
    inset-inline-end: 20px;
    z-index: var(--z-toast);
    inline-size: 360px;
    max-inline-size: calc(100vw - 40px);
    padding: 12px 14px;
    background: var(--surface-raised);
    /* The whole look, not just the position: the two bells wrote this card
       twice in inline styles, and an inline `border` shorthand would have
       taken the accent edge back off again. */
    border: 1px solid var(--border-subtle);
    border-inline-start: 3px solid var(--p-700);
    border-radius: var(--r-xl);
    box-shadow: 0 1px 2px rgba(15, 19, 32, 0.04), 0 12px 32px -8px rgba(15, 19, 32, 0.18);
    cursor: pointer;
    animation: q-toast-in var(--dur-mid) var(--ease-out);
}

/* ---------- Notification bell: panel, rows, toast ----------

   Both bells drew all of this in inline styles, and both drew it twice: the
   recruiter bell and the portal bell are separate components over separate
   tables, so every padding, tint and dot colour existed in two copies that had
   already drifted apart. The markup now comes from QNotificationPanel /
   QNotificationRow / QNotificationToast in Jork.Shared.UI and the look lives
   here, once.

   It is also what an inline style could never do: the rows have a hover and a
   focus ring, the day headers stick while the list scrolls under them, and the
   toast's countdown is an animation. A drop-down whose rows did not respond to
   the pointer at all was a large part of why the bell read as broken even on
   the pages where it was working. */

.qnote-pop {
    inline-size: 404px;
    padding: 0;
    display: flex;
    flex-direction: column;
}

/* ---------- Header ---------- */

.qnote-head-bar {
    display: flex;
    align-items: center;
    gap: var(--sp-4);
    padding: 14px 10px 12px 16px;
}

.qnote-head-title {
    font-size: var(--fs-base);
    font-weight: var(--fw-bold);
    color: var(--text-primary);
    letter-spacing: -0.01em;
}

/* The count, said in words, next to the title. The badge on the bell is gone
   from view the moment the panel opens over it, and "6 new" is the thing that
   tells you whether the list you are looking at needs anything from you. */
.qnote-newchip {
    font-size: var(--fs-2xs);
    font-weight: var(--fw-bold);
    color: var(--danger-700);
    background: var(--danger-50);
    border: 1px solid var(--danger-200);
    border-radius: var(--r-full);
    padding: 1px 7px;
    line-height: 1.6;
    white-space: nowrap;
}

.qnote-head-spacer { flex: 1 1 auto; }

.qnote-head-btn {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    block-size: 26px;
    padding: 0 9px;
    border: none;
    border-radius: var(--r-md);
    background: transparent;
    color: var(--p-700);
    font-family: inherit;
    font-size: var(--fs-xs);
    font-weight: var(--fw-semibold);
    cursor: pointer;
    white-space: nowrap;
}

.qnote-head-btn:hover { background: var(--p-50); }

/* Phone only — see the 640px block. On a desktop the panel is closed by
   clicking anywhere past its edge, and there is an invisible backdrop doing
   exactly that; on a phone the panel spans the header and has no edge to
   click past, which left no way out but the browser's back button. */
.qnote-close {
    inline-size: 36px;
    block-size: 36px;
    border: none;
    border-radius: var(--r-md);
    background: transparent;
    color: var(--text-secondary);
    display: none;
    align-items: center;
    justify-content: center;
    cursor: pointer;
}

.qnote-close:hover { background: var(--surface-hover); }

/* ---------- Filter ---------- */

/* "Mark all read" used to be the only tool in here, and it is the one action
   you cannot undo. Being able to look at just the unread ones is what most
   people wanted from it. */
.qnote-filter {
    display: flex;
    gap: 2px;
    margin: 0 14px 12px 16px;
    padding: 2px;
    background: var(--n-75);
    border-radius: var(--r-md);
}

.qnote-seg {
    flex: 1 1 0;
    min-inline-size: 0;
    block-size: 26px;
    border: none;
    border-radius: var(--r-sm);
    background: transparent;
    color: var(--text-secondary);
    font-family: inherit;
    font-size: var(--fs-sm);
    font-weight: var(--fw-medium);
    cursor: pointer;
}

.qnote-seg--on {
    background: var(--n-0);
    color: var(--text-primary);
    font-weight: var(--fw-semibold);
    box-shadow: var(--shadow-xs);
}

/* ---------- "Live updates are off" ----------

   The single most misleading state the old panel had. When the hub failed to
   start, the bell kept its last count and the list kept its last contents, and
   both looked exactly like a quiet inbox. Nothing on screen distinguished
   "nothing has happened" from "we stopped listening an hour ago". */
.qnote-live-off {
    display: flex;
    gap: 10px;
    align-items: flex-start;
    margin: 0 14px 12px 16px;
    padding: 10px 12px;
    background: var(--warning-50);
    border: 1px solid var(--gold-200);
    border-radius: var(--r-lg);
}

.qnote-live-off-icon {
    inline-size: 24px;
    block-size: 24px;
    flex-shrink: 0;
    border-radius: var(--r-full);
    background: var(--warning-100);
    color: var(--warning-700);
    display: flex;
    align-items: center;
    justify-content: center;
}

.qnote-live-off-text {
    display: flex;
    flex-direction: column;
    gap: 2px;
    min-inline-size: 0;
}

.qnote-live-off-title {
    font-size: var(--fs-sm);
    font-weight: var(--fw-semibold);
    color: var(--gold-900);
}

.qnote-live-off-body {
    font-size: var(--fs-xs);
    color: var(--warning-700);
    line-height: var(--lh-snug);
}

/* ---------- List ---------- */

.qnote-scroll { border-block-start: 1px solid var(--border-subtle); }

/* Day headers. Sticky inside the scroll box, so the run you are looking at
   always says which day it belongs to — the old list was one undifferentiated
   column where "2h" and "2d" sat next to each other with nothing between. */
.qnote-group {
    position: sticky;
    inset-block-start: 0;
    z-index: 1;
    padding: 9px 16px 6px;
    background: var(--n-25);
    border-block-end: 1px solid var(--n-75);
    font-size: var(--fs-2xs);
    font-weight: var(--fw-bold);
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--text-tertiary);
}

.qnote-row {
    inline-size: 100%;
    box-sizing: border-box;
    text-align: start;
    display: flex;
    gap: 11px;
    align-items: flex-start;
    padding: 12px 14px 12px 16px;
    border: none;
    border-block-end: 1px solid var(--n-75);
    background: transparent;
    font-family: inherit;
    cursor: pointer;
    transition: background var(--dur-fast) var(--ease-out);
}

.qnote-row:last-child { border-block-end: none; }
.qnote-row:hover { background: var(--surface-hover); }

/* Inset, not outset: the panel clips its own overflow, so a ring drawn outside
   the row is cut off on both sides. */
.qnote-row:focus-visible {
    outline: none;
    box-shadow: inset 0 0 0 2px var(--p-700);
}

/* Unread says so three ways — a rail, a tint and a heavier title — because one
   6px dot was easy to miss, and a dot is also the one cue that disappears
   entirely for anyone who cannot pick it out against the row behind it. The
   padding compensates for the 3px rail so the tiles stay in one column. */
.qnote-row--unread {
    background: var(--p-25);
    border-inline-start: 3px solid var(--p-700);
    padding-inline-start: 13px;
}

.qnote-row--unread:hover { background: var(--p-50); }

.qnote-row:not(.qnote-row--unread) .qnote-title {
    font-weight: var(--fw-medium);
    color: var(--n-700);
}

.qnote-row:not(.qnote-row--unread) .qnote-body { color: var(--text-tertiary); }

/* The kind, as a shape rather than a sentence. */
.qnote-tile {
    inline-size: 34px;
    block-size: 34px;
    flex-shrink: 0;
    border-radius: var(--r-md);
    display: flex;
    align-items: center;
    justify-content: center;
}

.qnote-main {
    flex: 1 1 auto;
    min-inline-size: 0;
    display: flex;
    flex-direction: column;
    gap: 3px;
}

.qnote-head {
    display: flex;
    align-items: baseline;
    gap: var(--sp-4);
}

.qnote-title {
    flex: 1 1 auto;
    min-inline-size: 0;
    font-size: var(--fs-md);
    font-weight: var(--fw-semibold);
    color: var(--text-primary);
    line-height: var(--lh-snug);
}

.qnote-age {
    flex-shrink: 0;
    font-size: var(--fs-2xs);
    color: var(--text-tertiary);
    font-family: var(--font-mono);
    white-space: nowrap;
}

/* Two lines, then an ellipsis. Unclamped, one long body pushed everything
   under it off the bottom of the panel. */
.qnote-body {
    font-size: var(--fs-sm);
    color: var(--text-secondary);
    line-height: 1.45;
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 2;
    line-clamp: 2;
    overflow: hidden;
}

.qnote-meta {
    display: flex;
    align-items: center;
    gap: var(--sp-3);
    margin-block-start: 3px;
    min-inline-size: 0;
}

.qnote-context {
    font-size: var(--fs-2xs);
    color: var(--text-tertiary);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* ---------- Empty ---------- */

.qnote-empty {
    padding: 44px 32px 40px;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--sp-5);
    text-align: center;
}

.qnote-empty-medal {
    inline-size: 56px;
    block-size: 56px;
    border-radius: var(--r-full);
    background: var(--n-75);
    color: var(--n-400);
    display: flex;
    align-items: center;
    justify-content: center;
}

.qnote-empty-title {
    font-size: var(--fs-base);
    font-weight: var(--fw-semibold);
    color: var(--text-primary);
}

.qnote-empty-body {
    font-size: var(--fs-sm);
    color: var(--text-secondary);
    line-height: var(--lh-normal);
}

/* ---------- Loading ----------

   Three rows, not one bar: a single placeholder reads as a row that failed
   rather than as a list on its way. The panel opens on the click now and fills
   a moment later, instead of the click appearing to do nothing until the query
   comes back. */
.qnote-skel {
    display: flex;
    gap: 11px;
    align-items: flex-start;
    padding: 12px 14px 12px 16px;
    border-block-end: 1px solid var(--n-75);
}

.qnote-skel-tile {
    inline-size: 34px;
    block-size: 34px;
    flex-shrink: 0;
    border-radius: var(--r-md);
    background: var(--n-100);
}

.qnote-skel-lines {
    flex: 1 1 auto;
    display: flex;
    flex-direction: column;
    gap: 7px;
    padding-block-start: 3px;
}

.qnote-skel-line {
    block-size: 8px;
    inline-size: 88%;
    border-radius: var(--r-xs);
    background: var(--n-75);
}

.qnote-skel-line--title {
    block-size: 9px;
    inline-size: 68%;
    background: var(--n-100);
}

.qnote-skel-line--short { inline-size: 44%; }

.qnote-skel-tile,
.qnote-skel-line { animation: qnote-pulse 1.4s var(--ease-in-out) infinite; }

@keyframes qnote-pulse {
    0%, 100% { opacity: 1; }
    50% { opacity: 0.55; }
}

/* ---------- Footer ---------- */

/* The whole width is the target. It used to be a 12px text link centred in the
   bar, which is a small thing to hit and a smaller thing to notice. */
.qnote-foot {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: var(--sp-3);
    padding: 11px 14px;
    border-block-start: 1px solid var(--border-subtle);
    background: var(--n-25);
    font-size: var(--fs-sm);
    font-weight: var(--fw-semibold);
    color: var(--p-700);
    text-decoration: none;
}

.qnote-foot:hover { background: var(--n-75); }

/* ---------- Arrival toast ----------

   Sits on .tb-toast for its corner, width and entrance; everything here is the
   part that changed. The old card was a single click target with no close
   button, so the only way to dismiss an interruption was to accept it — the
   click navigated away from whatever you were doing. */
.qnote-toast {
    padding: 0;
    cursor: default;
    overflow: hidden;
}

.qnote-toast-body {
    display: flex;
    gap: 11px;
    align-items: flex-start;
    padding: 13px 12px 13px 13px;
}

.qnote-toast-main {
    flex: 1 1 auto;
    min-inline-size: 0;
    display: flex;
    flex-direction: column;
    gap: 3px;
}

.qnote-toast-title {
    font-size: var(--fs-md);
    font-weight: var(--fw-semibold);
    color: var(--text-primary);
    line-height: var(--lh-snug);
}

.qnote-toast-text {
    font-size: var(--fs-sm);
    color: var(--text-secondary);
    line-height: 1.45;
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 2;
    line-clamp: 2;
    overflow: hidden;
}

.qnote-toast-actions {
    display: flex;
    gap: var(--sp-6);
    margin-block-start: var(--sp-3);
}

.qnote-toast-act {
    padding: 0;
    border: none;
    background: none;
    font-family: inherit;
    font-size: var(--fs-sm);
    font-weight: var(--fw-medium);
    color: var(--text-secondary);
    cursor: pointer;
}

.qnote-toast-act:hover { text-decoration: underline; }

.qnote-toast-act--primary {
    color: var(--p-700);
    font-weight: var(--fw-semibold);
}

.qnote-toast-x {
    inline-size: 24px;
    block-size: 24px;
    flex-shrink: 0;
    border: none;
    border-radius: var(--r-sm);
    background: transparent;
    color: var(--text-tertiary);
    display: flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
}

.qnote-toast-x:hover {
    background: var(--surface-hover);
    color: var(--text-primary);
}

/* How long it will stay if you do nothing. The card leaving on an unseen timer
   is indistinguishable from the card glitching.

   Same bar, same keyframe and the same --q-toast-dur knob as the central toast
   stack ("78. Shared: toasts"), because it is the same idea: a notification and
   a save confirmation should count down identically. */
.qnote-toast-timer {
    block-size: 3px;
    background: var(--n-100);
}

.qnote-toast-timer-fill {
    block-size: 3px;
    inline-size: 100%;
    background: var(--p-700);
    transform-origin: left center;
    animation: q-toast-time var(--q-toast-dur, 6000ms) linear forwards;
}

[dir="rtl"] .qnote-toast-timer-fill { transform-origin: right center; }

/* The pulse is decoration and stops. The countdown is not -- it is the only
   thing saying when the card leaves -- so it keeps running, exactly as
   .q-toast-bar does under the same rule. */
@media (prefers-reduced-motion: reduce) {
    .qnote-skel-tile,
    .qnote-skel-line { animation: none; }
}



/* ============================================================
   5. Shell: candidate portal
   Was Candidate/Navigation/CandidateShell.razor.css.
   ============================================================ */

/* What is left of this section once the candidate sidebar became the
   same object as the recruiter one.

   It used to be a block of !important overrides. The shell styled its
   sidebar inline - every width, colour and weight an attribute on the
   element - and an inline style cannot express a hover, a focus ring
   or a breakpoint, so the rows had none of the three, and collapsing
   the sidebar to a rail took a rule per property, each shouting over
   the markup. The shell now wears .app-sidebar and .nav-item from
   section 4, which carry all of that already, including the rail: the
   custom properties there do the collapsing, and nothing here has to
   fight anything.

   Three things stay candidate-specific, and they are all below. */

/* The accent. Section 4 keeps a violet local to itself because the
   product primary is navy and moving the token would restyle four
   portals - and by the same argument this portal keeps the navy: every
   band, pill and chart a candidate sees is navy, and the sidebar is
   the largest coloured surface on the screen. The tile hues are shared
   either way; it is the band, the marker and the chevron that differ.
   These four lines are the whole difference. */
.app-sidebar.cand-sidebar {
    --sb-accent: var(--p-700);
    --sb-accent-strong: var(--p-800);
    --sb-accent-soft: var(--p-50);
}

/* The rail is 64px wide here rather than section 4's 72px. Everything
   else about the rail is section 4's.

   Desktop only, and that `min-width` is load-bearing rather than tidiness.
   Below 800px section 4 no longer draws a rail at all - the sidebar is a
   drawer parked off the side of the screen - and this rule is (0,3,1) against
   that block's (0,2,1), so unscoped it won: on the candidate portal a
   "collapsed" carried over from the desktop pinned the open drawer at 64px.
   The class landed, the scrim came out, and the menu the arrow was supposed to
   reveal never appeared, at any phone width. There is no rail to size here, so
   there is nothing for this to say. */

@media (min-width: 801px) {
    html.jork-sidebar-collapsed .app-sidebar.cand-sidebar { --sb-w: 64px; }
}

/* The account menu sits where .user-more sits in the recruiter footer,
   but it is its own interactive island (CandidateAccountMenu) with the
   popover inside it, so it needs a positioning context of its own. */
.sb-user-menu {
    position: relative;
    display: var(--sb-x, flex);
    flex-shrink: 0;
}

/* ---------- The topbar ----------

   The shell wrote this bar in inline styles, and the search box carried
   min-width:340px among them. A floor is not a size: between 800px and about
   1000px - iPad portrait, a Surface, a half-screen laptop window - the box
   refused to shrink and slid straight under the profile pill, the language
   switcher and the bell, 113px of overlap at 820px. The one rule that rescued
   it was a min-width:0 !important at 800px, which is a cliff rather than a
   response: above it nothing gave, below it the floor was gone entirely.

   It is a basis here instead, so the box gives its space back gradually and
   the actions keep theirs. Below 560px the bar wraps and the search takes a
   line of its own - see the breakpoints at the end of this section. */

.candidate-topbar {
    display: flex;
    align-items: center;
    gap: var(--sp-8);
    height: 56px;
    flex-shrink: 0;
    padding-inline: var(--sp-12) var(--sp-10);
    background: var(--n-0);
    border-block-end: 1px solid var(--border-subtle);
    /* The same two lines .app-topbar carries, and for the same two reasons.
       Positioned, because on a phone the language switcher and the bell give
       up their own anchor and hang their panel off the header instead
       (section 4b); without this the panel would escape the bar entirely and
       measure itself against the viewport, which puts it back under the
       sidebar - the bug it was there to fix. And a level of its own, because
       a panel opened from the bar has to paint over page content that pins
       itself at --z-sticky. One below .app-sidebar, as on the shared bar. */
    position: relative;
    z-index: var(--z-chrome);
}

.candidate-topbar-main {
    display: flex;
    align-items: center;
    gap: var(--sp-4);
    flex: 1 1 auto;
    min-inline-size: 0;
}

.candidate-search {
    display: flex;
    align-items: center;
    gap: var(--sp-4);
    /* Grows into the room it is given and gives it back when there is none.
       Capped, because a search box that takes half a 1600px bar reads as the
       subject of the page rather than a way to leave it. */
    flex: 1 1 340px;
    min-inline-size: 0;
    max-inline-size: 420px;
    height: var(--h-md);
    padding-inline: var(--sp-5);
    background: var(--surface-sunken);
    border: 1px solid var(--border-subtle);
    border-radius: var(--r-md);
    transition: background-color var(--dur-fast), border-color var(--dur-fast),
                box-shadow var(--dur-fast);
}

/* The same three states the shared bar's box has. An inline style could not
   express any of them, so this box had none. */
.candidate-search:hover { background: var(--n-0); border-color: var(--border-default); }

.candidate-search:focus-within {
    background: var(--n-0);
    border-color: var(--border-focus);
    box-shadow: var(--shadow-focus);
}

.candidate-search > svg { flex-shrink: 0; color: var(--n-400); }
.candidate-search:focus-within > svg { color: var(--p-700); }

/* min-inline-size is the whole point of this rule. A flex child's default
   minimum is its own min-content width, so the placeholder held this input at
   151px inside a box that had shrunk to 80px, and the text ran out through
   the field's rounded edge - measured at 320px, 104px past the padding. */
.candidate-search-input {
    flex: 1 1 auto;
    min-inline-size: 0;
    border: none;
    outline: none;
    background: transparent;
    font-family: inherit;
    font-size: var(--fs-sm);
    color: var(--text-primary);
    text-overflow: ellipsis;
}

.candidate-search-input::placeholder { color: var(--text-disabled); }
.candidate-search-input::-webkit-search-cancel-button { display: none; }

.candidate-topbar-actions {
    display: flex;
    align-items: center;
    gap: var(--sp-5);
    flex-shrink: 0;
}




/* ============================================================
   5b. Topbar breakpoints (shared + candidate)

   Both bars are one row of a search box and a row of actions, and both broke
   the same way: the actions are flex-shrink:0, so every pixel the bar loses
   comes out of the search. Measured on the shared bar, the box went 380 ->
   154 at 430px, 114 at 390, 84 at 360 and 44 at 320 - and the input inside it
   was four pixels wide, a field nobody can read a placeholder in, let alone
   type into.

   There is no arithmetic that fixes that in one row. On a 320px phone the
   rail takes 64px and the four action controls take about 174 of the 224 that
   are left, so a usable search and the actions cannot share a line. Below
   560px the bar wraps instead: the actions keep the first line, the search
   takes the second at full width, and nothing is hidden to make room. The bar
   grows from 56px to about 96px, which nothing measures - it is flex-shrink:0
   inside a flex column and <main> takes what is left. The bell's dropdown
   hangs off the bell, so it follows.

   Between 560px and 800px there is room for one row, and these two blocks
   only tighten it.
   ============================================================ */

/* ---------- Tablet ----------
   The overlap at this size was the search box's old inline min-width, not its
   cap, so the cap stays: a basis that shrinks and a maximum that stops the
   box taking half the bar are both wanted here. Only the side padding gives. */

@media (max-width: 1024px) {
    .candidate-topbar { padding-inline: var(--sp-8) var(--sp-6); }
}

/* ---------- Popovers: anchored to the header, not to the button ----------

   360px of panel hung off a 32px button has nowhere to go once the bar itself
   is not much wider than the panel. Dropping the anchor's positioning hands
   the panel's containing block to the header, and it spans that instead: full
   width inside the bar's own gutters, below whichever number of rows the bar
   has ended up with. Nothing is hidden and nothing needs measuring.

   640px rather than the 560px the bar wraps at, because the panel has run out
   of room before the bar has: at 600px the header is 528px wide and a
   right-hung 360px panel already crowds the search box beside it. */

@media (max-width: 640px) {
    .tb-pop-anchor { position: static; }

    .tb-pop {
        inset-inline: var(--sp-4);
        inset-block-start: calc(100% + 6px);
        inline-size: auto;
        max-inline-size: none;
    }

    /* A panel that starts below a two-row header has less screen under it than
       one below a single row, and the header is the part that grows. */
    .tb-pop-scroll { max-block-size: min(380px, 42dvh); }

    /* The panel spans the header here, so there is no edge to click past and
       the backdrop is doing nothing a thumb can reach. */
    .qnote-pop { padding: 0; }
    .qnote-close { display: flex; }

    /* Touch targets. 26px is a comfortable click and an uncomfortable tap. */
    .qnote-seg { block-size: 32px; }
    .qnote-head-btn { block-size: 34px; }
    .qnote-foot { padding: 14px; font-size: var(--fs-md); }
    .qnote-toast-x { inline-size: 36px; block-size: 36px; }
    .qnote-toast-act { padding-block: 6px; }

    /* Top of the screen like every toast, inside the same gutters as
       everything else on a phone. It briefly covers a header that may be two
       rows tall; it leaves on its own. */
    .tb-toast {
        inset-block-start: calc(10px + env(safe-area-inset-top));
        inset-inline: 12px;
        inline-size: auto;
        max-inline-size: none;
    }
}

/* ---------- Phone: two rows ---------- */

@media (max-width: 560px) {
    .app-topbar,
    .candidate-topbar {
        height: auto;
        min-block-size: 56px;
        flex-wrap: wrap;
        align-content: center;
        padding-block: var(--sp-4);
        row-gap: var(--sp-4);
    }

    /* Actions first so the icons keep the top line, where a phone user looks
       for them, and the search reads as a field under the bar rather than as
       something wedged between them. */
    .tb-actions,
    .candidate-topbar-actions {
        order: 1;
        margin-inline-start: auto;
    }

    .tb-search,
    .candidate-topbar-main {
        order: 2;
        /* !important only on the basis, and only to answer the one at 800px:
           that block sets flex-basis:180px !important, and it has to, because
           the base .tb-search rule is further down the file and would
           otherwise win. Without this the shared bar stayed on one row here
           while the candidate bar wrapped - the same two bars behaving
           differently at the same width. */
        flex: 1 1 100% !important;
        /* The auto end margin is what held the actions right on one row.
           On two it would push the search off its own line. */
        margin-inline-end: 0;
    }

    /* Full width now, so the basis and the cap both stop applying. */
    .candidate-search {
        flex: 1 1 auto;
        max-inline-size: none;
    }

    /* A field the width of the screen can afford to be a comfortable height,
       and this is the one control on the bar that takes a caret. */
    .tb-search,
    .candidate-search { height: var(--h-lg); }
}

/* The narrowest phones. The AI entry point is a destination, not a control -
   it is in the sidebar as well - so its 32px goes to the row that holds the
   bell and the language switcher, both of which are only here. */

@media (max-width: 380px) {
    .app-topbar,
    .candidate-topbar { padding-inline: var(--sp-4); }

    .tb-actions { gap: var(--sp-2); }
    .candidate-topbar-actions { gap: var(--sp-3); }
}


/* ============================================================
   6. Recruiter: list pages (Clients, Jobs)
   Was Recruiter/Pages/Clients.razor.css.
   ============================================================ */

/* The recruiter list-page system. Clients was the first page built on it and
   the .cli-* prefix is from that page; Jobs renders the same three bands from
   the same rules, and anything genuinely Jobs-only lives in section 6b below.
   Changing a rule here changes both pages - that is the point of the block.

   Scoped styles for the recruiter Clients list.

   The page carried every rule as an inline style attribute, which meant no
   hover states, no focus rings, no media queries and no sticky header — and
   two tables on the same screen (the list and the import preview) that were
   styled by hand and drifted apart. Everything visual now lives here; the
   markup keeps only what is genuinely data-driven (a bar width, a status
   tone).

   Naming follows the dashboard's convention (.dash-*) so the two recruiter
   surfaces read as one system: same tokens, same header shape, same table
   metrics, same skeleton. */

/* Three stacked bands - header, stat row, working surface - matching .dash.
   The header and the numbers used to live inside the table card, which made
   the page's headline facts read as chrome attached to the grid; lifted out,
   they are the page and the card is the tool. */
.cli {
    display: flex;
    flex-direction: column;
    gap: 16px;
    padding-block-end: 24px;
}

/* ============================================================
   Header
   ============================================================ */

/* A pinned toolbar rather than a block that scrolls away: with fifty rows the
   title and the two actions that create clients used to leave the screen on
   the first flick.

   Full-bleed by design - the negative inline margins cancel <main>'s 8px
   padding so the bar meets both edges, and inset-block-start matches that
   same 8px so it pins flush to the very top rather than 8px down. Both
   numbers track main's padding; change one and change the other. */
.cli-head {
    position: sticky;
    inset-block-start: -8px;
    z-index: 30;
    display: flex;
    align-items: center;
    gap: 12px;
    flex-wrap: wrap;
    min-height: var(--cli-head-h);
    margin-block-start: -8px;
    margin-inline: -8px;
    padding: 8px 16px;
    /* White rather than the page ground, and lifted off the content with a
       downward shadow instead of a hairline alone: pinned, the bar is a layer
       over the scroll, and a border on its own reads as part of it. Both
       offsets are positive so nothing bleeds up over the topbar. */
    background: var(--surface-raised);
    border-block-end: 1px solid var(--border-subtle);
    box-shadow: 0 1px 2px rgba(15, 19, 32, 0.05), 0 4px 10px -4px rgba(15, 19, 32, 0.10);
}

/* The table header sticks below this bar, not under it. The identity block's
   two-line stack is shorter than its own 40px tile, so the tile sets the
   height: 40px plus the bar's 8px padding above and below. */
.cli { --cli-head-h: 56px; }

/* ---------- Identity block ----------
   Marked tile, parent section, page, scope. A page title on its own left the
   bar carrying one word and no answer to "what am I looking at" - and once
   the bar is pinned, whatever it carries is the only context that survives a
   scroll. */

.cli-id {
    flex: 1 1 320px;
    min-width: 0;
    display: flex;
    align-items: center;
    gap: 12px;
}

.cli-id-mark {
    display: grid;
    place-items: center;
    width: 40px;
    height: 40px;
    flex-shrink: 0;
    border-radius: var(--r-lg);
    background: var(--p-700);
    color: var(--text-inverse);
    box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.10), 0 1px 2px rgba(15, 19, 32, 0.14);
}

/* The three lines are set by their line-heights, not by a gap: a gap between
   an uppercase label, a heading and a caption stacks three different
   half-leadings and the block drifts off the tile's centre. */
.cli-id-txt {
    min-width: 0;
    display: flex;
    flex-direction: column;
}

.cli-id-crumb {
    align-self: flex-start;
    display: inline-flex;
    align-items: center;
    gap: 2px;
    margin-inline-start: -2px;
    font-size: 10px;
    font-weight: var(--fw-bold);
    letter-spacing: 0.07em;
    text-transform: uppercase;
    line-height: 1.3;
    color: var(--p-700);
    text-decoration: none;
}

.cli-id-crumb:hover { text-decoration: underline; }

.cli-id-crumb:focus-visible {
    outline: none;
    border-radius: var(--r-xs);
    box-shadow: var(--shadow-focus);
}

/* Title and the filter chip share one line, so the block stays two lines
   whether or not a filter is on. */
.cli-id-line {
    display: flex;
    align-items: center;
    gap: 8px;
    min-width: 0;
    margin-block-start: 1px;
}

/* 17px: the middle line of a three-line stack, not a standalone page title.
   It still reads as the heading because the lines around it are 10px caps and
   12.5px caption - the hierarchy comes from the stack, so the type does not
   have to carry it alone in a bar that never leaves the screen. */
.cli-title {
    margin: 0;
    font-size: var(--fs-xl);
    font-weight: var(--fw-bold);
    letter-spacing: -0.015em;
    line-height: 1.25;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.cli-actions {
    display: flex;
    align-items: center;
    gap: 8px;
    flex-wrap: wrap;
    margin-inline-start: auto;
}

/* Separates what looks at the table from what changes it. The 900px
   breakpoint already hides this - the cluster drops to its own line there and
   a divider inside a full-width row separates nothing - but the rule had no
   element to hide until now. */
.cli-actions-sep {
    width: 1px;
    height: 20px;
    flex-shrink: 0;
    margin-inline: 2px;
    background: var(--border-default);
}

/* Columns and Export as one segmented control. As bare ghost buttons they
   read as plain text sitting next to two real buttons - the hierarchy was
   ghost/ghost/outline/solid with nothing saying the first two belong
   together. Boxed, the toolbar is three objects: a view group, an import,
   and the one action that creates something. */
.cli-viewgrp {
    display: inline-flex;
    align-items: center;
    background: var(--surface-raised);
    border: 1px solid var(--border-default);
    border-radius: var(--r-md);
}

/* The buttons give up their own chrome to the group: square corners, no
   radius of their own, and a hairline between the two segments. The width is
   pinned to the height because .btn-icon sizes from --h-lg while these sit in
   a bordered box that is one pixel shorter on each side. */
.cli-viewgrp .btn {
    width: 34px;
    height: 34px;
    padding: 0;
    border-radius: 0;
    background: transparent;
    color: var(--text-secondary);
}

.cli-viewgrp .btn:hover { color: var(--text-primary); }

.cli-viewgrp > :first-child .btn { border-start-start-radius: var(--r-md); border-end-start-radius: var(--r-md); }
.cli-viewgrp > :last-child  .btn { border-start-end-radius: var(--r-md);  border-end-end-radius: var(--r-md); }
.cli-viewgrp > * + * { border-inline-start: 1px solid var(--border-subtle); }

/* Keeps the focus ring on top of the neighbouring segment's border. */
.cli-viewgrp .btn:focus-visible { position: relative; z-index: 1; }

/* ---------- Brand pairing ----------
   The mark is a blue J with a gold crescent, and the header is where the
   product introduces itself, so its controls carry that pairing rather than
   the neutral greys.

   Every control here gets a hairline and a navy label: as bare ghost text the
   two secondary controls read as links floating next to a real button, with
   nothing marking where each one starts. Gold is spent once - on the icon of
   the single action that creates something - because an accent used twice is
   no longer an accent.

   Scoped to .cli-head. .btn-ghost is a shared primitive used by every portal;
   this is the Clients header's treatment, not a change to the button. */

.cli-head .cli-viewgrp {
    border-color: var(--p-100);
}

.cli-head .cli-viewgrp > * + * {
    border-inline-start-color: var(--p-100);
}

.cli-head .cli-viewgrp .btn { color: var(--p-700); }

.cli-head .cli-viewgrp .btn:hover {
    background: var(--p-50);
    color: var(--p-800);
}

/* Direct children only: the view group's segments take their chrome from the
   group's own box, and a border on each would draw it twice. */
.cli-head .cli-actions > .btn-ghost {
    border: 1px solid var(--p-100);
    background: var(--surface-raised);
    color: var(--p-700);
    box-shadow: var(--shadow-xs);
}

.cli-head .cli-actions > .btn-ghost:hover {
    border-color: var(--p-300);
    background: var(--p-50);
    color: var(--p-800);
}

/* The gold, once. White would make the icon a second label; gold makes it the
   mark's accent, and at 6.7:1 on the navy it is still legible as a glyph. */
.cli-head .btn-primary > svg { color: var(--gold-300); }

.cli-head .cli-actions-sep { background: var(--p-100); }

.cli-sig {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    height: 24px;
    padding: 0 9px;
    border: 1px solid var(--border-default);
    border-radius: var(--r-full);
    background: var(--n-0);
    font-size: var(--fs-xs);
    font-weight: var(--fw-medium);
    color: var(--text-secondary);
    white-space: nowrap;
    text-decoration: none;
    transition: border-color var(--dur-fast) var(--ease-out),
                background-color var(--dur-fast) var(--ease-out);
}

.cli-sig b {
    font-family: var(--font-mono);
    font-variant-numeric: tabular-nums;
    font-weight: var(--fw-semibold);
    color: var(--text-primary);
}

.cli-sig b,
.cli-sig svg { flex-shrink: 0; }

.cli-sig-brand {
    background: var(--p-50);
    border-color: var(--p-100);
    color: var(--p-700);
}

.cli-sig-brand b { color: var(--p-800); }

/* Inline beside the title rather than stacked under it: at the full 24px it
   set that line's height and pushed the pinned bar taller the moment you
   filtered, which slid the table header - offset by the bar's own height -
   underneath it. */
.cli-sig-sm {
    height: 19px;
    padding: 0 7px;
    gap: 4px;
    font-size: var(--fs-2xs);
}

a.cli-sig:hover { border-color: currentColor; }

a.cli-sig:focus-visible {
    outline: none;
    box-shadow: var(--shadow-focus);
}

/* ============================================================
   Stat row

   The three headline numbers, in the shared KpiTile so they carry the same
   weight as the dashboard's. They sit here rather than in the header's
   identity block: as a line of running text under the title they were a
   caption, and these are the page's headline facts.
   ============================================================ */

.cli-kpis {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: 12px;
}

/* KpiTile renders its own root, so this is the one place the scope has to be
   pierced. It only adds a hover lift - if the rewrite ever stops matching,
   the tiles are unchanged rather than broken. Same rule as .dash-kpis. */
.cli-kpis > div {
    height: 100%;
    transition: box-shadow var(--dur-fast) var(--ease-out),
                border-color var(--dur-fast) var(--ease-out);
}

.cli-kpis > div:hover {
    border-color: var(--border-strong);
    box-shadow: var(--shadow-sm);
}

/* Matches a real tile's height so the skeleton does not resize on load. */
.cli-skel-kpi { height: 86px; }

/* ============================================================
   Control row

   Search, facets, filter and sort - everything that narrows the result set.
   The table's own view controls (columns, export) act on the whole table
   rather than on the query, so they sit in the header action cluster; this
   row is left to the query alone.
   ============================================================ */

/* Vertical padding tracks the table's row padding so the controls and the
   rows they filter sit on one rhythm. */
.cli-bar {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 12px 16px 12px 20px;
    border-block-end: 1px solid var(--border-subtle);
}

/* QFilterBar renders its own root, so this is the one place the scope has to
   be pierced. It only takes the free width - if the component is ever
   restyled the bar is unchanged rather than broken. */
.cli-bar .cli-bar-f {
    flex: 1 1 auto;
    min-width: 0;
}

/* The anchor for a dropdown panel. */
.cli-pop { position: relative; }

/* ============================================================
   Table
   ============================================================ */

.cli-table {
    width: 100%;
    border-collapse: collapse;
    font-size: 13px;
}

/* Sticky within <main>, which is the page's scroll container. With fifty rows
   the header used to scroll away in the first flick and every number below it
   became unlabelled. z-index stays low: a row's open action menu lifts its own
   cell to 65 and must still pass over the header. */
.cli-table thead th {
    position: sticky;
    /* Below the pinned page toolbar, not underneath it - at 0 the column
       labels slid behind the bar and were unreadable while scrolling. */
    inset-block-start: calc(var(--cli-head-h) - 8px);
    z-index: 2;
    padding: 0;
    text-align: start;
    background: var(--n-25);
    border-block-end: 1px solid var(--border-subtle);
    white-space: nowrap;
}

/* Uppercase at 2xs is already low-legibility type; at --text-tertiary on
   --n-25 it fell below comfortable reading contrast, so the labels the
   numbers depend on were the faintest thing in the table. */
.cli-th {
    display: block;
    padding: 11px 12px;
    font-size: var(--fs-2xs);
    font-weight: var(--fw-semibold);
    letter-spacing: 0.05em;
    text-transform: uppercase;
    color: var(--text-secondary);
}

/* A sortable header is a real button: the sort was reachable only through the
   filter bar's dropdown, which is three clicks away from "who has the most
   open roles". */
button.cli-th {
    width: 100%;
    display: flex;
    align-items: center;
    gap: 4px;
    /* Matches the plain .cli-th above - at tertiary the sortable columns were
       visibly fainter than the ones you cannot sort, which reads as disabled. */
    color: var(--text-secondary);
    transition: color var(--dur-fast) var(--ease-out);
}

button.cli-th:hover { color: var(--text-primary); }

button.cli-th:focus-visible {
    outline: none;
    box-shadow: inset var(--shadow-focus);
}

/* Right-aligns a numeric column's label over its numbers. Both declarations
   are needed: the sortable headers are flex buttons, the plain ones are
   blocks. */
.cli-th-end {
    justify-content: flex-end;
    text-align: end;
}

/* The arrow is hidden until the column is the sorted one, but it still
   occupies its width - otherwise every label shifts sideways on sort. */
.cli-th-ar {
    display: inline-flex;
    width: 11px;
    flex-shrink: 0;
    opacity: 0;
    transition: opacity var(--dur-fast) var(--ease-out);
}

th[aria-sort]:not([aria-sort="none"]) .cli-th { color: var(--text-primary); }
th[aria-sort]:not([aria-sort="none"]) .cli-th-ar { opacity: 1; }
button.cli-th:hover .cli-th-ar { opacity: 0.45; }

/* A two-line identity cell against 11px of padding gave rows barely 4px of
   air above the logo. The extra 3px is what lets the eye travel down the
   name column instead of reading a wall. */
.cli-table td {
    padding: 14px 12px;
    vertical-align: middle;
    border-block-start: 1px solid var(--border-subtle);
}

.cli-table tbody tr:first-child td { border-block-start: none; }

/* Header cells delegate their padding to the inner .cli-th so a sortable one
   can be a full-bleed button; the edge inset therefore lands on the inner
   element in the head and on the cell itself in the body. */
.cli-table thead th:first-child .cli-th,
.cli-table tbody td:first-child { padding-inline-start: 20px; }

.cli-table thead th:last-child .cli-th,
.cli-table tbody td:last-child { padding-inline-end: 16px; }

.cli-row {
    cursor: pointer;
    transition: background-color var(--dur-fast) var(--ease-out);
}

.cli-row:hover { background: var(--n-25); }

/* Selection has to survive a hover: without its own tone the row a bulk action
   is about to change looked exactly like the row under the pointer. */
.cli-row.is-sel { background: var(--p-50); }
.cli-row.is-sel:hover { background: var(--p-100); }

/* Numeric and chip columns shrink to fit so the width goes to the names. */
.cli-num,
.cli-tight {
    white-space: nowrap;
    width: 1%;
}

.cli-num {
    text-align: end;
    font-family: var(--font-mono);
    font-variant-numeric: tabular-nums;
}

.cli-dim { color: var(--text-tertiary); }
.cli-muted { color: var(--text-secondary); }

/* The checkbox column: as narrow as the control, and flush against the name
   cell so the two read as one hit area. */
.cli-table .cli-sel { width: 32px; }
.cli-table tbody td.cli-sel { padding-inline-end: 0; }

/* ---------- Client cell ---------- */

.cli-who {
    display: flex;
    align-items: center;
    gap: 10px;
    min-width: 0;
}

.cli-who-b {
    min-width: 0;
}

/* An anchor, not a row-level click handler alone: the row stays clickable, but
   the name is now reachable by keyboard and opens in a new tab on middle
   click, which is how a list of records is actually worked through. */
/* The one column everything else is read relative to, so it carries a little
   more size and a tighter fit than the 13px body - at parity it competed with
   the country code and the date for attention. */
.cli-name {
    display: block;
    font-size: 13.5px;
    font-weight: var(--fw-semibold);
    letter-spacing: -0.008em;
    color: inherit;
    text-decoration: none;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.cli-row:hover .cli-name { color: var(--p-700); }

.cli-name:focus-visible {
    outline: none;
    box-shadow: var(--shadow-focus);
    border-radius: var(--r-sm);
}

.cli-meta {
    display: flex;
    align-items: center;
    gap: 6px;
    margin-block-start: 2px;
    font-size: var(--fs-xs);
    color: var(--text-tertiary);
    min-width: 0;
}

.cli-code {
    font-family: var(--font-mono);
    flex-shrink: 0;
}

.cli-meta-x {
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* ---------- Status ----------
   A dot and a word, not a second pill. Every row already carries a tier pill;
   two filled chips per row turned a scannable list into confetti. */

.cli-status {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    font-size: var(--fs-sm);
    font-weight: var(--fw-medium);
    color: var(--text-secondary);
    white-space: nowrap;
}

.cli-dot {
    width: 7px;
    height: 7px;
    border-radius: 50%;
    flex-shrink: 0;
}

/* ---------- Pipeline bar ----------
   The count alone made every client look alike at a glance. The bar is scaled
   to the largest pipeline on the page, so it answers "who is busiest" without
   claiming to be an absolute measure. */

.cli-pipe {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: 8px;
}

.cli-pipe-n {
    font-family: var(--font-mono);
    font-variant-numeric: tabular-nums;
    color: var(--text-secondary);
    min-width: 22px;
    text-align: end;
}

/* A lighter track: at a pipeline of 0 the bar is all track, and against
   --n-100 an empty row read as a broken control rather than an empty one. */
.cli-pipe-bar {
    position: relative;
    width: 46px;
    height: 5px;
    border-radius: var(--r-full);
    background: var(--n-75);
    overflow: hidden;
    flex-shrink: 0;
}

.cli-pipe-fill {
    position: absolute;
    inset-block: 0;
    inset-inline-start: 0;
    max-width: 100%;
    border-radius: inherit;
    background: linear-gradient(90deg, var(--p-500), var(--p-700));
}

/* ---------- AI fit meter ----------

   A number and a meter rather than a filled pill. A coloured chip on every
   row of a long list turns a scannable table into confetti, and the bar ranks
   two people faster than two numbers do - the colour is then free to mean one
   thing only: which band the score is in.

   The band sets one custom property and both the number and the fill read it,
   so a threshold change is a single edit. Shared rather than the inbox's own:
   Applications scores one application against its job and Candidates scores a
   person against the book, and a recruiter reading both in one sitting should
   not have to learn the column twice. */

.cli-fit {
    --cli-fit: var(--warning-700);
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: 8px;
}

.cli-fit.is-strong { --cli-fit: var(--success-600); }
.cli-fit.is-fair   { --cli-fit: var(--gold-700); }
.cli-fit.is-low    { --cli-fit: var(--warning-700); }

.cli-fit-n {
    font-family: var(--font-mono);
    font-variant-numeric: tabular-nums;
    font-weight: var(--fw-semibold);
    color: var(--cli-fit);
    min-width: 20px;
    text-align: end;
}

.cli-fit-bar {
    position: relative;
    width: 38px;
    height: 5px;
    border-radius: var(--r-full);
    background: var(--n-75);
    overflow: hidden;
    flex-shrink: 0;
}

.cli-fit-fill {
    position: absolute;
    inset-block: 0;
    inset-inline-start: 0;
    max-width: 100%;
    border-radius: inherit;
    background: var(--cli-fit);
}

/* The action cell is the positioning context for its menu. Body cells only:
   the matching header cell must keep the position:sticky it inherits from
   .cli-table thead th. */
.cli-table .cli-act { width: 36px; }
.cli-table tbody td.cli-act { position: relative; }

/* ============================================================
   Bulk action bar

   Sticky to the bottom of the scroll area instead of pinned under the last
   row: with fifty rows selected from the top of the list, the bar used to sit
   a full screen below the rows it was acting on.
   ============================================================ */

.cli-bulk {
    position: sticky;
    inset-block-end: 0;
    z-index: 3;
    display: flex;
    align-items: center;
    gap: 10px;
    flex-wrap: wrap;
    padding: 10px 16px 10px 20px;
    border-block-start: 1px solid var(--p-100);
    background: var(--p-50);
}

.cli-bulk-n {
    font-size: var(--fs-sm);
    font-weight: var(--fw-semibold);
    color: var(--p-800);
    font-variant-numeric: tabular-nums;
}

.cli-bulk-msg {
    font-size: var(--fs-xs);
    color: var(--text-secondary);
}

/* ============================================================
   Footer / pagination
   ============================================================ */

.cli-foot {
    display: flex;
    align-items: center;
    gap: 10px;
    flex-wrap: wrap;
    padding: 12px 16px 12px 20px;
    border-block-start: 1px solid var(--border-subtle);
    font-size: var(--fs-xs);
    color: var(--text-secondary);
}

.cli-foot-l {
    flex: 1;
    min-width: 0;
    font-variant-numeric: tabular-nums;
}

.cli-pager {
    display: flex;
    align-items: center;
    gap: 8px;
}

.cli-pager span { font-variant-numeric: tabular-nums; }

/* ============================================================
   Empty states
   ============================================================ */

.cli-table td.cli-empty-cell { padding: 24px; }

/* ============================================================
   Column menu
   ============================================================ */

.cli-colitem {
    display: flex;
    align-items: center;
    gap: 8px;
}

.cli-check {
    display: inline-flex;
    width: 14px;
    flex-shrink: 0;
    color: var(--p-700);
}

/* ============================================================
   Loading skeleton - the shape of the page it is loading, so the layout
   stops jumping once the rows land.
   ============================================================ */

.cli-skel {
    background: linear-gradient(90deg, var(--n-100) 25%, var(--n-75) 38%, var(--n-100) 60%);
    background-size: 300% 100%;
    border-radius: var(--r-lg);
    animation: cli-shimmer 1.5s var(--ease-in-out) infinite;
}

/* Matches .cli-id-mark so the header does not resize on load. */
.cli-skel-mark {
    width: 40px;
    height: 40px;
    flex-shrink: 0;
    border-radius: var(--r-lg);
}

.cli-skel-rows {
    display: flex;
    flex-direction: column;
    gap: 1px;
    padding: 1px 0;
}

@keyframes cli-shimmer {
    from { background-position: 150% 0; }
    to   { background-position: -50% 0; }
}

@media (prefers-reduced-motion: reduce) {
    .cli-skel { animation: none; }
    .cli-att-card:hover { transform: none; }
}

/* ============================================================
   Overlays - drawers and the archive dialog

   Same chrome for the add/edit drawer and the import drawer; they were two
   hand-styled copies that had already drifted by a few pixels of padding.
   ============================================================ */

.cli-scrim {
    position: fixed;
    inset: 0;
    z-index: var(--z-drawer);
    background: var(--surface-overlay);
}

/* Transparent scrims that exist only to catch the next click and close a
   popover. They must sit under the popover but over the page. */
.cli-scrim-bare {
    position: fixed;
    inset: 0;
    z-index: 60;
    background: transparent;
}

.cli-scrim-bare.is-under { z-index: 59; }

/* The "reset SLA targets" opt-in, shown only when the tier actually changed. */
.cli-optin {
    display: flex;
    align-items: flex-start;
    gap: 8px;
    padding: 9px 11px;
    border: 1px solid var(--gold-200);
    border-radius: var(--r-md);
    background: var(--gold-50);
    font-size: var(--fs-sm);
    color: var(--text-secondary);
    cursor: pointer;
    line-height: var(--lh-snug);
}

.cli-optin input { margin-block-start: 2px; }

.cli-optin-h {
    display: block;
    margin-block-start: 3px;
    font-size: var(--fs-xs);
    color: var(--text-tertiary);
}

/* ---------- Drawer form: mark, sections ----------

   The add/edit client drawer is eleven fields and two decisions. Stacked flat
   they read as one undifferentiated column, so the form now names its sections
   and says in the footer what the disabled button is waiting for. */

/* The client's tile, drawn from the name as it is typed. is-empty is the state
   before there is anything to draw — a dashed well rather than a coloured tile
   with a placeholder letter, which would read as a real initial. */
.cli-dw-mark {
    display: grid;
    place-items: center;
    width: 44px;
    height: 44px;
    flex-shrink: 0;
    border-radius: var(--r-lg);
    color: var(--text-inverse);
    font-size: var(--fs-lg);
    font-weight: var(--fw-bold);
    letter-spacing: -0.01em;
    box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.18);
    transition: background var(--dur-mid) var(--ease-out);
}

.cli-dw-mark.is-empty {
    background: var(--n-0);
    border: 1px dashed var(--border-strong);
    color: var(--text-disabled);
    box-shadow: none;
}

/* QFormField carries its own inline margin-bottom. Inside a section that spaces
   its children with gap, that margin doubles the rhythm — 28px between fields
   where the design system says 14. */
.cli-ff { margin-block-end: 0 !important; }

/* A labelled group that is not an input: the tier cards, the status row. Label
   metrics match QFormField's so the two kinds of group line up. */
.cli-field {
    display: flex;
    flex-direction: column;
    gap: 6px;
}

.cli-field-l {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    font-size: var(--fs-xs);
    font-weight: var(--fw-semibold);
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--text-tertiary);
}

.cli-field-x {
    padding: 0;
    border: none;
    background: transparent;
    font: inherit;
    font-size: var(--fs-2xs);
    font-weight: var(--fw-semibold);
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--text-brand);
    cursor: pointer;
}

.cli-field-x:hover { text-decoration: underline; }

.cli-field-h {
    font-size: var(--fs-xs);
    color: var(--text-tertiary);
    line-height: var(--lh-snug);
}

/* ---------- Tier as a choice, not a letter ----------

   The tier seeds the SLA targets and the fee schedule of every job opened for
   the client, and a three-option select spelled "A / B / C" said none of that.
   Each card carries the consequence of picking it. */

.cli-tiers {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: 8px;
}

.cli-tier {
    padding: 10px;
    border: 1px solid var(--border-default);
    border-radius: var(--r-md);
    background: var(--n-0);
    font: inherit;
    text-align: start;
    cursor: pointer;
    transition: border-color var(--dur-fast) var(--ease-out), background var(--dur-fast) var(--ease-out);
}

.cli-tier:hover {
    border-color: var(--border-strong);
    background: var(--n-25);
}

.cli-tier-k {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 6px;
    font-size: var(--fs-sm);
    font-weight: var(--fw-bold);
}

.cli-tier-v {
    display: block;
    margin-block-start: 3px;
    font-size: var(--fs-2xs);
    color: var(--text-tertiary);
    line-height: var(--lh-snug);
}

.cli-tier-tick {
    display: grid;
    place-items: center;
    width: 14px;
    height: 14px;
    flex-shrink: 0;
    border-radius: var(--r-full);
    border: 1.5px solid var(--border-strong);
    color: transparent;
}

.cli-tier[aria-pressed="true"] {
    border-color: var(--p-700);
    background: var(--p-50);
    box-shadow: inset 0 0 0 1px var(--p-700);
}

.cli-tier[aria-pressed="true"] .cli-tier-k { color: var(--p-900); }

.cli-tier[aria-pressed="true"] .cli-tier-tick {
    background: var(--p-700);
    border-color: var(--p-700);
    color: var(--text-inverse);
}


/* ---------- Status as four states ----------

   Two across rather than four: the labels are translated, and Burmese status
   names do not fit a quarter of a 480px drawer. */
.cli-states {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 4px;
    padding: 3px;
    border: 1px solid var(--border-default);
    border-radius: var(--r-md);
    background: var(--surface-sunken);
}

.cli-states button {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 6px;
    min-width: 0;
    padding: 6px 8px;
    border: none;
    border-radius: var(--r-sm);
    background: transparent;
    font: inherit;
    font-size: var(--fs-sm);
    font-weight: var(--fw-semibold);
    color: var(--text-secondary);
    cursor: pointer;
    transition: background var(--dur-fast) var(--ease-out);
}

.cli-states button:hover { color: var(--text-primary); }

.cli-states button[aria-pressed="true"] {
    background: var(--n-0);
    color: var(--text-primary);
    box-shadow: var(--shadow-sm);
}

.cli-states-d {
    width: 6px;
    height: 6px;
    flex-shrink: 0;
    border-radius: var(--r-full);
    background: var(--n-300);
}

/* ---------- Website prefix ---------- */

.cli-affix { display: flex; align-items: stretch; }

.cli-affix-p {
    display: flex;
    align-items: center;
    padding-inline: 10px;
    border: 1px solid var(--border-default);
    border-inline-end: none;
    border-start-start-radius: var(--r-md);
    border-end-start-radius: var(--r-md);
    background: var(--surface-sunken);
    color: var(--text-tertiary);
}

/* QInput styles itself inline, so the corners it should give up to the prefix
   have to be taken back with !important. */
.cli-affix .q-input {
    flex: 1;
    min-width: 0;
    border-start-start-radius: 0 !important;
    border-end-start-radius: 0 !important;
}

/* ---------- Duplicate legal name ----------

   Advisory, never blocking: two entities do legitimately share a name. It says
   what already exists and offers to open it. */
.cli-dupe {
    display: flex;
    align-items: flex-start;
    gap: 8px;
    padding: 9px 11px;
    border: 1px solid var(--info-100);
    border-radius: var(--r-md);
    background: var(--info-50);
    font-size: var(--fs-sm);
    color: var(--info-700);
    line-height: var(--lh-snug);
}

.cli-dupe > svg {
    flex-shrink: 0;
    margin-block-start: 1px;
}

.cli-dupe-list {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    margin-block-start: 4px;
}

.cli-dupe-list a {
    font-weight: var(--fw-semibold);
    color: var(--info-700);
    text-decoration: underline;
}

.cli-dupe-h {
    margin-block-start: 4px;
    font-size: var(--fs-xs);
    color: var(--text-tertiary);
}

/* ---------- Footer: readiness, then commitment ---------- */

/* The ring is how much of the record exists; the sentence is what the disabled
   button is waiting for. A dead Create button that never says why is the single
   most-reported thing about this form. */
.cli-ready {
    display: flex;
    align-items: center;
    gap: 8px;
    min-width: 0;
    font-size: var(--fs-xs);
    color: var(--text-tertiary);
}

.cli-ready > span {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.cli-ready-r {
    width: 18px;
    height: 18px;
    flex-shrink: 0;
}

.cli-ready-bg {
    fill: none;
    stroke: var(--n-150);
    stroke-width: 3;
}

.cli-ready-fg {
    fill: none;
    stroke: var(--p-700);
    stroke-width: 3;
    stroke-linecap: round;
    transition: stroke-dasharray var(--dur-mid) var(--ease-out);
}

.cli-ready.is-ready .cli-ready-fg { stroke: var(--success-500); }

/* Split primary: the button does the common thing, the caret holds the two
   other places a recruiter goes straight after creating a client. */
.cli-split {
    position: relative;
    display: flex;
}

/* :not(:only-child) — in edit mode there is no caret, and the button keeps all
   four corners. */
.cli-split-a:not(:only-child) {
    border-start-end-radius: 0;
    border-end-end-radius: 0;
}

.cli-split-b {
    width: 28px;
    padding: 0;
    border-inline-start: 1px solid rgba(255, 255, 255, 0.2);
    border-start-start-radius: 0;
    border-end-start-radius: 0;
}

.cli-split-m {
    inset-block-start: auto;
    inset-block-end: calc(100% + 6px);
    inset-inline-end: 0;
    min-width: 216px;
    z-index: 5;
}

.cli-menu-catch {
    position: absolute;
    inset: 0;
    z-index: 4;
}

.cli-alert {
    padding: 8px 10px;
    border: 1px solid var(--danger-100);
    border-radius: var(--r-md);
    background: var(--danger-50);
    font-size: var(--fs-sm);
    color: var(--danger-700);
    line-height: var(--lh-snug);
}

/* ---------- Page-level result banners ----------

   .cli-alert says "this form failed"; these say "the thing you asked for
   happened", and they sit in the page band between the header and the tiles
   rather than inside a dialog. Jobs raises one after publish, status change
   and archive - actions that finish somewhere the row you clicked may no
   longer be. */

.cli-banner {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 9px 12px;
    border-radius: var(--r-md);
    font-size: var(--fs-sm);
    line-height: var(--lh-snug);
}

.cli-banner svg { flex-shrink: 0; }

.cli-banner-ok {
    border: 1px solid var(--success-100);
    background: var(--success-50);
    color: var(--success-700);
}

.cli-banner-bad {
    border: 1px solid var(--danger-100);
    background: var(--danger-50);
    color: var(--danger-700);
}

/* ---------- Archive dialog ---------- */

.cli-modal {
    position: fixed;
    inset-block-start: 50%;
    inset-inline-start: 50%;
    transform: translate(-50%, -50%);
    z-index: calc(var(--z-modal) + 1);
    width: 420px;
    max-width: calc(100vw - 32px);
    padding: 20px;
    border: 1px solid var(--border-default);
    border-radius: var(--r-xl);
    background: var(--n-0);
    box-shadow: var(--shadow-xl);
}

.cli-scrim-modal { z-index: var(--z-modal); }

.cli-modal-h {
    display: flex;
    align-items: center;
    gap: 10px;
    margin-block-end: 10px;
}

.cli-modal-ic {
    display: grid;
    place-items: center;
    width: 30px;
    height: 30px;
    flex-shrink: 0;
    border-radius: var(--r-md);
    background: var(--warning-50);
    color: var(--warning-700);
}

.cli-modal-t {
    font-size: var(--fs-lg);
    font-weight: var(--fw-bold);
    letter-spacing: -0.01em;
}

.cli-modal-b {
    margin: 0 0 16px;
    font-size: var(--fs-md);
    color: var(--text-secondary);
    line-height: var(--lh-snug);
}

.cli-modal .cli-alert { margin-block-end: 12px; }

/* ---------- Client CSV import (.cimp) ----------

   The import drawer, in two states: a spec before a file is chosen and a
   preview after. It is the same QDrawer as the add/edit form above it -
   same mark tile, same .drw-sec / .drw-legend sections, same footer ring -
   so only what goes inside those sections is here.

   The old version of this panel was a bare <input type=file> over a line of
   comma-separated column names, and both of its failure modes were silent -
   a misspelled header parsed to an empty field, and a Tier the parser did
   not know was dropped without a word. What is here is the same import with
   both of those said out loud: .cimp-map and .cimp-hdr for the header row,
   .cimp-why for the individual cell. */

/* The tile the form fills with the client's initials. A file has no initials
   worth drawing, so it carries its type instead - and the empty state is the
   same dashed well, with the verb in it. */
.cimp-mark { background: var(--p-700); }

.cimp-mark-x {
    font-family: var(--font-mono);
    font-size: var(--fs-2xs);
    font-weight: var(--fw-bold);
    letter-spacing: 0.04em;
}

/* A control belonging to a section, on the legend's own line. The legend is
   a flex row, so this only has to claim the space to its end. The colour
   reset is not optional: .drw-legend paints every svg under it in the muted
   legend grey, which would take the button's own icon with it. */
.cimp-legend-a { margin-inline-start: auto; }
.cimp-legend-a svg { color: inherit; }

.cimp-drop {
    position: relative;
    padding: 30px 16px;
    border: 2px dashed var(--border-default);
    border-radius: var(--r-lg);
    background: var(--n-25);
    text-align: center;
    transition: border-color var(--dur-fast) var(--ease-out),
                background-color var(--dur-fast) var(--ease-out);
}

.cimp-drop:hover {
    border-color: var(--p-300);
    background: var(--p-50);
}

/* The caption is decoration painted over the input that covers the panel.
   Without this the pointer lands on the text and the panel stops taking
   the drop. */
.cimp-drop-in {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 7px;
    pointer-events: none;
}

.cimp-drop-ic {
    display: grid;
    place-items: center;
    width: 40px;
    height: 40px;
    border-radius: var(--r-xl);
    background: var(--p-50);
    color: var(--p-700);
}

.cimp-drop-t {
    font-size: var(--fs-md);
    font-weight: var(--fw-semibold);
    color: var(--text-primary);
}

.cimp-drop-s {
    font-size: var(--fs-sm);
    color: var(--text-tertiary);
}

.cimp-drop input[type="file"],
.cimp-file-swap input[type="file"] {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    opacity: 0;
    cursor: pointer;
}

/* ---------- The spec ---------- */

/* Two columns of eleven names beats one column of eleven: the list is a
   reference to scan, not a sequence to read. Boxed, because it is the one
   block in the drawer that is reference material rather than a control. */
.cimp-cols {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(215px, 1fr));
    gap: 2px 14px;
    margin: 0;
    padding: 11px 13px;
    list-style: none;
    border: 1px solid var(--border-subtle);
    border-radius: var(--r-md);
    background: var(--surface-sunken);
}

.cimp-col {
    display: flex;
    align-items: baseline;
    gap: 6px;
    min-width: 0;
    padding-block: 2px;
}

.cimp-col-n {
    font-family: var(--font-mono);
    font-size: var(--fs-xs);
    color: var(--text-secondary);
}

/* The one column whose absence stops a row, so it is the one thing in this
   list that carries a colour. */
.cimp-col-req {
    font-size: var(--fs-2xs);
    font-weight: var(--fw-semibold);
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--danger-600);
}

/* The values the parser will accept, beside the column that takes them -
   the other half of the two silent failures this panel exists to end. */
.cimp-col-v {
    min-width: 0;
    font-size: var(--fs-2xs);
    color: var(--text-tertiary);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* ---------- The chosen file ---------- */

.cimp-file {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 9px 12px;
    border: 1px solid var(--border-subtle);
    border-radius: var(--r-md);
    background: var(--surface-sunken);
}

.cimp-file-ic {
    display: grid;
    place-items: center;
    width: 28px;
    height: 28px;
    flex-shrink: 0;
    border-radius: var(--r-md);
    background: var(--p-50);
    color: var(--p-700);
}

.cimp-file-m { flex: 1; min-width: 0; }

.cimp-file-n {
    font-size: var(--fs-sm);
    font-weight: var(--fw-semibold);
    color: var(--text-primary);
}

.cimp-file-s {
    font-size: var(--fs-xs);
    font-variant-numeric: tabular-nums;
    color: var(--text-tertiary);
}

.cimp-file-swap {
    position: relative;
    flex-shrink: 0;
    display: inline-flex;
}

/* The swap control is a styled span, not a button: the file input covers it
   and takes the click. Without pointer-events:none the pointer lands on the
   label instead and the swap stops opening the picker — the same reason
   .cimp-drop-in above is inert. */
.cimp-file-swap-btn {
    pointer-events: none;
}

/* ---------- Header mapping ---------- */

.cimp-map {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 6px;
    font-size: var(--fs-xs);
    color: var(--text-tertiary);
}

.cimp-map-ok {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    color: var(--success-700);
    font-weight: var(--fw-medium);
}

/* Neither of these is an error - the import will run either way - so the
   sentence stays in the reading colour and only the header names carry a
   tone. Struck through for a column the parser will not read; plain for one
   the file simply does not have. */
.cimp-hdr {
    display: flex;
    align-items: baseline;
    flex-wrap: wrap;
    gap: 5px;
    font-size: var(--fs-xs);
    color: var(--text-tertiary);
}

.cimp-hdr-l { font-weight: var(--fw-semibold); }

.cimp-ign,
.cimp-miss {
    padding: 1px 6px;
    border-radius: var(--r-full);
    font-family: var(--font-mono);
    font-size: var(--fs-2xs);
}

.cimp-ign {
    background: var(--warning-50);
    color: var(--warning-700);
    text-decoration: line-through;
}

.cimp-miss {
    background: var(--n-100);
    color: var(--text-tertiary);
}

/* ---------- Tally / filter ---------- */

.cimp-tally {
    display: flex;
    gap: 8px;
}

/* Each count is also the filter for the rows it counts, which is why these
   are buttons and not stat tiles. Pressed, the tile takes its own tone as a
   ring rather than a fill: a filled tile the width of a number reads as a
   badge, and the number stops being the thing being looked at. */
.cimp-tal {
    flex: 1;
    display: flex;
    flex-direction: column;
    gap: 1px;
    padding: 8px 10px;
    border: 1px solid var(--border-subtle);
    border-radius: var(--r-md);
    background: var(--surface-raised);
    text-align: start;
    cursor: pointer;
    transition: border-color var(--dur-fast) var(--ease-out),
                background-color var(--dur-fast) var(--ease-out);
}

.cimp-tal:hover { background: var(--surface-hover); }
.cimp-tal:focus-visible { outline: none; box-shadow: var(--shadow-focus); }

.cimp-tal-n {
    font-size: var(--fs-lg);
    font-weight: var(--fw-bold);
    font-variant-numeric: tabular-nums;
    letter-spacing: -0.02em;
    color: var(--text-primary);
    line-height: 1.1;
}

.cimp-tal-l {
    font-size: var(--fs-2xs);
    font-weight: var(--fw-semibold);
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: var(--text-tertiary);
}

.cimp-tal.is-ok  .cimp-tal-n { color: var(--success-700); }
.cimp-tal.is-warn .cimp-tal-n { color: var(--warning-700); }
.cimp-tal.is-bad .cimp-tal-n { color: var(--danger-600); }

.cimp-tal.is-on { border-color: var(--p-600); background: var(--p-50); }
.cimp-tal.is-ok.is-on { border-color: var(--success-600); background: var(--success-50); }
.cimp-tal.is-warn.is-on { border-color: var(--warning-600); background: var(--warning-50); }
.cimp-tal.is-bad.is-on { border-color: var(--danger-600); background: var(--danger-50); }

/* ---------- Preview ---------- */

/* clip, not hidden: the box is here to round the preview's corners, and a
   scroll container here would be the thing the header pins to - see
   .tbl-clip. The rows scroll in .drw-body, so that is where the header
   measures from. */
.cimp-wrap {
    border: 1px solid var(--border-subtle);
    border-radius: var(--r-md);
    overflow: clip;
}

.cimp-tbl {
    width: 100%;
    border-collapse: collapse;
    font-size: var(--fs-sm);
}

/* Pinned, like every other header in the app: a CSV drops two hundred rows
   in here and the check being made on them is column-by-column - which cell
   is the bad email, which row has no legal name - so the labels have to
   survive the scroll that reaches row forty. */
.cimp-tbl th {
    position: sticky;
    inset-block-start: var(--tbl-top, 0px);
    z-index: 2;
    /* border-collapse hands a header's bottom border to the first row, which
       scrolls out from under it; an inset shadow stays with the cell. */
    box-shadow: inset 0 -1px 0 var(--border-subtle);
    padding: 7px 10px;
    text-align: start;
    background: var(--n-25);
    font-size: var(--fs-2xs);
    font-weight: var(--fw-semibold);
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--text-tertiary);
    white-space: nowrap;
}

.cimp-tbl td {
    padding: 7px 10px;
    border-block-start: 1px solid var(--border-subtle);
    color: var(--text-secondary);
    vertical-align: top;
}

/* The line number in the file, which is how the row gets found again in the
   spreadsheet it came from. */
.cimp-c-r {
    width: 1%;
    padding-inline-end: 4px !important;
    font-family: var(--font-mono);
    font-size: var(--fs-2xs);
    font-variant-numeric: tabular-nums;
    color: var(--text-tertiary);
    text-align: end;
}

.cimp-nm {
    display: block;
    color: var(--text-primary);
}

/* Why this row is marked, under the name it is about. */
.cimp-why {
    display: flex;
    align-items: flex-start;
    gap: 4px;
    margin-block-start: 2px;
    font-size: var(--fs-2xs);
    line-height: var(--lh-snug);
}

.cimp-why svg { flex-shrink: 0; margin-block-start: 1px; }
.cimp-why.is-bad { color: var(--danger-600); }
.cimp-why.is-warn { color: var(--warning-700); }

/* A skipped row is still shown - it is what the count refers to - but it is
   greyed and marked at the edge, so the rows that will be created read as
   the body of the table and these read as the exceptions to it. */
.cimp-tbl tr.is-skip td { background: var(--danger-50); }
.cimp-tbl tr.is-skip .cimp-nm { color: var(--text-tertiary); }
.cimp-tbl tr.is-skip td:first-child { box-shadow: inset 2px 0 0 var(--danger-500); }
.cimp-tbl tr.is-warn td:first-child { box-shadow: inset 2px 0 0 var(--warning-500); }

.cimp-empty {
    margin: 0;
    padding: 18px 0;
    text-align: center;
    font-size: var(--fs-xs);
    color: var(--text-tertiary);
}

/* The footer's sentence is mostly counts, and counts that change width as
   the filter moves make the ring beside them jump. */
.cimp .cli-ready > span { font-variant-numeric: tabular-nums; }

/* ---------- Notes (jobs brief import) ---------- */

.cli-note {
    padding: 10px 12px;
    border: 1px solid var(--border-subtle);
    border-radius: var(--r-md);
    background: var(--n-25);
    font-size: var(--fs-xs);
    color: var(--text-secondary);
    line-height: var(--lh-snug);
}

.cli-note code {
    font-family: var(--font-mono);
    font-size: var(--fs-2xs);
}

.cli-hint {
    font-size: var(--fs-xs);
    color: var(--text-tertiary);
}

/* ============================================================
   Breakpoints

   Columns drop in reverse order of how often they are the reason someone
   opened this page: on Clients the client, its status and its open roles
   survive longest; on Jobs the client, the status and the SLA do. Hiding by
   class rather than by shrinking keeps the row action menu out of a
   horizontal scroll container, which would clip it.
   ============================================================ */

@media (max-width: 1240px) {
    .col-since { display: none; }
    .col-prio { display: none; }
}

@media (max-width: 1080px) {
    .col-country { display: none; }
    .col-loc { display: none; }
    .cli-pipe-bar { display: none; }
    /* Three tiles across stop fitting their labels before the table does;
       the odd one takes the full width rather than leaving a gap. */
    .cli-kpis { grid-template-columns: repeat(2, minmax(0, 1fr)); }
    .cli-kpis > :last-child { grid-column: 1 / -1; }
}

@media (max-width: 900px) {
    .col-pipeline { display: none; }
    .cli-fit-bar { display: none; }
    .cli-actions { margin-inline-start: 0; width: 100%; }
    /* The actions drop to their own line here, so the pinned bar is two rows
       tall and the table header has to clear both. */
    .cli-actions-sep { display: none; }
    /* The actions drop to their own line here, so the pinned bar is the tile
       plus a wrapped action row and the table header has to clear both. */
    .cli { --cli-head-h: 100px; }
}

@media (max-width: 700px) {
    .col-tier { display: none; }
    .col-days { display: none; }
    .cli-table thead th:first-child .cli-th,
    .cli-table tbody td:first-child,
    .cli-head,
    .cli-bar,
    .cli-bulk,
    .cli-foot { padding-inline-start: 14px; }
}

/* ---------- The list as a fixed-height region ----------

   The same shape the applications inbox and the Jobs list use, and here for
   the same reason: the column labels the figures depend on, the filter that
   produced the list and the pager that says how much is left all used to
   leave the screen on the first flick through fifty clients. Only the rows
   should move, so they get their own scrollport and everything around them
   stays where the recruiter put it.

   The chain is four boxes deep because the card is a component. .cl-page
   takes <main>'s height, and every box between it and the rows has to agree
   to be smaller than its content - a flex item refuses by default to be
   shorter than the fifty rows inside it, and that refusal hands the scroll
   back to <main>. .cl-card is passed to QCard as its surface class, so the
   one rule that reaches inside the component is the body hook it documents. */

/* block-size, not min-block-size: the height has to be definite or the boxes
   below it size to their content, the scrollport is as tall as fifty rows,
   and nothing scrolls anywhere except the page.

   The floor is the short-window escape hatch, and it belongs here rather than
   further down the chain: max(100%, 600px) is still a definite height, so the
   region keeps working, the card is drawn at its full size and the page takes
   the overflow. 600px is this page's chrome - a pinned header, the tile row
   and the filter bar - plus about four rows. */
.cl-page {
    block-size: 100%;
    min-block-size: 600px;
    /* The 24px tail belongs to a page that scrolls past its own end. Here it
       is dead space taken from the rows. */
    padding-block-end: 8px;
}

/* min-block-size: 0 on every box down the chain, and it is not optional: a
   flex item's default minimum is its content, so leaving it out sizes each
   box to fifty rows and the only thing that scrolls is the page.
   min-inline-size is the same bargain on the other axis - without it the
   widest column's min-content width propagates back up and the sideways
   scroll lands on <main> as a page-wide scrollbar. */
.cl-list {
    flex: 1 1 auto;
    min-block-size: 0;
    min-inline-size: 0;
    display: flex;
}

.cl-card,
.cl-card > .q-card-body {
    flex: 1 1 auto;
    min-block-size: 0;
    min-inline-size: 0;
    display: flex;
    flex-direction: column;
}

/* The one scroll container on the page. Only the block axis was asked for and
   both are given - overflow on one axis gives it on the other - which costs
   nothing here: this page answers "too many columns" with the column toggle
   in its header rather than by scrolling sideways. */
.cl-scroll {
    flex: 1 1 auto;
    min-block-size: 0;
    min-inline-size: 0;
    overflow: auto;
}

/* The labels pin to the top of their own scrollport. The shared rule measures
   from the pinned page toolbar, which is not the box they scroll inside any
   more - at 48px they would sit a toolbar's height below the first row and
   let it slide underneath. */
.cl-scroll .cli-table thead th { inset-block-start: 0; }

/* ============================================================
   6b. Recruiter: Jobs list

   The Jobs list is the same page as Clients and reuses everything above -
   header, tiles, filter bar, table metrics, bulk bar, drawers, skeleton. Only
   what Jobs has and Clients does not lives here: the age column's three
   states, and the AI strip that used to be a 320px right rail.
   ============================================================ */

/* ---------- Age of the requisition ----------

   Three states, not a gradient: on track, getting close, past it. The class is
   picked against the role's own SLA where it has one, so a 90-day executive
   search is not painted red for outliving a 20-day contract fill. */

.jb-days {
    font-family: var(--font-mono);
    font-variant-numeric: tabular-nums;
    font-weight: var(--fw-semibold);
    color: var(--text-secondary);
}

.jb-days.is-warn { color: var(--warning-700); }
.jb-days.is-late { color: var(--danger-600); }

/* ---------- Vacancy AI strip ----------

   Was a 320px column beside the table, which the table paid for on every
   screen - including the ones where the rail had nothing to say. As a strip it
   costs one row when there is something and one line when there is not, and it
   spans the same width as the card below it so the page keeps two edges rather
   than four. */

.jb-ai {
    display: flex;
    align-items: center;
    gap: 10px;
    flex-wrap: wrap;
    padding: 10px 12px;
    border: 1px solid var(--gold-200);
    border-radius: var(--r-lg);
    background: linear-gradient(180deg, var(--gold-50), var(--n-0));
}

.jb-ai-mark {
    display: grid;
    place-items: center;
    width: 26px;
    height: 26px;
    flex-shrink: 0;
    border-radius: 7px;
    background: var(--gold-500);
    color: var(--p-900);
}

.jb-ai-t {
    font-size: var(--fs-md);
    font-weight: var(--fw-semibold);
    white-space: nowrap;
}

.jb-ai-list {
    display: flex;
    flex: 1;
    flex-wrap: wrap;
    gap: 6px;
    min-width: 0;
}

/* Each finding is the filter that shows it, so the whole chip is the button
   rather than a sentence with a link bolted to the end. */
.jb-ai-item {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    padding: 4px 4px 4px 10px;
    border: 1px solid var(--gold-200);
    border-radius: var(--r-full);
    background: var(--n-0);
    font-size: var(--fs-sm);
    color: var(--text-primary);
    cursor: pointer;
}

.jb-ai-item:hover { border-color: var(--gold-400); }

.jb-ai-item:focus-visible {
    outline: none;
    box-shadow: var(--shadow-focus);
}

.jb-ai-item-t { white-space: nowrap; }

.jb-ai-go {
    padding: 2px 8px;
    border-radius: var(--r-full);
    background: var(--p-900);
    color: var(--gold-300);
    font-size: var(--fs-2xs);
    font-weight: var(--fw-semibold);
    white-space: nowrap;
}

.jb-ai-none {
    flex: 1;
    min-width: 0;
    font-size: var(--fs-sm);
    color: var(--text-secondary);
}

/* ---------- The list as a fixed-height region ----------

   The same shape the applications inbox uses, and here for the same reason:
   the column labels the figures depend on, the filter that produced the list
   and the pager that says how much is left all used to leave the screen on the
   first flick through fifty roles. Only the rows should move, so they get
   their own scrollport and everything around them stays where it was put.

   The chain is four boxes deep because the card is a component. .jb-page takes
   <main>'s height, and every box between it and the rows has to agree to be
   smaller than its content - a flex item refuses by default to be shorter than
   the fifty rows inside it, and that refusal hands the scroll back to <main>.
   .jb-card is passed to QCard as its surface class, so the one rule that
   reaches inside the component is the body hook it documents. */

/* block-size, not min-block-size: the height has to be definite or the boxes
   below it size to their content, the scrollport is as tall as fifty rows, and
   nothing scrolls anywhere except the page.

   The floor is the short-window escape hatch, and it belongs here rather than
   further down the chain: max(100%, 640px) is still a definite height, so the
   region keeps working, the card is drawn at its full size and the page takes
   the overflow. 640px is this page's chrome - a pinned header, the tile row
   and the AI strip the inbox does not carry - plus about four rows. */
.jb-page {
    block-size: 100%;
    min-block-size: 640px;
    /* The 24px tail belongs to a page that scrolls past its own end. Here it
       is dead space taken from the rows. */
    padding-block-end: 8px;
}

/* min-block-size: 0 on every box down the chain, and it is not optional: a
   flex item's default minimum is its content, so leaving it out sizes each box
   to fifty rows and the only thing that scrolls is the page. min-inline-size is
   the same bargain on the other axis - without it the widest column's
   min-content width propagates back up and the sideways scroll lands on
   <main> as a page-wide scrollbar. */
.jb-list {
    flex: 1 1 auto;
    min-block-size: 0;
    min-inline-size: 0;
    display: flex;
}

.jb-card,
.jb-card > .q-card-body {
    flex: 1 1 auto;
    min-block-size: 0;
    min-inline-size: 0;
    display: flex;
    flex-direction: column;
}

/* The one scroll container on the page. Only the block axis was asked for and
   both are given - overflow on one axis gives it on the other - which costs
   nothing here: this page answers "too many columns" with the column toggle in
   its header rather than by scrolling sideways. */
.jb-scroll {
    flex: 1 1 auto;
    min-block-size: 0;
    min-inline-size: 0;
    overflow: auto;
}

/* The labels pin to the top of their own scrollport. The shared rule measures
   from the pinned page toolbar, which is not the box they scroll inside any
   more - at 48px they would sit a toolbar's height below the first row and let
   it slide underneath. */
.jb-scroll .cli-table thead th { inset-block-start: 0; }

/* ============================================================
   Row action menu label

   .ctx-menu and .ctx-item are shared (Candidates and Jobs render the same
   panel) and live with the primitives in section 1. This one label is
   rendered by the two list pages - it used to sit in a <style> block inside
   Clients, which only reached the DOM while the page happened to be
   rendering.
   ============================================================ */

.ctx-label {
    padding: 5px 9px 3px;
    font-size: 10px;
    font-weight: var(--fw-semibold);
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: var(--text-tertiary);
}

/* The tick marking the status a client already has, pushed to the far edge of
   the menu item. */
.ctx-tick {
    display: inline-flex;
    margin-inline-start: auto;
    color: var(--text-tertiary);
}


/* ============================================================
   6c. Recruiter: Applications inbox

   The inbox is the same page as Jobs and Clients and reuses everything in 6
   and 6b - header, tiles, filter bar, table metrics, bulk bar, footer,
   skeleton. Only what the inbox has and the other two do not lives here: the
   keyboard cursor, the shortcut legend and the inline stage control. The
   AI-fit meter started here and moved to section 6 once Candidates scored
   its rows the same way.

   The page carried every rule as an inline style attribute, so it had no
   hover states, no focus rings, no sticky header, no breakpoints, and a
   selection colour that a row hover silently overwrote. It also reached for
   --surface-1 and --danger-200, neither of which this design system defines,
   so the stage dropdown had no background and the bulk reject button had no
   border - two declarations the browser dropped without a word.
   ============================================================ */

/* The list owns keyboard focus so J/K/S/X/Enter reach it, but a focus ring
   around the whole card on every click is noise - the cursor row is the
   feedback that matters, and it is drawn below. */
.ap-list { outline: none; }

/* ---------- The list as a fixed-height region ----------

   The rows scroll inside the card, in both directions, and nothing else on the
   page scrolls at all. Triage is a loop - read a row, press S or X, read the
   next one - and every part of that loop except the rows themselves used to
   leave the screen: the column labels the numbers depend on, the filter that
   produced the list, the pager that says how much is left, and, once eleven
   columns stopped fitting, <main> sliding the whole page sideways to reach one
   of them. The rows are the only thing here that should move.

   The chain is four boxes deep because the card is a component. .ap-page takes
   <main>'s height, and every box between it and the rows has to agree to be
   smaller than its content on both axes - a flex item refuses by default to be
   shorter than the fifty rows inside it or narrower than its own min-content
   width, and either refusal hands the scroll back to <main>. .ap-card is
   passed to QCard as its surface class, so the one rule that reaches inside
   the component is the body hook it documents.

   No width or height gate on any of this. A gate means a viewport where the
   wrapper is an ordinary div again, and there the table's width lands back on
   <main> as a page-wide horizontal scrollbar - which is the thing this is here
   to prevent. What varies by width is how many columns are worth showing, and
   that is the block at the end. */

/* block-size, not min-block-size: the height has to be definite or the boxes
   below it size to their content, the scrollport is as tall as fifty rows, and
   nothing scrolls anywhere except the page. Measured that way round - box not
   scrollable, pager 1347px down a 724px window.

   The floor is the short-window escape hatch, and it has to sit here rather
   than on the rows themselves: max(100%, 560px) is still a definite height, so
   the region keeps working, the card is drawn at its full size, and the page
   takes the overflow. A floor further down the chain instead leaves the card
   drawn at the height the flex line gave it with its own pager hanging out
   below the border. 560px is this page's chrome plus about four rows. */
.ap-page {
    block-size: 100%;
    min-block-size: 560px;
    /* The 24px tail belongs to a page that scrolls past its own end. Here it
       is dead space taken from the rows. */
    padding-block-end: 8px;
}

/* min-inline-size: 0 on every box down the chain, and nothing corresponding on
   the block axis. A flex item refuses by default to be narrower than its own
   min-content width, and the table's 1220px floor is part of that width, so
   without this the floor propagates back up: the card grows to 1220px inside a
   1000px column and the sideways scroll lands on <main> - the page-wide
   scrollbar all of this exists to prevent. Measured: box 1220 wide in a 982px
   window, page scrolling sideways, box not scrolling at all.

   min-block-size: 0 is the same bargain on the other axis, and it is not
   optional: a flex item's default minimum is its content, so leaving it out
   sizes every box to fifty rows and the only thing that scrolls is the page.
   Measured that way too - box not scrollable, pager 1347px down a 724px
   window. The height the region actually gets is settled by .ap-page above. */
.ap-list {
    flex: 1 1 auto;
    min-block-size: 0;
    min-inline-size: 0;
    display: flex;
}

.ap-card,
.ap-card > .q-card-body {
    flex: 1 1 auto;
    min-block-size: 0;
    min-inline-size: 0;
    display: flex;
    flex-direction: column;
}

/* The one scroll container on the page, and the only box on it that scrolls in
   either direction. */
.ap-scroll {
    flex: 1 1 auto;
    min-block-size: 0;
    min-inline-size: 0;
    overflow: auto;
}

/* The labels pin to the top of their own scrollport. The shared rule measures
   from the page toolbar, which is not what they are scrolling inside any more
   - at 48px they would sit a toolbar's height below the first row and let it
   slide underneath. */
.ap-scroll .cli-table thead th { inset-block-start: 0; }

/* ---------- Sideways, instead of dropping columns ----------

   From 700px up the eleven columns are all kept and the box scrolls to reach
   them. Hiding by class was the right answer while the only alternative was
   scrolling the page: the tiles, the pinned header and the pager would all
   have slid out from under the toolbar to bring one column into view. The rows
   have their own scrollport now, so sideways costs nothing that was on screen.

   Safe on this table in a way it is not on Clients: an open row menu is
   absolutely positioned and a scrollport clips it, and this row's action cell
   is two buttons and a native <select> - whose dropdown the browser draws
   outside the page entirely.

   Below 700px the breakpoint rules further down still win and the table is
   four columns wide, which is the right shape for a phone; the box is still
   the scroll container there, so what does not fit scrolls inside the card
   rather than dragging the page sideways. */
@media (min-width: 700px) {

    .ap-page .col-salary,
    .ap-page .col-contact,
    .ap-page .col-cv,
    .ap-page .col-applied,
    .ap-page .col-job,
    .ap-page .col-loc { display: table-cell; }

    /* A floor under the columns. Without it the table honours its width: 100%
       and squeezes to whatever the box gives it - names breaking across three
       lines and an email in a 90px column - and the scrollbar never appears,
       because nothing ever overflows. */
    .ap-scroll .cli-table { min-inline-size: 1220px; }

    /* Nothing to reach sideways on an empty inbox: the floor would only put a
       scrollbar under a single centred "no applications" panel. */
    .ap-scroll .cli-table:has(.cli-empty-cell) { min-inline-size: 0; }
}

/* ============================================================
   Footer: range, page size, pager

   One band under the rows, carrying everything about "where am I in the
   result set". Page size used to sit in the page header, a full table away
   from the pager it changes; the two controls that answer the same question
   now sit a glance apart, and the header is left to identity alone.
   ============================================================ */

/* The range takes the free width from .cli-foot-l, so the size control and
   the pager settle against the trailing edge as one cluster. A hairline
   between them marks where "how many per page" ends and "which page" begins -
   without it the select reads as the pager's first button. */
.ap-foot {
    row-gap: 10px;
}

.ap-per {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    padding-inline-end: 12px;
    border-inline-end: 1px solid var(--border-subtle);
}

/* A single page needs no pager, and a divider with nothing after it is a rule
   hanging off the end of the row. */
.ap-per:last-child {
    padding-inline-end: 0;
    border-inline-end: none;
}

.ap-per-l {
    font-size: var(--fs-xs);
    color: var(--text-tertiary);
    white-space: nowrap;
}

/* Matches .ap-pg so the whole footer cluster sits on one 30px line. */
.ap-per select {
    width: 68px;
    height: 30px;
}

/* ---------- Pager ----------

   Numbered, not just prev/next. On a hundred-page inbox a pair of arrows is
   the only way back to page 1 - forty clicks of it - and nothing on screen
   says how far in you are. The numbers make both ends reachable in one click
   and give the control a shape you can read your position from.

   One connected strip rather than eight loose buttons: at 8px apart they read
   as eight separate decisions, and the row is really one control with eight
   targets. */

.ap-pager {
    display: inline-flex;
    align-items: center;
    border: 1px solid var(--border-default);
    border-radius: var(--r-md);
    background: var(--surface-raised);
    overflow: hidden;
}

.ap-pg-nums {
    display: inline-flex;
    align-items: center;
}

/* Square targets, sized from the segment height so the numbers sit on a grid
   and a two-digit page does not widen its own cell. */
.ap-pg {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 30px;
    height: 30px;
    padding: 0 4px;
    border: none;
    border-inline-start: 1px solid var(--border-subtle);
    background: transparent;
    font-family: var(--font-mono);
    font-variant-numeric: tabular-nums;
    font-size: var(--fs-sm);
    color: var(--text-secondary);
    cursor: pointer;
    transition: background-color var(--dur-fast) var(--ease-out),
                color var(--dur-fast) var(--ease-out);
}

.ap-pager > :first-child { border-inline-start: none; }

.ap-pg:hover:not(:disabled):not(.is-on) {
    background: var(--p-50);
    color: var(--p-700);
}

.ap-pg:focus-visible {
    outline: none;
    box-shadow: inset var(--shadow-focus);
}

/* The page you are on. Solid rather than tinted: it is the one thing in the
   strip that is a statement rather than a target, and a tint at this size was
   indistinguishable from the hover it sits next to. */
.ap-pg.is-on {
    background: var(--p-700);
    color: var(--text-inverse);
    font-weight: var(--fw-semibold);
    cursor: default;
}

/* Greyed rather than hidden: an arrow that vanishes at page 1 makes the strip
   change width under the pointer, which moves every number sideways. */
.ap-pg:disabled {
    color: var(--n-300);
    cursor: not-allowed;
}

.ap-pg-arrow { color: var(--text-tertiary); }

/* First and last: two chevrons overlapped into one glyph, so the jump-to-end
   pair reads as a stronger version of the step-by-one pair beside it. */
.ap-pg-edge svg + svg { margin-inline-start: -7px; }

/* The elided run. Not a button, and not a focus stop. */
.ap-pg-gap {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 22px;
    height: 30px;
    border-inline-start: 1px solid var(--border-subtle);
    color: var(--text-tertiary);
    font-size: var(--fs-sm);
    user-select: none;
}

/* "Page 3 of 12" - the fallback for when the numbers do not fit. Hidden while
   they do. */
.ap-pg-compact {
    display: none;
    padding: 0 12px;
    border-inline-start: 1px solid var(--border-subtle);
    font-size: var(--fs-xs);
    font-variant-numeric: tabular-nums;
    color: var(--text-secondary);
    white-space: nowrap;
}

/* ---------- Keyboard cursor ----------

   Where J and K have got to. It has to survive both a hover and a selection:
   without its own tone the row the next keystroke will act on looked exactly
   like the row under the pointer, which is how S shortlists the wrong person.
   The inline start bar is what carries it when the row is also selected and
   the two backgrounds would otherwise cancel out. */

.cli-row.is-cur { background: var(--p-50); }
.cli-row.is-cur:hover { background: var(--p-100); }

.cli-row.is-cur td:first-child { box-shadow: inset 3px 0 0 var(--p-700); }

/* Selection wins the background - a bulk action is about to change these rows
   - and the cursor keeps its bar. */
.cli-row.is-sel.is-cur { background: var(--p-100); }

/* ---------- Job and contact cells ---------- */

.ap-job {
    display: block;
    color: var(--text-secondary);
    text-decoration: none;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.ap-job:hover { color: var(--p-700); text-decoration: underline; }

.ap-job:focus-visible {
    outline: none;
    border-radius: var(--r-sm);
    box-shadow: var(--shadow-focus);
}

.ap-contact {
    min-width: 0;
    max-width: 220px;
    font-size: var(--fs-sm);
    color: var(--text-secondary);
}

.ap-phone {
    display: block;
    margin-block-start: 2px;
    font-family: var(--font-mono);
    font-size: var(--fs-xs);
    color: var(--text-tertiary);
}

/* ---------- CV ----------
   An outlined chip, not a filled button: it is the third-most likely thing to
   be clicked in the row, and a solid control on every line would outrank the
   name. */

.ap-cv {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    height: 26px;
    padding: 0 10px;
    border: 1px solid var(--border-default);
    border-radius: var(--r-md);
    background: var(--surface-raised);
    font-size: var(--fs-xs);
    color: var(--text-secondary);
    text-decoration: none;
    white-space: nowrap;
    transition: border-color var(--dur-fast) var(--ease-out),
                color var(--dur-fast) var(--ease-out);
}

.ap-cv:hover {
    border-color: var(--p-300);
    color: var(--p-700);
}

.ap-cv:focus-visible { outline: none; box-shadow: var(--shadow-focus); }

/* ---------- Stage chip ----------

   The same chip the stage facets in the filter bar are drawn as: pill, tinted
   ground, the stage's own dot, 28px and 11.5px semibold. The column is the one
   a recruiter reads down rather than across - "what is still sitting in
   applied" is the question the inbox exists to answer - and twenty identical
   grey selects answered it twenty times over with the word alone. Reusing the
   facets' vocabulary also means the chip you filter by and the chip in the row
   are the same object.

   A button, not a select, because the list it opens is drawn below rather than
   by the OS. The select is still here for phones; which of the two is shown is
   settled at the foot of this section. */

.ap-stage { display: inline-flex; align-items: center; color: var(--st-fg); }

.ap-stage-chip {
    display: inline-flex;
    align-items: center;
    gap: 7px;
    height: 28px;
    max-inline-size: 172px;
    padding-inline: 10px 8px;
    border: 1px solid transparent;
    border-radius: var(--r-full);
    background: var(--st-bg);
    /* The label sits on its own tint here, where in the facet chips it sits on
       one at 11.5px too - and measured against these grounds two of the eight
       do not clear WCAG AA: screened at 3.57 and offer/hired at 3.25. Darkened
       80% toward black the worst of the eight is 4.77, and the hue is still
       plainly amber or green. Done here rather than in StageColors because that
       palette is copied across five pages and the dots, the facet chips and the
       pipeline all draw from it; this is the one place the colour became a
       background for text. */
    color: color-mix(in srgb, var(--st-fg) 80%, #000);
    font-family: inherit;
    font-size: var(--fs-xs);
    font-weight: var(--fw-semibold);
    cursor: pointer;
    transition: border-color var(--dur-fast) var(--ease-out);
}

/* The stage's own hue on hover, not the brand's: the chip already carries a
   colour, and a blue ring around an amber chip reads as a different control
   rather than as the same one under the pointer. */
.ap-stage-chip:hover:not(:disabled) { border-color: var(--st-dot); }

.ap-stage-chip:focus-visible { outline: none; box-shadow: var(--shadow-focus); }

/* Inert until the circuit is live: the prerendered markup carries the control
   but not its handler, so a pick made now is never sent. It fades rather than
   turning grey - the stage is still the information in the cell while the row
   cannot be moved yet. */
.ap-stage-chip:disabled { opacity: 0.5; cursor: not-allowed; }

.ap-stage-dot {
    width: 6px;
    height: 6px;
    border-radius: 50%;
    flex-shrink: 0;
    background: var(--st-dot);
}

.ap-stage-lbl { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* Quieter than the label it belongs to: it marks the chip as openable and is
   not itself information. */
.ap-stage-cx { display: inline-flex; flex-shrink: 0; opacity: 0.5; }

.ap-stage-chip[aria-expanded="true"] {
    border-color: var(--st-dot);
    box-shadow: var(--shadow-focus);
}

/* ---------- Stage panel ----------

   Eight stages are not one list: six are places in the pipeline and two are
   ways out of it. The rule between them is the whole point of drawing this
   ourselves - reject and withdraw stop sitting one pixel below the routine
   moves, which is how a fast hand dispositions the wrong person.

   Fixed, not absolute: the cell it belongs to lives inside .ap-scroll, and a
   panel positioned within that box would be clipped by it. Fixed escapes,
   because nothing between here and the viewport sets a transform. C# parks it
   from the trigger's rect and does not track it afterwards, which is what the
   veil is for - it takes the scroll that would otherwise leave the panel
   pointing at nothing. */

.ap-menu-veil {
    position: fixed;
    inset: 0;
    z-index: 70;
}

.ap-menu {
    position: fixed;
    z-index: 71;
    inline-size: 212px;
    /* An estimate places it; this keeps it on screen when the estimate is off,
       and when a translation runs the labels longer than the English. */
    max-block-size: calc(100dvh - 16px);
    overflow-y: auto;
    padding: 5px;
    background: var(--surface-raised);
    border: 1px solid var(--border-subtle);
    border-radius: var(--r-lg);
    box-shadow: var(--shadow-lg);
}

.ap-menu-lbl {
    padding: 7px 10px 5px;
    font-size: var(--fs-2xs);
    font-weight: var(--fw-semibold);
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--text-tertiary);
}

.ap-menu-rule {
    height: 1px;
    margin: 5px 8px;
    background: var(--border-subtle);
}

.ap-menu-item {
    display: flex;
    align-items: center;
    gap: 9px;
    inline-size: 100%;
    height: 32px;
    padding-inline: 9px;
    border: none;
    border-radius: var(--r-sm);
    background: none;
    font-family: inherit;
    font-size: var(--fs-md);
    color: var(--text-primary);
    text-align: start;
    cursor: pointer;
}

.ap-menu-item:hover,
.ap-menu-item:focus-visible { background: var(--surface-hover); outline: none; }

.ap-menu-item[aria-selected="true"] {
    background: var(--p-50);
    font-weight: var(--fw-semibold);
}

.ap-menu-dot {
    width: 7px;
    height: 7px;
    border-radius: 50%;
    flex-shrink: 0;
    background: var(--ms-dot);
}

.ap-menu-txt {
    flex: 1;
    min-inline-size: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.ap-menu-tick { display: inline-flex; flex-shrink: 0; color: var(--p-700); }

/* ---------- Which control ----------

   The panel is the desktop control. A phone keeps the native <select>: its
   picker is a sheet the OS draws at a size a thumb can hit, which is better
   than anything drawn here, and 700px is where this table already changes
   shape. */

.ap-stage-sel { display: none; }

@media (max-width: 700px) {
    .ap-stage-chip { display: none; }

    .ap-stage-sel {
        display: inline-flex;
        height: 28px;
        max-inline-size: 150px;
        padding-inline: 10px 6px;
        border: 1px solid transparent;
        border-radius: var(--r-full);
        background: var(--st-bg);
        color: color-mix(in srgb, var(--st-fg) 80%, #000);
        font-family: inherit;
        font-size: var(--fs-xs);
        font-weight: var(--fw-semibold);
        cursor: pointer;
    }

    .ap-stage-sel:focus-visible { outline: none; box-shadow: var(--shadow-focus); }
    .ap-stage-sel:disabled { opacity: 0.5; cursor: not-allowed; }
}

/* ---------- Row actions ----------
   Two icon buttons, revealed on hover or focus so the column is not a wall of
   glyphs at rest; always visible on touch, where there is no hover to reveal
   them with. */

.cli-table td.ap-act { width: 72px; white-space: nowrap; }

.ap-act .btn {
    opacity: 0;
    transition: opacity var(--dur-fast) var(--ease-out);
}

.cli-row:hover .ap-act .btn,
.cli-row.is-cur .ap-act .btn,
.ap-act .btn:focus-visible { opacity: 1; }

@media (hover: none) {
    .ap-act .btn { opacity: 1; }
}

/* ---------- Bulk reason ----------
   The sentence each of these people is about to receive, so it takes the free
   width in the bar rather than sitting as a stub beside the buttons. */

.ap-reason {
    flex: 1 1 200px;
    min-width: 160px;
    max-width: 420px;
    height: var(--h-md);
    padding: 0 10px;
    border: 1px solid var(--p-100);
    border-radius: var(--r-md);
    background: var(--n-0);
    font-family: inherit;
    font-size: var(--fs-sm);
    color: var(--text-primary);
}

.ap-reason::placeholder { color: var(--text-tertiary); }

.ap-reason:focus-visible { outline: none; box-shadow: var(--shadow-focus); }

/* ---------- Breakpoints ----------

   Eleven columns is more than any width holds, so they drop in reverse order
   of how often they are the reason someone opened the inbox: the applicant,
   the job, the stage and the fit survive longest, because those four are the
   triage decision.

   Only below 700px now. From there up the columns are all kept and the rows'
   own box scrolls sideways to reach them; see "Sideways, instead of dropping
   columns" above, which restores them. On a phone the drops still earn their
   place - four columns that fit beat eleven behind a sideways scroll on a
   380px screen - so these rules are what survives, and the box scrolls only
   when even those four do not fit. */

@media (max-width: 1400px) {
    .col-salary { display: none; }
}

@media (max-width: 1240px) {
    .col-contact { display: none; }
}

@media (max-width: 1080px) {
    .col-cv { display: none; }
}

@media (max-width: 900px) {
    .col-applied { display: none; }

    /* Seven 30px targets stop fitting beside the range and the size control.
       The arrows keep working and "Page 3 of 12" keeps the position on screen,
       which is the part of the strip that was carrying information rather
       than reach. */
    .ap-pg-nums { display: none; }
    .ap-pg-compact { display: inline-flex; align-items: center; }
}

@media (max-width: 700px) {
    .col-job { display: none; }

    /* The footer wraps to two lines here, so the range takes the first on its
       own and the two controls sit together on the second - the divider now
       separates two things on one line rather than two lines. */
    .cli-foot-l { flex: 1 0 100%; }
    .ap-per { margin-inline-start: auto; }
}


/* ============================================================
   6d. Recruiter: Candidates list

   The book of people. Same page as Clients, Jobs and the inbox, and it reuses
   everything in section 6 - header, tiles, filter bar, table, fit meter, bulk
   bar, footer, drawers, skeleton. Only what this page has and the other three
   do not lives here: the profile-completeness reading, an identity cell that
   carries a face, and the import drawer's dropzone and progress.

   Like the inbox before it, this page carried every rule as a style attribute
   - a 900px table with no sticky header, no hover state on a row that was
   nonetheless clickable, no focus rings, no breakpoints, and four overlays
   hand-styled four slightly different ways.
   ============================================================ */

/* ---------- The list as a fixed-height region ----------

   The same shape the applications inbox, Jobs and Clients use, and here for
   the same reason: the column labels the figures depend on, the filter that
   produced the list and the pager that says how much is left all used to
   leave the screen on the first flick through five hundred people. Only the
   rows should move, so they get their own scrollport and everything around
   them stays where the recruiter put it.

   The chain is four boxes deep because the card is a component. .cnd-page
   takes <main>'s height, and every box between it and the rows has to agree
   to be smaller than its content - a flex item refuses by default to be
   shorter than the fifty rows inside it, and that refusal hands the scroll
   back to <main>. .cnd-card is passed to QCard as its surface class, so the
   one rule that reaches inside the component is the body hook it documents. */

/* block-size, not min-block-size: the height has to be definite or the boxes
   below it size to their content, the scrollport is as tall as fifty rows,
   and nothing scrolls anywhere except the page.

   The floor is the short-window escape hatch, and it belongs here rather than
   further down the chain: max(100%, 600px) is still a definite height, so the
   region keeps working, the card is drawn at its full size and the page takes
   the overflow. 600px is this page's chrome - a pinned header, the tile row
   and the filter bar - plus about four rows. */
.cnd-page {
    block-size: 100%;
    min-block-size: 600px;
    /* The 24px tail belongs to a page that scrolls past its own end. Here it
       is dead space taken from the rows. */
    padding-block-end: 8px;
}

/* min-block-size: 0 on every box down the chain, and it is not optional: a
   flex item's default minimum is its content, so leaving it out sizes each
   box to fifty rows and the only thing that scrolls is the page.
   min-inline-size is the same bargain on the other axis - without it the
   widest column's min-content width propagates back up and the sideways
   scroll lands on <main> as a page-wide scrollbar. */
.cnd-list {
    flex: 1 1 auto;
    min-block-size: 0;
    min-inline-size: 0;
    display: flex;
}

.cnd-card,
.cnd-card > .q-card-body {
    flex: 1 1 auto;
    min-block-size: 0;
    min-inline-size: 0;
    display: flex;
    flex-direction: column;
}

/* The one scroll container on the page. Only the block axis was asked for and
   both are given - overflow on one axis gives it on the other - which costs
   nothing here: this page answers "too many columns" with the breakpoints
   below rather than by scrolling sideways. */
.cnd-scroll {
    flex: 1 1 auto;
    min-block-size: 0;
    min-inline-size: 0;
    overflow: auto;
}

/* The labels pin to the top of their own scrollport. The shared rule measures
   from the pinned page toolbar, which is not the box they scroll inside any
   more - at 48px they would sit a toolbar's height below the first row and
   let it slide underneath. */
.cnd-scroll .cli-table thead th { inset-block-start: 0; }

/* Nine columns need room. At width:100% they shared whatever the window gave
   them, so on a 1440px screen the right-hand ones - AI fit, profile, added -
   were squeezed until their contents clipped, with nothing to scroll because
   the table never grew past its box. A floor makes the scrollport do its job:
   above it nothing changes, below it the table scrolls sideways intact. */
.cnd-scroll .cli-table { min-inline-size: 1180px; }

/* ---------- Bulk actions ----------

   Candidates carries its selection actions at the right end of the filter
   toolbar rather than in the sticky strip below the rows that the other lists
   use, so the count sits next to the checkboxes that produced it. Wrapping is
   the narrow-window answer: the group drops to a line of its own, still right
   aligned and still above the table, instead of crushing the search box. */
.cnd-card .cli-bar { flex-wrap: wrap; }

/* Doubled class so these land on top of .cli-bulk's sticky-strip rules
   wherever they sit in the file, including the 700px override below. */
.cli-bulk.cnd-bulk {
    position: static;
    z-index: auto;
    flex: 0 0 auto;
    margin-inline-start: auto;
    gap: 8px;
    padding: 4px 8px;
    border: 1px solid var(--p-100);
    border-radius: var(--r-full);
}

/* ---------- Identity cell ----------

   The avatar earns its width here in a way it does not on Clients: a
   recruiter scrolling this list is usually looking for someone they have
   already spoken to, and a face is recognised faster than a name is read. */

.cnd-who {
    display: flex;
    align-items: center;
    gap: 10px;
    min-width: 0;
    color: inherit;
    text-decoration: none;
}

.cnd-who .avatar { flex-shrink: 0; }

/* No gap: .cli-meta already carries the 2px above it, and set in both places
   the headline drifted away from the name it belongs to. */
.cnd-who-t {
    display: flex;
    flex-direction: column;
    min-width: 0;
}

/* The focus ring belongs to the anchor, which is the whole cell here rather
   than the name alone, so .cli-name:focus-visible never fires on this page. */
.cnd-who:focus-visible {
    outline: none;
    border-radius: var(--r-sm);
    box-shadow: var(--shadow-focus);
}

/* ---------- Profile completeness ----------

   A percentage and a track, not a second coloured meter: the AI-fit column
   already owns colour in this table, and two ranked bars per row cancel each
   other out. This one stays neutral until the profile is thin enough to be
   the reason a submission would bounce, and only then borrows the warning
   tone - so colour in this column means "not ready to send". */

.cnd-prof {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: 8px;
    color: var(--text-secondary);
}

.cnd-prof.is-thin { color: var(--warning-700); }

.cnd-prof-n {
    font-family: var(--font-mono);
    font-variant-numeric: tabular-nums;
    font-weight: var(--fw-medium);
    min-width: 30px;
    text-align: end;
}

.cnd-prof-bar {
    position: relative;
    width: 30px;
    height: 5px;
    border-radius: var(--r-full);
    background: var(--n-75);
    overflow: hidden;
    flex-shrink: 0;
}

.cnd-prof-fill {
    position: absolute;
    inset-block: 0;
    inset-inline-start: 0;
    max-width: 100%;
    border-radius: inherit;
    background: var(--n-400);
}

.cnd-prof.is-thin .cnd-prof-fill { background: var(--warning-500); }

/* ---------- Résumé import drawer ----------

   Three sections that each answer one question — what am I sending, how far
   along is it, which files did not land — and a footer that says what the
   primary button is waiting for. The shell (QDrawer, .drw-sec,
   .drw-legend, .cli-ready) is the client import's; only the parts a batch of
   files needs that a CSV does not are new here. */

/* The header tile: a dashed well before there is anything to count, the batch
   size while it runs, brand-filled, and green once the batch is finished. */
.rimp-mark { background: var(--p-700); }
.rimp-mark.is-done { background: var(--success-600); }

.rimp-mark-n {
    font-size: var(--fs-md);
    font-variant-numeric: tabular-nums;
    letter-spacing: -0.02em;
}

/* Header pills sit on the subtitle line, which is a text line - they need the
   row to hold them apart. */
.rimp-hp {
    display: inline-flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 6px;
}

/* A control belonging to a section, on the legend's own line. The colour reset
   is not optional: .drw-legend paints every svg under it the muted legend
   grey, which would take the control's own icon with it. */
.rimp-legend-a {
    margin-inline-start: auto;
    /* .drw-legend is uppercase small-caps chrome; a control sitting on that
       line is still a control and reads in sentence case. */
    font-weight: var(--fw-semibold);
    text-transform: none;
    letter-spacing: 0;
}

.rimp-legend-a svg { color: inherit; }

/* The batch counter beside the Progress legend: the one number that says how
   far along this is, in the place the eye already goes for section metadata. */
.rimp-legend-n {
    margin-inline-start: auto;
    font-family: var(--font-mono);
    font-variant-numeric: tabular-nums;
    font-size: var(--fs-xs);
    font-weight: var(--fw-semibold);
    color: var(--text-secondary);
    letter-spacing: 0;
    text-transform: none;
}

/* "Add more" is a label wrapping a file input, so it cannot be a <button> and
   cannot borrow .btn without also borrowing the input's own box. */
.rimp-more {
    position: relative;
    display: inline-flex;
    align-items: center;
    gap: 5px;
    padding: 3px 8px;
    border: 1px solid var(--border-default);
    border-radius: var(--r-md);
    background: var(--surface-raised);
    font-size: var(--fs-2xs);
    text-transform: none;
    letter-spacing: 0;
    color: var(--p-700);
    cursor: pointer;
}

.rimp-more:hover { background: var(--p-50); border-color: var(--p-300); }

/* The drop target is the whole panel rather than a button inside one: people
   arrive here dragging forty files, and a 40px target for that is a joke. The
   file input sits transparent on top, so drop and click are one surface. */
.rimp-drop {
    position: relative;
    padding: 30px 16px;
    border: 2px dashed var(--border-default);
    border-radius: var(--r-lg);
    background: var(--n-25);
    text-align: center;
    transition: border-color var(--dur-fast) var(--ease-out),
                background-color var(--dur-fast) var(--ease-out);
}

.rimp-drop:hover {
    border-color: var(--p-300);
    background: var(--p-50);
}

/* The caption is decoration painted over the input that covers the panel;
   without this the pointer lands on the text and the panel stops taking the
   drop. */
.rimp-drop-in {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 7px;
    pointer-events: none;
}

.rimp-drop-ic {
    display: grid;
    place-items: center;
    width: 40px;
    height: 40px;
    border-radius: var(--r-xl);
    background: var(--p-50);
    color: var(--p-700);
}

.rimp-drop-t {
    font-size: var(--fs-md);
    font-weight: var(--fw-semibold);
    color: var(--text-primary);
}

.rimp-drop-s {
    font-size: var(--fs-sm);
    color: var(--text-tertiary);
}

.rimp-drop input[type="file"],
.rimp-folder input[type="file"],
.rimp-more input[type="file"] {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    opacity: 0;
    cursor: pointer;
}

/* The second door and the limits on one line: the folder picker is the rarer
   way in, and the caps are the thing people ask about after their first
   rejected file rather than before it. */
.rimp-ways {
    display: flex;
    align-items: center;
    justify-content: space-between;
    flex-wrap: wrap;
    gap: 8px;
}

.rimp-folder {
    position: relative;
    display: inline-flex;
    align-items: center;
    gap: 6px;
    font-size: var(--fs-sm);
    font-weight: var(--fw-semibold);
    color: var(--p-700);
    cursor: pointer;
}

.rimp-folder:hover { text-decoration: underline; }

.rimp-caps {
    font-size: var(--fs-xs);
    color: var(--text-tertiary);
}

/* Once the batch exists the drop panel has done its job and would only invite
   a second, rival import. What replaces it is the same file card the client
   import shows: what was sent, and where it has got to. */
.rimp-batch {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 10px 12px;
    border: 1px solid var(--border-subtle);
    border-radius: var(--r-lg);
    background: var(--surface-raised);
}

.rimp-batch-ic {
    display: grid;
    place-items: center;
    width: 32px;
    height: 32px;
    flex-shrink: 0;
    border-radius: var(--r-md);
    background: var(--p-50);
    color: var(--p-700);
}

.rimp-batch-m { flex: 1; min-width: 0; }

.rimp-batch-n {
    font-size: var(--fs-sm);
    font-weight: var(--fw-semibold);
}

.rimp-batch-s {
    font-size: var(--fs-xs);
    color: var(--text-tertiary);
}

.rimp-spin {
    width: 15px;
    height: 15px;
    flex-shrink: 0;
    border: 2px solid var(--n-150);
    border-block-start-color: var(--p-600);
    border-radius: var(--r-full);
    animation: rimp-spin var(--dur-slow) linear infinite;
}

@keyframes rimp-spin { to { transform: rotate(360deg); } }

@media (prefers-reduced-motion: reduce) {
    .rimp-spin { animation-duration: 3s; }
}

/* Said before the wait rather than after it: with no OCR engine every scan in
   the folder lands in the review bucket, and that is worth knowing while the
   images can still be pulled out. */
.rimp-note {
    display: flex;
    align-items: flex-start;
    gap: 8px;
    padding: 9px 11px;
    border: 1px solid var(--warning-100);
    border-radius: var(--r-md);
    background: var(--warning-50);
    font-size: var(--fs-sm);
    line-height: var(--lh-snug);
    color: var(--warning-700);
}

.rimp-note svg { flex-shrink: 0; margin-block-start: 2px; }

/* ---------- Progress ---------- */

.rimp-bar {
    height: 8px;
    border-radius: var(--r-full);
    background: var(--n-100);
    overflow: hidden;
}

.rimp-bar-fill {
    height: 100%;
    border-radius: inherit;
    background: var(--p-600);
    transition: width var(--dur-slow) var(--ease-out),
                background-color var(--dur-mid) var(--ease-out);
}

.rimp-bar.is-done .rimp-bar-fill { background: var(--success-500); }

/* ---------- Tally / filter ----------

   Each count is also the filter for the rows it counts, which is why these are
   buttons and not stat tiles. Pressed, the tile takes its own tone as a ring
   rather than a fill: a filled tile the width of a number reads as a badge,
   and the number stops being the thing being looked at. */
.rimp-tally {
    display: flex;
    gap: 6px;
}

.rimp-tal {
    flex: 1;
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: 1px;
    padding: 8px 9px;
    border: 1px solid var(--border-subtle);
    border-radius: var(--r-md);
    background: var(--surface-raised);
    text-align: start;
    cursor: pointer;
    transition: border-color var(--dur-fast) var(--ease-out),
                background-color var(--dur-fast) var(--ease-out);
}

.rimp-tal:hover { background: var(--surface-hover); }
.rimp-tal:focus-visible { outline: none; box-shadow: var(--shadow-focus); }

.rimp-tal-n {
    font-size: var(--fs-lg);
    font-weight: var(--fw-bold);
    font-variant-numeric: tabular-nums;
    letter-spacing: -0.02em;
    color: var(--text-primary);
    line-height: 1.1;
}

.rimp-tal-l {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-size: var(--fs-2xs);
    font-weight: var(--fw-semibold);
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: var(--text-tertiary);
}

.rimp-tal.is-ok   .rimp-tal-n { color: var(--success-700); }
.rimp-tal.is-gold .rimp-tal-n { color: var(--gold-700); }
.rimp-tal.is-bad  .rimp-tal-n { color: var(--danger-600); }

.rimp-tal.is-on { border-color: var(--p-600); background: var(--p-50); }
.rimp-tal.is-ok.is-on   { border-color: var(--success-600); background: var(--success-50); }
.rimp-tal.is-gold.is-on { border-color: var(--gold-600);    background: var(--gold-50); }
.rimp-tal.is-bad.is-on  { border-color: var(--danger-600);  background: var(--danger-50); }

/* ---------- Results ---------- */

.rimp-rows {
    display: flex;
    flex-direction: column;
    margin: 0;
    padding: 0;
    list-style: none;
    border: 1px solid var(--border-subtle);
    border-radius: var(--r-md);
    overflow: hidden;
}

.rimp-row {
    display: flex;
    align-items: center;
    gap: 9px;
    padding: 8px 11px;
    background: var(--surface-raised);
    /* The band, not the shade: one property carries the row's outcome, and
       both the dot and the status word read it. */
    --rimp-tone: var(--n-400);
}

.rimp-row + .rimp-row { border-block-start: 1px solid var(--border-subtle); }

.rimp-row.is-ok   { --rimp-tone: var(--success-600); }
.rimp-row.is-gold { --rimp-tone: var(--gold-600); }
.rimp-row.is-bad  { --rimp-tone: var(--danger-600); }

.rimp-row-d {
    width: 7px;
    height: 7px;
    flex-shrink: 0;
    border-radius: var(--r-full);
    background: var(--rimp-tone);
}

.rimp-row.is-wait .rimp-row-d { background: none; border: 1.5px solid var(--n-300); }

.rimp-row-m { flex: 1; min-width: 0; }

/* A file name is one unbroken token often enough that it has to be allowed to
   break anywhere, or it pushes Retry off the panel. */
.rimp-row-n {
    font-size: var(--fs-sm);
    font-weight: var(--fw-semibold);
    overflow-wrap: anywhere;
}

.rimp-row-s {
    display: flex;
    align-items: baseline;
    flex-wrap: wrap;
    gap: 6px;
    font-size: var(--fs-xs);
    color: var(--text-tertiary);
}

.rimp-row-st {
    font-weight: var(--fw-semibold);
    color: var(--rimp-tone);
}

.rimp-row-tag {
    padding: 0 5px;
    border-radius: var(--r-sm);
    background: var(--n-100);
    font-size: var(--fs-2xs);
    font-weight: var(--fw-semibold);
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--text-secondary);
}

/* One line of it, with the whole message on the title: a parser stack trace is
   1000 characters and would push every other row off the screen. */
.rimp-row-e {
    flex: 1;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.rimp-empty {
    margin: 0;
    padding: 14px 0;
    text-align: center;
    font-size: var(--fs-sm);
    color: var(--text-tertiary);
}

/* ---------- Add / edit drawer ----------

   Three repeaters - skills, roles, schools - each a row of fields ending in a
   remove button. The columns are proportional rather than equal, because the
   first field is the one that identifies the row. */

.cnd-sec-h {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
}

.cnd-rep {
    display: flex;
    flex-direction: column;
    gap: 10px;
}

.cnd-rep-skill {
    display: grid;
    grid-template-columns: 2fr 1.4fr 1fr auto;
    gap: 8px;
    align-items: center;
}

.cnd-rep-edu {
    display: grid;
    grid-template-columns: 1.6fr 1.2fr 1.2fr 0.7fr 0.7fr auto;
    gap: 8px;
    align-items: center;
}

/* An employment entry is three rows, so it gets a box: without one, six
   inputs in a column read as one long form rather than as two jobs. */
.cnd-exp {
    display: flex;
    flex-direction: column;
    gap: 8px;
    padding: 10px;
    border: 1px solid var(--border-subtle);
    border-radius: var(--r-md);
}

.cnd-exp-r {
    display: grid;
    grid-template-columns: 1fr 1fr auto;
    gap: 8px;
    align-items: center;
}

.cnd-cur {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    font-size: var(--fs-sm);
    color: var(--text-secondary);
    white-space: nowrap;
    cursor: pointer;
}

.cnd-none {
    font-size: var(--fs-sm);
    color: var(--text-tertiary);
}

/* ---------- Breakpoints ----------

   Nine columns, dropped in reverse order of how often they are the reason
   someone opened the book: the person, their current role, their status and
   their fit survive longest, because those four are the shortlist decision. */

@media (max-width: 1400px) {
    .col-profile { display: none; }
}

@media (max-width: 1240px) {
    .col-added { display: none; }
}

@media (max-width: 900px) {
    .col-apps { display: none; }
}

@media (max-width: 700px) {
    .col-current { display: none; }
    .cnd-rep-skill { grid-template-columns: 1fr auto; }
    .cnd-rep-edu { grid-template-columns: 1fr 1fr auto; }
    .cnd-exp-r { grid-template-columns: 1fr auto; }
}


/* ============================================================
   6e. Recruiter: One application (apd-*)

   The drill-down the inbox in 6c links each row to. It is the same
   record seen close up, so it wears the inbox's vocabulary — the
   stage palette, the fit bands, the card surface — and adds the two
   things a list cannot show: where in the pipeline this one
   application has got to, and every move it has made.

   Every rule on it was a style attribute, which bought no hover, no
   focus ring, no breakpoint and no RTL. Three specific costs:

     - The stage control, the only control on the page, was painted
       background: var(--surface-1) — a token nothing defines. The
       browser dropped the declaration and the select rendered on
       whatever was behind it. Same typo, same silence, as the two
       the inbox section documents.
     - The frame was grid-template-columns: minmax(280px,360px) 1fr
       with five children, so only the first two landed where they
       were meant to. "Draft a job like this" fell into the narrow
       column under the facts and "Ask a hiring manager" into the
       wide one under the timeline, each next to something it has
       nothing to do with. .apd-grid takes two children and the
       column decides its own order.
     - The header put four buttons and a status pill on one wrap
       line with the name, so on a laptop the name and the actions
       traded places row by row as the window moved. They are
       PageHeader's .pg-actions now, on the bar that used to carry
       one back-link and nothing else.

   What is here:
     .apd-hero    the application band: the role applied for and the
                  two dates, over the stage rail. Who the record is
                  about is PageHeader's job, not this card's
     .apd-job     the role, as a labelled target
     .apd-rail    the six pipeline stages as one progress rail,
                  with the stage <select> beside it
     .apd-grid    timeline + panels | facts, the facts sticky
     .apd-tl      the stage history, one entry per move
     .apd-fit     the AI score as a ring, banded by the same
                  thresholds the inbox meter uses
     .apd-note    one notice box, tinted by .is-bad / .is-warn
     .apd-cv      the CV, as a target rather than a text link

   The accent is the current stage's own colour, set on .apd-hero as
   --apd-accent from the shared stage palette. It is the one thing on
   the page that changes when the record does, so it is what the
   header is tinted by.
   ============================================================ */

/* The page is a .pg, which supplies the band column and the
   --pg-head-h that the pinned header is measured by. This adds only
   what the page needs on top of it.

   The way back used to be a lone .apd-back pill on a strip of its
   own — a full-width band carrying one 12.5px link and nothing else.
   PageHeader's crumb is that link now, in a bar that also carries
   the name and the actions. */
.apd { min-width: 0; }

.apd-load {
    padding: 48px 20px;
    text-align: center;
    font-size: var(--fs-sm);
    color: var(--text-tertiary);
}

/* ---------- The header ---------- */

.apd-hero {
    /* Declared here so the token resolves on its own even when the
       page has not set it: an application whose stage falls to the
       palette's default would otherwise leave the accent rule
       dropped and the top hairline invisible. */
    --apd-accent: var(--n-300);

    position: relative;
    overflow: hidden;
    display: flex;
    flex-direction: column;
    gap: 18px;
    padding: 20px 22px;
    background: var(--surface-raised);
    border: 1px solid var(--border-subtle);
    border-radius: var(--r-xl);
    box-shadow: var(--shadow-xs);
}

/* Three pixels of the current stage's colour. It costs no height and
   it is the only part of the page that answers "where is this?"
   before anything is read. */
.apd-hero::before {
    content: "";
    position: absolute;
    inset-block-start: 0;
    inset-inline: 0;
    height: 3px;
    background: var(--apd-accent);
}

.apd-hero-top {
    display: flex;
    align-items: center;
    gap: 16px;
    flex-wrap: wrap;
}

/* ---------- The role applied for ----------

   The other half of this record's identity, and it was a 12.5px link
   trailing the person's current title with a middot in front of it.
   The name went to the page header; this is what the band is about,
   so it is drawn the way the CV in the sidebar is — a labelled target
   rather than a word in a sentence. */
.apd-job {
    display: inline-flex;
    align-items: center;
    gap: 10px;
    min-width: 0;
    padding: 7px 13px 7px 9px;
    border: 1px solid var(--border-subtle);
    border-radius: var(--r-lg);
    background: var(--surface-sunken);
    text-decoration: none;
    transition: border-color var(--dur-fast) var(--ease-out),
                background-color var(--dur-fast) var(--ease-out);
}

.apd-job:hover { border-color: var(--p-300); background: var(--p-50); }
.apd-job:focus-visible { outline: none; box-shadow: var(--shadow-focus); }

[dir="rtl"] .apd-job { padding: 7px 9px 7px 13px; }

.apd-job-ic {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 30px;
    height: 30px;
    border-radius: var(--r-md);
    border: 1px solid var(--border-subtle);
    background: var(--surface-raised);
    color: var(--text-tertiary);
    flex-shrink: 0;
}

.apd-job:hover .apd-job-ic { color: var(--text-brand); border-color: var(--p-200); }

.apd-job-b {
    display: flex;
    flex-direction: column;
    min-width: 0;
}

.apd-job-t {
    font-size: var(--fs-2xs);
    font-weight: var(--fw-semibold);
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: var(--text-tertiary);
}

.apd-job-n {
    font-size: var(--fs-md);
    font-weight: var(--fw-semibold);
    color: var(--text-brand);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* ---------- The two dates, as chips ---------- */

.apd-facts {
    display: flex;
    flex-wrap: wrap;
    gap: 6px 8px;
    margin-inline-start: auto;
}

.apd-fact {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    height: var(--h-xs);
    padding: 0 9px;
    border-radius: var(--r-full);
    background: var(--surface-sunken);
    font-size: var(--fs-xs);
    color: var(--text-tertiary);
    white-space: nowrap;
}

.apd-fact svg { color: var(--n-400); flex-shrink: 0; }
.apd-fact b { font-weight: var(--fw-semibold); color: var(--text-secondary); }

/* ---------- The stage rail ---------- */

.apd-rail-row {
    display: flex;
    align-items: center;
    gap: 16px;
    flex-wrap: wrap;
    padding-block-start: 16px;
    border-block-start: 1px solid var(--border-subtle);
}

.apd-rail {
    display: flex;
    align-items: flex-start;
    flex: 1 1 420px;
    min-width: 0;
    margin: 0;
    padding: 0;
    list-style: none;
}

.apd-step {
    /* As with --apd-accent: the step owns a default so the marker is
       drawn even before the page hands it the stage's colour. */
    --sg-dot: var(--n-300);

    position: relative;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 6px;
    flex: 1 1 0;
    min-width: 0;
    text-align: center;
}

/* The connector, drawn from each step back to the one before it, so
   the first step has none and no :last-child rule is needed. Behind
   the markers, hence the negative z-index against the step's own
   stacking. */
.apd-step + .apd-step::before {
    content: "";
    position: absolute;
    inset-block-start: 8px;
    inset-inline-end: 50%;
    inline-size: 100%;
    block-size: 2px;
    background: var(--border-default);
}

/* The stretch of rail behind a stage that has been passed is filled,
   which is what makes the rail read as progress rather than as six
   labelled dots. */
.apd-step.is-done::before,
.apd-step.is-now::before { background: var(--sg-dot); }

.apd-step-mk {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    position: relative;
    width: 18px;
    height: 18px;
    border-radius: 50%;
    border: 2px solid var(--border-default);
    background: var(--surface-raised);
    color: var(--text-inverse);
    flex-shrink: 0;
}

/* Hollow until reached. The tick lives in every marker's markup and
   is revealed rather than conditionally rendered, so the row of
   markers keeps one shape as the stage moves. */
.apd-step-mk svg { display: none; }

.apd-step.is-done .apd-step-mk {
    border-color: var(--sg-dot);
    background: var(--sg-dot);
}

.apd-step.is-done .apd-step-mk svg { display: block; }

/* The one it is in: filled, ringed, and a size larger than the rest.
   A ring in the page's own background colour separates the dot from
   the connector running under it. */
.apd-step.is-now .apd-step-mk {
    border-color: var(--sg-dot);
    background: var(--sg-dot);
    box-shadow: 0 0 0 4px color-mix(in srgb, var(--sg-dot) 22%, transparent);
}

.apd-step-l {
    font-size: var(--fs-2xs);
    font-weight: var(--fw-medium);
    color: var(--text-tertiary);
    line-height: var(--lh-snug);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    max-inline-size: 100%;
}

.apd-step.is-done .apd-step-l { color: var(--text-secondary); }

.apd-step.is-now .apd-step-l {
    font-weight: var(--fw-semibold);
    /* The stage's hue is chosen to read as a dot, not as text: two of
       the eight fail AA at this size on white. Darkened toward black
       the way the inbox chip is, for the same reason. */
    color: color-mix(in srgb, var(--sg-dot) 75%, #000);
}

/* ---------- Left the pipeline ----------

   Rejected and withdrawn are not a seventh stage. The six keep their
   ticks — how far the person actually got is the thing the single
   word destroys — but they stop claiming to be in progress, and the
   ending hangs off the end of the rail. */
.apd-rail.is-out .apd-step:not(.is-term) { opacity: 0.55; }

.apd-step.is-term { flex: 0 0 auto; padding-inline-start: 10px; }

.apd-step.is-term::before { background: var(--border-default); }

.apd-step.is-term .apd-step-mk svg { display: block; }

/* ---------- The stage control ----------

   The select had no background token that resolved, no focus ring
   and no label. It is the only control on the page. */
.apd-stage {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    flex-shrink: 0;
}

.apd-stage-l {
    font-size: var(--fs-2xs);
    font-weight: var(--fw-semibold);
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: var(--text-tertiary);
    white-space: nowrap;
}

.apd-stage-sel {
    height: var(--h-md);
    padding-inline: 10px 30px;
    border: 1px solid var(--border-default);
    border-radius: var(--r-md);
    background-color: var(--surface-raised);
    /* The chevron the shared .input carries. Written out because this
       control is not an .input — it sits in the header band, not in a
       form — and inheriting the whole input skin would give it the
       wrong height and focus colour. */
    background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%23666e7d' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E");
    background-repeat: no-repeat;
    background-position: right 9px center;
    appearance: none;
    font-family: inherit;
    font-size: var(--fs-sm);
    font-weight: var(--fw-medium);
    color: var(--text-primary);
    cursor: pointer;
    transition: border-color var(--dur-fast) var(--ease-out);
}

[dir="rtl"] .apd-stage-sel { background-position: left 9px center; }

.apd-stage-sel:hover:not(:disabled) { border-color: var(--border-strong); }
.apd-stage-sel:focus-visible { outline: none; border-color: var(--border-focus); box-shadow: var(--shadow-focus); }

/* Inert until the circuit is live — the prerendered markup carries the
   control but not its handler, so a pick made now is never sent. It
   fades rather than greying: the stage is still the information in
   the control while it cannot yet be changed. */
.apd-stage-sel:disabled { opacity: 0.55; cursor: not-allowed; }

/* ---------- Notice boxes ----------

   The rejection banner and the draft-failed banner each restated the
   same padding, radius and tint from memory, in two spellings. */
.apd-note {
    display: flex;
    align-items: flex-start;
    gap: 10px;
    padding: 11px 13px;
    border: 1px solid var(--border-subtle);
    border-radius: var(--r-lg);
    background: var(--surface-sunken);
    font-size: var(--fs-sm);
    line-height: var(--lh-normal);
    color: var(--text-secondary);
}

.apd-note svg { flex-shrink: 0; margin-block-start: 1px; }
.apd-note-b { min-width: 0; }
.apd-note-b strong { font-weight: var(--fw-semibold); }
.apd-note-b div { margin-block-start: 2px; }

.apd-note.is-bad {
    border-color: var(--danger-100);
    background: var(--danger-50);
    color: var(--danger-700);
}

.apd-note.is-warn {
    margin-block-end: 10px;
    border-color: var(--warning-100);
    background: var(--warning-50);
    color: var(--warning-700);
}

/* ---------- The frame ----------

   Two children, not five. The facts column is 320px: the widest
   thing in it is an expected-salary range with a currency code in
   front of it, and at 300 that wrapped. */
.apd-grid {
    display: grid;
    grid-template-columns: minmax(0, 1fr) 320px;
    gap: 14px;
    align-items: start;
}

.apd-main,
.apd-side {
    display: flex;
    flex-direction: column;
    gap: 14px;
    min-width: 0;
}

/* The facts follow the timeline down. It is a read-only column with
   no form in it, so unlike a panel somebody submits from it can pin
   without hiding its own button. */
.apd-side {
    position: sticky;
    /* Under .pg-head, not under the top of the scrollport: the header
       is pinned and full-bleed, so a column pinned at 8px slides
       beneath it and the first card is read through the bar. Same
       expression .tabs-band uses, plus this page's own 8px gap. */
    inset-block-start: calc(var(--pg-head-h, 56px) - var(--pg-gy, 8px) + 8px);
}

/* ---------- The stage history ---------- */

.apd-tl {
    display: flex;
    flex-direction: column;
    margin: 0;
    padding: 0;
    list-style: none;
}

.apd-tl-i {
    --tl-dot: var(--n-300);

    position: relative;
    display: flex;
    gap: 12px;
    padding-block-end: 16px;
}

.apd-tl-i:last-child { padding-block-end: 0; }

/* The spine, drawn from each entry down to the next. It was a flex
   child sized by min-height, so it stretched or fell short of the
   text beside it depending on how long the stage names were; drawn
   from the marker it always reaches. */
.apd-tl-i:not(:last-child)::before {
    content: "";
    position: absolute;
    inset-block: 16px 0;
    inset-inline-start: 4px;
    inline-size: 1px;
    background: var(--border-subtle);
}

.apd-tl-mk {
    position: relative;
    width: 9px;
    height: 9px;
    margin-block-start: 4px;
    border-radius: 50%;
    background: var(--tl-dot);
    flex-shrink: 0;
}

/* The most recent move is the one the record is currently in, so it
   is the one that gets the halo — the same one the rail's current
   step wears, so the two agree. */
.apd-tl-i.is-now .apd-tl-mk {
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--tl-dot) 22%, transparent);
}

.apd-tl-b { min-width: 0; }

.apd-tl-h {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 6px;
    font-size: var(--fs-sm);
    font-weight: var(--fw-semibold);
    color: var(--text-primary);
}

/* Where it came from is context for where it went, so it is the
   quieter of the two. The arrow between them was a text arrow inside
   a coloured span; an icon keeps the weight of the row even when the
   page is set in a font with no U+2192. */
.apd-tl-from { font-weight: var(--fw-regular); color: var(--text-tertiary); }
.apd-tl-h svg { color: var(--n-400); flex-shrink: 0; }
[dir="rtl"] .apd-tl-h svg { transform: scaleX(-1); }

.apd-tl-d {
    margin-block-start: 2px;
    font-family: var(--font-mono);
    font-size: var(--fs-2xs);
    color: var(--text-tertiary);
}

/* ---------- The two offers ---------- */

.apd-panels {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 14px;
}

/* Stretched rather than start-aligned: one panel is a paragraph and a button,
   the other a paragraph, a field and a note, and sized to their own content the
   two cards ended half a card out of step with each other. */
.apd-panel { block-size: 100%; }

.apd-panel-b {
    margin: 0 0 12px;
    font-size: var(--fs-sm);
    line-height: var(--lh-relaxed);
    color: var(--text-secondary);
}

.apd-panel-n {
    margin: 6px 0 0;
    font-size: var(--fs-xs);
    color: var(--text-tertiary);
}

/* A URL long enough to need selecting, in the face that makes its
   token legible. */
.apd-link {
    inline-size: 100%;
    font-family: var(--font-mono);
    font-size: var(--fs-xs);
    color: var(--text-secondary);
}

/* ---------- The AI fit ----------

   It was a 38px pill with the number in it. Same three bands, same
   thresholds as the inbox meter — the score just fills a ring now,
   because a number between 0 and 100 with no scale drawn around it
   is read as a label rather than as a measurement. */
.apd-fit {
    --fit: 0%;
    --fit-c: var(--n-300);

    display: flex;
    align-items: center;
    gap: 14px;
}

.apd-fit-ring {
    position: relative;
    display: grid;
    place-items: center;
    width: 62px;
    height: 62px;
    border-radius: 50%;
    background: conic-gradient(var(--fit-c) var(--fit), var(--n-150) 0);
    flex-shrink: 0;
}

/* The hole, punched with a disc rather than a mask so the ring keeps
   working where conic-gradient does and mask-composite does not. */
.apd-fit-ring::before {
    content: "";
    position: absolute;
    inset: 6px;
    border-radius: 50%;
    background: var(--surface-raised);
}

.apd-fit-n {
    position: relative;
    font-family: var(--font-mono);
    font-size: var(--fs-lg);
    font-weight: var(--fw-bold);
    color: color-mix(in srgb, var(--fit-c) 78%, #000);
}

.apd-fit-b {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 8px;
    min-width: 0;
}

/* The same number a second time, as a length. The ring says which
   band it is in at a glance; the bar is what makes two applications
   in the same band comparable. */
.apd-fit-bar {
    /* align-self, not the column's align-items: the pill under it is a chip and
       has to keep its own width, so only the bar is told to fill. */
    align-self: stretch;
    min-inline-size: 96px;
    block-size: 5px;
    border-radius: var(--r-full);
    background: var(--n-150);
    overflow: hidden;
}

.apd-fit-bar span {
    display: block;
    inline-size: var(--fit);
    block-size: 100%;
    border-radius: inherit;
    background: var(--fit-c);
}

/* ---------- The CV ---------- */

.apd-cv {
    display: flex;
    align-items: center;
    gap: 10px;
    margin-block-end: 14px;
    padding: 9px 11px;
    border: 1px solid var(--border-subtle);
    border-radius: var(--r-lg);
    background: var(--surface-sunken);
    text-decoration: none;
    transition: border-color var(--dur-fast) var(--ease-out),
                background-color var(--dur-fast) var(--ease-out);
}

.apd-cv:hover { border-color: var(--p-300); background: var(--p-50); }
.apd-cv:focus-visible { outline: none; box-shadow: var(--shadow-focus); }

.apd-cv-ic {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 30px;
    height: 30px;
    border-radius: var(--r-md);
    background: var(--surface-raised);
    border: 1px solid var(--border-subtle);
    color: var(--text-tertiary);
    flex-shrink: 0;
}

.apd-cv-b {
    display: flex;
    flex-direction: column;
    min-width: 0;
    flex: 1;
}

.apd-cv-t {
    font-size: var(--fs-2xs);
    font-weight: var(--fw-semibold);
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: var(--text-tertiary);
}

/* A filename is the one string here with no spaces to break at, so
   it truncates rather than pushing the card wider than its column. */
.apd-cv-n {
    font-size: var(--fs-sm);
    font-weight: var(--fw-medium);
    color: var(--text-primary);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.apd-cv > svg { color: var(--text-tertiary); flex-shrink: 0; }
.apd-cv:hover > svg { color: var(--text-brand); }

/* Nothing was uploaded. It keeps the row's shape so the card does not
   change height between two applications, but it is not a target and
   does not pretend to be one. */
.apd-cv.is-none { background: transparent; border-style: dashed; }
.apd-cv.is-none .apd-cv-n { color: var(--text-tertiary); font-weight: var(--fw-regular); }

/* ---------- The facts ---------- */

.apd-dl {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr);
    gap: 9px 14px;
    margin: 0;
    font-size: var(--fs-sm);
    align-items: baseline;
}

.apd-dl dt {
    color: var(--text-tertiary);
    white-space: nowrap;
}

.apd-dl dd {
    margin: 0;
    min-width: 0;
    color: var(--text-primary);
    overflow-wrap: anywhere;
}

.apd-num { font-family: var(--font-mono); font-size: var(--fs-xs); }

/* An address and a number are there to be used. They were plain text
   in a definition list, to be selected by hand and retyped. */
.apd-lnk {
    color: var(--text-brand);
    text-decoration: none;
    border-radius: var(--r-xs);
}

.apd-lnk:hover { text-decoration: underline; }
.apd-lnk:focus-visible { outline: none; box-shadow: var(--shadow-focus); }

/* ---------- Narrower ---------- */

/* The facts stop being a column and become a band under the record.
   Below this the 320px column and a timeline share about 620px, and
   the timeline is the part that suffers. */
@media (max-width: 1080px) {
    .apd-grid { grid-template-columns: minmax(0, 1fr); }
    .apd-side { position: static; }
}

/* The dates stop being pushed to the far end of the band and follow
   the role they belong to. */
@media (max-width: 860px) {
    .apd-facts { margin-inline-start: 0; }
    .apd-job { inline-size: 100%; }
}

/* Six labelled steps in a row stop fitting: the rail keeps its
   markers and drops the words, which the select below it still
   carries in full. */
@media (max-width: 720px) {
    .apd-panels { grid-template-columns: minmax(0, 1fr); }

    .apd-rail-row { gap: 12px; }
    .apd-rail { flex-basis: 100%; }
    .apd-stage { inline-size: 100%; }
    .apd-stage-sel { flex: 1; min-inline-size: 0; }
}

@media (max-width: 520px) {
    .apd-hero { padding: 16px; }
    .apd-step-l { display: none; }
    .apd-step.is-term { padding-inline-start: 8px; }
    .apd-fit-ring { width: 54px; height: 54px; }
    .apd-dl { grid-template-columns: minmax(0, 1fr); gap: 2px; }
    .apd-dl dd { margin-block-end: 8px; }
}


/* ============================================================
   7. Recruiter: Dashboard
   Was Recruiter/Pages/Dashboard.razor.css.
   ============================================================ */

/* Scoped styles for the recruiter dashboard.

   The page used to carry every rule as an inline style attribute, which meant
   no hover states, no focus states, no media queries and no way to keep two
   tables looking alike. Everything visual now lives here; the markup keeps only
   the styles that are genuinely data-driven (a bar width, a tone colour).

   Layout is one 12-column grid so every card lands on the same rhythm, and the
   breakpoints collapse it in the order the content matters: the wide tables
   keep their width longest, the three narrow cards fold first. */

.dash {
    display: flex;
    flex-direction: column;
    gap: 20px;
    padding-block-end: 28px;
}

/* ============================================================
   Header
   ============================================================ */

.dash-head {
    display: flex;
    align-items: flex-end;
    gap: 16px;
    flex-wrap: wrap;
}

.dash-head-main {
    flex: 1 1 320px;
    min-width: 0;
}

.dash-date {
    font-size: var(--fs-2xs);
    font-weight: var(--fw-semibold);
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--gold-700);
}

.dash-title {
    margin: 4px 0 0;
    font-size: 28px;
    font-weight: var(--fw-bold);
    letter-spacing: -0.024em;
    line-height: var(--lh-tight);
    color: var(--p-950);
}

.dash-actions {
    display: flex;
    align-items: center;
    gap: 8px;
    flex-wrap: wrap;
    margin-inline-start: auto;
}

/* An anchor wrapping a QButton must add no styling of its own. */
.dash-link {
    display: inline-flex;
    color: inherit;
    text-decoration: none;
}

/* ---------- Signal chips ----------
   Ambient context only (tenant, clients, open roles) plus one jump link to
   the SLA rows when there are breaches. Actionable items are listed in full
   in the "Needs you today" card, so the header does not repeat them. */

.dash-signals {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 6px;
    margin-block-start: 12px;
}

.dash-sig {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    height: 24px;
    padding: 0 9px;
    border: 1px solid var(--border-default);
    border-radius: var(--r-full);
    background: var(--n-0);
    font-size: var(--fs-xs);
    font-weight: var(--fw-medium);
    color: var(--text-secondary);
    white-space: nowrap;
    text-decoration: none;
    transition: border-color var(--dur-fast) var(--ease-out),
                background-color var(--dur-fast) var(--ease-out);
}

.dash-sig b {
    font-family: var(--font-mono);
    font-variant-numeric: tabular-nums;
    font-weight: var(--fw-semibold);
    color: var(--text-primary);
}

.dash-sig b,
.dash-sig svg {
    flex-shrink: 0;
}

.dash-sig-org {
    background: var(--p-900);
    border-color: transparent;
    color: var(--n-0);
    font-weight: var(--fw-semibold);
    max-width: 260px;
    overflow: hidden;
    text-overflow: ellipsis;
}

.dash-sig-org svg { color: var(--gold-400); }

.dash-sig-bad {
    background: var(--danger-50);
    border-color: var(--danger-200);
    color: var(--danger-700);
}

.dash-sig-bad b { color: inherit; }

a.dash-sig:hover {
    border-color: currentColor;
}

a.dash-sig:focus-visible {
    outline: none;
    box-shadow: var(--shadow-focus);
}

/* ---------- Range toggle ----------
   Anchors, not buttons: the page is statically rendered, so a click handler
   would never fire. Styled to match the .segmented primitive. */

.dash-range {
    display: inline-flex;
    gap: 2px;
    padding: 3px;
    background: var(--n-100);
    border-radius: var(--r-md);
}

.dash-range a {
    display: inline-flex;
    align-items: center;
    height: 26px;
    padding: 0 11px;
    border-radius: var(--r-sm);
    font-size: var(--fs-sm);
    font-weight: var(--fw-semibold);
    color: var(--text-tertiary);
    text-decoration: none;
    transition: color var(--dur-fast) var(--ease-out),
                background-color var(--dur-fast) var(--ease-out);
}

.dash-range a:hover { color: var(--text-primary); }

.dash-range a.is-on {
    background: var(--n-0);
    color: var(--text-primary);
    box-shadow: var(--shadow-xs);
}

.dash-range a:focus-visible {
    outline: none;
    box-shadow: var(--shadow-focus);
}

/* ============================================================
   Grids
   ============================================================ */

.dash-kpis {
    display: grid;
    grid-template-columns: repeat(5, minmax(0, 1fr));
    gap: 12px;
}

/* KpiTile renders its own root, so this is the one place the scope has to be
   pierced. It only adds a hover lift - if the rewrite ever stops matching, the
   tiles are unchanged rather than broken. */
.dash-kpis > div {
    height: 100%;
    box-shadow: var(--shadow-xs);
    transition: box-shadow var(--dur-fast) var(--ease-out),
                border-color var(--dur-fast) var(--ease-out);
}

.dash-kpis > div:hover {
    border-color: var(--border-strong);
    box-shadow: var(--shadow-md);
}

/* Work column + action rail. The rail is a fixed reading width; the work
   column takes the rest, which is what the two tables need. */
.dash-body {
    display: grid;
    grid-template-columns: minmax(0, 1fr) 360px;
    gap: 20px;
    align-items: start;
}

.dash-main,
.dash-rail {
    display: flex;
    flex-direction: column;
    gap: 20px;
    min-width: 0;
}

/* Generic 12-column spans, kept for any page still reaching for them. */
.dash-grid {
    display: grid;
    grid-template-columns: repeat(12, minmax(0, 1fr));
    gap: 16px;
    align-items: start;
}

.c4  { grid-column: span 4; }
.c8  { grid-column: span 8; }
.c12 { grid-column: 1 / -1; }

/* ============================================================
   Pipeline funnel
   ============================================================ */

.dash-funnel {
    display: flex;
    align-items: stretch;
    gap: 0;
}

.dash-stage {
    flex: 1 1 0;
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: 6px;
    padding: 12px 12px 10px;
    border-radius: var(--r-lg);
    background: var(--n-25);
    border: 1px solid var(--border-subtle);
    transition: border-color var(--dur-fast) var(--ease-out),
                background-color var(--dur-fast) var(--ease-out);
}

.dash-stage:hover {
    border-color: var(--border-strong);
    background: var(--n-0);
}

/* The last stage is the outcome, so it is the one that carries colour. */
.dash-stage.is-last {
    background: var(--success-50);
    border-color: var(--success-100);
}

.dash-stage.is-last .dash-col-fill {
    background: linear-gradient(180deg, var(--success-500), var(--success-700));
}

.dash-stage-name {
    font-size: var(--fs-2xs);
    font-weight: var(--fw-semibold);
    letter-spacing: 0.05em;
    text-transform: uppercase;
    color: var(--text-tertiary);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.dash-stage-n {
    font-family: var(--font-mono);
    font-size: 24px;
    font-weight: var(--fw-medium);
    line-height: 1.05;
    letter-spacing: -0.02em;
    font-variant-numeric: tabular-nums;
    color: var(--text-primary);
}

/* A fixed-height column filled from the bottom: the tiles step down left to
   right, which is the funnel shape a row of equal cards never had. */
.dash-col {
    position: relative;
    height: 72px;
    margin-block-start: 4px;
    border-radius: var(--r-md);
    background: repeating-linear-gradient(0deg, var(--n-100) 0 1px, transparent 1px 18px), var(--n-75);
    overflow: hidden;
}

.dash-col-fill {
    position: absolute;
    inset-inline: 0;
    inset-block-end: 0;
    max-height: 100%;
    border-radius: var(--r-md) var(--r-md) 0 0;
    background: linear-gradient(180deg, var(--p-500), var(--p-800));
    overflow: hidden;
    transition: height var(--dur-slow) var(--ease-out);
}

.dash-col-ai {
    position: absolute;
    inset-inline: 0;
    inset-block-end: 0;
    background: var(--gold-400);
}

.dash-stage-foot {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 6px;
    min-height: 16px;
}

.dash-stage-pct {
    font-family: var(--font-mono);
    font-size: var(--fs-2xs);
    color: var(--text-tertiary);
    font-variant-numeric: tabular-nums;
}

.dash-stage-ai {
    display: inline-flex;
    align-items: center;
    gap: 3px;
    font-family: var(--font-mono);
    font-size: 10px;
    font-weight: var(--fw-semibold);
    color: var(--gold-700);
}

/* The connector carries the step conversion - the rate lives between the two
   numbers it is computed from. Decorative chevron, readable percentage. */
.dash-step-conv {
    flex: 0 0 44px;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 2px;
    color: var(--n-300);
}

.dash-step-pct {
    padding: 2px 5px;
    border-radius: var(--r-full);
    background: var(--p-50);
    font-family: var(--font-mono);
    font-size: 10px;
    font-weight: var(--fw-semibold);
    font-variant-numeric: tabular-nums;
    color: var(--p-700);
    white-space: nowrap;
}

/* ============================================================
   Row lists (needs you today)
   ============================================================ */

.dash-group {
    padding: 10px 8px 4px;
    font-size: var(--fs-2xs);
    font-weight: var(--fw-semibold);
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--text-tertiary);
}

.dash-group:first-child { padding-block-start: 4px; }

.dash-rows {
    display: flex;
    flex-direction: column;
    gap: 2px;
}

.dash-row {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 8px;
    border-radius: var(--r-md);
    color: inherit;
    text-decoration: none;
    transition: background-color var(--dur-fast) var(--ease-out);
}

a.dash-row:hover { background: var(--n-75); }

a.dash-row:focus-visible {
    outline: none;
    box-shadow: var(--shadow-focus);
}

.dash-row-main {
    flex: 1;
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: 1px;
}

.dash-row-t {
    font-size: var(--fs-sm);
    font-weight: var(--fw-semibold);
    color: var(--text-primary);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.dash-row-t.dash-wrap {
    white-space: normal;
    font-weight: var(--fw-medium);
    line-height: var(--lh-snug);
}

.dash-row-s {
    font-size: var(--fs-xs);
    color: var(--text-tertiary);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.dash-ic {
    display: grid;
    place-items: center;
    width: 28px;
    height: 28px;
    flex-shrink: 0;
    border-radius: var(--r-md);
    background: var(--n-100);
    color: var(--n-600);
}

.dash-ic-bad  { background: var(--danger-50);  color: var(--danger-600); }
.dash-ic-warn { background: var(--warning-50); color: var(--warning-700); }

.dash-over {
    flex-shrink: 0;
    padding: 2px 7px;
    border-radius: var(--r-full);
    font-family: var(--font-mono);
    font-size: var(--fs-2xs);
    font-weight: var(--fw-semibold);
    white-space: nowrap;
}

.dash-over-bad  { background: var(--danger-50);  color: var(--danger-700); }
.dash-over-warn { background: var(--warning-50); color: var(--warning-700); }

.dash-task-n {
    flex-shrink: 0;
    min-width: 26px;
    padding: 2px 7px;
    border-radius: var(--r-full);
    text-align: center;
    font-family: var(--font-mono);
    font-size: var(--fs-xs);
    font-weight: var(--fw-semibold);
    font-variant-numeric: tabular-nums;
    background: var(--n-100);
    color: var(--n-700);
}

.dash-task-n-bad  { background: var(--danger-600); color: var(--n-0); }
.dash-task-n-warn { background: var(--warning-100); color: var(--warning-700); }

.dash-chev { color: var(--n-400); flex-shrink: 0; }

.dash-more {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: 4px;
    padding: 4px 8px 2px;
    font-size: var(--fs-xs);
    font-weight: var(--fw-semibold);
    color: var(--text-brand);
    text-decoration: none;
}

.dash-more:hover { text-decoration: underline; }

.dash-badge {
    display: inline-grid;
    place-items: center;
    min-width: 22px;
    height: 22px;
    padding: 0 7px;
    border-radius: var(--r-full);
    background: var(--danger-600);
    color: var(--n-0);
    font-family: var(--font-mono);
    font-size: var(--fs-xs);
    font-weight: var(--fw-semibold);
    font-variant-numeric: tabular-nums;
}

/* The jump target from the header chip - leave room under the sticky topbar. */
.dash-needs { scroll-margin-top: 80px; }

/* ============================================================
   Jork AI card
   ============================================================ */

.dash-ai {
    position: relative;
    display: flex;
    flex-direction: column;
    gap: 12px;
    padding: 16px;
    border-radius: var(--r-xl);
    background: linear-gradient(155deg, var(--p-800) 0%, var(--p-950) 70%);
    color: var(--n-0);
    box-shadow: var(--shadow-lg);
    overflow: hidden;
    isolation: isolate;
}

.dash-ai::before {
    content: "";
    position: absolute;
    inset-block-start: -90px;
    inset-inline-end: -70px;
    z-index: -1;
    width: 240px;
    height: 240px;
    border-radius: 50%;
    background: radial-gradient(circle, rgba(245, 184, 0, 0.28) 0%, transparent 68%);
    pointer-events: none;
}

.dash-ai-head {
    display: flex;
    align-items: center;
    gap: 10px;
}

.dash-ai-mark {
    display: grid;
    place-items: center;
    width: 30px;
    height: 30px;
    flex-shrink: 0;
    border-radius: var(--r-lg);
    background: linear-gradient(145deg, var(--gold-300), var(--gold-600));
    color: var(--p-950);
    box-shadow: 0 2px 10px rgba(245, 184, 0, 0.35);
}

.dash-ai-title {
    flex: 1;
    margin: 0;
    font-size: var(--fs-lg);
    font-weight: var(--fw-semibold);
    letter-spacing: -0.01em;
    color: var(--n-0);
}

.dash-ai-live {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 2px 8px;
    border-radius: var(--r-full);
    background: rgba(255, 255, 255, 0.08);
    font-size: var(--fs-2xs);
    font-weight: var(--fw-semibold);
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--gold-300);
}

.dash-ai-dot {
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: var(--success-500);
    box-shadow: 0 0 0 0 rgba(16, 185, 129, 0.6);
    animation: dash-pulse 2s var(--ease-out) infinite;
}

@keyframes dash-pulse {
    0%   { box-shadow: 0 0 0 0 rgba(16, 185, 129, 0.55); }
    70%  { box-shadow: 0 0 0 7px rgba(16, 185, 129, 0); }
    100% { box-shadow: 0 0 0 0 rgba(16, 185, 129, 0); }
}

.dash-ai-note {
    margin: 0;
    font-size: var(--fs-sm);
    line-height: var(--lh-normal);
    color: rgba(255, 255, 255, 0.72);
}

.dash-ai-list {
    display: flex;
    flex-direction: column;
    gap: 6px;
}

.dash-ai-item {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 9px 10px;
    border: 1px solid rgba(255, 255, 255, 0.08);
    border-radius: var(--r-lg);
    background: rgba(255, 255, 255, 0.05);
    color: inherit;
    text-decoration: none;
    transition: background-color var(--dur-fast) var(--ease-out),
                border-color var(--dur-fast) var(--ease-out);
}

.dash-ai-item:hover {
    background: rgba(255, 255, 255, 0.1);
    border-color: rgba(245, 184, 0, 0.4);
}

.dash-ai-item:focus-visible {
    outline: 2px solid var(--gold-400);
    outline-offset: 1px;
}

.dash-ai-u {
    align-self: stretch;
    width: 3px;
    flex-shrink: 0;
    border-radius: var(--r-full);
    background: rgba(255, 255, 255, 0.3);
}

.dash-ai-u-high { background: var(--danger-500); }
.dash-ai-u-med  { background: var(--gold-400); }

.dash-ai-it {
    font-size: var(--fs-sm);
    font-weight: var(--fw-semibold);
    color: var(--n-0);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.dash-ai-is {
    font-size: var(--fs-xs);
    color: rgba(255, 255, 255, 0.6);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.dash-ai-sla {
    flex-shrink: 0;
    font-family: var(--font-mono);
    font-size: var(--fs-2xs);
    font-weight: var(--fw-semibold);
    color: var(--gold-300);
    white-space: nowrap;
}

.dash-ai-cta { align-self: flex-start; }

/* ============================================================
   Today's schedule - a timeline
   ============================================================ */

.dash-timeline {
    position: relative;
    margin: 0;
    padding: 0;
    list-style: none;
}

/* The spine runs through the dots, from the first entry to the last. */
.dash-timeline::before {
    content: "";
    position: absolute;
    inset-block: 18px;
    inset-inline-start: 62px;
    width: 2px;
    border-radius: var(--r-full);
    background: var(--n-100);
}

.dash-ev {
    position: relative;
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 8px 4px;
    --ev: var(--n-500);
    --ev-bg: var(--n-100);
}

.dash-ev-brand { --ev: var(--p-600);      --ev-bg: var(--p-50); }
.dash-ev-gold  { --ev: var(--gold-600);   --ev-bg: var(--gold-50); }
.dash-ev-warn  { --ev: var(--warning-600); --ev-bg: var(--warning-50); }
.dash-ev-bad   { --ev: var(--danger-600); --ev-bg: var(--danger-50); }

.dash-ev-time {
    width: 46px;
    flex-shrink: 0;
    text-align: end;
    font-family: var(--font-mono);
    font-size: var(--fs-xs);
    font-weight: var(--fw-semibold);
    font-variant-numeric: tabular-nums;
    color: var(--text-secondary);
}

.dash-ev-dot {
    position: relative;
    width: 10px;
    height: 10px;
    flex-shrink: 0;
    border-radius: 50%;
    background: var(--ev);
    box-shadow: 0 0 0 3px var(--n-0), 0 0 0 5px var(--ev-bg);
}

.dash-ev .dash-row-main {
    padding: 6px 10px;
    border-radius: var(--r-md);
    background: var(--ev-bg);
    border-inline-start: 3px solid var(--ev);
}

/* ============================================================
   Source performance - labelled bars
   ============================================================ */

.dash-src {
    display: flex;
    flex-direction: column;
    gap: 12px;
}

.dash-src-h {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 8px;
    margin-block-end: 5px;
}

.dash-src-name {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    min-width: 0;
    font-size: var(--fs-sm);
    font-weight: var(--fw-medium);
    color: var(--text-secondary);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.dash-src-row.is-ai .dash-src-name {
    color: var(--gold-700);
    font-weight: var(--fw-semibold);
}

.dash-src-v {
    display: inline-flex;
    align-items: baseline;
    gap: 6px;
    flex-shrink: 0;
    font-family: var(--font-mono);
    font-variant-numeric: tabular-nums;
}

.dash-src-v b {
    font-size: var(--fs-sm);
    font-weight: var(--fw-semibold);
    color: var(--text-primary);
}

.dash-src-hires {
    font-size: var(--fs-2xs);
    font-weight: var(--fw-semibold);
    color: var(--success-700);
}

.dash-src-track {
    position: relative;
    height: 8px;
    border-radius: var(--r-full);
    background: var(--n-100);
    overflow: hidden;
}

.dash-src-bar,
.dash-src-hire {
    position: absolute;
    inset-block: 0;
    inset-inline-start: 0;
    max-width: 100%;
    border-radius: inherit;
}

.dash-src-bar  { background: var(--p-300); }
.dash-src-hire { background: var(--p-800); }

.dash-src-row.is-ai .dash-src-bar  { background: var(--gold-300); }
.dash-src-row.is-ai .dash-src-hire { background: var(--gold-700); }

/* ============================================================
   Chart legend
   ============================================================ */

.dash-legend {
    display: flex;
    align-items: center;
    gap: 14px;
    flex-wrap: wrap;
    margin-block-start: 14px;
    padding-block-start: 12px;
    border-block-start: 1px solid var(--border-subtle);
}

.dash-legend-i {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    font-size: var(--fs-xs);
    color: var(--text-tertiary);
}

.dash-swatch {
    width: 8px;
    height: 8px;
    border-radius: var(--r-xs);
    flex-shrink: 0;
}

.dash-swatch-app  { background: var(--p-300); }
.dash-swatch-hire { background: var(--p-800); }

.dash-legend-ai {
    flex-basis: 100%;
    font-size: var(--fs-xs);
    font-weight: var(--fw-semibold);
    color: var(--gold-700);
}

/* ============================================================
   Tables
   ============================================================ */

/* The wrapper is the scrollport - overflow on one axis gives it on both - so
   the header inside pins to its top edge and not to the page's. The cap is
   what makes that mean anything: without a height the box grows to its rows,
   nothing ever scrolls inside it, and a pinned header pins to a port that
   never moves. Neither list is long enough today to reach the cap; both are
   fed by queries with a take, and this is what happens when one is raised. */
.dash-table-wrap {
    overflow: auto;
    max-block-size: clamp(280px, 52vh, 560px);
    --tbl-top: 0px;
}

.dash-table {
    width: 100%;
    border-collapse: collapse;
    font-size: 13px;
}

.dash-table th {
    position: sticky;
    inset-block-start: var(--tbl-top, 0px);
    z-index: 2;
    padding: 9px 12px;
    text-align: start;
    font-size: var(--fs-2xs);
    font-weight: var(--fw-semibold);
    letter-spacing: 0.05em;
    text-transform: uppercase;
    color: var(--text-tertiary);
    background: var(--n-25);
    /* The border belongs to the first row under border-collapse and scrolls
       away with it; the shadow belongs to the header cell. */
    box-shadow: inset 0 -1px 0 var(--border-subtle);
    white-space: nowrap;
}

.dash-table td {
    padding: 10px 12px;
    vertical-align: middle;
    border-block-start: 1px solid var(--border-subtle);
}

.dash-table tbody tr:first-child td { border-block-start: none; }

.dash-table tbody tr { transition: background-color var(--dur-fast) var(--ease-out); }
.dash-table tbody tr:hover { background: var(--n-25); }

.dash-table th:first-child,
.dash-table td:first-child { padding-inline-start: 20px; }

.dash-table th:last-child,
.dash-table td:last-child { padding-inline-end: 20px; }

/* SLA health as a 3px stripe on the row's leading edge: the table can be
   scanned for trouble without reading the last column. */
.dash-table tbody tr.dash-tr-bad  td:first-child { box-shadow: inset 3px 0 0 var(--danger-500); }
.dash-table tbody tr.dash-tr-warn td:first-child { box-shadow: inset 3px 0 0 var(--warning-500); }

/* Numeric and chip columns shrink to fit so the space goes to the names. */
.dash-num,
.dash-tight {
    white-space: nowrap;
    width: 1%;
}

.dash-num {
    text-align: end;
    font-family: var(--font-mono);
    font-variant-numeric: tabular-nums;
}

.dash-cell-t {
    font-size: 13px;
    font-weight: var(--fw-semibold);
    color: var(--text-primary);
}

.dash-cell-sub {
    display: flex;
    align-items: center;
    gap: 5px;
    margin-block-start: 2px;
    font-size: var(--fs-xs);
    color: var(--text-tertiary);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.dash-mono { font-family: var(--font-mono); }
.dash-small { font-size: 11.5px; }

.dash-muted { color: var(--text-secondary); }
.dash-dim { color: var(--text-tertiary); }

.dash-job {
    display: flex;
    flex-direction: column;
    min-width: 0;
    color: inherit;
    text-decoration: none;
}

a.dash-job:hover .dash-cell-t { color: var(--p-700); }

a.dash-job:focus-visible {
    outline: none;
    box-shadow: var(--shadow-focus);
    border-radius: var(--r-sm);
}

.dash-days {
    display: inline-block;
    min-width: 28px;
    padding: 1px 6px;
    border-radius: var(--r-sm);
    text-align: center;
    color: var(--text-secondary);
}

.dash-days.is-warn { background: var(--warning-50); color: var(--warning-700); }
.dash-days.is-bad  { background: var(--danger-50);  color: var(--danger-700); font-weight: var(--fw-semibold); }

.dash-person {
    display: inline-flex;
    align-items: center;
    gap: 9px;
    min-width: 0;
    color: inherit;
    text-decoration: none;
}

.dash-person-txt {
    display: flex;
    flex-direction: column;
    min-width: 0;
}

.dash-owner {
    max-width: 140px;
    overflow: hidden;
    text-overflow: ellipsis;
    font-size: var(--fs-sm);
}

a.dash-person:hover .dash-cell-t { color: var(--p-700); }

a.dash-person:focus-visible {
    outline: none;
    box-shadow: var(--shadow-focus);
    border-radius: var(--r-sm);
}

/* Fit score: a short meter beside the number, so 58 and 91 differ at a glance. */
.dash-fit {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    --fit: var(--n-400);
}

.dash-fit-good { --fit: var(--success-600); }
.dash-fit-warn { --fit: var(--warning-600); }

.dash-fit-track {
    position: relative;
    width: 48px;
    height: 6px;
    border-radius: var(--r-full);
    background: var(--n-100);
    overflow: hidden;
}

.dash-fit-bar {
    position: absolute;
    inset-block: 0;
    inset-inline-start: 0;
    border-radius: inherit;
    background: var(--fit);
}

.dash-fit-v {
    min-width: 20px;
    text-align: end;
    font-family: var(--font-mono);
    font-weight: var(--fw-semibold);
    font-variant-numeric: tabular-nums;
    color: var(--fit);
}

.dash-foot {
    padding: 10px 20px;
    border-block-start: 1px solid var(--border-subtle);
    font-size: var(--fs-xs);
    color: var(--text-tertiary);
}

/* ============================================================
   Empty state
   ============================================================ */

.dash-empty {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 9px;
    padding: 28px 16px;
    text-align: center;
    font-size: var(--fs-sm);
    color: var(--text-tertiary);
}

.dash-empty-ic {
    display: grid;
    place-items: center;
    width: 36px;
    height: 36px;
    border-radius: var(--r-lg);
    background: var(--n-75);
    color: var(--n-400);
}

.dash-empty-good .dash-empty-ic {
    background: var(--success-50);
    color: var(--success-600);
}

/* ============================================================
   Loading skeleton
   ============================================================ */

.dash-skel {
    background: linear-gradient(90deg, var(--n-100) 25%, var(--n-75) 38%, var(--n-100) 60%);
    background-size: 300% 100%;
    border-radius: var(--r-lg);
    animation: dash-shimmer 1.5s var(--ease-in-out) infinite;
}

.dash-skel-kpi { height: 104px; }

@keyframes dash-shimmer {
    from { background-position: 150% 0; }
    to   { background-position: -50% 0; }
}

@media (prefers-reduced-motion: reduce) {
    .dash-skel,
    .dash-ai-dot { animation: none; }
    .dash-col-fill { transition: none; }
}

/* ============================================================
   Verify-email banner
   ============================================================ */

/* Soft-gate reminder that sits above the dashboard until the signed-in user
   verifies their email. The message and the actions are two wrapping groups
   rather than four loose children, so "Resend" never strands on a line of its
   own while "Verify now" stays up on the message row. Below 560px the two
   groups stack and both buttons go full width at thumb height. */

.verify-banner {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    flex-wrap: wrap;
    margin-block-end: 16px;
    padding: 11px 16px;
    background: var(--gold-50, #FFFAE6);
    border: 1px solid var(--gold-300, #FCD96B);
    border-radius: 10px;
}

.verify-banner-msg {
    display: flex;
    align-items: flex-start;
    gap: 10px;
    /* Grows to fill the row, but is allowed to shrink past its content so a
       long email address wraps instead of pushing the page sideways. */
    flex: 1 1 260px;
    min-width: 0;
}

.verify-banner-icon {
    flex-shrink: 0;
    font-size: 16px;
    line-height: 1.45;
}

.verify-banner-text {
    min-width: 0;
    font-size: 13px;
    line-height: 1.45;
    color: #7a5c00;
    overflow-wrap: anywhere;
}

.verify-banner-text strong { color: #5c4500; }

.verify-banner-sent { color: #8a6d00; }

.verify-banner-actions {
    display: flex;
    align-items: center;
    gap: 8px;
    flex-shrink: 0;
}

.verify-banner-actions form { margin: 0; }

.verify-banner-cta {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    height: 32px;
    padding: 0 14px;
    background: var(--text-primary);
    color: var(--gold-500);
    border-radius: 7px;
    font-size: 12.5px;
    font-weight: 600;
    text-decoration: none;
    white-space: nowrap;
}

.verify-banner-cta:hover { color: var(--gold-500); opacity: 0.9; }

@media (max-width: 700px) {
    .verify-banner { padding: 10px 12px; }
    /* Force the break here rather than letting the actions squeeze onto the
       message row at an unreadable width. */
    .verify-banner-actions { width: 100%; }
}

@media (max-width: 560px) {
    .verify-banner-actions > * { flex: 1 1 0; min-width: 0; }
    .verify-banner-cta,
    .verify-banner-actions .btn {
        width: 100%;
        height: 36px;
    }
}

/* ============================================================
   Breakpoints - the rail folds above the work column first, then
   the funnel and KPI row collapse.
   ============================================================ */

@media (max-width: 1440px) {
    .dash-body { grid-template-columns: minmax(0, 1fr) 320px; }
}

@media (max-width: 1400px) {
    .dash-kpis { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}

/* Below this the tables need the full width. The rail moves above them as a
   two-up grid, so what needs doing still comes before what is worth reading. */
@media (max-width: 1200px) {
    .dash-body { grid-template-columns: minmax(0, 1fr); }
    .dash-rail {
        order: -1;
        display: grid;
        grid-template-columns: repeat(2, minmax(0, 1fr));
        align-items: start;
    }
    .c4 { grid-column: span 6; }
    .c8 { grid-column: 1 / -1; }
}

@media (max-width: 900px) {
    .dash-kpis { grid-template-columns: repeat(2, minmax(0, 1fr)); }
    /* Six columns do not fit: the funnel becomes a scrolling strip. */
    .dash-funnel { overflow-x: auto; padding-block-end: 4px; }
    .dash-stage { flex: 0 0 112px; }
    .dash-step-conv { flex-basis: 40px; }
}

@media (max-width: 820px) {
    .c4 { grid-column: 1 / -1; }
    .dash-rail { grid-template-columns: minmax(0, 1fr); }
    .dash-actions { margin-inline-start: 0; width: 100%; }
    .dash-title { font-size: 23px; }
}

@media (max-width: 560px) {
    .dash-kpis { grid-template-columns: minmax(0, 1fr); }
}


/* ============================================================
   8. Website: Landing hero + Portals page
   Was Website/Pages/{Landing,Portals}.razor.css (byte-identical copies).
   ============================================================ */

/* Landing page — pixel-translated from jork-design-files/index.html.
   This page intentionally does not use the authenticated-app design tokens;
   it lives in its own dark navy + gold visual world. */

.landing {
    min-height: 100vh;
    background: #0F1B2D;
    background-image:
        radial-gradient(ellipse 80% 50% at 50% 0%, rgba(245, 184, 0, 0.18), transparent 60%),
        radial-gradient(ellipse 60% 50% at 50% 100%, rgba(109, 40, 217, 0.15), transparent 70%);
    color: #fff;
    font-family: 'Inter', sans-serif;
}

.landing a {
    color: inherit;
    text-decoration: none;
}

.landing .wrap {
    max-width: 1240px;
    margin: 0 auto;
    padding: 32px 32px 64px;
}

/* ---------- Top brand strip ---------- */
.landing .top {
    display: flex;
    align-items: center;
    gap: 10px;
    color: #fff;
}

.landing .logo {
    width: 40px;
    height: 40px;
    border-radius: 10px;
    background: #F5B800;
    color: #0F1B2D;
    display: grid;
    place-items: center;
    font-weight: 800;
    font-size: 19px;
    letter-spacing: -0.02em;
}

.landing .brand-name {
    font-size: 15px;
    font-weight: 700;
}

.landing .brand-tag {
    font-size: 11px;
    color: #F5B800;
    font-weight: 600;
    letter-spacing: 0.04em;
    text-transform: uppercase;
}

/* ---------- Hero ---------- */
.hero {
    color: #fff;
    padding: 72px 0 48px;
    text-align: center;
}

.landing .pill {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 5px 12px;
    background: rgba(245, 184, 0, 0.14);
    color: #F5B800;
    border: 1px solid rgba(245, 184, 0, 0.3);
    border-radius: 999px;
    font-size: 11.5px;
    font-weight: 600;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    margin-bottom: 20px;
}

.landing .pill .dot {
    width: 6px;
    height: 6px;
    border-radius: 3px;
    background: #F5B800;
}

.hero h1 {
    font-size: 64px;
    font-weight: 800;
    letter-spacing: -0.03em;
    line-height: 1.0;
    margin: 0;
}

.hero h1 em {
    font-style: normal;
    color: #F5B800;
}

.sub {
    font-size: 18px;
    color: rgba(255, 255, 255, 0.72);
    margin: 18px auto 0;
    max-width: 640px;
    line-height: 1.55;
}

/* ---------- Card grid ---------- */
.landing .grid {
    display: grid;
    gap: 18px;
    margin: 48px auto 0;
}

/* 3-card row at desktop. Stays single-col on mobile via the existing
   media query at the bottom of this file. */
.landing .grid-3 {
    grid-template-columns: repeat(3, 1fr);
    max-width: 1080px;
}

.landing .card {
    background: rgba(255, 255, 255, 0.04);
    border: 1px solid rgba(255, 255, 255, 0.1);
    border-radius: 18px;
    padding: 28px;
    color: #fff;
    cursor: pointer;
    transition: all 0.2s;
    display: flex;
    flex-direction: column;
    gap: 14px;
    position: relative;
    overflow: hidden;
}

.landing .card:hover {
    background: rgba(255, 255, 255, 0.07);
    border-color: rgba(245, 184, 0, 0.4);
    transform: translateY(-2px);
}

.landing .card .accent {
    position: absolute;
    top: 0;
    right: 0;
    width: 120px;
    height: 120px;
    opacity: 0.18;
}

.landing .card-badge {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    font-size: 10.5px;
    font-weight: 700;
    color: #F5B800;
    letter-spacing: 0.06em;
    text-transform: uppercase;
}

.landing .card-badge-dot {
    width: 6px;
    height: 6px;
    border-radius: 3px;
    background: #F5B800;
}

.landing .card h3 {
    font-size: 24px;
    font-weight: 700;
    letter-spacing: -0.015em;
    margin: 0;
}

.landing .card p {
    font-size: 13.5px;
    color: rgba(255, 255, 255, 0.7);
    line-height: 1.55;
    margin: 0;
    min-height: 60px;
}

.landing .card .meta {
    display: flex;
    gap: 16px;
    padding-top: 14px;
    border-top: 1px solid rgba(255, 255, 255, 0.08);
    margin-top: auto;
    font-size: 11px;
    color: rgba(255, 255, 255, 0.5);
}

.landing .card .meta b {
    color: #F5B800;
    font-family: 'JetBrains Mono', monospace;
    font-weight: 500;
}

.landing .card .cta {
    display: flex;
    align-items: center;
    gap: 8px;
    font-size: 13.5px;
    font-weight: 600;
    color: #F5B800;
    margin-top: 10px;
}

.landing .card .cta .arrow {
    transition: transform 0.2s;
}

.landing .card:hover .cta .arrow {
    transform: translateX(4px);
}

/* ---------- Settings the product has not built yet ---------- */

/* Difficulty, time limit and proctoring are collected by the assign-assessment
   drawer and read by nothing: AssignAssessmentAsync takes a candidate, an
   application and a bank, and CandidateAssessment has no column for any of the
   three. Shown and disabled, with one line under the group saying so - the same
   answer the submit drawer gives its cover-letter and reference boxes. Deleting
   them would hide that the intent was ever expressed; leaving them live would
   let a recruiter set a 90-minute proctored test and be told it was sent. */
.cad-body .input:disabled {
    background: var(--n-50);
    color: var(--text-disabled);
    cursor: not-allowed;
}

/* ---------- The assessment link, once it exists ---------- */

.cad-copy { display: flex; gap: 6px; }

.cad-copy-i {
    flex: 1;
    min-width: 0;
    font-family: var(--font-mono);
    font-size: var(--fs-sm);
}

/* ---------- Footer ---------- */
.landing .footer {
    color: rgba(255, 255, 255, 0.4);
    font-size: 12px;
    text-align: center;
    margin-top: 64px;
    display: flex;
    justify-content: center;
    gap: 18px;
    align-items: center;
}

.landing .footer .ok {
    color: #10b981;
    display: inline-flex;
    gap: 5px;
    align-items: center;
}

.landing .footer .ok-dot {
    width: 6px;
    height: 6px;
    border-radius: 3px;
    background: #10b981;
}

/* ---------- Responsive: stack on small screens ---------- */
@media (max-width: 980px) {
    .landing .grid-3 { grid-template-columns: repeat(2, 1fr); max-width: 720px; }
}
@media (max-width: 720px) {
    .landing .grid, .landing .grid-3 { grid-template-columns: 1fr; max-width: 480px; }
    .hero h1 { font-size: 44px; }
    .landing .wrap { padding: 24px 20px 48px; }
}


/* ============================================================
   9. Super Admin: New role drawer
   SuperAdmin/Pages/RoleCreateDrawer.razor.

   A right-side drawer over the permission matrix, because the
   matrix is the context: the role is created and the tree behind
   it immediately reads as that role, without a page change in
   between.

   Inside, the form is a spine of numbered bands rather than a
   card per field. The five decisions are ordered - the portal
   decides which menu tree the access template writes against, so
   it has to come first - and a numbered spine shows that order
   where five stacked groups only showed five groups.

   The footer is not a button bar. It carries the role as it will
   be written, with the grant count taken off the live menu tree,
   because the answers that feed that line are five scroll
   positions apart by the time the button is reachable.

   The chrome is the shared drawer's (QDrawer, "20."): panel,
   scrim, pinned header and footer, and the body as the only
   thing that scrolls. What is here is the two places this form
   is not like the others, and then the form itself.
   ============================================================ */

/* The spine's bands carry their own block padding and draw their own
   rules between themselves, so the body neither pads nor gaps them.

   It keeps the raised ground too, and with it loses the scroll shadows:
   this body is a numbered spine, not a stack of panels, and the spine's
   hairline has to run unbroken from the first band to the last. Panels
   with gaps between them would cut it into five pieces. */
.rnew .drw-body {
    gap: 0;
    padding: 4px 20px 8px;
    background: var(--surface-raised);
}

/* Not a button bar: a summary line, then either the buttons or the discard
   question under it. .drw-foot is a row that wraps, and three full-width
   blocks in a wrapping row is a column with extra steps. .rnew-sum carries
   the 10px between itself and what follows, so the column adds none. */
.rnew .drw-foot {
    flex-direction: column;
    align-items: stretch;
    gap: 0;
}

.rnew-head-mark {
    display: grid;
    place-items: center;
    width: 34px;
    height: 34px;
    flex-shrink: 0;
    border-radius: var(--r-lg);
    background: var(--p-700);
    color: var(--text-inverse);
    box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.10), 0 1px 2px rgba(15, 19, 32, 0.14);
}

/* ---------- The spine ----------
   The number column carries a hairline the length of the form, punched out by
   each numbered disc. It is what makes five bands read as one ordered
   sequence instead of five unrelated groups - and the order is real: portal
   before access, because the template writes against the portal's menus. */

.rnew-sec {
    position: relative;
    display: grid;
    grid-template-columns: 26px minmax(0, 1fr);
    column-gap: 14px;
    padding-block: 18px;
    border-block-end: 1px solid var(--border-subtle);
}

.rnew-sec:last-of-type { border-block-end: 0; }

.rnew-sec::before {
    content: "";
    position: absolute;
    inset-block-start: 0;
    inset-inline-start: 12.5px;
    inline-size: 1px;
    block-size: 100%;
    background: var(--border-subtle);
}

.rnew-sec:first-of-type::before {
    inset-block-start: 32px;
    block-size: calc(100% - 32px);
}

.rnew-sec:last-of-type::before { block-size: 32px; }

.rnew-sec-n {
    position: relative;
    z-index: 1;
    display: grid;
    place-items: center;
    width: 26px;
    height: 26px;
    border-radius: var(--r-full);
    border: 1px solid var(--border-strong);
    background: var(--n-0);
    font-family: var(--font-mono);
    font-size: var(--fs-2xs);
    font-weight: var(--fw-semibold);
    color: var(--text-tertiary);
}

.rnew-sec-main { min-width: 0; }

/* Scope answers nothing when the template writes no grants. Dimmed rather
   than hidden: a section that vanishes takes its number with it, and the
   spine would count differently depending on an answer two bands up. */
.rnew-sec.is-muted { opacity: 0.5; }

.rnew-sec-t {
    margin: 3px 0 0;
    font-size: var(--fs-lg);
    font-weight: var(--fw-semibold);
    letter-spacing: -0.01em;
}

.rnew-sec-d {
    margin: 4px 0 12px;
    font-size: var(--fs-sm);
    line-height: var(--lh-relaxed);
    color: var(--text-secondary);
}

.rnew-banner {
    display: flex;
    align-items: flex-start;
    gap: 8px;
    margin-block-start: 16px;
    padding: 10px 12px;
    border: 1px solid var(--danger-200);
    border-radius: var(--r-md);
    background: var(--danger-50);
    color: var(--danger-700);
    font-size: var(--fs-sm);
    line-height: var(--lh-snug);
}

.rnew-banner > svg { flex-shrink: 0; margin-block-start: 1px; }

/* ---------- Choice tiles ----------
   Radios with room to explain themselves. Templates named "Standard" and
   "View only" mean nothing on their own; the function chips are the actual
   answer to what the choice does, so they sit on the tile, not in a tooltip. */

.rnew-pick { display: grid; gap: 8px; }

.rnew-pick-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.rnew-pick-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }

.rnew-tile {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 3px;
    padding: 11px 12px;
    border: 1px solid var(--border-default);
    border-radius: var(--r-lg);
    background: var(--n-0);
    text-align: start;
    transition: border-color .12s, background .12s, box-shadow .12s;
}

.rnew-tile:hover {
    border-color: var(--p-300);
    background: var(--n-25);
}

.rnew-tile:focus-visible {
    outline: none;
    box-shadow: var(--shadow-focus);
}

.rnew-tile.is-on {
    border-color: var(--p-600);
    background: var(--p-50);
    box-shadow: inset 0 0 0 1px var(--p-600);
}

/* Portal tiles: an icon and a word. At drawer width three blurbs would turn a
   choice into a wall of small print, so the description of the selected
   portal is one line under the group instead. */
.rnew-tile-sm {
    align-items: center;
    gap: 6px;
    padding: 12px 8px;
    text-align: center;
}

.rnew-tile-ico {
    display: grid;
    place-items: center;
    width: 28px;
    height: 28px;
    border-radius: var(--r-md);
    background: var(--p-50);
    color: var(--p-700);
}

.rnew-tile.is-on .rnew-tile-ico {
    background: var(--p-600);
    color: var(--text-inverse);
}

.rnew-tile-t {
    font-size: var(--fs-md);
    font-weight: var(--fw-semibold);
    color: var(--text-primary);
}

.rnew-tile.is-on .rnew-tile-t { color: var(--p-800); }

.rnew-tile-d {
    font-size: var(--fs-xs);
    line-height: var(--lh-snug);
    color: var(--text-secondary);
}

.rnew-tile-wide { min-height: 104px; }

/* The line under the portal tiles: what the portal is, and what it already
   holds. The counts belong to the selection, so they arrive with it. */
.rnew-ctx {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 8px;
    margin: 8px 0 0;
    font-size: var(--fs-xs);
    line-height: var(--lh-snug);
    color: var(--text-secondary);
}

.rnew-ctx-m {
    font-family: var(--font-mono);
    font-size: var(--fs-2xs);
    color: var(--text-tertiary);
}

.rnew-chips {
    display: flex;
    flex-wrap: wrap;
    gap: 4px;
    margin-block-start: 7px;
}

.rnew-chip {
    padding: 1px 7px;
    border-radius: var(--r-full);
    background: var(--n-75);
    color: var(--text-secondary);
    font-family: var(--font-mono);
    font-size: var(--fs-2xs);
}

.rnew-tile.is-on .rnew-chip {
    background: var(--p-100);
    color: var(--p-800);
}

.rnew-chip.is-off {
    background: transparent;
    border: 1px dashed var(--border-strong);
    color: var(--text-tertiary);
    font-family: var(--font-sans);
}

.rnew-tile.is-on .rnew-chip.is-off {
    background: transparent;
    border-color: var(--p-300);
    color: var(--p-700);
}

/* ---------- Fields ---------- */

.rnew-field { margin-block-end: 14px; }

.rnew-field:last-child { margin-block-end: 0; }

.rnew-lbl {
    display: block;
    margin-block-end: 5px;
    font-size: var(--fs-xs);
    font-weight: var(--fw-semibold);
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--text-tertiary);
}

.rnew-req { color: var(--danger-600); margin-inline-start: 2px; }

.rnew-help {
    margin-block-start: 5px;
    font-size: var(--fs-xs);
    line-height: var(--lh-snug);
    color: var(--text-tertiary);
}

/* ---------- The key ----------
   Read-only until asked otherwise, and dashed to say so: the key is derived,
   permanent, and what seeds and code refer to, so it is shown - a field you
   cannot see is a field you find out about from a migration.

   The input inherits its font, so mono lives on the wrapper rather than
   fighting the primitive's inline style. */

.rnew-keyline {
    display: flex;
    align-items: center;
    gap: 8px;
    height: var(--h-lg);
    padding-inline: 12px;
    border: 1px dashed var(--border-strong);
    border-radius: 8px;
    background: var(--n-25);
}

.rnew-keyval {
    min-width: 0;
    overflow: hidden;
    white-space: nowrap;
    text-overflow: ellipsis;
    font-family: var(--font-mono);
    font-size: var(--fs-sm);
    color: var(--p-800);
}

.rnew-keyval.is-empty {
    font-family: var(--font-sans);
    font-style: italic;
    color: var(--text-disabled);
}

.rnew-keyedit {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    margin-inline-start: auto;
    color: var(--p-700);
    font-size: var(--fs-xs);
    font-weight: var(--fw-semibold);
}

.rnew-keyedit:hover { text-decoration: underline; }

.rnew-monowrap { font-family: var(--font-mono); }

.rnew-keystate {
    display: flex;
    align-items: center;
    gap: 5px;
    margin-block-start: 6px;
    font-size: var(--fs-xs);
    line-height: var(--lh-snug);
    color: var(--text-tertiary);
}

.rnew-keystate > svg { flex-shrink: 0; }

.rnew-keystate-free { color: var(--success-700); }
.rnew-keystate-taken,
.rnew-keystate-invalid { color: var(--danger-700); }

.rnew-keyreset {
    margin-inline-start: auto;
    color: var(--p-700);
    font-size: var(--fs-xs);
    text-decoration: underline;
    white-space: nowrap;
}

.rnew-pulse,
.rnew-skel {
    display: inline-block;
    animation: rnew-pulse 1.1s ease-in-out infinite;
}

.rnew-pulse {
    width: 7px;
    height: 7px;
    border-radius: var(--r-full);
    background: var(--p-400);
}

.rnew-skel {
    width: 96px;
    height: 8px;
    border-radius: var(--r-xs);
    background: var(--n-200);
}

@keyframes rnew-pulse {
    0%, 100% { opacity: 0.25; }
    50% { opacity: 1; }
}

@media (prefers-reduced-motion: reduce) {
    .rnew-pulse,
    .rnew-skel { animation: none; }
}

/* ---------- Options ---------- */

.rnew-opt {
    display: flex;
    align-items: flex-start;
    gap: 12px;
    padding: 12px 14px;
    border: 1px solid var(--border-subtle);
    border-radius: var(--r-lg);
    background: var(--n-25);
}

.rnew-opt-t {
    font-size: var(--fs-md);
    font-weight: var(--fw-semibold);
}

.rnew-opt-d {
    margin-block-start: 2px;
    font-size: var(--fs-xs);
    line-height: var(--lh-snug);
    color: var(--text-secondary);
}

/* The default is single-occupancy, so turning it on here takes it off
   something else. Said before the click, not after. */
.rnew-warn {
    color: var(--warning-700);
    margin-inline-start: 3px;
}

/* ---------- Footer ---------- */

/* The role as it will be written, in the portal's own colours: the name
   people will pick, the key everything else will use, and what the click
   costs the database. */
.rnew-sum {
    display: flex;
    align-items: center;
    gap: 12px;
    margin-block-end: 10px;
    padding: 10px 12px;
    border-inline-start: 3px solid var(--gold-400);
    border-radius: var(--r-md);
    background: var(--p-700);
    color: var(--text-inverse);
}

.rnew-sum-id {
    flex: 1;
    min-width: 0;
}

.rnew-sum-name {
    font-size: var(--fs-md);
    font-weight: var(--fw-bold);
    line-height: 1.3;
    overflow: hidden;
    white-space: nowrap;
    text-overflow: ellipsis;
}

.rnew-sum-name.is-empty { color: var(--p-200); font-weight: var(--fw-medium); }

.rnew-sum-key {
    display: block;
    margin-block-start: 2px;
    font-family: var(--font-mono);
    font-size: var(--fs-2xs);
    color: var(--p-200);
    overflow: hidden;
    white-space: nowrap;
    text-overflow: ellipsis;
}

.rnew-sum-w {
    display: flex;
    align-items: center;
    gap: 6px;
    flex-shrink: 0;
    font-size: var(--fs-xs);
    color: var(--p-100);
    text-align: end;
}

.rnew-sum-w strong { color: var(--text-inverse); }

.rnew-sum-flag {
    padding: 1px 7px;
    border-radius: var(--r-full);
    background: var(--gold-400);
    color: var(--n-900);
    font-size: var(--fs-2xs);
    font-weight: var(--fw-bold);
    text-transform: uppercase;
    letter-spacing: 0.05em;
}

.rnew-foot-btns {
    display: flex;
    align-items: center;
    gap: 8px;
}

.rnew-foot-gap { flex: 1; }

/* A disabled primary button that will not say why is a dead end. This is the
   why, beside the button, and it turns into the go-ahead rather than
   disappearing - the spot stays occupied so the footer does not reflow on the
   last keystroke. */
.rnew-ready {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    min-width: 0;
    padding: 4px 10px;
    border-radius: var(--r-full);
    border: 1px solid var(--warning-100);
    background: var(--warning-50);
    color: var(--warning-700);
    font-size: var(--fs-xs);
    font-weight: var(--fw-semibold);
}

.rnew-ready > svg { flex-shrink: 0; }

.rnew-ready.is-ok {
    border-color: var(--success-100);
    background: var(--success-50);
    color: var(--success-700);
}

/* Closing a half-filled drawer asks first, and asks here rather than in a
   second dialog stacked on the first. */
.rnew-discard {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 8px 10px;
    border: 1px solid var(--danger-200);
    border-radius: var(--r-md);
    background: var(--danger-50);
    font-size: var(--fs-sm);
    font-weight: var(--fw-semibold);
    color: var(--danger-700);
}

.rnew-discard > span { flex: 1; }

/* ---------- Breakpoints ----------
   The drawer goes full width before its tiles start wrapping, and the spine
   is the last thing to go: at phone widths the numbers cost more room than
   the ordering they show is worth. */

@media (max-width: 620px) {
    .rnew-pick-2 { grid-template-columns: 1fr; }
    .rnew-tile-wide { min-height: 0; }
}

@media (max-width: 480px) {
    .rnew-sec { grid-template-columns: minmax(0, 1fr); }
    .rnew-sec-n,
    .rnew-sec::before { display: none; }
    .rnew-sum-w { display: none; }
}


/* ============================================================
   10. Recruiter: New role wizard
   Recruiter/Pages/JobCreateWizard.razor.

   Every rule on this page used to be an inline style attribute:
   no hover, no focus ring, no breakpoints, and four steps whose
   inputs were each given their own hand-written border - so the
   error state was a different string in six places and the page
   drifted from the list screens it opens from.

   Shape follows the recruiter list pages (.cli): a pinned
   identity header, then the working surface. What differs is
   what a wizard needs and a list does not - a rail that shows
   where the four steps end, and a footer that stays reachable,
   because Continue was the one control that scrolled away on the
   longest step.

   The rail is a spine, like the super-admin role drawer's: one
   hairline punched out by four discs. It is not a control -
   steps validate forward, so a click that jumped over Basics
   would be a link to a step the wizard cannot honour.
   ============================================================ */

.jnw {
    display: flex;
    flex-direction: column;
    gap: 16px;
}

/* ---------- Header ----------
   The same pinned, full-bleed bar the list pages use: the negative inline
   margins cancel main's 8px padding, and inset-block-start matches it so the
   bar pins flush to the top rather than 8px down. Both track main's padding. */

.jnw-head {
    position: sticky;
    inset-block-start: -8px;
    z-index: var(--z-sticky);
    display: flex;
    align-items: center;
    gap: 12px;
    flex-wrap: wrap;
    margin-block-start: -8px;
    margin-inline: -8px;
    padding: 8px 16px;
    /* Same white-and-lifted bar as .cli-head - see the note there. */
    background: var(--surface-raised);
    border-block-end: 1px solid var(--border-subtle);
    box-shadow: 0 1px 2px rgba(15, 19, 32, 0.05), 0 4px 10px -4px rgba(15, 19, 32, 0.10);
}

.jnw-id {
    flex: 1 1 320px;
    min-width: 0;
    display: flex;
    align-items: center;
    gap: 12px;
}

.jnw-id-mark {
    display: grid;
    place-items: center;
    width: 40px;
    height: 40px;
    flex-shrink: 0;
    border-radius: var(--r-lg);
    background: var(--p-700);
    color: var(--text-inverse);
    box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.10), 0 1px 2px rgba(15, 19, 32, 0.14);
}

.jnw-id-txt {
    min-width: 0;
    display: flex;
    flex-direction: column;
}

/* Back to the list the role will land in, not a dead label: the wizard is
   nearly always opened from Jobs, and this is the way back that does not go
   through the discard dialog when nothing has been typed. */
.jnw-crumb {
    align-self: flex-start;
    display: inline-flex;
    align-items: center;
    gap: 2px;
    margin-inline-start: -2px;
    font-size: 10px;
    font-weight: var(--fw-bold);
    letter-spacing: 0.07em;
    text-transform: uppercase;
    line-height: 1.3;
    color: var(--p-700);
}

.jnw-crumb:hover { text-decoration: underline; }

.jnw-crumb:focus-visible {
    outline: none;
    border-radius: var(--r-xs);
    box-shadow: var(--shadow-focus);
}

.jnw-id-line {
    display: flex;
    align-items: center;
    gap: 8px;
    min-width: 0;
    margin-block-start: 1px;
}

.jnw-title {
    margin: 0;
    font-size: var(--fs-xl);
    font-weight: var(--fw-bold);
    letter-spacing: -0.015em;
    line-height: 1.25;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* Position in the sequence, in figures rather than words: the rail names the
   steps, so the bar only has to say how far in this is - and it has to say it
   in whatever language the rail is in. */
.jnw-count {
    flex-shrink: 0;
    padding: 2px 8px;
    border-radius: var(--r-full);
    background: var(--p-50);
    color: var(--p-700);
    font-family: var(--font-mono);
    font-size: var(--fs-2xs);
    font-weight: var(--fw-semibold);
    letter-spacing: 0.04em;
}

.jnw-head-act {
    display: flex;
    align-items: center;
    gap: 8px;
    margin-inline-start: auto;
}

/* ---------- Layout ----------
   Full width, like the list pages: the rail takes its 208px and the form
   takes the rest of the screen, so nothing is capped short of a bar that
   runs edge to edge. The tracks absorb the extra width - a wide screen
   gets wider fields, not a column of white space beside them. */

.jnw-layout {
    display: grid;
    grid-template-columns: 208px minmax(0, 1fr);
    gap: 20px;
    align-items: start;
}

.jnw-main {
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: 14px;
}

/* ---------- Step rail ----------
   Sticks below the header, so on the description step - the one that runs
   several screens - the four steps are still on screen when the recruiter
   looks up from the editor. */

.jnw-rail {
    position: sticky;
    inset-block-start: 64px;
    padding: 16px 14px;
    border: 1px solid var(--border-default);
    border-radius: var(--r-lg);
    background: var(--n-0);
    box-shadow: var(--shadow-xs);
}

/* A meter, not a label. Four steps is a short walk and the discs already say
   which one this is; the bar is there so the walk has a visible end. */
.jnw-prog {
    height: 3px;
    margin-block-end: 14px;
    border-radius: var(--r-full);
    background: var(--n-100);
    overflow: hidden;
}

.jnw-prog-i {
    height: 100%;
    border-radius: var(--r-full);
    background: var(--p-700);
    transition: width var(--dur-mid) var(--ease-out);
}

.jnw-steps {
    margin: 0;
    padding: 0;
    list-style: none;
}

/* The spine: one hairline down the disc column, punched out by each disc,
   trimmed at both ends so it starts and stops on a step rather than in air. */
.jnw-step {
    position: relative;
    display: grid;
    grid-template-columns: 24px minmax(0, 1fr);
    column-gap: 10px;
    align-items: center;
    padding-block: 9px;
}

.jnw-step::before {
    content: "";
    position: absolute;
    inset-block-start: 0;
    inset-inline-start: 11.5px;
    inline-size: 1px;
    block-size: 100%;
    background: var(--border-default);
}

.jnw-step:first-child::before {
    inset-block-start: 50%;
    block-size: 50%;
}

.jnw-step:last-child::before { block-size: 50%; }

/* Done steps colour their own segment of the spine, so progress reads down
   the rail without counting the discs. */
.jnw-step.is-done::before { background: var(--p-300); }

.jnw-step-n {
    position: relative;
    z-index: 1;
    display: grid;
    place-items: center;
    width: 24px;
    height: 24px;
    border-radius: var(--r-full);
    border: 1px solid var(--border-strong);
    background: var(--n-0);
    font-family: var(--font-mono);
    font-size: var(--fs-2xs);
    font-weight: var(--fw-semibold);
    color: var(--text-tertiary);
}

.jnw-step.is-done .jnw-step-n {
    border-color: var(--p-700);
    background: var(--p-700);
    color: var(--text-inverse);
}

/* The current step is a ring rather than a fill: filled means finished, and
   two filled discs of different colours would be a legend to learn. */
.jnw-step.is-now .jnw-step-n {
    border-color: var(--p-700);
    color: var(--p-800);
    box-shadow: 0 0 0 3px var(--p-100);
}

.jnw-step-l {
    min-width: 0;
    font-size: var(--fs-sm);
    font-weight: var(--fw-medium);
    line-height: var(--lh-snug);
    color: var(--text-tertiary);
}

.jnw-step.is-done .jnw-step-l { color: var(--text-secondary); }

.jnw-step.is-now .jnw-step-l {
    font-weight: var(--fw-semibold);
    color: var(--text-primary);
}

/* ---------- Form grid ----------
   Six tracks, because the fields fall into thirds and halves: a title and a
   client share a line, and type/seniority/headcount is a third each. The span
   classes are the only place a field's width is decided. */

.jnw-grid {
    display: grid;
    grid-template-columns: repeat(6, minmax(0, 1fr));
    gap: 14px 16px;
}

.jnw-c2 { grid-column: span 2; }
.jnw-c3 { grid-column: span 3; }
.jnw-c4 { grid-column: span 4; }
.jnw-c6 { grid-column: span 6; }

/* Group breaks. The form has four clusters - what the role is, where it is
   worked, when it is due, what it pays - separated by a rule rather than by
   four headings, because the labels already name every field and a heading
   over three of them would only repeat them. */
.jnw-sep {
    grid-column: 1 / -1;
    height: 1px;
    margin-block: 2px;
    background: var(--border-subtle);
}

/* Figures line up when they are monospaced: headcount, salary and SLA are
   read against each other, and a proportional 8 is narrower than a 1. */
.jnw-mono { font-family: var(--font-mono); }

.jnw-err {
    display: flex;
    align-items: center;
    gap: 4px;
    margin: 5px 0 0;
    font-size: var(--fs-xs);
    color: var(--danger-700);
}

.jnw-err > svg { flex-shrink: 0; }

/* ---------- Two-pane steps ----------
   Description and Distribution both carry a main panel and a 320px rail of
   things that comment on it. */

.jnw-split {
    display: grid;
    grid-template-columns: minmax(0, 1fr) 320px;
    gap: 14px;
    align-items: start;
}

.jnw-side {
    display: flex;
    flex-direction: column;
    gap: 12px;
    min-width: 0;
}

.jnw-stack {
    display: flex;
    flex-direction: column;
    gap: 16px;
}

.jnw-lede {
    margin: 0;
    font-size: var(--fs-md);
    line-height: var(--lh-relaxed);
    color: var(--text-secondary);
}

/* ---------- Chip groups ----------
   Four tag lists in a row read as one long ribbon of colour. Boxed and paired,
   each list is a thing with edges, and the four tones stay the difference
   between must-have and nice-to-have rather than decoration. */

.jnw-chips {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 12px;
}

.jnw-chipbox {
    min-width: 0;
    padding: 12px 14px;
    border: 1px solid var(--border-subtle);
    border-radius: var(--r-lg);
    background: var(--n-25);
}

/* ---------- AI panels ----------
   Both AI checks are the same object: a sentence about what it does, one
   button, and a verdict that replaces nothing - the sentence stays, because a
   check that has run once is usually run again after an edit. */

.jnw-ai-d {
    margin: 0;
    font-size: var(--fs-sm);
    line-height: var(--lh-relaxed);
    color: var(--text-secondary);
}

.jnw-ai-btn { width: 100%; margin-block-start: 10px; }

.jnw-ai-res {
    margin-block-start: 10px;
    padding: 10px 12px;
    border: 1px solid var(--border-default);
    border-radius: var(--r-md);
    background: var(--n-25);
    font-size: var(--fs-xs);
    line-height: var(--lh-snug);
    color: var(--text-secondary);
}

.jnw-ai-res.is-ok {
    border-color: var(--success-100);
    background: var(--success-50);
    color: var(--success-700);
}

.jnw-ai-res.is-warn {
    border-color: var(--warning-100);
    background: var(--warning-50);
    color: var(--warning-700);
}

.jnw-ai-t {
    display: flex;
    align-items: center;
    gap: 5px;
    font-weight: var(--fw-semibold);
}

.jnw-ai-t > svg { flex-shrink: 0; }

.jnw-ai-list {
    margin: 6px 0 0;
    padding-inline-start: 16px;
}

.jnw-ai-list li + li { margin-block-start: 3px; }

.jnw-ai-band {
    margin-block-start: 5px;
    font-family: var(--font-mono);
    font-size: var(--fs-2xs);
}

.jnw-ai-note { margin-block-start: 4px; }

/* ---------- Options ----------
   A checkbox with room to explain itself. Tinted when on, because these two
   are the switches that change what the Save button does. */

.jnw-opt {
    display: flex;
    align-items: flex-start;
    gap: 10px;
    padding: 12px 14px;
    border: 1px solid var(--border-subtle);
    border-radius: var(--r-lg);
    background: var(--n-25);
    cursor: pointer;
    transition: border-color var(--dur-fast), background var(--dur-fast);
}

.jnw-opt:hover { border-color: var(--border-strong); }

.jnw-opt.is-on {
    border-color: var(--p-200);
    background: var(--p-50);
}

/* Publishing is the one irreversible thing this page does, so its switch is
   the gold one - the same accent the footer uses to say the click will go
   live. */
.jnw-opt.jnw-opt-go.is-on {
    border-color: var(--gold-200);
    background: var(--gold-50);
}

.jnw-opt-t {
    font-size: var(--fs-md);
    font-weight: var(--fw-semibold);
}

.jnw-opt-d {
    margin-block-start: 2px;
    font-size: var(--fs-xs);
    line-height: var(--lh-snug);
    color: var(--text-secondary);
}

.jnw-check {
    width: 16px;
    height: 16px;
    margin: 1px 0 0;
    flex-shrink: 0;
    accent-color: var(--p-700);
}

/* ---------- Stage flow ----------
   The five default stages are a sequence, and five equal cards in a row said
   "five options". A connected track says the pipeline runs left to right and
   ends at Hired - which is the whole content of this step. */

.jnw-flow {
    display: grid;
    grid-template-columns: repeat(5, minmax(0, 1fr));
    gap: 10px;
    margin-block-start: 16px;
}

.jnw-flow-s {
    position: relative;
    min-width: 0;
    padding: 12px;
    border: 1px solid var(--border-default);
    border-radius: var(--r-lg);
    background: var(--n-0);
}

/* The rule runs from this stage's card to the next one's, at the height of
   the dots rather than the middle of the card. Drawn per stage, so it never
   shows past the last one. */
.jnw-flow-s:not(:last-child)::after {
    content: "";
    position: absolute;
    inset-block-start: 16px;
    inset-inline-end: -10px;
    inline-size: 10px;
    block-size: 1px;
    background: var(--border-strong);
}

.jnw-flow-h {
    display: flex;
    align-items: center;
    gap: 6px;
}

.jnw-flow-dot {
    width: 8px;
    height: 8px;
    flex-shrink: 0;
    border-radius: var(--r-full);
}

.jnw-flow-t {
    font-size: var(--fs-sm);
    font-weight: var(--fw-semibold);
}

.jnw-flow-d {
    margin-block-start: 6px;
    font-size: var(--fs-2xs);
    line-height: var(--lh-snug);
    color: var(--text-tertiary);
}

/* ---------- Review ----------
   The last step's rail is the role as it will be written. A definition list,
   because that is what it is, and mono on the figures so the comp band is
   read as a number rather than a phrase. */

.jnw-rev {
    display: grid;
    grid-template-columns: minmax(0, 100px) minmax(0, 1fr);
    gap: 8px 12px;
    margin: 0;
    font-size: var(--fs-sm);
}

.jnw-rev dt {
    color: var(--text-tertiary);
    line-height: var(--lh-snug);
}

.jnw-rev dd {
    margin: 0;
    min-width: 0;
    line-height: var(--lh-snug);
    overflow-wrap: anywhere;
}

.jnw-rev dd.is-strong { font-weight: var(--fw-semibold); }

.jnw-rev dd.is-empty { color: var(--text-disabled); }

/* ---------- Submit error ---------- */

.jnw-alert {
    display: flex;
    align-items: flex-start;
    gap: 8px;
    padding: 11px 13px;
    border: 1px solid var(--danger-200);
    border-radius: var(--r-md);
    background: var(--danger-50);
    color: var(--danger-700);
    font-size: var(--fs-sm);
    line-height: var(--lh-snug);
}

.jnw-alert > svg { flex-shrink: 0; margin-block-start: 1px; }

/* ---------- Footer ----------
   Pinned to the bottom of the scroll container the same way the header is
   pinned to the top. Continue used to sit under the fold on the description
   step, so finishing a role meant scrolling back past everything already
   written.

   It stays in flow, so it takes its own space at the end of the page rather
   than covering the last field. */

.jnw-foot {
    position: sticky;
    inset-block-end: -8px;
    z-index: var(--z-sticky);
    display: flex;
    align-items: center;
    gap: 12px;
    flex-wrap: wrap;
    margin-block-start: 4px;
    margin-inline: -8px;
    padding: 10px 16px;
    background: var(--n-0);
    border-block-start: 1px solid var(--border-default);
    box-shadow: 0 -6px 18px -12px rgba(15, 19, 32, 0.22);
}

.jnw-foot-gap { flex: 1; }

.jnw-foot-btns {
    display: flex;
    align-items: center;
    gap: 8px;
}

/* ---------- Discard dialog ---------- */

.jnw-scrim {
    position: fixed;
    inset: 0;
    z-index: var(--z-modal);
    background: var(--surface-overlay);
}

.jnw-modal {
    position: fixed;
    inset-block-start: 50%;
    inset-inline-start: 50%;
    transform: translate(-50%, -50%);
    z-index: calc(var(--z-modal) + 1);
    width: 430px;
    max-width: calc(100vw - 32px);
    padding: 20px;
    border: 1px solid var(--border-default);
    border-radius: var(--r-lg);
    background: var(--n-0);
    box-shadow: var(--shadow-xl);
}

.jnw-modal-h {
    display: flex;
    align-items: center;
    gap: 10px;
}

.jnw-modal-mark {
    display: grid;
    place-items: center;
    width: 32px;
    height: 32px;
    flex-shrink: 0;
    border-radius: var(--r-md);
    background: var(--danger-50);
    color: var(--danger-600);
}

.jnw-modal-t {
    font-size: var(--fs-lg);
    font-weight: var(--fw-bold);
    letter-spacing: -0.01em;
}

.jnw-modal-b {
    margin: 10px 0 16px;
    font-size: var(--fs-md);
    line-height: var(--lh-relaxed);
    color: var(--text-secondary);
}

.jnw-modal-btns {
    display: flex;
    justify-content: flex-end;
    gap: 8px;
}

/* ---------- Breakpoints ----------
   The rail is the first thing to go: it costs 228px of the form's width and
   the header already says which step this is. Below it the form loses tracks
   in the order the fields pair up - thirds to halves, halves to one. */

@media (max-width: 1240px) {
    .jnw-split { grid-template-columns: minmax(0, 1fr); }
}

@media (max-width: 1080px) {
    .jnw-layout { grid-template-columns: minmax(0, 1fr); }
    .jnw-rail { display: none; }
    .jnw-flow { grid-template-columns: repeat(3, minmax(0, 1fr)); }
    .jnw-flow-s::after { display: none; }
}

@media (max-width: 820px) {
    .jnw-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
    .jnw-c2, .jnw-c3, .jnw-c4, .jnw-c6 { grid-column: span 2; }
    .jnw-chips { grid-template-columns: minmax(0, 1fr); }
}

@media (max-width: 560px) {
    .jnw-grid { grid-template-columns: minmax(0, 1fr); }
    .jnw-c2, .jnw-c3, .jnw-c4, .jnw-c6 { grid-column: span 1; }
    .jnw-flow { grid-template-columns: minmax(0, 1fr); }
    .jnw-head, .jnw-foot { padding-inline: 14px; }
    .jnw-head-act { margin-inline-start: 0; }
    .jnw-rev { grid-template-columns: minmax(0, 1fr); gap: 2px 0; }
    .jnw-rev dd { margin-block-end: 8px; }
}

@media (prefers-reduced-motion: reduce) {
    .jnw-prog-i { transition: none; }
}


/* ============================================================
   11. Recruiter: AI Center
   Recruiter/Pages/AiCenter.razor.

   Five surfaces behind one route - sourcing campaigns, talent-pool
   search, match-score review, outreach drafts, provider config.
   Every rule on the page used to be an inline style attribute, so
   the five tabs had five hand-built tables that had already drifted
   apart, no hover or focus state anywhere, and two "stat cards" on
   the first tab that were a div pretending to be the KpiTile every
   other recruiter screen uses.

   Shape follows the recruiter list pages (.cli) and reuses them
   outright: the same pinned identity header, the same stat row, the
   same table metrics, the same banners. What lives here is what AI
   Center has and a list does not - the candidate and draft cards
   and the config rail. The switch is the shared .tabs row.

   The gold-on-navy accent is the product's AI mark - the same
   pairing as the Jobs page's .jb-ai strip - and it is what tells
   these AI surfaces apart from the navy list pages they open from.
   ============================================================ */

/* The identity tile every recruiter header carries, struck in the AI
   pairing rather than plain navy. One declaration, so the header is
   otherwise the same object as the one on Jobs and Clients. */
.aic .cli-id-mark {
    background: var(--p-900);
    color: var(--gold-300);
}

/* ============================================================
   Tab rail

   The shared .tabs row from "9.", not a rail of its own. It was a
   segmented control here - a navy pill for the active surface -
   which read as "pick one setting" and made this the only screen
   whose tabs were a different object from Career Site Builder's,
   the offer/JD switch and Settings -> Team & roles. Five views of
   one workspace is what a tab bar is for.

   The primitive carries the hairline, the 2px active rule sitting
   on it, the travelling hover tint, the focus ring, the arrow keys
   and the narrow-width scroll - and, since .tab became a flex box
   in its own right, the icon alignment this block used to add. All
   that is left here is the space under the row.
   ============================================================ */
/* The switched surface. Same 16px rhythm as the bands above it. */
.aic-panel {
    display: flex;
    flex-direction: column;
    gap: 16px;
}

/* Loading and "nothing here" inside a Pad=0 card, at the padding the
   card would have had. Both used to be a bare 24px inline style. */
.aic-load {
    padding: 24px;
    font-size: var(--fs-md);
    color: var(--text-tertiary);
}

.aic-empty { padding: 24px; }

/* Numeric and action cells that .cli-num cannot carry - .cli-num also imposes
   the mono face, and these cells set their own. */
.aic-end { text-align: end; }

/* A toggle has no label box of its own, so it needs the offset that lines it
   up with the inputs beside it. */
.aic-toggle { padding-block-start: 8px; }

/* The list tables here are read, not opened: nothing on a campaign or a match
   row is a link, so the row takes .cli-table's metrics but not .cli-row's
   pointer cursor - which would have promised a click that never came. */
.aic-tr { transition: background-color var(--dur-fast) var(--ease-out); }
.aic-tr:hover { background: var(--n-25); }

/* ============================================================
   Sourcing: campaign rows
   ============================================================ */

/* Found against target, as a bar rather than "17 / 40": the ratio is
   the question the column is asked, and two numbers side by side made
   every row look the same until you had read them both. */
.aic-prog {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: 8px;
}

.aic-prog-n {
    font-family: var(--font-mono);
    font-variant-numeric: tabular-nums;
    font-size: var(--fs-sm);
    color: var(--text-secondary);
    white-space: nowrap;
}

.aic-prog-bar {
    position: relative;
    width: 56px;
    height: 5px;
    flex-shrink: 0;
    border-radius: var(--r-full);
    background: var(--n-75);
    overflow: hidden;
}

.aic-prog-fill {
    position: absolute;
    inset-block: 0;
    inset-inline-start: 0;
    max-width: 100%;
    border-radius: inherit;
    background: linear-gradient(90deg, var(--gold-400), var(--gold-600));
}

/* Imported is the only number on the row that means a candidate is
   now in the ATS, so it is the only one that is coloured. */
.aic-won {
    font-family: var(--font-mono);
    font-variant-numeric: tabular-nums;
    font-size: var(--fs-sm);
    font-weight: var(--fw-semibold);
    color: var(--success-700);
}

.aic-won.is-zero { color: var(--text-tertiary); font-weight: var(--fw-regular); }

/* ============================================================
   Talent pool: the search console and its results
   ============================================================ */

/* The console reads as one instrument: what the search does, then the
   two controls that run it, on a tinted ground so it is clearly the
   input to the results below rather than another result. */
.aic-console {
    display: flex;
    flex-direction: column;
    gap: 12px;
    padding: 16px 20px;
    border: 1px solid var(--border-default);
    border-radius: var(--r-lg);
    background: linear-gradient(180deg, var(--p-50), var(--n-0));
}

.aic-console-t {
    display: flex;
    align-items: center;
    gap: 8px;
    font-size: var(--fs-md);
    font-weight: var(--fw-semibold);
    color: var(--text-primary);
}

.aic-console-t svg { color: var(--p-700); flex-shrink: 0; }

/* Deliberately narrow: the sentence explains a consent rule, and past
   about 70 characters a line of explanation stops being read. */
.aic-console-b {
    max-width: 62ch;
    font-size: var(--fs-sm);
    color: var(--text-secondary);
    line-height: var(--lh-relaxed);
}

.aic-console-row {
    display: flex;
    align-items: center;
    gap: 10px;
    flex-wrap: wrap;
}

.aic-console-row .input { min-width: 280px; flex: 0 1 340px; }

.aic-note {
    font-size: var(--fs-sm);
    color: var(--text-tertiary);
}

/* The reading of the run, above the cards it produced. */
.aic-sum {
    display: flex;
    align-items: center;
    gap: 8px;
    flex-wrap: wrap;
    padding: 10px 14px;
    border: 1px solid var(--border-subtle);
    border-radius: var(--r-md);
    background: var(--n-25);
    font-size: var(--fs-sm);
    color: var(--text-secondary);
}

.aic-sum b {
    font-family: var(--font-mono);
    font-variant-numeric: tabular-nums;
    color: var(--text-primary);
}

.aic-sum svg { color: var(--gold-600); flex-shrink: 0; }

/* Two columns where there is room. A surfaced candidate is a card to
   be judged, not a row to be scanned - the score, the skills it is
   built from and the model's reason all have to be on screen together
   for the judgement to be possible at all. */
.aic-cands {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 12px;
}

.aic-cand {
    display: flex;
    gap: 14px;
    padding: 14px 16px;
    border: 1px solid var(--border-default);
    border-radius: var(--r-lg);
    background: var(--n-0);
    transition: border-color var(--dur-fast) var(--ease-out),
                box-shadow var(--dur-fast) var(--ease-out);
}

.aic-cand:hover {
    border-color: var(--border-strong);
    box-shadow: var(--shadow-sm);
}

/* A ring, not a filled disc: the disc claimed the score was a verdict.
   The ring shows it as a fraction of 100 - --v is the only thing the
   markup sets, because it is the only thing that is data. */
.aic-score {
    position: relative;
    display: grid;
    place-items: center;
    width: 48px;
    height: 48px;
    flex-shrink: 0;
    border-radius: 50%;
    background: conic-gradient(var(--aic-score-fg) calc(var(--v) * 1%), var(--n-100) 0);
}

.aic-score::before {
    content: '';
    position: absolute;
    inset: 4px;
    border-radius: 50%;
    background: var(--n-0);
}

.aic-score span {
    position: relative;
    font-family: var(--font-mono);
    font-variant-numeric: tabular-nums;
    font-size: var(--fs-md);
    font-weight: var(--fw-bold);
    color: var(--aic-score-fg);
}

.aic-score.is-high { --aic-score-fg: var(--success-600); }
.aic-score.is-mid  { --aic-score-fg: var(--gold-500); }
.aic-score.is-low  { --aic-score-fg: var(--n-400); }

.aic-cand-b {
    flex: 1;
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: 4px;
}

.aic-cand-h {
    display: flex;
    align-items: center;
    gap: 8px;
    flex-wrap: wrap;
}

.aic-cand-n {
    font-size: var(--fs-lg);
    font-weight: var(--fw-semibold);
    letter-spacing: -0.01em;
}

.aic-cand-loc {
    display: inline-flex;
    align-items: center;
    gap: 3px;
    font-size: var(--fs-xs);
    color: var(--text-tertiary);
}

.aic-cand-hl {
    font-size: var(--fs-sm);
    color: var(--text-secondary);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.aic-skills {
    display: flex;
    flex-wrap: wrap;
    gap: 5px;
    margin-block-start: 3px;
}

/* Held and missing are the same object in two states, so they are one
   class and a modifier - a strike-through was the only thing telling
   them apart before, and it is the first thing lost at a glance. */
.aic-skill {
    padding: 2px 8px;
    border-radius: var(--r-full);
    border: 1px solid var(--success-100);
    background: var(--success-50);
    color: var(--success-700);
    font-size: var(--fs-xs);
    font-weight: var(--fw-medium);
    white-space: nowrap;
}

.aic-skill.is-miss {
    border-color: var(--border-default);
    background: var(--n-50);
    color: var(--text-tertiary);
    font-weight: var(--fw-regular);
    text-decoration: line-through;
}

/* The model's reason for the score, marked as the model's - it is the
   one line on the card nobody wrote. */
.aic-why {
    display: flex;
    gap: 7px;
    margin-block-start: 4px;
    padding: 8px 10px;
    border-radius: var(--r-md);
    border: 1px solid var(--gold-100);
    background: var(--gold-50);
    font-size: var(--fs-sm);
    color: var(--text-secondary);
    line-height: var(--lh-normal);
}

.aic-why svg {
    color: var(--gold-600);
    flex-shrink: 0;
    margin-block-start: 2px;
}

.aic-cand-act {
    flex-shrink: 0;
    align-self: center;
}

.aic-in {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    font-size: var(--fs-sm);
    font-weight: var(--fw-semibold);
    color: var(--success-700);
    white-space: nowrap;
}

/* ============================================================
   Match review

   The score is the column the table exists for, so it is the only one
   drawn rather than printed: a bar the eye can sort without reading
   five numbers first.
   ============================================================ */

.aic-sc {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: 8px;
}

.aic-sc-n {
    font-family: var(--font-mono);
    font-variant-numeric: tabular-nums;
    font-size: var(--fs-md);
    font-weight: var(--fw-bold);
    min-width: 34px;
    text-align: end;
}

.aic-sc-bar {
    position: relative;
    width: 52px;
    height: 5px;
    flex-shrink: 0;
    border-radius: var(--r-full);
    background: var(--n-75);
    overflow: hidden;
}

.aic-sc-fill {
    position: absolute;
    inset-block: 0;
    inset-inline-start: 0;
    max-width: 100%;
    border-radius: inherit;
    background: currentColor;
}

.aic-sc.is-high { color: var(--success-600); }
.aic-sc.is-mid  { color: var(--gold-500); }
.aic-sc.is-low  { color: var(--n-300); }

.aic-sc.is-high .aic-sc-n { color: var(--success-700); }
.aic-sc.is-mid  .aic-sc-n { color: var(--gold-700); }
.aic-sc.is-low  .aic-sc-n { color: var(--text-secondary); }

/* Which model produced the row, and whether it was a model at all. A
   stub score that looks like a real one is the most expensive thing
   this table can say. */
.aic-model {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    font-family: var(--font-mono);
    font-size: var(--fs-xs);
    color: var(--text-tertiary);
    white-space: nowrap;
}

/* Two lines of explanation, then an ellipsis. */
.aic-expl {
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
    max-width: 380px;
    font-size: var(--fs-sm);
    color: var(--text-secondary);
    line-height: var(--lh-snug);
}

/* ============================================================
   Outreach drafts

   A draft is a message, so it is shown as one - channel mark, subject,
   body - rather than as a table row that happens to contain 240
   characters of prose.
   ============================================================ */

.aic-facets {
    display: flex;
    gap: 6px;
    flex-wrap: wrap;
}

.aic-facet {
    height: 30px;
    padding: 0 14px;
    border: 1px solid var(--border-default);
    border-radius: var(--r-full);
    background: var(--n-0);
    font-family: inherit;
    font-size: var(--fs-sm);
    font-weight: var(--fw-semibold);
    color: var(--text-secondary);
    cursor: pointer;
    transition: background-color var(--dur-fast) var(--ease-out),
                color var(--dur-fast) var(--ease-out),
                border-color var(--dur-fast) var(--ease-out);
}

.aic-facet:hover { border-color: var(--border-strong); color: var(--text-primary); }
.aic-facet:focus-visible { outline: none; box-shadow: var(--shadow-focus); }

.aic-facet.is-on {
    background: var(--p-900);
    border-color: var(--p-900);
    color: var(--gold-300);
}

.aic-msg {
    display: grid;
    grid-template-columns: 32px minmax(0, 1fr) auto;
    gap: 14px;
    padding: 14px 18px;
    border-block-start: 1px solid var(--border-subtle);
}

.aic-msg:first-child { border-block-start: none; }
.aic-msg:hover { background: var(--n-25); }

/* The channel, as the glyph for that channel rather than a third pill
   on a row that already had two. */
.aic-msg-mark {
    display: grid;
    place-items: center;
    width: 32px;
    height: 32px;
    border-radius: var(--r-md);
    background: var(--n-100);
    color: var(--n-600);
}

.aic-msg-mark.is-email    { background: var(--p-50);    color: var(--p-700); }
.aic-msg-mark.is-sms      { background: var(--info-50); color: var(--info-700); }
.aic-msg-mark.is-linkedin { background: var(--gold-50); color: var(--gold-700); }

.aic-msg-b { min-width: 0; }

.aic-msg-h {
    display: flex;
    align-items: center;
    gap: 8px;
    flex-wrap: wrap;
}

.aic-msg-who {
    font-size: var(--fs-md);
    font-weight: var(--fw-semibold);
}

.aic-msg-job { font-size: var(--fs-xs); color: var(--text-tertiary); }

.aic-msg-s {
    margin-block-start: 3px;
    font-size: var(--fs-sm);
    font-weight: var(--fw-semibold);
    color: var(--text-primary);
}

/* The draft itself, set apart by a rule the way a quotation is: this
   is the text that will be sent, and everything around it is about
   the text. */
.aic-msg-t {
    margin-block-start: 6px;
    padding-inline-start: 12px;
    border-inline-start: 2px solid var(--border-default);
    max-width: 68ch;
    font-size: var(--fs-sm);
    color: var(--text-secondary);
    line-height: var(--lh-relaxed);
    white-space: pre-wrap;
}

.aic-msg-at {
    margin-block-start: 8px;
    font-family: var(--font-mono);
    font-size: var(--fs-2xs);
    color: var(--text-tertiary);
}

.aic-msg-act {
    display: flex;
    align-items: flex-start;
    gap: 6px;
    flex-shrink: 0;
}

/* ============================================================
   Provider configuration
   ============================================================ */

.aic-cfg {
    display: grid;
    grid-template-columns: minmax(0, 1fr) 320px;
    gap: 14px;
    align-items: start;
}

.aic-cfg-main,
.aic-cfg-rail {
    display: flex;
    flex-direction: column;
    gap: 14px;
    min-width: 0;
}

.aic-grid2 {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 14px;
}

/* Key mode was two bare radios on one line, which made the choice
   between "we pay" and "you pay" look like a checkbox. Two cards: the
   consequence is written into the option, and the whole card is the
   target rather than a 13px dot. */
.aic-opts {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 10px;
    padding-block-start: 4px;
}

.aic-opt {
    display: flex;
    gap: 10px;
    padding: 12px 14px;
    border: 1px solid var(--border-default);
    border-radius: var(--r-md);
    background: var(--n-0);
    cursor: pointer;
    transition: border-color var(--dur-fast) var(--ease-out),
                background-color var(--dur-fast) var(--ease-out);
}

.aic-opt:hover { border-color: var(--border-strong); }

.aic-opt.is-on {
    border-color: var(--p-500);
    background: var(--p-50);
}

.aic-opt:focus-within { box-shadow: var(--shadow-focus); }

.aic-opt input { margin-block-start: 2px; flex-shrink: 0; }

.aic-opt > span { min-width: 0; }

.aic-opt-t {
    display: block;
    font-size: var(--fs-md);
    font-weight: var(--fw-semibold);
    color: var(--text-primary);
}

.aic-opt-s {
    display: block;
    margin-block-start: 2px;
    font-size: var(--fs-xs);
    color: var(--text-tertiary);
    line-height: var(--lh-normal);
}

/* The shared banner is written for the page band, where the .cli stack
   supplies the gap below it. Inside a card there is no stack, so it carries
   its own. */
.aic-cfg-main .cli-banner { margin-block-end: 14px; }

.aic-acts {
    display: flex;
    gap: 8px;
    margin-block-start: 16px;
    padding-block-start: 16px;
    border-block-start: 1px solid var(--border-subtle);
}

/* The dry run's verdict, in the tone of the verdict. */
.aic-dry {
    display: flex;
    align-items: center;
    gap: 8px;
    flex-wrap: wrap;
    margin-block-start: 12px;
    padding: 10px 12px;
    border: 1px solid var(--border-subtle);
    border-radius: var(--r-md);
    background: var(--n-25);
    font-size: var(--fs-sm);
    color: var(--text-secondary);
}

.aic-dry.is-ok  { border-color: var(--success-100); background: var(--success-50); }
.aic-dry.is-bad { border-color: var(--warning-100); background: var(--warning-50); }

/* The override editor lives in the same card as the table it writes
   to, so it is tinted like a toolbar rather than floating on the
   card's own white. */
.aic-ovr-new {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr)) auto;
    gap: 10px;
    align-items: end;
    padding: 14px 16px;
    border-block-end: 1px solid var(--border-subtle);
    background: var(--n-25);
}

/* ---------- Right rail ---------- */

/* Spend against budget. The number alone could not say whether it was
   fine or nearly out, which is the only question anyone opens this
   card to ask. */
.aic-spend-n {
    font-family: var(--font-mono);
    font-variant-numeric: tabular-nums;
    font-size: var(--fs-3xl);
    font-weight: var(--fw-bold);
    letter-spacing: -0.02em;
    color: var(--text-primary);
}

.aic-meter {
    position: relative;
    height: 6px;
    margin-block-start: 10px;
    border-radius: var(--r-full);
    background: var(--n-100);
    overflow: hidden;
}

.aic-meter-fill {
    position: absolute;
    inset-block: 0;
    inset-inline-start: 0;
    max-width: 100%;
    border-radius: inherit;
    background: linear-gradient(90deg, var(--p-500), var(--p-700));
}

.aic-meter-fill.is-warn { background: linear-gradient(90deg, var(--gold-400), var(--gold-600)); }
.aic-meter-fill.is-bad  { background: linear-gradient(90deg, var(--danger-500), var(--danger-700)); }

.aic-meter-c {
    display: flex;
    justify-content: space-between;
    gap: 8px;
    margin-block-start: 6px;
    font-size: var(--fs-xs);
    color: var(--text-tertiary);
}

.aic-cat {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 8px 0;
    border-block-start: 1px solid var(--border-subtle);
    font-size: var(--fs-sm);
}

.aic-cat:first-child { border-block-start: none; padding-block-start: 0; }

.aic-cat-n { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.aic-cat-c {
    font-family: var(--font-mono);
    font-size: var(--fs-2xs);
    color: var(--text-tertiary);
}

/* Routing is a fallthrough, so it is drawn as one: numbered rungs on a
   single rule, read top to bottom, first match wins. An ol with its
   default markers said "four things" rather than "in this order,
   until one of them answers". */
.aic-route {
    list-style: none;
    margin: 0;
    padding: 0;
    counter-reset: aic-route;
}

.aic-route li {
    position: relative;
    counter-increment: aic-route;
    padding: 0 0 14px 28px;
    font-size: var(--fs-sm);
    color: var(--text-secondary);
    line-height: var(--lh-snug);
}

.aic-route li:last-child { padding-block-end: 0; }

.aic-route li::before {
    content: counter(aic-route);
    position: absolute;
    inset-inline-start: 0;
    inset-block-start: 0;
    display: grid;
    place-items: center;
    width: 18px;
    height: 18px;
    border-radius: 50%;
    background: var(--p-50);
    color: var(--p-700);
    font-family: var(--font-mono);
    font-size: 10px;
    font-weight: var(--fw-bold);
}

/* The rule that joins the rungs, stopping at the last one. */
.aic-route li:not(:last-child)::after {
    content: '';
    position: absolute;
    inset-inline-start: 9px;
    inset-block: 20px 2px;
    width: 1px;
    background: var(--border-default);
}

/* The last rung is the fallback, not another provider. */
.aic-route li:last-child::before {
    background: var(--n-100);
    color: var(--text-tertiary);
}

/* ============================================================
   Breakpoints
   ============================================================ */

@media (max-width: 1240px) {
    .aic-cfg { grid-template-columns: minmax(0, 1fr); }
    .aic-cfg-rail { flex-direction: row; flex-wrap: wrap; }
    .aic-cfg-rail > * { flex: 1 1 260px; }
}

@media (max-width: 1080px) {
    .aic-cands { grid-template-columns: minmax(0, 1fr); }
}

@media (max-width: 820px) {
    .aic-grid2,
    .aic-opts { grid-template-columns: minmax(0, 1fr); }
    .aic-ovr-new { grid-template-columns: repeat(2, minmax(0, 1fr)); }
    .aic-cfg-rail { flex-direction: column; }
}

@media (max-width: 560px) {
    .aic-ovr-new { grid-template-columns: minmax(0, 1fr); padding-inline: 14px; }
    .aic-msg { grid-template-columns: 32px minmax(0, 1fr); padding-inline: 14px; }
    .aic-msg-act { grid-column: 2; }
    .aic-console { padding-inline: 14px; }
    .aic-console-row .input { min-width: 0; flex: 1 1 100%; }
}

@media (prefers-reduced-motion: reduce) {
    .aic-cand,
    .aic-opt,
    .aic-facet { transition: none; }
}


/* ============================================================
   12. Shared page chrome (pg-*)

   Sections 6 to 11 each rebuilt the same three bands - a pinned header, a
   row of headline numbers, a working surface - for one page at a time. That
   was right while the language was being found; it is wrong as the way every
   remaining page adopts it, because the fiftieth page would mean the fiftieth
   copy of .cli-head and the first one to drift would be the one nobody
   noticed.

   So the shape is lifted out once, under a neutral prefix, and every page
   that has no bespoke layout of its own uses it. The values are not
   approximations of .cli-head - they are the same numbers, because the point
   is that a recruiter moving from Clients to Invoices should not be able to
   tell that one page was built before the other.

   What lives here:
     .pg-*    the page bands - header, identity block, actions, tiles
     .sec-*   a titled section inside the working surface
     .tbl-*   a data table with the list pages' proportions
     .fld-*   a labelled form control and the grids that hold them
     .note-*  the semantic banners (info, success, warning, danger)
     plus the small type and skeleton helpers the pages kept re-declaring
     inline: .meta, .mono, .caps, .muted, .trunc, .skel.

   The .cli-* rules stay where they are. Grouping them into these selectors
   would be the tidier file and the riskier change: those six pages are done,
   and a shared rule that has to satisfy both is how the finished pages start
   drifting. When a .cli- page next needs work it can move onto .pg- and its
   own block shrinks; nothing here requires that to happen first.
   ============================================================ */

/* Three stacked bands, same rhythm as .cli and .dash. A page's own content
   goes in the third band; the first two are the same everywhere. */
.pg {
    --pg-head-h: 56px;
    display: flex;
    flex-direction: column;
    gap: 12px;
}

/* ---------- Header ----------
   Pinned, full-bleed, and carrying the identity block rather than a lone
   title - the same reasoning as .cli-head: once the bar survives a scroll,
   whatever it carries is the only context a page keeps.

   Full-bleed means cancelling the scroll container's own padding, and the
   four shells do not agree on what that is: AppShell pads its <main> by 8px,
   the client and interviewer portals by 24px/32px. .cli-head could hard-code
   -8px because only AppShell pages use it; this one is used by all four, so
   the gutter is a variable the shell sets on <main> and the header reads.
   The defaults are AppShell's, which makes this bar pixel-identical to
   .cli-head on every recruiter and admin page. */
.pg-head {
    position: sticky;
    inset-block-start: calc(0px - var(--pg-gy, 8px));
    z-index: 30;
    display: flex;
    align-items: center;
    gap: 12px;
    flex-wrap: wrap;
    /* The fallback matters: most pages use PageHeader on its own rather than
       inside a .pg, and --pg-head-h is only declared by .pg. Without it the
       declaration is dropped and the bar is whatever its contents happen to
       be. 56px is the tile plus the bar's own padding. */
    min-height: var(--pg-head-h, 56px);
    margin-block-start: calc(0px - var(--pg-gy, 8px));
    margin-inline: calc(0px - var(--pg-gx, 8px));
    /* The gap to whatever comes next. Inside .pg the flex gap already
       provides it, so the rule below takes this one back off - otherwise the
       three-band pages would carry 24px where every other page has 12. */
    margin-block-end: 12px;
    padding-block: 8px;
    /* The bar's own inset, on top of whatever the shell was padding by, so
       the title lands 16px from the edge under AppShell's 8px gutter. */
    padding-inline: calc(var(--pg-gx, 8px) + 8px);
    /* Same white-and-lifted bar as .cli-head - see the note there. */
    background: var(--surface-raised);
    border-block-end: 1px solid var(--border-subtle);
    box-shadow: 0 1px 2px rgba(15, 19, 32, 0.05), 0 4px 10px -4px rgba(15, 19, 32, 0.10);
}

.pg > .pg-head { margin-block-end: 0; }

/* A header with a .tabs-band directly under it is one piece of chrome, not
   two. The 12px above is the gap to the page's content, and the shadow says
   "content passes beneath me" - but a band is neither: it is the bottom half
   of the same bar, and it pins flush against this one the moment the page
   scrolls. Leaving both in place opens the page with a stripe of page grey
   between a header and the row that belongs to it, and the header's own
   shadow falling across the tabs, then closes the gap on the first scroll -
   so the resting state is the only one that looks wrong.
   The hairline stays: it separates the title from the switch without
   breaking the white. */
.pg-head:has(+ .tabs-band) {
    margin-block-end: 0;
    box-shadow: none;
}

/* A page whose content is a form or a document, not a scrolling table, has
   nothing to keep in view - and a sticky bar there only costs 56px of the
   thing you came to read. */
.pg-head-static { position: static; }

.pg-id {
    flex: 1 1 320px;
    min-width: 0;
    display: flex;
    align-items: center;
    gap: 12px;
}

.pg-id-mark {
    display: grid;
    place-items: center;
    width: 40px;
    height: 40px;
    flex-shrink: 0;
    border-radius: var(--r-lg);
    background: var(--p-700);
    color: var(--text-inverse);
    box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.10), 0 1px 2px rgba(15, 19, 32, 0.14);
}

/* The three lines are set by their line-heights, not by a gap: a gap between
   an uppercase label, a heading and a caption stacks three different
   half-leadings and the block drifts off the tile's centre. */
.pg-id-txt {
    min-width: 0;
    display: flex;
    flex-direction: column;
}

.pg-id-crumb {
    align-self: flex-start;
    display: inline-flex;
    align-items: center;
    gap: 2px;
    margin-inline-start: -2px;
    font-size: 10px;
    font-weight: var(--fw-bold);
    letter-spacing: 0.07em;
    text-transform: uppercase;
    line-height: 1.3;
    color: var(--p-700);
    text-decoration: none;
}

.pg-id-crumb:hover { text-decoration: underline; }

.pg-id-crumb:focus-visible {
    outline: none;
    border-radius: var(--r-xs);
    box-shadow: var(--shadow-focus);
}

/* Title and whatever shares its line - a status chip, a count, a filter
   signal - so the block stays two lines whether or not the chip is there. */
.pg-id-line {
    display: flex;
    align-items: center;
    gap: 8px;
    min-width: 0;
    margin-block-start: 1px;
}

/* 17px: the middle line of a stack, not a standalone page title. It reads as
   the heading because the lines around it are 10px caps and a 12.5px caption
   - the hierarchy comes from the stack, so the type does not have to carry it
   alone in a bar that never leaves the screen. */
.pg-title {
    margin: 0;
    font-size: var(--fs-xl);
    font-weight: var(--fw-bold);
    letter-spacing: -0.015em;
    line-height: 1.25;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* The third line. Truncated rather than wrapped: a subtitle that wraps grows
   the pinned bar, and anything measuring itself against --pg-head-h then sits
   in the wrong place. */
.pg-sub {
    margin: 0;
    font-size: var(--fs-sm);
    line-height: 1.35;
    color: var(--text-secondary);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.pg-actions {
    display: flex;
    align-items: center;
    gap: 8px;
    flex-wrap: wrap;
    margin-inline-start: auto;
}

.pg-actions-sep {
    width: 1px;
    height: 20px;
    flex-shrink: 0;
    margin-inline: 2px;
    background: var(--p-100);
}

/* ---------- Brand pairing ----------
   Lifted from .cli-head, and for the same reason: as bare ghost text a
   secondary control reads as a link floating beside a real button, with
   nothing marking where it starts. Every control in the bar gets a hairline
   and a navy label; the gold is spent once, on the icon of the single action
   that creates something, because an accent used twice is not an accent. */
.pg-head .pg-actions > .btn-ghost {
    border: 1px solid var(--p-100);
    background: var(--surface-raised);
    color: var(--p-700);
    box-shadow: var(--shadow-xs);
}

.pg-head .pg-actions > .btn-ghost:hover {
    border-color: var(--p-300);
    background: var(--p-50);
    color: var(--p-800);
}

.pg-head .btn-primary > svg { color: var(--gold-300); }

/* ---------- Stat row ----------
   auto-fit rather than a fixed count: pages carry three, four or five
   headline numbers and each one used to hard-code its own grid. The 190px
   floor is where a KpiTile's label stops wrapping. */
.pg-kpis {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
    gap: 12px;
}

/* KpiTile renders its own root, so this is the one place the scope has to be
   pierced. It only adds a hover lift - if the rewrite ever stops matching,
   the tiles are unchanged rather than broken. Same rule as .cli-kpis. */
.pg-kpis > div {
    height: 100%;
    transition: box-shadow var(--dur-fast) var(--ease-out),
                border-color var(--dur-fast) var(--ease-out);
}

.pg-kpis > div:hover {
    border-color: var(--border-strong);
    box-shadow: var(--shadow-sm);
}

/* ---------- Working surface ----------
   The third band. .pg-body is the single-column case; the grids are for
   pages that put a rail beside their main column. minmax(0, ...) on every
   track because a table or a long unbroken string in a grid child otherwise
   pushes the track past its share and the page scrolls sideways. */
.pg-body {
    display: flex;
    flex-direction: column;
    gap: 12px;
}

.pg-grid2 { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px; }
.pg-grid3 { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 12px; }
.pg-grid4 { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 12px; }

/* Main column plus a rail. The rail is the width of a KpiTile, so a page can
   move a block between the two without it changing size. */
.pg-rail { display: grid; grid-template-columns: minmax(0, 1fr) 320px; gap: 12px; align-items: start; }
.pg-rail-l { display: grid; grid-template-columns: 320px minmax(0, 1fr); gap: 12px; align-items: start; }

.pg-span { grid-column: 1 / -1; }

/* ============================================================
   Section - a titled block inside the working surface

   A card with a name. The header is a band rather than a heading with a
   margin: with actions on the right the two have to share a baseline, and a
   margin under a heading puts them on different ones.
   ============================================================ */

.sec-head {
    display: flex;
    align-items: center;
    gap: 10px;
    flex-wrap: wrap;
    padding: 12px 16px;
    border-block-end: 1px solid var(--border-subtle);
}

.sec-title {
    margin: 0;
    font-size: var(--fs-md);
    font-weight: var(--fw-semibold);
    letter-spacing: -0.005em;
    color: var(--text-primary);
}

.sec-sub {
    margin: 2px 0 0;
    font-size: var(--fs-xs);
    line-height: 1.4;
    color: var(--text-tertiary);
}

.sec-head-txt { flex: 1 1 240px; min-width: 0; }

.sec-actions {
    display: flex;
    align-items: center;
    gap: 6px;
    flex-wrap: wrap;
    margin-inline-start: auto;
}

/* The section body when it is not a table: padding the card does not carry,
   because a card holding a table must not pad it. */
.sec-body { padding: 16px; }
.sec-body-tight { padding: 12px; }

/* A label above a group of controls or rows - smaller than .sec-title, and
   the thing a form uses to separate its fieldsets. */
.sec-label {
    display: block;
    margin: 0 0 8px;
    font-size: var(--fs-2xs);
    font-weight: var(--fw-semibold);
    letter-spacing: 0.05em;
    text-transform: uppercase;
    color: var(--text-secondary);
}

/* ============================================================
   Table - the list pages' proportions, for every other page

   Same type size, same row padding, same hairline-between-rows treatment as
   .cli-table, minus the sticky header and the sortable-column machinery that
   only a fifty-row list needs. A page that grows into those moves to .cli-.
   ============================================================ */

/* A data table is as wide as its columns need. The wrapper scrolls; the card
   holding it does not have to. Only for tables with no row menu to clip - a
   menu that has to escape the card cannot live in a scroll container.

   It is the table's scrollport on the block axis as well, whether or not that
   was asked for: overflow on one axis gives it on both. That matters now the
   header pins, because a sticky element never escapes the nearest scrollport
   - the header of a wrapped table cannot measure from <main> the way an
   unwrapped one does. So the wrapper is given a height of its own and the
   header pins inside it, which is the shape .tm-grid-wrap below already had
   and the shape the applications list uses.

   A cap, not a height: a six-row table is still drawn at six rows and nothing
   scrolls. The clamp keeps the region taller than a few rows on a laptop and
   shorter than the viewport on a monitor. */
.tbl-wrap {
    overflow: auto;
    max-block-size: clamp(320px, calc(100vh - 260px), 860px);
    /* The rows here scroll under the header from the wrapper's own top edge,
       not from the page header's - that bar is not what they are inside. */
    --tbl-top: 0px;
}

/* .cli-table measures from the page toolbar rather than from --tbl-top, which
   is right while the page is what it scrolls in and wrong the moment it is
   given a region: at 48px the labels would sit a toolbar's height below the
   first row and let it slide underneath. The same line .ap-scroll and
   .jb-scroll each carry for their own port. */
.tbl-wrap .cli-table thead th { inset-block-start: 0; }

/* The other reason a box is put around a table: not to scroll it, only to cut
   its corners back to the card's radius. overflow: hidden does both - it clips
   and it becomes a scroll container - and the second half is what breaks the
   header, because a sticky cell measures from the nearest scrollport and this
   one never scrolls. The labels park at the top of the card and ride the page
   down with it, which is the thing pinning them was meant to stop.

   clip is the half that was wanted: same clipping, same radius, no scrollport,
   so the header goes on pinning to whatever the rows actually scroll in. Four
   boxes reached for hidden this way - the Candidate payouts card, the client
   invoice list, the integrations drawer's connections table, and the salary
   guide on the public site. */
.tbl-clip { overflow: clip; }

.tbl {
    width: 100%;
    border-collapse: collapse;
    font-size: 13px;
}

/* Pinned, for the same reason .cli-table's header is: past the first flick
   the column labels are gone and every figure under them is unlabelled, and a
   permissions grid or an invoice list is read exactly the way a fifty-row list
   is. It was the list pages' proportions minus the sticky header; the header
   was the half that had nothing to do with how long the list is.

   Where it pins is --tbl-top - the top of whatever scrollport the table is
   in, which for most tables is the shell's <main>. A handful of boxes move it,
   each of them the box that owns the scroll:
     - a page carrying a pinned .pg-head, so the labels stop below the bar
       rather than under it (the rule below);
     - .tbl-wrap, its own scrollport;
     - .drw-body, a drawer's;
     - .dash-table-wrap and .ql-imp-rows, the two capped regions;
     - the public site, whose sticky .nav sets it in website/site.css.

   The default is for the remaining case: a page with no pinned chrome, whose
   header therefore pins to <main> itself. It is negative for the same reason
   .pg-head's is - the shell pads <main>, and a sticky box measures from
   inside that padding, so at 0 the header parks a gutter's width down and the
   row passing behind it stays visible in the gap above. Measured: an 8px band
   of the previous row, between the topbar and the labels, on service-prices.

   The one thing that breaks this: a box between the table and the scrollport
   that clips rather than scrolls. It becomes the nearest scrollport, it never
   scrolls, and the header then pins to a top edge that rides the page down -
   which is the pinning undone, whatever the offset. Every box that was doing
   this only to round a table's corners now uses .tbl-clip above. A new one
   that genuinely has to clip and cannot use clip needs --tbl-top: 0 on itself.
   To scroll a table sideways use .tbl-wrap, which already handles this. */
.tbl thead th {
    position: sticky;
    inset-block-start: var(--tbl-top, calc(0px - var(--pg-gy, 8px)));
    /* Low: a positioned cell inside a row has to be able to pass over the
       header, and .pg-head's 30 has to keep painting over all of it. */
    z-index: 2;
    padding: 11px 12px;
    text-align: start;
    background: var(--n-25);
    /* border-collapse: collapse hands the header's bottom border to the row
       below it, which then scrolls out from under a sticky header and leaves
       the labels floating. An inset shadow belongs to the cell and travels
       with it. */
    box-shadow: inset 0 -1px 0 var(--border-subtle);
    font-size: var(--fs-2xs);
    font-weight: var(--fw-semibold);
    letter-spacing: 0.05em;
    text-transform: uppercase;
    color: var(--text-secondary);
    white-space: nowrap;
}

/* The bar's height less the gutter it cancels - the same arithmetic
   .tabs-band does, so the labels land flush under the header rather than 8px
   below it or hidden behind it. It is set on whatever box holds the header
   rather than on .pg, because most pages render PageHeader straight into the
   shell's <main> and never wrap it in one. */
:has(> .pg-head:not(.pg-head-static)) {
    --tbl-top: calc(var(--pg-head-h, 56px) - var(--pg-gy, 8px));
}

.tbl td {
    padding: 14px 12px;
    vertical-align: middle;
    border-block-start: 1px solid var(--border-subtle);
}

.tbl tbody tr:first-child td { border-block-start: none; }

/* The edge inset: the first and last columns clear the card's corner rather
   than starting hard against it. */
.tbl thead th:first-child,
.tbl tbody td:first-child { padding-inline-start: 20px; }

.tbl thead th:last-child,
.tbl tbody td:last-child { padding-inline-end: 20px; }

.tbl-row-link { cursor: pointer; transition: background-color var(--dur-fast) var(--ease-out); }
.tbl-row-link:hover { background: var(--n-25); }

/* Numbers line up on their digits or they are not a column, they are a list
   of unrelated figures that happen to be stacked. */
.tbl-num {
    text-align: end;
    font-variant-numeric: tabular-nums;
}

.tbl thead th.tbl-num { text-align: end; }

/* A column of ticks, dots or a single glyph per row - centred over its
   header rather than starting at the cell's edge. */
.tbl-mid { text-align: center; }
.tbl thead th.tbl-mid { text-align: center; }

/* Shrink-to-fit so the width goes to the names: a status chip or a row of
   icon buttons needs exactly its own width and no more. */
.tbl-fit { width: 1%; white-space: nowrap; }

.tbl-empty {
    padding: 32px 24px;
    text-align: center;
    font-size: var(--fs-md);
    color: var(--text-tertiary);
}



/* ============================================================
   Loading, toggle chips and the inline search box

   Three more shapes every page had been writing out by hand. The loading
   line in particular appeared eleven times in the interviewer portal alone,
   at 40px on some pages and 60px on others, so the content jumped by 20px
   depending on which page you had loaded.
   ============================================================ */

/* A word in the middle of the space the content will fill. Not as good as a
   skeleton - use .skel where the shape is known - but far better than the
   page collapsing to nothing while it waits. */
.pg-loading {
    padding: 56px 24px;
    text-align: center;
    font-size: var(--fs-md);
    color: var(--text-tertiary);
}

/* ---------- Toggle chip ----------
   A filter that is on or off, in a row of its siblings. Filled navy when on,
   because a row of these is read by scanning for the dark one. */
.chip-tog {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    height: 28px;
    padding: 0 12px;
    border: 1px solid transparent;
    border-radius: var(--r-full);
    background: var(--n-100);
    font-size: var(--fs-sm);
    font-weight: var(--fw-semibold);
    color: var(--text-secondary);
    white-space: nowrap;
    transition: background-color var(--dur-fast) var(--ease-out),
                color var(--dur-fast) var(--ease-out);
}

.chip-tog:hover { background: var(--n-150); color: var(--text-primary); }

.chip-tog.on {
    background: var(--p-700);
    color: var(--text-inverse);
}

.chip-tog.on:hover { background: var(--p-800); }

.chip-tog:focus-visible { outline: none; box-shadow: var(--shadow-focus); }

/* The count that rides along with a filter - "Scorecard due · 3". Set in the
   chip's own colour at reduced weight so it reads as a quantity, not a second
   label. */
.chip-tog-n {
    font-family: var(--font-mono);
    font-variant-numeric: tabular-nums;
    font-weight: var(--fw-medium);
    opacity: 0.75;
}

/* A row of them, wrapping rather than overflowing. */
.chip-row {
    display: flex;
    align-items: center;
    gap: 6px;
    flex-wrap: wrap;
}
/* ---------- Tile picker ----------
   A radio group as a row of labelled tiles: the name of each choice and
   the one line that says what picking it means. For the three-or-four-way
   decisions a form makes once - what type of flag this is, what kind of
   request came in - where a <select> hides two of the three answers and a
   row of radios has nowhere to put the sentence that distinguishes them.

   Written for the feature-flag drawer and immediately wanted by the DSR
   and certification drawers, which is the point at which a shape stops
   being one page's and moves here. Set --pick-cols for a count other
   than three.

   The markup is a <button role="radio"> per tile, .is-on for the chosen
   one, and aria-checked matching it. */
.pick {
    display: grid;
    grid-template-columns: repeat(var(--pick-cols, 3), minmax(0, 1fr));
    gap: 8px;
}

.pick-tile {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 3px;
    padding: 10px;
    border: none;
    border-radius: var(--r-md);
    background: var(--surface-raised);
    box-shadow: inset 0 0 0 1px var(--border-default);
    text-align: start;
    cursor: pointer;
    transition: background-color var(--dur-fast) var(--ease-out),
                box-shadow var(--dur-fast) var(--ease-out);
}

.pick-tile:hover { background: var(--surface-hover); }

/* The ring is 1.5px against the resting 1px, so the chosen tile is picked
   out by weight as well as by colour - the difference survives a screen
   where the navy and the hairline are closer than they are here. */
.pick-tile.is-on {
    background: var(--p-50);
    box-shadow: inset 0 0 0 1.5px var(--p-700);
}

.pick-tile-ico { color: var(--n-400); }
.pick-tile.is-on .pick-tile-ico { color: var(--p-700); }

.pick-tile-t {
    font-size: var(--fs-sm);
    font-weight: var(--fw-semibold);
    color: var(--text-primary);
}

.pick-tile-d {
    font-size: var(--fs-2xs);
    line-height: var(--lh-snug);
    color: var(--text-tertiary);
}

.pick-tile:focus-visible {
    outline: none;
    box-shadow: inset 0 0 0 1.5px var(--p-700), var(--shadow-focus);
}

/* Three tiles across a drawer on a phone leaves each one narrower than its
   own label, so the stack turns on its side: name at the start of the row,
   its line at the end. */
@media (max-width: 640px) {
    .pick { grid-template-columns: minmax(0, 1fr); }
    .pick-tile { flex-direction: row; align-items: center; gap: 8px; }
    .pick-tile-d { margin-inline-start: auto; }
}

/* ---------- Inline search ----------
   A search box that lives in a toolbar rather than at the top of a page: the
   glyph and the field are one control, so the border belongs to the wrapper
   and the input inside it has none of its own. */
.search-inline {
    display: flex;
    align-items: center;
    gap: 8px;
    height: var(--h-md);
    padding: 0 12px;
    background: var(--surface-sunken);
    border: 1px solid var(--border-subtle);
    border-radius: var(--r-md);
    color: var(--text-tertiary);
    transition: border-color var(--dur-fast) var(--ease-out),
                box-shadow var(--dur-fast) var(--ease-out);
}

.search-inline:focus-within {
    border-color: var(--border-focus);
    box-shadow: var(--shadow-focus);
}

.search-inline > svg { flex-shrink: 0; }

.search-inline input {
    flex: 1;
    min-width: 0;
    border: none;
    background: none;
    outline: none;
    font-size: var(--fs-sm);
    color: var(--text-primary);
}

.search-inline input::placeholder { color: var(--text-tertiary); }

/* ---------- Toolbar ----------
   The band above a table that holds its filters: chips on the left, the
   things that act on the whole table on the right. */
.tbar {
    display: flex;
    align-items: center;
    gap: 10px;
    flex-wrap: wrap;
    padding: 12px 16px;
    border-block-end: 1px solid var(--border-subtle);
}

.tbar-end { margin-inline-start: auto; display: flex; align-items: center; gap: 8px; }

/* ---------- Card footer ----------
   The count under a table, and anywhere else a card ends in a summary line
   rather than a row. */
.card-foot {
    display: flex;
    align-items: center;
    gap: 10px;
    flex-wrap: wrap;
    padding: 11px 16px;
    border-block-start: 1px solid var(--border-subtle);
    font-size: var(--fs-xs);
    color: var(--text-tertiary);
}

/* ---------- Meter ----------
   A thin bar showing one value against its track. Used for a rating
   dimension, a recommendation mix, a quota. --meter-h so a caller can make
   it chunkier without a second class. */
.meter {
    flex: 1;
    min-width: 60px;
    height: var(--meter-h, 8px);
    background: var(--n-100);
    border-radius: var(--r-full);
    overflow: hidden;
}

.meter-fill {
    height: 100%;
    border-radius: var(--r-full);
    background: var(--meter-c, var(--p-700));
    transition: width var(--dur-mid) var(--ease-out);
}

/* A label, its bar and its number on one line - the shape of every "how do I
   score against the team" readout. The label column is fixed so the bars
   start on one edge instead of wherever each label happened to end. */
.meter-row {
    display: flex;
    align-items: center;
    gap: 12px;
}

.meter-row-l {
    flex: 0 0 auto;
    width: 200px;
    min-width: 0;
    font-size: var(--fs-sm);
    color: var(--text-secondary);
}

.meter-row-v {
    flex-shrink: 0;
    width: 34px;
    font-family: var(--font-mono);
    font-variant-numeric: tabular-nums;
    font-size: var(--fs-sm);
    font-weight: var(--fw-semibold);
    text-align: end;
}

@media (max-width: 700px) {
    .meter-row-l { width: 120px; }
    .tbar { padding-inline: 14px; }
    .card-foot { padding-inline: 14px; }
}

/* ============================================================
   Row list - the other way a page shows a collection

   A table is for columns you compare down. This is for rows you scan: a
   mark, a name, a line of context under it, and whatever the row is worth on
   the right. It appears on nearly every page that is not a list page -
   recent candidates, today's interviews, a client's roles, a settings
   inventory - and every one of them had written it inline at its own padding.

   The hairline goes on the top edge of every row but the first, so a list
   that ends inside a card does not draw a rule against the card's own
   bottom border.
   ============================================================ */

.lst { display: flex; flex-direction: column; }

.lst-row {
    display: flex;
    align-items: center;
    gap: 11px;
    padding: 12px 16px;
    border-block-start: 1px solid var(--border-subtle);
    color: inherit;
    text-decoration: none;
    transition: background-color var(--dur-fast) var(--ease-out);
}

.lst-row:first-child { border-block-start: none; }

/* Only when the row goes somewhere. A hover tint on a row that does nothing
   promises a click that never happens. */
a.lst-row:hover,
button.lst-row:hover { background: var(--n-25); }

a.lst-row:focus-visible,
button.lst-row:focus-visible {
    outline: none;
    box-shadow: inset var(--shadow-focus);
}

/* A row rendered as a button has to be told it is full width and left-aligned;
   the browser's own button defaults are neither. */
button.lst-row {
    width: 100%;
    text-align: start;
    background: none;
    border-inline: none;
    border-block-end: none;
    font: inherit;
    cursor: pointer;
}

/* min-width: 0 so .trunc inside it can actually shrink. */
.lst-main { flex: 1; min-width: 0; }

.lst-name {
    font-size: var(--fs-md);
    font-weight: var(--fw-semibold);
    color: var(--text-primary);
}

.lst-sub {
    margin-block-start: 2px;
    font-size: var(--fs-xs);
    line-height: 1.4;
    color: var(--text-tertiary);
}

/* The right-hand end of a row: a timestamp, a count, a chip, or all three.
   Shrink-to-fit and never wrapping, so the name column takes the slack. */
.lst-end {
    display: flex;
    align-items: center;
    gap: 10px;
    flex-shrink: 0;
    font-size: var(--fs-xs);
    color: var(--text-tertiary);
    white-space: nowrap;
}

/* A leading mark where the row has no avatar - an icon tile in the row's own
   accent, matching the KpiTile chip so the two read as one family. */
.lst-mark {
    display: grid;
    place-items: center;
    width: 32px;
    height: 32px;
    flex-shrink: 0;
    border-radius: var(--r-md);
    background: var(--n-100);
    color: var(--n-700);
}

.lst-mark-brand { background: var(--p-50);       color: var(--p-700); }
.lst-mark-gold  { background: var(--gold-50);    color: var(--gold-700); }
.lst-mark-good  { background: var(--success-50); color: var(--success-700); }
.lst-mark-warn  { background: var(--warning-50); color: var(--warning-700); }
.lst-mark-bad   { background: var(--danger-50);  color: var(--danger-700); }

/* A denser list, for a rail or a panel where the rows are secondary. */
.lst-tight .lst-row { padding: 9px 14px; gap: 9px; }

@media (max-width: 700px) {
    .lst-row { padding-inline: 14px; }
}

/* ============================================================
   Form fields

   The label/control/hint stack that every settings and editor page was
   writing inline, and the grids that hold them. QFormField renders this
   shape already; these classes are for the pages that build a control by
   hand and still have to match it.
   ============================================================ */

.fld { display: flex; flex-direction: column; gap: 5px; min-width: 0; }

.fld-label {
    display: block;
    font-size: var(--fs-xs);
    font-weight: var(--fw-semibold);
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--text-secondary);
}

/* Marked on the label, not with an asterisk in the text: a translated label
   with punctuation baked in cannot be reused anywhere else. */
.fld-req::after {
    content: '*';
    margin-inline-start: 3px;
    color: var(--danger-600);
}

.fld-hint {
    font-size: var(--fs-xs);
    line-height: 1.45;
    color: var(--text-tertiary);
}

.fld-err {
    font-size: var(--fs-xs);
    line-height: 1.45;
    color: var(--danger-600);
}

/* auto-fit so a form reflows on its own rather than at a breakpoint each
   page has to remember to write. 240px is where a labelled 32px control
   stops being usable. */
.fld-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
    gap: 14px 16px;
}

.fld-grid-wide { grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); }

/* A field that needs the full width of the grid it sits in - a textarea, a
   long path, a list of chips. */
.fld-span { grid-column: 1 / -1; }

/* A row of controls that belong together: a value and its unit, a from and a
   to, a control and the button that acts on it. */
.fld-row { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }


/* ============================================================
   Résumé

   A résumé is the same object everywhere it appears - the interviewer's prep
   panel, the candidate's own profile, the recruiter's read of it - and each
   of those had drawn it at its own type sizes, so the same person's history
   was 13px in one place and 12.5px in another.

   The period is a fixed column rather than a line above the role: dates down
   one edge is how a résumé is actually skimmed, and a period that starts
   wherever the previous title ended cannot be skimmed at all.
   ============================================================ */

.cv-head {
    padding: 22px 24px;
    border-block-end: 1px solid var(--border-subtle);
}

.cv-id {
    display: flex;
    align-items: center;
    gap: 14px;
    min-width: 0;
}

.cv-name {
    font-size: var(--fs-2xl);
    font-weight: var(--fw-bold);
    letter-spacing: -0.01em;
}

.cv-headline {
    font-size: var(--fs-md);
    color: var(--text-secondary);
}

.cv-summary {
    margin: 14px 0 0;
    font-size: var(--fs-md);
    line-height: 1.6;
    color: var(--text-secondary);
}

.cv-body { padding: 18px 24px; }

.cv-item {
    display: flex;
    gap: 14px;
    padding-block-end: 16px;
    margin-block-end: 16px;
    border-block-end: 1px solid var(--border-subtle);
}

/* The last entry in a section has nothing under it to be separated from. */
.cv-item:last-child {
    padding-block-end: 0;
    margin-block-end: 0;
    border-block-end: none;
}

/* Education entries are two lines each; a rule between them is heavier than
   what it separates. */
.cv-item-tight {
    padding-block-end: 0;
    margin-block-end: 10px;
    border-block-end: none;
}

.cv-when {
    flex-shrink: 0;
    width: 90px;
    font-family: var(--font-mono);
    font-variant-numeric: tabular-nums;
    font-size: var(--fs-xs);
    color: var(--text-tertiary);
}

.cv-what { flex: 1; min-width: 0; }

.cv-role {
    font-size: var(--fs-base);
    font-weight: var(--fw-semibold);
    color: var(--text-primary);
}

.cv-org {
    margin-block-end: 6px;
    font-size: var(--fs-sm);
    color: var(--text-secondary);
}

.cv-points {
    display: flex;
    flex-direction: column;
    gap: 4px;
    margin: 0;
    padding-inline-start: 16px;
    font-size: var(--fs-sm);
    line-height: 1.5;
    color: var(--text-secondary);
}

.cv-desc {
    font-size: var(--fs-sm);
    line-height: 1.5;
    color: var(--text-secondary);
}

.cv-ok { color: var(--success-600); }

.cv-url {
    margin-inline-start: auto;
    max-width: 160px;
}

/* The scope note. Navy because it is the portal speaking about itself, and
   the one place on this panel that is not the candidate's own words. */
.cv-scope {
    display: flex;
    gap: 10px;
    padding: 10px 14px;
    border-radius: var(--r-md);
    background: var(--p-900);
    font-size: var(--fs-xs);
    line-height: 1.45;
    color: rgba(255, 255, 255, 0.78);
}

.cv-scope > svg {
    flex-shrink: 0;
    margin-block-start: 1px;
    color: var(--gold-300);
}

/* A skill, a tag, a label with no state. Not .chip-tog: nothing here is
   clickable, and a chip that looks pressable and is not is a worse lie than
   a plain one. */
.chip-plain {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    padding: 4px 10px;
    border-radius: var(--r-full);
    background: var(--n-75);
    font-size: var(--fs-xs);
    font-weight: var(--fw-medium);
    color: var(--text-secondary);
    white-space: nowrap;
}

/* ---------- Stepper ----------
   Minus, value, plus. The value is fixed-width and tabular so the two
   buttons do not shuffle sideways when it goes from 9 to 10. */
.stepper {
    display: flex;
    align-items: center;
    gap: 8px;
    flex-shrink: 0;
}

.stepper-v {
    width: 20px;
    font-family: var(--font-mono);
    font-variant-numeric: tabular-nums;
    font-size: var(--fs-lg);
    font-weight: var(--fw-semibold);
    text-align: center;
}

.stepper-pill {
    padding: 4px 10px;
    border-radius: var(--r-full);
    background: var(--n-100);
    font-size: var(--fs-sm);
    font-weight: var(--fw-semibold);
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

/* A row showing something that is switched off and cannot be switched on. */
.lst-row.is-off { opacity: 0.6; }

/* A shorter control, for a toolbar or a pair of dates in a rail. */
.input-sm {
    height: var(--h-md);
    padding: 0 10px;
    font-size: var(--fs-sm);
}

@media (max-width: 700px) {
    .cv-head { padding: 16px; }
    .cv-body { padding: 16px; }
    /* The period column costs more than it gives once the entry itself is
       down to one column of its own. */
    .cv-item { flex-direction: column; gap: 4px; }
    .cv-when { width: auto; }
}

/* ============================================================
   Availability grid
   ============================================================ */

.ivav {
    display: grid;
    grid-template-columns: 56px repeat(5, minmax(0, 1fr));
    gap: 6px;
}

.ivav-dow {
    padding-block-end: 4px;
    text-align: center;
    font-size: var(--fs-xs);
    font-weight: var(--fw-semibold);
    color: var(--text-tertiary);
}

.ivav-hour {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    padding-inline-end: 8px;
    font-family: var(--font-mono);
    font-variant-numeric: tabular-nums;
    font-size: var(--fs-xs);
    color: var(--text-tertiary);
}

/* Empty is a faint slot, not a blank: a grid of nothing gives the eye no
   target to click. */
.ivav-cell {
    display: grid;
    place-items: center;
    height: 42px;
    border: 1px solid var(--border-subtle);
    border-radius: var(--r-md);
    background: var(--n-25);
    color: var(--n-300);
    cursor: pointer;
    transition: background-color var(--dur-fast) var(--ease-out),
                border-color var(--dur-fast) var(--ease-out);
}

.ivav-cell:hover { border-color: var(--p-200); background: var(--p-50); }

.ivav-cell.on {
    border-color: var(--p-300);
    background: var(--p-50);
    color: var(--p-700);
}

.ivav-cell:focus-visible { outline: none; box-shadow: var(--shadow-focus); }

.ivav-none { margin-block-end: 10px; }

.ivav-off {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 6px 8px 6px 12px;
    margin-block-end: 8px;
    border: 1px solid var(--border-subtle);
    border-radius: var(--r-md);
    background: var(--n-25);
    color: var(--text-tertiary);
}

.ivav-off-t {
    flex: 1;
    min-width: 0;
    font-size: var(--fs-sm);
    color: var(--text-primary);
}

.ivav-add { margin-block-start: 6px; }

.ivav-date { width: auto; flex: 0 1 auto; }

@media (max-width: 560px) {
    .ivav { grid-template-columns: 44px repeat(5, minmax(0, 1fr)); gap: 4px; }
    .ivav-cell { height: 36px; }
}

/* ============================================================
   Notes - the semantic banners

   Four tones, one shape. Written out per page they drifted: the same warning
   appeared at three different paddings and two of them used a token that had
   never been defined, so the border simply did not render.
   ============================================================ */

.note {
    display: flex;
    align-items: flex-start;
    gap: 10px;
    padding: 12px 14px;
    border: 1px solid var(--border-default);
    border-radius: var(--r-lg);
    background: var(--surface-sunken);
    font-size: var(--fs-md);
    line-height: 1.5;
    color: var(--text-secondary);
}

.note > svg { flex-shrink: 0; margin-block-start: 1px; }

.note-body { min-width: 0; flex: 1; }

.note-title {
    margin: 0 0 2px;
    font-size: var(--fs-md);
    font-weight: var(--fw-semibold);
    color: var(--text-primary);
}

.note-info    { background: var(--info-50);    border-color: var(--info-100);    color: var(--info-700); }
.note-success { background: var(--success-50); border-color: var(--success-100); color: var(--success-700); }
.note-warning { background: var(--warning-50); border-color: var(--warning-100); color: var(--warning-700); }
.note-danger  { background: var(--danger-50);  border-color: var(--danger-200);  color: var(--danger-700); }

.note-info .note-title    { color: var(--info-700); }
.note-success .note-title { color: var(--success-700); }
.note-warning .note-title { color: var(--warning-700); }
.note-danger .note-title  { color: var(--danger-700); }


/* ---------- Empty state ----------
   The dashed border is the point: it says "this surface is real but has
   nothing in it yet", which a solid card cannot say without looking like a
   card that failed to load. Used inside a card as well as on its own, so it
   carries no shadow - two nested shadows read as a card inside a card. */
.q-empty {
    padding: 40px 24px;
    text-align: center;
    background: var(--surface-raised);
    border: 1px dashed var(--border-strong);
    border-radius: var(--r-xl);
}

.q-empty-mark {
    display: inline-grid;
    place-items: center;
    width: 48px;
    height: 48px;
    margin-block-end: 14px;
    border-radius: var(--r-xl);
    background: var(--surface-sunken);
    color: var(--text-disabled);
}

.q-empty-title {
    margin: 0;
    font-size: var(--fs-xl);
    font-weight: var(--fw-semibold);
    letter-spacing: -0.01em;
    color: var(--text-primary);
}

.q-empty-body {
    max-width: 420px;
    margin: 6px auto 0;
    font-size: var(--fs-md);
    line-height: 1.55;
    color: var(--text-secondary);
}

.q-empty-cta { margin-block-start: 18px; }

/* Inside a card body that already has its own padding and border, the empty
   state's own frame is a second box around nothing. */
.q-empty-bare {
    padding: 32px 24px;
    background: none;
    border: none;
}

/* ============================================================
   Small type

   The handful of text treatments that appeared inline on nearly every page -
   a caption, a code-ish value, a column label. Named once so a page can stop
   restating 12.5px and --text-tertiary, and so the sizes stay in step when
   one of them changes.
   ============================================================ */

.meta { font-size: var(--fs-sm); color: var(--text-tertiary); }
.meta-sm { font-size: var(--fs-xs); color: var(--text-tertiary); }
.muted { color: var(--text-tertiary); }
.strong { font-weight: var(--fw-semibold); color: var(--text-primary); }

/* .mono is already a primitive (font family + tabular-nums). This is the
   sized variant the pages kept writing inline - a code-ish value set one step
   down from body text, which is nearly always what a monospaced value in a
   table cell or a caption wants. Redefining .mono itself to add the size
   would have resized it on the hundred-odd pages already using it. */
.mono-sm {
    font-family: var(--font-mono);
    font-variant-numeric: tabular-nums;
    font-size: var(--fs-sm);
}

.caps {
    font-size: var(--fs-2xs);
    font-weight: var(--fw-semibold);
    letter-spacing: 0.05em;
    text-transform: uppercase;
    color: var(--text-secondary);
}

/* Needs min-width: 0 on the flex parent to do anything - a flex child's
   default min-width is its content, so it never gets small enough to clip. */
.trunc {
    min-width: 0;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* Two lines then an ellipsis, for a description in a card. */
.clamp2 {
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
}


/* ============================================================
   The small set of names the pages were spelling out inline

   Not a utility framework - eleven names, each one a thing the codebase was
   already writing hundreds of times in longhand. The test for whether one
   belongs here is whether the inline version appeared on ten pages and always
   meant the same thing.

   Two of them are worth reading twice:

   .lbl is the uppercase field label, which existed in seven spellings -
   11px/10.5px, 600/700, 0.04em/0.05em, and margin-bottom of 4, 5 or 6px. They
   were the same label, so they are one label now, and forms that sat beside
   each other no longer disagree about how a label is set.

   .meta / .meta-2 are the two captions: tertiary for a fact about the row
   (a timestamp, a count), secondary for something the reader is meant to
   actually read. The pages had been picking between them by eye.
   ============================================================ */

.muted-2 { color: var(--text-secondary); }

.meta-2 { font-size: var(--fs-sm); color: var(--text-secondary); }
.meta-2-sm { font-size: var(--fs-xs); color: var(--text-secondary); }

/* A separator between two facts on the same line, drawn rather than typed. A
   "·" in the markup is a character a screen reader reads out between every
   pair of facts on every row of a list. */
.dot-sep {
    display: inline-block;
    width: 3px;
    height: 3px;
    border-radius: var(--r-full);
    background: var(--n-300);
    vertical-align: middle;
}

.lbl {
    display: block;
    margin-block-end: 4px;
    font-size: var(--fs-xs);
    font-weight: var(--fw-semibold);
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--text-tertiary);
}

/* A label with real air under it - the one that opens a group of fields
   rather than sitting on a single control. */
.lbl-loose { margin-block-end: 8px; }

/* Navy instead of grey, for a label naming something the product did (an AI
   read, a system field) rather than something the user typed. */
.lbl-brand { color: var(--p-700); }

/* The asterisk on a required field's label. aria-hidden in the markup - the
   control carries `required`, and a screen reader reading "star" after every
   label is noise on top of information it already has. */
.lbl .req {
    margin-inline-start: 2px;
    color: var(--danger-600);
}

.flex { display: flex; }
.flex-1 { flex: 1; }

/* A flex child that should take the slack and be allowed to clip. min-width:0
   is the half everyone forgets: without it a flex child never shrinks below
   its content, so .trunc inside it never truncates. */
.flex-fill { flex: 1; min-width: 0; }

.rule-t { border-block-start: 1px solid var(--border-subtle); }

/* The grey block a page shows where a row will be. It was a static
   --n-100 rectangle; on .skel it shimmers like every other placeholder in the
   product, so a loading page reads as loading rather than as broken. */
.skel-bar { height: 28px; margin-block-end: 8px; }

/* ============================================================
   Skeleton

   Same shimmer as .cli-skel. A page loads into the shape it is about to
   show rather than a word in the middle of an empty screen, so the layout
   stops jumping when the data lands.
   ============================================================ */

.skel {
    background: linear-gradient(90deg, var(--n-100) 25%, var(--n-75) 38%, var(--n-100) 60%);
    background-size: 300% 100%;
    border-radius: var(--r-md);
    animation: cli-shimmer 1.5s var(--ease-in-out) infinite;
}

.skel-line { height: 12px; }
.skel-row { height: 54px; border-radius: var(--r-lg); }
.skel-kpi { height: 86px; border-radius: var(--r-lg); }

/* A card-sized block - a chart panel, a table, a titled section. Roughly the
   height a QCard holding one chart settles at, so a page loading two of them
   side by side does not shrink when the data lands. */
.skel-panel { height: 240px; border-radius: var(--r-lg); }

/* Matches .pg-id-mark so the header does not resize on load. */
.skel-mark { width: 40px; height: 40px; flex-shrink: 0; border-radius: var(--r-lg); }

.skel-stack { display: flex; flex-direction: column; gap: 8px; }

@media (prefers-reduced-motion: reduce) {
    .skel { animation: none; }
}

/* ============================================================
   Breakpoints

   The rail collapses before the grids do: a 320px rail beside a shrinking
   main column starves the column long before three cards across stop
   fitting.
   ============================================================ */

@media (max-width: 1120px) {
    .pg-rail,
    .pg-rail-l { grid-template-columns: minmax(0, 1fr); }
    .pg-grid4 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (max-width: 900px) {
    .pg-actions { margin-inline-start: 0; width: 100%; }
    /* The actions drop to their own line here, so the pinned bar is the tile
       plus a wrapped action row and anything offset by --pg-head-h has to
       clear both. */
    .pg { --pg-head-h: 100px; }
    /* .pg reads the taller bar from the line above. A page that renders
       PageHeader without .pg has only the 56px fallback, so the offset its
       table headers pin at is restated here. */
    :has(> .pg-head:not(.pg-head-static)) { --tbl-top: calc(100px - var(--pg-gy, 8px)); }
    .pg-actions-sep { display: none; }
    .pg-grid3 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (max-width: 700px) {
    .pg-grid2,
    .pg-grid3,
    .pg-grid4 { grid-template-columns: minmax(0, 1fr); }
    .pg-head { padding-inline: calc(var(--pg-gx, 8px) + 6px); }
    .tbl thead th:first-child,
    .tbl tbody td:first-child { padding-inline-start: 14px; }
    .tbl thead th:last-child,
    .tbl tbody td:last-child { padding-inline-end: 14px; }
    .sec-head { padding-inline: 14px; }
    .sec-body { padding: 14px; }
}


/* ============================================================
   13. Client portal

   The client portal is four read-only screens and one decision. It has no
   layout of its own worth inventing - everything here is the shared chrome
   from section 12, and this block only holds the handful of pieces those
   four screens need that nothing else does.

   The palette and proportions are deliberately the recruiter's, not a
   softened version of them: the client is looking at their agency's product,
   and a portal that looks like a different product reads as a different
   company.
   ============================================================ */

/* ---------- Roles list ---------- */

/* Title and status share a line; the status is the second thing you read,
   not a second line. */
.cjob-line {
    display: flex;
    align-items: center;
    gap: 8px;
    min-width: 0;
}

/* ---------- Metric ----------
   The three counts on a role's row. Mono and tabular so that three roles
   stacked line up on their digits rather than drifting by the width of a 1,
   which is the whole reason they are worth putting in a column. */
.cmetric {
    flex-shrink: 0;
    min-width: 64px;
    text-align: center;
}

.cmetric-v {
    font-family: var(--font-mono);
    font-variant-numeric: tabular-nums;
    font-size: var(--fs-lg);
    font-weight: var(--fw-semibold);
    line-height: 1.2;
    color: var(--text-primary);
}

.cmetric-l {
    margin-block-start: 1px;
    font-size: var(--fs-2xs);
    font-weight: var(--fw-medium);
    letter-spacing: 0.03em;
    text-transform: uppercase;
    color: var(--text-tertiary);
    white-space: nowrap;
}

/* The counts are the first thing to go when the row runs out of width: the
   role's name and where to see its candidates are why you are on the page. */
@media (max-width: 820px) {
    .cmetric { display: none; }
}

/* ---------- Candidate record ---------- */

.cdet-rows {
    display: flex;
    flex-direction: column;
    gap: 9px;
    font-size: var(--fs-sm);
}

/* A fixed label column, so the values start on one edge rather than each one
   beginning wherever its own label happened to end. */
.crow {
    display: flex;
    align-items: baseline;
    gap: 10px;
}

.crow-l {
    flex-shrink: 0;
    min-width: 78px;
    font-size: var(--fs-xs);
    color: var(--text-tertiary);
}

.crow-v {
    flex: 1;
    min-width: 0;
    color: var(--text-primary);
    word-break: break-word;
}

/* The identity band at the top of the record: the avatar beside the name
   rather than above it. Centred, the block was three stacked centred lines
   that agreed with nothing else on the page. */
.cdet-id {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 16px;
    border-block-end: 1px solid var(--border-subtle);
}

.cdet-id-txt { min-width: 0; }

.cdet-id-name {
    font-size: var(--fs-lg);
    font-weight: var(--fw-bold);
    letter-spacing: -0.01em;
    color: var(--text-primary);
}

.cdet-note { margin-block-start: 14px; }

.cdet-note-txt {
    margin: 8px 0 0;
    font-size: var(--fs-sm);
    line-height: 1.5;
    color: var(--text-secondary);
}

.cdet-locked { margin-block-start: 10px; }

.cdet-help { margin: 0 0 4px; }

.cdet-acts { margin-block-start: 12px; }


/* ============================================================
   14. Interviewer portal

   Five screens for somebody who is not a recruiter and does not live in this
   product: they arrive because a scorecard is due, do one thing, and leave.
   So the portal keeps the shared chrome from section 12 and spends its own
   rules on exactly two things - the next interview, and how well this person
   scores against their team.

   The hero is the one place in the product that inverts. It earns it: on a
   page that is otherwise a stack of white cards, the single thing the
   interviewer came to do has to be findable without reading, and the navy
   is the same --p-900 the sidebar's brand tile uses.
   ============================================================ */

/* ---------- Up next ---------- */

.ivh {
    position: relative;
    overflow: hidden;
    border: 1px solid var(--p-800);
    border-radius: var(--r-xl);
    background: var(--p-900);
    color: var(--n-0);
}

/* A gold bloom off the top-right corner. Decorative, and behind everything:
   at 22% over navy it is a warmth, not a shape you can name. */
.ivh::before {
    content: '';
    position: absolute;
    inset-block-start: -70px;
    inset-inline-end: -50px;
    width: 220px;
    height: 220px;
    border-radius: 50%;
    background: radial-gradient(circle, rgba(245, 184, 0, 0.22), transparent 70%);
    pointer-events: none;
}

.ivh-in {
    position: relative;
    padding: 20px 22px;
}

/* The eyebrow: when, a rule, and how. The rule fills whatever is left so the
   medium always sits on the right edge. */
.ivh-top {
    display: flex;
    align-items: center;
    gap: 8px;
    margin-block-end: 14px;
}

.ivh-when {
    font-size: var(--fs-2xs);
    font-weight: var(--fw-bold);
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--gold-300);
}

.ivh-rule {
    flex: 1;
    height: 1px;
    background: rgba(255, 255, 255, 0.12);
}

.ivh-medium {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    font-size: var(--fs-xs);
    color: rgba(255, 255, 255, 0.7);
}

.ivh-body {
    display: flex;
    align-items: flex-start;
    gap: 16px;
}

.ivh-who { flex: 1; min-width: 0; }

.ivh-name {
    font-size: var(--fs-3xl);
    font-weight: var(--fw-bold);
    letter-spacing: -0.02em;
    line-height: 1.15;
}

.ivh-headline {
    margin-block-start: 2px;
    font-size: var(--fs-md);
    color: rgba(255, 255, 255, 0.7);
}

.ivh-tags {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    margin-block-start: 12px;
}

/* Two weights of tag on the dark ground: the round you are running is gold,
   everything else is a translucent white. Gold once, again - if the role and
   the client were gold too, nothing would be. */
.ivh-tag {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 4px 10px;
    border-radius: var(--r-full);
    background: rgba(255, 255, 255, 0.06);
    font-size: var(--fs-xs);
    color: rgba(255, 255, 255, 0.82);
    white-space: nowrap;
}

.ivh-tag-mine {
    background: rgba(245, 184, 0, 0.16);
    font-weight: var(--fw-semibold);
    color: var(--gold-300);
}

.ivh-count {
    flex-shrink: 0;
    text-align: end;
}

.ivh-count-v {
    font-family: var(--font-mono);
    font-variant-numeric: tabular-nums;
    font-size: var(--fs-4xl);
    font-weight: var(--fw-medium);
    letter-spacing: -0.02em;
    line-height: 1.1;
    color: var(--gold-300);
}

.ivh-count-l {
    margin-block-start: 2px;
    font-size: var(--fs-xs);
    color: rgba(255, 255, 255, 0.5);
}

/* What you are assessing. A panel inside the hero rather than a paragraph on
   it: it is a briefing, and it should read as something handed to you. */
.ivh-focus {
    margin-block-start: 16px;
    padding: 12px 14px;
    border: 1px solid rgba(255, 255, 255, 0.08);
    border-radius: var(--r-md);
    background: rgba(255, 255, 255, 0.05);
}

.ivh-focus-l {
    margin-block-end: 5px;
    font-size: 10px;
    font-weight: var(--fw-bold);
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--gold-300);
}

.ivh-focus-t {
    font-size: var(--fs-sm);
    line-height: 1.5;
    color: rgba(255, 255, 255, 0.85);
}

.ivh-acts {
    display: flex;
    align-items: center;
    gap: 10px;
    flex-wrap: wrap;
    margin-block-start: 16px;
}

.ivh-spacer { flex: 1; }

/* The one gold button in the product. It is the only control on the page
   that starts something, and on navy it is the only thing that can be. */
.ivh-go {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    height: var(--h-xl);
    padding: 0 18px;
    border-radius: var(--r-md);
    background: var(--gold-500);
    color: var(--p-900);
    font-size: var(--fs-md);
    font-weight: var(--fw-bold);
    white-space: nowrap;
    text-decoration: none;
    transition: background-color var(--dur-fast) var(--ease-out);
}

.ivh-go:hover { background: var(--gold-400); }

.ivh-2nd {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    height: var(--h-xl);
    padding: 0 16px;
    border: 1px solid rgba(255, 255, 255, 0.12);
    border-radius: var(--r-md);
    background: rgba(255, 255, 255, 0.08);
    color: var(--n-0);
    font-size: var(--fs-md);
    font-weight: var(--fw-semibold);
    white-space: nowrap;
    text-decoration: none;
    transition: background-color var(--dur-fast) var(--ease-out);
}

.ivh-2nd:hover { background: rgba(255, 255, 255, 0.14); }

.ivh-go:focus-visible,
.ivh-2nd:focus-visible {
    outline: 2px solid var(--gold-300);
    outline-offset: 2px;
}

.ivh-panel {
    display: flex;
    align-items: center;
    gap: 8px;
    font-size: var(--fs-xs);
    color: rgba(255, 255, 255, 0.5);
}

/* ---------- Today's schedule ---------- */

/* The time column, fixed so the rows line up on their start times rather
   than on the left edge of whatever each one says. */
.ivs-when {
    flex-shrink: 0;
    width: 64px;
}

.ivs-time {
    font-family: var(--font-mono);
    font-variant-numeric: tabular-nums;
    font-size: var(--fs-base);
    font-weight: var(--fw-semibold);
    color: var(--text-primary);
}

.ivs-dur {
    font-size: var(--fs-xs);
    color: var(--text-tertiary);
}

/* ---------- Rating ---------- */

.ivr {
    display: flex;
    align-items: center;
    gap: 14px;
    margin-block-end: 14px;
}

.ivr-avg {
    font-family: var(--font-mono);
    font-variant-numeric: tabular-nums;
    font-size: var(--fs-5xl);
    font-weight: var(--fw-medium);
    letter-spacing: -0.02em;
    line-height: 1;
    color: var(--gold-700);
}

/* The big one, for the feedback page where the score is the whole card. */
.ivr-avg-xl { font-size: 52px; letter-spacing: -0.03em; }

.ivr-stars {
    display: flex;
    gap: 2px;
    margin-block-end: 3px;
    color: var(--n-200);
}

.ivr-stars-c { justify-content: center; margin: 10px 0 6px; gap: 3px; }

/* The filled ones. A class on the star rather than a colour computed in the
   markup, so the two star rows on two pages cannot drift apart. */
.ivr-star-on { color: var(--gold-500); }

.ivr-quote {
    padding: 8px 0;
    border-block-start: 1px solid var(--border-subtle);
    font-size: var(--fs-sm);
    line-height: 1.5;
    color: var(--text-secondary);
}

.ivr-quote:first-of-type { border-block-start: none; }

.ivr-mid { text-align: center; padding: 8px 0; }

/* ---------- Comments ---------- */

.ivc {
    display: flex;
    gap: 14px;
    padding: 14px 0;
    border-block-start: 1px solid var(--border-subtle);
}

.ivc:first-of-type { border-block-start: none; }

.ivc-body { flex: 1; min-width: 0; }

.ivc-head {
    display: flex;
    align-items: center;
    gap: 8px;
}

.ivc-name {
    font-size: var(--fs-md);
    font-weight: var(--fw-semibold);
}

.ivc-stars {
    display: flex;
    gap: 1px;
    margin-inline-start: auto;
    color: var(--n-200);
}

.ivc-txt {
    margin: 6px 0 0;
    font-size: var(--fs-sm);
    line-height: 1.55;
    color: var(--text-secondary);
}

/* ---------- Calibration ---------- */

.ivm-stack {
    display: flex;
    flex-direction: column;
    gap: 12px;
}

.ivm-head {
    display: flex;
    align-items: center;
    gap: 8px;
    margin-block-end: 5px;
}

.ivm-dot {
    width: 8px;
    height: 8px;
    flex-shrink: 0;
    border-radius: 50%;
    background: var(--ivm-c, var(--n-400));
}

.ivm-l {
    flex: 1;
    min-width: 0;
    font-size: var(--fs-sm);
    font-weight: var(--fw-semibold);
}

.ivm-v {
    font-family: var(--font-mono);
    font-variant-numeric: tabular-nums;
    font-size: var(--fs-xs);
    color: var(--text-secondary);
}

.ivm-team {
    font-size: var(--fs-xs);
    color: var(--text-tertiary);
}

/* The team's number as a tick on your own bar, not a second bar: the
   question is "how far off am I", and two bars make you measure it yourself. */
.ivm-track { position: relative; }

.ivm-tick {
    position: absolute;
    inset-block: -2px;
    inset-inline-start: var(--ivm-team, 50%);
    width: 2px;
    background: var(--n-500);
}

.ivm-note {
    margin: 0 0 14px;
    font-size: var(--fs-xs);
    line-height: 1.45;
    color: var(--text-tertiary);
}

/* ---------- Week view ---------- */

.ivw {
    display: grid;
    grid-template-columns: repeat(5, minmax(0, 1fr));
}

.ivw-day {
    min-height: 360px;
    border-inline-end: 1px solid var(--border-subtle);
}

.ivw-day:last-child { border-inline-end: none; }

.ivw-head {
    padding: 12px;
    text-align: center;
    border-block-end: 1px solid var(--border-subtle);
}

.ivw-dow {
    font-size: var(--fs-xs);
    font-weight: var(--fw-semibold);
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--text-tertiary);
}

.ivw-dom {
    margin-block-start: 2px;
    font-size: var(--fs-xl);
    font-weight: var(--fw-bold);
}

.ivw-slots {
    display: flex;
    flex-direction: column;
    gap: 8px;
    padding: 8px;
}

.ivw-slot {
    display: block;
    padding: 8px 10px;
    border: 1px solid var(--p-200);
    border-radius: var(--r-md);
    background: var(--p-50);
    text-decoration: none;
    transition: border-color var(--dur-fast) var(--ease-out),
                background-color var(--dur-fast) var(--ease-out);
}

.ivw-slot:hover { border-color: var(--p-300); background: var(--p-100); }

.ivw-slot-t {
    font-family: var(--font-mono);
    font-variant-numeric: tabular-nums;
    font-size: var(--fs-xs);
    font-weight: var(--fw-semibold);
    color: var(--p-700);
}

.ivw-slot-n {
    margin-block-start: 2px;
    font-size: var(--fs-sm);
    font-weight: var(--fw-semibold);
    color: var(--text-primary);
}

.ivw-slot-s {
    font-size: var(--fs-xs);
    color: var(--text-tertiary);
}

/* ---------- Shared interviewer bits ---------- */

/* Overlapping avatars. The ring is painted with a shadow rather than a
   border so it does not change the avatar's size. */
.ivp { display: inline-flex; }

.ivp > * {
    position: relative;
    border-radius: 50%;
    box-shadow: 0 0 0 2px var(--n-0);
}

.ivp > * + * { margin-inline-start: -8px; }

/* On the hero's navy the white ring reads as a halo; the ring has to be the
   ground it sits on. */
.ivh .ivp > * { box-shadow: 0 0 0 2px var(--p-900); }

.ivdots { display: inline-flex; gap: 3px; }

.ivdot {
    width: 7px;
    height: 7px;
    border-radius: 50%;
    background: var(--n-200);
}

.ivdot-on { background: var(--ivdot-c, var(--p-700)); }

.ivrec {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    height: 20px;
    padding: 0 8px;
    border-radius: var(--r-full);
    background: var(--ivrec-bg, var(--n-100));
    color: var(--ivrec-fg, var(--text-secondary));
    font-size: var(--fs-2xs);
    font-weight: var(--fw-bold);
    letter-spacing: 0.02em;
    text-transform: uppercase;
    white-space: nowrap;
}

.ivrec-lg {
    height: 28px;
    padding: 0 12px;
    font-size: var(--fs-md);
    letter-spacing: -0.01em;
    text-transform: none;
}

.ivrec-dot {
    width: 5px;
    height: 5px;
    border-radius: 50%;
    background: var(--ivrec-bar, currentColor);
}

.ivrec-lg .ivrec-dot { width: 7px; height: 7px; }

.ivmed {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    font-size: var(--fs-xs);
    color: var(--text-secondary);
}

.ivlabel {
    display: flex;
    align-items: center;
    margin-block-end: 10px;
}

.ivlabel > span:first-child {
    flex: 1;
    font-size: var(--fs-2xs);
    font-weight: var(--fw-bold);
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--text-tertiary);
}


/* ---------- Sessions ---------- */

/* Wide enough for a candidate's full name, which is what people actually
   type into it. */
.ivq { width: 240px; }

/* The clock time under the day word. Tertiary and mono: "Tomorrow" is the
   answer, "2:30 pm" is the detail. */
.ivq-t {
    font-size: var(--fs-xs);
    color: var(--text-tertiary);
}

.ivq-seg { margin-inline-end: 8px; }

@media (max-width: 700px) {
    .ivq { width: 100%; }
    .tbar-end { margin-inline-start: 0; width: 100%; }
}

/* ---------- Calibration ---------- */

/* Within 0.2 of the team is calibrated, further is worth looking at. Green
   and amber rather than green and red: being a harder marker than your team
   is a thing to know, not a fault. */
.ivm-delta {
    font-size: var(--fs-xs);
    font-weight: var(--fw-semibold);
}

.ivm-delta.is-near { color: var(--success-600); }
.ivm-delta.is-off  { color: var(--warning-600); }

/* The legend for the tick. Without it the mark on every bar is unexplained,
   and an unexplained mark reads as a rendering fault. */
.ivm-key {
    display: flex;
    align-items: center;
    gap: 6px;
    margin-block-start: 14px;
    font-size: var(--fs-xs);
    color: var(--text-tertiary);
}

.ivm-key-tick {
    width: 12px;
    height: 2px;
    background: var(--n-500);
}


/* ---------- Needs scorecard ---------- */

/* The good empty state. Green and a tick, because having nothing to do here
   is the outcome the page exists to produce - drawn like a missing list, it
   read as a failure to load. */
.ivdone {
    padding: 40px 24px;
    text-align: center;
    background: var(--surface-raised);
    border: 1px solid var(--border-default);
    border-radius: var(--r-lg);
    box-shadow: var(--shadow-xs);
}

.ivdone-mark {
    display: grid;
    place-items: center;
    width: 48px;
    height: 48px;
    margin: 0 auto 12px;
    border-radius: var(--r-xl);
    background: var(--success-50);
    color: var(--success-600);
}

.ivdone-t {
    font-size: var(--fs-lg);
    font-weight: var(--fw-semibold);
    color: var(--text-primary);
}

/* An owed scorecard. The amber hairline is the only thing marking it as
   overdue - a full amber card for four of them would be a wall. */
.ivdue {
    display: flex;
    align-items: center;
    gap: 16px;
    padding: 16px 20px;
    border-color: var(--warning-100);
}

.ivdue-line {
    display: flex;
    align-items: center;
    gap: 10px;
    min-width: 0;
}

.ivdue-name {
    font-size: var(--fs-lg);
    font-weight: var(--fw-semibold);
}

@media (max-width: 700px) {
    .ivdue { flex-wrap: wrap; padding: 14px; }
    .ivdue .lst-main { flex: 1 1 100%; }
}

/* ---------- Settings ---------- */

.ivset-id {
    display: flex;
    align-items: center;
    gap: 14px;
    margin-block-end: 14px;
    min-width: 0;
}

.ivset-name {
    font-size: var(--fs-xl);
    font-weight: var(--fw-bold);
    letter-spacing: -0.01em;
}

.ivset-mail { margin-block-start: 2px; }

/* A round this interviewer is trained on. Navy, and a shield: it is a
   credential, not a filter, so it does not use .chip-tog's clickable look. */
.chip-round {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 5px 11px;
    border-radius: var(--r-full);
    background: var(--p-50);
    font-size: var(--fs-xs);
    font-weight: var(--fw-semibold);
    color: var(--p-700);
}

.ivset-pw { gap: 8px; padding-block: 4px; }

.ivset-out { margin-block-start: 12px; }

/* Sign out is the one destructive thing on the page. It stays a secondary
   button - a red fill would make leaving look like deleting - and carries
   the danger colour on its label only. */
.ivset-signout { color: var(--danger-600); }

/* ---------- Switch ----------
   The shared on/off control. Lives here rather than with the primitives
   because it arrived with this portal's settings page; move it up when a
   second portal needs it. */
.switch {
    position: relative;
    width: 36px;
    height: 20px;
    flex-shrink: 0;
    padding: 0;
    border: none;
    border-radius: var(--r-full);
    background: var(--n-200);
    cursor: pointer;
    transition: background-color var(--dur-fast) var(--ease-out);
}

.switch.on { background: var(--p-700); }

.switch:disabled { cursor: not-allowed; opacity: 0.5; }

.switch:focus-visible { outline: none; box-shadow: var(--shadow-focus); }

.switch-knob {
    position: absolute;
    inset-block-start: 2px;
    inset-inline-start: 2px;
    width: 16px;
    height: 16px;
    border-radius: 50%;
    background: var(--n-0);
    box-shadow: var(--shadow-xs);
    transition: inset-inline-start var(--dur-fast) var(--ease-out);
}

.switch.on .switch-knob { inset-inline-start: 18px; }

@media (prefers-reduced-motion: reduce) {
    .switch,
    .switch-knob { transition: none; }
}


/* ============================================================
   Tabs

   An underlined row, not a segmented control: these switch what the page is
   showing, and a segmented control says "pick one setting" rather than "this
   is another view of the same thing". The active tab's mark sits on the
   group's own hairline - hence the -1px - so the two read as one edge with a
   piece of it picked out.

   The row was correct and under-drawn. Three things it did not do:

     - A tab gave no sign it was a target until it was already active. The
       only hover was the label darkening by one step and a grey rule
       appearing under it, which is a mark you notice after you have already
       moved the pointer there. There is a tint now, on the tab itself, with
       the top corners rounded so it reads as a tab rather than a rectangle
       painted behind a word.

     - The active mark was a full-width border-bottom, so on a wide tab it
       ran a centimetre past the label at both ends. It is a pseudo-element
       inset to the label with a rounded cap now, which is the difference
       between a marked tab and a tab with a line under the whole of it.

     - A count riding a tab was bare mono text hung off the label. It is a
       pill, which is what a count is everywhere else in this stylesheet, and
       it picks up the brand tint when its tab is the active one.

   What the row still did not do, once all three were in: say which tab you
   were on from across the page. An active tab was a navy word with a navy
   dash under it in a line of grey words - correct, and no louder than the
   labels either side of it. So the active tab carries a --p-50 wash that
   fades out into the hairline, and the mark is the brand's two steps with a
   bloom rather than one flat navy. The wash is the shape of a tab, the
   fade is what joins it to the panel it fronts, and together they are what
   you see before you read the row. The tab also got 3px taller and one step
   more corner, because the affordance is the target as well as the tint.

   .tabs-band is the page-level modifier: the row goes full-bleed so its
   hairline runs the whole width of the shell, and pins directly under
   .pg-head so the reader keeps the switch in view down a long report. It is
   opt-in because .tabs is also used in places with no pinned header above
   them, and a bar that sticks to the top of a short container unsticks a
   few pixels later, which looks like a bug rather than a decision.
   ============================================================ */

/* The row carries its own white rather than borrowing whatever it was
   dropped onto. A tab row is the top edge of the surface it switches, and
   on the page ground (--surface-page, a grey) the tabs read as sitting
   beside the panel instead of on it. White under the row joins the two:
   the hairline becomes the line where the switched surface starts, not a
   rule drawn across the page.

   It is on .tabs and not only on .tabs-band because most rows here are not
   banded, and a row inside an already-white card simply paints white on
   white and costs nothing. */
.tabs {
    display: flex;
    /* 4px rather than 2: the tabs now carry a tint and a rounded top, so
       the gap is the space between two shapes rather than the space
       between two words. At 2px the washes of neighbouring tabs read as
       one continuous band. */
    gap: 4px;
    background: var(--surface-raised);
    border-block-end: 1px solid var(--border-default);
    /* The gap to the panel, declared once. Every row that used one was
       declaring its own - 2px on the AI Center and the offer templates, 14px
       on the certification catalogue, the career site builder, the client and
       the tenant, 16px on the notifications centre, and a spacer <div> on the
       job - so six rows that are the same control stood six different
       distances off the thing they switch. 14px is the figure the most of
       them had already arrived at, and the one .cp-tabs uses. */
    margin-block-end: 14px;
}

/* A sheet is the panel the row switches, joined to it: it starts on the row's
   own hairline rather than 14px below it, or the section opens with a stripe
   of page grey through the middle of it. */
.tabs:has(+ .pg-sheet) { margin-block-end: 0; }

.tab {
    /* The mark is an ::after rather than a border-bottom, so it can be
       inset to the label and given a cap. The bottom padding carries the
       2px the border used to occupy. */
    position: relative;

    /* Each tab is the width of its own label, and the row ends after the
       last one. The alternative - equal shares of the full width, which is
       what this was - reads as tabs only while the row is narrow enough
       that a share is about the size of a word. On a 1900px console four
       tabs become four 400px cells with a word centred in each, and what
       the reader scans is a line of isolated labels several inches apart
       rather than one control. The group has to look like a group: that is
       the whole job of a tab row, and proximity is how it does it.

       The empty run after the last tab is not waste. The hairline still
       crosses the full width - it belongs to the row, not to the tabs
       standing on it - so the band reads exactly as it did, and the
       leftover is the same quiet margin every other left-aligned control
       on these pages sits in.

       A row that genuinely wants to divide its width says so itself, the
       way .cp-tabs does below: the candidate profile's seven tabs are a
       scrolling band, and there the share is the point.

       Still a centred flex line, which costs nothing now that the tab is
       its own width and is what keeps the icon-and-label rows aligned -
       the three lines .aic-tabs, .csb-tabs and .cp-tabs each used to add. */
    flex: 0 0 auto;
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 7px;

    /* 12/13 rather than 10/12. The row is the one control on these pages a
       reader hits over and over, and at the old height a tab was 34px of
       target under a 56px header - the smallest thing on the page to aim
       at, and the most used. */
    padding: 12px 14px 13px;
    margin-block-end: -1px;
    /* Only the top corners: the bottom of a tab is the hairline it sits on,
       and rounding that corner lifts the tint off the rule. --r-md rather
       than --r-sm now that the tab is taller and carries a fill: --r-sm on
       a 38px shape reads as a rectangle someone forgot to round. */
    border-radius: var(--r-md) var(--r-md) 0 0;
    background: none;
    font-size: var(--fs-md);
    font-weight: var(--fw-semibold);
    /* The page title above is set tight; six short labels in a line at the
       default tracking read loose against it. */
    letter-spacing: -0.006em;
    color: var(--text-secondary);
    white-space: nowrap;
    /* A tab that navigates rather than switches state is an anchor - the
       candidate profile section is two pages, not two panels - and an
       underlined tab label reads as a link that happens to sit in a tab row. */
    text-decoration: none;
    transition: color var(--dur-fast) var(--ease-out),
                background-color var(--dur-fast) var(--ease-out);
}

/* Inset to roughly the label rather than the tab, and capped. The bottom
   edge lands on the group's hairline - the -1px above pulls the tab into
   it - so an active tab still reads as a piece of that one line. */
.tab::after {
    content: '';
    position: absolute;
    /* 9px, which is the --r-md corner plus the 2px of straight edge the
       cap needs to land on. At 10px the mark started inside the curve. */
    inset-inline: 9px;
    inset-block-end: 0;
    height: 2px;
    border-radius: var(--r-full);
    background: transparent;
    transition: background-color var(--dur-fast) var(--ease-out);
}

/* The tint is the affordance; the rule hint underneath says which way the
   tab will be marked once it is chosen. */
.tab:hover {
    /* --n-75, not --n-50: the row's own ground is --surface-raised, and
       --n-50 over white is a two-step difference nobody sees at the speed
       a pointer crosses a tab row. */
    background: var(--n-75);
    color: var(--text-primary);
}

.tab:not(.on):hover::after { background: var(--border-strong); }

.tab.on {
    color: var(--p-700);
    /* A wash rather than a flat fill: strongest where the label sits and
       gone by the time it reaches the hairline, so the active tab reads as
       the front face of the panel below rather than as a coloured box
       parked on the row. The end stop is --p-50 at zero alpha written out
       rather than `transparent` - engines that interpolate through
       rgba(0, 0, 0, 0) put a grey cast down the middle of the fade. */
    background: linear-gradient(180deg, var(--p-50) 0%, rgba(238, 242, 255, 0) 100%);
}

/* The mark, in the brand's own two steps with a bloom under it rather than
   one flat navy. On a row of six grey labels the active one has to carry
   before the row is read, and 2px of solid --p-700 is a mark you find by
   reading rather than one you see. */
.tab.on::after {
    background: linear-gradient(90deg, var(--p-700), var(--p-500));
    box-shadow: 0 1px 6px rgba(30, 58, 138, 0.4);
}

/* The active tab keeps its own tint on hover, or hovering the tab you are
   already on reads as the row going dead under the pointer. One step
   deeper than its resting wash, so the row answers without the tab
   changing character under the pointer. */
.tab.on:hover {
    background: linear-gradient(180deg, var(--p-100) 0%, rgba(218, 227, 255, 0) 100%);
}

.tab:focus-visible {
    outline: none;
    box-shadow: var(--shadow-focus);
}

/* A count riding a tab. This section has claimed since it was written that a
   count here is a pill; the class the rows were already reaching for had no
   rule behind it, so it stayed the bare text it was supposed to replace. It
   takes the brand tint on the active tab and stays neutral off it, so a row
   of numbers does not read as a row of alerts. */
.tab-n {
    display: inline-grid;
    place-items: center;
    min-width: 19px;
    height: 19px;
    padding: 0 6px;
    border-radius: var(--r-full);
    background: var(--n-100);
    color: var(--text-secondary);
    font-size: var(--fs-2xs);
    font-weight: var(--fw-semibold);
    /* .cp-tab-n already does this. A count that changes from 9 to 10 on a
       refresh should not move the label beside it. */
    font-variant-numeric: tabular-nums;
    transition: background-color var(--dur-fast) var(--ease-out),
                color var(--dur-fast) var(--ease-out);
}

.tab.on .tab-n {
    background: var(--p-100);
    color: var(--p-700);
}

/* The icon on a tab, where a row has them. It never shrinks - a squeezed
   icon is a different icon - and it sits a shade back from the label off
   the active tab, so a row of six reads as six words with marks beside
   them rather than as six icons with captions. The same two lines
   .cp-tabs already spells out for its own svg. */
.tab-i { flex-shrink: 0; opacity: 0.7; transition: opacity var(--dur-fast) var(--ease-out); }

.tab.on .tab-i,
.tab:hover .tab-i { opacity: 1; }

/* ---------- The travelling hover tint ----------
   Same argument as the mark below, one step earlier. The tint was a
   background on .tab, so moving along the row was a box appearing under the
   pointer and another vanishing behind it - four buttons reacting one at a
   time. Drawn once by the row and moved, the row answers the pointer as the
   single control it is meant to be.

   It rides below the tabs without a z-index on either: a pseudo-element is
   painted as the first child of its element, so ::before lands under the
   buttons and ::after over them, which is also exactly what keeps the mark
   above the tint. Giving .tab a z-index to force the issue would lift it over
   .tabs::after and bury the mark instead.

   Handed over the way the mark is: .tab:hover keeps its own background in the
   stylesheet and only stops painting it once .has-hov says the row has taken
   the job on, so a page with no JavaScript still tints on hover. The active
   tab's --p-50 is deliberately not handed over - it says which tab you are on,
   not where the pointer is, and it has to survive the tint sliding off it. */
.tabs::before,
.cp-tabs::before {
    content: '';
    position: absolute;
    inset-block-start: var(--tab-hov-y, 0);
    inset-inline-start: var(--tab-hov-x, 0);
    width: var(--tab-hov-w, 0);
    height: var(--tab-hov-h, 0);
    /* Both track .tab above, or the travelling tint is a different shape
       from the one the no-JS fallback paints. */
    border-radius: var(--r-md) var(--r-md) 0 0;
    background: var(--n-75);
    opacity: 0;
    pointer-events: none;
    transition: opacity var(--dur-fast) var(--ease-out);
}

.tabs.has-hov::before,
.cp-tabs.has-hov::before { opacity: 1; }

/* The handover, and with it the hover rule the tint replaces. */
.tabs.has-hov .tab:not(.on):hover,
.cp-tabs.has-hov .tab:not(.on):hover { background: transparent; }

/* Travel, armed a frame after the first placement so the tint does not slide
   in from the start of the row the first time the pointer enters it. */
.tabs.tab-hov-ready::before,
.cp-tabs.tab-hov-ready::before {
    transition: inset-inline-start var(--dur-mid) var(--ease-out),
                width var(--dur-mid) var(--ease-out),
                opacity var(--dur-fast) var(--ease-out);
}

/* ---------- The scroll affordance ----------
   These rows hide their scrollbar, so a row with more tabs than width looked
   exactly like a row that ended where the last tab did: nothing said there was
   anything past the edge. The fade is set per side by jork-tabs.js and appears
   only where there is actually more to show, so a row that fits keeps its
   square edges and a row scrolled to the end stops advertising an end. */
.tabs.tab-cut-s,
.cp-tabs.tab-cut-s {
    mask-image: linear-gradient(to right, transparent 0, #000 36px);
}

.tabs.tab-cut-e,
.cp-tabs.tab-cut-e {
    mask-image: linear-gradient(to right, #000 calc(100% - 36px), transparent 100%);
}

.tabs.tab-cut-s.tab-cut-e,
.cp-tabs.tab-cut-s.tab-cut-e {
    mask-image: linear-gradient(to right, transparent 0, #000 36px,
                                #000 calc(100% - 36px), transparent 100%);
}

/* ---------- The travelling mark ----------
   The mark above belongs to the tab, and a mark drawn by the tab it belongs to
   can only appear and disappear: the old one fades out where it was, the new
   one fades in where it is, and nothing connects the two. Switching a tab is
   the one moment the row has to say the page turned rather than reloaded, and
   two independent fades do not say it.

   So the row draws it instead. One mark, positioned from three custom
   properties that js/jork-tabs.js sets from the active tab's own geometry, and
   moved rather than replaced. It is a pseudo-element and not an element on
   purpose: a <span> injected into a row Blazor renders is a node Blazor did not
   put there, and the next diff of that row is entitled to remove it.

   Both directions degrade. Until the row is measured it has no .has-ind, the
   custom properties are unset, and .tab.on::after above is still what draws the
   mark - so a page with no JavaScript, or one measured a frame late, is never
   left with an unmarked row. .has-ind is the handover: the tab's mark goes
   transparent at the same moment the row's becomes visible, in the same place
   and at the same width, so the swap has nothing to see.

   .tab-ind-ready arrives one frame after that, and is what turns the travel on.
   The first placement is not a journey from anywhere - without the delay the
   mark slides in from the start of the row on every page load. */

.tabs { position: relative; }

.tabs::after,
.cp-tabs::after {
    content: '';
    position: absolute;
    /* Content coordinates, so a row that scrolls on a narrow screen carries its
       mark with it. inset-inline-start rather than a transform: it is the right
       edge under RTL, which is the whole of what the mark needs to know. */
    inset-block-start: var(--tab-ind-y, 0);
    inset-inline-start: var(--tab-ind-x, 0);
    width: var(--tab-ind-w, 0);
    height: 2px;
    border-radius: var(--r-full);
    /* The same two-step gradient and bloom .tab.on::after paints, or the
       handover below swaps one mark for a different-looking one. */
    background: linear-gradient(90deg, var(--p-700), var(--p-500));
    box-shadow: 0 1px 6px rgba(30, 58, 138, 0.4);
    opacity: 0;
    pointer-events: none;
}

.tabs.has-ind::after,
.cp-tabs.has-ind::after { opacity: 1; }

/* The handover. The hover hint on an inactive tab is left alone - it says which
   way the tab will be marked once it is chosen, which is still its own job. */
.tabs.has-ind .tab.on::after,
.cp-tabs.has-ind .tab.on::after {
    background: transparent;
    /* The bloom goes with it. A transparent bar still casts its shadow,
       which would leave a second glow sitting under the row's own mark. */
    box-shadow: none;
}

/* Width as well as position: the mark is inset to its label, so moving between
   a short tab and a long one is a change of both. The vertical is deliberately
   not in the list - a row that grows a line should not have its mark slide down
   the page to meet it. */
.tabs.tab-ind-ready::after,
.cp-tabs.tab-ind-ready::after {
    transition: inset-inline-start var(--dur-mid) var(--ease-out),
                width var(--dur-mid) var(--ease-out);
}

@media (prefers-reduced-motion: reduce) {
    .tabs.tab-ind-ready::after,
    .cp-tabs.tab-ind-ready::after { transition: none; }

    /* The tint still appears and disappears - only the travel is dropped. */
    .tabs.tab-hov-ready::before,
    .cp-tabs.tab-hov-ready::before {
        transition: opacity var(--dur-fast) var(--ease-out);
    }
}

/* ---------- Page band ----------
   The row as a piece of page chrome rather than a control sitting in the
   content: full-bleed, so its hairline runs the width of the shell and
   continues the one under .pg-head, and pinned just below that header.

   The inline padding is .pg-head's own inset minus the tab's 14px, which
   lands the first tab's label on the same vertical as the header's mark.
   Anything else and the page opens with two left edges 14px apart. */
.tabs-band {
    position: sticky;
    inset-block-start: calc(var(--pg-head-h, 56px) - var(--pg-gy, 8px));
    /* Under .pg-head's 30: the header wins where the two meet. */
    z-index: 29;
    margin-inline: calc(0px - var(--pg-gx, 8px));
    padding-inline: calc(var(--pg-gx, 8px) - 6px);
    /* Matches .tabs above: the band is the same row, full-bleed. */
    background: var(--surface-raised);
}

/* ---------- The panel under the band ----------
   .tabs-band takes the row full-bleed and white; the panel it switches was
   still cards floating on the page grey. So the section read as a white
   strip with the page showing through under it rather than as one surface
   with a switch along the top - and the tab row, which is the top edge of
   the thing it switches, ended up looking like the bottom edge of the
   header instead. .pg-sheet is the other half of the band: same full-bleed,
   same white, and a hairline to close it.

   It cancels the shell's gutter rather than setting a width. The four
   shells pad <main> by different amounts - AppShell 8px, the client and
   interviewer portals 24px and 32px - and --pg-gx is the variable they
   publish for exactly this; it is what .pg-head and .tabs-band already
   read, so a page that adopts all three lines up in every shell without
   any of them knowing which one it is in.

   A card inside gives up its own frame. Two borders 8px apart - the
   sheet's and the card's - is the seam this was meant to close; the sheet
   is the card now. Content keeps the page's own inset, --pg-gx + 8px, the
   same figure .pg-head uses, so a table's first column lands on the
   vertical the page title is already on. */
.pg-sheet {
    margin-inline: calc(0px - var(--pg-gx, 8px));
    background: var(--surface-raised);
    border-block-end: 1px solid var(--border-default);
}

.pg-sheet > .card {
    border: 0;
    border-radius: 0;
    background: none;
    box-shadow: none;
}

.pg-sheet > .card > .sec-head,
.pg-sheet .tbl thead th:first-child,
.pg-sheet .tbl tbody td:first-child { padding-inline-start: calc(var(--pg-gx, 8px) + 8px); }

.pg-sheet > .card > .sec-head,
.pg-sheet .tbl thead th:last-child,
.pg-sheet .tbl tbody td:last-child { padding-inline-end: calc(var(--pg-gx, 8px) + 8px); }

/* The loading state is a direct child of the panel, not of a card, so it has
   no inset of its own to inherit and would otherwise run edge to edge. */
.pg-sheet > .skel { margin: 12px calc(var(--pg-gx, 8px) + 8px); }

/* Scrolls rather than wraps: a wrapped tab row changes height and pushes the
   content down as you switch between them. Snapping means a swipe lands a
   tab flush against the edge instead of halfway off it. */
@media (max-width: 700px) {
    .tabs {
        overflow-x: auto;
        scrollbar-width: none;
        scroll-snap-type: x proximity;
    }

    .tabs::-webkit-scrollbar { display: none; }

    .tab { scroll-snap-align: start; }

    /* .pg-head drops to --pg-gx + 6px here, so the band follows it down. */
    .tabs-band { padding-inline: calc(var(--pg-gx, 8px) - 8px); }
}

/* ============================================================
   Tile - a bordered row inside a card

   A criterion, a rubric band, a small fact with a mark beside it. Sunken
   rather than raised: it sits inside a card, and a raised box on a raised
   surface is a card inside a card.
   ============================================================ */

.tile {
    display: flex;
    align-items: flex-start;
    gap: 10px;
    padding: 10px 12px;
    border: 1px solid var(--border-subtle);
    border-radius: var(--r-md);
    background: var(--n-25);
}

.tile-i {
    flex-shrink: 0;
    margin-block-start: 2px;
    color: var(--p-700);
}

.tile-t {
    font-size: var(--fs-md);
    font-weight: var(--fw-semibold);
    color: var(--text-primary);
}

.tile-s {
    font-size: var(--fs-xs);
    line-height: 1.4;
    color: var(--text-tertiary);
}

/* ---------- Note extras ----------
   An undo that belongs to a note, and the pair of buttons a note asks a
   question with. */
.note-undo {
    margin-inline-start: auto;
    flex-shrink: 0;
    font-size: var(--fs-xs);
    font-weight: var(--fw-semibold);
    color: inherit;
    text-decoration: underline;
}

.note-acts {
    display: flex;
    align-items: center;
    gap: 6px;
    flex-shrink: 0;
    margin-inline-start: auto;
}

/* A destructive choice that is not the destructive action - "I have a
   conflict" declares something, it does not delete anything. Tinted rather
   than filled. */
.btn-soft-danger {
    border: 1px solid var(--danger-200);
    background: var(--danger-50);
    color: var(--danger-700);
}

.btn-soft-danger:hover { background: var(--danger-100); }

/* ============================================================
   Interview detail
   ============================================================ */

/* The brief under the title: round, role, when, medium. One line that wraps,
   rather than four labelled fields - these are read together or not at all. */
.ivd-facts {
    display: flex;
    flex-wrap: wrap;
    gap: 14px;
    font-size: var(--fs-sm);
    color: var(--text-secondary);
}

.ivd-fact {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    white-space: nowrap;
}

.ivd-fact strong { color: var(--text-primary); }

.ivd-fact-i { color: var(--p-700); }

.ivd-lede {
    margin: 0 0 14px;
    font-size: var(--fs-md);
    line-height: 1.55;
    color: var(--text-secondary);
}

.ivd-lede strong { color: var(--text-primary); }

.ivd-list {
    display: flex;
    flex-direction: column;
    gap: 5px;
    margin: 0;
    padding-inline-start: 16px;
    font-size: var(--fs-sm);
    line-height: 1.5;
    color: var(--text-secondary);
}

.ivd-q { align-items: flex-start; }

.ivd-q-dot { margin-block-start: 2px; }

.ivd-q-t {
    font-size: var(--fs-md);
    font-weight: var(--fw-medium);
    line-height: 1.45;
}

.ivd-q-m { margin-block-start: 4px; gap: 8px; }

/* The step number on a tip. A numbered square rather than an ordered list:
   the four tips are a checklist, not a sequence, and the number is there to
   count them. */
.ivd-n {
    display: grid;
    place-items: center;
    width: 20px;
    height: 20px;
    flex-shrink: 0;
    border-radius: var(--r-sm);
    background: var(--p-50);
    font-size: var(--fs-xs);
    font-weight: var(--fw-bold);
    color: var(--p-700);
}

/* A rubric band's number, in the band's own colour. White on the tone rather
   than the tone on white: at 26px the chip is the legend. */
.ivd-score {
    display: grid;
    place-items: center;
    width: 26px;
    height: 26px;
    flex-shrink: 0;
    border-radius: var(--r-md);
    background: var(--ivd-score-c, var(--n-400));
    font-family: var(--font-mono);
    font-size: var(--fs-md);
    font-weight: var(--fw-bold);
    color: var(--n-0);
}

.ivd-you {
    margin-inline-start: 6px;
    font-size: var(--fs-xs);
    font-weight: var(--fw-semibold);
    color: var(--p-700);
}


/* ============================================================
   Scorecard

   The one screen in this portal that is a form, and the only one where the
   controls are the content: four buttons per competency, four for the
   recommendation. They are big and labelled because the scale is the thing
   being taught - a select would hide it behind a click.
   ============================================================ */

.ivsc {
    padding-block-start: 16px;
    margin-block-start: 16px;
    border-block-start: 1px solid var(--border-subtle);
}

.ivsc:first-of-type {
    padding-block-start: 0;
    margin-block-start: 0;
    border-block-start: none;
}

.ivsc-head {
    display: flex;
    align-items: baseline;
    gap: 8px;
    margin-block-end: 3px;
}

.ivsc-l {
    font-size: var(--fs-md);
    font-weight: var(--fw-semibold);
}

.ivsc-dots { margin-inline-start: auto; }

.ivsc-given {
    display: flex;
    align-items: center;
    gap: 8px;
    margin-block-start: 6px;
}

.ivsc-given-l {
    font-size: var(--fs-md);
    font-weight: var(--fw-semibold);
}

.ivd-score-sm {
    width: 24px;
    height: 24px;
    font-size: var(--fs-sm);
    border-radius: var(--r-sm);
}

.ivsc-scale {
    display: flex;
    gap: 6px;
    margin-block-start: 6px;
}

.ivsc-btn {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 6px;
    flex: 1;
    min-width: 0;
    height: 38px;
    border: 1px solid var(--border-default);
    border-radius: var(--r-md);
    background: var(--surface-raised);
    color: var(--text-secondary);
    transition: border-color var(--dur-fast) var(--ease-out),
                background-color var(--dur-fast) var(--ease-out);
}

.ivsc-btn:hover { border-color: var(--border-strong); background: var(--surface-hover); }

/* The chosen band fills with its own tone, so the row reads as one mark on a
   scale rather than four buttons one of which is darker. */
.ivsc-btn.on {
    border-color: var(--ivsc-c, var(--p-700));
    background: var(--ivsc-c, var(--p-700));
    color: var(--n-0);
}

.ivsc-btn:focus-visible { outline: none; box-shadow: var(--shadow-focus); }

.ivsc-btn-v {
    font-family: var(--font-mono);
    font-size: var(--fs-md);
    font-weight: var(--fw-bold);
}

.ivsc-btn-l {
    font-size: var(--fs-xs);
    font-weight: var(--fw-semibold);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* ---------- Recommendation ---------- */

.ivrc {
    display: grid;
    grid-template-columns: repeat(4, minmax(0, 1fr));
    gap: 8px;
}

.ivrc-opt {
    padding: 14px 10px;
    border: 1px solid var(--border-default);
    border-radius: var(--r-lg);
    background: var(--surface-raised);
    text-align: center;
    transition: border-color var(--dur-fast) var(--ease-out),
                background-color var(--dur-fast) var(--ease-out);
}

.ivrc-opt:hover { border-color: var(--border-strong); }

/* Two rings rather than a thicker border: a 2px border on select would move
   the card's contents by a pixel as you click through the four. */
.ivrc-opt.on {
    border-color: var(--ivrc-bar);
    background: var(--ivrc-bg);
    box-shadow: inset 0 0 0 1px var(--ivrc-bar);
}

.ivrc-opt:focus-visible { outline: none; box-shadow: var(--shadow-focus); }

.ivrc-mark {
    display: grid;
    place-items: center;
    width: 30px;
    height: 30px;
    margin: 0 auto 8px;
    border-radius: 50%;
    background: var(--n-100);
    color: var(--text-tertiary);
}

.ivrc-opt.on .ivrc-mark {
    background: var(--ivrc-bar);
    color: var(--n-0);
}

.ivrc-l {
    display: block;
    font-size: var(--fs-md);
    font-weight: var(--fw-bold);
    color: var(--text-primary);
}

.ivrc-opt.on .ivrc-l { color: var(--ivrc-fg); }

@media (max-width: 900px) {
    .ivrc { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* ---------- Summary ---------- */

.ivsc-ta { min-height: 130px; line-height: 1.6; }

/* white-space: pre-wrap - the interviewer's paragraph breaks are part of what
   they wrote, and collapsing them turns a structured note into a wall. */
.ivsc-prose {
    margin: 0;
    font-size: var(--fs-md);
    line-height: 1.65;
    color: var(--text-secondary);
    white-space: pre-wrap;
}

.ivsc-want {
    display: flex;
    align-items: center;
    gap: 10px;
    margin-block-start: 14px;
}

.ivsc-want-l {
    font-size: var(--fs-md);
    font-weight: var(--fw-medium);
}

/* "Would want them on my team" is a positive signal, so its switch is green
   rather than the navy of a neutral setting. */
.switch-good.on { background: var(--success-600); }

/* ---------- Submit rail ---------- */

/* Sticky: on a long form the submit button and the reasons it is disabled
   are what you keep checking against. */
.ivsc-rail {
    position: sticky;
    inset-block-start: 0;
}

.ivsc-check {
    display: flex;
    flex-direction: column;
    gap: 9px;
    margin-block-end: 14px;
}

.ivsc-check-i {
    display: flex;
    align-items: center;
    gap: 9px;
    font-size: var(--fs-sm);
    color: var(--text-tertiary);
}

.ivsc-check-i.on { color: var(--text-primary); }

.ivsc-check-m {
    display: grid;
    place-items: center;
    width: 18px;
    height: 18px;
    flex-shrink: 0;
    border-radius: 50%;
    background: var(--n-100);
    color: var(--n-400);
}

.ivsc-check-i.on .ivsc-check-m {
    background: var(--success-50);
    color: var(--success-600);
}

.ivsc-err { margin-block-end: 10px; }

.ivsc-lock {
    margin: 10px 0 0;
    font-size: var(--fs-xs);
    line-height: 1.45;
    text-align: center;
    color: var(--text-tertiary);
}

.ivsc-cr {
    font-family: var(--font-mono);
    font-variant-numeric: tabular-nums;
    font-size: var(--fs-lg);
    font-weight: var(--fw-semibold);
}

/* A button that owns its row - a form's one commit. */
.btn-block { width: 100%; }

/* A gold-toned note, for something the product did on your behalf. */
.note-gold {
    background: var(--gold-50);
    border-color: var(--gold-200);
    color: var(--gold-700);
}

.note-gold .note-title { color: var(--gold-700); }

@media (max-width: 1120px) {
    /* The rail is under the form now, so there is nothing to stick to. */
    .ivsc-rail { position: static; }
}

@media (max-width: 640px) {
    /* The anchor words stop fitting long before the numbers do. */
    .ivsc-btn-l { display: none; }
}


/* ============================================================
   Live session

   The only full-screen page in the product: no sidebar, no topbar, no
   breadcrumb. It is used while a real conversation is happening in the room,
   so everything on it is one of three things - the plan you are working
   through, the notes you are taking, or the clock.

   Three columns, fixed rails, and the middle one scrolls. The rails are
   sized to their content: 300px holds a question without hyphenating, 340px
   holds a competency label and its four buttons on one line.
   ============================================================ */

.ivl {
    display: flex;
    flex-direction: column;
    height: 100vh;
    background: var(--surface-page);
    color: var(--text-primary);
}

.ivl-wait {
    display: grid;
    place-items: center;
    height: 100vh;
    color: var(--text-tertiary);
}

/* ---------- Top bar ---------- */

.ivl-bar {
    display: flex;
    align-items: center;
    gap: 16px;
    height: 60px;
    flex-shrink: 0;
    padding: 0 20px;
    background: var(--p-900);
    color: var(--n-0);
}

.ivl-exit {
    display: flex;
    align-items: center;
    gap: 6px;
    font-size: var(--fs-sm);
    font-weight: var(--fw-medium);
    color: rgba(255, 255, 255, 0.7);
    text-decoration: none;
    white-space: nowrap;
}

.ivl-exit:hover { color: var(--n-0); }

.ivl-sep {
    width: 1px;
    height: 26px;
    background: rgba(255, 255, 255, 0.14);
}

.ivl-who { min-width: 0; }

.ivl-who-n {
    font-size: var(--fs-md);
    font-weight: var(--fw-bold);
    letter-spacing: -0.01em;
    white-space: nowrap;
}

.ivl-who-s {
    font-size: var(--fs-xs);
    color: rgba(255, 255, 255, 0.6);
    white-space: nowrap;
}

.ivl-mid {
    flex: 1;
    display: flex;
    justify-content: center;
}

.ivl-clock {
    display: flex;
    align-items: center;
    gap: 14px;
}

/* The running dot. Gold with a halo while the clock runs, red once you are
   past the booked length, flat grey when paused - three states you read
   without looking at the numbers. */
.ivl-dot {
    width: 8px;
    height: 8px;
    border-radius: 50%;
    background: rgba(255, 255, 255, 0.4);
}

.ivl-dot.on {
    background: var(--gold-500);
    box-shadow: 0 0 0 4px rgba(245, 184, 0, 0.18);
}

.ivl-dot.on.over {
    background: var(--danger-500);
    box-shadow: 0 0 0 4px rgba(239, 68, 68, 0.2);
}

.ivl-t {
    font-family: var(--font-mono);
    font-variant-numeric: tabular-nums;
    font-size: 26px;
    font-weight: var(--fw-medium);
    letter-spacing: -0.01em;
    color: var(--n-0);
}

/* Not --danger-500: on this navy it fails contrast. The 300-weight red is
   the readable one. */
.ivl-t.over { color: var(--danger-300, #fca5a5); }

.ivl-t-of {
    font-size: var(--fs-xs);
    color: rgba(255, 255, 255, 0.5);
}

.ivl-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 7px;
    height: 34px;
    padding: 0 12px;
    border: none;
    border-radius: var(--r-md);
    background: rgba(255, 255, 255, 0.1);
    font-size: var(--fs-sm);
    font-weight: var(--fw-semibold);
    color: var(--n-0);
    white-space: nowrap;
    text-decoration: none;
    transition: background-color var(--dur-fast) var(--ease-out);
}

.ivl-btn:hover { background: rgba(255, 255, 255, 0.16); }

.ivl-btn-icon { width: 32px; height: 32px; padding: 0; }

/* The gold one, once: ending the interview is the only thing on this bar
   that changes anything. */
.ivl-end {
    display: inline-flex;
    align-items: center;
    gap: 7px;
    height: 34px;
    padding: 0 16px;
    border: none;
    border-radius: var(--r-md);
    background: var(--gold-500);
    font-size: var(--fs-md);
    font-weight: var(--fw-bold);
    color: var(--p-900);
    white-space: nowrap;
    transition: background-color var(--dur-fast) var(--ease-out);
}

.ivl-end:hover { background: var(--gold-400); }

.ivl-btn:focus-visible,
.ivl-end:focus-visible,
.ivl-exit:focus-visible {
    outline: 2px solid var(--gold-300);
    outline-offset: 2px;
}

.ivl-prog {
    height: 3px;
    flex-shrink: 0;
    background: var(--n-150);
}

.ivl-prog-fill {
    height: 100%;
    background: var(--gold-500);
    transition: width 1s linear;
}

.ivl-prog-fill.over { background: var(--danger-500); }

@media (prefers-reduced-motion: reduce) {
    .ivl-prog-fill { transition: none; }
}

/* ---------- Body ---------- */

.ivl-body {
    flex: 1;
    display: grid;
    grid-template-columns: 300px minmax(0, 1.6fr) 340px;
    min-height: 0;
}

.ivl-rail {
    display: flex;
    flex-direction: column;
    padding: 16px;
    overflow-y: auto;
    background: var(--surface-raised);
    border-inline-end: 1px solid var(--border-subtle);
}

.ivl-rail-r {
    border-inline-end: none;
    border-inline-start: 1px solid var(--border-subtle);
}

.ivl-rail-h {
    margin-block-end: 12px;
    font-size: var(--fs-2xs);
    font-weight: var(--fw-bold);
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--text-tertiary);
}

.ivl-rail-h-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    margin-block-end: 4px;
}

.ivl-rough {
    margin: 0 0 12px;
    font-size: var(--fs-xs);
    line-height: 1.45;
    color: var(--text-tertiary);
}

/* ---------- Question plan ---------- */

/* Asked questions go green and their text goes tertiary: the plan is a list
   you work down, and what is left has to be the part that stands out. */
.ivl-q {
    display: flex;
    gap: 10px;
    padding: 10px 11px;
    border: 1px solid var(--border-subtle);
    border-radius: var(--r-md);
    background: var(--n-25);
    text-align: start;
    transition: border-color var(--dur-fast) var(--ease-out),
                background-color var(--dur-fast) var(--ease-out);
}

.ivl-q:hover { border-color: var(--border-strong); }

.ivl-q.on {
    border-color: var(--success-100);
    background: var(--success-50);
}

.ivl-q:focus-visible { outline: none; box-shadow: var(--shadow-focus); }

.ivl-q-tick {
    display: grid;
    place-items: center;
    width: 18px;
    height: 18px;
    flex-shrink: 0;
    margin-block-start: 1px;
    border: 1.5px solid var(--border-strong);
    border-radius: 50%;
    color: var(--n-0);
}

.ivl-q.on .ivl-q-tick {
    border-color: var(--success-600);
    background: var(--success-600);
}

.ivl-q-txt { min-width: 0; }

.ivl-q-t {
    display: block;
    font-size: var(--fs-sm);
    font-weight: var(--fw-medium);
    line-height: 1.4;
    color: var(--text-primary);
}

.ivl-q.on .ivl-q-t { color: var(--text-tertiary); }

.ivl-q-m {
    display: block;
    margin-block-start: 3px;
    font-family: var(--font-mono);
    font-size: var(--fs-2xs);
    color: var(--text-tertiary);
}

/* ---------- Notes ---------- */

.ivl-notes {
    display: flex;
    flex-direction: column;
    min-width: 0;
    background: var(--surface-page);
}

.ivl-notes-h {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 12px 18px;
    border-block-end: 1px solid var(--border-subtle);
}

.ivl-notes-t {
    font-size: var(--fs-sm);
    font-weight: var(--fw-semibold);
}

.ivl-auto { margin-inline-start: auto; }

.ivl-tags {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 6px;
    padding: 10px 18px;
    border-block-end: 1px solid var(--border-subtle);
}

.ivl-tags-l { margin-inline-end: 2px; }

.ivl-tag {
    padding: 4px 10px;
    border: 1px solid var(--p-100);
    border-radius: var(--r-full);
    background: var(--p-50);
    font-size: var(--fs-xs);
    font-weight: var(--fw-semibold);
    color: var(--p-700);
    white-space: nowrap;
    transition: background-color var(--dur-fast) var(--ease-out);
}

.ivl-tag:hover { background: var(--p-100); }

.ivl-tag-bad {
    border-color: var(--danger-100);
    background: var(--danger-50);
    color: var(--danger-700);
}

.ivl-tag-bad:hover { background: var(--danger-100); }

.ivl-tag-gold {
    border-color: var(--gold-200);
    background: var(--gold-50);
    color: var(--gold-700);
}

.ivl-tag-gold:hover { background: var(--gold-100); }

.ivl-tag:focus-visible { outline: none; box-shadow: var(--shadow-focus); }

/* Borderless and transparent: the notes area is the page, not a control on
   it, and a box drawn round the whole middle column would be a box drawn
   round nothing. */
.ivl-ta {
    flex: 1;
    padding: 18px;
    border: none;
    outline: none;
    resize: none;
    background: transparent;
    font: inherit;
    font-size: var(--fs-base);
    line-height: 1.65;
    color: var(--text-primary);
}

.ivl-ta::placeholder { color: var(--text-tertiary); }

/* ---------- Live rubric ---------- */

.ivl-crit {
    padding: 11px 12px;
    border: 1px solid var(--border-subtle);
    border-radius: var(--r-md);
    background: var(--n-25);
}

.ivl-crit-l {
    margin-block-end: 8px;
    font-size: var(--fs-sm);
    font-weight: var(--fw-semibold);
}

.ivl-crit-row { display: flex; gap: 5px; }

.ivl-sc {
    width: 30px;
    height: 30px;
    border: 1px solid var(--border-default);
    border-radius: var(--r-md);
    background: var(--surface-raised);
    font-family: var(--font-mono);
    font-size: var(--fs-md);
    font-weight: var(--fw-bold);
    color: var(--text-secondary);
    transition: border-color var(--dur-fast) var(--ease-out),
                background-color var(--dur-fast) var(--ease-out);
}

.ivl-sc:hover { border-color: var(--border-strong); }

.ivl-sc.on {
    border-color: var(--ivsc-c, var(--p-700));
    background: var(--ivsc-c, var(--p-700));
    color: var(--n-0);
}

.ivl-sc:focus-visible { outline: none; box-shadow: var(--shadow-focus); }

.ivl-grow { flex: 1; }

.ivl-finish { margin-block-start: 14px; }

/* ---------- Breakpoints ----------
   The rails go before the notes do. On a laptop in a meeting room the plan
   is worth more than the live rubric, so the right rail folds first. */

@media (max-width: 1200px) {
    .ivl-body { grid-template-columns: 280px minmax(0, 1fr); }
    .ivl-rail-r { display: none; }
}

@media (max-width: 900px) {
    /* Stacked, not hidden: the question plan is the reason the page exists,
       and a phone-width layout that drops it leaves the interviewer with a
       blank box and a clock. The notes keep a workable height of their own
       so the page still opens on something you can type into. */
    .ivl { height: auto; min-height: 100vh; }
    .ivl-body { grid-template-columns: minmax(0, 1fr); }
    .ivl-rail,
    .ivl-rail-r {
        border-inline: none;
        border-block-end: 1px solid var(--border-subtle);
        overflow: visible;
    }
    .ivl-rail-r { order: 3; }
    .ivl-notes { order: 2; min-height: 320px; }
    .ivl-who { display: none; }
    .ivl-t { font-size: var(--fs-2xl); }
    .ivl-bar { gap: 10px; padding-inline: 12px; }
}

/* ---------- Breakpoints ---------- */

@media (max-width: 1000px) {
    /* The countdown goes first: it is the one number the eyebrow already
       gives you in words. */
    .ivh-count { display: none; }
}

@media (max-width: 820px) {
    .ivw { grid-template-columns: repeat(2, minmax(0, 1fr)); }
    .ivw-day { min-height: 0; border-block-end: 1px solid var(--border-subtle); }
}

@media (max-width: 640px) {
    .ivh-in { padding: 16px; }
    .ivh-name { font-size: var(--fs-2xl); }
    .ivh-go,
    .ivh-2nd { flex: 1 1 auto; justify-content: center; }
    .ivh-spacer { display: none; }
    .ivw { grid-template-columns: minmax(0, 1fr); }
    .ivs-when { width: 54px; }
}


/* ============================================================
   15. External portal shell (pshell-*)

   The client and interviewer portals each carry their own chrome rather than
   AppShell, and for a good reason: AppShell is driven by the menu registry
   and RBAC, which exist to describe an agency's staff. A client contact and
   an interviewer are not staff, have no role assignments, and get a fixed
   nav whose narrowness is the point.

   What was not a good reason is that they were two copies of the same file.
   Same 236px rail, same 56px brand row, same active-row treatment, same
   account footer - written twice, in inline styles, and already drifting:
   one had a group label above its nav and the other did not, and the two
   sidebars sat 2px apart at the bottom.

   One vocabulary, two callers. The recruiter sidebar (section 4) stays where
   it is - it is a different object, with rail collapse, per-row hues and a
   search field, and folding these into it would mean carrying all of that
   for a five-item nav.
   ============================================================ */

.pshell {
    display: flex;
    height: 100vh;
    background: var(--surface-page);
    color: var(--text-primary);
}

/* ---------- Sidebar ---------- */

.pshell-side {
    display: flex;
    flex-direction: column;
    width: 236px;
    flex-shrink: 0;
    background: var(--surface-raised);
    border-inline-end: 1px solid var(--border-subtle);
}

.pshell-brand {
    display: flex;
    align-items: center;
    gap: 10px;
    height: 56px;
    flex-shrink: 0;
    padding: 0 16px;
    border-block-end: 1px solid var(--border-subtle);
}

/* The same mark the recruiter sidebar uses: a navy tile, a gold J. It is the
   product introducing itself, and it should be the same product. */
.pshell-mark {
    display: grid;
    place-items: center;
    width: 28px;
    height: 28px;
    flex-shrink: 0;
    border-radius: var(--r-md);
    background: linear-gradient(135deg, var(--p-700), var(--p-900));
    font-family: var(--font-mono);
    font-size: var(--fs-md);
    font-weight: var(--fw-bold);
    color: var(--gold-300);
}

.pshell-brand-txt {
    display: flex;
    flex-direction: column;
    line-height: 1.1;
    min-width: 0;
}

.pshell-brand-n {
    font-size: var(--fs-lg);
    font-weight: var(--fw-bold);
    letter-spacing: -0.01em;
}

/* Which portal this is. The one word that tells a client they are not
   looking at the recruiter's screen. */
.pshell-brand-p {
    font-size: 10px;
    font-weight: var(--fw-semibold);
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--text-tertiary);
}

.pshell-nav {
    flex: 1;
    padding: 8px;
    overflow-y: auto;
}

.pshell-group { margin-block-end: 12px; }

.pshell-group-l {
    padding: 6px 10px 4px;
    font-size: var(--fs-2xs);
    font-weight: var(--fw-semibold);
    letter-spacing: 0.05em;
    text-transform: uppercase;
    color: var(--text-tertiary);
}

.pshell-item {
    position: relative;
    display: flex;
    align-items: center;
    gap: 10px;
    width: 100%;
    height: 32px;
    margin-block-end: 2px;
    padding: 0 10px;
    border-radius: var(--r-md);
    font-size: var(--fs-md);
    font-weight: var(--fw-medium);
    color: var(--text-secondary);
    text-decoration: none;
    transition: background-color var(--dur-fast) var(--ease-out),
                color var(--dur-fast) var(--ease-out);
}

.pshell-item:hover { background: var(--surface-hover); color: var(--text-primary); }

.pshell-item.on {
    background: var(--p-50);
    font-weight: var(--fw-semibold);
    color: var(--p-700);
}

/* The marker on the rail's edge, not a border on the row: a border would
   move the label by its own width when the row became active. */
.pshell-item.on::before {
    content: '';
    position: absolute;
    inset-block: 6px;
    inset-inline-start: 0;
    width: 2px;
    border-radius: 2px;
    background: var(--p-700);
}

.pshell-item:focus-visible { outline: none; box-shadow: var(--shadow-focus); }

.pshell-item-l { flex: 1; min-width: 0; }

/* A count that is a debt, not a total - amber, because the only badge in
   this nav is "you owe somebody a scorecard". */
.pshell-badge {
    padding: 1px 6px;
    border-radius: var(--r-full);
    background: var(--warning-50);
    font-size: var(--fs-2xs);
    font-weight: var(--fw-bold);
    font-variant-numeric: tabular-nums;
    color: var(--warning-700);
}

/* ---------- Scope notice ----------
   Both portals show somebody a slice of an agency's data, and both say so
   plainly at the bottom of the rail. A person who can see less than they
   expect should be told that by the product, not left to wonder. */
.pshell-scope {
    display: flex;
    gap: 8px;
    margin: 0 10px 8px;
    padding: 8px 10px;
    border: 1px solid var(--border-subtle);
    border-radius: var(--r-md);
    background: var(--n-50);
    font-size: var(--fs-2xs);
    line-height: 1.4;
    color: var(--text-tertiary);
}

.pshell-scope > svg { flex-shrink: 0; margin-block-start: 1px; }

/* ---------- Account footer ---------- */

.pshell-acct {
    position: relative;
    border-block-start: 1px solid var(--border-subtle);
}

.pshell-acct-btn {
    display: flex;
    align-items: center;
    gap: 10px;
    width: 100%;
    padding: 10px;
    background: transparent;
    text-align: start;
    transition: background-color var(--dur-fast) var(--ease-out);
}

.pshell-acct-btn:hover,
.pshell-acct-btn.on { background: var(--surface-hover); }

.pshell-acct-btn:focus-visible { outline: none; box-shadow: inset var(--shadow-focus); }

.pshell-acct-txt { flex: 1; min-width: 0; }

.pshell-acct-n {
    font-size: var(--fs-sm);
    font-weight: var(--fw-semibold);
}

.pshell-acct-s {
    font-size: var(--fs-2xs);
    color: var(--text-tertiary);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.pshell-acct-more {
    display: grid;
    place-items: center;
    width: 24px;
    height: 24px;
    flex-shrink: 0;
    color: var(--text-tertiary);
}

/* A full-viewport catcher under the menu, so a click anywhere closes it. */
.pshell-scrim {
    position: fixed;
    inset: 0;
    z-index: 90;
}

.pshell-menu {
    position: absolute;
    inset-block-end: calc(100% - 4px);
    inset-inline: 10px;
    z-index: 91;
    padding: 6px;
    border: 1px solid var(--border-default);
    border-radius: var(--r-lg);
    background: var(--surface-raised);
    box-shadow: var(--shadow-lg);
}

.pshell-menu-i {
    display: flex;
    align-items: center;
    gap: 10px;
    width: 100%;
    padding: 8px 10px;
    border-radius: var(--r-sm);
    background: none;
    font-size: var(--fs-md);
    font-weight: var(--fw-medium);
    color: inherit;
    text-align: start;
    text-decoration: none;
    transition: background-color var(--dur-fast) var(--ease-out);
}

.pshell-menu-i:hover { background: var(--surface-hover); }

.pshell-menu-i:focus-visible { outline: none; box-shadow: var(--shadow-focus); }

.pshell-menu-i > svg { color: var(--text-secondary); }

.pshell-menu-i.is-danger { color: var(--danger-600); }

.pshell-menu-i.is-danger > svg { color: currentColor; }

.pshell-menu-sep {
    height: 1px;
    margin: 4px 6px;
    background: var(--border-subtle);
}

/* ---------- Main column ---------- */

.pshell-main {
    flex: 1;
    display: flex;
    flex-direction: column;
    min-width: 0;
}

.pshell-top {
    display: flex;
    align-items: center;
    gap: 16px;
    height: 56px;
    flex-shrink: 0;
    padding: 0 24px;
    background: var(--surface-raised);
    border-block-end: 1px solid var(--border-subtle);
    /* The containing block the language switcher and the bell fall back to on
       a phone, where .tb-pop-anchor goes static and the panel spans the header
       instead of hanging off a 32px button (section 4b). No z-index with it:
       the account menu in the sidebar row above is z 91 in the root stacking
       context, and a stacking context here would bury it. */
    position: relative;
}

.pshell-crumbs {
    display: flex;
    align-items: center;
    gap: 6px;
    flex: 1;
    min-width: 0;
    /* The trail is nowrap by design - a breadcrumb that wraps mid-name is
       worse than one that is cut - so without this it simply ran out of the
       strip and under the controls on the right. Measured: 50px past at
       900px wide, 78px at 620px, because the full three-crumb trail is shown
       above 600px and no part of it could shrink. */
    overflow: hidden;
    color: var(--text-tertiary);
}

/* The chevrons hold their size; only names give way. */
.pshell-crumbs > svg { flex-shrink: 0; }

.pshell-crumb {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    font-size: var(--fs-md);
    font-weight: var(--fw-medium);
    color: var(--text-secondary);
    white-space: nowrap;
}

/* The page you are on is the one worth reading in full, so the ancestors
   above it are what the strip spends its shrinking on first - three times as
   fast, rather than every name being clipped equally. */
.pshell-crumb { flex: 0 3 auto; }
.pshell-crumb.is-last { flex: 0 1 auto; }

.pshell-crumb.is-last {
    font-weight: var(--fw-semibold);
    color: var(--text-primary);
}

.pshell-top-r {
    display: flex;
    align-items: center;
    gap: 6px;
}

/* The interviewer's own candidate rating, in the topbar. Gold because it is
   the one number on the screen that is about them rather than about the
   work. */
.pshell-score {
    display: flex;
    align-items: center;
    gap: 6px;
    padding: 4px 10px 4px 4px;
    border: 1px solid var(--gold-200);
    border-radius: var(--r-full);
    background: var(--gold-50);
}

.pshell-score-v {
    display: grid;
    place-items: center;
    width: 22px;
    height: 22px;
    border-radius: 50%;
    background: var(--gold-500);
    font-size: var(--fs-2xs);
    font-weight: var(--fw-bold);
    font-variant-numeric: tabular-nums;
    color: var(--p-900);
}

.pshell-score-l {
    font-size: var(--fs-xs);
    font-weight: var(--fw-semibold);
    color: var(--p-900);
}

/* The gutter the pinned page header cancels. Declared here rather than
   inline on every shell so the two cannot drift. */
.pshell-body {
    flex: 1;
    min-height: 0;
    overflow: auto;
    padding: 24px 32px;
    --pg-gx: 32px;
    --pg-gy: 24px;
}

/* ---------- Breakpoints ----------
   The rail goes at tablet width. These portals have no rail-collapse state
   of their own - a five-item nav does not earn one - so below this the nav
   moves under the topbar as a scrolling row of the same rows. */
@media (max-width: 860px) {
    .pshell { height: auto; min-height: 100vh; flex-direction: column; }

    .pshell-side {
        width: 100%;
        flex-direction: row;
        align-items: center;
        gap: 8px;
        border-inline-end: none;
        border-block-end: 1px solid var(--border-subtle);
    }

    .pshell-brand { border-block-end: none; }

    .pshell-nav {
        display: flex;
        gap: 4px;
        padding: 8px 0;
        overflow-x: auto;
        scrollbar-width: none;
    }

    .pshell-nav::-webkit-scrollbar { display: none; }

    .pshell-group { display: flex; gap: 4px; margin-block-end: 0; }

    /* The group names are a rail's structure; in a scrolling row they are
       four extra things to scroll past. */
    .pshell-group-l { display: none; }

    .pshell-item { width: auto; margin-block-end: 0; white-space: nowrap; }

    .pshell-item.on::before { display: none; }

    /* The scope notice is a paragraph; in a toolbar it is a wall. It is
       repeated on the pages that need it. */
    .pshell-scope { display: none; }

    .pshell-acct { margin-inline-start: auto; border-block-start: none; }

    .pshell-acct-txt { display: none; }

    /* The rail has become the row across the top of the page, so the account
       menu can no longer open upwards - above this row is the browser chrome.
       It was opening off the top of the screen entirely. */
    .pshell-menu {
        inset-block: calc(100% + 6px) auto;
        inset-inline: auto 10px;
        width: 220px;
    }

    .pshell-body { padding: 16px; --pg-gx: 16px; --pg-gy: 16px; }

    .pshell-top { padding-inline: 16px; }
}

@media (max-width: 600px) {
    .pshell-score-l { display: none; }
    .pshell-score { padding: 4px; }
    .pshell-crumb:not(.is-last) { display: none; }
    .pshell-crumbs > svg { display: none; }
}


/* ============================================================
   16. Public pages

   Five pages seen by people who have no Jork account and did not ask to be
   here: a referee verifying employment, a hiring manager commenting on a
   candidate, an HR contact claiming a view, somebody unsubscribing, and
   whoever a candidate sent their one-pager to.

   They were the furthest-drifted screens in the product - written entirely
   in raw hex (#1a1a1a, #666, #dde3e9, #f6f8fa), with a black submit button
   and two emoji standing in for status icons. None of that is the palette,
   which meant the first thing a stranger ever saw of Jork looked like a
   different company's form.

   The shape is deliberately plain: one centred card, one column, no nav.
   There is nowhere else to go from these pages, and chrome offering places
   to go would be chrome that lies.
   ============================================================ */

.pub {
    min-height: 100vh;
    padding: 32px 16px;
    background: var(--surface-page);
}

/* Centred and capped. 640px is about 80 characters at this size, which is
   where a paragraph stops being comfortable to read. */
.pub-in {
    width: 100%;
    max-width: 640px;
    margin-inline: auto;
}

.pub-in-wide { max-width: 760px; }
.pub-in-narrow { max-width: 460px; }

/* The brand, once, above the card. It is the only thing on these pages that
   says who is asking. */
.pub-brand {
    display: flex;
    align-items: center;
    gap: 10px;
    margin-block-end: 24px;
}

.pub-brand-n {
    font-size: var(--fs-3xl);
    font-weight: var(--fw-bold);
    letter-spacing: -0.02em;
    color: var(--text-primary);
}

/* The one card. A hairline and a small shadow rather than the heavier
   surround these pages used - the card is the page, and a page does not need
   to be lifted off itself. */
.pub-card {
    background: var(--surface-raised);
    border: 1px solid var(--border-default);
    border-radius: var(--r-xl);
    box-shadow: var(--shadow-sm);
}

.pub-pad { padding: 28px; }

/* A card whose whole job is one sentence: expired, already answered, thank
   you. Centred, because there is nothing else on it to align to. */
.pub-msg {
    padding: 40px 28px;
    text-align: center;
}

.pub-msg-mark {
    display: grid;
    place-items: center;
    width: 52px;
    height: 52px;
    margin: 0 auto 14px;
    border-radius: var(--r-2xl);
    background: var(--surface-sunken);
    color: var(--text-tertiary);
}

/* Three tones for the three answers these pages give. Emoji were doing this
   job, and an emoji renders as a different picture on every platform - which
   is not what a status marker can be. */
.pub-msg-mark-good { background: var(--success-50);  color: var(--success-600); }
.pub-msg-mark-warn { background: var(--warning-50);  color: var(--warning-600); }
.pub-msg-mark-bad  { background: var(--danger-50);   color: var(--danger-600); }

.pub-h1 {
    margin: 0 0 6px;
    font-size: var(--fs-2xl);
    font-weight: var(--fw-bold);
    letter-spacing: -0.015em;
    color: var(--text-primary);
}

.pub-h1-lg {
    font-size: var(--fs-3xl);
    letter-spacing: -0.02em;
}

.pub-p {
    margin: 0;
    font-size: var(--fs-md);
    line-height: 1.6;
    color: var(--text-secondary);
}

.pub-p + .pub-p,
.pub-h1 + .pub-p { margin-block-start: 0; }

/* The gap before an action. Stated once so the five pages do not each pick
   their own. */
.pub-gap { margin-block-start: 18px; }

.pub-eyebrow {
    margin-block-end: 6px;
    font-size: var(--fs-xs);
    font-weight: var(--fw-semibold);
    letter-spacing: 0.05em;
    text-transform: uppercase;
    color: var(--text-tertiary);
}

.pub-sub {
    font-size: var(--fs-md);
    color: var(--text-secondary);
}

.pub-fine {
    margin: 14px 0 0;
    font-size: var(--fs-xs);
    line-height: 1.5;
    color: var(--text-tertiary);
}

/* A section of the one-pager: a caps label and its content, ruled off from
   the next. */
.pub-sec {
    padding: 22px 30px;
    border-block-start: 1px solid var(--border-subtle);
}

.pub-sec:first-child { border-block-start: none; }

.pub-sec-l {
    margin-block-end: 8px;
    font-size: var(--fs-2xs);
    font-weight: var(--fw-bold);
    letter-spacing: 0.07em;
    text-transform: uppercase;
    color: var(--text-tertiary);
}

/* white-space: pre-wrap - a bio and a comment were typed with line breaks,
   and collapsing them turns a considered note into a block. */
.pub-prose {
    font-size: var(--fs-md);
    line-height: 1.65;
    color: var(--text-primary);
    white-space: pre-wrap;
}

.pub-head {
    padding: 28px 30px;
    border-block-end: 1px solid var(--border-subtle);
}

.pub-facts {
    display: flex;
    flex-wrap: wrap;
    gap: 14px;
    margin-block-start: 8px;
    font-size: var(--fs-sm);
    color: var(--text-tertiary);
}

/* An employment line the product itself checked. Green, and a word rather
   than a tick, because "verified" is a claim and it should be readable. */
.pub-verified {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    margin-inline-start: 6px;
    padding: 1px 7px;
    border-radius: var(--r-full);
    background: var(--success-50);
    font-size: var(--fs-2xs);
    font-weight: var(--fw-bold);
    color: var(--success-700);
    vertical-align: middle;
}

/* ---------- The referee form ----------
   The longest public page, and the only one that asks for anything. Its
   fields are the shared .fld-* stack so it matches every form inside the
   product. */

.pub-facts-card {
    padding: 14px 16px;
    margin-block-end: 20px;
    border-radius: var(--r-lg);
    background: var(--surface-sunken);
}

.pub-facts-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
    gap: 12px;
}

.pub-fact-l {
    font-size: var(--fs-2xs);
    font-weight: var(--fw-semibold);
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--text-tertiary);
}

.pub-fact-v {
    margin-block-start: 1px;
    font-size: var(--fs-md);
    font-weight: var(--fw-semibold);
    color: var(--text-primary);
}

.pub-field { margin-block-end: 20px; }

.pub-field-sep {
    padding-block-start: 16px;
    border-block-start: 1px solid var(--border-subtle);
}

/* A row of radios reading as one question. Inline-flex so the control and
   its word are one target. */
.pub-radios {
    display: flex;
    flex-wrap: wrap;
    gap: 6px 24px;
    margin-block-start: 8px;
}

.pub-radio {
    display: inline-flex;
    align-items: center;
    gap: 7px;
    font-size: var(--fs-md);
    color: var(--text-primary);
    cursor: pointer;
}

/* 1-5, as five buttons rather than stars: the referee is being asked for a
   rating on a scale, and stars invite a feeling. */
.pub-scale {
    display: flex;
    gap: 8px;
    margin-block-start: 8px;
}

.pub-scale-b {
    width: 40px;
    height: 40px;
    border: 1px solid var(--border-default);
    border-radius: var(--r-md);
    background: var(--surface-raised);
    font-family: var(--font-mono);
    font-size: var(--fs-lg);
    font-weight: var(--fw-semibold);
    color: var(--text-secondary);
    transition: border-color var(--dur-fast) var(--ease-out),
                background-color var(--dur-fast) var(--ease-out);
}

.pub-scale-b:hover { border-color: var(--border-strong); }

.pub-scale-b.on {
    border-color: var(--p-700);
    background: var(--p-700);
    color: var(--text-inverse);
}

.pub-scale-b:focus-visible { outline: none; box-shadow: var(--shadow-focus); }

/* ---------- Hiring manager comments ---------- */

/* A quoted note, marked by a rule down its edge rather than a box: several
   of them stacked as boxes would read as a form. */
.pub-note {
    padding: 2px 0 2px 12px;
    margin-block-end: 12px;
    border-inline-start: 2px solid var(--border-default);
}

.pub-note-by {
    font-size: var(--fs-xs);
    color: var(--text-tertiary);
}

.pub-note-t {
    font-size: var(--fs-md);
    line-height: 1.6;
    color: var(--text-primary);
    white-space: pre-wrap;
}

/* ---------- Loading ---------- */

.pub-wait {
    padding: 60px 24px;
    text-align: center;
    font-size: var(--fs-md);
    color: var(--text-tertiary);
}

/* A spinner, in the palette. It was a black-on-grey ring with its keyframes
   declared inside the markup. */
.pub-spin {
    display: inline-block;
    width: 32px;
    height: 32px;
    border: 3px solid var(--border-default);
    border-block-start-color: var(--p-700);
    border-radius: 50%;
    animation: pub-spin 0.8s linear infinite;
}

@keyframes pub-spin {
    to { transform: rotate(360deg); }
}

@media (prefers-reduced-motion: reduce) {
    .pub-spin { animation-duration: 2.4s; }
}

@media (max-width: 600px) {
    .pub { padding: 20px 12px; }
    .pub-pad,
    .pub-msg { padding: 20px; }
    .pub-head,
    .pub-sec { padding: 18px 20px; }
}

/* Small pieces the five pages needed beyond the shared shape. */

.pub-h2 {
    margin: 0 0 4px;
    font-size: var(--fs-lg);
    font-weight: var(--fw-semibold);
    color: var(--text-primary);
}

.pub-lede { margin-block-end: 16px; }

.pub-stack { margin-block-end: 16px; }

.pub-notes { margin-block-end: 18px; }

.pub-err { margin-block-end: 12px; }

.pub-ta { min-height: 96px; line-height: 1.6; }

.pub-by { text-align: center; }


/* ============================================================
   17. Candidate portal (cnp-*)

   The candidate's own screens. Prefix is cnp- because the two neighbours are
   already taken and a letter apart on purpose: cand- is the candidate shell
   (section 5), cnd- is the recruiter's list of candidates (section 6d).

   This is the one portal whose reader is not at work. The hero exists for
   that reason - a candidate opening this page wants to know where they stand
   before they want a table - and it is the only place in the product that
   greets someone by name.
   ============================================================ */

/* ---------- Hero ---------- */

.cnp-hero {
    position: relative;
    overflow: hidden;
    margin-block-end: 18px;
    padding: 24px 28px;
    border-radius: var(--r-xl);
    background: linear-gradient(135deg, var(--p-900) 0%, var(--p-800) 60%, var(--p-700) 100%);
    color: var(--n-0);
}

/* Two blooms, gold from the top right and blue from the bottom left, so the
   gradient does not read as a flat band. Decorative and inert. */
.cnp-hero::before,
.cnp-hero::after {
    content: '';
    position: absolute;
    border-radius: 50%;
    pointer-events: none;
}

.cnp-hero::before {
    inset-block-start: -60px;
    inset-inline-end: -60px;
    width: 260px;
    height: 260px;
    background: radial-gradient(circle, rgba(255, 200, 80, 0.15) 0%, transparent 70%);
}

.cnp-hero::after {
    inset-block-end: -80px;
    inset-inline-start: -40px;
    width: 300px;
    height: 300px;
    background: radial-gradient(circle, rgba(100, 120, 255, 0.18) 0%, transparent 70%);
}

.cnp-hero-in {
    position: relative;
    display: flex;
    align-items: center;
    gap: 32px;
}

.cnp-hero-txt { flex: 1; min-width: 0; }

.cnp-hero-hi {
    margin-block-end: 6px;
    font-size: var(--fs-xs);
    font-weight: var(--fw-semibold);
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--gold-300);
}

.cnp-hero-h1 {
    margin: 0 0 8px;
    font-size: var(--fs-4xl);
    font-weight: var(--fw-bold);
    letter-spacing: -0.02em;
    line-height: 1.2;
}

.cnp-hero-p {
    max-width: 560px;
    margin: 0 0 18px;
    font-size: var(--fs-base);
    line-height: 1.5;
    color: rgba(255, 255, 255, 0.8);
}

.cnp-hero-acts {
    display: flex;
    flex-wrap: wrap;
    gap: 10px;
}

/* The gold one is whatever the candidate should do next - review an offer if
   they have one, finish their profile if they do not. Only ever one. */
.cnp-cta {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    height: 34px;
    padding: 0 16px;
    border-radius: var(--r-md);
    background: var(--gold-500);
    font-size: var(--fs-md);
    font-weight: var(--fw-semibold);
    color: var(--p-900);
    text-decoration: none;
    transition: background-color var(--dur-fast) var(--ease-out);
}

.cnp-cta:hover { background: var(--gold-400); }

.cnp-cta-2 {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    height: 34px;
    padding: 0 16px;
    border: 1px solid rgba(255, 255, 255, 0.3);
    border-radius: var(--r-md);
    background: rgba(255, 255, 255, 0.1);
    font-size: var(--fs-md);
    font-weight: var(--fw-medium);
    color: var(--n-0);
    text-decoration: none;
    transition: background-color var(--dur-fast) var(--ease-out);
}

.cnp-cta-2:hover { background: rgba(255, 255, 255, 0.18); }

.cnp-cta:focus-visible,
.cnp-cta-2:focus-visible {
    outline: 2px solid var(--gold-300);
    outline-offset: 2px;
}

/* The three counts. They wrap onto their own line rather than shrinking,
   because the hero clips - a third tile that does not fit was not narrow, it
   was cut off with no way to reach it. */
.cnp-hero-stats {
    display: flex;
    gap: 10px;
    flex-shrink: 0;
}

.cnp-stat {
    min-width: 90px;
    padding: 14px 18px;
    border: 1px solid rgba(255, 255, 255, 0.2);
    border-radius: var(--r-lg);
    background: rgba(255, 255, 255, 0.1);
    text-align: center;
    backdrop-filter: blur(8px);
}

.cnp-stat-v {
    font-family: var(--font-mono);
    font-variant-numeric: tabular-nums;
    font-size: var(--fs-4xl);
    font-weight: var(--fw-bold);
    line-height: 1;
}

.cnp-stat-l {
    margin-block-start: 4px;
    font-size: var(--fs-xs);
    line-height: 1.3;
    color: rgba(255, 255, 255, 0.7);
}

/* ---------- Company mark ----------
   A round tile in the company's own colour with its initials. Not a logo:
   the product does not hold logos for most employers, and a grey placeholder
   on every row tells the candidate nothing. */
.cnp-logo {
    display: grid;
    place-items: center;
    width: 40px;
    height: 40px;
    flex-shrink: 0;
    border-radius: 50%;
    background: var(--cnp-logo-c, var(--n-400));
    font-family: var(--font-mono);
    font-size: var(--fs-md);
    font-weight: var(--fw-bold);
    color: var(--n-0);
}

/* ---------- Application row ---------- */

.cnp-row {
    display: flex;
    align-items: center;
    gap: 14px;
    padding: 12px 0;
    border-block-start: 1px solid var(--border-subtle);
}

.cnp-row:first-of-type { border-block-start: none; }

.cnp-row-main { flex: 1; min-width: 0; }

.cnp-row-top {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    margin-block-end: 2px;
}

.cnp-role {
    font-size: var(--fs-md);
    font-weight: var(--fw-semibold);
}

.cnp-org {
    margin-inline-start: 6px;
    font-size: var(--fs-sm);
    color: var(--text-secondary);
}

/* Five segments, filled to where the application has got to. A bar rather
   than a stage name alone: "Screening" does not say how far that is. */
.cnp-stages {
    display: flex;
    gap: 3px;
    margin-block-start: 6px;
}

.cnp-stage {
    flex: 1;
    height: 4px;
    border-radius: 2px;
    background: var(--n-100);
}

.cnp-stage.on { background: var(--p-700); }

/* ---------- Match ring ----------
   The score as a ring rather than a number alone: a candidate comparing three
   jobs reads the arcs, not the digits. The conic angle comes in as a custom
   property so the markup carries one number instead of a whole gradient. */
.cnp-ring {
    display: grid;
    place-items: center;
    width: 44px;
    height: 44px;
    border-radius: 50%;
    background: conic-gradient(var(--cnp-ring-c, var(--p-700)) var(--cnp-ring-deg, 0deg), var(--n-100) 0);
}

.cnp-ring-in {
    display: grid;
    place-items: center;
    width: 36px;
    height: 36px;
    border-radius: 50%;
    background: var(--surface-raised);
    font-family: var(--font-mono);
    font-variant-numeric: tabular-nums;
    font-size: var(--fs-sm);
    font-weight: var(--fw-semibold);
    color: var(--cnp-ring-c, var(--p-700));
}

.cnp-end {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 8px;
    flex-shrink: 0;
}

/* ---------- Job meta ---------- */

.cnp-jobline {
    display: flex;
    align-items: center;
    gap: 6px;
    flex-wrap: wrap;
    margin-block-end: 2px;
}

.cnp-meta {
    display: flex;
    align-items: center;
    gap: 6px;
    margin-block-end: 5px;
    font-size: var(--fs-xs);
    color: var(--text-tertiary);
}

.cnp-tags {
    display: flex;
    flex-wrap: wrap;
    gap: 4px;
}

.cnp-tag {
    padding: 1px 7px;
    border-radius: var(--r-full);
    background: var(--n-100);
    font-size: var(--fs-2xs);
    font-weight: var(--fw-medium);
    color: var(--text-secondary);
}

.cnp-apply {
    display: inline-flex;
    align-items: center;
    height: 26px;
    padding: 0 10px;
    border-radius: var(--r-md);
    background: var(--p-700);
    font-size: var(--fs-xs);
    font-weight: var(--fw-semibold);
    color: var(--n-0);
    text-decoration: none;
    transition: background-color var(--dur-fast) var(--ease-out);
}

.cnp-apply:hover { background: var(--p-800); }

/* ---------- Next steps ----------
   The gaps in this candidate's own profile. Gold, because it is the one
   card on the page asking them to do something rather than telling them
   where things stand. */
.cnp-gap {
    display: block;
    padding: 12px 14px;
    border: 1px solid var(--gold-200);
    border-radius: var(--r-md);
    background: var(--gold-50);
    text-decoration: none;
    transition: border-color var(--dur-fast) var(--ease-out);
}

.cnp-gap:hover { border-color: var(--gold-300); }

.cnp-gap-t {
    margin-block-end: 3px;
    font-size: var(--fs-sm);
    font-weight: var(--fw-semibold);
    color: var(--gold-700);
}

.cnp-gap-s {
    font-size: var(--fs-sm);
    line-height: 1.5;
    color: var(--text-secondary);
}

.cnp-cardhead {
    display: flex;
    align-items: center;
    gap: 8px;
    margin-block-end: 14px;
}

.cnp-cardhead-t {
    font-size: var(--fs-md);
    font-weight: var(--fw-semibold);
}

/* ---------- Deadlines ---------- */

.cnp-due {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 9px 0;
    border-block-start: 1px solid var(--border-subtle);
}

.cnp-due:first-of-type { border-block-start: none; }

.cnp-due-dot {
    width: 6px;
    height: 6px;
    flex-shrink: 0;
    border-radius: 50%;
    background: var(--warning-600);
}

.cnp-due-t {
    font-size: var(--fs-sm);
    font-weight: var(--fw-medium);
}

/* ---------- Profile strength ---------- */

.cnp-strength { margin-block-end: 14px; }

/* Navy into gold: the bar fills toward the brand's accent, so a complete
   profile ends on the colour the product uses for "good". */
.cnp-strength-track {
    height: 6px;
    margin-block-end: 4px;
    border-radius: var(--r-full);
    background: var(--n-100);
    overflow: hidden;
}

.cnp-strength-fill {
    height: 100%;
    border-radius: var(--r-full);
    background: linear-gradient(90deg, var(--p-700), var(--gold-500));
    transition: width var(--dur-mid) var(--ease-out);
}

.cnp-checks {
    display: flex;
    flex-direction: column;
    gap: 5px;
}

.cnp-check {
    display: flex;
    align-items: center;
    gap: 8px;
    font-size: var(--fs-sm);
    color: var(--text-tertiary);
}

.cnp-check.on { color: var(--text-primary); }

.cnp-check > svg { flex-shrink: 0; color: var(--n-300); }

.cnp-check.on > svg { color: var(--success-600); }

/* ---------- Earnings ---------- */

.cnp-earn-v {
    margin-block-end: 4px;
    font-family: var(--font-mono);
    font-variant-numeric: tabular-nums;
    font-size: var(--fs-3xl);
    font-weight: var(--fw-semibold);
    color: var(--gold-600);
}

.cnp-earn-s {
    margin-block-end: 14px;
    font-size: var(--fs-sm);
    color: var(--text-secondary);
}

.cnp-earn-link {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 6px;
    width: 100%;
    height: 32px;
    border: 1px solid var(--gold-300);
    border-radius: var(--r-md);
    background: var(--gold-50);
    font-size: var(--fs-sm);
    font-weight: var(--fw-semibold);
    color: var(--gold-700);
    text-decoration: none;
    transition: background-color var(--dur-fast) var(--ease-out);
}

.cnp-earn-link:hover { background: var(--gold-100); }

/* A quiet "view all" beside a card's title. */
.cnp-more {
    font-size: var(--fs-sm);
    font-weight: var(--fw-medium);
    color: var(--p-700);
    text-decoration: none;
}

.cnp-more:hover { text-decoration: underline; }

/* ---------- Breakpoints ---------- */

@media (max-width: 900px) {
    .cnp-hero-in { flex-wrap: wrap; gap: 20px; }
    .cnp-hero-stats { width: 100%; }
    .cnp-stat { flex: 1; min-width: 0; }
}

@media (max-width: 600px) {
    .cnp-hero { padding: 18px; }
    .cnp-hero-h1 { font-size: var(--fs-3xl); }
    .cnp-hero-acts > * { flex: 1 1 auto; justify-content: center; }
    .cnp-row { flex-wrap: wrap; }
}


/* ============================================================
   Status pages (st-)

   The screen behind /status/{code} and /Error: a mistyped URL, a link out of
   a two-year-old email, a bookmark to a job that has been closed, or a page
   belonging to a portal the signed-in account is not in.

   Every one of those used to render an unstyled <h3>Not Found</h3> - the only
   screen in the product with no palette on it at all, and the one a stranger
   is most likely to hit first.

   Built as a signpost, not an apology, and built to hold the whole viewport:
   a tinted wash, the status number set large behind the panel, the brand and
   the status chip on a top rail, the correlation id on a footer rail. The
   panel used to be a 456px card floating in the top third of an otherwise
   empty page, which reads as an unfinished screen rather than a deliberate
   one.

   Three tones carry the meaning before a word is read - brand for "dead end",
   gold for "wall", red for "broken" - and every tinted value is driven off the
   --st-* custom properties set by .st-t-*, so a fourth tone is one rule.
   ============================================================ */

.st {
    --st-accent: var(--p-700);
    --st-wash-a: rgba(30, 58, 138, 0.10);
    --st-wash-b: rgba(59, 130, 246, 0.09);
    --st-ghost: rgba(30, 58, 138, 0.06);

    position: relative;
    isolation: isolate;
    overflow: hidden;
    min-height: 100vh;
    min-height: 100dvh;
    display: grid;
    grid-template-rows: auto 1fr auto;
    gap: var(--sp-8);
    padding: var(--sp-10) var(--sp-10) var(--sp-12);
    background: var(--surface-page);
}

.st-t-warn {
    --st-accent: var(--gold-700);
    --st-wash-a: rgba(245, 184, 0, 0.14);
    --st-wash-b: rgba(30, 58, 138, 0.07);
    --st-ghost: rgba(176, 121, 0, 0.07);
}

.st-t-danger {
    --st-accent: var(--danger-700);
    --st-wash-a: rgba(239, 68, 68, 0.12);
    --st-wash-b: rgba(30, 58, 138, 0.07);
    --st-ghost: rgba(185, 28, 28, 0.06);
}

/* The wash. Two off-centre radials rather than one linear gradient, which
   banded visibly across a 1440px page at these opacities. */
.st::before {
    content: '';
    position: absolute;
    z-index: -2;
    inset-block-start: 0;
    inset-inline: 0;
    height: min(720px, 82vh);
    background:
        radial-gradient(58% 62% at 16% 0%, var(--st-wash-a) 0%, transparent 64%),
        radial-gradient(52% 58% at 86% 8%, var(--st-wash-b) 0%, transparent 62%);
    pointer-events: none;
}

/* The status number, set enormous and nearly transparent behind the panel.
   It is what makes the page read as composed at 1440px instead of as a small
   card on a blank field, and it is aria-hidden in the markup because the same
   digits are already in the chip and in the document title. */
.st-ghost {
    position: absolute;
    z-index: -1;
    inset-block-start: 50%;
    inset-inline-start: 50%;
    transform: translate(-50%, -52%);
    font-family: var(--font-display);
    /* Wide enough that the outer digits clear the 620px panel on a laptop -
       at 33vw the panel covered everything but two unreadable slivers, which
       looked like a rendering fault rather than a number. */
    font-size: clamp(300px, 46vw, 620px);
    font-weight: var(--fw-bold);
    letter-spacing: -0.06em;
    line-height: 1;
    color: var(--st-ghost);
    user-select: none;
    pointer-events: none;
}

/* ---------- Top rail ---------- */

.st-top {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--sp-8);
}

/* The brand, and the only link on the page that always works. */
.st-brand {
    font-size: var(--fs-2xl);
    font-weight: var(--fw-bold);
    letter-spacing: -0.02em;
    color: var(--text-primary);
}

.st-chip {
    display: inline-flex;
    align-items: center;
    gap: var(--sp-4);
    padding: 5px 12px 5px 10px;
    background: var(--surface-raised);
    border: 1px solid var(--border-default);
    border-radius: var(--r-full);
    box-shadow: var(--shadow-xs);
    font-family: var(--font-mono);
    font-size: var(--fs-2xs);
    font-weight: var(--fw-medium);
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--text-tertiary);
}

.st-chip::before {
    content: '';
    width: 6px;
    height: 6px;
    border-radius: var(--r-full);
    background: var(--st-accent);
}

/* ---------- The panel ---------- */

.st-main {
    display: grid;
    place-items: center;
    padding-block: var(--sp-6);
}

.st-card {
    width: 100%;
    max-width: 620px;
    padding: 44px 48px 32px;
    background: var(--surface-raised);
    border: 1px solid var(--border-default);
    border-radius: var(--r-2xl);
    box-shadow: var(--shadow-xl);
    text-align: center;
    animation: st-rise var(--dur-slow) var(--ease-out) both;
}

@keyframes st-rise {
    from { opacity: 0; transform: translateY(10px); }
    to   { opacity: 1; transform: none; }
}

@media (prefers-reduced-motion: reduce) {
    .st-card { animation: none; }
}

/* A 60px tile inside a ring of its own tint, so the tone is legible from
   across the room and not only in the icon. */
.st-mark {
    display: grid;
    place-items: center;
    width: 60px;
    height: 60px;
    margin: 0 auto 20px;
    border-radius: var(--r-2xl);
}

.st-mark-brand  { background: var(--p-50);      color: var(--p-700);      box-shadow: 0 0 0 8px rgba(30, 58, 138, 0.05); }
.st-mark-warn   { background: var(--gold-50);   color: var(--gold-700);   box-shadow: 0 0 0 8px rgba(245, 184, 0, 0.08); }
.st-mark-danger { background: var(--danger-50); color: var(--danger-700); box-shadow: 0 0 0 8px rgba(239, 68, 68, 0.06); }

.st-title {
    margin: 0 0 var(--sp-5);
    font-family: var(--font-display);
    font-size: var(--fs-4xl);
    font-weight: var(--fw-bold);
    letter-spacing: -0.025em;
    line-height: var(--lh-tight);
    color: var(--text-primary);
    text-wrap: balance;
}

.st-say {
    max-width: 44ch;
    margin: 0 auto;
    font-size: var(--fs-lg);
    line-height: var(--lh-relaxed);
    color: var(--text-secondary);
    text-wrap: pretty;
}

/* The path that failed. Left-aligned and scrollable rather than wrapped: a
   long path broken across three centred lines is unreadable, and the user is
   here to spot a typo in it. */
.st-path {
    display: flex;
    align-items: center;
    gap: var(--sp-5);
    margin-block-start: 24px;
    padding: 10px 12px;
    background: var(--surface-sunken);
    border: 1px solid var(--border-subtle);
    border-radius: var(--r-lg);
    font-family: var(--font-mono);
    font-size: var(--fs-xs);
    color: var(--text-tertiary);
    text-align: start;
    overflow-x: auto;
}

.st-path b {
    flex: none;
    font-weight: var(--fw-medium);
    color: var(--n-700);
}

.st-path span { white-space: nowrap; }

/* Who is being refused. Only on the 403 - "you do not have access" is not
   actionable until you know which account it means. A first name and the
   portal, never the address: this page is reachable by anyone holding the
   URL, and on a shared or projected screen the address is the one thing on it
   worth harvesting. */
.st-who {
    display: inline-flex;
    align-items: center;
    gap: var(--sp-6);
    margin-block-start: 24px;
    padding: 7px 18px 7px 7px;
    background: var(--surface-sunken);
    border: 1px solid var(--border-subtle);
    border-radius: var(--r-full);
    text-align: start;
}

.st-av {
    flex: none;
    display: grid;
    place-items: center;
    width: 34px;
    height: 34px;
    border-radius: var(--r-full);
    background: linear-gradient(140deg, var(--p-600), var(--p-800));
    color: var(--text-inverse);
    font-size: var(--fs-md);
    font-weight: var(--fw-semibold);
}

.st-who-n {
    font-size: var(--fs-md);
    font-weight: var(--fw-semibold);
    color: var(--n-800);
    overflow-wrap: anywhere;
}

.st-who-m {
    font-size: var(--fs-xs);
    color: var(--text-tertiary);
}

.st-acts {
    display: flex;
    flex-wrap: wrap;
    gap: var(--sp-5);
    justify-content: center;
    margin-block-start: 28px;
}

/* The sign-out POST is a form; without this it takes a line of its own and
   the two actions stop reading as a pair. */
.st-form { margin: 0; display: contents; }

/* Portal shortcuts, from the menu cache. Under a hairline because they are an
   offer, not part of the message. */
.st-links {
    display: flex;
    flex-wrap: wrap;
    gap: var(--sp-4);
    justify-content: center;
    margin-block-start: 30px;
    padding-block-start: 22px;
    border-block-start: 1px solid var(--border-subtle);
}

.st-links-lab {
    width: 100%;
    margin-block-end: var(--sp-2);
    font-size: var(--fs-xs);
    color: var(--text-disabled);
}

.st-links a {
    display: inline-flex;
    align-items: center;
    gap: var(--sp-3);
    padding: 7px 14px;
    background: var(--surface-raised);
    border: 1px solid var(--border-default);
    border-radius: var(--r-full);
    font-size: var(--fs-sm);
    font-weight: var(--fw-medium);
    color: var(--n-700);
    transition: all var(--dur-fast) var(--ease-out);
}

.st-links a:hover {
    background: var(--p-50);
    border-color: var(--p-200);
    color: var(--p-700);
    transform: translateY(-1px);
}

.st-links a:focus-visible { outline: none; box-shadow: var(--shadow-focus); }

/* ---------- Footer rail ---------- */

.st-foot {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: var(--sp-6);
    text-align: center;
}

.st-ref {
    font-family: var(--font-mono);
    font-size: var(--fs-2xs);
    letter-spacing: 0.02em;
    color: var(--text-disabled);
    overflow-wrap: anywhere;
}

@media (max-width: 720px) {
    .st { padding: var(--sp-8) var(--sp-6) var(--sp-10); gap: var(--sp-6); }
    .st-ghost { font-size: 54vw; }
    .st-card { padding: 32px 22px 26px; }
    .st-title { font-size: var(--fs-3xl); }
    .st-say { font-size: var(--fs-md); }
    .st-acts > *,
    .st-acts .btn { flex: 1 1 auto; justify-content: center; }
    .st-who { display: flex; }
}

@media (max-width: 420px) {
    .st-brand { font-size: var(--fs-xl); }
    .st-chip { display: none; }
}


/* ============================================================
   18. Settings hub (set-*)

   Shared by both hubs: /recruiter/settings and, since the platform
   console was rebuilt on it, /admin/settings. They are the same
   problem at two scales - a dozen-plus sections behind one route -
   and they were solved twice, badly, in two style attributes.

   Settings is nineteen sections behind one route, and the page that
   holds them was the last recruiter screen still drawing its own
   chrome inline: a 220px aside of hand-styled buttons, each one
   carrying eleven declarations and an absolutely positioned div for
   its active marker, all recomputed on every render because the
   colours were interpolated into the style attribute.

   That cost more than tidiness. Inline styles cannot express a
   hover, a focus ring or a breakpoint, so the rail had none of the
   three: no feedback under the pointer, nothing visible when tabbed
   to, and below a laptop width it kept its fixed 220px while the
   section beside it - a two-column Branding form, a Team table -
   was squeezed into what was left.

   So the rail moves here. It is the sidebar's idiom one step down
   in weight: a tinted tile, a label, an optional count, a navy band
   on the row you are on. It is not the sidebar's colour rotation -
   the hues are how the primary nav says "top level", and repeating
   them a level in would flatten the difference.

   What is here:
     .set        the two-column frame - rail plus working column
     .set-nav    the rail: a pinned card, scrolling on its own under
                 the shared .scroll-quiet scrollbar
     .set-find   the rail's filter, for finding one of nineteen
     .set-grp    a titled group of sections within the rail
     .set-link   one section row, .is-on when it is the open one
     .set-main   the column the section component renders into
     .set-sec    the section head every section opens with - title,
                 what it is for, and what acts on all of it
     .int-*      the integrations list, and .brand-* the branding
                 form - the two sections converted alongside Team

   Below 1080px the rail turns into a strip of chips above the
   content rather than shrinking: at that width a vertical rail is
   spending a fifth of the page on navigation the reader has already
   used.
   ============================================================ */

.set {
    /* Wide enough for "Agency CV templates" beside its tile without
       ellipsis - the longest label the seeded menu produces. */
    --set-rail: 244px;

    /* Where the rail pins. .pg-head sticks at -8px and stands 56px
       tall, so its lower edge is 48px down the scroll port; 12px
       more is the gap between bands everywhere else on the page. */
    --set-stick: 60px;

    /* The chrome above the scroll port - AppTopbar. Only needed to
       work out how much height the rail actually has. */
    --set-chrome: 56px;

    display: grid;
    grid-template-columns: var(--set-rail) minmax(0, 1fr);
    gap: 16px;
    align-items: start;
}

/* ---------- The rail ---------- */

.set-nav {
    position: sticky;
    inset-block-start: var(--set-stick);
    /* Under .pg-head, which is 30: the rail scrolls up to the header
       and stops behind it rather than over it. */
    z-index: 10;
    /* Five groups of sections are taller than a laptop viewport, and
       a sticky box taller than its scroll port stops following the
       scroll entirely - it pins once and then rides up out of view.
       Capping it at the space under the header keeps it pinned and
       gives the overflow to the rail's own scrollbar. */
    max-height: calc(100vh - var(--set-chrome) - var(--set-stick) - 12px);
    overflow-y: auto;
    /* Reaching the end of the rail must not start scrolling the page
       underneath it. */
    overscroll-behavior: contain;
    padding: 6px;
    background: var(--surface-raised);
    border: 1px solid var(--border-default);
    border-radius: var(--r-lg);
    box-shadow: var(--shadow-xs);
}

/* Groups are separated by a hairline rather than by margin alone:
   inside a card, whitespace between two label-and-rows blocks reads
   as a gap in the list, not as a boundary. */
.set-grp + .set-grp {
    margin-block-start: 6px;
    padding-block-start: 6px;
    border-block-start: 1px solid var(--border-subtle);
}

.set-grp-lab {
    margin: 0;
    padding: 7px 10px 5px;
    font-size: var(--fs-2xs);
    font-weight: var(--fw-semibold);
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--text-tertiary);
}

/* ---------- One section row ---------- */

.set-link {
    position: relative;
    display: flex;
    align-items: center;
    gap: 9px;
    width: 100%;
    height: 34px;
    padding: 0 10px;
    border: none;
    border-radius: var(--r-md);
    background: transparent;
    color: var(--text-secondary);
    font-size: var(--fs-sm);
    font-weight: var(--fw-medium);
    letter-spacing: -0.005em;
    text-align: start;
    cursor: pointer;
    transition: background-color var(--dur-fast) var(--ease-out),
                color var(--dur-fast) var(--ease-out);
}

.set-link + .set-link { margin-block-start: 1px; }

/* The tile is what carries the icon at this size. A 13px stroke
   glyph sitting directly on the card is a smudge next to a 12.5px
   label; on a tinted square it reads as a mark. */
.set-link-ico {
    display: grid;
    place-items: center;
    width: 22px;
    height: 22px;
    flex-shrink: 0;
    border-radius: var(--r-sm);
    background: var(--n-75);
    color: var(--n-500);
    transition: background-color var(--dur-fast) var(--ease-out),
                color var(--dur-fast) var(--ease-out),
                box-shadow var(--dur-fast) var(--ease-out);
}

.set-link-ico svg { color: inherit; }

.set-link-lab {
    flex: 1;
    min-width: 0;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* A count, not a status: neutral until the row it is on is the open
   one, when it takes the band's own tint so the row stays one
   object rather than a navy row with a grey chip stuck to it. */
.set-link-badge {
    flex-shrink: 0;
    padding: 1px 6px;
    border-radius: var(--r-full);
    background: var(--n-100);
    color: var(--text-secondary);
    font-size: 10px;
    font-weight: var(--fw-bold);
    line-height: 1.5;
}

.set-link:hover {
    background: var(--n-50);
    color: var(--text-primary);
}

.set-link:hover .set-link-ico {
    background: var(--n-100);
    color: var(--n-600);
}

/* Keyboard focus was invisible on this rail: a bare button with a
   background and no outline rule shows the UA ring on some browsers
   and nothing on others. The same ring every other control uses. */
.set-link:focus-visible {
    outline: none;
    box-shadow: var(--shadow-focus);
}

.set-link.is-on {
    background: var(--p-50);
    color: var(--p-800);
    font-weight: var(--fw-semibold);
}

/* The tile inverts on the open row - white on the tinted band, the
   same move .nav-item.active makes in the sidebar, which is what
   keeps the two rails reading as the same system. */
.set-link.is-on .set-link-ico {
    background: var(--surface-raised);
    color: var(--p-700);
    box-shadow: var(--shadow-xs);
}

.set-link.is-on .set-link-badge {
    background: var(--p-100);
    color: var(--p-700);
}

/* The marker, drawn by the row itself. It was an absolutely
   positioned child before, which is a node per row that the chip
   layout below would have had to hide anyway. */
.set-link.is-on::before {
    content: "";
    position: absolute;
    inset-block: 7px;
    inset-inline-start: 0;
    width: 2px;
    border-radius: var(--r-full);
    background: var(--p-700);
}

/* ---------- Finding a section ---------- */

/* Nineteen sections behind one route is a list you scan, not one you
   know: "Agency CV templates" is four groups down, and the rail was
   the only way to reach it. Typing filters the rows in place, which
   is cheaper than either remembering the grouping or scrolling past
   it. It sits inside the rail card, above the groups. */
.set-find { position: relative; margin: 2px 2px 6px; }

.set-find input {
    width: 100%;
    height: 30px;
    /* Room for the glyph on one end and the clear button on the other. */
    padding-inline: 28px 26px;
    border: 1px solid var(--border-default);
    border-radius: var(--r-md);
    background: var(--n-50);
    font: inherit;
    font-size: var(--fs-sm);
    color: var(--text-primary);
}

.set-find input::placeholder { color: var(--text-disabled); }
.set-find input:hover { border-color: var(--border-strong); }

/* White on focus: the field is sunken while it waits and raised while
   it is being used, which is the same move .input makes. */
.set-find input:focus {
    outline: none;
    background: var(--n-0);
    border-color: var(--border-focus);
    box-shadow: var(--shadow-focus);
}

.set-find-ico {
    position: absolute;
    inset-block: 0;
    inset-inline-start: 9px;
    display: flex;
    align-items: center;
    color: var(--text-disabled);
    pointer-events: none;
}

.set-find-x {
    position: absolute;
    inset-block: 0;
    inset-inline-end: 4px;
    display: flex;
    align-items: center;
    padding: 0 4px;
    border: none;
    border-radius: var(--r-sm);
    background: none;
    color: var(--text-tertiary);
    cursor: pointer;
}

.set-find-x:hover { color: var(--text-primary); }
.set-find-x:focus-visible { outline: none; box-shadow: var(--shadow-focus); }

/* The matched letters, marked. A filtered list that does not say why a
   row survived makes the reader re-read every row it kept. */
.set-link mark {
    background: var(--gold-100);
    color: inherit;
    border-radius: 2px;
    padding-inline: 1px;
}

.set-none {
    margin: 0;
    padding: 10px;
    font-size: var(--fs-xs);
    line-height: 1.45;
    color: var(--text-tertiary);
    text-align: center;
}

/* ---------- The section head ---------- */

/* Every section used to open with a card whose title repeated the rail
   row just clicked, and a section-level action - Invite member, Save
   branding, Re-check all - had nowhere to sit but inside that card,
   where it reads as acting on the card rather than on the section.
   This band is the section's own identity: what it is, one line on
   what it is for, and what can be done to all of it.

   A band with a rule under it rather than a sixth card: the cards
   below are the section's contents, and wrapping the title in one
   more box would put those contents two levels deep. */
.set-sec {
    display: flex;
    align-items: flex-start;
    gap: 12px;
    flex-wrap: wrap;
    padding: 0 2px 12px;
    border-block-end: 1px solid var(--border-default);
    margin-bottom: 8px;
}

.set-sec-txt { flex: 1 1 320px; min-width: 0; }

/* A count or a state belonging to the whole section shares the title's
   line - stacked, it pushes the description a row down every time a
   status appears. */
.set-sec-line { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }

.set-sec-title {
    margin: 0;
    font-size: var(--fs-lg);
    font-weight: var(--fw-semibold);
    letter-spacing: -0.012em;
    color: var(--text-primary);
}

/* 76ch, not the column: a description set to the full width of a wide
   settings column is a line nobody finishes. */
.set-sec-sub {
    margin: 3px 0 0;
    max-width: 76ch;
    font-size: var(--fs-sm);
    line-height: 1.5;
    color: var(--text-tertiary);
}

.set-sec-acts {
    display: flex;
    align-items: center;
    gap: 6px;
    flex-wrap: wrap;
    margin-inline-start: auto;
}

/* The section's own body. The head is not a card, so this is what
   keeps the rhythm between the cards under it. */
.set-sec-body { display: flex; flex-direction: column; gap: 12px; min-width: 0; }

/* ---------- Section: integrations (int-*) ---------- */

/* One row per tool, grouped by category. The rows were drawn inline with
   a top border each, which put a hairline above the first row of every
   group and under the group heading it already had one for. The heading
   is a band now - tinted, so a category reads as a divider rather than
   as a very small row. */

.int-grp {
    margin: 0;
    padding: 9px 16px 5px;
    background: var(--n-25);
    border-block-end: 1px solid var(--border-subtle);
    font-size: var(--fs-2xs);
    font-weight: var(--fw-semibold);
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--text-tertiary);
}

.int-row {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 11px 16px;
    border-block-end: 1px solid var(--border-subtle);
}

.int-row:last-child { border-block-end: none; }
.int-row:hover { background: var(--n-25); }

.int-ico {
    display: grid;
    place-items: center;
    width: 32px;
    height: 32px;
    flex-shrink: 0;
    border-radius: var(--r-md);
    background: var(--n-50);
    color: var(--p-700);
    box-shadow: inset 0 0 0 1px var(--border-subtle);
}

.int-txt { flex: 1; min-width: 0; }

.int-name { font-size: var(--fs-md); font-weight: var(--fw-semibold); letter-spacing: -0.005em; }
.int-desc { margin: 1px 0 0; font-size: var(--fs-xs); color: var(--text-tertiary); }

/* Why the row is in an error state, on the row itself. It is the only
   place the provider's own message is ever shown. */
.int-err { margin: 3px 0 0; font-size: var(--fs-xs); line-height: 1.45; color: var(--danger-700); }

/* The credential form, indented to the row's text so it reads as
   belonging to the integration above it rather than to the next one. */
.int-form {
    display: flex;
    flex-direction: column;
    gap: 10px;
    padding: 12px 16px 14px 60px;
    background: var(--n-25);
    border-block-end: 1px solid var(--border-subtle);
}

.int-check {
    display: flex;
    align-items: center;
    gap: 7px;
    font-size: var(--fs-sm);
    color: var(--text-secondary);
    cursor: pointer;
}

/* The result of a connect or disconnect, above the list. It was painted
   from --good-50 and --bad-50 with hex fallbacks; neither variable has
   ever been in the palette, so every one of those expressions resolved
   to its fallback and the banner was the one surface in Settings not
   drawn from tokens. */
.int-banner {
    padding: 9px 12px;
    border-radius: var(--r-md);
    font-size: var(--fs-sm);
    line-height: 1.45;
}

.int-banner-good {
    background: var(--success-50);
    color: var(--success-700);
    box-shadow: inset 0 0 0 1px var(--success-100);
}

.int-banner-bad {
    background: var(--danger-50);
    color: var(--danger-700);
    box-shadow: inset 0 0 0 1px var(--danger-200);
}

/* ---------- Section: branding (brand-*) ---------- */

/* auto-fit, not two fixed columns: at the width left by the settings rail,
   1fr 1fr put a 120px hex field beside a wrapped label. */
.brand-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
    gap: 12px;
    align-items: start;
}

.brand-id {
    display: flex;
    align-items: center;
    gap: 14px;
    padding-block-end: 16px;
    margin-block-end: 16px;
    border-block-end: 1px solid var(--border-subtle);
}

/* The tenant's own colour, so the card shows the setting rather than
   describing it. */
.brand-mark {
    display: grid;
    place-items: center;
    width: 64px;
    height: 64px;
    flex-shrink: 0;
    border-radius: var(--r-lg);
    color: var(--n-0);
    font-size: 22px;
    font-weight: var(--fw-bold);
    box-shadow: var(--shadow-sm);
}

.brand-name { font-size: var(--fs-lg); font-weight: var(--fw-semibold); letter-spacing: -0.01em; }
.brand-hint { margin: 2px 0 8px; font-size: var(--fs-xs); line-height: 1.45; color: var(--text-tertiary); }

/* An inset ring rather than a border: on a pale swatch a grey border reads
   as part of the colour. */
.brand-swatch {
    width: 28px;
    height: 28px;
    flex-shrink: 0;
    border-radius: var(--r-sm);
    box-shadow: inset 0 0 0 1px rgba(15, 19, 32, 0.12);
}

.brand-hex { width: 130px; }

.brand-url {
    padding: 9px 11px;
    border: 1px solid var(--border-subtle);
    border-radius: var(--r-md);
    background: var(--n-25);
    font-size: var(--fs-sm);
    color: var(--text-primary);
    overflow-wrap: anywhere;
}

/* The anchor is only there to make the button a link; it must not add an
   underline or a colour of its own. */
.brand-link { display: inline-flex; text-decoration: none; color: inherit; }

/* ---------- A padded box inside a flush card ---------- */

/* QCard sets its own padding inline, so a caller that passes Pad="0" to let a
   list or a table paint to the card's edge has no way to pad the one thing in
   it that is prose - a skeleton, or a note under a table. Three surfaces
   reached for QCard's internal `q-card-body` hook to do it, which has no rule
   in this stylesheet at all, so all three rendered flush against the corner. */
.set-pad { padding: 16px; }

/* The setting, shown rather than described. The two colour fields were
   a hex string and a 28px swatch, which tells a reader what the value
   is but not what it does - and what it does is paint the header, the
   buttons and the links of the page candidates actually land on. So the
   card carries a small standing-in for that page, painted live from the
   field above it. It is a picture, not a preview of real content: no
   job, no copy, nothing that could be mistaken for the site itself. */
.brand-prev {
    border-radius: var(--r-lg);
    background: var(--n-0);
    box-shadow: var(--shadow-xs), inset 0 0 0 1px var(--border-subtle);
    overflow: hidden;
}

/* The header band, in the primary colour. */
.brand-prev-top {
    display: flex;
    align-items: center;
    gap: 9px;
    padding: 10px 12px;
    color: var(--n-0);
}

.brand-prev-mark {
    display: grid;
    place-items: center;
    width: 22px;
    height: 22px;
    flex-shrink: 0;
    border-radius: var(--r-sm);
    background: rgba(255, 255, 255, 0.22);
    font-size: var(--fs-2xs);
    font-weight: var(--fw-bold);
}

.brand-prev-nm {
    font-size: var(--fs-sm);
    font-weight: var(--fw-semibold);
    letter-spacing: -0.005em;
    overflow: hidden;
    white-space: nowrap;
    text-overflow: ellipsis;
}

/* The nav a careers site carries. Drawn as bars rather than words: the
   labels are not a setting, and typing three of them here would put
   untranslated copy on a localized screen. */
.brand-prev-nav {
    display: flex;
    gap: 6px;
    margin-inline-start: auto;
}

.brand-prev-nav span {
    width: 26px;
    height: 5px;
    border-radius: var(--r-full);
    background: rgba(255, 255, 255, 0.42);
}

.brand-prev-body {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 12px;
}

.brand-prev-lines { flex: 1; display: flex; flex-direction: column; gap: 6px; }
.brand-prev-lines span { height: 6px; border-radius: var(--r-full); background: var(--n-100); }
.brand-prev-lines span:last-child { width: 62%; }

/* The button, in the same colour, so the contrast between the label and
   the fill is visible before anyone publishes it. */
.brand-prev-cta {
    padding: 6px 12px;
    border-radius: var(--r-md);
    color: var(--n-0);
    font-size: var(--fs-xs);
    font-weight: var(--fw-semibold);
    white-space: nowrap;
}

/* ---------- Section: integrations, continued ---------- */

/* The count of tools in a category, on the band that names it. Twelve
   integrations under five headings is a list you scan for the one you
   came for, and the heading is what tells you whether to keep going. */
.int-grp-n {
    margin-inline-start: 6px;
    font-variant-numeric: tabular-nums;
    color: var(--text-disabled);
}

/* A connected tool. The pill on the row already says so; the tile is
   what carries it back to the left edge, where the eye runs down. */
.int-row.is-on .int-ico {
    background: var(--success-50);
    color: var(--success-700);
    box-shadow: inset 0 0 0 1px var(--success-100);
}

.int-row.is-bad .int-ico {
    background: var(--danger-50);
    color: var(--danger-700);
    box-shadow: inset 0 0 0 1px var(--danger-200);
}

/* The row's own actions, so a wrapped button bar stays on the row's
   right rather than dropping under the description it is not about. */
.int-acts {
    display: flex;
    align-items: center;
    gap: 8px;
    flex-shrink: 0;
}

.int-banner {
    display: flex;
    align-items: flex-start;
    gap: 8px;
}

.int-banner svg { flex-shrink: 0; margin-block-start: 1px; }

/* The credential form's own footer, so Save and Cancel sit on a rule
   rather than floating under the last field. */
.int-form-foot {
    display: flex;
    align-items: center;
    gap: 8px;
    padding-block-start: 10px;
    border-block-start: 1px solid var(--border-subtle);
}

@media (max-width: 640px) {
    /* The tile stays with the name; the pill and the button take the
       second line rather than crushing the description to two words. */
    .int-row { flex-wrap: wrap; }
    .int-acts { margin-inline-start: 44px; }
    .int-form { padding-inline-start: 16px; }
}

/* ---------- The working column ---------- */

/* The sections stack their own cards; this only fixes the rhythm
   between them and stops a wide table inside one from pushing the
   grid track past its share. */
.set-main {
    display: flex;
    flex-direction: column;
    gap: 12px;
    min-width: 0;
}

/* ---------- Narrow ---------- */

/* A vertical rail at this width costs a fifth of the page. The same
   rows become a single scrolling strip of chips above the content:
   still every section, still one tap, no longer competing with the
   form for width. */
@media (max-width: 1080px) {
    .set {
        grid-template-columns: minmax(0, 1fr);
        gap: 12px;
    }

    .set-nav {
        max-height: none;
    }

    /* The filter keeps its own line above the strip: inside a
       horizontal scroller it would scroll away from the rows it
       filters. */
    .set-rows {
        display: flex;
        gap: 4px;
        overflow-x: auto;
        overflow-y: hidden;
    }

    /* The group's rows become items of the strip; the group box
       itself has nothing left to draw. */
    .set-grp { display: contents; }
    .set-grp-lab { display: none; }

    .set-link {
        width: auto;
        flex: 0 0 auto;
        margin-block-start: 0;
    }

    /* A 2px bar down the left of a chip is a hairline against its
       own corner radius. The chip's fill is the marker here. */
    .set-link.is-on::before { display: none; }
}

@media (max-width: 640px) {
    .set { --set-stick: 52px; }
    .set-nav { padding: 5px; }

    /* At this width the actions have wrapped under the description
       anyway; stretching them is friendlier than leaving two small
       buttons stranded on the left. */
    .set-sec-acts { flex: 1 1 auto; }
    .set-sec-acts .btn { flex: 1 1 auto; justify-content: center; }
}


/* ---------- The section's action footer (set-bar-*) ---------- */

/* Five sections of the platform hub are one long form ending in a Save
   button: Organization runs past 250 lines of fields, Security policy
   and the resume-storage form are not much shorter. The button sat at
   the bottom of the last card, so on any of them the way to commit the
   edit is below the fold from the moment you start typing - and the
   result of pressing it was a green or a red line drawn inline, at
   whatever padding the section had picked.

   The bar carries both, and stays on the floor of the working column
   while the form scrolls under it. Sticky rather than fixed: it belongs
   to the column, so it stops at the end of its own section instead of
   hanging over whatever comes next. Inset by 12px rather than flush to
   the viewport edge, so it reads as floating over the form rather than
   as a window chrome bar. */
.set-bar {
    position: sticky;
    inset-block-end: 12px;
    /* Under the rail (10) and the page header (30): a bar that covered
       either would be hiding navigation to show an action. */
    z-index: 9;
    display: flex;
    align-items: center;
    gap: 12px;
    flex-wrap: wrap;
    padding: 10px 14px;
    background: var(--surface-raised);
    border: 1px solid var(--border-default);
    border-radius: var(--r-lg);
    /* Heavier than a card's: while stuck, the bar is over the form and
       needs to say so. */
    box-shadow: var(--shadow-md);
}

/* The message slot holds its height whether or not there is a message,
   so the bar does not grow by a line the first time a save reports
   back and shove the field under it out from under the pointer. */
.set-bar-msg {
    flex: 1 1 220px;
    min-width: 0;
    display: flex;
    align-items: center;
    gap: 6px;
    min-height: 20px;
    font-size: var(--fs-sm);
    font-weight: var(--fw-medium);
    line-height: 1.4;
}

.set-bar-msg svg { flex-shrink: 0; }

/* Standing text - "Changes apply to every tenant" - is context, not a
   verdict, so it stays quiet and never takes a tone colour. */
.set-bar-hint {
    font-weight: var(--fw-regular);
    color: var(--text-tertiary);
}

.set-bar-success { color: var(--success-700); }
.set-bar-danger  { color: var(--danger-700); }
.set-bar-warning { color: var(--warning-700); }
.set-bar-info    { color: var(--info-700); }

.set-bar-acts {
    display: flex;
    align-items: center;
    gap: 8px;
    flex-shrink: 0;
    margin-inline-start: auto;
}

@media (max-width: 640px) {
    /* The commit takes the full width rather than sitting as a small
       button beside a message that has already wrapped past it. */
    .set-bar-acts { flex: 1 1 auto; margin-inline-start: 0; }
    .set-bar-acts .btn { flex: 1 1 auto; justify-content: center; }
}

/* ============================================================
   19. Recruiter: My profile (prof-*)

   The one settings section that is about a person rather than a
   configuration, and it was drawn as if it were neither: five
   QCards in a single column, every control carrying its own
   style attribute built from two const fields, a hand-rolled
   presence toggle painted from interpolated hex, and six bare
   checkboxes where the rest of the product uses .switch.

   Inline styles cannot express hover, focus or a breakpoint, so
   this screen had none of the three - the same gap the settings
   rail had before "18." moved it here. And with everything in one
   column, Save sat below a fold that got longer the moment a card
   was added, with nothing on screen saying whether there was
   anything to save.

   What is here:
     .prof-hero   the identity band - who you are, and the
                  read-only facts as chips
     .prof-grid   the two working columns: forms left, switches and
                  security right
     .prof-row    one labelled setting with its switch or button
     .prof-sess   was one signed-in device; the list is QSessionCard
                  and "35." now, shared with the platform console
     .prof-bar    the save bar, pinned to the foot of the column and
                  present only while something is unsaved

   The controls themselves are the shared ones: .fld / .input from
   "Form fields", .switch by way of QToggle, .card and .sec-head
   from the primitives. Nothing here restyles them; this block is
   only the arrangement around them.
   ============================================================ */

/* ---------- The identity band ---------- */

.prof-hero {
    position: relative;
    overflow: hidden;
    display: flex;
    align-items: flex-start;
    gap: 18px;
    flex-wrap: wrap;
    padding: 18px 20px;
    background: var(--surface-raised);
    border: 1px solid var(--border-default);
    border-radius: var(--r-lg);
    box-shadow: var(--shadow-xs);
}

/* One tinted wash across the top of the band, so the person reads as
   the head of the page and the cards under it read as the form. A
   border or a heavier fill would make it a sixth card. */
.prof-hero::before {
    content: "";
    position: absolute;
    inset: 0 0 auto;
    height: 72px;
    background: linear-gradient(180deg, var(--p-50), rgba(238, 242, 255, 0));
    pointer-events: none;
}

.prof-hero > * { position: relative; }

.prof-face { position: relative; flex-shrink: 0; }

/* The white halo lifts the avatar off the wash without giving it a
   border, which at this size reads as a frame. */
.prof-face .avatar {
    box-shadow: 0 0 0 3px var(--surface-raised), var(--shadow-md);
}

.prof-cam {
    position: absolute;
    inset-inline-end: -4px;
    inset-block-end: -4px;
    display: grid;
    place-items: center;
    width: 26px;
    height: 26px;
    padding: 0;
    border: 1px solid var(--border-default);
    border-radius: var(--r-full);
    background: var(--surface-raised);
    color: var(--text-secondary);
    cursor: pointer;
    box-shadow: var(--shadow-sm);
    transition: background-color var(--dur-fast) var(--ease-out),
                border-color var(--dur-fast) var(--ease-out),
                color var(--dur-fast) var(--ease-out);
}

.prof-cam:hover:not(:disabled) {
    background: var(--p-50);
    border-color: var(--p-200);
    color: var(--p-700);
}

.prof-cam:disabled { cursor: not-allowed; opacity: 0.55; }
.prof-cam:focus-visible { outline: none; box-shadow: var(--shadow-focus); }

/* The presence dot rides the avatar rather than the name, because it
   is a property of the person and not of the heading. */
.prof-dot {
    position: absolute;
    inset-inline-start: -1px;
    inset-block-end: 4px;
    width: 13px;
    height: 13px;
    border-radius: var(--r-full);
    background: var(--success-500);
    box-shadow: 0 0 0 3px var(--surface-raised);
}

.prof-who { flex: 1 1 300px; min-width: 0; }

.prof-name {
    display: flex;
    align-items: center;
    gap: 8px;
    flex-wrap: wrap;
    margin: 0;
    font-size: var(--fs-2xl);
    font-weight: var(--fw-semibold);
    letter-spacing: -0.02em;
    line-height: var(--lh-tight);
}

.prof-pref {
    font-size: var(--fs-sm);
    font-weight: var(--fw-medium);
    color: var(--text-tertiary);
}

.prof-role { margin: 3px 0 0; font-size: var(--fs-md); color: var(--text-secondary); }
.prof-role b { font-weight: var(--fw-semibold); color: var(--text-primary); }

/* The facts a person cannot change from this form - the sign-in
   address, the phone they typed, their zone, when they joined - as
   chips rather than as more form rows. A read-only input is a
   control that lies about being one. */
.prof-facts {
    display: flex;
    align-items: center;
    gap: 6px;
    flex-wrap: wrap;
    margin-block-start: 11px;
}

.prof-fact {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    height: 24px;
    padding: 0 9px;
    border-radius: var(--r-full);
    background: var(--n-75);
    color: var(--text-secondary);
    font-size: var(--fs-xs);
    font-weight: var(--fw-medium);
}

.prof-fact svg { color: var(--n-400); flex-shrink: 0; }
.prof-fact-mono { font-family: var(--font-mono); font-size: var(--fs-2xs); }

/* ---------- The photo line ---------- */

/* What the camera button accepts, and the way back to no photo at all. The
   platform console's band takes an upload where the recruiter's is still
   read-only, and the rules for it - three formats, 5 MB - have to be readable
   before the file dialog opens rather than only in the error after it. The
   same line reports the upload in progress: there is nowhere else on the band
   for it to speak from. */
.prof-photo {
    display: flex;
    align-items: center;
    gap: 10px;
    flex-wrap: wrap;
    margin-block-start: 10px;
}

.prof-photo-hint {
    min-width: 0;
    font-size: var(--fs-xs);
    line-height: 1.45;
    color: var(--text-tertiary);
}

/* ---------- The two working columns ---------- */

/* Forms on the left, the switches and the security card on the right.
   336px is the narrowest a labelled row plus its switch reads at
   without the sub-line wrapping on every row. */
.prof-grid {
    display: grid;
    grid-template-columns: minmax(0, 1fr) 336px;
    gap: 12px;
    align-items: start;
}

.prof-col { display: flex; flex-direction: column; gap: 12px; min-width: 0; }

/* An address this form cannot change. Dashed, so it reads as locked
   at a glance rather than only on the click that does nothing. */
.prof-locked {
    background: var(--n-50);
    color: var(--text-tertiary);
    border-style: dashed;
}

.prof-locked:hover { border-color: var(--border-default); }

/* A card-level verdict - "Login email updated", "Current password is
   incorrect" - above the fields it is about. .note carries the paint; this is
   only the gap to the first field, which each caller had been picking for
   itself. */
.prof-note { margin-block-end: 12px; }

/* A label with a live count on the other end of its line - the bio's
   remaining characters. The count is not a second label, so it drops
   the uppercase tracking the label carries. */
.prof-lab { display: flex; align-items: center; gap: 8px; }

.prof-count {
    margin-inline-start: auto;
    font-size: var(--fs-2xs);
    font-weight: var(--fw-medium);
    letter-spacing: 0;
    text-transform: none;
    color: var(--text-disabled);
    font-variant-numeric: tabular-nums;
}

/* ---------- One setting row ---------- */

.prof-rows { display: flex; flex-direction: column; }

.prof-row {
    display: flex;
    align-items: flex-start;
    gap: 12px;
    padding: 11px 0;
}

/* Hairlines rather than gaps: a stack of label-and-switch rows with
   only whitespace between them reads as one list with holes in it. */
.prof-row + .prof-row { border-block-start: 1px solid var(--border-subtle); }
.prof-row:first-child { padding-block-start: 0; }
.prof-row:last-child { padding-block-end: 0; }

.prof-row-txt { flex: 1; min-width: 0; }

.prof-row-lab {
    display: flex;
    align-items: center;
    gap: 7px;
    flex-wrap: wrap;
    font-size: var(--fs-md);
    font-weight: var(--fw-medium);
    letter-spacing: -0.005em;
}

/* What the setting actually does, in terms of the thing that happens.
   Six switches labelled only by feature name is a quiz. */
.prof-row-sub {
    margin: 2px 0 0;
    font-size: var(--fs-xs);
    line-height: 1.45;
    color: var(--text-tertiary);
}

.prof-sub-lab {
    padding: 10px 0 6px;
    font-size: var(--fs-2xs);
    font-weight: var(--fw-semibold);
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--text-disabled);
}

/* The group heading owns the boundary, so the first row under it does
   not draw a second one 6px away. */
.prof-sub-lab + .prof-row { border-block-start: none; padding-block-start: 0; }

/* A row whose body is a list - the signed-in devices - rather than
   one control sitting to the right of its label. */
.prof-row-block { display: block; }
.prof-row-head { display: flex; align-items: center; gap: 10px; }
.prof-row-body { margin-block-start: 9px; }

/* The switch sits on the label's line, not on the block's centre. */
.prof-switch { margin-block-start: 1px; }

/* ---------- Signed-in devices ---------- */

/* The device rows moved to QSessionCard and "35. Sessions & devices
   (sess-*)" - the same card the platform console draws for the same
   list, so one list does not read two ways in two portals. The block
   that stood here styled a third variant of it. */

/* ---------- The quiet footer ---------- */

/* Where a person looks for "remove my account" and cannot have it -
   only an admin can. Dashed and unfilled, because it is the answer
   to a question rather than an action on offer. */
.prof-quiet {
    display: flex;
    align-items: center;
    gap: 12px;
    flex-wrap: wrap;
    padding: 12px 16px;
    border: 1px dashed var(--border-strong);
    border-radius: var(--r-lg);
}

.prof-quiet-txt { flex: 1 1 320px; min-width: 0; }

.prof-quiet strong {
    display: block;
    margin-block-end: 2px;
    font-size: var(--fs-sm);
    color: var(--text-secondary);
}

.prof-quiet p { margin: 0; max-width: 62ch; font-size: var(--fs-xs); line-height: 1.5; color: var(--text-tertiary); }

/* ---------- The save bar ---------- */

/* Pinned to the foot of the working column, rendered only while
   something is unsaved. The buttons used to sit at the end of the
   scroll, which put them under the fold on every viewport once the
   fifth card was added, and nothing on screen said an edit was
   waiting. It names the fields that changed for the same reason. */
.prof-bar {
    position: sticky;
    inset-block-end: 12px;
    /* Over the cards, under .pg-head (30) and the drawer. */
    z-index: 20;
    display: flex;
    align-items: center;
    gap: 12px;
    flex-wrap: wrap;
    padding: 9px 10px 9px 14px;
    border: 1px solid var(--p-200);
    border-radius: var(--r-lg);
    background: rgba(255, 255, 255, 0.92);
    backdrop-filter: blur(8px);
    box-shadow: var(--shadow-lg);
}

.prof-bar-txt {
    display: flex;
    align-items: center;
    gap: 7px;
    min-width: 0;
    font-size: var(--fs-sm);
    font-weight: var(--fw-semibold);
    color: var(--p-800);
}

.prof-bar-txt::before {
    content: "";
    flex-shrink: 0;
    width: 7px;
    height: 7px;
    border-radius: var(--r-full);
    background: var(--gold-500);
}

.prof-bar-txt span {
    min-width: 0;
    font-weight: var(--fw-regular);
    color: var(--text-tertiary);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.prof-bar-acts { display: flex; gap: 6px; margin-inline-start: auto; }

/* ---------- Narrow ---------- */

/* One column before the settings rail folds, so the switches do not
   hold 336px while the form beside them wraps every field. */
@media (max-width: 1180px) {
    .prof-grid { grid-template-columns: minmax(0, 1fr); }
}

@media (max-width: 720px) {
    .prof-hero { gap: 14px; padding: 16px; }
    .prof-bar { padding: 9px 10px; }
    .prof-bar-acts { flex: 1 1 auto; }
    .prof-bar-acts .btn { flex: 1 1 auto; justify-content: center; }
}


/* ============================================================
   20. Shared: side drawer (drw-*)

   The chrome behind QDrawer, and behind every drawer in the
   product: the three hand-rolled families this generalised -
   .cli-drawer on the recruiter pages, the engage drawer and the
   super-admin role drawer - have all been moved onto it, and
   what each of them still owns is the form inside, not the panel
   around it. It sits at the end of the file for its history
   rather than its standing; it belongs in "1.".

   Why a drawer and not another QModal: a modal caps at 90vh and
   centres, so a form with a body field scrolls inside a floating
   window while the page behind it sits still, and the save button
   goes under the fold the moment a field is added. The drawer has
   the height of the screen, a header and footer that do not scroll,
   and it leaves the list it was opened from visible beside it.

   What is here:
     .drw-scrim   the dimmed page behind it, and the click target
                  that closes it
     .drw         the panel - a fixed-height flex column
     .drw-head    eyebrow, title, one line of what saving does
     .drw-body    the only part that scrolls
     .drw-foot    the pinned action bar
     .drw-alert   a form-level message above the fields
   ============================================================ */

.drw-scrim {
    position: fixed;
    inset: 0;
    z-index: var(--z-drawer);
    background: var(--surface-overlay);
    animation: drw-fade var(--dur-fast) var(--ease-out);
}

.drw {
    position: fixed;
    inset-block: 0;
    inset-inline-end: 0;
    z-index: calc(var(--z-drawer) + 1);

    /* The caller's width, but never wider than the screen: a 660px
       editor on a 390px phone would otherwise put its own left edge
       off-screen and take the close button with it. */
    width: min(var(--drw-w, 480px), 100vw);

    display: flex;
    flex-direction: column;
    /* min-height:0 on the body below is what makes the overflow
       engage; this is the other half of it. */
    overflow: hidden;

    background: var(--surface-raised);
    border-inline-start: 1px solid var(--border-default);
    box-shadow: var(--shadow-xl);
    outline: none;
    animation: drw-in var(--dur-mid) var(--ease-out);
}

@keyframes drw-fade { from { opacity: 0; } }
@keyframes drw-in { from { transform: translateX(16px); opacity: 0; } }

/* The panel arriving from off-screen is the thing that says "this is
   over the page, not a new page". Where motion is turned down it
   still has the scrim to say so. */
@media (prefers-reduced-motion: reduce) {
    .drw-scrim,
    .drw { animation: none; }
}

/* ---------- Head ---------- */

/* Header and footer sit on the sunken surface, the body stays on white: the
   chrome that does not scroll reads as chrome, and the scrolling form reads as
   the page. The .cli-drawer copies - clients, candidates, jobs, CSV import -
   were drawn that way from the start and this one was not, so a QDrawer
   beside them read as a panel with its bands missing. They are all this
   panel now, and the bands are why. */
/* The header row's layout, split from the band it sits on. QDrawer's own
   header is the row plus the chrome; the three DrawerHost-hosted drawers
   rebuild the same row inside a sticky band of their own (.cad-head-rich)
   that already paints the surface and the rule, and would draw both twice. */
.drw-head,
.drw-head-row {
    display: flex;
    align-items: flex-start;
    gap: 8px;
}

/* Deep navy, not the sunken grey the footer uses. Both bands were the
   same surface, so the header read as the top of the form rather than
   as the record the form is about; the two now say which is which - the
   band names what is being edited, the bar at the other end acts on it.
   The colour is not a new one: .rnew-sum has painted a --p-700 block
   with inverse text on the role drawer's footer since it was written.

   The footer deliberately stays light. Navy at both ends would swallow
   the primary button, which is the same navy. */
.drw-head {
    flex-shrink: 0;
    padding: 16px 20px 14px;
    border-block-end: 1px solid var(--p-900);
    background: var(--p-800);
    color: var(--text-inverse);
}

/* ---------- What sits on the band ----------

   Everything below re-states, for a dark ground, something a caller
   already puts in the header. None of it changes what is rendered -
   only what colour it is rendered in. */

/* Gold rather than --p-700: the eyebrow is the one line on the band
   that carries brand, and --p-200 there would read as a third grey. */
.drw-head .drw-eyebrow,
.cad-head-rich .drw-eyebrow { color: var(--gold-400); }

.drw-head .drw-title,
.cad-head-rich .drw-title { color: var(--text-inverse); }

.drw-head .drw-sub,
.cad-head-rich .drw-sub { color: var(--p-200); }

/* The record's code, and the marked-up words in the job drawers' hints. */
.drw-head .drw-sub .cli-code,
.drw-head .drw-sub code,
.cad-head-rich .drw-sub .cli-code { color: var(--p-200); }

/* The mark - an avatar, a logo tile, an icon for what is being made.
   A ring rather than a re-colour, because two of these tiles take their
   fill from an inline style the sheet cannot reach: the client drawer
   draws it from the record's name. The ring is what separates a dark
   tile from the dark band under it. */
.drw-head > :first-child:not(.drw-head-txt),
.drw-head-row > :first-child:not(.drw-head-txt) {
    box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.24), 0 1px 3px rgba(10, 21, 53, 0.4);
}

/* The three that are --p-700 flat, which on navy is a navy square on
   navy. A wash of the band's own light is enough to lift them. */
.drw-head .rnew-head-mark,
.drw-head .cimp-mark,
.drw-head .rimp-mark {
    background: rgba(255, 255, 255, 0.16);
    color: var(--text-inverse);
}

/* Except when it has finished, which is the one time the tile is
   carrying a state rather than a subject. */
.drw-head .rimp-mark.is-done { background: var(--success-600); }

/* The dashed well the import drawers show before a file is chosen. */
.drw-head .cli-dw-mark.is-empty {
    background: rgba(255, 255, 255, 0.07);
    border-color: rgba(255, 255, 255, 0.4);
    color: rgba(255, 255, 255, 0.62);
    box-shadow: none;
}

/* HeadExtra's controls. A ghost button is a hairline and muted text,
   both of which were picked for white. */
.drw-head .btn-ghost,
.drw-head-row .btn-ghost {
    background: transparent;
    border-color: rgba(255, 255, 255, 0.24);
    color: var(--p-100);
}

.drw-head .btn-ghost:hover,
.drw-head-row .btn-ghost:hover {
    background: rgba(255, 255, 255, 0.14);
    border-color: rgba(255, 255, 255, 0.38);
    color: var(--text-inverse);
}

/* The exam paper's countdown, which rides in HeadExtra. Its two-minute
   warning keeps its red - that is the only thing on the paper that can
   still be acted on, and it reads on navy exactly as well as on grey. */
.drw-head .certd-clock {
    background: rgba(255, 255, 255, 0.12);
    border-color: rgba(255, 255, 255, 0.22);
    color: var(--p-100);
}

.drw-head .certd-clock.is-low {
    background: var(--danger-50);
    border-color: var(--danger-200);
    color: var(--danger-700);
}

.drw-head-txt { flex: 1; min-width: 0; }

.drw-eyebrow {
    margin: 0 0 5px;
    font-size: var(--fs-2xs);
    font-weight: var(--fw-bold);
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--p-700);
}

.drw-title {
    margin: 0;
    font-size: var(--fs-xl);
    font-weight: var(--fw-bold);
    letter-spacing: -0.012em;
    color: var(--text-primary);
}

/* 68ch: the panel is narrow enough that the line length is fine, but a
   subtitle that wraps four times stops being a subtitle. */
.drw-sub {
    margin: 3px 0 0;
    max-width: 68ch;
    font-size: var(--fs-sm);
    line-height: var(--lh-snug);
    color: var(--text-tertiary);
}

.drw-head-acts {
    display: flex;
    align-items: center;
    gap: 6px;
    flex-shrink: 0;
}

.drw-x {
    flex-shrink: 0;
    display: grid;
    place-items: center;
    width: var(--h-md);
    height: var(--h-md);
    padding: 0;
    border: none;
    border-radius: var(--r-md);
    background: transparent;
    color: var(--text-tertiary);
    cursor: pointer;
    transition: background-color var(--dur-fast) var(--ease-out),
                color var(--dur-fast) var(--ease-out);
}

.drw-x { color: var(--p-200); }
.drw-x:hover { background: rgba(255, 255, 255, 0.14); color: var(--text-inverse); }
.drw-x:focus-visible { outline: none; box-shadow: 0 0 0 3px rgba(255, 255, 255, 0.35); }

/* ---------- Body ---------- */

.drw-body {
    /* A .tbl in here scrolls inside the drawer, so its header measures from
       the drawer's own top edge and not from the page header behind it. */
    --tbl-top: 0px;
    flex: 1;
    /* Without this a flex child refuses to shrink below its content and
       the overflow never engages - the mistake every drawer in the app
       made before DrawerHost fixed its own copy. */
    min-height: 0;
    overflow-y: auto;
    overscroll-behavior: contain;

    display: flex;
    flex-direction: column;
    gap: 14px;
    padding: 18px 20px;

    /* Sunken, so the groups below can be raised panels against it. What
       looks like five backgrounds is two things: the first pair are the
       page colour, painted `local` so they scroll away with the content;
       the second pair are shadows painted `scroll` so they stay pinned
       to the edges. Where there is nothing to scroll the local layer sits
       on top of the shadow and hides it, so a shadow appears at the top
       only once the body has been scrolled and at the bottom only while
       there is still form underneath.

       That condition is the whole point. A pinned bar casting over
       nothing is decoration; this one answers "is that everything?",
       which on a six-group form is a real question the drawer has never
       been able to answer. It costs no script and no state - the
       gradients are doing it. The zero stops repeat --surface-page's
       literal because a gradient to `transparent` fades through black in
       some engines. */
    background:
        linear-gradient(var(--surface-page) 30%, rgba(249, 250, 251, 0)) center top / 100% 16px local no-repeat,
        linear-gradient(rgba(249, 250, 251, 0), var(--surface-page) 70%) center bottom / 100% 16px local no-repeat,
        radial-gradient(farthest-side at 50% 0, rgba(15, 19, 32, 0.13), rgba(15, 19, 32, 0)) center top / 100% 7px scroll no-repeat,
        radial-gradient(farthest-side at 50% 100%, rgba(15, 19, 32, 0.13), rgba(15, 19, 32, 0)) center bottom / 100% 7px scroll no-repeat,
        var(--surface-page);
}

.drw-field { display: flex; flex-direction: column; }

/* A label with a control of its own on the same line - "Draft with AI"
   beside Body. Baseline, not centre: the two are text of different
   sizes and centring them leaves the label sitting low. */
.drw-field-head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 8px;
}

/* The label carries the gap under it (.lbl), so a head that replaces it
   has to carry the same one or the control jumps up 4px. */
.drw-field-head .lbl { margin-block-end: 0; }
.drw-field-head + * { margin-block-start: 4px; }

.drw-hint {
    margin: 5px 0 0;
    font-size: var(--fs-xs);
    line-height: var(--lh-snug);
    color: var(--text-tertiary);
}

/* The same line saying why this one field is wrong. It replaces the
   hint rather than joining it, so the control never grows a third
   line, and it is red because .input.error beside it already is. */
.drw-hint-bad {
    font-weight: var(--fw-medium);
    color: var(--danger-600);
}

/* Two short fields that read as one fact. They stop being a pair before
   either gets narrow enough to be unusable. */
.drw-pair {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
    gap: 12px;
}

/* ---------- Sections ---------- */

/* A titled group of fields. This and the two grids below came from
   "9." (.cli-fieldset / .cli-grid2 / .cli-grid3), where they were
   written against a drawer body that no longer exists; every
   drawer in the product is a QDrawer now, so they live with the
   rest of the chrome instead.

   The rule above the legend is what separates one group from the
   next, so a group carries its own top border rather than relying
   on an adjacent-sibling selector: a drawer that shows a section
   conditionally would otherwise lose the rule on whichever one
   happened to render first. */
/* A hairline between groups was all that separated them, so six groups
   read as one very long form with rules in it. As a panel each group is
   an object: its own ground, its own header, its own edge - and the
   drawer stops being a scroll and starts being a stack.

   The ring is a box-shadow rather than a border on purpose. Half of
   these panels are a <fieldset>, and a fieldset paints its border around
   the rendered legend, leaving a gap in the top edge exactly where this
   design puts the header band. A ring has no such rule. */
.drw-sec {
    display: flex;
    flex-direction: column;
    gap: 14px;
    margin: 0;
    padding: 14px;
    border: none;
    border-radius: var(--r-lg);
    background: var(--surface-raised);
    box-shadow: 0 0 0 1px var(--border-subtle), var(--shadow-xs);
}

/* The panel's header band. Flex rather than plain text because a legend
   can carry an icon and, in the import drawers, a control at its far end.

   The width is stated rather than left to auto, and stated as a calc,
   because both halves of that are load-bearing:

     - auto is wrong. A <legend> is the fieldset's *rendered legend*,
       which shrinks to its text whatever display it is given, so the
       band came out as a tab over the panel's top-left corner.
     - 100% is also wrong. A negative end margin pulls a sized box's
       edge across without widening it, so the band cleared the padding
       on the start side and stopped 14px short on the end.

   100% is the panel's content box, which excludes both paddings; adding
   them back makes the band exactly the panel's width, and the negative
   margins put it over both top corners. Below it .drw-sec's gap supplies
   the space to the first field, so the band needs no bottom margin. */
.drw-legend {
    display: flex;
    align-items: center;
    gap: 7px;
    width: calc(100% + 28px);
    margin: -14px -14px 0;
    padding: 11px 14px;
    background: var(--surface-sunken);
    border-block-end: 1px solid var(--border-subtle);
    border-start-start-radius: var(--r-lg);
    border-start-end-radius: var(--r-lg);
    font-size: var(--fs-xs);
    font-weight: var(--fw-bold);
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--text-tertiary);
}

.drw-legend svg { color: var(--n-400); }

/* Three groups in the candidate drawer - the skills, experience and
   education repeaters - put an "Add" button beside the group's name, and
   wrap the two in a row. The band is that row, then, not the legend
   inside it: bled out of a flex row the legend would take its own item's
   width and paint a stripe halfway across the panel.

   The import drawers do not need this. They put their control inside the
   legend and push it to the far end, which is the shape to prefer. */
.drw-sec > .cnd-sec-h {
    margin: -14px -14px 0;
    padding: 11px 14px;
    background: var(--surface-sunken);
    border-block-end: 1px solid var(--border-subtle);
    border-start-start-radius: var(--r-lg);
    border-start-end-radius: var(--r-lg);
}

.cnd-sec-h > .drw-legend {
    margin: 0;
    padding: 0;
    background: none;
    border: none;
    border-radius: 0;
}

/* ---------- Field grids ----------

   .drw-pair above is the automatic one: it wraps on measurement and is
   what a new form should reach for. These two are explicit, for rows
   whose columns have to stay aligned with the row above them. */
.drw-grid2 {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 12px;
}

/* Three across for the short fields that read as one fact - city / country,
   min / max / currency, from / to. They collapse to two before they collapse
   to one, because a 90px input is not made usable by being alone on a row. */
.drw-grid3 {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: 12px;
}

@media (max-width: 900px) {
    .drw-grid3 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (max-width: 700px) {
    .drw-grid2 { grid-template-columns: minmax(0, 1fr); }
    .drw-grid3 { grid-template-columns: minmax(0, 1fr); }
}

.drw-alert {
    padding: 9px 12px;
    border-radius: var(--r-md);
    font-size: var(--fs-sm);
    line-height: var(--lh-snug);
}

.drw-alert-bad {
    background: var(--danger-50);
    color: var(--danger-700);
    box-shadow: inset 0 0 0 1px var(--danger-200);
}

/* Not an error - the form will save. It is a cost or a delivery risk
   the writer should see before they save it, which is why it sits
   with the field it is about rather than at the top of the form. */
.drw-alert-warn {
    display: flex;
    align-items: flex-start;
    gap: 8px;
    background: var(--warning-50);
    color: var(--warning-700);
    box-shadow: inset 0 0 0 1px var(--warning-100);
}

.drw-alert-warn svg { flex-shrink: 0; margin-block-start: 1px; }

/* ---------- A value the form states rather than offers ----------

   The type an item belongs to, the schema a field is defined on, a slug
   stored data is keyed by. Shown rather than hidden: it is usually half of
   what the field beside it has to be unique within, and a disabled input
   saying the same thing reads as a control that has failed to load.

   The taxonomy drawer wrote this first as .tax-lockline and the schema
   drawer copied it; a third caller is what moved it here. */
.drw-lockline {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 8px 12px;
    border-radius: var(--r-md);
    background: var(--surface-sunken);
    box-shadow: inset 0 0 0 1px var(--border-subtle);
}

.drw-lockval {
    flex: 1;
    min-width: 0;
    font-family: var(--font-mono);
    font-size: var(--fs-md);
    color: var(--text-primary);
    overflow-wrap: anywhere;
}

/* The word at the end of the line saying why it cannot be typed into. */
.drw-lock {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    flex-shrink: 0;
    font-size: var(--fs-2xs);
    font-weight: var(--fw-bold);
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--text-tertiary);
}

/* QInput and QTextArea carry font-family:inherit in an inline style, which no
   class on the control can outrank; the family has to come from above it. */
.drw-monowrap { font-family: var(--font-mono); }

/* ---------- One choice out of a handful, as tiles ----------

   For a field whose options differ by a sentence rather than a word - the
   six schema field types, the four incident severities. In a select that
   sentence has nowhere to go, so the reader picks from six bare words and
   finds out what they meant afterwards.

   Two across in a drawer: the tile has to hold its own sentence, and three
   across leaves a 160px column that breaks "One value from a fixed list"
   over four lines. */
.drw-pick {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 8px;
}

.drw-pick-o {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr);
    grid-template-areas:
        "mark title"
        "mark blurb";
    align-items: start;
    column-gap: 9px;
    row-gap: 2px;
    padding: 10px 11px;
    border: 1px solid var(--border-default);
    border-radius: var(--r-md);
    background: var(--surface-raised);
    text-align: start;
    cursor: pointer;
    transition: border-color 0.12s ease, background 0.12s ease, box-shadow 0.12s ease;
}

.drw-pick-o:hover { border-color: var(--p-300); background: var(--n-25); }

.drw-pick-o:focus-visible { outline: none; box-shadow: var(--shadow-focus); }

/* The chosen one, said twice: the border and the mark. Colour alone is not a
   state a reader can pick out of six identical cards at a glance. */
.drw-pick-o.is-on {
    border-color: var(--p-600);
    background: var(--p-50);
    box-shadow: inset 0 0 0 1px var(--p-600);
}

.drw-pick-i {
    grid-area: mark;
    display: grid;
    place-items: center;
    inline-size: 26px;
    block-size: 26px;
    border-radius: var(--r-md);
    background: var(--n-100);
    color: var(--text-secondary);
}

.drw-pick-o.is-on .drw-pick-i { background: var(--p-600); color: var(--text-inverse); }

.drw-pick-t {
    grid-area: title;
    font-size: var(--fs-sm);
    font-weight: var(--fw-semibold);
    color: var(--text-primary);
}

.drw-pick-d {
    grid-area: blurb;
    font-size: var(--fs-xs);
    line-height: var(--lh-snug);
    color: var(--text-secondary);
}

@media (max-width: 620px) {
    .drw-pick { grid-template-columns: minmax(0, 1fr); }
}

/* ---------- Foot ---------- */

.drw-foot {
    flex-shrink: 0;
    display: flex;
    align-items: center;
    justify-content: flex-end;
    /* Wrap rather than squeeze. A bar with a note on the left and three
       buttons on the right - the offer drawer - runs out of room at 560px, and
       the thing that gives way is the note, which is the half that says why a
       button is disabled. On its own line it stays readable; with room to
       spare nothing wraps and the bar is unchanged. */
    flex-wrap: wrap;
    row-gap: 8px;
    gap: 8px;
    padding: 12px 20px;
    border-block-start: 1px solid var(--border-subtle);
    background: var(--surface-sunken);
    /* Its own stacking context, above a QDrawer Overlay: the catcher covers
       the whole panel to dismiss a save menu, and the menu's own items have
       to stay clickable through it. */
    position: relative;
    z-index: 5;
}

/* Anything that belongs on the left of the bar - a delete, or a note on
   what is about to be saved. */
.drw-foot-lead { margin-inline-end: auto; }

/* Buttons ganged to the right of a bar that is not .drw-foot - the
   DrawerHost-hosted drawers keep their own footer (.dr-foot) and still
   want the actions together at the end of it. */
.drw-foot-btns {
    display: flex;
    justify-content: flex-end;
    gap: 8px;
}

/* A note or a readiness ring at one end, the buttons at the other. For a
   .drw-foot use .drw-foot-lead instead; this is for the footers QDrawer does
   not draw - the DrawerHost-hosted drawers' own .dr-foot. */
.drw-foot-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
}

/* A form-level error on the action bar rather than at the top of the body.
   The button it explains is here, and at the top of a six-section form the
   message would be scrolled out of sight by the time Save is pressed.
   flex-basis 100% is what puts it on its own row above the buttons: the bar
   already wraps, so it needs no rule of its own to break the line. */
.drw-foot > .drw-alert { flex: 1 1 100%; }

@media (max-width: 640px) {
    .drw-head { padding: 14px 16px 12px; }
    .drw-body { padding: 16px; }
    .drw-foot { padding: 12px 16px; }

    /* Two stranded buttons on a full-width bar; stretched they are
       reachable with a thumb. */
    .drw-foot .btn { flex: 1 1 auto; justify-content: center; }
    .drw-foot-lead { margin-inline-end: 0; }
}


/* ============================================================
   21. Recruiter: Template library (tpl-*)

   The two-pane shape every template section in Settings uses: what
   the workspace has on the left, what the selected one says on the
   right. Email built it first and SMS was a second copy of the same
   screen written from scratch, so it is prefixed for templates
   rather than for email, and "22." below adds only the parts an SMS
   has that an email does not - a segment budget and a handset.

   Settings' email section was a table of names beside a panel that
   showed the selected row's category and a sentence telling you to
   press Edit. The template's actual text - the only thing anyone
   opens this screen to read - was not on the page at all, though
   the list already carries it: TemplateSummaryDto.Body comes down
   with every row.

   So the right pane shows the message, and the left becomes a list
   of what is in the workspace rather than a grid of four columns
   and three ghost buttons per row. The row is one button that
   selects; the actions live once, in the pane's header, acting on
   the template the reader is looking at.

   Editing moved to a drawer (QDrawer, "20." above). The body field
   is the tallest control in Settings, and in a centred modal it was
   a 220px textarea inside a box that could not grow.

   What is here:
     .tpl        the two panes - list and message
     .tpl-row    one template in the list, .is-on when selected
     .tpl-msg    the message pane: header, then the body as sent
     .tpl-sheet  the template text itself, set as a message
     .tpl-tok    a merge token inside a rendered message
   ============================================================ */

.tpl {
    display: grid;
    /* The list is a fixed lane and the message takes the rest: the
       message is what is being read, so it gets the slack. */
    grid-template-columns: minmax(240px, 320px) minmax(0, 1fr);
    gap: 12px;
    align-items: start;
}

/* Beside the 244px settings rail there is not room for two panes much
   before this; below it the message goes under the list rather than
   into a column narrower than the text it holds. */
@media (max-width: 1180px) {
    .tpl { grid-template-columns: minmax(0, 1fr); }
}

/* .card carries a radius but no clipping - nothing in the product had needed
   it, because a card's first child had always been padded away from the
   corner. These two panes start with a row that paints to the edge, so
   without this the selected row's fill and its marker square off the top of
   the card. */
.tpl-pane { overflow: hidden; }

/* ---------- The list ---------- */

.tpl-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    padding: 10px 14px;
    border-block-end: 1px solid var(--border-subtle);
}

/* Capped so the list cannot push the message pane down the page when a
   workspace has forty templates; it scrolls in its own lane instead. */
.tpl-rows {
    max-height: min(560px, 70vh);
    overflow-y: auto;
    overscroll-behavior: contain;
}

.tpl-row {
    position: relative;
    display: flex;
    align-items: flex-start;
    gap: 10px;
    width: 100%;
    padding: 10px 14px;
    border: none;
    border-block-end: 1px solid var(--border-subtle);
    background: transparent;
    text-align: start;
    font: inherit;
    color: inherit;
    cursor: pointer;
    transition: background-color var(--dur-fast) var(--ease-out);
}

.tpl-row:last-child { border-block-end: none; }
.tpl-row:hover { background: var(--n-25); }
.tpl-row:focus-visible { outline: none; box-shadow: inset 0 0 0 2px var(--border-focus); }

.tpl-row.is-on { background: var(--p-50); }

/* The same marker the settings rail draws on its open row, so the two
   lists in this column agree on what "you are here" looks like. */
.tpl-row.is-on::before {
    content: "";
    position: absolute;
    inset-block: 8px;
    inset-inline-start: 0;
    width: 2px;
    border-radius: var(--r-full);
    background: var(--p-700);
}

.tpl-ico {
    display: grid;
    place-items: center;
    width: 28px;
    height: 28px;
    flex-shrink: 0;
    margin-block-start: 1px;
    border-radius: var(--r-md);
    background: var(--n-75);
    color: var(--text-secondary);
    box-shadow: inset 0 0 0 1px var(--border-subtle);
}

.tpl-row.is-on .tpl-ico {
    background: var(--surface-raised);
    color: var(--p-700);
    box-shadow: var(--shadow-xs);
}

.tpl-txt { flex: 1; min-width: 0; }

/* The row's contents are spans - it is a button, and a button may not hold
   block elements - so the ones that stack have to be told to. .tpl-snip is
   not among them: .clamp2 already makes it a block-level -webkit-box, and a
   display of its own here would win on order and undo the clamp. */
.tpl-nm {
    display: block;
    font-size: var(--fs-md);
    font-weight: var(--fw-semibold);
    letter-spacing: -0.005em;
    color: var(--text-primary);
}

.tpl-tags {
    display: flex;
    align-items: center;
    gap: 6px;
    flex-wrap: wrap;
    margin-block-start: 3px;
}

.tpl-cat { font-size: var(--fs-xs); color: var(--text-tertiary); }

/* Two lines of the message under the name. It is what tells two
   templates called "Interview invitation" apart. */
.tpl-snip {
    margin: 4px 0 0;
    font-size: var(--fs-xs);
    line-height: var(--lh-snug);
    color: var(--text-tertiary);
}

/* ---------- The message pane ---------- */

.tpl-msg-head {
    display: flex;
    align-items: flex-start;
    gap: 10px;
    flex-wrap: wrap;
    padding: 11px 14px;
    border-block-end: 1px solid var(--border-subtle);
}

.tpl-msg-txt { flex: 1 1 220px; min-width: 0; }

.tpl-msg-nm {
    font-size: var(--fs-lg);
    font-weight: var(--fw-semibold);
    letter-spacing: -0.01em;
    color: var(--text-primary);
}

.tpl-msg-tags {
    display: flex;
    align-items: center;
    gap: 6px;
    flex-wrap: wrap;
    margin-block-start: 4px;
}

.tpl-msg-acts {
    display: flex;
    align-items: center;
    gap: 6px;
    flex-wrap: wrap;
    margin-inline-start: auto;
}

.tpl-msg-body { padding: 14px; }

/* The template text, set the way the email will be: a sheet on the
   page's sunken ground, one readable column, line breaks kept.
   pre-wrap is load-bearing - the blank line between two paragraphs is
   part of the message, and collapsing it here shows a shape the send
   does not use. */
.tpl-sheet {
    max-width: 74ch;
    padding: 16px 18px;
    border: 1px solid var(--border-subtle);
    border-radius: var(--r-lg);
    background: var(--n-25);
    box-shadow: var(--shadow-xs);
    font-size: var(--fs-md);
    line-height: var(--lh-relaxed);
    color: var(--text-primary);
    white-space: pre-wrap;
    overflow-wrap: anywhere;
}

/* A template with a body of nothing renders as an empty box otherwise,
   which reads as a broken pane rather than an unwritten message. */
.tpl-sheet-empty {
    color: var(--text-tertiary);
    font-style: italic;
    white-space: normal;
}

/* A merge token inside a template shown as written. It is the one
   part of the message that is not the message - marking it is what
   lets a reader check a name spelled {{candidate.frist_name}}
   against the list of tokens that actually resolve. */
.tpl-tok {
    padding: 0 3px;
    border-radius: var(--r-sm);
    background: var(--p-50);
    box-shadow: inset 0 0 0 1px var(--p-100);
    font-family: var(--font-mono);
    font-size: 0.92em;
    color: var(--p-700);
}

/* Nothing fills this one in, so it is sent to the candidate exactly
   as it is written here. */
.tpl-tok.is-bad {
    background: var(--danger-50);
    box-shadow: inset 0 0 0 1px var(--danger-100);
    color: var(--danger-700);
}

/* Why this template cannot be edited in place, above the message it
   belongs to. */
.tpl-note {
    display: flex;
    align-items: flex-start;
    gap: 8px;
    margin-block-end: 12px;
    padding: 9px 12px;
    border-radius: var(--r-md);
    background: var(--info-50);
    color: var(--info-700);
    font-size: var(--fs-sm);
    line-height: var(--lh-snug);
}

.tpl-note svg { flex-shrink: 0; margin-block-start: 1px; }

/* The same shape when what it has to say is that the read failed -
   a permission the workspace does not have, or a row that went away
   between the render and the click. */
.tpl-note-bad {
    background: var(--danger-50);
    color: var(--danger-700);
}

/* The one line under the sheet, on what the tokens in it are. Tertiary and
   small: it explains the surface above it rather than being read with it. */
.tpl-cap {
    margin: 8px 0 0;
    max-width: 74ch;
    font-size: var(--fs-xs);
    line-height: var(--lh-snug);
    color: var(--text-tertiary);
}

/* ---------- The editor's body field ---------- */

/* Monospace, and tall enough to hold a real email: the merge tokens are
   code in the middle of prose, and they are the part a reader has to
   check character by character. */
.tpl-code {
    min-height: 260px;
    font-family: var(--font-mono);
    font-size: var(--fs-sm);
    line-height: var(--lh-normal);
}


/* ============================================================
   22. Recruiter: SMS templates (stpl-*)

   The SMS section was a stack of rows in one card: the name in a
   170px lane, the body in a grey box beside it, three ghost buttons
   in a third column, and the editor in a centred modal where the
   length meter, four warnings and the delivered preview stacked
   under a textarea and pushed Save below the fold.

   It is the same two-pane library as email ("21." above) - the list
   on the left, the selected template on the right - plus the two
   things an SMS has that an email does not:

     a budget   160 GSM-7 characters, 70 if one curly quote drags
                the message into Unicode, and every segment past the
                first is another message billed. So the meter is not
                a footnote under the field: it is a bar with the
                number that decides the cost sitting on it.

     a handset  an SMS is read on a lock screen in one glance, and a
                12px paragraph in a bordered box does not show that.
                The preview is set as the message bubble it arrives
                as, with the tokens resolved to the sample values the
                send would put there.

   What is here:
     .stpl-msg     the pane's body - handset beside the numbers
     .stpl-phone   the handset the delivered message is set in
     .stpl-bubble  the message as the candidate receives it
     .stpl-meter   the segment budget as a bar
     .stpl-facts   encoding, parts, characters left
     .stpl-toks    the merge tokens offered under the message field
   ============================================================ */

/* The handset is a fixed lane: a message bubble stretched to 700px
   is no longer a preview of anything. The numbers take the slack. */
.stpl-msg {
    display: grid;
    grid-template-columns: minmax(0, 296px) minmax(0, 1fr);
    gap: 16px;
    align-items: start;
    padding: 14px;
}

/* Below this the two would each be too narrow to read; the numbers
   go under the handset instead of beside it. */
@media (max-width: 900px) {
    .stpl-msg { grid-template-columns: minmax(0, 1fr); }
}

/* ---------- The handset ---------- */

.stpl-phone {
    padding: var(--sp-5);
    border: 1px solid var(--border-strong);
    /* A large radius and an inset ring: enough to read as a device
       without drawing a phone with a home button on a settings
       page. */
    border-radius: var(--r-2xl);
    background: var(--n-75);
    box-shadow: var(--shadow-sm), inset 0 0 0 1px var(--n-0);
}

/* Who the message is from, where a handset puts it. It is the label
   for the bubble under it, so it stays quiet. */
.stpl-phone-bar {
    display: flex;
    align-items: center;
    gap: var(--sp-3);
    padding: 0 var(--sp-2) var(--sp-4);
    border-block-end: 1px solid var(--border-subtle);
    font-size: var(--fs-2xs);
    font-weight: var(--fw-bold);
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--text-tertiary);
}

.stpl-phone-bar svg { flex-shrink: 0; }

.stpl-thread {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: var(--sp-3);
    /* A short template leaves the frame a sliver otherwise, and the
       preview jumps in height between one template and the next. */
    min-height: 104px;
    padding-block-start: var(--sp-5);
}

/* Received, not sent: this is the candidate's screen, so the bubble
   sits on their left with the tail on the bottom-inline-start
   corner - the one shape everyone reads as "arrived". */
.stpl-bubble {
    max-width: 94%;
    padding: 9px 13px;
    border-radius: var(--r-xl) var(--r-xl) var(--r-xl) var(--r-xs);
    background: var(--surface-raised);
    box-shadow: var(--shadow-xs);
    font-size: var(--fs-sm);
    line-height: var(--lh-normal);
    color: var(--text-primary);
    /* The blank line between two sentences is part of an SMS and is
       charged for; collapsing it here shows a message the carrier
       does not send. */
    white-space: pre-wrap;
    overflow-wrap: anywhere;
}

/* A template with nothing in it yet - an empty bubble reads as a
   broken preview rather than an unwritten message. */
.stpl-bubble-empty {
    color: var(--text-tertiary);
    font-style: italic;
    white-space: normal;
}

/* What the carrier does with a message over the budget, said where
   the message is rather than in a box away from it. */
.stpl-parts {
    align-self: stretch;
    margin-block-start: var(--sp-1);
    font-size: var(--fs-2xs);
    line-height: var(--lh-snug);
    color: var(--text-tertiary);
}

/* ---------- The budget ---------- */

.stpl-meter { margin-block-end: var(--sp-6); }

/* In the drawer it is a strip between the message field and the
   tokens offered under it, not a block in a column of its own. */
.drw-field > .stpl-meter { margin-block: 10px 0; }

.stpl-meter-head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--sp-4);
}

.stpl-meter-k {
    font-size: var(--fs-2xs);
    font-weight: var(--fw-bold);
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--text-tertiary);
}

/* Tabular and monospaced: this number changes on every keystroke in
   the editor, and proportional digits make the whole row twitch. */
.stpl-meter-n {
    font-family: var(--font-mono);
    font-size: var(--fs-sm);
    font-variant-numeric: tabular-nums;
    color: var(--text-secondary);
}

.stpl-track {
    height: 6px;
    margin-block-start: var(--sp-3);
    border-radius: var(--r-full);
    background: var(--n-100);
    overflow: hidden;
}

.stpl-fill {
    height: 100%;
    border-radius: inherit;
    background: var(--success-500);
    transition: width var(--dur-mid) var(--ease-out),
                background-color var(--dur-mid) var(--ease-out);
}

/* The same three states the pill on the row uses, so a glance at
   either says the same thing: fits, costs more per character,
   costs another message. */
.stpl-fill.is-gold { background: var(--gold-400); }
.stpl-fill.is-warn { background: var(--warning-500); }

@media (prefers-reduced-motion: reduce) {
    .stpl-fill { transition: none; }
}

/* ---------- The facts under it ---------- */

.stpl-facts {
    margin: 0;
    border-block-start: 1px solid var(--border-subtle);
}

.stpl-fact {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--sp-5);
    padding: 7px 0;
    border-block-end: 1px solid var(--border-subtle);
}

.stpl-fact-k { font-size: var(--fs-xs); color: var(--text-tertiary); }

.stpl-fact-v {
    font-size: var(--fs-sm);
    font-weight: var(--fw-semibold);
    font-variant-numeric: tabular-nums;
    color: var(--text-primary);
}

/* ---------- The template as written ---------- */

/* Under the numbers rather than beside the handset: the delivered
   message is what the pane is for, and this is the source it was
   rendered from - read second, when a token in it is in question. */
.stpl-written { margin-block-start: var(--sp-6); }

.stpl-written .tpl-sheet {
    margin-block-start: var(--sp-3);
    padding: 11px 13px;
    font-size: var(--fs-sm);
    line-height: var(--lh-normal);
}

/* ---------- The editor ---------- */

/* Monospace and short: an SMS is two sentences, and a 260px field
   like the email editor's invites a message that will not fit. */
.stpl-input {
    min-height: 132px;
    font-family: var(--font-mono);
    font-size: var(--fs-sm);
    line-height: var(--lh-normal);
}

/* The tokens the send path actually resolves. Offering them is
   cheaper than validating a guessed one after the fact - the send
   leaves a token it cannot fill in the message as written, so a
   typo ships to the candidate verbatim.

   Shared with the offer/JD editor ("24." below), which needs the
   same palette under the same field. It is defined here rather than
   moved up to "21." so the SMS page keeps the class names it was
   written with; .tpl-* is the name a new caller should reach for. */
.tpl-toks,
.stpl-toks {
    display: flex;
    flex-wrap: wrap;
    gap: var(--sp-2);
    margin-block-start: var(--sp-3);
}

.tpl-tok-btn,
.stpl-tok-btn {
    padding: 3px 9px;
    border: 1px solid var(--border-default);
    border-radius: var(--r-full);
    background: var(--n-25);
    font-family: var(--font-mono);
    font-size: var(--fs-2xs);
    color: var(--text-secondary);
    cursor: pointer;
    transition: background-color var(--dur-fast) var(--ease-out),
                border-color var(--dur-fast) var(--ease-out),
                color var(--dur-fast) var(--ease-out);
}

.tpl-tok-btn:hover,
.stpl-tok-btn:hover {
    background: var(--p-50);
    border-color: var(--p-200);
    color: var(--p-700);
}

.tpl-tok-btn:focus-visible,
.stpl-tok-btn:focus-visible { outline: none; box-shadow: var(--shadow-focus); }

/* The handset again inside the drawer, where it is a preview beside
   a field rather than the subject of the pane. */
.stpl-phone-sm { padding: var(--sp-4); }
.stpl-phone-sm .stpl-thread { min-height: 0; padding-block-start: var(--sp-4); }

/* ============================================================
   21. Recruiter: Settings → Team & roles (tm-*)

   Two questions live on this screen, and they were stacked in one
   column: a members table, then a nine-column permission grid below
   the fold. Nobody scrolled to the second, and the first answered
   "what is Priya's role" with a bare <select> that a stray scroll
   over the row could change.

   So: a tab each. People carries the finding tools the list never
   had - a search, a role and desk filter, a status segment - and
   the role cell becomes a chip that opens a picker where each role
   says what it means. Roles carries the grid for ONE role at a
   time, picked from a rail that also shows who holds it, with a
   checkbox per function and a data-scope picker per row.

   What is here:
     .tm-tabs      People | Roles & permissions
     .tm-bar       the people toolbar: find, filters, seats
     .tm-chip      the role cell, and .tm-pop the picker it opens
     .tm-roles     the roles tab's rail-plus-grid frame
     .tm-grid      the permission grid, .tm-chk one grant
     .tm-scope     how much data the grant reaches
     .tm-drw-*     the two drawers' bodies

   Below 960px the roles rail sits above the grid rather than beside
   it: at that width a 296px rail leaves the grid too narrow for the
   seven function columns to stay legible.
   ============================================================ */

/* ---------- Tabs ---------- */

.tm-tabs {
    display: flex;
    align-items: center;
    gap: 2px;
    padding-inline: 2px;
    border-block-end: 1px solid var(--border-default);
}

.tm-tab {
    display: inline-flex;
    align-items: center;
    gap: 7px;
    height: 36px;
    padding: 0 12px;
    border: 0;
    background: transparent;
    font-family: inherit;
    font-size: var(--fs-sm);
    font-weight: var(--fw-medium);
    color: var(--text-tertiary);
    cursor: pointer;
    transition: color var(--dur-fast) var(--ease-out);
}

.tm-tab:hover { color: var(--text-primary); }
.tm-tab:focus-visible { outline: none; box-shadow: var(--shadow-focus); }

.tm-tab.is-on {
    font-weight: var(--fw-semibold);
    color: var(--p-700);
    box-shadow: inset 0 -2px 0 var(--p-700);
}

.tm-tab-ico { display: grid; place-items: center; }

/* ---------- People: toolbar ---------- */

.tm-bar {
    display: flex;
    align-items: center;
    gap: var(--sp-4);
    flex-wrap: wrap;
    padding: var(--sp-5) var(--sp-8);
    border-block-end: 1px solid var(--border-subtle);
}

.tm-find { position: relative; flex: 0 1 250px; min-width: 180px; }

.tm-find-ico {
    position: absolute;
    inset-block: 0;
    inset-inline-start: 10px;
    display: grid;
    place-items: center;
    color: var(--n-400);
    pointer-events: none;
}

.tm-find input {
    width: 100%;
    height: var(--h-md);
    padding-block: 0;
    padding-inline: 30px 10px;
    border: 1px solid var(--border-default);
    border-radius: var(--r-md);
    background: var(--surface-raised);
    font-family: inherit;
    font-size: var(--fs-sm);
    color: var(--text-primary);
}

.tm-find input::placeholder { color: var(--n-400); }
.tm-find input:focus-visible { outline: none; border-color: var(--border-focus); box-shadow: var(--shadow-focus); }

/* The two dropdowns beside it. Same height as the search so the row
   reads as one control strip rather than three of different sizes. */
.tm-pick {
    height: var(--h-md);
    padding: 0 8px;
    border: 1px solid var(--border-default);
    border-radius: var(--r-md);
    background: var(--surface-raised);
    font-family: inherit;
    font-size: var(--fs-sm);
    color: var(--text-primary);
    cursor: pointer;
}

.tm-pick:focus-visible { outline: none; border-color: var(--border-focus); box-shadow: var(--shadow-focus); }

/* Status is four values a reader flips between rather than searches. */
.tm-seg {
    display: inline-flex;
    align-items: center;
    gap: 2px;
    height: var(--h-md);
    padding: 2px;
    border: 1px solid var(--border-default);
    border-radius: var(--r-md);
    background: var(--surface-page);
}

.tm-seg-b {
    height: 26px;
    padding: 0 10px;
    border: 0;
    border-radius: var(--r-sm);
    background: transparent;
    font-family: inherit;
    font-size: var(--fs-sm);
    font-weight: var(--fw-medium);
    color: var(--text-tertiary);
    cursor: pointer;
}

.tm-seg-b:hover { color: var(--text-primary); }
.tm-seg-b:focus-visible { outline: none; box-shadow: var(--shadow-focus); }

.tm-seg-b.is-on {
    background: var(--surface-raised);
    box-shadow: var(--shadow-sm);
    font-weight: var(--fw-semibold);
    color: var(--text-primary);
}

.tm-seats { margin-inline-start: auto; display: flex; align-items: center; gap: var(--sp-5); }
.tm-seats-txt { text-align: end; }

.tm-seats-n { display: block; font-size: var(--fs-sm); font-weight: var(--fw-semibold); font-variant-numeric: tabular-nums; }
.tm-seats-sub { display: block; font-size: var(--fs-2xs); color: var(--text-tertiary); }

.tm-seats-bar {
    width: 92px;
    height: 6px;
    border-radius: var(--r-full);
    background: var(--n-100);
    overflow: hidden;
}

.tm-seats-bar > span { display: block; height: 100%; border-radius: var(--r-full); background: var(--p-700); }

.tm-none { margin: 0; padding: var(--sp-16) var(--sp-8); text-align: center; font-size: var(--fs-sm); color: var(--text-tertiary); }

/* ---------- People: rows ---------- */

.tm-who { min-width: 0; }
.tm-who-name { display: flex; align-items: center; gap: var(--sp-3); font-weight: var(--fw-medium); }

.tm-you {
    padding: 1px 6px;
    border-radius: var(--r-full);
    background: var(--p-50);
    color: var(--p-700);
    font-size: 9.5px;
    font-weight: var(--fw-semibold);
    letter-spacing: 0.02em;
    text-transform: uppercase;
}

.tm-cell-muted { font-size: var(--fs-sm); color: var(--text-secondary); }
.tm-cell-when { font-size: var(--fs-xs); color: var(--text-tertiary); font-family: var(--font-mono); }
.tm-when-gold { color: var(--gold-700); font-family: var(--font-sans); }

.tm-acts { text-align: end; white-space: nowrap; }
.tm-acts > * + * { margin-inline-start: var(--sp-2); }

/* An outstanding invite is a different kind of row: it is a promise,
   not a person at work. The tint fades out so it marks the row
   without colouring the whole table. */
.tm-row-invited { background: linear-gradient(90deg, rgba(255, 250, 235, 0.75), rgba(255, 250, 235, 0) 45%); }
.tm-row-off { opacity: 0.55; }

.btn.tm-danger { color: var(--danger-700); }
.btn.tm-danger:hover { background: var(--danger-50); color: var(--danger-700); }

/* ---------- The role chip and its picker ---------- */

.tm-role-cell { position: relative; }

.tm-chip {
    display: inline-flex;
    align-items: center;
    gap: var(--sp-3);
    height: 26px;
    padding-block: 0;
    padding-inline: 9px 6px;
    border: 1px solid var(--border-default);
    border-radius: var(--r-full);
    background: var(--surface-raised);
    font-family: inherit;
    font-size: var(--fs-sm);
    font-weight: var(--fw-medium);
    color: var(--text-primary);
    cursor: pointer;
    transition: border-color var(--dur-fast) var(--ease-out);
}

.tm-chip > svg { color: var(--n-400); }
.tm-chip:hover { border-color: var(--border-strong); }
.tm-chip:focus-visible { outline: none; box-shadow: var(--shadow-focus); }

.tm-chip.is-open {
    border-color: var(--p-700);
    font-weight: var(--fw-semibold);
    color: var(--p-700);
    box-shadow: var(--shadow-focus);
}

.tm-chip.is-open > svg { color: currentColor; }

/* Covers the page so the next click anywhere closes the picker.
   Transparent and inert to the eye; it is a button so it is reachable
   with a keyboard's Escape-equivalent (a click) rather than a trap. */
.tm-scrim {
    position: fixed;
    inset: 0;
    z-index: calc(var(--z-dropdown) - 1);
    border: 0;
    padding: 0;
    background: transparent;
    cursor: default;
}

.tm-pop {
    position: absolute;
    z-index: var(--z-dropdown);
    inset-inline-start: 8px;
    inset-block-start: calc(100% - 4px);
    width: 274px;
    padding: 5px;
    background: var(--surface-raised);
    border: 1px solid var(--border-default);
    border-radius: var(--r-lg);
    box-shadow: var(--shadow-lg);
}

.tm-pop-lab {
    margin: 0;
    padding: 7px 9px 5px;
    font-size: var(--fs-2xs);
    font-weight: var(--fw-semibold);
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--text-tertiary);
}

.tm-pop-rows { display: flex; flex-direction: column; gap: 1px; }

.tm-pop-row {
    display: flex;
    align-items: flex-start;
    gap: var(--sp-4);
    width: 100%;
    padding: 7px 9px;
    border: 0;
    border-radius: var(--r-sm);
    background: transparent;
    font-family: inherit;
    text-align: start;
    cursor: pointer;
}

.tm-pop-row:hover { background: var(--surface-hover); }
.tm-pop-row:focus-visible { outline: none; box-shadow: var(--shadow-focus); }
.tm-pop-row.is-on { background: var(--p-50); }

.tm-pop-tick { flex-shrink: 0; width: 14px; display: grid; place-items: center; color: var(--p-700); }
.tm-pop-txt { min-width: 0; }

.tm-pop-name { display: block; font-size: var(--fs-sm); font-weight: var(--fw-medium); color: var(--text-primary); }
.tm-pop-row.is-on .tm-pop-name { font-weight: var(--fw-semibold); color: var(--p-700); }

.tm-pop-sub { display: block; font-size: var(--fs-xs); line-height: 1.35; color: var(--text-tertiary); }
.tm-pop-row.is-on .tm-pop-sub { color: var(--p-600); }

.tm-pop-foot {
    margin: var(--sp-2) 0 0;
    padding: 7px 9px;
    border-block-start: 1px solid var(--border-subtle);
    font-size: var(--fs-xs);
    line-height: 1.4;
    color: var(--text-tertiary);
}

/* ---------- Roles tab: rail plus grid ---------- */

.tm-roles { display: grid; grid-template-columns: 296px minmax(0, 1fr); align-items: stretch; }

.tm-rolelist {
    display: flex;
    flex-direction: column;
    gap: 2px;
    padding: var(--sp-4);
    border-inline-end: 1px solid var(--border-default);
}

.tm-rolelist-lab {
    margin: 0;
    padding: 7px 10px 6px;
    font-size: var(--fs-2xs);
    font-weight: var(--fw-semibold);
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--text-tertiary);
}

.tm-role {
    display: flex;
    align-items: center;
    gap: var(--sp-5);
    padding: 9px 12px;
    border: 0;
    border-radius: var(--r-md);
    background: transparent;
    font-family: inherit;
    text-align: start;
    cursor: pointer;
}

.tm-role:hover { background: var(--surface-hover); }
.tm-role:focus-visible { outline: none; box-shadow: var(--shadow-focus); }
.tm-role.is-on { background: var(--p-50); box-shadow: inset 2px 0 0 var(--p-700); }

.tm-role-txt { flex: 1; min-width: 0; }
.tm-role-name { display: block; font-size: var(--fs-md); font-weight: var(--fw-medium); color: var(--text-primary); }
.tm-role.is-on .tm-role-name { font-weight: var(--fw-semibold); color: var(--p-700); }

.tm-role-sub {
    display: block;
    font-size: var(--fs-xs);
    line-height: 1.35;
    color: var(--text-tertiary);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.tm-role.is-on .tm-role-sub { color: var(--p-600); }

.tm-role-n {
    flex-shrink: 0;
    font-size: var(--fs-xs);
    font-family: var(--font-mono);
    font-variant-numeric: tabular-nums;
    color: var(--text-tertiary);
}

.tm-role.is-on .tm-role-n { font-weight: var(--fw-semibold); color: var(--p-700); }

.tm-rolelist-foot {
    margin: auto 0 0;
    padding: var(--sp-5);
    border-block-start: 1px solid var(--border-subtle);
    font-size: var(--fs-xs);
    line-height: 1.45;
    color: var(--text-tertiary);
}

.tm-grid-wrap { min-width: 0; display: flex; flex-direction: column; }

.tm-grid-head {
    display: flex;
    align-items: flex-start;
    gap: var(--sp-6);
    padding: var(--sp-6) var(--sp-8);
    border-block-end: 1px solid var(--border-subtle);
}

.tm-grid-head-txt { flex: 1; min-width: 0; }
.tm-grid-title { margin: 0; font-size: var(--fs-md); font-weight: var(--fw-semibold); }
.tm-grid-sub { margin: 2px 0 0; font-size: var(--fs-xs); color: var(--text-tertiary); }

/* What a tick actually changes. Warned rather than whispered when the
   controls are live: the grant is a platform-level row, so this is the
   one place the screen has to say that out loud. */
.tm-policy-note {
    display: flex;
    align-items: flex-start;
    gap: var(--sp-4);
    margin: 0;
    padding: var(--sp-5) var(--sp-8);
    border-block-end: 1px solid var(--border-subtle);
    font-size: var(--fs-xs);
    line-height: 1.45;
    color: var(--text-tertiary);
}

.tm-policy-note.is-warn { background: var(--gold-50); color: var(--gold-700); }
.tm-policy-ico { flex-shrink: 0; display: grid; place-items: center; padding-block-start: 1px; }

.tm-policy-err {
    display: block;
    margin: 0;
    padding: var(--sp-5) var(--sp-8);
    background: var(--danger-50);
    border-block-end: 1px solid var(--danger-100);
    font-size: var(--fs-sm);
    color: var(--danger-700);
}

/* ---------- The grid itself ---------- */

.tm-grid { min-width: 640px; }
.tm-grid td { padding-block: var(--sp-4); }

/* The grid runs to twenty-odd rows and the column headings used to leave the
   screen on the first flick, so the seven identical columns of ticks below the
   fold were unlabelled - "is that Delete or Export?" meant scrolling back up.
   The pinned header and the region it pins inside are .tbl's and .tbl-wrap's
   now; what stays here is this grid's own height, which is shorter than the
   shared cap so that the role rail and the policy warning stay in view while
   the ticks scroll rather than being pushed off the top.

   overscroll-behavior, because this pane is read by dragging across seven
   columns of checkboxes: reaching its last row should not then carry the page
   away underneath the pointer. */
.tm-grid-wrap > .tbl-wrap {
    max-block-size: clamp(280px, calc(100vh - 320px), 720px);
    overscroll-behavior: contain;
}

/* The row master. It reuses .checkbox rather than .tm-chk because a partly
   granted row has to draw the dash, and only .checkbox can; what follows is
   what .tm-chk gives its native siblings and .checkbox does not. The rule on
   the column keeps the master reading as the head of the seven functions
   rather than as an eighth one. */
.tm-chk-all { margin: 0; vertical-align: middle; }
.tm-chk-all:disabled { cursor: not-allowed; opacity: 0.45; }

.tm-grid tbody td.tm-all-col { border-inline-end: 1px solid var(--border-subtle); }
.tm-grid thead th.tm-all-col { box-shadow: inset -1px -1px 0 var(--border-subtle); }

/* A nav group, as a band rather than a row: twenty page rows in one
   flat list stop reading as the sidebar the person already knows. */
.tm-grp > th {
    padding: 7px var(--sp-8) 5px;
    background: var(--n-25);
    border-block: 1px solid var(--border-subtle);
    text-align: start;
    font-size: var(--fs-2xs);
    font-weight: var(--fw-semibold);
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--text-tertiary);
}

.tm-sect { display: flex; align-items: center; gap: var(--sp-3); font-size: var(--fs-sm); white-space: nowrap; }
.tm-lock { display: inline-grid; place-items: center; color: var(--n-400); }

/* A page the role cannot open at all is dimmed, not dropped: "no access
   to Invoices" is part of what defines the role. The checkboxes stay
   live, because turning one on is how that is undone. */
.tm-row-none .tm-sect { color: var(--text-tertiary); }
.tm-row-none .tm-scope { opacity: 0.6; }

.tm-chk {
    width: 16px;
    height: 16px;
    margin: 0;
    accent-color: var(--p-700);
    cursor: pointer;
}

.tm-chk:disabled { cursor: not-allowed; opacity: 0.45; }
.tm-chk:focus-visible { outline: none; box-shadow: var(--shadow-focus); }

.tm-scope {
    width: 100%;
    max-width: 160px;
    height: var(--h-sm);
    padding: 0 6px;
    border: 1px solid var(--border-default);
    border-radius: var(--r-sm);
    background: var(--surface-raised);
    font-family: inherit;
    font-size: var(--fs-xs);
    color: var(--text-primary);
    cursor: pointer;
}

.tm-scope:disabled { background: var(--surface-page); color: var(--text-tertiary); cursor: not-allowed; }
.tm-scope:focus-visible { outline: none; border-color: var(--border-focus); box-shadow: var(--shadow-focus); }

.tm-legend { gap: var(--sp-12); }
.tm-legend-i { display: inline-flex; align-items: center; gap: var(--sp-3); }

.tm-legend-box {
    width: 12px;
    height: 12px;
    border: 1px solid var(--border-strong);
    border-radius: 3px;
    background: var(--surface-raised);
}

.tm-legend-box.is-on { border-color: var(--p-700); background: var(--p-700); }

/* ---------- Both drawers ---------- */

.tm-drw-id { display: flex; align-items: center; gap: var(--sp-8); margin-block-end: var(--sp-10); }
.tm-drw-id-txt { min-width: 0; display: flex; flex-direction: column; gap: var(--sp-3); }
.tm-drw-id-sub { margin: 0; font-size: var(--fs-sm); color: var(--text-secondary); }

.tm-fld { margin-block-end: var(--sp-10); }
.tm-fld-2 { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--sp-5); margin-block-end: var(--sp-10); }

.tm-lab {
    margin-block-end: 5px;
    font-size: var(--fs-xs);
    font-weight: var(--fw-semibold);
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--text-tertiary);
}

.tm-inp {
    width: 100%;
    height: 34px;
    padding: 0 12px;
    border: 1px solid var(--border-default);
    border-radius: var(--r-md);
    background: var(--surface-raised);
    font-family: inherit;
    font-size: var(--fs-md);
    color: var(--text-primary);
}

.tm-inp:focus-visible { outline: none; border-color: var(--border-focus); box-shadow: var(--shadow-focus); }
.tm-area { min-height: 72px; padding: 10px 12px; line-height: var(--lh-normal); resize: vertical; }

/* The eight roles as cards rather than a dropdown: the choice carries a
   sentence each, and a <select> can show only the one already picked. */
.tm-rolecards { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--sp-3); }

.tm-rolecard {
    display: flex;
    align-items: flex-start;
    gap: var(--sp-4);
    padding: 9px 10px;
    border: 1px solid var(--border-default);
    border-radius: var(--r-md);
    background: var(--surface-raised);
    font-family: inherit;
    text-align: start;
    cursor: pointer;
    transition: border-color var(--dur-fast) var(--ease-out);
}

.tm-rolecard:hover { border-color: var(--border-strong); }
.tm-rolecard:focus-visible { outline: none; box-shadow: var(--shadow-focus); }
.tm-rolecard.is-on { border-color: var(--p-700); background: var(--p-50); box-shadow: var(--shadow-focus); }

.tm-rolecard-tick { flex-shrink: 0; width: 14px; display: grid; place-items: center; color: var(--p-700); }
.tm-rolecard-txt { min-width: 0; }
.tm-rolecard-name { display: block; font-size: var(--fs-sm); font-weight: var(--fw-medium); }
.tm-rolecard.is-on .tm-rolecard-name { font-weight: var(--fw-semibold); color: var(--p-700); }
.tm-rolecard-sub { display: block; font-size: var(--fs-xs); line-height: 1.35; color: var(--text-tertiary); }
.tm-rolecard.is-on .tm-rolecard-sub { color: var(--p-600); }

.tm-reach {
    display: flex;
    align-items: center;
    gap: var(--sp-4);
    margin: var(--sp-4) 0 0;
    padding: 9px 12px;
    border: 1px solid var(--border-default);
    border-radius: var(--r-md);
    background: var(--n-25);
    font-size: var(--fs-sm);
    color: var(--text-secondary);
}

.tm-link {
    margin-inline-start: auto;
    border: 0;
    padding: 0;
    background: none;
    font-family: inherit;
    font-size: var(--fs-sm);
    color: var(--p-700);
    cursor: pointer;
}

.tm-link:hover { text-decoration: underline; }
.tm-link:focus-visible { outline: none; box-shadow: var(--shadow-focus); }

.tm-facts { display: flex; flex-direction: column; }

.tm-fact {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--sp-6);
    padding: 7px 0;
    border-block-start: 1px solid var(--border-subtle);
    font-size: var(--fs-sm);
}

.tm-fact > :first-child { color: var(--text-tertiary); }
.tm-fact > :last-child { font-weight: var(--fw-medium); font-variant-numeric: tabular-nums; }
.tm-fact:first-child { border-block-start: none; }

.tm-danger-box {
    display: flex;
    align-items: flex-start;
    gap: var(--sp-5);
    padding: var(--sp-6);
    border: 1px solid var(--danger-200);
    border-radius: var(--r-md);
    background: var(--danger-50);
}

.tm-danger-ico { flex-shrink: 0; display: grid; place-items: center; padding-block-start: 1px; color: var(--danger-600); }
.tm-danger-txt { flex: 1; min-width: 0; }
.tm-danger-t { font-size: var(--fs-sm); font-weight: var(--fw-semibold); color: var(--danger-700); }
.tm-danger-txt p { margin: 2px 0 0; font-size: var(--fs-xs); line-height: 1.45; color: var(--danger-700); }

@media (max-width: 960px) {
    /* The rail stops being a rail: at this width it would leave the grid
       too narrow for seven function columns plus a scope picker. */
    .tm-roles { grid-template-columns: minmax(0, 1fr); }
    .tm-rolelist { border-inline-end: none; border-block-end: 1px solid var(--border-default); }
    .tm-rolelist-foot { margin-block-start: 0; }
    .tm-seats { margin-inline-start: 0; }
    .tm-rolecards { grid-template-columns: minmax(0, 1fr); }
    .tm-fld-2 { grid-template-columns: minmax(0, 1fr); }
}


/* ============================================================
   23. Candidate: My jobs rail (cnp-rail-*)

   Browse and Saved are one place with two views, and the portal
   drew them as two unrelated screens. The board carried a column
   of filter cards down its left; Saved carried a "back to jobs"
   link where that column would have been. Neither said the other
   existed, so the only route between them was a header button on
   one and a text link on the other.

   The filter column itself was the same problem the settings rail
   had before section 18: four .card boxes of bare radios and
   checkboxes, every one of them styled from an inline attribute.
   Inline styles cannot express a hover, a focus ring or a
   breakpoint, so a row gave no feedback under the pointer, showed
   nothing when tabbed to, and kept its 260px at every width.

   So both pages get one rail, and it is the product's own menu
   row, moved into the page: a group name with a hairline running
   out from it, a 44px row, a 32px tinted tile carrying the glyph,
   a 14px label, a soft chip for the count, and on the row you are
   on a pale band, an inverted white tile and a 4px marker flush
   to the card's inner edge. It is .nav-item from section 4 in a
   page rather than in a sidebar. A reader crossing from a menu
   into My Jobs meets the same row twice, which is the point - the
   rail is the menu for this section, not a filter panel that
   happens to sit where a menu would.

   What is new here is the arrow: a filter group shuts, because a
   board with four facets open is taller than the results beside
   it, and a candidate who has finished with Location should be
   able to put it away.

   What is here:
     .cnp-jobs      the two-column frame - rail plus results
     .cnp-rail      the rail: a pinned card scrolling on its own
                    under the shared .scroll-quiet scrollbar
     .cnp-rail-grp  one group, .is-shut when its arrow is turned
     .cnp-rail-head the group heading, which is the toggle
     .cnp-rail-link a nav row - Browse, Saved - .is-on for the page
     .cnp-rail-opt  one filter option, .is-on when it is selected
     .cnp-rail-n    a count, on a row or on a shut group

   The rows are .nav-item from section 4 at its own metrics, down
   to the hue pairs on the tiles, which is why those pairs are a
   standalone .hue-N there rather than part of .nav-item. What the
   rail does not take is the sidebar's violet: that accent is local
   to section 4 on purpose, and this rail stands in the candidate
   portal, where every band and pill is navy. Everything else -
   geometry, tile, badge, band, marker - is the same object.
   ============================================================ */

.cnp-jobs {
    /* Wide enough for "Executive search" beside a 32px tile and its
       count without ellipsis - the longest facet value the seeded
       data produces, against the widest row the menu draws. */
    --cnp-rail-w: 276px;

    /* Where the rail pins, matching .set: .pg-head sticks at -8px and
       stands 56px tall, so its lower edge is 48px down the scroll
       port, and 12px more is the gap between bands elsewhere. */
    --cnp-rail-stick: 60px;

    /* The chrome above the scroll port - the candidate topbar. Only
       needed to work out how much height the rail actually has. */
    --cnp-rail-chrome: 56px;

    display: grid;
    grid-template-columns: var(--cnp-rail-w) minmax(0, 1fr);
    gap: 16px;
    align-items: start;
}

/* ---------- The rail ---------- */

.cnp-rail {
    /* The menu's accent. Section 4 keeps a violet local to the sidebar
       because the product primary is navy and moving the token would
       restyle four portals; the same reasoning runs the other way
       here. This rail sits inside the candidate portal, where every
       band, pill and chart is navy, so it takes the navy and leaves
       the violet in the sidebar it belongs to. One place to change if
       that is ever decided differently. */
    --cnp-accent: var(--p-700);
    --cnp-accent-strong: var(--p-800);
    --cnp-band: var(--p-50);
    /* The far end of the band's gradient - a blue so pale it is nearly
       the card, which is what stops a 276px row reading as a slab. */
    --cnp-band-fade: #f9fbff;

    /* Named because the marker on the selected row is flush to the
       card's inner edge and has to cancel exactly this much. */
    --cnp-rail-pad: 6px;

    position: sticky;
    inset-block-start: var(--cnp-rail-stick);
    /* Under .pg-head, which is 30: the rail scrolls up to the header
       and stops behind it rather than over it. */
    z-index: 10;
    /* A sticky box taller than its scroll port stops following the
       scroll entirely - it pins once and then rides up out of view.
       Three open facet groups clear a laptop viewport easily, so the
       rail is capped and the overflow goes to its own scrollbar. */
    max-height: calc(100vh - var(--cnp-rail-chrome) - var(--cnp-rail-stick) - 12px);
    overflow-y: auto;
    /* Reaching the end of the rail must not start scrolling the board
       underneath it. */
    overscroll-behavior: contain;
    padding: var(--cnp-rail-pad);
    background: var(--surface-raised);
    border: 1px solid var(--border-default);
    border-radius: var(--r-lg);
    box-shadow: var(--shadow-xs);
}

.cnp-rail-list { display: block; }

/* The results beside the rail. It was an inline flex column carrying a
   flex:1 that a grid item ignores anyway; the rhythm between the job
   cards is the only thing this needs to set. */
.cnp-board {
    display: flex;
    flex-direction: column;
    gap: 10px;
    min-width: 0;
}

/* Space between groups, and the hairline in the name below carries the
   break - the arrangement .sb-group and .sb-group-head make. */
.cnp-rail-grp + .cnp-rail-grp { margin-block-start: 6px; }

/* Name and hairline are one row, as in .sb-group-head: the rule runs
   out from the name to the edge of the rail. It is the name's own
   ::after rather than a second element, because two of these headings
   are a plain <p> and the other three are a <button> with a count and
   an arrow after it - a sibling span would have to be added to both
   and would land in the wrong place in one of them. */
.cnp-rail-lab {
    display: flex;
    align-items: center;
    gap: 10px;
    margin: 0;
    min-width: 0;
    font-size: 10px;
    font-weight: var(--fw-bold);
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--n-400);
}

.cnp-rail-lab::after {
    content: "";
    flex: 1;
    height: 1px;
    background: var(--border-subtle);
}

/* The nav group's heading, which has no toggle and so carries its own
   padding. The first group opens the rail, where 16px above the name
   would be a gap at the top of the card. */
p.cnp-rail-lab { padding: 16px 4px 8px; }
.cnp-rail-grp:first-child p.cnp-rail-lab { padding-block-start: 6px; }

/* ---------- The group heading, which is the toggle ---------- */

.cnp-rail-head {
    display: flex;
    align-items: center;
    gap: 10px;
    width: 100%;
    padding: 16px 4px 8px;
    border: none;
    border-radius: var(--r-md);
    background: transparent;
    font: inherit;
    text-align: start;
    cursor: pointer;
}

.cnp-rail-grp:first-child .cnp-rail-head { padding-block-start: 6px; }

/* The name takes the width, so its rule runs out to the count and the
   arrow rather than stopping at the text. */
.cnp-rail-head .cnp-rail-lab { flex: 1; }

.cnp-rail-head:hover .cnp-rail-lab { color: var(--n-500); }
.cnp-rail-head:focus-visible { outline: none; box-shadow: var(--shadow-focus); }

.cnp-rail-ar {
    flex-shrink: 0;
    color: var(--n-400);
    transition: transform var(--dur-fast) var(--ease-out),
                color var(--dur-fast) var(--ease-out);
}

.cnp-rail-head:hover .cnp-rail-ar { color: var(--n-500); }

/* A quarter-turn, not a swap to a second glyph: the arrow is the same
   object before and after, which is what makes it read as a hinge. It
   points at the options while they are there and back at the heading
   once they are not. */
.cnp-rail-grp.is-shut .cnp-rail-ar { transform: rotate(-90deg); }
[dir="rtl"] .cnp-rail-grp.is-shut .cnp-rail-ar { transform: rotate(90deg); }

.cnp-rail-opts { display: block; padding-block-end: 2px; }

/* Why a group is empty, in the group. It was a grey sentence inside
   the match-fit card before, at a size nothing else on the rail used. */
.cnp-rail-hint {
    margin: 0;
    padding: 2px 12px 8px;
    font-size: var(--fs-xs);
    line-height: 1.45;
    color: var(--text-tertiary);
}

/* ---------- A row: nav link or filter option ---------- */

/* 44px tall, a 12px gap, a 14px label, a 14px radius: .nav-item's
   metrics, which are literals there rather than scale steps, so they
   are literals here too. Anything else and the two menus sit half a
   size apart on the same screen. */
.cnp-rail-link,
.cnp-rail-opt {
    position: relative;
    display: flex;
    align-items: center;
    gap: 12px;
    width: 100%;
    min-height: 44px;
    padding: 0 12px;
    border-radius: var(--r-xl);
    background: transparent;
    color: var(--n-700);
    font-size: var(--fs-base);
    font-weight: var(--fw-medium);
    text-align: start;
    text-decoration: none;
    cursor: pointer;
    transition: background-color var(--dur-fast) var(--ease-out),
                color var(--dur-fast) var(--ease-out);
}

.cnp-rail-link + .cnp-rail-link,
.cnp-rail-opt + .cnp-rail-opt { margin-block-start: 4px; }

/* The tile the glyph sits on, and the row's hue with it: the pairs are
   the .hue-N primitive from section 4, and the row picks one by
   position the way the sidebar's rows do. A 17px stroke glyph sitting
   directly on the card is a smudge beside a 14px label; on a tinted
   square it reads as a mark, and the count beside it takes the same
   pair so the whole row reads as one object. */
.cnp-rail-ico {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 32px;
    height: 32px;
    flex-shrink: 0;
    border-radius: var(--r-lg);
    background: var(--tile-bg, var(--n-75));
    color: var(--tile-fg, var(--n-500));
    transition: background-color var(--dur-fast) var(--ease-out),
                color var(--dur-fast) var(--ease-out),
                box-shadow var(--dur-fast) var(--ease-out);
}

.cnp-rail-ico svg { color: inherit; }

.cnp-rail-txt {
    flex: 1;
    min-width: 0;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* A count, not a status: .nav-badge - a 24px-wide pill wearing the
   row's own hue pair, so it belongs to its row rather than sitting on
   it as a grey chip. Neutral on the filter rows, which carry no hue.
   Tabular so the numbers line up down the rail. */
.cnp-rail-n {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    min-width: 24px;
    height: 22px;
    padding: 0 7px;
    border-radius: var(--r-full);
    background: var(--tile-bg, var(--n-100));
    color: var(--tile-fg, var(--text-tertiary));
    font-size: 11px;
    font-weight: var(--fw-bold);
    font-variant-numeric: tabular-nums;
    line-height: 1;
}

.cnp-rail-link:hover,
.cnp-rail-opt:hover { background: var(--n-50); }

/* Keyboard focus was invisible on this column: a bare checkbox in a
   card shows the UA ring on some browsers and nothing on others. The
   ring goes on the row, because the input itself is off-screen. */
.cnp-rail-link:focus-visible,
.cnp-rail-opt:focus-within {
    outline: none;
    box-shadow: var(--shadow-focus);
}

/* The band fades out across the row rather than filling it flat, which
   is what keeps a 276px row from reading as a slab. */
.cnp-rail-link.is-on,
.cnp-rail-opt.is-on {
    background: linear-gradient(90deg, var(--cnp-band) 0%, var(--cnp-band-fade) 100%);
    color: var(--cnp-accent-strong);
    font-weight: var(--fw-semibold);
}

[dir="rtl"] .cnp-rail-link.is-on,
[dir="rtl"] .cnp-rail-opt.is-on {
    background: linear-gradient(270deg, var(--cnp-band) 0%, var(--cnp-band-fade) 100%);
}

/* The tile inverts on the selected row - white on the band, so the row
   reads as lifted rather than as one more coloured chip in the column.
   The same move .nav-item.active and .set-link.is-on make. */
.cnp-rail-link.is-on .cnp-rail-ico {
    background: var(--n-0);
    color: var(--cnp-accent);
    box-shadow: var(--shadow-xs);
}

/* A shut group's heading has no band under it, so its chip carries the
   tint itself - it is the one thing a shut group can still say about
   itself, and it has to say it against the card. */
.cnp-rail-n-on {
    background: var(--p-100);
    color: var(--cnp-accent);
}

/* Only on the row you are on. A chevron on every row would read as
   "these all expand", which none of them do - and it goes on the nav
   rows alone, because a filter row does not go anywhere. */
.cnp-rail-go {
    display: none;
    flex-shrink: 0;
    color: var(--cnp-accent);
}

.cnp-rail-link.is-on .cnp-rail-go { display: block; }
[dir="rtl"] .cnp-rail-go { transform: scaleX(-1); }

/* The marker, drawn by the row itself rather than by an absolutely
   positioned child per row. Flush to the card's inner edge, which is
   what the negative inset cancelling --cnp-rail-pad is for: on the
   rail as on the sidebar it is a mark against the edge, not a border
   on the pill. */
.cnp-rail-link.is-on::before,
.cnp-rail-opt.is-on::before {
    content: "";
    position: absolute;
    inset-inline-start: calc(-1 * var(--cnp-rail-pad));
    top: 50%;
    transform: translateY(-50%);
    width: 4px;
    height: 28px;
    background: var(--cnp-accent);
    border-start-end-radius: 4px;
    border-end-end-radius: 4px;
}

/* ---------- The control under a filter row ---------- */

/* Off-screen, not display:none. A control that is not rendered is not
   focusable and not announced, and this is still a real radio group and
   a real set of checkboxes - the row is only its skin. */
.cnp-rail-ctl {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    border: 0;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
}

/* The tile slot on a filter row: the same 32px square the nav rows put
   their glyph on, so every label down the rail starts on one line, but
   drawn as the control it still is rather than tinted with a hue. A
   check on it once it is on, a ring while it is off. The nav rows say
   where you are; these say what is ticked, which is something a band
   alone cannot say for a group of four. */
.cnp-rail-box {
    display: grid;
    place-items: center;
    width: 32px;
    height: 32px;
    flex-shrink: 0;
    border-radius: var(--r-lg);
    background: var(--n-75);
    box-shadow: inset 0 0 0 1px var(--border-default);
    color: var(--cnp-accent);
    transition: background-color var(--dur-fast) var(--ease-out),
                box-shadow var(--dur-fast) var(--ease-out);
}

/* Round for a band, square for a facet: one of these is a choice
   between four and the other is a set you add to, and the shape is the
   only thing on the row that says which. */
.cnp-rail-box.is-radio { border-radius: var(--r-full); }

.cnp-rail-opt:hover .cnp-rail-box { background: var(--n-100); }

/* Inverted on the band, exactly as the nav row's tile is. */
.cnp-rail-opt.is-on .cnp-rail-box {
    background: var(--n-0);
    box-shadow: var(--shadow-xs);
}

/* The dot a checked radio gets, since it has no glyph inside it. */
.cnp-rail-opt.is-on .cnp-rail-box.is-radio::after {
    content: "";
    width: 11px;
    height: 11px;
    border-radius: var(--r-full);
    background: var(--cnp-accent);
}

/* An option that would leave nothing. Dimmed rather than removed: the
   count is the answer to "what is left to narrow by", and a facet value
   that vanishes takes its own zero with it. */
.cnp-rail-opt.is-empty { color: var(--text-tertiary); }
.cnp-rail-opt.is-empty .cnp-rail-n { color: var(--text-disabled); }

/* ---------- Clearing ---------- */

.cnp-rail-clear {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 6px;
    width: 100%;
    min-height: 36px;
    margin-block-start: 10px;
    padding: 0 12px;
    border: 1px solid var(--border-default);
    border-radius: var(--r-lg);
    background: transparent;
    font: inherit;
    font-size: var(--fs-xs);
    font-weight: var(--fw-medium);
    color: var(--text-secondary);
    cursor: pointer;
}

.cnp-rail-clear:hover { background: var(--n-50); color: var(--text-primary); }
.cnp-rail-clear:focus-visible { outline: none; box-shadow: var(--shadow-focus); }

/* ---------- Narrow ---------- */

/* A vertical rail at this width costs a quarter of the page, and the
   board beside it is already down to one job card per line. The rail
   goes full width above the results and lays its groups across that
   width instead of down it; the arrows still shut them. */
@media (max-width: 1080px) {
    .cnp-jobs {
        grid-template-columns: minmax(0, 1fr);
        gap: 12px;
    }

    .cnp-rail {
        position: static;
        max-height: none;
    }

    .cnp-rail-list {
        display: flex;
        flex-wrap: wrap;
        gap: 4px 12px;
        align-items: flex-start;
    }

    /* Each group takes a column of the strip. The 12px that separates
       one group from the next down a column would be a gap between two
       columns here, where the flex gap already sets the spacing. */
    .cnp-rail-grp {
        flex: 1 1 210px;
        min-width: 0;
    }

    .cnp-rail-grp + .cnp-rail-grp { margin-block-start: 0; }
}

/* On a phone the rail comes apart. Four open facet groups above the
   board would put the whole of the filtering between the candidate and
   the first job - which is why the filters used to be ordered below the
   results here - but the rail is navigation now as well, and Saved is
   not reachable from anywhere else on the page. So the nav group stays
   on top, the board follows it, and the filters go under the board.

   display: contents is what allows that: the rail stops being a box and
   its groups become items of .cnp-jobs, free to be ordered around the
   board between them. Each one takes the card the rail was carrying, so
   nothing loses its edge on the way. */
@media (max-width: 800px) {
    .cnp-jobs { gap: 10px; }

    .cnp-rail,
    .cnp-rail-list { display: contents; }

    /* The group is the card here, so it is its padding the marker on a
       selected row has to cancel. */
    .cnp-rail-grp {
        order: 3;
        padding: var(--cnp-rail-pad);
        background: var(--surface-raised);
        border: 1px solid var(--border-default);
        border-radius: var(--r-lg);
        box-shadow: var(--shadow-xs);
    }

    /* The nav group - Browse and Saved. It is the first child of the
       rail on both pages, and the only one on Saved. */
    .cnp-rail-grp:first-child { order: 1; }

    .cnp-board { order: 2; }

    /* The clear button is a child of the rail too, so it lands with the
       filters it clears rather than with the nav. */
    .cnp-rail-clear { order: 4; margin-block-start: 0; }
}

@media (max-width: 640px) {
    .cnp-jobs { --cnp-rail-stick: 52px; }
}


/* ============================================================
   24. Recruiter: Offer & JD templates (otpl-*)

   The two-pane library from "21." carries this section too - what
   the workspace has on the left, the selected one on the right -
   and this block adds only the parts an offer letter has that an
   email does not.

   The section was a two-column grid of cards. A card said a name, a
   category and a System pill, and the letter itself - the document
   that goes out to a candidate under the workspace's name, on the
   workspace's letterhead - was not on the screen at all. Reading
   one meant pressing Edit, waiting for a modal, and pressing
   Preview inside it; comparing two meant doing that twice with a
   close in between.

   What this block adds on top of the shared panes:

     a letterhead   an offer letter is a document, not a field of
                    text, and it prints on the sheet
                    Recruiter/Pages/OfferLetter.razor draws. The
                    pane sets it the same way: the workspace name on
                    a rule, the body at document measure and
                    leading. A JD gets the same sheet with its title
                    hint on the rule, because it is a document too.

     two readings   as written, with the merge tokens marked
                    (.tpl-tok from "21."), and merged, with them
                    filled from sample values. The renderer blanks a
                    token it cannot resolve, so a letter carrying
                    {{ offer.slary }} goes out with a hole in it and
                    a sentence that closes over the gap. That is not
                    a footnote under the sheet - it is a count above
                    it, in the warning colours, beside the switch
                    that shows which tokens they are.

     a kind switch  offer letters and job descriptions are two
                    libraries, not two filters over one: they share
                    no field but a name, and every action under the
                    switch acts on one store or the other. So it is
                    a tab bar under the heading, on the .tabs
                    primitive, rather than a segmented control in
                    the section's action row - where it sat beside
                    New offer template and read as a filter.

     hidden rows    a hidden platform template is recovery, not a
                    second list to read past. It was an always-open
                    grey box under the grid; it is a closed
                    disclosure that says how many are behind it.

   What is here:
     the kind switch is .tabs from "9." with nothing added
     .otpl-bar     the reading switch and the bad-token count
     .otpl-doc     the letterhead the template is set on
     .otpl-hidden  the hidden-platform-templates disclosure
     .otpl-code    the editor's body field - taller than email's
   ============================================================ */

/* ---------- The kind switch ---------- */

/* .tabs from "9.", not .segmented, and under the heading rather than
   in the section's action row.

   The argument is the one written on that primitive: a segmented
   control says "pick one setting", a tab bar says "another view of
   the same thing". These are two libraries - an offer letter and a
   job description share no field but a name, and every action under
   the switch acts on one store or the other - so beside New offer
   template it read as a filter over one list, which is the one thing
   it is not.

   The primitive carries all of it - the hairline, the marked tab, the
   icon at 15px with its dimming, the focus ring and the narrow-width
   scroll - so this section adds nothing of its own any more. */
/* ---------- The bar above the sheet ---------- */

.otpl-bar {
    display: flex;
    align-items: center;
    gap: var(--sp-5);
    flex-wrap: wrap;
    margin-block-end: 12px;
}

.otpl-modes { flex: 0 0 auto; }

/* What the send does to a token nothing fills, beside the switch
   that shows where they are. Warning rather than danger: the letter
   is not broken, it is missing a value, and it is still a draft. */
.otpl-bad {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 3px 9px;
    border-radius: var(--r-full);
    background: var(--warning-50);
    box-shadow: inset 0 0 0 1px var(--warning-100);
    font-size: var(--fs-xs);
    font-weight: var(--fw-semibold);
    color: var(--warning-700);
}

.otpl-bad svg { flex-shrink: 0; }

/* ---------- The letterhead ---------- */

/* Paper, not a panel: a hairline, a soft lift and a raised ground
   on the pane's own surface, so the sheet reads as the thing the
   pane is showing rather than as another box inside it. */
.otpl-doc {
    max-width: 74ch;
    border: 1px solid var(--border-subtle);
    border-radius: var(--r-lg);
    background: var(--surface-raised);
    box-shadow: var(--shadow-xs);
    overflow: hidden;
}

/* The rule under the letterhead is the one heavy line in a quiet
   palette, and it is deliberate: it is what the printed offer has,
   and it is what tells this apart from every other bordered box in
   Settings at a glance. Inset from the sheet's edge the way a
   letterhead is inset from the paper. */
.otpl-doc-head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--sp-5);
    margin-inline: 22px;
    padding-block: 16px 11px;
    border-block-end: 2px solid var(--p-900);
}

.otpl-doc-org {
    font-size: var(--fs-lg);
    font-weight: var(--fw-bold);
    letter-spacing: -0.01em;
    color: var(--text-primary);
}

.otpl-doc-kind {
    flex-shrink: 0;
    color: var(--text-tertiary);
}

/* Document proportions - a wider measure and looser leading than a
   list row - because this is read as prose. pre-wrap is
   load-bearing: the blank line between two paragraphs is part of
   the letter, and collapsing it here shows a shape the send does
   not produce. */
.otpl-doc-body {
    padding: 20px 22px 24px;
    font-size: var(--fs-base);
    line-height: var(--lh-relaxed);
    color: var(--text-primary);
    white-space: pre-wrap;
    overflow-wrap: anywhere;
}

/* The same sheet inside the drawer, where it is a preview beside a
   field rather than the subject of the pane. */
.otpl-doc-sm { margin-block-start: 4px; }

.otpl-doc-sm .otpl-doc-head { margin-inline: 16px; padding-block: 12px 9px; }

.otpl-doc-sm .otpl-doc-body {
    padding: 14px 16px 18px;
    font-size: var(--fs-md);
    line-height: var(--lh-normal);
}

/* ---------- Hidden platform templates ---------- */

.otpl-hidden {
    border: 1px solid var(--border-subtle);
    border-radius: var(--r-lg);
    background: var(--n-25);
}

.otpl-hidden > summary {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 10px 14px;
    border-radius: inherit;
    font-size: var(--fs-sm);
    font-weight: var(--fw-semibold);
    color: var(--text-secondary);
    cursor: pointer;
    list-style: none;
}

/* Safari draws its own disclosure triangle through this; the
   chevron at the end of the row is the affordance. */
.otpl-hidden > summary::-webkit-details-marker { display: none; }

.otpl-hidden > summary:hover { color: var(--text-primary); }
.otpl-hidden > summary:focus-visible { outline: none; box-shadow: var(--shadow-focus); }

.otpl-hidden > summary svg { flex-shrink: 0; color: var(--text-tertiary); }

.otpl-hidden-t { flex: 1; min-width: 0; }

.otpl-hidden-chev {
    display: inline-flex;
    transition: transform var(--dur-fast) var(--ease-out);
}

.otpl-hidden[open] .otpl-hidden-chev { transform: rotate(180deg); }

@media (prefers-reduced-motion: reduce) {
    .otpl-hidden-chev { transition: none; }
}

.otpl-hidden-sub {
    margin: 0;
    padding: 0 14px;
    max-width: 74ch;
    font-size: var(--fs-xs);
    line-height: var(--lh-snug);
    color: var(--text-tertiary);
}

.otpl-hidden-rows {
    margin: 10px 0 0;
    padding: 0;
    list-style: none;
    border-block-start: 1px solid var(--border-subtle);
}

.otpl-hidden-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--sp-5);
    padding: 6px 14px;
    border-block-end: 1px solid var(--border-subtle);
}

.otpl-hidden-row:last-child { border-block-end: none; }

.otpl-hidden-nm {
    min-width: 0;
    font-size: var(--fs-sm);
    color: var(--text-secondary);
}

/* ---------- The editor ---------- */

/* The AI draft button and the preview toggle sit on the body
   field's own label row, so neither reads as acting on the drawer
   the way the footer's buttons do. */
.otpl-field-acts {
    display: flex;
    align-items: center;
    gap: 12px;
    flex-wrap: wrap;
}

/* A text action, not a button: it changes what the field below it
   shows rather than doing anything to the template. */
.otpl-link {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    padding: 0;
    border: none;
    background: none;
    font-size: var(--fs-xs);
    font-weight: var(--fw-semibold);
    color: var(--p-700);
    cursor: pointer;
}

.otpl-link:hover { color: var(--p-900); }
.otpl-link:focus-visible { outline: none; border-radius: var(--r-sm); box-shadow: var(--shadow-focus); }
.otpl-link svg { flex-shrink: 0; }

/* Taller than .tpl-code: an offer letter runs to a page, and a
   field sized for an email invites one that stops mid-paragraph. */
.otpl-code { min-height: 320px; }


/* ============================================================
   25. Recruiter: Interview rubrics (rub-*)

   A rubric is its criteria: the weighted list an interviewer
   actually scores against. The section drew that list as pills
   reading "Communication 20%" wrapped into a paragraph, so the
   one thing the screen exists to show - how a hundred points
   are split - had to be added up in the reader's head, and a
   rubric whose weights came to 80 looked exactly like one that
   came to 100.

   So it takes the two-pane shape the other template sections
   use ("21." above): the workspace's rubrics on the left, the
   selected one's weighting on the right. The bar is the whole
   point - one track, one segment per criterion, sized by its
   weight. A gap at the end of the track is a rubric that does
   not add up, seen rather than computed.

   The editor moves from a centred QModal to the shared drawer
   ("20."). Criteria are rows that grow with the rubric, and in
   a modal capped at 90vh a sixth criterion pushed Save under
   the fold of a floating window.

   What is here:
     .rub-body    the right pane's contents
     .rub-mix     the weighting bar - a segment per criterion
     .rub-seg-N   the segment tints, cycling every six
     .rub-list    the criteria, read as rows
     .rub-item    one criterion: swatch, name, key, scale, weight
     .rub-edit    one criterion in the editor, as a field row
     .rub-total   the live sum, .is-ok only at exactly 100
   ============================================================ */

.rub-body { display: flex; flex-direction: column; gap: 16px; padding: 14px; }

/* ---------- The weighting ---------- */

.rub-mix-head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 8px;
    margin-block-end: 7px;
}

.rub-mix-k {
    font-size: var(--fs-2xs);
    font-weight: var(--fw-semibold);
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--text-tertiary);
}

/* The sum, in the same place on every rubric. Weight is the number
   the reader came for, so it is set in tabular figures and does not
   shift as the selection moves down the list. */
.rub-mix-n {
    font-size: var(--fs-sm);
    font-weight: var(--fw-semibold);
    font-variant-numeric: tabular-nums;
    color: var(--text-secondary);
}

.rub-mix-n.is-off { color: var(--warning-700); }

/* One track, one segment per criterion. The track's own fill shows
   through wherever the weights fall short of 100, which is the
   reason it is a single bar rather than a chip per criterion. */
.rub-mix {
    display: flex;
    height: 10px;
    border-radius: var(--r-full);
    overflow: hidden;
    background: var(--n-100);
    box-shadow: inset 0 0 0 1px var(--border-subtle);
}

/* A criterion weighted 1% still has to be visible, and two adjacent
   segments have to be told apart at any width. */
.rub-seg {
    min-width: 3px;
    box-shadow: inset -1px 0 0 rgba(255, 255, 255, 0.55);
    transition: width var(--dur-mid) var(--ease-out);
}

.rub-seg:last-child { box-shadow: none; }

/* A ramp inside the primary family with one gold step, rather than a
   categorical palette: the segments are parts of one quantity, so
   they should read as one object seen in slices. Six is more than
   any rubric in the product uses; past that it wraps. */
.rub-seg-0, .rub-dot-0 { background: var(--p-700); }
.rub-seg-1, .rub-dot-1 { background: var(--p-500); }
.rub-seg-2, .rub-dot-2 { background: var(--p-300); }
.rub-seg-3, .rub-dot-3 { background: var(--gold-400); }
.rub-seg-4, .rub-dot-4 { background: var(--p-400); }
.rub-seg-5, .rub-dot-5 { background: var(--p-200); }

/* ---------- The criteria ---------- */

.rub-list {
    display: flex;
    flex-direction: column;
    border: 1px solid var(--border-subtle);
    border-radius: var(--r-lg);
    overflow: hidden;
}

.rub-item {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 9px 12px;
    border-block-end: 1px solid var(--border-subtle);
    background: var(--surface-raised);
}

.rub-item:last-child { border-block-end: none; }

/* The swatch is the only thing tying a row to its slice of the bar
   above it. Without it the bar is decoration. */
.rub-dot {
    width: 10px;
    height: 10px;
    flex-shrink: 0;
    border-radius: var(--r-full);
    box-shadow: inset 0 0 0 1px rgba(15, 19, 32, 0.1);
}

.rub-item-txt { flex: 1; min-width: 0; }

.rub-item-nm {
    font-size: var(--fs-md);
    font-weight: var(--fw-semibold);
    letter-spacing: -0.005em;
    color: var(--text-primary);
}

/* Scores are stored against the key and never against the label, so
   a relabelled rubric shows both. Mono because it is an identifier
   and is read character by character. */
.rub-item-key {
    margin-block-start: 2px;
    font-family: var(--font-mono);
    font-size: var(--fs-2xs);
    color: var(--text-tertiary);
    overflow-wrap: anywhere;
}

.rub-item-meta {
    display: flex;
    align-items: center;
    gap: 8px;
    flex-shrink: 0;
}

.rub-item-w {
    min-width: 42px;
    text-align: end;
    font-size: var(--fs-md);
    font-weight: var(--fw-semibold);
    font-variant-numeric: tabular-nums;
    color: var(--text-primary);
}

/* ---------- The editor ---------- */

/* A criterion is four controls that belong to one thing, so it is a
   bordered row rather than a table line: at the width of a drawer a
   four-column table leaves the scale select about 60px. */
.rub-edit {
    display: grid;
    grid-template-columns: minmax(0, 1fr) 84px 116px auto;
    align-items: end;
    gap: 8px;
    padding: 10px;
    border: 1px solid var(--border-subtle);
    border-radius: var(--r-md);
    background: var(--n-25);
}

.rub-edit + .rub-edit { margin-block-start: 8px; }

.rub-edit .lbl { font-size: 9.5px; }

/* The key, under the fields, on its own line: it is the row's
   identity and it is never re-derived once the row has been saved. */
.rub-edit-key {
    grid-column: 1 / -1;
    font-family: var(--font-mono);
    font-size: var(--fs-2xs);
    color: var(--text-tertiary);
    overflow-wrap: anywhere;
}

.rub-edit-x {
    display: grid;
    place-items: center;
    width: var(--h-md);
    height: var(--h-md);
    border: 1px solid var(--border-default);
    border-radius: var(--r-md);
    background: var(--surface-raised);
    color: var(--text-tertiary);
    cursor: pointer;
    transition: color var(--dur-fast) var(--ease-out),
                border-color var(--dur-fast) var(--ease-out);
}

.rub-edit-x:hover { color: var(--danger-600); border-color: var(--danger-200); }
.rub-edit-x:focus-visible { outline: none; box-shadow: var(--shadow-focus); }

/* The controls that act on all the criteria at once - add one, even
   them out, start from the default. */
.rub-tools {
    display: flex;
    align-items: center;
    gap: 4px;
    flex-wrap: wrap;
}

/* The live sum, under the rows it totals. It is the one thing that
   stops the form saving, so it is stated while the weights are being
   typed rather than left to the message that appears after Save. */
.rub-total {
    display: flex;
    align-items: center;
    gap: 10px;
    margin-block-start: 10px;
    padding: 9px 12px;
    border-radius: var(--r-md);
    background: var(--warning-50);
    color: var(--warning-700);
    box-shadow: inset 0 0 0 1px var(--warning-100);
    font-size: var(--fs-sm);
    font-weight: var(--fw-semibold);
}

.rub-total.is-ok {
    background: var(--success-50);
    color: var(--success-700);
    box-shadow: inset 0 0 0 1px var(--success-100);
}

.rub-total-bar {
    flex: 1;
    height: 6px;
    border-radius: var(--r-full);
    background: rgba(15, 19, 32, 0.08);
    overflow: hidden;
}

.rub-total-fill {
    height: 100%;
    border-radius: var(--r-full);
    background: currentColor;
    transition: width var(--dur-mid) var(--ease-out);
}

.rub-total-n { font-variant-numeric: tabular-nums; }

@media (max-width: 560px) {
    /* Weight and scale keep a line of their own rather than shrinking
       into two unusable fields beside a truncated name. */
    .rub-edit { grid-template-columns: minmax(0, 1fr) auto; }
}


/* ============================================================
   26. Recruiter: Resume layouts (rlay-*)

   A layout is a picture, and the section listed it as text: a
   54px thumbnail in a padded row, the name beside it, four ghost
   buttons on the end. Choosing between eight layouts meant
   reading eight names, because at that size the thing being
   chosen was smaller than the button that chooses it.

   So the rows become a gallery. The swatch is painted from the
   layout's own tokens - there is no stored thumbnail to go stale
   - and it is now large enough to show what those tokens do:
   which side the sidebar is on, what colour the rule under the
   name is, how much of the page a banner takes.

   Editing moves into the shared drawer ("20."), which is what
   lets the preview sit with the colour fields that change it.
   The edit form used to be a third card appended to the bottom
   of the page, so pressing Edit on a layout near the end of the
   gallery opened a form below the fold.

   What is here:
     .rlay-grp    the band separating "Ours" from the platform's
     .rlay-grid   the gallery
     .rlay-card   one layout, .is-default for the chosen one
     .rlay-shot   the swatch on its ground
     .rlay-opt    a CV-share default, as a row with a switch
   ============================================================ */

.rlay-grp {
    margin: 0;
    padding: 9px 16px 7px;
    background: var(--n-25);
    border-block-end: 1px solid var(--border-subtle);
    font-size: var(--fs-2xs);
    font-weight: var(--fw-semibold);
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--text-tertiary);
}

/* The band closing one group and opening the next needs a rule above it
   as well as below: between two tinted bands the gallery would otherwise
   run straight into the next heading. */
.rlay-grid + .rlay-grp { border-block-start: 1px solid var(--border-subtle); }

.rlay-grid {
    display: grid;
    /* 210px is the narrowest a card can be and still hold "Make
       default" beside "Edit" without breaking to a third row. */
    grid-template-columns: repeat(auto-fill, minmax(210px, 1fr));
    gap: 12px;
    padding: 14px 16px;
}

.rlay-card {
    display: flex;
    flex-direction: column;
    border: 1px solid var(--border-default);
    border-radius: var(--r-lg);
    background: var(--surface-raised);
    box-shadow: var(--shadow-xs);
    overflow: hidden;
    transition: border-color var(--dur-fast) var(--ease-out),
                box-shadow var(--dur-fast) var(--ease-out);
}

.rlay-card:hover { border-color: var(--border-strong); box-shadow: var(--shadow-sm); }

/* The default renders every export that is not pinned elsewhere, so
   it is marked on the card and not only in a pill: a ring the eye
   finds before it reads anything. */
.rlay-card.is-default {
    border-color: var(--p-300);
    box-shadow: 0 0 0 1px var(--p-300), var(--shadow-sm);
}

/* The sheet floats on a tinted ground, the way a page does in every
   other document preview in the product. */
.rlay-shot {
    display: grid;
    place-items: center;
    padding: 14px;
    background: var(--n-50);
    border-block-end: 1px solid var(--border-subtle);
}

.rlay-card.is-default .rlay-shot { background: var(--p-50); }

/* The swatch itself. A drawn miniature needs a shadow to read as
   paper; without one it is a pale rectangle on a pale ground. */
.rlay-paper {
    display: flex;
    flex-direction: column;
    overflow: hidden;
    background: var(--n-0);
    border: 1px solid var(--border-default);
    border-radius: var(--r-xs);
    box-shadow: var(--shadow-sm);
}

.rlay-txt { padding: 10px 12px 12px; }

.rlay-nm {
    display: flex;
    align-items: center;
    gap: 6px;
    flex-wrap: wrap;
    font-size: var(--fs-md);
    font-weight: var(--fw-semibold);
    letter-spacing: -0.005em;
    color: var(--text-primary);
}

.rlay-meta {
    margin: 3px 0 0;
    font-size: var(--fs-xs);
    line-height: var(--lh-snug);
    color: var(--text-tertiary);
}

/* The actions sit on a band at the foot of the card, so a gallery of
   eight has its buttons on one line rather than at eight different
   heights. */
.rlay-acts {
    display: flex;
    align-items: center;
    gap: 4px;
    flex-wrap: wrap;
    margin-block-start: auto;
    padding: 8px 10px;
    border-block-start: 1px solid var(--border-subtle);
    background: var(--n-25);
}

/* ---------- What the client sees ---------- */

/* Two switches whose consequence has to be read before either is
   flipped - a contact detail shown to a client cannot be unshown.
   The hint is part of the control, not a footnote under the card. */
.rlay-opt {
    display: flex;
    align-items: flex-start;
    gap: 12px;
    padding: 11px 0;
}

.rlay-opt + .rlay-opt { border-block-start: 1px solid var(--border-subtle); }

.rlay-opt-txt { flex: 1; min-width: 0; }

.rlay-opt-k {
    font-size: var(--fs-md);
    font-weight: var(--fw-medium);
    color: var(--text-primary);
}

.rlay-opt-d {
    margin: 2px 0 0;
    max-width: 68ch;
    font-size: var(--fs-xs);
    line-height: var(--lh-snug);
    color: var(--text-tertiary);
}

/* ---------- The editor ---------- */

/* The preview is the first thing in the drawer body: the colour
   fields under it are what change it, and a preview that has
   scrolled away while they are being used is not a preview. */
.rlay-prev {
    display: grid;
    place-items: center;
    padding: 16px;
    border: 1px solid var(--border-subtle);
    border-radius: var(--r-lg);
    background: var(--n-50);
}

.rlay-swatches {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
    gap: 10px;
}

/* A colour field: the well, then the value it holds. The native
   control carries its own swatch, so the row only has to set it
   beside the hex it is showing. */
.rlay-color {
    display: flex;
    align-items: center;
    gap: 8px;
}

.rlay-color input[type="color"] {
    width: 32px;
    height: var(--h-md);
    flex-shrink: 0;
    padding: 0;
    border: 1px solid var(--border-default);
    border-radius: var(--r-sm);
    background: none;
    cursor: pointer;
}

.rlay-color input[type="color"]:focus-visible { outline: none; box-shadow: var(--shadow-focus); }

.rlay-hex {
    font-family: var(--font-mono);
    font-size: var(--fs-xs);
    color: var(--text-tertiary);
}


/* ============================================================
   27. Recruiter: Agency CV templates (acv-*)

   An agency uploads its own branded CV and the product fills
   candidate facts into that shell, so this section has two jobs
   no other template section has: take a file, and report what
   was found inside it. Both were drawn inline - a dashed label,
   a grid of bare labels, and a row per template whose three
   findings (logo lifted, no logo, no text) were three spans of
   coloured 10px text sitting in the same line as the name.

   Those findings are the difference between a template that
   works and one that quietly produces an unbranded CV, so they
   become pills, in the tones the rest of the product already
   uses for good, quiet and bad.

   Three things then changed in how a sample is taken and shown:

     - The target accepts a drop. The file input was display:none
       and could not be a drop target, so "drop a sample here"
       dropped onto the document instead and navigated away from
       settings. It now covers the target at zero opacity: the
       browser does the drop, and .is-over only lights the border.

     - There is an example to compare against. An agency that has
       never uploaded a shell cannot know what one should look
       like, so one ships under wwwroot/samples and .acv-sample
       offers it as a preview or a download.

     - Stored templates are cards. What is being chosen between
       is a page, so each is drawn as one - the lifted logo on a
       sheet's letterhead - and the sheet is the preview button.

   What is here:
     .acv-drop    the drop target: .is-over dragging, .is-armed held
     .acv-held    the file being held, named and sized
     .acv-sample  the shipped example CV
     .acv-form    the fields that travel with the upload
     .acv-grid    the stored templates
     .acv-card    one of them, .is-default for the chosen
     .acv-page    its sheet, with the extracted logo on top
     .acv-prev    the sample as a page, inside the preview modal
   ============================================================ */

/* ---------- Taking a file ---------- */

.acv-drop {
    position: relative;
    display: flex;
    flex-direction: column;
    align-items: center;
    padding: 26px 18px 22px;
    border: 1.5px dashed var(--border-strong);
    border-radius: var(--r-lg);
    background: var(--n-25);
    text-align: center;
    cursor: pointer;
    overflow: hidden;
    transition: border-color var(--dur-fast) var(--ease-out),
                background-color var(--dur-fast) var(--ease-out);
}

/* The wash that warms the target on hover and fills it while a file
   is over it. A pseudo-element rather than a background swap so the
   dashed border keeps its own colour through the transition. */
.acv-drop::after {
    content: "";
    position: absolute;
    inset: 0;
    background: radial-gradient(120% 90% at 50% 0%, var(--p-50), transparent 72%);
    opacity: 0;
    pointer-events: none;
    transition: opacity var(--dur-fast) var(--ease-out);
}

.acv-drop:hover::after { opacity: 0.7; }
.acv-drop.is-over::after { opacity: 1; }

/* Everything except the input sits above the wash. The input is
   excluded because it is absolutely positioned over all of it. */
.acv-drop > :not(.acv-drop-input) { position: relative; z-index: 1; }

/* The input is the drop target, not a hidden control the label
   forwards to: at zero opacity it still receives the file, so the
   browser's own drop handling does the work and nothing has to
   preventDefault its way out of a navigation. */
.acv-drop-input {
    position: absolute;
    inset: 0;
    z-index: 2;
    width: 100%;
    height: 100%;
    opacity: 0;
    cursor: pointer;
}

.acv-drop:hover { border-color: var(--p-300); }

/* The label has to show keyboard focus like a control: the input
   inside it is invisible and cannot. */
.acv-drop:focus-within {
    border-color: var(--border-focus);
    box-shadow: var(--shadow-focus);
}

/* A file is over the target. Solid and brand-coloured: the drop
   will be accepted if it is released here. */
.acv-drop.is-over {
    border-style: solid;
    border-color: var(--p-600);
}

/* A file is held and not yet uploaded. Solid rather than dashed: the
   target has been hit, and what is left to do is press Upload. */
.acv-drop.is-armed {
    border-style: solid;
    border-color: var(--p-300);
    background: var(--p-50);
}

.acv-drop-mark {
    display: grid;
    place-items: center;
    width: 48px;
    height: 48px;
    margin-block-end: 12px;
    border-radius: var(--r-lg);
    background: var(--surface-raised);
    color: var(--p-700);
    box-shadow: var(--shadow-sm), inset 0 0 0 1px var(--p-100);
    transition: transform var(--dur-fast) var(--ease-out);
}

.acv-drop:hover .acv-drop-mark { transform: translateY(-1px); }
.acv-drop.is-over .acv-drop-mark { transform: translateY(-3px) scale(1.06); }

.acv-drop-k {
    font-size: var(--fs-md);
    font-weight: var(--fw-semibold);
    letter-spacing: -0.005em;
    color: var(--text-primary);
}

/* The second half of the invitation. Brand-coloured because it is
   the half that describes a click, and the whole tile is one. */
.acv-drop-b {
    margin-block-start: 2px;
    font-size: var(--fs-sm);
    font-weight: var(--fw-medium);
    color: var(--p-700);
}

.acv-drop-d {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 7px;
    flex-wrap: wrap;
    margin: 10px 0 0;
    font-size: var(--fs-xs);
    color: var(--text-tertiary);
}

.acv-drop-sep { color: var(--n-300); }

/* What the format list and the size ceiling look like: a fact, not a
   warning. The ceiling is worth stating up front - a 40 MB CV that
   only bounces after the whole upload is a wasted minute. */
.acv-drop-fmt {
    font-weight: var(--fw-semibold);
    color: var(--text-secondary);
}

/* ---------- The file being held ---------- */

/* Outside the drop target, not inside it: the input covers the whole
   target, so a Remove button drawn within it could never be pressed. */
.acv-held {
    display: flex;
    align-items: center;
    gap: 10px;
    margin-block-start: 10px;
    padding: 8px 8px 8px 10px;
    border-radius: var(--r-md);
    background: var(--p-50);
    box-shadow: inset 0 0 0 1px var(--p-100);
}

.acv-held-mark {
    display: grid;
    place-items: center;
    width: 32px;
    height: 32px;
    flex-shrink: 0;
    border-radius: var(--r-sm);
    background: var(--surface-raised);
    color: var(--p-700);
    box-shadow: var(--shadow-xs);
}

.acv-held-txt {
    display: flex;
    flex-direction: column;
    gap: 2px;
    flex: 1;
    min-width: 0;
}

.acv-held-nm {
    font-size: var(--fs-sm);
    font-weight: var(--fw-semibold);
    color: var(--text-primary);
}

.acv-held-meta {
    display: flex;
    align-items: center;
    gap: 6px;
    font-size: var(--fs-xs);
    color: var(--text-tertiary);
}

.acv-held-ok {
    font-weight: var(--fw-semibold);
    color: var(--success-700);
}

/* The format, as a person says it. The stored value is a MIME type;
   "application/vnd.openxmlformats-officedocument.wordprocessingml.
   document" on a 228px card pushes everything after it off the end. */
.acv-kind {
    display: inline-flex;
    align-items: center;
    padding: 1px 6px;
    border-radius: var(--r-sm);
    background: var(--n-100);
    font-size: var(--fs-2xs);
    font-weight: var(--fw-semibold);
    letter-spacing: 0.04em;
    color: var(--text-secondary);
}

/* ---------- The shipped example ---------- */

.acv-sample {
    display: flex;
    align-items: center;
    gap: 14px;
    margin-block-start: 14px;
    padding: 12px 14px;
    border-radius: var(--r-lg);
    background: var(--n-25);
    box-shadow: inset 0 0 0 1px var(--border-subtle);
}

/* A page, at the size of an icon. Tilted a couple of degrees so it
   reads as a loose sheet someone can pick up rather than as another
   button; it straightens when the strip is hovered. */
.acv-sample-doc {
    display: flex;
    flex-direction: column;
    width: 36px;
    height: 48px;
    flex-shrink: 0;
    border-radius: 3px;
    background: var(--n-0);
    box-shadow: var(--shadow-sm), inset 0 0 0 1px var(--border-subtle);
    overflow: hidden;
    transform: rotate(-3deg);
    transition: transform var(--dur-fast) var(--ease-out);
}

.acv-sample:hover .acv-sample-doc { transform: rotate(0deg) translateY(-1px); }

.acv-sample-band {
    display: block;
    height: 13px;
    flex-shrink: 0;
    background: var(--p-700);
}

.acv-sample-lines {
    display: flex;
    flex-direction: column;
    gap: 3px;
    padding: 6px 5px 0;
}

.acv-sample-lines span {
    display: block;
    height: 2px;
    border-radius: 1px;
    background: var(--n-200);
}

.acv-sample-txt { flex: 1; min-width: 0; }

.acv-sample-k {
    font-size: var(--fs-sm);
    font-weight: var(--fw-semibold);
    color: var(--text-primary);
}

.acv-sample-d {
    margin: 2px 0 0;
    font-size: var(--fs-xs);
    line-height: var(--lh-snug);
    color: var(--text-tertiary);
}

.acv-sample-acts {
    display: flex;
    align-items: center;
    gap: 6px;
    flex-shrink: 0;
}

/* ---------- The rest of the upload card ---------- */

.acv-form {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
    gap: 12px;
    margin-block-start: 14px;
}

.acv-foot {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    flex-wrap: wrap;
    margin-block-start: 14px;
    padding-block-start: 12px;
    border-block-start: 1px solid var(--border-subtle);
}

.acv-foot-opt {
    display: flex;
    align-items: center;
    gap: 9px;
    font-size: var(--fs-sm);
    color: var(--text-secondary);
}

.acv-foot-acts { display: flex; align-items: center; gap: 6px; }

/* ---------- What is stored ---------- */

.acv-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(228px, 1fr));
    gap: 14px;
    padding: 16px;
}

.acv-card {
    position: relative;
    display: flex;
    flex-direction: column;
    border-radius: var(--r-lg);
    background: var(--surface-raised);
    transition: box-shadow var(--dur-fast) var(--ease-out);
}

/* The card's edge, drawn over its children rather than under them. An
   inset shadow on the card itself is painted beneath everything inside
   it, and the preview tile fills the card to its top two corners - so
   the ring simply vanished along the top half of every card. */
.acv-card::after {
    content: "";
    position: absolute;
    inset: 0;
    z-index: 1;
    border-radius: inherit;
    box-shadow: inset 0 0 0 1px var(--border-subtle);
    pointer-events: none;
    transition: box-shadow var(--dur-fast) var(--ease-out);
}

.acv-card:hover { box-shadow: var(--shadow-md); }
.acv-card:hover::after { box-shadow: inset 0 0 0 1px var(--border-strong); }

/* The one every export uses. A ring rather than the 2px rail the row
   list drew: a card has no left edge to hang a marker off. Declared
   after the hover rule and at the same weight, so hovering the default
   card does not trade its brand ring for a grey one. */
.acv-card.is-default { box-shadow: var(--shadow-xs); }
.acv-card.is-default::after { box-shadow: inset 0 0 0 1.5px var(--p-300); }

/* The sheet is the preview control. What a recruiter wants to look at
   is the page, so the page is what they press - the button beneath is
   there for the pointer that never finds it. */
.acv-card-prev {
    position: relative;
    display: block;
    width: 100%;
    padding: 16px 16px 14px;
    border: 0;
    border-radius: var(--r-lg) var(--r-lg) 0 0;
    background: var(--n-50);
    cursor: pointer;
    overflow: hidden;
}

.acv-card.is-default .acv-card-prev { background: var(--p-50); }

.acv-card-prev:focus-visible {
    outline: 2px solid var(--border-focus);
    outline-offset: -2px;
}

/* A4 at 1:1.41, which is what almost every uploaded sample is. */
.acv-page {
    display: flex;
    flex-direction: column;
    width: 96px;
    height: 136px;
    margin: 0 auto;
    border-radius: 3px;
    background: var(--n-0);
    box-shadow: var(--shadow-sm), inset 0 0 0 1px var(--border-subtle);
    overflow: hidden;
    transition: transform var(--dur-fast) var(--ease-out);
}

.acv-card:hover .acv-page { transform: translateY(-2px); }

/* The letterhead. Left white with a brand rule under it rather than
   filled: a logo lifted out of a CV is usually dark on transparent,
   and it would disappear on a dark band. */
.acv-page-band {
    display: grid;
    place-items: center;
    height: 32px;
    flex-shrink: 0;
    padding: 0 8px;
    border-block-end: 2px solid var(--p-700);
}

.acv-page-band img { max-width: 100%; max-height: 20px; object-fit: contain; }

/* Stands in when the extraction found no logo, so the sheet still
   reads as this template rather than as a blank page. */
.acv-page-brand {
    font-size: var(--fs-xs);
    font-weight: var(--fw-bold);
    letter-spacing: 0.06em;
    color: var(--p-700);
}

/* Ruled lines, not content: the body of a stored sample is not
   rendered here, and drawing something that looked like it was would
   be a claim the card cannot make. */
.acv-page-body {
    display: flex;
    flex-direction: column;
    gap: 5px;
    padding: 11px 10px 0;
}

.acv-page-h {
    height: 4px;
    width: 46%;
    border-radius: 1px;
    background: var(--n-300);
}

.acv-page-h2 { margin-block-start: 5px; }

.acv-page-l {
    height: 2px;
    border-radius: 1px;
    background: var(--n-200);
}

/* The affordance that says the sheet is pressable. It fades up over
   the bottom of the tile on hover, so a resting grid stays quiet. */
.acv-card-open {
    position: absolute;
    inset-inline: 0;
    inset-block-end: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 5px;
    padding: 10px 0 9px;
    background: linear-gradient(to top, var(--n-50) 58%, transparent);
    font-size: var(--fs-xs);
    font-weight: var(--fw-semibold);
    color: var(--p-700);
    opacity: 0;
    transform: translateY(4px);
    transition: opacity var(--dur-fast) var(--ease-out),
                transform var(--dur-fast) var(--ease-out);
}

.acv-card.is-default .acv-card-open {
    background: linear-gradient(to top, var(--p-50) 58%, transparent);
}

.acv-card:hover .acv-card-open,
.acv-card-prev:focus-visible .acv-card-open {
    opacity: 1;
    transform: none;
}

.acv-card-txt {
    flex: 1;
    min-width: 0;
    padding: 12px 14px 0;
}

.acv-card-nm {
    margin: 0;
    font-size: var(--fs-md);
    font-weight: var(--fw-semibold);
    letter-spacing: -0.005em;
    color: var(--text-primary);
}

.acv-card-meta {
    display: flex;
    align-items: center;
    gap: 6px;
    margin: 6px 0 0;
    font-size: var(--fs-xs);
    color: var(--text-tertiary);
}

.acv-card-pills {
    display: flex;
    align-items: center;
    gap: 4px;
    flex-wrap: wrap;
    margin-block-start: 9px;
}

/* !important because QPill sets flex-shrink and white-space inline, and
   an inline style outranks a stylesheet. It is right for a pill to
   refuse to wrap or shrink almost everywhere - but a two-column grid of
   150px cards on a phone is the one place a long label, or a longer
   translation of a short one, rode straight out of the card. Here it
   ellipsises instead. */
.acv-card-pills > * {
    min-width: 0;
    flex-shrink: 1 !important;
    overflow: hidden;
    text-overflow: ellipsis;
}

.acv-card-note {
    margin: 8px 0 0;
    font-size: var(--fs-xs);
    line-height: var(--lh-snug);
    color: var(--text-tertiary);
}

.acv-card-acts {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 6px;
    margin-block-start: 12px;
    padding: 7px 8px 7px 10px;
    border-block-start: 1px solid var(--border-subtle);
}

/* Holds the left edge of the action bar on the default card, where
   "Make default" is not offered - without it the delete button slides
   across and the grid stops lining up row to row. */
.acv-card-inuse {
    padding-inline-start: 2px;
    font-size: var(--fs-xs);
    font-weight: var(--fw-semibold);
    color: var(--text-tertiary);
}

/* ---------- The preview ---------- */

/* The logo the extraction found, above the sample it came out of: it
   is the one fact the preview exists to confirm. Also carries the
   note above the shipped example. */
.acv-prev-logo {
    display: flex;
    align-items: center;
    gap: 10px;
    margin-block-end: 12px;
    padding: 10px 12px;
    border-radius: var(--r-md);
    background: var(--n-25);
    box-shadow: inset 0 0 0 1px var(--border-subtle);
}

.acv-prev-logo img { max-height: 36px; max-width: 120px; object-fit: contain; }

/* The shape the sample is about to take, while it is being fetched. A
   modal that opens on a centred "Loading…" resizes the moment the file
   lands. */
.acv-prev-skel { height: 320px; }

.acv-frame {
    width: 100%;
    height: 72vh;
    border: 1px solid var(--border-subtle);
    border-radius: var(--r-md);
    background: var(--n-100);
}

/* Extracted text is what the model is actually handed, so it is set
   as the page it came from rather than as a blob in a modal. */
.acv-prev {
    max-height: 68vh;
    overflow: auto;
    padding: 20px;
    border-radius: var(--r-md);
    background: var(--n-100);
}

.acv-sheet {
    max-width: 794px;
    margin: 0 auto;
    padding: 40px 48px;
    background: var(--n-0);
    box-shadow: var(--shadow-md);
}

.acv-sheet pre {
    margin: 0;
    font-family: inherit;
    font-size: var(--fs-base);
    line-height: var(--lh-relaxed);
    color: var(--text-primary);
    white-space: pre-wrap;
    overflow-wrap: anywhere;
}

/* A tile whose only affordance appears on hover has none at all where
   there is no pointer, so on touch it is simply always there. */
@media (hover: none) {
    .acv-card-open { opacity: 1; transform: none; }
}

@media (max-width: 640px) {
    /* Two sheets still fit a phone at this size, and one per row would
       make a list of five templates a page and a half of scrolling. */
    .acv-grid {
        grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
        gap: 10px;
        padding: 12px;
    }

    .acv-page { width: 78px; height: 110px; }
    .acv-page-band { height: 26px; }

    /* The example's two buttons need the full width once the strip
       wraps; indented to the text so they stay part of it. */
    .acv-sample { flex-wrap: wrap; }
    .acv-sample-acts { width: 100%; padding-inline-start: 50px; }

    .acv-sheet { padding: 24px; }
}


/* Two tabs fit any width the settings column is given, so the
   primitive's scroll never engages here; what does get tight is the
   padding around them. */
@media (max-width: 640px) {
    .otpl-tab { padding-inline: 10px; gap: 6px; }
}


/* ============================================================
   28. Recruiter: Question library (ql-*)

   The bank every assessment draws from. The row was rebuilt once
   already - the question leads it, at reading size, with its id,
   type, difficulty and tags underneath as metadata, instead of
   sharing a six-column table with Difficulty and being cut at 90
   characters to fit. That part stands.

   What did not stand was everything around the row, because the
   page had no answer for a bank with five hundred questions in it:
   a strip of type chips was the only way to narrow, the Filter
   button beside them was disabled, the list stopped at fifty, and a
   row could not be opened at all.

   So three things are new here, and each one is a control the page
   was missing rather than a restyling of one it had:

     .ql-bar     search, difficulty, source and sort, with the type
                 chips beneath them - one band, because they are one
                 act. The chips are facets now: the count on a chip
                 is what it leaves after the field above it, so the
                 strip has to sit under the field rather than beside
                 it.
     .ql-row     a button. It is the only way into the editor, so it
                 carries a hover, a focus ring and a chevron that
                 says it opens - none of which a div can be given
                 honestly.
     .ql-foot    where the list has got to, and the way further. The
                 list used to end at fifty with a grey sentence.

   And two smaller ones the new controls needed: .ql-hit, the search
   term marked inside the question, and .ql-stats, the measured
   figures in the editor drawer.

   What is here:
     .ql-bar     the toolbar band: search, selects, type facets
     .ql-chip    one type, .is-on applied, .is-empty found nothing
     .ql-row     one question, as a button into the editor
     .ql-hit     the searched-for words inside a question
     .ql-foot    the count and the Show-more button
     .ql-more    the read-cap notice
     .ql-ai      what the generator just produced
     .ql-stats   the editor drawer's measured figures
     .ql-imp-*   the import drawer's two stages
   ============================================================ */

/* ---------- The toolbar ---------- */

/* One raised band above the list, holding both rows. They were two
   separate acts on the page before - chips above the card, nothing
   else anywhere - and a facet count only makes sense directly under
   the field that produced it. */
.ql-bar {
    display: flex;
    flex-direction: column;
    gap: 8px;
    padding: 8px;
    background: var(--surface-raised);
    border: 1px solid var(--border-default);
    border-radius: var(--r-lg);
    box-shadow: var(--shadow-xs);
}

.ql-bar-row {
    display: flex;
    align-items: center;
    gap: 8px;
    flex-wrap: wrap;
}

/* The search field takes what the selects do not. It is the control
   the page is opened for once the bank is past a screenful, so it
   gets the room rather than an equal share of it. */
.ql-search {
    flex: 1 1 220px;
    min-width: 180px;
}

/* Chromium and Safari draw their own clear button inside a search
   input, at their own size and colour. The field is 32px tall here
   and the native control crowds it. */
.ql-search input[type="search"]::-webkit-search-cancel-button {
    -webkit-appearance: none;
    appearance: none;
}

.ql-sel {
    flex: 0 1 auto;
    width: auto;
    min-width: 128px;
}

/* ---------- Filtering by type ---------- */

/* Scrolls sideways rather than wrapping to three lines: a bank with
   eleven types would otherwise push the first question below the
   fold. */
.ql-types {
    display: flex;
    align-items: center;
    gap: 6px;
    overflow-x: auto;
    overscroll-behavior-x: contain;
}

.ql-chip {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    flex: 0 0 auto;
    height: 28px;
    padding: 0 11px;
    border: none;
    border-radius: var(--r-full);
    background: transparent;
    color: var(--text-secondary);
    font: inherit;
    font-size: var(--fs-sm);
    font-weight: var(--fw-medium);
    white-space: nowrap;
    cursor: pointer;
    transition: background-color var(--dur-fast) var(--ease-out),
                color var(--dur-fast) var(--ease-out);
}

.ql-chip:hover { background: var(--n-75); color: var(--text-primary); }

/* The ring every other control in the product uses. A bare button with
   a background and no outline rule shows the UA ring on some browsers
   and nothing at all on others. */
.ql-chip:focus-visible { outline: none; box-shadow: var(--shadow-focus); }

.ql-chip.is-on {
    background: var(--p-50);
    color: var(--p-800);
    font-weight: var(--fw-semibold);
}

/* A type the search above has emptied. Dimmed rather than removed:
   the chip that is applied has to stay reachable, and a strip that
   reshuffles itself on every keystroke is unusable to aim at. */
.ql-chip.is-empty:not(.is-on) { opacity: 0.45; }

/* The count travels with the chip and inverts with it, so an applied
   filter stays one object rather than a tinted chip with a grey number
   stuck on its end. */
.ql-chip-n {
    padding: 0 6px;
    border-radius: var(--r-full);
    background: var(--n-100);
    color: var(--text-tertiary);
    font-size: var(--fs-2xs);
    font-weight: var(--fw-bold);
    font-variant-numeric: tabular-nums;
    line-height: 17px;
}

.ql-chip.is-on .ql-chip-n { background: var(--p-100); color: var(--p-700); }

/* ---------- One question ---------- */

/* A button, so it is tabbable and announced as something that opens.
   Which means undoing everything a UA gives a button - the width, the
   centring, the border, the inherited-nothing font - before it can
   look like a row again. */
.ql-row {
    display: flex;
    align-items: flex-start;
    gap: 12px;
    width: 100%;
    margin: 0;
    padding: 12px 16px;
    border: none;
    border-block-end: 1px solid var(--border-subtle);
    border-radius: 0;
    background: transparent;
    font: inherit;
    text-align: start;
    cursor: pointer;
    transition: background-color var(--dur-fast) var(--ease-out);
}

.ql-row:last-child { border-block-end: none; }
.ql-row:hover { background: var(--n-25); }

/* Inset, because the row runs the full width of the card: an outset
   ring on the first row would be clipped by the card's own corner. */
.ql-row:focus-visible {
    outline: none;
    box-shadow: inset var(--shadow-focus);
    background: var(--n-25);
}

.ql-txt { flex: 1; min-width: 0; }

/* The question, at reading size and on as many lines as it needs up to
   two. It was truncated at 90 characters to fit a table cell, which cut
   most behavioural questions mid-clause. */
.ql-q {
    margin: 0;
    font-size: var(--fs-md);
    line-height: var(--lh-snug);
    color: var(--text-primary);
}

/* The searched-for words inside the question. Gold rather than the
   browser's yellow, because the product already spends yellow on AI
   and a UA <mark> is a different yellow again. */
.ql-hit {
    padding: 0 1px;
    border-radius: var(--r-xs);
    background: var(--gold-100);
    color: inherit;
    font-weight: var(--fw-semibold);
}

/* Everything that is not the question: its id, status, difficulty and
   tags, on one line under it. */
.ql-meta {
    display: flex;
    align-items: center;
    gap: 8px;
    flex-wrap: wrap;
    margin-block-start: 6px;
}

.ql-id {
    font-family: var(--font-mono);
    font-size: var(--fs-2xs);
    font-weight: var(--fw-semibold);
    letter-spacing: 0.02em;
    color: var(--text-tertiary);
}

.ql-diff { font-size: var(--fs-xs); color: var(--text-tertiary); }

.ql-tag {
    padding: 1px 7px;
    border-radius: var(--r-sm);
    background: var(--n-75);
    box-shadow: inset 0 0 0 1px var(--border-subtle);
    font-family: var(--font-mono);
    font-size: var(--fs-2xs);
    color: var(--text-secondary);
}

/* How many times the question has actually been asked. Right-aligned
   and tabular so a column of them can be compared down the page. */
.ql-used {
    flex-shrink: 0;
    min-width: 56px;
    text-align: end;
    font-family: var(--font-mono);
    font-size: var(--fs-sm);
    font-variant-numeric: tabular-nums;
    color: var(--text-secondary);
}

.ql-used-k {
    display: block;
    font-family: var(--font-sans);
    font-size: var(--fs-2xs);
    font-weight: var(--fw-semibold);
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--text-disabled);
}

/* The affordance. Quiet until the row is under the pointer, because a
   column of dark chevrons down a list is louder than the questions it
   sits beside. */
.ql-go {
    display: flex;
    flex-shrink: 0;
    margin-block-start: 2px;
    color: var(--text-disabled);
    transition: color var(--dur-fast) var(--ease-out),
                transform var(--dur-fast) var(--ease-out);
}

.ql-row:hover .ql-go,
.ql-row:focus-visible .ql-go {
    color: var(--text-secondary);
    transform: translateX(2px);
}

/* ---------- Where the list has got to ---------- */

/* The count and the way further, on the card's last row. The list used
   to stop at fifty and say only that it had. */
.ql-foot {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    padding: 10px 16px;
    background: var(--n-25);
    border-block-start: 1px solid var(--border-subtle);
}

.ql-foot-n {
    font-size: var(--fs-xs);
    font-variant-numeric: tabular-nums;
    color: var(--text-tertiary);
}

/* The read itself was capped, which is the one thing the toolbar above
   cannot say: everything there filters what was loaded. */
.ql-more {
    margin: 0;
    padding: 9px 16px;
    background: var(--n-25);
    border-block-start: 1px solid var(--border-subtle);
    font-size: var(--fs-xs);
    color: var(--text-tertiary);
}

/* ---------- What the generator returned ---------- */

/* Gold, which is what the product uses for AI everywhere else, and
   above the list rather than below it: the question it just wrote is
   the first row of that list, and this note is why. */
.ql-ai {
    display: flex;
    align-items: flex-start;
    gap: 10px;
    padding: 11px 13px;
    border-radius: var(--r-lg);
    background: var(--gold-50);
    box-shadow: inset 0 0 0 1px var(--gold-200);
}

.ql-ai-mark {
    display: grid;
    place-items: center;
    width: 26px;
    height: 26px;
    flex-shrink: 0;
    border-radius: var(--r-md);
    background: var(--gold-500);
    color: var(--p-900);
}

.ql-ai-txt { min-width: 0; }

.ql-ai-k {
    font-size: var(--fs-sm);
    font-weight: var(--fw-semibold);
    color: var(--gold-800);
}

.ql-ai-d {
    margin: 2px 0 0;
    font-size: var(--fs-sm);
    line-height: var(--lh-snug);
    color: var(--text-secondary);
}

/* ---------- The editor drawer ---------- */

/* Room for a question rather than a line of one. Four rows is about
   two sentences, which is where a behavioural stem usually lands. */
.ql-ed-text {
    min-height: 96px;
    line-height: var(--lh-normal);
    resize: vertical;
}

/* What the bank has measured about this question, under the fields
   that can be typed and visibly not among them: sunken rather than
   raised, and no control anywhere in it. */
.ql-stats {
    padding: 12px 14px;
    border-radius: var(--r-lg);
    background: var(--surface-sunken);
    box-shadow: inset 0 0 0 1px var(--border-subtle);
}

.ql-stat-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(96px, 1fr));
    gap: 10px;
}

.ql-stat { min-width: 0; }

.ql-stat-k {
    display: block;
    font-size: var(--fs-2xs);
    font-weight: var(--fw-semibold);
    letter-spacing: 0.05em;
    text-transform: uppercase;
    color: var(--text-tertiary);
}

.ql-stat-v {
    display: block;
    margin-block-start: 2px;
    font-family: var(--font-mono);
    font-size: var(--fs-lg);
    font-variant-numeric: tabular-nums;
    color: var(--text-primary);
}

/* Why the figures above read as dashes, or how much they rest on. A
   correct-percentage off three answers and one off three hundred are
   the same number and not the same claim. */
.ql-stat-note {
    margin: 10px 0 0;
    font-size: var(--fs-xs);
    line-height: var(--lh-snug);
    color: var(--text-tertiary);
}

/* ---------- The import drawer ---------- */

/* "Paste, or upload" - a rule with the word in it, rather than two
   headings and the reader working out that they are alternatives. */
.ql-imp-or {
    display: flex;
    align-items: center;
    gap: 12px;
    font-size: var(--fs-xs);
    font-weight: var(--fw-semibold);
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--text-disabled);
}

.ql-imp-or::before,
.ql-imp-or::after {
    content: "";
    flex: 1;
    height: 1px;
    background: var(--border-subtle);
}

/* The paste box. Monospace because the format is columns: a question,
   then its type, then its options, one field per line. */
.ql-imp-paste {
    min-height: 150px;
    font-family: var(--font-mono);
    font-size: var(--fs-sm);
    line-height: var(--lh-normal);
}

/* The file input, which cannot be styled itself - only the box it sits
   in and the button it renders. */
.ql-imp-file {
    padding: 12px;
    border: 1px dashed var(--border-strong);
    border-radius: var(--r-lg);
    background: var(--n-25);
    font-size: var(--fs-sm);
    color: var(--text-secondary);
}

.ql-imp-file:focus-within {
    border-style: solid;
    border-color: var(--border-focus);
    box-shadow: var(--shadow-focus);
}

/* What the parse found, before anything is written. Counts first: they
   are the numbers that decide whether to go back or go on. */
.ql-imp-sum {
    display: flex;
    align-items: center;
    gap: 8px;
    flex-wrap: wrap;
}

.ql-imp-bank {
    margin-inline-start: auto;
    font-size: var(--fs-xs);
    color: var(--text-tertiary);
}

/* The parsed rows. Capped and scrolled inside the drawer body so the
   footer's Save stays where it was when the preview arrived. .tbl's own
   header pins; this only has to say that the box it pins to is this one and
   not the drawer around it. */
.ql-imp-rows {
    max-height: min(420px, 46vh);
    overflow: auto;
    overscroll-behavior: contain;
    border: 1px solid var(--border-subtle);
    border-radius: var(--r-lg);
    --tbl-top: 0px;
}

/* A row that will not import, marked on the row rather than only in its
   Status cell - the eye finds a tinted band before it finds a word at
   the far end of six columns. */
.ql-imp-bad > td { background: var(--danger-50); }

.ql-imp-q {
    max-width: 260px;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* The rescue control: the row named a skill the job does not list, and
   this is where a real one gets chosen. Bordered in the row's own
   danger tint so it reads as the thing to fix. */
.ql-imp-pick {
    height: 26px;
    max-width: 150px;
    padding: 0 6px;
    border: 1px solid var(--danger-200);
    border-radius: var(--r-sm);
    background: var(--n-0);
    font: inherit;
    font-size: var(--fs-xs);
    color: var(--text-primary);
}

.ql-imp-pick:focus-visible { outline: none; box-shadow: var(--shadow-focus); }

/* The status pill and, under it, the parse error the row failed on. The reason
   has to be visible: it was a title tooltip, which a touch user and a screen
   reader never get, and it is the only thing that says what to fix. */
.ql-imp-bad-cell {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 3px;
}

.ql-imp-why {
    font-size: var(--fs-xs);
    line-height: 1.4;
    color: var(--danger-700);
}

@media (max-width: 640px) {
    /* Each select takes a full half-row instead of being squeezed to
       its minimum beside three others. */
    .ql-search { flex-basis: 100%; }
    .ql-sel { flex: 1 1 calc(50% - 4px); min-width: 0; }

    .ql-row { flex-wrap: wrap; }
    /* The count keeps its own line rather than squeezing the question
       into a third of the row. */
    .ql-used { min-width: 0; text-align: start; }
    /* The chevron is the row's only decoration and the first thing to
       go when the row has to hold the question, its metadata and its
       count in one column. */
    .ql-go { display: none; }

    .ql-foot { flex-direction: column; align-items: stretch; gap: 8px; }
}


/* ============================================================
   29. Recruiter: Per-client assessment packs (pk-*)

   Which questions a given client's applicants answer. The screen
   opened with a gold band that looked like an AI result but was in
   fact the page title, and drew each client's packs into
   `repeat(3, 1fr)` with a right border on every cell but the last -
   so two packs left a third of the card empty, and four wrapped
   into a second row whose borders no longer lined up with the
   first.

   Packs are cards on an auto-fit grid instead. Three still sit in a
   row; two fill the width; five wrap without leaving a ladder of
   half-drawn rules behind them.

   What is here:
     .pk-client  the client's own band at the top of its card
     .pk-grid    that client's packs
     .pk-tile    one pack: what it covers, and how big it is
     .pk-edit    the strip that opens under the band to correct them
   ============================================================ */

.pk-client {
    display: flex;
    align-items: center;
    gap: 11px;
    padding: 12px 16px;
    border-block-end: 1px solid var(--border-subtle);
}

.pk-client-txt { flex: 1; min-width: 0; }

.pk-client-nm {
    font-size: var(--fs-lg);
    font-weight: var(--fw-semibold);
    letter-spacing: -0.01em;
    color: var(--text-primary);
}

.pk-client-sub { margin: 2px 0 0; font-size: var(--fs-xs); color: var(--text-tertiary); }

.pk-client-acts { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }

/* auto-fit, not three fixed columns. The gap is the separator, so no
   cell needs a border that then has to be cancelled on the last one. */
.pk-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
    gap: 10px;
    padding: 12px 16px 14px;
}

.pk-tile {
    display: flex;
    flex-direction: column;
    gap: 8px;
    padding: 11px 12px;
    border-radius: var(--r-lg);
    background: var(--n-25);
    box-shadow: inset 0 0 0 1px var(--border-subtle);
}

/* A pack applicants cannot skip is a different kind of object from one
   that is merely offered, so it is marked on the tile and not only by a
   pill inside it. */
.pk-tile.is-req {
    background: var(--p-50);
    box-shadow: inset 0 0 0 1px var(--p-100);
}

.pk-tile-nm {
    font-size: var(--fs-md);
    font-weight: var(--fw-semibold);
    letter-spacing: -0.005em;
    color: var(--text-primary);
    overflow-wrap: anywhere;
}

/* The role a pack covers is optional; a pack with none covers every
   role, which is a fact about it rather than a missing value. */
.pk-tile-all { color: var(--text-secondary); font-style: italic; }

.pk-tile-facts {
    display: flex;
    align-items: center;
    gap: 8px;
    flex-wrap: wrap;
    margin-block-start: auto;
    font-family: var(--font-mono);
    font-size: var(--fs-xs);
    color: var(--text-tertiary);
}

/* ---------- Correcting a pack ---------- */

/* Sunken, and directly under the client's band: it edits the packs in
   the grid below, and floating it on the card's own ground would read
   as a third thing on the card rather than as a mode the band is in. */
.pk-edit {
    padding: 10px 16px 12px;
    background: var(--n-25);
    border-block-end: 1px solid var(--border-subtle);
}

.pk-edit-row {
    display: flex;
    align-items: center;
    gap: 10px;
    flex-wrap: wrap;
    padding: 8px 0;
    border-block-start: 1px solid var(--border-subtle);
}

.pk-edit-row:first-child { border-block-start: none; }

/* The role field takes the slack; everything beside it is a fixed fact
   about the pack. */
.pk-edit-role { flex: 1 1 200px; min-width: 0; }

.pk-edit-n {
    font-family: var(--font-mono);
    font-size: var(--fs-xs);
    color: var(--text-tertiary);
    white-space: nowrap;
}

.pk-edit-foot {
    display: flex;
    align-items: center;
    gap: 10px;
    flex-wrap: wrap;
    margin-block-start: 10px;
    padding-block-start: 10px;
    border-block-start: 1px solid var(--border-subtle);
}

.pk-edit-hint { font-size: var(--fs-xs); line-height: var(--lh-snug); color: var(--text-tertiary); }

/* ---------- A client with no packs ---------- */

/* Not the shared zero state: that is a whole-section shape, and this is
   one card inside a list of clients that mostly do have packs. One line
   with the action inside it. */
.pk-none {
    padding: 16px;
    text-align: center;
    font-size: var(--fs-sm);
    color: var(--text-tertiary);
}

.pk-none-go {
    border: none;
    border-radius: var(--r-sm);
    background: none;
    font: inherit;
    font-weight: var(--fw-semibold);
    color: var(--p-700);
    cursor: pointer;
}

.pk-none-go:hover { text-decoration: underline; }
.pk-none-go:focus-visible { outline: none; box-shadow: var(--shadow-focus); }


/* ============================================================
   30. Recruiter: SLA & approvals (sla-*)

   Three tabs over one stored document: what the agency promises,
   what it actually did, and who has to say yes to an offer.

   The middle tab is the one that changed shape. Compliance was a
   seven-column table of raw counts, and the column that matters -
   how much of the target was actually met - was a percentage in
   the last cell, so comparing two targets meant reading two
   numbers twelve columns apart. Each target is a row with its own
   meter now: goal against actual, and a bar split into met,
   breached and still running. A short bar is a target that is
   mostly unmeasured; a bar that is mostly hatched is one nothing
   has finished under yet. Neither needed arithmetic.

   The approval chain was a stack of tinted boxes with a "1." typed
   into the first cell of each. Steps run in order, and an ordered
   list of things that hand off to each other is a chain - so it is
   drawn as one, with the number in a node and a line connecting
   each node to the next. The line stops at the last step, which is
   where the offer is finally approved.

   What is here:
     .sla-targets  the target fields
     .sla-num      a number that is a setting, not a text box
     .sla-tfoot    what that target is actually doing, under its field
     .sla-cell     one tier override in the defaults table
     .sla-sum      the compliance card's headline figure
     .sla-metric   one measured target, with its meter
     .sla-legend   the counts a meter is made of, in its own colours
     .sla-chain    the approval chain, .sla-step one node of it
     .sla-note     the two asides: informational, and load-bearing
   ============================================================ */

/* ---------- Targets ---------- */

/* auto-fit rather than the two fixed columns it had: beside the 244px
   settings rail, `1fr 1fr` put a 90px number field next to a label that
   wrapped onto three lines. */
.sla-targets {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
    gap: 12px 14px;
}

/* A number with its unit attached, rather than a number and a grey word
   floating beside it. The unit is part of the value - "30" and "30
   days" are different settings - so it sits inside the field's frame. */
.sla-num {
    display: flex;
    align-items: center;
    height: 34px;
    border: 1px solid var(--border-default);
    border-radius: var(--r-md);
    background: var(--n-0);
    overflow: hidden;
    transition: border-color var(--dur-fast) var(--ease-out),
                box-shadow var(--dur-fast) var(--ease-out);
}

.sla-num:hover { border-color: var(--border-strong); }

/* The ring goes on the frame, because the input inside it has no border
   of its own to carry one. */
.sla-num:focus-within {
    border-color: var(--border-focus);
    box-shadow: var(--shadow-focus);
}

.sla-num input {
    flex: 1;
    min-width: 0;
    height: 100%;
    padding: 0 10px;
    border: none;
    background: none;
    font: inherit;
    font-family: var(--font-mono);
    font-size: var(--fs-sm);
    font-variant-numeric: tabular-nums;
    color: var(--text-primary);
}

.sla-num input:focus { outline: none; }

.sla-num-u {
    align-self: stretch;
    display: grid;
    place-items: center;
    padding: 0 10px;
    border-inline-start: 1px solid var(--border-subtle);
    background: var(--n-25);
    font-size: var(--fs-xs);
    color: var(--text-tertiary);
    white-space: nowrap;
}

/* What the target above is actually doing, under the field that sets it.
   The number and its result were one tab apart, so a target was retyped
   without the one fact that says whether the current one is being kept.
   It arrives with the compliance scan rather than with the page - a foot
   that appears is less disruptive than one that changes what it says. */
.sla-tfoot {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    font-family: var(--font-mono);
    font-size: var(--fs-xs);
    font-variant-numeric: tabular-nums;
    color: var(--text-tertiary);
}

/* The status dot. Grey by default: still running and no cases at all are
   both "not a pass", and neither should borrow the good colour. */
.sla-tfoot i {
    width: 6px;
    height: 6px;
    flex-shrink: 0;
    border-radius: var(--r-full);
    background: var(--n-300);
}

.sla-tfoot.is-good { color: var(--success-700); }
.sla-tfoot.is-good i { background: var(--success-500); }
.sla-tfoot.is-warn { color: var(--warning-700); }
.sla-tfoot.is-warn i { background: var(--warning-500); }
.sla-tfoot.is-bad  { color: var(--danger-600); }
.sla-tfoot.is-bad i { background: var(--danger-500); }

/* The foot is optional and arrives late, so the fields align on their
   tops rather than stretching a row to the tallest one in it. */
.sla-targets > .sla-target { align-content: start; }

/* ---------- Tier defaults ---------- */

/* One override inside the defaults table. Placeholder rather than value
   is load-bearing here: a blank cell inherits the base target, and the
   greyed number in it is what it would inherit. */
.sla-cell {
    width: 84px;
    height: 30px;
    padding: 0 8px;
    border: 1px solid var(--border-default);
    border-radius: var(--r-sm);
    background: var(--n-0);
    font: inherit;
    font-family: var(--font-mono);
    font-size: var(--fs-xs);
    font-variant-numeric: tabular-nums;
    color: var(--text-primary);
}

.sla-cell::placeholder { color: var(--text-disabled); }
.sla-cell:hover { border-color: var(--border-strong); }
.sla-cell:focus { outline: none; border-color: var(--border-focus); box-shadow: var(--shadow-focus); }

/* ---------- What the targets measured ---------- */

/* The one number the tab is opened for, over every target at once, with
   the three counts it is made of beside it. It was absent: the card
   opened on seven rows and the reader did the arithmetic. */
.sla-sum {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: 12px 20px;
    padding: 14px 16px;
    border-block-end: 1px solid var(--border-subtle);
    background: var(--n-25);
}

.sla-sum-hd { display: flex; align-items: baseline; gap: 8px; min-width: 0; }

.sla-sum-n {
    font-family: var(--font-mono);
    font-size: var(--fs-3xl);
    font-weight: var(--fw-bold);
    font-variant-numeric: tabular-nums;
    letter-spacing: -0.02em;
    line-height: 1;
    color: var(--text-primary);
}

.sla-sum-n.is-good { color: var(--success-600); }
.sla-sum-n.is-warn { color: var(--gold-600); }
.sla-sum-n.is-bad  { color: var(--danger-600); }

/* Nothing finished yet is not a score, so it is not coloured like one. */
.sla-sum-n.is-none { color: var(--text-disabled); }

.sla-sum-l {
    font-size: var(--fs-xs);
    font-weight: var(--fw-semibold);
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--text-tertiary);
}

.sla-metric {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    gap: 4px 14px;
    align-items: center;
    padding: 12px 16px;
    border-block-end: 1px solid var(--border-subtle);
}

.sla-metric:last-child { border-block-end: none; }

.sla-metric-k {
    font-size: var(--fs-md);
    font-weight: var(--fw-semibold);
    letter-spacing: -0.005em;
    color: var(--text-primary);
}

/* Goal against actual, on one line under the name. Tabular so the two
   numbers in a pair line up, and the pairs line up down the column. */
.sla-metric-v {
    display: flex;
    align-items: baseline;
    gap: 8px;
    flex-wrap: wrap;
    margin-block-start: 3px;
    font-family: var(--font-mono);
    font-size: var(--fs-xs);
    font-variant-numeric: tabular-nums;
    color: var(--text-tertiary);
}

/* The measured number, which is the one being judged. */
.sla-metric-act { font-weight: var(--fw-semibold); color: var(--text-secondary); }
.sla-metric-act.is-bad { color: var(--danger-600); }

.sla-metric-pill { grid-column: 2; grid-row: 1 / span 2; }

/* The bar: met, breached, and still running, in that order, across the
   full width of the row. It spans both columns because the pill beside
   it is the same fact rounded to a percentage - putting them on one
   line would say it twice. */
.sla-meter {
    grid-column: 1 / -1;
    display: flex;
    height: 6px;
    margin-block-start: 8px;
    border-radius: var(--r-full);
    background: var(--n-100);
    overflow: hidden;
}

.sla-meter span { display: block; height: 100%; }
.sla-meter-met { background: var(--success-500); }
.sla-meter-bad { background: var(--danger-500); }

/* Still running is not a pass and not a breach. Hatched rather than a
   third solid colour: a solid amber segment reads as a warning state,
   and a case that has not finished has not gone wrong. */
.sla-meter-run {
    background: repeating-linear-gradient(
        135deg,
        var(--n-200) 0 3px,
        var(--n-150) 3px 6px);
}

/* The three counts a bar is made of, in the bar's own colours. The bar
   used to be the only rendering of them and the numbers were readable to
   a screen reader alone, so a sighted reader got three coloured widths
   and no key anywhere on the page. Repeating them under every bar costs
   one line and removes the need for a key entirely. */
.sla-legend {
    grid-column: 1 / -1;
    display: flex;
    flex-wrap: wrap;
    gap: 4px 14px;
    margin-block-start: 7px;
}

.sla-lg {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    font-family: var(--font-mono);
    font-size: var(--fs-xs);
    font-weight: var(--fw-semibold);
    font-variant-numeric: tabular-nums;
    color: var(--text-secondary);
}

/* The word after the count is the label, not the number. */
.sla-lg span {
    font-family: var(--font-sans);
    font-weight: var(--fw-regular);
    color: var(--text-tertiary);
}

.sla-lg i {
    width: 7px;
    height: 7px;
    flex-shrink: 0;
    border-radius: var(--r-xs);
}

.sla-lg.is-met i { background: var(--success-500); }
.sla-lg.is-bad i { background: var(--danger-500); }

.sla-lg.is-run i {
    background: repeating-linear-gradient(
        135deg,
        var(--n-200) 0 2px,
        var(--n-150) 2px 4px);
}

/* A zero is worth printing - it says the segment is empty rather than
   missing - but it is not worth reading first. */
.sla-lg.is-zero, .sla-lg.is-zero span { color: var(--text-disabled); }
.sla-lg.is-zero i { opacity: 0.45; }

/* On the summary strip the same legend is the tenant-wide total, so it
   carries the weight of a figure rather than of a caption. */
.sla-sum .sla-legend { grid-column: auto; margin-block-start: 0; gap: 4px 18px; }
.sla-sum .sla-lg { font-size: var(--fs-sm); }

/* A target nothing could be measured against. Said, not dropped: a row
   that disappears reads as a target that is fine. */
.sla-supp {
    display: flex;
    align-items: flex-start;
    gap: 8px;
    padding: 10px 16px;
    border-block-start: 1px solid var(--border-subtle);
    background: var(--n-25);
    font-size: var(--fs-xs);
    line-height: var(--lh-snug);
    color: var(--text-secondary);
}

.sla-supp svg { flex-shrink: 0; margin-block-start: 1px; color: var(--text-disabled); }
.sla-supp b { font-weight: var(--fw-semibold); color: var(--text-primary); }

/* ---------- The approval chain ---------- */

.sla-chain { display: flex; flex-direction: column; }

.sla-step {
    position: relative;
    display: grid;
    grid-template-columns: 26px minmax(0, 1fr);
    gap: 12px;
    padding-block-end: 14px;
}

.sla-step:last-child { padding-block-end: 0; }

/* The line to the next step, drawn by the step above it and stopped
   before the last node - the chain ends at the final approver, and a
   line running off the bottom says there is another step coming. */
.sla-step:not(:last-child)::before {
    content: "";
    position: absolute;
    inset-block: 30px 0;
    inset-inline-start: 12px;
    width: 2px;
    border-radius: var(--r-full);
    background: var(--border-default);
}

.sla-step-n {
    position: relative;
    z-index: 1;
    display: grid;
    place-items: center;
    width: 26px;
    height: 26px;
    border-radius: var(--r-full);
    background: var(--p-50);
    color: var(--p-700);
    box-shadow: inset 0 0 0 1px var(--p-100);
    font-family: var(--font-mono);
    font-size: var(--fs-xs);
    font-weight: var(--fw-bold);
}

.sla-step-box {
    display: flex;
    flex-direction: column;
    gap: 8px;
    padding: 10px 12px;
    border-radius: var(--r-lg);
    background: var(--n-25);
    box-shadow: inset 0 0 0 1px var(--border-subtle);
}

.sla-step-fields {
    display: grid;
    grid-template-columns: minmax(0, 1.3fr) minmax(0, 1fr) minmax(0, 1.3fr) 96px auto;
    gap: 8px;
    align-items: center;
}

/* Reorder and remove, kept together at the end of the row. Order is the
   whole meaning of a chain and there was no way to change it - moving a
   step meant deleting it and retyping the ones below. */
.sla-step-act { display: flex; align-items: center; gap: 2px; }

/* Every control in the step row, sized once. They were five copies of
   the same inline style constant. */
.sla-step-fields .input { height: 30px; font-size: var(--fs-sm); }

/* The hours field, with its unit beside it. */
.sla-step-h { display: flex; align-items: center; gap: 5px; }

.sla-step-h input {
    width: 100%;
    min-width: 0;
    height: 30px;
    padding: 0 8px;
    border: 1px solid var(--border-default);
    border-radius: var(--r-sm);
    background: var(--n-0);
    font: inherit;
    font-family: var(--font-mono);
    font-size: var(--fs-xs);
    font-variant-numeric: tabular-nums;
    color: var(--text-primary);
}

.sla-step-h input:focus { outline: none; border-color: var(--border-focus); box-shadow: var(--shadow-focus); }

/* How many people the step would actually reach. It is the only place a
   role that resolves to nobody is visible before an offer goes out one
   approval short, so a zero is amber and not grey. */
.sla-reach {
    display: flex;
    align-items: center;
    gap: 6px;
    font-size: var(--fs-xs);
    color: var(--text-tertiary);
}

.sla-reach svg { flex-shrink: 0; }

.sla-step.is-dead .sla-step-n {
    background: var(--warning-50);
    color: var(--warning-700);
    box-shadow: inset 0 0 0 1px var(--warning-100);
}

.sla-step.is-dead .sla-step-box { box-shadow: inset 0 0 0 1px var(--warning-100); }
.sla-step.is-dead .sla-reach { color: var(--warning-700); }

/* The step the Finance rule appends at runtime. The chain is not what the
   list above says it is whenever that rule is armed, and the rule lived on
   a separate card - so it is drawn in the chain, dashed, because whether it
   happens depends on the offer rather than on this screen. */
.sla-step-cond .sla-step-box {
    background: var(--warning-50);
    box-shadow: none;
    border: 1px dashed var(--warning-100);
}

.sla-step-n.is-cond {
    background: var(--warning-50);
    color: var(--warning-700);
    box-shadow: inset 0 0 0 1px var(--warning-100);
}

.sla-cond-hd { display: flex; align-items: baseline; flex-wrap: wrap; gap: 4px 8px; }

.sla-cond-k {
    font-size: var(--fs-sm);
    font-weight: var(--fw-semibold);
    color: var(--text-primary);
}

.sla-cond-w { font-size: var(--fs-xs); color: var(--text-tertiary); }

/* Where the chain ends. Without it the last approver reads as a step still
   waiting on one more, which is exactly the thing this list is consulted
   to settle. */
.sla-step-end { align-items: center; padding-block-end: 0; }

.sla-step-n.is-end {
    background: var(--success-50);
    color: var(--success-700);
    box-shadow: inset 0 0 0 1px var(--success-100);
}

.sla-end-k {
    align-self: center;
    font-size: var(--fs-sm);
    font-weight: var(--fw-semibold);
    color: var(--success-700);
}

/* No chain at all is a configuration, not an empty list: offers are
   approved the moment they are submitted. */
.sla-nochain {
    padding: 16px;
    border-radius: var(--r-lg);
    background: var(--n-25);
    box-shadow: inset 0 0 0 1px var(--border-subtle);
    text-align: center;
    font-size: var(--fs-sm);
    line-height: var(--lh-snug);
    color: var(--text-tertiary);
}

/* ---------- Asides ---------- */

/* The informational one: something worth knowing about the fields
   above, which can be read once and skipped afterwards. */
.sla-note {
    display: flex;
    align-items: flex-start;
    gap: 8px;
    margin-block-start: 14px;
    padding: 10px 12px;
    border-radius: var(--r-md);
    background: var(--info-50);
    color: var(--info-700);
    font-size: var(--fs-xs);
    line-height: var(--lh-snug);
}

.sla-note svg { flex-shrink: 0; margin-block-start: 1px; }

/* The one that has to be read. Amber, because what it says is that most
   of what was just typed changes nothing outside this screen - styled
   as an aside, that is how a value which reaches nowhere gets typed in,
   saved and believed. */
.sla-note-warn { background: var(--warning-50); color: var(--warning-700); }

/* A switch that is drawn and does nothing, kept visible because both of
   these shipped as working toggles. Removing them reads as a feature
   that vanished; leaving them live keeps making the promise. */
.sla-off {
    display: flex;
    align-items: flex-start;
    gap: 10px;
    color: var(--text-tertiary);
    cursor: not-allowed;
}

/* The switches that do work, and what each one costs. */
.sla-opt {
    display: flex;
    align-items: flex-start;
    gap: 10px;
    cursor: pointer;
}

/* Both rows are labels, and a label's contents stay inline unless they are
   told otherwise - which is why the switch and its explanation have to be
   made blocks rather than left to wrap around each other. */
.sla-opt-txt { min-width: 0; }

.sla-opt-k {
    display: block;
    font-size: var(--fs-sm);
    font-weight: var(--fw-medium);
    line-height: var(--lh-snug);
    color: var(--text-primary);
}

.sla-off .sla-opt-k { font-weight: var(--fw-regular); color: var(--text-tertiary); }

.sla-opt-d {
    display: block;
    margin: 2px 0 0;
    max-width: 66ch;
    font-size: var(--fs-xs);
    line-height: var(--lh-snug);
    color: var(--text-tertiary);
}

/* The deferred pair, set apart from the switch that works. A rule rather than
   whitespace: between two label rows, a gap reads as spacing in the same list
   rather than as a boundary. */
.sla-opts {
    display: flex;
    flex-direction: column;
    gap: 12px;
    margin-block-start: 14px;
    padding-block-start: 12px;
    border-block-start: 1px solid var(--border-subtle);
}

/* One escalation threshold is a two-digit percentage; at the width of the
   field grid it would otherwise stretch to a third of the card. */
.sla-num-sm { max-width: 220px; }

@media (max-width: 900px) {
    /* Five controls in a row need about 560px. Under that they stack in
       pairs rather than each shrinking to something unusable. */
    .sla-step-fields { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }
    .sla-step-fields > :last-child { justify-self: end; }
}


/* ============================================================
   31. Recruiter: Languages & i18n (lng-*)

   Which languages this workspace runs in, and how the strings get
   translated. The list was a five-track grid with the coverage
   number rendered as coloured monospace text - so "12%" and "94%"
   were the same size, the same weight and one hue apart, and a
   workspace with six languages had to read six numbers to find the
   one that was not ready.

   Coverage is a proportion, so it is drawn as one. The number
   stays - it is what gets quoted to a client - but the bar beside
   it is what makes an unfinished language obvious without reading
   anything.

   What is here:
     .lng-row   one language
     .lng-code  its code, as a tile rather than a prefix on the name
     .lng-cov   the coverage meter
     .lng-eng   the machine translator choice
     .lng-opt   one engine switch, with what it does under it
   ============================================================ */

.lng-row {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr) 170px 116px 96px auto;
    gap: 12px;
    align-items: center;
    padding: 11px 16px;
    border-block-end: 1px solid var(--border-subtle);
}

.lng-row:last-child { border-block-end: none; }
.lng-row:hover { background: var(--n-25); }

/* The code as a mark. It was a prefix on the name - "en . English" -
   which made the one thing that identifies the row the least
   distinguishable part of it. */
.lng-code {
    display: grid;
    place-items: center;
    width: 34px;
    height: 34px;
    border-radius: var(--r-md);
    background: var(--n-75);
    color: var(--text-secondary);
    box-shadow: inset 0 0 0 1px var(--border-subtle);
    font-family: var(--font-mono);
    font-size: var(--fs-xs);
    font-weight: var(--fw-bold);
    text-transform: uppercase;
}

/* The workspace's own default. It cannot be scoped away, so the row
   says which one it is rather than only disabling its select. */
.lng-row.is-def .lng-code {
    background: var(--p-50);
    color: var(--p-700);
    box-shadow: inset 0 0 0 1px var(--p-100);
}

.lng-txt { min-width: 0; }
.lng-nm { font-size: var(--fs-md); font-weight: var(--fw-medium); color: var(--text-primary); }
.lng-native { margin: 1px 0 0; font-size: var(--fs-xs); color: var(--text-tertiary); }

/* ---------- Coverage ---------- */

.lng-cov { display: flex; flex-direction: column; gap: 4px; }

.lng-cov-n {
    font-family: var(--font-mono);
    font-size: var(--fs-xs);
    font-weight: var(--fw-semibold);
    font-variant-numeric: tabular-nums;
    color: var(--text-secondary);
}

.lng-cov-bar {
    display: block;
    height: 5px;
    border-radius: var(--r-full);
    background: var(--n-100);
    overflow: hidden;
}

.lng-cov-fill {
    display: block;
    height: 100%;
    border-radius: var(--r-full);
    background: var(--n-300);
    transition: width var(--dur-slow) var(--ease-out);
}

/* The same three bands the state pill on the row uses, so the bar and
   the pill cannot disagree about whether a language is ready. */
.lng-cov.is-live .lng-cov-fill { background: var(--success-500); }
.lng-cov.is-live .lng-cov-n { color: var(--success-700); }
.lng-cov.is-part .lng-cov-fill { background: var(--warning-500); }
.lng-cov.is-part .lng-cov-n { color: var(--warning-700); }

/* ---------- The engine ---------- */

/* One choice of four, so a segmented control rather than four buttons
   that each look like an action. */
.lng-eng {
    display: inline-flex;
    gap: 2px;
    flex-wrap: wrap;
    padding: 3px;
    border-radius: var(--r-lg);
    background: var(--n-75);
    box-shadow: inset 0 0 0 1px var(--border-subtle);
}

.lng-eng-opt {
    height: 28px;
    padding: 0 13px;
    border: none;
    border-radius: var(--r-md);
    background: transparent;
    color: var(--text-secondary);
    font: inherit;
    font-size: var(--fs-sm);
    font-weight: var(--fw-medium);
    white-space: nowrap;
    cursor: pointer;
    transition: background-color var(--dur-fast) var(--ease-out),
                color var(--dur-fast) var(--ease-out),
                box-shadow var(--dur-fast) var(--ease-out);
}

.lng-eng-opt:hover { color: var(--text-primary); }
.lng-eng-opt:focus-visible { outline: none; box-shadow: var(--shadow-focus); }

/* Raised on the sunken track: the chosen segment is the only one that
   looks like a surface. */
.lng-eng-opt.is-on {
    background: var(--surface-raised);
    color: var(--p-700);
    font-weight: var(--fw-semibold);
    box-shadow: var(--shadow-xs);
}

/* ---------- The engine's switches ---------- */

/* A switch and what it does. The three of them were labels with the
   sentence beside the control and nothing under it, so "Require human
   review" did not say what it holds back, or until when. */
.lng-opt {
    display: flex;
    align-items: flex-start;
    gap: 11px;
    padding: 10px 0;
    border-block-start: 1px solid var(--border-subtle);
    cursor: pointer;
}

.lng-opt:first-of-type { border-block-start: none; }
.lng-opt-txt { min-width: 0; }

/* Both are spans - the row is a label, and a label's contents stay inline
   unless they are told otherwise. */
.lng-opt-k {
    display: block;
    font-size: var(--fs-sm);
    font-weight: var(--fw-medium);
    color: var(--text-primary);
}

.lng-opt-d {
    display: block;
    margin: 2px 0 0;
    max-width: 66ch;
    font-size: var(--fs-xs);
    line-height: var(--lh-snug);
    color: var(--text-tertiary);
}

@media (max-width: 900px) {
    /* The scope select and the meter keep their widths; the name takes
       what is left and the state and action drop to a second line. */
    .lng-row {
        grid-template-columns: auto minmax(0, 1fr) auto;
        row-gap: 10px;
    }

    .lng-row > :nth-child(3) { grid-column: 2 / -1; }
    .lng-row > :nth-child(4) { grid-column: 2; }
    .lng-row > :nth-child(5),
    .lng-row > :nth-child(6) { grid-column: 3; justify-self: end; }
}


/* ============================================================
   32. Recruiter: Audit log (aud-*)

   Append-only, and the one screen in Settings that is read rather
   than edited. It was a five-track grid - time, actor, sentence,
   entity, action - repeated five hundred times with a hairline
   between each pair, and the timestamp on every row printed the
   date as well as the clock. Two hundred consecutive rows saying
   "Mar 4" is two hundred rows of the same fact.

   So the date is lifted out into a band and the rows under it carry
   only the time. That turns the feed into what it actually is - a
   day at a time - and gives a reader something to scroll to.

   The rows are a timeline: a rule down the left, a node per entry,
   tinted where the actor was the AI rather than a person. Whether a
   workspace's recent history is mostly human or mostly automatic is
   the first thing anyone opens this page to find out, and it was
   previously carried by the colour of a name.

   What is here:
     .aud-day    the date band, pinned while its own rows scroll
     .aud-row    one entry
     .aud-node   its dot on the rule, .is-ai when the actor was AI
     .aud-foot   how much of the feed is actually on screen
   ============================================================ */

/* The feed scrolls in its own lane rather than lengthening the settings
   column by five hundred rows. */
.aud-feed {
    max-height: min(620px, 68vh);
    overflow-y: auto;
    overscroll-behavior: contain;
    border-block-start: 1px solid var(--border-subtle);
}

/* Pinned, so the day a row belongs to is on screen while its rows are.
   The date was on every row before, which is where the horizontal space
   for the description had gone. */
.aud-day {
    position: sticky;
    inset-block-start: 0;
    /* Above .aud-node, which is also positioned: the rows come later in the
       DOM, so at an equal z-index their dots painted over the band. */
    z-index: 2;
    display: flex;
    align-items: baseline;
    gap: 8px;
    margin: 0;
    padding: 8px 16px;
    background: var(--n-25);
    border-block-end: 1px solid var(--border-subtle);
    font-size: var(--fs-2xs);
    font-weight: var(--fw-semibold);
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--text-tertiary);
}

.aud-day-n {
    letter-spacing: 0;
    text-transform: none;
    font-weight: var(--fw-medium);
    font-variant-numeric: tabular-nums;
    color: var(--text-disabled);
}

.aud-row {
    position: relative;
    display: grid;
    grid-template-columns: 52px 18px minmax(0, 1fr) auto;
    gap: 10px;
    align-items: baseline;
    padding: 8px 16px;
}

.aud-row:hover { background: var(--n-25); }

/* The rule the nodes sit on. Drawn by the row so it stops at the last
   entry of a day rather than running under the footer. */
.aud-row::before {
    content: "";
    position: absolute;
    inset-block: 0;
    /* The middle of the node track: 16px padding + 52px time + 10px gap + half
       of the 18px track, less half the rule's own width. Both tracks keep their
       widths at the narrow breakpoint, so this holds there too. */
    inset-inline-start: 86.5px;
    width: 1px;
    /* --border-default, not --border-subtle: a 1px hairline in the subtle tint
       against the card's own white is not visible at all, and a timeline whose
       rule cannot be seen is just a column of dots. */
    background: var(--border-default);
}

/* The rule starts at the first row under a day band and stops at the last row
   of the feed, so it breaks at every band rather than running behind it. */
.aud-day + .aud-row::before { inset-block-start: 10px; }
.aud-row:last-child::before { inset-block-end: 10px; }

.aud-time {
    font-family: var(--font-mono);
    font-size: var(--fs-xs);
    font-variant-numeric: tabular-nums;
    color: var(--text-tertiary);
}

.aud-node {
    position: relative;
    z-index: 1;
    justify-self: center;
    align-self: start;
    width: 7px;
    height: 7px;
    margin-block-start: 5px;
    border-radius: var(--r-full);
    background: var(--n-300);
    /* The ring is the page's own ground, which is what makes the node
       sit on the rule rather than have the rule pass through it. */
    box-shadow: 0 0 0 3px var(--surface-raised);
}

.aud-row:hover .aud-node { box-shadow: 0 0 0 3px var(--n-25); }

/* Written by the AI rather than by a person. It was the colour of the
   actor's name; on the node it is legible at a scroll's distance. */
.aud-node.is-ai { background: var(--gold-500); }
.aud-node.is-sys { background: var(--n-200); }

.aud-txt { min-width: 0; }

.aud-who {
    display: block;
    font-size: var(--fs-sm);
    font-weight: var(--fw-semibold);
    color: var(--text-primary);
}

.aud-who.is-ai { color: var(--gold-700); }

/* The sentence. Two lines at most: it is generated from the entry's own
   detail and some of them are long, but a feed where one row is six
   lines tall stops being scannable. */
.aud-what {
    margin: 1px 0 0;
    font-size: var(--fs-sm);
    line-height: var(--lh-snug);
    color: var(--text-secondary);
}

.aud-meta {
    display: flex;
    align-items: center;
    gap: 8px;
    justify-self: end;
}

/* The raw action verb. Monospace because it is the stored value, not a
   translated label - it is what a reader greps the exported CSV for. */
.aud-act {
    font-family: var(--font-mono);
    font-size: var(--fs-2xs);
    color: var(--text-disabled);
    white-space: nowrap;
}

/* The two selects that scope the feed. In the section head, where they
   act on the whole log rather than on the card they used to sit in. */
.aud-pick {
    height: 28px;
    padding: 0 8px;
    border: 1px solid var(--border-default);
    border-radius: var(--r-md);
    background: var(--n-0);
    font: inherit;
    font-size: var(--fs-xs);
    color: var(--text-primary);
    cursor: pointer;
}

.aud-pick:hover { border-color: var(--border-strong); }
.aud-pick:focus { outline: none; border-color: var(--border-focus); box-shadow: var(--shadow-focus); }

.aud-foot {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    flex-wrap: wrap;
    padding: 10px 16px;
    border-block-start: 1px solid var(--border-subtle);
}

/* The feed is capped, and a capped feed that does not say so is a
   complete history as far as the reader can tell. */
.aud-cap {
    display: flex;
    align-items: center;
    gap: 6px;
    font-size: var(--fs-xs);
    color: var(--warning-700);
}

.aud-cap svg { flex-shrink: 0; }

@media (max-width: 720px) {
    /* The entity and action move under the sentence; at this width they
       were squeezing it to about twenty characters. */
    .aud-row {
        grid-template-columns: 52px 18px minmax(0, 1fr);
        row-gap: 6px;
    }

    .aud-meta { grid-column: 3; justify-self: start; }
}

/* ============================================================
   33. Recruiter: Pipeline stages (stg-*)

   The board's columns, configured. What made this screen hard to
   use was not its controls but that it showed a list of rows when
   what a recruiter is editing is a board.

   Two of those rows are also not equal to the other three. An
   application carries a canonical stage name - applied, screen,
   interview - not a stage id, so a row only becomes a column when
   it maps onto one of those names. ApplicationService drops every
   unmapped row, which means a stage could be named, coloured and
   saved, and simply never appear. The old screen could not have
   told you: it drew the rows and nothing else.

   So the section opens with the board, drawn from the form under
   it. The chips are the columns in order, each with the number of
   applications sitting in it; the rows that are not columns are
   listed separately with the reason. A mapping change moves a chip
   before it is saved, which is the whole point of drawing it.

   What is here:
     .stg-flow   the board preview - chips in order, chevrons between
     .stg-chip   one column, .is-entry where applications land,
                 .is-builtin for the six a workspace falls back to
     .stg-off    rows that exist but are not columns, and why
     .stg-note   a sentence about the board as a whole
     .stg-list   the editor: a column header and one .stg-row each
     .stg-ord    the reorder arrows and the position they act on
     .stg-sw     the colour tile, which is the colour input
     .stg-meta   volume in the stage, and the entry-stage toggle
     .stg-warn   what is wrong with this row, under this row

   Below 880px a row folds from one line into four: the name keeps
   the top line with the delete, and the mapping, the volume and the
   entry toggle each take their own. The arrows stay in a rail down
   the side of all four, because they act on the row and not on a
   line of it.
   ============================================================ */

/* ---------- The board, as it will be ---------- */

/* Scrolls in its own lane rather than wrapping: left to right is the
   pipeline's order, and a chip wrapping onto a second line reads as a
   second pipeline. */
.stg-flow {
    display: flex;
    align-items: stretch;
    gap: 18px;
    overflow-x: auto;
    overscroll-behavior-x: contain;
    /* Room under the chips for the scrollbar lane, so a scrolling board
       does not lay a bar across the last chip's own edge. */
    padding-block-end: 4px;
}

.stg-chip {
    position: relative;
    flex: 0 0 auto;
    min-width: 128px;
    max-width: 190px;
    padding: 10px 12px 11px;
    border-radius: var(--r-lg);
    background: var(--surface-raised);
    box-shadow: var(--shadow-xs), inset 0 0 0 1px var(--border-default);
}

/* The connector, drawn by the chip that follows: no node per gap, and
   the last chip has no dangling arm. */
.stg-chip + .stg-chip::before {
    content: "";
    position: absolute;
    inset-inline-start: -13px;
    inset-block-start: calc(50% - 4px);
    width: 7px;
    height: 7px;
    border-block-start: 1.5px solid var(--border-strong);
    border-inline-end: 1.5px solid var(--border-strong);
    transform: rotate(45deg);
}

/* The stage colour at the size it is actually used at on the board - a
   rule over the column head, not a 28px block of paint. */
.stg-chip-bar {
    display: block;
    width: 26px;
    height: 3px;
    margin-block-end: 8px;
    border-radius: var(--r-full);
}

.stg-chip-nm {
    display: block;
    font-size: var(--fs-sm);
    font-weight: var(--fw-semibold);
    letter-spacing: -0.005em;
    color: var(--text-primary);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* How many applications are in this stage now. Tabular so the figures
   line up down a scrolled board. */
.stg-chip-sub {
    display: block;
    margin-block-start: 2px;
    font-size: var(--fs-2xs);
    font-variant-numeric: tabular-nums;
    color: var(--text-tertiary);
}

/* Where new applications land. It is a property of one column, so it is
   marked on that column rather than stated in a sentence below. */
.stg-chip.is-entry { box-shadow: var(--shadow-xs), inset 0 0 0 1px var(--p-200); }

.stg-chip-entry {
    position: absolute;
    inset-block-start: 8px;
    inset-inline-end: 8px;
    padding: 0 5px;
    border-radius: var(--r-full);
    background: var(--p-50);
    color: var(--p-700);
    font-size: 9.5px;
    font-weight: var(--fw-bold);
    letter-spacing: 0.04em;
    text-transform: uppercase;
    line-height: 15px;
}

/* The six a workspace falls back to having configured none. Dashed,
   because they are what the board does rather than what anyone set. */
.stg-chip.is-builtin {
    background: var(--n-25);
    box-shadow: none;
    border: 1px dashed var(--border-strong);
}

/* ---------- Rows that are not columns ---------- */

.stg-off {
    display: flex;
    align-items: center;
    gap: 8px;
    flex-wrap: wrap;
    margin-block-start: 14px;
    padding-block-start: 12px;
    border-block-start: 1px solid var(--border-subtle);
}

.stg-off-lab {
    font-size: var(--fs-2xs);
    font-weight: var(--fw-semibold);
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--text-tertiary);
}

.stg-off-chip {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 3px 9px 3px 7px;
    border-radius: var(--r-full);
    background: var(--n-50);
    border: 1px solid var(--border-default);
    font-size: var(--fs-xs);
    color: var(--text-secondary);
}

/* Mapped to a closing stage is a decision; mapped to nothing is a
   mistake, and only that one is tinted. */
.stg-off-chip.is-warn {
    background: var(--warning-50);
    border-color: var(--warning-100);
    color: var(--warning-700);
}

.stg-off-dot {
    width: 7px;
    height: 7px;
    border-radius: var(--r-full);
    box-shadow: inset 0 0 0 1px rgba(15, 19, 32, 0.12);
}

/* The reason is secondary to the name it explains, so the rule between
   them carries the colour and the text is held back off it. */
.stg-off-why {
    padding-inline-start: 6px;
    border-inline-start: 1px solid currentColor;
    opacity: 0.72;
}

/* ---------- A sentence about the whole board ---------- */

.stg-note {
    display: flex;
    align-items: flex-start;
    gap: 7px;
    margin: 14px 0 0;
    font-size: var(--fs-xs);
    line-height: 1.5;
    color: var(--text-tertiary);
}

.stg-note.is-warn { color: var(--warning-700); }
.stg-note svg { flex-shrink: 0; margin-block-start: 1px; }

/* ---------- The editor ---------- */

.stg-list {
    /* One template, two consumers: the header strip and every row. Held
       here so a column cannot be widened in one and not the other. */
    --stg-cols: 24px 28px minmax(140px, 1fr) 172px auto 30px;
}

.stg-cols,
.stg-row {
    display: grid;
    grid-template-columns: var(--stg-cols);
    grid-template-areas:
        "ord sw   name map  meta del"
        "ord warn warn warn warn warn";
    align-items: center;
    column-gap: 10px;
    padding: 8px 16px;
}

/* Column names once, above the rows, rather than a placeholder repeated
   down every control. */
.stg-cols {
    padding-block: 7px;
    border-block-end: 1px solid var(--border-default);
    background: var(--n-25);
    font-size: var(--fs-2xs);
    font-weight: var(--fw-semibold);
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--text-tertiary);
}

.stg-cols + .stg-row,
.stg-row + .stg-row { border-block-start: 1px solid var(--border-subtle); }

.stg-row { transition: background-color var(--dur-fast) var(--ease-out); }
.stg-row:hover { background: var(--n-25); }

/* The entry stage, marked the way the rail marks the open section: a
   band on the edge of the row rather than a chip inside it. */
.stg-row.is-entry { box-shadow: inset 2px 0 0 var(--p-700); }

/* ---------- Reorder ---------- */

/* Arrows, not drag: this order is the board's order, and a keyboard has
   to be able to set it. The position sits between them, so the pair
   reads as one control acting on row n. */
.stg-ord {
    grid-area: ord;
    display: flex;
    flex-direction: column;
    align-items: center;
    align-self: start;
    gap: 1px;
    margin-block-start: 1px;
}

.stg-ord-btn {
    display: grid;
    place-items: center;
    width: 22px;
    height: 15px;
    border-radius: var(--r-xs);
    color: var(--text-disabled);
    transition: background-color var(--dur-fast) var(--ease-out),
                color var(--dur-fast) var(--ease-out);
}

.stg-ord-btn:hover:not(:disabled) { background: var(--n-100); color: var(--text-primary); }
.stg-ord-btn:focus-visible { outline: none; box-shadow: var(--shadow-focus); }
.stg-ord-btn:disabled { color: var(--n-200); cursor: default; }

.stg-pos {
    font-size: var(--fs-2xs);
    font-weight: var(--fw-semibold);
    font-variant-numeric: tabular-nums;
    color: var(--text-disabled);
}

/* ---------- Colour ---------- */

/* The tile is the input. A bare input[type=color] draws a different box
   in every browser - a bevelled swatch in one, a padded button in
   another - so the input is stretched over the tile at zero opacity and
   the tile paints itself. */
.stg-sw {
    grid-area: sw;
    position: relative;
    width: 28px;
    height: 28px;
    border-radius: var(--r-sm);
    box-shadow: inset 0 0 0 1px rgba(15, 19, 32, 0.14);
    cursor: pointer;
    overflow: hidden;
}

.stg-sw input {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    padding: 0;
    border: none;
    opacity: 0;
    cursor: pointer;
}

/* The ring the invisible input cannot show for itself. */
.stg-sw:focus-within {
    box-shadow: inset 0 0 0 1px rgba(15, 19, 32, 0.14), var(--shadow-focus);
}

/* ---------- Name and mapping ---------- */

.stg-nm {
    grid-area: name;
    height: var(--h-md);
    padding: 0 10px;
    font-size: var(--fs-sm);
    font-weight: var(--fw-semibold);
}

.stg-map {
    grid-area: map;
    height: var(--h-md);
    padding: 0 30px 0 10px;
    font-size: var(--fs-sm);
    color: var(--text-secondary);
}

/* ---------- Volume, and where applications land ---------- */

.stg-meta {
    grid-area: meta;
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: 8px;
}

.stg-here {
    font-size: var(--fs-xs);
    font-variant-numeric: tabular-nums;
    color: var(--text-disabled);
    white-space: nowrap;
}

/* An empty stage and a stage holding forty applications are different
   objects when the question is whether this row can be removed. */
.stg-here.is-some { color: var(--text-secondary); font-weight: var(--fw-medium); }

/* One row can be the entry stage, so this behaves as a radio: pressing
   it moves the mark here from wherever it was. */
.stg-def {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    height: var(--h-sm);
    padding: 0 9px;
    border-radius: var(--r-full);
    border: 1px solid var(--border-default);
    background: var(--surface-raised);
    color: var(--text-tertiary);
    font-size: var(--fs-2xs);
    font-weight: var(--fw-semibold);
    letter-spacing: 0.04em;
    text-transform: uppercase;
    white-space: nowrap;
    transition: background-color var(--dur-fast) var(--ease-out),
                border-color var(--dur-fast) var(--ease-out),
                color var(--dur-fast) var(--ease-out);
}

.stg-def:hover { border-color: var(--border-strong); color: var(--text-secondary); }
.stg-def:focus-visible { outline: none; box-shadow: var(--shadow-focus); }

.stg-def.is-on {
    background: var(--p-50);
    border-color: var(--p-200);
    color: var(--p-700);
}

.stg-del {
    grid-area: del;
    display: grid;
    place-items: center;
    width: 30px;
    height: 30px;
    border-radius: var(--r-md);
    color: var(--text-disabled);
    transition: background-color var(--dur-fast) var(--ease-out),
                color var(--dur-fast) var(--ease-out);
}

.stg-del:hover { background: var(--danger-50); color: var(--danger-600); }
.stg-del:focus-visible { outline: none; box-shadow: var(--shadow-focus); }

/* ---------- What is wrong with this row ---------- */

/* Under the row it is about rather than in a summary at the top: the
   control to fix is the one beside it. */
.stg-warn {
    grid-area: warn;
    display: flex;
    align-items: center;
    gap: 6px;
    margin: 5px 0 1px;
    font-size: var(--fs-xs);
    line-height: 1.45;
    color: var(--warning-700);
}

.stg-warn svg { flex-shrink: 0; }
.stg-warn.is-bad { color: var(--danger-700); font-weight: var(--fw-medium); }
.stg-warn.is-new { color: var(--text-disabled); }

/* ---------- Adding ---------- */

/* Where the list ends, as well as in the section head: after six rows
   the head button is off the top of the screen. */
.stg-add {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 6px;
    width: 100%;
    height: 38px;
    border-block-start: 1px dashed var(--border-strong);
    color: var(--text-tertiary);
    font-size: var(--fs-sm);
    font-weight: var(--fw-medium);
    transition: background-color var(--dur-fast) var(--ease-out),
                color var(--dur-fast) var(--ease-out);
}

.stg-add:hover { background: var(--n-25); color: var(--text-brand); }
.stg-add:focus-visible { outline: none; box-shadow: var(--shadow-focus); }

@media (max-width: 880px) {
    /* A 172px mapping select beside a name field is what breaks first,
       and shrinking both leaves two unreadable controls. The row folds
       instead: name and delete on the top line, then the mapping, then
       the volume and the entry toggle. The arrows keep a rail down the
       side of all of it - they act on the row, not on a line of it. */
    .stg-row {
        --stg-cols: 24px 28px minmax(0, 1fr) 30px;
        grid-template-areas:
            "ord sw   name del"
            "ord map  map  map"
            "ord meta meta meta"
            "ord warn warn warn";
        row-gap: 8px;
        padding-block: 10px;
    }

    .stg-meta { justify-content: flex-start; }
    .stg-warn { margin: 0; }

    /* The strip labels columns that no longer exist. */
    .stg-cols { display: none; }
}


/* ============================================================
   34. Super Admin: Platform console (con-*)

   The console was the last screen in the portal still carrying its whole
   appearance in style attributes, so it had no hover, no focus ring and no
   breakpoint - on the page every other admin screen is reached from.

   Almost all of it is the shared chrome now: .pg-head for the pinned header,
   .pg-kpis for the stat row, .pg-rail for the list-plus-rail surface, .lst-*
   for the two row lists. What is left here is the four shapes that are the
   console's own - the health chip in the header, the tenant-mix bars, the AI
   figure and the event timeline - plus the two lines an error banner needs.

   Nothing here restates a value the shared chrome already sets: the row
   padding and the hairlines come from .lst-row and .card-foot, so a console
   row and a row on any other page stay the same height.
   ============================================================ */

/* An anchor wrapping a QButton must add nothing of its own, or the button
   inherits a link's colour and stops looking like a button. */
.con-link {
    display: inline-flex;
    color: inherit;
    text-decoration: none;
}

/* ---------- Health chip ----------
   It replaces a sentence that was printed unconditionally - including while
   incidents were open - so it has to read as a reading rather than a label: a
   dot in the state's own colour, the count when there is one, and a link to
   the incident list either way. Green is the resting state, so it is the
   quiet one; amber is what the eye should find in the header without looking
   for it. */
.con-health {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    height: 22px;
    padding: 0 9px;
    border: 1px solid var(--success-100);
    border-radius: var(--r-full);
    background: var(--success-50);
    color: var(--success-700);
    font-size: var(--fs-xs);
    font-weight: var(--fw-semibold);
    white-space: nowrap;
    text-decoration: none;
    transition: border-color var(--dur-fast) var(--ease-out),
                background-color var(--dur-fast) var(--ease-out);
}

.con-health-dot {
    width: 6px;
    height: 6px;
    flex-shrink: 0;
    border-radius: var(--r-full);
    background: currentColor;
}

.con-health-warn {
    border-color: var(--warning-100);
    background: var(--warning-50);
    color: var(--warning-700);
}

.con-health:hover { border-color: currentColor; }

.con-health:focus-visible { outline: none; box-shadow: var(--shadow-focus); }

/* ---------- Error banner ----------
   The failure used to be a card titled "Error" with the exception as its body
   and the retry floating underneath as a separate block. As a .note the
   message sits inside the banner and the retry is part of it. */
.con-err-msg {
    margin: 4px 0 0;
    font-size: var(--fs-sm);
    line-height: 1.45;
    color: inherit;
    overflow-wrap: anywhere;
}

.con-err-act { margin-block-start: 10px; }

/* ---------- Tenant mix ----------
   Three statuses against the whole estate. A 72px label column rather than
   the 200px .meter-row uses: this lives in a 320px rail, and at 200px the bar
   - the part being compared - would be shorter than the word naming it. */
.con-mix {
    display: flex;
    flex-direction: column;
    gap: 10px;
    padding: 14px 16px;
}

.con-mix-row {
    display: flex;
    align-items: center;
    gap: 10px;
}

.con-mix-l {
    flex: 0 0 auto;
    width: 72px;
    font-size: var(--fs-sm);
    color: var(--text-secondary);
}

.con-mix-meter { --meter-h: 6px; }

.con-mix-n {
    flex-shrink: 0;
    width: 30px;
    font-family: var(--font-mono);
    font-variant-numeric: tabular-nums;
    font-size: var(--fs-sm);
    font-weight: var(--fw-semibold);
    text-align: end;
}

/* The fills are the tones the pills and tiles already use, so a status is one
   colour wherever the page shows it. */
.con-mix-fill-good { background: var(--success-500); }
.con-mix-fill-gold { background: var(--gold-500); }
.con-mix-fill-warn { background: var(--warning-500); }

/* ---------- AI figure ----------
   One number, at the size of a number that matters, with the sentence saying
   what it counts directly under it. What it replaces was an eight-row table
   in which every value was an em dash. */
.con-focus {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 14px 16px;
}

.con-focus-mark {
    display: grid;
    place-items: center;
    width: 36px;
    height: 36px;
    flex-shrink: 0;
    border-radius: var(--r-lg);
    background: var(--gold-50);
    color: var(--gold-700);
}

.con-focus-txt { min-width: 0; }

.con-focus-n {
    margin: 0;
    font-family: var(--font-mono);
    font-variant-numeric: tabular-nums;
    font-size: var(--fs-3xl);
    font-weight: var(--fw-medium);
    letter-spacing: -0.02em;
    line-height: 1.1;
    color: var(--text-primary);
}

.con-focus-cap {
    margin: 3px 0 0;
    font-size: var(--fs-xs);
    line-height: 1.45;
    color: var(--text-tertiary);
}

/* ---------- AI breakdown ----------
   What the headline figure is made of. A definition list because that is what
   it is - each label names the number beside it - and rules between the rows
   rather than around them, so the card keeps one border. */
.con-brk {
    margin: 0;
    padding: 0 16px 14px;
}

.con-brk-row {
    display: flex;
    align-items: center;
    gap: 10px;
    padding-block: 7px;
    border-block-start: 1px solid var(--border-subtle);
}

.con-brk-row:first-child { border-block-start: none; }

.con-brk-l {
    flex: 1;
    min-width: 0;
    font-size: var(--fs-sm);
    color: var(--text-secondary);
}

.con-brk-n {
    margin: 0;
    flex-shrink: 0;
    font-family: var(--font-mono);
    font-variant-numeric: tabular-nums;
    font-size: var(--fs-sm);
    font-weight: var(--fw-semibold);
}

/* The same tones the tiles and pills use, so one meaning keeps one colour. */
.con-brk-n-gold { color: var(--gold-700); }
.con-brk-n-brand { color: var(--p-700); }
.con-brk-n-warn { color: var(--warning-700); }
.con-brk-n-neutral { color: var(--text-primary); }

/* ---------- Event timeline ----------
   Four fixed-width columns became a timeline for the reason the audit log's
   feed did: the actor was clipped by a 200px box while the action - the part
   being read - took whatever was left over. Here the action leads, the time
   is a narrow mono gutter, and severity is the node on the rule, so a run of
   warnings is visible while scrolling rather than having to be read chip by
   chip down the right-hand edge. */
.con-ev {
    margin: 0;
    padding: 0;
    list-style: none;
}

.con-ev-row {
    display: grid;
    grid-template-columns: 52px 10px minmax(0, 1fr) auto;
    align-items: center;
    gap: 12px;
    padding: 11px 16px;
    border-block-start: 1px solid var(--border-subtle);
}

.con-ev-row:first-child { border-block-start: none; }

.con-ev-t {
    font-family: var(--font-mono);
    font-variant-numeric: tabular-nums;
    font-size: var(--fs-xs);
    color: var(--text-tertiary);
    white-space: nowrap;
}

/* The node is drawn by ::after and the rule it sits on by ::before, in that
   order, so the dot paints over the line without either one needing a
   z-index - a negative z-index here would put the line behind the card's own
   background and it would not be drawn at all. */
.con-ev-node {
    position: relative;
    justify-self: center;
    width: 9px;
    height: 9px;
}

.con-ev-node::before {
    content: "";
    position: absolute;
    inset-block: -30px;
    inset-inline-start: 50%;
    width: 1px;
    margin-inline-start: -0.5px;
    background: var(--border-subtle);
}

.con-ev-node::after {
    content: "";
    position: absolute;
    inset: 0;
    border-radius: var(--r-full);
    background: var(--n-300);
    box-shadow: 0 0 0 3px var(--surface-raised);
}

/* The rule starts at the first event and ends at the last, rather than
   running out of the feed into the card's header and footer. */
.con-ev-row:first-child .con-ev-node::before { inset-block-start: 50%; }
.con-ev-row:last-child .con-ev-node::before { inset-block-end: 50%; }

.con-ev-node-brand::after   { background: var(--p-500); }
.con-ev-node-good::after    { background: var(--success-500); }
.con-ev-node-warn::after    { background: var(--warning-500); }
.con-ev-node-bad::after     { background: var(--danger-500); }
.con-ev-node-neutral::after { background: var(--n-300); }

.con-ev-main { min-width: 0; }

.con-ev-act {
    margin: 0;
    font-size: var(--fs-md);
    font-weight: var(--fw-medium);
    color: var(--text-primary);
}

.con-ev-meta {
    margin: 2px 0 0;
    font-size: var(--fs-xs);
    color: var(--text-tertiary);
}

.con-ev-end {
    display: flex;
    align-items: center;
    gap: 8px;
    justify-self: end;
}

/* The category is a machine word - "ai", "tenant", "auth" - so it is set as a
   label rather than as a second pill competing with the severity. */
.con-ev-cat {
    font-size: var(--fs-2xs);
    font-weight: var(--fw-semibold);
    letter-spacing: 0.05em;
    text-transform: uppercase;
    color: var(--text-tertiary);
}

/* The chevron that closes a row: visible at rest so the row reads as a link,
   and only then does it move. */
.con-row-go {
    flex-shrink: 0;
    color: var(--text-disabled);
    transition: transform var(--dur-fast) var(--ease-out),
                color var(--dur-fast) var(--ease-out);
}

.lst-row:hover .con-row-go,
.lst-row:focus-visible .con-row-go {
    color: var(--p-700);
    transform: translateX(2px);
}

@media (prefers-reduced-motion: reduce) {
    .con-row-go { transition: none; }
    .lst-row:hover .con-row-go,
    .lst-row:focus-visible .con-row-go { transform: none; }
}

@media (max-width: 700px) {
    .con-mix { padding-inline: 14px; }
    .con-focus { padding-inline: 14px; }
    .con-brk { padding-inline: 14px; }

    /* The timeline folds rather than shrinks: at this width the severity chip
       was pushing the action down to two words a line. Time and severity keep
       the top line, the action takes the full width under them, and the node
       goes with the gutter it belonged to. */
    .con-ev-row {
        grid-template-columns: auto minmax(0, 1fr);
        grid-template-areas:
            "time sev"
            "main main";
        row-gap: 6px;
        padding-inline: 14px;
    }

    .con-ev-t { grid-area: time; }
    .con-ev-main { grid-area: main; }
    .con-ev-end { grid-area: sev; }
    .con-ev-node { display: none; }
}


/* ============================================================
   35. Sessions & devices (sess-*)

   The section behind "Sessions & devices" in the platform console,
   the same list inside the per-user drawer beside it, and the block
   at the foot of the recruiter's own profile. Three copies of one
   question - "is anything I do not recognise signed in as me?" -
   answered three ways: a six-column table of mono timestamps, a
   narrower inline-styled copy of that table, and a row with a grey
   line under it.

   A table is the wrong instrument. Nobody reads a session list for a
   value in a cell; they scan it for the row that is not theirs, and
   that row has to be findable without reading any of the others. So a
   session is a card: the device as a glyph first, its name second,
   the facts as labelled pairs rather than as positional columns, and
   the button that ends it on the right, where the eye already is.

   What is here:
     .sess-sum    the read-out above the list - what is signed in,
                  from how many places, and how recently
     .sess-list   the stack
     .sess        one device, .sess-cur for the one you are reading
                  this from
     .sess-grp    the heading that separates "you" from "everything
                  else"
     .sess-none   the good news, when there is nothing else
     .sess-danger sign out everywhere, kept apart from the per-row
                  buttons because it also ends the session reading it

   The card markup is QSessionCard in Jork.Shared.UI; this block is
   only the paint. The buttons, pills and cards inside it are the
   shared ones and are not restyled here.
   ============================================================ */

/* One column, one rhythm. The section gets this from .set-sec-body; the
   drawer that shows the same list for another user has no such parent,
   and its blocks used to touch. */
.sess-stack { display: flex; flex-direction: column; gap: 12px; min-width: 0; }

/* ---------- The read-out ---------- */

/* Four numbers a person can act on, above a list they would otherwise
   have to count. auto-fit rather than a fixed four: the same band sits
   in the console's working column and in a 720px drawer. */
.sess-sum {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
    gap: 10px;
}

.sess-sum-tile {
    padding: 11px 13px;
    background: var(--surface-raised);
    border: 1px solid var(--border-default);
    border-radius: var(--r-lg);
    box-shadow: var(--shadow-xs);
}

.sess-sum-lab {
    margin: 0;
    font-size: var(--fs-2xs);
    font-weight: var(--fw-semibold);
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--text-tertiary);
}

/* Display face and tabular figures: these are read as quantities, and
   the tiles only line up if the digits are the same width. */
.sess-sum-val {
    margin: 4px 0 0;
    font-family: var(--font-display);
    font-size: var(--fs-2xl);
    font-weight: var(--fw-semibold);
    line-height: var(--lh-tight);
    font-variant-numeric: tabular-nums;
    color: var(--text-primary);
}

.sess-sum-sub {
    margin: 2px 0 0;
    font-size: var(--fs-xs);
    color: var(--text-tertiary);
}

/* One of the four tiles reads a phrase rather than a figure. At the
   display size a count is set in, "12 minutes ago" wraps to two lines
   and drags the tile taller than its neighbours; it takes the text
   size and the body face instead, and the row stays level. */
.sess-sum-val.is-text {
    font-family: var(--font-sans);
    font-size: var(--fs-lg);
    line-height: 1.55;
}

/* A tile carrying something to act on says so in the colour too. Applied
   from the page, and only when the number is not the normal one: a zero
   failed-attempt count tinted amber teaches the reader to ignore amber. */
.sess-sum-tile.is-warn { border-color: var(--warning-100); background: var(--warning-50); }
.sess-sum-tile.is-warn .sess-sum-lab,
.sess-sum-tile.is-warn .sess-sum-val,
.sess-sum-tile.is-warn .sess-sum-sub { color: var(--warning-700); }

/* ---------- The group heading ---------- */

.sess-grp {
    display: flex;
    align-items: center;
    gap: 8px;
    margin: 4px 0 0;
    font-size: var(--fs-2xs);
    font-weight: var(--fw-semibold);
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--text-disabled);
}

/* The rule runs to the end of the column, so the heading reads as a
   divider between two lists rather than as a very small first row. */
.sess-grp::after {
    content: "";
    flex: 1;
    height: 1px;
    background: var(--border-subtle);
}

/* ---------- One device ---------- */

.sess-list { display: flex; flex-direction: column; gap: 8px; }

.sess {
    position: relative;
    display: flex;
    align-items: flex-start;
    gap: 12px;
    padding: 12px 14px;
    background: var(--surface-raised);
    border: 1px solid var(--border-default);
    border-radius: var(--r-lg);
    box-shadow: var(--shadow-xs);
    transition: border-color 120ms ease, box-shadow 120ms ease;
}

.sess:hover { border-color: var(--border-strong); box-shadow: var(--shadow-sm); }

/* Focus inside the card lifts the whole card, not only the button: a
   keyboard reader has to see which session the "Sign out" they are on
   belongs to. */
.sess:focus-within { border-color: var(--border-strong); box-shadow: var(--shadow-sm); }

/* The session doing the reading. A brand edge and a short wash rather
   than a filled card: it still has to read as one of the list, or the
   eye stops treating the rows as comparable. */
.sess-cur {
    border-color: var(--p-200);
    background: linear-gradient(90deg, var(--p-50), var(--surface-raised) 220px);
}

.sess-cur::before {
    content: "";
    position: absolute;
    inset: 0 auto 0 0;
    width: 3px;
    border-radius: var(--r-lg) 0 0 var(--r-lg);
    background: var(--p-700);
}

/* ---------- The glyph ---------- */

.sess-ico {
    display: grid;
    place-items: center;
    width: 34px;
    height: 34px;
    flex-shrink: 0;
    border-radius: var(--r-md);
    background: var(--n-100);
    color: var(--text-secondary);
}

/* Tinted by family, so a phone among four desktops is found before a
   word of any label is read. */
.sess-ico-phone   { background: var(--info-50); color: var(--info-700); }
.sess-ico-tablet  { background: var(--gold-50); color: var(--gold-700); }
.sess-ico-desktop { background: var(--n-100);   color: var(--n-600); }
.sess-cur .sess-ico { background: var(--p-100); color: var(--p-700); }

/* ---------- The label line ---------- */

.sess-main { flex: 1; min-width: 0; }

.sess-line {
    display: flex;
    align-items: center;
    gap: 7px;
    flex-wrap: wrap;
}

.sess-lab {
    margin: 0;
    font-size: var(--fs-md);
    font-weight: var(--fw-semibold);
    line-height: var(--lh-snug);
    color: var(--text-primary);
}

/* The portal is a fact about the session, not a status, so it is set as
   a quiet mono chip rather than as a second coloured pill competing
   with "This device". */
.sess-portal {
    padding: 1px 7px;
    border: 1px solid var(--border-subtle);
    border-radius: var(--r-full);
    background: var(--n-25);
    font-family: var(--font-mono);
    font-size: var(--fs-2xs);
    color: var(--text-tertiary);
    white-space: nowrap;
}

.sess-live {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    font-size: var(--fs-2xs);
    font-weight: var(--fw-semibold);
    letter-spacing: 0.02em;
    text-transform: uppercase;
    color: var(--success-700);
}

.sess-live-dot {
    width: 6px;
    height: 6px;
    border-radius: var(--r-full);
    background: var(--success-500);
    box-shadow: 0 0 0 3px var(--success-50);
    animation: sess-pulse 2.4s ease-in-out infinite;
}

@keyframes sess-pulse {
    0%, 100% { opacity: 1; }
    50%      { opacity: 0.35; }
}

@media (prefers-reduced-motion: reduce) {
    .sess-live-dot { animation: none; }
    .sess { transition: none; }
}

/* ---------- The facts ---------- */

/* Labelled pairs, because the columns of the table this replaces were
   only distinguishable by counting: three of the cells held a date in
   the same format and none of them said which date it was. */
.sess-facts {
    display: flex;
    flex-wrap: wrap;
    gap: 4px 20px;
    margin: 7px 0 0;
}

.sess-fact { min-width: 0; }

.sess-fact dt {
    font-size: var(--fs-2xs);
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--text-disabled);
}

.sess-fact dd {
    margin: 1px 0 0;
    font-size: var(--fs-xs);
    color: var(--text-secondary);
    white-space: nowrap;
}

/* Mono: an address is read character by character, and only the address
   is. The relative times beside it are read as words. */
.sess-fact-mono { font-family: var(--font-mono); }

.sess-fact-warn dd { color: var(--warning-700); font-weight: var(--fw-semibold); }

/* A caller drawing its own line - a translated one - in place of the grid. */
.sess-meta {
    margin: 4px 0 0;
    font-size: var(--fs-xs);
    color: var(--text-tertiary);
}

/* ---------- Ending one ---------- */

/* Present at rest rather than revealed on hover: a control that appears
   only under the pointer is invisible on a touch screen and unfindable
   by keyboard until it already has focus. It is quiet instead, and the
   card's own hover brings it forward. */
.sess-acts {
    display: flex;
    align-items: center;
    gap: 6px;
    flex-shrink: 0;
    margin-inline-start: auto;
    opacity: 0.72;
    transition: opacity 120ms ease;
}

.sess:hover .sess-acts,
.sess:focus-within .sess-acts { opacity: 1; }

/* ---------- Nothing else signed in ---------- */

/* Dashed and unfilled: the answer to the question the page was opened
   with, not a row of data and not an error. */
.sess-none {
    display: flex;
    align-items: center;
    gap: 9px;
    padding: 16px 18px;
    border: 1px dashed var(--border-default);
    border-radius: var(--r-lg);
    background: var(--n-25);
    font-size: var(--fs-sm);
    color: var(--text-tertiary);
}

.sess-none > svg { flex-shrink: 0; color: var(--success-600); }

/* The same shape carrying a fact rather than the all-clear. Green on
   "this session is not in the list" would read as reassurance. */
.sess-none-info > svg { color: var(--info-600); }

/* ---------- Sign out everywhere ---------- */

/* Apart from the per-row buttons, and last, because it is the only
   control here that also ends the session reading it. */
.sess-danger {
    display: flex;
    align-items: center;
    gap: 14px;
    flex-wrap: wrap;
    padding: 14px 16px;
    border: 1px solid var(--danger-200);
    border-radius: var(--r-lg);
    background: var(--danger-50);
}

.sess-danger-txt { flex: 1 1 260px; min-width: 0; }

.sess-danger-lab {
    margin: 0;
    font-size: var(--fs-sm);
    font-weight: var(--fw-semibold);
    color: var(--danger-700);
}

.sess-danger-sub {
    margin: 2px 0 0;
    font-size: var(--fs-xs);
    line-height: var(--lh-normal);
    color: var(--danger-700);
    opacity: 0.82;
}

.sess-danger form { margin: 0; margin-inline-start: auto; }

/* ---------- Narrow ---------- */

@media (max-width: 760px) {
    /* The button drops under the facts rather than squeezing the label
       to two words a line beside it. */
    .sess { flex-wrap: wrap; }

    .sess-acts {
        width: 100%;
        margin-inline-start: 46px;   /* glyph + gap: it stays the row's */
        opacity: 1;
    }

    .sess-facts { gap: 4px 14px; }

    .sess-danger form { margin-inline-start: 0; width: 100%; }
    .sess-danger form .btn { width: 100%; justify-content: center; }
}


/* ============================================================
   36. Candidate: Certifications (cert-*)

   The candidate's own credential wall and the catalogue under it.
   Before this block the page was one long run of inline styles -
   nine hard-coded hex fallbacks, four different card paddings and
   a grid whose minmax changed between the badges and the cards -
   so the two halves of the same screen did not agree on what a
   card was.

   The screen answers three questions in the order a candidate
   asks them. They are three tabs now rather than six stacked
   bands: the catalogue - the only place on the screen you can
   actually do something - started below the fold for anybody who
   already held a credential, and the further down a band sat the
   less likely it was ever read. The paint still follows the order
   of the questions rather than the order of the data:

     what do I already hold   the wall - the only place on the
                              page with a crest and a gold edge,
                              because it is the thing being
                              accumulated (section 3)
     what is one step away    the rung rail and the composite
                              paths, quieter, on a raised list
                              rather than a card of their own
     what else exists         the catalogue grid and the roadmap,
                              flattest of the three

   Coverage state is the page's other axis and it is carried by
   colour only through .cert-pill, never by the card around it: a
   whole card tinted amber for "coming soon" reads as a warning
   about the candidate rather than as a fact about the syllabus.

   The catalogue card has two targets on purpose. Its face opens
   the drawer beside it - the paper's terms, what they hold, why
   it is blocked - and only the button on the foot commits to an
   attempt. Reading about a credential should never be one
   mis-click away from starting a timed paper.

   What is here:
     .cert-pg      the page stack
     .cert-hero    the identity band and its three counts
     .cert-tabbar  the rail the three questions hang off
     .cert-sec     one titled band
     .cert-wall    the badges, .cert-badge one of them
     .cert-row     the shared list row - rungs, gaps, roadmap
     .cert-comb    a composite path and its component chips
     .cert-grid    the catalogue, .cert-c one card
     .cert-dw      the drawer body
   ============================================================ */

.cert-pg {
    display: flex;
    flex-direction: column;
    gap: 18px;
    min-width: 0;
}

/* ---------- The hero ---------- */

/* The page's identity band, in the portal's own idiom: the candidate
   dashboard opens on the same navy panel with a gold bloom (.cnp-hero,
   section 17), and these are the two screens of the portal a candidate
   spends the most time on. Before it the page opened on a 19px icon
   tile and a row of grey uppercase labels - correct, and completely
   without an argument for why any of it was worth doing.

   It also carries the lede, which the page header could only truncate.

   The three counts are read from what the page already loaded rather
   than fetched, so they can never disagree with the bands under them.
   A candidate whose wall is empty gets the one action in their place:
   a hero of three noughts says less than no hero at all. */
.cert-hero {
    position: relative;
    overflow: hidden;
    padding: 18px 22px;
    border-radius: var(--r-xl);
    background: linear-gradient(135deg, var(--p-900) 0%, var(--p-800) 58%, var(--p-700) 100%);
    color: var(--n-0);
}

/* Two blooms - gold from the top right, blue from the bottom left - so
   the gradient does not read as a flat band across a 1440px page.
   Decorative and inert. */
.cert-hero::before,
.cert-hero::after {
    content: "";
    position: absolute;
    border-radius: 50%;
    pointer-events: none;
}

.cert-hero::before {
    inset-block-start: -84px;
    inset-inline-end: -54px;
    width: 260px;
    height: 260px;
    background: radial-gradient(circle, rgba(245, 184, 0, 0.20) 0%, transparent 70%);
}

.cert-hero::after {
    inset-block-end: -120px;
    inset-inline-start: -60px;
    width: 300px;
    height: 300px;
    background: radial-gradient(circle, rgba(100, 120, 255, 0.18) 0%, transparent 70%);
}

/* Everything real sits above the blooms. */
.cert-hero-in {
    position: relative;
    display: flex;
    align-items: center;
    gap: 24px;
}

/* The wall's crest, filled. On the hero it stands for the thing the
   page is about rather than for one credential, so it gets the gold
   the badges wear rather than the outline the catalogue cards do. */
.cert-hero-crest {
    flex-shrink: 0;
    display: grid;
    place-items: center;
    width: 46px;
    height: 46px;
    border-radius: var(--r-lg);
    background: linear-gradient(140deg, var(--gold-300), var(--gold-500));
    color: var(--p-900);
    box-shadow: 0 2px 6px rgba(7, 10, 19, 0.28), inset 0 1px 0 rgba(255, 255, 255, 0.45);
}

.cert-hero-txt { flex: 1; min-width: 0; }

.cert-hero-h {
    margin: 0;
    font-family: var(--font-display);
    font-size: var(--fs-2xl);
    font-weight: var(--fw-bold);
    letter-spacing: -0.018em;
    line-height: var(--lh-snug);
}

.cert-hero-p {
    margin: 5px 0 0;
    max-width: 62ch;
    font-size: var(--fs-md);
    line-height: var(--lh-normal);
    color: rgba(255, 255, 255, 0.76);
}

/* The one warning the hero carries, and the only place on the page a
   credential running out is said before the wall is opened. Gold,
   because it is the wall's own colour going out. */
.cert-hero-warn {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    margin: 10px 0 0;
    padding: 4px 11px 4px 9px;
    border-radius: var(--r-full);
    background: rgba(245, 184, 0, 0.16);
    box-shadow: inset 0 0 0 1px rgba(245, 184, 0, 0.34);
    font-size: var(--fs-xs);
    font-weight: var(--fw-semibold);
    color: var(--gold-200);
}

.cert-hero-warn > svg { flex-shrink: 0; }

/* The counts wrap onto their own line rather than shrinking: the hero
   clips, and a third tile that does not fit was not narrow, it was cut
   off with no way to reach it. */
.cert-hero-stats {
    flex-shrink: 0;
    display: flex;
    gap: 10px;
}

.cert-stat {
    min-width: 94px;
    padding: 11px 16px;
    border: 1px solid rgba(255, 255, 255, 0.2);
    border-radius: var(--r-lg);
    background: rgba(255, 255, 255, 0.1);
    text-align: center;
}

.cert-stat-v {
    display: block;
    font-family: var(--font-mono);
    font-size: var(--fs-3xl);
    font-weight: var(--fw-bold);
    font-variant-numeric: tabular-nums;
    line-height: 1;
}

.cert-stat-l {
    display: block;
    margin-block-start: 5px;
    font-size: var(--fs-xs);
    line-height: var(--lh-snug);
    color: rgba(255, 255, 255, 0.7);
}

/* What an empty wall gets instead of the counts. Gold rather than the
   page's navy: on the navy panel a navy button is a rectangle, and
   this is the one thing a candidate with nothing can do from here. */
.cert-hero-cta {
    flex-shrink: 0;
    display: inline-flex;
    align-items: center;
    gap: 7px;
    height: var(--h-xl);
    padding: 0 18px;
    border: none;
    border-radius: var(--r-md);
    background: var(--gold-500);
    font: inherit;
    font-size: var(--fs-md);
    font-weight: var(--fw-semibold);
    color: var(--p-900);
    cursor: pointer;
    transition: background-color var(--dur-fast) var(--ease-out);
}

.cert-hero-cta:hover { background: var(--gold-400); }

/* The page's focus ring is navy and would vanish on this panel. */
.cert-hero-cta:focus-visible {
    outline: 2px solid var(--gold-300);
    outline-offset: 2px;
}

/* ---------- A band ---------- */

.cert-sec { min-width: 0; }

/* The heading and its count on one line, with the rule running to
   the end of the column so a band reads as a division of the page
   rather than as a very small first row. */
.cert-sec-head {
    display: flex;
    align-items: center;
    gap: 9px;
    margin-block-end: 10px;
}

.cert-sec-title {
    margin: 0;
    font-size: var(--fs-xs);
    font-weight: var(--fw-semibold);
    letter-spacing: 0.05em;
    text-transform: uppercase;
    color: var(--text-tertiary);
    white-space: nowrap;
}

.cert-sec-head::after {
    content: "";
    flex: 1;
    height: 1px;
    background: var(--border-subtle);
}

.cert-sec-sub {
    margin: -4px 0 10px;
    max-width: 74ch;
    font-size: var(--fs-sm);
    line-height: var(--lh-normal);
    color: var(--text-secondary);
}

/* ---------- Coverage pill ---------- */

.cert-pill {
    flex-shrink: 0;
    display: inline-flex;
    align-items: center;
    height: 20px;
    padding: 0 8px;
    border-radius: var(--r-full);
    font-size: var(--fs-2xs);
    font-weight: var(--fw-semibold);
    letter-spacing: 0.01em;
    white-space: nowrap;
    background: var(--n-100);
    color: var(--text-tertiary);
    box-shadow: inset 0 0 0 1px var(--border-subtle);
}

.cert-pill.is-live {
    background: var(--success-50);
    color: var(--success-700);
    box-shadow: inset 0 0 0 1px var(--success-100);
}

/* Dated rather than promised: the label in this state is a month,
   so it is set in the mono face to read as one. */
.cert-pill.is-dev {
    background: var(--gold-50);
    color: var(--gold-700);
    box-shadow: inset 0 0 0 1px var(--gold-200);
    font-family: var(--font-mono);
    font-size: 10px;
}

/* ---------- The wall ---------- */

.cert-wall {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(268px, 1fr));
    gap: 12px;
}

/* The only raised, gold-edged surface on the page. Everything else
   is a thing that could be earned; these are the ones that were. */
.cert-badge {
    position: relative;
    overflow: hidden;
    display: flex;
    flex-direction: column;
    padding: 15px 16px 12px;
    border: 1px solid var(--gold-200);
    border-radius: var(--r-xl);
    background: var(--surface-raised);
    box-shadow: var(--shadow-xs);
}

/* A wash from the crest's corner, not a filled card: the tint has
   to be readable behind 11px text at the bottom of it. */
.cert-badge::before {
    content: "";
    position: absolute;
    inset-block-start: -70px;
    inset-inline-end: -70px;
    width: 190px;
    height: 190px;
    border-radius: 50%;
    background: radial-gradient(circle, rgba(245, 184, 0, 0.16) 0%, transparent 70%);
    pointer-events: none;
}

/* Expired is not a failure and must not be painted as one. It is
   the same card with the light off - neutral edge, sunken ground,
   no wash - and only the date under it turns red. */
.cert-badge.is-expired {
    border-color: var(--border-subtle);
    background: var(--n-25);
    box-shadow: none;
}

.cert-badge.is-expired::before { display: none; }

.cert-badge-top {
    position: relative;
    display: flex;
    align-items: flex-start;
    gap: 10px;
}

.cert-badge-crest {
    flex-shrink: 0;
    display: grid;
    place-items: center;
    width: 36px;
    height: 36px;
    border-radius: var(--r-full);
    background: linear-gradient(140deg, var(--gold-300), var(--gold-500));
    color: var(--p-800);
    box-shadow: 0 1px 2px rgba(15, 19, 32, 0.14), inset 0 1px 0 rgba(255, 255, 255, 0.42);
}

.cert-badge.is-expired .cert-badge-crest {
    background: var(--n-100);
    color: var(--text-disabled);
    box-shadow: none;
}

.cert-badge-txt { flex: 1; min-width: 0; }

.cert-badge-name {
    margin: 0;
    font-size: var(--fs-md);
    font-weight: var(--fw-bold);
    line-height: var(--lh-snug);
    color: var(--text-primary);
}

.cert-badge-tier {
    margin: 1px 0 0;
    font-family: var(--font-mono);
    font-size: var(--fs-2xs);
    color: var(--text-tertiary);
}

/* The headline of the whole card: where they placed, not what they
   scored. Display face because it is the one line on the card
   meant to be read from across the grid. */
.cert-badge-band {
    position: relative;
    margin: 11px 0 0;
    font-family: var(--font-display);
    font-size: var(--fs-lg);
    font-weight: var(--fw-semibold);
    letter-spacing: -0.012em;
    line-height: var(--lh-snug);
    color: var(--p-700);
}

.cert-badge.is-expired .cert-badge-band { color: var(--text-secondary); }

.cert-badge-chips {
    position: relative;
    display: flex;
    flex-wrap: wrap;
    gap: 5px;
    margin-block-start: 9px;
}

.cert-chip {
    display: inline-flex;
    align-items: center;
    height: 19px;
    padding: 0 8px;
    border-radius: var(--r-full);
    background: var(--n-75);
    font-size: var(--fs-2xs);
    color: var(--text-secondary);
    box-shadow: inset 0 0 0 1px var(--border-subtle);
}

.cert-badge-exp {
    position: relative;
    margin: 10px 0 0;
    font-size: var(--fs-xs);
    line-height: var(--lh-snug);
    color: var(--text-tertiary);
}

.cert-badge-exp.is-soon { color: var(--gold-700); font-weight: var(--fw-semibold); }
.cert-badge-exp.is-gone { color: var(--danger-600); font-weight: var(--fw-semibold); }

.cert-badge-foot {
    position: relative;
    display: flex;
    align-items: center;
    gap: 10px;
    margin-block-start: auto;
    padding-block-start: 10px;
    padding-block-end: 1px;
}

/* The rule is inset from the wash rather than drawn across it. */
.cert-badge-foot::before {
    content: "";
    position: absolute;
    inset-block-start: 0;
    inset-inline: 0;
    height: 1px;
    background: var(--border-subtle);
}

.cert-badge-link {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    font-size: var(--fs-xs);
    font-weight: var(--fw-medium);
    color: var(--p-700);
    border-radius: var(--r-xs);
}

.cert-badge-link:hover { text-decoration: underline; }
.cert-badge-link:focus-visible { outline: none; box-shadow: var(--shadow-focus); }

/* Their credential, including the decision not to publish it. A
   checkbox rather than a switch: it is a property of the badge it
   sits on, not a setting for the page. */
.cert-badge-vis {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    margin-inline-start: auto;
    font-size: var(--fs-xs);
    color: var(--text-secondary);
    cursor: pointer;
    user-select: none;
}

.cert-badge-vis input {
    width: 14px;
    height: 14px;
    margin: 0;
    accent-color: var(--p-700);
    cursor: pointer;
}

/* ---------- The shared list row ---------- */

/* Rungs, gaps and roadmap entries are the same row with different
   payloads, and they used to be three near-copies a pixel apart. */
.cert-rows {
    display: flex;
    flex-direction: column;
    background: var(--surface-raised);
    border: 1px solid var(--border-default);
    border-radius: var(--r-lg);
    box-shadow: var(--shadow-xs);
    overflow: hidden;
}

.cert-row {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 12px 15px;
    min-width: 0;
}

.cert-row + .cert-row { border-block-start: 1px solid var(--border-subtle); }

/* Only the rows that lead somewhere light up. The gap rows do not
   link anywhere, so a hover on them would promise a click. */
.cert-row.is-link:hover { background: var(--n-25); }

/* Only the rung rows carry a mark. The band under them is a ladder,
   and unmarked rows read as a list of links rather than as steps. */
.cert-row-mark {
    flex-shrink: 0;
    display: grid;
    place-items: center;
    width: 30px;
    height: 30px;
    border-radius: var(--r-md);
    background: var(--p-50);
    color: var(--p-700);
    box-shadow: inset 0 0 0 1px var(--p-100);
}

.cert-row-txt { flex: 1; min-width: 0; }

.cert-row-name {
    margin: 0;
    font-size: var(--fs-md);
    font-weight: var(--fw-semibold);
    line-height: var(--lh-snug);
    color: var(--text-primary);
}

.cert-row-sub {
    margin: 2px 0 0;
    font-size: var(--fs-xs);
    line-height: var(--lh-snug);
    color: var(--text-secondary);
}

.cert-row-tier {
    margin: 1px 0 0;
    font-family: var(--font-mono);
    font-size: var(--fs-2xs);
    color: var(--text-tertiary);
}

.cert-row-act { flex-shrink: 0; }

/* The soft navy action - "see the syllabus", "start". Sized off
   the button tokens so a row with one lines up with a row without. */
.cert-go {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 6px;
    height: var(--h-md);
    padding: 0 13px;
    border: 1px solid var(--p-100);
    border-radius: var(--r-md);
    background: var(--p-50);
    font-size: var(--fs-sm);
    font-weight: var(--fw-semibold);
    color: var(--p-700);
    white-space: nowrap;
    transition: background-color var(--dur-fast) var(--ease-out),
                border-color var(--dur-fast) var(--ease-out);
}

.cert-go:hover { background: var(--p-100); border-color: var(--p-200); }
.cert-go:focus-visible { outline: none; box-shadow: var(--shadow-focus); }

/* ---------- Gaps ---------- */

/* The bar is the point of the row: six skills read as a shape
   first and as six numbers second. Fixed width so the bars stack
   into a column that can be compared down. */
.cert-meter {
    flex-shrink: 0;
    width: 96px;
    height: 6px;
    border-radius: var(--r-full);
    background: var(--n-100);
    overflow: hidden;
}

.cert-meter-fill {
    height: 100%;
    border-radius: var(--r-full);
    background: var(--warning-500);
}

/* Below half is the one worth acting on this week; the rest is a
   thing to keep an eye on. Two steps, not a gradient - a bar whose
   hue drifts with the value invites reading the hue as the value. */
.cert-meter-fill.is-low { background: var(--danger-500); }

.cert-pct {
    flex-shrink: 0;
    width: 42px;
    font-family: var(--font-mono);
    font-size: var(--fs-sm);
    font-variant-numeric: tabular-nums;
    text-align: end;
    color: var(--text-secondary);
}

/* ---------- Composite paths ---------- */

.cert-comb {
    padding: 13px 15px;
    min-width: 0;
}

.cert-comb + .cert-comb { border-block-start: 1px solid var(--border-subtle); }

.cert-comb-top {
    display: flex;
    align-items: flex-start;
    gap: 10px;
}

.cert-comb-parts {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
    margin-block-start: 9px;
}

/* What is missing is named rather than counted, so each component
   is its own target: somebody one credential short can see which
   one and go and get it. */
.cert-part {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    height: 24px;
    padding: 0 10px;
    border: 1px solid var(--border-default);
    border-radius: var(--r-full);
    background: var(--surface-raised);
    font-size: var(--fs-xs);
    color: var(--text-secondary);
    transition: border-color var(--dur-fast) var(--ease-out),
                color var(--dur-fast) var(--ease-out);
}

.cert-part:hover { border-color: var(--border-strong); color: var(--text-primary); }
.cert-part:focus-visible { outline: none; box-shadow: var(--shadow-focus); }

.cert-part.is-held {
    border-color: var(--success-100);
    background: var(--success-50);
    color: var(--success-700);
    font-weight: var(--fw-medium);
}

.cert-part.is-held:hover { border-color: var(--success-500); color: var(--success-700); }

.cert-part > svg { flex-shrink: 0; }

/* The empty marker on a component not yet held. A ring rather than
   a tick keeps the two chips the same length. */
.cert-part-o {
    width: 11px;
    height: 11px;
    border: 1.5px solid var(--n-300);
    border-radius: var(--r-full);
}

/* ---------- Catalogue toolbar ---------- */

.cert-tools {
    display: flex;
    align-items: center;
    gap: 12px;
    flex-wrap: wrap;
    margin-block-end: 12px;
}

.cert-search { width: min(360px, 100%); }

/* The count sits next to the box that changes it rather than on the tab: a
   badge that moves while you type reads as a different tab, not as a
   narrower list. */
.cert-tools-n {
    margin: 0;
    margin-inline-start: auto;
    font-size: var(--fs-xs);
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
    color: var(--text-tertiary);
}

/* The sentence a band opens with now that the band's heading is the tab. */
.cert-sec-lede {
    margin: 0 0 12px;
    max-width: 74ch;
    font-size: var(--fs-sm);
    line-height: var(--lh-normal);
    color: var(--text-secondary);
}

/* ---------- The catalogue ---------- */

.cert-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(304px, 1fr));
    gap: 12px;
}

.cert-c {
    position: relative;
    display: flex;
    flex-direction: column;
    min-width: 0;
    border: 1px solid var(--border-default);
    border-radius: var(--r-xl);
    background: var(--surface-raised);
    box-shadow: var(--shadow-xs);
    transition: border-color var(--dur-fast) var(--ease-out),
                box-shadow var(--dur-fast) var(--ease-out),
                transform var(--dur-fast) var(--ease-out);
}

.cert-c:hover {
    border-color: var(--border-strong);
    box-shadow: var(--shadow-md);
    transform: translateY(-1px);
}

.cert-c:focus-within { border-color: var(--p-300); }

/* One card already earned, in the catalogue rather than on the
   wall - a hairline of the wall's gold so it is findable in the
   grid without the crest and the badge's full wash coming with it. */
.cert-c.is-held { border-color: var(--gold-200); }

/* The card's own light: a wash off the top edge, lit on the pointer.
   Held cards keep it lit and keep it gold - they are a piece of the
   wall standing in the catalogue - so the grid can be scanned for
   what is already earned without reading a word of it.

   On the card rather than on the face, so the radius clips it; the
   face and the foot are positioned to sit above it. */
.cert-c::before {
    content: "";
    position: absolute;
    inset-block-start: 0;
    inset-inline: 0;
    height: 96px;
    border-radius: var(--r-xl) var(--r-xl) 0 0;
    background: linear-gradient(180deg, var(--p-50), transparent);
    opacity: 0;
    pointer-events: none;
    transition: opacity var(--dur-mid) var(--ease-out);
}

.cert-c:hover::before,
.cert-c:focus-within::before { opacity: 1; }

.cert-c.is-held::before {
    background: linear-gradient(180deg, var(--gold-50), transparent);
    opacity: 1;
}

@media (prefers-reduced-motion: reduce) {
    .cert-c { transition: none; }
    .cert-c:hover { transform: none; }
    .cert-c::before { transition: none; }
}

/* The face is a button: it opens the drawer. Reset rather than
   restyle - it inherits nothing useful from the user agent and
   everything useful from the card. */
.cert-c-face {
    position: relative;
    flex: 1;
    display: flex;
    flex-direction: column;
    gap: 8px;
    padding: 15px 16px 4px;
    background: none;
    border: none;
    border-radius: var(--r-xl) var(--r-xl) 0 0;
    text-align: start;
    font: inherit;
    color: inherit;
    cursor: pointer;
}

.cert-c-face:focus-visible { outline: none; box-shadow: var(--shadow-focus); }

.cert-c-top {
    display: flex;
    align-items: flex-start;
    gap: 10px;
}

/* The wall's mark, unfilled: the shape of a credential not held yet.
   It gives twenty near-identical text cards a rhythm to scan down,
   and it is the same 32px tile the rung rows carry so the two bands
   line up when a candidate moves between the tabs. */
.cert-c-mark {
    flex-shrink: 0;
    display: grid;
    place-items: center;
    width: 32px;
    height: 32px;
    border-radius: var(--r-md);
    background: var(--p-50);
    color: var(--p-700);
    box-shadow: inset 0 0 0 1px var(--p-100);
}

/* Earned, so it is filled - the crest the badge wears, at the
   catalogue's size. */
.cert-c.is-held .cert-c-mark {
    background: linear-gradient(140deg, var(--gold-300), var(--gold-500));
    color: var(--p-800);
    box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.42);
}

.cert-c-txt { flex: 1; min-width: 0; }

.cert-c-name {
    display: block;
    margin: 0;
    font-size: var(--fs-base);
    font-weight: var(--fw-bold);
    letter-spacing: -0.008em;
    line-height: var(--lh-snug);
    color: var(--text-primary);
}

.cert-c-tier {
    display: block;
    margin: 2px 0 0;
    font-family: var(--font-mono);
    font-size: var(--fs-2xs);
    color: var(--text-tertiary);
}

.cert-c-desc {
    display: block;
    margin: 0;
    font-size: var(--fs-sm);
    line-height: var(--lh-normal);
    color: var(--text-secondary);
}

/* The affordance for the face being a target at all. Present but
   nearly off until the card is under the pointer, because on a
   grid of twenty it would otherwise be twenty arrows. */
.cert-c-more {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    margin-block-start: auto;
    padding-block-start: 2px;
    font-size: var(--fs-2xs);
    font-weight: var(--fw-semibold);
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--text-disabled);
    transition: color var(--dur-fast) var(--ease-out);
}

.cert-c:hover .cert-c-more,
.cert-c:focus-within .cert-c-more { color: var(--p-700); }

.cert-c-foot {
    position: relative;
    display: flex;
    flex-direction: column;
    gap: 8px;
    padding: 10px 16px 14px;
}

/* What they hold, above the button that would sit it again. */
.cert-c-held {
    display: flex;
    align-items: center;
    gap: 7px;
    padding: 7px 10px;
    border-radius: var(--r-md);
    background: var(--success-50);
    font-size: var(--fs-sm);
    font-weight: var(--fw-semibold);
    color: var(--success-700);
}

.cert-c-held > svg { flex-shrink: 0; }

.cert-c-held.is-expired {
    background: var(--n-75);
    color: var(--text-secondary);
    font-weight: var(--fw-medium);
}

/* The one action on the card owns its row: the foot is a column
   and a 90px button floating in it reads as unfinished. */
.cert-c-foot .cert-go { width: 100%; }

/* Blocked is a sentence, not a disabled button: a greyed-out
   "Start" says no and nothing else, and every one of these states
   has a date or a reason attached to it. */
.cert-c-block {
    padding: 8px 10px;
    border: 1px dashed var(--border-default);
    border-radius: var(--r-md);
    font-size: var(--fs-xs);
    line-height: var(--lh-snug);
    text-align: center;
    color: var(--text-tertiary);
}

/* ---------- Roadmap ---------- */

.cert-votes {
    flex-shrink: 0;
    width: 78px;
    font-size: var(--fs-xs);
    font-variant-numeric: tabular-nums;
    text-align: end;
    color: var(--text-tertiary);
}

/* ---------- Nothing matched ---------- */

.cert-empty {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 6px;
    padding: 40px 24px;
    text-align: center;
}

.cert-empty-mark {
    display: grid;
    place-items: center;
    width: 40px;
    height: 40px;
    margin-block-end: 4px;
    border-radius: var(--r-full);
    background: var(--n-75);
    color: var(--text-disabled);
}

.cert-empty-h {
    margin: 0;
    font-size: var(--fs-lg);
    font-weight: var(--fw-semibold);
    color: var(--text-primary);
}

.cert-empty-p {
    margin: 0;
    max-width: 48ch;
    font-size: var(--fs-sm);
    line-height: var(--lh-normal);
    color: var(--text-tertiary);
}

.cert-empty .btn { margin-block-start: 10px; }

/* ---------- The tab rail ---------- */

/* Three questions, three tabs, on the shared .tabs primitive.

   No margin of its own. It carried 16px to keep a band off the rule - a band
   starting hard against it reads as part of the tab rather than as what the
   tab opened - but it is a child of .cert-pg, whose flex gap was already
   doing that, so the rule stood 38px off the thing it opened while every
   other pair on the page was 18px apart. */
.cert-tabbar { min-width: 0; }

/* ---------- Loading ---------- */

/* The shape of what is coming rather than a word in the middle of an empty
   column, so the wall and the grid land without the page jumping under them. */
.cert-skel {
    display: flex;
    flex-direction: column;
    gap: 14px;
}

.cert-skel-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(304px, 1fr));
    gap: 12px;
}

.cert-skel-card { height: 168px; border-radius: var(--r-xl); }

/* ---------- The drawer body ---------- */

/* QDrawer supplies the chrome (section 20); this is only what the
   certification puts inside it. */

.cert-dw {
    display: flex;
    flex-direction: column;
    gap: 16px;
    min-width: 0;
}

.cert-dw-lede {
    margin: 0;
    font-size: var(--fs-md);
    line-height: var(--lh-relaxed);
    color: var(--text-secondary);
}

/* The paper's terms as labelled pairs. Nobody reads these for a
   value in a cell; they read them to find the one that decides
   whether to start now. */
.cert-dw-facts {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(132px, 1fr));
    gap: 10px;
}

.cert-dw-fact {
    padding: 10px 12px;
    border: 1px solid var(--border-default);
    border-radius: var(--r-lg);
    background: var(--n-25);
}

.cert-dw-fact-lab {
    margin: 0;
    font-size: var(--fs-2xs);
    font-weight: var(--fw-semibold);
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--text-tertiary);
}

.cert-dw-fact-val {
    margin: 4px 0 0;
    font-family: var(--font-display);
    font-size: var(--fs-xl);
    font-weight: var(--fw-semibold);
    font-variant-numeric: tabular-nums;
    line-height: var(--lh-tight);
    color: var(--text-primary);
}

/* What they already hold, when they hold it. Gold rather than
   green: it is the wall's colour, and this is a piece of the wall
   shown in another place. */
.cert-dw-held {
    display: flex;
    align-items: flex-start;
    gap: 10px;
    padding: 12px 14px;
    border: 1px solid var(--gold-200);
    border-radius: var(--r-lg);
    background: var(--gold-50);
}

.cert-dw-held > svg { flex-shrink: 0; margin-block-start: 2px; color: var(--gold-700); }

.cert-dw-held-band {
    margin: 0;
    font-size: var(--fs-md);
    font-weight: var(--fw-semibold);
    color: var(--gold-800);
}

.cert-dw-held-sub {
    margin: 2px 0 0;
    font-size: var(--fs-xs);
    line-height: var(--lh-snug);
    color: var(--gold-700);
}

.cert-dw-held.is-expired {
    border-color: var(--border-default);
    background: var(--n-25);
}

.cert-dw-held.is-expired > svg { color: var(--text-disabled); }
.cert-dw-held.is-expired .cert-dw-held-band { color: var(--text-primary); }
.cert-dw-held.is-expired .cert-dw-held-sub { color: var(--text-tertiary); }

/* Why it cannot be started, in the drawer's own words. Neutral,
   not amber: the state is ordinary and dated, and the drawer is
   where the date is actually legible. */
.cert-dw-note {
    padding: 11px 13px;
    border: 1px solid var(--border-subtle);
    border-radius: var(--r-lg);
    background: var(--n-25);
    font-size: var(--fs-sm);
    line-height: var(--lh-normal);
    color: var(--text-secondary);
}

.cert-dw-note.is-warn {
    border-color: var(--gold-200);
    background: var(--gold-50);
    color: var(--gold-700);
}

/* The footer action fills the bar. There is one thing to do from
   here, and the drawer is narrow enough that a 90px button on the
   right reads as an afterthought. */
.drw-foot .cert-dw-cta { flex: 1; justify-content: center; }

/* ---------- Narrow ---------- */

/* The counts drop under the lede rather than squeezing three tiles
   into the space of one, and then share that line equally. */
@media (max-width: 960px) {
    .cert-hero-in { flex-wrap: wrap; gap: 18px; }
    .cert-hero-stats { width: 100%; }
    .cert-stat { flex: 1; min-width: 0; }
    .cert-hero-cta { width: 100%; justify-content: center; }
}

@media (max-width: 720px) {
    /* .cert-pg keeps its 18px here: the stack is already at the tighter
       measure the wide layout uses. */
    .cert-hero { padding: 16px; }
    .cert-hero-in { gap: 14px; }
    .cert-hero-crest { width: 38px; height: 38px; }
    .cert-hero-h { font-size: var(--fs-xl); }
    .cert-stat { min-width: 0; padding: 11px 10px; }
    .cert-stat-v { font-size: var(--fs-2xl); }

    /* The action drops under the text rather than squeezing a
       two-word label to one word a line beside it. */
    .cert-row { flex-wrap: wrap; }
    .cert-row-act { width: 100%; }
    .cert-row-act .cert-go { width: 100%; }

    .cert-meter { width: 64px; }

    .cert-badge-foot { flex-wrap: wrap; row-gap: 8px; }
    .cert-badge-vis { margin-inline-start: 0; }
}


/* ============================================================
   37. Super Admin: Certification authoring (cauth-*)

   The panel behind "New certification" in the platform console.
   It was the fifth hand-rolled drawer in the product - its own
   fixed div, its own scrim at a different opacity, 24px of padding
   and the scroll on the panel itself, which put Save under the
   fold the moment a fourth syllabus row was added. It now wears
   QDrawer (section 20) like the four settings editors do, so the
   header and the action bar stay put and only the form moves.

   What is left here is the part no other drawer has: the syllabus.
   It is not a list of labels - the weights become the sampling
   blueprint, so they decide how many questions come from each
   skill, and a paper is drawn to match what the candidate was
   shown. That is why the allocation is a meter rather than a
   number in a corner: the operator has to see 100% land, and see
   it while they are typing rather than when the save is refused.

   Three states, and only two of them are coloured. Under 100 is
   the ordinary state of a form being filled in and is left neutral;
   exactly 100 is the one worth confirming; over 100 is the one
   that has to be fixed.

   What is here:
     .cauth-nums   the three short numbers that read as one fact
     .cauth-syl    the syllabus block and its meter
     .cauth-row    one domain - skill, weight, remove
     .cauth-add    the dashed "one more domain"
   ============================================================ */

/* Questions served, pass mark, validity. Narrower than .drw-pair
   because these are two- and three-digit numbers, not names, and
   at 200px each they sit in a field twice the width of anything
   ever typed into it. */
.cauth-nums {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(118px, 1fr));
    gap: 10px;
}

/* ---------- Syllabus ---------- */

/* Its own surface inside the form: the rows below are a list with
   its own add button, and against the flat body they read as loose
   fields belonging to whatever field came before them. */
.cauth-syl {
    padding: 13px 14px;
    border: 1px solid var(--border-subtle);
    border-radius: var(--r-lg);
    background: var(--n-25);
}

.cauth-syl-head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 8px;
}

/* The label carries its own gap; inside the head it would push the
   allocation off its baseline. */
.cauth-syl-head .lbl { margin-block-end: 0; }

.cauth-alloc {
    flex-shrink: 0;
    font-family: var(--font-mono);
    font-size: var(--fs-xs);
    font-variant-numeric: tabular-nums;
    color: var(--text-tertiary);
}

.cauth-alloc.is-ok { color: var(--success-700); }
.cauth-alloc.is-over { color: var(--danger-600); font-weight: var(--fw-semibold); }

/* Directly under the head and above the rows it measures. Thin:
   it is a readout, not a control, and the rows are what the
   operator is working in. */
.cauth-track {
    height: 4px;
    margin-block-start: 8px;
    border-radius: var(--r-full);
    background: var(--n-150);
    overflow: hidden;
}

.cauth-fill {
    height: 100%;
    border-radius: var(--r-full);
    background: var(--n-400);
    transition: width var(--dur-mid) var(--ease-out);
}

.cauth-fill.is-ok { background: var(--success-500); }
.cauth-fill.is-over { background: var(--danger-500); }

@media (prefers-reduced-motion: reduce) {
    .cauth-fill { transition: none; }
}

.cauth-hint {
    margin: 8px 0 0;
    font-size: var(--fs-xs);
    line-height: var(--lh-snug);
    color: var(--text-tertiary);
}

/* ---------- The domains ---------- */

.cauth-rows {
    display: flex;
    flex-direction: column;
    gap: 7px;
    margin-block-start: 10px;
}

/* Skill, weight, remove. A grid rather than a flex row so the
   weight column and the button line up down the list however long
   a skill tag is. */
.cauth-row {
    display: grid;
    grid-template-columns: 1fr 76px var(--h-md);
    gap: 7px;
    align-items: center;
}

/* Both inputs one step down from the form's own: these are rows in
   a list, and at full height they out-weigh the fields above them. */
.cauth-row .input {
    height: var(--h-md);
    font-size: var(--fs-sm);
}

.cauth-row .input[type="number"] { text-align: end; }

.cauth-x {
    display: grid;
    place-items: center;
    width: var(--h-md);
    height: var(--h-md);
    padding: 0;
    border: 1px solid var(--border-default);
    border-radius: var(--r-md);
    background: var(--surface-raised);
    color: var(--text-tertiary);
    cursor: pointer;
    transition: color var(--dur-fast) var(--ease-out),
                border-color var(--dur-fast) var(--ease-out),
                background-color var(--dur-fast) var(--ease-out);
}

/* Red on hover, not at rest: a column of red buttons down a form
   reads as a column of errors. */
.cauth-x:hover {
    border-color: var(--danger-200);
    background: var(--danger-50);
    color: var(--danger-600);
}

.cauth-x:focus-visible { outline: none; box-shadow: var(--shadow-focus); }

/* Dashed and full width - the shape of a row that is not there
   yet, in the place the next one will appear. */
.cauth-add {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 6px;
    width: 100%;
    height: var(--h-md);
    margin-block-start: 8px;
    border: 1px dashed var(--border-strong);
    border-radius: var(--r-md);
    background: transparent;
    font-size: var(--fs-sm);
    font-weight: var(--fw-medium);
    color: var(--text-secondary);
    cursor: pointer;
    transition: border-color var(--dur-fast) var(--ease-out),
                color var(--dur-fast) var(--ease-out),
                background-color var(--dur-fast) var(--ease-out);
}

.cauth-add:hover {
    border-color: var(--p-700);
    background: var(--p-50);
    color: var(--p-700);
}

.cauth-add:focus-visible { outline: none; box-shadow: var(--shadow-focus); }

/* What an empty syllabus means, said once. Without it the block is
   a label, a flat bar and a dashed button, and nothing on it says
   that saving now samples the bank evenly. */
.cauth-none {
    margin: 10px 0 0;
    padding: 10px 12px;
    border: 1px dashed var(--border-default);
    border-radius: var(--r-md);
    font-size: var(--fs-xs);
    line-height: var(--lh-normal);
    color: var(--text-tertiary);
}

/* ---------- The bar ---------- */

/* Why Save is off, on the bar with the button rather than beside
   the field that is empty: by the time the operator is reaching
   for Save they are looking here. */
.cauth-why {
    font-size: var(--fs-xs);
    line-height: var(--lh-snug);
    color: var(--text-tertiary);
}

@media (max-width: 640px) {
    /* The weight keeps its column; the skill takes what is left.
       Below this the two inputs stop being usable side by side. */
    .cauth-row { grid-template-columns: 1fr 64px var(--h-md); }

    /* It is a note, not an action, and on a stacked bar it would
       sit above two full-width buttons as if it were one. */
    .cauth-why { display: none; }
}

/* ------------------------------------------------------------
   The catalogue page itself (ccat-*)

   /admin/certifications, the list the drawer above is opened
   from. Everything on it used to be written out in style
   attributes: seven table paddings, a mono stack spelled out
   nine times, and a tab row hand-built from a padded .card,
   which is a segmented control - "pick one setting" - on a page
   whose two tabs are two views of the same subject. It wears
   .tbar / .chip-tog / .tbl / QTabs now, so it lines up with
   Tenants and the platform team beside it in the nav.

   The two facts an operator actually decides from are drawn
   rather than spelled. A bank's distance from the minimum a
   credential needs is what gates the move to Live, and it was a
   fraction set in 10.5px type; a request's votes decide the
   authoring order, and they were a number to be compared down
   the column by eye. Both get a bar.

   Coverage is carried on the control that changes it, not by
   tinting the row: a row washed amber reads as a warning about
   that certification rather than as the state it is in.
   ------------------------------------------------------------ */

.ccat-kpis {
    display: grid;
    /* auto-fit, because a quarter of a laptop console is narrower
       than "Open to candidates" standing in it. */
    grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
    gap: 10px;
    margin-block-end: 16px;
}

/* ---------- Toolbar ---------- */

.ccat-find { width: min(260px, 100%); }

.ccat-count {
    margin: 0;
    margin-inline-start: auto;
    font-size: var(--fs-xs);
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
    color: var(--text-tertiary);
}

/* The one line of standing explanation the queue needs. Inside
   the card, on the sunken ground, so it reads as a caption on
   the table rather than as a banner about the page. */
.ccat-lede {
    margin: 0;
    padding: 11px 16px;
    background: var(--surface-sunken);
    border-block-end: 1px solid var(--border-subtle);
    font-size: var(--fs-sm);
    line-height: var(--lh-normal);
    color: var(--text-secondary);
}

/* ---------- Cells ---------- */

/* Seven columns, each the only place its fact appears, so the
   table scrolls inside .tbl-wrap rather than dropping one. */
.ccat-tbl { min-width: 980px; }

.ccat-col-tier   { width: 150px; }
.ccat-col-bank   { width: 220px; }
.ccat-col-n      { width: 84px; }
.ccat-col-cov    { width: 200px; }
.ccat-col-demand { width: 170px; }
.ccat-col-state  { width: 140px; }
.ccat-col-when   { width: 130px; }

.ccat-name {
    margin: 0;
    font-size: var(--fs-md);
    font-weight: var(--fw-semibold);
    color: var(--text-primary);
}

/* The version, the domain count and the pass mark, under the
   name all three belong to. Mono, because all three are figures. */
.ccat-meta {
    margin: 2px 0 0;
    font-family: var(--font-mono);
    font-size: var(--fs-2xs);
    color: var(--text-tertiary);
}

.ccat-tier {
    font-family: var(--font-mono);
    font-size: var(--fs-2xs);
    color: var(--text-secondary);
}

.ccat-when {
    margin: 0;
    font-family: var(--font-mono);
    font-size: var(--fs-2xs);
    color: var(--text-tertiary);
}

.ccat-expected { margin-block-start: 4px; }

.ccat-num {
    font-family: var(--font-mono);
    font-variant-numeric: tabular-nums;
}

.ccat-num.is-strong { font-weight: var(--fw-semibold); color: var(--text-primary); }

/* Nobody has earned it yet. The em dash is a fact, not a figure,
   and at full strength it reads as one more number in the column. */
.ccat-num.is-soft { color: var(--text-disabled); }

.ccat-none {
    font-size: var(--fs-xs);
    color: var(--text-tertiary);
}

/* ---------- The bank, and how far it has to go ---------- */

.ccat-nobank {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    font-size: var(--fs-xs);
    font-weight: var(--fw-semibold);
    color: var(--danger-600);
}

.ccat-nobank > svg { flex-shrink: 0; }

.ccat-bank-name {
    margin: 0;
    max-width: 190px;
    font-size: var(--fs-sm);
    color: var(--text-primary);
}

/* Ten banked per one served. The gap to that minimum is what
   decides whether the row beside it may go Live, so it is drawn:
   a column of these can be read at a glance, and a column of
   fractions could not. */
.ccat-cap {
    display: flex;
    align-items: center;
    gap: 7px;
    margin-block-start: 5px;
}

.ccat-cap-track {
    flex: 1;
    min-width: 44px;
    max-width: 92px;
    height: 4px;
    border-radius: var(--r-full);
    background: var(--n-100);
    overflow: hidden;
}

.ccat-cap-fill {
    display: block;
    height: 100%;
    border-radius: var(--r-full);
    background: var(--success-500);
}

.ccat-cap.is-short .ccat-cap-fill { background: var(--danger-500); }

.ccat-cap-n {
    font-family: var(--font-mono);
    font-size: var(--fs-2xs);
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
    color: var(--text-tertiary);
}

.ccat-cap.is-short .ccat-cap-n {
    font-weight: var(--fw-semibold);
    color: var(--danger-600);
}

/* ---------- Coverage, in the cell that changes it ---------- */

.ccat-sel {
    width: 100%;
    max-width: 182px;
    height: 30px;
    padding: 0 8px;
    border: 1px solid var(--border-default);
    border-radius: var(--r-md);
    background: var(--surface-raised);
    font-family: inherit;
    font-size: var(--fs-xs);
    color: var(--text-primary);
    cursor: pointer;
}

.ccat-sel:hover { border-color: var(--border-strong); }

.ccat-sel:focus-visible {
    outline: none;
    border-color: var(--border-focus);
    box-shadow: var(--shadow-focus);
}

/* The state is worn by the control, not by the row. Two of the
   four are coloured: the one an employer may rely on, and the one
   that has a date attached and is being worked. */
.ccat-sel.is-good {
    border-color: var(--success-100);
    background: var(--success-50);
    color: var(--success-700);
}

.ccat-sel.is-warn {
    border-color: var(--gold-200);
    background: var(--gold-50);
    color: var(--gold-700);
}

/* ---------- The request queue ---------- */

/* The queue is ordered by this number, so it is drawn against the
   leader instead of being compared down the column by eye. */
.ccat-demand {
    display: flex;
    align-items: center;
    gap: 9px;
}

.ccat-demand-n {
    min-width: 26px;
    font-family: var(--font-mono);
    font-size: var(--fs-md);
    font-weight: var(--fw-bold);
    font-variant-numeric: tabular-nums;
    text-align: end;
    color: var(--text-primary);
}

.ccat-demand-track {
    flex: 1;
    min-width: 40px;
    max-width: 108px;
    height: 5px;
    border-radius: var(--r-full);
    background: var(--n-100);
    overflow: hidden;
}

.ccat-demand-fill {
    display: block;
    height: 100%;
    border-radius: var(--r-full);
    background: var(--p-700);
}

@media (max-width: 720px) {
    /* The bar is a comparison, and a comparison across the four
       rows that still fit is not one. The figure stays. */
    .ccat-demand-track,
    .ccat-cap-track { display: none; }

    .ccat-count { margin-inline-start: 0; }
}

/* ── Marking one attempt a machine could not settle (cmark-*) ──
   §14.2's way out of pending_review. The whole paper is shown, not
   only the half that needs marking: a marker judging one written
   answer with no sight of how the rest went is judging it against
   nothing. */

.cmark-standing {
    border: 1px solid var(--border-default);
    border-radius: var(--r-md);
    background: var(--n-25);
    padding: 10px 12px;
    margin-bottom: 16px;
}

.cmark-standing-row {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 12px;
    font-size: var(--fs-sm);
}

.cmark-standing-row + .cmark-standing-row { margin-top: 6px; }

.cmark-standing-k { color: var(--text-tertiary); }

.cmark-standing-v {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    font-variant-numeric: tabular-nums;
}

/* The projected result is the decision being made, so it is ruled off
   from the two numbers that produce it rather than listed beside them. */
.cmark-standing-out {
    margin-top: 8px;
    padding-top: 8px;
    border-top: 1px solid var(--border-default);
}

.cmark-standing-out strong { font-size: var(--fs-md); }

.cmark-bar {
    color: var(--text-tertiary);
    font-size: var(--fs-xs);
}

.cmark-q {
    border: 1px solid var(--border-default);
    border-radius: var(--r-md);
    padding: 11px 12px;
    margin-bottom: 10px;
}

/* The marker's own questions carry the accent; the machine-marked ones
   recede, because they are context rather than work. */
.cmark-q-mine { border-inline-start: 3px solid var(--warning-500); }

.cmark-q-auto { opacity: 0.78; }

.cmark-q-head {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 10px;
    margin-bottom: 7px;
}

.cmark-q-text {
    font-weight: var(--fw-semibold);
    font-size: var(--fs-sm);
    line-height: var(--lh-snug);
}

.cmark-a { margin-bottom: 8px; }

/* pre, not a div: a written answer's own line breaks are part of what
   is being marked, and collapsing them changes the thing being judged. */
.cmark-a-text {
    margin: 0;
    padding: 8px 10px;
    background: var(--n-25);
    border-radius: var(--r-md);
    font-family: inherit;
    font-size: var(--fs-sm);
    line-height: 1.5;
    white-space: pre-wrap;
    overflow-wrap: anywhere;
}

.cmark-a-none {
    color: var(--text-tertiary);
    font-style: italic;
    font-size: var(--fs-sm);
}

.cmark-guide { margin-bottom: 9px; }

.cmark-guide > summary {
    cursor: pointer;
    font-size: var(--fs-xs);
    color: var(--text-tertiary);
    user-select: none;
}

/* Folded away by default. It is what the author was looking for, and a
   marker who reads it before the answer is marking the key, not the
   candidate. */
.cmark-guide-text {
    margin: 6px 0 0;
    padding: 8px 10px;
    background: var(--n-25);
    border-inline-start: 2px solid var(--border-default);
    border-radius: var(--r-md);
    font-family: inherit;
    font-size: var(--fs-xs);
    line-height: 1.5;
    white-space: pre-wrap;
    overflow-wrap: anywhere;
}

.cmark-award {
    display: flex;
    flex-direction: column;
    gap: 4px;
}

.cmark-award-in {
    width: 96px;
    font-variant-numeric: tabular-nums;
}


/* ============================================================
   38. Super Admin: Platform team (padm-*)

   /admin/settings/team - the list of people who can sign in to the
   platform console, and the five forms that act on one of them.

   It was the last section of the settings hub still drawn in style
   attributes: a nine-column table whose every cell repeated its own
   padding, six 28px squares of action per row, four filter buttons
   that rebuilt a segmented control out of ternaries, and five
   centred modals - one of them 720px wide inside a box capped at
   92vh, so the roles picker and the save button were both below the
   fold on a laptop the moment the notes field was touched.

   A style attribute cannot express a hover, a focus ring or a
   breakpoint, so none of it had any: the sortable headers gave no
   sign they could be clicked, the action squares did not respond to
   the pointer, nothing was visible when tabbed to, and the table
   kept its nine columns at every width.

   What changed, and why:

     - The four counts move above the list. "How many admins are
       locked out" was a question you could only answer by filtering
       and then reading the pager.
     - Name and phone fold into one Member cell with a face on it.
       Three columns of the same person is three columns the roles
       and the status have to fight for width against; both sorts
       stay, on the two headers that own them.
     - Status is a QPill, the same one the rest of the product uses
       for the same idea, rather than a chip mixed by hand per row.
     - The six action squares become one cluster of shared .btn icon
       buttons, so they hover, focus and disable like every other
       button in the console.
     - Every modal becomes a QDrawer ("20." above). A form with a
       roles picker and a notes field wants the height of the screen
       and a footer that does not scroll, and the reader keeps the
       row they opened it from in view beside it.

   Toolbar, chips, footer, meter and drawer chrome are all shared
   primitives (.tbar, .chip-tog, .card-foot, .meter, .drw-*); only
   what this screen has and no other does is below.

   What is here:
     .padm-sum     the four counts above the list
     .padm-tgl     the "show deleted" switchback in the card head
     .padm-bulk    the bar the selection raises
     .padm-sort    a sortable column header
     .padm-who     the member cell: face, name, phone, flags
     .padm-acts    the per-row action cluster
     .padm-nav     the pager on the card's footer
     .padm-drw-*   the parts of the five drawers that are not .fld
   ============================================================ */

/* ---------- The counts ----------
   auto-fit, because the same band sits in a working column that is
   244px narrower than the page and has to survive a laptop. */

.padm-sum {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(148px, 1fr));
    gap: 10px;
}

.padm-tile {
    padding: 12px 14px;
    border: 1px solid var(--border-subtle);
    border-radius: var(--r-lg);
    background: var(--surface-raised);
    box-shadow: var(--shadow-xs);
}

.padm-tile-lab {
    margin: 0;
    font-size: var(--fs-2xs);
    font-weight: var(--fw-semibold);
    letter-spacing: 0.05em;
    text-transform: uppercase;
    color: var(--text-tertiary);
}

.padm-tile-n {
    margin: 4px 0 0;
    font-size: var(--fs-2xl);
    font-weight: var(--fw-bold);
    line-height: var(--lh-tight);
    font-variant-numeric: tabular-nums;
    letter-spacing: -0.02em;
    color: var(--text-primary);
}

.padm-tile-sub {
    margin: 3px 0 0;
    font-size: var(--fs-xs);
    line-height: var(--lh-snug);
    color: var(--text-tertiary);
}

/* Tinted only when the number is one somebody has to do something
   about. A locked-out admin at zero is the normal state and must not
   read as a warning; at one it is the reason this band exists. */
.padm-tile.is-warn { border-color: var(--warning-100); background: var(--warning-50); }
.padm-tile.is-warn .padm-tile-n,
.padm-tile.is-warn .padm-tile-lab { color: var(--warning-700); }

.padm-tile.is-good .padm-tile-n { color: var(--success-700); }

/* ---------- Card head ---------- */

/* A checkbox in a header band of buttons. It keeps the buttons'
   height so the three controls sit on one line rather than on a line
   and a half. */
.padm-tgl {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    height: var(--h-md);
    padding-inline-end: 4px;
    font-size: var(--fs-sm);
    color: var(--text-secondary);
    white-space: nowrap;
    cursor: pointer;
}

.padm-tgl input { cursor: pointer; }

/* ---------- Toolbar ---------- */

/* The search is the toolbar's first control and the widest thing on
   it; below 720px it takes the row to itself rather than squeezing
   the status chips onto a second line one at a time. */
.padm-find { flex: 0 1 280px; min-width: 200px; }

/* ---------- Selection ----------
   Sticky to the foot of the card rather than pinned under the last
   row: with a page of fifty selected from the top, the bar was a full
   screen below the rows it acts on. */

.padm-bulk {
    position: sticky;
    inset-block-end: 0;
    z-index: 3;
    display: flex;
    align-items: center;
    gap: 8px;
    flex-wrap: wrap;
    padding: 10px 16px 10px 20px;
    border-block-start: 1px solid var(--p-100);
    background: var(--p-50);
}

.padm-bulk-n {
    margin-inline-end: 4px;
    font-size: var(--fs-sm);
    font-weight: var(--fw-semibold);
    font-variant-numeric: tabular-nums;
    color: var(--p-800);
}

.padm-bulk-end { margin-inline-start: auto; }

/* ---------- Rows ---------- */

/* The checkbox column: exactly its own width, so the width goes to
   the names instead. */
.padm-pick { width: 1%; }
.padm-pick input { display: block; cursor: pointer; }

/* A sortable header is a button, which is what makes it announce
   itself as one and take focus. The arrow is on the sorted column
   only - four grey carets say "sortable", not "sorted by". */
.padm-sort {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    padding: 0;
    border: 0;
    background: none;
    font: inherit;
    letter-spacing: inherit;
    text-transform: inherit;
    color: inherit;
    cursor: pointer;
    transition: color var(--dur-fast) var(--ease-out);
}

.padm-sort:hover { color: var(--text-primary); }
.padm-sort:focus-visible { outline: none; border-radius: var(--r-xs); box-shadow: var(--shadow-focus); }
.padm-sort.is-on { color: var(--p-700); }
.padm-sort-i { display: inline-grid; place-items: center; }

.padm-row { transition: background-color var(--dur-fast) var(--ease-out); }
.padm-row:hover { background: var(--n-25); }

/* Selected wins over hover, or the row just ticked loses its marking
   the moment the pointer is over it. */
.padm-row.is-on,
.padm-row.is-on:hover { background: var(--p-50); }

/* A deleted user is a record, not a colleague. Faded rather than
   hidden, because the list can be asked to show them. */
.padm-row.is-off { opacity: 0.55; }

/* The member cell: a face, the name, and the flags that belong to the
   person rather than to their session. */
.padm-who { display: flex; align-items: center; gap: 10px; min-width: 0; }
.padm-who-txt { min-width: 0; }

.padm-who-name {
    display: flex;
    align-items: center;
    gap: 6px;
    flex-wrap: wrap;
    font-weight: var(--fw-medium);
    color: var(--text-primary);
}

.padm-who-sub {
    margin: 1px 0 0;
    font-size: var(--fs-xs);
    color: var(--text-tertiary);
    font-family: var(--font-mono);
}

/* A standing fact about the account - MFA is required, the password
   is owed. Smaller than a QPill because a row can carry two of them
   beside a name that matters more than either. */
.padm-flag {
    padding: 1px 6px;
    border-radius: var(--r-full);
    font-size: 9.5px;
    font-weight: var(--fw-semibold);
    letter-spacing: 0.03em;
    text-transform: uppercase;
    white-space: nowrap;
}

.padm-flag-mfa { background: var(--p-50); color: var(--p-700); }
.padm-flag-pwd { background: var(--warning-50); color: var(--warning-700); }

.padm-mail { display: flex; align-items: center; gap: 5px; min-width: 0; }

.padm-mail-a {
    font-family: var(--font-mono);
    font-size: var(--fs-xs);
    color: var(--text-secondary);
    overflow-wrap: anywhere;
}

.padm-mail-ok { display: inline-grid; place-items: center; color: var(--success-600); }

.padm-mail-no {
    font-size: var(--fs-2xs);
    color: var(--text-tertiary);
    white-space: nowrap;
}

/* The roles a user holds, as chips rather than as a comma list: at
   three roles the list wrapped mid-name and read as one long role. */
.padm-roles { display: flex; flex-wrap: wrap; gap: 4px; }

.padm-role {
    padding: 2px 8px;
    border-radius: var(--r-full);
    background: var(--n-100);
    font-size: var(--fs-2xs);
    font-weight: var(--fw-medium);
    color: var(--n-700);
    white-space: nowrap;
}

.padm-none { color: var(--text-tertiary); }

/* Under the status pill: when the lock lifts, or how many attempts
   have missed since the last good one. */
.padm-stat-sub {
    margin: 3px 0 0;
    font-size: var(--fs-2xs);
    line-height: var(--lh-snug);
    color: var(--text-tertiary);
}

.padm-stat-sub.is-warn { color: var(--warning-700); }

.padm-when { font-size: var(--fs-xs); color: var(--text-secondary); font-variant-numeric: tabular-nums; }
.padm-when-sub { margin: 1px 0 0; font-size: var(--fs-2xs); color: var(--text-tertiary); font-family: var(--font-mono); }

/* ---------- Row actions ----------
   Six shared icon buttons in one cluster rather than six squares
   drawn by hand. Tight enough to read as one control group, and
   nowrap so the last one cannot fall onto a second line. */
.padm-acts {
    display: inline-flex;
    align-items: center;
    gap: 2px;
    flex-wrap: nowrap;
}

/* Delete carries its colour on the glyph. A red fill on a 28px square
   in every row would make the table look like a list of problems. */
.btn.padm-del { color: var(--danger-600); }
.btn.padm-del:hover:not(:disabled) { background: var(--danger-50); color: var(--danger-700); }

/* ---------- Footer ---------- */

.padm-nav { margin-inline-start: auto; display: flex; align-items: center; gap: 10px; }

.padm-per { display: inline-flex; align-items: center; gap: 6px; }

.padm-per select {
    width: 66px;
    height: var(--h-sm);
    padding-inline: 8px 26px;
    font-size: var(--fs-xs);
    background-position: right 6px center;
}

.padm-page {
    min-width: 54px;
    text-align: center;
    font-family: var(--font-mono);
    font-variant-numeric: tabular-nums;
    font-size: var(--fs-xs);
    color: var(--text-secondary);
}

/* ---------- The drawers ---------- */

/* Who the form is about, above the fields that change them. The
   header already carries the name; this carries the state, which is
   what an admin opens the drawer to check before editing. */
.padm-drw-id {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 12px 14px;
    border: 1px solid var(--border-subtle);
    border-radius: var(--r-lg);
    background: var(--surface-sunken);
}

.padm-drw-id-txt { min-width: 0; }
.padm-drw-id-pills { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }

.padm-drw-id-sub {
    margin: 5px 0 0;
    font-size: var(--fs-xs);
    line-height: var(--lh-snug);
    color: var(--text-tertiary);
}

/* A label that carries a control of its own - "Mark verified" beside
   Email. Baseline rather than centre: the two are text of different
   sizes and centring leaves the label sitting low. */
.padm-lab-row {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 8px;
}

/* Small enough to sit on a label line without becoming the loudest
   thing on it. */
.padm-mini {
    padding: 1px 8px;
    border: 1px solid var(--p-200);
    border-radius: var(--r-full);
    background: var(--p-50);
    font-family: inherit;
    font-size: var(--fs-2xs);
    font-weight: var(--fw-medium);
    color: var(--p-700);
    cursor: pointer;
    transition: background-color var(--dur-fast) var(--ease-out);
}

.padm-mini:hover { background: var(--p-100); }
.padm-mini:focus-visible { outline: none; box-shadow: var(--shadow-focus); }

.padm-verified {
    padding: 1px 8px;
    border-radius: var(--r-full);
    background: var(--success-50);
    font-size: var(--fs-2xs);
    font-weight: var(--fw-medium);
    color: var(--success-700);
    text-transform: none;
    letter-spacing: 0;
}

/* The strength read-out under a password field. .meter is the shared
   bar; only the line it sits on is here. */
.padm-pwd { display: flex; align-items: center; gap: 10px; margin-block-start: 8px; }

.padm-pwd-lab {
    min-width: 46px;
    text-align: end;
    font-size: var(--fs-2xs);
    font-weight: var(--fw-semibold);
}

/* The role checkboxes. Capped and scrolling, because a platform with
   thirty roles must not push the save footer off a drawer. */
.padm-rolebox {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
    gap: 6px 12px;
    max-height: 168px;
    padding: 10px 12px;
    border: 1px solid var(--border-default);
    border-radius: var(--r-md);
    overflow-y: auto;
    overscroll-behavior: contain;
}

/* The account switches, grouped on a tinted panel: they are three
   decisions about the same thing, and loose in the column they read
   as three unrelated fields. */
.padm-switches {
    display: flex;
    flex-direction: column;
    gap: 8px;
    padding: 12px 14px;
    border-radius: var(--r-lg);
    background: var(--surface-sunken);
}

.padm-check {
    display: flex;
    align-items: flex-start;
    gap: 8px;
    font-size: var(--fs-md);
    color: var(--text-primary);
    cursor: pointer;
}

.padm-check input { margin-block-start: 2px; cursor: pointer; flex-shrink: 0; }
.padm-check-sub { display: block; margin-block-start: 1px; font-size: var(--fs-xs); color: var(--text-tertiary); }

/* The delete drawer has no fields - just the sentence and what it
   means. Set as reading text rather than as a form. */
.padm-say { margin: 0; font-size: var(--fs-md); line-height: var(--lh-normal); color: var(--text-primary); }
.padm-say-sub { margin: 0; font-size: var(--fs-sm); line-height: var(--lh-normal); color: var(--text-secondary); }

/* ---------- Narrow ---------- */

@media (max-width: 720px) {
    /* The search stops sharing a line with the chips before either
       is squeezed to nothing. The table itself does not drop a
       column at any width - every one of its eight is either a sort
       control or the only place a fact appears, so it scrolls
       inside .tbl-wrap instead. */
    .padm-find { flex: 1 1 100%; }
    .padm-nav { width: 100%; margin-inline-start: 0; justify-content: space-between; }
}


/* ============================================================
   39. Super Admin: Organization (org-*)

   /admin/settings/org - the Jork legal entity itself: the name on
   a contract, the account an invoice is paid into, the signature
   at the foot of an offer letter. Eleven cards, ninety-odd fields
   and five repeating lists, and it was the longest section of the
   hub still drawn entirely in style attributes: every label
   carried the same six declarations from a const string, every
   input the same five, and three of the lists built a record out
   of a bordered div and a fixed five-column grid.

   A style attribute cannot express a hover, a focus ring or a
   breakpoint, so this screen had none of the three. The inputs did
   not respond to the pointer and showed nothing when tabbed to -
   on a form where the field being typed into is the only thing
   telling you where you are. The grids were pinned at three, four
   and five columns inside a working column already 244px narrower
   than the page, so on a laptop the bank block put IBAN, SWIFT and
   a sort code into three 90px boxes.

   What changed, and why:

     - A band opens the section: the logo the letterhead will use,
       the legal name, and the facts the rest of the form is
       qualified by - entity type, country, currency, timezone - as
       chips. It is the shape .prof-hero has one section up, so the
       two "who is this" screens in the hub read alike, and it
       answers "am I looking at Jork or at a tenant" before the
       first field rather than after the ninetieth.
     - Every grid becomes .fld-grid: auto-fit, so the form reflows
       on its own instead of at a column count each card picked by
       hand. The fields themselves are .fld / .input, which is
       where the hover and the focus ring come from.
     - Addresses, banks and signatories become numbered records
       with a head - what this one is, whether it is the default,
       and the one control that removes it. A list of four
       addresses was previously four identical unlabelled blocks.
     - The two tables keep their shape but move onto the shared
       .tbl inside .tbl-wrap, so at a narrow width they scroll
       rather than crushing six editable columns.
     - The six image slots get a dropzone that reacts: a hover, a
       ring while the file input inside it has focus, and a
       chequered tile behind the preview so a white logo on a white
       card is still visible.

   What is here:
     .org-hero    the identity band the section opens with
     .org-mark    the logo tile on it, monogram when there is none
     .org-fact    one qualifying fact as a chip
     .org-sub     a heading for a run of fields inside one card
     .org-rec     one record in a repeating list, with .org-rec-head
     .org-add     the row that ends a repeating list
     .org-img     one image slot: dropzone, preview, what it holds
     .org-swatch  a colour, as a well beside its hex
     .org-tbl     the density this section's two tables are edited at

   Everything else is shared: .fld, .input, .tbl, .note, .card,
   QCard, QPill, QButton, QSaveBar. Nothing below restyles them.
   ============================================================ */

/* ---------- The identity band ---------- */

.org-hero {
    position: relative;
    overflow: hidden;
    display: flex;
    align-items: flex-start;
    gap: 16px;
    flex-wrap: wrap;
    padding: 18px 20px;
    background: var(--surface-raised);
    border: 1px solid var(--border-default);
    border-radius: var(--r-lg);
    box-shadow: var(--shadow-xs);
}

/* The same wash .prof-hero uses, for the same reason: the band is
   the head of the section, not a twelfth card. */
.org-hero::before {
    content: "";
    position: absolute;
    inset: 0 0 auto;
    height: 72px;
    background: linear-gradient(180deg, var(--p-50), rgba(238, 242, 255, 0));
    pointer-events: none;
}

.org-hero > * { position: relative; }

/* A logo is any shape and often white on white, so it gets a tile
   with a hairline rather than sitting loose on the wash. */
.org-mark {
    display: grid;
    place-items: center;
    width: 64px;
    height: 64px;
    flex-shrink: 0;
    padding: 8px;
    border: 1px solid var(--border-subtle);
    border-radius: var(--r-lg);
    background: var(--surface-raised);
    box-shadow: 0 0 0 3px var(--surface-raised), var(--shadow-sm);
}

.org-mark img { max-width: 100%; max-height: 100%; object-fit: contain; }

/* No logo yet: the initials, so the tile is still recognisably the
   organisation rather than an empty square waiting for an upload. */
.org-mark-txt {
    font-size: var(--fs-2xl);
    font-weight: var(--fw-bold);
    letter-spacing: -0.02em;
    color: var(--p-700);
}

.org-who { flex: 1 1 320px; min-width: 0; }

.org-name {
    margin: 0;
    font-size: var(--fs-2xl);
    font-weight: var(--fw-bold);
    line-height: var(--lh-tight);
    letter-spacing: -0.02em;
    color: var(--text-primary);
}

.org-legal {
    margin: 3px 0 0;
    font-size: var(--fs-md);
    line-height: var(--lh-snug);
    color: var(--text-secondary);
}

.org-facts {
    display: flex;
    align-items: center;
    gap: 6px;
    flex-wrap: wrap;
    margin-block-start: 10px;
}

/* Read-only, so it is a chip and not a disabled input: nothing here
   is edited from the band, only summarised by it. */
.org-fact {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    max-width: 100%;
    padding: 3px 9px;
    border: 1px solid var(--border-subtle);
    border-radius: var(--r-full);
    background: var(--surface-sunken);
    font-size: var(--fs-xs);
    color: var(--text-secondary);
}

.org-fact > svg { flex-shrink: 0; color: var(--text-tertiary); }
.org-fact b { font-weight: var(--fw-semibold); color: var(--text-primary); }

/* ---------- A run of fields inside a card ----------
   Signatories has two: the one used by default, and the rest. Set
   as a rule with a label on it rather than as a bold line, so the
   break is visible without being read. */

.org-sub {
    display: flex;
    align-items: center;
    gap: 10px;
    margin: 0 0 10px;
    font-size: var(--fs-2xs);
    font-weight: var(--fw-semibold);
    letter-spacing: 0.05em;
    text-transform: uppercase;
    color: var(--text-tertiary);
}

.org-sub::after {
    content: "";
    flex: 1;
    height: 1px;
    background: var(--border-subtle);
}

/* Distance between the block a sub-head closes and the next one. */
.org-sub-gap { margin-block-start: 20px; }

/* ---------- Repeating records ---------- */

.org-recs {
    display: flex;
    flex-direction: column;
    gap: 10px;
    margin-block-end: 12px;
}

.org-rec {
    border: 1px solid var(--border-subtle);
    border-radius: var(--r-lg);
    background: var(--surface-raised);
    transition: border-color var(--dur-fast) var(--ease-out);
}

/* Not a hover state on a card that is not clickable - only enough
   to say which of five blocks the pointer is inside. */
.org-rec:hover { border-color: var(--border-default); }
.org-rec:focus-within { border-color: var(--border-strong); }

.org-rec-head {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 9px 12px;
    border-block-end: 1px solid var(--border-subtle);
    background: var(--n-25);
    border-start-start-radius: var(--r-lg);
    border-start-end-radius: var(--r-lg);
}

/* The ordinal, so a list of four unlabelled addresses can still be
   talked about. Tabular so the column does not shift at ten. */
.org-rec-n {
    display: grid;
    place-items: center;
    width: 20px;
    height: 20px;
    flex-shrink: 0;
    border-radius: var(--r-full);
    background: var(--surface-active);
    font-size: var(--fs-2xs);
    font-weight: var(--fw-semibold);
    font-variant-numeric: tabular-nums;
    color: var(--text-secondary);
}

.org-rec-title {
    min-width: 0;
    font-size: var(--fs-md);
    font-weight: var(--fw-semibold);
    color: var(--text-primary);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.org-rec-acts { margin-inline-start: auto; display: flex; align-items: center; gap: 6px; }

.org-rec-body { padding: 14px; }

/* The row a list ends on. Separated from the records above it so
   the button does not read as belonging to the last one. */
.org-add {
    display: flex;
    align-items: center;
    gap: 10px;
    flex-wrap: wrap;
}

.org-add-hint { font-size: var(--fs-xs); color: var(--text-tertiary); }

/* Nothing in the list yet. Inside a card that already has padding
   and a border, so no frame of its own beyond the dashed hint. */
.org-none {
    margin: 0 0 12px;
    padding: 14px;
    border: 1px dashed var(--border-default);
    border-radius: var(--r-md);
    font-size: var(--fs-sm);
    line-height: 1.5;
    color: var(--text-tertiary);
}

/* ---------- The two edited tables ----------
   .tbl is sized for reading a row; these are typed into, so the
   cells drop the vertical padding a 36px control brings its own. */

.org-tbl td { padding: 7px 12px; vertical-align: middle; }
.org-tbl thead th { white-space: nowrap; }
.org-tbl .org-tbl-mid { text-align: center; }
/* width:1% plus nowrap is the shrink-to-fit idiom: the remove column
   takes the button and nothing more, and the six typed columns keep
   the rest of the table. */
.org-tbl .org-tbl-end { text-align: end; width: 1%; white-space: nowrap; }

/* Under a table, on the card footer line rather than floating in a
   padded strip of its own. */
.org-foot { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }

/* ---------- Image slots ---------- */

.org-img {
    display: flex;
    align-items: center;
    gap: 12px;
    min-height: 76px;
    padding: 10px;
    border: 1px dashed var(--border-default);
    border-radius: var(--r-lg);
    background: var(--surface-raised);
    transition: border-color var(--dur-fast) var(--ease-out),
                background-color var(--dur-fast) var(--ease-out);
}

.org-img:hover { border-color: var(--border-strong); background: var(--n-25); }

/* The ring belongs to the zone, not to the hidden file input: the
   label wrapping it is what is actually reachable by keyboard. */
.org-img:focus-within {
    border-color: var(--border-focus);
    border-style: solid;
    box-shadow: var(--shadow-focus);
}

/* Chequered, because half these slots hold a transparent PNG and a
   white mark on a white tile is an empty box. */
.org-img-prev {
    display: grid;
    place-items: center;
    width: 52px;
    height: 52px;
    flex-shrink: 0;
    padding: 4px;
    overflow: hidden;
    border: 1px solid var(--border-subtle);
    border-radius: var(--r-md);
    background-color: var(--n-0);
    background-image:
        linear-gradient(45deg, var(--n-100) 25%, transparent 25%),
        linear-gradient(-45deg, var(--n-100) 25%, transparent 25%),
        linear-gradient(45deg, transparent 75%, var(--n-100) 75%),
        linear-gradient(-45deg, transparent 75%, var(--n-100) 75%);
    background-size: 10px 10px;
    background-position: 0 0, 0 5px, 5px -5px, -5px 0;
}

.org-img-prev img { max-width: 100%; max-height: 100%; object-fit: contain; }

.org-img-none {
    display: grid;
    place-items: center;
    width: 52px;
    height: 52px;
    flex-shrink: 0;
    border: 1px solid var(--border-subtle);
    border-radius: var(--r-md);
    background: var(--surface-sunken);
    color: var(--text-disabled);
}

.org-img-body { flex: 1; min-width: 0; }

.org-img-acts { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }

/* The upload control has to be a <label> - InputFile must be the thing
   actually clicked - and a label does not get the pointer cursor a
   <button> does. Everything else about it is .btn. */
.org-img-acts label.btn { cursor: pointer; }

/* The path, or what the slot will take. Breaks anywhere: an upload
   URL has no spaces and would otherwise widen the whole grid. */
.org-img-say {
    margin-block-start: 4px;
    font-size: var(--fs-2xs);
    line-height: 1.45;
    color: var(--text-tertiary);
    overflow-wrap: anywhere;
}

/* ---------- Colour, and the switches ---------- */

.org-swatch { display: flex; align-items: center; gap: 8px; }

/* A native colour input with its chrome removed, so it reads as the
   value rather than as a second control beside the hex. */
.org-swatch-well {
    width: var(--h-lg);
    height: var(--h-lg);
    flex-shrink: 0;
    padding: 0;
    border: 1px solid var(--border-default);
    border-radius: var(--r-md);
    background: var(--surface-raised);
    cursor: pointer;
    transition: border-color var(--dur-fast) var(--ease-out);
}

.org-swatch-well:hover { border-color: var(--border-strong); }
.org-swatch-well:focus-visible { outline: none; border-color: var(--border-focus); box-shadow: var(--shadow-focus); }
.org-swatch-well::-webkit-color-swatch-wrapper { padding: 3px; }
.org-swatch-well::-webkit-color-swatch { border: none; border-radius: var(--r-xs); }
.org-swatch-well::-moz-color-swatch { border: none; border-radius: var(--r-xs); }

/* A checkbox in a field grid has no label row above it, so it
   aligns to the bottom of the row its neighbours end on rather
   than to the top of their labels. */
.org-check {
    display: flex;
    align-items: center;
    gap: 8px;
    min-height: var(--h-lg);
    margin-block-start: auto;
    font-size: var(--fs-md);
    color: var(--text-primary);
    cursor: pointer;
}

.org-check input { flex-shrink: 0; cursor: pointer; }
.org-check-sub { display: block; font-size: var(--fs-xs); color: var(--text-tertiary); }

/* ---------- Narrow ---------- */

@media (max-width: 720px) {
    .org-hero { padding: 16px; gap: 14px; }
    .org-mark { width: 52px; height: 52px; }
    .org-name { font-size: var(--fs-xl); }

    /* The record head stops being one line: the title of a bank
       account is longer than the width left beside a remove button
       at this size. */
    .org-rec-head { flex-wrap: wrap; }
    .org-rec-acts { margin-inline-start: 0; }

    /* An image slot at this width is a stack, not a row - a 52px
       tile plus two buttons plus a path does not fit on one line. */
    .org-img { align-items: flex-start; flex-direction: column; }
}


/* ============================================================
   40. Super Admin: Tenants (tnt-*)

   /admin/tenants - the platform's list of customers, and the form
   that creates or edits one of them.

   It was the last console list still drawn entirely in style
   attributes: a six-column KPI band pinned at repeat(6, 1fr), a row
   of filter links that rebuilt the shared toggle chip out of
   ternaries and CSS variables spliced into strings, a ten-column
   table whose every cell repeated its own padding, and a centred
   520px modal for the onboarding form.

   A style attribute cannot express a hover, a focus ring or a
   breakpoint, so none of it had any: the rows gave no sign they
   could be clicked, the filter links had no focus state, and the
   six KPI tiles kept a sixth of the page each at every width - on a
   1280px console that is 190px per tile, narrower than the labels
   sitting in them.

   What changed, and why:

     - The KPI band is auto-fit. Six tiles is the count today; the
       band should reflow rather than crush when it is five or seven,
       or when the window is a laptop.
     - The filter links become .chip-tog, the shared toggle chip the
       rest of the console uses for the same idea. They stay anchors
       - the filter is in the query string, so each one is a real
       location and has to remain right-clickable - and pick up only
       the chip's paint.
     - Search and filters move into a .tbar inside the card, where
       every other list page in the product keeps them, rather than
       floating unattached above it.
     - The "Trend" column is gone. It rendered a fixed grey rectangle
       for every tenant - there is no trend series behind it, and a
       chart-shaped element with no data in it reads as a chart.
     - The onboarding modal becomes a QDrawer ("20." above), and the
       same drawer now edits an existing tenant. A form with seven
       fields wants the height of the screen and a footer that does
       not scroll, and the reader keeps the row they opened it from
       in view beside it.

   Toolbar, chips, table, footer, fields and drawer chrome are all
   shared primitives (.tbar, .chip-tog, .tbl, .card-foot, .fld-*,
   .drw-*); only what this screen has and no other does is below.

   What is here:
     .tnt-kpis    the KPI band above the list
     .tnt-chip    the filter chip, as a link rather than a button
     .tnt-find    the search box's share of the toolbar
     .tnt-who     the tenant cell: mark, name, domain
     .tnt-num     the figures, on one set of digits
                  (the row itself is the shared .tbl-row-link)
     .tnt-acts    the per-row action cluster
     .tnt-drw-*   the parts of the drawer that are not .fld
   ============================================================ */

/* ---------- The band ----------
   auto-fit rather than a fixed six: the same band sits beside a
   244px sidebar and has to survive a laptop, where a sixth of the
   remaining width is narrower than the labels in it. */

.tnt-kpis {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
    gap: 10px;
    margin-block-end: 14px;
}

/* ---------- Toolbar ---------- */

/* The search is the toolbar's widest control; below 720px it takes
   the row to itself rather than squeezing the chips onto a second
   line one at a time. */
.tnt-find { flex: 0 1 300px; min-width: 200px; }

/* A filter chip that is a link, because the filter it sets lives in
   the query string: each chip is a real location, so it has to stay
   right-clickable and openable in a new tab. Everything else about
   it is .chip-tog - this only strips the underline an anchor brings
   with it. */
.tnt-chip,
.tnt-chip:hover { text-decoration: none; }

/* ---------- Rows ----------
   The whole row navigates, and .tbl-row-link is the shared shape that
   says so - cursor, transition and hover wash. Nothing about this
   table needs its own. */

/* The tenant cell: the mark, the legal name, and the domain the
   account is keyed on. The domain sits under the name rather than in
   a column of its own - it identifies the row, it is not something
   the table is sorted or filtered by. */
.tnt-who {
    display: flex;
    align-items: center;
    gap: 10px;
    min-width: 0;
    color: inherit;
    text-decoration: none;
}

.tnt-who-txt { min-width: 0; }

.tnt-who-name {
    font-size: var(--fs-md);
    font-weight: var(--fw-semibold);
    letter-spacing: -0.005em;
    color: var(--text-primary);
}

/* Mono, because a domain is an identifier - read character by
   character rather than as a word. */
.tnt-who-sub {
    margin: 2px 0 0;
    font-family: var(--font-mono);
    font-size: var(--fs-2xs);
    color: var(--text-tertiary);
}

.tnt-plan { color: var(--text-secondary); }
.tnt-region { font-size: var(--fs-sm); color: var(--text-secondary); }

/* Every figure in the table on one set of digits. tabular-nums is
   what stops a column of seats jittering by a pixel per row. */
.tnt-num {
    font-family: var(--font-mono);
    font-variant-numeric: tabular-nums;
    color: var(--text-primary);
}

.tnt-num-strong { font-weight: var(--fw-medium); }
.tnt-num-soft { color: var(--text-tertiary); }

/* ---------- Row actions ----------
   Two shared buttons in one cluster. nowrap so the labelled one
   cannot fall onto a second line and make the row 20px taller than
   its neighbours. */
.tnt-acts {
    display: inline-flex;
    align-items: center;
    gap: 2px;
    flex-wrap: nowrap;
}

/* The row is a link to the tenant, so a control sitting inside it
   must not inherit the underline that comes with one. */
.tnt-acts a { text-decoration: none; }

/* ---------- The drawer ---------- */

/* Which tenant the form is about, above the fields that change it.
   The header carries the name; this carries the state, which is what
   an admin checks before editing. Only on the edit drawer - there is
   nothing to state about a tenant that does not exist yet. */
.tnt-drw-id {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 12px 14px;
    border: 1px solid var(--border-subtle);
    border-radius: var(--r-lg);
    background: var(--surface-sunken);
}

.tnt-drw-id-txt { min-width: 0; }

.tnt-drw-id-name {
    font-size: var(--fs-md);
    font-weight: var(--fw-semibold);
    color: var(--text-primary);
}

.tnt-drw-id-sub {
    margin: 5px 0 0;
    display: flex;
    align-items: center;
    gap: 8px;
    flex-wrap: wrap;
    font-family: var(--font-mono);
    font-size: var(--fs-2xs);
    color: var(--text-tertiary);
}

/* Seven fields in one undifferentiated column is a form you have to
   read to the bottom of before you know what it asks. Grouped, it is
   three short questions: who they are, how they are classified, what
   they start with. */
.tnt-grp { display: flex; flex-direction: column; gap: 10px; }

.tnt-grp-h {
    margin: 0;
    font-size: var(--fs-2xs);
    font-weight: var(--fw-bold);
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--text-tertiary);
}

/* A rule that carries the heading, rather than a heading floating
   over the fields under it. */
.tnt-grp + .tnt-grp {
    padding-block-start: 14px;
    border-block-start: 1px solid var(--border-subtle);
}

/* ---------- Narrow ---------- */

@media (max-width: 720px) {
    /* The search stops sharing a line with the chips before either is
       squeezed to nothing. The table drops no column at any width -
       each one is the only place its fact appears - so it scrolls
       inside .tbl-wrap instead. */
    .tnt-find { flex: 1 1 100%; }
}


/* ============================================================
   41. Super Admin: Question authoring (aqe-*)

   /admin/assess/question/{id} - the form that writes one item into
   the platform question bank, and the last console editor still
   drawn entirely in style attributes.

   What was there: a title row hand-built from a flex div and three
   inline spans, eight type buttons whose selected state was a
   border colour spliced into a string, six rail cards each opening
   with a title that only repeated what the field under it said, and
   the three saves pinned to the top of a form four screens long.

   A style attribute cannot express a hover, a focus ring or a
   breakpoint, so none of it had any: the type tiles gave no sign
   they were pressable, the industry and language chips had no focus
   state, and the grid held 1.6fr/1fr at every width - on a 1280px
   console that leaves the rail 380px to fit a four-column band
   picker into.

   What changed, and why:

     - The header is PageHeader, pinned. The form runs past four
       screens and the crumb, the code and the status are the only
       thing saying which of nine hundred items is open; scrolled
       away, the page stops identifying itself.
     - The three saves ride in that pinned bar. They were at the top
       before too, but on a header that scrolled away, so the way to
       commit an edit was gone the moment you started typing.
     - A save error goes in the bar beside them (.aqe-err). The
       commit can be pressed from any scroll position, so a banner
       drawn at the top of the content would be off screen exactly
       when it was raised.
     - The header pins to the shell's one scroll container -
       AppShell's <main>. Nothing on this page takes an overflow of
       its own, so the reader has one scrollbar rather than a page
       inside a page.
     - Six rail cards become three. Difficulty, Bloom, time, weight
       and core placement are one answer about scoring; industry,
       designation, band and skills are one answer about who the
       item is for.
     - The type tiles and the band picker are auto-fit grids, so
       both reflow rather than crush when the window is a laptop or
       a ninth type is added.
     - The correct option is tinted, not merely ticked. On a
       six-option question the answer key has to be readable at a
       glance rather than found.

   Header, cards, fields, chips and skeletons are all shared
   primitives (.pg-head, .card, .fld-*, .chip-tog, .skel); only what
   this screen has and no other does is below.

   What is here:
     .aqe          the two-column working area
     .aqe-life     the attempts/correct figure beside the title
     .aqe-err      a refused save, in the header beside the commit
     .aqe-types    the type picker, and .aqe-type one tile of it
     .aqe-count    the live character count in the stem card head
     .aqe-opt      one option row: key, pick, text, explanation
     .aqe-bands    the experience-band picker
     .aqe-tag      a skill tag and the button that drops it
     .aqe-lang     a language chip, on identifier type
     .aqe-grp      a labelled group inside a rail card
     .aqe-rule     a group carried by a rule rather than floating
   ============================================================ */

/* ---------- The working area ----------
   1.55fr/1fr rather than the 1.6fr/1fr this was, and collapsing to
   one column at 1180px rather than holding the pair at every width.

   minmax(0, ...) on both tracks, for the reason .pg-rail gives at
   "Working surface" above: a track with a floor cannot shrink past
   it, so a narrow enough column pushes the grid wider than the page
   and the whole thing scrolls sideways. The rail had a 320px floor
   for the band picker's sake; the picker is auto-fit and reflows on
   its own, and above the 1180px breakpoint the rail never resolves
   below 350px anyway - so the floor bought nothing and risked a
   second scrollbar. */

.aqe {
    display: grid;
    grid-template-columns: minmax(0, 1.55fr) minmax(0, 1fr);
    gap: 14px;
    align-items: start;
}

.aqe-main,
.aqe-side {
    display: flex;
    flex-direction: column;
    gap: 14px;
    min-width: 0;
}

/* ---------- A refused save ----------
   In the pinned header, immediately left of the button that was
   refused, because that button can be pressed from any scroll
   position and a banner in the content would not be on screen.

   It takes the slack in .pg-actions and gives it back: flex 0 1 auto
   with a max-width, so a long message wraps to two lines inside the
   bar rather than pushing Publish off the end of it. */
.aqe-err {
    display: inline-flex;
    align-items: flex-start;
    gap: 6px;
    flex: 0 1 auto;
    min-width: 0;
    max-width: 340px;
    margin-inline-end: 2px;
    padding: 5px 9px;
    border: 1px solid var(--danger-200);
    border-radius: var(--r-md);
    background: var(--danger-50);
    color: var(--danger-700);
    font-size: var(--fs-xs);
    font-weight: var(--fw-medium);
    line-height: 1.4;
}

/* The glyph keeps its size while the sentence beside it wraps. */
.aqe-err > svg { flex-shrink: 0; margin-block-start: 1px; }

.aqe-err-txt { min-width: 0; }

/* A fatal load failure, in the card that replaces the page. */
.aqe-fatal {
    margin-block-end: 12px;
    font-size: var(--fs-sm);
    color: var(--danger-700);
}

/* ---------- Identity ----------
   What the question has actually done, beside the name of it. Mono
   and tabular so the figure does not shift by a pixel between two
   questions read one after the other. */
.aqe-life {
    font-size: var(--fs-xs);
    color: var(--text-tertiary);
    white-space: nowrap;
}

/* ---------- Type picker ----------
   auto-fit at 168px: four tiles across a wide console, two on a
   laptop, one on a phone - without a breakpoint per count. */

.aqe-types {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(168px, 1fr));
    gap: 8px;
}

.aqe-type {
    position: relative;
    display: flex;
    align-items: flex-start;
    gap: 9px;
    padding: 10px 11px;
    text-align: start;
    background: var(--surface-raised);
    border: 1px solid var(--border-default);
    border-radius: var(--r-lg);
    cursor: pointer;
    transition: border-color var(--dur-fast), background var(--dur-fast), box-shadow var(--dur-fast);
}

.aqe-type:hover {
    border-color: var(--border-strong);
    background: var(--surface-hover);
}

.aqe-type:focus-visible { outline: none; box-shadow: var(--shadow-focus); }

/* Selected is a filled tile, not a 1.5px border that only reads at
   the edge nearest the pointer. */
.aqe-type.on {
    background: var(--p-50);
    border-color: var(--p-700);
}

.aqe-type-mark {
    display: grid;
    place-items: center;
    flex-shrink: 0;
    width: 26px;
    height: 26px;
    border-radius: var(--r-md);
    background: var(--n-100);
    color: var(--text-tertiary);
    transition: background var(--dur-fast), color var(--dur-fast);
}

.aqe-type.on .aqe-type-mark {
    background: var(--p-700);
    color: var(--text-inverse);
}

.aqe-type-txt { min-width: 0; }

.aqe-type-name {
    display: block;
    font-size: var(--fs-md);
    font-weight: var(--fw-semibold);
    color: var(--text-primary);
}

.aqe-type.on .aqe-type-name { color: var(--p-800); }

.aqe-type-sub {
    display: block;
    margin-block-start: 2px;
    font-size: var(--fs-2xs);
    line-height: 1.4;
    color: var(--text-tertiary);
}

/* The tick is confirmation, not the only signal - the fill and the
   marked icon carry it too, so colour alone is never the message. */
.aqe-type-on {
    position: absolute;
    inset-block-start: 7px;
    inset-inline-end: 7px;
    display: grid;
    place-items: center;
    width: 15px;
    height: 15px;
    border-radius: var(--r-full);
    background: var(--p-700);
    color: var(--text-inverse);
}

/* ---------- Stem ---------- */

/* The count sits in the card head, where the card already has a
   right slot, rather than as a line under the box pushing the next
   card down. */
.aqe-count {
    font-size: var(--fs-xs);
    color: var(--text-tertiary);
}

.aqe-stem { font-size: var(--fs-base); line-height: var(--lh-normal); }
.aqe-accept { font-size: var(--fs-md); }

/* ---------- Options ---------- */

.aqe-opts { display: flex; flex-direction: column; gap: 8px; }

/* Three fixed columns and a body that takes the rest: the key and
   the pick have to line up down the list however long an option
   text wraps. */
.aqe-opt {
    display: grid;
    grid-template-columns: 30px 28px minmax(0, 1fr);
    gap: 9px;
    align-items: start;
    padding: 9px;
    border: 1px solid var(--border-subtle);
    border-radius: var(--r-lg);
    background: var(--surface-raised);
    transition: border-color var(--dur-fast), background var(--dur-fast);
}

.aqe-opt:hover { border-color: var(--border-default); }

/* The answer key, readable as a block rather than found by reading
   every radio in the column. */
.aqe-opt.on {
    background: var(--success-50);
    border-color: var(--success-500);
}

.aqe-opt-key {
    display: grid;
    place-items: center;
    height: 30px;
    border-radius: var(--r-md);
    background: var(--n-75);
    font-size: var(--fs-md);
    font-weight: var(--fw-bold);
    color: var(--text-tertiary);
}

.aqe-opt.on .aqe-opt-key {
    background: var(--success-600);
    color: var(--text-inverse);
}

/* The whole cell is the hit area, not the 16px control inside it. */
.aqe-opt-pick {
    display: grid;
    place-items: center;
    height: 30px;
    cursor: pointer;
}

.aqe-opt-body { display: flex; flex-direction: column; gap: 5px; min-width: 0; }

.aqe-opt-text {
    min-height: 30px;
    padding: 6px 10px;
    font-size: var(--fs-md);
}

.aqe-opt-why { color: var(--text-secondary); font-size: var(--fs-xs); }

/* Add and shuffle read as one row: the thing that grows the list,
   and the rule that applies to all of it. */
.aqe-opt-foot {
    display: flex;
    align-items: center;
    gap: 10px;
    flex-wrap: wrap;
    margin-block-start: 10px;
}

.aqe-opt-foot .aqe-check { margin-inline-start: auto; }

/* ---------- Groups and rules ---------- */

/* A labelled group inside a rail card. The gap is the label gap, so
   a chip row and a field stack open the same distance under theirs. */
.aqe-grp { display: flex; flex-direction: column; gap: 6px; }
.aqe-grp + .aqe-grp { margin-block-start: 14px; }

/* A group that a rule carries, rather than a heading floating over
   the controls below it. */
.aqe-rule {
    margin-block-start: 14px;
    padding-block-start: 12px;
    border-block-start: 1px solid var(--border-subtle);
}

.aqe-stack { margin-block-start: 10px; }

/* A sentence about the control above it, at the size every other
   hint in the product uses. */
.aqe-note {
    margin: 6px 0 0;
    font-size: var(--fs-xs);
    line-height: 1.55;
    color: var(--text-tertiary);
}

/* A checkbox and its sentence on one baseline, the whole of it
   clickable. */
.aqe-check {
    display: flex;
    align-items: center;
    gap: 8px;
    font-size: var(--fs-sm);
    color: var(--text-secondary);
    cursor: pointer;
}

.aqe-check input { flex-shrink: 0; }

/* ---------- Bands ----------
   auto-fit rather than repeat(4, 1fr): four bands is what there is,
   but a 320px rail cannot hold four labelled tiles and should give
   two rows of two instead of four unreadable columns. */

.aqe-bands {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(74px, 1fr));
    gap: 6px;
}

.aqe-band {
    padding: 7px 6px;
    text-align: center;
    background: var(--surface-raised);
    border: 1px solid var(--border-default);
    border-radius: var(--r-md);
    cursor: pointer;
    transition: border-color var(--dur-fast), background var(--dur-fast);
}

.aqe-band:hover { border-color: var(--border-strong); background: var(--surface-hover); }
.aqe-band:focus-visible { outline: none; box-shadow: var(--shadow-focus); }

.aqe-band.on { background: var(--p-50); border-color: var(--p-700); }

.aqe-band-k {
    display: block;
    font-size: var(--fs-xs);
    font-weight: var(--fw-bold);
    color: var(--text-tertiary);
}

.aqe-band.on .aqe-band-k { color: var(--p-700); }

.aqe-band-v {
    display: block;
    font-size: var(--fs-xs);
    font-weight: var(--fw-medium);
    color: var(--text-primary);
}

.aqe-band.on .aqe-band-v { color: var(--p-800); font-weight: var(--fw-semibold); }

/* ---------- Skill tags ---------- */

.aqe-tags { display: flex; flex-wrap: wrap; gap: 4px; }

.aqe-tag {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    height: var(--h-xs);
    padding: 0 6px 0 10px;
    border-radius: var(--r-full);
    background: var(--gold-100);
    color: var(--gold-800);
    font-size: var(--fs-xs);
    font-weight: var(--fw-semibold);
}

.aqe-tag-x {
    display: grid;
    place-items: center;
    width: 15px;
    height: 15px;
    border-radius: var(--r-full);
    color: var(--gold-700);
    cursor: pointer;
    transition: background var(--dur-fast), color var(--dur-fast);
}

.aqe-tag-x:hover { background: var(--gold-300); color: var(--gold-900); }
.aqe-tag-x:focus-visible { outline: none; box-shadow: var(--shadow-focus); }

/* ---------- Languages ----------
   A language code is an identifier - read character by character -
   so it takes the mono face the rest of the console gives one. */
.aqe-lang {
    font-family: var(--font-mono);
    font-size: var(--fs-2xs);
    text-transform: uppercase;
    letter-spacing: 0.04em;
}

/* ---------- Numbers ----------
   The four scoring fields want to be two by two in the rail, not
   four across at 60px each; 130px is where a labelled 36px control
   with a spinner stops being usable. */
.aqe-nums { grid-template-columns: repeat(auto-fit, minmax(130px, 1fr)); gap: 12px; }

/* ---------- Narrow ---------- */

@media (max-width: 1180px) {
    /* One column before either is squeezed past its floor. The rail
       follows the form rather than sitting beside a 600px column. */
    .aqe { grid-template-columns: minmax(0, 1fr); }
}

@media (max-width: 560px) {
    /* The shuffle rule stops being pushed to the far end of a row it
       has already wrapped out of. */
    .aqe-opt-foot .aqe-check { margin-inline-start: 0; }
}


/* ---------- The same form, in a drawer (aqd-*) ----------

   The bank's "New question" mounts QuestionEditorForm in a QDrawer over
   the list instead of navigating to the page, so authoring keeps the
   filters and the scroll position it was working through.

   Only the chrome differs, which is why this is one rule. The body is
   .drw-body, which already brings the padding and the one scroll; the
   .aqe grid inside it needs nothing of its own, and its two columns
   collapse on the same 1180px breakpoint as the page's - below which
   the panel is the width of the screen anyway. The three commits ride
   .drw-foot rather than a pinned header, and a refused save goes on
   that bar through .drw-foot > .drw-alert. */

.aqd-mark {
    display: grid;
    place-items: center;
    inline-size: 34px;
    block-size: 34px;
    border-radius: var(--r-lg);
    background: rgba(255, 255, 255, 0.16);
    color: var(--text-inverse);
}

/* The lifecycle on the navy band. A QPill is drawn for white and goes
   muddy on --p-800, so the status is a glass chip with a coloured dot:
   the dot carries the state, the word says it. */
.aqd-status {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    height: var(--h-xs);
    padding: 0 10px;
    border-radius: var(--r-full);
    background: rgba(255, 255, 255, 0.12);
    box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.22);
    color: var(--p-100);
    font-size: var(--fs-xs);
    font-weight: var(--fw-semibold);
    white-space: nowrap;
}

.aqd-status-dot {
    inline-size: 7px;
    block-size: 7px;
    border-radius: var(--r-full);
    background: var(--n-400);
}

.aqd-status.is-live .aqd-status-dot { background: var(--success-500); box-shadow: 0 0 0 3px rgba(34, 197, 94, 0.25); }
.aqd-status.is-review .aqd-status-dot { background: var(--warning-500); }
.aqd-status.is-retired .aqd-status-dot { background: var(--danger-500); }

/* ---------- Edit summary ----------
   The first thing in the body when an existing question is open: which
   item this is, and what it has done in the field. A raised panel on the
   drawer's sunken body, the same weight as the cards under it, with the
   figures on a tabular grid so two questions opened one after the other
   read in the same places. */
.aqd-sum {
    display: flex;
    flex-direction: column;
    gap: 12px;
    padding: 14px 16px;
    border: 1px solid var(--border-subtle);
    border-inline-start: 3px solid var(--gold-500);
    border-radius: var(--r-lg);
    background: var(--surface-raised);
    box-shadow: var(--shadow-xs);
}

.aqd-sum-id {
    display: flex;
    align-items: center;
    gap: 10px;
    flex-wrap: wrap;
    min-width: 0;
}

.aqd-sum-code {
    padding: 2px 8px;
    border-radius: var(--r-md);
    background: var(--p-50);
    color: var(--p-700);
    font-size: var(--fs-sm);
    font-weight: var(--fw-bold);
    letter-spacing: 0.02em;
}

.aqd-sum-tags {
    display: flex;
    align-items: center;
    gap: 6px;
    flex-wrap: wrap;
    min-width: 0;
}

.aqd-sum-desig {
    max-width: 260px;
    font-size: var(--fs-sm);
    font-weight: var(--fw-medium);
    color: var(--text-secondary);
}

.aqd-sum-stats {
    display: grid;
    grid-template-columns: repeat(4, minmax(0, 1fr));
    margin: 0;
    border-block-start: 1px solid var(--border-subtle);
    padding-block-start: 12px;
}

.aqd-stat { min-width: 0; padding-inline: 14px; }
.aqd-stat:first-child { padding-inline-start: 0; }
.aqd-stat + .aqd-stat { border-inline-start: 1px solid var(--border-subtle); }

.aqd-stat dt {
    display: flex;
    align-items: center;
    gap: 5px;
    font-size: var(--fs-2xs);
    font-weight: var(--fw-bold);
    letter-spacing: 0.05em;
    text-transform: uppercase;
    color: var(--text-tertiary);
}

.aqd-stat dd {
    margin: 4px 0 0;
    font-size: var(--fs-lg);
    font-weight: var(--fw-semibold);
    font-variant-numeric: tabular-nums;
    color: var(--text-primary);
}

.aqd-stat dd.is-good { color: var(--success-700); }
.aqd-stat dd.is-warn { color: var(--warning-700); }
.aqd-stat dd.is-bad { color: var(--danger-700); }

.aqd-sum-note {
    display: flex;
    align-items: flex-start;
    gap: 7px;
    margin: 0;
    padding: 8px 10px;
    border-radius: var(--r-md);
    background: var(--warning-50);
    color: var(--warning-700);
    font-size: var(--fs-xs);
    line-height: 1.5;
}

.aqd-sum-note svg { flex-shrink: 0; margin-block-start: 1px; }

/* ---------- Footer state ----------
   Left end of the bar: whether a commit would change anything. Grey and
   quiet when it would not, amber when there is typing to lose. */
.aqd-dirty {
    display: inline-flex;
    align-items: center;
    gap: 7px;
    font-size: var(--fs-xs);
    font-weight: var(--fw-medium);
    color: var(--text-tertiary);
}

.aqd-dirty-dot {
    inline-size: 7px;
    block-size: 7px;
    border-radius: var(--r-full);
    background: var(--n-300);
}

.aqd-dirty.is-dirty { color: var(--warning-700); }
.aqd-dirty.is-dirty .aqd-dirty-dot { background: var(--warning-500); }

@media (max-width: 720px) {
    .aqd-sum-stats { grid-template-columns: repeat(2, minmax(0, 1fr)); row-gap: 12px; }
    .aqd-stat:nth-child(3) { padding-inline-start: 0; border-inline-start: none; }
}

/* ---------- Mapping rule drawer (mrd-*) ----------
   The JD -> assessment mapping editor. It borrows the question drawer's
   mark, status chip and dirty indicator (aqd-*) so the two editors on the
   Assessment Engine read as one family; only what a rule has that a
   question does not lives here. */

.mrd-sub-sep { margin-inline: 6px; opacity: 0.6; }

/* QInput / QTextArea carry font-family:inherit inline, so the mono family
   has to come from the wrapper. */
.mrd-mono { font-family: var(--font-mono); }
.mrd-mono textarea { font-size: var(--fs-sm); }

.mrd-lockline {
    display: flex;
    align-items: center;
    gap: 10px;
    min-block-size: 36px;
    padding: 0 12px;
    border-radius: var(--r-md);
    background: var(--surface-sunken);
    box-shadow: inset 0 0 0 1px var(--border-subtle);
}

.mrd-lockval {
    flex: 1;
    min-width: 0;
    font-family: var(--font-mono);
    font-size: var(--fs-sm);
    font-weight: var(--fw-semibold);
    color: var(--text-primary);
}

.mrd-lock {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    font-size: var(--fs-xs);
    color: var(--text-tertiary);
}

/* Confidence: the number, then how it reads on the same scale the rules
   table colours its Conf. column with. */
.mrd-conf {
    display: flex;
    align-items: center;
    gap: 12px;
}

.mrd-conf-in { flex: 0 0 110px; }

.mrd-meter {
    flex: 1;
    block-size: 6px;
    border-radius: var(--r-full);
    background: var(--surface-sunken);
    box-shadow: inset 0 0 0 1px var(--border-subtle);
    overflow: hidden;
}

.mrd-meter-fill {
    display: block;
    block-size: 100%;
    border-radius: inherit;
    background: var(--warning-500);
    transition: inline-size 160ms ease;
}

.mrd-meter.is-good .mrd-meter-fill { background: var(--success-500); }
.mrd-meter.is-mid .mrd-meter-fill { background: var(--gold-500); }

.mrd-conf-lbl {
    flex: 0 0 56px;
    font-size: var(--fs-xs);
    font-weight: var(--fw-semibold);
    text-align: end;
    color: var(--warning-700);
}

.mrd-conf-lbl.is-good { color: var(--success-700); }
.mrd-conf-lbl.is-mid { color: var(--gold-700); }

/* Active / paused as a settings row: what the switch means beside it. */
.mrd-toggle {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 10px 12px;
    border-radius: var(--r-md);
    background: var(--surface-sunken);
    box-shadow: inset 0 0 0 1px var(--border-subtle);
}

.mrd-toggle-txt {
    flex: 1;
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: 2px;
}

.mrd-toggle-t {
    font-size: var(--fs-sm);
    font-weight: var(--fw-semibold);
    color: var(--text-primary);
}

.mrd-toggle-d {
    font-size: var(--fs-xs);
    color: var(--text-tertiary);
}

@media (prefers-reduced-motion: reduce) {
    .mrd-meter-fill { transition: none; }
}


/* ============================================================
   42. Candidate: One certification (certd-*)

   /me/certifications/{id} - three screens behind one route, by
   state: the syllabus before the clock starts, the paper while
   it runs, and the verdict afterwards. Every one of them was
   drawn in style attributes - fifty-nine of them across the four
   files - including six colour literals for the three verdict
   states and two token names with hand-written fallbacks that
   had never been defined, so those two borders simply did not
   render.

   Each screen is laid out around the one thing it is for:

     the decision   two columns under a hero. What is on the
                    paper is one question and what sitting it
                    commits you to is another; stacked they read
                    as one list of eleven things, and the
                    syllabus pushed both the terms and Start off
                    the screen. The rail carries whichever
                    commitment the state actually offers - Start
                    when the paper can be sat, the request when
                    it cannot - and wears the wall's gold on its
                    top edge, because it is the commitment.

                    The route has no page header: it is three
                    screens behind one URL and only one of them
                    is a page. So the credential opens on the
                    portal's own hero (.cnp-hero, section 17)
                    rather than on a 20px word at the top of a
                    white card, which is a thin opening for the
                    one irreversible decision here.

     the paper      a drawer over the decision, not a third
                    screen behind the same URL. Starting used to
                    wipe the terms the candidate had just agreed
                    to, and leaving was a navigation; as a panel
                    the syllabus stays put and the paper reads as
                    a thing that is open. The two facts needed at
                    every moment of a timed attempt ride the
                    drawer's own chrome - the clock in the
                    header, the count and the submit in the
                    footer - so neither can be scrolled away
                    from. An option is a whole row rather than a
                    radio with a label beside it: at 9px of
                    padding the hit area was the dot.

     the verdict    one banded card. Everything the screen says
                    about the outcome belongs to a single
                    statement, and colour is carried by that card
                    alone - a fail is a diagnosis, so nothing
                    below the card is painted red.

   What is here:
     .certd-grid    the decision screen, main + rail
     .certd-hero    the credential, at the top of the decision
     .certd-commit  the rail's card - the terms and the button
     .certd-syl     a weighted syllabus row, reused by the
                    verdict's gap report
     .certd-term    one line of the terms table (RuleRow)
     .certd-paper-drw  the attempt's drawer, .certd-q one
                       question inside it
     .certd-bar-*   the parts of the drawer's submit footer
     .certd-verdict the outcome card and its three states
   ============================================================ */

/* ---------- Common to all three ---------- */

/* A chip rather than a bare blue word. It is the only chrome above
   the panel on a page with no header, and an underlined link floating
   on the page ground read as a stray sentence someone had left there. */
.certd-back {
    display: inline-flex;
    align-items: center;
    height: var(--h-sm);
    margin-block-end: 14px;
    padding: 0 13px;
    border: 1px solid var(--border-default);
    border-radius: var(--r-full);
    background: var(--surface-raised);
    box-shadow: var(--shadow-xs);
    font-size: var(--fs-sm);
    font-weight: var(--fw-medium);
    color: var(--text-secondary);
    transition: border-color var(--dur-fast) var(--ease-out),
                color var(--dur-fast) var(--ease-out);
}

.certd-back:hover {
    border-color: var(--border-strong);
    color: var(--text-primary);
    text-decoration: none;
}

.certd-back:focus-visible { outline: none; box-shadow: var(--shadow-focus); }

.certd-skel {
    display: flex;
    flex-direction: column;
    gap: 12px;
}

/* .card carries no padding of its own - it is a surface, and what
   sits on it decides its inset. */
.certd-card { padding: 20px 22px; }

.certd-title {
    margin: 0;
    font-family: var(--font-display);
    font-size: var(--fs-2xl);
    font-weight: var(--fw-bold);
    letter-spacing: -0.015em;
    line-height: var(--lh-snug);
    color: var(--text-primary);
}

.certd-tier {
    margin: 3px 0 0;
    font-family: var(--font-mono);
    font-size: var(--fs-xs);
    color: var(--text-tertiary);
}

/* ---------- The decision screen ---------- */

.certd-grid {
    display: grid;
    grid-template-columns: minmax(0, 1fr) 336px;
    gap: 16px;
    align-items: start;
}

.certd-main {
    display: flex;
    flex-direction: column;
    gap: 16px;
    min-width: 0;
}

/* Sticky, so the terms and the button stay in view while the
   syllabus beside them is read. That is the whole argument for
   the two columns. */
.certd-side {
    position: sticky;
    inset-block-start: 12px;
    min-width: 0;
}

@media (max-width: 1040px) {
    .certd-grid { grid-template-columns: minmax(0, 1fr); }

    /* Stacked, the rail is below the thing it was pinned beside,
       so pinning it only takes height off the syllabus. */
    .certd-side { position: static; }
}

@media (prefers-reduced-motion: reduce) {
    .certd-side { position: static; }
}

/* ---------- The credential, at the top of the decision ---------- */

/* The same navy panel with a gold bloom the candidate dashboard opens
   on. It replaced a header inside the first card: this route has no
   page chrome of its own, so the credential arrived as a 20px word on
   a white surface, and the screen read as a form rather than as the
   one place a candidate decides to sit a timed paper. */
.certd-hero {
    position: relative;
    overflow: hidden;
    padding: 22px 26px;
    border-radius: var(--r-xl);
    background: linear-gradient(135deg, var(--p-900) 0%, var(--p-800) 58%, var(--p-700) 100%);
    color: var(--n-0);
}

/* Decorative and inert - gold from the top right, blue from the
   bottom left, so the gradient does not read as a flat band. */
.certd-hero::before,
.certd-hero::after {
    content: "";
    position: absolute;
    border-radius: 50%;
    pointer-events: none;
}

.certd-hero::before {
    inset-block-start: -84px;
    inset-inline-end: -54px;
    width: 260px;
    height: 260px;
    background: radial-gradient(circle, rgba(245, 184, 0, 0.20) 0%, transparent 70%);
}

.certd-hero::after {
    inset-block-end: -120px;
    inset-inline-start: -60px;
    width: 300px;
    height: 300px;
    background: radial-gradient(circle, rgba(100, 120, 255, 0.18) 0%, transparent 70%);
}

.certd-hero-in {
    position: relative;
    display: flex;
    align-items: flex-start;
    gap: 16px;
}

/* The wall's crest, filled. On the catalogue an unearned credential
   gets the outline; here it is the subject of the whole screen, and
   an outlined tile on a navy panel is a hole rather than a mark. */
.certd-hero-crest {
    flex-shrink: 0;
    display: grid;
    place-items: center;
    width: 52px;
    height: 52px;
    border-radius: var(--r-lg);
    background: linear-gradient(140deg, var(--gold-300), var(--gold-500));
    color: var(--p-900);
    box-shadow: 0 2px 6px rgba(7, 10, 19, 0.30), inset 0 1px 0 rgba(255, 255, 255, 0.45);
}

.certd-hero-txt { flex: 1; min-width: 0; }

/* The coverage pill shares the title's line. It is a fact about this
   credential, not a heading of its own, and the pill's light ground
   reads correctly on the panel without a variant. */
.certd-hero-line {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 12px;
}

.certd-hero-title {
    margin: 0;
    font-family: var(--font-display);
    font-size: var(--fs-3xl);
    font-weight: var(--fw-bold);
    letter-spacing: -0.02em;
    line-height: var(--lh-tight);
}

.certd-hero-tier {
    margin: 6px 0 0;
    font-family: var(--font-mono);
    font-size: var(--fs-xs);
    letter-spacing: 0.02em;
    color: var(--gold-200);
}

.certd-hero-lede {
    margin: 13px 0 0;
    max-width: 68ch;
    font-size: var(--fs-md);
    line-height: var(--lh-relaxed);
    color: rgba(255, 255, 255, 0.78);
}

/* ---------- The syllabus, and the gap report ---------- */

/* One shape for both. They are the same statement read at two
   moments: what the paper will be drawn from, and what it found.

   On the decision screen the syllabus is now the card rather than a
   block inside it, so the margin that separated it from the identity
   block above is the grid's gap instead. */
.certd-syl { margin-block-start: 0; }

/* Why the bars are there. Without it a column of weighted bars on a
   screen that has not started yet reads as a score already given. */
.certd-syl-note {
    margin: 0 0 14px;
    max-width: 68ch;
    font-size: var(--fs-sm);
    line-height: var(--lh-normal);
    color: var(--text-tertiary);
}

.certd-syl-rows {
    display: flex;
    flex-direction: column;
    gap: 10px;
}

.certd-syl-row {
    display: flex;
    align-items: center;
    gap: 12px;
    min-width: 0;
}

.certd-syl-skill {
    flex: 0 1 34%;
    min-width: 0;
    font-size: var(--fs-md);
    color: var(--text-primary);
}

.certd-syl-track {
    flex: 1;
    min-width: 60px;
    height: 7px;
    border-radius: var(--r-full);
    background: var(--n-100);
    overflow: hidden;
}

.certd-syl-fill {
    display: block;
    height: 100%;
    border-radius: var(--r-full);
    background: var(--p-700);
}

/* Three steps on the gap report, not a gradient: a bar whose hue
   drifts with the value invites reading the hue as the value. */
.certd-syl-fill.is-good { background: var(--success-600); }
.certd-syl-fill.is-mid  { background: var(--gold-500); }
.certd-syl-fill.is-low  { background: var(--danger-500); }

.certd-syl-pct {
    flex-shrink: 0;
    width: 42px;
    font-family: var(--font-mono);
    font-size: var(--fs-xs);
    font-variant-numeric: tabular-nums;
    text-align: end;
    color: var(--text-tertiary);
}

/* ---------- The terms ---------- */

.certd-term-list {
    display: flex;
    flex-direction: column;
    border: 1px solid var(--border-subtle);
    border-radius: var(--r-lg);
    overflow: hidden;
}

.certd-term {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    gap: 14px;
    padding: 10px 13px;
    background: var(--surface-raised);
    font-size: var(--fs-md);
}

.certd-term + .certd-term { border-block-start: 1px solid var(--border-subtle); }

.certd-term-lab { color: var(--text-secondary); }

.certd-term-val {
    font-weight: var(--fw-semibold);
    text-align: end;
    color: var(--text-primary);
}

/* The rail's card wears the wall's gold on its top edge and stands a
   step off the page: everything else on the screen is in service of
   the button at the bottom of it, and two identical white cards side
   by side said the syllabus and the commitment were the same weight. */
.certd-commit {
    border-block-start: 2px solid var(--gold-400);
    box-shadow: var(--shadow-md);
}

.certd-commit-head {
    display: flex;
    align-items: center;
    gap: 9px;
    margin-block-end: 13px;
}

.certd-commit-mark {
    flex-shrink: 0;
    display: grid;
    place-items: center;
    width: 28px;
    height: 28px;
    border-radius: var(--r-md);
    background: var(--gold-50);
    color: var(--gold-700);
    box-shadow: inset 0 0 0 1px var(--gold-200);
}

.certd-commit-t {
    margin: 0;
    font-size: var(--fs-md);
    font-weight: var(--fw-semibold);
    line-height: var(--lh-snug);
    color: var(--text-primary);
}

/* The one action, and the only place on the screen an arrow is
   earned: what follows this button is a running clock. */
.certd-start { gap: 9px; }

.certd-start > svg { flex-shrink: 0; }

/* The integrity terms are a sentence, not a value, so they get a
   line rather than a cell they would have to be cut to fit. */
.certd-integrity {
    display: flex;
    gap: 9px;
    margin-block-start: 14px;
}

.certd-integrity > svg {
    flex-shrink: 0;
    margin-block-start: 2px;
    color: var(--text-tertiary);
}

.certd-integrity-lab {
    margin: 0;
    font-size: var(--fs-xs);
    font-weight: var(--fw-semibold);
    color: var(--text-primary);
}

.certd-integrity-txt {
    margin: 2px 0 0;
    font-size: var(--fs-xs);
    line-height: var(--lh-snug);
    color: var(--text-tertiary);
}

.certd-consent {
    margin: 14px 0 12px;
    font-size: var(--fs-xs);
    line-height: var(--lh-normal);
    color: var(--text-tertiary);
}

/* Blocked is a sentence with a date in it. A greyed-out Start
   says no and nothing else, and every one of these states has a
   reason attached. */
.certd-blocked { margin-block-start: 4px; }

/* What the rail says while the paper is open behind it. Gold, and in
   the note the product already uses for something it is doing on your
   behalf: the clock is running whether or not the panel is up. */
.certd-running { margin: 0 0 12px; }

/* ---------- Not offered: the way out ---------- */

.certd-notlive { margin: 0 0 14px; }

/* Once asked, say so. A button that stays put and does nothing
   visible on a second press reads as broken, not as counted. */
.certd-asked {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
    height: var(--h-lg);
    padding: 0 14px;
    border: 1px solid var(--success-100);
    border-radius: var(--r-md);
    background: var(--success-50);
    font-size: var(--fs-md);
    font-weight: var(--fw-semibold);
    color: var(--success-700);
}

.certd-asked > svg { flex-shrink: 0; }

.certd-alts { min-width: 0; }

.certd-alt-go {
    flex-shrink: 0;
    display: inline-flex;
    align-items: center;
    gap: 5px;
    font-size: var(--fs-sm);
    font-weight: var(--fw-semibold);
    color: var(--p-700);
}

/* ---------- The paper ---------- */

/* The attempt is a drawer over the syllabus (section 20 owns the
   chrome; this is only what the paper does with it).

   The body takes the sunken ground so the question cards read as
   raised on it. Everywhere else in the product a drawer holds a form,
   where fields on white is exactly right; a paper is a stack of
   twenty-five discrete things and needs the separation. */
.certd-paper-drw .drw-body {
    gap: 0;
    padding: 0;
    background: var(--surface-sunken);
}

/* Full-bleed, hard under the header, and the only progress that is
   visible while the middle of a long paper is being read - the count
   in the footer says the same thing in words. It stays put because
   the body scrolls under it, not with it. */
.certd-paper-rail {
    position: sticky;
    inset-block-start: 0;
    z-index: 2;
    display: block;
    height: 3px;
    background: var(--n-150);
}

.certd-paper-rail-fill {
    display: block;
    height: 100%;
    background: var(--p-700);
    transition: width var(--dur-mid) var(--ease-out);
}

@media (prefers-reduced-motion: reduce) {
    .certd-paper-rail-fill { transition: none; }
}

/* The clock rides the drawer's header, beside the title: the one fact
   that changes while you read, and the one you cannot recover by
   scrolling back. .drw-head never scrolls, so it is always there. */
.certd-clock {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    padding: 7px 12px;
    border: 1px solid var(--border-subtle);
    border-radius: var(--r-md);
    background: var(--surface-sunken);
    color: var(--text-secondary);
}

.certd-clock > svg { flex-shrink: 0; }

.certd-clock-n {
    font-family: var(--font-mono);
    font-size: var(--fs-lg);
    font-weight: var(--fw-bold);
    font-variant-numeric: tabular-nums;
    line-height: 1;
    color: var(--text-primary);
}

.certd-clock-lab { font-size: var(--fs-xs); }

/* Two minutes left. The only place on the paper that turns red,
   because it is the only thing that can still be acted on. */
.certd-clock.is-low {
    border-color: var(--danger-200);
    background: var(--danger-50);
    color: var(--danger-700);
}

.certd-clock.is-low .certd-clock-n { color: var(--danger-700); }

.certd-qs {
    display: flex;
    flex-direction: column;
    gap: 10px;
    padding: 16px 18px 20px;
}

.certd-q { padding: 16px 18px; }

/* Answered, at a glance down the left edge. The tick in the numeral
   says the same thing, but the edge is what makes an unanswered
   question findable while scrolling past twenty-four answered ones. */
.certd-q.is-done { border-color: var(--p-100); }

.certd-q-top {
    display: flex;
    gap: 11px;
}

/* The number lights up once the question is answered. It is the
   only per-question progress there is, and it costs no room. */
.certd-q-n {
    flex-shrink: 0;
    display: grid;
    place-items: center;
    width: 24px;
    height: 24px;
    border-radius: var(--r-full);
    background: var(--n-75);
    font-family: var(--font-mono);
    font-size: var(--fs-2xs);
    font-weight: var(--fw-semibold);
    font-variant-numeric: tabular-nums;
    color: var(--text-tertiary);
}

.certd-q.is-done .certd-q-n {
    background: var(--p-700);
    color: var(--n-0);
}

.certd-q-n > svg { display: block; }

.certd-q-text {
    flex: 1;
    min-width: 0;
    margin: 0;
    font-size: var(--fs-base);
    font-weight: var(--fw-medium);
    line-height: var(--lh-normal);
    color: var(--text-primary);
}

/* Indented to the question's text, so the options read as
   belonging to it rather than to the card. */
.certd-q-body {
    margin-block-start: 12px;
    padding-inline-start: 35px;
}

.certd-write {
    resize: vertical;
    line-height: var(--lh-normal);
}

.certd-q-hint {
    margin: 6px 0 0;
    font-size: var(--fs-2xs);
    line-height: var(--lh-snug);
    color: var(--text-tertiary);
}

.certd-q-hint-top { margin: 0 0 8px; }

.certd-opts {
    display: flex;
    flex-direction: column;
    gap: 7px;
}

/* The whole row is the target. As a bare radio with a label
   beside it the hit area was the dot, which on a phone is the
   difference between answering a question and mis-answering it. */
.certd-opt {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 11px 13px;
    border: 1px solid var(--border-default);
    border-radius: var(--r-md);
    background: var(--surface-raised);
    font-size: var(--fs-md);
    line-height: var(--lh-snug);
    color: var(--text-primary);
    cursor: pointer;
    transition: border-color var(--dur-fast) var(--ease-out),
                background-color var(--dur-fast) var(--ease-out);
}

.certd-opt:hover {
    border-color: var(--border-strong);
    background: var(--n-25);
}

/* The focus ring belongs to the row, not to the 16px control
   inside it: the row is what the pointer and the keyboard both
   act on. */
.certd-opt:focus-within {
    border-color: var(--border-focus);
    box-shadow: var(--shadow-focus);
}

.certd-opt input {
    flex-shrink: 0;
    width: 16px;
    height: 16px;
    margin: 0;
    accent-color: var(--p-700);
    cursor: pointer;
}

.certd-opt.is-on {
    border-color: var(--p-700);
    background: var(--p-50);
}

.certd-opt-txt { min-width: 0; }

@media (prefers-reduced-motion: reduce) {
    .certd-opt { transition: none; }
}

/* ---------- The submit floor ---------- */

/* Submitting is the one irreversible thing in the panel, so it never
   has to be hunted for - and the count beside it is the last chance to
   notice a question that was scrolled past. Both ride .drw-foot, which
   is already pinned; the paper no longer draws a floor of its own. */
.certd-paper-foot {
    display: flex;
    align-items: center;
    gap: 16px;
    flex-wrap: wrap;
    min-width: 0;
}

.certd-bar-progress {
    display: flex;
    align-items: center;
    gap: 9px;
    min-width: 0;
}

.certd-bar-track {
    width: 120px;
    height: 6px;
    border-radius: var(--r-full);
    background: var(--n-100);
    overflow: hidden;
}

.certd-bar-fill {
    display: block;
    height: 100%;
    border-radius: var(--r-full);
    background: var(--p-700);
    transition: width var(--dur-mid) var(--ease-out);
}

@media (prefers-reduced-motion: reduce) {
    .certd-bar-fill { transition: none; }
}

.certd-bar-n {
    font-size: var(--fs-sm);
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
    color: var(--text-secondary);
}

/* Autosave, stated rather than assumed. An attempt is one of
   three in a year, and the question a candidate has while typing
   is whether what they typed survived. */
.certd-bar-save {
    display: flex;
    align-items: center;
    gap: 6px;
    margin: 0;
    font-size: var(--fs-xs);
    color: var(--text-tertiary);
}

.certd-bar-save > svg { flex-shrink: 0; }

.certd-bar-save.is-bad {
    font-weight: var(--fw-semibold);
    color: var(--warning-700);
}

/* The autosave line and the count sit together on the left; the drawer
   footer's own flex-end puts the submit against the other edge. */
.certd-bar-save { margin-inline-end: auto; }

/* ---------- The verdict ---------- */

.certd-result {
    display: flex;
    flex-direction: column;
    gap: 14px;
}

.certd-verdict {
    padding: 30px 32px;
    text-align: center;
}

.certd-verdict-mark {
    display: grid;
    place-items: center;
    width: 60px;
    height: 60px;
    margin: 0 auto 14px;
    border-radius: var(--r-full);
}

.certd-verdict-h {
    margin: 0;
    font-family: var(--font-display);
    font-size: var(--fs-2xl);
    font-weight: var(--fw-bold);
    letter-spacing: -0.015em;
    color: var(--text-primary);
}

.certd-verdict-p {
    margin: 6px auto 0;
    max-width: 52ch;
    font-size: var(--fs-md);
    line-height: var(--lh-relaxed);
    color: var(--text-secondary);
}

/* Where they placed, not what they scored: "Top 15% of L3
   Backend candidates" is interpretable and "82/100" is not. */
.certd-verdict-band {
    margin: 16px 0 0;
    font-family: var(--font-display);
    font-size: var(--fs-3xl);
    font-weight: var(--fw-semibold);
    letter-spacing: -0.02em;
    line-height: var(--lh-tight);
    color: var(--p-700);
}

.certd-verdict-score {
    margin: 16px 0 0;
    font-family: var(--font-mono);
    font-size: var(--fs-3xl);
    font-weight: var(--fw-bold);
    font-variant-numeric: tabular-nums;
    line-height: var(--lh-tight);
}

.certd-verdict-note {
    margin: 10px 0 0;
    font-size: var(--fs-xs);
    color: var(--text-tertiary);
}

/* The three states. Each one moves the edge, the mark and the
   figure together; separately they drifted, and two of the six
   literals they were named tokens that do not exist. */
.certd-verdict.is-pass { border-color: var(--success-100); }
.certd-verdict.is-pass .certd-verdict-mark {
    background: var(--success-50);
    color: var(--success-600);
}

/* A fail is a diagnosis, not a rejection, so the card keeps the
   ordinary edge and only the mark and the figure carry it. */
.certd-verdict.is-fail .certd-verdict-mark {
    background: var(--danger-50);
    color: var(--danger-700);
}

.certd-verdict.is-fail .certd-verdict-score { color: var(--danger-700); }

.certd-verdict.is-review { border-color: var(--gold-200); }
.certd-verdict.is-review .certd-verdict-mark {
    background: var(--gold-50);
    color: var(--gold-700);
}

/* The link anyone can check. Inline-flex so the block is as wide
   as the code rather than as wide as the card. */
.certd-share {
    display: inline-flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 4px;
    max-width: 100%;
    margin-block-start: 16px;
    padding: 11px 14px;
    border: 1px solid var(--border-subtle);
    border-radius: var(--r-lg);
    background: var(--surface-sunken);
    text-align: start;
}

.certd-share-lab {
    margin: 0;
    font-size: var(--fs-2xs);
    font-weight: var(--fw-semibold);
    letter-spacing: 0.05em;
    text-transform: uppercase;
    color: var(--text-tertiary);
}

.certd-share-url {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    max-width: 100%;
    font-family: var(--font-mono);
    font-size: var(--fs-sm);
    color: var(--p-700);
    border-radius: var(--r-xs);
}

.certd-share-url > svg { flex-shrink: 0; }
.certd-share-url:hover { text-decoration: underline; }
.certd-share-url:focus-visible { outline: none; box-shadow: var(--shadow-focus); }

.certd-skills { min-width: 0; }

.certd-result-foot {
    display: flex;
    justify-content: center;
    padding-block-start: 4px;
}

/* ---------- Narrow ---------- */

@media (max-width: 560px) {
    .certd-card,
    .certd-q { padding: 15px 16px; }

    /* The crest drops to the badge's size and the designation to the
       largest that still fits two words on a 320px column - at 24px a
       three-word title broke across four lines under the pill. */
    .certd-hero { padding: 18px; }
    .certd-hero-in { gap: 12px; }
    .certd-hero-crest { width: 40px; height: 40px; }
    .certd-hero-title { font-size: var(--fs-2xl); }

    .certd-verdict { padding: 26px 18px; }

    /* The indent is worth 35px of a 320px column and the options
       are the widest thing on the card. */
    .certd-q-body { padding-inline-start: 0; }

    /* The skill name stops being a third of nothing. */
    .certd-syl-row { flex-wrap: wrap; }
    .certd-syl-skill { flex: 1 1 100%; }

    /* Stacked, the bar and the count are two lines that say the
       same thing; the count is the one that survives. The autosave
       line goes with it - the footer is two rows deep on a phone
       before the submit button has anywhere to sit. */
    .certd-bar-track { display: none; }
    .certd-bar-save { display: none; }

    .certd-qs { padding: 14px 14px 18px; }
}


/* ============================================================
   43. Super Admin: Brand & emails (pbrand-*)

   /admin/settings/branding edits the tokens a tenant's career
   site is painted with, and it was editing them blind: four
   text inputs in a two-column grid, each carrying the same
   eleven declarations in a style attribute, under one grey
   sentence naming the tenant. Nothing on the screen showed
   what any of the four values did - a hex string is not a
   colour and a URL is not a logo - so the only way to check an
   edit was to save it and open the site.

   A style attribute also cannot express a hover, a focus ring
   or a breakpoint, so those four fields had none of the three,
   and at the width the settings rail leaves, a fixed 1fr 1fr
   put a 120px hex field beside a wrapped label.

   The recruiter hub had already answered the same section -
   .brand-* in "18. Settings hub": a swatch beside every hex,
   and a small standing-in for the careers page painted live
   from the field above it. This section reuses all of that
   rather than restating it, and adds the two things only the
   platform view has: whose branding is open, and the asset
   URLs the recruiter form does not carry.

   What is here:
     .pbrand-hero   the identity band - which tenant, painted
                    in the value being edited
     .pbrand-fact   one token as a read-only chip on that band
     .pbrand-note   the standing line under the colour fields
     .pbrand-asset  a URL field beside the image it resolves to

   Everything else is shared: .brand-grid, .brand-swatch,
   .brand-hex, .brand-prev*, .fld, .input, .card, QCard,
   QPill, QSaveBar. Nothing below restyles them.
   ============================================================ */

/* ---------- The identity band ---------- */

/* Branding rows are per tenant, and this hub edits one of many.
   Which one is the first thing the section has to answer, and
   it was a sentence in the body of a card - the last place a
   reader looks before typing into the field above it. */
.pbrand-hero {
    position: relative;
    overflow: hidden;
    display: flex;
    align-items: flex-start;
    gap: 16px;
    flex-wrap: wrap;
    padding: 18px 20px;
    background: var(--surface-raised);
    border: 1px solid var(--border-default);
    border-radius: var(--r-lg);
    box-shadow: var(--shadow-xs);
}

/* The same wash .org-hero and .prof-hero use: the band is the
   head of the section, not a fourth card. It stays the product's
   navy rather than taking the tenant's colour - the tenant's
   colour is the thing being judged here, and a page tinted with
   it would be judging it against itself. */
.pbrand-hero::before {
    content: "";
    position: absolute;
    inset: 0 0 auto;
    height: 72px;
    background: linear-gradient(180deg, var(--p-50), rgba(238, 242, 255, 0));
    pointer-events: none;
}

.pbrand-hero > * { position: relative; }

/* The mark, in the colour being edited: the band shows the
   setting rather than describing it, and a primary that leaves
   its own monogram unreadable is visible before it is saved. */
.pbrand-hero-mark {
    display: grid;
    place-items: center;
    width: 64px;
    height: 64px;
    flex-shrink: 0;
    border-radius: var(--r-lg);
    color: var(--n-0);
    font-size: var(--fs-2xl);
    font-weight: var(--fw-bold);
    letter-spacing: -0.02em;
    /* A white ring, so a pale brand colour still has an edge
       against the card it sits on. */
    box-shadow: 0 0 0 3px var(--surface-raised), var(--shadow-sm);
}

/* With a logo in the slot the tile stops being the colour and
   becomes a frame for it: a logo is any shape, often white, and
   dropping it onto the brand colour hides half of them. */
.pbrand-hero-mark.has-img {
    padding: 8px;
    border: 1px solid var(--border-subtle);
}

.pbrand-hero-mark img { max-width: 100%; max-height: 100%; object-fit: contain; }

.pbrand-hero-who { flex: 1 1 320px; min-width: 0; }

.pbrand-hero-name {
    margin: 0;
    font-size: var(--fs-2xl);
    font-weight: var(--fw-bold);
    line-height: var(--lh-tight);
    letter-spacing: -0.02em;
    color: var(--text-primary);
}

/* 76ch, not the column: the same limit .set-sec-sub keeps, for
   the same reason - a line set to the full width of a settings
   column is one nobody finishes. */
.pbrand-hero-say {
    margin: 3px 0 0;
    max-width: 76ch;
    font-size: var(--fs-md);
    line-height: var(--lh-snug);
    color: var(--text-secondary);
}

.pbrand-facts {
    display: flex;
    align-items: center;
    gap: 6px;
    flex-wrap: wrap;
    margin-block-start: 10px;
}

/* Read-only, so a chip and not a disabled input: nothing on the
   band is edited from it, only summarised. */
.pbrand-fact {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    max-width: 100%;
    padding: 3px 9px 3px 4px;
    border: 1px solid var(--border-subtle);
    border-radius: var(--r-full);
    background: var(--surface-sunken);
    font-size: var(--fs-xs);
    color: var(--text-tertiary);
}

/* A chip whose value is a word rather than a colour has no
   swatch to make room for, so it keeps the padding a chip has. */
.pbrand-fact.is-plain { padding-inline-start: 9px; }

.pbrand-fact > svg { flex-shrink: 0; color: var(--text-tertiary); }
.pbrand-fact b { font-weight: var(--fw-semibold); color: var(--text-primary); }

/* The swatch on a chip, sized to the chip rather than to the
   28px well the form fields carry. An inset ring rather than a
   border: on a pale swatch a grey border reads as part of the
   colour. */
.pbrand-fact-sw {
    width: 16px;
    height: 16px;
    flex-shrink: 0;
    border-radius: var(--r-full);
    box-shadow: inset 0 0 0 1px rgba(15, 19, 32, 0.14);
}

/* ---------- Under the colour fields ---------- */

/* One line on what an empty field means, which is the question
   both optional fields raise and neither answered. A note rather
   than a hint: it is about the pair, so it cannot hang off
   either one of them. */
.pbrand-note {
    display: flex;
    align-items: flex-start;
    gap: 8px;
    margin-block-start: 14px;
    padding: 9px 11px;
    border-radius: var(--r-md);
    background: var(--surface-sunken);
    font-size: var(--fs-xs);
    line-height: 1.45;
    color: var(--text-tertiary);
}

.pbrand-note > svg { flex-shrink: 0; margin-block-start: 1px; color: var(--text-disabled); }

.pbrand-note code {
    font-family: var(--font-mono);
    font-size: var(--fs-2xs);
    color: var(--text-secondary);
}

/* ---------- The two asset URLs ---------- */

.pbrand-assets { display: flex; flex-direction: column; gap: 14px; }

/* The URL and what it actually resolves to, on one row. A path
   typed into a box says nothing about whether it loads; the tile
   beside it does, and it is the only check available without
   opening the career site itself. */
.pbrand-asset {
    display: flex;
    align-items: flex-start;
    gap: 12px;
}

/* Chequered, because a logo is usually a transparent PNG or an
   SVG, and a white mark on a white tile is an empty box. */
.pbrand-asset-prev {
    display: grid;
    place-items: center;
    width: 56px;
    height: 56px;
    flex-shrink: 0;
    padding: 5px;
    overflow: hidden;
    border: 1px solid var(--border-subtle);
    border-radius: var(--r-md);
    background-color: var(--n-0);
    background-image:
        linear-gradient(45deg, var(--n-100) 25%, transparent 25%),
        linear-gradient(-45deg, var(--n-100) 25%, transparent 25%),
        linear-gradient(45deg, transparent 75%, var(--n-100) 75%),
        linear-gradient(-45deg, transparent 75%, var(--n-100) 75%);
    background-size: 10px 10px;
    background-position: 0 0, 0 5px, 5px -5px, -5px 0;
}

.pbrand-asset-prev img { max-width: 100%; max-height: 100%; object-fit: contain; }

/* A favicon is rendered at 16px and its whole job is to survive
   that size, so it is shown at it rather than blown up to fill a
   tile it will never occupy. */
.pbrand-asset-prev.is-fav img { width: 16px; height: 16px; }

/* Nothing in the slot: a flat well rather than the chequerboard,
   so an empty slot and a transparent image do not look alike. */
.pbrand-asset-prev.is-empty {
    background-color: var(--surface-sunken);
    background-image: none;
    color: var(--text-disabled);
}

.pbrand-asset-fld { flex: 1; min-width: 0; }

/* A URL has no spaces, so left to itself it sets the width of the
   whole card rather than wrapping inside it. */
.pbrand-asset-fld .fld-hint { overflow-wrap: anywhere; }

/* ---------- Narrow ---------- */

@media (max-width: 720px) {
    .pbrand-hero { padding: 16px; gap: 14px; }
    .pbrand-hero-mark { width: 52px; height: 52px; font-size: var(--fs-xl); }
    .pbrand-hero-name { font-size: var(--fs-xl); }

    /* At this width the tile and a full-width URL field do not
       share a line. The tile keeps its place above the field it
       belongs to rather than shrinking to a thumbnail. */
    .pbrand-asset { flex-direction: column; }
}


/* ============================================================
   44. Super Admin: Platform analytics (an-*)

   /admin/analytics - the one screen that answers "how is the
   platform doing", and the last console overview still drawn
   entirely in style attributes.

   Three things were wrong with it, and none of them were the
   numbers:

     - It opened with four date-range buttons that were
       disabled, greyed and titled "not wired up yet". A control
       that cannot control is worse than no control: the reader
       spends their first click on it, and the one fact it stood
       in front of - that everything below is the last 30 days -
       was buried in the middle of the subtitle. The scope is
       stated now, as a chip, where the dead toggle was.

     - Two panels drew a chart with no data in it. The AI
       activity list rendered seven bars at 0% beside seven em
       dashes, which reads as seven features that ran zero
       times, not as a meter nobody has instrumented yet. Same
       reasoning that removed the Tenants list's "Trend" column
       in "40." above: a bar at zero is a measurement. An idle
       track - dashed, unfilled - is not.

     - The KPI band was pinned at repeat(6, 1fr), and the two
       tiles with nothing behind them sat second and last. The
       band is the shared auto-fit .pg-kpis now, and the page
       orders it so the measured numbers come first.

   Everything else on the screen is a shared primitive: the
   header (.pg-head via PageHeader), the KPI band (.pg-kpis),
   the working surface (.pg-body / .pg-rail / .pg-grid2), the
   table (.tbl), the bar (.meter) and the footer (.card-foot).
   Only what this screen has and no other does is below.

   What is here:
     .an-scope     the scope chip that replaced the dead toggle
     .an-conv-*    the outreach conversion pair, the page's lede
     .an-idle      an unfilled track, for a metric with no source
     .an-t-*       the top-tenants-by-MRR table
     .an-reg-*     the region rows in the rail
     .an-pend-*    the roster of metrics awaiting instrumentation
   ============================================================ */

/* ---------- Scope ----------
   Not a control. It says what the page is showing, in the place
   the reader's eye already goes looking for a range picker, and
   it is deliberately not chip-shaped enough to invite a click:
   no border, no hover, no pointer. */
.an-scope {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    height: var(--h-sm);
    padding-inline: 10px;
    border-radius: var(--r-full);
    background: var(--surface-sunken);
    font-size: var(--fs-xs);
    font-weight: var(--fw-medium);
    color: var(--text-secondary);
    white-space: nowrap;
}

.an-scope svg { color: var(--text-tertiary); }

/* ============================================================
   The lede - outreach conversion

   Two ratios that between them say whether the outreach product
   is a growth engine or a mail client. They were two hand-spaced
   divs of raw numbers below the fold; they open the page now,
   and each carries the bar that makes a percentage legible at a
   glance rather than read digit by digit.
   ============================================================ */

.an-conv {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 14px;
}

/* A cell, not a card: the two ratios are one measurement taken
   twice down the same funnel, so a rule beside each says "and
   then" without boxing them off from one another. */
.an-conv-cell {
    min-width: 0;
    padding-inline-start: 14px;
    border-inline-start: 2px solid var(--border-subtle);
}

/* Navy for the first step down the funnel, gold for the second, on
   the rule and on the bar together - so the two cells are told apart
   by the same pair of colours in both places rather than by their
   position on the line. */
.an-conv-cell:first-child { border-inline-start-color: var(--p-500); }
.an-conv-cell:first-child .meter { --meter-c: var(--p-700); }

.an-conv-cell:last-child { border-inline-start-color: var(--gold-500); }
.an-conv-cell:last-child .meter { --meter-c: var(--gold-500); }

.an-conv-l {
    display: block;
    margin: 0;
    font-size: var(--fs-2xs);
    font-weight: var(--fw-semibold);
    letter-spacing: 0.05em;
    text-transform: uppercase;
    color: var(--text-tertiary);
}

/* Mono and tabular so the two figures sit on one set of digits -
   a claim rate beside a conversion rate is a comparison, and
   proportional numerals make one of them look wider than it is. */
.an-conv-v {
    margin: 4px 0 0;
    font-family: var(--font-mono);
    font-variant-numeric: tabular-nums;
    font-size: 28px;
    font-weight: var(--fw-semibold);
    letter-spacing: -0.02em;
    line-height: var(--lh-tight);
    color: var(--text-primary);
}

/* The em dash is a placeholder, not a value - it should not be
   set in the weight of one. */
.an-conv-v.is-idle {
    font-weight: var(--fw-regular);
    color: var(--text-disabled);
}

.an-conv-bar { margin-block-start: 10px; }

.an-conv-sub {
    margin: 7px 0 0;
    font-size: var(--fs-xs);
    line-height: var(--lh-snug);
    color: var(--text-tertiary);
}

/* ---------- The unfilled track ----------
   What a bar looks like when there is nothing behind it. Dashed
   and hollow, because a solid track filled to 0% is indistin-
   guishable from a real measurement that came back zero. */
.an-idle {
    height: 8px;
    border: 1px dashed var(--border-default);
    border-radius: var(--r-full);
    background: none;
}

/* ============================================================
   Top tenants by MRR

   A ranked table. The rank column is what turns ten rows of
   money into a ranking - without it the order is something the
   reader has to notice - and the bar under each figure is that
   figure's share of the largest, so the shape of the top of the
   book is readable without comparing ten numbers pairwise.
   ============================================================ */

.an-t-rank {
    width: 1%;
    font-family: var(--font-mono);
    font-variant-numeric: tabular-nums;
    font-size: var(--fs-sm);
    color: var(--text-disabled);
}

.an-t-name {
    font-weight: var(--fw-semibold);
    color: var(--text-primary);
    text-decoration: none;
}

.an-t-name:hover { color: var(--text-brand); text-decoration: underline; }

.an-t-plan { font-size: var(--fs-sm); color: var(--text-secondary); }

/* The figure and its share, stacked in one cell: the bar belongs
   to the number, and a column of its own would put a chart
   between two columns of text. */
.an-t-mrr { min-width: 120px; }

.an-t-v {
    font-family: var(--font-mono);
    font-variant-numeric: tabular-nums;
    font-weight: var(--fw-semibold);
    color: var(--gold-700);
}

.an-t-share {
    --meter-h: 4px;
    --meter-c: var(--gold-500);
    margin-block-start: 6px;
    margin-inline-start: auto;
    max-width: 96px;
}

/* ============================================================
   Regions

   .meter-row is the shared label/bar/number line; its label
   column is 200px, which is most of a 320px rail. These lines
   are the rail's proportions for it, plus the share the count
   works out to - a count on its own does not say whether nine
   tenants is most of the platform or a rounding error.
   ============================================================ */

.an-reg { display: flex; flex-direction: column; gap: 12px; }

.an-reg .meter-row-l { width: 84px; }

.an-reg-end {
    flex-shrink: 0;
    text-align: end;
    line-height: var(--lh-tight);
}

.an-reg-n {
    font-family: var(--font-mono);
    font-variant-numeric: tabular-nums;
    font-size: var(--fs-sm);
    font-weight: var(--fw-semibold);
    color: var(--text-primary);
}

.an-reg-pct {
    margin: 2px 0 0;
    font-size: var(--fs-2xs);
    color: var(--text-tertiary);
}

/* ============================================================
   Awaiting instrumentation

   The metrics the page names but cannot yet measure. They are
   worth keeping on screen - the roster is the answer to "what
   will this show once it is wired" - but they have to look
   unmeasured: the label at reading contrast, an em dash where
   the number goes, and an idle track where the bar goes.
   ============================================================ */

.an-pend { display: flex; flex-direction: column; }

.an-pend-row {
    padding-block: 9px;
    border-block-start: 1px solid var(--border-subtle);
}

.an-pend-row:first-child { border-block-start: none; padding-block-start: 0; }

.an-pend-head {
    display: flex;
    align-items: baseline;
    gap: 10px;
    margin-block-end: 6px;
}

.an-pend-name {
    flex: 1;
    min-width: 0;
    font-size: var(--fs-sm);
    color: var(--text-secondary);
}

.an-pend-v {
    font-family: var(--font-mono);
    font-size: var(--fs-sm);
    color: var(--text-disabled);
}

/* The two pending panels size to their own content rather than
   both stretching to the taller of them. A card holding an empty
   state and then 200px of nothing under it reads as a card that
   failed to load, not as one with nothing to say yet. */
.an-band { align-items: start; }

/* .q-empty-bare drops the frame but keeps the 32px an empty state
   wears when it is the whole screen. Inside a card that already
   pads its body, and beside a rail, that is a third of a panel
   spent on one sentence. */
.an-empty { padding-block: 20px; }

/* ---------- Narrow ---------- */

@media (max-width: 700px) {
    /* The two ratios stop sharing a line before either figure has
       to shrink. Stacked, the accent rule still tells them apart,
       so nothing is lost but the side-by-side reading. */
    .an-conv { grid-template-columns: minmax(0, 1fr); }
    .an-conv-v { font-size: 24px; }
}


/* ============================================================
   45. Login: shared split-screen sign-in (lgn-*)
   Jork.Shared.UI/Components/Login/LoginPage.razor, used by
   /admin/login, /recruiter/login, /me/login, /interviewer/login
   and /client/login.

   The page carried ~90 inline style attributes, which is why it
   never responded to width: an inline style cannot hold a media
   query, so the 1.1fr/1fr split survived down to 320px. Every
   rule now lives here; the razor passes only the two per-portal
   values, as custom properties on the root element.
   ============================================================ */

.lgn {
    /* Per-portal, overridden inline from LoginTheme. Declared here so
       the pair always resolves even if a caller forgets one. */
    --lgn-panel: linear-gradient(180deg, #0f1b2d 0%, #0f2a4a 60%, #1a3a6a 100%);
    --lgn-accent: var(--gold-500);
    --lgn-form-bg: var(--n-25);
    --lgn-pad: clamp(22px, 3.4vw, 56px);

    display: grid;
    grid-template-columns: 1.05fr 1fr;
    grid-template-rows: 1fr auto;
    grid-template-areas:
        "brand form"
        "foot  form";
    min-height: 100vh;
    min-height: 100dvh;
    /* The gradient sits on the root rather than on the brand panel so the
       panel and the legal strip under it share one continuous ramp — and
       so the stacked layout can reorder them without a seam appearing. */
    background: var(--lgn-panel);
    color: var(--n-0);
    font-family: var(--font-sans);
    overflow-x: hidden;
}

/* ---------- Brand panel ---------- */

.lgn-brand {
    grid-area: brand;
    position: relative;
    overflow: hidden;
    min-width: 0;
    display: flex;
    flex-direction: column;
    padding: var(--lgn-pad);
    padding-block-end: 0;
}

/* Decorative per-portal SVG. Was sized in px on the element; at phone
   width a 700px circle set on a 320px panel is most of what you see. */
.lgn-art {
    position: absolute;
    pointer-events: none;
    aspect-ratio: 1;
    height: auto;
}

.lgn-art--admin     { inset-inline-end: -100px; inset-block-end: -100px;   width: min(700px, 150%); opacity: 0.10; }
.lgn-art--candidate { inset-inline-end: -80px;  inset-block-start: -80px;  width: min(600px, 140%); opacity: 0.18; }
.lgn-art--inhouse   { inset-inline-end: -60px;  inset-block-end: -60px;    width: min(520px, 130%); opacity: 0.15; }
.lgn-art--agency    { inset-inline-end: -80px;  inset-block-end: -80px;    width: min(580px, 135%); opacity: 0.18; }

.lgn-mark {
    position: relative;
    display: flex;
    align-items: center;
    gap: 10px;
    margin-block-end: auto;
}

.lgn-mark-img {
    height: 40px;
    width: auto;
    object-fit: contain;
    /* One source PNG, colourful on light; forced pure white for the dark panel. */
    filter: brightness(0) invert(1);
}

.lgn-mark-name {
    display: block;
    font-size: var(--fs-lg);
    font-weight: var(--fw-bold);
    letter-spacing: -0.01em;
    line-height: 1.15;
}

.lgn-mark-portal {
    display: block;
    font-size: var(--fs-xs);
    font-weight: var(--fw-semibold);
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--lgn-accent);
}

.lgn-restricted {
    margin-inline-start: auto;
    display: inline-flex;
    align-items: center;
    gap: 5px;
    padding: 4px 9px;
    border-radius: var(--r-full);
    background: color-mix(in srgb, var(--lgn-accent) 15%, transparent);
    border: 1px solid color-mix(in srgb, var(--lgn-accent) 30%, transparent);
    color: var(--lgn-accent);
    font-size: var(--fs-2xs);
    font-weight: var(--fw-semibold);
    letter-spacing: 0.06em;
    text-transform: uppercase;
    white-space: nowrap;
}

.lgn-restricted::before {
    content: "";
    width: 6px;
    height: 6px;
    border-radius: var(--r-full);
    background: currentColor;
}

.lgn-pitch {
    position: relative;
    max-width: 540px;
    margin-block: clamp(28px, 5vh, 56px) auto;
}

.lgn-tagline {
    margin: 0;
    font-size: clamp(30px, 1.1rem + 2vw, 48px);
    font-weight: var(--fw-bold);
    letter-spacing: -0.025em;
    line-height: 1.05;
    text-wrap: balance;
}

.lgn-body {
    margin: 16px 0 0;
    max-width: 48ch;
    font-size: clamp(14px, 0.8rem + 0.25vw, 16px);
    line-height: var(--lh-normal);
    color: rgba(255, 255, 255, 0.78);
}

.lgn-highlights {
    list-style: none;
    margin: 26px 0 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 13px;
}

.lgn-highlight {
    display: flex;
    align-items: flex-start;
    gap: 12px;
    font-size: var(--fs-md);
    line-height: var(--lh-normal);
    color: rgba(255, 255, 255, 0.9);
}

.lgn-tick {
    flex: 0 0 auto;
    margin-block-start: 1px;
    width: 22px;
    height: 22px;
    border-radius: var(--r-md);
    display: grid;
    place-items: center;
    background: color-mix(in srgb, var(--lgn-accent) 16%, transparent);
    color: var(--lgn-accent);
}

.lgn-tick svg { width: 12px; height: 12px; }

.lgn-proof {
    display: flex;
    flex-wrap: wrap;
    gap: 16px 36px;
    margin: 32px 0 0;
}

.lgn-proof-v {
    font-family: var(--font-mono);
    font-size: clamp(22px, 0.9rem + 0.9vw, 30px);
    font-weight: var(--fw-medium);
    letter-spacing: -0.02em;
    line-height: 1.1;
    color: var(--lgn-accent);
    font-variant-numeric: tabular-nums;
}

.lgn-proof-l {
    margin-block-start: 3px;
    font-size: var(--fs-xs);
    font-weight: var(--fw-semibold);
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: rgba(255, 255, 255, 0.6);
}

/* ---------- Legal strip ---------- */

.lgn-foot {
    grid-area: foot;
    position: relative;
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 6px 12px;
    padding: 20px var(--lgn-pad) 22px;
    font-size: var(--fs-xs);
    color: rgba(255, 255, 255, 0.55);
}

.lgn-foot a {
    color: inherit;
    text-decoration: none;
    border-block-end: 1px solid transparent;
    transition: color var(--dur-fast) var(--ease-out),
                border-color var(--dur-fast) var(--ease-out);
}

.lgn-foot a:hover { color: var(--n-0); border-block-end-color: rgba(255, 255, 255, 0.45); }

.lgn-sep { opacity: 0.45; }

/* .lgn-status and .lgn-led are gone with the "All systems operational" line
   they painted. It was a green light nothing could ever turn off — the same
   unmeasured claim removed from the platform console this week — and the
   sign-in page is precisely where somebody looks when they suspect an outage. */

/* ---------- Form panel ---------- */

.lgn-form-pane {
    grid-area: form;
    min-width: 0;
    background: var(--lgn-form-bg);
    color: var(--text-primary);
    display: flex;
    align-items: center;
    justify-content: center;
    padding: clamp(32px, 4vw, 56px) var(--lgn-pad);
}

.lgn-card {
    position: relative;
    width: 100%;
    max-width: 404px;
    padding: clamp(24px, 2.4vw, 34px);
    background: var(--surface-raised);
    border: 1px solid var(--border-subtle);
    border-radius: var(--r-2xl);
    box-shadow: var(--shadow-lg);
    animation: lgn-rise var(--dur-slow) var(--ease-out) both;
}

@keyframes lgn-rise {
    from { opacity: 0; transform: translateY(10px); }
    to   { opacity: 1; transform: none; }
}

.lgn-chip {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 4px 10px;
    border: 1px solid var(--border-subtle);
    border-radius: var(--r-full);
    background: var(--surface-page);
    font-size: var(--fs-xs);
    font-weight: var(--fw-semibold);
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--text-tertiary);
}

.lgn-chip-dot {
    width: 6px;
    height: 6px;
    border-radius: var(--r-full);
    background: var(--lgn-accent);
}

.lgn-title {
    margin: 16px 0 0;
    font-size: clamp(22px, 1rem + 0.7vw, 27px);
    font-weight: var(--fw-bold);
    letter-spacing: -0.02em;
    color: var(--text-primary);
}

.lgn-sub {
    margin: 5px 0 0;
    font-size: var(--fs-md);
    color: var(--text-secondary);
}

/* ---------- Alerts ---------- */

.lgn-alert {
    display: flex;
    align-items: flex-start;
    gap: 9px;
    margin-block-start: 16px;
    padding: 10px 12px;
    border-radius: var(--r-lg);
    font-size: var(--fs-sm);
    line-height: var(--lh-snug);
}

.lgn-alert svg {
    flex: 0 0 auto;
    width: 15px;
    height: 15px;
    margin-block-start: 1px;
}

.lgn-alert-error {
    background: var(--danger-50);
    border: 1px solid var(--danger-200);
    color: var(--danger-700);
}

.lgn-alert-ok {
    background: var(--success-50);
    border: 1px solid color-mix(in srgb, var(--success-500) 34%, var(--n-0));
    color: var(--success-700);
}

/* ---------- Fields ---------- */

.lgn-form {
    display: flex;
    flex-direction: column;
    gap: 14px;
    margin-block-start: 22px;
}

.lgn-field {
    display: flex;
    flex-direction: column;
    gap: 6px;
}

.lgn-label-row {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 10px;
}

.lgn-label {
    font-size: var(--fs-xs);
    font-weight: var(--fw-semibold);
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--text-tertiary);
}

.lgn-input-wrap {
    position: relative;
    display: flex;
    align-items: center;
}

.lgn-input-icon {
    position: absolute;
    inset-inline-start: 13px;
    width: 16px;
    height: 16px;
    color: var(--text-disabled);
    pointer-events: none;
    transition: color var(--dur-fast) var(--ease-out);
}

.lgn-input {
    width: 100%;
    height: 46px;
    padding-inline: 39px 12px;
    border: 1px solid var(--border-default);
    border-radius: var(--r-lg);
    background: var(--surface-raised);
    color: var(--text-primary);
    font-family: inherit;
    font-size: var(--fs-base);
    transition: border-color var(--dur-fast) var(--ease-out),
                box-shadow var(--dur-fast) var(--ease-out);
}

.lgn-input::placeholder { color: var(--text-disabled); }
.lgn-input:hover { border-color: var(--border-strong); }

.lgn-input:focus {
    outline: none;
    border-color: var(--border-focus);
    box-shadow: var(--shadow-focus);
}

.lgn-input-wrap:focus-within .lgn-input-icon { color: var(--text-brand); }

.lgn-input-pw { padding-inline-end: 66px; }

.lgn-reveal {
    position: absolute;
    inset-inline-end: 6px;
    height: 34px;
    padding: 0 10px;
    border: 0;
    border-radius: var(--r-md);
    background: transparent;
    color: var(--text-tertiary);
    font-family: inherit;
    font-size: var(--fs-xs);
    font-weight: var(--fw-semibold);
    letter-spacing: 0.04em;
    text-transform: uppercase;
    cursor: pointer;
    transition: color var(--dur-fast) var(--ease-out),
                background-color var(--dur-fast) var(--ease-out);
}

.lgn-reveal:hover { background: var(--surface-hover); color: var(--text-primary); }

.lgn-linkbtn {
    padding: 0;
    border: 0;
    background: none;
    font-family: inherit;
    font-size: var(--fs-sm);
    font-weight: var(--fw-medium);
    color: var(--text-brand);
    cursor: pointer;
}

.lgn-linkbtn:hover { text-decoration: underline; }

/* ---------- Submit ---------- */

.lgn-submit {
    margin-block-start: 4px;
    width: 100%;
    height: 48px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
    border: 0;
    border-radius: var(--r-lg);
    background: linear-gradient(180deg, #17263d 0%, #0f1b2d 100%);
    color: var(--lgn-accent);
    font-family: inherit;
    font-size: var(--fs-lg);
    font-weight: var(--fw-semibold);
    cursor: pointer;
    box-shadow: var(--shadow-md);
    transition: transform var(--dur-fast) var(--ease-out),
                box-shadow var(--dur-mid) var(--ease-out),
                filter var(--dur-fast) var(--ease-out);
}

.lgn-submit:hover { transform: translateY(-1px); box-shadow: var(--shadow-lg); filter: brightness(1.14); }
.lgn-submit:active { transform: translateY(0); box-shadow: var(--shadow-sm); }
.lgn-submit svg { width: 16px; height: 16px; transition: transform var(--dur-mid) var(--ease-out); }
.lgn-submit:hover svg { transform: translateX(3px); }

/* ---------- Alternate sign-in ---------- */

.lgn-divider {
    display: flex;
    align-items: center;
    gap: 10px;
    margin: 22px 0 14px;
}

.lgn-divider::before,
.lgn-divider::after {
    content: "";
    flex: 1;
    height: 1px;
    background: var(--border-subtle);
}

.lgn-divider span {
    font-size: var(--fs-xs);
    font-weight: var(--fw-semibold);
    letter-spacing: 0.05em;
    text-transform: uppercase;
    color: var(--text-disabled);
}

.lgn-alts {
    display: flex;
    flex-direction: column;
    gap: 8px;
}

.lgn-alt {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 10px;
    height: 46px;
    padding-inline: 14px;
    border: 1px solid var(--border-default);
    border-radius: var(--r-lg);
    background: var(--surface-raised);
    color: var(--text-primary);
    font-size: var(--fs-md);
    font-weight: var(--fw-semibold);
    text-decoration: none;
    transition: border-color var(--dur-fast) var(--ease-out),
                background-color var(--dur-fast) var(--ease-out),
                transform var(--dur-fast) var(--ease-out),
                box-shadow var(--dur-mid) var(--ease-out);
}

.lgn-alt:hover {
    border-color: var(--border-strong);
    background: var(--surface-hover);
    transform: translateY(-1px);
    box-shadow: var(--shadow-sm);
}

.lgn-alt-ico { width: 18px; height: 18px; flex: 0 0 auto; }
.lgn-alt-label { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* ---------- Closing rows ---------- */

.lgn-aside {
    margin: 18px 0 0;
    font-size: var(--fs-md);
    color: var(--text-secondary);
    text-align: center;
}

.lgn-aside a {
    color: var(--text-brand);
    font-weight: var(--fw-semibold);
    text-decoration: none;
}

.lgn-aside a:hover { text-decoration: underline; }

.lgn-switch {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: center;
    gap: 8px;
    margin-block-start: 22px;
    padding-block-start: 16px;
    border-block-start: 1px solid var(--border-subtle);
    font-size: var(--fs-sm);
    color: var(--text-disabled);
}

.lgn-switch a {
    color: var(--text-brand);
    font-weight: var(--fw-medium);
    text-decoration: none;
}

.lgn-switch a:hover { text-decoration: underline; }

/* ---------- Forgot-password dialog ---------- */

.lgn-modal {
    width: min(440px, calc(100vw - 32px));
    padding: 0;
    border: 0;
    border-radius: var(--r-xl);
    background: var(--surface-raised);
    color: var(--text-primary);
    box-shadow: var(--shadow-xl);
}

.lgn-modal::backdrop { background: var(--surface-overlay); }
.lgn-modal[open] { animation: lgn-pop var(--dur-mid) var(--ease-out) both; }

@keyframes lgn-pop {
    from { opacity: 0; transform: translateY(8px) scale(0.98); }
    to   { opacity: 1; transform: none; }
}

.lgn-modal-form { padding: 24px; }

.lgn-modal-head {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 12px;
}

.lgn-modal-title {
    margin: 0;
    font-size: var(--fs-xl);
    font-weight: var(--fw-bold);
    letter-spacing: -0.01em;
}

.lgn-modal-x {
    flex: 0 0 auto;
    display: grid;
    place-items: center;
    width: 28px;
    height: 28px;
    margin: -4px -4px 0 0;
    padding: 0;
    border: 0;
    border-radius: var(--r-md);
    background: none;
    color: var(--text-disabled);
    cursor: pointer;
    transition: color var(--dur-fast) var(--ease-out),
                background-color var(--dur-fast) var(--ease-out);
}

.lgn-modal-x:hover { color: var(--text-primary); background: var(--surface-hover); }
.lgn-modal-x svg { width: 14px; height: 14px; }

.lgn-modal-p {
    margin: 6px 0 18px;
    font-size: var(--fs-sm);
    line-height: var(--lh-normal);
    color: var(--text-secondary);
}

.lgn-modal-actions {
    display: flex;
    flex-wrap: wrap;
    justify-content: flex-end;
    gap: 8px;
    margin-block-start: 18px;
}

.lgn-btn-ghost,
.lgn-btn-solid {
    height: 40px;
    padding-inline: 16px;
    border-radius: var(--r-lg);
    font-family: inherit;
    font-size: var(--fs-md);
    font-weight: var(--fw-semibold);
    cursor: pointer;
    transition: filter var(--dur-fast) var(--ease-out),
                background-color var(--dur-fast) var(--ease-out);
}

.lgn-btn-ghost {
    border: 1px solid var(--border-default);
    background: var(--surface-raised);
    color: var(--text-secondary);
    font-weight: var(--fw-medium);
}

.lgn-btn-ghost:hover { background: var(--surface-hover); color: var(--text-primary); }

.lgn-btn-solid {
    border: 0;
    background: #0f1b2d;
    color: var(--lgn-accent);
}

.lgn-btn-solid:hover { filter: brightness(1.14); }

/* ---------- Focus ---------- */

.lgn :is(a, button):focus-visible,
.lgn-modal :is(a, button):focus-visible {
    outline: 2px solid var(--border-focus);
    outline-offset: 2px;
    border-radius: var(--r-sm);
}

/* The navy ring is invisible on the navy panel. */
.lgn-brand :is(a, button):focus-visible,
.lgn-foot a:focus-visible { outline-color: var(--lgn-accent); }

/* ---------- RTL ---------- */

[dir="rtl"] .lgn-submit svg { transform: scaleX(-1); }
[dir="rtl"] .lgn-submit:hover svg { transform: scaleX(-1) translateX(3px); }

/* ---------- Stacked (tablet and phone) ---------- */

@media (max-width: 1140px) {
    .lgn { grid-template-columns: 1fr 1fr; }
    .lgn-highlights { gap: 10px; }
    .lgn-proof { gap: 14px 26px; }
}

@media (max-width: 900px) {
    .lgn {
        grid-template-columns: minmax(0, 1fr);
        grid-template-rows: auto auto auto;
        grid-template-areas:
            "brand"
            "form"
            "foot";
        min-height: 0;
    }

    .lgn-brand { padding-block: clamp(26px, 5vw, 40px) 52px; }
    .lgn-mark { margin-block-end: 0; }
    .lgn-pitch { max-width: none; margin-block: 22px 0; }
    .lgn-tagline { font-size: clamp(26px, 3.6vw + 12px, 36px); }
    .lgn-body { margin-block-start: 12px; }

    /* Four long bullets between the logo and the sign-in form is a scroll
       before the thing the visitor came for. The pitch keeps the tagline,
       the sentence and the numbers. */
    .lgn-highlights { display: none; }

    .lgn-proof { margin-block-start: 22px; gap: 14px 30px; }
    .lgn-art { opacity: 0.12; }

    .lgn-form-pane { padding-block: 0 clamp(28px, 6vw, 44px); align-items: flex-start; }

    /* The card laps over the brand band, which ties the two halves together
       and reclaims the gap the stack would otherwise leave. */
    .lgn-card { margin-block-start: -32px; max-width: 460px; }

    .lgn-foot { padding-block: 18px 22px; }
}

@media (max-width: 560px) {
    .lgn { --lgn-pad: 18px; }
    .lgn-mark-img { height: 34px; }
    .lgn-brand { padding-block-end: 48px; }
    .lgn-tagline { font-size: clamp(24px, 5.4vw + 6px, 30px); }
    .lgn-body { font-size: var(--fs-md); }
    .lgn-proof { gap: 12px 22px; margin-block-start: 18px; }
    .lgn-proof-v { font-size: 21px; }
    .lgn-card { padding: 22px 18px; border-radius: var(--r-xl); }
    .lgn-restricted { padding: 3px 7px; letter-spacing: 0.04em; }
    .lgn-foot { justify-content: center; text-align: center; }
}

/* A phone this narrow spends a third of the fold on three figures. */
@media (max-width: 400px) {
    .lgn-proof { display: none; }
    .lgn-mark-portal { font-size: var(--fs-2xs); }
}

/* ---------- Motion / input ---------- */

@media (hover: none) {
    .lgn-submit:hover,
    .lgn-alt:hover { transform: none; box-shadow: var(--shadow-md); }
    .lgn-submit:hover svg { transform: none; }
}

@media (prefers-reduced-motion: reduce) {
    .lgn-card,
    .lgn-modal[open] { animation: none; }

    .lgn-submit,
    .lgn-submit svg,
    .lgn-alt,
    .lgn-input,
    .lgn-foot a { transition: none; }

    .lgn-submit:hover,
    .lgn-alt:hover { transform: none; }
}

/* ============================================================
   45b. Phone / OTP sign-in (otp-*)
   Jork.Shared.UI/Components/Login/PhoneOtpPage.razor, rendered
   by /recruiter/otp and /me/otp.

   Both pages were a 420px card centred on a grey page with every
   rule in a style attribute: no brand panel, nothing tying them
   to the sign-in page that links here, and — because an inline
   style cannot hold a media query — the same 32px padding at
   320px as at 1600px. They render the .lgn shell now, so the
   panel, the card, the fields and every breakpoint above are
   shared with sign-in; only what the two OTP steps need beyond
   it lives here.
   ============================================================ */

/* ---------- Step rail ---------- */

/* Two bars rather than "Step 1 of 2" alone: the label says where
   you are, the rail says how much is left, and the second step is
   one field long — worth showing before the number is typed. */
.otp-steps {
    display: flex;
    gap: 6px;
    margin-block-start: 16px;
}

.otp-step {
    flex: 1;
    height: 3px;
    border-radius: var(--r-full);
    background: var(--border-subtle);
}

.otp-step.is-on { background: var(--text-primary); }

/* ---------- Number recap (code step) ---------- */

/* The number is the one thing the code step cannot let you doubt,
   and mistyping it was previously a dead end — the only way back
   to the field was the browser's back button. */
.otp-recap {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 8px 12px;
    margin-block-start: 18px;
    padding: 10px 12px;
    border: 1px solid var(--border-subtle);
    border-radius: var(--r-lg);
    background: var(--surface-page);
}

.otp-recap svg {
    flex: 0 0 auto;
    width: 15px;
    height: 15px;
    color: var(--text-tertiary);
}

.otp-recap-num {
    min-width: 0;
    flex: 1;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-family: var(--font-mono);
    font-size: var(--fs-md);
    font-variant-numeric: tabular-nums;
    color: var(--text-primary);
}

.otp-recap a {
    flex: 0 0 auto;
    font-size: var(--fs-sm);
    font-weight: var(--fw-semibold);
    color: var(--text-brand);
    text-decoration: none;
}

.otp-recap a:hover { text-decoration: underline; }

/* ---------- Code field ---------- */

/* Six digits read as three pairs at this tracking, which is how
   people read a code off a lock screen. The indent cancels the
   trailing letter-space so the string stays optically centred. */
.otp-code {
    width: 100%;
    height: 62px;
    padding: 0 12px;
    border: 1px solid var(--border-default);
    border-radius: var(--r-lg);
    background: var(--surface-raised);
    color: var(--text-primary);
    font-family: var(--font-mono);
    font-size: clamp(24px, 1rem + 1.2vw, 30px);
    font-weight: var(--fw-medium);
    letter-spacing: 0.42em;
    text-indent: 0.42em;
    text-align: center;
    font-variant-numeric: tabular-nums;
    transition: border-color var(--dur-fast) var(--ease-out),
                box-shadow var(--dur-fast) var(--ease-out);
}

.otp-code::placeholder {
    color: var(--text-disabled);
    font-weight: var(--fw-regular);
}

.otp-code:hover { border-color: var(--border-strong); }

.otp-code:focus {
    outline: none;
    border-color: var(--border-focus);
    box-shadow: var(--shadow-focus);
}

/* The spinners a numeric keyboard hint brings with it on desktop. */
.otp-code::-webkit-outer-spin-button,
.otp-code::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }

/* ---------- Resend ---------- */

.otp-resend {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    justify-content: center;
    gap: 4px 8px;
    margin-block-start: 16px;
    font-size: var(--fs-sm);
    color: var(--text-tertiary);
}

.otp-resend form { display: contents; }

.otp-resend-btn {
    padding: 0;
    border: 0;
    background: none;
    font-family: inherit;
    font-size: var(--fs-sm);
    font-weight: var(--fw-semibold);
    color: var(--text-brand);
    cursor: pointer;
}

.otp-resend-btn:hover:not(:disabled) { text-decoration: underline; }

/* Set by js/jork-otp.js for the 30s the server refuses a second
   code. Without it the only feedback was a red "wait a moment"
   banner after the round trip. */
.otp-resend-btn:disabled {
    color: var(--text-disabled);
    cursor: default;
    font-variant-numeric: tabular-nums;
}

/* ---------- Footnote ---------- */

.otp-note {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 6px;
    margin: 14px 0 0;
    font-size: var(--fs-xs);
    color: var(--text-disabled);
    text-align: center;
}

.otp-note svg { flex: 0 0 auto; width: 13px; height: 13px; }

/* ---------- Narrow ---------- */

@media (max-width: 560px) {
    .otp-code { height: 56px; letter-spacing: 0.3em; text-indent: 0.3em; }
    .otp-recap { gap: 6px 10px; }
}

@media (prefers-reduced-motion: reduce) {
    .otp-code { transition: none; }
}

/* ============================================================
   45c. Sign-up extras (reg-*)
   Candidate/Pages/Register.razor — /me/register, step 1 of the
   three-step signup wizard — and, for everything on this list
   except the language select, the employer wizard in 45d below.
   Both are the same object: a progress rail, a name row, inline
   field notes, a strength meter and a legal line, on the .lgn
   shell. A second copy under an rreg- prefix would have drifted
   the first time one of the two changed a colour.

   The page was a sibling of the sign-in screen in look only: its
   own 1.1fr/1fr grid, its own brand panel, its own field markup,
   all of it in ~90 style attributes. An inline style cannot hold
   a media query, so the two-column split survived to 320px — the
   same defect the .lgn block above was written to fix, sitting on
   the page a candidate meets one click after the one that got
   fixed. It now renders on that shell; only the parts sign-in has
   no equivalent for live here.
   ============================================================ */

/* Five fields, the social buttons and the phone route make a card a good
   deal taller than sign-in's. Centred in a flex pane it would be clipped
   at the TOP on a laptop viewport, and no amount of scrolling reaches
   what a flex container overflows above its start edge. */
.reg .lgn-form-pane { align-items: flex-start; }
.reg .lgn-card { max-width: 452px; }

/* ---------- Step rail ---------- */

/* Sign-in shows a .lgn-chip because it is one screen. Signup is three, and
   "Step 1 of 3" as text alone leaves the other two unimagined — the rail
   shows how much is left, which is the whole reason to say it. It matches
   the progress bar on /me/register/build, steps 2 and 3. */
.reg-progress { margin-block-end: 20px; }

.reg-track {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 5px;
}

.reg-seg {
    height: 4px;
    border-radius: var(--r-full);
    background: var(--border-default);
}

.reg-seg--on { background: var(--lgn-accent); }

.reg-progress-label {
    margin: 10px 0 0;
    font-size: var(--fs-xs);
    font-weight: var(--fw-semibold);
    letter-spacing: 0.02em;
    color: var(--text-tertiary);
}

/* ---------- Name row ---------- */

.reg-row {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 10px;
}

/* No leading icon on the name pair, so the 27px inset .lgn-input reserves for
   one is dead space in the narrowest box on the card. */
.reg-name { padding-inline-start: 12px; }

/* Two 8-character-wide name boxes are worse than two full-width ones, so the
   pair stacks — but only once it has to. The common phones (390, 393, 430) keep
   the compact row and the ~70px of scroll that stacking would add. */
@media (max-width: 380px) {
    .reg-row { grid-template-columns: minmax(0, 1fr); }
}

/* ---------- Language select ---------- */

/* Only rendered when the tenant has more than one active language. Shares
   .lgn-input, so it inherits the height, ring and icon inset; these are the
   three things a <select> needs on top. The background-image declaration has
   to follow .lgn-input's `background` shorthand in source order, which it
   does — that shorthand resets background-image to none. */
.reg-select {
    appearance: none;
    padding-inline-end: 34px;
    background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 8' fill='none' stroke='%236b7280' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m1 1.5 5 5 5-5'/%3E%3C/svg%3E");
    background-repeat: no-repeat;
    background-position: right 13px center;
    background-size: 11px 8px;
    cursor: pointer;
}

[dir="rtl"] .reg-select { background-position: left 13px center; }

/* ---------- Inline field notes ---------- */

/* Email availability and password match. Reserves its line so the button
   below does not jump the moment the note appears under the cursor. */
.reg-note {
    margin: 0;
    min-height: 16px;
    font-size: var(--fs-xs);
    line-height: var(--lh-snug);
    color: var(--text-tertiary);
}

.reg-note--ok  { color: var(--success-700); }
.reg-note--bad { color: var(--danger-700); }

/* ---------- Password strength ---------- */

.reg-meter {
    display: flex;
    align-items: center;
    gap: 10px;
    margin-block-start: 9px;
}

.reg-meter-track {
    flex: 1;
    min-width: 0;
    height: 4px;
    border-radius: var(--r-full);
    background: var(--border-default);
    overflow: hidden;
}

.reg-meter-bar {
    height: 100%;
    width: 0;
    border-radius: inherit;
    background: var(--danger-600);
    transition: width var(--dur-mid) var(--ease-out),
                background-color var(--dur-mid) var(--ease-out);
}

/* The bar used to be the whole verdict, and it changed nothing but colour:
   silent to a screen reader, and to a red-green colour-blind reader four
   identical grey bars of different lengths. The word carries the meaning
   and the colour agrees with it. */
.reg-meter-label {
    flex: 0 0 auto;
    font-size: var(--fs-xs);
    font-weight: var(--fw-semibold);
    color: var(--text-tertiary);
    white-space: nowrap;
}

.reg-meter[data-score="0"] .reg-meter-bar { width: 12%;  background: var(--danger-600); }
.reg-meter[data-score="1"] .reg-meter-bar { width: 32%;  background: var(--danger-600); }
.reg-meter[data-score="2"] .reg-meter-bar { width: 56%;  background: var(--warning-500); }
.reg-meter[data-score="3"] .reg-meter-bar { width: 80%;  background: var(--info-500); }
.reg-meter[data-score="4"] .reg-meter-bar { width: 100%; background: var(--success-600); }

.reg-meter[data-score="0"] .reg-meter-label,
.reg-meter[data-score="1"] .reg-meter-label { color: var(--danger-700); }
.reg-meter[data-score="2"] .reg-meter-label { color: var(--warning-700); }
.reg-meter[data-score="3"] .reg-meter-label { color: var(--info-700); }
.reg-meter[data-score="4"] .reg-meter-label { color: var(--success-700); }

/* ---------- Legal line ---------- */

/* --text-tertiary, not --text-disabled: at 11.5px the disabled grey is about
   2.9:1 on white, and this is the line that says what the visitor is agreeing
   to. Quiet is the intent; unreadable is not. */
.reg-legal {
    margin: 14px 0 0;
    font-size: var(--fs-xs);
    line-height: var(--lh-normal);
    color: var(--text-tertiary);
    text-align: center;
    text-wrap: pretty;
}

/* ---------- Motion ---------- */

@media (prefers-reduced-motion: reduce) {
    .reg-meter-bar { transition: none; }
}

/* ============================================================
   45d. Recruiter sign-up (rreg-*)
   Recruiter/Pages/Register.razor - /recruiter/register, the
   employer self-serve wizard.

   The last auth screen still building its split by hand: its own
   1.1fr/1fr grid, its own brand panel, its own field markup, in
   around seventy style attributes. An inline style cannot hold a
   media query, so the two-column split reached 320px with a 44px
   headline in half a phone - the same defect .lgn was written to
   fix, on the page an employer meets one click after the one that
   got fixed. It renders on that shell now, and on 45c's sign-up
   extras, so the rail, the notes, the meter and the legal line
   are the same objects the candidate wizard uses.

   What is here is only what this page has and no other does:

     .rreg          the shell, plus the panel ramp per employer type
     .rreg-step     one wizard pane
     .rreg-types    the in-house / agency chooser
     .rreg-enrich   the website box and its auto-fill button
     .rreg-actions  Back beside the step's forward button

   The emoji standing in for icons here - a building, a handshake,
   a phone, a sparkle - are inline SVG now. The codebase does not
   put emoji in the UI, and at 19px they rendered as whatever the
   visitor's OS happened to ship.
   ============================================================ */

/* The panel answers the choice. In-house takes LoginTheme.InHouse's green
   ramp, agency LoginTheme.Recruiter's blue one, so the workspace the page
   promises to tailor visibly starts tailoring itself - and the sign-in
   screen this employer meets tomorrow is already the colour they picked.
   Declared here rather than inline on the element: an inline custom
   property outranks a stylesheet one, so the ramp could never change. */
.rreg { --lgn-panel: linear-gradient(180deg, #0F1B2D 0%, #0F2A4A 60%, #1a3a6a 100%); }
.rreg[data-type="in_house"] { --lgn-panel: linear-gradient(180deg, #0F1B2D 0%, #1c4a3a 60%, #2a6a55 100%); }

/* Three steps of very different heights: step 1 is eight fields and the social
   buttons, step 0 is two cards. Centred by align-items the tall one is clipped
   at its TOP on a laptop, and nothing scrolls back to what a flex container
   overflows above its start edge; pinned to the start the short one hangs off
   the top of an empty pane. An auto cross-axis margin is the pair of those:
   centred while it fits, top-aligned and fully scrollable once it does not. */
.rreg .lgn-form-pane { align-items: flex-start; }
.rreg .lgn-card { max-width: 468px; margin-block: auto; }

/* The step heading opens its pane, so the rail above it sets the gap on its
   own; the heading's own 16px would stack on top of it. When the server has
   sent an error back, the banner is what the heading follows instead. */
.rreg-step .lgn-title { margin-block-start: 0; }
.rreg .lgn-alert { margin-block-end: 18px; }

/* The shell rounds every focus ring to --r-sm, which squares off the corners
   of a 14px card the moment it is reached by keyboard. */
.rreg .rreg-type:focus-visible { border-radius: var(--r-xl); }
.rreg :is(.rreg-fill, .rreg-back, .lgn-submit):focus-visible { border-radius: var(--r-lg); }

/* ---------- Progress ---------- */

/* 45c's track, filled from the step the shell is on. The wizard's script
   owns one attribute on the root and the rail follows it, rather than
   reaching in to paint three elements itself. */
.rreg[data-step="0"] .reg-seg:nth-child(1),
.rreg[data-step="1"] .reg-seg:nth-child(-n + 2),
.rreg[data-step="2"] .reg-seg { background: var(--lgn-accent); }

/* ---------- Panes ---------- */

/* The step that arrives fades up. goStep() only flips display, and an
   element that becomes rendered again restarts its animation, so the
   transition costs the script nothing. */
.rreg-step { animation: rreg-step-in var(--dur-mid) var(--ease-out) both; }

@keyframes rreg-step-in {
    from { opacity: 0; transform: translateY(6px); }
    to   { opacity: 1; transform: none; }
}

.rreg-fields {
    display: flex;
    flex-direction: column;
    gap: 14px;
    margin-block-start: 20px;
}

/* The divider above already spends 14px; a second 20px under it opens a hole
   between "or sign up with email" and the first box it introduces. */
.lgn-divider + .rreg-fields { margin-block-start: 0; }

.rreg-step .lgn-alts { margin-block-start: 20px; }

/* Sign-in's field reserves a 39px inset for the icon in its two boxes. Eight
   boxes here have no icon - each is introduced by its own label, and eight
   decorative glyphs down a signup form is noise - so they take the plain
   inset. The select still needs its side kept clear for the chevron 45c
   paints, and this rule outranks 45c's, so it has to say so again. */
.rreg .lgn-input { padding-inline: 13px; }
.rreg .reg-select { padding-inline-end: 34px; }

/* 45c's meter opens its own gap because it follows the box directly; inside
   .lgn-field it follows a 6px flex gap as well. Empty, it is a 4px grey rule
   sitting 8px under the password box, which reads as an underline on the box
   rather than as a meter for it - hidden rather than removed, so the confirm
   field below does not jump the moment a key is pressed. */
.rreg .reg-meter { margin-block-start: 2px; }
.rreg .reg-meter[data-score=""] { visibility: hidden; }

/* ---------- Employer type ---------- */

.rreg-types {
    display: flex;
    flex-direction: column;
    gap: 10px;
    margin-block-start: 20px;
}

/* The whole card is the target, not the 14px of text inside it. */
.rreg-type {
    display: flex;
    align-items: flex-start;
    gap: 13px;
    width: 100%;
    padding: 15px 16px;
    border: 1.5px solid var(--border-default);
    border-radius: var(--r-xl);
    background: var(--surface-raised);
    text-align: start;
    cursor: pointer;
    transition: border-color var(--dur-fast) var(--ease-out),
                background-color var(--dur-fast) var(--ease-out),
                transform var(--dur-fast) var(--ease-out),
                box-shadow var(--dur-mid) var(--ease-out);
}

.rreg-type:hover {
    border-color: var(--border-strong);
    transform: translateY(-1px);
    box-shadow: var(--shadow-sm);
}

.rreg-type-ico {
    flex: 0 0 auto;
    display: grid;
    place-items: center;
    width: 38px;
    height: 38px;
    border-radius: var(--r-lg);
    background: var(--p-50);
    color: var(--p-700);
}

.rreg-type-ico svg { width: 19px; height: 19px; }

/* The agency card is the one that unlocks clients, fees and invoices, so it
   carries the brand accent instead of a second navy tile. */
.rreg-type[data-type="agency"] .rreg-type-ico { background: var(--gold-50); color: var(--gold-800); }

.rreg-type-body { flex: 1; min-width: 0; }

.rreg-type-t {
    display: block;
    font-size: var(--fs-lg);
    font-weight: var(--fw-bold);
    letter-spacing: -0.01em;
    color: var(--text-primary);
}

.rreg-type-d {
    display: block;
    margin-block-start: 3px;
    font-size: var(--fs-sm);
    line-height: var(--lh-snug);
    color: var(--text-tertiary);
    text-wrap: pretty;
}

/* Two cards where exactly one can win: the mark says so in the shape of the
   control, which a border colour alone never did - and it is what shows the
   choice again after the server bounces the form back to this step. */
.rreg-type-mark {
    flex: 0 0 auto;
    display: grid;
    place-items: center;
    width: 21px;
    height: 21px;
    margin-block-start: 9px;
    border: 1.5px solid var(--border-strong);
    border-radius: var(--r-full);
    color: var(--text-inverse);
    transition: background-color var(--dur-fast) var(--ease-out),
                border-color var(--dur-fast) var(--ease-out);
}

.rreg-type-mark svg {
    width: 11px;
    height: 11px;
    opacity: 0;
    transition: opacity var(--dur-fast) var(--ease-out);
}

.rreg[data-type="in_house"] .rreg-type[data-type="in_house"],
.rreg[data-type="agency"]   .rreg-type[data-type="agency"] {
    border-color: var(--p-700);
    background: var(--p-50);
    box-shadow: var(--shadow-focus);
}

.rreg[data-type="in_house"] .rreg-type[data-type="in_house"] .rreg-type-mark,
.rreg[data-type="agency"]   .rreg-type[data-type="agency"]   .rreg-type-mark {
    border-color: var(--p-700);
    background: var(--p-700);
}

.rreg[data-type="in_house"] .rreg-type[data-type="in_house"] .rreg-type-mark svg,
.rreg[data-type="agency"]   .rreg-type[data-type="agency"]   .rreg-type-mark svg { opacity: 1; }

/* ---------- Website + auto-fill ---------- */

.rreg-enrich { display: flex; gap: 8px; }

/* min-width:0 is the half that is always forgotten: without it the box never
   shrinks below its content and the button is pushed out of the card. */
.rreg-enrich .lgn-input { flex: 1; min-width: 0; }

.rreg-fill {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    gap: 6px;
    height: 46px;
    padding-inline: 14px;
    border: 1px solid var(--gold-300);
    border-radius: var(--r-lg);
    background: var(--gold-50);
    color: var(--gold-800);
    font-size: var(--fs-sm);
    font-weight: var(--fw-semibold);
    white-space: nowrap;
    cursor: pointer;
    transition: background-color var(--dur-fast) var(--ease-out),
                border-color var(--dur-fast) var(--ease-out);
}

.rreg-fill:hover:not(:disabled) { background: var(--gold-100); border-color: var(--gold-400); }
.rreg-fill:disabled { opacity: 0.55; cursor: default; }
.rreg-fill svg { width: 14px; height: 14px; }

/* ---------- Wizard floor ---------- */

.rreg-actions {
    display: flex;
    gap: 10px;
    margin-block-start: 22px;
}

.rreg-actions .lgn-submit { flex: 1; margin-block-start: 0; }

.rreg-back {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    gap: 7px;
    height: 48px;
    padding-inline: 16px;
    border: 1px solid var(--border-default);
    border-radius: var(--r-lg);
    background: var(--surface-raised);
    color: var(--text-secondary);
    font-size: var(--fs-md);
    font-weight: var(--fw-semibold);
    cursor: pointer;
    transition: background-color var(--dur-fast) var(--ease-out),
                border-color var(--dur-fast) var(--ease-out),
                color var(--dur-fast) var(--ease-out);
}

.rreg-back:hover { background: var(--surface-hover); border-color: var(--border-strong); color: var(--text-primary); }
.rreg-back svg { width: 15px; height: 15px; }

[dir="rtl"] .rreg-back svg { transform: scaleX(-1); }

/* ---------- Narrow ---------- */

@media (max-width: 420px) {
    .rreg-type { padding: 13px; gap: 11px; }
    .rreg-type-ico { width: 34px; height: 34px; }
    .rreg-type-t { font-size: var(--fs-base); }

    /* At this width the pair splits about 150px each, and "Create workspace"
       does not fit in 150px. Back goes under the thing it is the alternative
       to, which is also the order a thumb wants them in. */
    .rreg-actions { flex-direction: column-reverse; }
    .rreg-back { justify-content: center; height: 44px; }

    .rreg-enrich { flex-wrap: wrap; }
    .rreg-fill { width: 100%; justify-content: center; }
}

/* ---------- Motion / input ---------- */

@media (hover: none) {
    .rreg-type:hover { transform: none; box-shadow: none; }
}

@media (prefers-reduced-motion: reduce) {
    .rreg-step { animation: none; }

    .rreg-type,
    .rreg-type-mark,
    .rreg-type-mark svg,
    .rreg-fill,
    .rreg-back { transition: none; }

    .rreg-type:hover { transform: none; }
}

/* ============================================================
   46. Recruiter: Offer composer (ofw-*)

   /recruiter/offers/new - the five-step wizard that turns an
   application into a signed offer. Chrome only: every query,
   validation and write on the page is unchanged.

   It was an 880px column of cards, all of it in style
   attributes, and the three things wrong with it were the same
   thing three times - nothing stayed on screen.

     - The candidate was a line in the subtitle and the money
       was a card on the last step, so the base salary was
       typed on step two with the name two steps behind it and
       the total arriving two steps ahead. Both are a rail now,
       beside every step, recomputed as the figures change.

     - Back and Next sat under a column that runs past the fold
       the moment the letter editor opens. They are a pinned
       floor now, and the sentence explaining what "Create &
       send" actually does sits beside the button rather than
       under a card nobody scrolls to.

     - The step rail was QStepper's five hard-coded hexes -
       #0F2A4A, #9ca3af, #e5e7eb - none of them in the palette,
       and the labels wrapped the bar to three lines on a
       laptop.

   Everything else is a shared primitive: the header
   (PageHeader / .pg-head), the cards (QCard / .card and
   .sec-head), the fields (.fld-* / .input) and the banners
   (.note-*). Only what this screen has and no other does is
   below.

   What is here:
     .ofw-steps   the five-step rail across the top
     .ofw / -main / -rail   the working surface and its rail
     .ofw-who     who the offer is for
     .ofw-sum     what it comes to, on every step
     .ofw-total   the same figure on the step it is typed,
                  and in the offers list's create drawer
     .ofw-chain   the approval route, drawn as a route
     .ofw-pick    the by-hand approver picker
     .ofw-pane / -stage / -paper   the letter and its sheet
     .ofw-tok     the merge-field palette
     .ofw-foot    the wizard's floor
   ============================================================ */

/* ---------- Step rail ---------- */

.ofw-steps {
    display: flex;
    align-items: center;
    margin: 0 0 12px;
    padding: 12px 16px;
    list-style: none;
    background: var(--surface-raised);
    border: 1px solid var(--border-default);
    border-radius: var(--r-lg);
    box-shadow: var(--shadow-xs);
}

/* Each step takes an equal share of the bar and spends what its label does
   not need on the connector to the next one, so the rhythm holds whether the
   step is called "Letter" or "Review & Send". */
.ofw-step {
    display: flex;
    align-items: center;
    gap: 8px;
    flex: 1 1 0;
    min-width: 0;
}

.ofw-step:last-child { flex: 0 1 auto; }

.ofw-step::after {
    content: '';
    flex: 1 1 24px;
    min-width: 16px;
    height: 2px;
    margin-inline: 10px;
    border-radius: var(--r-full);
    background: var(--n-150);
}

.ofw-step:last-child::after { display: none; }

/* The connector belongs to the step behind it: filled means "you have been
   past here", which is the one thing a five-step bar has to say at a glance. */
.ofw-step.is-done::after { background: var(--p-700); }

.ofw-step-n {
    display: grid;
    place-items: center;
    width: 26px;
    height: 26px;
    flex-shrink: 0;
    border: 1.5px solid var(--border-default);
    border-radius: var(--r-full);
    background: var(--surface-raised);
    font-size: var(--fs-xs);
    font-weight: var(--fw-semibold);
    font-variant-numeric: tabular-nums;
    color: var(--text-tertiary);
    transition: background var(--dur-fast) var(--ease-out),
                border-color var(--dur-fast) var(--ease-out),
                color var(--dur-fast) var(--ease-out);
}

.ofw-step.is-done .ofw-step-n {
    border-color: var(--p-700);
    background: var(--p-700);
    color: var(--text-inverse);
}

/* Where you are is a ring, not a fill. Filled, it is the same shape as a
   step already done, and the bar then only says how far you got rather than
   where you are standing. */
.ofw-step.is-now .ofw-step-n {
    border-color: var(--p-700);
    color: var(--p-700);
    box-shadow: var(--shadow-focus);
}

.ofw-step-l {
    font-size: var(--fs-sm);
    font-weight: var(--fw-medium);
    color: var(--text-tertiary);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.ofw-step.is-done .ofw-step-l { color: var(--text-secondary); }

.ofw-step.is-now .ofw-step-l {
    font-weight: var(--fw-semibold);
    color: var(--text-primary);
}

/* A refusal from the service belongs above the step it was refused on. */
.ofw-err { margin-block-end: 12px; }


/* ---------- Working surface ---------- */

.ofw {
    display: grid;
    grid-template-columns: minmax(0, 1fr) 300px;
    gap: 12px;
    align-items: start;
}

.ofw-main {
    display: flex;
    flex-direction: column;
    gap: 12px;
    min-width: 0;
}

/* Sticky, because the point of the rail is that the candidate and the total
   are on screen while the letter step scrolls past them. */
.ofw-rail {
    position: sticky;
    inset-block-start: 12px;
    display: flex;
    flex-direction: column;
    gap: 10px;
    min-width: 0;
}


/* ---------- Who the offer is for ---------- */

.ofw-who {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 12px 14px;
}

.ofw-who-txt { min-width: 0; }

.ofw-who-n {
    font-size: var(--fs-md);
    font-weight: var(--fw-semibold);
    color: var(--text-primary);
}

.ofw-who-r {
    font-size: var(--fs-sm);
    color: var(--text-secondary);
}

.ofw-who-c {
    display: flex;
    align-items: center;
    gap: 4px;
    margin-block-start: 2px;
    font-size: var(--fs-xs);
    color: var(--text-tertiary);
}

.ofw-who-c svg { flex-shrink: 0; }

/* Step one is the step where there is no candidate yet, so the rail has to
   say "not picked" rather than render an empty card and look broken. */
.ofw-who-empty {
    display: flex;
    align-items: center;
    gap: 8px;
    font-size: var(--fs-sm);
    line-height: 1.4;
    color: var(--text-tertiary);
}

.ofw-who-empty svg { flex-shrink: 0; }


/* ---------- What it comes to ---------- */

.ofw-sum { padding: 14px; }

.ofw-sum-head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 8px;
    padding-block-end: 10px;
    margin-block-end: 10px;
    border-block-end: 1px solid var(--border-subtle);
}

.ofw-sum-k {
    font-size: var(--fs-2xs);
    font-weight: var(--fw-bold);
    letter-spacing: 0.07em;
    text-transform: uppercase;
    color: var(--text-tertiary);
}

/* The figure the whole wizard is about. Tabular, so it does not shift
   sideways a digit at a time while a base salary is being typed. */
.ofw-sum-v {
    font-family: var(--font-mono);
    font-size: var(--fs-xl);
    font-weight: var(--fw-bold);
    font-variant-numeric: tabular-nums;
    color: var(--p-700);
}

.ofw-sum-rows {
    display: flex;
    flex-direction: column;
    gap: 7px;
    margin: 0;
}

.ofw-sum-row {
    display: grid;
    grid-template-columns: minmax(0, auto) minmax(0, 1fr);
    gap: 10px;
    align-items: baseline;
}

.ofw-sum-row dt {
    font-size: var(--fs-xs);
    color: var(--text-tertiary);
}

/* An approver route is three job titles joined by arrows and a template name
   can be longer than the rail is wide. Both wrap rather than widening it. */
.ofw-sum-row dd {
    margin: 0;
    font-size: var(--fs-sm);
    font-weight: var(--fw-semibold);
    line-height: 1.4;
    text-align: end;
    color: var(--text-primary);
    overflow-wrap: anywhere;
}


/* ---------- The total, on the step it is typed ----------
   The rail carries it too, but a figure the reader is actively changing
   should not require a glance sideways to confirm. */

.ofw-total {
    display: flex;
    align-items: flex-start;
    gap: 10px;
    margin-block-start: 14px;
    padding: 12px 14px;
    background: var(--gold-50);
    border: 1px solid var(--gold-200);
    border-radius: var(--r-lg);
}

.ofw-total-mark {
    display: grid;
    place-items: center;
    width: 26px;
    height: 26px;
    flex-shrink: 0;
    border-radius: var(--r-md);
    background: var(--gold-500);
    color: var(--p-900);
}

.ofw-total-b { flex: 1; min-width: 0; }

.ofw-total-k {
    font-size: var(--fs-2xs);
    font-weight: var(--fw-bold);
    letter-spacing: 0.07em;
    text-transform: uppercase;
    color: var(--gold-700);
}

.ofw-total-v {
    margin-block: 1px 5px;
    font-family: var(--font-mono);
    font-size: var(--fs-2xl);
    font-weight: var(--fw-bold);
    font-variant-numeric: tabular-nums;
    line-height: 1.15;
    color: var(--text-primary);
}

/* The parts, in the order they add up. Separated by a rule the stylesheet
   draws rather than by punctuation baked into a translated string. */
.ofw-total-parts {
    display: flex;
    flex-wrap: wrap;
    gap: 3px 10px;
    font-size: var(--fs-xs);
    line-height: 1.5;
    color: var(--text-secondary);
}

.ofw-total-parts > span + span::before {
    content: '';
    display: inline-block;
    width: 3px;
    height: 3px;
    margin-inline-end: 9px;
    vertical-align: 2px;
    border-radius: var(--r-full);
    background: var(--gold-400);
}

/* The offers list's create drawer shows the same strip. In a .drw-body the
   column gap already provides the space this margin was for. */
.drw-body > .ofw-total { margin-block-start: 0; }


/* ---------- An opt-in that is not a stray tick ---------- */

.ofw-opt {
    display: flex;
    align-items: center;
    gap: 10px;
    margin-block-start: 12px;
    padding: 10px 12px;
    background: var(--surface-sunken);
    border: 1px solid var(--border-subtle);
    border-radius: var(--r-md);
    font-size: var(--fs-sm);
    color: var(--text-primary);
    cursor: pointer;
    transition: border-color var(--dur-fast) var(--ease-out);
}

.ofw-opt:hover { border-color: var(--border-strong); }

.ofw-opt input {
    flex-shrink: 0;
    accent-color: var(--p-700);
}

.ofw-opt-mark {
    display: grid;
    place-items: center;
    width: 22px;
    height: 22px;
    flex-shrink: 0;
    border-radius: var(--r-sm);
    background: var(--gold-100);
    color: var(--gold-700);
}

.ofw-opt-t { min-width: 0; }


/* ---------- Money and dates ----------
   Figures are set in the mono face for the same reason the summary is: a
   column of salaries that shifts as it is typed cannot be read down. */

.ofw-num {
    font-family: var(--font-mono);
    font-variant-numeric: tabular-nums;
}

/* The currency prefix. Wider than .input's own 34px because the fallback is
   a three-letter code, not a symbol. */
.input-wrap .ofw-num.has-prefix { padding-inline-start: 44px; }

.ofw-cur {
    font-family: var(--font-mono);
    font-size: var(--fs-xs);
    font-weight: var(--fw-semibold);
    color: var(--text-tertiary);
}


/* ---------- Approval chain ----------
   A chain is a sequence, so it is drawn as one: a rail down the numbers,
   rather than four cards that happen to be stacked. */

.ofw-help {
    margin: 0 0 12px;
    font-size: var(--fs-sm);
    line-height: 1.5;
    color: var(--text-secondary);
}

.ofw-chain {
    display: flex;
    flex-direction: column;
    gap: 8px;
    margin: 0 0 12px;
    padding: 0;
    list-style: none;
}

.ofw-chain-step {
    position: relative;
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 10px 12px;
    background: var(--surface-raised);
    border: 1px solid var(--border-subtle);
    border-radius: var(--r-md);
}

/* Drawn step to step rather than as one line behind the list, so it stops at
   the last node instead of trailing off under the band verdict. 25px is the
   card's padding plus half the node. */
.ofw-chain-step:not(:last-child)::before {
    content: '';
    position: absolute;
    inset-block-start: 100%;
    inset-inline-start: 25px;
    width: 2px;
    height: 8px;
    background: var(--p-200);
}

.ofw-chain-n {
    display: grid;
    place-items: center;
    width: 26px;
    height: 26px;
    flex-shrink: 0;
    border: 1px solid var(--p-200);
    border-radius: var(--r-full);
    background: var(--p-50);
    font-size: var(--fs-xs);
    font-weight: var(--fw-bold);
    font-variant-numeric: tabular-nums;
    color: var(--p-700);
}

.ofw-chain-b { flex: 1; min-width: 0; }

.ofw-chain-t {
    font-size: var(--fs-sm);
    font-weight: var(--fw-semibold);
    color: var(--text-primary);
}

.ofw-chain-w {
    margin-block-start: 1px;
    font-size: var(--fs-xs);
    line-height: 1.45;
    color: var(--text-tertiary);
}

/* A step the band rule added is not one the workspace configured, and that
   difference is the whole reason this panel is shown before sending. */
.ofw-chain-step.is-auto {
    background: var(--warning-50);
    border-color: var(--warning-100);
}

.ofw-chain-step.is-auto .ofw-chain-n {
    border-color: var(--warning-500);
    background: var(--warning-100);
    color: var(--warning-700);
}

.ofw-chain-step.is-auto::before { background: var(--warning-500); }

.ofw-chain-tag {
    flex-shrink: 0;
    font-size: var(--fs-2xs);
    font-weight: var(--fw-semibold);
    color: var(--warning-700);
}

.ofw-band-more {
    margin-block-start: 4px;
    font-size: var(--fs-sm);
}

.ofw-chain-act { margin-block-start: 12px; }


/* ---------- Choosing approvers by hand ---------- */

.ofw-picks {
    display: flex;
    flex-direction: column;
    gap: 6px;
}

.ofw-pick {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 8px 10px;
    background: var(--surface-raised);
    border: 1px solid var(--border-subtle);
    border-radius: var(--r-md);
    cursor: pointer;
    transition: background var(--dur-fast) var(--ease-out),
                border-color var(--dur-fast) var(--ease-out);
}

.ofw-pick:hover {
    background: var(--surface-hover);
    border-color: var(--border-strong);
}

.ofw-pick.is-on {
    background: var(--p-50);
    border-color: var(--p-300);
}

/* The row is the control; the box only reports what the row decided. Left
   clickable it toggles itself, then the re-render puts it back, which reads
   as a tick that refused. */
.ofw-pick input {
    flex-shrink: 0;
    accent-color: var(--p-700);
    pointer-events: none;
}

.ofw-pick-b { flex: 1; min-width: 0; }

.ofw-pick-n {
    font-size: var(--fs-sm);
    font-weight: var(--fw-semibold);
    color: var(--text-primary);
}

.ofw-pick-r {
    font-size: var(--fs-xs);
    color: var(--text-tertiary);
}


/* ---------- The letter and its sheet ---------- */

.ofw-letter {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 12px;
    align-items: start;
}

.ofw-pane {
    display: flex;
    flex-direction: column;
    min-width: 0;
}

/* The desk the sheet sits on. A white document on a white card is not a
   document, it is a paragraph; the recessed ground is what makes the preview
   read as the thing the candidate will open. */
.ofw-stage {
    flex: 1;
    padding: 14px;
    background: var(--surface-sunken);
    border-end-start-radius: var(--r-lg);
    border-end-end-radius: var(--r-lg);
}

.ofw-paper {
    padding: 22px 24px;
    background: var(--surface-raised);
    border: 1px solid var(--border-subtle);
    border-radius: var(--r-md);
    box-shadow: var(--shadow-sm);
    font-size: var(--fs-md);
    line-height: 1.75;
    color: var(--text-primary);
    white-space: pre-wrap;
    overflow-wrap: anywhere;
    overflow: auto;
}

/* Editor and preview are the same height, so the pair reads as one thing
   with two sides rather than two panels that happen to be adjacent. */
.input.ofw-body {
    display: block;
    height: 392px;
    padding: 12px 14px;
    font-family: var(--font-mono);
    font-size: var(--fs-sm);
    line-height: 1.65;
    resize: vertical;
}

.ofw-letter .ofw-paper { height: 420px; }

/* The review step has no editor beside it, so the sheet gets the width and
   as much height as it needs. */
.ofw-paper-lg { min-height: 460px; }


/* ---------- Merge-field palette ---------- */

.ofw-toks {
    margin-block-start: 14px;
    padding-block-start: 12px;
    border-block-start: 1px solid var(--border-subtle);
}

.ofw-toks-row {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
    margin-block-start: 8px;
}

/* A merge field is a literal string that gets pasted into the body, so it is
   set in the face the body is edited in: a token that looks different in the
   palette than it does in the textarea reads as a different thing. */
.ofw-tok {
    padding: 4px 8px;
    background: var(--surface-raised);
    border: 1px solid var(--border-default);
    border-radius: var(--r-sm);
    font-family: var(--font-mono);
    font-size: var(--fs-2xs);
    color: var(--text-secondary);
    cursor: pointer;
    transition: background var(--dur-fast) var(--ease-out),
                border-color var(--dur-fast) var(--ease-out),
                color var(--dur-fast) var(--ease-out);
}

.ofw-tok:hover {
    background: var(--p-50);
    border-color: var(--p-300);
    color: var(--p-700);
}

.ofw-tok:focus-visible {
    outline: none;
    box-shadow: var(--shadow-focus);
}


/* ---------- The wizard's floor ----------
   Sticky rather than fixed, and at the same offset as .set-bar so a
   recruiter moving between the settings forms and this one finds the commit
   in the same place. */

.ofw-foot {
    position: sticky;
    inset-block-end: 12px;
    /* Under the rail (10) and the page header (30), for the reason .set-bar
       gives: a bar that covered either would hide navigation to show an
       action. */
    z-index: 9;
    display: flex;
    align-items: center;
    gap: 14px;
    flex-wrap: wrap;
    margin-block-start: 12px;
    padding: 10px 14px;
    background: var(--surface-raised);
    border: 1px solid var(--border-default);
    border-radius: var(--r-lg);
    box-shadow: var(--shadow-md);
}

/* What "Create & send" actually does, beside the button that does it.
   Capped in measure so it does not push the buttons onto a line of their
   own on a wide screen. */
.ofw-foot-hint {
    flex: 1 1 240px;
    min-width: 0;
    max-width: 64ch;
    min-height: 20px;
    font-size: var(--fs-xs);
    line-height: 1.45;
    color: var(--text-tertiary);
}

.ofw-foot-acts {
    display: flex;
    align-items: center;
    gap: 8px;
    flex-wrap: wrap;
    margin-inline-start: auto;
}


/* ---------- Reflow ---------- */

@media (max-width: 1180px) {
    .ofw { grid-template-columns: minmax(0, 1fr); }

    /* Under the form there is nothing left to stick to, and two cards the
       width of the page would be two very empty cards. */
    .ofw-rail {
        position: static;
        flex-direction: row;
        flex-wrap: wrap;
    }

    .ofw-rail > * { flex: 1 1 260px; }
}

@media (max-width: 1000px) {
    .ofw-letter { grid-template-columns: minmax(0, 1fr); }

    .input.ofw-body,
    .ofw-letter .ofw-paper { height: 300px; }

    .ofw-paper-lg { min-height: 360px; }
}

/* The five labels stop fitting long before the five numbers do. Rather than
   wrap the bar to three lines, only the step you are on keeps its name -
   which is the one the bar exists to tell you. */
@media (max-width: 860px) {
    .ofw-step-l { display: none; }
    .ofw-step.is-now .ofw-step-l { display: block; }
}

@media (max-width: 560px) {
    .ofw-steps { padding: 10px 12px; }

    .ofw-paper { padding: 16px; }

    .ofw-total-v { font-size: var(--fs-xl); }

    /* The caveat takes its own line so the two terminal actions keep theirs. */
    .ofw-foot-hint { flex-basis: 100%; }

    .ofw-foot-acts {
        flex: 1 1 auto;
        margin-inline-start: 0;
    }
}

@media (prefers-reduced-motion: reduce) {
    .ofw-step-n,
    .ofw-opt,
    .ofw-pick,
    .ofw-tok { transition: none; }
}


/* ============================================================
   47. Recruiter: invoice create drawer (invd-*)

   The panel that raises an invoice, over the invoices list.
   It replaced a full page, and that move is the reason this
   block exists: a page could spend a 320px rail on a summary
   card, and 640px of drawer cannot. What the page spread
   across two columns is one column here, ordered the way an
   invoice is built - who is billed, what the fee is, what
   else is on it, when it is due, what it comes to.

   Everything structural is shared: QDrawer's chrome (.drw-*),
   .drw-field / .drw-pair for the fields, .input and .lbl for
   the controls. Only what this form has and no other does is
   below.

   What is here:
     .invd-sec      one step of the form, with its heading
     .invd-who      the placement being billed
     .invd-eq       the fee, written as the sum it is
     .invd-line     a line of the invoice
     .invd-line-fee the fee line, read-only among them
     .invd-sum      what the client will be asked for
     .invd-foot-tot the same total, pinned to the footer

   Money is set in the mono face throughout, for the reason
   the offer composer sets it there: a figure that changes as
   it is typed cannot be read down if the digits move.
   ============================================================ */

/* ---------- A step of the form ---------- */

/* A titled group, as a fieldset - which is what puts the rule
   alongside the legend rather than above it, the way the
   candidate form's six groups read. The legend is taken out of
   the fieldset's flow by the browser, so it sits in the gap it
   makes in the top border; .drw-body already spaces the groups
   apart. */
.invd-sec {
    display: flex;
    flex-direction: column;
    gap: 12px;
    margin: 0;
    padding: 14px 0 0;
    border: none;
    border-block-start: 1px solid var(--border-subtle);
    /* A fieldset will not shrink below its content on its own, and the
       lines table inside this one has 300px of fixed columns: without
       this the group widens the drawer body instead of the table
       scrolling. */
    min-inline-size: 0;
}

/* No rule above the first group: the drawer header is already that
   line, and drawn twice it reads as an empty section. Of-type, not
   first-child - an error alert can precede the groups, and it is not
   a fieldset. */
.drw-body > .invd-sec:first-of-type {
    padding-block-start: 0;
    border-block-start: none;
}

.invd-sec-h {
    margin: 0;
    /* A rendered legend carries the browser's own inline padding, which
       would indent the heading past every label under it. */
    padding: 0;
    padding-inline-end: 8px;
    font-size: var(--fs-2xs);
    font-weight: var(--fw-bold);
    letter-spacing: 0.07em;
    text-transform: uppercase;
    color: var(--text-tertiary);
}

/* A heading with controls of its own - the three add buttons.
   They wrap under it before either gets clipped. */
.invd-sec-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    flex-wrap: wrap;
}

.invd-adds {
    display: flex;
    align-items: center;
    gap: 2px;
}


/* ---------- Who is being billed ---------- */

/* Before a placement is picked there is nothing to show, and an
   empty card reads as a broken one. This says what the picker
   above it is for instead. */
.invd-blank {
    display: flex;
    align-items: flex-start;
    gap: 8px;
    padding: 12px 14px;
    border: 1px dashed var(--border-default);
    border-radius: var(--r-lg);
    background: var(--surface-sunken);
    font-size: var(--fs-sm);
    line-height: var(--lh-snug);
    color: var(--text-tertiary);
}

.invd-blank svg { flex-shrink: 0; margin-block-start: 1px; }

.invd-who {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 12px 14px;
}

.invd-who-txt { flex: 1; min-width: 0; }

.invd-who-n {
    font-size: var(--fs-md);
    font-weight: var(--fw-semibold);
    color: var(--text-primary);
}

.invd-who-r {
    font-size: var(--fs-sm);
    color: var(--text-secondary);
}

.invd-who-c {
    display: flex;
    align-items: center;
    gap: 4px;
    margin-block-start: 2px;
    font-size: var(--fs-xs);
    color: var(--text-tertiary);
}

.invd-who-c svg { flex-shrink: 0; }

/* The two facts the fee is computed from, set against the end of
   the card so the eye reads down the numbers, not across. */
.invd-who-meta {
    display: flex;
    flex-direction: column;
    align-items: flex-end;
    gap: 3px;
    flex-shrink: 0;
    text-align: end;
}

.invd-who-v {
    font-family: var(--font-mono);
    font-size: var(--fs-md);
    font-weight: var(--fw-semibold);
    font-variant-numeric: tabular-nums;
    color: var(--text-primary);
}

.invd-who-k {
    font-size: var(--fs-2xs);
    font-weight: var(--fw-semibold);
    letter-spacing: 0.05em;
    text-transform: uppercase;
    color: var(--text-tertiary);
}


/* ---------- The fee, as the sum it is ---------- */

/* Base and rate are both typed by hand, so the fee is shown as
   the multiplication rather than as its result: a rate entered
   as 20 where 2.0 was meant is obvious in "$160,000 x 20%" and
   invisible in "$32,000". */
.invd-eq {
    display: flex;
    align-items: baseline;
    flex-wrap: wrap;
    gap: 4px 8px;
    padding: 10px 12px;
    border: 1px solid var(--border-subtle);
    border-radius: var(--r-md);
    background: var(--surface-sunken);
}

.invd-eq-k {
    margin-inline-end: auto;
    font-size: var(--fs-xs);
    font-weight: var(--fw-semibold);
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--text-tertiary);
}

.invd-eq-m,
.invd-eq-v {
    font-family: var(--font-mono);
    font-variant-numeric: tabular-nums;
}

.invd-eq-m {
    font-size: var(--fs-sm);
    color: var(--text-secondary);
}

.invd-eq-op {
    font-size: var(--fs-xs);
    color: var(--text-disabled);
}

.invd-eq-v {
    font-size: var(--fs-lg);
    font-weight: var(--fw-bold);
    color: var(--gold-800);
}

/* Where the rate came from. A note, not a banner: it explains a
   number that is already correct. */
.invd-ai {
    display: flex;
    align-items: flex-start;
    gap: 6px;
    margin: 0;
    font-size: var(--fs-xs);
    line-height: var(--lh-snug);
    color: var(--text-tertiary);
}

.invd-ai svg {
    flex-shrink: 0;
    margin-block-start: 1px;
    color: var(--gold-600);
}


/* ---------- Added lines ---------- */

.invd-lines {
    display: flex;
    flex-direction: column;
    gap: 6px;
}

/* Glyph, description, qty, unit price, amount, remove. The
   description takes whatever the four fixed columns leave. */
.invd-line {
    display: grid;
    grid-template-columns: 22px minmax(0, 1fr) 58px 92px 84px 24px;
    align-items: center;
    gap: 6px;
}

/* Column names, once, above the rows. aria-hidden in the markup:
   every control carries its own label, and a screen reader
   reading six headings before each row is worse than none. */
.invd-line-h {
    font-size: var(--fs-2xs);
    font-weight: var(--fw-semibold);
    letter-spacing: 0.05em;
    text-transform: uppercase;
    color: var(--text-tertiary);
}

.invd-line-h .invd-e { text-align: end; }

/* B, E, C - which kind of line it is, in the tone its amount
   will read in. */
.invd-glyph {
    display: grid;
    place-items: center;
    width: 22px;
    height: 22px;
    border-radius: var(--r-full);
    font-size: var(--fs-2xs);
    font-weight: var(--fw-bold);
    background: var(--n-100);
    color: var(--text-secondary);
}

.invd-glyph-bonus {
    background: var(--success-100);
    color: var(--success-700);
}

.invd-glyph-credit {
    background: var(--danger-50);
    color: var(--danger-600);
}

.invd-line .invd-num { text-align: end; }

/* ---------- The fee, as the line it becomes ---------- */

/* The fee is the invoice's own line, not a category added to it, so its
   glyph is the brand's rather than one of the three tones an added line
   can take. */
.invd-glyph-fee {
    background: var(--p-50);
    color: var(--p-700);
}

/* Read here, edited in the calculation above. Deliberately not inputs: a
   disabled control invites the click it then refuses, and this row is a
   statement of what the client will read rather than a field. The tint is
   what separates it from the rows that can be typed into. */
.invd-line-fee {
    padding-block: 5px;
    padding-inline: 6px;
    margin-inline: -6px;
    border-radius: var(--r-md);
    background: var(--surface-sunken);
}

.invd-line-d {
    font-size: var(--fs-sm);
    color: var(--text-secondary);
}

/* Qty and unit price on the fee row - the same columns the editable rows
   set in mono, one step quieter because they cannot be changed here. */
.invd-line-q {
    font-family: var(--font-mono);
    font-size: var(--fs-sm);
    font-variant-numeric: tabular-nums;
    text-align: end;
    color: var(--text-tertiary);
}

.invd-line-t {
    font-family: var(--font-mono);
    font-size: var(--fs-sm);
    font-weight: var(--fw-semibold);
    font-variant-numeric: tabular-nums;
    text-align: end;
    color: var(--text-primary);
}

/* A credit is money going the other way, and it is the one line
   whose sign changes the total. */
.invd-line-neg { color: var(--danger-600); }

.invd-del {
    display: grid;
    place-items: center;
    width: 24px;
    height: 24px;
    padding: 0;
    border: none;
    border-radius: var(--r-sm);
    background: transparent;
    color: var(--text-tertiary);
    cursor: pointer;
    transition: background-color var(--dur-fast) var(--ease-out),
                color var(--dur-fast) var(--ease-out);
}

.invd-del:hover { background: var(--danger-50); color: var(--danger-600); }
.invd-del:focus-visible { outline: none; box-shadow: var(--shadow-focus); }


/* ---------- Dates and tax ---------- */

/* Three short fields that are one fact: when it is raised, when
   it is due, what is added. They break to two columns and then
   to one rather than shrinking a date input past its own text. */
.invd-trio {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
    gap: 12px;
}

.invd-num {
    font-family: var(--font-mono);
    font-variant-numeric: tabular-nums;
}

/* The currency prefix is a symbol, but the fallback is a
   three-letter code - the same reason .ofw-num widens its own. */
.input-wrap .invd-num.has-prefix { padding-inline-start: 44px; }

.invd-cur {
    font-family: var(--font-mono);
    font-size: var(--fs-xs);
    font-weight: var(--fw-semibold);
    color: var(--text-tertiary);
}

.invd-ta {
    height: auto;
    padding-block: 8px;
    line-height: var(--lh-snug);
    font-family: inherit;
    resize: vertical;
}

.invd-notes { margin-block-start: 2px; }


/* ---------- What the client is asked for ---------- */

/* The figure the whole drawer exists to produce, in the gold this
   product uses for money being committed to. It is the last thing
   above the footer, so it is what the eye passes through on the
   way to Save and send. */
.invd-sum {
    display: flex;
    flex-direction: column;
    gap: 7px;
    padding: 12px 14px;
    border: 1px solid var(--gold-200);
    border-radius: var(--r-lg);
    background: var(--gold-50);
}

.invd-sum-r {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 12px;
    font-size: var(--fs-sm);
    color: var(--text-secondary);
}

.invd-sum-m {
    font-family: var(--font-mono);
    font-variant-numeric: tabular-nums;
    color: var(--text-primary);
}

.invd-sum-tot {
    padding-block-start: 8px;
    border-block-start: 1px solid var(--gold-200);
    font-size: var(--fs-md);
    font-weight: var(--fw-bold);
    color: var(--text-primary);
}

.invd-sum-v {
    font-family: var(--font-mono);
    font-size: var(--fs-2xl);
    font-weight: var(--fw-bold);
    font-variant-numeric: tabular-nums;
    line-height: 1.15;
    color: var(--gold-800);
}


/* ---------- The same total, on the footer ---------- */

/* The summary above is the arithmetic; this is the figure Save and send
   commits to, and a form with five groups in it scrolls the arithmetic out
   of sight long before the button is pressed. Same number, always visible.
   .drw-foot-lead in the markup puts it at the far end of the bar and lets
   it wrap to its own line before the buttons squeeze. */
.invd-foot-tot {
    display: flex;
    align-items: baseline;
    gap: 8px;
}

.invd-foot-k {
    font-size: var(--fs-2xs);
    font-weight: var(--fw-bold);
    letter-spacing: 0.07em;
    text-transform: uppercase;
    color: var(--text-tertiary);
}

.invd-foot-v {
    font-family: var(--font-mono);
    font-size: var(--fs-lg);
    font-weight: var(--fw-bold);
    font-variant-numeric: tabular-nums;
    color: var(--text-primary);
}


/* On a phone the drawer is the screen. A line stops being a row
   and becomes a small block: the description keeps the width and
   the two numbers take their own lines under it, rather than
   every column shrinking to four characters. */
@media (max-width: 560px) {
    .invd-line {
        grid-template-columns: 22px minmax(0, 1fr) minmax(0, 1fr) 24px;
        gap: 6px 8px;
    }

    /* The names go with the row that no longer lines up under them;
       each control keeps its own label. */
    .invd-line-h { display: none; }

    /* Row one is the description and the way to remove it, row two
       the two numbers, row three what they come to. */
    .invd-line > .input:not(.invd-num) { grid-column: 2 / 4; }

    /* The fee row folds the same way, its description in place of the
       input the editable rows have there. */
    .invd-line-d { grid-column: 2 / 4; }
    .invd-line-q { text-align: start; }
    .invd-del { grid-column: 4 / 5; grid-row: 1; }
    .invd-line .invd-num { text-align: start; }
    .invd-line-t { grid-column: 2 / 5; }

    .invd-sum-v { font-size: var(--fs-xl); }
}

/* ============================================================
   48. Recruiter: Reports (rep-*)

   /recruiter/reports - six reports behind one tab row, and the
   last recruiter screen still drawn entirely in style
   attributes.

   Four things were wrong with it, and none of them were the
   numbers:

     - Type was hard-coded at eleven different sizes, four of
       them fractional (11.5px, 12.5px), and the mono family was
       written out by hand in eight places rather than taken
       from --font-mono. The funnel invented its own radius with
       a fallback for a token that has been defined since the
       first commit.

     - Every grid was pinned. 1fr 1fr for the chart pairs,
       repeat(3, 1fr) for the KPI rows and 1fr 2fr on the fill
       tab, at every width - so a sixth of a laptop held a 32px
       number under a two-line label, and the funnel stage
       column clipped the longer stage names. The bands are the
       shared auto-fit .pg-kpis and .pg-grid2 now, which already
       collapse at 1100px and 760px.

     - The funnel and the drop-off list each drew their own bar
       out of two absolutely-positioned divs inside a relative
       one, with the count laid over the fill and its colour
       switched on whether the bar was wider than 30%. That is
       the .meter primitive with a legibility bug attached: the
       number flipped from white to dark mid-column as the data
       moved. Both are .meter now, with the number outside the
       track where it does not have to fight the fill.

     - The adverse-impact tables were hand-built - their own
       border-collapse, their own uppercase header, their own
       tabular-nums - one padding step off .tbl, which every
       neighbouring page uses.

   Everything else is a shared primitive: the header (.pg-head
   via PageHeader), the KPI band (.pg-kpis with KpiTile), the
   working surface (.pg-body / .pg-grid2), the table (.tbl), the
   bar (.meter) and the skeletons (.skel). Only what this screen
   has and no other does is below.

   What is here:
     .rep-none     a card with nothing to say, said in one line
     .rep-fn-*     the pipeline funnel, with a column header
     .rep-m-*      the assessment-health metric tiles
     .rep-do-*     the where-candidates-stop rows
     .rep-adv-*    the two adverse-impact readouts
   ============================================================ */

/* A card body whose answer is "nothing yet". One line, tertiary,
   at the card's own padding - not a QEmptyState, which is a
   whole panel and would give a sentence the weight of a screen
   inside a card the reader is scanning past. */
.rep-none {
    margin: 0;
    font-size: var(--fs-sm);
    line-height: var(--lh-relaxed);
    color: var(--text-tertiary);
}

/* ============================================================
   The pipeline funnel

   Stage, bar, count, carry-through. The carry-through column is
   the reason the funnel is worth drawing at all - a stage is
   only interesting relative to the one above it - and it had no
   header, so the reader had to work out what the trailing
   percentage meant from the fact that the first row was blank.
   ============================================================ */

.rep-fn {
    display: flex;
    flex-direction: column;
    gap: 8px;
}

/* The stage column is wide enough for the longest seeded stage
   name without wrapping; the count and carry-through columns are
   fixed so the digits line up down the page rather than tracking
   the right edge of a bar that changes length. */
.rep-fn-row {
    display: grid;
    grid-template-columns: 132px minmax(0, 1fr) 56px 76px;
    align-items: center;
    gap: 12px;
}

.rep-fn-head {
    padding-block-end: 2px;
    border-block-end: 1px solid var(--border-subtle);
    font-size: var(--fs-2xs);
    font-weight: var(--fw-semibold);
    letter-spacing: 0.05em;
    text-transform: uppercase;
    color: var(--text-tertiary);
}

.rep-fn-l {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-size: var(--fs-sm);
    color: var(--text-secondary);
    text-transform: capitalize;
}

/* The header label is a written word, not a stage key out of the
   database, so it does not take the row capitalisation. */
.rep-fn-head .rep-fn-l { text-transform: uppercase; }

.rep-fn .meter { --meter-h: 22px; }

.rep-fn-n,
.rep-fn-c {
    font-family: var(--font-mono);
    font-variant-numeric: tabular-nums;
    font-size: var(--fs-sm);
    text-align: end;
}

.rep-fn-n {
    font-weight: var(--fw-semibold);
    color: var(--text-primary);
}

.rep-fn-c { color: var(--text-secondary); }

/* Where a stage is losing people. Under half carrying through
   from the stage above is the line worth marking, and marking it
   is what saves the reader from comparing every pair of numbers
   by eye. */
.rep-fn-c.is-drop {
    font-weight: var(--fw-semibold);
    color: var(--danger-700);
}

/* The header row borrows the two numeric class names for its
   alignment and wants none of their type. */
.rep-fn-head .rep-fn-n,
.rep-fn-head .rep-fn-c {
    font-family: inherit;
    font-size: inherit;
    font-weight: inherit;
    color: inherit;
}

/* Below this the four columns cannot all keep their width, and
   the one to give up is the bar: the numbers are the report and
   the bar is the reading aid. */
@media (max-width: 700px) {
    .rep-fn-row { grid-template-columns: minmax(0, 1fr) 56px 68px; }
    .rep-fn-row .meter { display: none; }
    .rep-fn-head > :nth-child(2) { display: none; }
}

/* ============================================================
   Assessment health

   One tile per measured thing, and a tile that cannot be
   measured says so instead of showing a zero. The watch state is
   a border and a tint rather than a red number, because the
   number is not the problem - what it crossed is.
   ============================================================ */

.rep-m {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(230px, 1fr));
    gap: 12px;
}

.rep-m-card {
    padding: 12px 14px;
    border: 1px solid var(--border-subtle);
    border-radius: var(--r-md);
    background: var(--n-25);
}

.rep-m-card.is-watch {
    border-color: var(--danger-200);
    background: var(--danger-50);
}

.rep-m-l {
    display: block;
    font-size: var(--fs-2xs);
    font-weight: var(--fw-medium);
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--text-tertiary);
}

.rep-m-v {
    margin: 5px 0 0;
    font-family: var(--font-mono);
    font-variant-numeric: tabular-nums;
    font-size: var(--fs-xl);
    font-weight: var(--fw-semibold);
    letter-spacing: -0.02em;
    color: var(--text-primary);
}

/* A dash is not a value. Dropping it to tertiary stops a row of
   tiles reading as a row of results with some zeroes in it. */
.rep-m-v.is-idle { color: var(--text-tertiary); }

.rep-m-s {
    margin: 4px 0 0;
    font-size: var(--fs-xs);
    line-height: 1.45;
    color: var(--text-tertiary);
}

.rep-m-w {
    display: flex;
    align-items: flex-start;
    gap: 6px;
    margin: 7px 0 0;
    font-size: var(--fs-xs);
    line-height: 1.45;
    color: var(--danger-700);
}

.rep-m-w svg {
    flex-shrink: 0;
    margin-block-start: 1px;
}

/* ============================================================
   Where candidates stop

   Question number, how many got that far, and the percentage. A
   cliff between two adjacent rows is the finding, so the rows
   are tight - the eye reads a step down a short list and does
   not read one down a spaced-out one.
   ============================================================ */

.rep-do {
    display: flex;
    flex-direction: column;
    gap: 6px;
}

.rep-do-row {
    display: grid;
    grid-template-columns: 84px minmax(0, 1fr) 48px;
    align-items: center;
    gap: 12px;
}

.rep-do-l {
    font-size: var(--fs-sm);
    color: var(--text-secondary);
}

.rep-do .meter { --meter-h: 18px; }

.rep-do-n {
    font-family: var(--font-mono);
    font-variant-numeric: tabular-nums;
    font-size: var(--fs-xs);
    text-align: end;
    color: var(--text-secondary);
}

/* ============================================================
   Adverse impact

   Two cards - the assessment gate and the AI screen - reading
   identically on purpose. A reader comparing them needs the
   suppression wording, the four-fifths colouring and the table
   proportions to be the same, or the difference between the two
   readouts looks like a difference in the finding.
   ============================================================ */

.rep-adv-intro {
    margin: 0 0 14px;
    font-size: var(--fs-sm);
    line-height: var(--lh-relaxed);
    color: var(--text-secondary);
}

/* Coverage, and the models behind the decisions. Ruled off from
   the tables below because it qualifies all of them: a ratio off
   a tenth of the candidates describes that tenth, and that has
   to be read before the first table rather than after the last. */
.rep-adv-cov {
    margin-block-end: 16px;
    padding-block-end: 12px;
    border-block-end: 1px solid var(--border-subtle);
}

.rep-adv-cov p {
    margin: 0;
    font-size: var(--fs-xs);
    line-height: 1.5;
    color: var(--text-tertiary);
}

.rep-adv-cov p + p { margin-block-start: 6px; }

.rep-adv-dim + .rep-adv-dim { margin-block-start: 18px; }

.rep-adv-l {
    margin: 0 0 8px;
    font-size: var(--fs-2xs);
    font-weight: var(--fw-semibold);
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--text-secondary);
}

/* .tbl insets its first and last cells by 20px, for a card that
   pads nothing. Here it sits inside a padded card body, so the
   inset is cancelled or the columns stand off the heading above
   them. */
.rep-adv-dim .tbl thead th:first-child,
.rep-adv-dim .tbl tbody td:first-child { padding-inline-start: 0; }

.rep-adv-dim .tbl thead th:last-child,
.rep-adv-dim .tbl tbody td:last-child { padding-inline-end: 0; }

/* A group selected at under four-fifths of the reference rate.
   The whole row, not just the ratio: the rate and the decision
   count are what you read next, and colouring one cell sends the
   eye to the number that is only the symptom. */
.rep-adv-dim .tbl tbody tr.is-below td { color: var(--danger-700); }

.rep-adv-dim .tbl tbody tr.is-below td:last-child { font-weight: var(--fw-semibold); }

.rep-adv-note {
    display: flex;
    align-items: flex-start;
    gap: 6px;
    margin: 7px 0 0;
    font-size: var(--fs-xs);
    line-height: 1.5;
    color: var(--text-tertiary);
}

.rep-adv-note.is-flag { color: var(--danger-700); }

.rep-adv-note svg {
    flex-shrink: 0;
    margin-block-start: 1px;
}

/* ============================================================
   49. Recruiter: Career site builder (csb-*)

   /recruiter/career-site - the page a recruiter builds a public
   site on, and until now the one recruiter screen whose whole
   layout lived in style attributes: a little over a hundred of
   them across seven tabs, including the three-column editor,
   the browser mock and every panel inside it.

   What was wrong with it, beyond the longhand:

     - The identity bar was one nowrap flex row carrying a site
       picker, three metrics, a publish state and four actions.
       There is no width at which all of that fits, so the state
       wrapped to two lines inside a 36px-tall row and the
       buttons kept squeezing until their labels clipped. It
       wraps as three groups now - identity, metrics, actions -
       and the actions stay on the row's trailing edge.

     - Type was hard-coded at nine sizes and five weights, four
       of the sizes fractional, and the mono family was written
       out by hand in fourteen places. The panel headers, the
       field labels and the captions each had two spellings.

     - The canvas is the only part a recruiter looks at, and it
       had the least of the page's attention: every block wore a
       permanent dark tag over its top-left corner, so a preview
       of a hero read as a preview with a label stuck on it. The
       tag belongs to the block you are pointing at or have
       selected, which is a hover state, not a paint layer.

     - The three rails agreed on nothing. The pages rail and the
       inspector had different header heights, different section
       padding and different active states, and the block
       library's tiles were the only buttons on the screen with
       a border and no hover.

   The bands, the notes, the tab row, the fields and the empty
   states are the shared primitives (.pg-*, .note, .tabs,
   .fld-*, QEmptyState). Only what this screen has and no other
   does is below.

   What is here:
     .csb-bar     the site identity bar and its picker
     .csb-grid    the three-column editor frame
     .csb-rail    the pages rail
     .csb-canvas  the device toolbar, browser mock and paper
     .csb-insp    the right-hand inspector and block library
     .csb-dns     the custom-domain records
     .csb-ver     the publish history rows
   ============================================================ */

/* ---------- The identity bar ----------
   Who you are editing, how it is doing, and what you can do to
   it. Three groups on one row while there is room, and three
   rows in that order when there is not: the actions come last
   because they are the only group you reach for after reading
   either of the others. */
.csb-bar {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 10px 14px;
    position: relative;
    margin-block-end: 14px;
    padding: 10px 12px;
}

/* The picker is a button sitting on a card, so it carries no
   chrome of its own until it is pointed at. */
.csb-site {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 5px 10px 5px 5px;
    border-radius: var(--r-md);
    text-align: start;
}

.csb-site:hover { background: var(--surface-hover); }

/* The brand colour comes off the site record, so it arrives as
   an inline --csb-mark and everything else about the tile is
   here. The inset highlight keeps a pale brand colour from
   reading as an unfinished square. */
.csb-mark {
    display: grid;
    place-items: center;
    flex-shrink: 0;
    width: 36px;
    height: 36px;
    border-radius: var(--r-md);
    background: var(--csb-mark, var(--p-700));
    box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.22),
                inset 0 0 0 1px rgba(15, 19, 32, 0.08);
    color: var(--n-0);
    font-size: var(--fs-lg);
    font-weight: var(--fw-bold);
    line-height: 1;
}

.csb-mark-sm {
    width: 26px;
    height: 26px;
    border-radius: var(--r-sm);
    font-size: var(--fs-xs);
}

.csb-site-name {
    display: flex;
    align-items: center;
    gap: 6px;
    font-size: var(--fs-base);
    font-weight: var(--fw-semibold);
    color: var(--text-primary);
}

/* The address line: the domain the site answers on, then its
   state. Mono because it is a URL, and a reader checks a URL
   character by character. */
.csb-site-meta {
    display: flex;
    align-items: center;
    gap: 6px;
    margin-block-start: 1px;
    font-family: var(--font-mono);
    font-size: var(--fs-2xs);
    color: var(--text-tertiary);
}

/* draft / published, as a dot and a word rather than a coloured
   bullet character typed into the markup. */
.csb-live {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    font-weight: var(--fw-semibold);
    color: var(--warning-700);
}

.csb-live::before {
    content: '';
    width: 5px;
    height: 5px;
    border-radius: var(--r-full);
    background: currentColor;
}

.csb-live.is-live { color: var(--success-700); }

.csb-bar-sep {
    align-self: stretch;
    width: 1px;
    min-height: 32px;
    background: var(--border-default);
}

/* ---------- Metrics ----------
   Three facts about the last thirty days. They sit between the
   identity and the actions because they are the reason to
   publish, and they keep their own row once the bar wraps. */
.csb-stats {
    display: flex;
    align-items: center;
    gap: 20px;
}

.csb-stat-v {
    margin-block-start: 1px;
    font-family: var(--font-mono);
    font-variant-numeric: tabular-nums;
    font-size: var(--fs-lg);
    font-weight: var(--fw-semibold);
    color: var(--text-primary);
}

/* The same stat on the analytics tab, where it is the content of
   the card rather than a fact riding on a toolbar. */
.csb-stat-lg { font-size: var(--fs-2xl); }

.csb-bar-acts {
    display: flex;
    align-items: center;
    gap: 8px;
    margin-inline-start: auto;
}

/* What stands between the draft and what the public can see.
   Amber while there is something unpublished, quiet once there
   is not - the resting state is a timestamp, not a badge. */
.csb-state {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    font-size: var(--fs-xs);
    color: var(--text-tertiary);
    white-space: nowrap;
}

.csb-state.is-dirty {
    padding: 3px 9px;
    border: 1px solid var(--warning-100);
    border-radius: var(--r-full);
    background: var(--warning-50);
    font-weight: var(--fw-semibold);
    color: var(--warning-700);
}

.csb-state.is-dirty::before {
    content: '';
    width: 5px;
    height: 5px;
    border-radius: var(--r-full);
    background: var(--warning-500);
}

/* Below this the three groups cannot share a row. The actions
   take the full width and stay end-aligned. */
@media (max-width: 1180px) {
    .csb-bar-sep { display: none; }

    .csb-bar-acts {
        flex: 1 0 100%;
        margin-inline-start: 0;
        justify-content: flex-end;
    }
}

/* ---------- The site picker ---------- */
.csb-picker {
    position: absolute;
    inset-block-start: calc(100% + 6px);
    inset-inline-start: 0;
    z-index: var(--z-dropdown);
    width: 320px;
    max-width: calc(100vw - 32px);
    padding: 6px;
    box-shadow: var(--shadow-lg);
}

.csb-picker-head {
    padding: 8px 10px 4px;
    font-size: var(--fs-2xs);
    font-weight: var(--fw-semibold);
    letter-spacing: 0.05em;
    text-transform: uppercase;
    color: var(--text-tertiary);
}

.csb-picker-row {
    display: flex;
    align-items: center;
    gap: 10px;
    width: 100%;
    padding: 7px 8px;
    border-radius: var(--r-sm);
    text-align: start;
}

.csb-picker-row:hover { background: var(--surface-hover); }
.csb-picker-row.on { background: var(--p-50); }

.csb-picker-name {
    font-size: var(--fs-sm);
    font-weight: var(--fw-semibold);
    color: var(--text-primary);
}

.csb-picker-dom {
    font-family: var(--font-mono);
    font-size: var(--fs-2xs);
    color: var(--text-tertiary);
}

.csb-picker-foot {
    margin-block-start: 4px;
    padding: 6px 4px 2px;
    border-block-start: 1px solid var(--border-subtle);
}

/* ---------- The tab row ----------
   The shared .tabs, with icons. .tab is a flex box in its own
   right now, so the alignment this block used to set comes from
   the primitive; the one real difference left is the gap, which
   is a point tighter here than the 7px default. */
/* ---------- The editor frame ----------
   The pages rail, the canvas and the inspector. The rails hold
   their widths while there is room for the canvas between them;
   below that they stack, so a preview wider than the viewport
   scrolls inside its own column instead of pushing the page
   sideways and clipping its left edge. */
.csb-grid {
    display: grid;
    grid-template-columns: 244px minmax(0, 1fr) 330px;
    gap: 14px;
    align-items: start;
}

/* The inspector drops under the canvas first: it is the rail
   you consult, not the one you navigate with. */
@media (max-width: 1400px) {
    .csb-grid { grid-template-columns: 244px minmax(0, 1fr); }
    .csb-grid > :nth-child(3) { grid-column: 1 / -1; }
}

@media (max-width: 900px) {
    .csb-grid { grid-template-columns: minmax(0, 1fr); }
    .csb-grid > :nth-child(3) { grid-column: auto; }
}

/* One header height for both rails, so the pages list and the
   inspector start on the same line either side of the canvas. */
.csb-panel-head {
    display: flex;
    align-items: center;
    gap: 8px;
    min-height: 38px;
    padding: 0 12px;
    border-block-end: 1px solid var(--border-subtle);
    font-size: var(--fs-sm);
    font-weight: var(--fw-semibold);
    color: var(--text-primary);
}

/* ---------- Pages rail ---------- */
.csb-rail {
    overflow: hidden;
    position: sticky;
    inset-block-start: 8px;
}

/* Stacked, the rail is above the canvas rather than beside it,
   and a sticky rail there would pin the list over the page it
   is scrolling. */
@media (max-width: 900px) {
    .csb-rail { position: static; }
}

.csb-rail-list { padding: 6px; }

.csb-rail-group {
    padding: 10px 8px 4px;
    font-size: var(--fs-2xs);
    font-weight: var(--fw-semibold);
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--text-tertiary);
}

.csb-rail-group:first-child { padding-block-start: 4px; }

/* A page row. The active one takes a brand bar on its leading
   edge as well as the tint: at 12.5px on a pale fill the tint
   alone was doing the work of telling you which of eleven pages
   you were editing. */
.csb-page {
    display: flex;
    align-items: center;
    gap: 8px;
    width: 100%;
    margin-block-end: 1px;
    padding: 7px 8px 7px 10px;
    border-radius: var(--r-sm);
    border-inline-start: 2px solid transparent;
    font-size: var(--fs-sm);
    font-weight: var(--fw-medium);
    color: var(--text-primary);
    text-align: start;
    transition: background-color var(--dur-fast) var(--ease-out);
}

.csb-page:hover { background: var(--surface-hover); }

.csb-page.on {
    border-inline-start-color: var(--p-700);
    background: var(--p-50);
    font-weight: var(--fw-semibold);
    color: var(--p-800);
}

.csb-page > svg { flex-shrink: 0; color: var(--text-tertiary); }
.csb-page.on > svg { color: var(--p-700); }

.csb-page-t {
    flex: 1;
    min-width: 0;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* A page that is not published yet. This was a QPill, and on a
   seeded site ten of the eleven pages are drafts - a rail of
   amber capsules, one per row, saying the same thing about
   nearly every page. As a word it still reads, and the pill
   goes back to meaning "this row is different". */
.csb-page-st {
    flex-shrink: 0;
    font-size: 9.5px;
    font-weight: var(--fw-bold);
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--warning-700);
}

.csb-page.on .csb-page-st { color: var(--warning-600); }

/* How many blocks the page has. Tertiary and mono: it is the
   one number on the row, and it should not compete with the
   title for the same glance. */
.csb-page-n {
    flex-shrink: 0;
    font-family: var(--font-mono);
    font-variant-numeric: tabular-nums;
    font-size: var(--fs-2xs);
    font-weight: var(--fw-regular);
    color: var(--text-tertiary);
}

/* The add-a-page form, opened from the rail header. Tinted so
   it reads as something that just appeared rather than as the
   first row of the list. */
.csb-newpage {
    display: flex;
    flex-direction: column;
    gap: 6px;
    padding: 10px 12px;
    border-block-end: 1px solid var(--p-100);
    background: var(--p-50);
}

.csb-newpage-acts {
    display: flex;
    align-items: center;
    gap: 6px;
    flex-wrap: wrap;
}

/* ---------- Canvas column ---------- */
.csb-canvas {
    display: flex;
    flex-direction: column;
    gap: 10px;
    min-width: 0;
}

/* The one sentence explaining that the preview is the editor.
   It is instruction, not status, so it takes the note's shape
   and none of its urgency. */
.csb-hint {
    align-items: center;
    padding: 9px 12px;
    border-color: var(--p-100);
    background: linear-gradient(180deg, var(--p-50), var(--surface-raised));
    font-size: var(--fs-sm);
    line-height: var(--lh-snug);
}

.csb-hint-mark {
    display: grid;
    place-items: center;
    flex-shrink: 0;
    width: 26px;
    height: 26px;
    border-radius: var(--r-sm);
    background: var(--p-700);
    color: var(--n-0);
}

.csb-hint strong { color: var(--text-primary); }

.csb-tools {
    display: flex;
    align-items: center;
    gap: 10px;
    flex-wrap: wrap;
    padding: 8px 10px;
}

.csb-tools-px,
.csb-tools-path {
    font-family: var(--font-mono);
    font-variant-numeric: tabular-nums;
    font-size: var(--fs-xs);
    color: var(--text-tertiary);
}

/* ---------- The browser mock ----------
   A dark chrome around the paper. It is not a themed surface -
   it is a picture of a browser, and it stays this colour so the
   white page inside it reads as a page. The greys are the
   neutral ramp's dark end rather than four hexes typed in. */
.csb-frame {
    padding: 10px;
    border-radius: var(--r-lg);
    background: var(--n-900);
    box-shadow: var(--shadow-md);
}

.csb-frame-bar {
    display: flex;
    align-items: center;
    gap: 10px;
    margin-block-end: 10px;
}

.csb-frame-dots {
    display: flex;
    gap: 6px;
    flex-shrink: 0;
}

.csb-frame-dots i {
    width: 11px;
    height: 11px;
    border-radius: var(--r-full);
    background: var(--n-600);
}

.csb-frame-url {
    display: flex;
    align-items: center;
    gap: 6px;
    flex: 1;
    min-width: 0;
    height: 24px;
    padding: 0 10px;
    border-radius: var(--r-md);
    background: var(--n-800);
    font-family: var(--font-mono);
    font-size: var(--fs-2xs);
    color: var(--n-300);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.csb-frame-url > svg { flex-shrink: 0; }

.csb-frame-tag {
    flex-shrink: 0;
    font-family: var(--font-mono);
    font-size: var(--fs-2xs);
    letter-spacing: 0.04em;
    color: var(--n-400);
}

/* The scroll port. The paper scrolls inside the mock so the
   toolbar and the address bar stay put while a long page moves
   under them. */
.csb-screen {
    max-height: 720px;
    overflow: auto;
    padding: 8px;
    border-radius: var(--r-md);
    background: var(--n-950);
}

/* The page itself, at the width the device buttons pick. White
   in every theme: this is a rendering of the public site, not a
   surface of the admin. */
.csb-paper {
    width: var(--csb-w, 1180px);
    max-width: 100%;
    margin: 0 auto;
    overflow: hidden;
    border-radius: var(--r-sm);
    background: #ffffff;
}

.csb-paper-empty {
    padding: 72px 40px;
    text-align: center;
    color: var(--text-tertiary);
}

.csb-paper-empty-t {
    margin-block-end: 6px;
    font-size: var(--fs-base);
    font-weight: var(--fw-semibold);
    color: var(--text-primary);
}

.csb-paper-empty-b {
    font-size: var(--fs-sm);
    line-height: var(--lh-normal);
}

/* ---------- A block on the canvas ----------
   Selectable, and otherwise out of the way. The outline and the
   type tag are hover and selection states: a permanent tag over
   every section turns a preview of the site into a preview of
   the editor. */
.csb-blk {
    position: relative;
    cursor: pointer;
    outline: 2px solid transparent;
    outline-offset: -2px;
    transition: outline-color var(--dur-fast) var(--ease-out);
}

.csb-blk:hover { outline-color: var(--p-300); }
.csb-blk.on { outline-color: var(--p-700); }

/* A block switched off still renders, faded, so its place in
   the page stays visible while it is hidden from the public. */
.csb-blk.is-off { opacity: 0.45; }

.csb-blk-tag {
    position: absolute;
    inset-block-start: 4px;
    inset-inline-start: 4px;
    z-index: 2;
    padding: 2px 6px;
    border-radius: var(--r-xs);
    background: var(--n-800);
    font-family: var(--font-mono);
    font-size: 9.5px;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--n-0);
    pointer-events: none;
    opacity: 0;
    transition: opacity var(--dur-fast) var(--ease-out);
}

.csb-blk:hover .csb-blk-tag,
.csb-blk.on .csb-blk-tag { opacity: 1; }

.csb-blk.on .csb-blk-tag { background: var(--p-700); }

/* The one tag that stays up. A faded section with nothing on it
   reads as a rendering fault; the tag is the only thing saying
   the block is switched off rather than broken. */
.csb-blk.is-off .csb-blk-tag { opacity: 1; }

/* Pointer events are off inside the preview so a click anywhere
   on a block selects it, rather than landing on a link inside
   the rendered markup. */
.csb-blk-inert { pointer-events: none; }

@media (prefers-reduced-motion: reduce) {
    .csb-page,
    .csb-blk,
    .csb-blk-tag { transition: none; }
}

/* ---------- Inspector ---------- */
.csb-insp {
    display: flex;
    flex-direction: column;
    max-height: 820px;
    overflow: hidden;
}

.csb-insp-tabs {
    display: flex;
    border-block-end: 1px solid var(--border-subtle);
    background: var(--surface-sunken);
}

.csb-insp-tab {
    flex: 1;
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 5px;
    height: 38px;
    padding: 0 8px;
    border-block-end: 2px solid transparent;
    font-size: var(--fs-xs);
    font-weight: var(--fw-medium);
    color: var(--text-secondary);
    white-space: nowrap;
}

.csb-insp-tab:hover { color: var(--text-primary); }

.csb-insp-tab.on {
    border-block-end-color: var(--p-700);
    background: var(--surface-raised);
    font-weight: var(--fw-bold);
    color: var(--p-700);
}

/* The "+" tab opens the block library; it is an action wearing
   a tab's clothes, so it takes an icon's width, not a share. */
.csb-insp-tab-add { flex: 0 0 44px; }

.csb-insp-body {
    flex: 1;
    overflow-y: auto;
}

/* What the pane below is about: the page, the block, or the
   page's search listing. Tinted so the pane still names its
   subject when scrolled to the bottom of a form. */
.csb-insp-head {
    padding: 11px 14px;
    border-block-end: 1px solid var(--p-100);
    background: var(--p-50);
}

.csb-insp-title {
    display: flex;
    align-items: center;
    gap: 8px;
    margin-block-start: 2px;
    font-size: var(--fs-base);
    font-weight: var(--fw-bold);
    color: var(--text-primary);
}

.csb-insp-title > svg { flex-shrink: 0; color: var(--p-700); }

.csb-insp-mark {
    display: grid;
    place-items: center;
    flex-shrink: 0;
    width: 24px;
    height: 24px;
    border-radius: var(--r-sm);
    background: var(--p-700);
    color: var(--n-0);
}

.csb-insp-meta {
    margin-block-start: 3px;
    font-family: var(--font-mono);
    font-size: var(--fs-2xs);
    line-height: var(--lh-normal);
    color: var(--text-tertiary);
    word-break: break-word;
}

.csb-insp-form {
    display: flex;
    flex-direction: column;
    gap: 12px;
    padding: 14px;
}

.csb-insp-acts {
    display: flex;
    align-items: center;
    gap: 6px;
    flex-wrap: wrap;
    margin-block-start: 2px;
}

/* A checkbox and its sentence, set as a field's sibling rather
   than as a heading. */
.csb-check {
    display: flex;
    align-items: center;
    gap: 7px;
    font-size: var(--fs-sm);
    color: var(--text-secondary);
    cursor: pointer;
}

/* The nothing-selected state inside the inspector - a panel's
   worth of empty, not a page's. */
.csb-insp-none {
    padding: 32px 24px;
    text-align: center;
}

.csb-insp-none-mark {
    display: grid;
    place-items: center;
    width: 40px;
    height: 40px;
    margin: 0 auto 12px;
    border-radius: var(--r-lg);
    background: var(--n-100);
    color: var(--text-tertiary);
}

.csb-insp-none-t {
    margin-block-end: 4px;
    font-size: var(--fs-md);
    font-weight: var(--fw-semibold);
    color: var(--text-primary);
}

.csb-insp-none-b {
    max-width: 240px;
    margin: 0 auto;
    font-size: var(--fs-xs);
    line-height: var(--lh-normal);
    color: var(--text-tertiary);
}

/* A textarea holding code - a block's JSON config, or the
   custom-HTML block's markup. */
.csb-code {
    font-family: var(--font-mono);
    font-size: var(--fs-xs);
    line-height: var(--lh-normal);
}

/* ---------- Block library ---------- */
.csb-lib { padding: 12px; }

.csb-lib-group { margin-block-end: 14px; }

.csb-lib-group:last-child { margin-block-end: 0; }

/* auto-fill rather than two fixed columns: the inspector is a
   330px rail beside the canvas and a full-width panel under it,
   and at that width two columns leave half the row empty. */
.csb-lib-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(128px, 1fr));
    gap: 6px;
}

.csb-lib-btn {
    display: flex;
    align-items: center;
    gap: 7px;
    padding: 8px 10px;
    border: 1px solid var(--border-subtle);
    border-radius: var(--r-md);
    background: var(--surface-sunken);
    font-size: var(--fs-xs);
    font-weight: var(--fw-medium);
    color: var(--text-secondary);
    text-align: start;
    transition: border-color var(--dur-fast) var(--ease-out),
                background-color var(--dur-fast) var(--ease-out);
}

.csb-lib-btn:hover {
    border-color: var(--p-300);
    background: var(--p-50);
    color: var(--p-800);
}

.csb-lib-btn > svg { flex-shrink: 0; color: var(--text-tertiary); }
.csb-lib-btn:hover > svg { color: var(--p-700); }

.csb-lib-btn span {
    min-width: 0;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* ---------- Brand kit ---------- */
.csb-brand-id {
    display: flex;
    align-items: center;
    gap: 14px;
    flex-wrap: wrap;
    margin-block-end: 16px;
}

.csb-brand-logo {
    display: grid;
    place-items: center;
    flex-shrink: 0;
    width: 56px;
    height: 56px;
    overflow: hidden;
    border: 1px solid var(--border-default);
    border-radius: var(--r-md);
    background: var(--csb-mark, var(--p-700));
    color: var(--n-0);
    font-size: var(--fs-2xl);
    font-weight: var(--fw-bold);
}

.csb-brand-logo img {
    width: 100%;
    height: 100%;
    object-fit: contain;
    background: var(--n-0);
}

/* The upload control is a label wrapping a hidden input, so it
   has to be given the button box .btn would have set. */
.csb-upload {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    height: var(--h-md);
    padding: 0 12px;
    cursor: pointer;
    font-size: var(--fs-sm);
    font-weight: var(--fw-semibold);
}

.csb-brand-grid { max-width: 680px; }

/* A colour and its hex, editable either way round. */
.csb-swatch {
    display: flex;
    align-items: center;
    gap: 8px;
}

.csb-swatch input[type="color"] {
    flex-shrink: 0;
    width: 44px;
    height: var(--h-lg);
    padding: 2px;
    border: 1px solid var(--border-default);
    border-radius: var(--r-md);
    background: var(--surface-raised);
    cursor: pointer;
}

.csb-swatch .input { font-family: var(--font-mono); }

.csb-brand-foot {
    display: flex;
    align-items: center;
    gap: 10px;
    flex-wrap: wrap;
    margin-block-start: 16px;
}

/* ---------- Domain ---------- */
.csb-dom-host {
    display: flex;
    align-items: center;
    gap: 8px;
    flex-wrap: wrap;
    margin-block-end: 16px;
    padding: 10px 12px;
    border: 1px solid var(--border-subtle);
    border-radius: var(--r-md);
    background: var(--surface-sunken);
    font-size: var(--fs-sm);
    color: var(--text-secondary);
}

.csb-dom {
    margin-block-end: 10px;
    padding: 12px;
    border: 1px solid var(--border-default);
    border-radius: var(--r-md);
}

.csb-dom-head {
    display: flex;
    align-items: center;
    gap: 8px;
    margin-block-end: 8px;
}

.csb-dom-name {
    flex: 1;
    min-width: 0;
    font-family: var(--font-mono);
    font-size: var(--fs-sm);
    font-weight: var(--fw-semibold);
    word-break: break-all;
}

/* The two records to create, as a record list rather than a
   sentence with arrows in it: this gets copied into a DNS panel
   one field at a time. */
.csb-dns {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr);
    gap: 4px 10px;
    margin: 6px 0 0;
    padding: 9px 11px;
    border-radius: var(--r-sm);
    background: var(--surface-sunken);
    font-family: var(--font-mono);
    font-size: var(--fs-2xs);
    line-height: var(--lh-normal);
    color: var(--text-secondary);
    word-break: break-all;
}

.csb-dns dt {
    font-weight: var(--fw-semibold);
    color: var(--text-tertiary);
}

.csb-dns dd { margin: 0; }

.csb-dom-add {
    display: flex;
    align-items: center;
    gap: 8px;
    max-width: 480px;
    margin-block-start: 12px;
}

/* ---------- Publish history ---------- */
.csb-ver {
    display: flex;
    align-items: center;
    gap: 10px;
    flex-wrap: wrap;
    padding: 9px 0;
    border-block-start: 1px solid var(--border-subtle);
    font-size: var(--fs-sm);
}

.csb-ver:first-child { border-block-start: none; }

.csb-ver-v {
    flex-shrink: 0;
    font-family: var(--font-mono);
    font-weight: var(--fw-semibold);
    color: var(--text-primary);
}

.csb-ver-when {
    flex-shrink: 0;
    font-family: var(--font-mono);
    font-size: var(--fs-xs);
    color: var(--text-tertiary);
}


/* ============================================================
   50. Candidate: My profile (cpf-*)

   The one page a candidate shows other people, and it was the last
   screen in the portal still drawn entirely in style attributes -
   roughly ninety of them, four of which were the same 40px circle
   beside a title and a caption, written out four times at three
   different font sizes.

   The band at the top was the bigger problem. A 140px gradient
   banner with the avatar, the name and both buttons hanging off it
   absolutely: out of flow, so nothing below moved when the name
   wrapped, which is why "2. Host globals" carries a .hero-sm rule
   whose whole job is to switch the effect off on a phone. It also
   put the name half on the banner and half on the page, so the
   text had to be page-coloured to stay readable and the banner
   ended up as 140px of decoration that nothing sat on.

   And the same number was on screen twice. Profile strength was a
   KPI tile and a progress bar in the sidebar; profile views was a
   KPI tile and the heading of the views card. Four tiles, two of
   them echoes of a card 400px away.

   What is here:
     .cpf-hero   the identity band - avatar, name, the facts as
                 chips, the two actions - in flow, so it wraps
                 instead of overlapping
     .cpf-foot   the numbers strip along the bottom of that band:
                 the strength ring and the three counts, each shown
                 once and only here
     .cpf-item   the row that was written four times - a mark, a
                 title line, a caption, an optional paragraph
     .cpf-tag    the chip, for skills and for languages, which had
                 been two different chips
     .cpf-todo   a completeness gap as a target: it links to the
                 editor, so it hovers and it has a chevron
     .cpf-row    a label, its sub-line and a read-only switch

   The controls are the shared ones - .card and .sec-head by way of
   QCard, .btn, .switch, .avatar, QPill. Nothing here restyles them.
   ============================================================ */

.cpf { display: flex; flex-direction: column; gap: 14px; }

/* ---------- The identity band ---------- */

.cpf-hero {
    position: relative;
    overflow: hidden;
    background: var(--surface-raised);
    border: 1px solid var(--border-default);
    border-radius: var(--r-xl);
    box-shadow: var(--shadow-xs);
}

/* The banner gradient, kept as a wash under the top of the band
   rather than as a block of its own. It still reads as the head of
   the page; it no longer costs 140px that nothing sits on. */
.cpf-hero::before {
    content: "";
    position: absolute;
    inset: 0 0 auto;
    height: 124px;
    background: linear-gradient(115deg, var(--p-50) 0%, rgba(255, 250, 235, 0.85) 52%, rgba(255, 255, 255, 0) 88%);
    pointer-events: none;
}

/* The gold orb the banner had, at the size a wash can carry. It is
   the only warm thing on an otherwise navy-and-grey page and it is
   what made the old banner recognisable, so it stays. */
.cpf-hero::after {
    content: "";
    position: absolute;
    inset-block-start: -96px;
    inset-inline-end: 8%;
    width: 240px;
    height: 240px;
    border-radius: var(--r-full);
    background: radial-gradient(circle, rgba(245, 184, 0, 0.20), rgba(245, 184, 0, 0) 66%);
    pointer-events: none;
}

.cpf-hero > * { position: relative; }

.cpf-hero-main {
    display: flex;
    align-items: flex-start;
    gap: 20px;
    flex-wrap: wrap;
    padding: 20px 22px;
}

/* A button, because it opens the basics drawer rather than navigating.
   That means shedding the padding, border and background a browser gives
   one - left on, the avatar sits in a grey rectangle. */
.cpf-face {
    position: relative;
    flex-shrink: 0;
    display: flex;
    padding: 0;
    border: none;
    background: none;
    cursor: pointer;
}

/* Bigger than any .avatar size class, because on this page the
   photo is the subject rather than a row marker. The halo is a
   shadow rather than a border: a 4px white ring at this diameter
   reads as a frame around the picture. */
.cpf-face .avatar {
    width: 88px;
    height: 88px;
    font-size: var(--fs-3xl);
    box-shadow: 0 0 0 4px var(--surface-raised), var(--shadow-md);
}

/* The way to the photo upload, which lives in the editor. It was a
   static badge on an anchor with no hover at all, so the only sign
   the avatar was clickable was the cursor. */
.cpf-cam {
    position: absolute;
    inset-inline-end: 0;
    inset-block-end: 2px;
    display: grid;
    place-items: center;
    width: 28px;
    height: 28px;
    border: 1px solid var(--border-default);
    border-radius: var(--r-full);
    background: var(--surface-raised);
    color: var(--text-secondary);
    box-shadow: var(--shadow-sm);
    transition: background-color var(--dur-fast) var(--ease-out),
                border-color var(--dur-fast) var(--ease-out),
                color var(--dur-fast) var(--ease-out);
}

.cpf-face:hover .cpf-cam,
.cpf-face:focus-visible .cpf-cam {
    background: var(--p-50);
    border-color: var(--p-200);
    color: var(--p-700);
}

.cpf-face:focus-visible {
    outline: none;
    border-radius: var(--r-full);
    box-shadow: var(--shadow-focus);
}

.cpf-who { flex: 1 1 280px; min-width: 0; }

.cpf-name {
    display: flex;
    align-items: center;
    gap: 9px;
    flex-wrap: wrap;
    margin: 0;
    font-size: var(--fs-3xl);
    font-weight: var(--fw-bold);
    letter-spacing: -0.02em;
    line-height: var(--lh-tight);
    color: var(--text-primary);
}

.cpf-role {
    margin: 5px 0 0;
    max-width: 60ch;
    font-size: var(--fs-lg);
    line-height: var(--lh-snug);
    color: var(--text-secondary);
}

/* Location, remote and the rest as chips rather than as one
   interpolated string joined by middots. The old line built
   "Headline - City, Country" in C#, so a candidate with no
   headline got a line that opened with a separator. */
.cpf-facts {
    display: flex;
    align-items: center;
    gap: 6px;
    flex-wrap: wrap;
    margin-block-start: 12px;
}

.cpf-fact {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    height: 24px;
    padding: 0 9px;
    border-radius: var(--r-full);
    background: var(--n-75);
    color: var(--text-secondary);
    font-size: var(--fs-xs);
    font-weight: var(--fw-medium);
}

.cpf-fact svg { color: var(--n-400); flex-shrink: 0; }

.cpf-acts {
    display: flex;
    align-items: center;
    gap: 8px;
    flex-wrap: wrap;
    margin-inline-start: auto;
}

/* ---------- The numbers strip ---------- */

/* Along the foot of the band, so the four figures sit with the
   person they describe instead of in a row of cards under them.
   Each one appears here and nowhere else on the page. */
.cpf-foot {
    display: flex;
    align-items: center;
    padding: 14px 22px;
    border-block-start: 1px solid var(--border-subtle);
    background: var(--n-25);
}

.cpf-stat {
    flex: 1 1 0;
    min-width: 0;
    padding-inline: 20px;
}

.cpf-stat + .cpf-stat { border-inline-start: 1px solid var(--border-subtle); }
.cpf-stat:first-child { padding-inline-start: 0; }
.cpf-stat:last-child { padding-inline-end: 0; }

.cpf-stat-v {
    font-family: var(--font-mono);
    font-variant-numeric: tabular-nums;
    font-size: var(--fs-3xl);
    font-weight: var(--fw-semibold);
    line-height: 1.15;
    letter-spacing: -0.02em;
    color: var(--text-primary);
}

.cpf-stat-l { margin-block-start: 3px; }

/* The lead cell is the strength, and it is the one figure with a
   shape to it - a ring rather than a fourth number, because it is
   a proportion and the other three are counts. */
.cpf-stat-lead {
    display: flex;
    align-items: center;
    gap: 14px;
    flex: 1 1 auto;
}

/* --cpf-pct is set per profile in the markup. Declared here as
   well so the ring is an empty track rather than a dropped
   gradient if it ever renders without one. */
.cpf-ring {
    --cpf-pct: 0;
    position: relative;
    display: grid;
    place-items: center;
    flex-shrink: 0;
    width: 74px;
    height: 74px;
    border-radius: var(--r-full);
    background: conic-gradient(from 180deg,
        var(--p-700) 0,
        var(--gold-500) calc(var(--cpf-pct) * 1%),
        var(--n-150) calc(var(--cpf-pct) * 1%));
}

/* The hole. A mask would be one declaration, but an inset disc on
   the strip's own colour is the version that survives an old
   Safari and a printed page. */
.cpf-ring::before {
    content: "";
    position: absolute;
    inset: 8px;
    border-radius: var(--r-full);
    background: var(--n-25);
}

.cpf-ring-n {
    position: relative;
    font-family: var(--font-mono);
    font-variant-numeric: tabular-nums;
    font-size: var(--fs-xl);
    font-weight: var(--fw-semibold);
    letter-spacing: -0.03em;
    color: var(--p-700);
}

.cpf-ring-n i { font-style: normal; font-size: var(--fs-sm); color: var(--text-tertiary); }

/* The label and the checklist count sit beside the ring rather than under
   it: the ring already carries the number, so what belongs next to it is
   what the number means. */
.cpf-lead-txt { min-width: 0; }
.cpf-lead-txt .meta-sm { margin-block-start: 4px; }

/* ---------- The two working columns ---------- */

/* 336px is the settings rail width, and for the same reason: it is
   the narrowest a labelled row plus a switch reads at without the
   sub-line wrapping on every row. */
.cpf-grid {
    display: grid;
    grid-template-columns: minmax(0, 1fr) 336px;
    gap: 14px;
    align-items: start;
}

.cpf-col { display: flex; flex-direction: column; gap: 14px; min-width: 0; }

/* ---------- One entry in a list ---------- */

/* Experience, education, certifications, projects, references and
   the viewer list were six copies of this, and the four with a
   mark drew it at three different font sizes. */
.cpf-list { display: flex; flex-direction: column; }

.cpf-item {
    display: flex;
    align-items: flex-start;
    gap: 13px;
    padding: 13px 0;
}

/* Hairlines rather than gaps, as everywhere else: a stack of rows
   separated only by whitespace reads as one list with holes. */
.cpf-item + .cpf-item { border-block-start: 1px solid var(--border-subtle); }
.cpf-item:first-child { padding-block-start: 0; }
.cpf-item:last-child { padding-block-end: 0; }

/* A rounded square, not a circle. A circle at 40px is an avatar,
   and these are employers, schools and credentials - the shape was
   telling the reader they were looking at people. */
.cpf-mark {
    display: grid;
    place-items: center;
    flex-shrink: 0;
    width: 40px;
    height: 40px;
    border-radius: var(--r-md);
    background: var(--n-75);
    border: 1px solid var(--border-subtle);
    color: var(--text-secondary);
    font-family: var(--font-mono);
    font-size: var(--fs-sm);
    font-weight: var(--fw-bold);
    letter-spacing: -0.02em;
}

/* The viewer list is people, so that one keeps the round tinted
   mark it had. */
.cpf-mark-who {
    width: 34px;
    height: 34px;
    border-radius: var(--r-full);
    background: var(--p-50);
    border-color: var(--p-100);
    color: var(--p-700);
    font-size: var(--fs-xs);
}

.cpf-body { flex: 1; min-width: 0; }

.cpf-t {
    display: flex;
    align-items: center;
    gap: 8px;
    flex-wrap: wrap;
    font-size: var(--fs-md);
    font-weight: var(--fw-semibold);
    letter-spacing: -0.005em;
    color: var(--text-primary);
}

.cpf-sub { margin: 2px 0 0; font-size: var(--fs-xs); color: var(--text-tertiary); }

/* The paragraph under a role. Capped, because a description that
   runs the full width of the column is a wall rather than prose. */
.cpf-d {
    margin: 6px 0 0;
    max-width: 68ch;
    font-size: var(--fs-sm);
    line-height: var(--lh-relaxed);
    color: var(--text-secondary);
}

/* The date on the far end of a viewer row. */
.cpf-when {
    flex-shrink: 0;
    margin-inline-start: auto;
    font-family: var(--font-mono);
    font-size: var(--fs-xs);
    color: var(--text-tertiary);
}

/* Opens the drawer for this one row. Quiet until the row is under the
   pointer, then bordered: a pencil on every row at full contrast turns a
   list of jobs into a list of buttons. It never disappears, though - an
   affordance that only exists on hover does not exist on a phone. */
.cpf-edit {
    display: grid;
    place-items: center;
    flex-shrink: 0;
    width: 28px;
    height: 28px;
    padding: 0;
    border: 1px solid transparent;
    border-radius: var(--r-md);
    background: transparent;
    color: var(--n-300);
    cursor: pointer;
    transition: background-color var(--dur-fast) var(--ease-out),
                border-color var(--dur-fast) var(--ease-out),
                color var(--dur-fast) var(--ease-out);
}

.cpf-item:hover .cpf-edit { border-color: var(--border-default); color: var(--text-secondary); }

.cpf-edit:hover {
    background: var(--p-50);
    border-color: var(--p-200);
    color: var(--p-700);
}

.cpf-edit:focus-visible { outline: none; box-shadow: var(--shadow-focus); }

/* "Verified" beside a title. Was 10.5px in success-700 written
   inline in three places, twice with the shield and once without. */
.cpf-ok {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    font-size: var(--fs-2xs);
    font-weight: var(--fw-semibold);
    letter-spacing: 0.02em;
    text-transform: uppercase;
    color: var(--success-700);
}

.cpf-ok svg { color: var(--success-600); }

/* A credential or project link. Underlined on hover only - a row of
   these under every entry, permanently underlined, is a page with
   a rash. */
.cpf-link {
    font-size: var(--fs-xs);
    font-weight: var(--fw-medium);
    color: var(--p-700);
    text-decoration: none;
}

.cpf-link:hover { text-decoration: underline; }
.cpf-link:focus-visible { outline: none; border-radius: var(--r-xs); box-shadow: var(--shadow-focus); }

/* ---------- Chips ---------- */

.cpf-tags { display: flex; flex-wrap: wrap; gap: 6px; }

.cpf-tag {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    height: 26px;
    padding: 0 11px;
    border: 1px solid transparent;
    border-radius: var(--r-full);
    background: var(--n-75);
    color: var(--text-secondary);
    font-size: var(--fs-sm);
    font-weight: var(--fw-medium);
    white-space: nowrap;
}

/* Green rather than the brand tint the skill chip used: this mark
   means "checked", and on this page navy already means "brand". */
.cpf-tag-ok {
    background: var(--success-50);
    border-color: var(--success-100);
    color: var(--success-700);
}

.cpf-tag-ok svg { color: var(--success-600); }

.cpf-tag b { font-weight: var(--fw-semibold); color: var(--text-primary); }
.cpf-tag-ok b { color: var(--success-700); }

/* The line under the chips saying what the mark means. */
.cpf-legend {
    display: flex;
    align-items: center;
    gap: 6px;
    margin-block-start: 12px;
    padding-block-start: 10px;
    border-block-start: 1px solid var(--border-subtle);
    font-size: var(--fs-xs);
    color: var(--text-tertiary);
}

.cpf-legend svg { color: var(--success-600); flex-shrink: 0; }

/* ---------- Completeness gaps ---------- */

.cpf-todo { display: flex; flex-direction: column; gap: 7px; }

/* Each one is a link into the editor and none of them looked like
   one: a flat sunken block with no hover, no focus ring and no
   mark saying it went anywhere. */
/* Both a button and an anchor wear this: four of the five gaps open a
   drawer, and the résumé one navigates to the tab that owns uploading.
   width and text-align are what a button needs to stop shrink-wrapping
   its content and centring it. */
.cpf-todo-a {
    display: flex;
    align-items: center;
    gap: 11px;
    width: 100%;
    text-align: start;
    cursor: pointer;
    padding: 10px 12px;
    border: 1px solid var(--border-subtle);
    border-radius: var(--r-md);
    background: var(--n-25);
    text-decoration: none;
    transition: background-color var(--dur-fast) var(--ease-out),
                border-color var(--dur-fast) var(--ease-out);
}

.cpf-todo-a:hover { background: var(--p-50); border-color: var(--p-200); }
.cpf-todo-a:focus-visible { outline: none; box-shadow: var(--shadow-focus); }

.cpf-todo-i {
    display: grid;
    place-items: center;
    flex-shrink: 0;
    width: 24px;
    height: 24px;
    border-radius: var(--r-full);
    background: var(--p-50);
    color: var(--p-700);
}

.cpf-todo-a:hover .cpf-todo-i { background: var(--p-100); }

.cpf-todo-t {
    display: block;
    font-size: var(--fs-sm);
    font-weight: var(--fw-semibold);
    color: var(--text-primary);
}

.cpf-todo-s { display: block; font-size: var(--fs-xs); color: var(--text-tertiary); }

/* Nudges on hover, which is the cheapest way to say "this is the
   direction you are going". */
.cpf-todo-go {
    flex-shrink: 0;
    margin-inline-start: auto;
    color: var(--n-300);
    transition: transform var(--dur-fast) var(--ease-out),
                color var(--dur-fast) var(--ease-out);
}

.cpf-todo-a:hover .cpf-todo-go { transform: translateX(2px); color: var(--p-700); }

/* ---------- A label and its read-only switch ---------- */

.cpf-rows { display: flex; flex-direction: column; }

.cpf-row {
    display: flex;
    align-items: flex-start;
    gap: 12px;
    padding: 11px 0;
}

.cpf-row + .cpf-row { border-block-start: 1px solid var(--border-subtle); }
.cpf-row:first-child { padding-block-start: 0; }
.cpf-row:last-child { padding-block-end: 0; }

.cpf-row-txt { flex: 1; min-width: 0; }

.cpf-row-t {
    font-size: var(--fs-md);
    font-weight: var(--fw-medium);
    letter-spacing: -0.005em;
    color: var(--text-primary);
}

.cpf-row-s { margin: 2px 0 0; font-size: var(--fs-xs); line-height: 1.45; color: var(--text-tertiary); }

/* These report what is stored; they are changed in settings.
   .switch already draws a disabled control, and using it means the
   reader sees the same switch here as on the screen that owns it -
   the track this page hand-painted was two thirds the size and a
   different navy. */
.cpf-row .switch { margin-block-start: 2px; }

/* ---------- Documents ---------- */

.cpf-doc {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 11px 0;
}

.cpf-doc + .cpf-doc { border-block-start: 1px solid var(--border-subtle); }
.cpf-doc:first-child { padding-block-start: 0; }
.cpf-doc:last-child { padding-block-end: 0; }

.cpf-doc-i {
    display: grid;
    place-items: center;
    flex-shrink: 0;
    width: 36px;
    height: 36px;
    border-radius: var(--r-md);
    background: var(--n-75);
    border: 1px solid var(--border-subtle);
    color: var(--text-tertiary);
}

.cpf-doc-n {
    display: flex;
    align-items: center;
    gap: 7px;
    flex-wrap: wrap;
    font-size: var(--fs-md);
    font-weight: var(--fw-medium);
    color: var(--text-primary);
}

/* ---------- Empty and loading ---------- */

/* Dashed and unfilled: the card carries a heading promising a list,
   and a bare grey sentence under that heading reads as a list that
   failed to load rather than as one with nothing in it yet. Empty,
   four of these cards used to render nothing under the heading at
   all.

   Where the card has an action of its own the box holds that action
   rather than a sentence about it. The whole of "no experience yet"
   is that the button in the header is the thing to press, and the
   header is the last place a reader looks when the body is blank. */
.cpf-empty {
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 16px 14px;
    border: 1px dashed var(--border-default);
    border-radius: var(--r-md);
    background: var(--n-25);
    text-align: center;
    font-size: var(--fs-sm);
    line-height: 1.5;
    color: var(--text-tertiary);
}

.cpf-loading {
    display: flex;
    align-items: center;
    justify-content: center;
    min-height: 220px;
    font-size: var(--fs-md);
    color: var(--text-tertiary);
}

/* ---------- The AI suggestion ---------- */

/* The suggestion is a draft of something the candidate already
   wrote, so it is shown as a quoted block rather than as body copy
   - otherwise nothing on screen says which of the two texts is the
   proposal. */
.cpf-ai-b {
    padding: 12px 14px;
    border: 1px solid var(--border-subtle);
    border-inline-start: 3px solid var(--gold-400);
    border-radius: var(--r-md);
    background: var(--n-25);
}

/* The headline as a value and the summary as prose - the pair the card and
   the modal both show, so they are set once and read the same in both. */
.cpf-h { font-size: var(--fs-lg); font-weight: var(--fw-semibold); color: var(--text-primary); }
.cpf-p { margin: 0; font-size: var(--fs-md); line-height: var(--lh-relaxed); color: var(--text-secondary); }

/* The gap between the first of those and the label opening the second. */
.cpf-gap { margin-block-start: 16px; }

/* The failure line that was here is a toast now - see "78. Shared: toasts". */

/* ---------- The edit drawers ----------

   Every form on this page opens in QDrawer ("20. Shared: side
   drawer") rather than on /me/profile/edit. The page is the thing
   being edited, and a form that replaces it takes away the only
   copy of what you were changing: you edited a headline on one
   screen and found out what it looked like on another.

   The chrome, the fields and the footer are all the drawer's own -
   .drw-body, .drw-field, .lbl, .input, .drw-pair, .drw-foot. What
   is here is only the four arrangements those do not cover. */

/* The photo, its button and the rules for it, at the head of the
   basics drawer. A file input cannot be styled, so the button is a
   <label> for it and the input itself is hidden - which is also why
   the hint has to be visible before the file dialog opens rather
   than only in the error after it. */
.cpf-photo {
    display: flex;
    align-items: center;
    gap: 16px;
    flex-wrap: wrap;
}

.cpf-photo .avatar {
    width: 72px;
    height: 72px;
    font-size: var(--fs-2xl);
    box-shadow: 0 0 0 3px var(--surface-raised), var(--shadow-sm);
}

.cpf-photo-txt { flex: 1 1 200px; min-width: 0; }

.cpf-photo-hint {
    margin: 7px 0 0;
    font-size: var(--fs-xs);
    line-height: var(--lh-snug);
    color: var(--text-tertiary);
}

/* A switch with its label, on its own rather than in a list, so no
   hairline: one rule under one row reads as a list of one. */
.cpf-sw {
    display: flex;
    align-items: center;
    gap: 10px;
}

.cpf-sw-t {
    font-size: var(--fs-md);
    font-weight: var(--fw-medium);
    color: var(--text-primary);
}

/* The skills drawer. Its chips carry a remove control, which the
   read-only ones on the page do not - a chip you can delete and a
   chip you cannot should not look identical. */
.cpf-tag-x {
    display: grid;
    place-items: center;
    width: 16px;
    height: 16px;
    margin-inline-end: -4px;
    padding: 0;
    border: none;
    border-radius: var(--r-full);
    background: transparent;
    color: var(--text-tertiary);
    cursor: pointer;
    transition: background-color var(--dur-fast) var(--ease-out),
                color var(--dur-fast) var(--ease-out);
}

.cpf-tag-x:hover { background: var(--danger-100); color: var(--danger-700); }
.cpf-tag-x:focus-visible { outline: none; box-shadow: var(--shadow-focus); }
.cpf-tag-x:disabled { cursor: not-allowed; opacity: 0.5; }

/* The field that adds one. Enter submits it, so the button is the
   second way rather than the only one. */
.cpf-add { display: flex; align-items: center; gap: 8px; }
.cpf-add .input { flex: 1; min-width: 0; }

/* The drawer that edits a list keeps the list above the field, and
   a rule between the two: without it the chips read as the value of
   the input under them. */
.cpf-drw-sep {
    margin: 0;
    border: none;
    border-block-start: 1px solid var(--border-subtle);
}

/* ---------- Narrow ---------- */

/* One column before the sidebar folds, so the three narrow cards do
   not hold 336px while every entry beside them wraps. */
@media (max-width: 1180px) {
    .cpf-grid { grid-template-columns: minmax(0, 1fr); }
}

/* Four figures across need about 620px before the labels wrap onto
   two lines each. Under that the strip is a 2x2 with the strength
   spanning the top, which keeps the ring at its own size instead of
   squeezing it into a quarter of the width. */
@media (max-width: 860px) {
    .cpf-foot {
        display: grid;
        grid-template-columns: repeat(2, minmax(0, 1fr));
        gap: 14px 0;
        align-items: start;
    }

    .cpf-stat { padding-inline: 0 14px; }
    .cpf-stat + .cpf-stat { border-inline-start: none; }

    .cpf-stat:nth-child(even) {
        padding-inline: 14px 0;
        border-inline-start: 1px solid var(--border-subtle);
    }

    .cpf-stat-lead {
        grid-column: 1 / -1;
        padding-block-end: 14px;
        border-block-end: 1px solid var(--border-subtle);
    }
}

@media (max-width: 620px) {
    .cpf-hero-main { gap: 16px; padding: 16px; }
    .cpf-foot { padding: 14px 16px; }
    .cpf-face .avatar { width: 72px; height: 72px; font-size: var(--fs-2xl); }
    .cpf-name { font-size: var(--fs-2xl); }
    .cpf-role { font-size: var(--fs-md); }

    /* The actions were pinned to the top corner of the banner, which
       on a phone put two buttons above the name of the person they
       act on. In flow they land under the identity, full width. */
    .cpf-acts { flex: 1 1 100%; margin-inline-start: 0; }
    .cpf-acts .btn { flex: 1 1 auto; justify-content: center; }
}

@media (prefers-reduced-motion: reduce) {
    .cpf-todo-go { transition: none; }
    .cpf-todo-a:hover .cpf-todo-go { transform: none; }
}


/* ============================================================
   51. Candidate: Referrals (ref-*)

   The screen a candidate uses to bring somebody else onto the
   platform, and the only screen in the portal that pays them.

   Before this block it was a gold notice card holding the share
   link, an inline four-field form that pushed the whole page down
   when it opened, four KPI tiles, and a list row built from eight
   fixed min-widths - so the row could not narrow, and below about
   1100px the reward and the date left the card.

   Three things were wrong with it beyond the paint:

     the money was said twice   "Total earned" was a tile and the
                                heading of the earnings card beside
                                it, 400px apart, from one field
     the form moved the page    it opened between the link and the
                                stats, so pressing "Refer someone"
                                pushed the list the referrer was
                                looking at off the bottom
     progress was a bare bar    five grey-or-navy segments with the
                                status as a 10px word beside them,
                                and no sense of how many referrals
                                were sitting at each step

   So: the money lives once, in the hero, next to the link that
   earns it. The form is a QDrawer ("20." above), which leaves the
   list in place behind it. And the five statuses became a funnel
   across the top - the same five words, each carrying how many
   referrals have reached it - which is the question a referrer
   actually opens this page with.

   What is here:
     .ref          the page stack
     .ref-hero     the navy band: the pitch, the share link, and
                   the purse
     .ref-share    the link as one control - field and copy button
                   inside a single pill
     .ref-purse    earned all-time, what is still owed, and the way
                   through to the payout history
     .ref-funnel   the five statuses as a funnel, only when there
                   is something in it
     .ref-row      one referral: who, where they are, what it pays
     .ref-note     the reward line in the drawer

   The chips over the list are the shared .chip-tog / .chip-row from
   "12. Shared page chrome", and the drawer's fields are .drw-*.
   Nothing here restyles either.
   ============================================================ */

.ref {
    display: flex;
    flex-direction: column;
    gap: 16px;
    min-width: 0;
}

/* ---------- The hero ---------- */

/* The portal's own identity idiom - the dashboard and the
   certifications wall open on the same navy panel with a gold bloom
   - carrying the one thing this page exists to hand out: the link.
   It sits on the hero rather than in a card under it because the
   sentence above it is the argument for using it. */
.ref-hero {
    position: relative;
    overflow: hidden;
    padding: 20px 24px;
    border-radius: var(--r-xl);
    background: linear-gradient(135deg, var(--p-900) 0%, var(--p-800) 58%, var(--p-700) 100%);
    color: var(--n-0);
}

/* Gold from the top right, blue from the bottom left, so the
   gradient does not read as a flat band on a wide page. Decorative
   and inert. */
.ref-hero::before,
.ref-hero::after {
    content: "";
    position: absolute;
    border-radius: var(--r-full);
    pointer-events: none;
}

.ref-hero::before {
    inset-block-start: -90px;
    inset-inline-end: -50px;
    width: 280px;
    height: 280px;
    background: radial-gradient(circle, rgba(245, 184, 0, 0.22) 0%, transparent 70%);
}

.ref-hero::after {
    inset-block-end: -130px;
    inset-inline-start: -70px;
    width: 320px;
    height: 320px;
    background: radial-gradient(circle, rgba(100, 120, 255, 0.18) 0%, transparent 70%);
}

.ref-hero-in {
    position: relative;
    display: flex;
    align-items: flex-start;
    gap: 24px;
    flex-wrap: wrap;
}

.ref-crest {
    flex-shrink: 0;
    display: grid;
    place-items: center;
    width: 46px;
    height: 46px;
    border-radius: var(--r-lg);
    background: linear-gradient(140deg, var(--gold-300), var(--gold-500));
    color: var(--p-900);
    box-shadow: 0 2px 6px rgba(7, 10, 19, 0.28), inset 0 1px 0 rgba(255, 255, 255, 0.45);
}

/* 340px is the point below which the share pill inside it stops
   being a link and a button on one line. Under that the purse takes
   its own row instead of squeezing this one. */
.ref-hero-txt { flex: 1 1 340px; min-width: 0; }

.ref-hero-h {
    margin: 0;
    font-family: var(--font-display);
    font-size: var(--fs-2xl);
    font-weight: var(--fw-bold);
    letter-spacing: -0.018em;
    line-height: var(--lh-snug);
}

.ref-hero-p {
    margin: 5px 0 0;
    max-width: 62ch;
    font-size: var(--fs-md);
    line-height: var(--lh-normal);
    color: rgba(255, 255, 255, 0.76);
}

/* ---------- The share link ---------- */

/* Field and button in one pill rather than a code block beside a
   button: the two are a single control, and the link is long enough
   that anything which wraps it stops being copyable by eye. */
.ref-share {
    display: flex;
    align-items: center;
    gap: 6px;
    max-width: 520px;
    margin-block-start: 16px;
    padding: 4px 4px 4px 14px;
    border-radius: var(--r-full);
    background: rgba(255, 255, 255, 0.1);
    box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.22);
}

/* A real input, not a span: selecting the text by hand is the
   fallback when the browser withholds the clipboard, and jorkCopy's
   last resort depends on the referrer being able to do it. */
.ref-share-in {
    flex: 1;
    min-width: 0;
    height: 28px;
    padding: 0;
    border: none;
    outline: none;
    background: transparent;
    color: var(--n-0);
    font-family: var(--font-mono);
    font-size: var(--fs-xs);
    text-overflow: ellipsis;
}

.ref-share-in:focus-visible { text-overflow: clip; }

/* Text only, no icon: jorkCopy confirms by swapping textContent, so
   an icon inside the button would be wiped by the confirmation. */
.ref-copy {
    flex-shrink: 0;
    height: 28px;
    padding: 0 15px;
    border: none;
    border-radius: var(--r-full);
    background: var(--gold-400);
    color: var(--p-900);
    font-size: var(--fs-xs);
    font-weight: var(--fw-bold);
    cursor: pointer;
    transition: background-color var(--dur-fast) var(--ease-out);
}

.ref-copy:hover { background: var(--gold-300); }
.ref-copy:focus-visible { outline: 2px solid var(--gold-200); outline-offset: 2px; }

.ref-share-hint {
    margin: 7px 0 0;
    font-size: var(--fs-xs);
    line-height: var(--lh-snug);
    color: rgba(255, 255, 255, 0.6);
}

/* ---------- The purse ---------- */

/* Earned, owed, and the way through to what those totals are made
   of. This is the only place on the page either figure is printed:
   the funnel under it counts people, never money. */
.ref-purse {
    flex-shrink: 0;
    min-width: 232px;
    padding: 14px 18px;
    border: 1px solid rgba(255, 255, 255, 0.2);
    border-radius: var(--r-lg);
    background: rgba(255, 255, 255, 0.1);
}

.ref-purse-l {
    font-size: var(--fs-2xs);
    font-weight: var(--fw-bold);
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: rgba(255, 255, 255, 0.66);
}

.ref-purse-v {
    margin-block-start: 4px;
    font-family: var(--font-mono);
    font-size: var(--fs-4xl);
    font-weight: var(--fw-bold);
    font-variant-numeric: tabular-nums;
    line-height: 1;
    color: var(--gold-300);
}

.ref-purse-sub {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 10px;
    margin-block-start: 12px;
    padding-block-start: 11px;
    border-block-start: 1px solid rgba(255, 255, 255, 0.18);
    font-size: var(--fs-sm);
    color: rgba(255, 255, 255, 0.72);
}

.ref-purse-sub b {
    font-family: var(--font-mono);
    font-variant-numeric: tabular-nums;
    font-weight: var(--fw-semibold);
    color: var(--n-0);
}

.ref-purse-a {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    margin-block-start: 10px;
    font-size: var(--fs-sm);
    font-weight: var(--fw-semibold);
    color: var(--gold-200);
    text-decoration: none;
}

.ref-purse-a:hover { color: var(--gold-100); text-decoration: underline; }
.ref-purse-a svg { transition: transform var(--dur-fast) var(--ease-out); }
.ref-purse-a:hover svg { transform: translateX(2px); }

/* ---------- The funnel ---------- */

/* The five statuses a referral moves through, each carrying how many
   have reached it. Cumulative on purpose - somebody who has been
   hired has also applied - so the row reads as a funnel narrowing
   left to right rather than as five unrelated buckets.

   Rendered only when there is at least one referral. Five noughts in
   a row is a wall of chrome that says nothing the empty state below
   it does not say better. */
.ref-funnel {
    display: grid;
    grid-template-columns: repeat(5, minmax(0, 1fr));
    overflow: hidden;
    border: 1px solid var(--border-default);
    border-radius: var(--r-xl);
    background: var(--surface-raised);
    box-shadow: var(--shadow-xs);
}

.ref-stage { padding: 14px 16px 15px; min-width: 0; }
.ref-stage + .ref-stage { border-inline-start: 1px solid var(--border-subtle); }

.ref-stage-v {
    display: block;
    font-family: var(--font-mono);
    font-size: var(--fs-2xl);
    font-weight: var(--fw-bold);
    font-variant-numeric: tabular-nums;
    line-height: 1;
    color: var(--text-primary);
}

.ref-stage-l {
    display: block;
    margin-block-start: 5px;
    font-size: var(--fs-xs);
    font-weight: var(--fw-semibold);
    color: var(--text-secondary);
}

/* The share of all referrals that got this far, as a bar. It is what
   turns five numbers into a shape. */
.ref-stage-bar {
    height: 3px;
    margin-block-start: 10px;
    border-radius: var(--r-full);
    background: var(--n-100);
    overflow: hidden;
}

.ref-stage-fill {
    display: block;
    height: 100%;
    border-radius: inherit;
    background: var(--p-500);
}

/* The end of the funnel is the only stage that pays, so it is the
   only one wearing the page's gold. */
.ref-stage.is-end .ref-stage-v { color: var(--gold-700); }
.ref-stage.is-end .ref-stage-fill { background: var(--gold-500); }

/* ---------- One referral ---------- */

.ref-list { display: flex; flex-direction: column; }

/* Four lanes: the avatar that anchors the row, who they are, where
   they have got to, and what it pays. No fixed min-widths - the old
   row had eight of them, which is why it left the card below
   1100px. */
.ref-row {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr) minmax(150px, 0.85fr) auto;
    grid-template-areas: "av who track money";
    align-items: center;
    gap: 8px 16px;
    padding: 12px 16px;
    transition: background-color var(--dur-fast) var(--ease-out);
}

.ref-row + .ref-row { border-block-start: 1px solid var(--border-subtle); }
.ref-row:hover { background: var(--surface-hover); }

.ref-av {
    grid-area: av;
    display: grid;
    place-items: center;
    width: 34px;
    height: 34px;
    border-radius: var(--r-full);
    color: var(--n-0);
    font-family: var(--font-mono);
    font-size: var(--fs-xs);
    font-weight: var(--fw-bold);
}

.ref-who { grid-area: who; min-width: 0; }

.ref-nm {
    font-size: var(--fs-md);
    font-weight: var(--fw-semibold);
    color: var(--text-primary);
}

.ref-sub {
    margin-block-start: 2px;
    font-size: var(--fs-xs);
    color: var(--text-tertiary);
}

/* ---------- Where they are ---------- */

.ref-track { grid-area: track; min-width: 0; }

.ref-segs { display: flex; gap: 2px; }

.ref-seg {
    flex: 1;
    height: 4px;
    border-radius: var(--r-full);
    background: var(--n-150);
}

.ref-seg.is-on { background: var(--p-500); }

/* A hired referral turns the whole track over rather than adding a
   fifth navy segment: the list is scanned for the green one. */
.ref-track.is-win .ref-seg.is-on { background: var(--success-500); }

.ref-track-l {
    display: block;
    margin-block-start: 6px;
    font-size: var(--fs-2xs);
    font-weight: var(--fw-bold);
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--text-tertiary);
}

.ref-track.is-win .ref-track-l { color: var(--success-700); }

/* ---------- What it pays ---------- */

.ref-money {
    grid-area: money;
    display: flex;
    flex-direction: column;
    align-items: flex-end;
    gap: 4px;
    text-align: end;
}

.ref-amt {
    font-family: var(--font-mono);
    font-size: var(--fs-md);
    font-weight: var(--fw-semibold);
    font-variant-numeric: tabular-nums;
    color: var(--text-primary);
}

/* An estimate until it is settled, and it should not be read as
   money already in hand. */
.ref-amt.is-soft { color: var(--text-tertiary); }

.ref-when {
    font-size: var(--fs-2xs);
    color: var(--text-tertiary);
    white-space: nowrap;
}

/* ---------- The drawer's reward line ---------- */

/* What sending this actually promises, said beside the button that
   sends it rather than in the page behind the drawer. */
.ref-note {
    display: flex;
    align-items: flex-start;
    gap: 9px;
    padding: 11px 13px;
    border-radius: var(--r-md);
    background: var(--gold-50);
    box-shadow: inset 0 0 0 1px var(--gold-200);
    font-size: var(--fs-sm);
    line-height: var(--lh-snug);
    color: var(--gold-900);
}

.ref-note svg { flex-shrink: 0; margin-block-start: 1px; color: var(--gold-700); }

/* ---------- Narrow ---------- */

/* The funnel is five columns of one number and one word; below this
   the word wraps three ways before the number does anything. Two
   rows of them keeps every stage at a readable width. */
@media (max-width: 900px) {
    .ref-funnel { grid-template-columns: repeat(3, minmax(0, 1fr)); }

    .ref-stage:nth-child(3n + 1) { border-inline-start: none; }
    .ref-stage:nth-child(n + 4) { border-block-start: 1px solid var(--border-subtle); }
}

/* The track drops under the name and the money keeps its lane: the
   reward is the shortest thing in the row and the first thing looked
   for. */
@media (max-width: 780px) {
    .ref-row {
        grid-template-columns: auto minmax(0, 1fr) auto;
        grid-template-areas:
            "av who money"
            ". track track";
    }
}

@media (max-width: 640px) {
    .ref-hero { padding: 18px 16px; }
    .ref-hero-in { gap: 16px; }

    /* Full width rather than a 232px block stranded beside nothing. */
    .ref-purse { flex: 1 1 100%; min-width: 0; }

    .ref-funnel { grid-template-columns: repeat(2, minmax(0, 1fr)); }
    .ref-stage:nth-child(3n + 1) { border-inline-start: 1px solid var(--border-subtle); }
    .ref-stage:nth-child(odd) { border-inline-start: none; }
    .ref-stage:nth-child(n + 3) { border-block-start: 1px solid var(--border-subtle); }
}

@media (prefers-reduced-motion: reduce) {
    .ref-purse-a svg { transition: none; }
    .ref-purse-a:hover svg { transform: none; }
}


/* ============================================================
   52. Candidate: Applications (capp-*)

   The tracker. A candidate opens it with three questions, in this
   order: is anything still moving, is anything waiting on me, and
   where did each one get to.

   Before this block the page answered only the third, as a
   six-column table written in inline styles - and an inline style
   cannot hold a hover, a focus ring or a breakpoint, so the table
   scrolled sideways inside its own card on a phone and the actions
   column was the part that left the screen. Three things were
   wrong under the paint:

     the shape said spreadsheet    this reader is not at work, and
                                   five equal columns give the AI
                                   score the same weight as the
                                   name of the company
     nothing was aggregated        eleven status chips, seven of
                                   them behind a More menu, and no
                                   single line saying how many
                                   applications are still live
     the confirm was 220px wide    the withdraw dialog was built
                                   inside the last table cell, so
                                   the reason box was a two-line
                                   textarea pinned to the right
                                   edge of a scrolling table

   So: a standing band across the top says the whole tracker as one
   bar and four figures, and the bar's segments carry the same
   colours as the facet chips - pressing one is the eighth chip
   without opening the menu. The rows below are a list rather than
   a table: a monogram, the role, how far it got, what it scored.
   The grid reflows twice instead of scrolling. The withdraw
   confirm is a panel across the full width of its own row.

   What is here:
     .capp        the page stack
     .capp-sum    the standing band - figures, bar, key
     .capp-fu     the follow-up panel, for outreach nobody answered
     .capp-row    one application
     .capp-rail   how far it got, in that stage's own colour
     .capp-fit    the AI fit ring
     .capp-panel  the withdraw confirm, full width under its row

   Stage colour arrives as --capp-dot / --capp-bg / --capp-fg, set
   on the element from StageColors() in Applications.razor, so the
   band, the chips and the rails cannot disagree about what
   "Interview" looks like. The toolbar above the list is the shared
   QFilterBar and nothing here restyles it.
   ============================================================ */

.capp {
    display: flex;
    flex-direction: column;
    gap: 14px;
    min-width: 0;
}

/* ---------- The page as a fixed-height region ----------

   <main> is the portal's scroll container and its height is definite, so a
   page can take that height rather than grow past it. This one does: the list
   is the only unbounded thing here and the only thing that scrolls. The
   header, the standing band, the follow-up panel and the filter toolbar all
   stay put.

   The reason is the tracker. The band answers "how many of these are still
   going" and its segments are how the seven stage filters behind the More
   menu become reachable in one press - and both of those left the screen as
   soon as the candidate scrolled to the application they were about to act
   on. Twenty applications was enough to lose them. PageHeader's sticky bar
   was the only part of the chrome that survived, and it carries the least.

   Every box between .capp-page and the rows has to agree to be shorter than
   its content. A flex item's default minimum is its content height, so one
   refusal anywhere down the chain hands the scroll straight back to <main>
   and none of this happens.

   The 560px floor is the short-window escape hatch. It is still a definite
   height, so the region keeps working and the page takes the overflow instead
   of the list being squeezed to nothing. That is this page's chrome plus
   about three rows. */

.capp-page {
    display: flex;
    flex-direction: column;
    block-size: 100%;
    min-block-size: 560px;
    min-inline-size: 0;
    /* The tail belongs to a page that scrolls past its own end. Here it is
       dead space taken from the rows. */
    padding-block-end: 8px;
}

.capp {
    flex: 1 1 auto;
    min-block-size: 0;
}

/* ---------- The standing band ----------
   Four figures and one bar. The figures are the aggregates nothing
   else on the page says - the status chips count one stage each and
   cannot add up to "three of these have reached an interview". */

.capp-sum {
    padding: 16px 18px 14px;
    border: 1px solid var(--border-default);
    border-radius: var(--r-xl);
    background: var(--surface-raised);
    box-shadow: var(--shadow-xs);
}

/* auto-fit rather than four fixed columns: at 640px four figures
   are 90px wide each and every label wraps to three lines.

   Capped, because 1fr columns take whatever the band is given: on a
   wide screen the four figures were 280px apart and stopped reading
   as one set of related numbers. The cap holds them together at the
   left; below it they go back to filling the row. */
.capp-figs {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(128px, 1fr));
    gap: 14px 18px;
    max-inline-size: 720px;
}

.capp-fig { min-width: 0; }

.capp-fig-n {
    display: block;
    font-family: var(--font-mono);
    font-variant-numeric: tabular-nums;
    font-size: var(--fs-3xl);
    font-weight: var(--fw-medium);
    letter-spacing: -0.02em;
    line-height: var(--lh-tight);
    color: var(--text-primary);
}

/* The three that are not the headline read as supporting figures,
   not as three more headlines. */
.capp-fig-sub .capp-fig-n {
    font-size: var(--fs-2xl);
    color: var(--text-secondary);
}

/* A figure worth noticing keeps its colour at the smaller size - an
   offer on the table is the one number on this page a candidate
   came to see. */
.capp-fig-good .capp-fig-n { color: var(--success-700); }
.capp-fig-gold .capp-fig-n { color: var(--gold-700); }

.capp-fig-l {
    display: block;
    margin-block-start: 3px;
    font-size: var(--fs-xs);
    font-weight: var(--fw-medium);
    line-height: var(--lh-snug);
    color: var(--text-tertiary);
}

/* ---------- The bar ----------
   Every application in the tracker, in stage order, at its share of
   the width. Closed outcomes are in it too: a tracker that is
   two-thirds grey is telling the candidate something, and hiding
   that would be flattery rather than design. */

.capp-bar {
    display: flex;
    gap: 2px;
    margin-block-start: 16px;
    height: 10px;
}

.capp-bar-seg {
    min-width: 6px;
    padding: 0;
    border: none;
    border-radius: var(--r-xs);
    background: var(--capp-dot, var(--n-200));
    cursor: pointer;
    transition: opacity var(--dur-fast) var(--ease-out),
                transform var(--dur-fast) var(--ease-out);
}

/* Nothing selected means every segment is live; once a stage is
   picked the rest step back, so the bar says which slice of itself
   the list below is showing. */
.capp-bar-dim .capp-bar-seg { opacity: 0.3; }
.capp-bar-dim .capp-bar-seg.on { opacity: 1; }

.capp-bar-seg:hover { transform: scaleY(1.4); }

.capp-bar-seg:focus-visible {
    outline: none;
    box-shadow: var(--shadow-focus);
}

.capp-key {
    display: flex;
    flex-wrap: wrap;
    gap: 4px 6px;
    margin-block-start: 10px;
}

/* Each key entry is the segment's label and the way to select it -
   a legend that only names colours makes the reader look back up at
   a 10px bar to act on what it says. */
.capp-key-b {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    height: var(--h-sm);
    padding: 0 10px;
    border: 1px solid transparent;
    border-radius: var(--r-full);
    background: transparent;
    font-family: inherit;
    font-size: var(--fs-xs);
    font-weight: var(--fw-medium);
    color: var(--text-secondary);
    white-space: nowrap;
    cursor: pointer;
    transition: background-color var(--dur-fast) var(--ease-out),
                color var(--dur-fast) var(--ease-out);
}

.capp-key-b:hover { background: var(--capp-bg, var(--n-75)); }

.capp-key-b.on {
    border-color: var(--capp-dot, var(--border-strong));
    background: var(--capp-bg, var(--n-100));
    color: var(--capp-fg, var(--text-primary));
    font-weight: var(--fw-bold);
}

.capp-key-b:focus-visible { outline: none; box-shadow: var(--shadow-focus); }

.capp-key-dot {
    width: 7px;
    height: 7px;
    flex-shrink: 0;
    border-radius: var(--r-full);
    background: var(--capp-dot, var(--n-400));
}

.capp-key-n {
    font-family: var(--font-mono);
    font-variant-numeric: tabular-nums;
    font-size: var(--fs-2xs);
    font-weight: var(--fw-bold);
    opacity: 0.75;
}

/* ---------- Waiting on a reply ----------
   Gold, because it is the only block on this page the candidate can
   still act on - everything below it is a status somebody else has
   to change. */

/* Capped, and it scrolls itself past the cap. The panel is one row per
   application that has gone quiet, which is unbounded - a candidate chasing
   a dozen of them would otherwise push the list out of the fixed region
   entirely and take the page's scroll back. Three rows is the common case
   and fits well inside the cap. */
.capp-fu {
    max-block-size: 34vh;
    overflow-y: auto;
    overflow-x: hidden;
    border: 1px solid var(--gold-200);
    border-radius: var(--r-xl);
    background: var(--surface-raised);
    box-shadow: var(--shadow-xs);
}

.capp-fu-head {
    display: flex;
    align-items: flex-start;
    gap: 10px;
    padding: 13px 16px;
    border-block-end: 1px solid var(--gold-200);
    background: var(--gold-50);
}

.capp-fu-head > svg {
    flex-shrink: 0;
    margin-block-start: 2px;
    color: var(--gold-700);
}

.capp-fu-h {
    margin: 0;
    font-size: var(--fs-md);
    font-weight: var(--fw-semibold);
    color: var(--text-primary);
}

.capp-fu-p {
    margin: 3px 0 0;
    max-width: 68ch;
    font-size: var(--fs-sm);
    line-height: var(--lh-normal);
    color: var(--text-secondary);
}

.capp-fu-row {
    padding: 12px 16px;
    border-block-start: 1px solid var(--border-subtle);
}

.capp-fu-top {
    display: flex;
    align-items: flex-start;
    gap: 12px;
    flex-wrap: wrap;
}

.capp-fu-main { flex: 1 1 220px; min-width: 0; }

.capp-fu-role {
    font-size: var(--fs-md);
    font-weight: var(--fw-semibold);
    line-height: var(--lh-snug);
    color: var(--text-primary);
}

.capp-fu-meta {
    margin-block-start: 2px;
    font-size: var(--fs-xs);
    line-height: var(--lh-snug);
    color: var(--text-tertiary);
}

/* The strongest reason to chase: they read it. Green because it is
   the one line in this panel that is good news. */
.capp-fu-open {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    margin-block-start: 4px;
    font-size: var(--fs-xs);
    font-weight: var(--fw-semibold);
    color: var(--success-700);
}

.capp-fu-last {
    margin-block-start: 4px;
    font-size: var(--fs-2xs);
    color: var(--text-tertiary);
}

.capp-fu-acts {
    display: flex;
    align-items: center;
    gap: 8px;
    flex-shrink: 0;
}

.capp-fu-note {
    box-sizing: border-box;
    width: 100%;
    margin-block-start: 10px;
    padding: 10px 12px;
    border: 1px solid var(--border-default);
    border-radius: var(--r-md);
    font-family: inherit;
    font-size: var(--fs-sm);
    line-height: var(--lh-relaxed);
    color: var(--text-primary);
    resize: vertical;
}

.capp-fu-note:focus-visible {
    outline: none;
    border-color: var(--border-focus);
    box-shadow: var(--shadow-focus);
}

/* ---------- The toolbar row ----------

   The shared QFilterBar, and on the right the one line that says whether
   what is on screen is the whole tracker.

   The bar used to print all eleven stages as chips here. Seven of them read
   "0" for most candidates, the row wrapped to a second line, and it repeated
   the key in the band above - which prints only the stages that hold
   something. The chips are one Status dropdown now (InlineFacetCount="0"),
   which is also what makes room for the count: it was previously visible only
   inside the filter panel, so an empty list could equally have meant an empty
   tracker or a search that matched nothing. */

.capp-tools {
    display: flex;
    align-items: center;
    gap: 8px 14px;
    flex-wrap: wrap;
}

/* Takes the row and lets the count sit at the far end; below about 560px of
   toolbar the basis stops applying and the count wraps under it instead. */
.capp-tools-bar {
    flex: 1 1 380px;
    min-width: 0;
}

.capp-tools-end {
    display: flex;
    align-items: center;
    gap: 8px;
    margin-inline-start: auto;
}

/* Tabular figures but the body face: set in the mono family the whole string
   took the extra tracking, and "of" and "shown" are words, not numbers. */
.capp-count {
    font-variant-numeric: tabular-nums;
    font-size: var(--fs-xs);
    color: var(--text-tertiary);
    white-space: nowrap;
}

/* Only rendered while something is actually narrowing the list. A permanent
   reset is a button that does nothing nine times out of ten. */
.capp-clear {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    height: var(--h-sm);
    padding: 0 10px;
    border: 1px solid var(--border-default);
    border-radius: var(--r-full);
    background: var(--surface-raised);
    font-family: inherit;
    font-size: var(--fs-xs);
    font-weight: var(--fw-medium);
    color: var(--text-secondary);
    white-space: nowrap;
    cursor: pointer;
    transition: border-color var(--dur-fast) var(--ease-out),
                color var(--dur-fast) var(--ease-out);
}

.capp-clear:hover { border-color: var(--border-strong); color: var(--text-primary); }
.capp-clear:focus-visible { outline: none; box-shadow: var(--shadow-focus); }

/* ---------- The list ---------- */

/* The one scroll container on the page. Block axis only: the row grid
   reflows twice rather than widening, so a horizontal scrollbar here would
   only ever mean a layout bug - and overflow-x: hidden is still what keeps
   the first and last rows inside the rounded corners. */
.capp-list {
    flex: 1 1 auto;
    min-block-size: 0;
    overflow-y: auto;
    overflow-x: hidden;
    list-style: none;
    margin: 0;
    padding: 0;
    border: 1px solid var(--border-default);
    border-radius: var(--r-xl);
    background: var(--surface-raised);
    box-shadow: var(--shadow-xs);
}

/* --capp-ground is the row's own background, named so the hole in
   the AI fit ring can be painted the same colour. The hole is paint,
   not a cut-out, so a row that changes ground on hover or when it is
   closed has to tell the ring about it. */
.capp-row {
    --capp-ground: var(--surface-raised);
    border-block-start: 1px solid var(--border-subtle);
    background: var(--capp-ground);
    transition: background-color var(--dur-fast) var(--ease-out);
}

.capp-row:first-child { border-block-start: none; }
.capp-row:hover { --capp-ground: var(--n-25); }

/* Withdrawn and rejected stay legible rather than being faded to
   55% - a candidate re-reading how an application ended should not
   have to squint at it. The row steps back by losing weight, not
   contrast: sunken ground, and the monogram desaturated. */
.capp-row-closed { --capp-ground: var(--n-25); }
.capp-row-closed:hover { --capp-ground: var(--n-75); }
.capp-row-closed .capp-mark { filter: grayscale(0.7); opacity: 0.85; }
.capp-row-closed .capp-role { color: var(--text-secondary); }

.capp-row-in {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr) 148px 40px auto;
    grid-template-areas: "mark main stage fit acts";
    align-items: center;
    gap: 8px 14px;
    padding: 13px 16px;
}

/* ---------- The monogram ---------- */

.capp-mark {
    grid-area: mark;
    display: grid;
    place-items: center;
    width: 38px;
    height: 38px;
    flex-shrink: 0;
    border-radius: var(--r-lg);
    background: var(--capp-co, var(--n-400));
    color: var(--n-0);
    font-family: var(--font-mono);
    font-size: var(--fs-sm);
    font-weight: var(--fw-bold);
    letter-spacing: 0.02em;
    /* The colour is chosen per company and some of them are light;
       an inner top highlight and a hairline keep the letters
       readable at either end of that range. */
    box-shadow: inset 0 0 0 1px rgba(15, 19, 32, 0.08),
                inset 0 1px 0 rgba(255, 255, 255, 0.28);
}

/* ---------- Role, company, dates ---------- */

.capp-main { grid-area: main; min-width: 0; }

.capp-role {
    display: block;
    font-size: var(--fs-base);
    font-weight: var(--fw-semibold);
    line-height: var(--lh-snug);
    letter-spacing: -0.005em;
    color: var(--text-primary);
    text-decoration: none;
}

a.capp-role:hover { color: var(--text-brand); text-decoration: underline; }

a.capp-role:focus-visible {
    outline: 2px solid var(--border-focus);
    outline-offset: 2px;
    border-radius: var(--r-xs);
}

.capp-co {
    display: flex;
    align-items: center;
    gap: 6px;
    flex-wrap: wrap;
    margin-block-start: 2px;
    font-size: var(--fs-sm);
    color: var(--text-secondary);
}

/* Same row, same stages, but nobody on the other side has a Jork
   inbox - so this is a tag, not a footnote in the same grey as the
   date beside it. */
.capp-tag {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    height: 18px;
    padding: 0 7px;
    border-radius: var(--r-full);
    background: var(--info-50);
    font-size: var(--fs-2xs);
    font-weight: var(--fw-semibold);
    color: var(--info-700);
    white-space: nowrap;
    cursor: help;
}

.capp-dates {
    display: flex;
    gap: 4px 10px;
    flex-wrap: wrap;
    margin-block-start: 4px;
    font-size: var(--fs-xs);
    color: var(--text-tertiary);
}

.capp-dates b {
    font-family: var(--font-mono);
    font-variant-numeric: tabular-nums;
    font-weight: var(--fw-medium);
    color: var(--text-secondary);
}

/* ---------- How far it got ---------- */

.capp-stage { grid-area: stage; min-width: 0; }

.capp-chip {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    height: 20px;
    padding: 0 9px;
    border-radius: var(--r-full);
    background: var(--capp-bg, var(--n-100));
    font-size: var(--fs-2xs);
    font-weight: var(--fw-bold);
    letter-spacing: 0.03em;
    text-transform: uppercase;
    color: var(--capp-fg, var(--n-700));
    white-space: nowrap;
}

/* Five steps: applied, screening, interview, assessment, offer.
   Filled in the stage's own colour rather than navy, so the rail
   and the chip above it are visibly the same fact. */
.capp-rail {
    display: flex;
    gap: 3px;
    margin-block-start: 7px;
}

.capp-rail-s {
    flex: 1;
    height: 4px;
    border-radius: var(--r-full);
    background: var(--n-150);
}

.capp-rail-s.on { background: var(--capp-dot, var(--p-700)); }

/* ---------- AI fit ---------- */

.capp-fit {
    --capp-fit-deg: 0deg;
    --capp-fit-c: var(--n-400);
    grid-area: fit;
    display: grid;
    place-items: center;
    width: 40px;
    height: 40px;
    border-radius: var(--r-full);
    background: conic-gradient(var(--capp-fit-c) var(--capp-fit-deg), var(--n-150) 0);
}

/* The hole is painted, not cut, so it wears the row's ground rather
   than a fixed white - the row tints on hover and again when it is
   closed, and a white disc on either would read as a chip. */
.capp-fit-in {
    display: grid;
    place-items: center;
    width: 31px;
    height: 31px;
    border-radius: var(--r-full);
    background: var(--capp-ground, var(--surface-raised));
    font-family: var(--font-mono);
    font-variant-numeric: tabular-nums;
    font-size: var(--fs-xs);
    font-weight: var(--fw-semibold);
    color: var(--capp-fit-c);
}

/* Nothing scored. A dash rather than a nought: an unscored
   application is not a bad match, it is an unread one. */
.capp-fit-none {
    grid-area: fit;
    display: grid;
    place-items: center;
    width: 40px;
    height: 40px;
    font-size: var(--fs-md);
    color: var(--text-disabled);
}

/* ---------- Actions ---------- */

.capp-acts {
    grid-area: acts;
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: 8px;
}

.capp-open {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    height: var(--h-sm);
    padding: 0 12px;
    border: 1px solid var(--p-100);
    border-radius: var(--r-md);
    background: var(--p-50);
    font-size: var(--fs-sm);
    font-weight: var(--fw-semibold);
    color: var(--p-700);
    text-decoration: none;
    white-space: nowrap;
    transition: background-color var(--dur-fast) var(--ease-out);
}

.capp-open:hover { background: var(--p-100); }
.capp-open:focus-visible { outline: none; box-shadow: var(--shadow-focus); }

.capp-wd {
    height: var(--h-sm);
    padding: 0 10px;
    border: 1px solid var(--border-default);
    border-radius: var(--r-md);
    background: var(--surface-raised);
    font-family: inherit;
    font-size: var(--fs-xs);
    font-weight: var(--fw-medium);
    color: var(--text-tertiary);
    white-space: nowrap;
    cursor: pointer;
    transition: border-color var(--dur-fast) var(--ease-out),
                color var(--dur-fast) var(--ease-out);
}

.capp-wd:hover { border-color: var(--danger-200); color: var(--danger-700); }
.capp-wd:focus-visible { outline: none; box-shadow: var(--shadow-focus); }

/* ---------- The withdraw confirm ----------
   Across the whole row, under it. Built into the actions cell it
   was 220px wide at the right-hand edge of a table that scrolled,
   which is the worst place on the page for an irreversible decision
   and the box explaining it. */

.capp-panel {
    padding: 14px 16px 16px;
    border-block-start: 1px solid var(--danger-100);
    background: var(--danger-50);
}

.capp-panel-h {
    margin: 0;
    font-size: var(--fs-md);
    font-weight: var(--fw-semibold);
    color: var(--danger-700);
}

.capp-panel-p {
    margin: 3px 0 0;
    max-width: 62ch;
    font-size: var(--fs-sm);
    line-height: var(--lh-normal);
    color: var(--text-secondary);
}

.capp-panel-in {
    box-sizing: border-box;
    width: 100%;
    max-width: 520px;
    margin-block-start: 10px;
    padding: 8px 11px;
    border: 1px solid var(--border-default);
    border-radius: var(--r-md);
    background: var(--surface-raised);
    font-family: inherit;
    font-size: var(--fs-sm);
    line-height: var(--lh-normal);
    color: var(--text-primary);
    resize: vertical;
}

.capp-panel-in:focus-visible {
    outline: none;
    border-color: var(--danger-500);
    box-shadow: 0 0 0 3px rgba(220, 38, 38, 0.14);
}

.capp-panel-acts {
    display: flex;
    align-items: center;
    gap: 8px;
    margin-block-start: 10px;
}

.capp-danger {
    height: var(--h-lg);
    padding: 0 16px;
    border: 1px solid var(--danger-600);
    border-radius: var(--r-md);
    background: var(--danger-600);
    font-family: inherit;
    font-size: var(--fs-sm);
    font-weight: var(--fw-semibold);
    color: var(--text-inverse);
    cursor: pointer;
    transition: background-color var(--dur-fast) var(--ease-out);
}

.capp-danger:hover:not(:disabled) {
    background: var(--danger-700);
    border-color: var(--danger-700);
}

.capp-danger:disabled { opacity: 0.6; cursor: default; }
.capp-danger:focus-visible { outline: 2px solid var(--danger-700); outline-offset: 2px; }

.capp-quiet {
    height: var(--h-lg);
    padding: 0 14px;
    border: 1px solid var(--border-default);
    border-radius: var(--r-md);
    background: var(--surface-raised);
    font-family: inherit;
    font-size: var(--fs-sm);
    font-weight: var(--fw-medium);
    color: var(--text-secondary);
    cursor: pointer;
}

.capp-quiet:hover { background: var(--surface-hover); }
.capp-quiet:focus-visible { outline: none; box-shadow: var(--shadow-focus); }

/* One size down, for the follow-up panel's pair - they sit on a row
   beside the text rather than under a heading. */
.capp-sm { height: var(--h-md); padding-inline: 12px; }

.capp-go {
    height: var(--h-md);
    padding: 0 14px;
    border: 1px solid var(--p-700);
    border-radius: var(--r-md);
    background: var(--p-700);
    font-family: inherit;
    font-size: var(--fs-sm);
    font-weight: var(--fw-semibold);
    color: var(--text-inverse);
    white-space: nowrap;
    cursor: pointer;
    transition: background-color var(--dur-fast) var(--ease-out);
}

.capp-go:hover:not(:disabled) { background: var(--p-800); border-color: var(--p-800); }
.capp-go:disabled { opacity: 0.6; cursor: default; }
.capp-go:focus-visible { outline: none; box-shadow: var(--shadow-focus); }

/* ---------- Breakpoints ----------
   Three reflows and one change of kind. The stage block is the widest fixed
   column, so it drops to its own line first; the actions follow once the role
   and the company have nowhere left to shrink; the fit ring joins the stage
   line when the role is down to its last hundred pixels. Below a tablet the
   page stops being a fixed-height region at all - see the note on that block. */

@media (max-width: 1060px) {
    .capp-row-in {
        grid-template-columns: auto minmax(0, 1fr) 40px auto;
        grid-template-areas:
            "mark main  fit   acts"
            ".    stage stage stage";
    }

    .capp-stage { display: flex; align-items: center; gap: 12px; }
    .capp-rail { flex: 1; max-width: 220px; margin-block-start: 0; }
}

/* ---------- Below a tablet: a document again ----------

   The fixed-height region above is a desktop idea and it inverts on a phone.
   It assumes the chrome over the list is about 250px, which is true while the
   figures are one row, the key is one row and the toolbar is one row. At
   390px none of that holds: the band is 296px, the toolbar wraps to three
   lines, and the list - the page - is handed what is left. Measured on a
   390x844 phone that was 140px, less than one application; at 360x640 the
   band alone was taller than the pane and both the follow-up panel and the
   list were crushed to two pixels, so the one thing on this page a candidate
   can act on was not on the screen at all.

   So below this the page is a document: .capp-page takes its content's
   height, the list scrolls with everything else, and <main> carries the one
   scroll. PageHeader is still sticky, which is the part worth pinning on a
   phone; the band and the toolbar scroll away, which is what a reader expects
   of them at this size.

   The height clause is the same failure from the other direction - a short
   window squeezes the region exactly as a narrow one does, and 560px of floor
   does not save it. A 844x390 landscape phone left the list 90px.

   Inside this block every inner scroller has to be released too, or the page
   grows its own scroll and keeps a second one nested in it. */

@media (max-width: 820px), (max-height: 700px) {
    .capp-page {
        block-size: auto;
        min-block-size: 0;
    }

    .capp { flex: 0 0 auto; }

    /* No longer the scroll container: no cap, no overflow, and nothing above
       it may be shrunk to make room for it. */
    .capp-list {
        flex: 0 0 auto;
        overflow: visible;
    }

    .capp-sum,
    .capp-fu,
    .capp-tools { flex-shrink: 0; }

    .capp-fu {
        max-block-size: none;
        overflow: visible;
    }
}

@media (max-width: 620px) {
    .capp-row-in {
        grid-template-columns: auto minmax(0, 1fr) 40px;
        grid-template-areas:
            "mark  main  fit"
            "stage stage stage"
            "acts  acts  acts";
        gap: 10px 12px;
        padding-inline: 14px;
    }

    .capp-acts { justify-content: flex-start; }
    .capp-rail { max-width: none; }
    .capp-sum { padding-inline: 14px; }

    .capp-fu-head,
    .capp-fu-row,
    .capp-panel { padding-inline: 14px; }

    .capp-fu-acts { width: 100%; }
    .capp-fu-acts > * { flex: 1; }

    /* The one thing on this page a candidate can still act on, so it gets the
       same target as everything else here. .capp-sm is a desktop refinement -
       it exists so the pair sits quietly beside the text rather than under a
       heading, and at this width there is no beside. */
    .capp-fu-acts .capp-sm,
    .capp-fu-acts .capp-go { height: var(--h-lg); }

    /* The band's own reflow. auto-fit at 128px drops to a single column below
       about 380px of band, which stands four figures on top of each other and
       spends 217px of a phone screen saying four numbers. 104px keeps them
       two by two, which is the shape they are read in. */
    .capp-figs {
        grid-template-columns: repeat(auto-fit, minmax(104px, 1fr));
        gap: 12px;
    }

    /* The bar is decoration here, not eleven controls. Its segments are sized
       by share of the tracker, so on a 320px screen the small stages are 8px
       wide - a target that can only be hit by accident, and every accident
       filters the list to something the candidate did not ask for. It is
       already aria-hidden with tabindex="-1"; the key below is the labelled
       way to do the same thing, and it is a proper target at this size. */
    .capp-bar-seg {
        pointer-events: none;
        cursor: default;
    }

    /* Touch sizing for the controls that survive. 28px is a mouse target. */
    .capp-key-b { height: var(--h-lg); padding-inline: 12px; }
    .capp-open,
    .capp-wd,
    .capp-clear { height: var(--h-lg); }

    /* Full width rather than two buttons pinned left with a gap of dead
       space, and each a real target. */
    .capp-acts > * { flex: 1 1 0; justify-content: center; text-align: center; }

    /* The count reads as a caption under the toolbar it belongs to, not as
       something aligned to the far edge on a line of its own. */
    .capp-tools-end {
        inline-size: 100%;
        margin-inline-start: 0;
    }

    /* "Withdraw application" and "Cancel" side by side need about 245px; at
       320px they had 210px and both buttons lost the end of their labels
       rather than the row wrapping. */
    .capp-panel-acts { flex-wrap: wrap; }
    .capp-panel-acts > * { flex: 1 1 auto; }
}

/* ---------- Phone ----------
   The role is the line a candidate scans, and by here it is the line with the
   least room: the monogram and the fit ring take 78px plus gaps out of a
   208px row, and a real job title was wrapping to four lines in what was left.
   The ring moves down to the stage line - it is a number, it can sit beside
   the stage it belongs to - and the role gets the top row to itself. */

@media (max-width: 460px) {
    .capp-row-in {
        grid-template-columns: auto minmax(0, 1fr) 40px;
        grid-template-areas:
            "mark  main  main"
            "stage stage fit"
            "acts  acts  acts";
        gap: 8px 10px;
        padding: 12px;
    }

    .capp-mark {
        width: 34px;
        height: 34px;
    }

    /* Two lines now, so the rail is not fighting a 144px chip for the 60px
       left over beside it. */
    .capp-stage {
        flex-wrap: wrap;
        gap: 7px 10px;
    }

    .capp-rail {
        flex: 1 1 100%;
        max-width: none;
    }
}

/* The narrowest phones still in use. auto-fit has nothing left to measure
   with here - 104px twice plus the gap is more than the band is wide - so the
   two-by-two is stated outright and the figures step down one size to pay
   for it. */
@media (max-width: 400px) {
    .capp-figs {
        grid-template-columns: 1fr 1fr;
        gap: 10px 12px;
    }

    .capp-fig-n { font-size: var(--fs-2xl); }
    .capp-fig-sub .capp-fig-n { font-size: var(--fs-xl); }
}

@media (prefers-reduced-motion: reduce) {
    .capp-bar-seg:hover { transform: none; }
}


/* ============================================================
   53. Candidate: Settings (cset-*)

   /me/settings was the last candidate screen still drawing itself
   in style attributes: five QCards in a 620px column, and inside
   them a toggle built from two nested <div>s with an @onclick.

   That shape had three real costs, not just untidiness.

     the toggles were not controls   a <div @onclick> is not
                                     focusable, has no role, and
                                     announces nothing - so the six
                                     switches that decide what this
                                     person hears from Jork could
                                     not be reached from a keyboard
                                     at all, and a screen reader
                                     read six unlabelled boxes
     nothing said the current state  five sections, each of which
                                     has to be opened and read to
                                     find out whether 2FA is on or
                                     whether presence is hidden
     the column was fixed at 620px   which is a long scroll on a
                                     laptop and, with inline styles,
                                     had no breakpoint on a phone

   So the page moves onto the chrome the two settings hubs already
   use - .set / .set-nav / .set-main from "18. Settings hub", and
   QSettingsSection for each section head. The rail here is a table
   of contents rather than a section switcher: five sections is a
   scroll, not a menu, and every section stays on the page. What
   the rail adds is the answer to "what is my account set to" -
   each row carries its section's current value as a badge, so the
   state is legible before anything is opened, and the row is also
   the way down to the control that changes it.

   The rows inside the sections are the shared .lst list, the
   switches are QToggle (.switch - a real role="switch" button),
   the extra-time choices are .chip-tog, and the password form is
   .fld + .input closed by a QSaveBar. Almost nothing here is new.

   What is here, and it is deliberately short:
     .cset-sec    a section as a jump target - the scroll offset
                  that keeps its head clear of the pinned header
     .cset-chan   the two delivery channels, as tiles

   Delivery was two bare <input type="checkbox">, immediately under
   six switches that mean the same kind of thing. Two idioms for one
   question in one card is the reason they are tiles now: they are a
   choice of destination, not a seventh notification, and the tile
   is the shape that says so.
   ============================================================ */

/* .pg-head pins at -8px and stands 56px tall, so its lower edge is
   48px down the scroll port; 12px more is the gap between bands
   everywhere else, and the section head lands clear of it rather
   than under it. */
.cset-sec { scroll-margin-block-start: 60px; }

/* Smooth only where it is wanted: a jump the reader asked for is
   easier to follow when the page travels, but honouring the OS
   setting matters more. */
@media (prefers-reduced-motion: no-preference) {
    .cset { scroll-behavior: smooth; }
}

/* ---------- The rail, at five rows instead of nineteen ----------

   .set-link truncates its label, which is right for a seeded menu of
   nineteen where the reader is hunting for a name they already know.
   Here the labels are section headings - "Assessments and
   accessibility" is the whole of what that section is - and clipping
   one to "Assessments and acc..." hides the half that distinguishes
   it. Five rows can afford the second line. */
.cset .set-link {
    height: auto;
    min-height: 34px;
    padding-block: 7px;
    align-items: flex-start;
}

.cset .set-link-ico { margin-block-start: 1px; }

.cset .set-link-lab {
    white-space: normal;
    overflow: visible;
    line-height: 1.35;
}

.cset .set-link-badge { margin-block-start: 2px; }

/* ---------- Spacing this page owns ----------
   Three rules rather than three style attributes, because the whole
   point of the rewrite was that a style attribute cannot carry a
   breakpoint or a state. */

/* A note under the control it qualifies. */
.cset-gap { margin-block-start: 12px; }

/* The password grid under its own caption. */
.cset-gap-sm { margin-block-start: 10px; }

/* The sign-out POST is a form only because that is what the endpoint
   needs; it must not add a line box to the row it sits in. */
.cset-form { margin: 0; }

/* ---------- Delivery channels ---------- */


.cset-chan {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
    gap: 8px;
}

.cset-chan-b {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 11px 12px;
    border: 1px solid var(--border-default);
    border-radius: var(--r-lg);
    background: var(--surface-raised);
    font: inherit;
    text-align: start;
    cursor: pointer;
    transition: border-color var(--dur-fast) var(--ease-out),
                background-color var(--dur-fast) var(--ease-out);
}

.cset-chan-b:hover { border-color: var(--border-strong); background: var(--n-25); }

.cset-chan-b:focus-visible { outline: none; box-shadow: var(--shadow-focus); }

.cset-chan-b.is-on {
    border-color: var(--p-700);
    background: var(--p-50);
}

/* The tile picks up the band's tint when it is on, for the reason
   .set-link.is-on inverts its own: the row stays one object rather
   than a tinted box with a grey square stuck to it. */
.cset-chan-ico {
    display: grid;
    place-items: center;
    width: 30px;
    height: 30px;
    flex-shrink: 0;
    border-radius: var(--r-md);
    background: var(--n-100);
    color: var(--n-600);
    transition: background-color var(--dur-fast) var(--ease-out),
                color var(--dur-fast) var(--ease-out);
}

.cset-chan-b.is-on .cset-chan-ico {
    background: var(--surface-raised);
    color: var(--p-700);
    box-shadow: var(--shadow-xs);
}

.cset-chan-txt { flex: 1; min-width: 0; }

.cset-chan-lab {
    display: block;
    font-size: var(--fs-md);
    font-weight: var(--fw-semibold);
    color: var(--text-primary);
}

/* Off is a state, not an absence. Without this line an unselected
   tile says nothing at all, and the reader has to work out that the
   one that is not tinted is the one that is off. */
.cset-chan-sub {
    display: block;
    margin-block-start: 1px;
    font-size: var(--fs-xs);
    line-height: 1.4;
    color: var(--text-tertiary);
}

.cset-chan-b.is-on .cset-chan-sub { color: var(--p-700); }

.cset-chan-mark {
    display: grid;
    place-items: center;
    width: 18px;
    height: 18px;
    flex-shrink: 0;
    border-radius: var(--r-full);
    background: var(--p-700);
    color: var(--text-inverse);
}


/* ---------- Narrow ----------
   Below 1080px "18. Settings hub" turns the rail into a horizontal
   scrolling strip, and a chip that wraps onto two lines in a strip is
   a chip twice as tall as its neighbours. The label goes back to one
   line here; nothing is being hunted for in a strip of five. */
@media (max-width: 1080px) {
    .cset .set-link {
        height: 34px;
        padding-block: 0;
        align-items: center;
    }

    .cset .set-link-ico,
    .cset .set-link-badge { margin-block-start: 0; }

    .cset .set-link-lab { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
}

@media (prefers-reduced-motion: reduce) {
    .cset-chan-b,
    .cset-chan-ico { transition: none; }
}

/* ------------------------------------------------------------
   Resume template gallery (rtpl-*)

   /admin/resume-templates. Left out of the console redesign and
   written entirely in style attributes - fifty-five of them in
   two hundred and seventy lines, including its own stat tile
   beside the shared KpiTile and its own pill row beside
   .chip-tog. It wears PageHeader / KpiTile / .chip-tog / .tbl
   now, so it lines up with Certifications and Tenants beside it
   in the nav.

   Two things are fixed here rather than moved. The subtitle was
   one long sentence in a single-line slot and was ellipsised
   mid-word at every width, so it wraps to two lines. And the
   cards stepped down wherever a template name wrapped to a
   second line, because each row's content found its own height;
   the name now reserves both lines whether it needs them or
   not, which is what keeps a row of Customize buttons on one
   line.
   ------------------------------------------------------------ */

.rtpl-kpis {
    display: grid;
    /* auto-fit for the same reason the catalogue uses it: a
       quarter of a laptop console is narrower than the longest
       of these labels standing in it. */
    grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
    gap: 10px;
    margin-block-end: 16px;
}

.rtpl-bar { margin-block-end: 14px; }

/* The count riding each filter. Tabular so the chips do not
   change width as the filter moves, and at --fs-xs rather than
   the 10.5px it was: JetBrains Mono's dotted zero below about
   11px reads as an 8, which turned a "10" on the All chip into
   an 18 that matched nothing else on the page. */
.rtpl-chip-n {
    margin-inline-start: 6px;
    font-size: var(--fs-xs);
    font-variant-numeric: tabular-nums;
    opacity: 0.75;
}

/* ---------- Cards ---------- */

.rtpl-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
    gap: 14px;
}

.rtpl-card {
    overflow: hidden;
    display: flex;
    flex-direction: column;
}

.rtpl-thumb {
    position: relative;
    /* A fixed band with the page drawn in A4 proportion inside it,
       rather than a box the preview stretches to fill. Sizing the
       band itself by aspect-ratio made it 460px tall on a two-up
       grid with everything in the top quarter; a portrait sheet
       centred in a fixed band reads as a document at every card
       width, which is the whole job of a thumbnail. */
    height: 190px;
    display: grid;
    place-items: center;
    padding: 14px;
    background: var(--surface-sunken);
}

.rtpl-thumb-pill {
    position: absolute;
    inset-block-start: 8px;
    inset-inline-end: 8px;
}

.rtpl-body {
    display: flex;
    flex-direction: column;
    gap: 6px;
    padding: 12px 14px;
    border-block-start: 1px solid var(--border-subtle);
}

/* No reserved second line: .rtpl-desc takes the slack, so the
   meta, render count and button sit on the card foot and line up
   across a row whether or not a name wrapped. */
.rtpl-name {
    margin: 0;
    font-size: var(--fs-base);
    font-weight: 600;
    line-height: var(--lh-snug);
}

.rtpl-desc {
    margin: 0;
    flex: 1;
    font-size: var(--fs-xs);
    line-height: var(--lh-normal);
    color: var(--text-tertiary);
}

.rtpl-meta {
    margin: 0;
    display: flex;
    align-items: center;
    gap: 8px;
}

.rtpl-code {
    font-family: var(--font-mono);
    font-size: var(--fs-xs);
    color: var(--text-tertiary);
}

.rtpl-renders {
    margin: 0;
    font-size: var(--fs-xs);
    font-variant-numeric: tabular-nums;
    color: var(--text-tertiary);
}

/* ---------- The drawn preview ---------- */

.rtpl-pv {
    height: 100%;
    /* A4 portrait. The page is as wide as its height allows, so
       the sheet stays the same shape whatever the card does. */
    aspect-ratio: 1 / 1.414;
    display: flex;
    flex-direction: column;
    overflow: hidden;
    border-radius: var(--r-md);
    background: var(--n-0);
    box-shadow: 0 1px 3px rgb(0 0 0 / 0.08);
}

.rtpl-pv-banner { padding: 8px 10px; }

.rtpl-pv-banner-bar {
    display: block;
    height: 7px;
    width: 45%;
    border-radius: var(--r-xs);
    background: rgb(255 255 255 / 0.85);
}

.rtpl-pv-cols { flex: 1; display: flex; min-height: 0; }
.rtpl-pv-cols.is-rev { flex-direction: row-reverse; }

.rtpl-pv-side { flex-grow: 0; flex-shrink: 0; padding: 12px 9px; }

.rtpl-pv-avatar {
    display: block;
    width: 24px;
    height: 24px;
    margin-block-end: 9px;
    border-radius: 50%;
    background: rgb(255 255 255 / 0.22);
}

.rtpl-pv-sline {
    display: block;
    height: 4px;
    width: 88%;
    margin-block-end: 4px;
    border-radius: var(--r-xs);
    background: rgb(255 255 255 / 0.22);
}

.rtpl-pv-sline.is-strong { height: 5px; width: 85%; background: rgb(255 255 255 / 0.55); }
.rtpl-pv-sline.is-mid { width: 62%; background: rgb(255 255 255 / 0.3); }
.rtpl-pv-sline.is-short { width: 70%; }

/* An unfilled sidebar is still a sidebar. Drawn as a ruled
   column rather than a coloured one, so a two-column template
   with a transparent rail still reads as two columns under a
   card labelled two-col. */
.rtpl-pv-side.is-ghost { border-inline-end: 1px solid var(--border-subtle); }
.rtpl-pv-side.is-ghost .rtpl-pv-avatar { background: var(--n-150); }
.rtpl-pv-side.is-ghost .rtpl-pv-sline { background: var(--n-150); }
.rtpl-pv-side.is-ghost .rtpl-pv-sline.is-strong { background: var(--n-300); }
.rtpl-pv-side.is-ghost .rtpl-pv-sline.is-mid { background: var(--n-200); }

.rtpl-pv-cols.is-rev .rtpl-pv-side.is-ghost {
    border-inline-end: 0;
    border-inline-start: 1px solid var(--border-subtle);
}

.rtpl-pv-main { flex: 1; min-width: 0; padding: 14px 12px; }

/* Height and width come from the head font, so a serif and a
   mono template do not draw the same bar. */
.rtpl-pv-head { display: block; border-radius: var(--r-xs); margin-block-end: 9px; }

.rtpl-pv-sec {
    display: block;
    height: 5px;
    width: 30%;
    border-radius: var(--r-xs);
}

.rtpl-pv-line {
    display: block;
    height: 3px;
    border-radius: var(--r-xs);
    opacity: 0.45;
}

/* ---------- Registry table ---------- */

.rtpl-registry { margin-block-start: 18px; }

.rtpl-hint { font-size: var(--fs-xs); color: var(--text-tertiary); }

.rtpl-tbl { min-width: 860px; }

.rtpl-col-layout  { width: 140px; }
.rtpl-col-tokens  { width: 220px; }
.rtpl-col-status  { width: 110px; }
.rtpl-col-renders { width: 120px; }
.rtpl-col-act     { width: 56px; }

.rtpl-cell-name { display: block; font-weight: 600; }

.rtpl-cell-code {
    display: block;
    font-family: var(--font-mono);
    font-size: var(--fs-xs);
    color: var(--text-tertiary);
}

.rtpl-cell-tokens {
    font-family: var(--font-mono);
    font-size: var(--fs-xs);
    color: var(--text-secondary);
    white-space: nowrap;
}

.rtpl-cell-tokens .rtpl-sw { margin-inline-end: 0; }

.rtpl-cell-n { font-family: var(--font-mono); font-variant-numeric: tabular-nums; }

.rtpl-sw {
    display: inline-block;
    width: 9px;
    height: 9px;
    margin-inline-end: 4px;
    border-radius: var(--r-xs);
    vertical-align: middle;
}

/* Stacked, so the two swatches read as two named facts rather
   than as one value printed twice. */
.rtpl-tok { display: flex; align-items: center; gap: 4px; }
.rtpl-tok + .rtpl-tok { margin-block-start: 3px; }

.rtpl-tok-k {
    min-width: 3.6rem;
    font-family: var(--font-sans);
    color: var(--text-tertiary);
}

/* ------------------------------------------------------------
   Resume designer (rdsg-*)

   /admin/resume-designer. Only the Page & layout fields are
   lifted out of style attributes here; the rest of this screen
   is still inline and is its own piece of work.

   They were a two-up grid in a 380px rail, which is not wide
   enough for "Two-column · sidebar right" - the select rendered
   "Two-column · sidet", cut mid-word, on the one control that
   says what the live preview beside it is showing.
   ------------------------------------------------------------ */

/* ---------- Top bar ---------- */

/* Wraps into bands rather than compressing. Flex items shrink
   below their max-width by default, which is how the name field
   ended up reading "Template na" on a 1024 laptop. */
.rdsg-bar {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 10px;
    margin-block-end: 14px;
}

.rdsg-bar-id {
    flex: 1 1 24rem;
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 10px;
    min-width: 0;
}

.rdsg-bar-act {
    display: flex;
    align-items: center;
    gap: 8px;
    margin-inline-start: auto;
}

.rdsg-name-in { flex: 1 1 12rem; max-width: 280px; font-weight: 600; }
.rdsg-code-in { flex: 0 1 11rem; max-width: 180px; font-family: var(--font-mono); }

/* The unsaved marker is three words that mean one thing; broken
   over three lines between the buttons it read as three. */
.rdsg-state {
    font-size: var(--fs-xs);
    color: var(--text-tertiary);
    white-space: nowrap;
}

/* ---------- Preview header ---------- */

.rdsg-pv-lbl {
    flex: 1;
    min-width: 0;
    font-size: var(--fs-sm);
    font-weight: 600;
    /* One line or an ellipsis. "Live preview · A4 · Two-col ·
       sidebar left" wrapping to three lines pushed the zoom
       control down and made the header taller than the toolbar
       it sits under. */
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* ---------- Split ---------- */

.rdsg-split { display: grid; gap: 14px; align-items: stretch; }

@media (min-width: 1200px) {
    .rdsg-split { grid-template-columns: 380px minmax(0, 1fr); }
}

/* ---------- Preview page ---------- */

.rdsg-pv-scroll { flex: 1; overflow: auto; padding: 20px; }

/* Reserves the scaled footprint. A transformed element still
   occupies its untransformed size in layout, so without this the
   scroll container sizes to a full A4 page whatever the zoom
   says. */
.rdsg-pv-frame {
    position: relative;
    flex: none;
    margin-inline: auto;
    /* On the frame rather than the page, so the shadow does not
       thin out as the page scales down. */
    box-shadow: 0 8px 24px rgb(15 19 32 / 0.10);
}

.rdsg-pv-page {
    position: absolute;
    inset-block-start: 0;
    inset-inline-start: 0;
    transform-origin: top left;
    display: flex;
    background: #fff;
}

/* ---------- Page & layout fields ---------- */

.rdsg-fields { display: grid; gap: 10px; }

.rdsg-field { display: flex; flex-direction: column; gap: 4px; }

.rdsg-lbl { font-size: var(--fs-sm); font-weight: 500; }

/* Room for both once the rail has it. The A4/Letter values are
   short, so page size takes the narrower half. */
@media (min-width: 1600px) {
    .rdsg-fields { grid-template-columns: minmax(0, 0.8fr) minmax(0, 1.2fr); }
}


/* ============================================================
   54. Recruiter: Offers list (off-*)

   The list as a fixed-height region - the shape the applications
   inbox and the jobs list already carry, and here for the same reason.
   Past the first flick through fifty offers the column labels were gone,
   and three right-aligned money columns are exactly the thing that cannot
   be read without them: base, equity and total Y1 are the same shape as
   each other, so an unlabelled figure is not a smaller number, it is an
   unknown one. Only the rows should move; the page header, the tiles, the
   status filter and the AI rail all stay where the recruiter left them.

   The chain is four boxes deep because the card is a component. .off-page
   takes <main>'s height, and every box between it and the rows has to
   agree to be smaller than its content on both axes - a flex item refuses
   by default to be shorter than the fifty rows inside it or narrower than
   its own min-content width, and either refusal hands the scroll back to
   <main>. .off-card is passed to QCard as its surface class, so the one
   rule that reaches inside the component is the body hook it documents.

   Nothing here is gated on width. A gate means a viewport where the
   wrapper is an ordinary div again and the region is undone; what the
   page does at a narrow width is unchanged, because the two-column shape
   this replaced had no breakpoint either.
   ============================================================ */

/* block-size, not min-block-size: the height has to be definite or the
   boxes below it size to their content, the scrollport is as tall as
   fifty rows, and nothing scrolls anywhere except the page.

   The floor is the short-window escape hatch, and it belongs here rather
   than further down the chain: max(100%, 620px) is still a definite
   height, so the region keeps working, the card is drawn at its full size
   and the page takes the overflow. 620px is this page's chrome - a pinned
   header, the tile row and the search-and-status bar - plus about four
   rows. */
.off-page {
    block-size: 100%;
    min-block-size: 620px;
    display: flex;
    flex-direction: column;
    /* The tail belongs to a page that scrolls past its own end. Here it
       is dead space taken from the rows. */
    padding-block-end: 8px;
}

/* The working row: the table and the rail it comments on. The grid was an
   inline style on the same two tracks - it is a class now because the row
   has to be told to be shorter than its content, which is the half an
   inline style could not say.

   min-block-size: 0 is not optional: a flex item's default minimum is its
   content, so leaving it out sizes this box to fifty offers and the only
   thing that scrolls is the page. min-inline-size is the same bargain on
   the other axis - without it a wide column's min-content width
   propagates back up and the sideways scroll lands on <main> as a
   page-wide scrollbar. */
.off-body {
    flex: 1 1 auto;
    min-block-size: 0;
    min-inline-size: 0;
    display: grid;
    grid-template-columns: 1fr 320px;
    gap: 16px;
}

/* The card stretches to the row - it is the box the rows scroll inside,
   so it has to be the height of the region rather than the height of its
   own content. */
.off-card,
.off-card > .q-card-body {
    min-block-size: 0;
    min-inline-size: 0;
    display: flex;
    flex-direction: column;
}

/* The one box on the page that scrolls. Only the block axis was asked
   for and both are given - overflow on one axis gives it on the other -
   which costs nothing here: seven columns fit, and the row's click target
   opens a drawer rather than a menu a scrollport would clip. */
.off-scroll {
    flex: 1 1 auto;
    min-block-size: 0;
    min-inline-size: 0;
    overflow: auto;
    /* The labels pin to the top of their own scrollport. The shared rule
       measures from the pinned page header, which is not the box they
       scroll inside any more - at 48px they would sit a bar's height
       below the first row and let it slide underneath. */
    --tbl-top: 0px;
}

/* Neither state has rows to scroll, and the card is now a full-height
   surface either way: centred, rather than a line of grey text stranded
   at the top of 600px of white. */
.off-card > .q-card-body > .pg-loading,
.off-card > .q-card-body > .q-empty { margin-block: auto; }

/* Its natural height, and a scrollbar of its own only if it is ever
   taller than the region. Stretching it instead would leave the
   commission card's two figures floating in the middle of an empty
   700px column - the rail is a stack of cards, not a panel. */
.off-rail {
    align-self: start;
    max-block-size: 100%;
    overflow: auto;
}


/* ============================================================
   55. Candidate: job board (cjb-*)

   The board a candidate actually looks for work on. It was a
   filter rail and a column of cards whose every rule lived in an
   inline style attribute — no hover, no focus ring, no
   breakpoint, and a card whose whole right-hand column collapsed
   into a 110px gutter on a phone.

   The shape here is the one the reference design asks for: a
   banner that says what the page is for and carries the search, a
   filter card down the left with its groups folded under headings,
   and a results column under a bar that names what is currently
   narrowing it — chips for the filters, a sort, and three layouts
   for the same jobs.

   What is here:
     .cjb-hero    the banner, its search box and its flat artwork
     .cjb-grid    the two-column frame — filters plus results
     .cjb-side    the filter card, pinned and scrolling on its own
     .cjb-grp     one filter group, .is-shut when folded away
     .cjb-opt     one facet row: a real checkbox wearing a tile
     .cjb-range   the pay band — two handles over one track
     .cjb-bar     the chips, the sort and the layout switch
     .cjb-card    one vacancy, .is-top for a strong match
     .cjb-pager   first, last, and the current page either side

   The accent is the product navy, as everywhere else in this
   portal: section 4 keeps a violet local to the sidebar, and the
   banner's gradient is the one place that reaches past the navy —
   it runs from --p-700 out to indigo, which is what stops a
   150px block of one flat colour reading as an error state.
   ============================================================ */

.cjb {
    display: flex;
    flex-direction: column;
    gap: 14px;
    /* The board is the whole page; the shell's own padding is all the
       gutter it needs. */
    min-width: 0;
}

/* ---------- The banner ---------- */

.cjb-hero {
    position: relative;
    overflow: hidden;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 20px;
    min-height: 132px;
    padding: 24px 28px;
    border-radius: var(--r-xl);
    background: linear-gradient(102deg, var(--p-700) 0%, var(--p-500) 54%, #6366f1 100%);
    color: var(--text-inverse);
}

.cjb-hero-txt { min-width: 0; }

.cjb-hero-t {
    margin: 0;
    font-family: var(--font-display);
    font-size: var(--fs-3xl);
    font-weight: var(--fw-bold);
    line-height: var(--lh-tight);
    letter-spacing: -0.01em;
}

.cjb-hero-b {
    margin: 6px 0 0;
    max-width: 52ch;
    font-size: var(--fs-md);
    color: rgba(255, 255, 255, 0.82);
}

/* The same search the topbar sends its term to, where somebody who
   came straight to the board can find it. */
.cjb-find {
    display: flex;
    align-items: center;
    gap: 9px;
    width: min(440px, 100%);
    height: var(--h-xl);
    margin-block-start: 16px;
    padding: 0 14px;
    border-radius: var(--r-full);
    background: var(--n-0);
    box-shadow: var(--shadow-md);
}

.cjb-find-i { color: var(--text-tertiary); flex-shrink: 0; }

.cjb-find-in {
    flex: 1;
    min-width: 0;
    border: none;
    outline: none;
    background: transparent;
    font: inherit;
    font-size: var(--fs-sm);
    color: var(--text-primary);
}

/* Decorative, and the first thing to go when the banner runs out of
   width — it carries no information. */
.cjb-hero-art {
    flex-shrink: 0;
    width: 240px;
    height: 130px;
}

/* ---------- The frame ---------- */

.cjb-grid {
    display: grid;
    /* Wide enough for "Executive search" beside its checkbox and its
       count without ellipsis — the longest facet value the seeded data
       produces, against the widest row this panel draws. */
    grid-template-columns: 284px minmax(0, 1fr);
    gap: 16px;
    align-items: start;
}

/* ---------- The filter card ---------- */

.cjb-side {
    position: sticky;
    /* Clear of the shell's own 8px of padding. */
    inset-block-start: 8px;
    /* A sticky box taller than its scroll port stops following the
       scroll entirely — it pins once and then rides up out of view.
       Four open groups clear a laptop viewport, so the panel is capped
       and the overflow goes to its own scrollbar. */
    max-height: calc(100vh - 76px);
    overflow-y: auto;
    /* Reaching the end of the filters must not start scrolling the
       results underneath them. */
    overscroll-behavior: contain;
    padding: 14px;
    background: var(--surface-raised);
    border: 1px solid var(--border-subtle);
    border-radius: var(--r-xl);
    box-shadow: var(--shadow-xs);
}

.cjb-side-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    padding-block-end: 12px;
}

/* The sheet's own chrome: the button that raises it, the scrim behind it,
   its grab bar, its close and its "show results". All of it is off above
   900px, where the panel is a card that is simply there. */
.cjb-fbtn,
.cjb-scrim,
.cjb-grab,
.cjb-sheet-x { display: none; }

/* Two classes, because .cjb-btn below sets a display of its own and would
   otherwise win this on source order alone. The rule that turns it back on
   at 900px is written the same way for the same reason. */
.cjb-btn.cjb-sheet-done { display: none; }

/* Transparent to the desktop card and to the tablet strip alike: the groups
   have to stay direct children of .cjb-side in both, because the strip lays
   them out as grid items. It becomes the sheet's scrolling middle at 900px,
   between the pinned head and foot. */
.cjb-side-body { display: contents; }

.cjb-side-t {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    font-size: var(--fs-base);
    font-weight: var(--fw-semibold);
    color: var(--text-primary);
}

.cjb-side-t svg { color: var(--text-tertiary); }

.cjb-reset {
    padding: 2px 4px;
    border: none;
    border-radius: var(--r-sm);
    background: transparent;
    font: inherit;
    font-size: var(--fs-xs);
    font-weight: var(--fw-semibold);
    color: var(--text-brand);
    cursor: pointer;
}

.cjb-reset:hover { text-decoration: underline; }
.cjb-reset:focus-visible { outline: none; box-shadow: var(--shadow-focus); }

/* ---------- Browse / Saved ---------- */

.cjb-tabs {
    display: flex;
    flex-direction: column;
    gap: 3px;
    padding-block-end: 6px;
}

.cjb-tab {
    display: flex;
    align-items: center;
    gap: 9px;
    min-height: var(--h-lg);
    padding: 0 10px;
    border-radius: var(--r-lg);
    font-size: var(--fs-sm);
    font-weight: var(--fw-medium);
    color: var(--text-secondary);
    text-decoration: none;
}

.cjb-tab svg { color: var(--text-tertiary); flex-shrink: 0; }
.cjb-tab-t { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cjb-tab:hover { background: var(--n-50); color: var(--text-primary); }
.cjb-tab:focus-visible { outline: none; box-shadow: var(--shadow-focus); }

.cjb-tab.is-on {
    background: var(--p-50);
    color: var(--text-brand);
    font-weight: var(--fw-semibold);
}

.cjb-tab.is-on svg { color: var(--text-brand); }

/* ---------- One filter group ---------- */

.cjb-grp { border-block-start: 1px solid var(--border-subtle); }

.cjb-grp-h {
    display: flex;
    align-items: center;
    gap: 8px;
    width: 100%;
    min-height: 42px;
    padding: 0 2px;
    border: none;
    background: transparent;
    font: inherit;
    font-size: var(--fs-md);
    font-weight: var(--fw-semibold);
    color: var(--text-primary);
    text-align: start;
    cursor: pointer;
}

/* Two of these headings have nothing to fold — a select is one row
   either way — so they are a div, and must not look pressable. */
.cjb-grp-h.is-static { cursor: default; }

.cjb-grp-i { color: var(--text-tertiary); flex-shrink: 0; }
.cjb-grp-t { flex: 1; min-width: 0; }
.cjb-grp-h:hover:not(.is-static) .cjb-grp-t { color: var(--text-brand); }
.cjb-grp-h:focus-visible { outline: none; box-shadow: var(--shadow-focus); border-radius: var(--r-md); }

/* The arrow points down while the group is open and turns a quarter
   turn when it shuts — the rotation the sidebar's collapse chevron
   makes in section 4. */
.cjb-grp-ar {
    flex-shrink: 0;
    color: var(--text-tertiary);
    transition: transform var(--dur-fast) var(--ease-out);
}

.cjb-grp.is-shut .cjb-grp-ar { transform: rotate(-90deg); }
[dir="rtl"] .cjb-grp.is-shut .cjb-grp-ar { transform: rotate(90deg); }

.cjb-grp-b { padding-block-end: 10px; }

.cjb-hint {
    margin: 0;
    padding: 0 2px 4px;
    font-size: var(--fs-xs);
    line-height: var(--lh-snug);
    color: var(--text-tertiary);
}

/* ---------- One facet row ---------- */

.cjb-opt {
    display: flex;
    align-items: center;
    gap: 9px;
    position: relative;
    min-height: 30px;
    padding: 0 6px;
    border-radius: var(--r-md);
    font-size: var(--fs-sm);
    color: var(--text-secondary);
    cursor: pointer;
}

.cjb-opt:hover { background: var(--n-50); color: var(--text-primary); }
.cjb-opt:focus-within { outline: none; box-shadow: var(--shadow-focus); }
.cjb-opt.is-on { color: var(--text-primary); font-weight: var(--fw-medium); }

/* Off-screen, not display:none. A control that is not rendered is not
   focusable and not announced, and this is still a real set of
   checkboxes — the row is only its skin. */
.cjb-ctl {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    border: 0;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
}

.cjb-box {
    display: grid;
    place-items: center;
    flex-shrink: 0;
    width: 17px;
    height: 17px;
    border-radius: var(--r-sm);
    background: var(--n-0);
    box-shadow: inset 0 0 0 1.5px var(--border-strong);
    color: var(--text-inverse);
    transition: background-color var(--dur-fast) var(--ease-out),
                box-shadow var(--dur-fast) var(--ease-out);
}

.cjb-opt:hover .cjb-box { box-shadow: inset 0 0 0 1.5px var(--p-400); }

.cjb-opt.is-on .cjb-box {
    background: var(--p-700);
    box-shadow: inset 0 0 0 1.5px var(--p-700);
}

.cjb-opt-t {
    flex: 1;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* Tabular, so counts line up down the panel rather than drifting with
   the digit widths. */
.cjb-n {
    flex-shrink: 0;
    font-size: var(--fs-xs);
    font-variant-numeric: tabular-nums;
    color: var(--text-tertiary);
}

.cjb-n.is-on {
    padding: 1px 7px;
    border-radius: var(--r-full);
    background: var(--p-700);
    color: var(--text-inverse);
    font-weight: var(--fw-semibold);
}

/* A value that would leave nothing. Dimmed rather than removed: the
   count is the answer to "what is left to narrow by", and a facet
   value that vanishes takes its own zero with it. */
.cjb-opt.is-empty { color: var(--text-disabled); }
.cjb-opt.is-empty .cjb-n { color: var(--text-disabled); }

/* ---------- The pay band ---------- */

.cjb-range {
    position: relative;
    height: 22px;
    margin: 8px 8px 0;
}

.cjb-range-track,
.cjb-range-fill {
    position: absolute;
    inset-block-start: 50%;
    height: 4px;
    margin-block-start: -2px;
    border-radius: var(--r-full);
}

.cjb-range-track {
    inset-inline: 0;
    background: var(--n-150);
}

.cjb-range-fill { background: var(--p-700); }

/* Both handles sit on one track, so the two inputs are stacked and
   transparent to the pointer everywhere except their thumbs —
   otherwise the upper input would swallow every click meant for the
   lower one. */
.cjb-range-in {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 22px;
    margin: 0;
    padding: 0;
    background: transparent;
    pointer-events: none;
    -webkit-appearance: none;
    appearance: none;
}

.cjb-range-in::-webkit-slider-thumb {
    pointer-events: auto;
    -webkit-appearance: none;
    appearance: none;
    width: 16px;
    height: 16px;
    border: 2px solid var(--p-700);
    border-radius: var(--r-full);
    background: var(--n-0);
    box-shadow: var(--shadow-sm);
    cursor: pointer;
}

.cjb-range-in::-moz-range-thumb {
    pointer-events: auto;
    width: 16px;
    height: 16px;
    border: 2px solid var(--p-700);
    border-radius: var(--r-full);
    background: var(--n-0);
    box-shadow: var(--shadow-sm);
    cursor: pointer;
}

.cjb-range-in::-moz-range-track { background: transparent; }
.cjb-range-in:focus-visible { outline: none; }
.cjb-range-in:focus-visible::-webkit-slider-thumb { box-shadow: var(--shadow-focus); }
.cjb-range-in:focus-visible::-moz-range-thumb { box-shadow: var(--shadow-focus); }

.cjb-range-v {
    margin: 10px 0 0;
    font-size: var(--fs-sm);
    font-weight: var(--fw-semibold);
    font-variant-numeric: tabular-nums;
    color: var(--text-brand);
}

/* ---------- Selects ---------- */

.cjb-sel-w { position: relative; display: block; }

.cjb-sel {
    width: 100%;
    height: var(--h-lg);
    padding-inline: 11px 30px;
    border: 1px solid var(--border-default);
    border-radius: var(--r-md);
    background: var(--n-0);
    font: inherit;
    font-size: var(--fs-sm);
    color: var(--text-primary);
    cursor: pointer;
    -webkit-appearance: none;
    appearance: none;
}

.cjb-sel:hover { border-color: var(--border-strong); }
.cjb-sel:focus-visible { outline: none; border-color: var(--border-focus); box-shadow: var(--shadow-focus); }

/* Decorative: the select draws no arrow of its own once its appearance
   is reset, and this one sits where the native arrow was. */
.cjb-sel-ar {
    position: absolute;
    inset-inline-end: 10px;
    inset-block-start: 50%;
    transform: translateY(-50%);
    color: var(--text-tertiary);
    pointer-events: none;
}

.cjb-sel-sm .cjb-sel { height: var(--h-md); min-width: 148px; font-size: var(--fs-xs); }

/* ---------- Keeping a search ---------- */

.cjb-side-foot {
    display: flex;
    flex-direction: column;
    gap: 8px;
    padding-block-start: 12px;
    border-block-start: 1px solid var(--border-subtle);
}

.cjb-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 7px;
    min-height: var(--h-lg);
    padding: 0 14px;
    border: 1px solid var(--border-default);
    border-radius: var(--r-lg);
    background: var(--n-0);
    font: inherit;
    font-size: var(--fs-sm);
    font-weight: var(--fw-semibold);
    color: var(--text-secondary);
    cursor: pointer;
}

.cjb-btn:hover { background: var(--n-50); color: var(--text-primary); }
.cjb-btn:focus-visible { outline: none; box-shadow: var(--shadow-focus); }

.cjb-btn-p {
    border-color: var(--p-700);
    background: var(--p-700);
    color: var(--text-inverse);
}

.cjb-btn-p:hover { background: var(--p-800); border-color: var(--p-800); color: var(--text-inverse); }
.cjb-btn-p:disabled { opacity: 0.5; cursor: not-allowed; }
.cjb-btn-p:disabled:hover { background: var(--p-700); }

.cjb-btn-wide { width: 100%; }

.cjb-name {
    width: 100%;
    height: var(--h-lg);
    padding: 0 11px;
    border: 1px solid var(--border-default);
    border-radius: var(--r-md);
    font: inherit;
    font-size: var(--fs-sm);
    color: var(--text-primary);
}

.cjb-name:focus-visible { outline: none; border-color: var(--border-focus); box-shadow: var(--shadow-focus); }

.cjb-name-row { display: flex; gap: 8px; }
.cjb-name-row .cjb-btn { flex: 1; }

/* ---------- Results column ---------- */

.cjb-main {
    display: flex;
    flex-direction: column;
    gap: 10px;
    min-width: 0;
}

/* ---------- Saved searches ---------- */

.cjb-saved {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 8px;
    padding: 9px 14px;
    background: var(--surface-raised);
    border: 1px solid var(--border-subtle);
    border-radius: var(--r-lg);
}

.cjb-saved-t {
    font-size: var(--fs-2xs);
    font-weight: var(--fw-semibold);
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--text-tertiary);
}

.cjb-saved-item {
    display: inline-flex;
    align-items: center;
    border: 1px solid var(--border-default);
    border-radius: var(--r-full);
}

.cjb-saved-open {
    max-width: 190px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    padding: 3px 4px 3px 11px;
    border: none;
    background: transparent;
    font: inherit;
    font-size: var(--fs-xs);
    font-weight: var(--fw-semibold);
    color: var(--text-secondary);
    cursor: pointer;
}

.cjb-saved-open:hover { color: var(--text-brand); }

.cjb-saved-del {
    display: grid;
    place-items: center;
    padding: 3px 9px 3px 3px;
    border: none;
    background: transparent;
    color: var(--text-tertiary);
    cursor: pointer;
}

.cjb-saved-del:hover { color: var(--danger-600); }
.cjb-saved-open:focus-visible,
.cjb-saved-del:focus-visible { outline: none; box-shadow: var(--shadow-focus); border-radius: var(--r-full); }

/* ---------- The bar ---------- */

.cjb-bar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    flex-wrap: wrap;
    gap: 10px 14px;
    padding: 9px 14px;
    background: var(--surface-raised);
    border: 1px solid var(--border-subtle);
    border-radius: var(--r-lg);
}

.cjb-chips {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 7px;
    min-width: 0;
}

.cjb-chip {
    display: inline-flex;
    align-items: center;
    gap: 2px;
    max-width: 220px;
    padding-inline-start: 11px;
    border-radius: var(--r-full);
    background: var(--p-50);
    font-size: var(--fs-xs);
    font-weight: var(--fw-semibold);
    line-height: 24px;
    color: var(--text-brand);
    white-space: nowrap;
    overflow: hidden;
}

.cjb-chip-x {
    display: grid;
    place-items: center;
    width: 22px;
    height: 24px;
    border: none;
    background: transparent;
    color: inherit;
    opacity: 0.7;
    cursor: pointer;
}

.cjb-chip-x:hover { opacity: 1; }
.cjb-chip-x:focus-visible { outline: none; box-shadow: var(--shadow-focus); border-radius: var(--r-full); }

.cjb-clear {
    padding: 2px 6px;
    border: none;
    border-radius: var(--r-sm);
    background: transparent;
    font: inherit;
    font-size: var(--fs-xs);
    font-weight: var(--fw-semibold);
    color: var(--text-brand);
    cursor: pointer;
}

.cjb-clear:hover { text-decoration: underline; }
.cjb-clear:focus-visible { outline: none; box-shadow: var(--shadow-focus); }

.cjb-bar-r {
    display: flex;
    align-items: center;
    gap: 8px;
    margin-inline-start: auto;
}

.cjb-sort-l {
    font-size: var(--fs-xs);
    color: var(--text-tertiary);
    white-space: nowrap;
}

/* ---------- Layout switch ---------- */

.cjb-views {
    display: inline-flex;
    gap: 2px;
    padding: 2px;
    border: 1px solid var(--border-default);
    border-radius: var(--r-md);
    background: var(--n-75);
}

.cjb-view {
    display: grid;
    place-items: center;
    width: 28px;
    height: 26px;
    border: none;
    border-radius: var(--r-sm);
    background: transparent;
    color: var(--text-tertiary);
    cursor: pointer;
}

.cjb-view:hover { color: var(--text-primary); }
.cjb-view:focus-visible { outline: none; box-shadow: var(--shadow-focus); }

.cjb-view.is-on {
    background: var(--n-0);
    color: var(--text-brand);
    box-shadow: var(--shadow-xs);
}

.cjb-count {
    margin: 0;
    font-size: var(--fs-sm);
    color: var(--text-secondary);
}

/* ---------- The cards ---------- */

.cjb-list {
    display: flex;
    flex-direction: column;
    gap: 10px;
}

.cjb-card {
    display: grid;
    grid-template-columns: 48px minmax(0, 1fr) auto;
    gap: 14px;
    padding: 16px 18px;
    background: var(--surface-raised);
    border: 1px solid var(--border-subtle);
    border-radius: var(--r-xl);
    transition: border-color var(--dur-fast) var(--ease-out),
                box-shadow var(--dur-fast) var(--ease-out);
}

.cjb-card:hover {
    border-color: var(--border-default);
    box-shadow: var(--shadow-md);
}

/* A strong match. The mark is an inset shadow rather than a border, so
   the card does not shift by 3px against the ones above and below it. */
.cjb-card.is-top {
    box-shadow: inset 3px 0 0 var(--p-700);
    background: linear-gradient(90deg, var(--p-50) 0%, var(--surface-raised) 42%);
}

[dir="rtl"] .cjb-card.is-top {
    box-shadow: inset -3px 0 0 var(--p-700);
    background: linear-gradient(270deg, var(--p-50) 0%, var(--surface-raised) 42%);
}

.cjb-card.is-top:hover { box-shadow: inset 3px 0 0 var(--p-700), var(--shadow-md); }
[dir="rtl"] .cjb-card.is-top:hover { box-shadow: inset -3px 0 0 var(--p-700), var(--shadow-md); }

.cjb-logo {
    display: grid;
    place-items: center;
    width: 48px;
    height: 48px;
    border-radius: var(--r-lg);
    color: var(--text-inverse);
    font-family: var(--font-mono);
    font-size: var(--fs-lg);
    font-weight: var(--fw-bold);
    text-transform: uppercase;
}

.cjb-body { min-width: 0; }

.cjb-co {
    margin: 0;
    font-size: var(--fs-sm);
    font-weight: var(--fw-semibold);
    color: var(--text-brand);
}

.cjb-role {
    display: block;
    margin-block-start: 2px;
    font-size: var(--fs-xl);
    font-weight: var(--fw-bold);
    line-height: var(--lh-snug);
    color: var(--text-primary);
    text-decoration: none;
}

.cjb-role:hover { color: var(--text-brand); text-decoration: underline; }
.cjb-role:focus-visible { outline: none; box-shadow: var(--shadow-focus); border-radius: var(--r-sm); }

.cjb-meta {
    display: flex;
    flex-wrap: wrap;
    gap: 4px 16px;
    margin-block-start: 7px;
    font-size: var(--fs-sm);
    color: var(--text-secondary);
}

.cjb-meta-i {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    min-width: 0;
}

.cjb-meta-i svg { color: var(--text-tertiary); flex-shrink: 0; }

.cjb-tags {
    display: flex;
    flex-wrap: wrap;
    gap: 5px;
    margin-block-start: 10px;
}

.cjb-tag {
    padding: 3px 9px;
    border-radius: var(--r-full);
    background: var(--p-50);
    font-size: var(--fs-2xs);
    font-weight: var(--fw-medium);
    color: var(--text-brand);
}

/* Work mode is not a skill, and reading as one made "Remote" look like
   something the candidate had to know how to do. */
.cjb-tag.is-mode {
    background: var(--n-0);
    box-shadow: inset 0 0 0 1px var(--border-default);
    color: var(--text-secondary);
}

.cjb-why {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    margin: 10px 0 0;
    padding: 4px 10px;
    border: 1px solid var(--gold-200);
    border-radius: var(--r-md);
    background: var(--gold-50);
    font-size: var(--fs-xs);
    color: var(--gold-700);
}

/* ---------- The card's right-hand column ---------- */

.cjb-col {
    display: flex;
    flex-direction: column;
    align-items: flex-end;
    justify-content: space-between;
    gap: 12px;
    flex-shrink: 0;
    /* Wherever this column turns into a row - the grid layout, a phone - the
       badges and the apply button have to be allowed to take two lines. Held
       on one they pushed the match ring out through the card's edge. */
    min-width: 0;
}

.cjb-flags {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    justify-content: flex-end;
    gap: 6px;
}

.cjb-flag {
    padding: 3px 9px;
    border-radius: var(--r-full);
    font-size: var(--fs-2xs);
    font-weight: var(--fw-semibold);
    white-space: nowrap;
}

.cjb-flag.is-top { background: var(--gold-50); color: var(--gold-700); }
.cjb-flag.is-new { background: var(--success-50); color: var(--success-700); }
.cjb-flag.is-replies { background: var(--info-50); color: var(--info-700); }

.cjb-heart {
    display: grid;
    place-items: center;
    width: 30px;
    height: 30px;
    border: 1px solid var(--border-default);
    border-radius: var(--r-full);
    background: var(--n-0);
    color: var(--text-tertiary);
    cursor: pointer;
    transition: color var(--dur-fast) var(--ease-out),
                border-color var(--dur-fast) var(--ease-out);
}

.cjb-heart:hover { color: var(--danger-600); border-color: var(--danger-200); }
.cjb-heart:focus-visible { outline: none; box-shadow: var(--shadow-focus); }

.cjb-heart.is-on {
    border-color: var(--danger-200);
    background: var(--danger-50);
    color: var(--danger-600);
}

.cjb-heart.is-on svg { fill: currentColor; }

.cjb-act {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    flex-wrap: wrap;
    gap: 8px 10px;
    min-width: 0;
}

/* The match score. A ring rather than a number alone, because the
   number means nothing without the arc it fills. */
.cjb-ring {
    display: grid;
    place-items: center;
    width: 38px;
    height: 38px;
    border-radius: var(--r-full);
    flex-shrink: 0;
}

.cjb-ring.is-none {
    border: 1px dashed var(--border-default);
    color: var(--text-tertiary);
}

.cjb-ring-in {
    display: grid;
    place-items: center;
    width: 30px;
    height: 30px;
    border-radius: var(--r-full);
    background: var(--n-0);
    font-family: var(--font-mono);
    font-size: var(--fs-sm);
    font-weight: var(--fw-semibold);
}

.cjb-ring.is-none .cjb-ring-in { background: transparent; }

.cjb-posted {
    font-size: var(--fs-xs);
    color: var(--text-tertiary);
    white-space: nowrap;
}

.cjb-apply {
    display: inline-flex;
    align-items: center;
    gap: 7px;
    height: var(--h-lg);
    padding: 0 16px;
    border-radius: var(--r-lg);
    background: var(--p-700);
    font-size: var(--fs-sm);
    font-weight: var(--fw-semibold);
    color: var(--text-inverse);
    text-decoration: none;
    white-space: nowrap;
}

.cjb-apply:hover { background: var(--p-800); color: var(--text-inverse); }
.cjb-apply:focus-visible { outline: none; box-shadow: var(--shadow-focus); }

/* ---------- Grid and compact ---------- */

/* Same cards, less width each: the right-hand column drops under the
   body rather than squeezing the title into a 90px gutter. */
.cjb-list.is-grid {
    display: grid;
    /* 360, not 330: below that the badges, the ring and the apply button
       cannot share a line, and the row wraps on every card. */
    grid-template-columns: repeat(auto-fill, minmax(360px, 1fr));
    gap: 12px;
}

.cjb-list.is-grid .cjb-card { grid-template-columns: 44px minmax(0, 1fr); }
.cjb-list.is-grid .cjb-logo { width: 44px; height: 44px; }

.cjb-list.is-grid .cjb-col {
    grid-column: 1 / -1;
    flex-direction: row;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: 8px 10px;
    padding-block-start: 12px;
    border-block-start: 1px solid var(--border-subtle);
}

/* One line per job, for somebody scanning forty of them. The tags and
   the rationale are the first things a scan does not read. */
.cjb-list.is-compact .cjb-card {
    align-items: center;
    grid-template-columns: 34px minmax(0, 1fr) auto;
    gap: 12px;
    padding: 10px 16px;
    border-radius: var(--r-lg);
}

.cjb-list.is-compact .cjb-logo {
    width: 34px;
    height: 34px;
    border-radius: var(--r-md);
    font-size: var(--fs-sm);
}

.cjb-list.is-compact .cjb-role { font-size: var(--fs-base); }
.cjb-list.is-compact .cjb-meta { margin-block-start: 3px; }
.cjb-list.is-compact .cjb-tags,
.cjb-list.is-compact .cjb-why,
.cjb-list.is-compact .cjb-flags { display: none; }
.cjb-list.is-compact .cjb-col { flex-direction: row; align-items: center; }

/* ---------- Nothing to show ---------- */

.cjb-empty {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 6px;
    padding: 44px 24px;
    background: var(--surface-raised);
    border: 1px solid var(--border-subtle);
    border-radius: var(--r-xl);
    text-align: center;
}

.cjb-empty-mark {
    display: grid;
    place-items: center;
    width: 46px;
    height: 46px;
    margin-block-end: 4px;
    border-radius: var(--r-full);
    background: var(--n-75);
    color: var(--text-tertiary);
}

.cjb-empty-t { margin: 0; font-size: var(--fs-base); font-weight: var(--fw-semibold); }
.cjb-empty-b { margin: 0; font-size: var(--fs-sm); color: var(--text-secondary); }
.cjb-empty .cjb-btn { margin-block-start: 10px; }

/* ---------- Paging ---------- */

.cjb-foot {
    display: flex;
    align-items: center;
    justify-content: space-between;
    flex-wrap: wrap;
    gap: 10px;
    padding-block-start: 4px;
}

.cjb-pager {
    display: flex;
    align-items: center;
    gap: 4px;
    margin-inline-start: auto;
}

.cjb-pg {
    display: grid;
    place-items: center;
    min-width: 32px;
    height: 32px;
    padding: 0 6px;
    border: 1px solid transparent;
    border-radius: var(--r-md);
    background: transparent;
    font: inherit;
    font-size: var(--fs-sm);
    font-variant-numeric: tabular-nums;
    color: var(--text-secondary);
    cursor: pointer;
}

.cjb-pg:hover:not(:disabled):not(.is-on) { background: var(--n-75); color: var(--text-primary); }
.cjb-pg:focus-visible { outline: none; box-shadow: var(--shadow-focus); }

.cjb-pg.is-on {
    background: var(--p-700);
    border-color: var(--p-700);
    color: var(--text-inverse);
    font-weight: var(--fw-semibold);
}

.cjb-pg-ar { border-color: var(--border-default); }
.cjb-pg:disabled { opacity: 0.4; cursor: default; }
[dir="rtl"] .cjb-pg-ar svg { transform: scaleX(-1); }

.cjb-pg-gap {
    display: grid;
    place-items: center;
    min-width: 24px;
    height: 32px;
    color: var(--text-tertiary);
}

/* ---------- Narrow ---------- */

/* A vertical filter panel at this width costs a quarter of the page,
   and the results beside it are already down to one card per line. The
   panel goes full width above them and lays its groups across that
   width instead of down it; the arrows still fold them. */
@media (max-width: 1080px) {
    .cjb-grid { grid-template-columns: minmax(0, 1fr); gap: 12px; }

    .cjb-side {
        position: static;
        max-height: none;
        display: grid;
        grid-template-columns: repeat(auto-fit, minmax(212px, 1fr));
        align-items: start;
        gap: 0 18px;
    }

    /* The head, the two nav rows and the save button read across the
       whole strip; only the groups take a column each. */
    .cjb-side-head,
    .cjb-tabs,
    .cjb-side-foot { grid-column: 1 / -1; }

    .cjb-tabs { flex-direction: row; }
    .cjb-tab { flex: 1; }
    .cjb-btn-wide { width: auto; align-self: start; }
}

/* ---------- The sheet ---------- */

/* A phone has no room for a filter panel that is simply present: the strip
   above costs four rows before the first card, and stacking the groups costs
   the whole screen. The panel becomes a bottom sheet instead - the same thing
   the public board does at its own breakpoint - raised by a button in the bar
   over the results.

   It is moved, not rebuilt. .cjb-side keeps its markup and its state, so a
   filter ticked in the sheet is the one the card shows when the viewport
   grows, and there is no second copy of the panel to keep in step.

   visibility rather than display, so the slide can animate and the controls
   inside keep their values either way. */
@media (max-width: 900px) {
    .cjb-fbtn {
        display: inline-flex;
        align-items: center;
        gap: 8px;
        min-height: var(--h-lg);
        padding: 0 14px;
        border: 1px solid var(--p-700);
        border-radius: var(--r-lg);
        background: var(--p-700);
        font: inherit;
        font-size: var(--fs-sm);
        font-weight: var(--fw-semibold);
        color: var(--text-inverse);
        white-space: nowrap;
        cursor: pointer;
    }

    .cjb-fbtn:focus-visible { outline: none; box-shadow: var(--shadow-focus); }

    /* How many filters are in force, so the button says whether the board is
       narrowed without the sheet having to be opened to find out. */
    .cjb-fbtn-n {
        display: inline-flex;
        align-items: center;
        justify-content: center;
        min-width: 19px;
        height: 19px;
        padding: 0 5px;
        border-radius: var(--r-full);
        background: rgba(255, 255, 255, 0.24);
        font-size: var(--fs-2xs);
        font-weight: var(--fw-bold);
    }

    .cjb-scrim {
        display: block;
        position: fixed;
        inset: 0;
        z-index: 900;
        background: rgba(15, 23, 42, 0.5);
        opacity: 0;
        pointer-events: none;
        transition: opacity 0.22s ease;
    }

    .cjb-scrim.is-on { opacity: 1; pointer-events: auto; }

    /* max-width with margin-inline:auto between the two insets is what stops
       this being full-bleed on a tablet: the rows inside are a tick box, a
       label and a count, and run across 880px the box sits at one edge with
       its count at the other.

       max-height is dvh with a vh fallback - on mobile Safari vh assumes the
       address bar is hidden, so 88vh of it puts the foot of the sheet, the
       one control that puts it away, off the bottom of the screen. */
    .cjb-side {
        position: fixed;
        inset-block: auto 0;
        inset-inline: 0;
        z-index: 901;
        display: flex;
        flex-direction: column;
        /* The strip above is a grid with align-items:start; left standing in
           a flex column that is a cross-axis rule, and the head, the groups
           and the foot would each shrink to their own width. */
        align-items: stretch;
        gap: 0;
        margin-inline: auto;
        max-width: 560px;
        max-height: 88vh;
        max-height: 88dvh;
        padding: 0;
        border: none;
        border-radius: var(--r-xl) var(--r-xl) 0 0;
        box-shadow: 0 -14px 40px rgba(15, 23, 42, 0.28);
        overflow: hidden;
        transform: translateY(101%);
        visibility: hidden;
        transition: transform 0.26s cubic-bezier(0.22, 0.68, 0.32, 1), visibility 0.26s;
    }

    .cjb-side.is-open { transform: none; visibility: visible; }

    .cjb-grab {
        display: block;
        flex-shrink: 0;
        width: 38px;
        height: 4px;
        margin: 8px auto 0;
        border-radius: var(--r-full);
        background: var(--border-default);
    }

    /* Title, then Reset, then the close: space-between would strand Reset in
       the middle of the head now that there are three of them. */
    .cjb-side-head {
        flex-shrink: 0;
        padding: 11px 14px 12px;
    }

    .cjb-side-t { margin-inline-end: auto; }

    .cjb-sheet-x {
        display: inline-grid;
        place-items: center;
        width: 32px;
        height: 32px;
        border: none;
        border-radius: var(--r-md);
        background: transparent;
        color: var(--text-tertiary);
        cursor: pointer;
    }

    .cjb-sheet-x:hover { background: var(--n-50); color: var(--text-primary); }
    .cjb-sheet-x:focus-visible { outline: none; box-shadow: var(--shadow-focus); }

    .cjb-side-body {
        display: block;
        flex: 1 1 auto;
        padding-inline: 14px;
        overflow-y: auto;
        /* Reaching the end of the filters must not start scrolling the
           results underneath the sheet. */
        overscroll-behavior: contain;
    }

    /* The card's rows are pointed at; these are pressed with a thumb. */
    .cjb-opt { min-height: 40px; }
    .cjb-box { width: 19px; height: 19px; }
    /* 16px is the size below which iOS Safari zooms the page on focus and
       does not zoom back out. */
    .cjb-sel { height: 42px; font-size: var(--fs-base); }

    /* env() keeps the primary action clear of the home indicator. */
    .cjb-side-foot {
        flex-shrink: 0;
        padding: 11px 14px calc(14px + env(safe-area-inset-bottom));
        border-block-start: 1px solid var(--border-subtle);
    }

    /* The strip above turns these loose; in a sheet foot they are full-width
       rows again. */
    .cjb-btn-wide { width: 100%; align-self: stretch; }
    .cjb-btn.cjb-sheet-done { display: inline-flex; }

    /* Two navy blocks stacked in the foot would give the sheet two primary
       actions. The one that puts it away is the primary here; keeping the
       search is the quieter of the pair. */
    .cjb-side-foot .cjb-btn-p:not(.cjb-sheet-done) {
        border-color: var(--border-default);
        background: var(--n-0);
        color: var(--text-secondary);
    }

    .cjb-side-foot .cjb-btn-p:not(.cjb-sheet-done):hover {
        background: var(--n-50);
        color: var(--text-primary);
    }
}

@media (prefers-reduced-motion: reduce) {
    .cjb-side,
    .cjb-scrim { transition: none; }
}

@media (max-width: 800px) {
    .cjb-hero {
        min-height: 0;
        padding: 20px;
        border-radius: var(--r-lg);
    }

    .cjb-hero-t { font-size: var(--fs-2xl); }
    /* Decorative, and there is no width left for it. */
    .cjb-hero-art { display: none; }

    /* The right-hand column kept beside the body on a phone left the
       role name about 110px to live in, so every title broke across
       three lines. Below the details it gets the width, and the apply
       button gets a tap target. */
    .cjb-card,
    .cjb-list.is-compact .cjb-card { grid-template-columns: 44px minmax(0, 1fr); }

    .cjb-logo,
    .cjb-list.is-compact .cjb-logo { width: 44px; height: 44px; }

    .cjb-col,
    .cjb-list.is-compact .cjb-col {
        grid-column: 1 / -1;
        flex-direction: row-reverse;
        flex-wrap: wrap;
        align-items: center;
        justify-content: space-between;
        gap: 8px 10px;
        padding-block-start: 12px;
        border-block-start: 1px solid var(--border-subtle);
    }

    .cjb-list.is-compact .cjb-flags { display: flex; }
    .cjb-bar-r { margin-inline-start: 0; flex-wrap: wrap; }
}

@media (max-width: 560px) {
    .cjb-find { margin-block-start: 12px; }
    .cjb-apply { flex: 1; justify-content: center; }
    .cjb-act { width: 100%; }
    .cjb-foot { justify-content: center; }
    .cjb-pager { margin-inline-start: 0; }
}


/* ============================================================
   56. Candidate: one job (cjd-*)

   The page a candidate reads before deciding to spend twenty
   minutes applying. It is the other half of section 55: the board
   hands off to this screen, so it wears the same surfaces — the
   xl-radius card, the subtle border, the navy accent — and keeps
   the meta row with its icons unchanged, so a job looks like the
   same job in both places.

   Every rule on it used to be a style attribute. That bought no
   hover, no focus ring, no breakpoint and no RTL: the interview
   strip was a repeat(N,1fr) grid that put five columns into a
   320px phone, the screening inputs shared one hard-coded string
   with no focus state, and nine notice boxes each restated the
   same padding, radius and tint from memory in four spellings.

   What is here:
     .cjd-back    the way back to the board
     .cjd-head    logo, company, title, the meta row, the pills
     .cjd-card    a body card, divided into .cjd-sec sections
     .cjd-steps   the interview process — a rail on wide, a
                  timeline once the tiles stop fitting
     .cjd-match   the score, its ring and the factors behind it
     .cjd-side    the sticky column: apply, assessments, people
     .cjd-note    one notice box, tinted by .is-warn/.is-info/
                  .is-bad/.is-mute — the nine became one
     .cjd-mini    another job offered inside a panel

   The accent is the portal navy. Gold stays where it means
   something: the pay pill, and the match card, which is the one
   surface here not stating a fact the employer wrote but a
   judgement we made.
   ============================================================ */

.cjd {
    display: flex;
    flex-direction: column;
    gap: 14px;
    min-width: 0;
}

/* ---------- Back to the board ---------- */

/* It was a bare 12.5px link with a margin, sitting on nothing. At
   that size a text link is a poor pointer target, so it wears a
   pill and gets the hover the rest of the page has. */
.cjd-back {
    display: inline-flex;
    align-items: center;
    align-self: flex-start;
    gap: 7px;
    height: var(--h-md);
    padding: 0 13px 0 10px;
    border: 1px solid var(--border-subtle);
    border-radius: var(--r-full);
    background: var(--surface-raised);
    font-size: var(--fs-sm);
    font-weight: var(--fw-medium);
    color: var(--text-secondary);
    text-decoration: none;
    transition: color var(--dur-fast) var(--ease-out),
                border-color var(--dur-fast) var(--ease-out);
}

.cjd-back:hover { color: var(--text-brand); border-color: var(--border-default); }
.cjd-back:focus-visible { outline: none; box-shadow: var(--shadow-focus); }
.cjd-back svg { color: var(--text-tertiary); flex-shrink: 0; }
.cjd-back:hover svg { color: var(--text-brand); }
[dir="rtl"] .cjd-back { padding: 0 10px 0 13px; }
[dir="rtl"] .cjd-back svg { transform: scaleX(-1); }

/* ---------- The frame ---------- */

.cjd-grid {
    display: grid;
    /* 340, not the old 320: the apply panel carries the screening
       questions, and a question label with a "Must have" chip after
       it needs the extra twenty pixels to keep the chip on the same
       line as the end of the label. */
    grid-template-columns: minmax(0, 1fr) 340px;
    gap: 16px;
    align-items: start;
}

.cjd-main,
.cjd-side {
    display: flex;
    flex-direction: column;
    gap: 14px;
    min-width: 0;
}

/* The panel somebody acts from follows them down the page. No
   max-height and no scroller of its own, unlike the board's filter
   card: this column is a form, and a form that scrolls inside a box
   hides the button that submits it. Taller than the viewport it
   simply pins once, which is what it did before. */
.cjd-side {
    position: sticky;
    inset-block-start: 8px;
}

/* ---------- The header ---------- */

.cjd-head {
    position: relative;
    overflow: hidden;
    padding: 22px 24px;
    background: var(--surface-raised);
    border: 1px solid var(--border-subtle);
    border-radius: var(--r-xl);
    box-shadow: var(--shadow-xs);
}

/* The board's banner gradient, three pixels of it. It is the whole
   visual handoff between the two screens, and it costs no height. */
.cjd-head::before {
    content: "";
    position: absolute;
    inset-block-start: 0;
    inset-inline: 0;
    height: 3px;
    background: linear-gradient(102deg, var(--p-700) 0%, var(--p-500) 54%, #6366f1 100%);
}

.cjd-head-top {
    display: flex;
    align-items: flex-start;
    gap: 16px;
    min-width: 0;
}

.cjd-logo {
    display: grid;
    place-items: center;
    flex-shrink: 0;
    width: 60px;
    height: 60px;
    border-radius: var(--r-xl);
    color: var(--text-inverse);
    font-family: var(--font-mono);
    font-size: var(--fs-xl);
    font-weight: var(--fw-bold);
    text-transform: uppercase;
}

.cjd-id { min-width: 0; flex: 1; }

.cjd-co {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    margin: 0;
    font-size: var(--fs-sm);
    font-weight: var(--fw-semibold);
    color: var(--text-brand);
}

.cjd-co svg { color: var(--p-400); flex-shrink: 0; }

.cjd-title {
    margin: 3px 0 0;
    font-family: var(--font-display);
    font-size: var(--fs-3xl);
    font-weight: var(--fw-bold);
    line-height: var(--lh-tight);
    letter-spacing: -0.02em;
    color: var(--text-primary);
}

/* The board's meta row, unchanged, so the same facts sit in the
   same order with the same icons on both screens. */
.cjd-meta {
    display: flex;
    flex-wrap: wrap;
    gap: 5px 16px;
    margin-block-start: 9px;
    font-size: var(--fs-sm);
    color: var(--text-secondary);
}

.cjd-meta-i {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    min-width: 0;
}

.cjd-meta-i svg { color: var(--text-tertiary); flex-shrink: 0; }

/* How often this employer replies. Three tones rather than a colour
   computed in the markup — and shown at all only when there is
   enough to judge, which is the DTO's job, not this rule's. */
.cjd-replies {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    padding: 2px 9px;
    border-radius: var(--r-full);
    font-size: var(--fs-xs);
    font-weight: var(--fw-semibold);
    white-space: nowrap;
}

.cjd-replies.is-good { background: var(--success-50); color: var(--success-700); }
.cjd-replies.is-mid  { background: var(--gold-50); color: var(--gold-700); }
.cjd-replies.is-low  { background: var(--n-75); color: var(--text-tertiary); }

/* ---------- The pills ---------- */

.cjd-pills {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
    margin-block-start: 16px;
    padding-block-start: 15px;
    border-block-start: 1px solid var(--border-subtle);
}

.cjd-pill {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    padding: 4px 11px;
    border-radius: var(--r-full);
    background: var(--n-75);
    font-size: var(--fs-xs);
    font-weight: var(--fw-medium);
    color: var(--text-secondary);
    white-space: nowrap;
}

/* Pay is the fact most people came for, so it is the one pill
   allowed to be louder than its neighbours. */
.cjd-pill.is-pay {
    background: var(--gold-50);
    box-shadow: inset 0 0 0 1px var(--gold-200);
    color: var(--gold-700);
    font-weight: var(--fw-semibold);
    font-variant-numeric: tabular-nums;
}

/* Contract and level describe the role; the loose tags after them
   are skills. Section 55 makes the same split for the same reason —
   read as one set, "Remote" looked like something to know how to do. */
.cjd-pill.is-fact {
    background: var(--n-0);
    box-shadow: inset 0 0 0 1px var(--border-default);
}

.cjd-pill.is-tag { background: var(--p-50); color: var(--text-brand); }

/* ---------- A body card ---------- */

.cjd-card {
    padding: 22px 24px;
    background: var(--surface-raised);
    border: 1px solid var(--border-subtle);
    border-radius: var(--r-xl);
    box-shadow: var(--shadow-xs);
    min-width: 0;
}

/* Sections inside one card, separated by a rule rather than by a
   gap: About, the duties and the requirements are one document, and
   four cards down the page read as four unrelated things. */
.cjd-sec + .cjd-sec {
    margin-block-start: 22px;
    padding-block-start: 20px;
    border-block-start: 1px solid var(--border-subtle);
}

.cjd-sec-h {
    display: flex;
    align-items: center;
    gap: 10px;
    margin: 0 0 12px;
}

.cjd-sec-i {
    display: grid;
    place-items: center;
    flex-shrink: 0;
    width: 28px;
    height: 28px;
    border-radius: var(--r-md);
    background: var(--p-50);
    color: var(--text-brand);
}

.cjd-sec-t {
    margin: 0;
    font-size: var(--fs-lg);
    font-weight: var(--fw-semibold);
    letter-spacing: -0.01em;
    color: var(--text-primary);
}

.cjd-prose {
    margin: 0;
    font-size: var(--fs-md);
    line-height: var(--lh-relaxed);
    color: var(--text-secondary);
    /* An employer's description arrives as one field with its own
       newlines in it. Honouring them is the difference between a
       set of paragraphs and a wall. */
    white-space: pre-line;
}

/* ---------- Bulleted lists ---------- */

/* A disc marker at 13px sits above the first line's baseline and
   drifts between browsers. These are drawn, and the two kinds are
   told apart: what the job involves, and what it demands. */
.cjd-list {
    display: flex;
    flex-direction: column;
    gap: 9px;
    margin: 0;
    padding: 0;
    list-style: none;
}

.cjd-list li {
    position: relative;
    padding-inline-start: 24px;
    font-size: var(--fs-md);
    line-height: var(--lh-normal);
    color: var(--text-secondary);
}

.cjd-list li::before {
    content: "";
    position: absolute;
    inset-inline-start: 6px;
    inset-block-start: 7px;
    width: 7px;
    height: 7px;
    border-radius: var(--r-full);
    background: var(--p-300);
}

.cjd-list.is-req li::before {
    background: var(--n-0);
    box-shadow: inset 0 0 0 2px var(--gold-400);
}

/* ---------- The interview process ---------- */

/* It was grid-template-columns: repeat(N, 1fr) with N read from the
   data, so a five-step process put five tiles across a 320px phone
   and each one held about forty pixels of title. It is a rail now,
   and it turns into a timeline when the tiles stop fitting. */
.cjd-steps {
    position: relative;
    display: flex;
    gap: 10px;
}

/* The line the numbers sit on. Behind them, and inset so it runs
   between the first and last circle rather than out to the edges. */
.cjd-steps::before {
    content: "";
    position: absolute;
    inset-block-start: 26px;
    inset-inline: 12%;
    height: 2px;
    background: var(--border-subtle);
}

.cjd-step {
    position: relative;
    flex: 1 1 0;
    min-width: 0;
    padding: 12px 8px 13px;
    border: 1px solid var(--border-subtle);
    border-radius: var(--r-lg);
    background: var(--surface-raised);
    text-align: center;
}

.cjd-step-n {
    display: grid;
    place-items: center;
    width: 28px;
    height: 28px;
    margin: 0 auto 8px;
    border-radius: var(--r-full);
    background: var(--p-50);
    box-shadow: 0 0 0 3px var(--surface-raised);
    font-family: var(--font-mono);
    font-size: var(--fs-sm);
    font-weight: var(--fw-bold);
    font-variant-numeric: tabular-nums;
    color: var(--text-brand);
}

.cjd-step-t {
    font-size: var(--fs-sm);
    font-weight: var(--fw-semibold);
    line-height: var(--lh-snug);
    color: var(--text-primary);
}

.cjd-step-d {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    margin-block-start: 4px;
    font-size: var(--fs-xs);
    font-variant-numeric: tabular-nums;
    color: var(--text-tertiary);
}

.cjd-step-d svg { flex-shrink: 0; }

/* ---------- The match ---------- */

.cjd-match {
    padding: 20px 22px;
    border: 1px solid var(--gold-200);
    border-radius: var(--r-xl);
    background: linear-gradient(160deg, var(--gold-50) 0%, var(--n-0) 62%);
    box-shadow: var(--shadow-xs);
    min-width: 0;
}

.cjd-match-top {
    display: flex;
    align-items: center;
    gap: 16px;
    min-width: 0;
}

/* The arc is the data; --cjd-deg is the only thing the markup sets,
   because it is the only thing that varies per job. */
.cjd-ring {
    display: grid;
    place-items: center;
    flex-shrink: 0;
    width: 64px;
    height: 64px;
    border-radius: var(--r-full);
    background: conic-gradient(var(--success-600) var(--cjd-deg, 0deg), var(--n-150) 0);
}

.cjd-ring-in {
    display: grid;
    place-items: center;
    width: 50px;
    height: 50px;
    border-radius: var(--r-full);
    background: var(--n-0);
    font-family: var(--font-mono);
    font-size: var(--fs-lg);
    font-weight: var(--fw-bold);
    font-variant-numeric: tabular-nums;
    color: var(--success-600);
}

.cjd-match-txt { min-width: 0; }

.cjd-match-t {
    margin: 0;
    font-size: var(--fs-xl);
    font-weight: var(--fw-bold);
    line-height: var(--lh-snug);
    letter-spacing: -0.01em;
    color: var(--text-primary);
}

.cjd-match-b {
    margin: 3px 0 7px;
    font-size: var(--fs-sm);
    color: var(--text-secondary);
}

.cjd-ai {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    padding: 2px 9px;
    border: 1px solid var(--gold-200);
    border-radius: var(--r-full);
    background: var(--n-0);
    font-size: var(--fs-2xs);
    font-weight: var(--fw-semibold);
    color: var(--gold-700);
}

.cjd-factors {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
    gap: 8px;
    margin-block-start: 16px;
}

.cjd-factor {
    display: flex;
    align-items: center;
    gap: 9px;
    min-width: 0;
    padding: 8px 10px;
    border: 1px solid var(--border-subtle);
    border-radius: var(--r-md);
    background: rgba(255, 255, 255, 0.72);
}

/* The tone was two colours computed in the markup and applied to
   three properties each. It is one class now, and the mark is a
   glyph in a tinted disc rather than a bare character sitting on
   the text baseline. */
.cjd-factor-i {
    display: grid;
    place-items: center;
    flex-shrink: 0;
    width: 20px;
    height: 20px;
    border-radius: var(--r-full);
}

.cjd-factor-t {
    flex: 1;
    min-width: 0;
    font-size: var(--fs-xs);
    line-height: var(--lh-snug);
    color: var(--text-secondary);
}

.cjd-factor-v {
    flex-shrink: 0;
    padding: 1px 7px;
    border: 1px solid;
    border-radius: var(--r-full);
    font-size: var(--fs-2xs);
    font-weight: var(--fw-bold);
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

.cjd-factor.is-good .cjd-factor-i { background: var(--success-50); color: var(--success-600); }
.cjd-factor.is-good .cjd-factor-v { background: var(--success-50); border-color: var(--success-100); color: var(--success-700); }
.cjd-factor.is-warn .cjd-factor-i { background: var(--warning-50); color: var(--warning-600); }
.cjd-factor.is-warn .cjd-factor-v { background: var(--warning-50); border-color: var(--warning-100); color: var(--warning-700); }

.cjd-match-foot {
    margin: 14px 0 0;
    font-size: var(--fs-xs);
    line-height: var(--lh-normal);
    color: var(--text-tertiary);
}

/* ---------- A side panel ---------- */

.cjd-panel {
    background: var(--surface-raised);
    border: 1px solid var(--border-subtle);
    border-radius: var(--r-xl);
    box-shadow: var(--shadow-xs);
    min-width: 0;
}

/* The panel somebody applies from. Marked the way the header is —
   the same three pixels of the board's gradient — so the eye finds
   it first in a column of otherwise equal cards. */
.cjd-panel.is-act {
    position: relative;
    overflow: hidden;
    border-color: var(--border-default);
    box-shadow: var(--shadow-sm);
}

.cjd-panel.is-act::before {
    content: "";
    position: absolute;
    inset-block-start: 0;
    inset-inline: 0;
    height: 3px;
    background: linear-gradient(102deg, var(--p-700) 0%, var(--p-500) 54%, #6366f1 100%);
}

.cjd-panel-h {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 15px 16px 0;
}

.cjd-panel-h svg { color: var(--text-tertiary); flex-shrink: 0; }

.cjd-panel-t {
    margin: 0;
    flex: 1;
    min-width: 0;
    font-size: var(--fs-md);
    font-weight: var(--fw-semibold);
    color: var(--text-primary);
}

.cjd-panel-b { padding: 14px 16px 16px; }

/* A rule with a heading on it, inside a panel: the roles offered
   after a submit, and the second half of the applied state. */
.cjd-split {
    margin-block-start: 14px;
    padding-block-start: 13px;
    border-block-start: 1px solid var(--border-subtle);
}

.cjd-sub-h {
    margin: 0 0 8px;
    font-size: var(--fs-2xs);
    font-weight: var(--fw-semibold);
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--text-tertiary);
}

/* ---------- Assessments ---------- */

.cjd-as-list { display: flex; flex-direction: column; gap: 9px; }

.cjd-as {
    padding: 10px 12px;
    border: 1px solid var(--border-subtle);
    border-radius: var(--r-lg);
    background: var(--n-25);
}

/* A test that can end the application on a score is not the same
   object as one that is merely sat, and it should not look like it. */
.cjd-as.is-gated {
    border-color: var(--warning-100);
    background: var(--warning-50);
}

.cjd-as-t {
    font-size: var(--fs-sm);
    font-weight: var(--fw-semibold);
    line-height: var(--lh-snug);
    color: var(--text-primary);
}

.cjd-as-m {
    margin-block-start: 4px;
    font-size: var(--fs-xs);
    line-height: var(--lh-normal);
    color: var(--text-secondary);
}

.cjd-as-note {
    margin-block-start: 6px;
    font-size: var(--fs-xs);
    line-height: var(--lh-snug);
    color: var(--text-tertiary);
}

.cjd-as-note.is-hard {
    display: flex;
    align-items: flex-start;
    gap: 6px;
    padding-block-start: 6px;
    border-block-start: 1px solid var(--warning-100);
    color: var(--warning-700);
    font-weight: var(--fw-medium);
}

.cjd-as-note.is-hard svg { flex-shrink: 0; margin-block-start: 1px; }

/* ---------- Notices ---------- */

/* Nine boxes became one class with four tints. Every one of them
   was a hand-written padding, radius and colour triple, and three
   of them disagreed about the padding. */
.cjd-note {
    padding: 11px 13px;
    border: 1px solid;
    border-radius: var(--r-lg);
    font-size: var(--fs-sm);
    line-height: var(--lh-normal);
}

.cjd-note.is-warn { background: var(--warning-50); border-color: var(--warning-100); color: var(--warning-700); }
.cjd-note.is-info { background: var(--info-50);    border-color: var(--info-100);    color: var(--info-700); }
.cjd-note.is-bad  { background: var(--danger-50);  border-color: var(--danger-100);  color: var(--danger-700); }
.cjd-note.is-mute { background: var(--n-50);       border-color: var(--border-default); color: var(--text-secondary); }

.cjd-note-t { display: block; font-weight: var(--fw-semibold); }
.cjd-note.is-mute .cjd-note-t { color: var(--text-primary); }
.cjd-note-b { margin-block-start: 3px; }

.cjd-note-l {
    margin: 6px 0 0;
    padding-inline-start: 18px;
    font-weight: var(--fw-semibold);
}

.cjd-note a { font-weight: var(--fw-semibold); text-decoration: underline; }
.cjd-note a:focus-visible { outline: none; box-shadow: var(--shadow-focus); border-radius: var(--r-xs); }

/* ---------- Another job, offered inside a panel ---------- */

.cjd-mini-list { display: flex; flex-direction: column; gap: 7px; }

.cjd-mini {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 9px 11px;
    border: 1px solid var(--border-subtle);
    border-radius: var(--r-lg);
    background: var(--surface-raised);
    text-decoration: none;
    transition: border-color var(--dur-fast) var(--ease-out),
                background-color var(--dur-fast) var(--ease-out);
}

.cjd-mini:hover { border-color: var(--p-200); background: var(--p-50); }
.cjd-mini:focus-visible { outline: none; box-shadow: var(--shadow-focus); }

.cjd-mini-txt { flex: 1; min-width: 0; }

.cjd-mini-t {
    font-size: var(--fs-sm);
    font-weight: var(--fw-semibold);
    color: var(--text-primary);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.cjd-mini-m {
    margin-block-start: 1px;
    font-size: var(--fs-xs);
    color: var(--text-tertiary);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.cjd-mini-ar { flex-shrink: 0; color: var(--text-tertiary); }
.cjd-mini:hover .cjd-mini-ar { color: var(--text-brand); }
[dir="rtl"] .cjd-mini-ar { transform: scaleX(-1); }

/* ---------- People and company facts ---------- */

.cjd-person {
    display: flex;
    align-items: center;
    gap: 11px;
    padding: 9px 0;
}

.cjd-person + .cjd-person { border-block-start: 1px solid var(--border-subtle); }
.cjd-person:first-child { padding-block-start: 0; }
.cjd-person:last-child { padding-block-end: 0; }

.cjd-avatar {
    display: grid;
    place-items: center;
    flex-shrink: 0;
    width: 34px;
    height: 34px;
    border-radius: var(--r-full);
    background: var(--p-100);
    font-size: var(--fs-xs);
    font-weight: var(--fw-bold);
    color: var(--text-brand);
    text-transform: uppercase;
}

.cjd-person-txt { min-width: 0; }
.cjd-person-n { font-size: var(--fs-sm); font-weight: var(--fw-semibold); }
.cjd-person-r { font-size: var(--fs-xs); color: var(--text-tertiary); }

.cjd-fact {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 12px;
    padding: 8px 0;
}

.cjd-fact + .cjd-fact { border-block-start: 1px solid var(--border-subtle); }
.cjd-fact:first-child { padding-block-start: 0; }
.cjd-fact:last-child { padding-block-end: 0; }

.cjd-fact-k { font-size: var(--fs-sm); color: var(--text-tertiary); }

.cjd-fact-v {
    font-size: var(--fs-sm);
    font-weight: var(--fw-medium);
    color: var(--text-primary);
    text-align: end;
    word-break: break-word;
}

/* ---------- The apply form ---------- */

.cjd-field + .cjd-field,
.cjd-field + .cjd-note,
.cjd-note + .cjd-field,
.cjd-note + .cjd-note { margin-block-start: 10px; }

.cjd-lbl {
    display: block;
    margin: 0 0 6px;
    font-size: var(--fs-2xs);
    font-weight: var(--fw-semibold);
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--text-tertiary);
}

/* The one CV there is: nothing to choose, so it is stated rather
   than offered as a control that does nothing. */
.cjd-cv {
    display: flex;
    align-items: center;
    gap: 9px;
    padding: 9px 11px;
    border: 1px solid var(--border-default);
    border-radius: var(--r-md);
    background: var(--n-25);
    font-size: var(--fs-sm);
}

.cjd-cv svg { color: var(--text-tertiary); flex-shrink: 0; }
.cjd-cv-n { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* The screening inputs. They shared one inline string with no
   hover, no focus ring and a hard-coded left padding that put the
   caret on the wrong side of an RTL box. */
.cjd-in,
.cjd-ta,
.cjd-sel {
    width: 100%;
    border: 1px solid var(--border-default);
    border-radius: var(--r-md);
    background: var(--n-0);
    font: inherit;
    font-size: var(--fs-sm);
    color: var(--text-primary);
    transition: border-color var(--dur-fast) var(--ease-out),
                box-shadow var(--dur-fast) var(--ease-out);
}

.cjd-in,
.cjd-sel { height: var(--h-lg); padding-inline: 10px; }
.cjd-ta { padding: 8px 10px; line-height: var(--lh-normal); resize: vertical; }

.cjd-in:hover,
.cjd-ta:hover,
.cjd-sel:hover { border-color: var(--border-strong); }

.cjd-in:focus-visible,
.cjd-ta:focus-visible,
.cjd-sel:focus-visible {
    outline: none;
    border-color: var(--border-focus);
    box-shadow: var(--shadow-focus);
}

.cjd-sel-w { position: relative; display: block; }
.cjd-sel { padding-inline-end: 30px; cursor: pointer; -webkit-appearance: none; appearance: none; }

.cjd-sel-ar {
    position: absolute;
    inset-inline-end: 10px;
    inset-block-start: 50%;
    transform: translateY(-50%);
    color: var(--text-tertiary);
    pointer-events: none;
}

.cjd-q + .cjd-q { margin-block-start: 11px; }

.cjd-q-l {
    display: block;
    margin: 0 0 5px;
    font-size: var(--fs-sm);
    font-weight: var(--fw-semibold);
    line-height: var(--lh-snug);
    color: var(--text-primary);
}

/* A question that can end the application by itself is marked
   before it is answered — that is the whole basis on which an
   instant rejection is fairer than weeks of silence. */
.cjd-must {
    display: inline-flex;
    align-items: center;
    margin-inline-start: 6px;
    padding: 1px 7px;
    border: 1px solid var(--warning-100);
    border-radius: var(--r-full);
    background: var(--warning-50);
    font-size: var(--fs-2xs);
    font-weight: var(--fw-semibold);
    color: var(--warning-700);
    white-space: nowrap;
}

.cjd-req { margin-inline-start: 3px; color: var(--danger-600); }

.cjd-q-h {
    margin: 0 0 5px;
    font-size: var(--fs-xs);
    line-height: var(--lh-snug);
    color: var(--text-tertiary);
}

/* The staleness recheck: one question, and never a gate. */
.cjd-stale-row {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 7px;
    margin-block-start: 8px;
}

.cjd-stale-in {
    width: 76px;
    height: var(--h-sm);
    padding-inline: 8px;
    border: 1px solid var(--border-default);
    border-radius: var(--r-sm);
    background: var(--n-0);
    font: inherit;
    font-size: var(--fs-sm);
    font-variant-numeric: tabular-nums;
    color: var(--text-primary);
}

.cjd-stale-in:focus-visible { outline: none; border-color: var(--border-focus); box-shadow: var(--shadow-focus); }

.cjd-stale-act { display: flex; flex-wrap: wrap; gap: 7px; margin-block-start: 10px; }

/* ---------- Buttons ---------- */

.cjd-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
    height: var(--h-xl);
    padding: 0 16px;
    border: 1px solid var(--border-default);
    border-radius: var(--r-md);
    background: var(--n-0);
    font: inherit;
    font-size: var(--fs-md);
    font-weight: var(--fw-semibold);
    color: var(--text-secondary);
    text-decoration: none;
    cursor: pointer;
    transition: background-color var(--dur-fast) var(--ease-out),
                border-color var(--dur-fast) var(--ease-out);
}

.cjd-btn:hover { background: var(--n-50); color: var(--text-primary); }
.cjd-btn:focus-visible { outline: none; box-shadow: var(--shadow-focus); }
.cjd-btn-wide { width: 100%; }
.cjd-btn-sm { height: var(--h-md); padding: 0 13px; font-size: var(--fs-sm); border-radius: var(--r-sm); }
.cjd-btn + .cjd-btn { margin-block-start: 8px; }

.cjd-btn-p {
    border-color: var(--p-700);
    background: var(--p-700);
    color: var(--text-inverse);
}

.cjd-btn-p:hover { background: var(--p-800); border-color: var(--p-800); color: var(--text-inverse); }
.cjd-btn-p:disabled { opacity: 0.55; cursor: default; }
.cjd-btn-p:disabled:hover { background: var(--p-700); border-color: var(--p-700); }

.cjd-btn-s {
    border-color: var(--p-200);
    background: var(--p-50);
    color: var(--text-brand);
}

.cjd-btn-s:hover { background: var(--p-100); border-color: var(--p-300); color: var(--text-brand); }

/* Already applied. Not a control any more — it states a fact and
   carries the date, so it must not offer a pointer or a hover. */
.cjd-btn-done {
    border-color: var(--success-600);
    background: var(--success-600);
    color: var(--text-inverse);
    cursor: default;
}

.cjd-btn-done:hover { background: var(--success-600); border-color: var(--success-600); color: var(--text-inverse); }

.cjd-hint {
    margin: 10px 0 0;
    font-size: var(--fs-xs);
    line-height: var(--lh-normal);
    color: var(--text-tertiary);
    text-align: center;
}

.cjd-status {
    margin: 9px 0 0;
    font-size: var(--fs-xs);
    color: var(--text-tertiary);
    text-align: center;
}

/* What the employer will actually see. A link, so it does not
   compete with the two buttons under it. */
.cjd-peek {
    display: block;
    margin-block-end: 10px;
    font-size: var(--fs-sm);
    color: var(--text-secondary);
    text-align: center;
    text-decoration: underline;
}

.cjd-peek:hover { color: var(--text-brand); }
.cjd-peek:focus-visible { outline: none; box-shadow: var(--shadow-focus); border-radius: var(--r-sm); }

/* ---------- Loading ---------- */

/* It was the word "Loading" in the middle of an empty page. The
   page has a known shape before its data arrives, so it draws it. */
.cjd-skel { display: flex; flex-direction: column; gap: 14px; }

.cjd-skel-row {
    display: grid;
    grid-template-columns: minmax(0, 1fr) 340px;
    gap: 16px;
    align-items: start;
}

.cjd-skel-box {
    padding: 22px 24px;
    border: 1px solid var(--border-subtle);
    border-radius: var(--r-xl);
    background: var(--surface-raised);
}

.cjd-skel-bar {
    height: 12px;
    border-radius: var(--r-full);
    background: linear-gradient(90deg, var(--n-100) 0%, var(--n-75) 50%, var(--n-100) 100%);
    background-size: 200% 100%;
    animation: cjd-shimmer 1.4s var(--ease-in-out) infinite;
}

.cjd-skel-bar + .cjd-skel-bar { margin-block-start: 12px; }
.cjd-skel-bar.is-t { height: 24px; width: 58%; }
.cjd-skel-bar.is-s { width: 34%; }
.cjd-skel-bar.is-m { width: 82%; }

@keyframes cjd-shimmer {
    from { background-position: 200% 0; }
    to   { background-position: -200% 0; }
}

@media (prefers-reduced-motion: reduce) {
    .cjd-skel-bar { animation: none; }
}

/* ---------- Rhythm inside the apply panel ---------- */

/* The panel's contents are conditional: a notice, a field, a rule with
   jobs under it, then the buttons — in almost any combination. Spacing
   it per element meant a margin declared on nine elements that each
   only sometimes had a neighbour, and two of them disagreed. It is the
   joins that carry it instead, so every combination lands on the same
   rhythm and an absent block leaves no gap behind. */
.cjd-note + .cjd-btn,
.cjd-field + .cjd-btn { margin-block-start: 12px; }

.cjd-status + .cjd-note,
.cjd-split + .cjd-note { margin-block-start: 12px; }

.cjd-note .cjd-mini-list { margin-block-start: 9px; }

/* Yes and No sit side by side; the general button stack must not push
   the second one onto its own line. */
.cjd-stale-act .cjd-btn + .cjd-btn { margin-block-start: 0; }

/* ---------- Narrow ---------- */

/* The apply panel stops following the page and takes its place
   under the description. Everything in the side column that is not
   the apply panel lays out across the width instead of down it —
   three half-empty cards stacked on a tablet was a lot of
   scrolling for very little. */
@media (max-width: 1080px) {
    .cjd-grid,
    .cjd-skel-row { grid-template-columns: minmax(0, 1fr); }

    .cjd-side {
        position: static;
        display: grid;
        grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
        align-items: start;
        gap: 14px;
    }

    /* The thing they came to do gets the full width, above the rest. */
    .cjd-panel.is-act { grid-column: 1 / -1; }
}

@media (max-width: 800px) {
    .cjd-head,
    .cjd-card,
    .cjd-match { padding: 18px; border-radius: var(--r-lg); }

    .cjd-title { font-size: var(--fs-2xl); }
    .cjd-logo { width: 52px; height: 52px; font-size: var(--fs-lg); }

    /* A five-step process cannot be five columns here. The rail
       turns on its side: the numbers run down a line and each step
       gets the whole width for its name. */
    .cjd-steps { flex-direction: column; gap: 0; }
    .cjd-steps::before { inset-block: 20px; inset-inline: 14px auto; width: 2px; height: auto; }
    [dir="rtl"] .cjd-steps::before { inset-inline: auto 14px; }

    .cjd-step {
        display: grid;
        grid-template-columns: 28px minmax(0, 1fr);
        align-items: center;
        gap: 12px;
        padding: 8px 0;
        border: none;
        border-radius: 0;
        background: transparent;
        text-align: start;
    }

    .cjd-step-n { margin: 0; }
    .cjd-step-d { margin-block-start: 2px; }
}

@media (max-width: 560px) {
    .cjd-head-top { gap: 12px; }
    .cjd-logo { width: 44px; height: 44px; border-radius: var(--r-lg); font-size: var(--fs-md); }
    .cjd-title { font-size: var(--fs-xl); }
    .cjd-match-top { flex-wrap: wrap; gap: 12px; }
    .cjd-factors { grid-template-columns: minmax(0, 1fr); }
    .cjd-fact { flex-direction: column; align-items: flex-start; gap: 2px; }
    .cjd-fact-v { text-align: start; }
}

/* ============================================================
   57. Recruiter: candidate profile tab row (cp-tabs)

   Seven views of one candidate — summary, role match, activity,
   conversations, assessments, profile, history — and the switch
   between them was a bare hairline drawn straight on the page
   grey. Three things were wrong with it, and all three are the
   same thing: the row was not a surface.

     - Nothing said where the switch ended. The tabs sat on the
       same ground as the panels under them, so the eye read the
       first panel's top edge as the row's bottom edge and the
       band as a gap.

     - Every rule was a style attribute built from an `on` bool,
       which bought no hover, no focus ring and no RTL. A tab
       gave no sign it was a target until it was already active.

     - Each icon wore its own 18px bordered box. Seven outlined
       squares in a row read as seven checkboxes, and the count
       pills then had to shout over them.

   It is a card now — white, on the page grey, the same border,
   radius and shadow as the header card above it — so the row
   reads as one object spanning the content width. It pins to the
   top of the scroller, because a profile is long and the switch
   is the one control you reach for while reading it.

   The tabs themselves are the shared .tab from section 9: the
   hover tint, the inset 2px mark with its cap, and the focus
   ring all come from there. What is scoped here is the band, the
   icon alignment (.tab is set for a label alone — the same three
   lines .aic-tabs and .csb-tabs add) and the count pill.
   ============================================================ */

.cp-tabs {
    display: flex;
    align-items: stretch;
    gap: 2px;
    /* Pinned rather than scrolled away: seven tabs over a page you
       read for a minute at a time, and a switch you have to scroll
       back up to reach is a switch you stop using. The scroller is
       <main> and its padding box is the scrollport, so 0 pins the
       band flush against the top edge with nothing showing above
       it - the panels pass behind an opaque card, not under a
       floating row. */
    position: sticky;
    inset-block-start: 0;
    /* Under a drawer and the topbar, over the page's own panels. */
    z-index: 20;
    margin-block-end: 14px;
    padding: 4px 6px;
    background: var(--surface-raised);
    border: 1px solid var(--border-default);
    border-radius: var(--r-lg);
    /* One step up from the header card's --shadow-xs: content passes
       under this one, and the band has to sit above what it covers. */
    box-shadow: var(--shadow-sm);
    /* Scrolls rather than wraps — a wrapped row changes height and
       pushes the panels down as you move between tabs. */
    overflow-x: auto;
    scrollbar-width: none;
    scroll-snap-type: x proximity;
}

.cp-tabs::-webkit-scrollbar { display: none; }

.cp-tabs .tab {
    display: inline-flex;
    align-items: center;
    gap: 7px;
    /* Grows into the rest of the band so seven tabs divide it with
       nothing left over, but never shrinks: a flex row squeezes
       labels to fit before it agrees to scroll, and this row scrolls.
       Hence 1 0 auto rather than the 1 1 0 the shared .tab takes -
       the band is a scroller, and a share of a row that is already
       too small is not a share. */
    flex: 1 0 auto;
    padding: 9px 13px 10px;
    /* The band is the surface the mark sits in, so the tab keeps its
       own bottom edge instead of being pulled onto a group hairline
       it no longer has. */
    margin-block-end: 0;
    border-radius: var(--r-sm);
    scroll-snap-align: start;
}

/* currentColor, so the icon follows the label through hover and on
   rather than being a second colour to keep in step. */
.cp-tabs .tab > svg { flex-shrink: 0; opacity: 0.75; }
.cp-tabs .tab.on > svg { opacity: 1; }

/* Flat, against the downward wash the shared .tab.on now carries. That wash
   fades out onto a group hairline; this row has no hairline - its tabs are
   rounded on all four corners inside a card - so the fade would read as a
   tint that stops halfway down the tab for no reason. */
.cp-tabs .tab.on,
.cp-tabs .tab.on:hover { background: var(--p-50); }

/* A count, in the same pill a count wears everywhere else here. It
   takes the brand tint on the active tab and stays neutral off it,
   so one row of numbers does not read as seven alerts. */
.cp-tab-n {
    display: inline-grid;
    place-items: center;
    min-width: 18px;
    height: 18px;
    padding: 0 5px;
    border-radius: var(--r-full);
    background: var(--n-100);
    color: var(--text-secondary);
    font-size: var(--fs-2xs);
    font-weight: var(--fw-semibold);
    font-variant-numeric: tabular-nums;
    transition: background-color var(--dur-fast) var(--ease-out),
                color var(--dur-fast) var(--ease-out);
}

.cp-tabs .tab.on .cp-tab-n {
    background: var(--p-700);
    color: var(--n-0);
}

@media (max-width: 700px) {
    /* Tighter, so more of the row is readable before it has to be
       swiped. The labels stay — they are what names the tab. */
    .cp-tabs { padding: 3px 4px; }
    .cp-tabs .tab { padding: 9px 10px 10px; gap: 6px; }
}

/* ============================================================
   58. Recruiter: the candidate action drawer (cad-*, dr-foot)

   One component draws eighteen right-hand drawers on the
   candidate page — submit, schedule, email, offer, log activity,
   assign test, and the rest — and their fields are styled by two
   C# strings, InputStyle and TextareaStyle, that restate .input's
   height, padding, border and radius from memory. What a string
   cannot restate is the half of .input that is not a declaration:
   the hover, the focus ring, the placeholder colour and the error
   state. So these drawers are the one place in the product where
   tabbing into a field shows nothing at all.

   Log activity is on .input and on the recruiter drawer's own
   form language - the .cli-* sections, legends, tier cards,
   labels and readiness footer that the add-client and
   add-candidate drawers already use, so the three read as one
   product. Schedule interview followed it and reuses the same
   pieces from its own file, .siv-* at the end of this sheet.
   The remaining sixteen still carry the strings and the plain
   header, and each one that is next worked on should lose
   them. What is here is only what that language has no piece
   for:

     .cad-body       the panel-height column, so the action bar
                     sits on the bottom edge of the drawer
                     instead of floating halfway up it under a
                     short form
     .cad-head       the plain title and the subject it acts on,
                     for the seventeen not yet converted
     .cad-head-rich  the drawer header band rebuilt for a body
                     whose scroller belongs to DrawerHost
     .cad-mark       the header tile, carrying the kind
     .cad-kinds      the .cli-tier cards, two across
     .cad-chip       today / tomorrow / next week
     .dr-foot        the action bar, shared by every recruiter
                     drawer

   On .cad-kinds specifically: it was a Segment, and a Segment
   paints its selected option in solid --p-700. That is the fill
   of the primary button in the same drawer, so "Log activity"
   opened with two navy blocks eleven fields apart and nothing
   saying which one submits. It is now the same card the tier
   picker uses - marked with a ring and a tick, not filled - and
   each card says what choosing it means, which four one-word
   segments could not.
   ============================================================ */

/* Sunken, for the same reason .drw-body is: the .drw-sec groups inside
   are raised panels and need a ground to be raised against. */
.cad-body {
    background: var(--surface-page);
    display: flex;
    flex-direction: column;
    /* The panel is a fixed-height flex column and this is the child of its
       scroller, so 100% resolves — and the column can still grow past it. */
    min-height: 100%;
    padding: 18px 20px;
}

.cad-head {
    margin-block-end: 14px;
    padding-block-end: 12px;
    border-block-end: 1px solid var(--border-subtle);
}

.cad-title {
    margin: 0;
    font-size: var(--fs-lg);
    font-weight: var(--fw-bold);
}

/* Who or what the action is about - a name, a role, a submission. */
.cad-sub {
    margin-block-start: 2px;
    font-size: var(--fs-xs);
    color: var(--text-tertiary);
}

/* ---------- The rich header ---------- */

/* QDrawer draws its own header, but these three drawers live inside
   DrawerHost, where the scroll is on the wrapper and the body is padded. The
   same band is made here out of sticky and negated padding — the trick
   .dr-foot already uses at the other end of the panel. What sits inside it is
   the shared header row: .drw-head-row with .drw-head-txt, .drw-eyebrow,
   .drw-title and .drw-sub, the same pieces .drw-head puts on a QDrawer. */
.cad-head-rich {
    position: sticky;
    inset-block-start: 0;
    z-index: 2;
    margin: -18px -20px 16px;
    padding: 16px 20px 14px;
    /* The same dark band a QDrawer's header carries ("20."), so a drawer
       that happens to be hosted by DrawerHost does not read as a
       different kind of drawer. */
    background: var(--p-800);
    border-block-end: 1px solid var(--p-900);
    color: var(--text-inverse);
}

/* The client's mark is drawn from the name; there is no name to draw an
   activity from, so this one carries the kind — and carries it in the kind's
   own colour, which is the only place in the drawer that colour appears. */
.cad-mark {
    display: grid;
    place-items: center;
    width: 44px;
    height: 44px;
    flex-shrink: 0;
    border-radius: var(--r-lg);
    background: var(--cad-k);
    color: var(--text-inverse);
    box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.18);
    transition: background var(--dur-mid) var(--ease-out);
}

.cad-mark { --cad-k: var(--p-600); }
.cad-mark.k-call { --cad-k: var(--info-600); }
.cad-mark.k-meeting { --cad-k: var(--gold-600); }
.cad-mark.k-task { --cad-k: var(--success-600); }

/* ---------- The activity-type picker ---------- */

/* .cli-tier cards, two across: four kinds, and a label plus a line of
   consequence does not fit a quarter of the panel once it is translated.
   The cards are the client drawer's, unchanged — what is added is the icon
   beside the label, because a kind is recognised as a mark before it is read
   as a word. */
.cad-kinds {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 8px;
}

.cad-kind-n {
    display: flex;
    align-items: center;
    gap: 7px;
    min-width: 0;
}

.cad-kind-n > svg { flex-shrink: 0; color: var(--text-tertiary); }

.cli-tier[aria-pressed="true"] .cad-kind-n > svg { color: var(--p-700); }

/* The assessment banks, and the candidates an offer can be written for, drawn
   as the same card. The count is the tenant's rather than a fixed four, so the
   grid scrolls once it is past about four rows instead of pushing the rest of
   the form and the footer off the panel. Under the cap nothing scrolls and the
   block is indistinguishable from the kinds above it. */
.cad-picks {
    max-height: 296px;
    overflow-y: auto;
    overscroll-behavior: contain;
    /* Room for the scrollbar, so a card is not clipped by one when it appears. */
    padding-inline-end: 2px;
}

/* An option that exists and cannot be taken - a bank with no questions in it.
   Present but unavailable, rather than missing. */
.cli-tier:disabled { opacity: 0.55; cursor: not-allowed; }
.cli-tier:disabled:hover { border-color: var(--border-default); background: var(--n-0); }

/* The required mark on a group label. QFormField draws one for a field; a
   .cli-field-l has to carry its own. */
.cad-req { color: var(--danger-600); font-size: var(--fs-2xs); }

/* ---------- When it happened ---------- */

/* Today, tomorrow and next week, sitting on the label's own line — the slot
   the client drawer gives "Clear tier". Small and uppercase, so they read as
   the label's controls rather than as three more answers. */
.cad-quick { display: flex; flex-wrap: wrap; gap: 6px; }

.cad-chip {
    height: var(--h-xs);
    padding: 0 9px;
    background: var(--n-0);
    border: 1px solid var(--border-default);
    border-radius: var(--r-full);
    font: inherit;
    font-size: var(--fs-2xs);
    font-weight: var(--fw-semibold);
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--text-secondary);
    cursor: pointer;
    transition: border-color var(--dur-fast) var(--ease-out),
                background-color var(--dur-fast) var(--ease-out),
                color var(--dur-fast) var(--ease-out);
}

.cad-chip:hover { border-color: var(--border-strong); color: var(--text-primary); }
.cad-chip.on { border-color: var(--p-700); background: var(--p-50); color: var(--p-800); }
.cad-chip:focus-visible { outline: none; box-shadow: var(--shadow-focus); }

/* The browser's own picker button, which arrives at full black on a field
   whose own text is --text-primary and outweighs it. */
.cad-body input[type="date"]::-webkit-calendar-picker-indicator,
.cad-body input[type="time"]::-webkit-calendar-picker-indicator {
    opacity: 0.45;
    cursor: pointer;
}

.cad-body input[type="date"]:hover::-webkit-calendar-picker-indicator,
.cad-body input[type="time"]:hover::-webkit-calendar-picker-indicator { opacity: 0.8; }

/* ---------- Footer ---------- */

/* .dr-foot pushes its children to the end; the readiness row wants the whole
   width, because half of it belongs at the other end. */
.dr-foot > .drw-foot-row { flex: 1; min-width: 0; }

@media (max-width: 560px) {
    .cad-body { padding: 16px 14px; }
    .cad-head-rich { margin-inline: -14px; padding-inline: 14px; }
    /* One card per row before the consequence line wraps to four. */
    .cad-kinds { grid-template-columns: minmax(0, 1fr); }
    /* The shortcuts drop under the label rather than squeezing it to nothing. */
    .cad-body .cli-field-l { flex-wrap: wrap; }
}

/* ---------- The action bar ---------- */

.dr-foot {
    position: sticky;
    inset-block-end: 0;
    /* Out to the drawer's own edges: the bar is the panel's bottom, not a box
       inside the form. The values are the body's padding, negated. */
    margin: 14px -20px -18px;
    padding: 12px 20px;
    display: flex;
    gap: 8px;
    justify-content: flex-end;
    align-items: center;
    background: var(--n-25);
    border-block-start: 1px solid var(--border-default);
}

/* In the flex-column body, auto takes the slack and drops the bar onto the
   bottom edge; once the form is taller than the panel there is no slack, auto
   is zero, and the 14px above it comes from the last field's own margin -
   which is what a block-layout drawer collapses to anyway. */
.cad-body .dr-foot { margin-block-start: auto; }

/* Only the body that narrowed its own padding above: every other drawer
   using this bar keeps 20px, and a bar inset 6px from the panel it is
   supposed to close reads as a misdrawn edge. */
@media (max-width: 560px) {
    .cad-body .dr-foot { margin-inline: -14px; padding-inline: 14px; }
}

/* ============================================================
   SCHEDULE INTERVIEW DRAWER (.siv-*)
   ============================================================

   The second of the eighteen recruiter drawers to move onto the
   drawer form language. It shares everything the language
   already has - .cad-body, .cad-head-rich, .cad-mark, .cad-kinds,
   .cad-quick / .cad-chip, .drw-sec, .drw-legend, .cli-field,
   .cli-tier, .cli-ready and .dr-foot - so what is here is only
   the four things scheduling has and logging an activity does
   not: the candidate it is already bound to, the list of times
   offered to that candidate, the five venues, and the panel.

   The type also joins .cad-mark's key colours. A screen, a
   technical round, an onsite and a final are four different
   commitments of the panel's day, and the tile is the one place
   in the drawer that says which without being read.
   ============================================================ */

.cad-mark.k-iv-screen { --cad-k: var(--info-600); }
.cad-mark.k-iv-technical { --cad-k: var(--p-600); }
.cad-mark.k-iv-onsite { --cad-k: var(--gold-600); }
.cad-mark.k-iv-final { --cad-k: var(--success-600); }

/* ---------- The candidate, when the drawer is opened pre-bound ----------

   Opened from an application there is nothing to pick, and the picker was
   simply hidden - so the form said who it was for nowhere except the header
   subtitle. This is the same row the panel below uses, which is the point:
   the candidate and the interviewers read as people, not as fields. */
.siv-who {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 9px 11px;
    border: 1px solid var(--border-subtle);
    border-radius: var(--r-md);
    background: var(--n-25);
}

.siv-who-m { flex: 1; min-width: 0; }

.siv-who-n {
    font-size: var(--fs-sm);
    font-weight: var(--fw-semibold);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* ---------- The times offered to the candidate ----------

   Numbered, because the candidate's portal presents them as a numbered list
   and a recruiter checking what was sent should be reading the same thing.
   Two is the floor ScheduleAsync enforces, so those two rows keep a disabled
   remove rather than losing it - a control that vanishes reads as a fault. */
.siv-slots {
    display: flex;
    flex-direction: column;
    gap: 8px;
}

.siv-slot {
    display: grid;
    grid-template-columns: 64px minmax(0, 1fr) 108px 36px;
    gap: 8px;
    align-items: center;
}

.siv-slot-n {
    font-size: var(--fs-2xs);
    font-weight: var(--fw-semibold);
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--text-tertiary);
}

.siv-slot-x,
.siv-p-x {
    display: grid;
    place-items: center;
    border: 1px solid var(--border-default);
    border-radius: var(--r-md);
    background: var(--n-0);
    color: var(--text-tertiary);
    cursor: pointer;
    transition: border-color var(--dur-fast) var(--ease-out),
                color var(--dur-fast) var(--ease-out),
                background-color var(--dur-fast) var(--ease-out);
}

.siv-slot-x { height: 36px; }

.siv-slot-x:hover:not(:disabled),
.siv-p-x:hover {
    border-color: var(--danger-500);
    background: var(--danger-50);
    color: var(--danger-600);
}

.siv-slot-x:disabled { opacity: 0.45; cursor: not-allowed; }

.siv-slot-x:focus-visible,
.siv-p-x:focus-visible,
.siv-add:focus-visible,
.siv-via:focus-visible { outline: none; box-shadow: var(--shadow-focus); }

/* Dashed, so it reads as the place a row is added rather than as a sixth
   answer sitting under five real ones. */
.siv-add {
    display: flex;
    align-items: center;
    align-self: flex-start;
    gap: 6px;
    height: 32px;
    padding: 0 12px;
    border: 1px dashed var(--border-strong);
    border-radius: var(--r-md);
    background: transparent;
    font: inherit;
    font-size: var(--fs-sm);
    font-weight: var(--fw-semibold);
    color: var(--p-700);
    cursor: pointer;
    transition: border-color var(--dur-fast) var(--ease-out), background-color var(--dur-fast) var(--ease-out);
}

.siv-add:hover:not(:disabled) { border-color: var(--p-700); background: var(--p-50); }
.siv-add:disabled { opacity: 0.45; cursor: not-allowed; }

/* ---------- Where it happens ----------

   Five pills rather than a select. The select hid the only fact that decides
   whether anyone has to paste a URL: whether the provider is connected. The
   dot says it on the pill itself, before the venue is even chosen. */
.siv-vias { display: flex; flex-wrap: wrap; gap: 6px; }

.siv-via {
    display: flex;
    align-items: center;
    gap: 7px;
    height: 34px;
    padding: 0 12px;
    border: 1px solid var(--border-default);
    border-radius: var(--r-full);
    background: var(--n-0);
    font: inherit;
    font-size: var(--fs-sm);
    font-weight: var(--fw-semibold);
    color: var(--text-secondary);
    cursor: pointer;
    transition: border-color var(--dur-fast) var(--ease-out),
                background-color var(--dur-fast) var(--ease-out),
                color var(--dur-fast) var(--ease-out);
}

.siv-via > svg { color: var(--text-tertiary); }
.siv-via:hover { border-color: var(--border-strong); color: var(--text-primary); }

.siv-via[aria-pressed="true"] {
    border-color: var(--p-700);
    background: var(--p-50);
    color: var(--p-800);
    box-shadow: inset 0 0 0 1px var(--p-700);
}

.siv-via[aria-pressed="true"] > svg { color: var(--p-700); }

/* Connected, so a link is generated rather than pasted. */
.siv-via-dot {
    width: 6px;
    height: 6px;
    border-radius: var(--r-full);
    background: var(--success-500);
}

/* Not connected is not an error - a pasted link works perfectly well - so the
   note is neutral rather than the red .cli-banner-bad. */
.siv-banner-note {
    border: 1px solid var(--border-subtle);
    background: var(--n-25);
    color: var(--text-secondary);
}

.siv-banner-note svg { color: var(--n-400); }

/* ---------- The panel ---------- */

.siv-panel {
    display: flex;
    flex-direction: column;
    gap: 6px;
}

.siv-p {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 7px 10px;
    border: 1px solid var(--border-subtle);
    border-radius: var(--r-md);
    background: var(--n-25);
}

.siv-p-m { flex: 1; min-width: 0; }

.siv-p-n {
    font-size: var(--fs-sm);
    font-weight: var(--fw-semibold);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.siv-p-x { width: 26px; height: 26px; flex-shrink: 0; }

/* Name, email, role, Add. The button sizes to its own label, which is
   translated - "Add" is short in English and rather less so elsewhere. */
.siv-add-row {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) 118px auto;
    gap: 6px;
    align-items: center;
}

/* ---------- Suggested questions ----------

   Gold is the product's AI surface and this is the only gold in the drawer,
   so the block does not need anything else to say what it is. */
.siv-ai {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 8px;
    padding: 12px;
    border: 1px solid var(--gold-200);
    border-radius: var(--r-lg);
    background: var(--gold-50);
}

.siv-ai-h {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    width: 100%;
}

.siv-ai-t {
    display: flex;
    align-items: center;
    gap: 6px;
    font-size: var(--fs-xs);
    font-weight: var(--fw-bold);
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--gold-800);
}

.siv-ai-t > svg { color: var(--gold-700); }

.siv-ai-e {
    font-size: var(--fs-xs);
    color: var(--text-secondary);
    line-height: var(--lh-snug);
}

.siv-ai-l {
    display: flex;
    flex-direction: column;
    gap: 5px;
    margin: 0;
    padding-inline-start: 18px;
    font-size: var(--fs-sm);
    color: var(--text-primary);
    line-height: var(--lh-snug);
}

/* The category, as an aside on the question rather than a second sentence. */
.siv-ai-c { color: var(--text-tertiary); }
.siv-ai-c::before { content: "\00b7\00a0"; }

@media (max-width: 560px) {
    /* The option number moves onto its own line before the date input is
       squeezed to a width no picker fits in. */
    .siv-slot { grid-template-columns: minmax(0, 1fr) 108px 36px; }
    .siv-slot-n { grid-column: 1 / -1; }
    /* Name and email stack; the role and Add keep their row. */
    .siv-add-row { grid-template-columns: minmax(0, 1fr) auto; }
    .siv-add-row > .input:first-child { grid-column: 1 / -1; }
}

/* ============================================================
   59. Recruiter: the offer timeline drawer (otd-*)

   The third recruiter drawer on the shared form language, and
   the first one that mostly reads rather than writes. It brings
   its chrome from "58." - .cad-body, .cad-head-rich, .dr-foot -
   its sections from the .cli-* language, and its money strip
   from the composer (.ofw-total in "46."), so the year-one total
   is the same object in the drawer as on the composer step that
   set it.

   What is here is only what reading an offer needs and writing
   one does not:

     .otd-state  the status line in the header: the pill, what
                 the offer is actually sitting on, and the clock
                 on that step
     .otd-flow   drafted, approved, sent, answered - four dots on
                 one track, filled as far as the offer got
     .otd-terms  the dates either side of the package
     .otd-ctr    the candidate's counter, each ask beside the
                 figure it would replace
     .otd-doc    the letter, and where it went
     .otd-ap     one approver: the step number, who, and whether
                 the offer is waiting on them
     .otd-tl     the chronology, oldest first

   On .otd-flow specifically: the status pill says where an offer
   is, and said nothing about where it had been. An offer
   withdrawn after it went out and one withdrawn before it left
   the desk were the same grey word. The rail is read from the
   dates rather than from the status, so those two now look as
   different as they are.
   ============================================================ */

/* The strip already sits in a section that spaces its children; the composer
   opens its own gap because there it follows a field directly. */
.otd .ofw-total { margin-block-start: 0; }

/* ---------- The state line ---------- */

/* Under the title, inside the sticky header: the pill, then the sentence the
   pill leaves out. "Pending approval" never said who it was pending on. */
.otd-state {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 8px;
    margin-block-start: 10px;
}

.otd-state-s {
    flex: 1;
    min-width: 0;
    font-size: var(--fs-xs);
    color: var(--text-secondary);
    line-height: var(--lh-snug);
}

/* The step's own clock. Neutral until it is nearly out, because a countdown
   painted red from the first hour stops meaning anything by the fourth. */
.otd-sla {
    flex-shrink: 0;
    font-family: var(--font-mono);
    font-size: var(--fs-2xs);
    font-variant-numeric: tabular-nums;
    color: var(--text-tertiary);
}

.otd-sla.is-soon { color: var(--warning-700); }
.otd-sla.is-over { color: var(--danger-700); font-weight: var(--fw-semibold); }

/* ---------- How far it got ---------- */

.otd-flow {
    display: grid;
    grid-template-columns: repeat(4, minmax(0, 1fr));
    margin: 0;
    padding: 0;
    list-style: none;
}

.otd-step {
    position: relative;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 6px;
    min-width: 0;
    padding-inline: 4px;
    text-align: center;
}

/* The track segment leading into this step, drawn from the previous dot's
   centre to this one's. The first step has nothing behind it. */
.otd-step::before {
    content: '';
    position: absolute;
    inset-block-start: 8px;
    inset-inline-start: -50%;
    inline-size: 100%;
    block-size: 2px;
    background: var(--n-150);
}

.otd-step:first-child::before { content: none; }
.otd-step.is-done::before,
.otd-step.is-now::before { background: var(--p-700); }
.otd-step.is-bad::before { background: var(--danger-500); }

.otd-step-d {
    position: relative;
    display: grid;
    place-items: center;
    inline-size: 18px;
    block-size: 18px;
    flex-shrink: 0;
    border: 2px solid var(--n-150);
    border-radius: var(--r-full);
    background: var(--n-0);
    color: var(--text-inverse);
}

.otd-step.is-done .otd-step-d {
    border-color: var(--p-700);
    background: var(--p-700);
}

/* The step it is sitting on: open, ringed, and the only dot with a halo. */
.otd-step.is-now .otd-step-d {
    border-color: var(--p-700);
    box-shadow: 0 0 0 3px var(--p-50);
}

.otd-step.is-bad .otd-step-d {
    border-color: var(--danger-500);
    background: var(--danger-500);
}

.otd-step-l {
    font-size: var(--fs-xs);
    font-weight: var(--fw-semibold);
    color: var(--text-tertiary);
    line-height: var(--lh-snug);
}

.otd-step.is-done .otd-step-l,
.otd-step.is-now .otd-step-l { color: var(--text-primary); }
.otd-step.is-bad .otd-step-l { color: var(--danger-700); }

/* Reserved even when empty, so a step with no date yet does not shorten its
   column and knock the labels out of line. */
.otd-step-w {
    min-block-size: 14px;
    font-family: var(--font-mono);
    font-size: var(--fs-2xs);
    font-variant-numeric: tabular-nums;
    color: var(--text-tertiary);
}

/* ---------- The dates either side of the package ---------- */

.otd-terms {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
    gap: 10px;
    margin: 0;
}

.otd-terms dt {
    font-size: var(--fs-2xs);
    font-weight: var(--fw-semibold);
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--text-tertiary);
}

.otd-terms dd {
    margin: 2px 0 0;
    font-size: var(--fs-sm);
    font-weight: var(--fw-semibold);
    font-variant-numeric: tabular-nums;
    color: var(--text-primary);
}

/* How long the expiry leaves. A date is only readable against today's, and
   today's is not on the panel. */
.otd-terms-x {
    margin-inline-start: 6px;
    font-weight: var(--fw-regular);
    color: var(--text-tertiary);
}

.otd-terms dd.is-soon,
.otd-terms dd.is-soon .otd-terms-x { color: var(--danger-700); }

/* ---------- The candidate's counter ---------- */

/* Warm rather than red: a counter is a negotiation, not a failure - the
   decline is the red thing and it lives in the footer. */
.otd-ctr {
    display: flex;
    flex-direction: column;
    gap: 8px;
    padding: 12px;
    border: 1px solid var(--warning-100);
    border-radius: var(--r-lg);
    background: var(--warning-50);
}

.otd-ctr-r {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 4px 8px;
}

.otd-ctr-k {
    flex: 1;
    min-width: 0;
    font-size: var(--fs-2xs);
    font-weight: var(--fw-semibold);
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--warning-700);
}

.otd-ctr-v {
    font-family: var(--font-mono);
    font-size: var(--fs-md);
    font-weight: var(--fw-semibold);
    font-variant-numeric: tabular-nums;
    color: var(--text-primary);
}

/* What the ask replaces, struck through: the comparison is the whole point of
   showing the counter here rather than as a line on the timeline. */
.otd-ctr-w {
    font-family: var(--font-mono);
    font-size: var(--fs-2xs);
    color: var(--text-tertiary);
    text-decoration: line-through;
}

.otd-ctr-n {
    margin: 0;
    padding-block-start: 8px;
    border-block-start: 1px solid var(--warning-100);
    font-size: var(--fs-xs);
    color: var(--text-secondary);
    line-height: var(--lh-snug);
}

/* ---------- The letter ---------- */

.otd-doc {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 10px 12px;
    border: 1px solid var(--border-subtle);
    border-radius: var(--r-md);
    background: var(--n-25);
}

.otd-doc-m {
    display: grid;
    place-items: center;
    inline-size: 32px;
    block-size: 32px;
    flex-shrink: 0;
    border-radius: var(--r-md);
    background: var(--n-0);
    border: 1px solid var(--border-subtle);
    color: var(--text-tertiary);
}

.otd-doc-b { flex: 1; min-width: 0; }

.otd-doc-t {
    font-size: var(--fs-sm);
    font-weight: var(--fw-semibold);
}

.otd-doc-a {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
}

/* ---------- Approvers ---------- */

.otd-aps {
    display: flex;
    flex-direction: column;
    gap: 6px;
}

.otd-ap {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 8px 10px;
    border: 1px solid var(--border-subtle);
    border-radius: var(--r-md);
    background: var(--n-25);
}

/* The one the offer is actually waiting on. Every other row was the same
   weight, so the chain said who was on it but not where it had stopped. */
.otd-ap.is-now {
    border-color: var(--warning-500);
    background: var(--warning-50);
}

/* A step whose other members were no longer needed: present, spent. */
.otd-ap.is-off { opacity: 0.6; }

.otd-ap-n {
    display: grid;
    place-items: center;
    inline-size: 20px;
    block-size: 20px;
    flex-shrink: 0;
    border-radius: var(--r-full);
    background: var(--n-100);
    font-family: var(--font-mono);
    font-size: var(--fs-2xs);
    font-weight: var(--fw-semibold);
    color: var(--text-secondary);
}

.otd-ap.is-now .otd-ap-n {
    background: var(--warning-500);
    color: var(--p-900);
}

.otd-ap-b { flex: 1; min-width: 0; }

.otd-ap-t {
    font-size: var(--fs-sm);
    font-weight: var(--fw-semibold);
}

/* ---------- The chronology ---------- */

.otd-tl {
    display: flex;
    flex-direction: column;
    margin: 0;
    padding: 0;
    list-style: none;
}

.otd-tl-i {
    position: relative;
    display: flex;
    gap: 10px;
    padding-block-end: 14px;
}

/* The rail between the dots, stopping at the last one rather than running off
   the end of the list. */
.otd-tl-i::before {
    content: '';
    position: absolute;
    inset-block: 14px 0;
    inset-inline-start: 4px;
    inline-size: 1px;
    background: var(--border-subtle);
}

.otd-tl-i:last-child { padding-block-end: 0; }
.otd-tl-i:last-child::before { content: none; }

.otd-tl-d {
    position: relative;
    inline-size: 9px;
    block-size: 9px;
    flex-shrink: 0;
    margin-block-start: 4px;
    border-radius: var(--r-full);
    background: var(--n-300);
}

.otd-tl-d.t-good { background: var(--success-500); }
.otd-tl-d.t-warn { background: var(--warning-500); }
.otd-tl-d.t-bad { background: var(--danger-500); }
.otd-tl-d.t-brand { background: var(--p-700); }

.otd-tl-b { flex: 1; min-width: 0; }

.otd-tl-t {
    font-size: var(--fs-sm);
    font-weight: var(--fw-semibold);
}

.otd-tl-w {
    margin-block-start: 2px;
    font-family: var(--font-mono);
    font-size: var(--fs-2xs);
    font-variant-numeric: tabular-nums;
    color: var(--text-tertiary);
}

/* ---------- Rejecting, and the bar ---------- */

.otd-rej-t {
    block-size: auto;
    min-block-size: 68px;
    padding-block: 8px;
    resize: vertical;
}

/* Ending the offer keeps the far end of the bar; .drw-foot-row pushes the two
   apart, and the empty div holds the split when there is nothing to end. */
.otd-foot-l {
    display: flex;
    min-width: 0;
}

@media (max-width: 560px) {
    /* Two rows of two: four labels and four dates do not survive a quarter of a
       narrow panel, and the track still reads across each row. */
    .otd-flow { grid-template-columns: repeat(2, minmax(0, 1fr)); row-gap: 12px; }
    .otd-step:nth-child(3)::before { content: none; }
}

/* ============================================================
   60. Recruiter: the new-offer drawer (ofcd)

   The offer form laid out the way the add-candidate drawer is:
   ruled .drw-sec groups under uppercase legends, .drw-grid2 /
   .drw-grid3 rows of labelled fields, and Cancel plus the two
   ways out on the bar. Every piece of that is in "20." with the
   rest of the drawer chrome and the fields are in "1.", because
   the two drawers are the same form filled in about two
   different records. One rule is all that is left here.
   ============================================================ */

/* The total strip opens its own gap in the composer because there it follows a
   field directly. In a group that already spaces its children it would be
   double. */
.ofcd .ofw-total { margin-block-start: 0; }

/* ============================================================
   61. Super Admin: Packages (pkg-*)

   /admin/packages is a list of nine-ish rows over an editor for
   the one you picked, and both halves are the shared chrome —
   .pg for the bands, .tbl for the list, .fld-grid for the form,
   .set-bar for the commit, all in "12." and "18.". What is left
   here is the part that is only true of a package: a row whose
   identity is an audience-coloured mark, three columns of
   figures that have to line up on their digits, and the two
   price tables whose entire right-hand column is one narrow
   numeric field per row.
   ============================================================ */

/* ---------- The list ---------- */

/* The row being edited. A tint alone was what this had, and on a
   nine-row table it reads as a hover that got stuck; the navy
   edge says the form below belongs to this row. inset, so it
   costs no layout and cannot push the first cell's text. */
.pkg-row.is-editing > td {
    background: var(--p-50);
    box-shadow: inset 2px 0 0 var(--p-700);
}

/* Only the first cell draws the edge — one per row, not one per
   column. */
.pkg-row.is-editing > td:not(:first-child) { box-shadow: none; }

.pkg-id {
    display: flex;
    align-items: center;
    gap: 11px;
    min-width: 0;
}

/* min-width: 0 so .trunc inside can actually shrink rather than
   widening the column until the table scrolls. */
.pkg-id-txt { min-width: 0; }

/* The name and the custom chip share a line: stacked, the chip
   would put a third line under half the rows and the table's
   rhythm would depend on which packages happen to be bespoke. */
.pkg-id-line {
    display: flex;
    align-items: center;
    gap: 7px;
    min-width: 0;
}

.pkg-name {
    font-size: var(--fs-md);
    font-weight: var(--fw-semibold);
    color: var(--text-primary);
}

.pkg-id-sub {
    margin-block-start: 2px;
    /* The tenant a custom package was cut for, or the tag a
       standard one carries. Capped rather than free: a note runs
       to a sentence and would take the width the figures need. */
    max-width: 320px;
    font-size: var(--fs-xs);
    line-height: 1.4;
    color: var(--text-tertiary);
}

/* Price, credits and rights are compared down their columns, so
   they are tabular mono or they are three stacked lists of
   unrelated numbers. */
.pkg-fig {
    font-family: var(--font-mono);
    font-variant-numeric: tabular-nums;
    font-size: var(--fs-sm);
    font-weight: var(--fw-medium);
    color: var(--text-primary);
}

/* A figure that is context rather than the answer — the global
   default beside an override, the count of rights beside a
   price. */
.pkg-fig-quiet {
    font-weight: var(--fw-regular);
    color: var(--text-tertiary);
}

/* "/mo", "/user/mo", "free". It belongs to the number, so it
   follows it on the same line at the size of a caption instead
   of becoming a column of its own. */
.pkg-unit {
    margin-inline-start: 4px;
    font-size: var(--fs-2xs);
    color: var(--text-tertiary);
}

/* ---------- The editor drawer ---------- */

/* The audience tile in the drawer header, on QDrawer's navy
   band rather than on white — so it is the audience's colour
   lifted out of the band, not the pale chip the table rows
   carry, which on navy would read as a hole. */
.pkg-dw-mark {
    display: grid;
    place-items: center;
    width: 38px;
    height: 38px;
    flex-shrink: 0;
    border-radius: var(--r-lg);
    background: rgba(255, 255, 255, 0.12);
    color: var(--text-inverse);
    box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.16);
}

.pkg-dw-mark-brand { color: var(--p-200); }
.pkg-dw-mark-gold  { color: var(--gold-300); }
.pkg-dw-mark-good  { color: var(--success-100); }

/* Money and credits are read against the figures in the tables
   below them, so the two fields that produce those numbers are
   set in the same face. */
.pkg-in-num {
    font-family: var(--font-mono);
    font-variant-numeric: tabular-nums;
}

/* Label at one end of the row, the switch at the other, and the
   word for what it is currently doing next to it — the shape
   every other on/off setting in the product has. */
.pkg-tog {
    display: flex;
    align-items: center;
    gap: 10px;
}

/* .lbl carries a bottom margin for the stacked case; on a row it
   only pushes the label off centre. */
.pkg-tog .lbl {
    flex: 1;
    min-width: 0;
    margin-block-end: 0;
}

/* A price table inside a .drw-sec panel. Bled to the panel's
   edges the way .drw-legend is, and for the same reason: inset
   by the panel's own 14px, the header row paints as a grey
   stripe with white either side of it, and the rules between
   rows stop short of the edge the panel already draws.

   The bottom corners are cut back because the table is the
   panel's last child and would otherwise square off the ring's
   radius. No block scrolling of its own: the drawer body is
   already a scrollport, and a second one nested inside it means
   a wheel over the table moves the wrong thing. */
.pkg-dw-tbl {
    width: calc(100% + 28px);
    margin: 0 -14px -14px;
    overflow-x: auto;
    border-end-start-radius: var(--r-lg);
    border-end-end-radius: var(--r-lg);
}

/* The 20px edge inset is for a table filling a card. Here the
   panel's own padding is the gutter, so the columns line up
   with the fields above them instead of starting 6px further
   in. */
.pkg-dw-tbl .tbl thead th:first-child,
.pkg-dw-tbl .tbl tbody td:first-child { padding-inline-start: 14px; }

.pkg-dw-tbl .tbl thead th:last-child,
.pkg-dw-tbl .tbl tbody td:last-child { padding-inline-end: 14px; }

/* Denser than a list page's rows: every row here is two lines of
   text and a field, and at 14px the two tables alone are most of
   a screen. */
.pkg-dw-tbl .tbl td { padding-block: 9px; }

.pkg-act {
    font-size: var(--fs-md);
    font-weight: var(--fw-medium);
    color: var(--text-primary);
}

/* The billable action's key. Mono because it is an identifier a
   platform operator matches against the price list, and quiet
   because the readable name is the line above it. */
.pkg-akey {
    margin-block-start: 1px;
    font-family: var(--font-mono);
    font-size: var(--fs-2xs);
    color: var(--text-tertiary);
}

/* The override field. Fixed width and right-aligned so a column
   of them is one edge, and mono so a typed value sits directly
   under the global default it is replacing. */
.pkg-in-cell {
    width: 128px;
    text-align: end;
    font-family: var(--font-mono);
    font-variant-numeric: tabular-nums;
}

/* Right-aligned text in a field pushes the placeholder to the
   same edge, where "inherit" reads as a value someone entered.
   It is the absence of one, so it sits at the start. */
.pkg-in-cell::placeholder {
    font-family: var(--font-sans);
    text-align: start;
}

@media (max-width: 700px) {
    /* Tighter, never uncapped. Lifting the cap here was the
       obvious-looking move and the wrong one: the sub-line is
       the widest thing in the cell, so with nothing holding it
       the longest note in the table sets the column's width and
       pushes every figure off the side of the scrollport. It is
       already ellipsised — this only decides where. */
    .pkg-id-sub { max-width: 200px; }
    .pkg-in-cell { width: 100px; }
}

/* ============================================================
   62. Super Admin: Feature flags (ff-*)

   /admin/feature-flags is a list over one drawer, and the
   drawer's chrome, sections, fields and action bar are all the
   shared ones — .drw-sec, .drw-legend, .drw-field, .drw-hint,
   .drw-foot, all in "20.". What is left here is the part that
   is only true of a flag.

   Which is, almost entirely, its value. A flag's value is one
   column in the database and three different things to read: a
   state, a share, a name. Rendered as the string it is stored
   as, "0" is indistinguishable from "off" and from a variant
   called nothing — so the value is drawn as what it means
   (.ff-val), and edited in the shape of its type (.ff-seg for a
   boolean, .ff-pct for a percentage, a mono field for a
   variant). The list and the drawer read it the same way,
   which is the point of them being here rather than in either.
   ============================================================ */

/* ---------- The value, wherever it is read ---------- */

.ff-val {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    font-size: var(--fs-xs);
    font-weight: var(--fw-medium);
}

/* Green for on, grey for off, and the dot rather than the word carrying it:
   down a column of forty rows the eye finds the lit dots without reading. */
.ff-val-on { color: var(--success-700); }
.ff-val-off { color: var(--text-tertiary); }

.ff-val-dot {
    inline-size: 7px;
    block-size: 7px;
    border-radius: var(--r-full);
    background: currentColor;
    flex-shrink: 0;
}

.ff-val-on .ff-val-dot { box-shadow: 0 0 0 3px var(--success-50); }
.ff-val-off .ff-val-dot { background: var(--n-300); }

.ff-val-pct { gap: 8px; }

/* A share is a quantity, and a quantity read off a bar is read faster than
   one read off two digits. The digits stay beside it - "7%" and "70%" are
   two pixels apart on a 56px track. */
.ff-val-meter {
    inline-size: 56px;
    block-size: 5px;
    border-radius: var(--r-full);
    background: var(--n-150);
    overflow: hidden;
    flex-shrink: 0;
}

.ff-val-meter > span {
    display: block;
    block-size: 100%;
    border-radius: var(--r-full);
    background: var(--p-700);
}

.ff-val-num {
    font-family: var(--font-mono);
    font-variant-numeric: tabular-nums;
    color: var(--text-secondary);
}

/* A variant is a name the code compares against, so it is set as code. */
.ff-val-var {
    padding: 2px 7px;
    border-radius: var(--r-sm);
    background: var(--surface-sunken);
    box-shadow: inset 0 0 0 1px var(--border-subtle);
    font-family: var(--font-mono);
    color: var(--text-secondary);
}

/* ---------- The drawer's own chrome ---------- */

/* Flat navy would be a navy square on the navy band; a wash of the band's
   own light is what lifts it, the same answer "20." gives the three marks
   it names. */
.ffd-mark {
    display: grid;
    place-items: center;
    inline-size: 34px;
    block-size: 34px;
    border-radius: var(--r-lg);
    background: rgba(255, 255, 255, 0.16);
    color: var(--text-inverse);
}

/* Editing a flag, the title is its key - the string the code checks, and
   the reason an engineer opened this. It is set as one. */
.ffd-edit .drw-title {
    font-family: var(--font-mono);
    font-size: var(--fs-lg);
    letter-spacing: 0;
}

.ffd-sub-sep { margin-inline: 6px; opacity: 0.6; }

/* QInput carries font-family:inherit in an inline style, which no class on
   the control can outrank; the family has to come from above it. */
.ff-monowrap { font-family: var(--font-mono); }

/* ---------- The key, once it is permanent ---------- */

.ff-keyline {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 8px 12px;
    border-radius: var(--r-md);
    background: var(--surface-sunken);
    box-shadow: inset 0 0 0 1px var(--border-subtle);
}

.ff-keyval {
    flex: 1;
    min-width: 0;
    font-family: var(--font-mono);
    font-size: var(--fs-md);
    color: var(--text-primary);
    overflow-wrap: anywhere;
}

.ff-lock {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    flex-shrink: 0;
    font-size: var(--fs-2xs);
    font-weight: var(--fw-bold);
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--text-tertiary);
}


/* ---------- Value: boolean ---------- */

/* Two states, both visible, the live one filled. A checkbox would say the
   same thing in half the space and none of the certainty: what is being
   set here is what every tenant gets, and it is worth being unambiguous. */
.ff-seg {
    display: inline-flex;
    /* .drw-field is a column flex container, and a column's default stretch
       pulls an inline-flex child out to the panel's full width - a two-state
       control reading as a full-width band of empty grey. */
    align-self: flex-start;
    gap: 3px;
    padding: 3px;
    border-radius: var(--r-md);
    background: var(--surface-sunken);
    box-shadow: inset 0 0 0 1px var(--border-subtle);
}

.ff-seg-b {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 6px;
    min-inline-size: 92px;
    padding: 6px 14px;
    border: none;
    border-radius: var(--r-sm);
    background: transparent;
    color: var(--text-tertiary);
    font-size: var(--fs-sm);
    font-weight: var(--fw-semibold);
    cursor: pointer;
    transition: background-color var(--dur-fast) var(--ease-out),
                color var(--dur-fast) var(--ease-out);
}

.ff-seg-b:hover:not(.is-on) { color: var(--text-primary); }

.ff-seg-on.is-on {
    background: var(--success-600);
    color: var(--text-inverse);
    box-shadow: var(--shadow-xs);
}

/* Grey, not red. Off is a resting state for a flag, not a failure. */
.ff-seg-off.is-on {
    background: var(--n-600);
    color: var(--text-inverse);
    box-shadow: var(--shadow-xs);
}

.ff-seg-b:focus-visible { outline: none; box-shadow: var(--shadow-focus); }

/* ---------- Value: percentage ---------- */

.ff-pct {
    display: flex;
    align-items: center;
    gap: 12px;
}

/* The slider is for the gesture - "about a tenth" - and the field beside it
   for the number someone was given. Both write the same value, so neither
   has to be the way in. */
.ff-pct-range {
    flex: 1;
    min-inline-size: 0;
    accent-color: var(--p-700);
}

.ff-pct-out {
    display: inline-flex;
    align-items: center;
    gap: 1px;
    flex-shrink: 0;
    padding-inline: 10px;
    block-size: var(--h-lg);
    border-radius: var(--r-md);
    background: var(--surface-raised);
    box-shadow: inset 0 0 0 1px var(--border-default);
}

.ff-pct-num {
    inline-size: 42px;
    padding: 0;
    border: none;
    background: transparent;
    outline: none;
    font-family: var(--font-mono);
    font-variant-numeric: tabular-nums;
    font-size: var(--fs-md);
    color: var(--text-primary);
    text-align: end;
}

.ff-pct-sign {
    font-size: var(--fs-sm);
    color: var(--text-tertiary);
}

/* ---------- Tenant overrides ---------- */

/* The one control that belongs on a section's band rather than under it:
   "add" acts on the group the band names. */
.ff-legend-act {
    margin-inline-start: auto;
    display: inline-flex;
    align-items: center;
    gap: 4px;
    padding: 3px 9px;
    border: none;
    border-radius: var(--r-full);
    background: var(--surface-raised);
    box-shadow: inset 0 0 0 1px var(--border-default);
    color: var(--text-brand);
    font-size: var(--fs-2xs);
    font-weight: var(--fw-bold);
    text-transform: uppercase;
    letter-spacing: 0.05em;
    cursor: pointer;
    transition: background-color var(--dur-fast) var(--ease-out);
}

.ff-legend-act:hover { background: var(--p-50); }
.ff-legend-act:focus-visible { outline: none; box-shadow: var(--shadow-focus); }

/* The section's own lede sits directly under the band, where .drw-sec's gap
   is meant for a field. */
.ff-ovr-lede { margin-block-start: -4px; }

.ff-ovr {
    display: flex;
    flex-direction: column;
    margin: 0;
    padding: 0;
    list-style: none;
}

.ff-ovr-row {
    display: flex;
    align-items: center;
    gap: 10px;
    padding-block: 9px;
    border-block-start: 1px solid var(--border-subtle);
}

.ff-ovr-row:first-child { border-block-start: none; padding-block-start: 0; }

.ff-ovr-main { flex: 1; min-width: 0; }

.ff-ovr-name {
    font-size: var(--fs-sm);
    font-weight: var(--fw-medium);
    color: var(--text-primary);
}

.ff-ovr-meta {
    margin-block-start: 2px;
    font-size: var(--fs-2xs);
    color: var(--text-tertiary);
}

.ff-ovr-x {
    flex-shrink: 0;
    display: grid;
    place-items: center;
    inline-size: var(--h-md);
    block-size: var(--h-md);
    border: none;
    border-radius: var(--r-md);
    background: transparent;
    color: var(--text-tertiary);
    cursor: pointer;
    transition: background-color var(--dur-fast) var(--ease-out),
                color var(--dur-fast) var(--ease-out);
}

.ff-ovr-x:hover { background: var(--danger-50); color: var(--danger-600); }
.ff-ovr-x:focus-visible { outline: none; box-shadow: var(--shadow-focus); }

/* Dashed, because the absence of overrides is a normal state of a flag and
   not a thing that failed to load. */
.ff-ovr-empty {
    margin: 0;
    padding: 14px;
    border: 1px dashed var(--border-default);
    border-radius: var(--r-md);
    font-size: var(--fs-sm);
    color: var(--text-tertiary);
    text-align: center;
}

/* The add form, sunken inside the panel: it is a form within the group
   rather than another panel beside it. */
.ff-add {
    display: flex;
    flex-direction: column;
    gap: 10px;
    padding: 12px;
    border-radius: var(--r-md);
    background: var(--surface-sunken);
    box-shadow: inset 0 0 0 1px var(--border-subtle);
}

.ff-add-acts {
    display: flex;
    justify-content: flex-end;
    gap: 6px;
}

@media (max-width: 640px) {
    /* Stretched, so the two states are thumb-width on a phone. */
    .ff-seg { display: flex; align-self: stretch; }
    .ff-seg-b { flex: 1; min-inline-size: 0; }
}

/* ---------- The list ----------

   The page's three bands and its table are the shared chrome (.pg-kpis,
   .tbar, .search-inline, .chip-tog, .tbl, .card-foot, all in "12."). What
   is here is the four things a row of flags needs that a row of anything
   else does not: an identity that is a code key with a name under it, a
   count of tenants that have been moved off the default, and the two
   states - archived, and nobody's name against it - that have to read as
   quieter than a live flag without disappearing. */

/* Wide enough for a dotted key, which is what people paste in here. It
   gives up its width before the facet chips wrap, not after. */
.ff-search {
    inline-size: 240px;
    flex-shrink: 1;
    min-inline-size: 150px;
}

.ff-owner { max-inline-size: 190px; }

/* select.input draws its chevron at the physical right and reserves the
   space for it there, so under rtl the glyph lands on the side the text
   starts from. Mirrored here rather than in "1.", which is the same
   answer .apd-stage-sel and .reg-select each reached for themselves. */
[dir="rtl"] .ff-owner {
    padding-inline: 10px 32px;
    background-position: left 8px center;
}

/* ---------- The flag cell ---------- */

/* Three columns became one: the key is the identity, and the name and the
   description are what it is for. Capped, or one long description sets the
   width of the table and pushes the value columns off the scrollport. */
.ff-id {
    display: flex;
    flex-direction: column;
    gap: 2px;
    min-width: 0;
    max-inline-size: 520px;
}

.ff-id-k {
    display: flex;
    align-items: center;
    gap: 8px;
    min-width: 0;
    font-size: var(--fs-sm);
    font-weight: var(--fw-medium);
    color: var(--text-primary);
}

/* The key is what clips when the cell runs out of room. The pill beside it
   already refuses to shrink - QPill carries flex-shrink:0 of its own. */
.ff-id-k > .mono { min-width: 0; }

/* Block, not flex: the ellipsis belongs to the line, and a flex row would
   put it on the container where it has nothing to clip. */
.ff-id-s {
    display: block;
    font-size: var(--fs-xs);
    line-height: var(--lh-snug);
    color: var(--text-tertiary);
}

.ff-id-s .dot-sep { margin-inline: 6px; }

/* ---------- Tenants off the default ---------- */

/* Amber rather than grey, and absent rather than zero in the rows that have
   none: the flags worth opening are the ones somebody has been excepted
   from, and a column of "0" hides the three that are not. */
.ff-ovr-n {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    padding: 2px 8px;
    border-radius: var(--r-full);
    background: var(--warning-50);
    box-shadow: inset 0 0 0 1px var(--warning-100);
    color: var(--warning-700);
    font-size: var(--fs-xs);
    font-weight: var(--fw-semibold);
}

/* ---------- Archived ---------- */

/* Still readable, still searchable, plainly not live. On the cells rather
   than the row so the hover tint underneath is not faded with it. */
.ff-row-off > td { opacity: 0.55; }
.ff-row-off:hover > td { opacity: 0.85; }

/* The no-match cell: the sentence, then the way out of it. */
.ff-none {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 10px;
}

@media (max-width: 700px) {
    /* Full width on its own line, above the chips: at this width the two
       cannot share a row without the search box becoming a stub. */
    .ff-search { inline-size: 100%; }
    .ff-owner { max-inline-size: none; flex: 1; }
    .ff-id { max-inline-size: 260px; }
}

/* ============================================================
   63. Super Admin: Compliance (cmp-*, cmpd-*)

   /admin/compliance is four registers on one page - DSR queue,
   DPA agreements, certifications, retention windows - and two
   of them now open a drawer: DsrCreateDrawer and
   CertificationDrawer. The chrome for both is the shared
   drawer's ("20."), the fields are "1."'s, and the tile pickers
   are the shared .pick ("12."), so what is left here is three
   things.

   A certification card that is a button, because opening the
   record is what a card is for; the drawer's own header mark;
   and the deadline that rides on the DSR drawer's action bar,
   which is the one piece of information the old modal never
   showed and the whole reason the form is worth reading before
   it is saved.
   ============================================================ */

/* ---------- The certification card ---------- */

/* It was a <div>; it is the way into the record, so it is a button. That
   means undoing what a button brings with it - a centred inline box with a
   border and a font of its own - and keeping .card's surface. */
.cmp-cert {
    display: block;
    inline-size: 100%;
    padding: 12px;
    border: 1px solid var(--border-subtle);
    font: inherit;
    color: inherit;
    text-align: start;
    cursor: pointer;
    transition: border-color var(--dur-fast) var(--ease-out),
                box-shadow var(--dur-fast) var(--ease-out);
}

.cmp-cert:hover {
    border-color: var(--border-strong);
    box-shadow: var(--shadow-sm);
}

.cmp-cert:focus-visible { outline: none; box-shadow: var(--shadow-focus); }

/* The chevron is the only thing on the card that says it opens. Quiet until
   the card is under the pointer, or the keyboard has reached it. */
.cmp-cert-go {
    flex-shrink: 0;
    margin-block-start: 2px;
    color: var(--n-300);
    transition: color var(--dur-fast) var(--ease-out),
                transform var(--dur-fast) var(--ease-out);
}

.cmp-cert:hover .cmp-cert-go,
.cmp-cert:focus-visible .cmp-cert-go {
    color: var(--p-700);
    transform: translateX(2px);
}

[dir="rtl"] .cmp-cert-go { transform: scaleX(-1); }
[dir="rtl"] .cmp-cert:hover .cmp-cert-go,
[dir="rtl"] .cmp-cert:focus-visible .cmp-cert-go { transform: scaleX(-1) translateX(2px); }

/* ---------- The two drawers ---------- */

/* Flat navy would be a navy square on the navy band; a wash of the band's
   own light is what lifts it, the same answer "20." gives the marks it
   names. */
.cmpd-mark {
    display: grid;
    place-items: center;
    inline-size: 34px;
    block-size: 34px;
    border-radius: var(--r-lg);
    background: rgba(255, 255, 255, 0.16);
    color: var(--text-inverse);
}

/* The statutory deadline, on the bar with the button that starts it. Amber
   rather than grey: it is a commitment being made, not a caption. */
.cmpd-due {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 4px 10px;
    border-radius: var(--r-full);
    background: var(--warning-50);
    box-shadow: inset 0 0 0 1px var(--warning-100);
    color: var(--warning-700);
    font-size: var(--fs-xs);
    font-weight: var(--fw-semibold);
    white-space: nowrap;
}

.cmpd-due svg { flex-shrink: 0; }

/* A date field is the one control .input sizes wrong: the picker glyph is
   drawn inside the box by the engine, and without room for it the year digit
   sits under the icon. */
.cmpd input[type="date"] {
    inline-size: 100%;
    block-size: var(--h-lg);
}

@media (max-width: 640px) {
    /* On its own row above the buttons rather than squeezed beside them -
       .drw-foot already wraps, so this only has to stop reserving the slack. */
    .cmpd-due { margin-inline-end: 0; }
}

/* ============================================================
   63. Super Admin: Pipeline & taxonomy (tax-*)

   /admin/taxonomy is six lists behind one tab row, and every
   change to any of them is now made in a drawer beside the list
   rather than in a centred box over it. The drawer's chrome,
   sections, fields and action bar are the shared ones - .drw-*,
   all in "20." - so what is left here is the part that is only
   true of a taxonomy.

   Which is, mostly, that its rows are pairs: a code and the
   word a tenant reads, a stage's colour and its name. The code
   half is mono and quiet, the word half is the row's subject -
   .tax-name and .tax-val set that relationship once, and the
   table and the drawer read it the same way.

   The tab row is the shared .tabs primitive, marked up as a
   tablist rather than as six buttons in a row, and the page's
   one remaining piece of chrome - the line saying stages are
   seeded - sits above the rows it is about (.tax-lede) rather
   than under a button that is no longer there to explain.
   ============================================================ */

/* ---------- The lists ---------- */

/* The one thing the stages tab has to say about itself, above the rows it
   is about. Inside the card, so it is part of the list rather than a note
   floating over the page. */
.tax-lede {
    margin: 0;
    /* The sheet's inset, from the same variable the table and .pg-head read,
       rather than the 16px that happens to equal it under AppShell's gutter
       and nowhere else. */
    padding: 12px calc(var(--pg-gx, 8px) + 8px) 0;
    font-size: var(--fs-xs);
    color: var(--text-tertiary);
}

.tax-err {
    margin-block-end: 12px;
    font-size: var(--fs-md);
    color: var(--danger-700);
}

.tax-tbl { min-width: 560px; }

/* A stage's name, with the colour it is drawn in on the board. The swatch
   was a column of its own holding nothing else, which spent a whole column
   of a narrow table on 14 pixels. */
.tax-name {
    display: flex;
    align-items: center;
    gap: 8px;
    min-width: 0;
    font-weight: var(--fw-medium);
}

.tax-dot {
    flex-shrink: 0;
    inline-size: 10px;
    block-size: 10px;
    border-radius: var(--r-full);
    background: var(--tax-hue, var(--p-600));
    box-shadow: inset 0 0 0 1px rgba(10, 21, 53, 0.18);
}

/* The translated label - the row's subject, beside a code that is not. */
.tax-val {
    display: block;
    font-weight: var(--fw-medium);
    color: var(--text-primary);
}

/* ---------- The drawer's header ---------- */

.tax-mark {
    display: grid;
    place-items: center;
    inline-size: 34px;
    block-size: 34px;
    border-radius: var(--r-lg);
    background: rgba(255, 255, 255, 0.16);
    color: var(--text-inverse);
}

/* A stage's tile is the stage's colour: on the board that is what names it
   before its name is read. */
.tax-mark-hue { background: var(--tax-hue, var(--p-600)); }

.tax-sub-sep { margin-inline: 6px; opacity: 0.6; }

/* .drw-head already colours a code on the band; this only sets the family. */
.tax-sub-code {
    font-family: var(--font-mono);
    font-size: var(--fs-xs);
}

/* ---------- Fields ---------- */

/* QInput carries font-family:inherit in an inline style, which no class on
   the control can outrank; the family has to come from above it. */
.tax-monowrap { font-family: var(--font-mono); }

/* A value the form shows but cannot change - the type an item belongs to.
   The same line the flag drawer gives a locked key. */
.tax-lockline {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 8px 12px;
    border-radius: var(--r-md);
    background: var(--surface-sunken);
    box-shadow: inset 0 0 0 1px var(--border-subtle);
}

.tax-lockval {
    flex: 1;
    min-width: 0;
    font-family: var(--font-mono);
    font-size: var(--fs-md);
    color: var(--text-primary);
    overflow-wrap: anywhere;
}

.tax-lock {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    flex-shrink: 0;
    font-size: var(--fs-2xs);
    font-weight: var(--fw-bold);
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--text-tertiary);
}

/* A section that is one sentence rather than a field: the note sits where
   a field would, so it drops the top margin .drw-hint carries. */
.tax-reach { margin-block-start: 0; }

/* ---------- The colour, picked two ways ---------- */

.tax-hue {
    display: flex;
    align-items: center;
    gap: 10px;
}

/* The native picker, stripped back to the swatch it draws. The vendor
   pseudo-elements are the only way to reach the inside of the control;
   without them it renders as a bevelled button with a border the sheet
   cannot otherwise remove. */
.tax-hue-dot {
    flex-shrink: 0;
    inline-size: 38px;
    block-size: 38px;
    padding: 0;
    border: none;
    border-radius: var(--r-md);
    background: none;
    box-shadow: inset 0 0 0 1px var(--border-default);
    cursor: pointer;
    -webkit-appearance: none;
    appearance: none;
}

.tax-hue-dot::-webkit-color-swatch-wrapper { padding: 0; }
.tax-hue-dot::-webkit-color-swatch { border: none; border-radius: var(--r-md); }
.tax-hue-dot::-moz-color-swatch { border: none; border-radius: var(--r-md); }

.tax-hue-dot:focus-visible { outline: none; box-shadow: var(--shadow-focus); }

/* The hex the swatch wrote, and the one a brand sheet is pasted into. Its
   height is the swatch's rather than a QInput's 36px: side by side, two
   controls two pixels apart read as one of them being slightly wrong. */
.tax-hue-hex {
    flex: 1;
    min-width: 0;
    block-size: 38px;
    padding: 0 12px;
    border: 1px solid var(--border-default);
    border-radius: var(--r-md);
    background: var(--surface-raised);
    color: var(--text-primary);
    font-family: var(--font-mono);
    font-size: var(--fs-md);
}

.tax-hue-hex:focus-visible {
    outline: none;
    border-color: var(--p-500);
    box-shadow: var(--shadow-focus);
}

.tax-hue-hex.is-bad {
    border-color: var(--danger-500);
    background: var(--danger-50);
}

/* ---------- What the console may only read ---------- */

.tax-facts {
    display: flex;
    flex-direction: column;
    gap: 1px;
    margin: 0;
    border-radius: var(--r-md);
    background: var(--border-subtle);
    overflow: hidden;
}

/* The gap above is the divider: each row paints over the parent, so the
   1px showing through is the line between two of them. */
.tax-fact {
    display: flex;
    align-items: baseline;
    gap: 12px;
    padding: 9px 12px;
    background: var(--surface-sunken);
}

.tax-fact dt {
    flex-shrink: 0;
    inline-size: 96px;
    font-size: var(--fs-xs);
    font-weight: var(--fw-semibold);
    color: var(--text-tertiary);
}

.tax-fact dd {
    display: flex;
    align-items: baseline;
    gap: 8px;
    flex-wrap: wrap;
    margin: 0;
    min-width: 0;
    font-size: var(--fs-sm);
    color: var(--text-primary);
}

/* What the flag means, after the flag itself: "default" is a word, "new
   applications land here" is what it does. */
.tax-fact-note {
    font-size: var(--fs-xs);
    color: var(--text-secondary);
}

@media (max-width: 640px) {
    /* The label column stops earning its 96px once the value has to wrap
       under it anyway. */
    .tax-fact { flex-direction: column; align-items: stretch; gap: 4px; }
    .tax-fact dt { inline-size: auto; }
}


/* ============================================================
   64. Super Admin: Templates library (tlib-*)

   /admin/templates now edits in a right-side drawer beside the
   list rather than a centred box over it, so the chrome, the
   fields and the action bar are the shared .drw-* set in "20."
   and QDrawer. What is left here is the two controls that are
   not prose: the body every tenant inherits, and the tenant ID
   that decides whether the template is a system one at all.
   Both are read character by character, so both are mono.
   ============================================================ */

/* Taller than textarea.input's 80px: the body is a whole email or
   offer letter, and a field that shows four lines of one makes the
   drawer's height pointless. */
.tlib-code {
    min-height: 260px;
    font-family: var(--font-mono);
    font-size: var(--fs-sm);
}

.tlib-mono {
    font-family: var(--font-mono);
    font-size: var(--fs-sm);
}

/* A template's text in each language it has been written in. The chips are the
   languages that exist; picking one opens its text below the original. */
.tlib-locales {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
}

.tlib-loc {
    display: flex;
    align-items: center;
    gap: 2px;
    padding: 2px 4px 2px 10px;
    border: 1px solid var(--border-subtle);
    border-radius: 999px;
    background: var(--surface-sunken);
}

.tlib-loc.on {
    border-color: var(--p-500);
    background: var(--p-50);
}

.tlib-loc-n {
    display: flex;
    align-items: center;
    gap: 7px;
    padding: 3px 2px;
    border: 0;
    background: none;
    cursor: pointer;
    font-size: var(--fs-sm);
    color: var(--text-secondary);
}

.tlib-loc-code {
    font-family: var(--font-mono);
    font-size: 10.5px;
    font-weight: 700;
    text-transform: uppercase;
    color: var(--text-tertiary);
}

.tlib-loc-edit {
    display: flex;
    flex-direction: column;
    gap: 10px;
    margin-top: 12px;
    padding-top: 12px;
    border-top: 1px dashed var(--border-subtle);
}

/* Two columns of the same text. Mono on the rows, because the merge tokens are
   the part read character by character and they have to line up between them. */
.tlib-diff {
    overflow: hidden;
    border: 1px solid var(--border-subtle);
    border-radius: var(--r-md);
    font-family: var(--font-mono);
    font-size: var(--fs-xs);
}

.tlib-diff-h,
.tlib-diff-r {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 1px;
    background: var(--border-subtle);
}

.tlib-diff-h {
    font-family: var(--font-sans);
    font-size: 11px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.04em;
}

.tlib-diff-h > span {
    padding: 6px 9px;
    background: var(--surface-sunken);
    color: var(--text-tertiary);
}

.tlib-diff-r > span {
    min-height: 18px;
    padding: 4px 9px;
    background: var(--n-0);
    white-space: pre-wrap;
    word-break: break-word;
}

/* A line with nothing opposite it: a paragraph the translation dropped, or one
   it invented. */
.tlib-diff-r.gap > span {
    background: var(--warning-50);
}


/* ============================================================
   65. Super Admin: Integrations catalog (integ-*)

   /admin/integrations kept eight centred boxes and one drawer of
   its own making. All nine are QDrawer now, so the chrome, the
   fields and the action bar are the shared .drw-* set in "20."
   and the page's [data-jfield] override - twenty lines of
   !important put there to give those boxes' inputs a border -
   went with them: .input has one.

   What is left is the four things a connector form says that a
   form in general does not: a value read back rather than typed,
   a setting that is on or off before anything else on the panel
   matters, a host with its port beside it, and the several
   fields that are literals a provider will reject if a character
   is wrong.
   ============================================================ */

/* A read-only fact in the detail panel - the category, the connection
   type. Not a control: it is the .lbl above it that pairs them. */
.integ-fact {
    font-size: var(--fs-md);
    color: var(--text-primary);
}

/* Slugs, hosts, tokens, callback URLs, payload templates. Every one of
   them is copied into somebody's provider console character by
   character, which is the whole argument for mono. */
.integ-mono {
    font-family: var(--font-mono);
    font-size: var(--fs-sm);
}

/* Taller than textarea.input's 80px: a .p8 key is five lines and a
   config schema is longer, and neither is readable four lines at a
   time. */
.integ-code {
    min-height: 180px;
    font-family: var(--font-mono);
    font-size: var(--fs-sm);
}

/* The one setting the rest of the panel is conditional on - "send real
   SMS", "show this on the candidate login". It leads the form rather
   than sitting among the fields, so it is a row with its own consequence
   written under it rather than a checkbox with a label after it. */
.integ-tgl {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 14px;
    padding: 10px 12px;
    border-radius: var(--r-md);
    background: var(--surface-sunken);
    box-shadow: inset 0 0 0 1px var(--border-subtle);
}

.integ-tgl-txt { min-width: 0; }

.integ-tgl-t {
    font-size: var(--fs-md);
    font-weight: var(--fw-semibold);
    color: var(--text-primary);
}

.integ-tgl-s {
    margin-block-start: 2px;
    font-size: var(--fs-xs);
    line-height: var(--lh-snug);
    color: var(--text-tertiary);
}

/* A host and its port. Not .drw-pair: the port is four characters and
   an auto-fit grid gives it half the row. It collapses with the pair
   rules so the two halves of one address never wrap apart from each
   other before the rest of the form does. */
.integ-host-port {
    display: grid;
    grid-template-columns: 1fr 96px;
    gap: 12px;
}

@media (max-width: 700px) {
    .integ-host-port { grid-template-columns: 1fr; }
}

/* A section's own lede, directly under the legend band, where .drw-sec's
   gap already supplies the space a hint's top margin would add. */
.integ-sec-lede { margin-block-start: 0; }

/* The slug is fixed once the connector exists. Nothing else in a drawer
   is disabled, so .input has no disabled state of its own outside the
   candidate action drawer's copy. */
.integ-drw .input:disabled {
    background: var(--n-50);
    color: var(--text-disabled);
    cursor: not-allowed;
}


/* ============================================================
   65b. Super Admin: Integrations catalog — the page body

   The drawers above were done first; the page they open was still
   the one written before this file had a page shape. It opened
   with a 22px word that scrolled away, a tab row built by
   concatenating style strings in a ternary, and two grids that
   drew their own cell borders from `i % 3` — correct at exactly
   three columns and wrong at every width where the column count
   was not three. The delivery tab's panel was outside <AppShell>
   altogether, so it drew under the shell rather than in it.

   The bands are the shared ones now: PageHeader, .tabs-band,
   .pg-kpis, .tbar, .chip-tog, QCard, .card-foot. What is below is
   only what this page has and no other:

     .integ-find    the search box's share of the toolbar
     .integ-grid    the catalog, auto-fit
     .integ-card    one connector — a tile that lifts, opened by a
                    real button rather than a div with a handler
     .integ-chs     the sign-in / messaging channel cards
     .integ-cb      the callback URL, read and copied
     .integ-chain   the passcode fallback order, as an ordered list
     .integ-rows    the same rows without the ranking, for email
   ============================================================ */

/* ---------- Toolbar ---------- */

/* The search is the toolbar's widest control; below 720px it takes the
   row to itself rather than squeezing the status chips onto a second
   line one at a time. */
.integ-find { flex: 0 1 300px; min-width: 200px; }

/* The categories are open-ended, so they stay a select — a chip row of
   them wraps to three lines on a seeded catalog. */
.integ-cat { width: auto; min-width: 160px; }

/* ---------- The catalog ---------- */

/* auto-fit, not repeat(3). The grid sits beside a 244px sidebar and has
   to survive a laptop; 260px is where a connector's name stops sharing
   its line with the status pill. */
.integ-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
    gap: 12px;
    padding: 16px;
}

/* Sunken against the card it sits in — a raised tile on a raised card is
   a card inside a card, which is what .tile settled for the same reason.
   The lift is the hover: the tile comes up to white with a shadow, which
   is the whole affordance, and it is why a card no longer needs a
   `cursor: pointer` declared on a div to say it can be opened. */
.integ-card {
    position: relative;
    display: flex;
    flex-direction: column;
    gap: 10px;
    min-width: 0;
    /* Three extra pixels at the start: the lifecycle rail lives in them. */
    padding: 14px 14px 14px 17px;
    border: 1px solid var(--border-subtle);
    border-radius: var(--r-lg);
    background: var(--n-25);
    transition: background-color var(--dur-fast) var(--ease-out),
                border-color var(--dur-fast) var(--ease-out),
                box-shadow var(--dur-fast) var(--ease-out),
                transform var(--dur-fast) var(--ease-out);
}

/* The lifecycle, on the edge. The pill in the corner already spells it, but a
   word has to be read one card at a time and a catalog is forty cards: the
   rail is what answers "is anything here deprecated" in one pass down the
   grid. Inset -1px so it sits over the border rather than beside it, and it
   stops 12px short of each corner, which is where the radius starts. */
.integ-card::before {
    content: "";
    position: absolute;
    inset-block: 12px;
    inset-inline-start: -1px;
    width: 3px;
    border-radius: var(--r-full);
    background: var(--n-200);
}

.integ-card.is-live::before       { background: var(--success-500); }
.integ-card.is-beta::before       { background: var(--gold-400); }
.integ-card.is-deprecated::before { background: var(--danger-500); }

/* The lift is the affordance - it is what says the tile opens, in place of
   the `cursor: pointer` a div used to carry. A pixel of travel and a shadow
   one step deeper: enough to read as "this came forward", not enough to
   shuffle the row it sits in. */
.integ-card:hover {
    background: var(--surface-raised);
    border-color: var(--border-strong);
    box-shadow: var(--shadow-md);
    transform: translateY(-1px);
}

/* A keyboard gets the same card, not just a ring on it. Without this the
   focused tile stays flat while a hovered one comes forward, so tabbing
   through the grid reads as less than pointing at it. */
.integ-card:has(.integ-card-open:focus-visible) {
    background: var(--surface-raised);
    border-color: var(--border-strong);
    box-shadow: var(--shadow-md);
}

@media (prefers-reduced-motion: reduce) {
    .integ-card { transition: none; }
    .integ-card:hover { transform: none; }
}

.integ-card-head {
    display: flex;
    align-items: flex-start;
    gap: 10px;
    min-width: 0;
}

/* A supplied logo is whatever the catalog row says it is, so it is
   constrained rather than trusted: BrandLogo beside it is exactly 36px,
   and a 200px PNG would otherwise set the row's height. */
.integ-card-logo {
    width: 36px;
    height: 36px;
    flex-shrink: 0;
    border-radius: var(--r-md);
    object-fit: cover;
    background: var(--n-100);
    /* A white or transparent PNG otherwise has no edge and floats on the
       tile. Inset, so the mark keeps its full 36px. */
    box-shadow: inset 0 0 0 1px var(--border-subtle);
}

.integ-card-id { flex: 1; min-width: 0; }

/* The name is the control, and its ::after covers the whole tile. That
   is what makes the card clickable without the card being a <button> —
   which it cannot be, since BrandLogo's fallback is a <div> and a button
   may only hold phrasing content. A keyboard gets one real button per
   card either way, which a div carrying @onclick never gave it. */
.integ-card-open {
    display: block;
    max-width: 100%;
    padding: 0;
    border: none;
    background: none;
    font-size: var(--fs-md);
    font-weight: var(--fw-semibold);
    letter-spacing: -0.005em;
    line-height: 1.3;
    color: var(--text-primary);
    text-align: start;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.integ-card-open::after {
    content: '';
    position: absolute;
    inset: 0;
    border-radius: var(--r-lg);
}

/* The ring goes on the overlay, so it draws around the tile rather than
   around the eight characters of the name. */
.integ-card-open:focus-visible { outline: none; }
.integ-card-open:focus-visible::after { box-shadow: var(--shadow-focus); }

/* Mono, because a slug is an identifier — read character by character
   rather than as a word. */
.integ-card-slug {
    margin: 2px 0 0;
    font-family: var(--font-mono);
    font-size: var(--fs-2xs);
    color: var(--text-tertiary);
}

/* Two lines then an ellipsis (.clamp2), and a fixed two-line box: without
   the min-height a card whose description is one line is 18px shorter
   than the one beside it and the footers stop lining up across the row. */
.integ-card-desc {
    margin: 0;
    min-height: calc(2 * 1.45em);
    font-size: var(--fs-sm);
    line-height: 1.45;
    color: var(--text-secondary);
}

/* Pushed to the bottom edge so the footers align across a row of cards
   whose descriptions wrap differently. */
.integ-card-foot {
    display: flex;
    align-items: center;
    gap: 8px;
    flex-wrap: wrap;
    margin-block-start: auto;
    padding-block-start: 10px;
    border-block-start: 1px solid var(--border-subtle);
    font-size: var(--fs-2xs);
    color: var(--text-tertiary);
}

.integ-card-cat {
    font-weight: var(--fw-semibold);
    color: var(--text-secondary);
}

.integ-card-type {
    padding: 1px 6px;
    border-radius: var(--r-full);
    background: var(--n-100);
    font-family: var(--font-mono);
    color: var(--text-secondary);
}

/* The count of tenants on this connector, at the far end where the eye
   scans a column of numbers rather than reading each row. */
.integ-card-n {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    margin-inline-start: auto;
    font-family: var(--font-mono);
    font-variant-numeric: tabular-nums;
}

/* ---------- Channel cards (sign-in, messaging) ---------- */

/* Wider than the catalog's cells: these hold a full callback URL, and a
   URL that has to be copied exactly is not helped by being wrapped in a
   240px column. They are raised, unlike .integ-card, because they sit on
   the page rather than inside a card. */
.integ-chs {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(340px, 1fr));
    gap: 12px;
}

.integ-ch {
    position: relative;
    display: flex;
    flex-direction: column;
    gap: 12px;
    min-width: 0;
    padding: 16px 16px 16px 19px;
    border: 1px solid var(--border-default);
    border-radius: var(--r-lg);
    background: var(--surface-raised);
    box-shadow: var(--shadow-xs);
    transition: border-color var(--dur-fast) var(--ease-out),
                box-shadow var(--dur-fast) var(--ease-out);
}

/* The catalog tile's rail, on the three states the delivery chain already
   paints: live, configured-but-off, never set up. Gold is the colour the
   header chip counts in - "3 need setup" up there and three gold edges down
   here are the same three cards. */
.integ-ch::before {
    content: "";
    position: absolute;
    inset-block: 12px;
    inset-inline-start: -1px;
    width: 3px;
    border-radius: var(--r-full);
    background: var(--n-200);
}

.integ-ch.is-live::before  { background: var(--success-500); }
.integ-ch.is-off::before   { background: var(--n-300); }
.integ-ch.is-unset::before { background: var(--gold-400); }

.integ-ch:hover {
    border-color: var(--border-strong);
    box-shadow: var(--shadow-sm);
}

/* Colour on the mark means a candidate can use this channel right now. One
   that is switched off, or was never finished, keeps its shape and loses its
   brand - which is the literal truth of the setting: it is not on the login
   screen. The pill still says which of the two it is, because grey cannot. */
.integ-ch:not(.is-live) .integ-ch-head > :first-child {
    filter: grayscale(1);
    opacity: 0.6;
}

.integ-ch-head {
    display: flex;
    align-items: center;
    gap: 12px;
    min-width: 0;
}

.integ-ch-id { flex: 1; min-width: 0; }

.integ-ch-name {
    margin: 0;
    font-size: var(--fs-lg);
    font-weight: var(--fw-semibold);
    letter-spacing: -0.01em;
    color: var(--text-primary);
}

.integ-ch-meta {
    margin: 2px 0 0;
    font-family: var(--font-mono);
    font-size: var(--fs-2xs);
    color: var(--text-tertiary);
}

/* The messaging card's one line of state, where the sign-in card carries
   its callback URL. Same slot, so the two grids keep one card height. */
.integ-ch-line {
    margin: 0;
    font-size: var(--fs-sm);
    line-height: 1.45;
    color: var(--text-secondary);
}

.integ-ch-foot { margin-block-start: auto; }

/* ---------- The callback URL ----------
   The one value on a sign-in card that is read rather than typed, and the
   only reason to open this page twice. Sunken, so it reads as output. */
.integ-cb { min-width: 0; }

.integ-cb-row {
    display: flex;
    align-items: center;
    gap: 6px;
    min-width: 0;
    padding: 5px 6px 5px 10px;
    border: 1px solid var(--border-subtle);
    border-radius: var(--r-md);
    background: var(--surface-sunken);
}

.integ-cb-val {
    flex: 1;
    min-width: 0;
    font-family: var(--font-mono);
    font-size: var(--fs-2xs);
    color: var(--text-secondary);
}

/* jorkCopy swaps the button's textContent to confirm, so the button has
   to keep its width across "Copy" and "Copied" or the row jumps. */
.integ-cb-row .btn { flex-shrink: 0; min-width: 58px; justify-content: center; }

/* ---------- The passcode chain ----------
   Twilio, the HTTP gateway and the dev sink are not three settings of
   equal standing: they are one ordered fallback, and which of them
   actually sends a code is the question the tab exists to answer. Three
   identical unranked boxes never answered it.

   So it is an <ol>, and the rank is drawn: a numbered rail down the left
   with the steps hung off it. */
.integ-chain,
.integ-rows {
    margin: 0;
    padding: 0;
    list-style: none;
}

.integ-step,
.integ-row {
    position: relative;
    display: flex;
    align-items: flex-start;
    gap: 12px;
    padding: 14px 16px;
}

/* The rail the numbers hang off. Without it the three circles are three
   unrelated badges that happen to read 1, 2, 3; with it they are one ordered
   fallback, which is the only thing this tab is trying to say. It runs from
   under one node to the next, centred on the 24px circle - 16px of row
   padding plus 11px. */
.integ-step:not(:last-child)::before {
    content: "";
    position: absolute;
    inset-inline-start: 27px;
    inset-block: 43px -14px;
    width: 2px;
    border-radius: var(--r-full);
    background: var(--border-default);
}

.integ-step + .integ-step,
.integ-row + .integ-row { border-block-start: 1px solid var(--border-subtle); }

/* The rank. A circle rather than a bare digit because the three of them
   have to read as one sequence from across the page, and it carries the
   step's state as its colour — the reader is looking for which one is
   green, not for which one is first. */
.integ-step-n {
    display: grid;
    place-items: center;
    width: 24px;
    height: 24px;
    flex-shrink: 0;
    margin-block-start: 1px;
    border-radius: var(--r-full);
    background: var(--n-100);
    font-family: var(--font-mono);
    font-size: var(--fs-2xs);
    font-weight: var(--fw-bold);
    color: var(--text-tertiary);
}

.integ-step-txt,
.integ-row-txt {
    flex: 1;
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: 3px;
}

.integ-step-t {
    display: flex;
    align-items: center;
    gap: 8px;
    flex-wrap: wrap;
    font-size: var(--fs-md);
    font-weight: var(--fw-semibold);
    color: var(--text-primary);
}

/* The line that says what the setting currently is — a from-number, a
   URL, a host and port. Wrapped rather than truncated: an endpoint the
   reader is checking is one they have to be able to read all of. */
.integ-step-s {
    font-size: var(--fs-xs);
    line-height: 1.45;
    color: var(--text-tertiary);
    overflow-wrap: anywhere;
}

/* Three states, one wash. Live is the only one that gets colour on the
   row itself; the other two are told apart by their pill, because a page
   where every row is tinted has no signal left for the one that matters. */
/* Solid, not a wash. The question this tab answers is which channel actually
   sends a passcode, and one filled node in a column of three is that answer
   read from across the room. The halo lifts it off the rail behind it. */
.integ-step.is-live .integ-step-n {
    background: var(--success-600);
    color: var(--n-0);
    box-shadow: 0 0 0 3px var(--success-50);
}

.integ-step.is-off .integ-step-n {
    background: var(--n-100);
    color: var(--text-secondary);
}

.integ-step.is-unset .integ-step-t,
.integ-row.is-unset .integ-step-t { color: var(--text-secondary); }

.integ-row.is-live .integ-step-t { color: var(--text-primary); }

/* The email rows have no rank to hang off - outbound and inbound are one
   mailbox, not a first and a second choice - so they get a dot where the
   chain has a number. It occupies the same 24px column (8px wide, 8px of
   margin each side) so the two cards start their text on one line, and the
   column is not a bare indent: there is something in it, which is what the
   indent alone could not manage. */
.integ-row { padding-inline-start: 16px; }

.integ-row::before {
    content: "";
    width: 8px;
    height: 8px;
    flex-shrink: 0;
    margin: 8px;
    border-radius: var(--r-full);
    background: var(--n-300);
}

.integ-row.is-live::before {
    background: var(--success-600);
    box-shadow: 0 0 0 3px var(--success-50);
}

.integ-row.is-unset::before { background: var(--gold-400); }

/* ---------- The development sink's table ---------- */

/* The code is the thing being read off the screen and typed into a
   phone, so it is the one cell set larger than its row. */
.integ-code-cell {
    font-family: var(--font-mono);
    font-size: var(--fs-md);
    font-weight: var(--fw-bold);
    letter-spacing: 0.08em;
    color: var(--text-primary);
}

/* ---------- The detail panel's tenant table ---------- */

/* Three cells that were carrying their own style attributes - a weight, a
   padding and an 11px literal - inside a table whose own rules already set
   all three, and one empty row hand-styled beside .tbl-empty. What is left
   here is the only thing particular to them: a sync time is a timestamp, so
   it is mono, quiet, and does not wrap in a 560px panel. */
.integ-conn-tenant { font-weight: var(--fw-medium); }

.integ-conn-sync {
    font-family: var(--font-mono);
    font-size: var(--fs-2xs);
    color: var(--text-tertiary);
    white-space: nowrap;
}

/* ---------- Narrow ---------- */

@media (max-width: 720px) {
    /* The search stops sharing a line with the chips before either is
       squeezed to nothing, and the category select takes the width it
       is left with rather than shrinking to its longest option. */
    .integ-find { flex: 1 1 100%; }
    .integ-cat { width: 100%; }
    .integ-grid { padding: 12px; gap: 10px; }
}


/* ============================================================
   66. Super Admin: Profile schema (psch-*)

   /admin/schema defines the fields a candidate, a job and an
   application record carry, and every one of them was added and
   edited in a centred 640px box written inline on the page - the
   same overlay the taxonomy tabs had before they moved into a
   drawer.

   A field is the widest thing this console defines: a slug, a
   type, a list of options for two of those types, five flags
   that decide what the platform may do with the value, and a
   position. The box could only ask for them as a flat list, so
   the five flags were five bare checkboxes wrapped onto one line
   with nothing said about what any of them meant, and the type
   was a select whose six options differ by a sentence each.

   The form is a drawer now (QDrawer, "20."), so the chrome,
   sections, hints and action bar are all shared. What is here is
   the part that is only true of a schema field:

     .psch-opts      the five flags, a line of prose each
     .psch-sum       what the save will write, on the action bar

   The locked lines and the type tiles are .drw-lockline and
   .drw-pick, both shared from "20." - the incident drawers ask
   for the same two shapes.
   ============================================================ */

/* ---------- The drawer's header ---------- */

.psch-mark {
    display: grid;
    place-items: center;
    inline-size: 34px;
    block-size: 34px;
    border-radius: var(--r-lg);
    background: rgba(255, 255, 255, 0.16);
    color: var(--text-inverse);
}

.psch-sub-sep { margin-inline: 6px; opacity: 0.6; }

/* .drw-head already colours a code on the band; this only sets the family. */
.psch-sub-code {
    font-family: var(--font-mono);
    font-size: var(--fs-xs);
}


/* The options editor sits under the tiles rather than beside them, and only
   for the two types that take a list. */
.psch-opt-json { margin-block-start: 4px; }

/* A number with a lane of its own: a display order is two digits, and a
   full-width input for two digits reads as a field that wants a sentence. */
.psch-order { inline-size: 120px; }

/* ---------- The six field types ---------- */
/* ---------- The five flags ---------- */

/* One stack with hairlines between, rather than five loose rows: they are
   read down the column as a set - what is stored, what is indexed, what
   leaves for a model - and a set with gaps in it reads as five decisions
   that have nothing to do with each other. */
.psch-opts {
    display: flex;
    flex-direction: column;
    gap: 1px;
    border-radius: var(--r-md);
    background: var(--border-subtle);
    overflow: hidden;
}

/* The gap above is the divider: each row paints over the parent, so the
   1px showing through is the line between two of them. */
.psch-opt {
    display: flex;
    align-items: flex-start;
    gap: 11px;
    padding: 11px 12px;
    background: var(--surface-raised);
}

/* The switch sits on the first line of the text beside it rather than in
   the middle of a two-line block. */
.psch-opt > .switch { margin-block-start: 1px; flex-shrink: 0; }

.psch-opt-txt { min-width: 0; }

.psch-opt-t {
    display: flex;
    align-items: center;
    gap: 7px;
    font-size: var(--fs-md);
    font-weight: var(--fw-medium);
    color: var(--text-primary);
}

.psch-opt-d {
    margin-block-start: 2px;
    font-size: var(--fs-xs);
    line-height: var(--lh-snug);
    color: var(--text-secondary);
}

/* Personal data is the one flag on the list with a consequence outside this
   screen - masking in exports, and what may be sent to a model - so the row
   is marked while it is on rather than only carrying a pill. A rule at the
   edge rather than a wash over the row: the pill it carries is already
   --warning-50, and on a tinted row it would have nothing to sit against. */
.psch-opt.is-pii { box-shadow: inset 3px 0 0 var(--warning-500); }

/* ---------- What the save will write ---------- */

/* On the action bar, at the end of a form whose slug field is four sections
   above the button. .drw-foot-lead already pushes it to the far end. */
.psch-sum {
    display: flex;
    align-items: center;
    gap: 8px;
    min-width: 0;
}

.psch-sum-slug {
    font-family: var(--font-mono);
    font-size: var(--fs-sm);
    font-weight: var(--fw-semibold);
    color: var(--text-primary);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.psch-sum-slug.is-empty { color: var(--text-tertiary); font-weight: var(--fw-medium); }

.psch-sum-type {
    flex-shrink: 0;
    padding: 2px 7px;
    border-radius: var(--r-full);
    background: var(--n-100);
    font-size: var(--fs-2xs);
    font-weight: var(--fw-semibold);
    color: var(--text-secondary);
}

@media (max-width: 640px) {
    /* The bar wraps at this width and the buttons stretch across it, so the
       summary stops competing for the row and sits above them. */
    .psch-sum { flex-basis: 100%; }
}

/* ---------- Under the tab band ---------- */

/* The row the page switches on is a .tabs-band: full-bleed and pinned, with
   its own hairline as the bottom edge. What follows is the switched panel, and
   it needs the gap the band does not carry - the band closes on its hairline
   so that the line stays the top edge of the panel rather than a rule with a
   margin hanging off it. One class for all three states the page shows here
   (loading, error, the field list), so the panel starts at the same place
   whichever of them is on screen and nothing shifts when the load lands. */
.psch-body { margin-block-start: 14px; }


/* ============================================================
   67. Super Admin: Incidents (inc-*)

   /admin/incidents had two overlays, both a card floating in a
   centred scrim: the declare form, and the incident itself.

   Neither shape fitted what it held. The declare form asked for
   a severity from a bare select of P1..P4, which is the one
   answer on the form that decides which rota is woken, and the
   difference between a P1 and a P2 is a sentence about blast
   radius rather than a digit. The detail card was worse: it is
   not a form that is filled in and saved but a record being read
   while three separate things are posted against it - an update,
   an impacted tenant, a resolution - inside a box capped at the
   viewport, over a scrim that hid the queue the reader had just
   come from.

   Both are drawers now (QDrawer, "20."), so the chrome, the
   sections, the tiles and the action bar are shared. What is
   here is the part that is only true of an incident:

     .inc-mark    the severity, as the drawer's header tile
     .inc-facts   where it stands, as a definition list
     .inc-tl      the chronology of updates
     .inc-form    a writer sitting under the list it appends to
     .inc-res     closing it
   ============================================================ */

/* ---------- The drawer's header ---------- */

.inc-mark {
    display: grid;
    place-items: center;
    inline-size: 34px;
    block-size: 34px;
    border-radius: var(--r-lg);
    background: rgba(255, 255, 255, 0.16);
    color: var(--text-inverse);
}

/* On the navy band the tile carries the severity as a word, so the drawer
   says P1 before its title is read. The tones are the list's own. */
.inc-mark-sev {
    font-family: var(--font-mono);
    font-size: var(--fs-sm);
    font-weight: var(--fw-bold);
    letter-spacing: 0.02em;
}

.inc-mark-sev.t-bad  { background: var(--danger-600); }
.inc-mark-sev.t-warn { background: var(--warning-600); }
.inc-mark-sev.t-gold { background: var(--gold-600); }

.inc-sub-sep { margin-inline: 6px; opacity: 0.6; }

/* .drw-head already colours a code on the band; this only sets the family. */
.inc-sub-code {
    font-family: var(--font-mono);
    font-size: var(--fs-xs);
}

/* ---------- The severity tiles ----------

   The tiles themselves are .drw-pick from "20."; only the mark is this
   page's, because a severity has no icon - the label is the thing. */
.inc-sev-n {
    font-family: var(--font-mono);
    font-size: var(--fs-2xs);
    font-weight: var(--fw-bold);
    letter-spacing: 0.02em;
}

/* Off, the badge carries the severity's own colour, which is what makes the
   four tiles scannable as a scale rather than four identical cards. Selected,
   .drw-pick-o.is-on takes it back to the brand so the chosen one reads as
   chosen rather than as the most alarming. */
.inc-sev-n.t-bad  { background: var(--danger-50);  color: var(--danger-700); }
.inc-sev-n.t-warn { background: var(--warning-50); color: var(--warning-700); }
.inc-sev-n.t-gold { background: var(--gold-50);    color: var(--gold-700); }

/* A stated value that is a sentence rather than an identifier: the notify
   line, which says why it is off. .drw-lockval sets mono for codes. */
.inc-lockword {
    font-family: inherit;
    font-size: var(--fs-sm);
    color: var(--text-secondary);
}

/* ---------- What the declare button will raise ---------- */

.inc-sum {
    display: flex;
    align-items: center;
    gap: 8px;
    min-width: 0;
}

.inc-sum-sev {
    flex-shrink: 0;
    padding: 2px 8px;
    border-radius: var(--r-full);
    background: var(--n-100);
    color: var(--text-secondary);
    font-family: var(--font-mono);
    font-size: var(--fs-2xs);
    font-weight: var(--fw-bold);
}

.inc-sum-sev.t-bad  { background: var(--danger-50);  color: var(--danger-700); }
.inc-sum-sev.t-warn { background: var(--warning-50); color: var(--warning-700); }
.inc-sum-sev.t-gold { background: var(--gold-50);    color: var(--gold-700); }

.inc-sum-w {
    font-size: var(--fs-sm);
    color: var(--text-secondary);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* ---------- Where it stands ---------- */

.inc-facts {
    display: flex;
    flex-direction: column;
    gap: 1px;
    margin: 0;
    border-radius: var(--r-md);
    background: var(--border-subtle);
    overflow: hidden;
}

/* The gap above is the divider: each row paints over the parent, so the
   1px showing through is the line between two of them. */
.inc-fact {
    display: flex;
    align-items: baseline;
    gap: 12px;
    padding: 8px 12px;
    background: var(--surface-sunken);
}

.inc-fact dt {
    flex-shrink: 0;
    inline-size: 96px;
    font-size: var(--fs-xs);
    font-weight: var(--fw-semibold);
    color: var(--text-tertiary);
}

.inc-fact dd {
    display: flex;
    align-items: baseline;
    gap: 8px;
    flex-wrap: wrap;
    margin: 0;
    min-width: 0;
    font-size: var(--fs-sm);
    color: var(--text-primary);
}

/* "12h 40m" and then what that means - that it is still running. */
.inc-fact-note {
    font-size: var(--fs-xs);
    color: var(--text-secondary);
}

@media (max-width: 640px) {
    /* The label column stops earning its 96px once the value has to wrap
       under it anyway. */
    .inc-fact { flex-direction: column; align-items: stretch; gap: 4px; }
    .inc-fact dt { inline-size: auto; }
}

.inc-desc-f { margin-block-start: 12px; }

.inc-desc {
    margin: 0;
    font-size: var(--fs-md);
    line-height: var(--lh-relaxed);
    color: var(--text-primary);
    white-space: pre-wrap;
}

/* Said once, wherever a list or a value is empty. */
.inc-empty {
    margin: 0;
    font-size: var(--fs-sm);
    color: var(--text-tertiary);
}

/* The affected systems as the names they are, rather than the JSON array the
   record stores them in. */
.inc-sys {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
}

.inc-sys-t {
    padding: 3px 8px;
    border-radius: var(--r-md);
    background: var(--n-100);
    font-family: var(--font-mono);
    font-size: var(--fs-xs);
    color: var(--text-primary);
}

/* ---------- The chronology ---------- */

.inc-tl {
    display: flex;
    flex-direction: column;
    margin: 0;
    padding: 0;
    list-style: none;
}

.inc-tl-i {
    position: relative;
    display: flex;
    gap: 10px;
    padding-block-end: 14px;
}

/* The rail between the dots, stopping at the last one rather than running
   off the end of the list. */
.inc-tl-i::before {
    content: '';
    position: absolute;
    inset-block: 14px 0;
    inset-inline-start: 4px;
    inline-size: 1px;
    background: var(--border-subtle);
}

.inc-tl-i:last-child { padding-block-end: 0; }
.inc-tl-i:last-child::before { content: none; }

.inc-tl-d {
    position: relative;
    inline-size: 9px;
    block-size: 9px;
    flex-shrink: 0;
    margin-block-start: 5px;
    border-radius: var(--r-full);
    background: var(--n-300);
}

/* An update that moved the incident is drawn in where it moved it to; one
   that only said something keeps the brand dot. */
.inc-tl-d.t-good  { background: var(--success-500); }
.inc-tl-d.t-warn  { background: var(--warning-500); }
.inc-tl-d.t-brand { background: var(--p-700); }

.inc-tl-b { flex: 1; min-width: 0; }

.inc-tl-t {
    display: flex;
    align-items: center;
    gap: 7px;
    flex-wrap: wrap;
    font-size: var(--fs-sm);
    font-weight: var(--fw-semibold);
    color: var(--text-primary);
}

.inc-tl-m {
    margin-block-start: 2px;
    font-size: var(--fs-sm);
    line-height: var(--lh-snug);
    color: var(--text-secondary);
    white-space: pre-wrap;
}

.inc-tl-w {
    margin-block-start: 3px;
    font-family: var(--font-mono);
    font-size: var(--fs-2xs);
    font-variant-numeric: tabular-nums;
    color: var(--text-tertiary);
}

/* ---------- Who it reached ---------- */

.inc-imps {
    display: flex;
    flex-direction: column;
    gap: 1px;
    margin: 0;
    padding: 0;
    list-style: none;
    border-radius: var(--r-md);
    background: var(--border-subtle);
    overflow: hidden;
}

.inc-imp {
    display: flex;
    align-items: baseline;
    gap: 12px;
    padding: 8px 12px;
    background: var(--surface-raised);
}

.inc-imp-t {
    flex-shrink: 0;
    inline-size: 150px;
    font-size: var(--fs-sm);
    font-weight: var(--fw-medium);
    color: var(--text-primary);
    overflow-wrap: anywhere;
}

.inc-imp-d {
    flex: 1;
    min-width: 0;
    font-size: var(--fs-sm);
    color: var(--text-secondary);
}

@media (max-width: 640px) {
    .inc-imp { flex-direction: column; align-items: stretch; gap: 3px; }
    .inc-imp-t { inline-size: auto; }
}

/* ---------- A writer, under the list it appends to ----------

   Not a section of its own: what is posted here is the next line of the
   chronology above it, so it sits inside that section, sunken, with the
   list's own gap between them. */
.inc-form {
    display: flex;
    flex-direction: column;
    gap: 8px;
    margin-block-start: 12px;
    padding: 10px;
    border-radius: var(--r-md);
    background: var(--surface-sunken);
    box-shadow: inset 0 0 0 1px var(--border-subtle);
}

.inc-form-h {
    font-size: var(--fs-2xs);
    font-weight: var(--fw-bold);
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--text-tertiary);
}

.inc-form-row {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 8px;
}

@media (max-width: 560px) {
    .inc-form-row { grid-template-columns: minmax(0, 1fr); }
}

.inc-form-act {
    display: flex;
    justify-content: flex-end;
}

/* ---------- Closing it ----------

   Green, because the panel is the one place on the screen where the
   incident stops. It is not a warning and not an alert - it is the
   outcome every other section is working towards. */
.inc-res {
    display: flex;
    flex-direction: column;
    gap: 6px;
    padding: 10px;
    border-radius: var(--r-md);
    background: var(--success-50);
    box-shadow: inset 0 0 0 1px var(--success-600);
}

.inc-res .drw-hint { margin-block-start: 0; color: var(--success-700); }

/* ============================================================
   68. Super Admin: Service prices (svp-*)

   /admin/service-prices is one long price list over the two
   globals it is denominated in, and almost all of it is shared
   chrome - .pg for the bands, .tbar for the filters, .tbl for
   the list, .fld-grid for the form, .set-bar for the commit,
   all in "12.", "18." and the toolbar block above.

   What is left here is the part that is only true of a price
   list: a band that names the category a run of rows belongs
   to, a rate cell that is a narrow numeric field with its unit
   under it, and an effective-charge cell whose three answers -
   a dollar figure, a multiplier, and "free" - have to be told
   apart at a glance down one column.
   ============================================================ */

/* ---------- The two globals ---------- */

/* A multiplier and a peg are read against the figures in the
   table below them, so the fields that produce those figures
   are set in the same face. Capped because both are four or
   five digits and a field the width of the grid track puts the
   caret a long way from the number. */
.svp-num {
    max-width: 200px;
    font-family: var(--font-mono);
    font-variant-numeric: tabular-nums;
}

/* The worked example under a field, rather than a second line of
   rule. Both of these numbers only become legible once something
   has been run through them: "2.0" means nothing, "a $2.00
   charge on a $1.00 call" is the whole behaviour. Navy rather
   than grey because it is the product answering, not a caption -
   the same distinction .lbl-brand makes. */
.svp-ex {
    display: flex;
    align-items: flex-start;
    gap: 6px;
    margin-block-start: 2px;
    font-size: var(--fs-xs);
    line-height: 1.45;
    color: var(--p-700);
}

.svp-ex > svg { flex-shrink: 0; margin-block-start: 2px; }

/* ---------- The toolbar ---------- */

/* Capped rather than free: .tbar is a flex row, and an uncapped
   search box takes every pixel the segmented control and the
   chips are not using, which on a wide screen is most of the
   band. */
.svp-srch { flex: 1 1 220px; max-width: 320px; }

/* The count riding a segment - "Charged 17". Same treatment as
   .chip-tog-n for the same reason: it is a quantity attached to
   a label, not a second label. */
.svp-seg-n {
    margin-inline-start: 6px;
    font-family: var(--font-mono);
    font-variant-numeric: tabular-nums;
    font-weight: var(--fw-medium);
    opacity: 0.7;
}

/* The category row takes the whole width of the toolbar rather
   than competing with the search box for the line. Nine chips
   and a field on one row is a row that wraps mid-chip at any
   width short of a desktop, and the chips are the half that
   should stay whole. */
.svp-cats { flex: 1 0 100%; }

/* ---------- The list ---------- */

/* The category band. The category was a column: the same word
   printed down thirty-four rows, taking width from the figures,
   and saying nothing about the group it names. As a band it is
   written once over the rows it owns and has room for the figure
   a column could not carry - how much of this group is actually
   being charged for.

   No top border on the first one: the header already draws the
   line under itself, and a second hairline 1px below it reads as
   a rendering fault rather than a divider. */
.svp-grp > td {
    padding: 9px 20px 8px;
    background: var(--surface-sunken);
    border-block-start: 1px solid var(--border-subtle);
}

.svp-tbl tbody tr:first-child > td { border-block-start: none; }

.svp-grp-in {
    display: flex;
    align-items: center;
    gap: 9px;
}

/* A band is not a row, so its mark is a step down from the one a
   list row carries - enough to hold the colour, not enough to
   set the band's height. */
.svp-grp .lst-mark {
    width: 24px;
    height: 24px;
    border-radius: var(--r-sm);
}

.svp-grp-t {
    font-size: var(--fs-sm);
    font-weight: var(--fw-semibold);
    letter-spacing: 0.02em;
    text-transform: uppercase;
    color: var(--text-secondary);
}

/* Pushed to the far end of the band, where the figures it
   summarises are. */
.svp-grp-n {
    margin-inline-start: auto;
    font-family: var(--font-mono);
    font-variant-numeric: tabular-nums;
    font-size: var(--fs-2xs);
    color: var(--text-tertiary);
}

/* Two lines per row and a field in two of the cells, so a list
   page's 14px would make thirty-four rows most of three screens. */
.svp-tbl td { padding-block: 9px; }

.svp-act {
    font-size: var(--fs-md);
    font-weight: var(--fw-medium);
    color: var(--text-primary);
}

/* The billable action's key. Mono because it is the identifier an
   operator matches against a package override or a usage line,
   and quiet because the readable name is directly above it. */
.svp-key {
    margin-block-start: 1px;
    font-family: var(--font-mono);
    font-size: var(--fs-2xs);
    color: var(--text-tertiary);
}

/* An action that is switched off is still on the list, still has
   a price, and charges nothing. It is not disabled - every field
   in the row stays editable, because pricing something before
   turning it on is the normal order - so the row is stated
   rather than greyed: the name loses its weight and the
   effective cell says so outright. */
.svp-row.is-off .svp-act {
    font-weight: var(--fw-regular);
    color: var(--text-secondary);
}

/* ---------- The rate cell ---------- */

/* Fixed width and right-aligned so a column of them is one edge
   rather than a ragged one, and mono so a typed figure sits
   directly under the one above it. */
.svp-in {
    width: 116px;
    text-align: end;
    font-family: var(--font-mono);
    font-variant-numeric: tabular-nums;
}

/* Right-aligned text in a field drags the placeholder to the same
   edge, where the global multiplier reads as a value somebody
   entered. It is the absence of one, so it sits at the start. */
.svp-in::placeholder {
    font-family: var(--font-sans);
    text-align: start;
    color: var(--text-tertiary);
}

/* "credits", "x provider cost". The unit belongs to the field, so
   it sits under it at caption size instead of taking a column of
   its own or being baked into the header for only half the rows. */
.svp-in-u {
    margin-block-start: 3px;
    font-size: var(--fs-2xs);
    color: var(--text-tertiary);
}

/* ---------- The effective-charge cell ---------- */

/* The answer column. Read down rather than across, so it is
   tabular mono at the weight of a figure that matters. */
.svp-eff {
    font-family: var(--font-mono);
    font-variant-numeric: tabular-nums;
    font-size: var(--fs-md);
    font-weight: var(--fw-medium);
    color: var(--text-primary);
}

/* "per use", "global", "custom" - what the figure above is a
   figure of. */
.svp-eff-u {
    margin-block-start: 2px;
    font-size: var(--fs-2xs);
    color: var(--text-tertiary);
}

/* Free is an answer, not a missing value, so it is a word set in
   the column's own face rather than an em dash. Quiet, because
   the rows that charge something are the ones being compared. */
.svp-free {
    font-family: var(--font-mono);
    font-size: var(--fs-sm);
    color: var(--text-tertiary);
}

/* Switched on, flat, priced at zero: charged, and charging
   nothing. The only cell on the page that is a fault rather than
   a value, so it is the only one carrying a warning colour -
   and it sits in the column an operator is already reading down
   for the figure it is standing in for. */
.svp-todo {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    font-size: var(--fs-sm);
    font-weight: var(--fw-semibold);
    color: var(--warning-700);
}

.svp-todo > svg { flex-shrink: 0; }

/* ---------- Narrow ---------- */

@media (max-width: 700px) {
    /* The band keeps the table's own gutter rather than the wider
       card inset, so it stays over the rows it names. */
    .svp-grp > td { padding-inline: 14px; }
    .svp-in { width: 92px; }
    .svp-srch { max-width: none; }

    /* The model pill is the column that goes on a phone. It is not
       information lost: the unit under the rate field is the same
       distinction said another way - "credits" is flat, "x provider
       cost" is metered - and dropping it brings the effective charge
       and the switch back inside the scrollport, which are the two
       things the page is read for. The band rows span the table, so
       they are matched on .svp-row rather than on the column. */
    .svp-tbl thead th:nth-child(2),
    .svp-row > td:nth-child(2) { display: none; }
}

/* ============================================================
   69. Super Admin: Wallets (wal-*)

   /admin/wallets is one row per tenant over the prepaid balance
   it holds, plus three drawers: the ledger, the offline-payment
   grant, and the package assignment.

   Almost all of it is shared chrome - .pg for the bands, .tbar
   for the filters, .tbl for the list, .drw-* for the drawers,
   .segmented and .note from the toolbar block - in "12.", "18."
   and "20." above.

   What is left here is the part that is only true of money held
   on account: a balance cell that has to be read as a level and
   not just a number, a runway cell whose whole job is to
   separate "six days" from "eight months" at a glance down a
   column, the conversion chain that turns a payment into
   credits, and a ledger whose rows are signed.
   ============================================================ */

/* ---------- The toolbar ---------- */

/* Capped rather than free: .tbar is a flex row, and an uncapped
   search box takes every pixel the segmented control is not
   using, which on a wide screen is most of the band. */
.wal-srch { flex: 1 1 220px; max-width: 320px; }

/* The count riding a segment - "Out of credit 3". Same treatment
   as .chip-tog-n for the same reason: a quantity attached to a
   label, not a second label. */
.wal-seg-n {
    margin-inline-start: 6px;
    font-family: var(--font-mono);
    font-variant-numeric: tabular-nums;
    font-weight: var(--fw-medium);
    opacity: 0.7;
}

/* ---------- The list ---------- */

/* Two lines in four of the seven cells, so a list page's default
   row height would make forty tenants three screens. */
.wal-tbl td { padding-block: 9px; }

.wal-name {
    font-size: var(--fs-md);
    font-weight: var(--fw-medium);
    color: var(--text-primary);
}

/* Which package the tenant is on, under its name. It decides the
   prices the balance beside it is spent at, so it is a caption on
   the tenant rather than a column of its own - forty rows of
   "Growth" would take width from the figures, which are what the
   list is actually scanned for. */
.wal-pkg {
    display: flex;
    align-items: center;
    gap: 4px;
    margin-block-start: 1px;
    font-size: var(--fs-2xs);
    color: var(--text-secondary);
}

.wal-pkg > svg { flex-shrink: 0; }

/* No package is not a missing value - it is the global price list,
   which is a real answer - but it is the default one, so it is
   set quieter than a named package. */
.wal-pkg-none { color: var(--text-tertiary); }

/* ---------- Figures ---------- */

/* Every credit figure in the list is mono and tabular, so a column
   of them lines up digit under digit and the eye can compare
   lengths instead of reading each one. */
.wal-bal,
.wal-fig {
    font-family: var(--font-mono);
    font-variant-numeric: tabular-nums;
    font-size: var(--fs-md);
    color: var(--text-primary);
}

.wal-bal { font-weight: var(--fw-medium); }

/* A balance of zero is the page's alarm, not a small number. It is
   the only figure in the list that carries a colour, because it is
   the only one that means something is currently broken. */
.wal-bal.is-zero {
    color: var(--danger-700);
    font-weight: var(--fw-semibold);
}

/* The dollar value under a credit figure, and the per-day rate
   under a spend. Credits are the unit the ledger is kept in;
   money is the unit the payment arrived in, and an operator
   thinks in the second. */
.wal-sub {
    margin-block-start: 2px;
    font-size: var(--fs-2xs);
    color: var(--text-tertiary);
}

/* "No spend", "Out", "Never". Words rather than em dashes: each
   one is an answer, and a column of dashes says only that the
   query returned nothing. */
.wal-none {
    font-size: var(--fs-sm);
    color: var(--text-tertiary);
}

/* ---------- Runway ---------- */

/* The answer column - balance divided by burn - and the one the
   list is ordered by. A figure and a bar, because "6 days" and
   "260 days" have to separate at a glance down forty rows and a
   number alone does not: the eye reads a length before it reads
   a digit. */
.wal-rw {
    display: flex;
    flex-direction: column;
    gap: 5px;
    min-width: 92px;
}

.wal-rw-n {
    font-family: var(--font-mono);
    font-variant-numeric: tabular-nums;
    font-size: var(--fs-sm);
    color: var(--text-primary);
}

.wal-rw-bar {
    display: block;
    height: 4px;
    border-radius: 999px;
    background: var(--n-200);
    overflow: hidden;
}

/* Capped at a month, so the bar compares the tenants that are
   running out rather than the ones that never will - past thirty
   days the exact figure has stopped being the point, and letting
   a well-funded tenant set the scale flattens every row that
   matters into the first two pixels. */
.wal-rw-fill {
    display: block;
    height: 100%;
    border-radius: inherit;
    transition: width var(--dur-fast) var(--ease-out);
}

.wal-rw-fill.t-good    { background: var(--success-600); }
.wal-rw-fill.t-warn    { background: var(--warning-600); }
.wal-rw-fill.t-bad     { background: var(--danger-600); }
.wal-rw-fill.t-neutral { background: var(--n-400); }

/* A timestamp is context, not a figure to compare, so it stays at
   caption weight next to the columns that are. */
.wal-when {
    font-size: var(--fs-2xs);
    color: var(--text-tertiary);
    white-space: nowrap;
}

/* The two things done to a wallet, kept on one line so the column
   does not set the row height. */
.wal-acts {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: 4px;
}

/* ---------- Drawer chrome ---------- */

/* The mark in a wallet drawer's header. Brand rather than a status
   colour: all three drawers are about money moving, and none of
   them is an alarm. */
.wal-mark {
    display: grid;
    place-items: center;
    width: 32px;
    height: 32px;
    border-radius: var(--r-md);
    background: var(--p-50);
    color: var(--p-700);
}

/* The tenant a drawer is about, when it was opened from a row.
   Stated as a fact with the figure it is about to change, rather
   than offered as a dropdown that has already been answered. */
.wal-who {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 12px;
    padding: 10px 12px;
    border: 1px solid var(--border-subtle);
    border-radius: var(--r-md);
    background: var(--surface-sunken);
}

.wal-who-n {
    font-size: var(--fs-md);
    font-weight: var(--fw-semibold);
    color: var(--text-primary);
}

.wal-who-b {
    display: flex;
    align-items: center;
    gap: 5px;
    white-space: nowrap;
}

.wal-who-b > svg { flex-shrink: 0; color: var(--text-tertiary); }

.wal-who-u {
    font-size: var(--fs-2xs);
    color: var(--text-tertiary);
}

/* ---------- The grant drawer ---------- */

/* Currency and amount are one quantity typed in two fields, so
   they sit on one row - and the currency is three letters, which
   does not need a track the width of the amount. */
.wal-pay {
    display: grid;
    grid-template-columns: 110px 1fr;
    gap: 10px;
    align-items: start;
}

/* Money typed in, aligned to the end so the decimal point sits where the
   eye expects it. Not mono: QInput writes font-family into a style
   attribute, which beats anything a class can say, so a rule setting it
   here would be a rule that quietly does nothing. */
.wal-amt {
    font-variant-numeric: tabular-nums;
    text-align: end;
}

/* The conversion, as the chain it is. A payment becomes credits
   through a rate and a peg, and each step is shown with its own
   figure rather than summarised in one sentence ending in a
   credit count - an operator checking a grant is checking the
   steps, not the total. */
.wal-chain {
    display: flex;
    flex-direction: column;
    border: 1px solid var(--border-subtle);
    border-radius: var(--r-md);
    background: var(--surface-sunken);
    overflow: hidden;
}

.wal-step {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 12px;
    padding: 8px 12px;
    border-block-start: 1px solid var(--border-subtle);
    font-size: var(--fs-sm);
}

.wal-chain > .wal-step:first-child { border-block-start: none; }

.wal-step-l {
    color: var(--text-secondary);
    min-width: 0;
}

.wal-step-v {
    font-variant-numeric: tabular-nums;
    font-weight: var(--fw-medium);
    color: var(--text-primary);
    white-space: nowrap;
}

/* The step that is the output rather than an intermediate: this is
   the figure that will be on the ledger. */
.wal-step.is-out {
    background: var(--success-50);
}

.wal-step.is-out .wal-step-v { color: var(--success-700); }

/* What the wallet becomes - the consequence, held apart from the
   conversion that produced it. */
.wal-after {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 12px;
    padding: 8px 12px;
    border-block-start: 1px solid var(--border-default);
    background: var(--surface-raised);
    font-size: var(--fs-sm);
}

/* The figure riding the action bar, beside the button that posts
   it. Same role as .inc-sum on the incident drawer: the footer
   states what is about to happen in the units it happens in. */
.wal-sum {
    display: flex;
    align-items: baseline;
    gap: 6px;
    min-width: 0;
}

.wal-sum-n {
    font-variant-numeric: tabular-nums;
    font-size: var(--fs-lg);
    font-weight: var(--fw-semibold);
    color: var(--success-700);
}

/* On the grant and package bars this figure is credits about to be added,
   so it is green. On the ledger's bar it is the balance - a state, not a
   gain - and green there would announce a change that has not happened. */
.wal-sum-n.is-plain { color: var(--text-primary); }

.wal-sum-n.is-zero { color: var(--danger-700); }

.wal-sum-w {
    font-size: var(--fs-sm);
    color: var(--text-secondary);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* ---------- The package drawer ---------- */

/* Packages are compared, not picked from a list, so the tiles run
   two-up where there is room. .drw-pick stacks them one per row,
   which is right for four severities carrying a sentence each and
   wrong for eight packages carrying a tier and a credit figure. */
.wal-pick {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
    gap: 8px;
}

/* The tier, in the slot the severity letter occupies on the
   incident drawer. Tiers are words rather than single letters, so
   the box is sized by its content instead of square. */
.wal-pick-i {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 26px;
    height: 22px;
    padding-inline: 7px;
    border-radius: var(--r-sm);
    background: var(--p-50);
    color: var(--p-700);
    font-size: var(--fs-2xs);
    font-weight: var(--fw-semibold);
    text-transform: uppercase;
    letter-spacing: 0.03em;
}

/* The credits a package grants, on the tile that grants them -
   the figure the toggle below the list is about to post. */
.wal-pick-c {
    margin-inline-start: 6px;
    padding-inline-start: 6px;
    border-inline-start: 1px solid var(--border-default);
    font-family: var(--font-mono);
    font-variant-numeric: tabular-nums;
}

/* A switch with its consequence spelled out beside it. It was a
   bare checkbox next to the words "grant its credits", with the
   number it would post nowhere on screen. */
.wal-grantline {
    display: flex;
    align-items: flex-start;
    gap: 12px;
    padding: 12px;
    border: 1px solid var(--border-subtle);
    border-radius: var(--r-md);
    background: var(--surface-sunken);
}

.wal-grantline-b { flex: 1; min-width: 0; }

.wal-grantline-t {
    font-size: var(--fs-md);
    font-weight: var(--fw-medium);
    color: var(--text-primary);
}

/* ---------- The ledger drawer ---------- */

/* What the ledger adds up to, before the rows that add up to it.
   A ledger is opened to answer "where did the money go", and the
   answer is a shape - in, out, left - that a hundred rows state
   only by being summed. */
.wal-tot {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 1px;
    margin-block-end: 4px;
    border: 1px solid var(--border-subtle);
    border-radius: var(--r-md);
    background: var(--border-subtle);
    overflow: hidden;
}

.wal-tot-c {
    display: flex;
    flex-direction: column;
    gap: 2px;
    padding: 10px 12px;
    background: var(--surface-raised);
}

.wal-tot-l {
    font-size: var(--fs-2xs);
    font-weight: var(--fw-semibold);
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--text-tertiary);
}

.wal-tot-v {
    font-variant-numeric: tabular-nums;
    font-size: var(--fs-lg);
    font-weight: var(--fw-semibold);
    color: var(--text-primary);
}

.wal-tot-v.is-in   { color: var(--success-700); }
.wal-tot-v.is-zero { color: var(--danger-700); }

.wal-tot-u {
    font-size: var(--fs-2xs);
    color: var(--text-tertiary);
}

/* The totals are over what has been loaded; the balance is live.
   That distinction matters on a paged ledger, so it is stated
   under the panel rather than left to be inferred. */
.wal-tot-h { margin-block: 0 4px; }

.wal-lseg { margin-block-end: 10px; }

/* ---------- Ledger rows ---------- */

.wal-led {
    display: flex;
    flex-direction: column;
    border: 1px solid var(--border-subtle);
    border-radius: var(--r-md);
    overflow: hidden;
}

/* The date, once, over the rows it owns - rather than repeated
   down a column twenty times for one busy afternoon. It carries
   the figure a column could not: what the day came to. */
.wal-day {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 10px;
    padding: 7px 12px;
    background: var(--surface-sunken);
    border-block-start: 1px solid var(--border-subtle);
}

.wal-led > .wal-day:first-child { border-block-start: none; }

.wal-day-t {
    font-size: var(--fs-2xs);
    font-weight: var(--fw-semibold);
    text-transform: uppercase;
    letter-spacing: 0.03em;
    color: var(--text-secondary);
}

.wal-day-n {
    font-family: var(--font-mono);
    font-variant-numeric: tabular-nums;
    font-size: var(--fs-2xs);
    color: var(--text-tertiary);
}

/* One ledger entry. A grid rather than a table: the reversal
   panel opens underneath the row it corrects and has to span the
   full width, which inside a table row means a colspan cell and
   a second set of column rules. */
.wal-tx {
    display: grid;
    grid-template-columns: 1fr auto auto;
    align-items: start;
    gap: 10px;
    padding: 9px 12px;
    border-block-start: 1px solid var(--border-subtle);
}

.wal-tx-main { min-width: 0; }

.wal-tx-head {
    display: flex;
    align-items: center;
    gap: 7px;
    flex-wrap: wrap;
}

.wal-tx-time {
    font-family: var(--font-mono);
    font-variant-numeric: tabular-nums;
    font-size: var(--fs-2xs);
    color: var(--text-tertiary);
}

/* A row that has been corrected is still on the ledger - it has
   to be, the ledger is append-only - so it is marked rather than
   removed, and its figures are dimmed to say they no longer
   stand. */
.wal-tx-rev {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    font-size: var(--fs-2xs);
    font-weight: var(--fw-semibold);
    color: var(--warning-700);
}

.wal-tx.is-reversed .wal-tx-amt {
    text-decoration: line-through;
    color: var(--text-tertiary);
}

.wal-tx.is-reversed .wal-tx-detail { color: var(--text-tertiary); }

/* What the row was for. On an AI debit this is the audit trail -
   model, tokens, provider cost - which is exactly what somebody
   checking a charge came to read, so it is allowed to wrap
   rather than being clipped to one line. */
.wal-tx-detail {
    margin-block-start: 2px;
    font-size: var(--fs-sm);
    line-height: 1.45;
    color: var(--text-secondary);
    overflow-wrap: anywhere;
}

/* The operator's own words on a manual row - the payment
   reference, or why something was reversed. */
.wal-tx-note {
    margin-block-start: 2px;
    font-size: var(--fs-2xs);
    line-height: 1.45;
    color: var(--text-tertiary);
    overflow-wrap: anywhere;
}

.wal-tx-figs { text-align: end; }

/* Signed, and coloured only when positive. Debits are the normal
   traffic on this ledger and colouring them all red would make
   the page a wall of alarm; a credit arriving is the event worth
   picking out. */
.wal-tx-amt {
    font-family: var(--font-mono);
    font-variant-numeric: tabular-nums;
    font-size: var(--fs-md);
    font-weight: var(--fw-medium);
    color: var(--text-primary);
    white-space: nowrap;
}

.wal-tx-amt.is-in { color: var(--success-700); }

/* The running balance at the moment the row posted - the figure
   that makes a ledger auditable rather than a list of amounts. */
.wal-tx-bal {
    margin-block-start: 2px;
    font-family: var(--font-mono);
    font-variant-numeric: tabular-nums;
    font-size: var(--fs-2xs);
    color: var(--text-tertiary);
    white-space: nowrap;
}

/* Kept quiet until the row is hovered or the button is focused:
   a reversal is a rare, deliberate act, and a column of live
   Reverse buttons down a ledger invites one. */
.wal-tx-act { opacity: 0; transition: opacity var(--dur-fast) var(--ease-out); }

.wal-tx:hover .wal-tx-act,
.wal-tx:focus-within .wal-tx-act { opacity: 1; }

/* Touch and keyboard have no hover, and an action that only
   appears under a pointer is an action they cannot find. */
@media (hover: none) {
    .wal-tx-act { opacity: 1; }
}

/* The correction, opened under the row it corrects rather than in
   a modal over the ledger - the row above it is the evidence the
   reason is being written about. */
.wal-rev {
    grid-column: 1 / -1;
    display: flex;
    flex-direction: column;
    gap: 8px;
    margin-block-start: 4px;
    padding: 12px;
    border: 1px solid var(--warning-100);
    border-radius: var(--r-md);
    background: var(--warning-50);
}

.wal-rev-q {
    margin: 0;
    font-size: var(--fs-md);
    font-weight: var(--fw-semibold);
    color: var(--warning-700);
}

.wal-rev .drw-hint { margin: 0; }

.wal-rev-acts {
    display: flex;
    justify-content: flex-end;
    gap: 8px;
}

.wal-more {
    display: flex;
    justify-content: center;
    margin-block-start: 10px;
}

/* ---------- Narrow ---------- */

@media (max-width: 860px) {
    /* The two columns that go on a phone. Neither is information
       lost: the spend figure is the input to the runway beside it,
       which is kept, and the timestamp is a coarser version of the
       status pill, which is also kept. Dropping them brings the
       balance, the runway and the actions back inside the
       scrollport - the three things the page is opened for. */
    .wal-tbl thead th:nth-child(3),
    .wal-tbl thead th:nth-child(5),
    .wal-row > td:nth-child(3),
    .wal-row > td:nth-child(5) { display: none; }

    .wal-srch { max-width: none; }
}

@media (max-width: 560px) {
    /* The status pill goes, and the row keeps its buttons. The state is on
       the row twice over - the runway bar carries the same tone and a zero
       balance is already red - and of the two, the pill is the one costing
       about a hundred pixels. Without this the actions the list exists to
       reach sit behind a sideways scroll. */
    .wal-tbl thead th:nth-child(6),
    .wal-row > td:nth-child(6) { display: none; }

    /* Stacked, so the actions column is the width of one button rather than
       two side by side. Dropping the status pill alone still left the row
       about a hundred pixels over the scrollport, and these are the two
       controls the whole list exists to reach - a taller row is the cheaper
       price than a sideways scroll to find them. */
    .wal-acts {
        flex-direction: column;
        align-items: stretch;
    }

    /* The bar can be shorter here: at this width it is telling apart four
       rows, not forty. */
    .wal-rw { min-width: 64px; }

    /* The last of the width. .tbl pays 12px a side per cell and 20px at the
       card's edges, which is right for a wide table and is the difference
       between fitting and not at 420px. */
    .wal-tbl thead th,
    .wal-tbl tbody td { padding-inline: 8px; }

    .wal-tbl thead th:first-child,
    .wal-tbl tbody td:first-child { padding-inline-start: 14px; }

    .wal-tbl thead th:last-child,
    .wal-tbl tbody td:last-child { padding-inline-end: 14px; }

    /* The amount keeps the width; the currency is three letters. */
    .wal-pay { grid-template-columns: 96px 1fr; }

    /* One package per row rather than two 100px tiles. */
    .wal-pick { grid-template-columns: 1fr; }

    /* The figures move under the detail rather than beside it: at
       this width a three-track row leaves the detail column about
       as wide as one word. */
    .wal-tx { grid-template-columns: 1fr auto; }
    .wal-tx-act { grid-column: 1 / -1; opacity: 1; display: flex; justify-content: flex-end; }
}

/* ============================================================
   70. Super Admin: the menu drawer on Roles & scopes (mnud-*)

   /admin/settings/roles opens this to add a row to the menu tree
   or to edit one. It used to be a fixed div with an 840px width
   and a 360px + 1fr body written into the markup, which meant
   that below about 900px the panel hung off the right edge of
   the screen - close button included - and its two columns went
   on splitting a width that had stopped being two columns wide.

   It is a QDrawer now, so the chrome, the scrim, Escape, the
   pinned header and footer and the width cap are all "20."'s.
   The groups are .drw-sec panels and the fields .drw-field, so
   what is left here is only what is true of this drawer:

     .mnud-cols   two columns down to 900px, one below it
     .mnud-prev   the sidebar row the fields above it describe
     .mnud-icons  the icon palette, which wraps rather than picks
     .mnud-vis    four switches, each a full-width row
     .mnud-fn-*   the function list - three tracks, then two
     .mnud-add    the custom-function composer

   Every breakpoint below is the viewport, which is a fair proxy
   for the panel: .drw caps its width at 100vw, so under 940px
   the drawer *is* the screen and the two measure the same thing.
   ============================================================ */

.mnud-mark {
    flex-shrink: 0;
    display: grid;
    place-items: center;
    inline-size: 34px;
    block-size: 34px;
    border-radius: var(--r-md);
    background: var(--p-600);
    color: var(--text-inverse);
}

/* ---------- The two columns ---------- */

/* The left column is the menu; the right is what may be done on it.
   Not 1fr 1fr: the left holds seven controls and the right a list of
   ten short rows, so the list gets the narrower track and the form
   gets the slack. align-items:start keeps the function panel its own
   height rather than stretching it to match the column beside it. */
.mnud-cols {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 360px);
    align-items: start;
    gap: 14px;
}

.mnud-col {
    display: flex;
    flex-direction: column;
    gap: 14px;
    min-width: 0;
}

/* ---------- The preview row ---------- */

/* Icon, name and description are three inputs on this form and one
   line in the sidebar. This is that line, live: it is the only place
   the icon picker's answer can be checked against the name it will
   sit beside. */
.mnud-prev {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 10px 12px;
    border-radius: var(--r-md);
    background: var(--surface-sunken);
    box-shadow: inset 0 0 0 1px var(--border-subtle);
}

.mnud-prev-i {
    flex-shrink: 0;
    display: grid;
    place-items: center;
    inline-size: 28px;
    block-size: 28px;
    border-radius: var(--r-sm);
    background: var(--p-50);
    color: var(--p-700);
}

.mnud-prev-txt {
    flex: 1;
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: 1px;
}

.mnud-prev-t {
    font-size: var(--fs-sm);
    font-weight: var(--fw-semibold);
    color: var(--text-primary);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.mnud-prev-t.is-empty { color: var(--text-disabled); font-weight: var(--fw-medium); }

.mnud-prev-d {
    font-size: var(--fs-xs);
    color: var(--text-tertiary);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.mnud-prev-tag {
    flex-shrink: 0;
    padding: 2px 8px;
    border-radius: var(--r-full);
    background: var(--n-100);
    font-size: var(--fs-2xs);
    font-weight: var(--fw-semibold);
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--text-tertiary);
}

/* ---------- Route and filter fields ---------- */

/* QInput and QTextArea both set font-family:inherit inline, so a class
   on the control cannot reach them and a class on their wrapper can.
   Same trick as .rnew-monowrap in "9.". */
.mnud-mono { font-family: var(--font-mono); }

/* ---------- The icon palette ---------- */

/* auto-fill, not auto-fit: a fixed cell keeps every glyph the same size
   whatever the width, where auto-fit would stretch twenty-four of them
   across a desktop drawer into twenty-four wide empty buttons. */
.mnud-icons {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(38px, 1fr));
    gap: 6px;
}

.mnud-ico {
    display: grid;
    place-items: center;
    block-size: 38px;
    padding: 0;
    border: 1px solid var(--border-default);
    border-radius: var(--r-md);
    background: var(--surface-raised);
    color: var(--text-secondary);
    cursor: pointer;
    transition: border-color var(--dur-fast) var(--ease-out),
                background var(--dur-fast) var(--ease-out),
                color var(--dur-fast) var(--ease-out);
}

.mnud-ico:hover { border-color: var(--p-300); color: var(--p-700); }

.mnud-ico:focus-visible {
    outline: none;
    border-color: var(--p-600);
    box-shadow: 0 0 0 3px var(--p-100);
}

.mnud-ico.is-on {
    border-color: var(--p-600);
    background: var(--p-50);
    color: var(--p-700);
    box-shadow: inset 0 0 0 1px var(--p-600);
}

/* ---------- Visibility switches ---------- */

.mnud-vis {
    display: flex;
    flex-direction: column;
}

/* A rule between rows rather than around each: four switches in four
   ringed boxes is four objects where there is one setting with four
   parts. min-height is the touch target, and it is why these are
   switches and not the 14px checkboxes they replaced. */
.mnud-vis-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    min-block-size: 44px;
    padding-block: 6px;
}

.mnud-vis-row + .mnud-vis-row { border-block-start: 1px solid var(--border-subtle); }

.mnud-vis-txt {
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: 1px;
}

.mnud-vis-t {
    font-size: var(--fs-sm);
    font-weight: var(--fw-medium);
    color: var(--text-primary);
}

.mnud-vis-d {
    font-size: var(--fs-xs);
    line-height: var(--lh-snug);
    color: var(--text-tertiary);
}

/* ---------- The function list ---------- */

/* The panel keeps its own padding for the list, which is edge to edge
   inside it: a list of ten rows reads as a list when the rules run the
   full width, and as ten cards when they do not. */
.mnud-fn .mnud-fn-list {
    margin-inline: -14px;
    border-block: 1px solid var(--border-subtle);
}

/* Type is a module or sub-module: SaveDrawer writes no functions for
   either, so the panel says so and steps back rather than offering ten
   checkboxes that go nowhere. Not disabled - the answer is to change
   the type in the panel beside it, and a greyed-out list that still
   responds is how you say "this, but not yet". */
.mnud-fn.is-muted .mnud-fn-list,
.mnud-fn.is-muted .mnud-add { opacity: 0.45; }

.mnud-fn-count {
    margin-inline-start: auto;
    padding: 1px 7px;
    border-radius: var(--r-full);
    background: var(--n-100);
    font-size: var(--fs-2xs);
    font-weight: var(--fw-semibold);
    letter-spacing: 0;
    text-transform: none;
    color: var(--text-secondary);
}

.mnud-note {
    display: flex;
    align-items: flex-start;
    gap: 7px;
    margin: 0;
    padding: 9px 11px;
    border-radius: var(--r-md);
    background: var(--p-50);
    font-size: var(--fs-xs);
    line-height: var(--lh-snug);
    color: var(--p-800);
}

.mnud-note > svg { flex-shrink: 0; margin-block-start: 1px; }

/* Three tracks: the checkbox, the name that takes the slack, the
   controls. The controls are `auto` rather than a measured width so
   the pill can say "hidden" and "shown" without the column jumping. */
.mnud-fn-row {
    display: grid;
    grid-template-columns: 34px minmax(0, 1fr) auto;
    align-items: center;
    gap: 8px;
    padding: 7px 14px;
    background: var(--surface-sunken);
}

.mnud-fn-row + .mnud-fn-row { border-block-start: 1px solid var(--border-subtle); }

/* An enabled function is on the raised ground, a disabled one stays on
   the sunken: which rows are live is then readable down the list
   without reading a single checkbox. */
.mnud-fn-row.is-on { background: var(--surface-raised); }

.mnud-fn-check { display: flex; justify-content: center; color: var(--warning-600); }

.mnud-fn-name {
    min-width: 0;
    display: flex;
    align-items: baseline;
    gap: 7px;
}

.mnud-fn-t {
    font-size: var(--fs-sm);
    font-weight: var(--fw-medium);
    color: var(--text-disabled);
    white-space: nowrap;
}

.mnud-fn-row.is-on .mnud-fn-t,
.mnud-fn-row.is-custom .mnud-fn-t {
    font-weight: var(--fw-semibold);
    color: var(--text-primary);
}

/* The key, not the label: it is what a grant row and a seed refer to,
   and renaming the label above it does not change it. */
.mnud-fn-k {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    font-family: var(--font-mono);
    font-size: var(--fs-2xs);
    color: var(--text-disabled);
}

.mnud-fn-acts {
    display: flex;
    align-items: center;
    gap: 5px;
}

.mnud-fn-band {
    padding: 6px 14px;
    background: var(--warning-50);
    border-block: 1px solid var(--warning-100);
    font-size: var(--fs-2xs);
    font-weight: var(--fw-bold);
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--warning-700);
}

.mnud-fn-row.is-custom { background: var(--warning-50); }
.mnud-fn-row.is-custom + .mnud-fn-row.is-custom { border-block-start-color: var(--warning-100); }

/* The shown/hidden pill. A button that reads as its own state rather
   than as an instruction: "shown" is what is true now, and pressing it
   makes the other one true. aria-pressed carries the same thing. */
.mnud-vp {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    block-size: 26px;
    padding-inline: 8px;
    border: 1px solid var(--border-default);
    border-radius: var(--r-full);
    background: var(--surface-raised);
    font-size: var(--fs-2xs);
    font-weight: var(--fw-semibold);
    color: var(--text-disabled);
    cursor: pointer;
    white-space: nowrap;
    transition: border-color var(--dur-fast) var(--ease-out),
                background var(--dur-fast) var(--ease-out),
                color var(--dur-fast) var(--ease-out);
}

.mnud-vp > svg { flex-shrink: 0; }

.mnud-vp.is-on {
    border-color: var(--p-300);
    background: var(--p-50);
    color: var(--p-700);
}

.mnud-vp:focus-visible {
    outline: none;
    border-color: var(--p-600);
    box-shadow: 0 0 0 3px var(--p-100);
}

/* The two square buttons beside it. 26px to match the pill's height,
   so the three read as one control group and not as three sizes. */
.mnud-ib {
    display: grid;
    place-items: center;
    inline-size: 26px;
    block-size: 26px;
    padding: 0;
    border: 1px solid var(--border-default);
    border-radius: var(--r-sm);
    background: var(--surface-raised);
    color: var(--text-secondary);
    cursor: pointer;
    transition: border-color var(--dur-fast) var(--ease-out),
                background var(--dur-fast) var(--ease-out),
                color var(--dur-fast) var(--ease-out);
}

.mnud-ib:hover { border-color: var(--p-300); color: var(--p-700); }

.mnud-ib:focus-visible {
    outline: none;
    border-color: var(--p-600);
    box-shadow: 0 0 0 3px var(--p-100);
}

.mnud-ib.is-bad { color: var(--danger-600); }
.mnud-ib.is-bad:hover { border-color: var(--danger-200); background: var(--danger-50); color: var(--danger-700); }

.mnud-ib.is-ok {
    border-color: var(--p-600);
    background: var(--p-600);
    color: var(--text-inverse);
}

/* ---------- The composer ---------- */

.mnud-add {
    display: flex;
    align-items: center;
    gap: 8px;
}

.mnud-add > :first-child { flex: 1; min-width: 0; }

/* ---------- The footer note ---------- */

.mnud-ready {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    font-size: var(--fs-xs);
    font-weight: var(--fw-medium);
    color: var(--text-tertiary);
}

.mnud-ready > svg { flex-shrink: 0; }
.mnud-ready.is-ok { color: var(--success-700); }

.mnud-foot-btns {
    display: flex;
    align-items: center;
    gap: 8px;
}

/* ============================================================
   Narrow
   ============================================================ */

/* 900px is where the drawer stops being a panel over the matrix and
   becomes the screen. Two columns of a 900px drawer are 500 and 360;
   two columns of a 700px one are 320 and 360, which is a select with
   half a word of room. One column from here down. */
@media (max-width: 900px) {
    .mnud-cols { grid-template-columns: minmax(0, 1fr); }

    /* Stacked, the function panel is the last thing on a long scroll,
       so it gets the rule that says a new subject starts here. */
    .mnud-fn { box-shadow: 0 0 0 1px var(--border-default), var(--shadow-xs); }
}

/* Under a tablet the drawer is a phone-width sheet. The three-track
   function row is the thing that breaks first: a 34px checkbox, a name
   and three controls need about 300px, and the row has 290 to give. */
@media (max-width: 560px) {
    .mnud-cols { gap: 12px; }

    /* Name on the first line with the checkbox, controls under it,
       indented to the name so the row still reads as one thing. The
       key drops: it is the only part of the row that is reference
       rather than control, and it is on the matrix behind this. */
    .mnud-fn-row {
        grid-template-columns: 34px minmax(0, 1fr);
        row-gap: 6px;
        padding-block: 9px;
        /* Two rows tall now, so centring puts the checkbox between the
           name and the controls rather than beside either. Top-aligned
           it sits on the name, which is what it ticks. */
        align-items: start;
    }

    .mnud-fn-check { padding-block-start: 3px; }

    .mnud-fn-acts {
        grid-column: 2;
        justify-content: flex-start;
    }

    .mnud-fn-k { display: none; }

    /* Both halves of the composer get the full width: an input and a
       button sharing 290px leaves the input too short to read back the
       name being typed into it. */
    .mnud-add { flex-wrap: wrap; }
    .mnud-add > :first-child { flex: 1 1 100%; }
    .mnud-add > :last-child { flex: 1 1 100%; justify-content: center; }

    /* The buttons take the bar. .drw-foot already wraps, and
       .drw-foot-lead already puts the note on the line above; this is
       only about the two buttons filling the line they land on. */
    .mnud-foot-btns { flex: 1 1 100%; }
    .mnud-foot-btns > * { flex: 1; justify-content: center; }

    /* Five across rather than whatever 38px lets fit, so the palette is
       a shape and not a ragged edge. */
    .mnud-icons { grid-template-columns: repeat(auto-fill, minmax(36px, 1fr)); }
    .mnud-ico { block-size: 36px; }
}

/* A pointer that cannot hover cannot discover a 26px target by
   pointing at it either. The row's controls grow to the 32px that a
   thumb actually lands on; nothing else about the row changes. */
@media (hover: none) {
    .mnud-ib { inline-size: 32px; block-size: 32px; }
    .mnud-vp { block-size: 32px; padding-inline: 10px; }
}

/* ---------- The matrix the drawer opens from ---------- */

/* Eleven columns: a menu name, seven 54px checkboxes, then Data,
   Functions and Actions at 130, 160 and 88. That is 756px of fixed
   track before the name column gets a pixel, and the box around it
   clipped the overflow - so under about 1000px the three columns the
   row is actually worked in were gone, with nothing to say they were
   there. A scrollport says it: the bar under the table is the whole
   message.

   The floor is 1000px rather than 756 so the menu name keeps ~244px
   at the narrowest - enough for a label and its indent. Grids are
   direct children of the port, so the floor goes on them rather than
   on a wrapper element the markup does not have. */
/* Not `overflow: auto`: the box is only ever too wide, never too tall -
   the page is what scrolls down - and a second scrollbar on a table
   that has no overflow to show is a scrollbar that never moves. The
   `hidden` on the block axis also keeps the radius clipping the
   header's navy corners, which is what the `overflow: hidden` this
   replaced was there for. */
.rsx-matrix {
    overflow-x: auto;
    overflow-y: hidden;
    overscroll-behavior-x: contain;
}

.rsx-matrix > div { min-inline-size: 1000px; }

/* ============================================================
   71. Super Admin: Localization (l10n-*)

   /admin/localization is four views of one matrix — languages
   down one side, source strings across the other — and almost
   all of its chrome is shared: .pg for the bands, .tabs through
   QTabs, .tbl for all five tables, .drw-* for both drawers,
   .set-bar for the commit. What is left here is the part that
   is only true of this screen: a row whose identity is a
   language code, a proportion drawn the same way in five
   places, a filter row that sits inside a card rather than
   above it, and a field that has to be typed in a script the
   page it lives on is not written in.
   ============================================================ */

/* ---------- The language as a row ---------- */

.l10n-id {
    display: flex;
    align-items: center;
    gap: 11px;
    min-width: 0;
}

/* The code is the mark. It is what the browser, the URL and
   every translated row call this language, so it leads the cell
   at a fixed minimum — a column of them is one edge, and "en"
   and "th-TH" do not shift the names beside them. */
.l10n-code {
    display: grid;
    place-items: center;
    flex-shrink: 0;
    min-width: 42px;
    height: 26px;
    padding: 0 8px;
    border-radius: var(--r-md);
    background: var(--p-50);
    font-family: var(--font-mono);
    font-size: var(--fs-2xs);
    font-weight: var(--fw-semibold);
    color: var(--p-700);
    text-transform: uppercase;
}

/* min-width: 0 so .trunc inside can actually shrink rather than
   widening the column until the table scrolls sideways. */
.l10n-id-txt { min-width: 0; }

/* The name and the source chip share a line. Stacked, the chip
   would put a third line under exactly one row and the table's
   rhythm would depend on which language happens to be default. */
.l10n-id-line {
    display: flex;
    align-items: center;
    gap: 7px;
    min-width: 0;
}

.l10n-nm {
    font-size: var(--fs-md);
    font-weight: var(--fw-semibold);
    color: var(--text-primary);
}

/* What speakers of the language call it, in their own script. It
   carries dir from the row, so an RTL name runs the right way
   here as well as in the switcher. */
.l10n-native {
    margin: 2px 0 0;
    font-size: var(--fs-xs);
    line-height: 1.4;
    color: var(--text-tertiary);
}

/* An inactive language is still configured and still counted, so
   it is dimmed rather than hidden — but only its text. Dimming
   the row would take the switch that turns it back on with it. */
.l10n-row.is-off .l10n-nm,
.l10n-row.is-off .l10n-native { color: var(--text-tertiary); }

.l10n-row.is-off .l10n-code {
    background: var(--n-100);
    color: var(--text-tertiary);
}

/* ---------- A proportion ---------- */

/* Coverage appears in five places on this screen and is the same
   thing in all of them: a number that gets quoted, and a bar
   that makes the unfinished locale obvious without reading down
   the column. The number is mono so a column of them lines up
   on its digits. */
.l10n-cov {
    display: flex;
    align-items: center;
    gap: 9px;
    min-width: 0;
}

.l10n-cov-n {
    flex-shrink: 0;
    min-width: 38px;
    font-family: var(--font-mono);
    font-variant-numeric: tabular-nums;
    font-size: var(--fs-sm);
    font-weight: var(--fw-semibold);
    color: var(--text-primary);
    text-align: end;
}

.l10n-bar {
    /* Capped: past about 220px the bar stops reading as a
       proportion and starts reading as a rule across the cell. */
    flex: 1 1 auto;
    max-width: 220px;
    min-width: 64px;
    height: 6px;
    border-radius: var(--r-full);
    background: var(--n-100);
    overflow: hidden;
}

.l10n-bar-fill {
    display: block;
    height: 100%;
    border-radius: inherit;
    background: var(--n-300);
    transition: width var(--dur-slow) var(--ease-out);
}

/* Three bands, not a gradient. The question the bar answers is
   "can this locale be switched on yet", and that has three
   answers: yes, nearly, no. */
.l10n-bar.is-good .l10n-bar-fill { background: var(--success-600); }
.l10n-bar.is-part .l10n-bar-fill { background: var(--warning-600); }
.l10n-bar.is-low  .l10n-bar-fill { background: var(--n-300); }

/* Work outstanding — the one figure on a coverage row that is
   not a measurement but a backlog. */
.l10n-gap {
    font-weight: var(--fw-semibold);
    color: var(--warning-700);
}

/* ---------- The filter row ---------- */

/* Inside the card rather than above it: these filters change
   what the table below them holds, and a bar floating over the
   card reads as belonging to the page. Not sticky — the row is
   short, and the table scrolls the page rather than itself. */
.l10n-tools {
    display: flex;
    align-items: center;
    gap: 8px;
    flex-wrap: wrap;
    padding: 11px 16px;
    border-block-end: 1px solid var(--border-subtle);
}

/* Takes the slack so the two controls beside it keep their own
   size; floored so it does not collapse to a caret's width when
   the row wraps. */
.l10n-search {
    flex: 1 1 200px;
    min-width: 160px;
}

/* One choice of four, so a segmented control rather than a third
   select — Missing is the reason most visits happen, and inside
   a closed menu it costs two clicks and a read. */
.l10n-seg {
    display: inline-flex;
    padding: 2px;
    border-radius: var(--r-md);
    background: var(--n-100);
}

.l10n-seg-opt {
    padding: 4px 11px;
    border: none;
    border-radius: var(--r-sm);
    background: transparent;
    font-size: var(--fs-xs);
    font-weight: var(--fw-medium);
    color: var(--text-secondary);
    white-space: nowrap;
    cursor: pointer;
    transition: background-color var(--dur-fast) var(--ease-out),
                color var(--dur-fast) var(--ease-out);
}

.l10n-seg-opt:hover { color: var(--text-primary); }

.l10n-seg-opt.is-on {
    background: var(--surface-raised);
    color: var(--text-primary);
    font-weight: var(--fw-semibold);
    box-shadow: var(--shadow-xs);
}

.l10n-seg-opt:focus-visible { outline: none; box-shadow: var(--shadow-focus); }

/* The language picker on the translations tab. .chip-row and
   .chip-tog do the work; this only sets the code apart inside
   the chip, where it has to read as an identifier against the
   name beside it. */
.l10n-picker .l10n-chip-code {
    font-family: var(--font-mono);
    font-size: var(--fs-2xs);
    text-transform: uppercase;
    opacity: 0.7;
}

/* A sentence about the table under it, said once above the rows
   rather than repeated in each of them. Same inset as the filter
   row below it, so the two read as one head. */
.l10n-lede {
    margin: 0;
    padding: 12px 16px 0;
    font-size: var(--fs-xs);
    line-height: 1.5;
    color: var(--text-tertiary);
}

.l10n-err {
    margin: 0 0 12px;
    font-size: var(--fs-sm);
    color: var(--danger-700);
}

/* ---------- Strings and keys ---------- */

/* The key is an identifier a person matches against source, so
   it is mono; the namespace under it is context, so it is
   quieter and smaller rather than a column of its own. */
.l10n-key {
    font-family: var(--font-mono);
    font-size: var(--fs-xs);
    font-weight: var(--fw-medium);
    color: var(--text-primary);
    word-break: break-word;
}

.l10n-ns {
    margin-block-start: 2px;
    font-family: var(--font-mono);
    font-size: var(--fs-2xs);
    color: var(--text-tertiary);
}

/* On the translations tab the English leads the cell and the key
   is the line under it — the translator reads the sentence, and
   only needs the key when something looks wrong. */
.l10n-src {
    display: block;
    max-width: 460px;
    font-size: var(--fs-sm);
    line-height: 1.45;
    color: var(--text-primary);
}

.l10n-key-sm {
    margin-block-start: 3px;
    font-family: var(--font-mono);
    font-size: var(--fs-2xs);
    color: var(--text-tertiary);
    word-break: break-word;
}

/* The dot between namespace and key. Quieter than either, so the
   two parts read as two parts. */
.l10n-sep { color: var(--n-300); }

.l10n-ns-tag {
    font-family: var(--font-mono);
    font-size: var(--fs-xs);
    color: var(--text-primary);
}

/* ---------- Which languages have this string ---------- */

/* One tag per translatable language rather than a count. "2 of
   3" says how many are done and not which, and which is the
   question — a gap is always in a particular locale. */
.l10n-dots {
    display: flex;
    align-items: center;
    gap: 4px;
    flex-wrap: wrap;
}

.l10n-dot {
    display: grid;
    place-items: center;
    min-width: 28px;
    height: 18px;
    padding: 0 5px;
    border-radius: var(--r-sm);
    font-family: var(--font-mono);
    font-size: 9.5px;
    font-weight: var(--fw-semibold);
    text-transform: uppercase;
    /* Never the only signal: each tag carries its language and
       its state in a title, so colour is doing the scanning
       rather than the telling. */
    cursor: default;
}

.l10n-dot.is-ok     { background: var(--success-50); color: var(--success-700); }
.l10n-dot.is-review { background: var(--info-50);    color: var(--info-700); }

/* Missing is an outline rather than a fill: an absence should
   read as a hole in the row, and three filled greys beside two
   filled greens read as five states of present. */
.l10n-dot.is-none {
    background: transparent;
    color: var(--text-tertiary);
    box-shadow: inset 0 0 0 1px var(--border-default);
}

/* ---------- The translation field ---------- */

.l10n-tr-tbl .l10n-in { width: 100%; }

/* An edited row, before it is committed. The same navy edge the
   packages list uses for the row being edited, and for the same
   reason: a tint alone reads as a hover that got stuck. inset,
   so it costs no layout and cannot push the cell's text. */
.l10n-tr-tbl tr.is-dirty > td {
    background: var(--p-50);
    box-shadow: inset 2px 0 0 var(--p-700);
}

.l10n-tr-tbl tr.is-dirty > td:not(:first-child) { box-shadow: none; }

/* The coverage row whose namespaces are broken out below. Same
   mark, same meaning: the panel under this is about this row. */
.tbl tr.is-picked > td {
    background: var(--p-50);
    box-shadow: inset 2px 0 0 var(--p-700);
}

.tbl tr.is-picked > td:not(:first-child) { box-shadow: none; }

/* ---------- Pager ---------- */

.l10n-pager {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 10px 16px;
    border-block-start: 1px solid var(--border-subtle);
}

/* ---------- Both drawers ---------- */

/* On QDrawer's navy band rather than on white, so the code is
   that band's own colour lifted out of it — the pale chip the
   table rows carry would read as a hole here. */
.l10n-dw-mark {
    display: grid;
    place-items: center;
    min-width: 38px;
    height: 38px;
    padding: 0 9px;
    flex-shrink: 0;
    border-radius: var(--r-lg);
    background: rgba(255, 255, 255, 0.12);
    font-family: var(--font-mono);
    font-size: var(--fs-sm);
    font-weight: var(--fw-semibold);
    color: var(--text-inverse);
    text-transform: uppercase;
    box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.16);
}

/* Label at one end of the row, the switch at the other — the
   shape every other on/off setting in the product has. */
.l10n-tog {
    display: flex;
    align-items: center;
    gap: 10px;
}

/* .lbl carries a bottom margin for the stacked case; on a row it
   only pushes the label off centre. */
.l10n-tog .lbl {
    flex: 1;
    min-width: 0;
    margin-block-end: 0;
}

/* The code beside a language's name on a field label. Small and
   quiet: the name is the label, this says which row it writes. */
.l10n-lbl-code {
    margin-inline-start: 6px;
    font-family: var(--font-mono);
    font-size: var(--fs-2xs);
    font-weight: var(--fw-regular);
    color: var(--text-tertiary);
    text-transform: uppercase;
}

/* The English a translation is made from. Read-only, so it is
   set as text on the panel's own tint rather than in a disabled
   field — a greyed input invites a click that does nothing. */
.l10n-sd-src {
    margin: 0;
    padding: 10px 12px;
    border-radius: var(--r-md);
    background: var(--n-50);
    box-shadow: inset 0 0 0 1px var(--border-subtle);
    font-size: var(--fs-sm);
    line-height: 1.5;
    color: var(--text-primary);
}

/* ---------- Narrow ---------- */

@media (max-width: 900px) {
    /* The source cell is the widest thing in the translations
       table and the only one that can give. Capped tighter
       rather than uncapped: with nothing holding it, the longest
       string on the page sets the column and pushes the field
       off the side of the scrollport. */
    .l10n-src { max-width: 260px; }

    /* The filter row wraps to two lines here, so the search
       takes the first on its own rather than sharing it with
       half a select. */
    .l10n-search { flex-basis: 100%; }
}

@media (max-width: 560px) {
    /* Below this the bar and the number cannot both fit beside
       the columns to their left. The number is the part that
       gets quoted, so the bar is what goes. */
    .l10n-bar { display: none; }

    .l10n-cov-n { min-width: 0; }

    .l10n-seg { width: 100%; }

    .l10n-seg-opt { flex: 1; }
}


/* ============================================================
   72. Super Admin: audit log filters (audf-*)

   /admin/audit filters on seven fields, and they used to sit
   in a four-column band above the table — two rows of chrome
   on every visit, for controls most visits never touch, on the
   one screen whose value is how many rows fit on the page.

   They are a QDrawer now, which is all .drw-*; the fields
   inside it are .lbl and .input like every other form. What is
   left here is the pair of things a drawer costs: a count on
   the button that opens it, and a line on the page saying what
   the hidden controls are currently doing to the table.
   ============================================================ */

/* ---------- The count on the Filters button ---------- */

/* Inside a .btn-primary, so it borrows the button's own text
   colour and sits on a wash of it rather than carrying a second
   hue into the action row. */
.audf-count {
    display: inline-grid;
    place-items: center;
    min-width: 17px;
    height: 17px;
    padding: 0 5px;
    border-radius: 999px;
    background: color-mix(in srgb, currentColor 22%, transparent);
    font-size: 10.5px;
    font-weight: 700;
    font-variant-numeric: tabular-nums;
    line-height: 1;
}

/* ---------- What is applied ---------- */

.audf-bar {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 6px;
    margin-block-end: 12px;
}

.audf-bar-lbl {
    font-size: 10.5px;
    font-weight: 600;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--text-tertiary);
}

/* Every chip is the button that drops its own filter, which is
   why the x is inside it rather than beside it: the whole chip
   is one target, and at 24px it is a comfortable one. */
.audf-chip {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    height: 24px;
    padding: 0 8px;
    border: 1px solid var(--border-default);
    border-radius: 999px;
    background: var(--surface-raised);
    color: var(--text-primary);
    font-size: 11.5px;
    line-height: 1;
    cursor: pointer;
}

/* Red on hover because the click removes something. Nothing
   else on this bar is destructive, so the colour is the whole
   warning. */
.audf-chip:hover {
    border-color: var(--danger-200);
    color: var(--danger-700);
}

.audf-chip:focus-visible {
    outline: none;
    box-shadow: var(--shadow-focus);
}

.audf-chip-k { color: var(--text-tertiary); }

/* A pasted tenant Guid is 36 characters and would push the rest
   of the bar onto a second line on its own. Truncated, the chip
   still says which field it is and the drawer has the full
   value. */
.audf-chip-v {
    font-weight: 600;
    max-width: 180px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.audf-clear {
    padding: 0 4px;
    border: 0;
    background: none;
    color: var(--p-700);
    font-size: 11.5px;
    font-weight: 600;
    cursor: pointer;
}

.audf-clear:hover { text-decoration: underline; }

.audf-clear:focus-visible {
    outline: none;
    box-shadow: var(--shadow-focus);
    border-radius: var(--r-sm);
}

/* ---------- The form inside the drawer ---------- */

.audf-form {
    display: flex;
    flex-direction: column;
    gap: 14px;
}

.audf-pair {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 10px;
}

/* A datetime-local input has a spinner and a picker button of
   the browser's own making, and below this the two of them stop
   fitting either half of the row. */
@media (max-width: 460px) {
    .audf-pair { grid-template-columns: minmax(0, 1fr); }
}

/* ============================================================
   73. Super Admin: the manage-roles drawer on Roles & scopes
       (mrol-*)

   /admin/settings/roles opens this to rename the roles a portal
   already has, and to hand off to the new-role drawer. It was a
   centred 1100 x 90vh modal with a 280px + 1fr + 80px + 160px
   grid inside it: four tracks for four short values, which on a
   1100px box left the description a column it kept wrapping in
   and on anything narrower clipped the actions off the end.

   It is a QDrawer now, like the other two panels on this screen,
   so the chrome, the scrim, Escape, the pinned header and footer
   and the width cap are "20."'s. What is left here is the list:

     .mrol-item   one role - name, key and description on the
                  left, its user count and Edit on the right
     .mrol-form   the same row while it is being renamed
     .mrol-add    the hand-off to the new-role drawer

   The one breakpoint is the viewport, which is a fair proxy for
   the panel: .drw caps its width at 100vw, so under 620px the
   drawer is the screen and the two measure the same thing.
   ============================================================ */

.mrol-mark {
    flex-shrink: 0;
    display: grid;
    place-items: center;
    inline-size: 34px;
    block-size: 34px;
    border-radius: var(--r-md);
    background: var(--p-600);
    color: var(--text-inverse);
}

/* The list is the panel's body, so the panel keeps no padding of its
   own: the rows carry theirs, and their separators run edge to edge.
   With the padding gone the legend needs neither the negative margins
   that bled it over it nor the calc that sized it around them. */
.mrol-sec { padding: 0; gap: 0; }

.mrol-sec > .drw-legend { width: 100%; margin: 0; }

/* How many roles the portal has, in the legend rather than the
   subtitle: it changes when the stakeholder picker above it does,
   and the header does not re-read as easily as the band does. */
.mrol-count {
    display: inline-grid;
    place-items: center;
    min-inline-size: 20px;
    padding: 1px 6px;
    border-radius: 999px;
    background: var(--p-50);
    color: var(--p-700);
    font-size: var(--fs-2xs);
    font-weight: var(--fw-bold);
    letter-spacing: 0;
}

.mrol-empty {
    margin: 0;
    padding: 28px 14px;
    text-align: center;
    color: var(--text-tertiary);
    font-size: var(--fs-sm);
}

.mrol-list {
    margin: 0;
    padding: 0;
    list-style: none;
}

.mrol-item {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 12px 14px;
    border-block-start: 1px solid var(--border-subtle);
}

.mrol-item:first-child { border-block-start: none; }

/* Renaming is a form, not a row: it stacks, so it drops the row's
   alignment and takes the tinted ground that says which of the
   roles below the panel is the one being written to. */
.mrol-item.is-editing {
    display: block;
    background: var(--surface-sunken);
}

.mrol-main { flex: 1; min-width: 0; }

.mrol-name {
    font-size: var(--fs-sm);
    font-weight: var(--fw-semibold);
    color: var(--text-primary);
}

/* The key is what seeds and policy checks refer to. Shown under the
   name because renaming does not change it, and half of why anyone
   opens this panel is to find out which row a key belongs to. */
.mrol-key {
    margin-block-start: 2px;
    font-family: var(--font-mono);
    font-size: var(--fs-2xs);
    color: var(--text-disabled);
    overflow-wrap: anywhere;
}

.mrol-desc {
    margin: 4px 0 0;
    font-size: var(--fs-xs);
    line-height: var(--lh-snug);
    color: var(--text-secondary);
}

.mrol-side {
    flex-shrink: 0;
    display: flex;
    align-items: center;
    gap: 8px;
}

.mrol-users {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    padding: 3px 8px;
    border-radius: 999px;
    background: var(--surface-sunken);
    box-shadow: inset 0 0 0 1px var(--border-subtle);
    font-family: var(--font-mono);
    font-size: var(--fs-2xs);
    color: var(--text-secondary);
}

.mrol-users svg { color: var(--n-400); }

.mrol-form {
    display: flex;
    flex-direction: column;
    gap: 10px;
}

.mrol-form-foot {
    display: flex;
    align-items: center;
    gap: 8px;
}

/* ---------- The hand-off to the new-role drawer ---------- */

/* Not a form: a role is created in its own drawer, which asks about
   access and data scope as well and writes the grants for the
   answers. This is the door to it, sized like a row of the list
   above so the panel reads as one column. */
.mrol-add {
    display: flex;
    align-items: center;
    gap: 12px;
    inline-size: 100%;
    padding: 14px;
    border: 1px dashed var(--border-strong);
    border-radius: var(--r-lg);
    background: var(--surface-raised);
    text-align: start;
    cursor: pointer;
}

.mrol-add:hover {
    border-color: var(--p-400);
    background: var(--p-50);
}

.mrol-add:focus-visible {
    outline: none;
    box-shadow: var(--shadow-focus);
}

.mrol-add > svg { flex-shrink: 0; color: var(--p-600); }

.mrol-add-i {
    flex-shrink: 0;
    display: grid;
    place-items: center;
    inline-size: 32px;
    block-size: 32px;
    border-radius: var(--r-md);
    background: var(--p-600);
    color: var(--text-inverse);
}

.mrol-add-txt { flex: 1; min-width: 0; }

.mrol-add-t {
    display: block;
    font-size: var(--fs-sm);
    font-weight: var(--fw-semibold);
    color: var(--text-primary);
}

.mrol-add-s {
    display: block;
    margin-block-start: 2px;
    font-size: var(--fs-xs);
    line-height: var(--lh-snug);
    color: var(--text-secondary);
}

.mrol-foot-note {
    font-size: var(--fs-xs);
    color: var(--text-tertiary);
}

/* Under this the drawer is the screen, and a row that keeps its user
   count and Edit button on the right leaves the description about
   four words wide. Stack instead: the actions go under the text, at
   its end, which is where the thumb already is. */
@media (max-width: 520px) {
    .mrol-item { align-items: stretch; flex-direction: column; gap: 10px; }
    .mrol-side { justify-content: flex-end; }
    .mrol-add { align-items: flex-start; }
    .mrol-add > svg { margin-block-start: 6px; }
}


/* ============================================================
   74. Candidate: Edit profile (cpe-*)

   /me/profile/edit was the last candidate screen still drawn
   entirely in style attributes: six QCards in a 720px column and
   roughly a hundred inline declarations, four of which built the
   same on/off switch out of two nested <div>s with a click
   handler.

   That shape had four real costs.

     the switches were not controls  a <div @onclick> is not
                                     focusable, has no role and
                                     announces nothing - so the
                                     four switches that decide who
                                     can see this profile could not
                                     be reached from a keyboard at
                                     all, and a screen reader read
                                     four unlabelled boxes
     nothing had a hover or a ring   inline styles cannot express
                                     either, so no control on the
                                     page gave feedback under the
                                     pointer or when tabbed to
     every Save was below the fold   each of the six sections ended
                                     in its own button at the
                                     bottom of its own card, and
                                     the acknowledgement was a
                                     green span drawn at whatever
                                     padding that section picked
     nothing said what was missing   six sections have to be opened
                                     and read to find out which
                                     ones are still empty, which is
                                     the whole question a candidate
                                     opens this page with

   So it moves onto the chrome /me/settings already uses - .set /
   .set-nav / .set-main from "18. Settings hub", QSettingsSection
   per section, .lst rows, QToggle, and a QSaveBar on the floor of
   each section. The rail is a table of contents, not a switcher:
   seven sections is a scroll, and every one stays on the page.
   What the rail adds is the missing answer - a badge per row
   saying how many roles, how many skills, whether there is a photo
   - and the profile-strength meter at its foot, which is the same
   figure the profile page leads with and which appeared nowhere in
   the editor that moves it.

   What is here, and it is deliberately short:
     .cpe-sec     a section as a jump target - the scroll offset
                  that keeps its head clear of the pinned header
     .cpe-meter   profile strength, under the table of contents
     .cpe-photo   the avatar, what is set, and the file control
     .cpe-tag     a skill, with a remove button rather than a glyph
     .cpe-row     one entry in the experience / education list, in
                  one of its three states
     .cpe-form    the add / edit form both lists share
     .cpe-verify  the referee panel, which is the one place on the
                  page that writes to a third party

   Everything else is the shared vocabulary: .card, .fld, .input,
   .lst, .btn, .switch, .int-banner, .q-empty. Nothing here
   restyles any of them.
   ============================================================ */

/* .pg-head pins at -8px and stands 56px tall, so its lower edge is
   48px down the scroll port; 12px more is the gap between bands
   everywhere else, and a section head lands clear of it rather
   than under it. */
.cpe-sec { scroll-margin-block-start: 60px; }

/* Smooth only where it is wanted: a jump the reader asked for is
   easier to follow when the page travels, but the OS setting wins. */
@media (prefers-reduced-motion: no-preference) {
    .cpe { scroll-behavior: smooth; }
}

/* ---------- The rail ----------

   .set-link truncates its label, which is right for a seeded menu
   of nineteen where the reader is hunting for a name they already
   know. Seven section headings are short enough to keep whole, and
   "Salary preferences" clipped to "Salary prefe..." loses the half
   that says which preferences. */
.cpe .set-link { height: auto; min-height: 34px; padding-block: 7px; }
.cpe .set-link-lab { white-space: normal; overflow: visible; line-height: 1.35; }

/* Below 1080px the rail is a horizontal strip of chips, and a chip
   that wraps to two lines makes the whole strip twice as tall. The
   rows go back to one clipped line at exactly the width where they
   stop being rows. */
@media (max-width: 1080px) {
    .cpe .set-link-lab { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
}

/* ---------- Profile strength ----------

   The one number that describes the whole page, at the foot of the
   thing that lists its parts. It is the same figure the profile
   page leads with, and until now it appeared nowhere in the editor
   that actually moves it. */
.cpe-meter {
    margin: 6px 2px 2px;
    padding: 10px;
    border-radius: var(--r-md);
    background: var(--n-25);
    box-shadow: inset 0 0 0 1px var(--border-subtle);
}

.cpe-meter-top {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 8px;
    margin-block-end: 7px;
}

.cpe-meter-lab {
    font-size: var(--fs-2xs);
    font-weight: var(--fw-semibold);
    letter-spacing: 0.05em;
    text-transform: uppercase;
    color: var(--text-tertiary);
}

.cpe-meter-v {
    font-family: var(--font-mono);
    font-variant-numeric: tabular-nums;
    font-size: var(--fs-md);
    font-weight: var(--fw-bold);
    color: var(--text-primary);
}

.cpe-meter-bar {
    height: 6px;
    border-radius: var(--r-full);
    background: var(--n-150);
    overflow: hidden;
}

.cpe-meter-fill {
    display: block;
    height: 100%;
    border-radius: var(--r-full);
    transition: width var(--dur-mid) var(--ease-out);
}

/* The same three tones the strength pill in the page header takes,
   so the bar and the pill never disagree about how far along this
   profile is. */
.cpe-meter-good { background: var(--success-500); }
.cpe-meter-warn { background: var(--gold-500); }
.cpe-meter-bad  { background: var(--danger-500); }

@media (prefers-reduced-motion: reduce) {
    .cpe-meter-fill { transition: none; }
}

/* ---------- Spacing this page owns ----------
   Named rules rather than style attributes, because the point of
   the rewrite is that a style attribute carries no state and no
   breakpoint. */
.cpe-gap { margin-block-start: 12px; }
.cpe-gap-sm { margin-block-start: 8px; }
.cpe-none { margin: 0 0 12px; }

/* A banner that belongs to the card above it, inset to the card's
   own padding rather than flush to its border. */
.cpe-note { padding: 0 16px 16px; }

/* A block under a padded card body that is a list rather than more
   fields: .lst drops the rule above its first row, so the boundary
   has to be drawn here. */
.cpe-sub { border-block-start: 1px solid var(--border-subtle); }

/* ---------- The photo ---------- */

.cpe-photo {
    display: flex;
    align-items: center;
    gap: 16px;
    flex-wrap: wrap;
}

/* Bigger than any .avatar size class: on this card the photo is
   the subject rather than a row marker. The halo is a shadow
   rather than a border - at this diameter a 4px ring reads as a
   frame around the picture. */
.cpe-face { flex-shrink: 0; display: flex; }

.cpe-face .avatar {
    width: 72px;
    height: 72px;
    font-size: var(--fs-2xl);
    box-shadow: 0 0 0 4px var(--surface-raised), var(--shadow-sm);
}

.cpe-photo-txt { flex: 1 1 220px; min-width: 0; }

/* Whether there is a photo at all, said in words. The old card
   showed initials in a circle and left the reader to work out that
   a circle with letters in it meant no photo had been uploaded. */
.cpe-photo-now {
    margin: 0 0 3px;
    font-size: var(--fs-md);
    font-weight: var(--fw-semibold);
    color: var(--text-primary);
}

/* A <label> wearing .btn. It needs the two things a browser gives
   a <button> and not a <label>: the pointer, and a disabled look
   while the upload is in flight - :disabled cannot reach it. */
.cpe-file { cursor: pointer; flex-shrink: 0; }
.cpe-file.is-busy { opacity: 0.6; cursor: progress; }

/* Hidden rather than dropped: a bare file input is the one control
   in the product no stylesheet can reach, and the label above is
   what the reader actually presses. Clipped rather than
   display:none, so it can still take focus through its label. */
.cpe-file-in {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
}

/* ---------- Skills ---------- */

.cpe-tags {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
    margin-block-end: 14px;
}

/* .chip-plain plus a remove button. Not .chip-plain itself: that
   chip is explicitly for a label with no state, and these are
   pressable at one end. */
.cpe-tag {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    padding: 3px 4px 3px 10px;
    border-radius: var(--r-full);
    background: var(--n-75);
    color: var(--text-secondary);
    font-size: var(--fs-xs);
    font-weight: var(--fw-medium);
    white-space: nowrap;
}

/* An AI-verified skill is worth more to a recruiter than a
   self-reported one, and the profile page already says so. The
   editor was the one screen where the two looked identical. */
.cpe-tag.is-ok {
    background: var(--success-50);
    color: var(--success-700);
    box-shadow: inset 0 0 0 1px var(--success-100);
}

.cpe-tag > svg { flex-shrink: 0; }

.cpe-tag-n { min-width: 0; }

/* A real button with a real target. It was a 14px glyph inside a
   span with a click handler: not focusable, not announced, and
   below every guideline for a pointer target. */
.cpe-tag-x {
    display: grid;
    place-items: center;
    width: 18px;
    height: 18px;
    flex-shrink: 0;
    border-radius: var(--r-full);
    color: var(--text-tertiary);
    transition: background-color var(--dur-fast) var(--ease-out),
                color var(--dur-fast) var(--ease-out);
}

.cpe-tag-x:hover { background: var(--danger-50); color: var(--danger-600); }
.cpe-tag-x:focus-visible { outline: none; box-shadow: var(--shadow-focus); }

/* The field and the button that acts on it, on one line. */
.cpe-addrow { display: flex; align-items: center; gap: 8px; }
.cpe-addrow .input { flex: 1; min-width: 0; }

/* ---------- One entry, in one of three states ---------- */

.cpe-row { padding: 14px 16px; }
.cpe-row + .cpe-row { border-block-start: 1px solid var(--border-subtle); }

.cpe-entry { display: flex; align-items: flex-start; gap: 12px; }

/* An icon tile rather than the company's initials in a grey
   circle. Initials next to the company name is the name twice;
   the mark is there to say what kind of row this is. */
.cpe-entry-mark {
    display: grid;
    place-items: center;
    width: 32px;
    height: 32px;
    flex-shrink: 0;
    margin-block-start: 1px;
    border-radius: var(--r-md);
    background: var(--p-50);
    color: var(--p-700);
}

.cpe-entry-txt { flex: 1; min-width: 0; }

.cpe-entry-top {
    display: flex;
    align-items: center;
    gap: 8px;
    flex-wrap: wrap;
}

.cpe-entry-name {
    font-size: var(--fs-md);
    font-weight: var(--fw-semibold);
    color: var(--text-primary);
}

.cpe-entry-org {
    margin-block-start: 1px;
    font-size: var(--fs-sm);
    color: var(--text-secondary);
}

/* The period on its own line with a mark against it, rather than
   run into the company with a middot. A date is the thing a
   history is skimmed by; it should be findable at a glance. */
.cpe-entry-when {
    display: flex;
    align-items: center;
    gap: 5px;
    margin-block-start: 4px;
    font-family: var(--font-mono);
    font-variant-numeric: tabular-nums;
    font-size: var(--fs-xs);
    color: var(--text-tertiary);
}

.cpe-entry-when svg { color: var(--n-400); flex-shrink: 0; }

.cpe-entry-desc {
    margin: 7px 0 0;
    max-width: 70ch;
    font-size: var(--fs-sm);
    line-height: 1.55;
    color: var(--text-secondary);
}

.cpe-entry-acts {
    display: flex;
    align-items: center;
    gap: 6px;
    flex-shrink: 0;
}

/* Delete is the one destructive control in the list, and it was a
   red word in a grey-bordered box exactly like the Edit beside it.
   Ghost until it is reached for, then red. */
.btn.cpe-del { color: var(--text-tertiary); }

.btn.cpe-del:hover:not(:disabled) {
    background: var(--danger-50);
    border-color: var(--danger-200);
    color: var(--danger-600);
}

/* ---------- The add / edit form ---------- */

/* Sunken, so an open form reads as a working surface inside the
   list rather than as another entry in it. */
.cpe-form {
    padding: 14px;
    border-radius: var(--r-lg);
    background: var(--surface-sunken);
    box-shadow: inset 0 0 0 1px var(--border-subtle);
}

.cpe-form-cap {
    margin: 0 0 12px;
    font-size: var(--fs-2xs);
    font-weight: var(--fw-semibold);
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--text-tertiary);
}

/* The one switch inside a form, which is a statement about what
   the form is for rather than a field of it. */
.cpe-form-sw {
    display: flex;
    align-items: center;
    gap: 10px;
    margin-block-start: 14px;
}

.cpe-form-sw-lab {
    font-size: var(--fs-md);
    font-weight: var(--fw-medium);
    color: var(--text-primary);
}

.cpe-form-foot {
    display: flex;
    justify-content: flex-end;
    gap: 8px;
    margin-block-start: 14px;
}

/* The foot of a list: where the form opens, and where the button
   that opens it sits while it is closed. */
.cpe-foot {
    padding: 14px 16px;
    border-block-start: 1px solid var(--border-subtle);
    background: var(--n-25);
    border-end-start-radius: var(--r-lg);
    border-end-end-radius: var(--r-lg);
}

/* Dashed and full width: it is the end of a list rather than an
   action on what is above it, and the dashes say "another one goes
   here" in the same language the empty state uses. */
.cpe-add {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 7px;
    width: 100%;
    height: 38px;
    border: 1px dashed var(--border-strong);
    border-radius: var(--r-md);
    background: transparent;
    color: var(--text-secondary);
    font-size: var(--fs-md);
    font-weight: var(--fw-medium);
    transition: border-color var(--dur-fast) var(--ease-out),
                background-color var(--dur-fast) var(--ease-out),
                color var(--dur-fast) var(--ease-out);
}

.cpe-add:hover {
    border-color: var(--p-700);
    background: var(--p-50);
    color: var(--text-brand);
}

.cpe-add:focus-visible { outline: none; box-shadow: var(--shadow-focus); }

/* ---------- The referee panel ---------- */

/* Tinted navy rather than sunken grey, because this is the one
   control on the page that writes to somebody else: sending it
   emails a third party, and it should not look like the rest of
   the form. */
.cpe-verify {
    margin-block-start: 12px;
    padding: 14px;
    border-radius: var(--r-lg);
    background: var(--p-50);
    box-shadow: inset 0 0 0 1px var(--p-100);
}

.cpe-verify-h {
    margin: 0;
    font-size: var(--fs-md);
    font-weight: var(--fw-semibold);
    color: var(--text-primary);
}

.cpe-verify-b {
    margin: 4px 0 0;
    max-width: 70ch;
    font-size: var(--fs-sm);
    line-height: 1.55;
    color: var(--text-secondary);
}

/* The salary question, which is a consent rather than a field. */
.cpe-check {
    display: flex;
    align-items: center;
    gap: 8px;
    margin-block-start: 12px;
    font-size: var(--fs-md);
    color: var(--text-primary);
    cursor: pointer;
}

/* ---------- Narrow ---------- */

@media (max-width: 700px) {
    .cpe-row,
    .cpe-foot { padding-inline: 14px; }

    /* Three small icon buttons beside a wrapped title is a row that
       has run out of width; under it they have the whole of it. */
    .cpe-entry { flex-wrap: wrap; }
    .cpe-entry-acts { width: 100%; justify-content: flex-end; }

    /* The photo controls stack under the avatar rather than being
       squeezed beside it. */
    .cpe-photo { gap: 14px; }
    .cpe-file { width: 100%; }

    .cpe-form-foot .btn { flex: 1 1 auto; justify-content: center; }
}


/* ============================================================
   75. Candidate: the full profile editor drawer (cped-*)

   The hero button on /me/profile says "Edit profile" and opened
   the basics drawer - five fields of a record that has eight
   sections, and none of the pay preferences, which the profile
   page never showed at all. The one control that names the whole
   thing delivered a sixth of it, and the rest was only reachable
   by leaving the page for /me/profile/edit.

   So this is that editor, in the container the rest of the screen
   already uses. Two decisions are worth writing down.

   The rail switches, it does not scroll. On /me/profile/edit the
   rail is a table of contents and every section stays on the page,
   which is right for a page: the scroll is the whole viewport. A
   drawer body is a fixed-height scroll port about 600px tall, so
   the same shape would be six sections stacked inside a window.
   One pane at a time, and the rail carries what each section holds
   so nothing is hidden by being closed.

   The body is the only part that scrolls, and the rail is inside
   it rather than beside it. .drw-body is the scroll container, so
   a sticky rail pins against the drawer's own top edge and the
   pane moves under it - the same relationship .set-nav has with
   the page on /me/settings, one level in.

   What is here, and it is only the chrome:
     .cped        the rail-plus-pane frame inside .drw-body
     .cped-nav    the rail: sections, their state, a dot on any
                  with changes the footer has not committed
     .cped-pane   the working column, and its head
     .cped-auto   the note on a pane that writes as you go
     .cped-foot-note  what Save is about to write, named

   Everything inside a pane is the .cpe-* vocabulary from "74.
   Candidate: Edit profile" plus the shared .card / .fld / .lst /
   .input. That is deliberate: the page editor and the drawer
   editor are one design, and a field should not change shape
   because of the container it is in.
   ============================================================ */

/* Wider than any other drawer in the product, because this one is a
   rail and a form rather than a form. .drw already caps at 100vw. */
.cped-drw .drw-body { padding: 16px; }

.cped {
    display: grid;
    grid-template-columns: 212px minmax(0, 1fr);
    gap: 16px;
    align-items: start;
}

/* ---------- The rail ---------- */

/* Pinned against the top of the scrolling body, capped so a rail
   longer than the port keeps following the scroll instead of
   riding up out of it - the same rule .set-nav carries. */
.cped-nav {
    position: sticky;
    inset-block-start: 0;
    z-index: 2;
    max-height: calc(100vh - 190px);
    overflow-y: auto;
    overscroll-behavior: contain;
    padding: 6px;
    background: var(--surface-raised);
    border: 1px solid var(--border-default);
    border-radius: var(--r-lg);
    box-shadow: var(--shadow-xs);
}

.cped-nav-lab {
    margin: 0;
    padding: 6px 10px 5px;
    font-size: var(--fs-2xs);
    font-weight: var(--fw-semibold);
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--text-tertiary);
}

/* A button, not an anchor: it switches the pane beside it rather
   than going anywhere. Which means it needs the three things a
   browser gives a button and a stylesheet has to take back - the
   padding, the border and the background. */
.cped-link {
    position: relative;
    display: flex;
    align-items: center;
    gap: 9px;
    width: 100%;
    min-height: 34px;
    padding: 6px 10px;
    border: none;
    border-radius: var(--r-md);
    background: transparent;
    color: var(--text-secondary);
    font-size: var(--fs-sm);
    font-weight: var(--fw-medium);
    letter-spacing: -0.005em;
    text-align: start;
    cursor: pointer;
    transition: background-color var(--dur-fast) var(--ease-out),
                color var(--dur-fast) var(--ease-out);
}

.cped-link + .cped-link { margin-block-start: 1px; }

/* The tile is what carries the icon at this size: a 13px stroke
   glyph sitting directly on the card is a smudge beside a 12.5px
   label; on a tinted square it reads as a mark. */
.cped-link-ico {
    display: grid;
    place-items: center;
    width: 22px;
    height: 22px;
    flex-shrink: 0;
    border-radius: var(--r-sm);
    background: var(--n-75);
    color: var(--n-500);
    transition: background-color var(--dur-fast) var(--ease-out),
                color var(--dur-fast) var(--ease-out);
}

.cped-link-ico svg { color: inherit; }

.cped-link-lab {
    flex: 1;
    min-width: 0;
    line-height: 1.35;
}

.cped-link-badge {
    flex-shrink: 0;
    padding: 1px 6px;
    border-radius: var(--r-full);
    background: var(--n-100);
    color: var(--text-secondary);
    font-size: 10px;
    font-weight: var(--fw-bold);
    line-height: 1.5;
}

.cped-link:hover { background: var(--n-50); color: var(--text-primary); }
.cped-link:hover .cped-link-ico { background: var(--n-100); color: var(--n-600); }

.cped-link:focus-visible { outline: none; box-shadow: var(--shadow-focus); }

.cped-link.is-on {
    background: var(--p-50);
    color: var(--text-brand);
    font-weight: var(--fw-semibold);
}

.cped-link.is-on .cped-link-ico {
    background: var(--surface-raised);
    color: var(--p-700);
    box-shadow: var(--shadow-xs);
}

.cped-link.is-on .cped-link-badge {
    background: var(--p-100);
    color: var(--p-700);
}

/* The band down the open row's leading edge, the same mark
   .set-link.is-on uses so the two rails read as one idiom. */
.cped-link.is-on::before {
    content: "";
    position: absolute;
    inset-block: 7px;
    inset-inline-start: 0;
    width: 2px;
    border-radius: var(--r-full);
    background: var(--p-700);
}

/* A section holding changes the footer has not written yet. Gold
   rather than red: it is work in progress, not a problem, and gold
   is the one accent in the palette that means "look here" without
   meaning "something is wrong". */
.cped-dot {
    width: 6px;
    height: 6px;
    flex-shrink: 0;
    border-radius: var(--r-full);
    background: var(--gold-500);
}

/* ---------- The working pane ---------- */

.cped-pane {
    display: flex;
    flex-direction: column;
    gap: 12px;
    min-width: 0;
}

.cped-pane-head { min-width: 0; }

.cped-pane-h {
    margin: 0;
    font-size: var(--fs-xl);
    font-weight: var(--fw-semibold);
    letter-spacing: -0.01em;
    color: var(--text-primary);
}

.cped-pane-s {
    margin: 3px 0 0;
    max-width: 66ch;
    font-size: var(--fs-sm);
    line-height: 1.5;
    color: var(--text-secondary);
}

/* The note on a pane whose writes have already happened. Without
   it the footer's Save looks like it acts on the list, and a
   candidate who closes the drawer with Cancel would reasonably
   expect the skill they just added to be gone. */
.cped-auto {
    display: flex;
    align-items: center;
    gap: 7px;
    margin: 0;
    padding: 8px 11px;
    border-radius: var(--r-md);
    background: var(--info-50);
    color: var(--info-700);
    font-size: var(--fs-xs);
    line-height: 1.45;
}

.cped-auto svg { flex-shrink: 0; }

/* ---------- The footer's note ---------- */

/* Named sections rather than a bare "unsaved changes": on a six
   section editor, which one is the whole question. .drw-foot-lead
   pushes it to the far end from the buttons it explains. */
.cped-foot-note {
    display: flex;
    align-items: center;
    gap: 6px;
    margin-inline-end: auto;
    min-width: 0;
    font-size: var(--fs-sm);
    font-weight: var(--fw-medium);
    line-height: 1.4;
    color: var(--text-tertiary);
}

.cped-foot-note svg { flex-shrink: 0; }

.cped-foot-note.is-pending { color: var(--gold-700); }

/* ---------- Narrow ---------- */

/* A 212px rail inside a panel that is now the width of the phone
   is a third of the editor spent on navigation. The same rows
   become a scrolling strip of chips above the pane - the fold
   .set-nav makes at 1080px, at the width a drawer reaches it. */
@media (max-width: 860px) {
    .cped {
        grid-template-columns: minmax(0, 1fr);
        gap: 12px;
    }

    .cped-nav {
        display: flex;
        gap: 4px;
        max-height: none;
        overflow-x: auto;
        overflow-y: hidden;
        overscroll-behavior-x: contain;
    }

    .cped-nav-lab { display: none; }

    .cped-link {
        width: auto;
        flex: 0 0 auto;
        margin-block-start: 0;
    }

    /* One line in a chip, however long the heading. */
    .cped-link-lab { white-space: nowrap; }

    /* A 2px bar down the leading edge of a chip is a hairline
       against its own corner radius; the fill is the marker here. */
    .cped-link.is-on::before { display: none; }

    /* The note wraps to its own row above the buttons rather than
       squeezing them - .drw-foot already wraps, so it only needs
       to stop claiming the leading end. */
    .cped-foot-note { flex: 1 1 100%; margin-inline-end: 0; }
}


/* ============================================================
   76. Candidate: Resume (crs-*)

   /me/profile/resume was a 860px column of two QCards drawn in
   style attributes: an upload box, then a flat list where the
   resume in use looked like every other row apart from a pill,
   and each row carried five same-weight buttons.

   The page has one question to answer first - which resume am I
   sending? - so that one leads, as a drawn page with its actions
   beside it. Every other version is a quiet row under it with one
   labelled action (Use this) and the rest as icon buttons. Upload
   moves to a side column with the three-step explanation, since
   Upload, Enhance and Use this only make sense together.

   What is here:
     .crs-head      title band with the resume count
     .crs-feature   the resume in use: page, name, actions
     .crs-paper     a drawn sheet - a thumbnail stand-in
     .crs-ext       the file-type tag (PDF / DOCX / TXT)
     .crs-list      the other versions, one .crs-doc per file
     .crs-drop      the upload zone; the file input covers it,
                    transparent, so a dropped file is accepted
     .crs-how       the three steps
     .crs-empty     no resumes at all

   Controls are the shared ones: .card, .btn, QPill, .int-banner.
   ============================================================ */

.crs { display: flex; flex-direction: column; gap: 14px; }

.crs-loading {
    display: grid;
    place-items: center;
    height: 200px;
    color: var(--text-tertiary);
    font-size: var(--fs-md);
}

/* ---------- Title band ---------- */

.crs-head {
    position: relative;
    overflow: hidden;
    display: flex;
    align-items: center;
    gap: 16px;
    padding: 18px 22px;
    background: var(--surface-raised);
    border: 1px solid var(--border-default);
    border-radius: var(--r-xl);
    box-shadow: var(--shadow-xs);
}

/* The same wash and gold orb as the profile band, so the two tabs
   read as one page. */
.crs-head::before {
    content: "";
    position: absolute;
    inset: 0;
    background: linear-gradient(115deg, var(--p-50) 0%, rgba(255, 250, 235, 0.85) 52%, rgba(255, 255, 255, 0) 88%);
    pointer-events: none;
}

.crs-head::after {
    content: "";
    position: absolute;
    inset-block-start: -110px;
    inset-inline-end: 6%;
    width: 240px;
    height: 240px;
    border-radius: var(--r-full);
    background: radial-gradient(circle, rgba(245, 184, 0, 0.20), rgba(245, 184, 0, 0) 66%);
    pointer-events: none;
}

.crs-head > * { position: relative; }

.crs-head-mark {
    display: grid;
    place-items: center;
    flex-shrink: 0;
    width: 46px;
    height: 46px;
    border-radius: var(--r-lg);
    background: var(--surface-raised);
    border: 1px solid var(--border-default);
    color: var(--p-700);
    box-shadow: var(--shadow-sm);
}

.crs-head-txt { flex: 1 1 auto; min-width: 0; }

.crs-title {
    margin: 0;
    font-size: var(--fs-2xl);
    font-weight: var(--fw-bold);
    letter-spacing: -0.02em;
    line-height: var(--lh-tight);
    color: var(--text-primary);
}

.crs-sub {
    margin: 4px 0 0;
    max-width: 62ch;
    font-size: var(--fs-md);
    line-height: var(--lh-snug);
    color: var(--text-secondary);
}

.crs-head-count {
    display: flex;
    flex-direction: column;
    align-items: flex-end;
    gap: 2px;
    flex-shrink: 0;
    padding-inline-start: 18px;
    border-inline-start: 1px solid var(--border-subtle);
}

.crs-head-n {
    font-family: var(--font-mono);
    font-variant-numeric: tabular-nums;
    font-size: var(--fs-3xl);
    font-weight: var(--fw-semibold);
    line-height: 1;
    color: var(--text-primary);
}

.crs-notice {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    flex-wrap: wrap;
}

.crs-notice-txt { display: inline-flex; align-items: center; gap: 6px; }

.crs-link {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    font-weight: var(--fw-semibold);
    color: var(--p-700);
    text-decoration: none;
    white-space: nowrap;
}

.crs-link:hover { text-decoration: underline; }

/* ---------- Layout ---------- */

.crs-grid {
    display: grid;
    grid-template-columns: minmax(0, 1fr) 300px;
    gap: 14px;
    align-items: start;
}

.crs-main,
.crs-side { display: flex; flex-direction: column; gap: 14px; min-width: 0; }

.crs-side { position: sticky; inset-block-start: 12px; }

/* ---------- The file-type tag ---------- */

.crs-ext {
    display: inline-flex;
    align-items: center;
    height: 18px;
    padding: 0 6px;
    border-radius: var(--r-xs);
    font-family: var(--font-mono);
    font-size: 10px;
    font-style: normal;
    font-weight: var(--fw-semibold);
    letter-spacing: 0.04em;
    color: var(--n-0);
    background: var(--n-500);
}

.crs-ext.is-pdf  { background: var(--danger-600); }
.crs-ext.is-docx { background: var(--info-600); }
.crs-ext.is-txt  { background: var(--n-500); }

/* ---------- The drawn page ---------- */

.crs-paper {
    position: relative;
    flex-shrink: 0;
    display: flex;
    flex-direction: column;
    gap: 6px;
    width: 132px;
    aspect-ratio: 1 / 1.3;
    padding: 18px 14px;
    border: 1px solid var(--border-default);
    border-radius: var(--r-sm);
    background: var(--n-0);
    box-shadow: var(--shadow-md);
    cursor: pointer;
    transition: transform var(--dur-fast) var(--ease-out),
                box-shadow var(--dur-fast) var(--ease-out);
}

/* The lines of text. */
.crs-paper > span {
    display: block;
    height: 4px;
    border-radius: 2px;
    background: var(--n-150);
}

.crs-paper > span:nth-child(3n)   { width: 72%; }
.crs-paper > span:nth-child(3n+1) { width: 88%; }

.crs-paper > .crs-paper-top {
    width: 58%;
    height: 7px;
    margin-block-end: 6px;
    background: var(--p-700);
}

.crs-paper > .crs-ext {
    position: absolute;
    inset-inline-start: 10px;
    inset-block-end: 10px;
}

/* A view affordance that appears under the pointer, so the sheet
   says it opens before it is clicked. */
.crs-paper-eye {
    position: absolute;
    inset: 0;
    display: grid;
    place-items: center;
    border-radius: inherit;
    background: rgba(15, 19, 32, 0.45);
    color: var(--n-0);
    opacity: 0;
    transition: opacity var(--dur-fast) var(--ease-out);
}

.crs-paper:hover:not(:disabled),
.crs-paper:focus-visible {
    transform: translateY(-2px) rotate(-1deg);
    box-shadow: var(--shadow-lg);
}

.crs-paper:hover:not(:disabled) .crs-paper-eye,
.crs-paper:focus-visible .crs-paper-eye { opacity: 1; }

.crs-paper:focus-visible { outline: none; box-shadow: var(--shadow-focus), var(--shadow-lg); }
.crs-paper:disabled { cursor: progress; }

/* ---------- The resume in use ---------- */

.crs-feature {
    position: relative;
    display: flex;
    gap: 22px;
    padding: 22px;
    background: linear-gradient(135deg, var(--surface-raised) 55%, var(--p-50) 100%);
    border: 1px solid var(--p-200);
    border-radius: var(--r-xl);
    box-shadow: var(--shadow-sm);
}

/* The brand edge that marks this card as the chosen one. */
.crs-feature::before {
    content: "";
    position: absolute;
    inset-block: 18px;
    inset-inline-start: -1px;
    width: 3px;
    border-radius: 0 3px 3px 0;
    background: var(--p-700);
}

.crs-feature-body {
    display: flex;
    flex-direction: column;
    flex: 1 1 auto;
    min-width: 0;
}

.crs-pills { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; margin-block-end: 8px; }

.crs-feature-name {
    margin: 0 0 3px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-size: var(--fs-xl);
    font-weight: var(--fw-semibold);
    letter-spacing: -0.01em;
    color: var(--text-primary);
}

.crs-feature-hint {
    margin: 12px 0 0;
    max-width: 52ch;
    font-size: var(--fs-sm);
    line-height: 1.55;
    color: var(--text-secondary);
}

.crs-feature-acts {
    display: flex;
    align-items: center;
    gap: 8px;
    flex-wrap: wrap;
    margin-block-start: auto;
    padding-block-start: 16px;
}

.crs-feature-acts .crs-del { margin-inline-start: auto; }

/* ---------- Other versions ---------- */

.crs-list { padding: 6px 0 4px; }

.crs-list-title {
    margin: 0;
    padding: 10px 18px 8px;
    font-size: var(--fs-md);
    font-weight: var(--fw-semibold);
    color: var(--text-primary);
}

.crs-list-empty {
    padding: 4px 18px 14px;
    font-size: var(--fs-sm);
    color: var(--text-tertiary);
}

.crs-doc {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 11px 18px;
    border-block-start: 1px solid var(--border-subtle);
    transition: background-color var(--dur-fast) var(--ease-out);
}

.crs-doc:hover { background: var(--surface-hover); }

.crs-doc-mark {
    position: relative;
    display: grid;
    place-items: center;
    flex-shrink: 0;
    width: 38px;
    height: 44px;
    border-radius: var(--r-sm);
    border: 1px solid var(--border-default);
    background: var(--n-0);
    color: var(--n-400);
}

.crs-doc-mark > em {
    position: absolute;
    inset-inline-start: -5px;
    inset-block-end: 4px;
    padding: 1px 4px;
    border-radius: var(--r-xs);
    font-family: var(--font-mono);
    font-size: 8.5px;
    font-style: normal;
    font-weight: var(--fw-bold);
    letter-spacing: 0.03em;
    color: var(--n-0);
    background: var(--n-500);
}

.crs-doc-mark.is-pdf > em  { background: var(--danger-600); }
.crs-doc-mark.is-docx > em { background: var(--info-600); }

.crs-doc-main { flex: 1 1 auto; min-width: 0; }

.crs-doc-name {
    display: flex;
    align-items: center;
    gap: 6px;
    min-width: 0;
    margin-block-end: 2px;
    font-size: var(--fs-md);
    font-weight: var(--fw-semibold);
    color: var(--text-primary);
}

.crs-doc-acts {
    display: flex;
    align-items: center;
    gap: 4px;
    flex-shrink: 0;
}

/* Secondary actions stay quiet until the row is in play. */
.crs-doc-acts .btn-ghost { border-color: transparent; }
.crs-doc:hover .crs-doc-acts .btn-ghost,
.crs-doc-acts .btn-ghost:focus-visible { border-color: var(--border-subtle); }

.crs-ai svg { color: var(--gold-600); }

.crs-del:hover:not(:disabled) {
    background: var(--danger-50);
    border-color: var(--danger-200);
    color: var(--danger-700);
}

/* ---------- Upload ---------- */

.crs-drop {
    position: relative;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 4px;
    padding: 26px 18px 22px;
    text-align: center;
    background: var(--surface-sunken);
    border: 2px dashed var(--border-strong);
    box-shadow: none;
    transition: border-color var(--dur-fast) var(--ease-out),
                background-color var(--dur-fast) var(--ease-out);
}

.crs-drop:hover,
.crs-drop:focus-within {
    border-color: var(--p-700);
    background: var(--p-50);
}

.crs-drop:focus-within { box-shadow: var(--shadow-focus); }

.crs-drop-mark {
    display: grid;
    place-items: center;
    width: 48px;
    height: 48px;
    margin-block-end: 8px;
    border-radius: var(--r-full);
    background: var(--p-700);
    color: var(--n-0);
    box-shadow: 0 0 0 6px var(--p-100);
    transition: transform var(--dur-fast) var(--ease-out);
}

.crs-drop:hover .crs-drop-mark { transform: translateY(-2px); }

.crs-drop-title {
    font-size: var(--fs-lg);
    font-weight: var(--fw-semibold);
    color: var(--text-primary);
}

.crs-drop-cta { font-size: var(--fs-sm); color: var(--text-secondary); }

.crs-drop-fmt { display: flex; gap: 4px; margin: 10px 0 4px; }

/* Covers the whole zone so both a click and a drop land on it. */
.crs-drop-input {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    opacity: 0;
    cursor: pointer;
}

/* ---------- How it works ---------- */

.crs-how { padding: 16px 18px; }

.crs-steps {
    display: flex;
    flex-direction: column;
    gap: 14px;
    margin: 12px 0 0;
    padding: 0;
    list-style: none;
}

.crs-steps li {
    position: relative;
    display: flex;
    gap: 10px;
}

/* The thread joining the three numbers. */
.crs-steps li + li::before {
    content: "";
    position: absolute;
    inset-inline-start: 11px;
    inset-block-start: -14px;
    height: 14px;
    width: 1px;
    background: var(--border-default);
}

.crs-step-n {
    display: grid;
    place-items: center;
    flex-shrink: 0;
    width: 23px;
    height: 23px;
    border-radius: var(--r-full);
    background: var(--p-50);
    border: 1px solid var(--p-200);
    color: var(--p-700);
    font-size: var(--fs-xs);
    font-weight: var(--fw-bold);
}

.crs-step-n.is-ai {
    background: var(--gold-50);
    border-color: var(--gold-300);
    color: var(--gold-700);
}

.crs-step-t {
    font-size: var(--fs-sm);
    font-weight: var(--fw-semibold);
    color: var(--text-primary);
}

/* ---------- Nothing uploaded ---------- */

.crs-empty {
    display: flex;
    flex-direction: column;
    align-items: center;
    padding: 40px 24px;
    text-align: center;
    background: var(--surface-raised);
    border: 1px dashed var(--border-strong);
    border-radius: var(--r-xl);
}

.crs-paper-ghost {
    width: 92px;
    margin-block-end: 18px;
    padding: 14px 10px;
    cursor: default;
    box-shadow: var(--shadow-sm);
    transform: rotate(-4deg);
}

.crs-paper-ghost > .crs-paper-top { background: var(--n-300); }

.crs-empty-title {
    margin: 0;
    font-size: var(--fs-xl);
    font-weight: var(--fw-semibold);
    color: var(--text-primary);
}

.crs-empty-body {
    max-width: 420px;
    margin: 6px auto 18px;
    font-size: var(--fs-md);
    line-height: 1.55;
    color: var(--text-secondary);
}

/* ---------- Narrow ---------- */

@media (max-width: 980px) {
    .crs-grid { grid-template-columns: minmax(0, 1fr); }
    .crs-side { position: static; order: -1; }
    .crs-how { display: none; }
}

@media (max-width: 600px) {
    .crs-head { padding: 16px; }
    .crs-head-mark { display: none; }
    .crs-feature { flex-direction: column; padding: 18px; }
    .crs-paper { width: 100px; }
    .crs-feature-acts .btn { flex: 1 1 auto; justify-content: center; }
    .crs-feature-acts .crs-del { flex: 0 0 auto; margin-inline-start: 0; }
    .crs-doc { flex-wrap: wrap; padding-inline: 14px; }
    .crs-doc-acts { width: 100%; padding-inline-start: 50px; }
    .crs-doc-acts .btn-ghost { border-color: var(--border-subtle); }
}

@media (prefers-reduced-motion: reduce) {
    .crs-paper,
    .crs-drop-mark { transition: none; }
    .crs-paper:hover:not(:disabled),
    .crs-drop:hover .crs-drop-mark { transform: none; }
}

/* ============================================================
   77. Recruiter: the language manage drawer (lngd-*)

   Opened from Manage on a row of Settings -> Languages & i18n.
   Chrome is "58." - .cad-body, .cad-head-rich, .drw-sec and
   .drw-legend - and the status filter reuses the engine's
   segmented control from "31." (.lng-eng). What is here is only
   what this panel has:

     .lngd-mark   the language code as the header tile
     .lngd-sum    overall coverage: the number and a bar split
                  into approved / needs review / missing
     .lngd-ns     one namespace, as a pickable row that filters
                  the strings below it
     .lngd-mt     the machine-translate call to action
     .lngd-str    one string: key, source, translation, and the
                  Save that only appears once it has changed

   The bands (is-live / is-part) are the page's 90 and 50, so a
   row and the drawer it opens cannot disagree about readiness.
   ============================================================ */

.lngd-stack {
    display: flex;
    flex-direction: column;
    gap: 14px;
}

/* ---------- Header ---------- */

.lngd-mark {
    display: grid;
    place-items: center;
    width: 44px;
    height: 44px;
    flex-shrink: 0;
    border-radius: var(--r-lg);
    background: rgba(255, 255, 255, 0.14);
    color: var(--text-inverse);
    font-family: var(--font-mono);
    font-size: var(--fs-sm);
    font-weight: var(--fw-bold);
    text-transform: uppercase;
    letter-spacing: 0.02em;
}

.lngd-def {
    display: inline-flex;
    align-items: center;
    margin-inline-start: 8px;
    padding: 1px 8px;
    border-radius: var(--r-full);
    background: rgba(255, 255, 255, 0.12);
    color: var(--gold-400);
    font-size: var(--fs-2xs);
    font-weight: var(--fw-semibold);
    letter-spacing: 0.04em;
    text-transform: uppercase;
    vertical-align: 1px;
}

/* A control at the far end of a legend - Refresh, clear the filter. The
   legend is uppercase chrome, so the control carries its own case. */
.lngd-legend-act {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    margin-inline-start: auto;
    height: 24px;
    padding: 0 9px;
    border: 1px solid var(--border-subtle);
    border-radius: var(--r-md);
    background: var(--surface-raised);
    color: var(--text-secondary);
    font: inherit;
    font-size: var(--fs-2xs);
    font-weight: var(--fw-semibold);
    letter-spacing: 0.02em;
    text-transform: none;
    cursor: pointer;
    transition: border-color var(--dur-fast) var(--ease-out),
                color var(--dur-fast) var(--ease-out);
}

.lngd-legend-act:hover:not(:disabled) { border-color: var(--border-strong); color: var(--text-primary); }
.lngd-legend-act:focus-visible { outline: none; box-shadow: var(--shadow-focus); }
.lngd-legend-act:disabled { opacity: 0.55; cursor: not-allowed; }
.drw-legend .lngd-legend-act svg { color: currentColor; }

/* ---------- Overall ---------- */

.lngd-sum {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr);
    gap: 22px;
    align-items: center;
}

.lngd-sum-pct {
    display: flex;
    flex-direction: column;
    gap: 2px;
    min-width: 92px;
    padding-inline-end: 22px;
    border-inline-end: 1px solid var(--border-subtle);
}

.lngd-sum-n {
    font-size: 34px;
    line-height: 1;
    font-weight: var(--fw-bold);
    letter-spacing: -0.02em;
    font-variant-numeric: tabular-nums;
    color: var(--text-secondary);
}

.lngd-sum-n small {
    margin-inline-start: 1px;
    font-size: var(--fs-lg);
    font-weight: var(--fw-semibold);
    color: var(--text-tertiary);
}

.lngd-sum.is-live .lngd-sum-n { color: var(--success-700); }
.lngd-sum.is-part .lngd-sum-n { color: var(--warning-700); }

.lngd-sum-k {
    font-size: var(--fs-2xs);
    font-weight: var(--fw-semibold);
    letter-spacing: 0.05em;
    text-transform: uppercase;
    color: var(--text-tertiary);
}

.lngd-sum-main { display: flex; flex-direction: column; gap: 10px; min-width: 0; }

/* Missing is the track itself, so the segments always add up to the full
   width without a third element to keep in step. */
.lngd-split {
    display: flex;
    height: 10px;
    border-radius: var(--r-full);
    background: var(--n-100);
    overflow: hidden;
}

.lngd-split > span {
    display: block;
    height: 100%;
    transition: width var(--dur-slow) var(--ease-out);
}

.lngd-split-ok { background: var(--success-500); }
.lngd-split-rv { background: var(--warning-500); }

.lngd-keys {
    display: flex;
    flex-wrap: wrap;
    gap: 6px 18px;
    margin: 0;
    padding: 0;
    list-style: none;
}

.lngd-keys li {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    font-size: var(--fs-xs);
    color: var(--text-secondary);
}

.lngd-keys b {
    font-weight: var(--fw-semibold);
    font-variant-numeric: tabular-nums;
    color: var(--text-primary);
}

.lngd-dot {
    width: 8px;
    height: 8px;
    border-radius: var(--r-full);
    background: var(--n-200);
    box-shadow: inset 0 0 0 1px var(--n-300);
}

.lngd-dot.is-ok { background: var(--success-500); box-shadow: none; }
.lngd-dot.is-rv { background: var(--warning-500); box-shadow: none; }

/* ---------- Namespaces ---------- */

.lngd-nss {
    display: flex;
    flex-direction: column;
    margin: -6px -8px;
}

.lngd-ns {
    display: grid;
    grid-template-columns: minmax(90px, 1fr) 84px minmax(80px, 1.4fr) 42px 88px;
    gap: 14px;
    align-items: center;
    width: 100%;
    padding: 9px 8px;
    border: none;
    border-radius: var(--r-md);
    background: transparent;
    font: inherit;
    text-align: start;
    color: var(--text-primary);
    cursor: pointer;
    transition: background-color var(--dur-fast) var(--ease-out),
                box-shadow var(--dur-fast) var(--ease-out);
}

.lngd-ns + .lngd-ns { margin-block-start: 1px; }
.lngd-ns:hover { background: var(--n-25); }
.lngd-ns:focus-visible { outline: none; box-shadow: var(--shadow-focus); }

/* Picked: a ring and a wash, not a fill - the fill is the primary button. */
.lngd-ns.is-on {
    background: var(--p-50);
    box-shadow: inset 0 0 0 1px var(--p-200);
}

.lngd-ns-nm {
    font-family: var(--font-mono);
    font-size: var(--fs-sm);
    font-weight: var(--fw-medium);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.lngd-ns.is-on .lngd-ns-nm { color: var(--p-800); font-weight: var(--fw-semibold); }

.lngd-ns-ct,
.lngd-ns-pct {
    font-family: var(--font-mono);
    font-size: var(--fs-xs);
    font-variant-numeric: tabular-nums;
    color: var(--text-tertiary);
    text-align: end;
    white-space: nowrap;
}

.lngd-ns-pct { font-weight: var(--fw-semibold); color: var(--text-secondary); }
.lngd-ns.is-live .lngd-ns-pct { color: var(--success-700); }
.lngd-ns.is-part .lngd-ns-pct { color: var(--warning-700); }

.lngd-ns-bar {
    display: block;
    height: 6px;
    border-radius: var(--r-full);
    background: var(--n-100);
    overflow: hidden;
}

.lngd-ns-fill {
    display: block;
    height: 100%;
    border-radius: var(--r-full);
    background: var(--n-300);
    transition: width var(--dur-slow) var(--ease-out);
}

.lngd-ns.is-live .lngd-ns-fill { background: var(--success-500); }
.lngd-ns.is-part .lngd-ns-fill { background: var(--warning-500); }

.lngd-ns-miss {
    justify-self: end;
    padding: 1px 8px;
    border-radius: var(--r-full);
    background: var(--warning-50);
    color: var(--warning-700);
    font-size: var(--fs-2xs);
    font-weight: var(--fw-semibold);
    white-space: nowrap;
}

.lngd-ns-miss.is-done {
    display: grid;
    place-items: center;
    width: 20px;
    height: 20px;
    padding: 0;
    background: var(--success-50);
    color: var(--success-700);
}

/* ---------- Machine translation ---------- */

.lngd-mt {
    display: flex;
    align-items: center;
    gap: 14px;
    padding: 14px 16px;
    border-radius: var(--r-lg);
    background: linear-gradient(135deg, var(--p-50), var(--surface-raised) 70%);
    box-shadow: 0 0 0 1px var(--p-100), var(--shadow-xs);
}

.lngd-mt-mark {
    display: grid;
    place-items: center;
    width: 38px;
    height: 38px;
    flex-shrink: 0;
    border-radius: var(--r-lg);
    background: var(--p-700);
    color: var(--gold-400);
}

.lngd-mt-txt { flex: 1; min-width: 0; }

.lngd-mt-t {
    font-size: var(--fs-md);
    font-weight: var(--fw-semibold);
    color: var(--text-primary);
}

.lngd-mt-d {
    margin-block-start: 2px;
    font-size: var(--fs-xs);
    line-height: var(--lh-snug);
    color: var(--text-secondary);
}

.lngd-mt-scope {
    margin-block-start: 4px;
    font-family: var(--font-mono);
    font-size: var(--fs-2xs);
    color: var(--text-tertiary);
}

.lngd-mt > .btn { flex-shrink: 0; }

.lngd-mt.is-done {
    background: var(--surface-raised);
    box-shadow: 0 0 0 1px var(--border-subtle), var(--shadow-xs);
}

.lngd-mt.is-done .lngd-mt-mark {
    background: var(--success-50);
    color: var(--success-700);
}

/* ---------- Strings ---------- */

.lngd-tools {
    display: flex;
    gap: 8px;
    align-items: center;
}

.lngd-find { flex: 1; min-width: 0; }
.lngd-ns-pick { width: auto; max-width: 180px; }

.lngd-seg { align-self: flex-start; }

.lngd-ro {
    gap: 8px;
    font-size: var(--fs-xs);
    color: var(--text-tertiary);
}

.lngd-list {
    display: flex;
    flex-direction: column;
    margin: 0 -14px -14px;
    padding: 0;
    list-style: none;
    border-block-start: 1px solid var(--border-subtle);
}

.lngd-str {
    display: flex;
    flex-direction: column;
    gap: 8px;
    padding: 12px 14px;
    border-block-end: 1px solid var(--border-subtle);
    box-shadow: inset 3px 0 0 transparent;
    transition: background-color var(--dur-fast) var(--ease-out),
                box-shadow var(--dur-fast) var(--ease-out);
}

.lngd-str:last-child { border-block-end: none; }
.lngd-str:hover { background: var(--n-25); }

/* An unsaved edit is marked at the edge, so scrolling past one is noticed. */
.lngd-str.is-dirty {
    background: var(--p-50);
    box-shadow: inset 3px 0 0 var(--p-600);
}

.lngd-str-h {
    display: flex;
    align-items: center;
    gap: 8px;
    min-width: 0;
}

.lngd-str-ns {
    flex-shrink: 0;
    padding: 1px 6px;
    border-radius: var(--r-sm);
    background: var(--n-75);
    font-family: var(--font-mono);
    font-size: var(--fs-2xs);
    color: var(--text-tertiary);
}

.lngd-str-key {
    flex: 1;
    min-width: 0;
    font-family: var(--font-mono);
    font-size: var(--fs-xs);
    font-weight: var(--fw-medium);
    color: var(--text-primary);
    background: none;
}

.lngd-str-src,
.lngd-str-dst {
    display: grid;
    grid-template-columns: 34px minmax(0, 1fr);
    gap: 10px;
    align-items: start;
}

.lngd-str-src {
    font-size: var(--fs-sm);
    line-height: var(--lh-snug);
    color: var(--text-secondary);
    overflow-wrap: anywhere;
}

.lngd-str-lang {
    display: grid;
    place-items: center;
    height: 20px;
    margin-block-start: 1px;
    border-radius: var(--r-sm);
    background: var(--n-75);
    font-family: var(--font-mono);
    font-size: 10px;
    font-weight: var(--fw-bold);
    text-transform: uppercase;
    color: var(--text-tertiary);
}

.lngd-str-lang.is-dst {
    background: var(--p-50);
    color: var(--p-700);
    box-shadow: inset 0 0 0 1px var(--p-100);
}

.lngd-str-dst .lngd-str-lang { margin-block-start: 7px; }

textarea.input.lngd-ta {
    min-height: 34px;
    padding: 6px 10px;
    font-size: var(--fs-sm);
    line-height: var(--lh-snug);
    background: var(--surface-raised);
}

.lngd-str-val {
    padding-block: 7px 0;
    font-size: var(--fs-sm);
    line-height: var(--lh-snug);
    color: var(--text-primary);
    overflow-wrap: anywhere;
}

.lngd-str-val.is-none { color: var(--text-tertiary); font-style: italic; }

.lngd-str-acts {
    display: flex;
    justify-content: flex-end;
    gap: 6px;
}

.lngd-empty {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 8px;
    padding: 28px 16px;
    font-size: var(--fs-sm);
    color: var(--text-tertiary);
}

.lngd-empty svg { color: var(--n-300); }

.lngd-more {
    display: flex;
    justify-content: center;
    margin: 0 -14px -14px;
    padding: 10px 14px;
    border-block-start: 1px solid var(--border-subtle);
    background: var(--n-25);
    border-end-start-radius: var(--r-lg);
    border-end-end-radius: var(--r-lg);
}

/* ---------- Narrow ---------- */

@media (max-width: 640px) {
    .lngd-sum { grid-template-columns: minmax(0, 1fr); gap: 14px; }
    .lngd-sum-pct {
        flex-direction: row;
        align-items: baseline;
        gap: 8px;
        padding: 0 0 12px;
        border-inline-end: none;
        border-block-end: 1px solid var(--border-subtle);
    }

    .lngd-ns { grid-template-columns: minmax(0, 1fr) auto auto; row-gap: 6px; }
    .lngd-ns-ct { display: none; }
    .lngd-ns-bar { grid-column: 1 / -1; grid-row: 2; }

    .lngd-mt { flex-wrap: wrap; }
    .lngd-mt > .btn { width: 100%; justify-content: center; }

    .lngd-tools { flex-wrap: wrap; }
    .lngd-ns-pick { max-width: none; flex: 1; }
}

@media (prefers-reduced-motion: reduce) {
    .lngd-split > span,
    .lngd-ns-fill { transition: none; }
}


/* ============================================================
   78. Shared: toasts (q-toast*)

   The look behind QToastHost - every IDrawerService.Toast() in
   every portal. A stack in the top-right corner (top-left under
   dir="rtl"), below the 56px header, newest nearest the corner.

   It replaces two spellings: DrawerHost's tinted pill in the
   bottom-right, and the candidate profile's red bar at the
   bottom centre.

   What is here:
     .q-toasts      the fixed stack; lets clicks through its gaps
     .q-toast       one card, toned by --q-toast-accent/-tint
     .q-toast-icon  the tone badge
     .q-toast-msg   the message
     .q-toast-x     dismiss
     .q-toast-bar   time left before it leaves on its own,
                    run for --q-toast-dur (set per toast)
   ============================================================ */

.q-toasts {
    position: fixed;
    inset-block-start: 68px;
    inset-inline-end: 20px;
    z-index: var(--z-toast);
    display: flex;
    flex-direction: column;
    gap: 10px;
    inline-size: 360px;
    max-inline-size: calc(100vw - 40px);
    /* The stack is as tall as its cards; the gaps between them must not
       swallow a click meant for the page underneath. */
    pointer-events: none;
}

.q-toast {
    --q-toast-accent: var(--info-600);
    --q-toast-tint: var(--info-50);

    position: relative;
    display: flex;
    align-items: flex-start;
    gap: 12px;
    padding: 12px 10px 14px 12px;
    overflow: hidden;
    background: var(--surface-raised);
    border: 1px solid var(--border-subtle);
    border-radius: var(--r-xl);
    box-shadow: 0 1px 2px rgba(15, 19, 32, 0.04), 0 12px 32px -8px rgba(15, 19, 32, 0.18);
    color: var(--text-primary);
    pointer-events: auto;
    animation: q-toast-in var(--dur-mid) var(--ease-out);
}

.q-toast--success { --q-toast-accent: var(--success-600); --q-toast-tint: var(--success-50); }
.q-toast--error   { --q-toast-accent: var(--danger-600);  --q-toast-tint: var(--danger-50); }
.q-toast--warn    { --q-toast-accent: var(--warning-600); --q-toast-tint: var(--warning-50); }

.q-toast-icon {
    display: grid;
    place-items: center;
    flex-shrink: 0;
    width: 28px;
    height: 28px;
    border-radius: var(--r-full);
    background: var(--q-toast-tint);
    color: var(--q-toast-accent);
}

.q-toast-msg {
    flex: 1;
    min-width: 0;
    margin: 0;
    /* Centred on the 28px badge for one line; further lines run on below. */
    padding-block-start: 5px;
    font-size: var(--fs-md);
    font-weight: var(--fw-medium);
    line-height: var(--lh-snug);
    overflow-wrap: anywhere;
}

.q-toast-x {
    display: grid;
    place-items: center;
    flex-shrink: 0;
    width: 28px;
    height: 28px;
    padding: 0;
    border: none;
    border-radius: var(--r-md);
    background: transparent;
    color: var(--text-tertiary);
    cursor: pointer;
    transition: background var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out);
}

.q-toast-x:hover { background: var(--n-100); color: var(--text-primary); }
.q-toast-x:focus-visible { outline: none; box-shadow: var(--shadow-focus); }

.q-toast-bar {
    position: absolute;
    inset-inline: 0;
    inset-block-end: 0;
    height: 3px;
    background: var(--q-toast-accent);
    opacity: 0.55;
    transform-origin: left center;
    animation: q-toast-time var(--q-toast-dur, 4500ms) linear forwards;
}

[dir="rtl"] .q-toast-bar { transform-origin: right center; }

@keyframes q-toast-in {
    from { opacity: 0; transform: translateY(-8px) scale(0.98); }
    to   { opacity: 1; transform: none; }
}

@keyframes q-toast-time {
    from { transform: scaleX(1); }
    to   { transform: scaleX(0); }
}

/* A phone: full width inside the gutters, at the top of the screen. It covers
   the header for the few seconds it is there rather than sitting on the
   content somebody is reading. The same breakpoint as .tb-toast's. */
@media (max-width: 640px) {
    .q-toasts {
        inset-block-start: calc(10px + env(safe-area-inset-top));
        inset-inline: 12px;
        inline-size: auto;
        max-inline-size: none;
    }
}

@media (prefers-reduced-motion: reduce) {
    .q-toast,
    .tb-toast { animation: none; }
    /* The bar still tells time; it only stops sliding the card in. */
    .q-toast-bar { animation: q-toast-time var(--q-toast-dur, 4500ms) linear forwards; }
}


/* ============================================================
   79. Candidate: Wallet (cwl-*)

   /me/wallet - what Jork owes a referrer, and where to send it.
   Nothing on it moves money: finance pays by hand, so every
   word here is about a request, not a transfer.

   It was a 22px heading, two bordered boxes in a 1fr 1fr grid,
   three stacked sections and an inline form, all written in
   style attributes. The balance sat at the top and the button
   that asks for it sat 300px below under its own heading, with
   the destination printed a third time under that - one
   question answered in three places, none of them beside the
   number it is about.

   So the band at the top carries both: the figure on the left,
   and on the right the single panel that is whichever of the
   four states the wallet is in - request it, add a method
   first, nothing owed, or one already on its way. An open
   request then gets a track of its own under the band, because
   "where in the queue is it" is the only question somebody who
   has asked for money comes back with.

   What is here:
     .cwl-hero     the navy band: balance, the one action, and
                   paid-to-date on a quiet foot
     .cwl-act      that action panel - on glass, four states
     .cwl-req      an open payout: three-step track and the
                   facts behind it
     .cwl-grid     methods beside history; stacked under 1000px
     .cwl-method   one payout method, with a mark per kind
     .cwl-brand    the kind mark - navy bank, tinted wallets
     .cwl-ledger   the history, grouped by month
     .cwl-kinds    the kind picker in the add drawer

   The cards are QCard, the pills QPill, the empty states
   QEmptyState, the form a QDrawer (".20 Shared: side drawer"),
   and removal a QConfirm. Nothing here restyles any of them.
   ============================================================ */

.cwl {
    display: flex;
    flex-direction: column;
    gap: 14px;
    min-width: 0;
}

/* ---------- The band ---------- */

/* The portal's identity idiom - the dashboard, the referrals
   page and the certifications wall all open on this navy panel
   with a gold bloom - carrying the figure the page exists for. */
.cwl-hero {
    position: relative;
    overflow: hidden;
    padding: 20px 24px 0;
    border-radius: var(--r-xl);
    background: linear-gradient(135deg, var(--p-900) 0%, var(--p-800) 58%, var(--p-700) 100%);
    color: var(--n-0);
}

/* Gold from the top right, blue from the bottom left, so the
   gradient does not read as a flat band on a wide page.
   Decorative and inert. */
.cwl-hero::before,
.cwl-hero::after {
    content: "";
    position: absolute;
    border-radius: var(--r-full);
    pointer-events: none;
}

.cwl-hero::before {
    inset-block-start: -100px;
    inset-inline-end: -60px;
    width: 300px;
    height: 300px;
    background: radial-gradient(circle, rgba(245, 184, 0, 0.22) 0%, transparent 70%);
}

.cwl-hero::after {
    inset-block-end: -140px;
    inset-inline-start: -80px;
    width: 330px;
    height: 330px;
    background: radial-gradient(circle, rgba(100, 120, 255, 0.18) 0%, transparent 70%);
}

.cwl-hero-in {
    position: relative;
    display: flex;
    align-items: flex-start;
    gap: 24px;
    flex-wrap: wrap;
    padding-block-end: 20px;
}

/* ---------- The figure ---------- */

.cwl-bal {
    display: flex;
    align-items: flex-start;
    gap: 14px;
    /* 260px is the width the amount needs before "MMK 1,250,000"
       starts wrapping under its own label. Below that the action
       panel takes its own row rather than squeezing this one. */
    flex: 1 1 260px;
    min-width: 0;
}

.cwl-crest {
    flex-shrink: 0;
    display: grid;
    place-items: center;
    width: 44px;
    height: 44px;
    border-radius: var(--r-lg);
    background: linear-gradient(140deg, var(--gold-300), var(--gold-500));
    color: var(--p-900);
    box-shadow: 0 2px 6px rgba(7, 10, 19, 0.28), inset 0 1px 0 rgba(255, 255, 255, 0.45);
}

.cwl-bal-txt { min-width: 0; }

.cwl-bal-l {
    font-size: var(--fs-2xs);
    font-weight: var(--fw-semibold);
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: rgba(255, 255, 255, 0.66);
}

/* Mono and tabular: this figure sits directly above another one
   on the foot, and two money amounts that do not share a column
   width read as two different kinds of number. */
.cwl-bal-v {
    margin-block-start: 3px;
    font-family: var(--font-mono);
    font-size: var(--fs-4xl);
    font-weight: var(--fw-medium);
    line-height: var(--lh-tight);
    letter-spacing: -0.02em;
    font-variant-numeric: tabular-nums;
    word-break: break-word;
}

.cwl-bal-h {
    margin-block-start: 4px;
    font-size: var(--fs-sm);
    color: rgba(255, 255, 255, 0.7);
}

/* ---------- The one action ---------- */

/* On glass rather than in a card below: the button that asks
   for the money has to be beside the money, and a white card on
   the navy band would read as a second page opening inside the
   first. */
.cwl-act {
    position: relative;
    flex: 1 1 300px;
    min-width: 0;
    padding: 14px 16px;
    border-radius: var(--r-lg);
    background: rgba(255, 255, 255, 0.1);
    box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.2);
}

.cwl-act-h {
    display: flex;
    align-items: center;
    gap: 7px;
    font-size: var(--fs-md);
    font-weight: var(--fw-semibold);
}

.cwl-act-p {
    margin: 5px 0 0;
    font-size: var(--fs-sm);
    line-height: var(--lh-normal);
    color: rgba(255, 255, 255, 0.76);
}

/* The destination inside the sentence. It is the only thing in
   that line worth reading twice, so it is the only thing lifted
   back to full white. */
.cwl-act-p b {
    color: var(--n-0);
    font-weight: var(--fw-semibold);
}

/* Gold, not the primary navy: a navy button on a navy band is
   the one shape .btn-primary cannot do, and this is the page's
   single most important control. */
.cwl-cta {
    margin-block-start: 12px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    height: var(--h-lg);
    padding: 0 18px;
    border: none;
    border-radius: var(--r-md);
    background: linear-gradient(140deg, var(--gold-300), var(--gold-500));
    color: var(--p-950);
    font-family: inherit;
    font-size: var(--fs-md);
    font-weight: var(--fw-semibold);
    letter-spacing: -0.005em;
    cursor: pointer;
    box-shadow: 0 2px 8px rgba(7, 10, 19, 0.3);
    transition: filter var(--dur-fast) var(--ease-out),
                transform var(--dur-fast) var(--ease-out);
}

.cwl-cta:hover:not(:disabled) { filter: brightness(1.06); transform: translateY(-1px); }
.cwl-cta:active:not(:disabled) { transform: translateY(0); }

.cwl-cta:focus-visible {
    outline: 2px solid var(--n-0);
    outline-offset: 2px;
}

.cwl-cta:disabled { opacity: 0.55; cursor: default; box-shadow: none; }

/* Nothing owed is not a failure and not an error - it is the
   ordinary state of a wallet between hires, so the panel steps
   back rather than lighting up. */
.cwl-act.is-quiet {
    background: rgba(255, 255, 255, 0.06);
    box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.12);
}

/* A payout already asked for: gold ring and a live dot, the
   same pairing the track under the band uses. */
.cwl-act.is-wait {
    background: rgba(245, 184, 0, 0.12);
    box-shadow: inset 0 0 0 1px rgba(254, 200, 75, 0.42);
}

.cwl-act-dot {
    flex-shrink: 0;
    width: 8px;
    height: 8px;
    border-radius: var(--r-full);
    background: var(--gold-300);
    box-shadow: 0 0 0 0 rgba(254, 200, 75, 0.55);
    animation: cwl-pulse 2.4s var(--ease-out) infinite;
}

@keyframes cwl-pulse {
    0%   { box-shadow: 0 0 0 0 rgba(254, 200, 75, 0.5); }
    70%  { box-shadow: 0 0 0 7px rgba(254, 200, 75, 0); }
    100% { box-shadow: 0 0 0 0 rgba(254, 200, 75, 0); }
}

/* ---------- Paid to date ---------- */

/* A foot rather than a second tile: it is history, and given
   equal weight beside the balance it becomes a second number to
   work out which of the two you are owed. */
.cwl-hero-foot {
    position: relative;
    display: flex;
    align-items: baseline;
    flex-wrap: wrap;
    gap: 4px 10px;
    padding: 11px 0 13px;
    border-block-start: 1px solid rgba(255, 255, 255, 0.14);
    font-size: var(--fs-sm);
    color: rgba(255, 255, 255, 0.62);
}

.cwl-foot-l {
    font-weight: var(--fw-semibold);
    letter-spacing: 0.05em;
    text-transform: uppercase;
    font-size: var(--fs-2xs);
}

.cwl-foot-v {
    font-family: var(--font-mono);
    font-size: var(--fs-lg);
    font-weight: var(--fw-medium);
    font-variant-numeric: tabular-nums;
    color: var(--n-0);
}

.cwl-foot-h { color: rgba(255, 255, 255, 0.5); }

/* Where the money in this wallet comes from. Pushed to the end
   of the foot so it does not sit between the label and its
   figure. */
.cwl-foot-a {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    margin-inline-start: auto;
    color: var(--gold-300);
    font-weight: var(--fw-semibold);
    text-decoration: none;
}

.cwl-foot-a:hover { color: var(--gold-200); }
.cwl-foot-a svg { transition: transform var(--dur-fast) var(--ease-out); }
.cwl-foot-a:hover svg { transform: translateX(2px); }

/* ---------- A payout on its way ---------- */

.cwl-req {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 16px 24px;
    padding: 16px 20px;
    border: 1px solid var(--gold-200);
    border-radius: var(--r-xl);
    background: var(--gold-50);
}

/* Three steps, not a progress bar: the middle one is where every
   request sits for three working days, and a bar at 50% says
   nothing about who is holding it. */
.cwl-track {
    display: flex;
    align-items: center;
    gap: 0;
    flex: 1 1 280px;
    min-width: 0;
    margin: 0;
    padding: 0;
    list-style: none;
}

.cwl-step {
    position: relative;
    display: flex;
    align-items: center;
    gap: 8px;
    padding-inline-end: 14px;
    font-size: var(--fs-sm);
    color: var(--text-tertiary);
}

/* The connector, drawn from each step to the next rather than as
   its own element, so the row keeps three children and wraps as
   three things. */
.cwl-step:not(:last-child)::after {
    content: "";
    flex: 1 0 18px;
    height: 1px;
    margin-inline-start: 6px;
    background: var(--gold-200);
}

.cwl-step-m {
    flex-shrink: 0;
    display: grid;
    place-items: center;
    width: 20px;
    height: 20px;
    border-radius: var(--r-full);
    border: 1.5px solid var(--gold-300);
    background: var(--n-0);
    color: var(--n-0);
}

.cwl-step.is-done .cwl-step-m {
    border-color: var(--gold-500);
    background: var(--gold-500);
}

.cwl-step.is-done .cwl-step-l { color: var(--gold-800); }

/* The live step: the same pulse as the dot on the band, so the
   two read as one state said twice rather than two states. */
.cwl-step.is-now .cwl-step-m {
    border-color: var(--gold-600);
    background: var(--gold-100);
    animation: cwl-pulse 2.4s var(--ease-out) infinite;
}

.cwl-step.is-now .cwl-step-l {
    color: var(--gold-900);
    font-weight: var(--fw-semibold);
}

.cwl-step-l { white-space: nowrap; }

/* The facts behind the request. A dl, because each one is a
   label and its value and nothing else. */
.cwl-facts {
    display: flex;
    flex-wrap: wrap;
    gap: 6px 26px;
    margin: 0;
    min-width: 0;
}

.cwl-fact { min-width: 0; }

.cwl-fact dt {
    font-size: var(--fs-2xs);
    font-weight: var(--fw-semibold);
    letter-spacing: 0.05em;
    text-transform: uppercase;
    color: var(--gold-700);
}

.cwl-fact dd {
    margin: 2px 0 0;
    font-size: var(--fs-md);
    font-weight: var(--fw-semibold);
    color: var(--gold-900);
}

.cwl-fact-mono {
    font-family: var(--font-mono);
    font-variant-numeric: tabular-nums;
}

/* Cancelling is a way back, not the point of the band, so it is
   a ghost button pushed to the end. */
.cwl-req-act { margin-inline-start: auto; }

/* ---------- Methods beside history ---------- */

/* 1.25fr on the ledger: a transaction label is a sentence
   ("Referral reward - Aye Chan hired") where a method is a name
   and four digits. */
.cwl-grid {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1.25fr);
    gap: 14px;
    align-items: start;
}

.cwl-card { min-width: 0; }

/* The count beside a card heading - methods against their cap,
   entries in the ledger. */
.cwl-count {
    font-family: var(--font-mono);
    font-size: var(--fs-xs);
    font-variant-numeric: tabular-nums;
    color: var(--text-tertiary);
}

/* ---------- One payout method ---------- */

.cwl-methods {
    display: flex;
    flex-direction: column;
    padding: 6px;
    gap: 2px;
}

.cwl-method {
    display: flex;
    align-items: center;
    gap: 11px;
    padding: 10px 12px;
    border: 1px solid transparent;
    border-radius: var(--r-md);
    transition: background var(--dur-fast) var(--ease-out);
}

.cwl-method:hover { background: var(--surface-hover); }

/* The account the next payout actually goes to. It is marked on
   the row as well as pilled, because the pill is at the far end
   and the row is what gets scanned. */
.cwl-method.is-primary {
    border-color: var(--border-subtle);
    background: var(--surface-raised);
    box-shadow: inset 3px 0 0 var(--success-500);
}

/* The kind mark. Tinted per provider so three mobile wallets
   with the same icon are still telling apart at a glance;
   decorative, since the kind is named in words beside it. */
.cwl-brand {
    flex-shrink: 0;
    display: grid;
    place-items: center;
    width: 34px;
    height: 34px;
    border-radius: var(--r-md);
    background: var(--n-100);
    color: var(--n-600);
}

.cwl-brand-bank    { background: var(--p-50);      color: var(--p-700); }
.cwl-brand-kbzpay  { background: var(--info-50);   color: var(--info-700); }
.cwl-brand-wavepay { background: var(--gold-50);   color: var(--gold-700); }
.cwl-brand-ayapay  { background: var(--danger-50); color: var(--danger-700); }

.cwl-method-txt { flex: 1; min-width: 0; }

.cwl-method-h {
    font-size: var(--fs-md);
    font-weight: var(--fw-semibold);
    color: var(--text-primary);
}

/* Mono on the masked digits only: the four that identify the
   account are the part worth aligning down the column, and the
   product name beside them is a name. */
.cwl-mask {
    font-family: var(--font-mono);
    font-size: var(--fs-sm);
    font-weight: var(--fw-regular);
    color: var(--text-secondary);
}

.cwl-method-s {
    margin-block-start: 1px;
    font-size: var(--fs-sm);
    color: var(--text-secondary);
}

/* Promoting is a labelled action; removing is an icon. The two
   are not equals - one is a preference, the other loses the
   account a reward would have been sent to. */
.cwl-mk {
    flex-shrink: 0;
    height: var(--h-sm);
    padding: 0 10px;
    border: 1px solid var(--border-default);
    border-radius: var(--r-sm);
    background: var(--surface-raised);
    color: var(--text-secondary);
    font-family: inherit;
    font-size: var(--fs-xs);
    font-weight: var(--fw-semibold);
    cursor: pointer;
    white-space: nowrap;
    transition: color var(--dur-fast) var(--ease-out),
                border-color var(--dur-fast) var(--ease-out);
}

.cwl-mk:hover:not(:disabled) {
    border-color: var(--p-300);
    color: var(--p-700);
}

.cwl-mk:disabled { opacity: 0.5; cursor: default; }

.cwl-del {
    flex-shrink: 0;
    display: grid;
    place-items: center;
    width: var(--h-sm);
    height: var(--h-sm);
    border: 1px solid transparent;
    border-radius: var(--r-sm);
    background: transparent;
    color: var(--text-tertiary);
    cursor: pointer;
    transition: color var(--dur-fast) var(--ease-out),
                background var(--dur-fast) var(--ease-out);
}

.cwl-del:hover:not(:disabled) {
    background: var(--danger-50);
    border-color: var(--danger-200);
    color: var(--danger-700);
}

.cwl-del:disabled { opacity: 0.5; cursor: default; }

.cwl-note {
    margin: 0;
    padding: 0 18px 14px;
    font-size: var(--fs-xs);
    color: var(--text-tertiary);
}

/* ---------- The ledger ---------- */

.cwl-ledger {
    display: flex;
    flex-direction: column;
    padding: 0 0 6px;
}

/* A sticky month rule rather than a date on every row: the list
   is newest-first, and a reward and the payment against it are
   usually weeks apart.

   --tbl-top is the pinned header's height less the gutter it
   cancels, set by "12. Shared page chrome" on whatever box holds
   PageHeader and already corrected for the taller bar under
   900px. Pinning at 0 instead would park every month rule behind
   that header. */
.cwl-mon {
    position: sticky;
    inset-block-start: var(--tbl-top, 0px);
    /* Under .pg-head's 30 - the header wins where they meet. */
    z-index: 5;
    padding: 7px 18px;
    background: var(--surface-sunken);
    border-block-end: 1px solid var(--border-subtle);
    font-size: var(--fs-2xs);
    font-weight: var(--fw-semibold);
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--text-tertiary);
}

.cwl-line {
    display: flex;
    align-items: center;
    gap: 11px;
    padding: 10px 18px;
    border-block-end: 1px solid var(--border-subtle);
}

.cwl-line:last-child { border-block-end: none; }

.cwl-line-m {
    flex-shrink: 0;
    display: grid;
    place-items: center;
    width: 28px;
    height: 28px;
    border-radius: var(--r-full);
}

.cwl-line-m.is-in  { background: var(--success-50); color: var(--success-700); }
.cwl-line-m.is-out { background: var(--n-100);      color: var(--n-600); }

.cwl-line-txt { flex: 1; min-width: 0; }

.cwl-line-h {
    font-size: var(--fs-md);
    color: var(--text-primary);
}

.cwl-line-s {
    display: flex;
    align-items: center;
    gap: 8px;
    margin-block-start: 1px;
    min-width: 0;
    font-size: var(--fs-xs);
    color: var(--text-tertiary);
}

/* The payment reference, which is what finance quotes back when
   a candidate asks where their money went. Mono so a transfer
   id is readable character by character. */
.cwl-ref {
    max-width: 22ch;
    padding: 1px 6px;
    border-radius: var(--r-xs);
    background: var(--surface-sunken);
    font-family: var(--font-mono);
    font-size: var(--fs-2xs);
    color: var(--text-secondary);
}

/* Green for what was earned, plain for what was sent: a payout
   is not a loss, so it is not red. */
.cwl-amt {
    flex-shrink: 0;
    font-family: var(--font-mono);
    font-size: var(--fs-md);
    font-weight: var(--fw-medium);
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

.cwl-amt.is-in  { color: var(--success-700); }
.cwl-amt.is-out { color: var(--text-secondary); }

/* ---------- The kind picker, in the drawer ---------- */

/* Four tiles rather than a select: the choice decides whether
   the bank field below exists, and a closed dropdown hides the
   one control it changes. */
.cwl-kinds {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 8px;
}

.cwl-kind {
    display: flex;
    align-items: center;
    gap: 9px;
    padding: 9px 11px;
    border: 1px solid var(--border-default);
    border-radius: var(--r-md);
    background: var(--surface-raised);
    color: var(--text-secondary);
    font-family: inherit;
    font-size: var(--fs-md);
    font-weight: var(--fw-medium);
    text-align: start;
    cursor: pointer;
    transition: border-color var(--dur-fast) var(--ease-out),
                background var(--dur-fast) var(--ease-out);
}

.cwl-kind:hover { border-color: var(--border-strong); }

.cwl-kind.on {
    border-color: var(--p-700);
    background: var(--p-25);
    color: var(--text-primary);
    box-shadow: var(--shadow-focus);
}

.cwl-kind .cwl-brand {
    width: 28px;
    height: 28px;
    border-radius: var(--r-sm);
}

.cwl-kind-l { min-width: 0; overflow: hidden; text-overflow: ellipsis; }

/* ---------- Narrower ---------- */

/* Below this the ledger label and its amount start colliding
   inside half a page, so the two cards take the full width and
   keep their rows intact. */
@media (max-width: 1000px) {
    .cwl-grid { grid-template-columns: minmax(0, 1fr); }
}

@media (max-width: 640px) {
    .cwl-hero { padding: 18px 16px 0; }
    .cwl-bal-v { font-size: var(--fs-3xl); }

    /* Full width rather than a 300px panel stranded beside
       nothing. */
    .cwl-act { flex: 1 1 100%; }

    /* The link drops under the figure instead of being pushed to
       an end that is no longer there. */
    .cwl-foot-a { margin-inline-start: 0; flex-basis: 100%; }

    /* Stacked, the connectors point across a row that no longer
       exists. */
    .cwl-track { flex-direction: column; align-items: flex-start; gap: 8px; }
    .cwl-step:not(:last-child)::after { display: none; }

    .cwl-req-act { margin-inline-start: 0; flex-basis: 100%; }

    .cwl-kinds { grid-template-columns: minmax(0, 1fr); }
}

/* The dot and the live step say the same thing as the words
   beside them, so neither has to move for it to be read. */
@media (prefers-reduced-motion: reduce) {
    .cwl-act-dot,
    .cwl-step.is-now .cwl-step-m { animation: none; }

    .cwl-cta,
    .cwl-foot-a svg { transition: none; }

    .cwl-cta:hover:not(:disabled) { transform: none; }
    .cwl-foot-a:hover svg { transform: none; }
}

/* ============================================================
   80. Candidate: Messages (cmsg-*)

   /me/messages - the candidate's half of every conversation an
   employer starts.

   It was one grid written in style attributes: a 320px column
   of threads, a column of bubbles, and a calc(100vh - 132px)
   holding the whole thing up. An inline style cannot express a
   hover, a focus ring or a breakpoint, so the page had none of
   the three - on a 390px phone the two fixed columns ran off
   the side, and the only thing separating an unread thread
   from a read one was an 18px badge.

   Three things changed beyond moving the styles here.

   Runs, not rows. Consecutive messages from one sender share a
   single avatar, and only the last bubble of a run carries a
   time and a receipt. The old stream repeated the avatar and
   the clock on every line, which is most of why it read as
   noise rather than as a conversation.

   The rail. A candidate's thread is always about an
   application - ThreadSummaryDto has carried ApplicationId and
   ApplicationRole from the start and nothing drew either. So
   the third column answers "what is this about": the role, the
   stage, when they applied, the next interview if one is
   booked, and the files that have crossed the thread.

   Suggested replies moved out of the scroller. They used to
   sit among the bubbles, which shoved the conversation around
   as they arrived and scrolled away exactly when somebody went
   looking for them. They are a tray above the composer now.

   What is here:
     .cmsg          the shell - list | conversation | rail
     .cmsg-list     threads, with search and a state filter
     .cmsg-row      one thread; .is-unread and .is-open are the
                    two states, and they are tint + bar +
                    weight rather than a badge alone
     .cmsg-convo    the conversation, head to composer
     .cmsg-run      one run of bubbles from one sender
     .cmsg-ai-tray  suggested replies, pinned above the composer
     .cmsg-comp     the composer: one field carrying attach and
                    the AI draft, with send outside it
     .cmsg-rail     what the conversation is about

   Below 1120px the rail folds away - everything on it is a tap
   away on the application itself. Below 860px the page is one
   pane at a time, list then conversation, with a back control
   in the conversation head.

   Avatar fills come from the DTO's OtherPartyColor, so those
   stay inline. Everything else is here.
   ============================================================ */

.cmsg {
    display: grid;
    grid-template-columns: 296px minmax(0, 1fr) 260px;
    height: calc(100dvh - 88px);
    min-height: 420px;
    min-width: 0;
    overflow: hidden;
    background: var(--surface-raised);
    border: 1px solid var(--border-subtle);
    border-radius: var(--r-xl);
    box-shadow: var(--shadow-sm);
}

/* ---------- The thread column ---------- */

.cmsg-list {
    display: flex;
    flex-direction: column;
    min-width: 0;
    min-height: 0;
    border-inline-end: 1px solid var(--border-subtle);
    background: var(--n-25);
}

.cmsg-list-head {
    flex-shrink: 0;
    display: flex;
    flex-direction: column;
    gap: 10px;
    padding: 12px;
    background: var(--surface-raised);
    border-block-end: 1px solid var(--border-subtle);
}

.cmsg-find { display: flex; gap: 8px; }

.cmsg-search {
    flex: 1 1 auto;
    min-width: 0;
    height: 34px;
    display: flex;
    align-items: center;
    gap: 8px;
    padding-inline: 10px;
    background: var(--surface-sunken);
    border: 1px solid var(--border-subtle);
    border-radius: var(--r-lg);
    color: var(--text-tertiary);
    transition: border-color 120ms ease, box-shadow 120ms ease;
}

.cmsg-search:focus-within {
    border-color: var(--border-focus);
    box-shadow: var(--shadow-focus);
}

.cmsg-search input {
    flex: 1 1 auto;
    min-width: 0;
    border: none;
    outline: none;
    background: transparent;
    font-family: inherit;
    font-size: 12.5px;
    color: var(--text-primary);
}

/* Only drawn once there is something to clear, so it never
   sits in the box as a dead mark. */
.cmsg-search-x {
    flex-shrink: 0;
    width: 18px;
    height: 18px;
    display: grid;
    place-items: center;
    padding: 0;
    border: none;
    border-radius: var(--r-full);
    background: var(--n-200);
    color: var(--n-600);
    cursor: pointer;
}

.cmsg-search-x:hover { background: var(--n-300); }

.cmsg-new {
    flex-shrink: 0;
    width: 34px;
    height: 34px;
    display: grid;
    place-items: center;
    border: 1px solid var(--border-default);
    border-radius: var(--r-lg);
    background: var(--surface-raised);
    color: var(--p-700);
    cursor: pointer;
    transition: background 120ms ease, border-color 120ms ease;
}

.cmsg-new:hover { background: var(--p-50); border-color: var(--p-200); }
.cmsg-new:focus-visible { outline: none; box-shadow: var(--shadow-focus); }

/* ---------- The state filter ---------- */

.cmsg-seg {
    display: flex;
    gap: 4px;
    padding: 3px;
    background: var(--surface-sunken);
    border-radius: var(--r-lg);
}

.cmsg-seg button {
    flex: 1 1 0;
    height: 26px;
    min-width: 0;
    border: none;
    border-radius: var(--r-md);
    background: transparent;
    font-family: inherit;
    font-size: 11.5px;
    font-weight: 600;
    color: var(--text-tertiary);
    cursor: pointer;
    transition: background 120ms ease, color 120ms ease;
}

.cmsg-seg button:hover { color: var(--text-secondary); }

.cmsg-seg button.is-on {
    background: var(--surface-raised);
    box-shadow: var(--shadow-xs);
    color: var(--text-primary);
}

.cmsg-seg button:focus-visible { outline: none; box-shadow: var(--shadow-focus); }

/* ---------- Start a conversation ---------- */

.cmsg-start {
    flex-shrink: 0;
    max-height: 210px;
    overflow-y: auto;
    padding: 8px;
    background: var(--surface-sunken);
    border-block-end: 1px solid var(--border-subtle);
}

.cmsg-start-lbl {
    padding: 2px 6px 6px;
    font-size: 10px;
    font-weight: 700;
    letter-spacing: 0.07em;
    text-transform: uppercase;
    color: var(--text-disabled);
}

.cmsg-start-row {
    width: 100%;
    display: flex;
    align-items: center;
    gap: 9px;
    padding: 7px 8px;
    border: none;
    border-radius: var(--r-md);
    background: transparent;
    font-family: inherit;
    font-size: 12.5px;
    color: var(--text-primary);
    text-align: start;
    cursor: pointer;
}

.cmsg-start-row:hover { background: var(--surface-raised); }
.cmsg-start-row:focus-visible { outline: none; box-shadow: var(--shadow-focus); }

.cmsg-start-name {
    flex: 1 1 auto;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.cmsg-start-has {
    flex-shrink: 0;
    font-size: 10.5px;
    color: var(--text-tertiary);
}

/* ---------- The rows ---------- */

.cmsg-rows {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
}

.cmsg-row {
    width: 100%;
    display: flex;
    gap: 11px;
    padding: 13px 14px 13px 12px;
    border: none;
    border-block-end: 1px solid var(--border-subtle);
    background: transparent;
    font-family: inherit;
    text-align: start;
    cursor: pointer;
    transition: background 120ms ease;
}

.cmsg-row:hover { background: var(--surface-hover); }
.cmsg-row:focus-visible { outline: none; box-shadow: var(--shadow-focus); }

/* Unread and open answer different questions - "is there
   something here for me" and "is this the one I am reading" -
   so they are two weights of one idiom rather than two tints
   that have to be told apart. */
.cmsg-row.is-unread {
    background: var(--p-25);
    box-shadow: inset 2px 0 0 var(--p-300);
}

.cmsg-row.is-unread:hover { background: var(--p-50); }

.cmsg-row.is-open,
.cmsg-row.is-open.is-unread {
    background: var(--p-50);
    box-shadow: inset 2px 0 0 var(--p-700);
}

.cmsg-av {
    position: relative;
    flex-shrink: 0;
    width: 38px;
    height: 38px;
}

.cmsg-av-fill {
    width: 38px;
    height: 38px;
    display: grid;
    place-items: center;
    border-radius: var(--r-full);
    color: var(--n-0);
    font-size: 12px;
    font-weight: 700;
}

/* The assistant is the one party in the list that is not a
   person, and gold is how the rest of the portal says so. */
.cmsg-av-ai {
    background: var(--gold-100);
    border: 1px solid var(--gold-200);
    color: var(--gold-700);
}

/* The same mark at two other sizes. They follow .cmsg-av-fill
   rather than carrying their own inline width, so the only
   thing still written in a style attribute on this page is the
   fill colour, which comes from the DTO. */
.cmsg-start-av {
    width: 24px;
    height: 24px;
    font-size: 9.5px;
    background: var(--p-700);
}

.cmsg-av.is-head,
.cmsg-av.is-head .cmsg-av-fill { width: 36px; height: 36px; }
.cmsg-av.is-head .cmsg-av-fill { font-size: 11.5px; }

/* Presence rides the avatar instead of taking a line of prose
   under the name: the row has room for one fact per line and
   the last message is the one worth reading. */
.cmsg-dot {
    position: absolute;
    inset-block-end: -1px;
    inset-inline-end: -1px;
    width: 11px;
    height: 11px;
    border-radius: var(--r-full);
    background: var(--n-300);
    border: 2px solid var(--surface-raised);
}

.cmsg-dot.is-online { background: var(--success-500); }
.cmsg-row.is-unread .cmsg-dot { border-color: var(--p-25); }
.cmsg-row.is-open .cmsg-dot { border-color: var(--p-50); }

.cmsg-row-main {
    flex: 1 1 auto;
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: 3px;
}

.cmsg-row-top { display: flex; align-items: baseline; gap: 8px; }

.cmsg-row-name {
    flex: 1 1 auto;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-size: 12.5px;
    font-weight: 600;
    color: var(--text-primary);
}

.cmsg-row.is-unread .cmsg-row-name { font-weight: 700; }

.cmsg-row-when {
    flex-shrink: 0;
    font-size: 10.5px;
    color: var(--text-tertiary);
}

.cmsg-row.is-unread .cmsg-row-when {
    font-weight: 600;
    color: var(--p-700);
}

.cmsg-row-role {
    font-size: 11px;
    color: var(--text-tertiary);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.cmsg-row-foot { display: flex; align-items: center; gap: 8px; }

.cmsg-row-prev {
    flex: 1 1 auto;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-size: 11.5px;
    color: var(--text-tertiary);
}

.cmsg-row.is-unread .cmsg-row-prev {
    font-weight: 600;
    color: var(--n-800);
}

.cmsg-row.is-open .cmsg-row-prev { color: var(--text-secondary); }

.cmsg-badge {
    flex-shrink: 0;
    min-width: 18px;
    height: 18px;
    display: grid;
    place-items: center;
    padding-inline: 5px;
    border-radius: var(--r-full);
    background: var(--p-700);
    color: var(--n-0);
    font-size: 10px;
    font-weight: 700;
}

/* An empty pane with no words in it reads as broken. */
.cmsg-empty {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 12px;
    padding: 30px 22px;
    text-align: center;
}

.cmsg-empty-mark {
    width: 48px;
    height: 48px;
    display: grid;
    place-items: center;
    border-radius: var(--r-full);
    background: var(--p-50);
    color: var(--p-700);
}

.cmsg-empty-h {
    font-size: 14px;
    font-weight: 700;
    color: var(--text-primary);
}

.cmsg-empty-p {
    max-width: 30ch;
    font-size: 12.5px;
    line-height: 1.55;
    color: var(--text-tertiary);
}

.cmsg-empty-btn {
    height: 34px;
    display: inline-flex;
    align-items: center;
    gap: 7px;
    padding-inline: 14px;
    border: none;
    border-radius: var(--r-lg);
    background: var(--p-700);
    font-family: inherit;
    font-size: 12.5px;
    font-weight: 600;
    color: var(--n-0);
    cursor: pointer;
}

.cmsg-empty-btn:hover { background: var(--p-800); }

/* The list's empty state sits at the top of its column; the
   conversation's has a whole pane to itself and centres in it. */
.cmsg-convo > .cmsg-empty { margin: auto; }

/* ---------- The conversation ---------- */

.cmsg-convo {
    display: flex;
    flex-direction: column;
    min-width: 0;
    min-height: 0;
    background: var(--n-25);
}

.cmsg-convo-head {
    flex-shrink: 0;
    display: flex;
    align-items: center;
    gap: 11px;
    padding: 11px 18px;
    background: var(--surface-raised);
    border-block-end: 1px solid var(--border-subtle);
}

/* Only ever drawn in the one-pane layout, where it is the way
   back to the list. */
.cmsg-back {
    display: none;
    flex-shrink: 0;
    width: 36px;
    height: 36px;
    place-items: center;
    padding: 0;
    border: none;
    border-radius: var(--r-lg);
    background: transparent;
    color: var(--p-700);
    cursor: pointer;
}

.cmsg-back:hover { background: var(--p-50); }

.cmsg-who {
    flex: 1 1 auto;
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: 2px;
}

.cmsg-who-name {
    font-size: 13.5px;
    font-weight: 700;
    letter-spacing: -0.01em;
    color: var(--text-primary);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.cmsg-who-sub {
    font-size: 11.5px;
    color: var(--text-tertiary);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.cmsg-who-sub.is-online { color: var(--success-700); }

.cmsg-head-link {
    flex-shrink: 0;
    height: 30px;
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding-inline: 11px;
    border: 1px solid var(--border-default);
    border-radius: var(--r-md);
    background: var(--surface-raised);
    font-size: 12px;
    font-weight: 600;
    color: var(--p-700);
    text-decoration: none;
    transition: background 120ms ease, border-color 120ms ease;
}

.cmsg-head-link:hover {
    background: var(--p-50);
    border-color: var(--p-200);
    color: var(--p-800);
}

/* ---------- The stream ---------- */

.cmsg-stream {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    padding: 16px 18px 10px;
    display: flex;
    flex-direction: column;
    gap: 4px;
}

.cmsg-day {
    display: flex;
    align-items: center;
    gap: 10px;
    padding-block: 10px;
}

.cmsg-day::before,
.cmsg-day::after {
    content: "";
    flex: 1 1 auto;
    height: 1px;
    background: var(--border-subtle);
}

.cmsg-day span {
    flex-shrink: 0;
    font-size: 10.5px;
    font-weight: 700;
    letter-spacing: 0.05em;
    text-transform: uppercase;
    color: var(--text-disabled);
}

.cmsg-sys {
    align-self: center;
    margin-block: 6px;
    padding: 4px 11px;
    border-radius: var(--r-full);
    background: var(--surface-sunken);
    font-size: 10.5px;
    color: var(--text-tertiary);
    text-align: center;
}

/* One run: every message from the same sender with nothing
   between them. The avatar is drawn once at the foot of the
   run, and the time and receipt once at the end of it. */
.cmsg-run {
    display: flex;
    gap: 9px;
    align-items: flex-end;
    padding-block-start: 8px;
}

.cmsg-run.is-out { justify-content: flex-end; }

.cmsg-run-av {
    flex-shrink: 0;
    width: 26px;
    height: 26px;
    display: grid;
    place-items: center;
    border-radius: var(--r-full);
    color: var(--n-0);
    font-size: 9.5px;
    font-weight: 700;
}

.cmsg-run-body {
    min-width: 0;
    max-width: 76%;
    display: flex;
    flex-direction: column;
    gap: 3px;
    align-items: flex-start;
}

.cmsg-run.is-out .cmsg-run-body { align-items: flex-end; }

.cmsg-b {
    box-sizing: border-box;
    padding: 9px 13px;
    border-radius: var(--r-xs) var(--r-xl) var(--r-xl) var(--r-xs);
    background: var(--surface-raised);
    border: 1px solid var(--n-150);
    font-size: 13px;
    line-height: 1.55;
    color: var(--n-800);
    overflow-wrap: anywhere;
}

/* The corner that points at the avatar is squared off on the
   last bubble only, so a run reads as one block with one tail
   rather than as three separate shapes. */
.cmsg-run-body > .cmsg-b:first-child { border-start-start-radius: var(--r-xl); }
.cmsg-run-body > .cmsg-b:last-child { border-end-start-radius: var(--r-xs); }
.cmsg-run-body > .cmsg-b:only-child { border-radius: var(--r-xl) var(--r-xl) var(--r-xl) var(--r-xs); }

.cmsg-run.is-ai .cmsg-b {
    background: var(--gold-50);
    border-color: var(--gold-200);
}

.cmsg-run.is-out .cmsg-b {
    background: var(--p-700);
    border-color: var(--p-700);
    color: var(--n-0);
    border-radius: var(--r-xl) var(--r-xs) var(--r-xs) var(--r-xl);
}

.cmsg-run.is-out .cmsg-run-body > .cmsg-b:first-child { border-start-end-radius: var(--r-xl); }
.cmsg-run.is-out .cmsg-run-body > .cmsg-b:last-child { border-end-end-radius: var(--r-xs); }
.cmsg-run.is-out .cmsg-run-body > .cmsg-b:only-child { border-radius: var(--r-xl) var(--r-xl) var(--r-xs) var(--r-xl); }

.cmsg-meta {
    display: flex;
    align-items: center;
    gap: 5px;
    padding-inline: 4px;
    font-size: 10px;
    color: var(--text-disabled);
}

/* Read was gold on navy, the one pairing in the palette that
   does not carry at 10px. It is brand blue on the page ground
   now, beside the time it belongs to. */
.cmsg-tick { display: inline-flex; color: var(--text-disabled); }
.cmsg-tick.is-read { color: var(--p-500); }

/* No typing indicator here on purpose: IMessageService carries no
   typing signal, and a dot animation with nothing behind it is a
   lie the page tells every time somebody is quiet. */

/* ---------- Suggested replies ---------- */

.cmsg-ai-tray {
    flex-shrink: 0;
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 9px 18px;
    background: var(--gold-50);
    border-block-start: 1px solid var(--gold-200);
}

.cmsg-ai-lbl {
    flex-shrink: 0;
    display: inline-flex;
    align-items: center;
    gap: 6px;
    font-size: 11px;
    font-weight: 700;
    color: var(--gold-700);
}

.cmsg-chips {
    flex: 1 1 auto;
    min-width: 0;
    display: flex;
    gap: 7px;
    overflow-x: auto;
    scrollbar-width: none;
}

.cmsg-chips::-webkit-scrollbar { display: none; }

.cmsg-chip {
    flex-shrink: 0;
    height: 27px;
    padding-inline: 12px;
    border: 1px solid var(--gold-300);
    border-radius: var(--r-full);
    background: var(--surface-raised);
    font-family: inherit;
    font-size: 11.5px;
    font-weight: 500;
    color: var(--gold-800);
    white-space: nowrap;
    cursor: pointer;
    transition: background 120ms ease;
}

.cmsg-chip:hover { background: var(--gold-100); }
.cmsg-chip:focus-visible { outline: none; box-shadow: var(--shadow-focus); }

.cmsg-tray-x {
    flex-shrink: 0;
    width: 24px;
    height: 24px;
    display: grid;
    place-items: center;
    padding: 0;
    border: none;
    border-radius: var(--r-md);
    background: transparent;
    color: var(--gold-800);
    cursor: pointer;
}

.cmsg-tray-x:hover { background: var(--gold-100); }

/* ---------- The composer ---------- */

.cmsg-comp {
    flex-shrink: 0;
    display: flex;
    flex-direction: column;
    gap: 9px;
    padding: 12px 18px 14px;
    background: var(--surface-raised);
    border-block-start: 1px solid var(--border-subtle);
}

.cmsg-comp-err {
    display: flex;
    align-items: flex-start;
    gap: 7px;
    padding: 8px 10px;
    border: 1px solid var(--danger-200);
    border-radius: var(--r-lg);
    background: var(--danger-50);
    font-size: 11.5px;
    line-height: 1.5;
    color: var(--danger-700);
}

/* Picked but not yet sent, and above the box so a file can be
   dropped before anybody commits to it. */
.cmsg-files { display: flex; flex-wrap: wrap; gap: 7px; }

.cmsg-file {
    display: inline-flex;
    align-items: center;
    gap: 7px;
    max-width: 240px;
    padding: 5px 8px 5px 6px;
    border: 1px solid var(--border-default);
    border-radius: var(--r-full);
    background: var(--n-25);
    font-size: 11.5px;
}

.cmsg-file-mark {
    flex-shrink: 0;
    width: 20px;
    height: 20px;
    display: grid;
    place-items: center;
    border-radius: var(--r-full);
    background: var(--p-50);
    color: var(--p-700);
}

.cmsg-file-name {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-weight: 500;
}

.cmsg-file-size { flex-shrink: 0; color: var(--text-disabled); font-size: 10.5px; }

.cmsg-file-x {
    flex-shrink: 0;
    width: 16px;
    height: 16px;
    display: grid;
    place-items: center;
    padding: 0;
    border: none;
    background: transparent;
    color: var(--text-tertiary);
    cursor: pointer;
}

.cmsg-file-x:hover { color: var(--danger-600); }

.cmsg-comp-row { display: flex; align-items: flex-end; gap: 9px; }

/* One field carrying the text, the paperclip and the AI draft.
   They were three controls in a row competing with Send for
   the same edge; inside the box they read as things you do to
   the message rather than instead of sending it. */
.cmsg-field {
    flex: 1 1 auto;
    min-width: 0;
    display: flex;
    align-items: flex-end;
    gap: 8px;
    padding: 8px 8px 8px 12px;
    border: 1px solid var(--border-default);
    border-radius: var(--r-xl);
    background: var(--surface-raised);
    transition: border-color 120ms ease, box-shadow 120ms ease;
}

.cmsg-field:focus-within {
    border-color: var(--border-focus);
    box-shadow: var(--shadow-focus);
}

.cmsg-ta {
    flex: 1 1 auto;
    min-width: 0;
    max-height: 132px;
    padding-block: 3px;
    border: none;
    outline: none;
    resize: none;
    background: transparent;
    font-family: inherit;
    font-size: 13px;
    line-height: 1.5;
    color: var(--text-primary);
}

.cmsg-icon-btn {
    position: relative;
    flex-shrink: 0;
    width: 30px;
    height: 30px;
    display: grid;
    place-items: center;
    padding: 0;
    border: none;
    border-radius: var(--r-lg);
    background: transparent;
    color: var(--text-tertiary);
    cursor: pointer;
    transition: background 120ms ease, color 120ms ease;
}

.cmsg-icon-btn:hover { background: var(--surface-sunken); color: var(--text-secondary); }
.cmsg-icon-btn:focus-within { box-shadow: var(--shadow-focus); }

/* Out of sight but still in the tab order. The file input was
   display:none, which does not hide a control so much as
   delete it - the attach label was unreachable by keyboard,
   and the ring above has nothing to draw around if nothing in
   the label can take focus. */
.cmsg-file-input {
    position: absolute;
    width: 1px;
    height: 1px;
    opacity: 0;
    pointer-events: none;
}

.cmsg-ai-btn {
    flex-shrink: 0;
    height: 30px;
    display: inline-flex;
    align-items: center;
    gap: 5px;
    padding-inline: 10px;
    border: 1px solid var(--gold-300);
    border-radius: var(--r-lg);
    background: var(--gold-50);
    font-family: inherit;
    font-size: 11.5px;
    font-weight: 600;
    color: var(--gold-800);
    white-space: nowrap;
    cursor: pointer;
    transition: background 120ms ease;
}

.cmsg-ai-btn:hover:not(:disabled) { background: var(--gold-100); }

.cmsg-ai-btn:disabled {
    border-color: var(--n-150);
    background: var(--n-50);
    color: var(--text-disabled);
    cursor: not-allowed;
}

.cmsg-send {
    flex-shrink: 0;
    width: 46px;
    height: 46px;
    display: grid;
    place-items: center;
    padding: 0;
    border: none;
    border-radius: var(--r-xl);
    background: var(--p-700);
    color: var(--n-0);
    cursor: pointer;
    transition: background 120ms ease, opacity 120ms ease;
}

.cmsg-send:hover:not(:disabled) { background: var(--p-800); }
.cmsg-send:disabled { opacity: 0.55; cursor: not-allowed; }
.cmsg-send:focus-visible { outline: none; box-shadow: var(--shadow-focus); }

.cmsg-hint { font-size: 10.5px; color: var(--text-disabled); }

/* ---------- The rail ---------- */

.cmsg-rail {
    display: flex;
    flex-direction: column;
    min-width: 0;
    min-height: 0;
    overflow-y: auto;
    background: var(--surface-raised);
    border-inline-start: 1px solid var(--border-subtle);
}

.cmsg-rail-sec {
    display: flex;
    flex-direction: column;
    gap: 10px;
    padding: 14px 16px;
    border-block-end: 1px solid var(--border-subtle);
}

.cmsg-rail-sec:last-child { border-block-end: none; }

.cmsg-rail-lbl {
    font-size: 10px;
    font-weight: 700;
    letter-spacing: 0.07em;
    text-transform: uppercase;
    color: var(--text-disabled);
}

.cmsg-rail-role {
    font-size: 13.5px;
    font-weight: 700;
    line-height: 1.35;
    color: var(--text-primary);
    text-decoration: none;
}

.cmsg-rail-role:hover { color: var(--p-700); text-decoration: underline; }

.cmsg-rail-co { font-size: 11.5px; color: var(--text-tertiary); }

.cmsg-pill {
    align-self: flex-start;
    padding: 3px 9px;
    border-radius: var(--r-full);
    background: var(--success-50);
    color: var(--success-700);
    font-size: 11px;
    font-weight: 600;
}

/* A track rather than a chart: one row per stage, one of them
   now. */
.cmsg-steps { display: flex; flex-direction: column; gap: 9px; }

.cmsg-step {
    display: flex;
    align-items: center;
    gap: 9px;
    font-size: 12px;
    color: var(--text-disabled);
}

.cmsg-step-m {
    flex-shrink: 0;
    width: 16px;
    height: 16px;
    display: grid;
    place-items: center;
    border-radius: var(--r-full);
    border: 2px solid var(--n-200);
    background: var(--surface-raised);
}

.cmsg-step.is-done { color: var(--text-secondary); }

.cmsg-step.is-done .cmsg-step-m {
    border-color: var(--success-500);
    background: var(--success-500);
    color: var(--n-0);
}

.cmsg-step.is-now { color: var(--text-primary); font-weight: 700; }
.cmsg-step.is-now .cmsg-step-m { border-color: var(--p-700); }

.cmsg-step.is-now .cmsg-step-m::after {
    content: "";
    width: 6px;
    height: 6px;
    border-radius: var(--r-full);
    background: var(--p-700);
}

.cmsg-step-name { flex: 1 1 auto; min-width: 0; }

.cmsg-step-when { flex-shrink: 0; font-size: 10.5px; color: var(--text-disabled); }
.cmsg-step.is-now .cmsg-step-when { color: var(--p-700); font-weight: 600; }

.cmsg-iv {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 11px;
    border: 1px solid var(--border-subtle);
    border-radius: var(--r-lg);
    background: var(--p-25);
    text-decoration: none;
}

.cmsg-iv:hover { border-color: var(--p-200); }

.cmsg-iv-d {
    flex-shrink: 0;
    width: 38px;
    display: flex;
    flex-direction: column;
    align-items: center;
}

.cmsg-iv-dow {
    font-size: 9.5px;
    font-weight: 700;
    letter-spacing: 0.05em;
    text-transform: uppercase;
    color: var(--danger-700);
}

.cmsg-iv-day { font-size: 18px; font-weight: 700; line-height: 1.1; color: var(--text-primary); }

.cmsg-iv-main { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.cmsg-iv-t { font-size: 12px; font-weight: 600; color: var(--text-primary); }
.cmsg-iv-s { font-size: 11px; color: var(--text-tertiary); }

.cmsg-doc { display: flex; align-items: center; gap: 9px; text-decoration: none; }

.cmsg-doc-mark {
    flex-shrink: 0;
    width: 26px;
    height: 26px;
    display: grid;
    place-items: center;
    border-radius: var(--r-md);
    background: var(--p-50);
    color: var(--p-700);
}

.cmsg-doc-main { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 1px; }

.cmsg-doc-n {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-size: 11.5px;
    font-weight: 600;
    color: var(--text-primary);
}

.cmsg-doc:hover .cmsg-doc-n { color: var(--p-700); }

.cmsg-doc-s { font-size: 10px; color: var(--text-disabled); }

/* ---------- Narrower than a desk ---------- */

/* The rail goes first. Everything on it is one tap away on the
   application itself, and 260px of context is not worth taking
   from a 600px conversation. */
@media (max-width: 1120px) {
    .cmsg { grid-template-columns: 284px minmax(0, 1fr); }
    .cmsg-rail { display: none; }
}

/* One pane at a time. .is-open means the conversation is the
   one being read, which on a desk is only ever "a thread is
   selected" and changes nothing there. */
@media (max-width: 860px) {
    .cmsg {
        grid-template-columns: minmax(0, 1fr);
        height: auto;
        min-height: calc(100dvh - 104px);
        border-radius: var(--r-lg);
    }

    .cmsg-list { border-inline-end: none; }
    .cmsg-convo { display: none; }

    .cmsg.is-open .cmsg-list { display: none; }
    .cmsg.is-open .cmsg-convo { display: flex; }

    .cmsg-back { display: grid; }

    .cmsg-convo-head { padding-inline: 12px; }
    .cmsg-stream { padding: 14px 14px 8px; }
    .cmsg-run-body { max-width: 84%; }
    .cmsg-ai-tray { padding-inline: 14px; }
    .cmsg-comp { padding: 10px 14px 16px; }

    /* Thumb-sized, and the field stops competing for the row:
       the AI draft is the one control that can go, because the
       chips above the box are the same offer in fewer taps. */
    .cmsg-field { border-radius: var(--r-lg); }
    .cmsg-ta { font-size: 14px; }
    .cmsg-ai-btn { display: none; }
    .cmsg-icon-btn { width: 36px; height: 36px; }
    .cmsg-send { width: 44px; height: 44px; border-radius: var(--r-lg); }
    .cmsg-chip { height: 32px; font-size: 12.5px; }
    .cmsg-hint { display: none; }
}

/* Every one of these is a tint or a ring arriving over 120ms, and
   each says the same thing the instant it lands. */
@media (prefers-reduced-motion: reduce) {
    .cmsg-row,
    .cmsg-search,
    .cmsg-field,
    .cmsg-send,
    .cmsg-new,
    .cmsg-chip,
    .cmsg-ai-btn,
    .cmsg-icon-btn,
    .cmsg-head-link,
    .cmsg-seg button { transition: none; }
}
