/**
 * Admin Panel + Admin Shell styles
 * ============================================
 * Tokenized version of the Gate-1.8 admin scaffold, plus the new shared
 * shell used by Workspace Admin and Board Admin. All colours / spacing /
 * radii / shadows route through the design tokens in css/main.css.
 *
 * Class layering:
 *   .admin-shell                — modal root (extends .modal)
 *   .admin-shell__content       — inner shell (extends .modal-content)
 *   .admin-shell__header        — title + close
 *   .admin-shell__tabs          — tab strip
 *   .admin-shell__tab           — tab button (.is-active variant)
 *   .admin-shell__body          — active tab content
 *
 * Within a tab body, panels use the shared utility classes:
 *   .ap-panel                   — standard tab content wrapper
 *   .ap-row, .ap-table          — row + table primitives
 *   .ap-error, .ap-empty        — feedback states
 *   .ap-chip(s)                 — filter chips (audit tab)
 *   .ap-radio-group             — privacy / entry-mode radios
 * ============================================
 */

/* ============================================================
   1. SHELL ROOT (extends .modal from style.css)
   ============================================================ */

.admin-shell {
  /* .modal already provides position/inset/z-index/backdrop. */
  align-items: flex-start;
  justify-content: center;
  padding: 40px 16px;
  overflow: auto;
}

.admin-shell__content {
  max-width: 960px;
  width: 100%;
  background: var(--surface-primary);
  color: var(--text-primary);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-xl);
  overflow: hidden;
  display: flex;
  flex-direction: column;
  max-height: calc(100vh - 80px);
}

/* ============================================================
   2. HEADER
   ============================================================ */

.admin-shell__header {
  display: flex;
  align-items: center;
  gap: var(--spacing-md);
  padding: var(--spacing-md) var(--spacing-lg);
  border-bottom: 1px solid var(--border-color);
  background: var(--surface-primary);
}

.admin-shell__title-group {
  display: flex;
  align-items: center;
  gap: var(--spacing-sm);
  margin-right: auto;
  min-width: 0;
}

.admin-shell__title-group i {
  color: var(--primary-500);
  font-size: var(--font-size-lg);
}

/* Brand tile + subtitle (2026-09-07): the header anatomy shared with
   Fairness / Insights / Quota (css/panel-chrome.css). */
.admin-shell__icon {
  width: 32px;
  height: 32px;
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--radius-sm, 8px);
  background: var(--cmx-4fc9339359);
  background: color-mix(in srgb, var(--primary-500, #14b8a6) 12%, transparent);
  color: var(--primary-600, #0d9488);
}
.admin-shell__icon i {
  font-size: 14px;
  color: inherit;
}
.admin-shell__title-text {
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.admin-shell__subtitle {
  display: block;
  font-size: 11px;
  font-weight: 500;
  color: var(--text-secondary, #64748b);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
body.dark-mode .admin-shell__icon {
  background: rgba(45, 212, 191, 0.14);
  color: #5eead4;
}
@media (max-width: 640px) {
  .admin-shell__subtitle { white-space: normal; }
}

.admin-shell__title {
  font-size: 15px;
  line-height: 1.2;
  font-weight: var(--font-weight-semibold);
  color: var(--text-primary);
  margin: 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.admin-shell__close {
  width: 32px;
  height: 32px;
  border-radius: var(--radius-full, 9999px);
  border: none;
  background: transparent;
  color: var(--text-secondary);
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  transition: background var(--transition-fast), color var(--transition-fast);
}

.admin-shell__close:hover,
.admin-shell__close:focus-visible {
  background: var(--bg-secondary);
  color: var(--text-primary);
  outline: none;
}

/* ============================================================
   3. TABS
   ============================================================ */

.admin-shell__tabs {
  display: flex;
  gap: var(--spacing-xs);
  padding: var(--spacing-sm) var(--spacing-md);
  border-bottom: 1px solid var(--border-color);
  background: var(--bg-secondary);
  overflow-x: auto;
  scrollbar-width: none;
}

.admin-shell__tabs::-webkit-scrollbar { display: none; }

.admin-shell__tab {
  appearance: none;
  border: none;
  background: transparent;
  color: var(--text-secondary);
  padding: var(--spacing-sm) var(--spacing-md);
  border-radius: var(--radius-sm);
  cursor: pointer;
  font: inherit;
  font-size: var(--font-size-sm);
  font-weight: var(--font-weight-medium);
  display: inline-flex;
  align-items: center;
  gap: var(--spacing-xs);
  transition: background var(--transition-fast), color var(--transition-fast),
              box-shadow var(--transition-fast);
  white-space: nowrap;
  /* 2026-08-06: the strip is a flex row with overflow-x:auto, and flex
     children shrink below their content width by default. With nowrap
     forbidding a line break, each tab's label overflowed its own button and
     collided with the next one ("MembersSeats", "FairnessSecurity") on any
     viewport too narrow for all tabs. Refusing to shrink is what makes the
     horizontal scroll actually engage. */
  flex-shrink: 0;
}

.admin-shell__tab i {
  font-size: 0.95em;
  opacity: 0.85;
}

.admin-shell__tab:hover {
  background: var(--color-background-elevated);
  color: var(--text-primary);
}

.admin-shell__tab.is-active {
  background: var(--surface-primary);
  color: var(--text-primary);
  box-shadow: var(--shadow-sm);
}

.admin-shell__tab.is-active i {
  color: var(--primary-500);
  opacity: 1;
}

.admin-shell__tab:focus-visible {
  outline: none;
  box-shadow: 0 0 0 3px var(--color-primary-light);
}

/* ============================================================
   4. BODY
   ============================================================ */

.admin-shell__body {
  flex: 1;
  padding: var(--spacing-lg);
  min-height: 240px;
  overflow-y: auto;
}

/* ============================================================
   5. SKELETON / EMPTY / ERROR
   ============================================================ */

.admin-shell__skeleton {
  display: flex;
  flex-direction: column;
  gap: var(--spacing-sm);
}

.admin-shell__skeleton-row {
  height: 36px;
  border-radius: var(--radius-sm);
  background: linear-gradient(
    90deg,
    var(--bg-secondary) 0%,
    var(--bg-primary) 50%,
    var(--bg-secondary) 100%
  );
  background-size: 200% 100%;
  animation: admin-shell-shimmer 1.4s infinite;
}

@keyframes admin-shell-shimmer {
  0%   { background-position: 200% 0; }
  100% { background-position: -200% 0; }
}

.admin-shell__empty {
  text-align: center;
  padding: var(--spacing-2xl) var(--spacing-md);
  color: var(--text-secondary);
}

.admin-shell__empty-icon {
  width: 56px;
  height: 56px;
  border-radius: var(--radius-full);
  background: var(--color-primary-light);
  color: var(--primary-500);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: var(--font-size-xl);
  margin-bottom: var(--spacing-md);
}

.admin-shell__empty-title {
  font-size: var(--font-size-base);
  font-weight: var(--font-weight-semibold);
  color: var(--text-primary);
  margin: 0 0 var(--spacing-xs);
}

.admin-shell__empty-body {
  font-size: var(--font-size-sm);
  color: var(--text-secondary);
  margin: 0;
  max-width: 360px;
  margin-inline: auto;
}

/* P2-22: primary CTA rendered inside the empty state body. */
.admin-shell__empty-action {
  margin-top: var(--spacing-md, 16px);
}

.admin-shell__error {
  display: flex;
  align-items: flex-start;
  gap: var(--spacing-sm);
  background: var(--error-light);
  color: var(--error);
  padding: var(--spacing-sm) var(--spacing-md);
  border-radius: var(--radius-sm);
  border: 1px solid rgba(239, 68, 68, 0.25);
  font-size: var(--font-size-sm);
}

.admin-shell__error i {
  margin-top: 2px;
  flex-shrink: 0;
}

.admin-shell__error-text {
  flex: 1;
  color: inherit;
}

.admin-shell__error-retry {
  margin-left: auto;
}

/* ============================================================
   6. PANEL PRIMITIVES (used inside tab bodies)
   ============================================================ */

.ap-panel {
  display: flex;
  flex-direction: column;
  gap: var(--spacing-md);
}

.ap-panel__header {
  display: flex;
  align-items: center;
  gap: var(--spacing-sm);
  flex-wrap: wrap;
}

.ap-panel__title {
  font-size: var(--font-size-base);
  font-weight: var(--font-weight-semibold);
  color: var(--text-primary);
  margin: 0;
}

.ap-panel__hint {
  color: var(--text-secondary);
  font-size: var(--font-size-sm);
  margin: 0;
}

.ap-panel__actions {
  display: flex;
  gap: var(--spacing-sm);
  margin-left: auto;
}

/* Panel-footer variant: actions that sit under content rather than in the
   header row (Billing's "Manage billing"). Replaces the inline
   margin-top/margin-left styles the JS used to carry. */
.ap-panel__actions--top-gap {
  margin-top: var(--spacing-md);
  margin-left: 0;
}

/* Generic action row inside a section (Security save/clear, seat assign).
   Was referenced by the JS but never defined — it only rendered because of
   a duplicated inline style. */
.ap-actions {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--spacing-sm);
  margin-top: var(--spacing-md);
}

/* Avatar + name/text cell layout, used in every member-ish table cell.
   Replaces the 3× duplicated inline flex style. */
.ap-cell-flex {
  display: flex;
  align-items: center;
  gap: 10px;
}

.ap-cell-right {
  text-align: right;
}

/* Section heading row with a right-aligned action (Seats header). */
.ap-section__row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--spacing-sm);
}

.ap-section__hint--tight {
  margin-top: 6px;
}

/* Inline assign-seat form (Seats tab). */
.ap-assign-form .ap-input {
  flex: 1 1 180px;
  min-width: 0;
  padding: var(--spacing-xs) var(--spacing-sm);
  border: 1px solid var(--border-color);
  border-radius: var(--radius-sm);
  background: var(--surface-primary);
  color: var(--text-primary);
  font-size: var(--font-size-sm);
}

.ap-assign-form .ap-role-select {
  flex: 1 1 180px;
  min-width: 0;
}

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

.ap-table {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--font-size-sm);
}

.ap-table th,
.ap-table td {
  text-align: left;
  padding: var(--spacing-sm) var(--spacing-md);
  border-bottom: 1px solid var(--color-border-light);
  vertical-align: middle;
}

.ap-table th {
  font-weight: var(--font-weight-semibold);
  color: var(--text-secondary);
  background: var(--bg-secondary);
  position: sticky;
  top: 0;
  z-index: 1;
}

.ap-table tbody tr:hover {
  background: var(--bg-secondary);
}

.ap-table__name {
  font-weight: var(--font-weight-medium);
  color: var(--text-primary);
}

.ap-table__sub {
  color: var(--text-secondary);
  font-size: var(--font-size-xs);
  /* A long address has no spaces to wrap at, so without this it widens its
     column and pushes the table past the panel — measured at 700-1280px with
     a 60-character address. Breaking mid-string is the lesser evil: the
     alternative is a sideways-scrolling desktop table. (2026-09-02) */
  overflow-wrap: anywhere;
}

/* ---- Avatars ---- */

.ap-avatar {
  width: 32px;
  height: 32px;
  border-radius: var(--radius-full);
  background: var(--color-primary-light);
  color: var(--primary-500);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: var(--font-size-xs);
  font-weight: var(--font-weight-semibold);
  letter-spacing: 0.02em;
  flex-shrink: 0;
}

/* ---- Role badges + dropdown ---- */

.ap-role-select {
  appearance: none;
  border: 1px solid var(--border-color);
  background: var(--surface-primary);
  color: var(--text-primary);
  font: inherit;
  font-size: var(--font-size-sm);
  padding: 6px 28px 6px 10px;
  border-radius: var(--radius-sm);
  cursor: pointer;
  transition: border-color var(--transition-fast), box-shadow var(--transition-fast);
  background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 8' fill='none' stroke='%2364748b' stroke-width='1.5'%3E%3Cpath d='M1 1l5 5 5-5'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 8px center;
  background-size: 10px;
}

.ap-role-select:focus-visible {
  outline: none;
  border-color: var(--color-border-focus);
  box-shadow: 0 0 0 3px var(--color-primary-light);
}

.ap-role-select[disabled] {
  background-color: var(--bg-secondary);
  color: var(--text-muted);
  cursor: not-allowed;
}

.ap-role-badge {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 2px 8px;
  border-radius: var(--radius-full);
  font-size: var(--font-size-xs);
  font-weight: var(--font-weight-semibold);
  background: var(--bg-secondary);
  color: var(--text-secondary);
}

.ap-role-badge--owner  { background: rgba(124, 58, 237, 0.12); color: #7c3aed; }
.ap-role-badge--admin  { background: var(--color-primary-light); color: var(--primary-600); }
.ap-role-badge--member { background: var(--info-light); color: var(--info); }
.ap-role-badge--viewer { background: var(--bg-secondary); color: var(--text-secondary); }

.ap-you-badge {
  display: inline-flex;
  align-items: center;
  padding: 1px 6px;
  border-radius: var(--radius-full);
  background: var(--primary-500);
  color: #fff;
  font-size: 10px;
  font-weight: var(--font-weight-semibold);
  margin-left: 6px;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

/* ---- Status pills (calendar / connection) ---- */

.ap-status-pill {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 2px 8px;
  border-radius: var(--radius-full);
  font-size: var(--font-size-xs);
  font-weight: var(--font-weight-medium);
}

.ap-status-pill--ok      { background: var(--success-light); color: var(--success); }
.ap-status-pill--pending { background: var(--warning-light); color: var(--warning); }
.ap-status-pill--off     { background: var(--bg-secondary); color: var(--text-muted); }
/* Invite-sourced / guest status (2026-06-19, People-surface consolidation).
   Reuses the SAME defined warning tokens as --pending so it reads as one amber
   "not-yet-a-full-member" family across the app — deliberately NOT the session
   badge's undefined --warning-100/--warning-700 fallback tokens. The members
   table has no 'invited'/'guest' status row yet (members data is active/pending/
   suspended only), so this class is currently inert — it's the visual vocabulary
   ready for the P3 step that surfaces invite-sourced rows. Adding the class now
   is harmless and lets P3 be CSS-free. */
.ap-status-pill--guest   { background: var(--warning-light); color: var(--warning); }

/* ---- Buttons (re-use .btn / .btn-primary / .btn-secondary / .btn-danger) ---- */

.ap-btn-icon {
  width: 32px;
  height: 32px;
  border-radius: var(--radius-sm);
  border: 1px solid transparent;
  background: transparent;
  color: var(--text-secondary);
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  transition: all var(--transition-fast);
}

.ap-btn-icon:hover {
  background: var(--bg-secondary);
  color: var(--text-primary);
}

/* The word next to a row action's icon. Desktop has hover, so the `title`
   tooltip carries the meaning and the button stays a compact 32px square;
   the label is suppressed here and revealed in the phone block, where there
   is no hover to reveal anything. (2026-09-02) */
.ap-btn-icon__label {
  display: none;
}

.ap-btn-icon--danger:hover {
  background: var(--error-light);
  color: var(--error);
}

/* ---- Filter chips (audit tab) ---- */

.ap-chips {
  display: flex;
  flex-wrap: wrap;
  gap: var(--spacing-xs);
}

.ap-chip {
  appearance: none;
  border: 1px solid var(--border-color);
  background: var(--surface-primary);
  color: var(--text-secondary);
  font: inherit;
  font-size: var(--font-size-xs);
  font-weight: var(--font-weight-medium);
  padding: 4px 10px;
  border-radius: var(--radius-full);
  cursor: pointer;
  transition: all var(--transition-fast);
}

.ap-chip:hover {
  border-color: var(--color-border-focus);
  color: var(--text-primary);
}

.ap-chip.is-active {
  background: var(--primary-500);
  border-color: var(--primary-500);
  color: #fff;
}

/* ---- Filter bar ---- */

.ap-filter {
  display: flex;
  gap: var(--spacing-sm);
  align-items: center;
  flex-wrap: wrap;
  margin-bottom: var(--spacing-sm);
}

.ap-filter input,
.ap-filter select {
  padding: 6px 10px;
  border: 1px solid var(--border-color);
  border-radius: var(--radius-sm);
  background: var(--surface-primary);
  color: var(--text-primary);
  font: inherit;
  font-size: var(--font-size-sm);
  transition: border-color var(--transition-fast), box-shadow var(--transition-fast);
}

.ap-filter input:focus,
.ap-filter select:focus {
  outline: none;
  border-color: var(--color-border-focus);
  box-shadow: 0 0 0 3px var(--color-primary-light);
}

/* ---- Pagination ---- */

.ap-pager {
  display: flex;
  justify-content: flex-end;
  align-items: center;
  gap: var(--spacing-sm);
  margin-top: var(--spacing-md);
  font-size: var(--font-size-sm);
  color: var(--text-secondary);
}

/* ---- Definition list (billing) ---- */

.ap-dl {
  display: grid;
  grid-template-columns: minmax(120px, 30%) 1fr;
  gap: var(--spacing-xs) var(--spacing-md);
  margin: 0;
}

.ap-dl dt {
  color: var(--text-secondary);
  font-size: var(--font-size-sm);
  font-weight: var(--font-weight-medium);
  padding: var(--spacing-xs) 0;
}

.ap-dl dd {
  color: var(--text-primary);
  font-size: var(--font-size-sm);
  margin: 0;
  padding: var(--spacing-xs) 0;
}

/* ---- Radio groups (privacy / entry-mode) ---- */

.ap-radio-group {
  display: flex;
  flex-direction: column;
  gap: var(--spacing-xs);
  border: 1px solid var(--border-color);
  border-radius: var(--radius-md);
  padding: var(--spacing-xs);
  background: var(--surface-primary);
}

.ap-radio {
  display: flex;
  gap: var(--spacing-sm);
  align-items: flex-start;
  padding: var(--spacing-sm) var(--spacing-md);
  border-radius: var(--radius-sm);
  cursor: pointer;
  transition: background var(--transition-fast);
}

.ap-radio:hover {
  background: var(--bg-secondary);
}

.ap-radio input[type="radio"] {
  margin-top: 3px;
  accent-color: var(--primary-500);
  flex-shrink: 0;
}

.ap-radio-content {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}

.ap-radio-title {
  font-size: var(--font-size-sm);
  font-weight: var(--font-weight-medium);
  color: var(--text-primary);
}

.ap-radio-desc {
  font-size: var(--font-size-xs);
  color: var(--text-secondary);
  line-height: 1.4;
}

/* A caveat that qualifies the WHOLE radio group, not one option. Sits under
   the last choice, indented to the option text so it reads as a footnote to
   the set rather than a fourth option. (2026-09-02) */
.ap-radio-note {
  margin: 2px var(--spacing-md) 2px;
  padding-top: var(--spacing-xs);
  border-top: 1px solid var(--color-border-light);
  font-size: var(--font-size-xs);
  line-height: 1.45;
  color: var(--text-secondary);
}

body.dark-mode .ap-radio-note,
.dark-mode .ap-radio-note {
  color: var(--color-dark-text-secondary);
  border-top-color: var(--color-dark-border);
}

.ap-radio.is-selected {
  background: var(--color-primary-light);
}

.ap-radio.is-selected .ap-radio-title {
  color: var(--primary-600);
}

/* ---- Section card ---- */

.ap-section {
  border: 1px solid var(--border-color);
  border-radius: var(--radius-md);
  padding: var(--spacing-md);
  background: var(--surface-primary);
  display: flex;
  flex-direction: column;
  gap: var(--spacing-sm);
}

.ap-section__heading {
  font-size: var(--font-size-sm);
  font-weight: var(--font-weight-semibold);
  color: var(--text-primary);
  margin: 0;
  display: flex;
  align-items: center;
  gap: var(--spacing-xs);
}

.ap-section__heading i {
  color: var(--primary-500);
}

.ap-section__hint {
  font-size: var(--font-size-xs);
  color: var(--text-secondary);
  margin: 0;
  line-height: 1.45;
}

/* Immutable-at-creation statement (2026-08-20). Used where a setting operates
   BENEATH a ceiling frozen when the group was created — the founding
   visibility class. It is a FACT, not a hint, so it reads darker than
   .ap-section__hint and carries a lock: the owner's guarantee ("groups locked
   into the visibility assigned at creation") has to be legible before the
   restrict-only choice below it, not buried in a trailing clause. */
.ap-section__lock {
  display: flex;
  align-items: flex-start;
  gap: 8px;
  margin: 0 0 6px;
  padding: 8px 10px;
  border-radius: 8px;
  background: var(--slate-100, #f1f5f9);
  border: 1px solid var(--border-color, #e2e8f0);
  font-size: var(--font-size-xs);
  line-height: 1.45;
  color: var(--text-primary, #1e293b);
}
.ap-section__lock i {
  margin-top: 2px;
  color: var(--text-secondary, #64748b);
}
body.dark-mode .ap-section__lock {
  background: var(--slate-800, #1e293b);
  border-color: var(--slate-700, #334155);
  color: var(--slate-100, #f1f5f9);
}

/* Security tab — email-domain allowlist editor. Mirrors .ap-filter inputs so
   the multi-line control matches the rest of the panel's form styling. */
.ap-textarea {
  width: 100%;
  margin-top: var(--spacing-sm);
  padding: var(--spacing-sm) var(--spacing-md);
  border: 1px solid var(--border-color);
  border-radius: var(--radius-sm);
  background: var(--surface-primary);
  color: var(--text-primary);
  font: inherit;
  font-size: var(--font-size-sm);
  line-height: 1.5;
  resize: vertical;
  transition: border-color var(--transition-fast), box-shadow var(--transition-fast);
}

.ap-textarea:focus {
  outline: none;
  border-color: var(--color-border-focus);
  box-shadow: 0 0 0 3px var(--color-primary-light);
}

/* .ap-mini-profile* deleted 2026-08-06: zero usages anywhere in js/ — the
   review-dashboard inline popover it was written for never shipped. */

/* ============================================================
   7. DARK MODE
   ============================================================ */

body.dark-mode .admin-shell__content,
.dark-mode .admin-shell__content {
  background: var(--color-dark-surface);
  color: var(--color-dark-text);
}

body.dark-mode .admin-shell__header,
.dark-mode .admin-shell__header {
  background: var(--color-dark-surface);
  border-bottom-color: var(--color-dark-border);
}

body.dark-mode .admin-shell__title,
.dark-mode .admin-shell__title {
  color: var(--color-dark-text);
}

body.dark-mode .admin-shell__close,
.dark-mode .admin-shell__close {
  color: var(--color-dark-text-secondary);
}

body.dark-mode .admin-shell__close:hover,
.dark-mode .admin-shell__close:hover {
  background: var(--color-dark-bg-elevated);
  color: var(--color-dark-text);
}

body.dark-mode .admin-shell__tabs,
.dark-mode .admin-shell__tabs {
  background: var(--color-dark-bg-secondary);
  border-bottom-color: var(--color-dark-border);
}

body.dark-mode .admin-shell__tab.is-active,
.dark-mode .admin-shell__tab.is-active {
  background: var(--color-dark-surface);
  color: var(--color-dark-text);
}

body.dark-mode .ap-table th,
.dark-mode .ap-table th {
  background: var(--color-dark-bg-secondary);
  color: var(--color-dark-text-secondary);
}

body.dark-mode .ap-table tbody tr:hover,
.dark-mode .ap-table tbody tr:hover {
  background: var(--color-dark-bg-secondary);
}

/* ⚠️CELL TEXT HAD NO DARK-MODE COLOUR (2026-09-02). Plain-text cells — the
   "Joined" date is the only one today — inherited a light-mode ink and
   rendered rgb(30,27,75) on the rgb(15,23,42) card: ~1.2:1, far under the
   4.5:1 floor, effectively invisible. It survived unnoticed because on the
   desktop table the row sits on a different background; the phone card is
   what made it obvious. Set the colour rather than relying on inheritance. */
body.dark-mode .ap-table td,
.dark-mode .ap-table td {
  color: var(--color-dark-text);
}

body.dark-mode .ap-table__sub,
.dark-mode .ap-table__sub {
  color: var(--color-dark-text-secondary);
}

/* The stacked cards and their per-cell labels (phone widths). */
body.dark-mode .ap-table tbody tr,
.dark-mode .ap-table tbody tr {
  background: var(--color-dark-surface);
  border-color: var(--color-dark-border);
}

body.dark-mode .ap-table td[data-label]::before,
.dark-mode .ap-table td[data-label]::before {
  color: var(--color-dark-text-secondary);
}

body.dark-mode .ap-btn-icon,
.dark-mode .ap-btn-icon {
  color: var(--color-dark-text-secondary);
}

body.dark-mode .ap-role-select,
.dark-mode .ap-role-select {
  background-color: var(--color-dark-surface);
  color: var(--color-dark-text);
  border-color: var(--color-dark-border);
}

body.dark-mode .ap-section,
body.dark-mode .ap-radio-group,
.dark-mode .ap-section,
.dark-mode .ap-radio-group {
  background: var(--color-dark-surface);
  border-color: var(--color-dark-border);
}

body.dark-mode .ap-radio:hover,
.dark-mode .ap-radio:hover {
  background: var(--color-dark-bg-secondary);
}

body.dark-mode .ap-chip,
.dark-mode .ap-chip {
  background: var(--color-dark-surface);
  border-color: var(--color-dark-border);
  color: var(--color-dark-text-secondary);
}

body.dark-mode .ap-assign-form .ap-input,
.dark-mode .ap-assign-form .ap-input {
  background: var(--color-dark-surface);
  border-color: var(--color-dark-border);
  color: var(--color-dark-text);
}

/* ============================================================
   8. RESPONSIVE
   ============================================================ */

/* ⚠️TWO BREAKPOINTS, DELIBERATELY DIFFERENT (2026-09-02).
   The full-height sheet is a PHONE decision (<=640px). Whether the member
   table still FITS is a different question with a different answer: measured
   on the real panel, the columns stop fitting below ~780px, so 641-779px —
   small tablets, a half-screen desktop window — was left rendering a table
   that overflowed its panel by up to 127px. The card layout therefore starts
   at 780px, above the sheet breakpoint, so the two regimes meet with no gap.
   ⛔Do not "tidy" these into one number without re-measuring: collapsing to
   640 restores the overflow band; collapsing to 780 makes tablets render a
   full-bleed sheet they have the room to avoid. */
/* ---- Phone chrome: the modal becomes a full-bleed sheet (<=640px) ---- */
@media (max-width: 640px) {
  /* Full-height sheet on phones. The old rule kept a floating card with a
     40px inset, which — combined with the 6-tab strip and wide member/seat
     tables — overflowed the viewport and clipped controls (the "workspace
     mobile layout is broken" report, 2026-07-12). Go edge-to-edge and let the
     body scroll, honouring the safe-area insets used elsewhere (commit
     d4e43901). 100dvh avoids the iOS URL-bar 100vh gap. */
  .admin-shell {
    padding: 0;
    align-items: stretch;
  }
  .admin-shell__content {
    max-width: none;
    width: 100%;
    height: 100dvh;
    max-height: 100dvh;
    border-radius: 0;
    padding-top: env(safe-area-inset-top, 0px);
    padding-bottom: env(safe-area-inset-bottom, 0px);
  }

  .admin-shell__header { padding: var(--spacing-sm) var(--spacing-md); }

  /* Three tabs fit a phone if they stop reserving room for icons that add
     nothing at this size. Dropping the icon lets the labels share the width
     evenly with no scroll and no clipped third tab — previously "Invitations"
     was cut off at the right edge with only the scrollbar to hint at it.
     (2026-09-02) */
  .admin-shell__tabs {
    padding: var(--spacing-xs) var(--spacing-xs);
    gap: 2px;
  }
  .admin-shell__tab {
    min-height: 40px;                 /* WCAG 2.5.5 touch target */
    flex: 1 1 0;
    min-width: 0;
    justify-content: center;
    padding: var(--spacing-sm) var(--spacing-xs);
    font-size: var(--font-size-xs);
    /* The label must be able to shrink; nowrap + flex-shrink:0 (the desktop
       rule) is what forced the strip wider than the screen. */
    flex-shrink: 1;
    overflow: hidden;
    text-overflow: ellipsis;
  }
  .admin-shell__tab i { display: none; }

  .admin-shell__body { padding: var(--spacing-md); }

  /* The title + primary action wrapped to two lines with the button stranded
     on the right of the second, which read as an orphan. Stack them: the
     count reads as a heading, the action spans the width as a real button.
     The inline `margin-left:auto` the JS sets is neutralised here. */
  .ap-panel__header {
    flex-direction: column;
    align-items: stretch;
    gap: var(--spacing-sm);
  }
  .ap-panel__header .btn {
    margin-left: 0 !important;
    width: 100%;
    justify-content: center;
    min-height: 44px;
  }

  /* Billing definition list stacks to one column. */
  .ap-dl { grid-template-columns: 1fr; gap: 0; }
  .ap-dl dt { padding-bottom: 0; }
  .ap-dl dd { padding-top: 0; padding-bottom: var(--spacing-sm); }
}

/* ---- Row layout: cards instead of a too-wide table (<=780px) ---- */
@media (max-width: 780px) {

  /* ⭐STACKED CARDS, NOT A SIDEWAYS TABLE (2026-09-02, owner: "this area seems
     unoptimized for mobile", screenshot of Group settings -> Members).
     The previous rule set `min-width: 520px` to FORCE a horizontal scroll,
     on the theory that scrolling beats crushing the columns. Measured on the
     real panel: the members table renders 884px wide inside a 288-398px
     panel, so 486-596px of it sits off-screen at every phone width tested
     (320/375/390/412/430). Everything past the member's name — Role,
     Calendar, Joined, and the ENTIRE actions column — was unreachable
     without discovering a scrollbar the layout never advertised. A pending
     join request showed no Approve/Reject at all: an admin could not approve
     a member on a phone.
     A table whose columns cannot fit is the wrong element at this width, so
     each row becomes a self-contained card and each cell carries its own
     label via data-label. Nothing is hidden, nothing needs a sideways swipe.
     Desktop keeps the table — it has the room. */
  .ap-table,
  .ap-table thead,
  .ap-table tbody,
  .ap-table tr,
  .ap-table th,
  .ap-table td {
    display: block;
  }

  /* The header row names columns that no longer exist as columns; every cell
     now labels itself. Hidden from sight AND from the a11y tree, because a
     stacked card is not a grid and announcing stale headers misleads. */
  .ap-table thead {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0 0 0 0);
    clip-path: inset(50%);
    white-space: nowrap;
    border: 0;
  }

  .ap-table {
    font-size: var(--font-size-sm);
    width: 100%;
    min-width: 0;                     /* was 520px here, and `width:auto; min-width:100%`
                                         in the <=480 block — TOGETHER they let the
                                         table size to its CONTENT (895px measured
                                         @390) instead of to its panel */
    border-collapse: separate;
    border-spacing: 0;
  }

  .ap-table tbody tr {
    border: 1px solid var(--border-color);
    border-radius: var(--radius-md);
    background: var(--surface-primary);
    padding: var(--spacing-sm) var(--spacing-md);
    margin-bottom: var(--spacing-sm);
  }

  .ap-table tbody tr:last-child { margin-bottom: 0; }

  .ap-table td {
    border-bottom: none;
    padding: 6px 0;
    white-space: normal;
    /* Label left, value right — a two-column mini-grid inside the card. */
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--spacing-md);
    min-height: 32px;
  }

  /* The label comes from the cell itself, so a column added to the table
     cannot silently render an unlabelled row on mobile. */
  .ap-table td[data-label]::before {
    content: attr(data-label);
    flex: 0 0 auto;
    color: var(--text-secondary);
    font-size: var(--font-size-xs);
    font-weight: var(--font-weight-medium);
  }

  /* The identity cell is the card's heading: full width, no label, no
     right-alignment, and it keeps its own avatar+text flex layout. */
  .ap-table td[data-label="Member"],
  .ap-table td[data-label="Sent to"] {
    display: block;
    padding-top: 2px;
    padding-bottom: var(--spacing-sm);
    margin-bottom: 2px;
    border-bottom: 1px solid var(--color-border-light);
  }
  .ap-table td[data-label="Member"]::before,
  .ap-table td[data-label="Sent to"]::before { content: none; }

  /* Actions sit on their own full-width row, left-aligned under the facts,
     with room for 40px targets. The inline `text-align:right` the JS sets is
     overridden here rather than removed there — desktop still wants it. */
  .ap-table td.ap-cell-actions {
    justify-content: flex-start;
    flex-wrap: wrap;
    gap: var(--spacing-xs);
    text-align: left !important;
    /* The JS sets `white-space:nowrap` inline for the desktop table; labelled
       pills must be free to wrap onto a second line at 320px. */
    white-space: normal !important;
    padding-top: var(--spacing-sm);
    margin-top: 2px;
    border-top: 1px solid var(--color-border-light);
  }
  .ap-table td.ap-cell-actions::before {
    /* Label the action row so the icon buttons are not an unexplained strip. */
    margin-right: auto;
  }
  /* An empty actions cell (e.g. the owner's row) must not paint a stray
     divider and 40px of blank space. */
  .ap-table td.ap-cell-actions:empty {
    display: none;
  }

  .ap-table tbody tr:hover { background: var(--surface-primary); }

  /* Touch targets: role select + icon actions meet the 44px comfortable floor
     that responsive.css used to enforce globally. */
  .ap-role-select { min-height: 40px; }

  /* A touch screen cannot hover, so the `title` tooltip that explains each
     icon is unreachable — the row offered a bare ✓ and ✕ beside a person's
     name. Show the word. The button becomes an auto-width pill rather than a
     40px square, and gets a border so it reads as pressable. (2026-09-02) */
  .ap-btn-icon {
    width: auto;
    height: auto;
    min-height: 40px;
    padding: 8px 12px;
    gap: 6px;
    border-color: var(--border-color);
    font-size: var(--font-size-sm);
    font-weight: var(--font-weight-medium);
  }
  .ap-btn-icon__label { display: inline; }
  .ap-btn-icon--danger { color: var(--error); border-color: rgba(239, 68, 68, 0.3); }
  .ap-btn-icon--ok     { color: var(--success); border-color: rgba(16, 185, 129, 0.35); }

  /* Audit filter row + panel actions stack full-width so nothing overflows. */
  .ap-filter { flex-direction: column; align-items: stretch; }
  .ap-filter input,
  .ap-filter select,
  .ap-filter .btn { width: 100%; }
  .ap-panel__actions {
    margin-left: 0;
    flex-wrap: wrap;
  }
  .ap-panel__actions .btn { flex: 1 1 auto; }

  /* Security allowlist textarea uses the full width. */
  .ap-textarea { width: 100%; }

  /* Pagination centred so Prev/Next don't crowd the right edge. */
  .ap-pager { justify-content: center; flex-wrap: wrap; }
}

/* Tight-phone hardening (<=480px).
   ⚠️This block used to re-assert the horizontal-scroll model (`width:auto`,
   `nowrap`) that the <=640px block above has now replaced with stacked cards.
   Left as it was, it would have re-broken the narrowest phones — the exact
   widths the cards exist to serve. What remains is the part that is still
   true at 320-480px: long unbroken strings (an email, a group name) must wrap
   or break rather than push the card past the viewport. */
@media (max-width: 480px) {
  .ap-panel,
  .admin-shell__body {
    overflow-x: clip;
    overscroll-behavior-x: contain;
  }

  /* An email with no spaces is the realistic overflow source here — it cannot
     wrap at a space because it has none. Break it rather than let it widen
     the card. */
  .ap-table__sub,
  .ap-table td[data-label="Sent to"] {
    overflow-wrap: anywhere;
    word-break: break-word;
  }

  .ap-table__name,
  .ap-table__sub {
    white-space: normal;
  }

  /* Reclaim horizontal room: at 320px the label + value pair is tight. */
  .ap-table tbody tr { padding: var(--spacing-sm); }
}
