/* Component classes — reference tokens.css variables only (no hard-coded values).
   Layout: fixed dark sidebar + top search bar + scrolling content (see design-refs/). */
body {
  margin: 0;
  background: var(--ps-bg);
  color: var(--ps-text);
  font-family: var(--ps-font);
  -webkit-font-smoothing: antialiased;
}
* { box-sizing: border-box; }

/* ----- App shell ------------------------------------------------------- */
/* Shell: full-width banner strip on top, then the nav+content row fills the rest. */
.ps-shell { display: flex; flex-direction: column; min-height: 100vh; }
.ps-app { display: flex; flex: 1 1 auto; min-height: 0; }

.ps-sidebar {
  width: var(--ps-sidebar-w);
  flex: 0 0 var(--ps-sidebar-w);
  background: var(--ps-sidebar);
  color: var(--ps-sidebar-text);
  border-right: 1px solid var(--ps-border);
  display: flex;
  flex-direction: column;
  padding: 16px 12px;
  position: sticky;
  top: var(--ps-topbar-h);
  height: calc(100vh - var(--ps-topbar-h));
}
.ps-brand {
  display: flex; align-items: center; gap: 10px;
  font-weight: 800; font-size: 19px; letter-spacing: -.02em;
  color: var(--ps-text); padding: 4px 10px 20px;
}
.ps-brand-mark {
  width: 26px; height: 26px; border-radius: 8px;
  background: linear-gradient(135deg, #6f7bff, var(--ps-primary));
}
.ps-nav-group { display: flex; flex-direction: column; gap: 2px; }
.ps-nav {
  display: flex; align-items: center; gap: 11px;
  padding: 8px 12px; border-radius: var(--ps-radius-sm);
  color: var(--ps-sidebar-text); text-decoration: none;
  font-weight: 500; font-size: 13.5px; cursor: pointer;
}
.ps-nav-ico { width: 18px; text-align: center; font-size: 15px; opacity: .75; }
/* The click target inside a nav link — it has to FILL the link, or a press on the
   link's own padding is not a press on the thing that counts them. */
.ps-nav-hit { display: flex; align-items: center; gap: 11px; flex: 1; min-width: 0; }
.ps-nav:hover { background: var(--ps-sidebar-2); color: var(--ps-text); }

/* Which screen am I on? This is where people look to answer that, so it has to be
   answerable at a glance and from the corner of your eye.
   It previously said so in colour alone — a #eef0fe tint on a #ffffff sidebar, which is
   a whisker away from the #f4f5f9 hover and near-invisible in a screenshot. Two problems
   with that: you cannot see it, and colour-only state fails WCAG 1.4.1 for anyone who
   cannot distinguish it at all.
   So the primary cue is now a SHAPE — a solid bar down the leading edge, which reads
   instantly and survives being colour-blind, low-contrast or squinting. The tint and the
   weight stay as reinforcement.
   John, 2026-08-11, on the 3px version: *"I can't see at a glance if the employee grid I'm
   looking at is the home screen, the overview screen, or the employee data screen."* Those
   three ARE three big grids of names and salaries, so the nav is doing the whole job of
   telling them apart — 4px and a shorter inset, which is the difference between a hairline
   and a marker. */
.ps-nav.active {
  position: relative;
  background: var(--ps-primary-soft);
  color: var(--ps-sidebar-active);
  font-weight: 600;
}
.ps-nav.active::before {
  content: ""; position: absolute; left: 0; top: 5px; bottom: 5px;
  width: 4px; border-radius: 0 3px 3px 0;
  background: var(--ps-sidebar-active);
}
/* Hovering the item you are already on must not make it look like a different item. */
.ps-nav.active:hover { background: var(--ps-primary-soft); color: var(--ps-sidebar-active); }
.ps-nav.active .ps-nav-ico { opacity: 1; }
.ps-nav-spacer { flex: 1; }
/* Unread count on the Inbox nav row. Sits right of the label and pushes to the edge, so a
   two-digit count never shoves the label out of the sidebar. Empty when nothing is unread —
   the span always renders (a Dash Output needs its id present), it just has nothing in it. */
.ps-nav-badge:not(:empty) {
  margin-left: auto; min-width: 18px; padding: 0 5px; border-radius: 9px;
  background: var(--ps-primary); color: #fff;
  font-size: 11px; font-weight: 700; line-height: 18px; text-align: center;
  font-variant-numeric: tabular-nums;
}
/* Sidebar-foot action ('+ Add task') — a nav row, not a pill floating over the grid. */
.ps-nav-btn {
  width: 100%; text-align: left; font: inherit; font-size: 13.5px; font-weight: 500;
  border: 1px dashed var(--ps-border); background: transparent; cursor: pointer;
  color: var(--ps-text-muted);
}
.ps-nav-btn:hover { background: var(--ps-sidebar-2); color: var(--ps-text); }

.ps-userbox {
  display: flex; align-items: center; gap: 10px;
  padding: 10px; border-radius: 9px; background: var(--ps-sidebar-2); margin-top: 8px;
}
.ps-avatar {
  width: 32px; height: 32px; border-radius: 50%; flex: 0 0 32px;
  background: linear-gradient(135deg, #6f7bff, var(--ps-primary));
  color: #fff; display: flex; align-items: center; justify-content: center;
  font-weight: 700; font-size: 12px;
}
.ps-userinfo { display: flex; flex-direction: column; line-height: 1.25; overflow: hidden; }
.ps-userinfo b { font-size: 13px; color: var(--ps-text); }
.ps-userinfo span { font-size: 11px; color: var(--ps-text-muted); text-transform: capitalize; }

/* Numbers align down a column wherever this class is applied. */
.num, .ps-num { font-variant-numeric: tabular-nums; }

/* ----- The type scale, as classes -------------------------------------
   These exist so pages stop hand-rolling inline font sizes (there were ~90
   one-off "fontSize" literals). Anything textual should be able to reach for
   one of these instead of inventing a size. */
.ps-t-title  { font-size: 18px; font-weight: 600; letter-spacing: -.01em; }
.ps-t-card   { font-size: 14.5px; font-weight: 700; letter-spacing: -.01em; }
.ps-t-body   { font-size: 13.5px; }
.ps-t-sm     { font-size: 12.5px; }
.ps-t-caps {
  font-size: 11px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase;
  color: var(--ps-faint);
}
/* Semantic text colours — use these instead of literal greens/reds/ambers. */
.ps-pos  { color: var(--ps-positive); font-weight: 600; }
.ps-neg  { color: var(--ps-negative); font-weight: 600; }
.ps-warn { color: var(--ps-warn); font-weight: 600; }
.ps-muted { color: var(--ps-text-muted); }
.ps-faint { color: var(--ps-faint); }

/* Keyboard focus is always visible (accessibility floor). */
a:focus-visible, button:focus-visible, input:focus-visible,
select:focus-visible, textarea:focus-visible, [tabindex]:focus-visible {
  outline: 2px solid var(--ps-primary); outline-offset: 2px; border-radius: 6px;
}

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

/* ----- Topbar: where you are · which cycle · what needs attention · who you are -- */
.ps-topbar {
  height: var(--ps-topbar-h); display: flex; align-items: center; gap: 14px;
  padding: 0 24px; background: var(--ps-surface);
  border-bottom: 1px solid var(--ps-border); position: sticky; top: 0; z-index: 20;
}
.ps-topbar-title {
  margin: 0; font-size: 18px; font-weight: 600; letter-spacing: -.01em;
  color: var(--ps-text); white-space: nowrap;
}
.ps-topbar-cycle { display: flex; align-items: center; min-width: 0; }
.ps-cyclepill-phase { color: #7d86e0; font-weight: 500; }

/* Attention chip — one amber pill replacing the old stack of full-width banners. */
.ps-status-chip {
  display: inline-flex; align-items: center; gap: 7px; cursor: pointer;
  font: inherit; font-size: 12.5px; font-weight: 600;
  padding: 6px 12px; border-radius: 999px;
  border: 1px solid var(--ps-warn-border);
  /* A fact about the row, not a warning about it — the amber attention chip
     would read as "something is wrong with this person". */
  background: var(--ps-row-hover); color: var(--ps-faint);
  border: 1px solid var(--ps-border);
}
.ps-status-chip:hover { filter: brightness(.98); }
.ps-status-chip.info {
  border-color: color-mix(in srgb, var(--ps-primary) 22%, transparent);
  background: var(--ps-primary-soft); color: var(--ps-primary-dark);
}
.ps-status-count {
  background: var(--ps-warn-solid); color: #fff; border-radius: 999px;
  padding: 0 6px; font-size: 11px; line-height: 17px; min-width: 17px; text-align: center;
}
.ps-status-chip.info .ps-status-count { background: var(--ps-primary); }
.ps-status-drop { padding: 6px !important; }
.ps-status-head {
  font-size: 11px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase;
  color: var(--ps-faint); padding: 6px 10px 8px;
}
.ps-status-row {
  display: flex; align-items: flex-start; gap: 10px;
  padding: 9px 10px; border-top: 1px solid var(--ps-row-border); font-size: 13px;
}
.ps-status-dot {
  width: 7px; height: 7px; border-radius: 50%; flex: 0 0 7px; margin-top: 6px;
  background: var(--ps-warn-solid);
}
.ps-status-dot.info { background: var(--ps-primary); }
.ps-status-text { flex: 1; min-width: 0; line-height: 1.45; }
.ps-status-link {
  display: inline-block; margin-left: 6px; font-weight: 600;
  color: var(--ps-primary); text-decoration: none;
}
.ps-status-link:hover { text-decoration: underline; }
.ps-status-x {
  border: none; background: transparent; cursor: pointer; font-size: 13px;
  color: var(--ps-faint); padding: 0 2px; line-height: 1.4;
}
.ps-status-x:hover { color: var(--ps-text); }

/* Avatar menu (replaces the sidebar user card + Log out). */
.ps-avatar-btn { border: none; cursor: pointer; font-family: inherit; }
.ps-user-drop { padding: 6px !important; min-width: 210px; }
.ps-user-id { display: flex; flex-direction: column; padding: 8px 10px 6px; }
.ps-user-id b { font-size: 13.5px; }
.ps-user-email { font-size: 12px; color: var(--ps-text-muted); }
.ps-user-role {
  font-size: 11px; font-weight: 700; letter-spacing: .05em; text-transform: uppercase;
  color: var(--ps-faint); margin-top: 3px;
}
.ps-search {
  display: flex; align-items: center; gap: 8px;
  background: var(--ps-bg); border: 1px solid var(--ps-border);
  border-radius: 9px; padding: 0 12px; width: 340px; height: 38px;
}
.ps-search-ico { color: var(--ps-text-muted); font-size: 15px; }
.ps-search-input {
  border: none; background: transparent; outline: none; width: 100%;
  font-family: var(--ps-font); font-size: 14px; color: var(--ps-text);
}
.ps-page { padding: 16px 28px 26px; }   /* top 16px aligns the heading with the nav card top */

/* ----- FX warning banners ---------------------------------------------- */
.ps-banner {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  padding: 12px 28px; font-size: 13.5px; border-bottom: 1px solid var(--ps-border);
}
.ps-banner.warn { background: #fdf1df; color: #8a5a14; }
/* "Draft changes are not yet saved to the cycle", beside the Save button on Pay Bands.
   The same amber as .ps-banner.warn so unsaved work reads the same everywhere, but as a
   compact pill: the page-banner padding (12px 28px + a bottom border) is built to span the
   window and would have forced the table header taller whether or not there was anything to
   warn about. It renders only while there ARE unsaved edits — a permanently visible warning
   is furniture, and furniture is ignored. */
.ps-draft-flag {
  background: #fdf1df; color: #8a5a14; font-size: 12px; font-weight: 600;
  padding: 4px 10px; border-radius: 999px; white-space: nowrap;
  /* On a narrow screen the FLAG gives way (ellipsis), never the Save button beside it —
     "Save changes to cycle" clipped to "Save changes to" is an instruction to do half a
     thing. John, 2026-08-25: *"On smaller screens the button text gets trimmed."* */
  min-width: 0; overflow: hidden; text-overflow: ellipsis;
}

/* Buttons in a card header never shrink below their label. Flex rows shrink every child
   when space runs out, and a Mantine button clips its own text when squeezed — an action
   whose name is cut off mid-phrase reads as a different action. The texty things beside
   them give way instead: .ps-draft-flag above, .ps-undo-msg (already ellipsized further
   down this file). */
.ps-tablehead .mantine-Button-root { flex-shrink: 0; }
.ps-banner.info { background: var(--ps-primary-soft); color: var(--ps-primary-dark); }
.ps-banner-link { font-weight: 600; color: inherit; text-decoration: underline; }
.ps-banner-x {
  margin-left: auto; border: none; background: transparent; cursor: pointer;
  font: inherit; font-weight: 600; color: inherit; text-decoration: underline;
}

/* ----- Page header ----------------------------------------------------- */
.ps-pagehead { display: flex; justify-content: space-between; align-items: flex-end; margin-bottom: 22px; }
.ps-pagehead h2 { margin: 0; font-size: 23px; font-weight: 700; letter-spacing: -.02em; }
.ps-pagehead .ps-sub { color: var(--ps-text-muted); font-size: 13px; margin-top: 4px; }
.ps-cyclepill {
  font-size: 12px; font-weight: 600; color: var(--ps-primary);
  background: var(--ps-primary-soft); padding: 6px 13px; border-radius: 20px;
}

/* ----- KPI icon-badge cards ------------------------------------------- */
/* Band header: what this money is scoped to (left) · cost basis (right). */
.ps-band-toolbar {
  display: flex; align-items: center; justify-content: space-between;
  gap: 10px; margin-bottom: 10px;
}
.ps-band-label { display: flex; align-items: center; gap: 9px; min-width: 0; }
.ps-band-name {
  font-size: 11px; font-weight: 700; letter-spacing: .07em; text-transform: uppercase;
  color: var(--ps-faint);
}
.ps-band-count {
  font-size: 12px; color: var(--ps-text-muted);
  background: var(--ps-surface); border: 1px solid var(--ps-border);
  border-radius: 999px; padding: 2px 9px; font-weight: 600;
}
.ps-costbasis { display: flex; align-items: center; gap: 8px; }
.ps-costbasis-v { font-size: 12.5px; font-weight: 600; color: var(--ps-text); }
.ps-toolbar-label { color: var(--ps-text-muted); font-size: 13px; font-weight: 500; }

/* Single-figure facts at the right-hand end of the band's top row — promotions spend, the
   two-stage saving. Each was a card on its own line for one number; here they cost no
   vertical space at all. They wrap under the cost-basis control on a narrow window rather
   than squeezing the pool bars. */
.ps-band-toolbar-right { display: flex; align-items: center; gap: 18px; flex-wrap: wrap;
  justify-content: flex-end; }
.ps-band-asides { display: flex; align-items: center; gap: 18px; flex-wrap: wrap; }
.ps-band-aside { display: flex; align-items: baseline; gap: 7px; white-space: nowrap; }
.ps-band-aside-v { font-weight: 700; font-size: 14px; color: var(--ps-text); }
.ps-band-aside-pos .ps-band-aside-v { color: var(--ps-positive); }
.ps-band-aside-sub { color: var(--ps-faint); font-size: 11.5px; }

/* ----- Budget pools: one compact bar per pool (2+ pools) ---------------- */
.ps-pools { display: flex; flex-direction: column; gap: 10px; margin-bottom: var(--ps-gap); }
.ps-pool {
  /* ONE ROW: who · Budget · the bar · the figures · the corner stack, all vertically centred
     (John, 2026-10-15: the stack "is to be vertically aligned with the current items in the
     tile" — never a section above or below them). The Budget column is fixed so every
     pool's bar starts in the same place. Narrower screens: see the media queries below. */
  display: grid;
  grid-template-columns: minmax(120px, 160px) 190px minmax(110px, 180px) 1fr auto;
  grid-template-areas: "who budget bar figs stack";
  gap: 20px; align-items: center; padding: 12px 20px;
}
.ps-pool-who { grid-area: who; }
.ps-pool-budget { grid-area: budget; }
.ps-pool-progress { grid-area: bar; }
.ps-pool-figs { grid-area: figs; }
.ps-pool .ps-pool-stack, .ps-pool .ps-pool-corner { grid-area: stack; }
.ps-pool-figs { display: flex; flex-wrap: nowrap; gap: 12px 22px; align-items: flex-start; }
.ps-pool-figs > .ps-pool-fig { flex: 0 1 auto; }
/* How the budget was set ("3% of £9,192,688 current payroll"), under the Budget figure. */
.ps-pool-budget-how { font-size: 11.5px; color: var(--ps-text-muted); margin-top: 2px; }
/* The space between what's left of the budget and the spend figures. */
.ps-pool-figs > .ps-pool-fig-gap {
  margin-left: 14px; padding-left: 26px; border-left: 1px solid var(--ps-border);
}
/* A figure never breaks inside: the +2.1% stays beside its number, every label on one line,
   so the numbers and the words under them line up along the row. */
.ps-pool .ps-pool-fig-v, .ps-pool .ps-pool-fig-l { white-space: nowrap; }
.ps-pool-who { min-width: 0; }
.ps-pool-name { font-weight: 700; font-size: 14.5px; letter-spacing: -.01em; }
.ps-pool-meta {
  display: flex; align-items: center; flex-wrap: wrap; gap: 6px;
  font-size: 12px; color: var(--ps-text-muted); margin-top: 3px;
}
.ps-pool-tag {
  font-size: 10px; font-weight: 700; letter-spacing: .05em; text-transform: uppercase;
  background: var(--ps-bg); border: 1px solid var(--ps-border);
  color: var(--ps-text-muted); border-radius: 5px; padding: 1px 6px;
}
.ps-pool-track {
  height: 8px; border-radius: 999px; background: #eef0f4; overflow: hidden;
}
.ps-pool-fill { height: 100%; border-radius: 999px; background: var(--ps-positive); }
.ps-pool-fill.near { background: var(--ps-kpi-amber); }
.ps-pool-fill.over { background: var(--ps-negative); }
.ps-pool-cap { font-size: 11.5px; color: var(--ps-text-muted); margin-top: 6px; }
.ps-pool-cap.warn { color: var(--ps-warn); font-weight: 600; margin-top: 0; }
.ps-pool-fig-v { font-size: 16.5px; font-weight: 700; letter-spacing: -.01em; }
.ps-pool-fig.pos .ps-pool-fig-v { color: var(--ps-positive); }
.ps-pool-fig.neg .ps-pool-fig-v { color: var(--ps-negative); }
.ps-pool-fig-l {
  font-size: 11px; font-weight: 600; letter-spacing: .05em; text-transform: uppercase;
  color: var(--ps-faint); margin-top: 2px;
}
.ps-pool-delta { font-size: 12.5px; font-weight: 600; }
.ps-pool-delta.pos { color: var(--ps-positive); }
.ps-pool-delta.neg { color: var(--ps-negative); }
.ps-pool-warn { border-color: var(--ps-warn-border); background: #fffdf8; }
.ps-pools-more > summary {
  cursor: pointer; font-size: 13px; font-weight: 600; color: var(--ps-primary);
  padding: 8px 4px; user-select: none;
}
/* The layout follows the width of the TILES, not the window: with the sidebar open on a
   laptop the window can be wide while the tiles are not, and the figures were squeezed until
   Proposed payroll wrapped under Over budget (John, 2026-10-16: "Can that stay on the
   right?"). So the figures never wrap — the row they sit in changes instead.
   The toolbar copy (.ps-band-mini) is a flex row sized by its content, so it is no container. */
.ps-pools { container-type: inline-size; }
.ps-band-mini .ps-pools { container-type: normal; }
/* Not quite wide enough for one row (a laptop): the bar moves UNDER the Budget figure it
   measures, and everything else stays on the row — figures in the middle, the stack on the
   right. A bar stretched across a row of its own was mostly empty track (John, 2026-10-16:
   "we waste so much space with that chart line"). Widths measured on Masabi's tile: one row
   needs ~1360px, this ~1240px. */
@container (max-width: 1420px) {
  .ps-pool {
    grid-template-columns: minmax(120px, 160px) 200px 1fr auto;
    grid-template-areas: "who budget figs stack" "who bar figs stack";
    row-gap: 8px;
  }
}
/* Narrower: the figures take a row of their own under the rest. */
@container (max-width: 1260px) {
  .ps-pool {
    grid-template-columns: minmax(120px, 160px) 200px 1fr;
    grid-template-areas: "who budget stack" "who bar stack" "figs figs figs";
    row-gap: 10px;
  }
}
/* Narrower still: the stack drops under the figures. */
@container (max-width: 1000px) {
  .ps-pool {
    grid-template-columns: minmax(120px, 160px) 190px 1fr;
    grid-template-areas: "who budget bar" "figs figs figs" "stack stack stack";
  }
}
/* A phone: one column, and only now may the figures wrap. */
@container (max-width: 640px) {
  .ps-pool { grid-template-columns: 1fr;
             grid-template-areas: "who" "budget" "bar" "figs" "stack"; }
  .ps-pool-figs { flex-wrap: wrap; }
  .ps-pool-figs > .ps-pool-fig-gap { margin-left: 0; padding-left: 0; border-left: 0; }
}

/* Manager columns hold an email; the grid formatter strips the domain and this
   capitalises the result, so "lewis.theobald@…" reads as "Lewis Theobald". */
.ag-theme-quartz.ps-grid .ps-name-cell { text-transform: capitalize; }

/* ----- Employee identity cell (name + job title) ------------------------ */
.ps-emp-cell { line-height: 1.25; padding: 4px 0; }
.ps-emp-name { font-weight: 600; }
/* ★ in the name cell (assets/ag-renderers.js narraStar). The empty star only shows on hover,
   so a grid of unflagged people isn't a column of outlines. */
.ps-name-star { margin-left: 6px; cursor: pointer; font-size: 15px; line-height: 1;
                color: #f59f00; font-weight: 400; }
.ps-name-star.asked { opacity: .85; }
.ps-name-star.empty { color: #c9ced8; opacity: 0; }
.ag-row-hover .ps-name-star.empty, .ps-name-star.empty:focus { opacity: 1; }
.ps-name-star:hover { color: #f59f00; opacity: 1; }
.ps-emp-sub {
  font-size: 12px; color: var(--ps-text-muted); margin-top: 1px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

.ps-kpis { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--ps-gap); margin-bottom: var(--ps-gap); }
.ps-kpis-4 { grid-template-columns: repeat(4, 1fr); }
/* Per-pool tiles: a fixed 5-column grid so tiles line up vertically across pools (a 3-tile
   fixed pool's cells align under a 5-tile % pool's; empty trailing columns just stay blank). */
.ps-kpis-5 { grid-template-columns: repeat(5, 1fr); }
/* As many tiles as fit, wrapping on a narrow screen (closed-cycle pool tiles). */
.ps-kpis-auto { grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); }
.ps-kpi {
  background: var(--ps-surface); border: 1px solid var(--ps-border);
  border-radius: var(--ps-radius); box-shadow: var(--ps-shadow);
  padding: 16px 18px; display: flex; align-items: center; gap: 14px;
}
.ps-kpi-badge {
  width: 40px; height: 40px; flex: 0 0 40px; border-radius: 10px;
  display: flex; align-items: center; justify-content: center; font-size: 17px;
}
.ps-kpi-badge.blue  { color: var(--ps-kpi-blue);  background: var(--ps-kpi-blue-bg); }
.ps-kpi-badge.amber { color: var(--ps-kpi-amber); background: var(--ps-kpi-amber-bg); }
.ps-kpi-badge.green { color: var(--ps-kpi-green); background: var(--ps-kpi-green-bg); }
.ps-kpi-body { display: flex; flex-direction: column; }
.ps-kpi-value { font-size: 21px; font-weight: 700; letter-spacing: -.01em; font-variant-numeric: tabular-nums; }
.ps-kpi-value.pos { color: var(--ps-positive); }
.ps-kpi-value.neg { color: var(--ps-negative); }
.ps-kpi-label { color: var(--ps-text-muted); font-size: 12px; font-weight: 500; margin-top: 2px; }
/* A tile with no icon badge (Pay Equity, Insights) — same type, no leading gap. */
.ps-kpi-plain { flex: 1; min-width: 150px; }

/* A KPI tile that is also the way in to its list — conflicts & coverage gaps. The number is
   the headline; the list behind it opens underneath, one at a time. */
.ps-kpi-btn { cursor: pointer; position: relative; transition: border-color .12s, box-shadow .12s; }
.ps-kpi-btn:hover { border-color: var(--ps-primary); }
.ps-kpi-btn.is-open { border-color: var(--ps-primary); box-shadow: 0 0 0 1px var(--ps-primary); }
/* The chevron points right while closed and down once the list is open, so the tile says
   which one you are reading. */
.ps-kpi-chev {
  margin-left: auto; color: var(--ps-faint); font-size: 18px; line-height: 1;
  transition: transform .12s; transform-origin: 50% 50%;
}
.ps-kpi-btn.is-open .ps-kpi-chev { transform: rotate(90deg); color: var(--ps-primary); }

/* ----- Spend breakdown panel (Current & Proposed columns adjacent) ------ */
.ps-spend {
  background: var(--ps-surface); border: 1px solid var(--ps-border);
  border-radius: var(--ps-radius); box-shadow: var(--ps-shadow);
  padding: 16px 22px; margin-bottom: var(--ps-gap);
}
.ps-spend-title { color: var(--ps-text-muted); font-size: 13px; font-weight: 500; }
.ps-spend-summary {
  cursor: pointer; font-size: 14px; font-weight: 500; color: var(--ps-text-muted);
  list-style-position: inside; user-select: none;
}
.ps-spend-summary::marker { color: var(--ps-text-muted); }
/* label fills the left; current / proposed / change columns sit on the right. */
.ps-spend-row {
  display: grid; grid-template-columns: 1fr 140px 140px 96px; column-gap: 18px;
  align-items: center; padding: 8px 0; border-top: 1px solid var(--ps-row-border);
}
.ps-chg.pos { color: var(--ps-positive); }
.ps-chg.neg { color: var(--ps-negative); }
.ps-spend-label { display: flex; align-items: center; min-width: 0; }
.ps-spend-amt { text-align: right; font-variant-numeric: tabular-nums; font-weight: 600; font-size: 14px; }
/* Column captions */
.ps-spend-caption { border-top: none; padding-bottom: 2px; }
.ps-spend-caption .ps-spend-amt {
  color: var(--ps-text-muted); font-weight: 600; font-size: 11px;
  text-transform: uppercase; letter-spacing: .04em;
}
/* The headline totals row */
.ps-spend-totalsrow { border-top: none; padding: 2px 0 8px; }
.ps-spend-totalsrow .ps-spend-amt {
  font-size: 24px; font-weight: 750; letter-spacing: -.02em;
}
/* Grouping dropdown in the caption row */
.ps-spend-grouper { width: 200px; font-size: 13px; }
.ps-spend-grouper .Select-control,
.ps-spend-grouper .is-focused:not(.is-open) > .Select-control {
  border: 1px solid var(--ps-border); border-radius: 8px; box-shadow: none;
}

/* Currency pill (shared with table) */
.ps-pill {
  display: inline-block; font-size: 11px; font-weight: 700; letter-spacing: .03em;
  color: var(--ps-primary); background: var(--ps-primary-soft);
  padding: 3px 9px; border-radius: 6px;
}
/* Spend-breakdown inner text → match the nav type (weight 500, muted); labels as plain text
   rather than coloured pills. Scoped to .ps-spend so the table's currency pills are untouched. */
.ps-spend .ps-spend-label .ps-pill {
  background: transparent; color: var(--ps-text-muted);
  font-weight: 500; font-size: 14px; letter-spacing: 0; padding: 0;
}
.ps-spend .ps-spend-amt { font-weight: 500; }

/* Signed % deltas in the grid */
.ps-num-pos { color: var(--ps-positive); font-weight: 600; }
.ps-num-neg { color: var(--ps-negative); font-weight: 600; }
.ps-num-burden { color: #b8860b; font-weight: 600; }  /* Loading Modifier — dark yellow */
/* Info (ⓘ) columns: clip so no ellipsis ".." renders after the icon. */
.ps-grid .ps-info-col { text-overflow: clip; padding-left: 4px; padding-right: 4px; }
/* Proposals list table */
.ps-proptable { width: 100%; border-collapse: collapse; }
.ps-proptable th, .ps-proptable td {
  text-align: left; padding: 8px 10px; border-bottom: 1px solid var(--ps-border);
  font-size: 14px; }
.ps-proptable th { color: var(--ps-text-muted); font-weight: 600; font-size: 12px;
  text-transform: uppercase; letter-spacing: 0.03em; }

/* ----- Card + neat data table ----------------------------------------- */
.ps-card {
  background: var(--ps-surface); border: 1px solid var(--ps-border);
  border-radius: var(--ps-radius); box-shadow: var(--ps-shadow);
  padding: 16px; margin-bottom: var(--ps-gap);
}
/* Card title — the ONE house standard for a card/section heading (matches .ps-tablehead h3),
   so html.H3 inside a card needs no per-instance margin/size overrides. */
.ps-card h3, .ps-card-title {
  margin: 0 0 var(--ps-gap-sm); font-size: 15px; font-weight: 650;
  letter-spacing: -.01em; color: var(--ps-text);
}
/* Muted help / secondary text — the ONE house standard (replaces ~140 inline copies).
   NB the sizes here are 13/12px on purpose: they match the values the pages already
   use inline, so class and literal land on the SAME step rather than differing by half
   a pixel. Migrate literals to these classes when touching a file. */
.ps-help { color: var(--ps-text-muted); font-size: 13px; }
.ps-help-sm { color: var(--ps-text-muted); font-size: 12px; }
/* Action feedback text after a save/sync etc. */
.ps-feedback { font-size: 13px; }
.ps-feedback.ok { color: var(--ps-positive); }
.ps-feedback.err { color: var(--ps-negative); }
/* Horizontal layout inside a card without inline flex/gap literals. */
.ps-card-row { display: flex; gap: var(--ps-gap); align-items: flex-start; }
.ps-tablecard {
  background: var(--ps-surface); border: 1px solid var(--ps-border);
  border-radius: var(--ps-radius); box-shadow: var(--ps-shadow);
  overflow: hidden; margin-bottom: var(--ps-gap);
}
/* Grid toolbar — lives INSIDE the table card, separated from the grid by a hairline. */
.ps-tablehead {
  display: flex; align-items: center; gap: 10px; padding: 12px 16px;
  border-bottom: 1px solid var(--ps-border);
}
.ps-tablehead h3 { margin: 0; font-size: 14.5px; font-weight: 700; }
.ps-count {
  font-size: 12px; font-weight: 700; color: var(--ps-text-muted);
  background: var(--ps-bg); padding: 2px 9px; border-radius: 20px;
}
.ps-tablehead .ps-nav-spacer { flex: 1; }

/* Category tag in a table cell (Audit Log). The job is scanning: an access change or an
   export sitting in a long list of pay edits should catch the eye before it's read. Only
   those two get the amber variant — tag everything and nothing stands out. */
.ps-chip {
  display: inline-block; font-size: 11.5px; font-weight: 600; white-space: nowrap;
  padding: 2px 8px; border-radius: 20px;
  background: var(--ps-bg); color: var(--ps-text-muted);
  border: 1px solid var(--ps-border);
}
/* A timestamp is one thing; wrapping "2026-08-04 12:57" onto three lines makes a table
   three times as tall and no easier to read. */
.ps-nowrap { white-space: nowrap; }
/* For a cell holding something with no spaces in it — a filename, a long email. Without
   this its min-content width is the whole token, which pushes the table wider than its card
   and clips the column off the right-hand edge. */
.ps-wrap-any { overflow-wrap: anywhere; }
.ps-chip.warn {
  /* A fact about the row, not a warning about it — the amber attention chip
     would read as "something is wrong with this person". */
  background: var(--ps-row-hover); color: var(--ps-faint);
  border: 1px solid var(--ps-border);
  border-color: var(--ps-warn-border);
}

/* AG Grid quartz overrides → restrained, horizontal-ruled, comfortable rows */
.ag-theme-quartz.ps-grid {
  --ag-font-family: var(--ps-font);
  --ag-foreground-color: var(--ps-text);
  --ag-secondary-foreground-color: var(--ps-text-muted);
  --ag-header-foreground-color: var(--ps-text-muted);
  --ag-header-background-color: var(--ps-surface);
  --ag-background-color: var(--ps-surface);
  --ag-odd-row-background-color: var(--ps-surface);
  --ag-row-hover-color: var(--ps-row-hover);
  --ag-selected-row-background-color: var(--ps-primary-soft);
  --ag-border-color: var(--ps-row-border);
  --ag-row-border-color: var(--ps-row-border);
  --ag-header-column-separator-display: none;
  --ag-cell-horizontal-border: transparent;
  --ag-wrapper-border-radius: 0;
  --ag-borders: none;
  --ag-row-height: 46px;
  --ag-header-height: 42px;
  --ag-font-size: 13.5px;
  --ag-accent-color: var(--ps-primary);
  --ag-cell-horizontal-padding: 18px;
}
.ag-theme-quartz.ps-grid .ag-header {
  border-bottom: 1px solid var(--ps-border);
  text-transform: uppercase; font-size: 11px; letter-spacing: .04em; font-weight: 600;
}
.ag-theme-quartz.ps-grid .ag-row { border-bottom: 1px solid var(--ps-row-border); }
/* Left-align all cell content, and keep the sort/filter triangle on the RIGHT of every
   column heading — overriding AG Grid's right-aligned (numeric) columns, which would
   otherwise right-align the cell and push the icon to the LEFT. */
.ag-theme-quartz.ps-grid .ag-cell { text-align: left; justify-content: flex-start; }
.ag-theme-quartz.ps-grid .ag-cell .ag-cell-value { text-align: left; }
.ag-theme-quartz.ps-grid .ag-cell-editor input { text-align: left; }
.ag-theme-quartz.ps-grid .ag-header-cell .ag-cell-label-container { flex-direction: row; }
.ag-theme-quartz.ps-grid .ag-header-cell .ag-header-cell-label { flex-direction: row; }
/* Let heading text wrap onto multiple lines so long labels are fully readable
   (pairs with wrapHeaderText + autoHeaderHeight on the column defs). flex:1 lets it take
   the available width and wrap rather than clip; the sort arrow stays pinned right. */
.ag-theme-quartz.ps-grid .ag-header-cell-text {
  text-align: left; flex: 1 1 auto;
  white-space: normal; overflow-wrap: anywhere; line-height: 1.2;
}
/* Force the sort arrow (and any header icon) to the far right of the heading, no matter
   how AG Grid laid the header out for this column. */
.ag-theme-quartz.ps-grid .ag-header-cell-label .ag-sort-indicator-container {
  margin-left: auto; order: 99;
}
.ag-theme-quartz.ps-grid .ag-header-cell .ag-header-icon,
.ag-theme-quartz.ps-grid .ag-header-cell .ag-header-cell-menu-button { order: 100; }
/* Comp header-group colour bands so Current / Proposed / Change read at a glance. */
.ag-theme-quartz.ps-grid .ps-grp-current  { background: #eaf6f1 !important; color: #15715a; }
.ag-theme-quartz.ps-grid .ps-grp-proposed { background: #e8f0fe !important; color: #1b4fa0; }
.ag-theme-quartz.ps-grid .ps-grp-change   { background: #fbf0e4 !important; color: #97600f; }
.ag-theme-quartz.ps-grid .ps-grp-mgr      { background: #ede9fe !important; color: #5b3ba5; }
.ag-theme-quartz.ps-grid .ps-grp-mgr-loaded { background: #ddd3fb !important; color: #4a2e8c; }
/* Proposed-base cell while the manager hasn't accepted the drafted change yet. */
.ag-theme-quartz.ps-grid .ps-cell-unaccepted { background: #fff3cd !important; }
/* Proposed-base cell a manager has queried (review requested — needs HR attention). */
.ag-theme-quartz.ps-grid .ps-cell-queried { background: #fcd6e6 !important; }
/* Proposed pay BELOW current pay — a pay cut. Strong red so it can't be missed. */
.ag-theme-quartz.ps-grid .ps-cut { background: #ffc9c9 !important; color: #c92a2a;
                                   font-weight: 800; }
/* No proposed salary at all — same red as a cut: an empty pay decision needs fixing. */
.ag-theme-quartz.ps-grid .ps-cell-missing { background: #ffc9c9 !important; }
/* OTE columns are the headline comparison — render them bold. */
.ag-theme-quartz.ps-grid .ps-bold-cell { font-weight: 700; }
/* Loaded sub-bars: a slightly darker shade of the parent group colour. */
.ag-theme-quartz.ps-grid .ps-grp-current-loaded  { background: #d2ebe0 !important; color: #0f5a47; }
.ag-theme-quartz.ps-grid .ps-grp-proposed-loaded { background: #d2e2fb !important; color: #163f80; }
/* Keep the selection checkbox / info icon cells centered. */
.ag-theme-quartz.ps-grid .ag-selection-checkbox,
.ag-theme-quartz.ps-grid .ps-info-col { justify-content: center; text-align: center; }
/* Editable cells stay clean until hovered/focused, then read as a periwinkle
   input box (per refs) — keeps dense admin grids tidy. */
.ag-theme-quartz.ps-grid .ps-editable-cell:hover,
.ag-theme-quartz.ps-grid .ps-editable-cell.ag-cell-focus {
  box-shadow: inset 0 0 0 1px var(--ps-primary);
  border-radius: 7px; background: #fcfdff;
}

/* A cell that can't be edited until something else is set (promoted-to level before the
   promotion tick): greyed, and no input-box hover. */
.ag-theme-quartz.ps-grid .ps-editable-cell.ps-cell-off { background: #f4f5f7 !important; color: #a0a6b1; cursor: not-allowed; }
.ag-theme-quartz.ps-grid .ps-editable-cell.ps-cell-off:hover { box-shadow: none; }

/* Employee Data → Data Quality: section title left, its count right. */
.ps-dq-head {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 10px; margin-bottom: 4px;
}

/* ----- Pay Equity: cohort gap table ------------------------------------- */
.ps-cohort-wrap { overflow-x: auto; }
.ps-cohort-table { width: 100%; border-collapse: collapse; }
.ps-cohort-th {
  text-align: left; padding: 8px 10px; font-size: 11px; font-weight: 700;
  color: var(--ps-faint); text-transform: uppercase; letter-spacing: .06em;
  white-space: nowrap;
}
.ps-cohort-td {
  padding: 8px 10px; font-size: 13px; border-top: 1px solid var(--ps-row-border);
  font-variant-numeric: tabular-nums; white-space: nowrap;
}
/* The un-measurable cohorts are context, not findings — folded away by default. */
.ps-cohort-more { margin-top: 14px; border-top: 1px solid var(--ps-border); }
.ps-cohort-more > summary {
  cursor: pointer; user-select: none; padding: 10px 2px 6px;
  font-size: 13px; font-weight: 600; color: var(--ps-text-muted);
}
.ps-cohort-more > summary:hover { color: var(--ps-primary); }
.ps-cohort-more[open] > summary { color: var(--ps-text); }

/* ----- Review Cycles: current-cycle hero + phase stepper ---------------- */
.ps-cyclehero {
  display: flex; align-items: center; gap: 26px; cursor: pointer;
  border-left: 3px solid var(--ps-primary); padding: 16px 20px;
}
.ps-cyclehero-name { font-size: 16px; font-weight: 700; letter-spacing: -.01em; }
/* The open cycle's card (Review Cycles list): soft indigo with an indigo edge down the
   whole card, header included — so it reads as THE live cycle next to the closed ones. The
   edge is an inset shadow, so the card keeps its hairline border and rounded corners. */
.ps-tablecard.ps-current-cycle {
  background: var(--ps-primary-soft);
  box-shadow: inset 4px 0 0 var(--ps-primary), var(--ps-shadow);
}
.ps-current-cycle .ps-tablehead { border-bottom-color: rgba(85, 98, 234, .16); }
.ps-current-cycle .ps-cyclehero { border-left: none; }
/* The whole card opens the cycle (no button) — so it says so on hover. */
.ps-tablecard.ps-current-cycle { cursor: pointer; transition: border-color .12s ease; }
.ps-tablecard.ps-current-cycle:hover { border-color: var(--ps-primary); }
.ps-cyclestat-v { font-size: 21px; font-weight: 700; letter-spacing: -.01em; }
.ps-cyclestat-l {
  font-size: 11px; font-weight: 700; letter-spacing: .05em; text-transform: uppercase;
  color: var(--ps-faint); margin-top: 1px;
}

/* A cycle's phases run in order, so they read as a numbered stepper: equal height,
   one chip style, the current step outlined. */
.ps-steps { display: grid; grid-template-columns: repeat(4, 1fr); gap: 10px; }
.ps-step {
  background: var(--ps-surface); border: 1px solid var(--ps-border);
  border-radius: var(--ps-radius); box-shadow: var(--ps-shadow);
  padding: 13px 16px; display: flex; flex-direction: column; gap: 5px;
  align-items: flex-start;
}
.ps-step.clickable { cursor: pointer; }
.ps-step.clickable:hover { border-color: var(--ps-primary); background: #fcfcff; }
.ps-step.current { border-color: var(--ps-primary); box-shadow: inset 0 0 0 1px var(--ps-primary); }
.ps-step-n { font-size: 11px; font-weight: 700; letter-spacing: .06em; color: var(--ps-faint); }
.ps-step.current .ps-step-n { color: var(--ps-primary); }
.ps-step-t { font-weight: 700; font-size: 13.5px; }
.ps-step-s { font-size: 12px; color: var(--ps-text-muted); line-height: 1.45; }
.ps-step-chip {
  font-size: 10.5px; font-weight: 700; border-radius: 5px; padding: 1px 7px;
}
.ps-step-chip.current { background: var(--ps-primary-soft); color: var(--ps-primary); }
.ps-step-chip.move {
  background: var(--ps-bg); color: var(--ps-text-muted); border: 1px dashed var(--ps-border);
}
@media (max-width: 1180px) {
  .ps-steps { grid-template-columns: repeat(2, 1fr); }
  .ps-cyclehero { flex-wrap: wrap; gap: 16px; }
}

/* Settings → budget-pool rows: identity · budget · headcount · actions, on the same
   row grammar as the Job Families list. */
.ps-poolrow {
  display: grid; grid-template-columns: minmax(200px, 1fr) 160px 110px auto;
  gap: 14px; align-items: center;
  padding: 12px 0; border-top: 1px solid var(--ps-row-border);
}
.ps-poolrow-who { min-width: 0; }
.ps-poolrow-kind, .ps-poolrow-count { font-size: 13px; color: var(--ps-text-muted); }
.ps-pool-roster > summary {
  cursor: pointer; user-select: none; margin-top: 3px; list-style-position: inside;
}
@media (max-width: 1180px) {
  .ps-poolrow { grid-template-columns: 1fr auto; row-gap: 8px; }
  .ps-poolrow-kind, .ps-poolrow-count { grid-column: 1; }
}

/* Settings → manager-access columns: heading left, its action right. */
.ps-mgrcol-head {
  display: flex; align-items: flex-start; gap: 10px; margin-bottom: 10px;
}

/* ----- Empty states: name the next action, never "No Rows To Show" ------ */
.ps-empty { text-align: center; padding: 30px 20px; }
.ps-empty-title { font-size: 14.5px; font-weight: 700; color: var(--ps-text); }
.ps-empty-sub {
  font-size: 13px; color: var(--ps-text-muted); margin-top: 5px; line-height: 1.5;
}

/* ----- Insights grid --------------------------------------------------- */
.ps-filterbar {
  display: flex; flex-wrap: wrap; gap: 12px; align-items: flex-end;
  margin-bottom: var(--ps-gap);
}
.ps-filterbar-wrap { display: flex; flex-wrap: wrap; gap: 16px; align-items: flex-end; }
.ps-section-head {
  display: flex; align-items: center; gap: 4px; margin: 28px 0 14px;
  font-size: 17px; font-weight: 700;
}
/* Chart grid: plain cards on the page ground — the charts carry the colour. */
.ps-insights-grid {
  display: grid; grid-template-columns: repeat(2, 1fr); gap: var(--ps-gap);
}
.ps-insight {
  margin-bottom: 0; padding: 14px 16px;
  background: var(--ps-surface);
  border: 1px solid var(--ps-border);
  border-radius: var(--ps-radius);
  box-shadow: var(--ps-shadow);
}
.ps-insight .js-plotly-plot { cursor: pointer; }
@media (max-width: 1100px) { .ps-insights-grid { grid-template-columns: 1fr; } }

/* Narrative summary card */
.ps-narrative { margin-bottom: var(--ps-gap); }
.ps-narrative-title {
  font-size: 12px; font-weight: 600; text-transform: uppercase; letter-spacing: .05em;
  color: var(--ps-text-muted); margin-bottom: 8px;
}
.ps-narrative ul { margin: 0; padding-left: 18px; }
.ps-narrative li { margin: 5px 0; font-size: 14.5px; line-height: 1.5; }

/* Stat tiles (text + number summaries) — same type as .ps-kpi so Insights' tiles
   and Home's read as one component rather than two sizes of the same idea. */
.ps-stats {
  display: grid; grid-template-columns: repeat(5, 1fr); gap: var(--ps-gap);
  margin-bottom: var(--ps-gap);
}
@media (max-width: 1200px) { .ps-stats { grid-template-columns: repeat(2, 1fr); } }
.ps-stat { margin-bottom: 0; padding: 16px 18px; }
.ps-stat-label {
  font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: .06em;
  color: var(--ps-faint);
}
.ps-stat-value {
  font-size: 21px; font-weight: 700; letter-spacing: -.01em; margin-top: 6px;
  font-variant-numeric: tabular-nums;
}
.ps-stat-value.pos { color: var(--ps-positive); }
.ps-stat-value.neg { color: var(--ps-negative); }
.ps-stat-sub { font-size: 12.5px; color: var(--ps-text-muted); margin-top: 4px; }
.ps-stat-sub.pos { color: var(--ps-positive); }
.ps-stat-sub.neg { color: var(--ps-negative); }

/* Drill-through people table (in the modal) */
.ps-people-table { width: 100%; border-collapse: collapse; font-size: 13.5px; }
.ps-people-table th {
  text-align: left; font-size: 11px; font-weight: 600; text-transform: uppercase;
  letter-spacing: .04em; color: var(--ps-text-muted);
  padding: 8px 12px; border-bottom: 1px solid var(--ps-border);
}
.ps-people-table td { padding: 9px 12px; border-bottom: 1px solid var(--ps-row-border); }
.ps-people-table td.num,
.ps-people-table th.num { text-align: right; font-variant-numeric: tabular-nums; }
.ps-people-table tbody tr:hover { background: var(--ps-row-hover); }
/* A figure in the person's own currency with the base-currency equivalent beneath it. The
   second line is quieter because it is the same fact restated for comparison — the top line
   is what the person is actually paid, and that is the one being read. */
.ps-people-table .ps-pair-sub {
  display: inline-block; color: var(--ps-text-muted); font-size: 11.5px;
  font-variant-numeric: tabular-nums;
}
/* A saved band version, opened out beside what the family holds now. Compact: this is a
   comparison you scan, not a grid you work in. */
.ps-ver-table { font-size: 12.5px; }
.ps-ver-table th, .ps-ver-table td { padding: 5px 10px; }
/* A row that a restore would actually change — the rest are there for context. */
.ps-ver-table tr.ps-ver-changed td:first-child { box-shadow: inset 2px 0 0 var(--ps-warn-solid); }

/* "10% of base" / "Fixed" — the decision, above the amount it comes to. */
.ps-people-table .ps-pair-defn {
  display: inline-block; color: var(--ps-faint); font-size: 10.5px;
  text-transform: uppercase; letter-spacing: .04em;
}

/* ----- People preview GRIDS (family membership + band configure) -------- */
/* The ag-grid version of the list above — one component however many people, because the
   component-per-cell table froze the browser at 60 (see _people_grid in pay_bands.py). */
.ps-people-grid .ps-twoline { line-height: 1.3; padding-top: 3px; }
.ps-people-grid .ps-pair-sub {
  color: var(--ps-text-muted); font-size: 11.5px; font-variant-numeric: tabular-nums;
}
.ps-people-grid .ps-pair-defn {
  color: var(--ps-faint); font-size: 10px;
  text-transform: uppercase; letter-spacing: .04em;
}
/* A 'Set as' peg: quiet until pegged, so sixty rows of Low/Mid/High don't shout. */
.ps-people-grid .ps-anchor {
  display: inline-block; padding: 1px 8px; border-radius: 10px; font-size: 11px;
  color: var(--ps-text-muted); background: var(--ps-row-hover);
}
.ps-people-grid .ps-anchor-on {
  color: #fff; background: var(--mantine-color-indigo-6, #4c6ef5); font-weight: 600;
}
.ps-people-grid .ps-anchor:hover { outline: 1px solid var(--mantine-color-indigo-4, #748ffc); }

/* Every row of a gap list opens the place-a-person dialog, so the whole grid is a control.
   (These were tables of components until the browser took ~20s to draw ninety of them — see
   `_gap_table`.) */
.ps-gap-grid .ag-row { cursor: pointer; }

/* ----- The band tiles on "Configure bands" ------------------------------ */
/* A picker, not a page. These were flex items with `flex: 1 1 220px`, so one band stretched
   the full width of the window and two took half each — a band's tile grew with how few
   bands you had, which is backwards. John, 2026-08-28: *"the tiles spread across the entire
   screen. Lets make them much smaller, so some % of the screen width and they can get
   smaller if there are more of them, but there's a set limit to how large they are."*

   So: a share of the row (`flex-basis: 19%`, about five across), free to shrink as more
   bands appear, and capped — `max-width` is the "set limit", `min-width` the point at which
   they stop shrinking and wrap onto the next row instead. */
.ps-band-grid { display: flex; flex-wrap: wrap; gap: 10px; align-items: stretch; }
.ps-band-card {
  flex: 1 1 19%; min-width: 168px; max-width: 232px;
  padding: 10px 12px; margin-bottom: 0; cursor: pointer;
}
/* The name is the thing you are picking by: let it wrap onto a second line rather than
   truncate, and never let the badge beside it squeeze it out. */
.ps-band-card-name { min-width: 0; overflow-wrap: anywhere; line-height: 1.25; }
.ps-band-card:hover { border-color: var(--ps-primary); }

/* ----- Family detail: page action bar + chart control bar --------------- */
/* The left config pane is gone: its controls became one horizontal bar so the
   band chart — this app's signature view — gets the full page width. */
.ps-detailbar {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  margin-bottom: var(--ps-gap);
}
.ps-detail-back { white-space: nowrap; }
.ps-detail-title { display: flex; align-items: center; gap: 2px; min-width: 0; }
/* The band title is a block, not a row: it has no chevron beside it, and its textarea —
   which Mantine wraps in two divs of its own — has to be free to take the full width. */
.ps-detail-title:has(textarea) { display: block; }
/* dcc.Input renders a WRAPPER div and puts `className` on THAT, not on the <input>.
   Every rule here used to target the wrapper, so the real control kept its browser
   default width and ignored all of it — which is why "Account Manager" was clipped in its
   own page heading however the box was sized. `display: contents` drops the wrapper out of
   the layout so the input is a direct flex child of .ps-detail-title, and the styling now
   addresses the input itself. */
.ps-detail-nameinput { display: contents; }
/* The BAND name, which is a textarea so that it can wrap (see pay_bands.py).

   Its own class, NOT `ps-detail-nameinput`: that one is on a dcc.Input's WRAPPER div and
   carries `display: contents` to drop the wrapper out of the layout. Mantine puts its class
   on the control itself, and `display: contents` on a form control computes to `display:
   none` — the field vanished completely, 0x0, while every other rule applied to it. */
textarea.ps-bandname-input {
  font: inherit; font-size: 18px; font-weight: 600; letter-spacing: -.01em; line-height: 1.3;
  color: var(--ps-text); background: transparent;
  border: 1px solid transparent; border-radius: var(--ps-radius-sm);
  padding: 4px 8px; width: 100%; max-width: 100%; resize: none; overflow: hidden;
}
textarea.ps-bandname-input:hover {
  border-color: var(--ps-border); background: var(--ps-surface);
}
textarea.ps-bandname-input:focus {
  outline: none; border-color: var(--ps-primary); background: var(--ps-surface);
}
.ps-detail-nameinput > input {
  font: inherit; font-size: 18px; font-weight: 600; letter-spacing: -.01em;
  color: var(--ps-text); background: transparent;
  border: 1px solid transparent; border-radius: var(--ps-radius-sm);
  padding: 4px 8px;
  /* As wide as the name it holds. Browsers without field-sizing fall back to the `size`
     attribute, which pb_name_autosize keeps in step with the value.
     flex:0 0 auto stops the flex row shrinking it back below its content; max-width keeps
     a very long family name from pushing the rest of the action bar off screen. */
  field-sizing: content;
  flex: 0 0 auto;
  width: auto; min-width: 8ch; max-width: min(46ch, 52vw);
}
.ps-detail-nameinput > input:hover {
  border-color: var(--ps-border); background: var(--ps-surface);
}
.ps-detail-nameinput > input:focus {
  outline: none; border-color: var(--ps-primary); background: var(--ps-surface);
}
.ps-detail-locs { font-size: 12px; color: var(--ps-text-muted); }

.ps-ctlbar {
  display: flex; align-items: center; column-gap: 8px; row-gap: 8px; flex-wrap: wrap;
  padding: 11px 16px; border-bottom: 1px solid var(--ps-border);
}
/* An empty hint takes no gap — with the Loaded switch the bar is tight on a laptop. */
.ps-ctlbar > span:empty { display: none; }
.ps-ctl-label {
  font-size: 11px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase;
  color: var(--ps-faint); white-space: nowrap;
}
.ps-ctl-bands { flex: 1 1 220px; min-width: 200px; max-width: 300px; }
.ps-ctl-ccy { width: 92px; }
/* Level checkboxes read as a compact row of chips, not a stack of form fields. */
.ps-ctlbar .mantine-Checkbox-root { margin: 0; }
.ps-ctlbar .mantine-Checkbox-label { padding-left: 5px; font-size: 12.5px; font-weight: 600; }
.ps-ctlbar .mantine-Checkbox-inner { width: 17px; height: 17px; }
.ps-ctlbar .mantine-Checkbox-input { width: 17px; height: 17px; }
.ps-ctl-help {
  padding: 8px 16px 0; font-size: 12.5px; color: var(--ps-text-muted);
}
/* Kept for any page still using a side config pane. */
.ps-controls-pane {
  width: 300px; flex-shrink: 0; border-right: 1px solid var(--ps-border);
  padding-right: 20px;
}

/* ----- Job-families master list (redesign) ----------------------------- */
.ps-band-th {
  text-transform: uppercase; font-size: 11px; letter-spacing: 0.06em;
  color: var(--ps-faint); font-weight: 700; text-align: left;
}
.ps-band-th.r, .ps-people-table td.r { text-align: right; }
.ps-band-table td { vertical-align: middle; padding: 13px 12px; }
.ps-band-table th { padding: 9px 12px; }
.ps-fam-name { font-weight: 600; font-size: 14px; }
.ps-band-chips { display: flex; gap: 6px; flex-wrap: wrap; }
/* A band nobody is in yet reads as an outline, so live bands lead.

   Squared off, not a lozenge — John, 2026-09-07: *"keep the flag, but make the pill more
   square, similar to the other button shapes on the page."* 4px and 8px of side padding are
   the Open button sitting in the same row (Mantine's `defaultRadius: "sm"`, see design.py),
   so a band reads as a thing you can act on rather than as a status badge. */
.ps-loc-pill {
  display: inline-flex; align-items: center;
  background: var(--ps-surface); color: var(--ps-text);
  border: 1px solid var(--ps-border);
  border-radius: 4px; padding: 3px 8px; font-size: 12px; font-weight: 600;
  line-height: 1.4; white-space: nowrap;
}
.ps-loc-pill:has(.ps-count-chip:empty), .ps-loc-pill.zero {
  border-style: dashed; color: var(--ps-faint);
}
.ps-cov { font-size: 13.5px; }
.ps-cov-of { color: var(--ps-faint); }
.ps-cov.warn, .ps-cov.warn .ps-cov-of { color: var(--ps-warn); font-weight: 600; }
.ps-cov-none { color: var(--ps-faint); }
.ps-inline-cta { color: var(--ps-primary); font-weight: 600; cursor: pointer; }
.ps-inline-cta:hover { text-decoration: underline; }
.ps-row-actions { display: flex; gap: 6px; justify-content: flex-end; align-items: center; }
.ps-kebab {
  border: 1px solid transparent; background: transparent; border-radius: var(--ps-radius-sm);
  color: var(--ps-text-muted); font-size: 16px; line-height: 1; padding: 5px 9px;
  cursor: pointer; font-family: inherit;
}
.ps-kebab:hover { background: var(--ps-bg); border-color: var(--ps-border); }
.ps-table-foot {
  padding: 10px 16px; color: var(--ps-faint); font-size: 12.5px;
  border-top: 1px solid var(--ps-border); background: #fafbfc;
}
.ps-link-action {
  color: var(--ps-primary); cursor: pointer; font-size: 13px; font-weight: 600;
}
.ps-link-action:hover { text-decoration: underline; }
.ps-link-danger { color: var(--ps-negative); }

/* ----- Overview: 'View team' cell + Home focus filter ------------------ */
.ps-team-cell {
  color: var(--ps-primary); cursor: pointer; font-weight: 600; font-size: 13px;
}
.ps-team-cell:hover { text-decoration: underline; }
.ps-focus-note {
  display: flex; align-items: center; gap: 12px; margin-bottom: 12px;
  padding: 8px 14px; border-radius: var(--ps-radius-sm); font-size: 13px; font-weight: 600;
  background: var(--ps-primary-soft); color: var(--ps-primary-dark);
}
.ps-focus-clear { color: inherit; font-weight: 600; text-decoration: underline; }
.ag-theme-quartz.ps-grid .ag-row.ps-row-highlight {
  background: #fff4d6; box-shadow: inset 3px 0 0 var(--ps-accent);
}

/* ----- Modeling: snapshot cards --------------------------------------- */
.ps-snap-list { display: flex; flex-direction: column; gap: 12px; }
.ps-snap-card {
  background: var(--ps-surface); border: 1px solid var(--ps-border);
  border-radius: var(--ps-radius); box-shadow: var(--ps-shadow);
  padding: 16px 18px; display: flex; align-items: center; gap: 16px;
  justify-content: space-between; flex-wrap: wrap;
}
.ps-snap-main { display: flex; flex-direction: column; gap: 10px; flex: 1;
  min-width: 320px; }
.ps-snap-meta { color: var(--ps-text-muted); font-size: 12px; margin-top: 2px; }
.ps-snap-stats { display: flex; gap: 26px; flex-wrap: wrap; }
.ps-snap-stat { display: flex; flex-direction: column; }
.ps-snap-stat-v { font-weight: 700; font-size: 15px; font-variant-numeric: tabular-nums; }
.ps-snap-stat-l { color: var(--ps-text-muted); font-size: 11px;
  text-transform: uppercase; letter-spacing: .04em; }
.ps-snap-actions { display: flex; gap: 8px; align-items: center; }
.ps-snap-btn {
  border: 1px solid var(--ps-border); background: var(--ps-surface);
  border-radius: 8px; padding: 7px 14px; font-size: 13px; font-weight: 600;
  cursor: pointer; text-decoration: none; color: var(--ps-text);
}
.ps-snap-view { color: var(--ps-primary); border-color: var(--ps-primary-soft);
  background: var(--ps-primary-soft); }
.ps-snap-live { color: #fff; background: var(--ps-primary);
  border-color: var(--ps-primary); }
.ps-snap-del { color: var(--ps-negative); }
.ps-snap-empty { color: var(--ps-text-muted); padding: 20px 2px; }
.ps-snap-detailbar { display: flex; align-items: center; gap: 14px;
  margin-bottom: 16px; flex-wrap: wrap; }

/* ----- Tasks: status/priority cell tints ------------------------------- */
/* (The floating '+ Add task' bubble is gone — it overlapped grid pagination.
   Its trigger is now a row at the foot of the sidebar; see .ps-nav-btn.) */
.ps-status-blocked { color: var(--ps-negative); font-weight: 600; }
.ps-status-done { color: var(--ps-positive); font-weight: 600; }
.ps-prio-urgent { color: var(--ps-negative); font-weight: 700; }
.ps-prio-high { color: #b25e09; font-weight: 600; }
/* A filtered column's heading (SwapHeader): the name in the accent colour. */
.ps-swap-name.ps-swap-filtered { color: var(--ps-primary); font-weight: 700; }
/* A Done task: every cell on its row struck through and muted, so the open ones stand out.
   Not the Delete button — it still works, and a struck-through button reads as disabled. */
.ag-theme-quartz.ps-grid .ag-row.ps-task-done .ag-cell:not([col-id="_del"]) {
  text-decoration: line-through;
  color: var(--ps-text-muted);
}

/* ----- Info cell (opens comp history) ---------------------------------- */
.ps-info-cell { color: var(--ps-accent); cursor: pointer; font-size: 15px;
  font-weight: 700; }
.ps-info-cell:hover { opacity: 0.7; }

/* ----- Pay band master list -------------------------------------------- */
.ps-band-row:hover { background: var(--ps-row-hover); }
/* The name is the affordance to open a family (the row's action cell must stay inert,
   or a kebab click would navigate before its menu opens). */
.ps-fam-cell { cursor: pointer; }
.ps-fam-cell:hover .ps-fam-name { color: var(--ps-primary); text-decoration: underline; }

/* ----- Delete-row cell ------------------------------------------------- */
.ps-delete-cell { color: var(--ps-negative); cursor: pointer; font-size: 12.5px;
  font-weight: 600; }
.ps-delete-cell:hover { text-decoration: underline; }

/* ----- Cell comments + @-mentions -------------------------------------- */
.ps-mention-menu {
  position: absolute; left: 0; right: 0; z-index: 40;
  background: var(--ps-surface); border: 1px solid var(--ps-border);
  border-radius: 9px; box-shadow: var(--ps-shadow); padding: 4px;
  max-height: 220px; overflow-y: auto;
}
.ps-comment-thread { max-height: 360px; overflow-y: auto; display: flex;
  flex-direction: column; gap: 8px; }
.ps-comment {
  background: var(--ps-bg); border: 1px solid var(--ps-border);
  border-radius: 9px; padding: 9px 12px; font-size: 13.5px;
}
.ps-comment-when { color: var(--ps-text-muted); font-size: 12px; }
.ps-comment-body { margin-top: 3px; white-space: pre-wrap; }

/* ----- Rescinded comments ----------------------------------------------
   The change this comment argued for was taken back with Ctrl+Z. It keeps its words — nobody
   edits what somebody wrote — but it must not be mistaken for advice that still stands, so it
   carries a stamp and sits behind a disclosure rather than in the flow of the thread. */
.ps-comment-rescinded {
  border-color: rgba(216, 69, 59, .40);          /* --ps-negative, softened */
  background: rgba(216, 69, 59, .045);
}
.ps-comment-rescinded .ps-comment-body { color: var(--ps-text-muted); }
.ps-rescinded-badge {
  display: inline-block; margin-left: 7px; padding: 0 5px;
  border: 1px solid var(--ps-negative); border-radius: 4px;
  color: var(--ps-negative); background: #fff;
  font-size: 9.5px; font-weight: 800; letter-spacing: .06em; line-height: 15px;
  text-transform: uppercase; vertical-align: 1px;
}
.ps-rescinded-toggle > summary {
  cursor: pointer; user-select: none; list-style: none;
  color: var(--ps-text-muted); font-size: 12px; padding: 1px 0;
}
.ps-rescinded-toggle > summary::-webkit-details-marker { display: none; }
.ps-rescinded-toggle > summary::before { content: "▸ "; }
.ps-rescinded-toggle[open] > summary { margin-bottom: 8px; }
.ps-rescinded-toggle[open] > summary::before { content: "▾ "; }
.ps-rescinded-toggle > summary:hover { color: var(--ps-text); }
.ps-rescinded-list { display: flex; flex-direction: column; gap: 8px; }

/* ----- Login ----------------------------------------------------------- */
.ps-login-shell {
  min-height: 100vh; display: flex; align-items: center; justify-content: center;
  padding: 24px;
}
.ps-login {
  width: 100%; max-width: 400px; margin: 0; padding: 40px 36px 28px;
  text-align: center; background: var(--ps-surface);
  border: 1px solid var(--ps-border); border-radius: var(--ps-radius);
  box-shadow: var(--ps-shadow);
}
/* Brand glyph at the top of the login card. */
.ps-login-mark {
  width: 60px; height: 60px; border-radius: 18px; margin: 0 auto 20px;
  display: flex; align-items: center; justify-content: center; color: #fff;
  background: linear-gradient(135deg, #5562ea, #12b3a6);
  box-shadow: 0 10px 24px -8px rgba(85, 98, 234, 0.55);
}
.ps-login h1 { font-size: 26px; font-weight: 750; letter-spacing: -0.02em; margin: 0 0 6px; }
.ps-login-tag { color: var(--ps-text-muted); font-size: 14px; margin: 0 0 26px; }
.ps-login-foot {
  color: var(--ps-text-muted); font-size: 12px; margin-top: 22px;
  padding-top: 16px; border-top: 1px solid var(--ps-row-border);
}

/* AG Grid filter/column popups must be OPAQUE — the glass theme otherwise lets the
   grid text behind bleed through and the popup becomes unreadable. */
.ag-theme-quartz .ag-popup-child,
.ag-theme-quartz .ag-menu,
.ag-theme-quartz .ag-filter,
.ag-theme-quartz .ag-list,
.ag-theme-quartz .ag-select-list,
.ag-popup .ag-menu,
.ag-popup .ag-filter {
  background-color: #ffffff !important;
  backdrop-filter: none !important;
  -webkit-backdrop-filter: none !important;
  opacity: 1 !important;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.18) !important;
}

/* Salary-cell markers ---------------------------------------------------------- */
/* Two independent cautions can apply to one figure:
     amber = part-time, so this is ACTUAL FTE-weighted pay and reads low vs full-time peers
     blue  = annualised from a day/hourly rate, so it is an estimate of a year, not a salary
   A part-time contractor carries both.

   These are INLINE, not absolutely positioned. AG Grid lays cells out with
   `position: absolute` and a computed `left`, so putting `position: relative` on the cell
   to anchor an absolute marker cancelled that offset — the cell dropped into normal flow
   and rendered on top of its neighbour, drawing the salary twice. Never set `position` on
   an .ag-cell. Amber sits raised, blue sits lowered, so when both apply they read as a
   stacked pair without either escaping the cell. */
.ps-pt-salary::after {
  content: "*";
  color: var(--ps-warn-solid);
  font-weight: 700;
  font-size: 0.85em;
  vertical-align: super;
  margin-left: 2px;
}
.ps-annualised-salary::after {
  content: "*";
  color: var(--ps-primary);
  font-weight: 700;
  font-size: 0.85em;
  vertical-align: sub;
  margin-left: 2px;
}
/* Both apply: one ::after can't carry two colours, so the amber one moves to ::before and
   is pulled back over the blue with a negative margin — giving the stacked pair. */
.ps-pt-salary.ps-annualised-salary::before {
  content: "*";
  color: var(--ps-warn-solid);
  font-weight: 700;
  font-size: 0.85em;
  vertical-align: super;
  margin-left: 2px;
  margin-right: -7px;
}
.ps-pt-salary.ps-annualised-salary::after { margin-left: 2px; }
/* While the cell is being edited the input owns the space — no markers on top of it. */
.ag-cell-inline-editing.ps-pt-salary::after,
.ag-cell-inline-editing.ps-pt-salary::before,
.ag-cell-inline-editing.ps-annualised-salary::after { content: none; }

/* ── Help page ───────────────────────────────────────────────────────────────
   Renders docs/USER-GUIDE.md (pages/help.py). Two columns: a sticky contents
   rail and the prose. Prose is capped at a readable measure — the app is
   otherwise all wide grids, so long lines here would be unreadable. */
/* NB: `.ps-help` is already taken app-wide for muted caption text — this wrapper must
   NOT reuse it, or every help caption in the app becomes a two-column grid. */
.ps-helppage {
  display: grid;
  grid-template-columns: 200px minmax(0, 1fr);
  gap: var(--ps-gap);
  align-items: start;
}
@media (max-width: 1100px) { .ps-helppage { grid-template-columns: 1fr; } }

.ps-help-toc {
  /* The topbar is fixed and the document is the scroller, so the sticky offset has to
     clear the topbar or the contents rail slides under it. */
  position: sticky;
  top: calc(var(--ps-topbar-h) + var(--ps-gap));
  max-height: calc(100vh - var(--ps-topbar-h) - var(--ps-gap) * 2);
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: 2px;
}
@media (max-width: 1100px) { .ps-help-toc { position: static; } }

.ps-help-toc-head {
  font-size: 11px;
  font-weight: 600;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--ps-faint);
  padding: 6px 10px;
}
.ps-help-toc-link {
  display: block;
  padding: 6px 10px;
  border-radius: var(--ps-radius-sm);
  font-size: 13px;
  line-height: 1.35;
  color: var(--ps-text-muted);
  text-decoration: none;
}
.ps-help-toc-link:hover { background: var(--ps-row-hover); color: var(--ps-text); }

.ps-help-body { padding: 28px 32px; }
.ps-help-section { max-width: 74ch; scroll-margin-top: calc(var(--ps-topbar-h) + 16px); }
.ps-help-section + .ps-help-section {
  margin-top: 32px;
  padding-top: 32px;
  border-top: 1px solid var(--ps-border);
}

.ps-help-h2 {
  margin: 0 0 14px;
  font-size: 19px;
  font-weight: 600;
  color: var(--ps-text);
}
.ps-help-md { font-size: 14px; line-height: 1.65; color: var(--ps-text); }
.ps-help-md h1 { font-size: 22px; font-weight: 600; margin: 0 0 8px; }
.ps-help-md h3 { font-size: 15px; font-weight: 600; margin: 22px 0 6px; }
.ps-help-md h4 { font-size: 14px; font-weight: 600; margin: 18px 0 4px; }
.ps-help-md p, .ps-help-md ul, .ps-help-md ol { margin: 0 0 12px; }
.ps-help-md ul, .ps-help-md ol { padding-left: 20px; }
.ps-help-md li { margin-bottom: 4px; }
.ps-help-md li > p { margin-bottom: 4px; }
.ps-help-md a { color: var(--ps-primary); }
.ps-help-md hr { display: none; }        /* sections are already separated by a rule */

.ps-help-md code {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: .88em;
  background: var(--ps-primary-soft);
  color: var(--ps-primary-dark);
  padding: 1px 5px;
  border-radius: 4px;
}
.ps-help-md pre {
  background: var(--ps-field-bg);
  border: 1px solid var(--ps-border);
  border-radius: var(--ps-radius-sm);
  padding: 12px 14px;
  overflow-x: auto;
}
.ps-help-md pre code { background: none; color: var(--ps-text); padding: 0; }

.ps-help-md blockquote {
  margin: 0 0 14px;
  padding: 10px 14px;
  border-left: 3px solid var(--ps-warn-solid);
  background: var(--ps-warn-bg);
  border-radius: 0 var(--ps-radius-sm) var(--ps-radius-sm) 0;
  color: var(--ps-warn);
}
.ps-help-md blockquote p:last-child { margin-bottom: 0; }

/* Tables carry a lot of the guide (keystrokes, chart symbols) — they must be legible
   and must scroll rather than push the page sideways. */
.ps-help-md table {
  width: 100%;
  border-collapse: collapse;
  margin: 0 0 14px;
  font-size: 13px;
}
.ps-help-md th, .ps-help-md td {
  text-align: left;
  padding: 7px 10px;
  border-bottom: 1px solid var(--ps-row-border);
  vertical-align: top;
}
.ps-help-md th {
  font-weight: 600;
  color: var(--ps-text-muted);
  border-bottom: 1px solid var(--ps-border);
  white-space: nowrap;
}
.ps-help-md tr:last-child td { border-bottom: none; }

/* ----- Ask the guide -------------------------------------------------------
   A panel that drops from under the topbar on the right, on every screen. Fixed
   rather than in the flow, because it must not move the page underneath it: you
   open it BECAUSE you are looking at something, and a layout that jumps has
   already lost you the thing you were pointing at.

   Closed is a class, not an unmount — the conversation has to still be there
   when you open it again. */
.ps-askbtn {
  display: inline-flex; align-items: center; gap: 6px;
  height: 30px; margin-left: 6px; padding: 0 10px;
  color: var(--ps-ai-dark); background: var(--ps-ai-soft);
  border: 1px solid transparent; border-radius: 8px; cursor: pointer;
  font-size: 12.5px; font-weight: 600;
}
.ps-askbtn:hover { border-color: var(--ps-ai); }
.ps-askbtn:focus-visible { outline: 2px solid var(--ps-ai); outline-offset: 1px; }
.ps-askbtn-label { line-height: 1; }
/* The Narra button in a page's own button row ("Ask Narra to create a band") — the same
   Narra purple, sized like the buttons beside it. */
.ps-askbtn-inline { height: 36px; margin-left: 0; padding: 0 14px; font-size: 13.5px; }
@media (max-width: 900px) { .ps-askbtn-label { display: none; } }

.ps-askdock {
  position: fixed;
  top: var(--ps-topbar-h);
  right: 14px;
  z-index: 60;
  width: 420px;
  max-width: calc(100vw - 28px);
  max-height: calc(100vh - var(--ps-topbar-h) - 24px);
  display: flex;
  flex-direction: column;
  background: var(--ps-surface);
  border: 1px solid var(--ps-ai-border);
  border-top: none;
  border-radius: 0 0 var(--ps-radius-sm) var(--ps-radius-sm);
  box-shadow: 0 12px 28px rgba(16, 20, 40, .16);
  /* Closed: folded up under the topbar rather than removed, so it slides rather
     than blinking, and so the panel keeps its scroll position and its text. */
  transform: translateY(-8px);
  opacity: 0;
  pointer-events: none;
  visibility: hidden;
  transition: transform .16s ease, opacity .16s ease, visibility 0s linear .16s;
}
.ps-askdock.open {
  transform: translateY(0);
  opacity: 1;
  pointer-events: auto;
  visibility: visible;
  transition: transform .16s ease, opacity .16s ease, visibility 0s;
}
@media (max-width: 560px) { .ps-askdock { right: 0; left: 0; width: auto; } }

.ps-askdock-head {
  display: flex; align-items: center; gap: 7px;
  padding: 10px 12px; border-bottom: 1px solid var(--ps-ai-border);
  color: var(--ps-ai-dark); background: var(--ps-ai-soft);
  background-image: var(--ps-ai-grad); background-size: 100% 3px;
  background-repeat: no-repeat; background-position: top;
}
.ps-askdock-title { font-size: 13.5px; font-weight: 600; }
.ps-askdock-x {
  display: inline-flex; align-items: center; justify-content: center;
  width: 24px; height: 24px; padding: 0; cursor: pointer;
  color: var(--ps-text-muted); background: transparent; border: none;
  border-radius: 6px;
}
.ps-askdock-x:hover { background: rgba(0, 0, 0, .06); color: var(--ps-text); }
/* The conversation scrolls; the question box below it does not go with it. */
.ps-askdock-body { flex: 1; overflow-y: auto; padding: 12px 14px; }
.ps-askdock-foot { padding: 10px 14px 12px; border-top: 1px solid var(--ps-border); }

/* Buttons sit at the bottom edge of the (several-line) question box, beside where you type. */
.ps-ask-row { display: flex; gap: 8px; align-items: flex-end; }
.ps-ask-input { flex: 1; }
.ps-askdock-info {
  display: inline-flex; color: var(--ps-ai-dark); opacity: .6; cursor: help;
}
.ps-askdock-info:hover { opacity: 1; }

/* "It heard you." The question and this appear in the same render, so the panel is
   never silent between asking and answering — which is what made a six-second wait
   read as the button not having worked. */
.ps-ask-working {
  display: flex; align-items: center; gap: 9px;
  color: var(--ps-text-muted); font-size: 12.5px;
}
.ps-ask-dots { display: inline-flex; gap: 4px; }
.ps-ask-dots i {
  width: 6px; height: 6px; border-radius: 50%;
  background: var(--ps-ai); opacity: .35;
  animation: ps-ask-blink 1.1s infinite ease-in-out;
}
.ps-ask-dots i:nth-child(2) { animation-delay: .18s; }
.ps-ask-dots i:nth-child(3) { animation-delay: .36s; }
@keyframes ps-ask-blink {
  0%, 70%, 100% { opacity: .25; transform: translateY(0); }
  35%           { opacity: 1;   transform: translateY(-2px); }
}
/* Somebody who has asked the browser to stop animating things gets a static row that
   still says the same thing. */
@media (prefers-reduced-motion: reduce) {
  .ps-ask-dots i { animation: none; opacity: .6; }
}

.ps-ask-pair + .ps-ask-pair {
  margin-top: 14px; padding-top: 14px; border-top: 1px solid var(--ps-border);
}
.ps-ask-turn { font-size: 13.5px; }
.ps-ask-you { font-weight: 600; color: var(--ps-text); margin-bottom: 8px; }
.ps-ask-you::before { content: "You asked: "; color: var(--ps-text-muted);
                      font-weight: 400; }
.ps-ask-bot {
  background: #fdf8fe; border: 1px solid var(--ps-ai-border);
  border-left: 3px solid var(--ps-ai);
  border-radius: var(--ps-radius-sm); padding: 12px 14px;
}
.ps-ask-md { font-size: 13.5px; line-height: 1.6; color: var(--ps-text); }
.ps-ask-md p, .ps-ask-md ul, .ps-ask-md ol { margin: 0 0 8px; }
.ps-ask-md > *:last-child { margin-bottom: 0; }
.ps-ask-md ul, .ps-ask-md ol { padding-left: 20px; }
.ps-ask-md code {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: .88em;
  background: var(--ps-ai-soft); color: var(--ps-ai-dark);
  padding: 1px 5px; border-radius: 4px;
}
/* An answer the guide does not support says so ABOVE itself. */
.ps-ask-uncovered {
  font-size: 12.5px; font-weight: 600; color: var(--ps-warn); margin-bottom: 8px;
}
.ps-ask-err { border-color: var(--ps-warn-border); border-left-color: var(--ps-warn-solid);
              background: var(--ps-warn-bg); }

/* Citations. An assistant you cannot check is one you end up checking anyway, so
   the sections behind an answer are always one click away. */
.ps-ask-cites { margin-top: 10px; display: flex; flex-wrap: wrap; gap: 6px;
                align-items: baseline; }
.ps-ask-citelabel { font-size: 11.5px; color: var(--ps-text-muted); }
.ps-ask-cite {
  font-size: 11.5px; color: var(--ps-ai-dark); text-decoration: none;
  border: 1px solid var(--ps-border); border-radius: 999px; padding: 2px 9px;
  background: var(--ps-surface); cursor: pointer;
}
.ps-ask-cite:hover { background: var(--ps-ai-soft); }
.ps-ask-goto {
  display: inline-block; margin-top: 10px; font-size: 12.5px; font-weight: 600;
  color: var(--ps-ai-dark); text-decoration: none;
}
.ps-ask-goto:hover { text-decoration: underline; }
.ps-ask-arrow { font-weight: 400; }
/* The Ask button and the question box take the advisor's colour too, so the whole
   panel reads as one thing that is not the app. */
.ps-askdock-foot .mantine-Button-root { background: var(--ps-ai-grad); border: none; }
.ps-askdock-foot .mantine-Button-root:hover { filter: brightness(1.06); }
.ps-askdock-foot .mantine-TextInput-input:focus { border-color: var(--ps-ai); }
.ps-askdock-approachbtn {
  display: inline-flex; align-items: center; gap: 5px; height: 24px; padding: 0 8px;
  font-size: 12px; font-weight: 600; color: var(--ps-ai-dark); cursor: pointer;
  background: var(--ps-surface); border: 1px solid var(--ps-ai-border); border-radius: 6px;
}
.ps-askdock-approachbtn:hover { border-color: var(--ps-ai); }
/* What the company has told it. Above the conversation, because it shapes every answer. */
.ps-ask-approach {
  padding: 10px 14px; border-bottom: 1px solid var(--ps-ai-border);
  background: var(--ps-ai-soft); font-size: 12.5px; max-height: 40vh; overflow-y: auto;
  flex-shrink: 0;
}
.ps-ask-approach-title { font-weight: 600; color: var(--ps-ai-dark); margin-bottom: 6px; }
.ps-ask-approach-row {
  display: flex; gap: 8px; align-items: flex-start; justify-content: space-between;
  padding: 5px 0; border-top: 1px solid var(--ps-ai-border); line-height: 1.45;
}
.ps-ask-approach-k { font-weight: 600; margin-right: 6px; }
.ps-ask-approach-empty { color: var(--ps-text-muted); font-size: 12px; margin-top: 6px; }
.ps-ask-noted {
  margin-top: 10px; font-size: 12px; color: var(--ps-ai-dark);
  background: var(--ps-ai-soft); border-radius: 6px; padding: 5px 8px;
}
.ps-ask-noted-k { font-weight: 600; }
/* A change the advisor proposed. Nothing happens until Do it. */
.ps-ask-card {
  margin-top: 12px; padding: 10px 12px; border-radius: 8px;
  background: var(--ps-surface); border: 1px solid var(--ps-ai-border);
  box-shadow: 0 1px 2px rgba(16, 20, 40, .05);
}
.ps-ask-card.is-done { border-color: #bfe3cf; }
.ps-ask-card.is-dismissed, .ps-ask-card.is-undone { opacity: .7; }
.ps-ask-card-title { font-weight: 600; font-size: 13px; margin-bottom: 4px; }
/* What the change acts on, and — when the user pointed at it — which one Narra took it to be. */
.ps-ask-card-target { font-size: 12px; font-weight: 600; color: var(--ps-primary);
                      margin-bottom: 4px; }
.ps-ask-card-check {
  font-size: 12px; color: #8a5a00; background: #fff4db; border: 1px solid #f3d9a4;
  border-radius: 6px; padding: 6px 8px; margin: 2px 0 6px;
}
.ps-ask-card-lines { margin: 0 0 6px; padding-left: 18px; font-size: 12.5px; line-height: 1.5; }
.ps-ask-card-warn {
  font-size: 12px; color: var(--ps-warn); background: var(--ps-warn-bg);
  border-radius: 6px; padding: 4px 8px; margin-bottom: 6px;
}
.ps-ask-card-err { font-size: 12px; color: var(--ps-negative); margin-bottom: 6px; }
.ps-ask-card-foot { display: flex; gap: 8px; align-items: center; margin-top: 4px; }
.ps-ask-card-do.mantine-Button-root { background: var(--ps-ai-grad); border: none; }
.ps-ask-card-state { font-size: 12px; font-weight: 600; color: var(--ps-text-muted); }
.ps-ask-card-state.ok { color: var(--ps-positive); }

/* A principle with a limit did not keep its promise for these people, and the row
   says which way. Opposite states, marked as opposites: one had money taken off
   the rule's promise, the other had money added. Everybody else is left plain —
   they got exactly what the card says, and that is worth being able to see at a
   glance. Kept to a left bar and a tint so the figures stay the loudest thing. */
.ag-theme-quartz.ps-grid .ag-row.ps-pp-held {
  background: var(--ps-warn-bg);
  box-shadow: inset 3px 0 0 var(--ps-warn-solid);
}
.ag-theme-quartz.ps-grid .ag-row.ps-pp-lifted {
  background: var(--ps-kpi-green-bg, #e6f6ee);
  box-shadow: inset 3px 0 0 var(--ps-positive);
}
/* Matched, but a figure set by hand keeps the principle off it. Red, because the
   card's headline ("+3% for everybody here") is not true for this row. */
.ag-theme-quartz.ps-grid .ag-row.ps-pp-manual {
  background: color-mix(in srgb, var(--ps-negative) 9%, transparent);
  box-shadow: inset 3px 0 0 var(--ps-negative);
}

/* ----- Pay Bands: job families as folders ---------------------------------
   A folder is a header and the jobs under it. Deliberately NOT a card each: a
   card says "separate object", and twenty families as twenty cards is a page of
   borders with a table inside every one. The header carries the structure; the
   table inside is the same one the flat list always used. */
.ps-jg-list { display: flex; flex-direction: column; gap: 6px; }
/* What just happened, where the list cannot show it by itself. */
.ps-jg-note {
  font-size: 12.5px; color: var(--ps-primary-dark); background: var(--ps-primary-soft);
  border-radius: var(--ps-radius-sm); padding: 8px 10px; margin-bottom: 8px;
}
/* A family HEAD is tinted, a job row is not. Closed, the screen is a stack of blue blocks
   — the shape of the company, readable in one look; expand one and its jobs sit on the
   ordinary work surface underneath, so the level you are on is never in doubt. The
   separating hairline goes with it: the tint already says where one family ends. */
.ps-jg-head {
  display: flex; align-items: center; gap: 10px;
  padding: 10px 10px; cursor: pointer; user-select: none;
  border-radius: var(--ps-radius-sm);
  background: var(--ps-primary-soft);
  border: 1px solid transparent;
}
.ps-jg-head:hover { border-color: var(--ps-primary); }
/* Open, the head is the lid of the thing below it rather than a block of its own. */
.ps-jg.open > .ps-jg-head { border-bottom-left-radius: 0; border-bottom-right-radius: 0; }
.ps-jg-caret { width: 12px; color: var(--ps-primary); font-size: 11px; }
.ps-jg-name { font-weight: 600; font-size: 14px; color: var(--ps-primary-dark); }
/* Uncategorised is not a record, it is the absence of one — so it is named quietly.
   It also sorts last, because a screen that opens on the pile nobody has sorted
   reads as the state of the company rather than as the work left to do. */
.ps-jg-name.ps-jg-loose { font-weight: 500; color: var(--ps-text-muted); font-style: italic; }
.ps-jg-count { font-size: 12px; color: var(--ps-text-muted); }
/* People in the jobs and none of them in a band: the family is unfinished, and the count
   is the only place on the closed list that can say so. */
.ps-jg-count.ps-jg-gap { color: var(--ps-warn); font-weight: 600; }
/* The jobs sit on the page's own surface, indented under the tinted head. */
.ps-jg-body {
  display: none; padding: 4px 8px 12px 22px;
  background: var(--ps-surface);
  border: 1px solid var(--ps-border); border-top: none;
  border-radius: 0 0 var(--ps-radius-sm) var(--ps-radius-sm);
}
.ps-jg.open > .ps-jg-body { display: block; }

/* ----- Pay Principles: say it in English ----------------------------------
   Above the list, because it is where a principle now starts. The conversation
   grows DOWNWARD into the page (unlike the Ask panel, which is a fixed dock and
   so puts the newest first) — here you are reading a proposal being refined, and
   the order it happened in is the order that makes sense. */
.ps-nl { margin-bottom: var(--ps-gap); }
.ps-nl-head { display: flex; align-items: center; gap: 7px; margin-bottom: 10px;
              color: var(--ps-primary-dark); }
.ps-nl-title { font-size: 14px; font-weight: 600; }
.ps-nl-sub { font-size: 12px; color: var(--ps-text-muted); font-weight: 400; }
/* Under the conversation, not above it: this is where you answer. */
.ps-nl-row { display: flex; gap: 8px; align-items: flex-start; margin-top: 12px; }
.ps-nl-input { flex: 1; }

.ps-nl-turn { margin-top: 12px; font-size: 13.5px; }
.ps-nl-you { font-weight: 600; }
.ps-nl-you::before { content: "You said: "; color: var(--ps-text-muted); font-weight: 400; }
.ps-nl-bot {
  background: var(--ps-bg); border: 1px solid var(--ps-border);
  border-radius: var(--ps-radius-sm); padding: 12px 14px;
}
.ps-nl-err { border-color: var(--ps-warn-border); background: var(--ps-warn-bg); }
.ps-nl-reply { line-height: 1.55; }

/* The proposal, in the same two lines a saved card uses — so what you approve and
   what appears in the list cannot describe themselves differently. */
.ps-nl-draft {
  margin-top: 10px; padding: 10px 12px; background: var(--ps-surface);
  border: 1px solid var(--ps-border); border-radius: var(--ps-radius-sm);
}
.ps-nl-draftrow { display: flex; gap: 10px; padding: 3px 0; font-size: 13px; }
.ps-nl-lbl { min-width: 74px; color: var(--ps-text-muted); }
.ps-nl-val { font-weight: 600; color: var(--ps-text); }

/* What it would NOT build. Never folded away: a dropped clause is the one thing on
   this screen you must not be able to miss, and the whole reason the assistant is
   told to refuse rather than substitute is so that this list exists to be read. */
.ps-nl-cant {
  margin-top: 10px; padding: 10px 12px;
  background: var(--ps-warn-bg); border: 1px solid var(--ps-warn-border);
  border-radius: var(--ps-radius-sm);
}
.ps-nl-cant-head { font-size: 12px; font-weight: 700; color: var(--ps-warn);
                   text-transform: uppercase; letter-spacing: .04em; }
.ps-nl-cant-row { margin-top: 6px; font-size: 13px; }
.ps-nl-cant-asked { font-style: italic; color: var(--ps-text-muted); }
.ps-nl-cant-why { color: var(--ps-text); }
.ps-nl-cant-near { margin-top: 3px; font-weight: 600; color: var(--ps-primary-dark); }

/* Its question, with the answers to it. A question that leaves you composing the word
   "yes" in a text box is a question that stops the conversation. */
.ps-nl-opts { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 10px; }

/* ----- Settings → Fields & wording: the custom-field list -------------------
   Four aligned columns with the headings written once, so "On grid by default"
   isn't repeated down the card on every row. Below 760px the row stacks, the
   header row is dropped (it can't line up with anything), and each switch takes
   its own visible label back. */
.ps-cf-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 140px 170px 96px;
  gap: 12px;
  align-items: center;
  padding: 7px 0;
  border-bottom: 1px solid var(--ps-row-border);
}
.ps-cf-row:last-child { border-bottom: none; }
.ps-cf-head { padding-bottom: 4px; border-bottom: 1px solid var(--ps-border); }
.ps-cf-switch { display: flex; align-items: center; gap: 8px; }
.ps-cf-switch-label { display: none; font-size: 13px; color: var(--ps-text-muted); }
/* Separates "Add a field" from the list — a heavier rule than the row hairlines,
   so the form doesn't read as one more row. */
.ps-cf-add { margin-top: 16px; padding-top: 14px; border-top: 1px solid var(--ps-border); }
.ps-cf-add h4 { font-size: 14px; font-weight: 600; color: var(--ps-text); }

@media (max-width: 760px) {
  .ps-cf-row { grid-template-columns: 1fr; gap: 8px; padding: 10px 0; }
  .ps-cf-head { display: none; }
  .ps-cf-switch-label { display: inline; }
}

/* ----- Imports: the change preview ---------------------------------------- */
.ps-diff-table { border-collapse: collapse; width: 100%; font-size: 13px; }
.ps-diff-table th {
  text-align: left; font-weight: 600; color: var(--ps-text-muted);
  padding: 4px 10px 4px 0; border-bottom: 1px solid var(--ps-border);
  white-space: nowrap;
}
.ps-diff-table td {
  padding: 4px 10px 4px 0; border-bottom: 1px solid var(--ps-row-border);
  vertical-align: top;
}
.ps-diff-table tr:last-child td { border-bottom: none; }

/* ----- Setup: the step list ---------------------------------------------- */
.ps-setup-row {
  display: grid;
  grid-template-columns: 24px minmax(0, 1fr) auto;
  gap: 12px;
  align-items: start;
  padding: 12px 0;
  border-bottom: 1px solid var(--ps-row-border);
}
.ps-setup-row:last-child { border-bottom: none; }
@media (max-width: 620px) {
  .ps-setup-row { grid-template-columns: 24px minmax(0, 1fr); }
  .ps-setup-row > :last-child { grid-column: 2; }
}

/* Always-visible horizontal scrollbar inside a grid that overflows sideways.
   macOS uses overlay scrollbars: they fade out when idle, so a grid with more columns than
   fit looks like a grid with nothing more to see. In the pay-band editor that is thirteen
   columns in a narrow pane — the scrollbar is the only thing telling you the rest exists,
   so it has to stay on screen. */
.ps-grid .ag-body-horizontal-scroll-viewport::-webkit-scrollbar {
  -webkit-appearance: none;
  height: 10px;
}
.ps-grid .ag-body-horizontal-scroll-viewport::-webkit-scrollbar-track {
  background: var(--ps-row-hover);
  border-radius: 5px;
}
.ps-grid .ag-body-horizontal-scroll-viewport::-webkit-scrollbar-thumb {
  background: #c3cad9;
  border-radius: 5px;
  border: 2px solid var(--ps-row-hover);
}
.ps-grid .ag-body-horizontal-scroll-viewport::-webkit-scrollbar-thumb:hover {
  background: #a9b2c5;
}

/* Career-track accordion (Settings → Job levels).
   The track name gets a tinted bar across the full width so it reads as the parent of the
   levels inside it. Without it a track heading and a level row carry the same visual weight
   and the hierarchy has to be inferred from indentation alone. */
.ps-track-acc .mantine-Accordion-item {
  border: 1px solid var(--ps-border);
  border-radius: var(--ps-radius-sm);
  overflow: hidden;
  margin-bottom: 10px;
  background: var(--ps-surface);
}
.ps-track-acc .mantine-Accordion-control {
  background: var(--ps-primary-soft);
  border-bottom: 1px solid #dfe3fb;
  padding-top: 10px;
  padding-bottom: 10px;
}
.ps-track-acc .mantine-Accordion-control:hover { background: #e6e9fd; }
.ps-track-acc .mantine-Accordion-chevron { color: var(--ps-primary); }
.ps-track-acc .mantine-Accordion-content { padding-top: 12px; }


/* ----- Band membership: one condition, and the button that removes it ---- */
/* Each condition is a row you can act on, so it looks like one: a bordered strip with the
   remove button inside it. Before this the row was bare text with a 14px ✕ floated to the
   far right — the control was there and nobody could find it. */
.ps-cond-row {
  display: flex; align-items: center; gap: 8px; margin-bottom: 5px;
  padding: 5px 6px 5px 8px; background: var(--ps-surface);
  border: 1px solid var(--ps-border); border-radius: var(--ps-radius-sm);
}
.ps-cond-text { font-size: 13px; color: var(--ps-text); }
.ps-cond-rm {
  display: inline-flex; align-items: center; gap: 4px; margin-left: auto;
  /* Real padding: the old target was a 14px glyph, below any sensible tap size. */
  padding: 3px 9px; font: inherit; font-size: 12px; font-weight: 600;
  color: var(--ps-negative); background: transparent; cursor: pointer;
  border: 1px solid transparent; border-radius: 999px;
}
.ps-cond-rm:hover { background: var(--ps-warn-bg, #fdf3f2); border-color: currentColor; }
.ps-cond-rm:focus-visible { outline: 2px solid var(--ps-primary); outline-offset: 1px; }

/* ----- Icons ------------------------------------------------------------- */
/* Drawn as a mask over currentColor, so an icon takes the colour of the thing it sits in —
   a red Remove button, muted help text, white on an indigo button. An <img> could not do
   that. Vendored in assets/icons because CSP (connect-src 'self') blocks the Iconify CDN
   that dash-iconify fetches from, which drew every icon in the app as nothing. */
.ps-ic {
  display: inline-block; flex: 0 0 auto; vertical-align: -0.125em;
  background-color: currentColor;
  -webkit-mask: var(--ps-ic) no-repeat center / contain;
  mask: var(--ps-ic) no-repeat center / contain;
}

/* The job-family list's summary line — above the table, because it orients you before you
   read it rather than commenting on it afterwards. */
.ps-table-lead {
  padding: 2px 0 10px; color: var(--ps-text-muted); font-size: 12.5px;
}

/* ----- "Viewing as" — an admin looking through another role ---------------- */
/* Always visible while scoped down. Amber rather than red: it is a deliberate state, not
   an error — but it must not be possible to forget you are in it. */
.ps-viewas {
  display: inline-flex; align-items: center; gap: 8px; margin-left: 14px;
  padding: 3px 6px 3px 11px; font-size: 12.5px; color: var(--ps-warn);
  background: var(--ps-warn-bg, #fff8ec); border: 1px solid var(--ps-warn);
  border-radius: 999px;
}
.ps-viewas b { color: var(--ps-text); }
.ps-viewas-exit {
  padding: 2px 9px; font: inherit; font-size: 11.5px; font-weight: 600;
  color: var(--ps-warn); background: transparent; cursor: pointer;
  border: 1px solid currentColor; border-radius: 999px;
}
.ps-viewas-exit:hover { background: #fff; }
.ps-viewas-exit:focus-visible { outline: 2px solid var(--ps-primary); outline-offset: 1px; }
.ps-user-viewlabel {
  padding: 6px 10px 2px; font-size: 11px; font-weight: 600; letter-spacing: 0.04em;
  text-transform: uppercase; color: var(--ps-text-muted);
}
.ps-viewas-ro { font-weight: 600; }
.ps-user-viewnote {
  padding: 4px 10px 8px; font-size: 11px; color: var(--ps-text-muted);
}

/* ----- "Everyone will be imported" — the who's-in step's default state -------- */
/* Stated as a fact about what will happen, not as a muted note about what has not been set:
   somebody about to import their whole company should be told so in those words. */
.ps-everyone {
  padding: 9px 12px; font-size: 13px; color: var(--ps-text);
  background: var(--ps-surface); border: 1px solid var(--ps-border);
  border-left: 3px solid var(--ps-primary); border-radius: var(--ps-radius-sm);
}
.ps-everyone span { color: var(--ps-text-muted); }
.ps-wiz-warn {
  padding: 9px 12px; font-size: 13px; color: var(--ps-text);
  background: var(--ps-warn-bg, #fff8ec); border: 1px solid var(--ps-warn);
  border-left: 3px solid var(--ps-warn); border-radius: var(--ps-radius-sm);
}
.ps-wiz-warn span { color: var(--ps-text-muted); }

/* ----- Work in progress ---------------------------------------------------- */
/* Red and interrupting, on purpose. These screens carry real company numbers, and somebody
   acting on an unfinished figure has been misled by us — a subtle chip would not do. */
.ps-wip-tag {
  margin-left: 6px; font-size: 10px; font-weight: 700; letter-spacing: 0.04em;
  color: var(--ps-negative);
}
.ps-wip {
  margin: 0 0 16px; padding: 12px 16px;
  background: var(--ps-warn-bg, #fdf3f2);
  border: 1px solid var(--ps-negative);
  border-left: 4px solid var(--ps-negative);
  border-radius: var(--ps-radius);
}
.ps-wip-title {
  margin: 0 0 2px; font-size: 15px; font-weight: 700; color: var(--ps-negative);
}
.ps-wip-body { font-size: 13px; color: var(--ps-text-muted); }

/* ----- Report a bug -------------------------------------------------------- */
/* Quiet until you need it: the same weight as the attention chip beside it, not competing
   with the page. Full-strength on hover so it is obviously a control. */
.ps-bug-btn {
  display: inline-flex; align-items: center; justify-content: center;
  width: 30px; height: 30px; margin-left: 6px; padding: 0;
  color: var(--ps-text-muted); background: transparent; cursor: pointer;
  border: 1px solid var(--ps-border); border-radius: 8px;
}
.ps-bug-btn:hover { color: var(--ps-negative); border-color: var(--ps-negative); }
.ps-bug-btn:focus-visible { outline: 2px solid var(--ps-primary); outline-offset: 1px; }

/* ----- Person modal: every editable field as current | proposed ---------- */
/* One CSS grid so the columns line up down the whole modal. Without a shared track the
   "current" figures wander with the width of each label and the comparison — the entire
   point of this modal — stops being a straight vertical read. */
.ps-pm-row {
  display: grid;
  grid-template-columns: 1.35fr 0.9fr 1.25fr 28px 1.6fr;
  gap: 8px; align-items: center;
  padding: 7px 0; border-bottom: 1px solid var(--ps-border);
}
.ps-pm-head {
  font-size: 11px; font-weight: 700; letter-spacing: .04em; text-transform: uppercase;
  color: var(--ps-text-muted); border-bottom: 1px solid var(--ps-border); padding-bottom: 4px;
}
.ps-pm-label { font-size: 13px; font-weight: 600; }
.ps-pm-cur { font-size: 13px; color: var(--ps-text-muted); }
.ps-pm-none { color: var(--ps-border); }
/* The definition pair (% / fixed + the figure) has to sit on one line or the row height
   jumps for two fields out of eleven and the grid stops reading as a table. */
.ps-pm-defn { display: flex; gap: 6px; align-items: center; }
.ps-pm-defn > *:last-child { flex: 1; min-width: 0; }
/* The expanded ⓘ thread spans the full row, under it. */
.ps-pm-thread { grid-column: 1 / -1; padding: 2px 0 6px; }
/* The one message under Save. Red, right-aligned to sit with the button it refers to. */
.ps-pm-msg {
  text-align: right; margin-top: 6px; font-size: 12.5px; font-weight: 600;
  color: var(--ps-negative);
}

/* ----- Pages that fill the window instead of scrolling it ----------------- */
/* John, 2026-08-12: "that bar should be visible on all screen sizes all the time no matter
   how far up or down you are scrolled."

   The grid's horizontal scrollbar sits at the bottom of the grid, so the only way to keep it
   on screen is for the grid to END at the bottom of the window. It did not: the page was 92px
   taller than the viewport on a 883px window, which put the scrollbar 13px below the fold the
   moment you scrolled at all. The old fix guessed the chrome above at `100dvh - 22rem`, which
   is wrong again as soon as a banner appears or a budget pool is added.

   So the page becomes a flex column that fills the window, and the grid takes whatever is
   left. Nothing above it needs to know its own height.

   Scoped with :has() so only pages that opt in change — long pages (Settings, Help, Pay Bands)
   must keep scrolling normally. */
.ps-shell:has(.ps-page-fill) { height: 100dvh; min-height: 0; overflow: hidden; }
.ps-shell:has(.ps-page-fill) .ps-main,
.ps-shell:has(.ps-page-fill) .ps-page { min-height: 0; overflow: hidden;
                                        display: flex; flex-direction: column;
                                        flex: 1 1 auto; }
/* Dash's page_container adds two unclassed wrappers between .ps-page and the page's own
   root. They are plain blocks, so the height stopped dead at them — measured: .ps-main 818px,
   .ps-page 528px, and the grid stuck at 144px. Every link in the chain has to grow AND be
   allowed to shrink (`min-height: 0`), or one missing link puts the scrollbar back below
   the fold. */
.ps-shell:has(.ps-page-fill) .ps-page > div,
.ps-shell:has(.ps-page-fill) .ps-page > div > div {
  flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column;
}
/* …except Dash's own `_pages_dummy`, which is an empty div sitting beside the page content.
   Told to grow, it happily took 145px of the window and pushed the scrollbar back down. */
.ps-shell:has(.ps-page-fill) #_pages_dummy { flex: 0 0 auto; min-height: 0; }
.ps-page-fill { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; }
.ps-page-fill .mantine-Tabs-root { flex: 1 1 auto; min-height: 0; display: flex;
                                   flex-direction: column; }
/* All panels, not just the active one: Mantine hides the inactive panel with `display: none`,
   which wins anyway, and matching on the active attribute was simply not matching. */
.ps-page-fill .mantine-Tabs-panel { flex: 1 1 auto; min-height: 0;
                                    display: flex; flex-direction: column; }
.ps-tablecard-fill { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; }
.ps-tablecard-fill > .ps-tablehead { flex: 0 0 auto; }
.ps-tablecard-fill > div:last-child { flex: 1 1 auto; min-height: 0; }

/* ----- Collapsed sidebar: an icon rail ------------------------------------ */
/* John, 2026-08-12: "make the left nav bar collapsible please so we can save space for users
   on small screens." On a 1280px laptop the sidebar is 224px — nearly a fifth of the window —
   spent on labels you already know, next to grids that are the widest thing in the product.

   Collapsed keeps the ICONS rather than hiding the nav outright, so the active-screen marker
   is still there and you can still change screen without expanding first. A drawer you have
   to open to navigate would cost more clicks than it saves pixels. */
.ps-shell.ps-nav-collapsed .ps-sidebar {
  width: var(--ps-sidebar-w-rail);
  flex: 0 0 var(--ps-sidebar-w-rail);
  padding-left: 8px; padding-right: 8px;
}
/* The label goes, the icon stays put. `display:none` rather than zero width so the flex row
   collapses cleanly and the icon centres in the rail. */
.ps-shell.ps-nav-collapsed .ps-sidebar .ps-nav { justify-content: center; padding: 8px 0; }
.ps-shell.ps-nav-collapsed .ps-sidebar .ps-nav > *:not(.ps-nav-ico):not(.ps-nav-badge),
.ps-shell.ps-nav-collapsed .ps-sidebar .ps-collapse-label { display: none; }
/* The unread count would overflow a 56px rail, so it becomes a dot on the icon. */
.ps-shell.ps-nav-collapsed .ps-sidebar .ps-nav-badge:not(:empty) {
  position: absolute; top: 2px; right: 6px; min-width: 8px; height: 8px;
  padding: 0; font-size: 0; border-radius: 999px;
}
.ps-shell.ps-nav-collapsed .ps-sidebar .ps-nav { position: relative; }
/* Keep the active marker: it is the whole reason for collapsing to icons rather than hiding. */
.ps-shell.ps-nav-collapsed .ps-sidebar .ps-nav.active::before { top: 3px; bottom: 3px; }

/* ----- Maximised grid: the table takes the window ------------------------- */
/* John, 2026-08-12: "we are struggling to see the data on smaller screens ... allow the user
   to maximise the grid to fill the entire browser window (covering the tiles and nav menu)."

   Twenty-odd columns of pay data is the widest thing in the product, and on a laptop it is
   competing with the narrowest space. Collapsing the menu buys back 170px; this buys back the
   tiles, the menu, the topbar and the page padding at once.

   `position: fixed` over the lot rather than restructuring the page: the grid keeps its own
   DOM position, so every callback that reads or writes it is untouched, and minimising is
   removing one class. */
/* A window control, in the place a window puts one: top-left of the card. Green means it
   will expand, red means it will come back — the colour carries the direction, which is why
   the label is only there for screen readers. Borrowed from the macOS traffic lights on the
   assumption that everyone already knows what a small coloured dot at the top-left does. */
.ps-grid-max-btn {
  display: inline-flex; align-items: center; justify-content: center;
  width: 20px; height: 20px; flex: 0 0 20px; padding: 0;
  border-radius: 999px; cursor: pointer;
  background: #28c840; border: 1px solid rgba(0, 0, 0, .14);
  /* The glyph is dark-on-green rather than white: at 11px a white icon on that green is thin
     enough to disappear, especially on a dim laptop panel. */
  color: rgba(0, 0, 0, .55);
  transition: background .12s ease, transform .12s ease;
}
.ps-grid-max-btn:hover { transform: scale(1.08); background: #23b23a; }
.ps-tablecard-max .ps-grid-max-btn { background: #ff5f57; }
.ps-tablecard-max .ps-grid-max-btn:hover { background: #ec4a42; }
/* Present for screen readers and for the tooltip, never drawn. */
.ps-visually-hidden {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

.ps-tablecard-max {
  position: fixed; inset: 0; z-index: 200;
  margin: 0; border-radius: 0; border: none;
  display: flex; flex-direction: column;
  background: var(--ps-surface);
  padding: 12px 16px;
}
/* The grid fills whatever the header leaves. Without this it keeps the height it had in the
   page and maximising gains you a lot of white space and no extra rows. */
.ps-tablecard-max > .ps-tablehead { flex: 0 0 auto; }
.ps-tablecard-max > div:last-child { flex: 1 1 auto; min-height: 0; height: auto !important; }
/* The modals live outside this card in the DOM, so they must still come out on top of it —
   editing a cell while maximised opens the reason box, and a dialog behind an opaque overlay
   is a dead end.
   ONLY the modal root. Lifting `.mantine-Overlay-root` too put the grey backdrop level with
   the dialog it is supposed to sit behind, so the whole screen dimmed and nothing could be
   typed into — you had to press Escape to get the page back. Mantine already stacks the
   overlay and the content correctly INSIDE the root; the root is the only thing that needed
   to clear the maximised card. */
.mantine-Modal-root { z-index: 400; }

/* ----- Budget figures inside a maximised grid's header -------------------- */
/* Maximising covers the tiles, which is fine until you are editing pay and want to watch the
   budget move. This is the same `pool-tiles` tree, mirrored into the grid header and squeezed
   onto one line — hidden entirely until the grid is maximised, so the normal view is unchanged
   and the header keeps the height it has now.
   The real class names matter here: the figures are `.ps-pool-fig` with `-v`/`-l` inside, and
   `.ps-pools` is a COLUMN with a bottom margin. Guessing at `.ps-pool-figure` the first time
   meant none of this applied and the header grew from 59px to 144px. */
.ps-band-mini { display: none; }
.ps-tablecard-max .ps-band-mini {
  display: flex; align-items: center; min-width: 0; overflow: hidden; margin-right: 4px;
}
/* One line, always. A toolbar that changes height when you maximise is its own small betrayal;
   figures that do not fit scroll sideways rather than pushing the row taller. */
.ps-tablecard-max .ps-tablehead { flex-wrap: nowrap; }
.ps-band-mini .ps-pools {
  display: flex; flex-direction: row; flex-wrap: nowrap; gap: 16px; align-items: center;
  margin: 0; min-width: 0; overflow-x: auto; scrollbar-width: none;
}
.ps-band-mini .ps-pools::-webkit-scrollbar { display: none; }
/* The pool bar is a 5-column grid built for a full-width card; in a toolbar it is just a row
   of figures. The progress track, the headcount pills and the pool name all go. */
.ps-band-mini .ps-pool {
  display: flex; flex: 0 0 auto; align-items: center; gap: 12px;
  padding: 0; border: none; background: none; box-shadow: none;
}
.ps-band-mini .ps-pool-progress,
.ps-band-mini .ps-pool-meta,
.ps-band-mini .ps-pool-cap { display: none; }
.ps-band-mini .ps-pool-name {
  font-size: 10.5px; font-weight: 700; text-transform: uppercase; letter-spacing: .03em;
  color: var(--ps-text-muted);
}
.ps-band-mini .ps-pool-fig { display: flex; flex-direction: column; line-height: 1.1; }
.ps-band-mini .ps-pool-fig-v { font-size: 13px; }
.ps-band-mini .ps-pool-fig-l { font-size: 9px; margin-top: 0; }
.ps-band-mini .ps-pool-delta { font-size: 10.5px; }
.ps-band-mini, .ps-band-mini * { white-space: nowrap; }
/* The corner stack and how the budget was set: one line in a toolbar, empty slots dropped. */
.ps-band-mini .ps-pool-stack { display: flex; gap: 10px; min-height: 0; }
.ps-band-mini .ps-stack-line:empty, .ps-band-mini .ps-pool-budget-how { display: none; }
/* On a narrow window the figures win and the search box gives up its width — the numbers are
   the reason this is here. */
.ps-tablecard-max .ps-tablehead .ps-search-input { max-width: 160px; }

/* The undo's own voice. A keystroke with no visible result is indistinguishable from a
   keystroke that did nothing, so Ctrl+Z always says what it did — or why it did not. */
.ps-undo-msg {
  font-size: 12px; font-weight: 600; color: var(--ps-text-muted);
  white-space: nowrap; max-width: 320px; overflow: hidden; text-overflow: ellipsis;
}

/* The band's Configure-bands notes, shown inside Band values & overlap on View bands so
   they can be read against the plotted chart without changing tabs. */
.ps-bandnotes { margin: 6px 0 2px; font-size: 12.5px; color: var(--ps-text);
                background: rgba(255,255,255,0.5); border-left: 3px solid var(--ps-primary);
                padding: 6px 10px; border-radius: 6px; white-space: pre-wrap; }
.ps-bandnotes:empty { display: none; }

/* The floating horizontal scrollbar (assets/sticky-hscroll.js): pinned to the window
   bottom while a wide grid is on screen with its own scrollbar below the fold. */
.ps-float-hscroll {
  position: fixed; bottom: 0; height: 14px; display: none;
  overflow-x: auto; overflow-y: hidden; z-index: 60;
  background: rgba(255, 255, 255, 0.82);
  border-top: 1px solid var(--ps-border);
  backdrop-filter: blur(4px);
}
.ps-float-hscroll > div { height: 1px; }

/* "not in a band" in the membership grid — a to-do, not an error. */
.ps-people-grid .ps-band-missing { color: var(--ps-text-muted); font-style: italic; }

/* Why somebody on the family list is not on the chart, said on their row.
   Excluded: taken out by hand, red, still listed — an exclusion you cannot see is one the
   next person undoes by widening the rule. Conflict: two families claim them, so the app
   places them in neither; amber because nothing is wrong with the person, the rules
   overlap. Both keep the row readable — a strike-through would say "deleted". */
.ag-theme-quartz.ps-grid .ag-row.ps-row-excluded {
  background: var(--ps-negative-bg, #fdf2f2);
  box-shadow: inset 3px 0 0 var(--ps-negative);
}
.ag-theme-quartz.ps-grid .ag-row.ps-row-conflict {
  box-shadow: inset 3px 0 0 var(--ps-warn-solid);
}
.ps-people-grid .ps-membership-out { color: var(--ps-negative); font-weight: 600; }
.ps-people-grid .ps-membership-clash { color: var(--ps-warn); font-weight: 600; }

/* A card header that is itself the show/hide control. */
.ps-tablehead-toggle:hover { background: var(--ps-row-hover); }
.ps-tablehead-toggle { user-select: none; }

/* "This page was left behind by a deploy" — see assets/stale-tab.js.
   Bottom-centre rather than a top strip: it must not push the layout down or cover the
   topbar, because the user is mid-task when it appears and the page must stay usable. */
.ps-stale-bar {
  position: fixed; left: 50%; bottom: 18px; transform: translateX(-50%);
  z-index: 9000; max-width: min(680px, calc(100vw - 32px));
  display: flex; align-items: center; gap: 12px;
  padding: 10px 14px; border-radius: var(--ps-radius);
  background: var(--ps-surface); color: var(--ps-text);
  border: 1px solid var(--ps-negative);
  box-shadow: 0 6px 24px rgba(0, 0, 0, 0.16);
  font-size: 13px; line-height: 1.35;
}
.ps-stale-btn, .ps-stale-dismiss {
  flex: 0 0 auto; cursor: pointer; font-size: 13px; font-weight: 600;
  padding: 6px 12px; border-radius: var(--ps-radius-sm, 6px);
  border: 1px solid transparent;
}
.ps-stale-btn { background: var(--ps-negative); color: #fff; }
.ps-stale-dismiss {
  background: transparent; color: var(--ps-text-muted);
  border-color: var(--ps-border); font-weight: 500;
}
.ps-stale-dismiss:hover { background: var(--ps-row-hover); }
@media (max-width: 620px) { .ps-stale-bar { flex-wrap: wrap; } }

/* The band's change list — its own panel, no longer speech bubbles in the AI chat. */
.ps-changerow {
  display: flex; gap: 8px; align-items: baseline;
  padding: 5px 0; border-bottom: 1px solid var(--ps-border);
  font-size: 13px;
}
.ps-changerow:last-child { border-bottom: 0; }
.ps-changemark { width: 14px; flex: 0 0 14px; color: var(--ps-text-muted); }
.ps-changerow:first-child .ps-changemark { color: var(--ps-negative); font-weight: 700; }
.ps-changetitle { font-weight: 600; margin-right: 8px; }
.ps-changenote { color: var(--ps-text-muted); }

/* ---------------------------------------------------------------------------
   Pay Principles (/principles)
   Every value here is a token — see DESIGN-SYSTEM.md §1. Opaque surfaces only;
   the card is .ps-card, so it inherits the app's panel treatment and this file
   only adds the layout the list needs.
   --------------------------------------------------------------------------- */
.ps-pp-intro { margin-bottom: var(--ps-gap); }
.ps-pp-introrow {
  display: flex; align-items: flex-start; justify-content: space-between;
  gap: var(--ps-gap);
}

.ps-pp-card { margin-bottom: var(--ps-gap-sm); padding: 12px 14px; }
/* A live principle is doing something to people's pay right now; an off one is not.
   The left edge is the cheapest way to see which is which down a long list. */
.ps-pp-card.ps-pp-live    { border-left: 3px solid var(--ps-primary); }
.ps-pp-card.ps-pp-draft   { border-left: 3px solid var(--ps-warn); }
.ps-pp-card.ps-pp-disabled{ border-left: 3px solid var(--ps-border); }

.ps-pp-head { display: flex; align-items: flex-start; gap: 10px; flex-wrap: wrap; }
.ps-pp-order { display: flex; flex-direction: column; gap: 2px; padding-top: 2px; }
/* A text triangle rather than an icon: at this size an 8px chevron rendered as a grey
   square, and the glyph needs no network round trip to arrive. */
.ps-pp-move {
  border: 1px solid var(--ps-border); background: var(--ps-surface);
  border-radius: var(--ps-radius-sm); width: 24px; height: 20px; cursor: pointer;
  display: flex; align-items: center; justify-content: center;
  color: var(--ps-text-muted); font-size: 9px; line-height: 1; padding: 0;
}
.ps-pp-move:hover:not(:disabled) { background: var(--ps-row-hover); color: var(--ps-text); }
/* Still legible when it cannot be pressed — a disabled arrow must read as an arrow that is
   unavailable, not as an empty box. */
.ps-pp-move:disabled { opacity: .5; cursor: default; }

/* The title is a real button (DESIGN-SYSTEM.md §3: no clickable spans) but has to read
   as a heading, so the button chrome is removed rather than the semantics. */
.ps-pp-title {
  flex: 1; text-align: left; background: none; border: 0; padding: 0; cursor: pointer;
  font: inherit; color: inherit; display: flex; align-items: center;
  gap: 8px; flex-wrap: wrap;
}
.ps-pp-name { font-size: 14.5px; font-weight: 700; }
.ps-pp-summary {
  flex-basis: 100%; font-size: 13px; color: var(--ps-text-muted); margin-top: 2px;
}
.ps-pp-right {
  display: flex; align-items: center; gap: 10px;
  flex-wrap: wrap; justify-content: flex-end;
}
.ps-pp-count { font-weight: 600; color: var(--ps-text); }

.ps-pp-dot {
  font-size: 11px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase;
  padding: 2px 7px; border-radius: 999px;
}
.ps-pp-dot.ps-pp-live     { background: var(--ps-kpi-green-bg); color: var(--ps-kpi-green); }
.ps-pp-dot.ps-pp-draft    { background: var(--ps-warn-bg); color: var(--ps-warn); }
.ps-pp-dot.ps-pp-disabled { background: var(--ps-field-bg); color: var(--ps-text-muted); }
.ps-pp-dot.ps-pp-archived { background: var(--ps-field-bg); color: var(--ps-faint); }

/* The mode switch sits on the bar, beside the count. It never shrinks into an unreadable
   sliver: on a narrow screen the right-hand group wraps under the title instead. */
.ps-pp-modeswitch { flex-shrink: 0; }
/* A fixed slot for the count, so the switches line up down the list whatever it says. */
.ps-pp-cost { min-width: 220px; display: inline-flex; align-items: center;
               justify-content: flex-end; gap: 8px; }
/* The cost, so it pops down the list. Blue for a spend, green for a saving. */
.ps-pp-costpill {
  font-weight: 700; font-size: 13px; padding: 3px 10px; border-radius: 999px;
  background: var(--ps-kpi-blue-bg); color: var(--ps-kpi-blue); white-space: nowrap;
}
.ps-pp-costpill-save { background: var(--ps-kpi-green-bg); color: var(--ps-kpi-green); }
.ps-pp-del {
  border: 0; background: none; cursor: pointer; color: var(--ps-text-muted);
  padding: 4px; border-radius: var(--ps-radius-sm); display: flex;
}
.ps-pp-del:hover:not(:disabled) { color: var(--ps-negative); background: var(--ps-row-hover); }
.ps-pp-del:disabled { opacity: .35; cursor: default; }

.ps-pp-body { margin-top: 10px; }
.ps-pp-section { padding: 10px 0 2px; }
.ps-pp-preview {
  margin-top: 14px; padding: 12px; border-radius: var(--ps-radius-sm);
  background: var(--ps-field-bg); border: 1px solid var(--ps-border);
}

/* --- the condition builder ------------------------------------------------- */
.ps-cond-empty { font-size: 13.5px; color: var(--ps-text-muted); }
.ps-cond-group {
  display: flex; align-items: flex-start; gap: 8px; justify-content: space-between;
  padding: 8px 10px; border: 1px solid var(--ps-border);
  border-radius: var(--ps-radius-sm); background: var(--ps-surface);
}
.ps-cond-pills { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; }
.ps-cond-pill {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 3px 6px 3px 9px; border-radius: 999px;
  background: var(--ps-primary-soft); color: var(--ps-text); font-size: 12.5px;
}
.ps-cond-warn { color: var(--ps-warn); display: inline-flex; }
.ps-cond-x, .ps-cond-grp-x {
  border: 0; background: none; cursor: pointer; color: var(--ps-text-muted);
  font-size: 13px; line-height: 1; padding: 2px 4px; border-radius: var(--ps-radius-sm);
}
.ps-cond-x:hover, .ps-cond-grp-x:hover { color: var(--ps-negative); }
.ps-cond-grp-x { font-size: 11.5px; white-space: nowrap; }
/* OR between groups: a separator that reads as a word, not a border. */
.ps-cond-or {
  font-size: 11px; font-weight: 700; letter-spacing: .08em; color: var(--ps-faint);
  padding: 4px 0 4px 10px;
}
/* Provenance chip: where a copied principle came from. */
.ps-pp-from {
  font-size: 11px; font-weight: 600; letter-spacing: .03em;
  padding: 2px 6px; border-radius: var(--ps-radius-sm);
  background: var(--ps-field-bg); color: var(--ps-text-muted);
}

/* A cell a pay principle is driving, and one a human has taken back from it.
   Left edge rather than a fill: these sit on money columns that already use colour for
   pay cuts, queries and unaccepted changes, and a second background would fight them. */
.ps-cell-principle { box-shadow: inset 2px 0 0 var(--ps-primary); }
.ps-cell-pinned    { box-shadow: inset 2px 0 0 var(--ps-warn); }
/* "Exclude" on a principle's people grid. Muted until hovered: it is available on every
   row, and a column of red would read as a warning about the people rather than an action. */
.ps-exclude-cell {
  cursor: pointer; font-size: 12.5px; color: var(--ps-text-muted);
  text-decoration: underline; text-underline-offset: 2px;
}
.ps-exclude-cell:hover { color: var(--ps-negative); }

/* The two lists under the grid: who was taken out, and who was overridden by hand. */
.ps-pp-aside { margin-top: 12px; }
.ps-pp-aside summary {
  cursor: pointer; font-size: 12.5px; font-weight: 600; color: var(--ps-text-muted);
  padding: 4px 0;
}
.ps-pp-aside-row {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  padding: 6px 10px; border-bottom: 1px solid var(--ps-row-border); font-size: 13px;
}
.ps-pp-aside-row:last-child { border-bottom: 0; }
.ps-pp-aside-why { color: var(--ps-text-muted); font-size: 12.5px; }

/* The rule, as editable rows. Each row is one condition; the join before it says how it
   relates to the one above. No separate "add a condition" form — the rule IS the thing. */
.ps-rule { display: flex; flex-direction: column; gap: 6px; }
.ps-rule-row { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
/* Keeps the first row's controls aligned with every later row's, which have a join in
   front of them. */
.ps-rule-join-spacer { width: 96px; flex: 0 0 96px; }   /* = the AND/OR/EXCEPT select */
.ps-rule-x {
  border: 0; background: none; cursor: pointer; color: var(--ps-text-muted);
  font-size: 15px; line-height: 1; padding: 2px 6px; border-radius: var(--ps-radius-sm);
}
.ps-rule-x:hover { color: var(--ps-negative); background: var(--ps-row-hover); }
/* "Not saved yet" — the figures above describe what is on screen, while the heading on the
   card still names the saved rule. Saying so is the difference between a live preview and a
   number that quietly disagrees with the line above it. */
.ps-pp-unsaved {
  font-size: 12.5px; color: var(--ps-warn); margin-bottom: 10px;
  padding-bottom: 8px; border-bottom: 1px dashed var(--ps-warn-border);
}
/* "Saved" — a quiet confirmation, not a button. There is nothing to press: editing the
   rule saves it. It fades so it confirms without becoming furniture. */
.ps-pp-saved { color: var(--ps-positive); font-weight: 600; animation: ps-fade 2.5s ease-out forwards; }
@keyframes ps-fade { 0%, 55% { opacity: 1; } 100% { opacity: 0; } }
/* Bonus impact. Deliberately BELOW the cost figures and on its own line: base salary is
   what the pools and the KPI tiles count, and a bonus figure sitting in the same row as
   the cost would be read as part of it. */
.ps-pp-aside-note {
  margin-top: 12px; padding-top: 10px;
  border-top: 1px dashed var(--ps-border); font-size: 13px;
}

/* ----- The review list (AI Support) ------------------------------------- */
/* One row per person: who and why on the left, what it costs in the middle, the two
   decisions on the right. The evidence sentence is the row — it is set at reading size,
   not caption size, because it is the thing being judged. */
.ps-sug {
  display: flex; align-items: flex-start; gap: var(--ps-gap);
  padding: 12px 2px; border-top: 1px solid var(--ps-border);
}
.ps-sug:first-child { border-top: none; }
.ps-sug-main { flex: 1 1 auto; min-width: 0; }
.ps-sug-who { display: flex; align-items: center; gap: 8px; margin-bottom: 2px; }
.ps-sug-name { font-weight: 650; letter-spacing: -.01em; }
.ps-sug-tag {
  font-size: 11px; font-weight: 600; letter-spacing: .02em; text-transform: uppercase;
  padding: 1px 6px; border-radius: 3px;
  /* A fact about the row, not a warning about it — the amber attention chip
     would read as "something is wrong with this person". */
  background: var(--ps-row-hover); color: var(--ps-faint);
  border: 1px solid var(--ps-border);
}
.ps-sug-why { font-size: 13px; color: var(--ps-text-muted); line-height: 1.5; }
.ps-sug-money { flex: 0 0 150px; text-align: right; }
.ps-sug-cost { font-weight: 650; font-variant-numeric: tabular-nums; }
.ps-sug-noprice { font-weight: 500; color: var(--ps-faint); }
a.ps-sug-name { color: var(--ps-text); text-decoration: none; }
a.ps-sug-name:hover { color: var(--ps-primary); text-decoration: underline; }
.ps-sug-run {
  font-size: 12px; color: var(--ps-text-muted); font-variant-numeric: tabular-nums;
}
/* Past the money left in the pool. Marks where the line falls rather than cutting the
   list off there — the trade-off below the line is still the user's to make. */
.ps-sug-over .ps-sug-run { color: var(--ps-negative); font-weight: 600; }
.ps-sug-act { flex: 0 0 auto; display: flex; gap: 6px; align-items: center; }
.ps-as-old { margin-top: 26px; }
.ps-as-old > summary {
  cursor: pointer; font-weight: 600; font-size: 13px; color: var(--ps-text-muted);
  padding: 8px 0;
}
.ps-sug-aside { margin-top: 14px; border-top: 1px solid var(--ps-border); padding-top: 10px; }
.ps-sug-aside > summary {
  cursor: pointer; font-size: 12.5px; color: var(--ps-text-muted); font-weight: 600;
}
.ps-sug-aside-row {
  display: flex; align-items: center; gap: 8px; font-size: 13px;
  color: var(--ps-text-muted); padding: 4px 0;
}

/* ----- Which figure an anchor actually uses ------------------------------ */
/* John, 2026-09-08: *"the user can add the P25, P50 and P75 to the same row and click the
   box to use as anchor, so how do they know what value is used?"* They know because that
   one figure is marked and the other two are not: ticking the box makes the level's
   MIDPOINT equal to its P50, exactly. */
.ag-theme-quartz .ps-anchored-figure {
  font-weight: 700; color: var(--ps-primary-dark);
  background: var(--ps-primary-soft);
}
/* A tick with no P50 under it anchors nothing — the fit needs a market figure before it
   will use a level — and it did so silently. */
.ag-theme-quartz .ps-anchor-empty { background: var(--ps-warn-bg); }

/* ----- One person's pay principles (the ⚖ dialog on Home) ---------------- */
.ps-prrow {
  display: flex; align-items: center; gap: var(--ps-gap);
  padding: 9px 2px; border-top: 1px solid var(--ps-row-border);
}
.ps-prrow:first-child { border-top: none; }
.ps-prrow-main { flex: 1 1 auto; min-width: 0; }
.ps-prrow-name { font-weight: 600; }
/* A principle that HAS this person, versus one merely offered. */
.ps-prrow.on .ps-prrow-name { color: var(--ps-primary-dark); }
.ps-prrow-act { flex: 0 0 auto; }
/* The ⚖ itself: quiet until it has something to say. */
.ps-info-cell.ps-principle-on { color: var(--ps-primary); font-weight: 700; }

/* Drift from a derived band's recorded percentage: attention, not error. A band you
   re-anchored on purpose has not failed anything. */
.ps-num-drift { color: var(--ps-warn); font-weight: 600; }

/* Ask for draft changes (View bands → People on the chart). Sits between the table's header
   and the grid it edits, so the request and its effect are on one screen. */
.ps-dask { margin: 10px 0 12px; display: flex; flex-direction: column; gap: 6px; }
.ps-dask-row { display: flex; gap: 8px; align-items: flex-start; }
.ps-dask-result { margin-top: 4px; display: flex; flex-direction: column; gap: 6px; }
.ps-dask-understood { font-weight: 600; color: var(--ps-text); }
.ps-dask-question { font-weight: 600; color: var(--ps-warn); }
.ps-dask-steps { margin: 0; padding-left: 20px; color: var(--ps-text-muted); font-size: 13px; }
.ps-dask-table { max-width: 760px; }
/* A decrease is the one outcome somebody must see before saving — "match John" can pull a
   person down. Marked on the row, not just in the figure. */
.ps-dask-table tr.ps-dask-down td { background: var(--ps-warn-bg); }

/* Live principles behind what they say (pages/principles.py `_out_of_date`). Amber like the
   other "needs you" banners; only drawn when re-applying would change somebody. */
.ps-card.ps-pp-stale { border-color: var(--ps-warn-border); background: var(--ps-warn-bg); }
.ps-pp-stale-row { display: flex; gap: 16px; align-items: center; justify-content: space-between; }
.ps-pp-stale-row .mantine-Button-root { flex-shrink: 0; }
.ps-pp-stale-title { font-weight: 600; color: var(--ps-warn); margin-bottom: 2px; }
.ps-pp-stale-who { cursor: pointer; font-size: 12.5px; font-weight: 600; margin: 10px 0 8px;
                   color: var(--ps-warn); }

/* The advisor's paperclip and what it holds (components/askbar.py, assets/ask-upload.js). */
.ps-ask-clip {
  display: inline-flex; align-items: center; justify-content: center; flex-shrink: 0;
  width: 30px; height: 30px; border-radius: 6px; cursor: pointer; color: var(--ps-ai-dark);
  border: 1px solid var(--ps-ai-border); background: var(--ps-surface);
}
.ps-ask-clip:hover { border-color: var(--ps-ai); background: var(--ps-ai-soft); }
.ps-ask-chip {
  display: inline-flex; align-items: center; gap: 6px; margin-bottom: 8px; max-width: 100%;
  font-size: 12px; padding: 3px 4px 3px 9px; border-radius: 999px;
  background: var(--ps-ai-soft); color: var(--ps-ai-dark); border: 1px solid var(--ps-ai-border);
}
.ps-ask-chip.is-err { background: var(--ps-warn-bg); color: var(--ps-warn);
                      border-color: var(--ps-warn-border); }
.ps-ask-chip-x { border: none; background: transparent; cursor: pointer; color: inherit;
                 font-size: 11px; padding: 0 5px; }
.ps-ask-attached { font-size: 12px; font-weight: 400; color: var(--ps-ai-dark); margin-top: 3px; }

/* A principle that matches people but reaches none of them in the stack (pages/principles.py
   `_preview_block`). */
.ps-pp-blocked { margin-top: 8px; font-size: 12.5px; color: var(--ps-warn);
                 background: var(--ps-warn-bg); border-radius: 6px; padding: 6px 10px; }

/* Pay Principles → Coverage (pages/principles.py `_coverage_view`). */
.ps-pp-cov-head { margin-bottom: 10px; }

/* New principle window (pages/principles.py `_new_modal`): describe it, or build it. */
#pp-new-modal .ps-nl { box-shadow: none; border: 1px solid var(--ps-border); margin: 0; }
.ps-nl-blank { padding: 4px 2px; }
.ps-nl-blank .ps-nl-head { margin-bottom: 8px; }

/* An open principle's heading is its name field (pages/principles.py `_open_title`). */
.ps-pp-title.is-open { cursor: default; }
.ps-pp-collapse { background: none; border: 0; cursor: pointer; color: var(--ps-text-muted);
                  font-size: 14px; padding: 0 2px; }
.ps-pp-name-input { flex: 1; min-width: 220px; }
.ps-pp-name-input .mantine-TextInput-input { font-size: 14.5px; font-weight: 700;
  border-bottom: 1px dashed var(--ps-border); border-radius: 0; height: auto; min-height: 0;
  padding: 1px 2px; }
.ps-pp-name-input .mantine-TextInput-input:focus { border-bottom-color: var(--ps-primary); }

/* Principles: the two ways to start one, in place of the old intro card. */
.ps-pp-actions { display: flex; gap: 10px; align-items: center; flex-wrap: wrap;
                 margin: 4px 0 12px; }
/* A two-stage principle's saving: money not spent, so it reads as good news. */
.ps-pp-saving { color: var(--ps-positive); }

/* Promotions and the two-stage saving, in the company pool's bottom-right corner —
   positioned, so the bar keeps its height (John, 2026-10-09). */
/* In the bar's grid, not over it: it used to be absolutely positioned at the bottom right,
   which only cleared the figures when the bar had a second row (a bonus uplift) — on a bar
   without one it sat on top of the last figure's label (backlog #134, seen in the demo
   video). As a grid item spanning the figure columns it shares the second row with the bonus
   uplift when there is one (no extra height, as asked on 2026-10-09), and takes a short row
   of its own, tucked up, when there is not. */
.ps-pool-corner {
  justify-self: end;
  font-size: 11px; color: var(--ps-text-muted); white-space: nowrap;
}
.ps-pool-corner .num { font-weight: 700; color: var(--ps-text); }
.ps-pool-corner .num.ps-pool-corner-pos { color: var(--ps-positive); }
.ps-pool-corner-sep { color: var(--ps-faint); }
/* A pool bar's corner: three lines stacked, always the same height, so the lines keep their
   places — promotions, the increase in bonuses, one more (John, 2026-10-15). */
.ps-pool-stack {
  justify-self: end; text-align: right;
  font-size: 11px; color: var(--ps-text-muted); line-height: 16px; min-height: 48px;
}
.ps-pool-corner .ps-stack-line { min-height: 0; }
.ps-stack-line { min-height: 16px; white-space: nowrap; }
.ps-pool-stack .num, .ps-pool-corner .num { font-weight: 700; color: var(--ps-text); }
.ps-pool-stack .num.ps-pool-corner-pos { color: var(--ps-positive); }

/* A grid cell edited from a list (Pay Bands → Use as anchor point): a ▾ so it reads as a
   dropdown, which one click opens (John, 2026-10-09). */
.ag-cell.ps-select-cell { position: relative; padding-right: 26px; cursor: pointer; }
.ag-cell.ps-select-cell::after {
  content: "▾"; position: absolute; right: 10px; top: 50%; transform: translateY(-50%);
  color: var(--ps-text-muted); font-size: 12px; pointer-events: none;
}
.ag-cell.ps-select-cell.ag-cell-inline-editing::after { content: none; }

/* Settings → On-cost override rules: the add/edit form, marked while a rule is being
   edited so it is obvious what Save will change (John, 2026-10-09). */
.ps-ocr-form { border-radius: var(--ps-radius-sm); transition: background .2s, box-shadow .2s; }
.ps-ocr-form.is-editing {
  background: var(--ps-primary-soft, rgba(79, 70, 229, .05));
  box-shadow: 0 0 0 2px var(--ps-primary); padding: 4px 14px 14px;
}

/* Something is being worked on (e.g. Pay Bands → deleting a job family): dimmed, and the
   cursor says so. Set by a callback's `running` and cleared when it finishes. */
.ps-busy { opacity: .5; pointer-events: none; cursor: progress; transition: opacity .15s; }

/* Configure band: Derive + Generate/Regenerate, bottom right of the band's figures. */
.ps-gen-row { display: flex; justify-content: flex-end; align-items: center; gap: 8px;
              margin-top: 8px; }

/* ----- Ladder template picker (components/template_picker.py) ----------------------------
   Three cards side by side, each a pay philosophy; the narrative opens in place. */
.ps-tpl-picker { display: flex; flex-direction: column; gap: 12px; }
.ps-tpl-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(230px, 1fr)); gap: 12px; }
.ps-tpl-card {
  border: 1px solid var(--ps-border); border-radius: var(--ps-radius); background: var(--ps-surface);
  padding: 14px 14px 12px; display: flex; flex-direction: column; min-width: 0;
  container-type: inline-size;
}
/* A narrow card stacks each fact's label over its figure, rather than squeezing the figure
   into a column three words wide. */
@container (max-width: 330px) {
  .ps-tpl-card .ps-tpl-facts { grid-template-columns: 1fr; gap: 0; }
  .ps-tpl-card .ps-tpl-facts dd { margin-bottom: 5px; }
}
/* "Read more" opens a side panel (`.ps-tpl-drawer`), so the cards never move. */
.ps-tpl-morebtn { align-self: flex-start; margin-top: 10px; padding-left: 0; }
.ps-tpl-drawer .mantine-Drawer-title { font-weight: 700; font-size: 18px; }
.ps-tpl-drawer .ps-tpl-detail { gap: 14px; }
.ps-tpl-card.is-selected { border-color: var(--ps-primary); box-shadow: inset 0 0 0 1px var(--ps-primary); }
.ps-tpl-plain { background: var(--ps-bg); }
/* Create new band → "Derive from existing band": a full-width strip above the templates, in
   the soft indigo the open cycle uses, so the other way to start is seen first. */
.ps-derive-strip {
  display: flex; align-items: center; justify-content: space-between; gap: 16px;
  padding: 14px 16px; border: 1px solid rgba(85, 98, 234, .25); border-radius: var(--ps-radius);
  background: var(--ps-primary-soft); box-shadow: inset 4px 0 0 var(--ps-primary);
}
.ps-derive-strip .ps-tpl-card-text { margin-top: 2px; }
.ps-tpl-mine {
  font-size: 10.5px; font-weight: 700; letter-spacing: .05em; text-transform: uppercase;
  color: var(--ps-text-muted); background: var(--ps-bg); padding: 1px 6px; border-radius: 4px;
}
.ps-tpl-actions { display: flex; gap: 6px; margin-top: 8px; flex-wrap: wrap; }
.ps-tpl-default-row { display: grid; grid-template-columns: minmax(140px, 220px) minmax(0, 340px);
  gap: 12px; align-items: center; padding: 6px 0; border-bottom: 1px solid var(--ps-row-border); }
.ps-tpl-head { display: flex; align-items: center; gap: 8px; }
.ps-tpl-name { font-weight: 700; font-size: 15px; }
.ps-tpl-current {
  font-size: 10.5px; font-weight: 700; letter-spacing: .05em; text-transform: uppercase;
  color: var(--ps-primary); background: var(--ps-primary-soft); padding: 1px 6px; border-radius: 4px;
}
.ps-tpl-card-text { font-size: 13px; color: var(--ps-text); margin-top: 6px; }
.ps-tpl-fit { font-size: 12.5px; color: var(--ps-text-muted); margin-top: 6px; }
.ps-tpl-shape { margin-top: 8px; }
.ps-tpl-headline { font-size: 12px; color: var(--ps-text); font-variant-numeric: tabular-nums; margin-top: 2px; }
.ps-tpl-k { font-weight: 600; color: var(--ps-text); }
.ps-tpl-facts { display: grid; grid-template-columns: auto 1fr; gap: 2px 10px; margin: 10px 0 0; font-size: 12px; }
.ps-tpl-facts dt { color: var(--ps-text-muted); }
.ps-tpl-facts dd { margin: 0; font-variant-numeric: tabular-nums; }
.ps-tpl-card > button { align-self: flex-start; margin-top: auto; }
.ps-tpl-more, .ps-tpl-chooser, .ps-tpl-sources { font-size: 13px; }
.ps-tpl-more { margin-top: 10px; }
.ps-tpl-more > summary, .ps-tpl-chooser > summary, .ps-tpl-sources > summary {
  cursor: pointer; color: var(--ps-primary); font-weight: 500; font-size: 12.5px;
}
.ps-tpl-chooser > summary { font-size: 13.5px; }
.ps-tpl-chooser ol { margin: 8px 0; padding-left: 20px; display: flex; flex-direction: column; gap: 6px; }
.ps-tpl-q > div { margin-bottom: 2px; }
.ps-tpl-notes { margin: 6px 0 0; padding-left: 18px; color: var(--ps-text-muted); font-size: 12.5px; }
.ps-tpl-detail { margin-top: 8px; display: flex; flex-direction: column; gap: 10px; }
.ps-tpl-ladder h5 { margin: 6px 0 4px; font-size: 13px; }
.ps-tpl-para { font-size: 12.5px; line-height: 1.5; margin-top: 6px; }
.ps-tpl-tablewrap { overflow-x: auto; }
.ps-tpl-table { border-collapse: collapse; font-size: 12px; width: 100%; }
.ps-tpl-table th { text-align: left; font-weight: 600; color: var(--ps-text-muted); padding: 3px 8px 3px 0; white-space: nowrap; }
.ps-tpl-table td { padding: 3px 8px 3px 0; border-top: 1px solid var(--ps-row-border); }
.ps-tpl-table td.num { font-variant-numeric: tabular-nums; white-space: nowrap; }
.ps-tpl-sources ul { margin: 6px 0; padding-left: 18px; font-size: 12.5px; }
/* The band's template, on the configure screen and the band tiles. */
.ps-tpl-line { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; font-size: 13px; }
.ps-tpl-badge { font-weight: 600; }
.ps-tpl-badge.is-edited, .ps-tpl-badge.is-custom { color: var(--ps-text-muted); }
.ps-tpl-tile { font-size: 11.5px; color: var(--ps-text-muted); margin-top: 2px; }

/* Pay Principles → "increase by % for length of service": one row per step of service. */
.ps-pp-tiers { display: flex; flex-direction: column; gap: 6px; margin-top: 10px; max-width: 360px; }
.ps-pp-tier { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.ps-pp-tier-label { font-size: 13px; color: var(--ps-text); }

/* Band editor: "Switch back to COP to drag" — a refusal, so it reads as one. */
.ps-unlock-refused { color: var(--ps-negative); font-weight: 600; }

/* Grid hovers can run to several lines (the bonus/incentive explanation): keep the breaks. */
.ag-theme-quartz .ag-tooltip { white-space: pre-line; max-width: 420px; }
/* A cut-off grid cell, whole, on hover (assets/cell-peek.js). Above the cell, so a column's own
   explanation tooltip below the pointer can show at the same time. */
.ps-cell-peek {
  position: fixed; z-index: 3000; max-width: 420px; max-height: 50vh; overflow: auto;
  padding: 7px 10px; border-radius: 8px; white-space: pre-line; overflow-wrap: anywhere;
  font-size: 13px; line-height: 1.4; color: var(--ps-text);
  background: var(--ps-surface); border: 1px solid var(--ps-border); box-shadow: var(--ps-shadow);
}

/* "Override manual entries" — the warning, and who it changes. */
.ps-pp-ov-warn {
  font-size: 13px; color: #8a2a2a; background: #fff1f1; border: 1px solid #f3c4c4;
  border-radius: 6px; padding: 8px 10px; margin-bottom: 10px;
}
.ps-pp-ov-table { width: 100%; font-size: 13px; border-collapse: collapse; }
.ps-pp-ov-table th { text-align: left; font-weight: 600; color: var(--ps-text-muted);
                     border-bottom: 1px solid var(--ps-border); padding: 4px 6px; }
.ps-pp-ov-table td { padding: 4px 6px; border-bottom: 1px solid var(--ps-border); }

/* Pay Bands → View bands: the key under the chart (managers). Swatches mirror the marks
   _ladder_figure draws — see pages/pay_bands.py _chart_key. */
.ps-chart-key {
  display: flex; flex-wrap: wrap; align-items: center; gap: 6px 18px;
  margin: 8px 16px 4px; padding: 10px 12px;
  border: 1px solid var(--ps-border); border-radius: var(--ps-radius-sm);
  font-size: 12px; color: var(--ps-text);
}
.ps-chart-key-title {
  font-size: 11px; font-weight: 650; letter-spacing: .04em; text-transform: uppercase;
  color: var(--ps-faint);
}
.ps-chart-key-item { display: inline-flex; align-items: center; gap: 6px; white-space: nowrap; }
.ps-chart-key-note { flex-basis: 100%; }
.ps-key-band {
  display: inline-block; width: 26px; height: 12px; border: 1px solid; border-radius: 4px;
}
.ps-key-line { display: inline-block; width: 0; height: 16px; }
.ps-key-person {
  display: inline-block; width: 14px; height: 14px; background: #868e96;
  border: 1.5px solid #fff; border-radius: 50%; box-shadow: 0 0 0 1px #dee2e6;
  box-sizing: border-box;
}
.ps-key-person.ring { border-width: 3px; box-shadow: none; }
.ps-key-person.up, .ps-key-person.down { border: 0; border-radius: 0; box-shadow: none; }
.ps-key-person.up { clip-path: polygon(50% 0, 100% 100%, 0 100%); }
.ps-key-person.down { clip-path: polygon(0 0, 100% 0, 50% 100%); }
.ps-key-promo { position: relative; display: inline-flex; }
.ps-key-promo::after {
  content: ""; position: absolute; left: 50%; bottom: -3px; transform: translateX(-50%);
  width: 6px; height: 6px; border-radius: 50%; background: #e03131;
  box-shadow: 0 0 0 1px #fff;
}

/* Insights → My Team: needs-attention cards. Each is a button: it opens the people concerned. */
.ps-attn-list { display: grid; grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
  gap: 10px; margin-top: 8px; }
.ps-attn-card { text-align: left; cursor: pointer; padding: 12px 14px; border-radius: 10px;
  border: 1px solid var(--ps-border); border-left: 3px solid #e8590c;
  background: var(--ps-surface); font: inherit; color: inherit; }
.ps-attn-card:hover { border-color: var(--ps-primary); border-left-color: #e8590c; }
.ps-attn-title { font-weight: 700; font-size: 14px; margin-bottom: 4px; }

/* Insights → My Team: the bar that carves the team up stays in view while you scroll. */
.ps-team-bar { position: sticky; top: var(--ps-topbar-h); z-index: 15; display: flex; flex-wrap: wrap;
  gap: 10px 14px; align-items: flex-end; padding: 10px 0 12px; margin-bottom: 6px;
  background: var(--ps-bg, #f4f5f8); border-bottom: 1px solid var(--ps-border); }
/* A stat tile that opens the people it counts. */
.ps-stat-click { text-align: left; cursor: pointer; font: inherit; color: inherit;
  width: 100%; }
.ps-stat-click:hover { border-color: var(--ps-primary); }
.ps-stat-more { font-size: 12px; font-weight: 600; color: var(--ps-primary); margin-top: 6px; }

/* ★ Top Performer column (TopPerformerCell). */
.ps-tp-cell { cursor: pointer; font-size: 17px; line-height: 1; color: #c9ced8; }
.ps-tp-cell.ps-tp-on { color: #f59f00; }
.ps-tp-cell.ps-tp-asked { color: #f59f00; opacity: .85; }
.ps-tp-cell:hover { color: #f59f00; }
.ps-tp-status { font-size: 15px; display: flex; align-items: center; gap: 4px; }
.ps-tp-h { font-weight: 700; font-size: 13px; margin: 12px 0 4px; }
.ps-tp-history { margin: 0; padding-left: 18px; max-height: 240px; overflow-y: auto; }
.ps-tp-history li { margin-bottom: 6px; }

/* ---------------------------------------------------------------------------------------- */
/* Manager Home — the page People Ops configure for managers (components/manager_home.py)  */
/* and its editor in Settings (components/manager_page_editor.py). One readable column:    */
/* it is mostly prose, and prose wider than ~80 characters is hard to read.                 */
/* ---------------------------------------------------------------------------------------- */
.ps-mh { max-width: 860px; display: flex; flex-direction: column; gap: var(--ps-gap); }
.ps-mh-head { display: flex; align-items: flex-end; justify-content: space-between;
              gap: var(--ps-gap); flex-wrap: wrap; margin-bottom: 2px; }
.ps-mh-title { font-size: 21px; font-weight: 700; margin: 0 0 2px; color: var(--ps-text); }
.ps-mh-block { margin: 0; }
.ps-mh-md p:last-child { margin-bottom: 0; }
.ps-mh-empty { padding: 4px 0; }
.ps-mh-headline { font-size: 15px; font-weight: 600; margin: 2px 0 10px; }
.ps-mh-principles { margin: 8px 0 0; padding-left: 20px; display: flex;
                    flex-direction: column; gap: 10px; }
.ps-mh-principle-name { font-weight: 600; color: var(--ps-text); }
.ps-mh-steps { list-style: none; margin: 4px 0 8px; padding: 0; display: flex;
               flex-wrap: wrap; gap: 6px 18px; }
.ps-mh-step { display: flex; align-items: center; gap: 6px; font-size: 13.5px;
              color: var(--ps-text-muted); }
.ps-mh-dot { width: 10px; height: 10px; border-radius: 50%; border: 2px solid var(--ps-border);
             background: var(--ps-surface); }
.ps-mh-step.is-done .ps-mh-dot { background: var(--ps-positive); border-color: var(--ps-positive); }
.ps-mh-step.is-now { color: var(--ps-text); font-weight: 600; }
.ps-mh-step.is-now .ps-mh-dot { background: var(--ps-primary); border-color: var(--ps-primary); }
.ps-mh-now, .ps-mh-yours { font-size: 11px; font-weight: 700; letter-spacing: .04em;
                           padding: 1px 6px; border-radius: var(--ps-radius-sm); }
.ps-mh-now { color: var(--ps-primary); background: var(--ps-primary-soft); }
.ps-mh-yours { color: var(--ps-text-muted); background: var(--ps-row-hover); }

.ps-mpe-head { display: flex; align-items: flex-start; justify-content: space-between;
               gap: var(--ps-gap); flex-wrap: wrap; }
.ps-mpe-list { display: flex; flex-direction: column; }
.ps-mpe-row { display: flex; align-items: center; gap: 12px; padding: 10px 0;
              border-top: 1px solid var(--ps-row-border); }
.ps-mpe-row:first-child { border-top: 0; }
.ps-mpe-ico { color: var(--ps-text-muted); display: inline-flex; }
.ps-mpe-main { flex: 1; min-width: 0; }
.ps-mpe-title { font-weight: 600; color: var(--ps-text); }
.ps-mpe-version, .ps-mpe-principle { border: 1px solid var(--ps-border);
                  border-radius: var(--ps-radius-sm); padding: 10px 12px; margin-top: 10px;
                  display: flex; flex-direction: column; gap: 6px; }
/* One card per principle: name in a header strip, its description controls inside. A ticked
   (shown-to-managers) card gets an indigo edge so the shared ones stand out in a long list. */
.ps-mpe-principle { padding: 0; gap: 0; overflow: hidden; background: var(--ps-surface);
                    border-left: 3px solid var(--ps-border); }
.ps-mpe-principle:has(.ps-mpe-phead input:checked) { border-left-color: var(--ps-primary); }
.ps-mpe-phead { padding: 9px 12px; background: var(--ps-row-hover);
                border-bottom: 1px solid var(--ps-row-border); }
.ps-mpe-pname { font-weight: 600; color: var(--ps-text); }
.ps-mpe-pbody { padding: 10px 12px 12px; display: flex; flex-direction: column; gap: 6px; }
.ps-mpe-vhead { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
.ps-mpe-vrm { margin-left: auto; background: none; border: 0; cursor: pointer;
              color: var(--ps-negative); font-size: 12px; }
.ps-mpe-head > div:first-child { flex: 1 1 420px; min-width: 0; }
.ps-mh-principles .ps-mh-md p { margin: 2px 0 0; }
.ps-mpe-faq { display: flex; flex-direction: column; gap: 10px; margin-top: 8px; }
.ps-mpe-qa { display: flex; flex-direction: column; gap: 4px; padding-top: 8px;
             border-top: 1px solid var(--ps-row-border); }
.ps-mpe-qa-head { display: flex; align-items: flex-end; gap: 8px; }
/* Audit Log grid: the categories worth noticing (access changes, exports) — pages/audit.py. */
.ps-audit-warn { color: var(--ps-warn); font-weight: 600; }
.ag-theme-quartz.ps-grid .ag-cell.ps-audit-wrap { line-height: 1.45; padding-top: 11px;
                                                 padding-bottom: 11px; word-break: break-word; }
.ps-mpe-autodesc { margin-top: 4px; padding: 8px 10px; border-radius: var(--ps-radius-sm);
                   background: var(--ps-row-hover); color: var(--ps-text-muted); font-size: 13px; }
/* Review Cycle Status: each stage explains itself on hover (components/manager_home.py). */
.ps-mh-steps > li { list-style: none; }
.ps-mh-step { cursor: help; border-bottom: 1px dotted transparent; }
.ps-mh-step:hover .ps-mh-step-label, .ps-mh-step:focus .ps-mh-step-label {
  border-bottom: 1px dotted var(--ps-text-muted); }
.ps-mh-tip-title { font-weight: 600; margin-bottom: 4px; color: var(--ps-text); }
/* Formatted text (data/rich_text.py). In the editor: a bordered box with its toolbar. On a
   manager's page: the same editor, read-only, drawn flush like any other text. */
.ps-mpe-label { font-size: 13px; font-weight: 500; color: var(--ps-text); margin: 8px 0 4px; }
.ps-rte .ProseMirror { min-height: 64px; }
.ps-rt-view { border: 0 !important; background: transparent !important; }
.ps-rt-view .mantine-RichTextEditor-content,
.ps-rt-view .ProseMirror { padding: 0 !important; background: transparent; }
.ps-rt-view .ProseMirror p:last-child { margin-bottom: 0; }
.ps-mh-block > .ps-rt-view + * { margin-top: 10px; }
/* A formatted block name: plain paragraphs read as a card title; H2–H4 are its bigger sizes. */
.ps-mh-title-rich .ProseMirror > p { font-size: 14.5px; font-weight: 700; margin: 0 0 8px; }
.ps-mh-title-rich .ProseMirror > :is(h2, h3, h4) { margin: 0 0 8px; }
/* The how-to video and its chapters (components/manager_home.py, assets/manager-guide.js).
   The player fills the card; the chapters sit under it as a numbered list, each a button
   that jumps the video there. The chapter playing now is marked as the video runs. */
.ps-mh-video { display: flex; flex-direction: column; gap: 12px; margin-top: 4px; }
.ps-mh-video-player { width: 100%; aspect-ratio: 16 / 9; display: block; background: #0f1220;
                      border-radius: var(--ps-radius-sm); }
.ps-mh-chapters { list-style: none; margin: 0; padding: 0; display: grid;
                  grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); gap: 6px; }
.ps-mh-chapter { width: 100%; display: flex; align-items: flex-start; gap: 10px;
                 padding: 8px 10px; text-align: left; font: inherit; cursor: pointer;
                 background: transparent; border: 1px solid var(--ps-border);
                 border-radius: var(--ps-radius-sm); color: var(--ps-text); }
.ps-mh-chapter:hover { background: var(--ps-row-hover); border-color: var(--ps-primary); }
.ps-mh-chapter:focus-visible { outline: 2px solid var(--ps-primary); outline-offset: 1px; }
.ps-mh-chapter.is-playing { background: var(--ps-primary-soft); border-color: var(--ps-primary); }
.ps-mh-chapter-num { flex: none; width: 22px; height: 22px; border-radius: 50%;
                     display: inline-flex; align-items: center; justify-content: center;
                     font-size: 12px; font-weight: 700; color: var(--ps-primary);
                     background: var(--ps-primary-soft); }
.ps-mh-chapter.is-playing .ps-mh-chapter-num { color: #fff; background: var(--ps-primary); }
.ps-mh-chapter-text { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 1px; }
.ps-mh-chapter-title { font-weight: 600; font-size: 13.5px; }
.ps-mh-chapter-sub { font-size: 12.5px; color: var(--ps-text-muted); line-height: 1.35; }
.ps-mh-chapter-at { flex: none; font-size: 12px; color: var(--ps-text-muted);
                    font-variant-numeric: tabular-nums; padding-top: 2px; }
/* A compa-ratio that opens the person's pay band on double-click (components/grids.py). */
.ag-theme-quartz.ps-grid .ag-cell.ps-compa-link { cursor: pointer; }
.ag-theme-quartz.ps-grid .ag-cell.ps-compa-link:hover .ag-cell-value {
  text-decoration: underline dotted; text-underline-offset: 3px; }
/* Review requests tab (pages/home.py): severity reads at a glance; rows open the thread. */
.ag-theme-quartz.ps-grid .ag-cell.ps-sev-major { color: var(--ps-negative); font-weight: 700; }
.ag-theme-quartz.ps-grid .ag-cell.ps-sev-medium { color: var(--ps-warn); font-weight: 700; }
.ag-theme-quartz.ps-grid .ag-cell.ps-sev-minor { color: var(--ps-text-muted); font-weight: 600; }
.ag-theme-quartz.ps-grid .ag-row.ps-click-row { cursor: pointer; }
.ag-theme-quartz.ps-grid .ag-cell.ps-dir-up { color: var(--ps-primary); font-weight: 600; }
.ag-theme-quartz.ps-grid .ag-cell.ps-dir-down { color: var(--ps-warn); font-weight: 600; }
/* Pay Bands: "People on the chart | Review requests in this chart" sit side by side in the
   card's header row, where the heading used to be. */
.ps-tablehead .ps-pb-people-tabs { flex: none; flex-wrap: nowrap; border-bottom: 0; }
.ps-tablehead .ps-pb-people-tabs .mantine-Tabs-tab { white-space: nowrap; }
/* ⚖ dialog: a principle that matches but a hand-set figure overrides (pages/home.py). */
.ps-prrow.overridden .ps-prrow-name { color: var(--ps-warn); }

/* ---- Review button + review panel (components/review_panel.py, 2026-10-24) ----------- */
.ps-review-pill { display: inline-block; padding: 2px 10px; border-radius: 999px;
                  font-size: 12px; font-weight: 600; line-height: 18px; cursor: pointer;
                  white-space: nowrap; border: 1px solid transparent; }
.ps-review-pill.draft    { background: #f1f3f5; color: #6b7280; }
.ps-review-pill.todo     { background: #eef2ff; color: #4338ca; border-color: #c7d2fe; }
.ps-review-pill.po       { background: #fff4e0; color: #9a5b00; border-color: #f5c77e; }
.ps-review-pill.back     { background: #e8f0fe; color: #1d4ed8; border-color: #93b4f8; }
.ps-review-pill.declined { background: #fdecec; color: #b42318; border-color: #f4b4ae; }
.ps-review-pill.done     { background: #e7f6ee; color: #146c43; border-color: #a6dcbf; }
.ps-review-pill:hover    { filter: brightness(.97); }
.ps-rp-title { font-weight: 700; font-size: 17px; }
.ps-rp-head { display: flex; flex-direction: column; gap: 6px; }
.ps-rp-figs { display: flex; align-items: flex-end; gap: 14px; margin-top: 4px; }
.ps-rp-fig { font-size: 18px; font-weight: 700; }
.ps-rp-pct { font-size: 13px; font-weight: 600; color: var(--ps-positive, #146c43); }
.ps-rp-arrow { font-size: 18px; color: var(--ps-text-muted); padding-bottom: 2px; }
.ps-rp-status { display: flex; flex-direction: column; gap: 4px; margin-top: 6px;
                padding: 8px 10px; border-radius: 8px; background: var(--ps-surface-2, #f8f9fb); }
.ps-rp-status .ps-review-pill { align-self: flex-start; cursor: default; }
.ps-rp-thread { max-height: 38vh; overflow-y: auto; }
.ps-prog-tiles { display: flex; gap: 12px; flex-wrap: wrap; }
.ps-prog-tile { min-width: 150px; padding: 10px 14px; border-radius: 10px; background: #fff;
                border: 1px solid var(--ps-border, #e5e7eb); }
.ps-prog-tile.po { border-left: 4px solid #f59f00; }
.ps-prog-tile.mgr { border-left: 4px solid #4c6ef5; }
.ps-prog-tile.done { border-left: 4px solid #2f9e44; }
.ps-prog-tile.draft { border-left: 4px solid #adb5bd; }
.ps-prog-n { font-size: 22px; font-weight: 700; }
.ps-prog-l { font-size: 12px; color: var(--ps-text-muted); }
.ps-progbar-wrap { display: flex; align-items: center; gap: 8px; height: 100%; }
.ps-progbar { flex: 1; display: flex; height: 10px; border-radius: 6px; overflow: hidden;
              background: #f1f3f5; }
.ps-progbar-seg.done { background: #40c057; }
.ps-progbar-seg.mgr { background: #748ffc; }
.ps-progbar-seg.po { background: #fcc419; }
.ps-progbar-seg.draft { background: #ced4da; }
.ps-progbar-txt { font-size: 12px; color: var(--ps-text-muted); min-width: 44px; }
.ps-remind-btn { cursor: pointer; padding: 2px 10px; border-radius: 6px; font-size: 12px;
                 font-weight: 600; color: #4338ca; border: 1px solid #c7d2fe; background: #eef2ff; }
.ps-remind-btn:hover { background: #e0e7ff; }
.ps-team-band-row { display: flex; align-items: center; gap: 16px; padding: 10px 0;
                    border-bottom: 1px solid var(--ps-border, #eef0f3); }
.ps-team-band-row > span, .ps-team-band-row > a { flex: 0 0 auto; white-space: nowrap; }
/* Review panel, round two: the proposal in context (components/review_panel.py) */
.ps-rp-ctx { margin-top: 10px; }
.ps-rp-table { width: 100%; border-collapse: collapse; font-size: 13px; }
.ps-rp-table th { text-align: right; font-weight: 600; font-size: 11px; letter-spacing: .03em;
                  text-transform: uppercase; color: var(--ps-text-muted); padding: 4px 6px; }
.ps-rp-table th:first-child, .ps-rp-table td:first-child { text-align: left; }
.ps-rp-table td { text-align: right; padding: 5px 6px; border-top: 1px solid var(--ps-border, #eef0f3);
                  font-variant-numeric: tabular-nums; }
.ps-rp-table tr.ps-rp-ote td { font-weight: 700; }
.ps-rp-band { margin-top: 12px; padding: 10px; border-radius: 8px;
              background: var(--ps-surface-2, #f8f9fb); }
.ps-rp-band-bar { position: relative; height: 10px; margin: 10px 4px 6px;
                  border-radius: 6px; background: linear-gradient(90deg, #dbe4ff, #bac8ff); }
.ps-rp-band-mid { position: absolute; top: -3px; width: 2px; height: 16px; background: #212529; }
.ps-rp-band-dot { position: absolute; top: -4px; width: 14px; height: 14px; margin-left: -7px;
                  border-radius: 50%; border: 2px solid #4c6ef5; background: #fff; }
.ps-rp-band-dot.new { background: #4c6ef5; }
.ps-rp-change { margin-top: 12px; padding: 12px; border-radius: 10px;
                border: 1px solid #c7d2fe; background: #f5f7ff; }
.ps-rp-h { font-weight: 700; margin-bottom: 4px; }
.ps-rp-defn { margin-top: 10px; }
.ps-rp-ote-cards { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
