/* Organization people — styling for org-people.js and org-people-bulk.js. Existing tokens only,
   no new colour literals (sys_design-system.md) — CSS/tokens.css already resolves everything
   here for both themes; nothing in this file redefines a token. */

/* These buttons reconcile to the Suite-wide 26px `.btn.small` contract (sys_design-system.md
   §3) rather than the dialog's own 28-32px standard button — a 6px gap against
   `#adminModal button.btn` accepted as the cost. Must carry `#adminModal` to win specificity
   over the unqualified `.alt-app .btn.small` rule (alt-admin.css). `align-self:center`
   overrides an inherited `flex-end` left over from classic_team_board.html's column layout. */
.alt-app #adminModal .org-people-action-row button.btn.small,
.alt-app #adminModal .org-people-selection button.btn.small,
.alt-app #adminModal .org-people-invites-list button.btn.small{
  align-self:center;
  height:26px; padding:0 9px;
  font-size:12px; font-weight:600;
}

/* Email invitations, Assign people to boards, Invite people and Permissions by role read left
   to right as one toolbar (orgPeopleSyncActionRow(), org-people.js) — wraps because four
   buttons including a longer label don't reliably fit one line at this dialog's narrow widths. */
.alt-app .org-people-action-row{
  display:flex; flex-wrap:wrap; align-items:center; gap:8px; margin:0 0 8px;
}

/* The invite button and its status sentence (orgPeopleActionStatusGroup()) are kept as one flex
   item so the outer row's wrap can never separate them onto different lines. */
.alt-app .org-people-invite-status-group{
  display:flex; flex-wrap:wrap; align-items:center; gap:10px;
  flex:1 1 260px; min-width:0;
}
.alt-app .org-people-invite-status-group .btn{ flex:0 0 auto; }
/* `flex:1 1 220px`, the same number `.invite-actions .cfg-status` (alt-team.css) uses for the
   identical pairing: large enough that wrap fires only when truly needed, not on every width
   (`auto`) or never (`0`, which overflows). `min-width:0` lets it shrink below 220px once alone
   on its own line. `#adminModal` must be named to outrank `.alt-app #adminModal .cfg-status`'s
   own margin-top (alt-team.css) by id rather than by classes alone. `overflow-wrap:break-word`
   breaks a long email address that has no spaces to wrap at. */
.alt-app #adminModal .org-people-invite-status-group .cfg-status{
  margin:0; flex:1 1 220px; min-width:0; overflow-wrap:break-word;
}

/* The license badge is a block line below the row (not edge-pinned) because the row now wraps;
   absent from the DOM entirely until org-people.js has a number to show
   (orgPeopleBuildLicenseBadge()), so no placeholder variant is needed. */
.alt-app .org-people-license{
  display:block; margin:0 0 10px; font-size:12px; font-weight:600; color:var(--text-dim);
}

/* ---------- THE BUTTONS IN THIS PANE, WHICH HAD NO GEOMETRY OF THEIR OWN ----------
   A denial and a load failure are told apart from each other and from the plain `.empty-state`
   used for a genuinely empty roster (alt-team.css, unchanged). More classes than that base rule
   win on specificity. Left-aligned, not centred: a sentence to read, not a placeholder to glance
   past. */
.alt-app #adminModal .empty-state.denied,
.alt-app #adminModal .empty-state.load-error{
  text-align:left; padding:14px 16px; margin:0 0 8px;
  border:1px solid var(--border-soft); border-left-width:3px; border-radius:var(--radius-sm);
  background:var(--panel-2); color:var(--text-dim); font-size:13px;
}
/* Denied — a permission boundary, not a fault; `--yellow` is this pane's attention tone. */
.alt-app #adminModal .empty-state.denied{ border-left-color:var(--yellow); }
/* Load-error — an actual fault the reader may retry; `--red-text` matches `.org-people-menu-item.danger`. */
.alt-app #adminModal .empty-state.load-error{ border-left-color:var(--red-text); }
/* Unavailable: neither denial nor fault — the capability isn't deployed yet, so neutral. */
.alt-app #adminModal .empty-state.unavailable{ border-left-color:var(--border-soft); color:var(--text-faint); }

/* Nested inside `.org-people-invites`'s own panel, these three flatten to a left accent instead
   of a second box-in-a-box — width/style reset, never border-left-color, so the three colour
   rules above keep deciding the hue. Unchanged everywhere else these classes are used (the
   roster's own denial above keeps its full bordered box). */
.alt-app #adminModal .org-people-invites .empty-state.denied,
.alt-app #adminModal .org-people-invites .empty-state.load-error,
.alt-app #adminModal .org-people-invites .empty-state.unavailable{
  padding:6px 4px 6px 10px; margin:0; background:transparent; border-radius:0;
  border-top:0; border-right:0; border-bottom:0; border-left-width:3px; border-left-style:solid;
}

/* The fourth state this section can show ("Checking…", "No outstanding invitations.") brought
   into the same left-aligned, compact rhythm as its three siblings above, reusing this
   section's own row padding. `:not()`-scoped so it can never also match .denied/.load-error/
   .unavailable — the four are mutually exclusive by construction. */
.alt-app #adminModal .org-people-invites .empty-state:not(.denied):not(.load-error):not(.unavailable){
  text-align:left; padding:8px 4px;
}

/* ---------- THE FILTER ROW'S OWN BOX — owner, 2026-09-24; UN-MERGED FROM INVITES, 2026-09-25 ---------- */
/* Wraps only the roster's filter/search row now — the invitations panel moved into `#inviteBody`
   (team_board.html's "Invite people" card, orgPeopleMountInvitesSection()) so its own fold hides
   it too; the filter row stayed because "find a person" must never be foldable. */
.alt-app .org-people-panel{
  margin:0 0 10px; padding:9px 11px;
  background:var(--panel-2); border:1px solid var(--border-soft); border-radius:var(--radius-sm);
}
/* ---------- §39 — OUTSTANDING INVITATIONS ---------- */
/* Its own box, mounted inside `#inviteBody` after `.invite-panel`. `:not(:empty)` is load-bearing:
   orgPeopleBuildInvitesSection() returns childless when the reader may not administer invites,
   and an unconditional box would paint an empty bordered rectangle for exactly that reader
   (Definition of Done 4f). */
.alt-app .org-people-invites:not(:empty){
  margin:10px 0 0; padding:9px 11px;
  background:var(--panel-2); border:1px solid var(--border-soft); border-radius:var(--radius-sm);
}
.alt-app .org-people-invites-head{
  font-size:12px; font-weight:700; text-transform:uppercase; letter-spacing:.4px;
  color:var(--text-faint); margin:0 0 6px;
}
.alt-app .org-people-invites-list{ display:flex; flex-direction:column; }
.alt-app .org-people-invite-row{
  display:flex; flex-wrap:wrap; align-items:center; gap:10px;
  padding:8px 4px; border-bottom:1px solid var(--border-soft); font-size:13px;
}
.alt-app .org-people-invite-email{ flex:2 1 200px; color:var(--text); overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.alt-app .org-people-invite-role{ flex:0 0 auto; color:var(--text-dim); font-size:12px; }
.alt-app .org-people-invite-status{ flex:0 0 auto; color:var(--text-dim); font-size:12px; min-width:120px; }
.alt-app .org-people-invite-expiry{ flex:0 0 auto; color:var(--text-faint); font-size:12px; }
.alt-app .org-people-invite-actions{ margin-left:auto; display:flex; gap:6px; }

@media (max-width:520px){
  .alt-app .org-people-invite-row{ flex-direction:column; align-items:stretch; gap:4px; }
  .alt-app .org-people-invite-actions{ margin-left:0; }
  /* Measured (org-people-drive.py): stacked to a column, these items don't shrink to the
     container's cross size on their own, so width:100% is needed to actually fill it — same fix
     as `.org-people-filters` below, same reason. */
  .alt-app .org-people-invite-email,
  .alt-app .org-people-invite-role,
  .alt-app .org-people-invite-status,
  .alt-app .org-people-invite-expiry,
  .alt-app .org-people-invite-actions{ width:100%; }
  .alt-app .org-people-invite-actions{ flex-wrap:wrap; }
}

.alt-app .org-people-filters{
  display:flex; flex-wrap:wrap; gap:8px; align-items:center; margin:0 0 8px;
}
/* The filter controls are dressed by the dialog (`.alt-app #adminModal select`/
   `input[type=search]`, alt-team.css) — those win on specificity over anything declared here, so
   nothing is restated. Only the search field's own row-sharing is this pane's to set. */
.alt-app .org-people-filters input[type="search"]{ flex:1 1 160px; min-width:120px; }

.alt-app .org-people-selection{
  display:flex; flex-wrap:wrap; align-items:center; gap:8px; margin:0 0 10px;
  font-size:12px; color:var(--text-dim);
}
.alt-app .org-people-selection-count{ font-weight:600; color:var(--text); }

/* Nine columns matching the design's order: checkbox, Name, Email, Teams, Boards, Groups, Role,
   Status, Actions. Teams/Boards/Groups are narrow because they hold a bare count. */
.alt-app .org-people-table{ display:flex; flex-direction:column; width:100%; }
.alt-app .org-people-row{
  display:grid;
  /* Proportions measured off the design render; Role is the widest flexible column after Email
     because "Organization Owner" is the longest value any cell holds. Minimums keep every column
     readable at the dialog's 760px floor. */
  grid-template-columns:28px minmax(110px,14.4fr) minmax(150px,23.1fr) minmax(56px,6.7fr) minmax(56px,7.2fr) minmax(56px,8.7fr) minmax(140px,19.5fr) minmax(80px,12.6fr) minmax(62px,4.1fr);
  gap:6px 10px; align-items:center; padding:8px 4px; border-bottom:1px solid var(--border-soft);
}
.alt-app .org-people-row:hover{ background:var(--panel-2); }
.alt-app .org-people-head{
  position:sticky; top:0; z-index:1; background:var(--panel);
  padding:2px 4px 6px; font-size:11px; text-transform:uppercase; letter-spacing:.4px;
  color:var(--text-faint); font-weight:700; border-bottom:1px solid var(--border-soft);
}
.alt-app .org-people-row[role="cell"],
.alt-app .org-people-row > [role="cell"]{ font-size:13px; color:var(--text); overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }

.alt-app .org-people-chip{
  display:inline-block; padding:2px 8px; border-radius:999px; font-size:11px; font-weight:600;
  border:1px solid var(--border-soft); color:var(--text-dim); background:var(--panel-2);
}
.alt-app .org-people-chip.owner{ border-color:var(--accent); color:var(--accent-text); background:var(--accent-soft); }

.alt-app .org-people-status{ font-size:12px; color:var(--text-dim); }
.alt-app .org-people-status.active{ color:var(--green-text); }
.alt-app .org-people-status.inactive{ color:var(--text-faint); }
/* `--warn-text`, not `--yellow` — this line is TEXT, and sys_design-system.md §1 rule 4
   requires the text role for warning text while fills keep `--yellow`. */
.alt-app .org-people-status.pending{ color:var(--warn-text); }

.alt-app .org-people-foot{
  display:flex; flex-wrap:wrap; align-items:center; gap:10px; margin-top:10px;
  font-size:12px; color:var(--text-dim);
}

/* The row menu (•••), mounted inside multiSelectHost() — inherits that host's layer-sweep
   exemption, so no positioning rule is needed here beyond `.cfg-popover`'s own. */
.alt-app .org-people-menu-btn{ padding:2px 10px; line-height:1; font-size:16px; }
.alt-app .org-people-menu{ display:flex; flex-direction:column; min-width:170px; padding:4px; }
.alt-app .org-people-menu-item{
  display:block; width:100%; text-align:left; padding:7px 10px; border:0; background:none;
  border-radius:var(--radius-sm); color:var(--text); font-size:13px; cursor:pointer;
}
.alt-app .org-people-menu-item:hover:not(:disabled){ background:var(--panel-2); }
.alt-app .org-people-menu-item:disabled{ color:var(--text-faint); cursor:not-allowed; }
.alt-app .org-people-menu-item.danger{ color:var(--red-text); }

.alt-app .org-bulk-selected-bar{
  font-size:13px; font-weight:600; color:var(--text); margin-bottom:10px;
}

/* "Already has" — read-only context above the pickers (orgBulkRenderCurrentConfig()). Context,
   not an error affordance (CLAUDE.md 4d): no coloured border, just the same neutral `--panel-2`
   well the invite row's empty states use, since nothing here is blocked or broken. */
.alt-app .org-bulk-current{
  border:1px solid var(--border-soft); border-radius:var(--radius-sm);
  background:var(--panel-2); padding:10px 12px; margin:0 0 10px; font-size:13px;
}
.alt-app .org-bulk-current-head{ font-weight:600; color:var(--text); margin-bottom:4px; }
.alt-app .org-bulk-current-row{ display:flex; gap:8px; color:var(--text-dim); }
.alt-app .org-bulk-current-row + .org-bulk-current-row{ margin-top:2px; }
.alt-app .org-bulk-current-label{
  flex:0 0 56px; color:var(--text-faint); font-weight:700; font-size:11px;
  text-transform:uppercase; letter-spacing:.4px;
}

/* ---------- THE TWO BARE LABELS THIS DIALOG'S OWN MARKUP NEVER SIZED ----------
   Neither `.settings-modal` nor `.settings-modal-body` sets a base font-size, so these two bare
   spans inherited the page body's 16px next to 13px neighbours. Scoped to this dialog's own
   class, not to the shared `.control-row`/`.inline-check` primitives other dialogs also build. */
.alt-app .org-bulk-label{ font-size:13px; }

/* Narrow widths: the same collapse-to-one-column treatment #adminModal's `.people-grid` already
   uses, applied to this table's nine columns, so a phone width never scrolls sideways. */
@media (max-width:520px){
  .alt-app .org-people-row{ grid-template-columns:1fr; gap:4px; padding:10px 6px; }
  .alt-app .org-people-head{ display:none; }
  .alt-app .org-people-filters{ flex-direction:column; align-items:stretch; }
  .alt-app .org-people-filters select,
  .alt-app .org-people-filters input[type="search"]{ width:100%; }
}

/* ================= PERMISSIONS BY ROLE — the reference matrix and its two triggers =========
   Dresses JS/components/permissions-matrix-dialog.js, opened from a button in Edit assignments
   and in Assign people. Lives in this sheet rather than its own because both hosts are this
   capability's dialogs and nothing else opens this surface today.

   Redesigned against the "permissions by role" wireframe — a role selector, category filter,
   search and a role-view card grid sit above the original "Compare roles" table, unchanged.

   No z-index below: the matrix opens above whichever dialog launched it, assigned at runtime by
   JS/core/layers.js, one step above whatever is top-most.

   No new colour — the allowed/not-allowed pair is the existing `--green-text`/`--text-faint`
   pair; the two extra cell states change SHAPE (filled ring, dashed ring, bare dash, dotted
   ring), not hue, per sys_design-system.md's colour-is-never-the-only-signal rule, and every
   mark also carries a sentence.

   No second accent hue for the Organization axis: this app has exactly one accent
   (`--accent`/`--a-blue`), so that axis is drawn with a left-border device instead, the same one
   the comparison table already uses, and selection stays uniformly `--accent`.

   No height on any new button — `.pmx-rules-btn`/`.pmx-tech-btn` are `.btn.small`, not a new
   tier; the category chips reuse `.a-chip` and the view switch reuses `.a-tabs`/`.a-tab`,
   neither of which carries the `--a-ctl` control-geometry risk a new button height would. */

/* ---------- the trigger, in both host dialogs ---------- */

body.alt-app .pmx-trigger{
  display:inline-flex; align-items:center; gap:6px;
}
/* Edit assignments' identity row is already flex, so the trigger reaches its edge via
   margin-left:auto; the bulk dialog wants it directly after the role <select> instead — a
   modifier, not part of the base. */
body.alt-app .pmx-trigger-end{ margin-left:auto; }
body.alt-app .pmx-trigger-ic{
  display:inline-flex; width:14px; height:14px; flex:0 0 auto;
}
body.alt-app .pmx-trigger-ic svg{ width:14px; height:14px; display:block; }
body.alt-app .pmx-trigger-t{ white-space:nowrap; }

/* ---------- the dialog shell ---------- */

/* A pre-JS fallback, not the answer (sys_design-system.md §12 rule 5) — sizeSettingsDialog()
   writes an inline width the instant the dialog opens, which always outranks this rule. What
   this controls is the one frame before that JS runs, matching sizeSettingsDialog()'s own
   maxW so the card doesn't visibly widen afterward. */
body.alt-app .settings-modal.pmx-modal{ width:min(980px, 100%); }
/* ---------- THE FROZEN BLOCK SPENDS A SHARE OF THE WINDOW, NOT A CONSTANT ----------
   The owner: "render the iframe based on a percentage of the viewable space based relative to
   the browser height so instead of pinning at a px level, rather use the % so the iframe always
   renders with the maximum vertical space available." ("iframe" is his word for the scroll
   region.)

   The frozen children were a fixed px constant regardless of window height, so the scroller
   below them was starved on a short window. The fix is a proportion WITH A FLOOR, not a strict
   percentage (which would waste space on a tall window and crush the cards on a phone) — every
   gap and pad is `clamp(floor, Nvh, ceiling)`, the same idiom CSS/components.css already uses
   for the task description box.

   The floor is held by a test, not a `min-height`: `.pmx-body` is overflow:hidden, so a
   min-height floor that exceeds the body would clip rather than grow the scroller.
   permissions-matrix-geometry-probe.py asserts the share directly, at four viewports in both
   themes — weigh anything added to the frozen block against its 300px ceiling. */
body.alt-app .pmx-body{ gap:clamp(8px, 1.3vh, 12px); }
body.alt-app .settings-modal-body.pmx-body{ padding:clamp(10px, 1.8vh, var(--a-4)); }

/* ---------- THE FROZEN BLOCK — WHAT IS PINNED, AND WHAT PAYS FOR IT ----------
   The owner asked for the title, subtitle and column header to stay in view while rows scroll;
   the frozen block now also carries the role selector, legend and rules disclosure. "How to
   read this" moved into `.pmx-roleview` since it isn't needed while scrolling.

   Each view owns its own scroll region, and the two never nest — `.pmx-tablewrap` and
   `.pmx-roleview` are siblings, only one ever visible, so a hidden region receives no wheel
   input to disagree with the visible one over. */
body.alt-app .pmx-body{ overflow:hidden; min-height:0; }
body.alt-app .pmx-body > *{ flex:0 0 auto; }
body.alt-app .pmx-body > .pmx-tablewrap,
body.alt-app .pmx-body > .pmx-roleview{ flex:1 1 auto; min-height:0; }
body.alt-app .pmx-body > .pmx-sub{ margin:0; }
/* `.a-callout`'s own bottom margin would double up with this flex gap. Scoped to
   `.pmx-roleview`, where pmxBuildReadCallout() actually mounts it now, not `.pmx-body`. */
body.alt-app .pmx-roleview > .a-callout{ margin-bottom:0; }

/* ---------- the role selector ---------- */

/* auto-fit down to a horizontal strip, not a second row — matches the wireframe's own phone
   breakpoint: the row stays one card tall and scrolls sideways instead of adding a row height
   to the frozen block on every narrow viewport. */
body.alt-app .pmx-roles{
  display:grid; grid-template-columns:repeat(4, minmax(0, 1fr)); gap:8px;
}
body.alt-app .pmx-role{
  display:flex; align-items:flex-start; gap:9px; min-width:0;
  padding:clamp(6px, 1.1vh, 10px);
  border:1px solid var(--border-soft); border-radius:var(--radius);
  background:var(--panel-2); text-align:left; font:inherit; color:inherit; cursor:pointer;
}
body.alt-app .pmx-role:hover{ border-color:var(--border-strong); }
body.alt-app .pmx-role.active{
  border-color:var(--accent); background:var(--accent-soft);
  box-shadow:0 0 0 1px var(--accent) inset;
}
body.alt-app .pmx-role-org{ border-left:3px solid var(--border-strong); }
body.alt-app .pmx-role-ic{
  flex:0 0 auto; width:30px; height:30px; display:grid; place-items:center;
  border-radius:var(--radius-sm); background:var(--panel); color:var(--text-dim);
}
body.alt-app .pmx-role.active .pmx-role-ic{ color:var(--accent-text); }
body.alt-app .pmx-role-ic svg{ width:16px; height:16px; }
body.alt-app .pmx-role-bd{ min-width:0; display:flex; flex-direction:column; gap:2px; }
body.alt-app .pmx-role-axis{
  font-size:9px; font-weight:800; letter-spacing:.06em; text-transform:uppercase;
  color:var(--text-faint);
}
body.alt-app .pmx-role-name{ font-size:13px; font-weight:700; color:var(--text); }
body.alt-app .pmx-role-text{
  margin:2px 0 0; font-size:11px; line-height:1.4; color:var(--text-dim);
}

/* ---------- the legend and the "Role rules" disclosure ---------- */

body.alt-app .pmx-legend-row{
  display:flex; flex-wrap:wrap; align-items:center; gap:10px;
}
body.alt-app .pmx-legend{
  display:flex; flex-wrap:wrap; align-items:center; gap:11px; color:var(--text-dim);
}
body.alt-app .pmx-legend-item{ display:inline-flex; align-items:center; gap:5px; font-size:11px; }
body.alt-app .pmx-legend-t{ white-space:nowrap; }
body.alt-app .pmx-rules-btn{ margin-left:auto; }
body.alt-app .pmx-rules-ic svg{ width:14px; height:14px; }
/* Reuses `.pmx-foots`' list treatment and adds its own container chrome, since as a disclosure
   panel it needs to read as one contained thing when it opens. */
body.alt-app .pmx-rules-panel{
  margin:0; padding:10px 12px 10px 28px;
  border:1px solid var(--border-soft); border-radius:var(--radius-sm); background:var(--panel-2);
}

/* ---------- the view switch ---------- */

/* `.a-tabs`' own bottom border and padding are for a full-width strip; cleared here, where it
   sits among several controls in the frozen block rather than as a page's section nav. */
body.alt-app .pmx-viewtabs{ border-bottom:0; padding:0; }
body.alt-app .pmx-viewtabs .a-tab svg{ width:15px; height:15px; }

/* ---------- the role view ---------- */

body.alt-app .pmx-roleview{
  overflow-y:auto; overflow-x:hidden; position:relative;
  display:flex; flex-direction:column; gap:clamp(7px, 1.1vh, 10px);
  border:1px solid var(--border-soft); border-radius:var(--radius-sm);
  padding:clamp(7px, 1.2vh, 10px);
}
/* Without this, a scroll-container child (`.pmx-cats`) is the only one whose automatic minimum
   size is 0, so it alone absorbs all the shrink pressure in this tall flex column and gets
   crushed to near-zero height — proven by mutation: removing this line reproduces it. */
body.alt-app .pmx-roleview > *{ flex:0 0 auto; }
/* `display:flex`/`display:grid` here must outrank the UA sheet's bare `[hidden]{display:none}`
   (both are 0 ids, 1-2 classes — this file's extra class wins), or toggling `hidden` from JS
   leaves the whole role-view toolbar rendered on top of the comparison table instead of
   disappearing. Caught by headless screenshot; the DOM-shim suite has no layout engine and
   cannot see two views occupying one box. */
body.alt-app .pmx-roleview[hidden],
body.alt-app .pmx-cards[hidden]{ display:none; }

/* The search field: `.a-search`/`.a-search-ico` place the icon; `.settings-modal
   input[type="search"]` draws the border/ground/ring. This input deliberately doesn't carry
   `.a-input` (that would pull in its `--a-ctl` height too), so the icon's left padding is
   restated here, scoped to just this input. */
body.alt-app .pmx-search{ display:block; }
body.alt-app .pmx-search input[type="search"]{ width:100%; padding-left:30px !important; }

/* One row, scrolled, never wrapped — wrapping cost a second line in the scroller most starved
   for height on a phone. */
body.alt-app .pmx-cats{ display:flex; flex-wrap:nowrap; overflow-x:auto; gap:6px; padding-bottom:2px; }
body.alt-app .pmx-cat-chip{ flex:0 0 auto; }
/* `.a-chip` caps itself at 170px with an ellipsis, measured for a short tag or name — this
   pane's category labels are full sentence fragments, so max-width/overflow are overridden here
   only, keeping every other chip in the app at its usual 170px. */
body.alt-app .pmx-cat-chip{ max-width:none; overflow:visible; }
body.alt-app .pmx-cat-chip.on{ background:var(--accent-soft); border-color:var(--accent); color:var(--accent-text); }

body.alt-app .pmx-rv-head{
  display:flex; flex-wrap:wrap; align-items:center; gap:10px;
  padding-top:2px; border-top:1px solid var(--border-soft);
}
body.alt-app .pmx-rv-who{ display:flex; align-items:center; gap:8px; min-width:0; }
body.alt-app .pmx-rv-who-ic{
  flex:0 0 auto; width:26px; height:26px; display:grid; place-items:center;
  border-radius:var(--radius-sm); background:var(--panel-2); color:var(--text-dim);
}
body.alt-app .pmx-rv-who-ic svg{ width:14px; height:14px; }
body.alt-app .pmx-rv-who-bd{ display:flex; flex-direction:column; gap:1px; min-width:0; }
body.alt-app .pmx-rv-who-name{ font-size:12px; font-weight:700; color:var(--text); }
body.alt-app .pmx-rv-who-count{ font-size:11px; color:var(--text-faint); }
body.alt-app .pmx-rv-counts{
  display:flex; flex-wrap:wrap; align-items:center; gap:8px; margin-left:auto;
  font-size:11px; color:var(--text-dim);
}
body.alt-app .pmx-count{ display:inline-flex; align-items:center; gap:4px; }
body.alt-app .pmx-count-n{ font-weight:700; font-variant-numeric:tabular-nums; }
body.alt-app .pmx-count-yes .pmx-count-n{ color:var(--green-text); }
body.alt-app .pmx-count-cond .pmx-count-n{ color:var(--green-text); }
body.alt-app .pmx-count-no .pmx-count-n,
body.alt-app .pmx-count-na .pmx-count-n{ color:var(--text-faint); }

body.alt-app .pmx-cards{
  display:grid; grid-template-columns:repeat(auto-fill, minmax(260px, 1fr)); gap:8px;
}
body.alt-app .pmx-card{
  display:flex; gap:10px; min-width:0;
  padding:11px; border:1px solid var(--border-soft); border-radius:var(--radius); background:var(--panel-2);
}
body.alt-app .pmx-card-ic{
  flex:0 0 auto; width:32px; height:32px; display:grid; place-items:center;
  border-radius:var(--radius-sm); background:var(--panel); color:var(--text-dim);
}
body.alt-app .pmx-card-ic svg{ width:16px; height:16px; }
body.alt-app .pmx-card-bd{ min-width:0; display:flex; flex-direction:column; gap:6px; flex:1 1 auto; }
body.alt-app .pmx-card-title{ margin:0; font-size:13px; font-weight:700; color:var(--text); }
body.alt-app .pmx-status{ display:inline-flex; align-items:center; gap:6px; }
body.alt-app .pmx-status-t{ font-size:12px; font-weight:650; }
body.alt-app .pmx-status-yes,
body.alt-app .pmx-status-cond{ color:var(--green-text); }
body.alt-app .pmx-status-no,
body.alt-app .pmx-status-na{ color:var(--text-faint); }
/* `.pmx-note`, the table's own condition style, reused verbatim on a <p> here. */
body.alt-app .pmx-card .pmx-note{ margin:0; }

/* The enforcement disclosure: `body.alt-app .settings-modal .pmx-tech-btn` must be the full
   selector, not the shorter form — `.settings-modal .btn` (alt-shared-dialogs.css) is one class
   more specific and would otherwise win, silently overriding height:auto back to the full
   control height. Caught by getComputedStyle(), not a screenshot. */
body.alt-app .settings-modal .pmx-tech-btn{
  align-self:flex-start; height:auto; padding:3px 8px; font-size:11px; font-weight:650;
  color:var(--text-dim); background:transparent;
}
body.alt-app .settings-modal .pmx-tech-btn:hover{ color:var(--text); border-color:var(--border-strong); }
body.alt-app .pmx-tech-ic svg{ width:13px; height:13px; }
body.alt-app .pmx-tech-panel{
  margin-top:2px; padding:8px 10px; border:1px solid var(--border-soft); border-radius:var(--radius-sm);
  background:var(--panel);
}
body.alt-app .pmx-tech-line{ margin:0; font-size:11px; line-height:1.5; color:var(--text-dim); }
body.alt-app .pmx-tech-line + .pmx-tech-line{ margin-top:3px; }
body.alt-app .pmx-tech-line b{ color:var(--text); font-weight:650; }

/* `.a-empty` already carries padding and centring; a border is added because this is the first
   use of it that doesn't already sit inside a panel with its own edge. */
body.alt-app .pmx-rv-empty{ border:1px dashed var(--border-soft); border-radius:var(--radius); }

/* ---------- the comparison table ("Compare roles") ----------
   Unchanged from the previous design — `.pmx-roleview` is the redesign's own scroller and never
   nests inside this one (see the frozen-block note above). */

/* The wide table scrolls inside itself, never the dialog. `overflow-x:auto` alone would also
   compute overflow-y to auto, giving a second nested vertical scrollbar, so both axes are set
   explicitly. `position:relative` is needed because every cell holds an absolutely-positioned
   `.a-sr` span that would otherwise resolve against the page, not this box (see the team board's
   own 1325px sideways-scroll defect, alt-shell.css). Focusable, with both axes, since this is
   this view's one scroll region. */
body.alt-app .pmx-tablewrap{
  overflow:auto; position:relative;
  border:1px solid var(--border-soft); border-radius:var(--radius-sm);
}
body.alt-app .pmx-tablewrap:focus-visible{ outline:2px solid var(--focus-ring); outline-offset:1px; }

body.alt-app .pmx-table{
  width:100%; min-width:720px; border-collapse:collapse;
  font-size:13px; line-height:1.45; color:var(--text);
}
/* No wash behind the column headers — the mockup's hierarchy spends that signal on the group
   rows instead, which are what a reader navigates by. */
/* Sticky on the cells, not `<thead>` — the broadly-supported spelling. Needs an opaque ground
   (`--panel`, the modal's own surface) or rows scroll through it. The rule underneath is an
   inset shadow rather than border-bottom, because a collapsed border belongs to the table grid
   under border-collapse and vanishes once the cell goes sticky. */
body.alt-app .pmx-table .pmx-h{
  position:sticky; top:0; z-index:1;
  background:var(--panel); color:var(--text-dim);
  font-size:12px; font-weight:600; text-align:center;
  padding:9px 8px; box-shadow:inset 0 -1px 0 var(--border);
}
/* Layout only, so this wins the header's own first-cell geometry without also taking its
   font-weight down to a row's — the weight is set on `.pmx-row > .pmx-what` instead. */
body.alt-app .pmx-table .pmx-what{
  text-align:left; vertical-align:top;
  width:42%; min-width:290px; padding:9px 12px;
}
/* The four role columns split what the capability column leaves (42% + 4×14.5% = 100), measured
   against the mockup so "Organization Owner" doesn't wrap to two lines. */
body.alt-app .pmx-table .pmx-h-role{ width:14.5%; }
/* The boundary between the two role axes (`organization_memberships.org_role` and
   `team_users.role`, which sys_product-rules.md forbids merging) is drawn with a left border so
   the fourth column reads as a different question without a sentence. */
body.alt-app .pmx-table .pmx-org{ border-left:1px solid var(--border); }

body.alt-app .pmx-tbody + .pmx-tbody{ border-top:1px solid var(--border); }
/* Full-strength 12px text, not a faint label — the mockup's group band reads as a section
   heading because it is one. */
body.alt-app .pmx-grouphead{
  text-align:left; background:var(--panel-2); color:var(--text);
  font-size:12px; font-weight:700; text-transform:uppercase; letter-spacing:.04em;
  padding:14px 12px 8px;
}
body.alt-app .pmx-row > .pmx-what,
body.alt-app .pmx-row > .pmx-cell{ border-top:1px solid var(--border-soft); }
body.alt-app .pmx-row > .pmx-what{ font-weight:500; }
body.alt-app .pmx-cap{ display:block; color:var(--text); }
/* The condition sits under the capability it qualifies — "sometimes" with no "when" is less
   than a plain no. Reused verbatim on the role-view cards (`.pmx-card .pmx-note` above). */
body.alt-app .pmx-note{
  display:block; margin-top:3px; font-size:12px; line-height:1.5; color:var(--text-dim);
}

body.alt-app .pmx-cell{
  text-align:center; vertical-align:top; padding:9px 8px; position:relative;
}
/* 22px at a 6px radius, the wireframe's own measured geometry — a rounded square, not a circle.
   Shared by the table, the legend and the role-view cards; one mark, drawn once. */
body.alt-app .pmx-mark{
  display:inline-flex; align-items:center; justify-content:center;
  width:22px; height:22px; border-radius:var(--a-r-sm);
  font-size:12px; font-weight:700; line-height:1;
}
/* Four states, four shapes — filled ring, dashed ring, no ring, dotted ring — told apart with
   colour removed, per sys_design-system.md. */
body.alt-app .pmx-yes{
  background:var(--green-soft); border:1.5px solid var(--green-text); color:var(--green-text);
}
body.alt-app .pmx-cond{
  background:transparent; border:1.5px dashed var(--green-text); color:var(--green-text);
}
body.alt-app .pmx-no{
  background:transparent; border:0; color:var(--text-faint);
}
body.alt-app .pmx-na{
  background:transparent; border:1.5px dotted var(--text-faint); color:var(--text-faint);
}

/* ---------- "Role rules" list content (reused footnotes) ---------- */

body.alt-app .pmx-foots{
  margin:0; padding:0 0 0 18px;
  font-size:12px; line-height:1.55; color:var(--text-dim);
}
body.alt-app .pmx-foot-item + .pmx-foot-item{ margin-top:4px; }

/* ---------- narrow widths ---------- */

/* The role cards become a one-row horizontal strip, copied from the wireframe's own phone
   breakpoint rather than left to wrap to 2x2. */
@media (max-width:720px){
  body.alt-app .pmx-roles{ display:flex; overflow-x:auto; padding-bottom:2px; }
  body.alt-app .pmx-role{ flex:0 0 200px; }
  body.alt-app .pmx-cards{ grid-template-columns:1fr; }
}

/* The table keeps its 720px floor and scrolls inside `.pmx-tablewrap` — the one thing on this
   surface allowed to be wider than the dialog. At phone height the frozen block (subtitle,
   callout, role cards, legend row) can starve `.pmx-roleview` to nearly nothing, so the legend
   row is forced to one line and each role card's one-line description is dropped — it repeats
   what the callout above already states in full sentences. */
@media (max-width:520px){
  body.alt-app .pmx-table .pmx-what{ min-width:240px; padding:9px 10px; }
  body.alt-app .pmx-table{ min-width:660px; }
  body.alt-app .pmx-rules-btn{ margin-left:0; }
  body.alt-app .pmx-rv-counts{ margin-left:0; }
  body.alt-app .pmx-roleview > .a-callout{ padding:8px 10px; }
  body.alt-app .pmx-roleview > .a-callout p{ line-height:1.35; }
  body.alt-app .pmx-role-text{ display:none; }
  body.alt-app .pmx-body{ gap:9px; }
  /* `.pmx-legend` is one non-shrinking flex child of `.pmx-legend-row`, so the row's own wrap
     can't help it; `display:contents` un-nests its items so all five (four legend items plus
     the button) wrap together and fill each line before starting the next. */
  body.alt-app .pmx-legend{ display:contents; }
}

/* ---------- SHORT WINDOWS: the same decision, on the other axis ----------
   A proportion can't express visibility — an element is either shown or it isn't — so this one
   rule is a media query rather than a clamp, applying the same "drop the role description, it's
   a gloss the cards already restate" trade the width block above makes, on height instead. 720px
   is where the scroller's share actually falls away (measured: 62% at 900px window down to
   40.5% at 640), chosen so ordinary laptop windows are left alone. */
@media (max-height:720px){
  body.alt-app .pmx-role-text{ display:none; }
}
