/* ==========================================================================
   Reset and base
   ========================================================================== */
*, *::before, *::after { box-sizing: border-box; }
html, body { height: 100%; }
body {
  margin: 0;
  font-family: var(--font-sans);
  font-size: var(--text-body);
  line-height: 1.45;
  color: var(--color-text);
  background: var(--color-bg);
  -webkit-font-smoothing: antialiased;
  font-variant-numeric: tabular-nums;
}
button, input, select, textarea { font: inherit; color: inherit; }
button { cursor: pointer; }
a { color: var(--color-accent-ink); }
:focus-visible {
  outline: 2px solid var(--focus-ring);
  outline-offset: 2px;
  border-radius: var(--radius-xs);
}
/* The rail is the one dark canvas inside the light theme, so it carries its
   own ring. outline-offset draws the ring OUTSIDE the element, on the rail
   fill, which is why the ring is measured against the rail and not against a
   selected pill it happens to be sitting on. */
.rail :focus-visible { outline-color: var(--v2-rail-focus-ring); }
.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}
.eyebrow {
  text-transform: uppercase;
  letter-spacing: var(--eyebrow-tracking);
  font-size: 11px;
  font-weight: 700;
}

/* ==========================================================================
   App shell - FULL WIDTH. No max-width, no auto margins.
   The live app caps at 1360px (public/styles/app.css .app). That cap is
   deliberately absent here; this is the change Juan asked to see.
   ========================================================================== */
/* minmax(0, 1fr) on every fr track, never a bare 1fr. A bare 1fr floors at the
   track min-content width, so one wide table pushes the whole PAGE sideways,
   and sticky is vertical-only, so the rail and the top bar slide off screen
   with it. The grid overflow-x container has to absorb that width instead, at
   every breakpoint. */
.shell {
  display: grid;
  grid-template-columns: 232px minmax(0, 1fr);
  min-height: 100vh;
  width: 100%;
}
.shell.is-collapsed { grid-template-columns: 68px minmax(0, 1fr); }

/* ---------------- Left rail ---------------- */
.rail {
  background: var(--v2-rail-bg);
  color: var(--v2-rail-ink);
  display: flex;
  flex-direction: column;
  border-right: 1px solid var(--v2-rail-rule);
  position: sticky;
  top: 0;
  height: 100vh;
  overflow: hidden;
}
.rail__brand {
  display: flex; align-items: center; gap: var(--space-sm);
  padding: var(--space-lg) var(--space-lg) var(--space-md);
  min-height: 62px;
}
.rail__mark {
  width: 28px; height: 28px; flex: 0 0 28px; border-radius: var(--radius-sm);
  background: var(--surface-brand-hero);
  display: grid; place-items: center;
  color: var(--on-surface-brand-hero); font-weight: 800; font-size: 12px; letter-spacing: .02em;
}
.rail__wordmark { font-weight: 700; font-size: 15px; color: var(--v2-rail-ink-strong); letter-spacing: -.01em; }
.rail__wordmark small { display: block; font-weight: 500; font-size: 11px; color: var(--v2-rail-ink-mute); letter-spacing: .02em; }
.rail__nav { flex: 1 1 auto; overflow-y: auto; padding: var(--space-sm) var(--space-md) var(--space-lg); }
.rail__section { margin: var(--space-lg) 0 var(--space-xs); padding: 0 var(--space-sm); color: var(--v2-rail-ink-mute); }
.rail__section:first-child { margin-top: var(--space-xs); }
.navitem {
  display: flex; align-items: center; gap: 10px; width: 100%;
  padding: 8px 10px; margin-bottom: 2px;
  background: none; border: 0; border-radius: var(--radius-sm);
  color: var(--v2-rail-ink); font-size: var(--text-body); font-weight: 500;
  text-align: left; line-height: 1.2;
}
.navitem:hover { background: var(--v2-rail-hover); color: var(--v2-rail-ink-strong); }
.navitem.is-active { background: var(--v2-rail-active); color: var(--v2-rail-active-ink); font-weight: 600; }
.navitem svg { flex: 0 0 17px; width: 17px; height: 17px; opacity: .9; }
.navitem__count {
  margin-left: auto; font-size: 11px; font-weight: 700;
  background: var(--v2-rail-badge);
  padding: 1px 7px; border-radius: var(--radius-pill);
}
.navitem.is-active .navitem__count { background: var(--v2-rail-badge-active); }
.rail__user {
  border-top: 1px solid var(--v2-rail-rule);
  background: var(--v2-rail-bg-2);
  padding: var(--space-md);
  display: flex; align-items: center; gap: 10px;
}
.avatar {
  width: 34px; height: 34px; flex: 0 0 34px; border-radius: var(--radius-pill);
  display: grid; place-items: center; font-weight: 700; font-size: 12px;
  background: var(--v2-avatar-bg); color: var(--v2-avatar-ink);
}
.rail__user-meta { min-width: 0; flex: 1 1 auto; }
.rail__user-name { font-size: 13px; font-weight: 600; color: var(--v2-rail-ink-strong); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.rail__user-role { font-size: 11px; color: var(--v2-rail-ink-mute); text-transform: capitalize; }
.iconbtn {
  background: none; border: 1px solid transparent; border-radius: var(--radius-sm);
  color: inherit; padding: 6px; display: grid; place-items: center; line-height: 0;
}
.iconbtn:hover { background: var(--v2-rail-hover); }
.shell.is-collapsed .rail__wordmark,
.shell.is-collapsed .rail__section,
.shell.is-collapsed .navitem span,
.shell.is-collapsed .navitem__count,
.shell.is-collapsed .rail__user-meta,
.shell.is-collapsed .rail__user .iconbtn { display: none; }
.shell.is-collapsed .rail__brand,
.shell.is-collapsed .rail__user { justify-content: center; padding-left: 0; padding-right: 0; }
.shell.is-collapsed .navitem { justify-content: center; padding: 9px 0; }

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

/* ---------------- Top bar ---------------- */
.topbar {
  position: sticky; top: 0; z-index: 60;
  display: flex; align-items: center; gap: var(--space-md);
  padding: 10px var(--space-xl);
  background: var(--v2-topbar);
  border-bottom: 1px solid var(--color-border);
  min-height: var(--topbar-h);
}
.topbar .iconbtn { border-color: transparent; color: var(--color-text-muted); }
.topbar .iconbtn:hover { background: var(--color-surface-2); color: var(--color-text); }
.crumbs { display: flex; align-items: center; gap: 6px; font-size: 13px; color: var(--color-text-muted); white-space: nowrap; }
.crumbs b { color: var(--color-text); font-weight: 600; }
.searchwrap { position: relative; flex: 1 1 auto; max-width: 560px; margin: 0 auto; }
.searchwrap svg { position: absolute; left: 10px; top: 50%; transform: translateY(-50%); color: var(--color-text-muted); pointer-events: none; }
.input {
  width: 100%; padding: 7px 10px; border-radius: var(--radius-sm);
  /* GL-003 s2.3 assigns --color-border to hairlines and gridlines and
     --color-border-strong to input borders. This was reaching for the hairline
     role, which bites hardest in the drawer where the field fill equals the
     panel fill and the border is the only thing marking the control.
     THIS IS THE ROLE CORRECTION AND NOT A CONTRAST FIX: the stronger rung
     measures 1.61:1 light and 2.11:1 dark, both still under SC 1.4.11's 3:1.
     Closing that gap is a brand-layer decision and it is on the Iris note. */
  border: 1px solid var(--color-border-strong); background: var(--color-surface);
  color: var(--color-text); font-size: 13px;
}
.input::placeholder { color: var(--color-text-muted); }
.searchwrap .input { padding-left: 32px; background: var(--color-surface-2); }
.topbar__right { display: flex; align-items: center; gap: var(--space-sm); margin-left: auto; }
.pill {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 4px 10px; border-radius: var(--radius-pill);
  font-size: 12px; font-weight: 600;
  background: var(--surface-status-success); color: var(--on-surface-status-success);
}
.pill .dot { width: 7px; height: 7px; border-radius: var(--radius-pill); background: currentColor; }
/* THE ROLE SWITCHER IS GONE, and its three rules went with it rather than
   sitting here unreferenced. In the mock it was a preview control that changed
   what the mock let you do without authenticating anyone. In the real app the
   role comes from GET /api/data/me and is enforced server-side on every route,
   so a client-side switch would be a control that lies. On localhost the same
   job is done by ?role=admin|sdr|viewer, which only the dev-host session
   fallback reads and which is inert in production. */

/* The account button wraps an avatar rather than an icon, so it loses the
   icon button's padding and takes the avatar's own box. Was a style= literal. */
.iconbtn--avatar { padding: 2px; }
.avatar--top { width: 30px; height: 30px; flex-basis: 30px; }

/* SKIP LINK. The mock had none: it opened by double-click and had no landmark
   to skip to. This tree serves a real page, so SC 2.4.1 applies. Ported from
   v1's app.css, token for token, including the explicit dark ink - the bare
   `a` rule in the dark theme out-specifies a class selector and would
   otherwise paint the focused link in the accent ink.
   It is a REAL button once focused, never a 1px clip: a sighted keyboard user
   has to see where focus went (SC 2.4.7). */
.skip-link {
  position: absolute; left: var(--space-sm); top: -48px;
  background: var(--color-surface); color: var(--color-text-strong);
  padding: var(--space-sm) var(--space-md); border-radius: var(--radius-sm);
  box-shadow: var(--shadow-md); z-index: 60; transition: top .12s ease;
}
.skip-link:focus { top: var(--space-sm); }
:root[data-theme="dark"] .skip-link { color: var(--color-text-strong); }
@media (prefers-reduced-motion: reduce) {
  .skip-link { transition: none; }
}

/* ---------------- Content ---------------- */
.content { padding: var(--space-xl) var(--space-xl) 96px; flex: 1 1 auto; min-width: 0; }
.pagehead { margin-bottom: var(--space-lg); }
.pagehead h1 { font-size: 26px; line-height: 1.15; margin: 0 0 4px; letter-spacing: -.02em; color: var(--color-text-strong); }
.pagehead p {
  margin: 0; font-size: 13px; color: var(--color-text-muted);
  /* One line, always. A second line here moves every row below it. */
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}

/* ---------------- KPI tiles ---------------- */
.tiles { display: grid; grid-template-columns: repeat(6, minmax(0, 1fr)); gap: var(--space-md); margin-bottom: var(--space-md); }
@media (max-width: 1280px) { .tiles { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
.tile {
  position: relative; text-align: left; overflow: hidden;
  background: var(--color-surface); border: 1px solid var(--color-border);
  border-radius: var(--radius-md); padding: var(--space-md) var(--space-lg) var(--space-lg);
  box-shadow: var(--shadow-sm);
}
.tile:hover { border-color: var(--color-border-strong); }
.tile.is-selected { border-color: var(--border-selected); box-shadow: 0 0 0 1px var(--border-selected), var(--shadow-sm); }
.tile__label { font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: var(--eyebrow-tracking); color: var(--color-text-muted); }
.tile__val { font-size: var(--text-metric); font-weight: 700; line-height: 1.05; margin-top: 6px; color: var(--color-text-strong); letter-spacing: -.02em; }
.tile__sub { font-size: 12px; color: var(--color-text-muted); margin-top: 2px; }
.tile__bar { position: absolute; left: 0; right: 0; bottom: 0; height: 2px; background: var(--bar, var(--viz-cat-1)); }

/* ---------------- Pipeline stage band ---------------- */
.stageband {
  display: flex; align-items: stretch;
  background: var(--color-surface); border: 1px solid var(--color-border);
  border-radius: var(--radius-md); box-shadow: var(--shadow-sm);
  padding: var(--space-md) var(--space-sm); margin-bottom: var(--space-lg);
}
.stage { flex: 1 1 0; text-align: center; padding: 2px var(--space-xs); min-width: 0; }
.stage__label { font-size: 11px; font-weight: 600; color: var(--color-text-muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.stage__val { font-size: var(--text-metric-sm); font-weight: 700; color: var(--color-text-strong); line-height: 1.2; }

/* ---------------- Quick filters ---------------- */
.quickbar {
  display: flex; gap: var(--space-sm); flex-wrap: nowrap;
  overflow-x: auto; margin-bottom: var(--space-md);
  scrollbar-width: none;
}
.quickbar::-webkit-scrollbar { display: none; }

/* Fade the edge that has content beyond it, and only that edge. Zero vertical
   pixels, so pinning the strip height still holds. Toggled from JS because CSS
   cannot ask whether a box overflows. */
/* Overlaid on the strip, so they add no vertical pixels and cannot reopen the
   row-displacement defect that the suppressed scrollbar exists to prevent. */
.stripnav {
  position: absolute; top: 50%; transform: translateY(-50%); z-index: 4;
  display: none; place-items: center;
  width: 24px; height: 24px; padding: 0;
  border: 1px solid var(--color-border-strong); border-radius: var(--radius-pill);
  background: var(--color-surface); color: var(--color-text);
  box-shadow: var(--shadow-sm);
}
.stripnav--left { left: -2px; }
.stripnav--right { right: -2px; }
.stripnav:hover { border-color: var(--border-selected); color: var(--color-accent-ink); }
.strip-fade[data-of="right"] { mask-image: linear-gradient(to right, #000 calc(100% - 24px), transparent); }
.strip-fade[data-of="left"]  { mask-image: linear-gradient(to left,  #000 calc(100% - 24px), transparent); }
.strip-fade[data-of="both"]  { mask-image: linear-gradient(to right, transparent, #000 24px, #000 calc(100% - 24px), transparent); }
.quick { flex: 0 0 auto; }
.quick {
  display: inline-flex; align-items: center; gap: var(--space-sm);
  min-height: 34px; padding: 0 var(--space-md);
  border: 1px solid var(--color-border-strong); border-radius: var(--radius-pill);
  background: var(--color-surface); color: var(--color-text);
  font-size: 13px; font-weight: 600;
}
.quick:hover { border-color: var(--border-selected); }
.quick.is-on {
  background: var(--surface-brand); border-color: var(--surface-brand);
  color: var(--on-surface-brand);
}
.quick.is-on .countpill { color: var(--on-surface-brand); }

/* ---------------- Pipeline stage pills ---------------- */
/* The band used to be a readout. It filters now, one pill per stage. */
.pillstrip {
  display: flex; gap: var(--space-xs); flex-wrap: nowrap;
  overflow-x: auto;
  background: var(--color-surface); border: 1px solid var(--color-border);
  border-radius: var(--radius-md); box-shadow: var(--shadow-sm);
  padding: var(--space-sm); margin-bottom: var(--space-lg);
  /* Height is pinned by nowrap AND by suppressing the scrollbar, so opening
     the drawer moves nothing below it at any width. */
  scrollbar-width: none;
}
.pillstrip::-webkit-scrollbar { display: none; }
.pill-stage { flex: 0 0 auto; }
.pill-stage {
  display: inline-flex; align-items: baseline; gap: 6px;
  min-height: 30px; padding: 4px 10px;
  border: 1px solid transparent; border-radius: var(--radius-sm);
  background: none; color: var(--color-text-muted);
  font-size: 12px; font-weight: 600; white-space: nowrap;
}
.pill-stage:hover { background: var(--color-surface-2); color: var(--color-text); }
.pill-stage.is-on {
  background: color-mix(in srgb, var(--color-accent) 12%, transparent);
  border-color: var(--border-selected); color: var(--color-accent-ink);
}
.pill-stage__n { font-size: 14px; font-weight: 700; color: var(--color-text-strong); }
.pill-stage.is-on .pill-stage__n { color: var(--color-accent-ink); }
/* A STAGE THIS DATA CANNOT REACH READS "n/a", NOT "0", and that is the whole
   reason this rule exists. The API stores no stage; four of the eight ruled
   labels are derived from fields it does store and four are not reachable from
   any field on the row. A zero is a measurement and would tell a reader that
   nobody is in Under Review. Disabled + dashed + "n/a" says the opposite: the
   question has not been asked yet. cc2-map.js deriveStage() holds the rule.

   NOT dimmed below the text floor. 1.4.3 exempts a disabled control, and this
   one still has to be READ - it is carrying information, not just refusing a
   click - so it keeps --color-text-muted rather than dropping to 40% alpha. */
.pill-stage[disabled] {
  border-style: dashed; border-color: var(--color-border);
  color: var(--color-text-muted); cursor: default;
}
.pill-stage[disabled]:hover { background: none; color: var(--color-text-muted); }
.pill-stage__na {
  font-size: 11px; font-weight: 700; letter-spacing: var(--eyebrow-tracking);
  text-transform: uppercase; color: var(--color-text-muted);
}
/* The new-row's placeholder text. It was a style= colour literal in the mock,
   which the token layer could not reach. */
.newrow__hint { color: var(--color-text-muted); }

/* ---------------- Card ---------------- */
.card {
  background: var(--color-surface); border: 1px solid var(--color-border);
  border-radius: var(--radius-md); box-shadow: var(--shadow-sm);
}
.card__head {
  display: flex; align-items: center; gap: var(--space-md);
  padding: var(--space-md) var(--space-lg); border-bottom: 1px solid var(--color-border);
  flex-wrap: wrap;
}
.card__title { font-size: var(--text-h2); font-weight: 700; margin: 0; color: var(--color-text-strong); }
.card__sub { font-size: 12px; color: var(--color-text-muted); margin: 2px 0 0; }

/* ---------------- Buttons ---------------- */
.btn {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 6px 11px; border-radius: var(--radius-sm);
  border: 1px solid var(--color-border); background: var(--color-surface);
  color: var(--color-text); font-size: 13px; font-weight: 600;
}
.btn:hover { background: var(--color-surface-2); border-color: var(--color-border-strong); }
.btn.is-on { border-color: var(--border-selected); color: var(--color-accent-ink); background: color-mix(in srgb, var(--color-accent) 8%, transparent); }
.btn--primary { background: var(--surface-cta); border-color: var(--surface-cta); color: var(--on-surface-cta); }
.btn--primary:hover { background: var(--surface-cta-hover); border-color: var(--surface-cta-hover); }
.btn--ghost { border-color: transparent; background: none; color: var(--color-text-muted); }
.btn--ghost:hover { background: var(--color-surface-2); color: var(--color-text); }
.btn[disabled] { opacity: .45; cursor: not-allowed; }
.btngroup { display: inline-flex; border: 1px solid var(--color-border); border-radius: var(--radius-sm); overflow: hidden; }
.btngroup .btn { border: 0; border-radius: 0; }
.btngroup .btn + .btn { border-left: 1px solid var(--color-border); }

/* ---------------- LOI Deals workspace ---------------- */
/* One column. The saved-view list moved into the toolbar, so the grid gets the
   full content width at every breakpoint. */
.workspace { display: grid; grid-template-columns: minmax(0, 1fr); align-items: start; }

.views { position: sticky; top: 74px; }
.views__head { margin: 0; padding: var(--space-md) var(--space-lg) var(--space-sm); color: var(--color-text-muted); }
.views__list { padding: 0 var(--space-sm) var(--space-md); }
.views__group {
  padding: var(--space-md) var(--space-sm) var(--space-xs);
  color: var(--color-text-muted);
}
.views__group:first-child { padding-top: var(--space-xs); }
.viewitem {
  display: flex; align-items: center; gap: var(--space-sm); width: 100%;
  padding: 7px 10px; margin-bottom: 1px; border: 0; border-radius: var(--radius-sm);
  background: none; color: var(--color-text); font-size: 13px; text-align: left;
}
.viewitem:hover { background: var(--color-surface-2); }
.viewitem.is-active { background: color-mix(in srgb, var(--color-accent) 10%, transparent); color: var(--color-accent-ink); font-weight: 700; }
.viewitem__name { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* One look, two owners. The saved-view count and the Columns badge share the
   styling but not the class name: .viewitem__count means "a saved-view count"
   and nothing else, so a selector for it cannot pick up a toolbar badge. */
.viewitem__count, .countpill { font-size: 12px; font-weight: 700; color: var(--color-text-muted); }
.viewitem.is-active .viewitem__count { color: var(--color-accent-ink); }

/* nowrap, so the toolbar cannot grow a second line and push the grid down when
   the drawer narrows the page. Its scrollbar is suppressed for the same
   reason the pill strip suppresses its own. */
.toolbar {
  display: flex; align-items: center; gap: var(--space-sm); flex-wrap: nowrap;
  overflow-x: auto; scrollbar-width: none;
}
.toolbar::-webkit-scrollbar { display: none; }
.pillstrip::-webkit-scrollbar { display: none; }
.toolbar > * { flex: 0 0 auto; }
.toolbar .searchwrap { flex: 1 1 260px; max-width: 380px; margin: 0; }

/* ---------------- The grid ---------------- */
/* scroll-padding keeps focus() from parking a cell UNDER a sticky element.
   The header sticks to the top and the add-row sticks to the bottom, and the
   add-row was covering up to 99.3% of a focused row in the last row of the
   viewport. These two values are the sticky heights, rounded up. */
/* No max-height. Juan asked for 25 rows, or 50 if they fit, and the PAGE
   scrolls to show them: he has never objected to vertical scrolling, only to
   horizontal. overflow-x stays, because the grid must still absorb its own
   width when a rep turns extra columns on. */
.gridscroll {
  /* ONE STATE NOW, and dynamic widths are what made that possible.
     LOW-4 IS CLOSED, AND NOT FOR THE REASON IT WAS EXPECTED TO CLOSE. It
     recorded six headers clipping by 2 to 10px at 1440 with a record open,
     accepted rather than fixed. The guess was that the expanded row would
     dissolve it by taking the drawer out of wide. Re-measured 2026-09-02:
     zero of 15 headers clip at 1440 with a record open, and 1440 is DETAIL,
     where the drawer still reserves its band. So the drawer is not what fixed
     it. The column set falling from 19 to 17 is, because the two link columns
     stopped competing for the narrowed container. Zero of 17 clip at 2560.
     The allocator sizes the colgroup to the CONTAINER on every render, so the
     table can no longer be wider than the box it sits in, in any state. That
     removes the reason the drawer-open case ever needed a scrollport: with the
     band reserved, the container is narrower and the columns are simply
     narrower with it.

     The consequences are all good ones. Nothing paints under the drawer, so
     the round-five blocker stays closed. Nothing is clipped out of reach, so
     the round-six correction to that fix stays honoured. And because this is
     not a scrollport, the header sticks to the VIEWPORT in every state,
     including with a record open, which is what MED-8 asked for.

     overflow-x: clip is a guard against sub-pixel spill only. If it ever
     clips anything a reader needed, the allocator is broken, not this line. */
  overflow-x: clip;
  overflow-y: visible;
  border-radius: 0 0 var(--radius-md) var(--radius-md);
  scroll-padding-top: 96px;
  scroll-padding-bottom: 40px;
  scroll-padding-right: 24px;
}
/* No narrow-width override either. The allocator fits the columns to whatever
   container it is given, so there is nothing to scroll at any width and the
   header keeps sticking all the way down. */
/* THE DRAWER IS FIXED AND OVERLAYS THE GRID, so a cell can be fully inside the
   scroller and still be invisible behind it. Measured: a focused Contact cell
   at 1600 sat 100% behind the drawer, reachable by ArrowRight and by plain Tab
   (SC 2.4.11). Reserving the drawer's width means no cell can occupy that band
   at all. Clamping the roving anchor's column instead would hide those columns
   from the keyboard entirely, which is the worse trade.

   THE RESERVATION IS ON .main, AND IT ONLY WORKS BECAUSE SOMETHING CLIPS.
   padding-right on an overflow container reserves space only at the END of its
   scrollable area, so an overflowing table still paints from the left edge
   across the full width. Narrowing the containing block is the other half, and
   it is inert on its own: for two rounds .gridscroll carried overflow:visible
   above 1400, nothing clipped the 930px table to its 482px box, and 188 of 470
   cells sat fully behind the drawer while this comment claimed otherwise.
   WHAT THE PROMISE RESTS ON TODAY IS THE ALLOCATOR, not a scrollport. An
   earlier revision of this paragraph said the scroller becomes a real
   scrollport while the drawer is open. It does not, and has not since dynamic
   widths landed: allocateWidths() sizes the colgroup to the CONTAINER on every
   render, so the table cannot be wider than the box it sits in, in any state.
   Measured drawer open and closed: overflow-x is clip, scrollWidth equals
   clientWidth, and a scrollLeft of 400 reads back 0. overflow-x: clip is the
   sub-pixel guard behind that arithmetic and nothing more.

   This paragraph is left long on purpose. It described an architecture the
   file had already dropped, and a reader who trusted it could have restored a
   scrollport this design deliberately does without. */
/* THE RESERVATION IS ON .main, NOT ON .content. .topbar is a SIBLING of
   .content inside .main, so reserving on .content never reached it and five
   top-bar controls sat entirely behind the drawer: the freshness pill, the
   role switch, the bell, the theme toggle and the avatar. Three Tabs from the
   search box put focus on a button nobody could see (SC 2.4.11). Reserving on
   their shared parent narrows the bar and the page together, in one rule. */
.shell.drawer-open .main { padding-right: 460px; }
/* Below 1400 the reserved top bar cannot hold everything. The freshness pill
   and the breadcrumb are readouts, not controls, so they stand down and every
   interactive control keeps a place on screen. */
@media (max-width: 1400px) {
  /* The breadcrumb stands down: it duplicates the H1 40px below it and the
     active rail item. The freshness pill does NOT, because "Synced, just now"
     occurs once in the document and display:none would take it out of the
     accessibility tree at the exact moment a rep is about to edit. It keeps
     its dot, its DOM node and its state in aria-label. 7px of bar. */
  .shell.drawer-open .crumbs { display: none; }
  .shell.drawer-open #freshness { padding: 4px 6px; gap: 0; }
  .shell.drawer-open #freshness .pilltext { display: none; }
}
.topbar > .searchwrap { min-width: 0; }
@media (max-width: 600px) { .shell.drawer-open .main { padding-right: 0; } }
/* NO TRANSITION ON THE RESERVED BAND, and this is a correctness rule rather
   than a taste one. renderDrawer() adds .drawer-open and calls
   renderDealsBody() in the same task, so a transitioned padding meant the
   allocator measured the PRE-transition container and sized the colgroup 458px
   wider than the box it had to fit. With overflow-x: clip and no scrollport
   that 458px had no route to it: 188 cells outside the port, the roving anchor
   still stepping onto them.

   The control that proves it: under prefers-reduced-motion, where this
   transition was already none, the identical gesture measured 0px overflow.
   The band now lands in the same task the allocation reads, at every setting.
   The DRAWER still slides; only the reservation is instant, which is what
   reduced-motion users were already getting correctly. */
.main { transition: none; }
/* table-layout: fixed makes the colgroup widths BINDING. Under auto they were
   advisory, text-overflow never fired, and columns resized as rows filtered. A
   grid whose columns move under the reader is not a grid. */
table.grid { border-collapse: separate; border-spacing: 0; width: 100%; font-size: 13px; table-layout: fixed; }
table.grid th, table.grid td {
  border-right: 1px solid var(--color-border);
  border-bottom: 1px solid var(--color-border);
  padding: 6px 9px; text-align: left; white-space: nowrap;
  overflow: hidden; text-overflow: ellipsis; vertical-align: middle;
  background: var(--color-surface);
}
/* 3px, not 2px. td sets overflow:hidden, which CLIPS an absolutely positioned
   child, so a declared 24.000 box measured 23.609 once the padding box cut it.
   This is the same defect fixed in the header and not carried to the cell.
   Vertical only, so the declared colgroup sum and the no-scroll floor do not
   move a pixel. */
table.grid.is-compact th, table.grid.is-compact td { padding: 3px 8px; line-height: 1.55; }
/* Density acts on the DATA cells. The header keeps its 11px eyebrow size in
   both densities: the old combined rule out-specified `table.grid thead th`
   and pushed the header to 12px while taking padding away from it, which is
   what truncated `Amount ($)` in compact and not in comfortable. Stated as
   its own rule so the next reader sees the intent rather than the accident. */
table.grid.is-compact tbody td { font-size: 12px; }
table.grid.is-compact thead th { font-size: 11px; }
table.grid thead th {
  /* Sticks to the VIEWPORT, under the top bar, because the grid no longer owns
     a vertical scroller of its own. Driven by the same token the bar is. */
  position: sticky; top: var(--topbar-h); z-index: 3;
  background: var(--color-surface-2); color: var(--color-text-muted);
  font-size: 11px; font-weight: 700; text-transform: uppercase;
  letter-spacing: var(--eyebrow-tracking);
  border-bottom: 1px solid var(--color-border-strong);
}
table.grid th.col-sticky-1, table.grid td.col-sticky-1 { position: sticky; left: 0; z-index: 2; }
table.grid th.col-sticky-2, table.grid td.col-sticky-2 { position: sticky; left: var(--sticky2, 40px); z-index: 2; box-shadow: 1px 0 0 var(--color-border-strong); }
table.grid thead th.col-sticky-1, table.grid thead th.col-sticky-2 { z-index: 4; }
table.grid tbody tr:nth-child(even) td { background: var(--v2-zebra); }
table.grid tbody tr:nth-child(even) td.col-sticky-1,
table.grid tbody tr:nth-child(even) td.col-sticky-2 {
  background: color-mix(in srgb, var(--color-surface) 100%, transparent);
  background-image: linear-gradient(var(--v2-zebra), var(--v2-zebra));
}
table.grid tbody tr:hover td { background: var(--v2-cell-hover); }
table.grid tbody tr:hover td.col-sticky-1,
table.grid tbody tr:hover td.col-sticky-2 { background-image: linear-gradient(var(--v2-cell-hover), var(--v2-cell-hover)); }
td.cell-edit { cursor: cell; }
td.cell-edit:hover { box-shadow: inset 0 0 0 1px var(--color-border-strong); }
td.is-editing { padding: 0 !important; box-shadow: inset 0 0 0 2px var(--border-selected); }
td.is-dirty::after {
  content: ""; position: absolute; top: 0; right: 0;
  border-width: 0 6px 6px 0; border-style: solid;
  border-color: transparent var(--v2-dirty) transparent transparent;
}
td { position: relative; }
/* Has to out-specify `table.grid th, table.grid td { text-align: left }`, which
   scores (0,1,2). The old `td.is-num` scored (0,1,1) and lost, so money
   rendered left-aligned in both themes. A right edge is what lets an eye
   compare magnitudes down a currency column. */
table.grid td.is-num, table.grid th.is-num { text-align: right; }
td.is-locked { background-image: linear-gradient(var(--v2-readonly-bg), var(--v2-readonly-bg)); cursor: default; }
.celledit {
  width: 100%; height: 100%; border: 0; outline: 0; background: var(--color-surface);
  padding: 5px 8px; font-size: 13px; color: var(--color-text);
}
.th-inner {
  display: flex; align-items: center; gap: 4px;
  /* EACH LINE OWNS ITS OWN FUNNEL, and a header cell can hold two lines.
     Without this the divs are static, so BOTH .funnel children resolve against
     the th, which is the whole two-line cell: line one's centres on the
     BOUNDARY between the lines and line two's is dragged across it, and the
     later button then covers the earlier one outright. MEASURED at every width
     from 1280 to 2200: the Date Sent funnel was hittable on 0 of 49 sampled
     pixels and its own pixels opened Date Closed. */
  position: relative;
  /* Room for the overlaid funnel. 20px is exactly what the old inline button
     and its flex gap consumed, so no label loses a pixel to the target fix. */
  padding-right: 20px;
  /* The th sets overflow:hidden, which CLIPS the hit area of an overlaid
     child. Without this the 24px box would be cut to the header text height
     (about 17px at compact) and SC 2.5.8 would still fail. */
  min-height: 24px;
}
.th-inner--nofilter { padding-right: 0; }
.th-inner span { overflow: hidden; text-overflow: ellipsis; }
/* Line two of the header, mirroring line two of every data row. */
.th-inner--sec {
  margin-top: 2px; padding-top: 2px;
  border-top: 1px dashed var(--color-border);
  font-size: 10px; font-weight: 600; text-transform: none;
  letter-spacing: 0; color: var(--color-text-muted); opacity: .9;
}
/* Two funnels on line one where a column carries an attached link. The offset
   that separates them lives with .funnel, for the source-order reason spelled
   out there. */
.th-inner--haslink { padding-right: 46px; }

/* Line two of a data cell. Smaller and muted, always behind its own label. */
.cell1 { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* THE ATTACHED LINK. Anchored inside line one rather than the cell, so it
   centres on the TEXT in every shape: the cell is 58px tall in detail, 32 in
   lean and anywhere from 34 to 107 in wide, and centring on the cell would put
   the icon between the two lines. Absolutely positioned, so it takes no width
   from the name, and line one reserves its slot instead of running under it. */
td.has-link .cell1 { padding-right: 28px; }
td.has-link .cell2 { padding-right: 28px; }
/* Anchored to the CELL, not to line one. Line one clips to keep its ellipsis,
   and an icon inside it measured 18.8px against a 24px floor. The cell clips
   too, but its box is the whole row and never falls under 24.6px in any shape
   or density. Centred where the row is one block, pinned to the first line
   where the row carries two. */
.celllink, .celladd {
  position: absolute; right: 3px; top: 50%; transform: translateY(-50%);
  width: 24px; height: 24px; padding: 0; line-height: 0;
  display: inline-flex; align-items: center; justify-content: center;
  border-radius: var(--radius-xs); text-decoration: none;
  border: 1px solid transparent; background: none;
  color: var(--color-accent-ink);
}
.celllink:hover, .celladd:hover {
  background: var(--color-surface-2); border-color: var(--border-selected);
}
.celladd {
  color: var(--color-text-muted); border: 1px dashed var(--color-border-strong);
  font-size: 13px; font-weight: 700; line-height: 1;
}
.celladd:hover { color: var(--color-accent-ink); }
td.has-sec .celllink, td.has-sec .celladd { top: 4px; transform: none; }
/* Line two can carry a pair now, so its parts are grouped.

   PORT NOTE, and it is a source-order bug rather than a design decision. In the
   approved mock this rule sat ABOVE the `.cell2` block below, which also
   declares `gap`, so the later 5px won at equal specificity and the 10px this
   line asks for never painted. On the mock's fixture nothing showed it; against
   324 real rows the two labels on Property read as one word ("DetailsSource").
   The declaration is folded into the block below rather than left here to be
   shadowed. Nothing about the intended value changed. */
/* SC 2.5.8 LIVES ON THE GROUP, not on the line. When line two held one field
   the line itself was the click target and the 24px floor below was doing the
   work. Pairing moved the target down to the group and the floor did not
   follow it, so every editable line-two field measured 14.84px. */
.cell2g { display: inline-flex; align-items: center; gap: 5px; min-width: 0; min-height: 24px; }
.cell2g.cell2--edit { cursor: cell; }
.cell2g.cell2--edit:hover { background: var(--v2-cell-hover); }
.cell2g.is-dirty2 .cell2__v { box-shadow: inset 0 -2px 0 var(--v2-dirty); }
.cell2 {
  display: flex; align-items: center; gap: 10px;
  margin-top: 2px; padding-top: 2px;
  border-top: 1px dashed var(--color-border);
  font-size: 11px; line-height: 1.35; min-width: 0;
  /* SC 2.5.8. It measured 17.84px with a line-one target 2px above it, so the
     spacing exception could not apply either: the neighbour is also a target.
     This floor holds the LINE; the group above holds each target inside it. */
  min-height: 24px;
}
.cell2__k { color: var(--color-text-muted); flex: 0 0 auto; font-weight: 600; }
.cell2__v { color: var(--color-text); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cell2__v.is-unset { color: var(--color-text-muted); font-style: italic; }
.cell2--edit { cursor: cell; }
.cell2--edit:hover { background: var(--v2-cell-hover); }
.cell2.is-dirty2 .cell2__v { box-shadow: inset 0 -2px 0 var(--v2-dirty); }
.cell2.is-editing2 { padding-top: 0; border-top: 0; }
.celledit--sec { font-size: 11px; padding: 1px 4px; }
table.grid td { vertical-align: top; }

/* ---------------------------- EXPANDED ROW (c8p) --------------------------
   The row grows into an editing strip directly beneath itself. Nothing slides
   over the table, so the table never narrows and the reserved band is not
   needed in this shape at all.

   ROWS ABOVE DO NOT MOVE. dy is 0.0 for every row above the expanded one
   because the strip is INSERTED after its row rather than overlaid or pushed
   in from anywhere: the rows above are simply not in the layout path. The row
   the reader clicked stays exactly where it was. */
tr.striprow > td.stripcell { padding: 0; border-bottom: 1px solid var(--color-border); }
tr.is-expanded > td { background: var(--v2-cell-hover); }
tr.is-expanded > td:first-child { box-shadow: inset 3px 0 0 var(--color-accent); }
.strip {
  overflow: hidden;
  background: var(--color-surface-2);
  border-top: 2px solid var(--color-accent);
}
/* FLAT AND SHORT. Everything sits on ONE line: the fields grid takes the space
   and the close button sits at its end. The strip is an editing strip, so its
   height is the cost it charges in records, and the only defensible height is
   the smallest one that still edits comfortably. */
.strip__inner { padding: 8px 12px 9px; outline: none; display: flex; align-items: flex-end; gap: 10px; }
/* The region takes focus on open, so it needs a visible ring like anything
   else that can hold focus. :focus-visible alone shows nothing for a
   programmatic focus() in Chromium, which is exactly how this one arrives.
   --focus-ring and --color-accent are the names this file DECLARES. The first
   draft reached for --color-brand, --color-focus and --surface-sunken, none of
   which exist here, and an invalid var() on an inherited property is what made
   the RBI mark paint at 78% white a round ago. */
.strip__inner:focus { box-shadow: inset 0 0 0 2px var(--focus-ring); }
.strip__fields {
  flex: 1 1 auto; min-width: 0;
  display: grid; gap: 8px 16px;
  grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
  align-items: end;
}
/* THE STRIP REUSES .frow AND TIGHTENS IT, deliberately. An earlier draft here
   declared a stacked label with the same one-class specificity as .frow, which
   is declared further down the sheet and therefore won: the rule did nothing
   and the layout that shipped was an accident that happened to look right.
   .strip .frow--strip outranks it, so what is written is what renders.

   The label stays BESIDE its control rather than above it. Stacking costs a
   second line per field, and the height of this strip is the number of records
   it hides, which is the whole subject of the ruling. */
.strip .frow--strip {
  display: grid; grid-template-columns: auto minmax(0, 1fr);
  gap: 8px; align-items: center;
  padding: 0; border-bottom: 0; min-width: 0;
}
.strip .frow--strip > .flabel { font-size: 11px; white-space: nowrap; }
/* The free text takes twice the width of a plain field rather than a line of
   its own. Two rows in a box this wide already hold far more than the 300px
   cell does, and resize:vertical lets a reader pull it taller for a long note.
   A full-width row of its own cost 90px and bought nothing at this width. */
.frow--text { grid-column: span 2; }
.strip__area { resize: vertical; min-height: 38px; font: inherit; line-height: 1.35; }
.strip__close { flex: 0 0 auto; margin-bottom: 1px; }
/* A 240ms grow, and ONLY on the open gesture. Every other repaint renders the
   strip at its resting height, so a filter tick or a save does not re-animate
   a strip that was already open. */
@keyframes stripopen { from { max-height: 0; opacity: 0; } to { max-height: 420px; opacity: 1; } }
.strip.is-opening { animation: stripopen .24s ease-out; }
@media (prefers-reduced-motion: reduce) {
  .strip.is-opening { animation: none; }
}
/* WIDE SHAPE: 17 real columns carrying all 19 fields, and a cell takes a
   second line only when its own content needs one. That is what a spreadsheet
   does, and it is why this shape shows the same 19 fields in fewer vertical
   pixels than a row that gives every record a second line whether it needs one
   or not. */
table.grid.is-wide th, table.grid.is-wide td { white-space: normal; }
table.grid.is-wide .cell1 { white-space: normal; overflow: visible; }
table.grid.is-wide .th-inner span { white-space: normal; overflow: visible; }
table.grid.is-wide .th-inner { align-items: flex-start; }
table.grid th { vertical-align: top; }

/* ---------------- Pagination ---------------- */
.pager { display: flex; align-items: center; gap: var(--space-sm); margin-left: auto; }
.pager__n { font-size: 12px; color: var(--color-text-muted); }
.pagesize { display: inline-flex; align-items: center; gap: 6px; font-size: 12px; color: var(--color-text-muted); }
.pagesize select {
  border: 1px solid var(--color-border-strong); border-radius: var(--radius-xs);
  background: var(--color-surface); color: var(--color-text);
  font-size: 12px; padding: 3px 6px; min-height: 24px;
}
/* 24x24 outright rather than leaning on the spacing exception. At compact
   density the row is 24.61px tall, which left 11.53px of clearance where the
   exception needs 12, so eight of eight funnels failed on the short axis. The
   glyph stays 12px; the box around it is what grew. GL-003 s8.5 warns that
   retuning spacing re-opens every other target measurement, so this is done
   with the box and not with the row height. */
/* Absolutely positioned, so its 24x24 target does not sit in the flex flow and
   does not shrink the label. Its containing block is .th-inner, which is why
   that rule carries position:relative: the th is one cell for TWO header lines,
   so positioning against the th centres a line-one funnel on the boundary
   between them and lets line two's cover it. */
.funnel {
  position: absolute; right: 2px; top: 50%; transform: translateY(-50%);
  border: 0; background: none; padding: 0; line-height: 0;
  color: var(--color-text-muted); border-radius: var(--radius-xs);
  min-width: 24px; min-height: 24px;
  display: inline-flex; align-items: center; justify-content: center;
}
/* BOTH MODIFIERS MUST FOLLOW .funnel. Each carries one class, so does .funnel,
   and equal specificity leaves source order as the only thing deciding. While
   they sat ABOVE it every declaration in both was silently overridden: line
   two's funnel kept .funnel's top and transform and stretched across the line
   boundary, and the attached-link funnel kept right:2px and landed exactly on
   top of its own column's filter. MEASURED on the shipped file: Client Name and
   Property were hittable on 0 of 25 sampled pixels at EVERY width, wide shape
   included, because the link funnel covered them outright. */
/* Line two's funnel sits at the bottom of line two rather than centred in it. */
.funnel--sec { top: auto; bottom: 0; transform: none; }
/* The second funnel on line one, 24px clear of the column's own. */
.funnel--attach { right: 26px; }
.funnel:hover { background: color-mix(in srgb, var(--color-text) 10%, transparent); color: var(--color-text); }
.funnel.is-on { color: var(--color-accent-ink); background: color-mix(in srgb, var(--color-accent) 15%, transparent); }
/* A sorted column's funnel wears the arrow instead of the funnel glyph. It is
   the only mark on screen saying the book is not in the order it arrived in,
   so it is drawn at full text colour rather than the muted resting one. */
.funnel.is-sorted { color: var(--color-accent-ink); }
/* The add-row is pinned to the bottom of the scroll viewport. It is still the
   last row of the grid, and a rep never scrolls 45 rows to reach it. A cell can
   carry a bottom AND a left sticky offset at once, so the two frozen columns
   keep working inside it. */
.newrow td {
  position: sticky; bottom: 0; z-index: 2;
  background: var(--color-surface) !important;
  background-image: linear-gradient(
    color-mix(in srgb, var(--color-accent) 8%, transparent),
    color-mix(in srgb, var(--color-accent) 8%, transparent)) !important;
  border-top: 2px solid var(--border-selected);
}
.newrow td.col-sticky-1, .newrow td.col-sticky-2 { z-index: 4; }
.newrow .plus { color: var(--color-accent-ink); font-weight: 800; }
.hslink {
  display: inline-flex; align-items: center; gap: 4px;
  padding: 1px 7px 1px 5px; border-radius: var(--radius-pill);
  background: var(--surface-status-info); color: var(--on-surface-status-info);
  font-size: 11px; font-weight: 700; text-decoration: none;
}
.hslink:hover { text-decoration: underline; }
.hsadd {
  border: 1px dashed var(--color-border-strong); background: none;
  color: var(--color-text-muted); font-size: 11px; font-weight: 600;
  padding: 0 7px; border-radius: var(--radius-pill);
  /* One of 22 missed the short axis by 1.09px at compact. */
  min-height: 24px; display: inline-flex; align-items: center;
}
.hsadd:hover { color: var(--color-accent-ink); border-color: var(--border-selected); }
.hsnone { color: var(--color-text-muted); font-size: 11px; }
.chip {
  display: inline-block; padding: 1px 8px; border-radius: var(--radius-pill);
  font-size: 11px; font-weight: 700; white-space: nowrap;
}
.chip--won  { background: var(--surface-status-success); color: var(--on-surface-status-success); }
.chip--lost { background: var(--surface-status-danger);  color: var(--on-surface-status-danger); }
.chip--ex   { background: var(--surface-status-warning); color: var(--on-surface-status-warning); }
.chip--proc { background: var(--surface-status-info);    color: var(--on-surface-status-info); }
.chip--none { background: var(--color-surface-2); color: var(--color-text-muted); }
.gridfoot {
  display: flex; align-items: center; gap: var(--space-md); flex-wrap: wrap;
  padding: var(--space-sm) var(--space-lg); border-top: 1px solid var(--color-border);
  font-size: 12px; color: var(--color-text-muted);
}

/* ---------------- Secondary cell affordances ---------------- */
/* Both sit at the right edge of their cell and appear on hover or focus, so
   the resting grid stays quiet and neither one costs a tab stop. */
.hsedit, .rowopen {
  position: absolute; right: 3px; top: 50%; transform: translateY(-50%);
  opacity: 0; line-height: 0; padding: 0;
  border: 1px solid var(--color-border-strong); border-radius: var(--radius-xs);
  background: var(--color-surface); color: var(--color-text-muted);
  /* 24x24 for SC 2.5.8. Five of 68 pencils missed by 0.24px at compact. */
  min-width: 24px; min-height: 24px;
  display: inline-flex; align-items: center; justify-content: center;
}
td:hover .hsedit, td:focus-within .hsedit,
td:hover .rowopen, td:focus-within .rowopen { opacity: 1; }
.hsedit:hover, .rowopen:hover { color: var(--color-accent-ink); border-color: var(--border-selected); }
.hsedit:focus-visible, .rowopen:focus-visible { opacity: 1; }
.rownum { color: var(--color-text-muted); }
.viewhint {
  margin: 0; padding: var(--space-sm) var(--space-lg);
  font-size: 12px; color: var(--on-surface-status-info);
  background: var(--surface-status-info);
  border-bottom: 1px solid var(--color-border);
}

/* ---------------- Row-detail drawer ---------------- */
/* Every field the grid does not show. Juan does not want the table sliding
   sideways, so the overflow goes here instead of into a horizontal scroll. */
.drawer {
  position: fixed; top: 0; right: 0; bottom: 0; z-index: 180;
  width: 460px; max-width: 94vw;
  display: flex; flex-direction: column;
  background: var(--color-surface);
  border-left: 1px solid var(--color-border-strong);
  box-shadow: var(--shadow-lg);
  transform: translateX(102%); visibility: hidden;
  transition: transform .2s ease, visibility 0s linear .2s;
}
.drawer.is-open { transform: none; visibility: visible; transition: transform .2s ease, visibility 0s; }
.drawer__head {
  display: flex; align-items: flex-start; gap: var(--space-md);
  padding: var(--space-lg); border-bottom: 1px solid var(--color-border);
}
.drawer__head > div { flex: 1 1 auto; min-width: 0; }
.drawer__title { font-size: var(--text-h2); font-weight: 700; margin: 0; color: var(--color-text-strong); }
.drawer__sub { font-size: 12px; color: var(--color-text-muted); margin: 3px 0 0; }
.drawer__body { flex: 1 1 auto; overflow: auto; padding: 0 var(--space-lg) var(--space-2xl); }
/* Revealed on focus. The sr-only rule clips it to 1px; this undoes every part
   of that clipping so the stop is visible to the reader who is standing on it. */
.drawer__back:focus-visible {
  position: static; width: auto; height: auto; margin: 0;
  clip: auto; clip-path: none; overflow: visible; white-space: normal;
  display: block; align-self: flex-start;
  margin: 0 var(--space-lg) var(--space-lg);
  padding: 8px 14px; border-radius: var(--radius-sm);
  border: 1px solid var(--border-selected); background: var(--color-surface);
  color: var(--color-accent-ink); font-size: 13px; font-weight: 600;
}
.drawer__group { margin-top: var(--space-lg); }
.drawer__group > h3 {
  margin: 0 0 var(--space-xs); font-size: 11px; font-weight: 700;
  text-transform: uppercase; letter-spacing: var(--eyebrow-tracking);
  color: var(--color-text-muted);
}
.frow {
  display: grid; grid-template-columns: 128px minmax(0, 1fr);
  gap: var(--space-md); align-items: center;
  padding: 6px 0; border-bottom: 1px solid var(--color-border);
}
.frow > .flabel { font-size: 12px; font-weight: 600; color: var(--color-text-muted); }
.frow .input, .frow select { width: 100%; }
.frow .ro { font-size: 13px; overflow: hidden; text-overflow: ellipsis; }
.frow .ro.is-unset { color: var(--color-text-muted); }
.linkrow { display: flex; gap: var(--space-xs); align-items: center; }
.linkrow .input { flex: 1 1 auto; min-width: 0; }

/* ---------------- Empty state ---------------- */
.empty { padding: var(--space-3xl) var(--space-lg); text-align: center; }
.empty__icon {
  width: 46px; height: 46px; margin: 0 auto var(--space-md);
  display: grid; place-items: center; border-radius: var(--radius-pill);
  background: var(--color-surface-2); color: var(--color-text-muted);
}
.empty__title { font-size: 15px; font-weight: 700; color: var(--color-text-strong); margin: 0 0 2px; }
.empty__body { font-size: 13px; color: var(--color-text-muted); margin: 0; }

/* ---------------- Popover ---------------- */
.pop {
  position: fixed; z-index: 200; min-width: 230px; max-width: 300px;
  background: var(--color-surface); border: 1px solid var(--color-border-strong);
  border-radius: var(--radius-sm); box-shadow: var(--shadow-lg); padding: var(--space-sm);
}
.pop[hidden] { display: none; }
.pop__head { font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: var(--eyebrow-tracking); color: var(--color-text-muted); padding: 2px 4px 6px; }
.pop__list { max-height: 250px; overflow: auto; margin: 0; padding: 0; list-style: none; }
.pop__list li { margin: 0; }
.checkrow {
  display: flex; align-items: center; gap: var(--space-sm); width: 100%;
  padding: 4px 6px; border-radius: var(--radius-xs); font-size: 13px;
  background: none; border: 0; text-align: left; color: var(--color-text);
}
.checkrow:hover { background: var(--color-surface-2); }
.checkrow input { accent-color: var(--color-accent); margin: 0; }
.checkrow .cnt { margin-left: auto; font-size: 11px; color: var(--color-text-muted); }
.pop__foot { display: flex; gap: var(--space-xs); padding-top: var(--space-sm); border-top: 1px solid var(--color-border); margin-top: var(--space-xs); }
.pop__foot .btn { flex: 1 1 auto; justify-content: center; padding: 4px 8px; font-size: 12px; }
.popnote { font-size: 12px; color: var(--color-text-muted); padding: 4px 6px; }

/* ---------------- Column menu: sort, clear, and the value tree ----------------
   The shape of an Excel autofilter menu in v2's own chassis. Sort entries and
   the clear entry read as MENU LINES, full width and left aligned, so they are
   not mistaken for the two footer buttons that commit and cancel.
   Every colour is a token; nothing here is a new value. */
.popmenu { display: flex; flex-direction: column; padding-bottom: var(--space-xs); }
.popmenu + .popmenu { border-top: 1px solid var(--color-border); padding-top: var(--space-xs); }
.popitem {
  display: block; width: 100%; text-align: left; font: inherit; font-size: 13px;
  padding: 5px 8px; border: 0; border-radius: var(--radius-xs);
  background: none; color: var(--color-text); cursor: pointer;
}
.popitem:hover:not([disabled]) { background: var(--color-surface-2); }
.popitem[disabled] { color: var(--color-text-muted); opacity: .55; cursor: not-allowed; }
/* The live direction is marked, not merely hinted: a tick a reader can see
   without comparing two rows to each other. */
.popitem.is-on { color: var(--color-accent-ink); font-weight: 600; }
.popitem.is-on::before { content: "✓"; margin-right: 6px; }
.popitem:not(.is-on)::before { content: ""; margin-right: 6px; display: inline-block; width: 1ch; }
.pop__search { width: 100%; margin: var(--space-xs) 0; }

.poptree { max-height: 230px; overflow: auto; margin: 0; padding: 0; list-style: none; }
.poptree ul { margin: 0; padding: 0; list-style: none; }
.poptree__row {
  display: flex; align-items: center; gap: 6px; font-size: 13px;
  padding: 3px 6px; border-radius: var(--radius-xs); cursor: pointer;
  color: var(--color-text);
}
.poptree__row:hover { background: var(--color-surface-2); }
.poptree__row:focus-visible { outline: 2px solid var(--color-accent); outline-offset: -2px; }
.poptree__row[aria-level="2"] { padding-left: 20px; }
.poptree__row[aria-level="3"] { padding-left: 34px; }
.poptree__twisty {
  flex: 0 0 12px; width: 12px; text-align: center; font-size: 12px; line-height: 1;
  color: var(--color-text-muted);
}
.poptree__box {
  flex: 0 0 13px; width: 13px; height: 13px; border-radius: 2px;
  border: 1px solid var(--color-border-strong); background: var(--color-surface);
  display: grid; place-items: center; font-size: 10px; line-height: 1;
}
.poptree__row.is-checked .poptree__box,
.poptree__row.is-mixed .poptree__box {
  background: var(--color-accent); border-color: var(--color-accent);
}
.poptree__row.is-checked .poptree__box::after { content: "✓"; color: var(--on-surface-cta); }
.poptree__row.is-mixed .poptree__box::after { content: "–"; color: var(--on-surface-cta); }
.poptree__label {
  flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.poptree__row .cnt { flex: 0 0 auto; font-size: 11px; color: var(--color-text-muted); }

/* ---------------- Save bar ---------------- */
.savebar {
  position: fixed; left: 50%; bottom: var(--space-xl); z-index: 150;
  /* transform moves paint and nothing else, so the parked bar still measured
     visibility:visible with two tabbable buttons below the fold (SC 2.4.11 and
     2.4.3). visibility is what takes it out of the tab order and the a11y
     tree. Hiding is delayed one transition so the slide-out still renders. */
  transform: translate(-50%, 160%);
  visibility: hidden;
  transition: transform .22s ease, visibility 0s linear .22s;
  display: flex; align-items: center; gap: var(--space-md);
  padding: 10px var(--space-md) 10px var(--space-lg);
  background: var(--surface-brand); color: var(--on-surface-brand);
  border-radius: var(--radius-pill); box-shadow: var(--shadow-lg);
}
.savebar.is-open {
  transform: translate(-50%, 0);
  visibility: visible;
  transition: transform .22s ease, visibility 0s;
}
.savebar__txt { font-size: 13px; font-weight: 600; }
.savebar .btn { border-color: var(--v2-onbrand-border); background: none; color: var(--on-surface-brand); }
.savebar .btn:hover { background: var(--v2-onbrand-hover); }
.savebar .btn--primary { background: var(--surface-cta); border-color: var(--surface-cta); color: var(--on-surface-cta); }
/* The line above it wins on class count, so Save used to lose its green under
   the pointer and paint the neutral on-brand wash instead. Two classes and a
   pseudo-class beat two classes; this one matches .savebar .btn:hover at three
   and takes it on source order. Discard is untouched and keeps that wash. */
.savebar .btn--primary:hover { background: var(--surface-cta-hover); border-color: var(--surface-cta-hover); }

/* ---------------- Toast ---------------- */
.toast {
  position: fixed; right: var(--space-xl); bottom: var(--space-xl); z-index: 300;
  background: var(--color-text-strong); color: var(--color-bg);
  padding: 8px 14px; border-radius: var(--radius-sm); font-size: 13px; font-weight: 600;
  box-shadow: var(--shadow-lg); opacity: 0; transform: translateY(8px);
  transition: opacity .18s ease, transform .18s ease; pointer-events: none;
}
[data-theme="dark"] .toast { background: var(--color-surface-2); color: var(--color-text-strong); border: 1px solid var(--color-border-strong); }
.toast.is-open { opacity: 1; transform: translateY(0); }

/* Honor a reduced-motion preference. The save bar and the toast are the only
   animated surfaces here. Both keep their end states and drop the travel. */
@media (prefers-reduced-motion: reduce) {
  .savebar { transition: visibility 0s linear .01s; }
  .savebar.is-open { transition: visibility 0s; }
  .drawer { transition: visibility 0s linear .01s; }
  .drawer.is-open { transition: visibility 0s; }
  .toast { transition: none; }
}

/* ---------------- Dashboard page ---------------- */
.filterbar {
  display: flex; align-items: flex-end; gap: var(--space-lg); flex-wrap: wrap;
  padding: var(--space-md) var(--space-lg);
}
.field { display: flex; flex-direction: column; gap: 3px; }
.field label { font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: var(--eyebrow-tracking); color: var(--color-text-muted); }
.field select { padding: 6px 9px; border-radius: var(--radius-sm); border: 1px solid var(--color-border); background: var(--color-surface); font-size: 13px; min-width: 150px; }
table.lead { border-collapse: separate; border-spacing: 0; width: 100%; font-size: 13px; }
table.lead th, table.lead td { padding: 9px var(--space-md); border-bottom: 1px solid var(--color-border); text-align: right; white-space: nowrap; }
table.lead th:nth-child(-n+2), table.lead td:nth-child(-n+2) { text-align: left; }
table.lead thead th {
  position: sticky; top: 0; background: var(--color-surface-2); z-index: 2;
  font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: var(--eyebrow-tracking);
  color: var(--color-text-muted); border-bottom: 1px solid var(--color-border-strong);
}
table.lead thead th button {
  background: none; border: 0; padding: 0; font: inherit; color: inherit;
  display: inline-flex; align-items: center; gap: 4px;
}
table.lead thead th button:hover { color: var(--color-text); }
table.lead thead th[aria-sort] button { color: var(--color-accent-ink); }
table.lead tbody tr:hover td { background: var(--v2-cell-hover); }
table.lead tbody tr.is-me td { background: color-mix(in srgb, var(--color-accent) 7%, transparent); }
.rank { width: 34px; font-weight: 700; color: var(--color-text-muted); }
.who { display: flex; align-items: center; gap: var(--space-sm); }
.who .avatar { width: 26px; height: 26px; flex: 0 0 26px; font-size: 10px; }
.bar { height: 6px; border-radius: var(--radius-pill); background: var(--color-surface-2); overflow: hidden; min-width: 60px; }
.bar > i { display: block; height: 100%; background: var(--viz-cat-1); border-radius: var(--radius-pill); }
.grid2 { display: grid; grid-template-columns: minmax(0, 2fr) minmax(0, 1fr); gap: var(--space-lg); align-items: start; }
@media (max-width: 1200px) { .grid2 { grid-template-columns: minmax(0, 1fr); } }
.actrow {
  display: flex; align-items: center; gap: var(--space-md); width: 100%;
  padding: 9px var(--space-lg); border: 0; border-bottom: 1px solid var(--color-border);
  background: none; text-align: left; color: var(--color-text); font-size: 13px;
}
.actrow:hover { background: var(--color-surface-2); }
.actrow__main { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; }
.actrow__name { font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.actrow__meta { font-size: 11px; color: var(--color-text-muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.actrow__amt { font-weight: 700; }

.mocknote {
  margin: var(--space-lg) 0 0; padding: var(--space-sm) var(--space-md);
  border-left: 3px solid var(--color-warning); background: var(--surface-status-warning);
  color: var(--on-surface-status-warning); border-radius: var(--radius-xs);
  font-size: 12px; font-weight: 600;
}
/* The scroll chevrons are absolutely positioned against the strip's PARENT, so
   that parent needs a positioning context. The mock set `style.position` from
   JS, which is a declaration the token layer cannot reach and which a stricter
   CSP (`style-src-attr 'none'`) would silently drop. A class does the same job
   and stays inside the stylesheet. */
.strip-host { position: relative; }

/* ---- classes that replace the mock's inline style= attributes -------------
   The mock wrote six geometry values through style="". Every one is a static
   declaration, so it belongs in the stylesheet: an attribute declaration is
   the first thing a stricter policy (`style-src-attr 'none'`) drops, and it is
   invisible to the token layer while it survives. The ONE value that is
   genuinely dynamic - the leaderboard bar's fill - stays a CSSOM write, which
   is the mechanism v1 already uses for the same reason. */
.avatar--sm { width: 26px; height: 26px; flex-basis: 26px; font-size: 10px; }
.metriccell {
  display: inline-flex; align-items: center; gap: var(--space-sm);
  justify-content: flex-end; width: 100%;
}
.metriccell .bar { width: 56px; }
.leadscroll { overflow: auto; }
.lead__total { font-weight: 700; }
.chip--scope { align-self: flex-start; padding: 5px 10px; }
/* Same ruling as .pill-stage[disabled]: a stage no stored field can reach
   reads "n/a", never "0". See cc2-map.js deriveStage(). */
.stage__val--na {
  font-size: 13px; letter-spacing: var(--eyebrow-tracking);
  text-transform: uppercase; color: var(--color-text-muted);
}

/* Two classes that replace the last two style= attributes. Under `style-src
   'self'` the attribute is refused outright, so a static declaration has to live
   in the stylesheet. See cc2-dom.js for the measurement. */
.gridfoot__push { margin-left: auto; }
.ro--shrink { min-width: 0; }
