/* ═══════════════════════════════════════════════════════════════════
   components.css — canonical UI primitives. Loads after tokens.css,
   before page sheets.

   Rules of the layer:
   - New UI uses these classes instead of inventing page-scoped variants
     (the codebase has 100+ *btn* classes; that number only goes down).
   - Everything references tokens.css — no raw hex values here.
   - Logical properties only (margin-inline-*, text-align: start) so RTL
     is correct by construction.
   - Buttons: .btn-primary / .btn-secondary / .btn-danger / .btn-ghost,
     sized with .btn-sm, iconified with .btn-icon.
   - New primitives are ui-prefixed (.ui-input, .ui-card, .ui-badge,
     .ui-empty, .ui-skeleton) to avoid colliding with legacy markup.
   ═══════════════════════════════════════════════════════════════════ */

/* ── Buttons ──
   The canonical trio (.btn-primary / .btn-secondary / .btn-danger) is
   defined in style.css for cascade-order compatibility with legacy
   overrides; it moves here once style.css is dismembered. This sheet
   adds the missing variant and modifiers. */

/* `display: inline-flex` on a button class outranks the UA's
   `[hidden] { display: none }` — same specificity, and the class wins on order.
   A control hidden from someone who lacks the permission therefore stayed on
   screen; the DOM said hidden, so nothing that asserted `.hidden` noticed. The
   endpoints were still gated, but the button had no business being there. */
.btn-primary[hidden],
.btn-secondary[hidden],
.btn-danger[hidden],
.btn-ghost[hidden] { display: none; }

.btn-ghost {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.35rem;
  min-height: 2.25rem;
  border-radius: 10px;
  padding: 0.5rem 0.875rem;
  border: 1px solid transparent;
  font-weight: 600;
  line-height: 1.2;
  cursor: pointer;
  text-decoration: none;
  white-space: nowrap;
  transition: background-color 0.15s, border-color 0.15s, color 0.15s, box-shadow 0.15s;
  background: transparent;
  color: var(--muted);
}

.btn-ghost:hover:not(:disabled) {
  background: var(--btn-hover);
  color: var(--text);
}

.btn-ghost:focus-visible {
  outline: 2px solid color-mix(in srgb, var(--accent) 82%, var(--text));
  outline-offset: 2px;
}

.btn-ghost:disabled {
  opacity: 0.55;
  cursor: not-allowed;
  box-shadow: none;
}

/* Size / shape modifiers */
.btn-sm {
  min-height: 1.875rem;
  padding: 0.3125rem 0.625rem;
  font-size: var(--text-sm);
  border-radius: var(--radius-md);
}

.btn-icon {
  padding: 0.5rem;
  gap: 0;
}

.btn-icon.btn-sm {
  padding: 0.3125rem;
}

/* ── Text inputs / selects / textareas ── */
.ui-input {
  box-sizing: border-box;
  width: 100%;
  min-height: 2.75rem;
  border-radius: 10px;
  border: 1px solid var(--input-b);
  background: var(--input-bg);
  color: var(--text);
  padding: 0.625rem 0.9rem;
  font-size: var(--text-md);
  font-family: inherit;
  line-height: 1.4;
  transition: border-color 0.15s, box-shadow 0.15s;
}

.ui-input::placeholder {
  color: var(--muted);
}

.ui-input:focus-visible {
  outline: none;
  border-color: color-mix(in srgb, var(--accent) 78%, white 22%);
  box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent) 30%, transparent);
}

.ui-input:disabled {
  opacity: 0.55;
  cursor: not-allowed;
}

.ui-input[aria-invalid="true"] {
  border-color: var(--danger);
}

textarea.ui-input {
  min-height: 8.5rem;
  line-height: 1.5;
  resize: vertical;
}

.ui-label {
  display: block;
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--text);
  margin-block-end: var(--space-2);
}

.ui-hint {
  font-size: var(--text-xs);
  color: var(--muted);
  margin-block-start: var(--space-1);
}

.ui-error {
  font-size: var(--text-xs);
  color: var(--danger);
  margin-block-start: var(--space-1);
}

/* ── Back-to-chat link (the one up-navigation control) ──────────────────────
   Every page outside /chat carries exactly one of these, as the FIRST child of
   its topbar — leading edge, then a divider, then the <h1>. It is deliberately
   borderless: this is up-navigation, not a page action, and bordered buttons
   are reserved for the actions in the topbar's trailing cluster.

   Before this existed the same link was hand-rolled six ways (bare ghost link,
   rail footer row, solid secondary button, ghost button with a literal "←",
   34px bordered icon square, bordered pill with a hover lift) across six i18n
   keys, in three different corners of the screen — and rendered twice at once
   on /dashboard and /inventory. Add pages to this class; do not fork it. */
.zij-back-link {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  flex-shrink: 0;
  font-size: 13px;
  font-weight: 500;
  line-height: 1.2;
  color: var(--muted);
  text-decoration: none;
  padding: 6px 8px;
  border: 1px solid transparent;
  border-radius: 8px;
  white-space: nowrap;
  transition: background-color 0.15s, color 0.15s;
}
.zij-back-link:hover {
  background: rgba(var(--accent-rgb), 0.06);
  color: var(--text);
}
.zij-back-link:focus-visible {
  outline: 2px solid color-mix(in srgb, var(--accent) 82%, var(--text));
  outline-offset: 2px;
}
.zij-back-link svg {
  width: 16px;
  height: 16px;
  flex-shrink: 0;
}
/* "Back" points toward the start of the line, which is the right in Arabic. */
[dir="rtl"] .zij-back-link svg { transform: scaleX(-1); }

/* The divider that separates the back link from the page title. */
.zij-topbar-divider {
  width: 1px;
  height: 22px;
  flex-shrink: 0;
  background: var(--border);
  margin: 0 4px;
}

/* ── Workspace switcher (workspace-nav.js) ──────────────────────────────────
   The "where do I go" control of the topbar — Dashboard, My tickets,
   Publishing, Automations, Playbooks, with the counts of what awaits the
   person — mounted into `[data-zij-workspace-nav]` on the chat page and on
   each of those five pages. The avatar menu keeps the things about the person.
   Same visual family as the avatar trigger and dropdown so the two read as one
   cluster; logical properties throughout so RTL needs no overrides. Colours are
   tokens only: the icon tile follows the workspace accent, the count pill is the
   semantic warn colour every "waiting on you" badge already uses. */
.ws-nav {
  position: relative;
  display: inline-flex;
  flex-shrink: 0;
}
.ws-nav-trigger {
  min-height: 32px;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding-block: 2px;
  padding-inline: 5px 8px;
  border-radius: 12px;
  border: 1px solid color-mix(in srgb, var(--card-b) 75%, transparent);
  background: color-mix(in srgb, var(--surface) 65%, transparent);
  color: var(--text);
  font: inherit;
  font-size: 12px;
  font-weight: 600;
  line-height: 1.2;
  white-space: nowrap;
  cursor: pointer;
  transition: background-color 0.15s, border-color 0.15s;
}
.ws-nav-trigger:hover {
  background: var(--btn-hover);
}
.ws-nav-trigger[aria-expanded="true"] {
  background: var(--btn-hover);
  border-color: var(--accent);
}
.ws-nav-trigger:focus-visible {
  outline: 2px solid color-mix(in srgb, var(--accent) 82%, var(--text));
  outline-offset: 2px;
}
.ws-nav-trigger-icon,
.ws-nav-item-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  border-radius: 7px;
  background: color-mix(in srgb, var(--accent) 8%, var(--surface));
  border: 1px solid color-mix(in srgb, var(--accent) 16%, var(--card-b));
  color: color-mix(in srgb, var(--accent) 78%, var(--text));
}
.ws-nav-trigger-icon { width: 22px; height: 22px; }
.ws-nav-item-icon { width: 26px; height: 26px; }
.ws-nav-trigger-icon svg,
.ws-nav-item-icon svg,
.ws-nav-caret {
  width: 13px;
  height: 13px;
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.ws-nav-caret { width: 14px; height: 14px; color: var(--muted); }
.ws-nav-count,
.ws-nav-item-count {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 18px;
  height: 18px;
  padding-inline: 5px;
  border-radius: 999px;
  font-size: 11px;
  font-weight: 700;
  line-height: 1;
  letter-spacing: 0;
  background: var(--warn);
  color: var(--accent-contrast, #14172E);
}
/* `display: inline-flex` outranks the UA's [hidden] rule (see .ui-badge). */
.ws-nav-count[hidden],
.ws-nav-item-count[hidden],
.ws-nav-item[hidden] { display: none; }
.ws-nav-menu {
  position: absolute;
  top: calc(100% + 8px);
  inset-inline-end: 0;
  z-index: 45;
  width: min(260px, calc(100vw - 24px));
  padding: 6px;
  border-radius: 14px;
  border: 1px solid color-mix(in srgb, var(--card-b) 74%, transparent);
  background: color-mix(in srgb, var(--card) 96%, transparent);
  box-shadow: 0 18px 38px rgba(2, 6, 23, 0.28);
  display: flex;
  flex-direction: column;
  gap: 1px;
  text-align: start;
  opacity: 0;
  transform: translateY(-5px) scale(0.98);
  pointer-events: none;
  visibility: hidden;
  /* Visibility flips at once on open (so the first row can take keyboard
     focus immediately) and only after the fade on close. */
  transition: opacity 160ms ease, transform 160ms ease, visibility 0s linear 160ms;
}
.ws-nav-menu.is-open {
  opacity: 1;
  transform: none;
  pointer-events: auto;
  visibility: visible;
  transition: opacity 160ms ease, transform 160ms ease, visibility 0s;
}
.ws-nav-menu.is-start {
  inset-inline-end: auto;
  inset-inline-start: 0;
}
.ws-nav-item {
  display: flex;
  align-items: center;
  gap: 9px;
  min-height: 34px;
  padding: 5px 8px;
  border-radius: 9px;
  color: var(--text);
  text-decoration: none;
  font-size: 12.5px;
  font-weight: 600;
  line-height: 1.3;
  transition: background-color 0.15s ease;
}
.ws-nav-item:hover,
.ws-nav-item:focus-visible {
  background: var(--btn-hover);
  outline: none;
}
.ws-nav-item:focus-visible {
  box-shadow: inset 0 0 0 2px color-mix(in srgb, var(--accent) 70%, transparent);
}
.ws-nav-item.is-current {
  background: color-mix(in srgb, var(--accent) 10%, transparent);
}
.ws-nav-item.is-current .ws-nav-item-icon {
  background: color-mix(in srgb, var(--accent) 22%, var(--surface));
  border-color: color-mix(in srgb, var(--accent) 40%, var(--card-b));
}
.ws-nav-item-label {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
@media (max-width: 640px) {
  /* Icon + count only: the label is the first thing to give on a phone. */
  .ws-nav-trigger-label { display: none; }
}

/* ── Page topbars on phones ─────────────────────────────────────────────────
   The destination pages' topbars (tickets, playbooks, publishing, automations)
   are one flex row: back link · mark · title + subtitle · spacer · actions ·
   lockup. A phone cannot hold that row: the subtitle folded into five lines
   and the language toggle ran off the right edge. Below 768px the bar becomes
   two rows — the title's, then the actions' — the subtitle and the decorative
   mark step aside, and the back link keeps its chevron only. The dashboard
   command bar has its own phone layout in dashboard.css and is not tagged. */
@media (max-width: 767px) {
  /* `header.` outranks the page's own `.xx-topbar` rule, which is declared
     later (inline <style>) at equal specificity and would keep its padding. */
  header.zij-page-topbar {
    flex-wrap: wrap;
    gap: 0 10px;
    padding: 8px 12px;
  }
  .zij-page-topbar .zij-back-link {
    padding: 6px;
    gap: 0;
  }
  .zij-page-topbar .zij-back-link span,
  .zij-page-topbar .zij-topbar-divider,
  .zij-page-topbar .zij-topbar-mark,
  .zij-page-topbar h1 + p { display: none; }
  .zij-page-topbar h1 {
    font-size: 16px;
    line-height: 1.25;
  }
  /* Row 1: back link · title · lockup. Row 2: the page's actions. The spacer
     becomes the row break (8px tall, so no row-gap is needed and none is
     doubled around it); everything after it drops to row 2, except the
     lockup, which comes back up to close the title row. */
  .zij-page-topbar .zij-topbar-break {
    order: 1;
    flex: 1 0 100%;
    height: 8px;
  }
  .zij-page-topbar .zij-topbar-break ~ * { order: 2; }
  .zij-page-topbar .zij-topbar-break ~ [data-zij-lockup],
  .zij-page-topbar [data-zij-lockup] {
    order: 0;
    margin-inline-start: auto;
  }

  /* Touch floor. The Workspace control and its rows were 32px and 35px — below
     the 40px minimum every other control on these pages already clears, and the
     one dashboard-mobile.smoke.mjs enforces. Phone-only: the desktop sizes are
     deliberate density next to the avatar, and a pointer does not need 40px. */
  .ws-nav-trigger {
    min-height: 40px;
    padding-inline: 8px 10px;
  }
  .ws-nav-item {
    min-height: 40px;
  }
}

/* ── New-ticket form (Admin → Tickets) ──────────────────────────────────────
   `.settings-modal-body` only sets top padding — it is a container for the
   settings grids, not a form layout. This gives the stacked label/field pairs
   their own rhythm so the fields do not run together. */
.ticket-new-form {
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.ticket-new-form .ui-label { margin-top: 12px; }
.ticket-new-form .ui-label:first-child { margin-top: 0; }
.ticket-new-form .ui-hint { margin: 2px 0 0; }
/* The form-level message slot is shared with API errors, so the field at fault
   is marked as well — a sentence at the foot of a form does not point. */
.ticket-new-form .input-field[aria-invalid="true"] {
  border-color: var(--danger);
}
.ticket-new-body {
  min-height: 7rem;
  resize: vertical;
  font-family: inherit;
  line-height: 1.5;
}

/* ── Cards ── */
.ui-card {
  background: var(--card);
  border: 1px solid var(--card-b);
  border-radius: var(--radius-lg);
  padding: var(--space-5);
}

.ui-card--raised {
  box-shadow: var(--shadow-md);
}

/* ── Badges ── */
.ui-badge {
  display: inline-flex;
  align-items: center;
  gap: 0.25rem;
  padding: 0.125rem 0.5rem;
  border-radius: var(--radius-full);
  font-size: var(--text-xs);
  font-weight: 600;
  line-height: 1.6;
  background: var(--btn-hover);
  color: var(--muted);
}

/* `display: inline-flex` above outranks the UA's `[hidden] { display: none }`,
   so a hidden badge rendered as an empty grey pill — visible on any page that
   hides one (the Organization header's scope badge was the first sighting). */
.ui-badge[hidden] { display: none; }

.ui-badge--ok     { background: var(--ok-soft);     color: var(--ok); }
.ui-badge--warn   { background: var(--warn-soft);   color: var(--warn); }
.ui-badge--danger { background: var(--danger-soft); color: var(--danger); }
.ui-badge--info   { background: var(--info-soft);   color: var(--info); }
.ui-badge--accent { background: var(--accent-soft); color: var(--accent); }

/* ── Empty state ── */
.ui-empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  padding: var(--space-10) var(--space-5);
  text-align: center;
  color: var(--muted);
  border: 1px dashed var(--surface-b);
  border-radius: var(--radius-lg);
}

.ui-empty-title {
  font-size: var(--text-lg);
  font-weight: 600;
  color: var(--text);
}

/* ── Loading skeleton ── */
.ui-skeleton {
  border-radius: var(--radius-sm);
  background: linear-gradient(
    90deg,
    var(--surface-b) 25%,
    color-mix(in srgb, var(--surface-b) 55%, var(--surface)) 50%,
    var(--surface-b) 75%
  );
  background-size: 200% 100%;
  animation: ui-skeleton-shimmer 1.4s ease-in-out infinite;
  color: transparent;
  -webkit-user-select: none;
  user-select: none;
}

@keyframes ui-skeleton-shimmer {
  0%   { background-position: 200% 0; }
  100% { background-position: -200% 0; }
}

@media (prefers-reduced-motion: reduce) {
  .ui-skeleton { animation: none; }
}

/* ── ZIJ lockup (layer 0) ───────────────────────────────────────────────────
   The permanent product attribution that sits beside the workspace's own
   branding. Tenant branding personalizes ZIJ; it does not erase ZIJ.

   Deliberately minimal and token-free: `color` is set INLINE by brand.js from a
   server-resolved value that is contrast-checked against the tenant's surface
   (bc_agent/product_identity.py). No brand token drives visibility, size or
   display here — a tenant palette must not be able to hide it, and a rule that
   read `--brand-*` could. */
.zij-lockup {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  min-width: 0;
  font-size: 11px;
  font-weight: 500;
  line-height: 1.2;
  letter-spacing: 0.01em;
  -webkit-user-select: none;
  user-select: none;
  color: var(--zij-lockup-color, inherit);
}
.zij-lockup[hidden] { display: none; }

/* The server's colour is contrast-checked against the TENANT's surface tokens,
   which are light-mode values — brand.js states the rule it follows: "FEED,
   DON'T MERGE. ink/surface/card are light-mode values; pushing them into the
   chrome token layer would break dark mode outright." The lockup colour is
   derived from exactly those, so on the dark topbar it landed at 3.0:1 while
   the session line it shares a row with sat at 6.7:1.
   In dark mode the ground is ZIJ's own — no tenant token reaches --bg/--card —
   so that guard has nothing left to guard here, and the chrome's own muted
   token is both legible (5.2:1 after the 0.85 fade) and the correct rank: one
   step below the session line, not competing with the title.
   The hero opts out: it paints its own dark gradient and stays white. */
html.dark .zij-lockup:not(.zij-lockup--hero) {
  color: color-mix(in srgb, var(--muted) 88%, var(--bg));
}
.zij-lockup .zij-lockup-mark {
  width: 13px;
  height: 13px;
  flex: 0 0 auto;
  /* currentColor inside the SVG — see brand.js markSvg(). */
}
.zij-lockup .zij-lockup-text {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* App topbars carry the lockup at the trailing edge. TWO variants, because some
   topbars already end in an actions group holding `margin-inline-start: auto` —
   giving the lockup a second auto margin there would SPLIT the free space
   between them and drag that group into the middle of the bar. So the pages
   with an actions group use the --tail variant, which simply follows it. */
.zij-lockup--topbar,
.zij-lockup--topbar-tail {
  flex: 0 0 auto;
}
.zij-lockup--topbar {
  margin-inline-start: auto;
}

/* Pages whose topbar is already full carry the lockup as a page footer. */
.plan-attribution {
  display: flex;
  justify-content: center;
  padding: 20px 16px 28px;
}

/* In a nav rail / sidebar footer the lockup is a full-width caption. */
.zij-lockup--stacked { display: flex; width: 100%; padding: 6px 2px 0; }

/* On the login hero it sits under the identity block at reading size.
   `color` is a LITERAL here, not a token: the hero is an ZIJ-owned gradient that
   mixes the accent with 48-82% black, so it is dark whatever the tenant accent
   is — and white is therefore always readable on it. The slot carries
   data-zij-lockup-color="inherit" so brand.js does not override this with the
   app-surface colour, which would be dark text on a dark panel. */
.zij-lockup--hero {
  margin-top: 14px;
  /* Sized for VISIBILITY, not just legibility. Measured on the login hero, the
     old 12px/500 at 0.82 opacity already had 12:1 contrast — it read as faint
     because it was the smallest, lightest thing on the panel and sat over the
     constellation pattern, not because it was hard to see. So the fix is scale
     and weight; chasing contrast would have achieved nothing.
     It still ranks BELOW the workspace name and tagline: this is an
     attribution, and out-shouting the tenant's own identity would be the
     opposite failure. */
  font-size: 13px;
  font-weight: 600;
  letter-spacing: 0.015em;
  color: #fff;
  opacity: 1;
  /* The hero paints a constellation of faint dots and lines. Where a dot lands
     behind the text the local contrast collapses even though the contrast
     against the gradient is fine — this keeps the glyph edges defined over it. */
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.55);
}
/* The mark scales with the text it sits beside, or it reads as a stray dot. */
.zij-lockup--hero .zij-lockup-mark {
  width: 15px;
  height: 15px;
}

/* Chat topbar: the session line and the lockup share one row so the lockup sits
   beside the workspace identity rather than competing with the title. */
.chat-topbar-identity-row {
  display: flex;
  align-items: center;
  gap: 10px;
  min-width: 0;
  flex-wrap: wrap;
}
.chat-topbar-identity-row > p { margin: 0; min-width: 0; }

/* The admin sidebar is long and SCROLLS, so a lockup at its foot was both rarely
   in view and — squeezed as a flex item — overlapping the last nav button, which
   made that tab unclickable. It lives in the topbar beside the title instead,
   which is also where chat puts it. Kept as a note rather than a rule so the
   placement is not re-litigated. */

/* ── Platform → Branding: the three layers ──────────────────────────────────
   Laid out in resolution order. The OEM section is styled as a hazard, not as
   another preference: it is the only control that can suppress ZIJ identity. */
.brand-layer {
  padding: 16px 0;
  border-top: 1px solid var(--border, rgba(148, 163, 184, 0.22));
}
.brand-layer:first-of-type { border-top: 0; padding-top: 4px; }
.brand-layer-head {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  margin-bottom: 4px;
}
.brand-layer-head .admin-sec-section-title { margin: 0; }
.brand-layer-pill {
  font-size: 11px; font-weight: 600; padding: 2px 8px; border-radius: 999px;
  border: 1px solid var(--border, rgba(148, 163, 184, 0.3));
  color: var(--muted, #64748b);
}
.brand-layer-pill.is-locked {
  color: var(--accent, #C9973F);
  border-color: rgba(var(--accent-rgb, 67, 148, 108), 0.4);
  background: rgba(var(--accent-rgb, 67, 148, 108), 0.08);
}
.brand-layer-pill.is-danger {
  color: #e11d48; border-color: rgba(225, 29, 72, 0.4); background: rgba(225, 29, 72, 0.08);
}
.brand-layer--oem {
  margin-top: 8px; padding: 16px; border: 1px solid rgba(225, 29, 72, 0.28);
  border-radius: 12px; background: rgba(225, 29, 72, 0.04);
}
.brand-layer--info { opacity: 0.9; }
.brand-layer-grid {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: 12px 16px; margin-top: 12px;
}
.brand-layer-input-row { display: flex; align-items: center; gap: 8px; min-width: 0; }
.brand-layer-swatch {
  width: 34px; height: 34px; flex: 0 0 auto; padding: 0;
  border: 1px solid var(--border, rgba(148, 163, 184, 0.3)); border-radius: 8px;
  background: none; cursor: pointer;
}
.brand-layer-hex { min-width: 0; }
.brand-layer-facts {
  display: grid; grid-template-columns: minmax(140px, auto) 1fr;
  gap: 6px 16px; margin: 12px 0 0; font-size: 13px;
}
.brand-layer-facts dt { color: var(--muted, #64748b); }
.brand-layer-facts dd { margin: 0; font-weight: 500; }
.brand-layer-facts dd.is-danger { color: #e11d48; font-weight: 600; }
.brand-oem-row { margin-top: 12px; }

/* ── Admin modal ─────────────────────────────────────────────────────────────
   The class existed in the markup with no rules behind it, so every "modal" in
   the Organization tab rendered inline in the page flow — under the content it
   was supposed to sit over. Nothing caught it because every assertion measured
   the DOM (`.hidden`, `[hidden]`) rather than the layout, and the page
   screenshots were all taken with no modal open.

   `.hidden` is the toggle the admin JS uses; `[hidden]` is here too so either
   spelling works and neither is outranked by the display below. */
.admin-modal {
  position: fixed;
  inset: 0;
  z-index: 1000;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--space-5);
  background: rgb(15 23 42 / 55%);
  overflow-y: auto;
}
.admin-modal.hidden,
.admin-modal[hidden] { display: none; }

.admin-modal-card {
  display: flex;
  flex-direction: column;
  width: 100%;
  max-width: 560px;
  max-height: 90vh;
  background: var(--card);
  border: 1px solid var(--card-b);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-md);
  overflow: hidden;
}

.admin-modal-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  padding: var(--space-4) var(--space-5);
  border-block-end: 1px solid var(--card-b);
}
.admin-modal-title { margin: 0; font-size: var(--text-lg); font-weight: 600; }
.admin-modal-close {
  border: 0; background: transparent; color: var(--muted);
  font-size: 1.5rem; line-height: 1; cursor: pointer; padding: 0 0.25rem;
}
.admin-modal-close:hover { color: var(--text); }
.admin-modal-close:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* The scrolling region, so a long form scrolls inside the card rather than
   pushing the footer off the screen. */
.admin-modal-body {
  padding: var(--space-5);
  overflow-y: auto;
  min-height: 0;
}
.admin-modal-body[hidden] { display: none; }

.admin-modal-foot {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: var(--space-2);
  margin-block-start: var(--space-4);
  padding-block-start: var(--space-4);
  border-block-start: 1px solid var(--card-b);
}

/* ── Admin · Email (signature + sending rules) ───────────────────────────────
   Logical properties throughout so the Arabic mirror is correct by
   construction. The layout is a two-column split above 1100px — the form and
   the thing it produces have to be on screen together, or nobody uses the
   preview — and a single column below it. */
.admin-sig-panel { display: flex; flex-direction: column; gap: var(--space-4); }

/* Section switcher. Three cards on one scroll made an admin editing a template
   scroll past somebody else's phone number to reach the preview. */
.sig-sectionnav {
  display: flex; gap: var(--space-1); flex-wrap: wrap;
  border-block-end: 1px solid var(--card-b); padding-block-end: var(--space-2);
}
.sig-sectionnav[hidden] { display: none; }
.sig-sectionbtn {
  border: 0; background: transparent; color: var(--muted); cursor: pointer;
  padding: 0.45rem 0.85rem; border-radius: var(--radius-md);
  font-size: var(--text-sm); font-weight: 600;
}
.sig-sectionbtn[hidden] { display: none; }
.sig-sectionbtn:hover { background: var(--btn-hover); color: var(--text); }
.sig-sectionbtn.is-active { background: var(--accent-soft); color: var(--accent); }
.sig-sectionbtn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

.sig-section[hidden] { display: none; }

.sig-split { display: grid; grid-template-columns: 1fr; gap: var(--space-5); }
@media (min-width: 1100px) {
  .sig-split { grid-template-columns: minmax(0, 1fr) minmax(20rem, 26rem); }
  /* The preview follows you down a long template. */
  .sig-previewpane { position: sticky; top: var(--space-4); align-self: start; }
}
.sig-fields { display: flex; flex-direction: column; gap: var(--space-4); min-width: 0; }
.sig-fields[hidden] { display: none; }

/* Short values do not deserve a full-width input: a phone number in a 900px
   box is harder to scan, not easier. */
.sig-grid {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(13rem, 1fr));
  gap: var(--space-3) var(--space-4);
}
.sig-field { display: flex; flex-direction: column; min-width: 0; }
.sig-field--wide { grid-column: 1 / -1; }
.sig-field--narrow { max-width: 14rem; }
.sig-field .ui-label { margin-block-end: var(--space-1); }

.sig-check {
  display: flex; align-items: flex-start; gap: var(--space-2);
  font-size: var(--text-sm); cursor: pointer; position: relative;
}
.sig-check input { margin-block-start: 0.2rem; flex: 0 0 auto; }
.sig-check > span { display: flex; flex-direction: column; gap: 2px; }
.sig-check-hint { font-size: var(--text-xs); color: var(--muted); }
/* A gate nobody can see is not a gate: the label has to look unavailable, not
   just refuse the click. */
.sig-check input:disabled { cursor: not-allowed; }
.sig-check input:disabled + span { opacity: 0.5; cursor: not-allowed; }
.sig-check--switch {
  padding: var(--space-3); border-radius: var(--radius-md);
  border: 1px solid var(--card-b);
}
/* On but doing nothing. Not an error — a state the admin has to be told about,
   because they believe this switch is protecting them. */
.sig-check.is-inert { border-color: var(--warn); background: var(--warn-soft); }
.sig-inert-note {
  margin: var(--space-2) 0 0; font-size: var(--text-xs); color: var(--warn);
  flex-basis: 100%;
}

.sig-actions {
  display: flex; align-items: center; gap: var(--space-2); flex-wrap: wrap;
  padding-block-start: var(--space-3); border-block-start: 1px solid var(--card-b);
}

/* A value owned elsewhere is READONLY, not disabled — `:disabled` dims the
   text, and a real job title in placeholder grey reads as an empty field. */
.ui-input:read-only:not(textarea) { background: var(--btn-hover); cursor: default; }

.sig-warning {
  display: flex; gap: var(--space-3);
  padding: var(--space-4); border-radius: var(--radius-md);
  border: 1px solid var(--warn); background: var(--warn-soft);
  margin-block-end: var(--space-4);
}
.sig-warning p { margin: 0 0 var(--space-2); font-size: var(--text-sm); }
.sig-warning p:last-of-type { margin-block-end: var(--space-3); }
.sig-warning-icon { flex: 0 0 auto; color: var(--warn); }
.sig-warning-icon svg { width: 1.25rem; height: 1.25rem; }

.sig-notice {
  padding: var(--space-3); border-radius: var(--radius-md);
  border: 1px dashed var(--surface-b); color: var(--muted);
  font-size: var(--text-sm); margin-block-end: var(--space-4);
}
.sig-notice[hidden] { display: none; }

.sig-template { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; }
.sig-chips-label { margin-block: var(--space-3) var(--space-1); }
.sig-placeholders { display: flex; flex-wrap: wrap; gap: var(--space-1); }
.sig-chip {
  border: 1px solid var(--card-b); background: var(--surface); color: var(--muted);
  border-radius: var(--radius-full); padding: 0.125rem 0.5rem; cursor: pointer;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: var(--text-xs);
}
.sig-chip:hover { background: var(--btn-hover); color: var(--text); }
.sig-chip.is-static { cursor: default; }
.sig-chip.is-static:hover { background: var(--surface); color: var(--muted); }

/* Template mistakes that would otherwise render as silence. */
.sig-lint {
  margin-block-start: var(--space-2); padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-md); font-size: var(--text-xs);
}
.sig-lint[hidden] { display: none; }
.sig-lint p { margin: 0 0 var(--space-1); }
.sig-lint p:last-child { margin-block-end: 0; }
.sig-lint--warn {
  border: 1px solid var(--warn); background: var(--warn-soft); color: var(--warn);
}

.sig-preview-head { margin-block-end: var(--space-2); }
.sig-preview-controls {
  display: flex; gap: var(--space-2); flex-wrap: wrap;
  margin-block-start: var(--space-2);
}
.sig-preview-frame { transition: max-width 0.15s; }
/* A phone-width column, because a signature that wraps badly on a handset is
   the version most recipients see. */
.sig-preview-frame.is-narrow { max-width: 22rem; }
.sig-preview {
  width: 100%; min-height: 9rem;
  border: 1px solid var(--card-b); border-radius: var(--radius-md);
  background: #fff;
}

/* Try an address */
.sig-try { display: flex; gap: var(--space-2); align-items: center; flex-wrap: wrap; }
.sig-try .ui-input { flex: 1 1 14rem; min-height: 2.25rem; }
.sig-tryresult { margin-block-start: var(--space-3); }
.sig-verdict {
  margin: 0 0 var(--space-2); font-weight: 600; font-size: var(--text-sm);
}
.sig-verdict.is-ok { color: var(--ok); }
.sig-verdict.is-approval { color: var(--warn); }
.sig-verdict.is-blocked { color: var(--danger); }
.sig-trylist { list-style: none; margin: 0; padding: 0; display: flex;
  flex-direction: column; gap: var(--space-1); font-size: var(--text-sm); }
.sig-trylist li { display: flex; align-items: center; gap: var(--space-2); }

/* ── Admin · Organization ────────────────────────────────────────────────────
   Logical properties throughout (margin-inline, text-align: start) so the
   Arabic RTL layout is correct by construction rather than by a mirrored
   stylesheet — org charts are the classic place an LTR-only assumption shows. */
.admin-org-panel { display: flex; flex-direction: column; gap: var(--space-4); }
.org-card { padding: var(--space-5); }
.org-card-head {
  display: flex; align-items: flex-start; justify-content: space-between;
  gap: var(--space-4); flex-wrap: wrap; margin-block-end: var(--space-3);
}
.org-card-actions { display: flex; align-items: center; gap: var(--space-2); flex-wrap: wrap; }
.org-search { min-width: 180px; max-width: 260px; flex: 1 1 180px; }
.org-muted { color: var(--muted); }

/* A compact empty state. The default .ui-empty is a 150px-tall dashed box,
   which is right for a whole page and wrong three times down one tab. */
.ui-empty--inline { padding: var(--space-4) var(--space-3); border-style: dashed; }
.ui-empty[hidden] { display: none; }

/* First run. Deliberately NOT a flex column: a flex item's display is
   blockified, which drops the list marker — and these are three ordered steps,
   not three bullets. `list-style` is restated because the admin stylesheet
   strips markers globally. */
.org-setup-steps {
  margin: var(--space-3) 0; padding-inline-start: var(--space-5);
  color: var(--muted); font-size: var(--text-sm);
  list-style: decimal;
}
.org-setup-steps li { list-style: decimal; margin-block-end: var(--space-1); }
.org-setup-actions { display: flex; gap: var(--space-2); flex-wrap: wrap; }

/* Hierarchy / list toggle */
.org-viewtoggle {
  display: inline-flex; border: 1px solid var(--card-b); border-radius: var(--radius-md);
  overflow: hidden;
}
.org-viewbtn {
  border: 0; background: transparent; color: var(--muted); cursor: pointer;
  padding: 0.35rem 0.75rem; font-size: var(--text-sm); font-weight: 600;
}
.org-viewbtn + .org-viewbtn { border-inline-start: 1px solid var(--card-b); }
.org-viewbtn.is-active { background: var(--btn-hover); color: var(--text); }
.org-viewbtn:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }

/* My position */
.org-me-grid {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: var(--space-4);
}
.org-me-cell { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.org-me-label {
  font-size: var(--text-xs); color: var(--muted); text-transform: uppercase;
  letter-spacing: .04em;
}
.org-me-sub { font-size: var(--text-sm); color: var(--muted); }
.org-chain {
  margin-block-start: var(--space-3); font-size: var(--text-sm); color: var(--muted);
}

/* People */
.org-people { display: flex; flex-direction: column; gap: 2px; }
.org-person {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--space-3); flex-wrap: wrap;
  padding: var(--space-3); border-radius: var(--radius-md);
  border: 1px solid transparent;
  position: relative;
  /* padding-inline-start, never padding-left: the tree indents from the reading
     edge, so one rule draws an Arabic chart right-to-left too. */
  padding-inline-start: calc(var(--space-3) + var(--org-depth, 0) * 1.5rem);
}
/* The rule that makes an indent read as "reports to the row above". A border
   rather than a glyph, so it inherits the theme and mirrors in RTL. */
.org-person[style*="--org-depth"]::before {
  content: ""; position: absolute; inset-block: 0;
  inset-inline-start: calc(var(--space-3) + (var(--org-depth, 0) - 0.6) * 1.5rem);
  border-inline-start: 1px solid var(--card-b);
}
.org-person:hover { background: var(--btn-hover); }
.org-person-main { display: flex; flex-direction: column; min-width: 0; flex: 1 1 200px; }
.org-person-title { font-size: var(--text-sm); color: var(--muted); }
.org-person-meta { display: flex; align-items: center; gap: var(--space-2); flex-wrap: wrap; }
.org-person-emp { font-size: var(--text-xs); color: var(--muted); font-family: monospace; }
.org-unplaced-note { margin-block-start: var(--space-3); }

/* Units — same indent mechanics as the person tree, so a department sitting
   inside a division reads the same way a report sitting under a manager does. */
.org-units { display: flex; flex-direction: column; gap: 2px; }
.org-unit {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--space-3); flex-wrap: wrap;
  padding: var(--space-3); border-radius: var(--radius-md);
  border: 1px solid transparent; position: relative;
  padding-inline-start: calc(var(--space-3) + var(--org-depth, 0) * 1.5rem);
}
.org-unit[style*="--org-depth"]::before {
  content: ""; position: absolute; inset-block: 0;
  inset-inline-start: calc(var(--space-3) + (var(--org-depth, 0) - 0.6) * 1.5rem);
  border-inline-start: 1px solid var(--card-b);
}
.org-unit:hover { background: var(--btn-hover); }
.org-unit-main { display: flex; flex-direction: column; min-width: 0; flex: 1 1 200px; }
.org-unit-kind { font-size: var(--text-sm); color: var(--muted); }

.org-scope-setting {
  margin-block-start: var(--space-5); padding-block-start: var(--space-4);
  border-block-start: 1px solid var(--card-b); max-width: 44rem;
}

/* Delegations */
.org-delegations { display: flex; flex-direction: column; gap: 2px; }
.org-delegation {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--space-3); flex-wrap: wrap;
  padding: var(--space-3); border-radius: var(--radius-md);
  border: 1px solid transparent;
}
.org-delegation:hover { background: var(--btn-hover); }
/* An ended grant is history, not a live arrangement — it must not read as one. */
.org-delegation.is-ended { opacity: .6; }
.org-delegation-main { display: flex; flex-direction: column; min-width: 0; flex: 1 1 260px; }
/* "X acts for Y" is one sentence, so it is one line — not three stacked words. */
.org-delegation-line { display: block; }
.org-delegation-meta { font-size: var(--text-sm); color: var(--muted); }
.org-me-deleg {
  margin-block-start: var(--space-3); display: flex; flex-direction: column;
  gap: var(--space-1); font-size: var(--text-sm);
}
.org-scopes { border: 0; padding: 0; margin: 0; }
.org-check {
  display: flex; align-items: center; gap: var(--space-2);
  margin-block-end: var(--space-1); font-size: var(--text-sm);
}
.org-check input { margin: 0; }

/* Review queue */
.org-proposals { display: flex; flex-direction: column; gap: var(--space-3); }
.org-proposal {
  padding: var(--space-3); border-radius: var(--radius-md);
  border: 1px solid var(--card-b); background: var(--surface);
}
/* A conflict is the only thing in this queue that needs a decision, so it is
   the only thing that gets a colour. */
.org-proposal.is-conflict {
  border-color: var(--warn); background: var(--warn-soft);
}
.org-proposal-head {
  display: flex; align-items: center; gap: var(--space-2); flex-wrap: wrap;
  margin-block-end: var(--space-2);
}
.org-proposal-subject { font-size: var(--text-md); }
.org-proposal-type { font-size: var(--text-sm); color: var(--muted); }
.org-proposal-via { font-size: var(--text-xs); color: var(--muted); }
.org-proposal-key { font-size: var(--text-xs); color: var(--muted); }
.org-proposal-detail { font-size: var(--text-sm); margin-block-end: var(--space-2); }
.org-proposal-actions { display: flex; gap: var(--space-2); margin-block-start: var(--space-2); }
.org-diff { display: flex; flex-direction: column; gap: 2px; font-size: var(--text-sm); }
.org-diff-row { display: flex; align-items: baseline; gap: var(--space-2); flex-wrap: wrap; }
.org-diff-key { min-width: 140px; color: var(--muted); font-size: var(--text-xs); }
.org-diff-old { color: var(--muted); text-decoration: line-through; }
.org-diff-arrow { color: var(--muted); }
.org-diff-new { font-weight: 600; }

/* Sources */
.org-sources { display: flex; flex-direction: column; gap: var(--space-2); }
.org-source {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--space-3); flex-wrap: wrap;
  padding: var(--space-3); border-radius: var(--radius-md); border: 1px solid var(--card-b);
}
.org-source.is-manual { border-style: dashed; }
/* Available but not turned on: present so the lifecycle is reachable, quiet so
   it does not read as something already importing. */
.org-source.is-available { border-style: dashed; opacity: .85; }
.org-source-actions { display: flex; align-items: center; gap: var(--space-2); flex-wrap: wrap; }

/* CSV upload */
.org-upload {
  margin-block-start: var(--space-5); padding-block-start: var(--space-4);
  border-block-start: 1px solid var(--card-b);
}
.org-upload-title { font-size: var(--text-md); font-weight: 600; margin: 0; }
.org-upload-actions {
  display: flex; align-items: center; gap: var(--space-2); flex-wrap: wrap;
  margin-block-start: var(--space-3);
}
.org-file { flex: 1 1 240px; max-width: 22rem; min-height: 2.25rem; padding: 0.35rem 0.5rem; }
.org-csv-result { margin-block-start: var(--space-3); }
.org-csv-summary p { margin: 0 0 var(--space-1); }
.org-csv-conflicts { color: var(--warn); font-weight: 600; }
.org-csv-issues {
  margin-block: var(--space-3); padding: var(--space-3);
  border-radius: var(--radius-md); border: 1px solid var(--warn);
  background: var(--warn-soft); font-size: var(--text-sm);
}
.org-csv-issues ul { margin: var(--space-2) 0 0; padding-inline-start: var(--space-5); }

/* Import runs */
.org-runs { display: flex; flex-direction: column; gap: 2px; }
.org-run {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--space-3); flex-wrap: wrap;
  padding: var(--space-3); border-radius: var(--radius-md);
  border: 1px solid transparent;
}
.org-run:hover { background: var(--btn-hover); }
.org-run.is-failed { border-color: var(--danger); background: var(--danger-soft); }
.org-run-main { display: flex; flex-direction: column; min-width: 0; flex: 1 1 280px; }
.org-run-line { display: block; }
.org-run-meta { font-size: var(--text-sm); color: var(--muted); }
/* An armed destructive button has to look different from the one that armed
   it, or the second click is the same decision as the first. */
.org-run .is-armed { background: var(--danger-soft); color: var(--danger); }

/* Approve-with-changes + history */
.org-override-fields { display: flex; flex-direction: column; gap: var(--space-1); }
.org-history-pane { max-height: 60vh; overflow-y: auto; }
.org-history { display: flex; flex-direction: column; gap: var(--space-3); }
.org-hist {
  padding: var(--space-3); border-radius: var(--radius-md);
  border: 1px solid var(--card-b);
}
.org-hist-head {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: var(--space-2); flex-wrap: wrap; margin-block-end: var(--space-2);
}
.org-hist-meta { font-size: var(--text-xs); color: var(--muted); }

.org-modal-card { max-width: 520px; }
.org-form { display: flex; flex-direction: column; gap: var(--space-2); }
/* Same trap as the buttons and badges: `display: flex` outranks the UA's
   [hidden] rule, so hiding the form to show the history or the archive
   confirmation left BOTH on screen, stacked. */
.org-form[hidden] { display: none; }
.org-modal-foot { display: flex; align-items: center; gap: var(--space-2); }
.org-modal-spacer { flex: 1 1 auto; }
.org-danger { color: var(--danger); }
.org-archive-who { font-weight: 600; margin-block-end: var(--space-3); }
.org-impact {
  padding: var(--space-3); border-radius: var(--radius-md);
  border: 1px solid var(--warn); background: var(--warn-soft);
}
.org-impact ul { margin: var(--space-2) 0; padding-inline-start: var(--space-5); }

/* ── Support tickets (Admin → Tickets) ──────────────────────────────────────
   Two panes: the queue on the left, the investigation on the right. The list
   stays narrow on purpose — a ticket row answers "is this mine to deal with",
   and everything needed to actually deal with it lives in the detail pane. */
/* KPI row. `auto-fit` rather than a fixed column count: a fixed 3-up grid wrapped
   the fourth tile onto a row of its own, which read as an afterthought. */
.ticket-stats {
  display: grid; gap: var(--space-3);
  grid-template-columns: repeat(auto-fit, minmax(170px, 1fr));
  margin-block: var(--space-4) var(--space-3);
}
/* Composes `.ui-card` (per this sheet's own rule) and only tightens it: a KPI
   tile wants less padding than a content card. */
.ticket-stat {
  display: flex; flex-direction: column; gap: 2px;
  padding: var(--space-3) var(--space-4);
}
.ticket-stat-label {
  display: flex; align-items: center; gap: 6px;
  font-size: var(--text-xs); color: var(--muted);
}
/* Proportional figures, deliberately: `tabular-nums` gives every digit the width
   of a zero, which makes a standalone display number look gappy. Tabular is for
   columns that must align, not for a hero value. */
.ticket-stat-value { font-size: 1.6rem; font-weight: 600; line-height: 1.15; color: var(--text); }
/* State is carried by the dot AND the label — never by recolouring the value,
   which would put meaning in a channel a colourblind reader cannot read, and
   would also break the rule that text wears text tokens. */
.ticket-stat.is-critical { border-color: color-mix(in srgb, var(--danger) 45%, var(--card-b)); }
.ticket-stat-dot {
  inline-size: 7px; block-size: 7px; border-radius: 50%;
  background: var(--danger); flex: none;
}

/* Empty states compose `.ui-empty` / `.ui-empty-title`; only the icon medallion
   and the measure of the sub-line are ticket-specific. Two states, saying
   different things — see `_renderEmpty`. */
.ticket-empty { padding: var(--space-8) var(--space-4); }
.ticket-empty-icon {
  display: grid; place-items: center;
  inline-size: 44px; block-size: 44px; border-radius: 50%;
  background: var(--btn-hover); color: var(--muted);
}
/* `.ui-empty-title` is --text-lg, which shouts inside a 380px column. */
.ticket-empty .ui-empty-title { font-size: var(--text-sm); }
.ticket-empty-sub { font-size: var(--text-xs); max-inline-size: 34ch; line-height: 1.5; }
/* The detail column's placeholder keeps the two-pane geometry stable instead of
   letting the pane collapse and the queue float against dead space. It is a
   hint, not a failure, so it drops the dashed frame the queue's state keeps. */
.ticket-empty--pane { border: 0; padding-block: var(--space-10); }

.admin-feedback-pointer {
  display: flex; align-items: center; gap: var(--space-3); flex-wrap: wrap;
  justify-content: space-between;
  padding: var(--space-3);
  border: 1px dashed var(--card-b); border-radius: var(--radius-md);
}
.admin-feedback-pointer p { margin: 0; flex: 1 1 260px; }

.ticket-filters {
  display: flex; gap: var(--space-2); flex-wrap: wrap; align-items: center;
  margin-block: var(--space-3);
}
/* `.input-field` is block-width by default, which stacks the four filters into
   four full-width rows. Constrain them here rather than overriding the shared
   class, so nothing else that uses it changes. */
.ticket-filters .input-field { flex: 0 1 168px; inline-size: auto; }
.ticket-filters .ticket-search { flex: 1 1 220px; min-inline-size: 180px; }

.ticket-layout {
  display: grid; gap: var(--space-3);
  grid-template-columns: minmax(280px, 380px) 1fr;
  align-items: start;
}
/* Below the breakpoint the detail pane stacks under the queue rather than
   squeezing beside it — a stack trace in a 200px column is unreadable. */
@media (max-width: 900px) {
  .ticket-layout { grid-template-columns: 1fr; }
}

.ticket-list {
  display: flex; flex-direction: column; gap: var(--space-2);
  max-block-size: 62vh; overflow-y: auto;
}
.ticket-row {
  display: flex; flex-direction: column; gap: 2px;
  inline-size: 100%; text-align: start;
  padding: var(--space-3); border-radius: var(--radius-md);
  border: 1px solid var(--card-b);
  /* The severity stripe is the one thing scannable at a glance down a long
     queue, so it is a border rather than a badge colour alone. */
  border-inline-start-width: 3px;
  background: transparent; cursor: pointer;
}
.ticket-row:hover { background: var(--btn-hover); }
.ticket-row.is-selected { border-color: var(--accent); background: var(--btn-hover); }
.ticket-row.is-critical { border-inline-start-color: var(--danger); }
.ticket-row.is-high { border-inline-start-color: var(--warn); }
.ticket-row.is-normal { border-inline-start-color: var(--card-b); }
.ticket-row.is-low { border-inline-start-color: var(--card-b); }

.ticket-row-top { display: flex; align-items: center; gap: var(--space-2); flex-wrap: wrap; }
.ticket-row-title { font-weight: 600; font-size: var(--text-sm); }
.ticket-row-meta {
  display: flex; align-items: center; gap: 6px; flex-wrap: wrap;
  color: var(--muted); font-size: var(--text-xs);
}
.ticket-id { font-size: var(--text-xs); font-weight: 700; letter-spacing: .02em; }
.ticket-sev, .ticket-status, .ticket-repeat, .ticket-auto {
  font-size: var(--text-xs); padding: 1px 6px; border-radius: var(--radius-sm);
  border: 1px solid var(--card-b);
}
.ticket-sev.is-critical { color: var(--danger); background: var(--danger-soft); border-color: transparent; }
.ticket-sev.is-high { color: var(--warn); background: var(--warn-soft); border-color: transparent; }
.ticket-repeat { color: var(--warn); border-color: var(--warn); }
.ticket-auto { color: var(--muted); font-style: italic; border-color: transparent; }

.ticket-detail {
  display: flex; flex-direction: column; gap: var(--space-3);
  padding: var(--space-4); border-radius: var(--radius-md);
  border: 1px solid var(--card-b);
  max-block-size: 62vh; overflow-y: auto;
}
/* No `:empty { display:none }` — the pane always renders a placeholder, so the
   column keeps its width and the layout does not jump when a row is selected. */
/* The header is pinned: "which ticket, whose workspace, what state" has to stay
   answerable from any scroll position — the pane is 62vh and a stack trace can
   fill all of it. It carries its own background for the same reason. */
.ticket-detail-head {
  position: sticky; inset-block-start: calc(var(--space-4) * -1);
  z-index: 2;
  display: flex; flex-direction: column; gap: var(--space-2);
  margin: calc(var(--space-4) * -1) calc(var(--space-4) * -1) 0;
  padding: var(--space-4);
  background: var(--card);
  border-block-end: 1px solid var(--card-b);
}
.ticket-head-ids { display: flex; align-items: center; gap: var(--space-2); flex-wrap: wrap; }
.ticket-detail-head h3 { font-size: var(--text-base); font-weight: 600; line-height: 1.35; }
.ticket-head-meta {
  display: flex; align-items: center; gap: 6px; flex-wrap: wrap;
  color: var(--muted); font-size: var(--text-xs);
}
/* Separators as generated content: the meta items are conditional, and a
   hand-placed "·" between two of them leaves a dangling dot when one is absent. */
.ticket-head-meta > span + span::before { content: "·"; margin-inline-end: 6px; opacity: .6; }
.ticket-assignee { color: var(--text); }
.ticket-head-actions {
  display: flex; gap: var(--space-2); flex-wrap: wrap;
  padding-block-start: var(--space-1, 4px);
}

/* Which workspace filed this. A platform triager reads it on every row; a
   tenant admin reads it once, on the ticket they are about to escalate. */
.ticket-tenant {
  font-size: var(--text-xs); padding: 1px 7px; border-radius: 999px;
  color: var(--accent); background: color-mix(in srgb, var(--accent) 12%, transparent);
  border: 1px solid color-mix(in srgb, var(--accent) 35%, transparent);
  max-inline-size: 14ch; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* The reporter's own categorisation — triage metadata that used to be legible
   only by expanding the raw diagnostics snapshot. */
.ticket-reasons { display: flex; gap: 4px; flex-wrap: wrap; }
.ticket-reason {
  font-size: var(--text-xs); padding: 1px 7px; border-radius: var(--radius-sm);
  color: var(--muted); background: var(--btn-hover); border: 1px solid var(--card-b);
}

/* ── Card system ─────────────────────────────────────────────────────────
   Nine identical stacked blocks read as one undifferentiated dump, which is
   what made this pane feel like a diagnostic printout rather than a support
   tool. Each concern is now a bounded card with its own heading. */
.ticket-card {
  display: flex; flex-direction: column; gap: var(--space-2);
  padding: var(--space-3);
  border: 1px solid var(--card-b); border-radius: var(--radius-md);
  background: color-mix(in srgb, var(--surface) 45%, transparent);
}
.ticket-card-head {
  display: flex; align-items: center; gap: var(--space-2);
  font-size: var(--text-xs); font-weight: 700; text-transform: uppercase;
  letter-spacing: .04em; color: var(--muted);
}
.ticket-card-count {
  font-size: var(--text-xs); font-weight: 600; padding: 0 6px;
  border-radius: 999px; background: var(--btn-hover); color: var(--text);
  text-transform: none; letter-spacing: 0;
}
.ticket-card-stage {
  margin-inline-start: auto; font-size: var(--text-xs); font-weight: 600;
  text-transform: none; letter-spacing: 0; color: var(--muted);
}
.ticket-subsection { display: flex; flex-direction: column; gap: 4px; }
.ticket-subsection h5,
.ticket-resolution h5 {
  font-size: var(--text-xs); font-weight: 600; color: var(--muted);
}
.ticket-resolution {
  display: flex; flex-direction: column; gap: 4px;
  padding-block-start: var(--space-2);
  border-block-start: 1px solid var(--card-b);
}

/* The conversation is the workflow, so it is the one card that reads as the
   primary surface rather than as supporting material. */
.ticket-card--conversation { background: transparent; border-color: var(--accent-b, var(--card-b)); }

/* The AI fix lane is deliberately distinct: it is an authority boundary (a
   human approves, only then does work dispatch), and the code worker attaches
   to the end of it. Its stripe is the fastest read of where that stands. */
.ticket-card--ai { border-inline-start-width: 3px; }
.ticket-card--ai.is-pending { border-inline-start-color: var(--warn); }
.ticket-card--ai.is-approved { border-inline-start-color: var(--ok); }
.ticket-card--ai.is-decided { border-inline-start-color: var(--card-b); }
.ticket-card--ai.is-none { border-inline-start-color: var(--card-b); }
.ticket-tasks { display: flex; flex-direction: column; gap: 4px; font-size: var(--text-xs); }
.ticket-tasks li { display: flex; align-items: center; gap: var(--space-2); flex-wrap: wrap; }

/* ── Folded detail ───────────────────────────────────────────────────────── */
.ticket-fold > summary {
  cursor: pointer; font-size: var(--text-xs); font-weight: 600; color: var(--muted);
  display: flex; align-items: center; gap: var(--space-2);
  text-transform: uppercase; letter-spacing: .04em;
}
.ticket-fold > summary:hover { color: var(--text); }
.ticket-fold > summary:focus-visible {
  outline: 2px solid var(--accent); outline-offset: 2px; border-radius: var(--radius-sm);
}
.ticket-fold[open] > summary { margin-block-end: var(--space-2); }
.ticket-fold--flush > summary { text-transform: uppercase; }

.ticket-diag-highlights {
  display: grid; gap: var(--space-2);
  grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
}
.ticket-diag-cell { display: flex; flex-direction: column; gap: 1px; min-inline-size: 0; }
.ticket-diag-cell-label { font-size: var(--text-xs); color: var(--muted); }
.ticket-diag-cell-value {
  font-size: var(--text-xs); word-break: break-all;
}
.ticket-diag-message {
  margin: 0; padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-md); background: var(--danger-soft);
  color: var(--danger);
  font-size: var(--text-xs); white-space: pre-wrap; word-break: break-word;
  max-block-size: 9rem; overflow-y: auto;
}

.ticket-section { display: flex; flex-direction: column; gap: var(--space-2); }
.ticket-section h4 {
  font-size: var(--text-xs); font-weight: 700; text-transform: uppercase;
  letter-spacing: .04em; color: var(--muted);
}
.ticket-body {
  white-space: pre-wrap; word-break: break-word; font-size: var(--text-sm);
  padding: var(--space-3); border-radius: var(--radius-md); background: var(--btn-hover);
}

.ticket-diag { inline-size: 100%; border-collapse: collapse; font-size: var(--text-xs); }
.ticket-diag th {
  text-align: start; font-weight: 500; color: var(--muted);
  padding: 3px var(--space-3) 3px 0; white-space: nowrap; vertical-align: top;
}
.ticket-diag td { padding: 3px 0; word-break: break-all; }

.ticket-trace summary { cursor: pointer; font-size: var(--text-xs); color: var(--muted); }
.ticket-trace pre {
  margin-block-start: var(--space-2); padding: var(--space-3);
  border-radius: var(--radius-md); background: var(--btn-hover);
  font-size: var(--text-xs); overflow-x: auto; max-block-size: 320px;
}

.ticket-links { display: flex; flex-direction: column; gap: 4px; font-size: var(--text-xs); }
.ticket-links li { display: flex; align-items: center; gap: var(--space-2); }
.ticket-link-rel { color: var(--muted); min-inline-size: 88px; }

.ticket-timeline {
  display: flex; flex-direction: column; gap: var(--space-2);
  font-size: var(--text-xs);
  padding-inline-start: var(--space-3);
  border-inline-start: 1px solid var(--card-b);
}
.ticket-timeline li { display: flex; align-items: baseline; gap: var(--space-2); flex-wrap: wrap; }
.ticket-ev-type { font-weight: 600; }
.ticket-ev-detail { color: var(--text); }
.ticket-ev-meta { color: var(--muted); margin-inline-start: auto; }

.ticket-actions { gap: var(--space-2); }
.ticket-action-row { display: flex; gap: var(--space-2); flex-wrap: wrap; align-items: center; }
/* `.input-field` is block-width by default, which stacked every control in
   these rows into its own line — a severity select the width of the pane reads
   as a form, not as a toolbar. Same fix the queue filters already carry. */
.ticket-action-row .input-field { inline-size: auto; flex: 0 1 auto; min-inline-size: 9rem; }
.ticket-note { flex: 1 1 180px; min-inline-size: 12rem; }
.ticket-pager {
  display: flex; align-items: center; justify-content: center;
  gap: var(--space-3); margin-block-start: var(--space-3);
}

/* ── Ticket lifecycle: conversation, approvals, AI tasks ────────────────────
   An internal note has to be unmistakable at a glance: an operator about to
   type something candid is one missed label away from sending it to the
   customer. The server decides visibility; this is what stops the mistake
   being made in the first place. */
.ticket-replies { display: flex; flex-direction: column; gap: var(--space-2); }
.ticket-reply {
  display: flex; flex-direction: column; gap: 3px;
  padding: var(--space-3); border-radius: var(--radius-md);
  background: var(--btn-hover); font-size: var(--text-xs);
}
.ticket-reply.is-internal {
  background: var(--warn-soft);
  border-inline-start: 3px solid var(--warn);
}
.ticket-reply-head { display: flex; align-items: center; gap: var(--space-2); flex-wrap: wrap; }
.ticket-reply-vis {
  font-size: var(--text-xs); padding: 0 5px; border-radius: var(--radius-sm);
  border: 1px solid var(--card-b); color: var(--muted);
}
.ticket-reply.is-internal .ticket-reply-vis { color: var(--warn); border-color: var(--warn); }
.ticket-reply-body { white-space: pre-wrap; word-break: break-word; font-size: var(--text-sm); }

.ticket-approvals { display: flex; flex-direction: column; gap: var(--space-2); font-size: var(--text-xs); }
.ticket-approval {
  display: flex; flex-direction: column; gap: 2px;
  padding: var(--space-2) var(--space-3); border-radius: var(--radius-md);
  border: 1px solid var(--card-b); border-inline-start-width: 3px;
}
.ticket-approval.is-pending  { border-inline-start-color: var(--warn); }
.ticket-approval.is-approved { border-inline-start-color: var(--ok); }
.ticket-approval.is-denied,
.ticket-approval.is-expired  { border-inline-start-color: var(--danger); }
.ticket-approval-prompt { white-space: pre-wrap; word-break: break-word; }

/* ── Admin · Setup readiness checklist ───────────────────────────────────────
   Layout only. Every colour here comes from the badge variants and the shared
   tokens, so a tenant's accent moves the page and neither theme needs a patch.
   No physical left/right anywhere — the whole panel mirrors under dir="rtl"
   from the logical properties alone. */
.setup-summary {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin: var(--space-3) 0 var(--space-4);
}
.setup-summary[hidden] { display: none; }

.setup-groups {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
}

.setup-group { padding: var(--space-4) var(--space-5); }

.setup-group-head {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  margin-bottom: var(--space-3);
}

.setup-group-title {
  margin: 0;
  font-size: var(--text-md);
  font-weight: 700;
  color: var(--text);
}
/* The group's open-count badge sits at the trailing edge in both directions. */
.setup-group-head .ui-badge { margin-inline-start: auto; }

.setup-task-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
}

.setup-task {
  display: flex;
  align-items: flex-start;
  gap: var(--space-4);
  padding: var(--space-3) 0;
  border-top: 1px solid var(--card-b);
}
.setup-task:first-child { border-top: 0; }

/* A blocking row is the only one that earns a rule of its own: it is the
   difference between "unfinished" and "the assistant cannot answer at all",
   and a badge alone did not carry that at a glance down a long list. The
   marker is an inline-start border so it mirrors with the text. */
.setup-task.is-blocking {
  border-inline-start: 3px solid var(--danger);
  padding-inline-start: var(--space-3);
  background: color-mix(in srgb, var(--danger) 5%, transparent);
  border-radius: var(--radius-sm);
}

/* Satisfied and out-of-plan rows recede: the page is read for what is LEFT, so
   anything settled must not compete with it. They stay fully present though —
   no collapsing, no filtering — because "what IS configured" is the second
   question this page gets asked, usually right after something breaks.
   Colour only, never opacity: a translucent row loses contrast against the card
   and these lines still have to be legible. */
.setup-task.is-done .setup-task-title,
.setup-task.is-not_in_plan .setup-task-title { color: var(--muted); }

/* The accent tint is what made a finished row pull the eye down the list. A
   settled detail is a record, not a finding. */
.setup-task.is-done .setup-task-detail,
.setup-task.is-not_in_plan .setup-task-detail { color: var(--muted); }

/* Still reachable, but it stops advertising itself. */
.setup-task.is-done .setup-task-open {
  color: var(--muted);
  border-color: transparent;
}
.setup-task.is-done .setup-task-open:hover {
  color: var(--text);
  border-color: var(--card-b);
}

.setup-task-main { flex: 1 1 auto; min-width: 0; }

.setup-task-head {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  flex-wrap: wrap;
}

.setup-task-title {
  font-size: var(--text-md);
  font-weight: 600;
  color: var(--text);
}

.setup-task-why {
  margin: var(--space-1) 0 0;
  font-size: var(--text-sm);
  color: var(--muted);
}

/* The probe's own words. Monospaced-adjacent treatment on purpose: this is a
   machine's finding quoted verbatim, not our prose about it. */
.setup-task-detail {
  margin: var(--space-1) 0 0;
  font-size: var(--text-sm);
  color: color-mix(in srgb, var(--accent) 40%, var(--text));
  overflow-wrap: anywhere;
  /* The probe's wording is quoted machine output and may be in either script.
     `plaintext` derives direction from the run's own first strong character, so
     an English detail inside the Arabic page keeps its trailing full stop where
     it belongs instead of having it flipped to the leading edge. */
  unicode-bidi: plaintext;
  text-align: start;
}

.setup-task-note {
  margin: 0;
  font-size: var(--text-xs);
  color: var(--muted);
  max-width: 18ch;
  text-align: end;
}

.setup-task-action {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: flex-end;
}

/* Ghost alone did not read as clickable down a list of rows, and this is the
   one control the page has. A hairline border is enough without competing with
   the primary actions on the tab it jumps to. */
.setup-task-open {
  border-color: var(--card-b);
  color: var(--text);
}
.setup-task-open:hover { border-color: color-mix(in srgb, var(--accent) 45%, var(--card-b)); }

.setup-skeleton-line { height: 1rem; margin-bottom: var(--space-3); }
.setup-all-done { margin-bottom: var(--space-4); }

/* Sidebar tab badge — the checklist's only claim on attention from other tabs. */
.setup-tab-badge {
  margin-inline-start: auto;
  flex: 0 0 auto;
  min-width: 1.25rem;
  padding: 0 0.35rem;
  border-radius: var(--radius-full);
  font-size: var(--text-xs);
  font-weight: 700;
  line-height: 1.5;
  text-align: center;
  background: var(--warn-soft);
  color: var(--warn);
}
.setup-tab-badge.is-blocking {
  background: var(--danger-soft);
  color: var(--danger);
}
.setup-tab-badge.hidden { display: none; }

@media (max-width: 640px) {
  /* Stack rather than squeeze: the trailing control and the note both need
     their own line before the title starts wrapping mid-word. */
  .setup-task { flex-direction: column; gap: var(--space-2); }
  .setup-task-action { justify-content: flex-start; width: 100%; }
  .setup-task-note { max-width: none; text-align: start; }
}

/* ── Admin · Setup quick start ────────────────────────────────────────────────
   Shown only while blocking work remains (see setup.js `_quickStart`). Accent-
   tinted rather than danger-tinted: this is the way forward, not an alarm — the
   blocking rows below already carry the alarm. */
.setup-quickstart {
  border: 1px solid color-mix(in srgb, var(--accent) 35%, var(--card-b));
  background: color-mix(in srgb, var(--accent) 6%, var(--card));
  border-radius: var(--radius-lg);
  padding: var(--space-5);
  margin-bottom: var(--space-4);
}

.setup-quickstart-title {
  margin: 0;
  font-size: var(--text-lg);
  font-weight: 700;
  color: var(--text);
}

.setup-quickstart-body {
  margin: var(--space-1) 0 var(--space-4);
  font-size: var(--text-sm);
  color: var(--muted);
}

.setup-quickstart-steps {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

.setup-quickstart-step {
  display: flex;
  align-items: center;
  gap: var(--space-3);
}

.setup-quickstart-num {
  flex: 0 0 auto;
  width: 1.5rem;
  height: 1.5rem;
  border-radius: var(--radius-full);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: var(--text-xs);
  font-weight: 700;
  background: var(--accent-soft);
  color: var(--accent);
}

.setup-quickstart-label {
  flex: 1 1 auto;
  min-width: 0;
  font-size: var(--text-md);
  font-weight: 600;
  color: var(--text);
}

/* Trailing edge in both directions — the numbers lead, the action follows. */
.setup-quickstart-go { flex: 0 0 auto; margin-inline-start: auto; }

.setup-quickstart-more {
  margin: var(--space-4) 0 0;
  font-size: var(--text-xs);
  color: var(--muted);
}

@media (max-width: 640px) {
  .setup-quickstart-step { flex-wrap: wrap; }
  .setup-quickstart-go { margin-inline-start: 0; width: 100%; }
}

/* ── Work Profile (guided personalization — chat page) ──
   Wizard + review states rendered into the #workProfileModal shell by
   js/chat/work-profile.js, plus the empty-state invitation card. Chips are
   selection state, not navigation: selected = accent-soft fill, same shape
   as .ui-badge but tappable. Logical properties throughout (RTL-correct). */

.wp-empty-card {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: var(--space-3);
  margin-block-start: var(--space-5);
  padding: var(--space-4) var(--space-5);
  background: var(--card);
  border: 1px solid var(--card-b);
  border-radius: var(--radius-lg);
  text-align: start;
}

.wp-empty-card-title {
  margin: 0;
  font-size: var(--text-md);
  font-weight: 600;
  color: var(--text);
}

.wp-empty-card-sub {
  margin: 0;
  font-size: var(--text-sm);
  color: var(--muted);
}

.wp-empty-card-actions {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  flex: 0 0 auto;
}

.wp-card-never {
  background: none;
  border: none;
  padding: 0.3125rem 0.25rem;
  font-size: var(--text-xs);
  color: var(--muted);
  cursor: pointer;
  text-decoration: underline;
  text-underline-offset: 2px;
}

.wp-card-never:hover { color: var(--text); }

.wp-modal-body { display: flex; flex-direction: column; gap: var(--space-3); }

.wp-progress {
  font-size: var(--text-xs);
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--muted);
}

.wp-question {
  margin: 0;
  font-size: var(--text-lg);
  font-weight: 600;
  color: var(--text);
}

.wp-chips {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
}

.wp-chip {
  padding: 0.4375rem 0.75rem;
  border-radius: var(--radius-full);
  border: 1px solid var(--input-b);
  background: var(--input-bg);
  color: var(--text);
  font-size: var(--text-sm);
  font-weight: 500;
  line-height: 1.3;
  cursor: pointer;
  transition: background-color 0.15s, border-color 0.15s, color 0.15s;
}

.wp-chip:hover { border-color: var(--accent); }

.wp-chip.is-selected {
  background: var(--accent-soft);
  border-color: var(--accent);
  color: var(--accent);
  font-weight: 600;
}

.wp-chip:focus-visible {
  outline: 2px solid color-mix(in srgb, var(--accent) 82%, var(--text));
  outline-offset: 2px;
}

.wp-other-row {
  display: flex;
  align-items: center;
  gap: var(--space-2);
}

.wp-other-label {
  flex: 0 0 auto;
  font-size: var(--text-sm);
  color: var(--muted);
}

.wp-note { resize: vertical; }

.wp-actions {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: var(--space-2);
  margin-block-start: var(--space-2);
}

.wp-actions--review { justify-content: flex-start; }

.wp-msg { margin: 0; font-size: var(--text-xs); color: var(--danger); min-height: 1em; }

.wp-done { text-align: center; padding: var(--space-6) 0; }
.wp-done h4 { margin: 0 0 var(--space-2); font-size: var(--text-lg); color: var(--text); }
.wp-done p { margin: 0 0 var(--space-4); font-size: var(--text-sm); color: var(--muted); }
.wp-done .wp-actions { justify-content: center; }

.wp-review-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
}

.wp-review-item {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-3) 0;
  border-block-end: 1px solid var(--surface-b);
}

.wp-review-item:last-child { border-block-end: none; }

.wp-review-text {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 0.125rem;
}

.wp-review-q { font-size: var(--text-xs); color: var(--muted); }
.wp-review-value { font-size: var(--text-sm); color: var(--text); overflow-wrap: anywhere; }
.wp-review-muted { font-size: var(--text-sm); color: var(--muted); font-style: italic; }

.wp-preview-wrap { margin-block-start: var(--space-2); }

.wp-preview-title {
  margin: 0 0 var(--space-2);
  font-size: var(--text-xs);
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--muted);
}

/* The literal block the model receives — LTR like code, scrolls in place. */
.wp-preview {
  margin: 0;
  padding: var(--space-3);
  background: var(--input-bg);
  border: 1px solid var(--input-b);
  border-radius: var(--radius-md);
  font-size: var(--text-xs);
  line-height: 1.55;
  color: var(--text);
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  max-height: 14rem;
  overflow-y: auto;
}

.wp-footnotes {
  margin-block-start: var(--space-2);
  padding-block-start: var(--space-3);
  border-block-start: 1px solid var(--surface-b);
}

.wp-footnotes p { margin: 0 0 var(--space-1); font-size: var(--text-xs); color: var(--muted); }

/* Brain-popover shortcut into the profile modal (appended by chat.js). */
.chat-memory-manage-link {
  display: block;
  width: 100%;
  padding: 0.5rem 0.75rem;
  background: none;
  border: none;
  border-block-start: 1px solid var(--surface-b);
  color: var(--accent);
  font-size: var(--text-sm);
  font-weight: 600;
  text-align: start;
  cursor: pointer;
}

.chat-memory-manage-link:hover { background: var(--btn-hover); }

/* ── Work Profile — Phase 2 surfaces ── */

/* Suggestion provenance line under the chips (prefill-and-confirm). */
.wp-suggest-hint {
  margin: 0;
  font-size: var(--text-xs);
  color: var(--accent);
}

/* Once-ever post-capture invitation, above the composer card. */
.wp-nudge {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  margin-block-end: var(--space-2);
  padding: var(--space-2) var(--space-3);
  background: var(--accent-soft);
  border: 1px solid var(--accent);
  border-radius: var(--radius-md);
}

.wp-nudge-text {
  flex: 1 1 auto;
  min-width: 0;
  font-size: var(--text-sm);
  color: var(--text);
}

.wp-nudge-close {
  flex: 0 0 auto;
  background: none;
  border: none;
  padding: 0 0.25rem;
  font-size: 1.1rem;
  line-height: 1;
  color: var(--muted);
  cursor: pointer;
}

.wp-nudge-close:hover { color: var(--text); }

/* Quiet staleness prompt at the top of the review state. */
.wp-stale {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  padding: var(--space-2) var(--space-3);
  background: var(--warn-soft);
  border: 1px solid var(--warn);
  border-radius: var(--radius-md);
  font-size: var(--text-sm);
  color: var(--text);
}

/* Admin adoption tiles (counts only). */
.wp-admin-stats {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr));
  gap: var(--space-3);
}

.wp-admin-stat {
  display: flex;
  flex-direction: column;
  gap: 0.125rem;
  padding: var(--space-3) var(--space-4);
  background: var(--card);
  border: 1px solid var(--card-b);
  border-radius: var(--radius-md);
}

.wp-admin-stat-value {
  font-size: var(--text-xl);
  font-weight: 700;
  color: var(--text);
  font-variant-numeric: tabular-nums;
}

.wp-admin-stat-label {
  font-size: var(--text-xs);
  color: var(--muted);
}

/* ── Product tour (onboarding — chat page) ──
   The multi-step modal rendered into the #tourModal .outlook-composer shell by
   js/chat/tour.js. The media frame, the dots and the footer nav are emitted by
   renderStepContent()/navHtml(), which the future spotlight presenter reuses
   verbatim — so anything POSITIONAL belongs on the presenter, never here.
   Tokens only, logical properties only (RTL-correct by construction).
   components.css loads after style.css, so these win over .outlook-composer
   without !important. */

.tour-card { width: min(760px, 100%); }
.tour-card:focus { outline: none; }          /* programmatic focus target only */
.tour-card:focus-visible {
  outline: 2px solid color-mix(in srgb, var(--accent) 82%, var(--text));
  outline-offset: -2px;
}

.tour-body-wrap {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  padding-block: var(--space-4);
}

/* ── Media frame ── */
.tour-media {
  position: relative;
  aspect-ratio: 16 / 10;
  border-radius: var(--radius-lg);
  border: 1px solid var(--card-b);
  background: var(--input-bg);
  overflow: hidden;
  flex: 0 0 auto;
}

.tour-media-img {
  display: block;
  inline-size: 100%;
  block-size: 100%;
  /* `contain`, never `cover`: cropping a screenshot hides the very UI the step
     is pointing at (the documented logo-crop bug, in a new place). */
  object-fit: contain;
  object-position: center top;   /* vertical only — direction-neutral */
  opacity: 0;
  transition: opacity 0.18s ease;
}
.tour-media-img.is-loaded { opacity: 1; }

.tour-media-skeleton {
  position: absolute;
  inset: 0;
  background: linear-gradient(
    100deg,
    var(--input-bg) 30%,
    color-mix(in srgb, var(--surface-b) 60%, var(--input-bg)) 50%,
    var(--input-bg) 70%
  );
  background-size: 220% 100%;
  animation: tour-shimmer 1.25s linear infinite;
}
@keyframes tour-shimmer {
  from { background-position: 180% 0; }
  to   { background-position: -80% 0; }
}
.tour-media.is-loaded .tour-media-skeleton,
.tour-media.is-error  .tour-media-skeleton { display: none; }

/* A screenshot that 404s (or has not been captured yet) degrades to a designed
   panel. A broken-image glyph in an onboarding flow reads as a broken product. */
.tour-media-fallback {
  position: absolute;
  inset: 0;
  display: none;
  align-items: center;
  justify-content: center;
  color: color-mix(in srgb, var(--accent) 55%, var(--muted));
  background:
    radial-gradient(circle at 50% 40%,
      color-mix(in srgb, var(--accent) 10%, transparent), transparent 65%),
    var(--input-bg);
}
.tour-media.is-error .tour-media-img { display: none; }
.tour-media.is-error .tour-media-fallback { display: flex; }
.tour-media-fallback svg { inline-size: 2.75rem; block-size: 2.75rem; }

/* ── Copy ── */
.tour-eyebrow {
  margin: 0;
  font-size: var(--text-xs);
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--accent);
  text-align: start;
}
.tour-title {
  margin: 0;
  font-size: var(--text-2xl);
  font-weight: 650;
  line-height: var(--leading-tight);
  color: var(--text);
  text-align: start;
}
.tour-body {
  margin: 0;
  font-size: var(--text-md);
  line-height: var(--leading-relaxed);
  color: var(--muted);
  text-align: start;
}
.tour-cta { align-self: start; }

.tour-note {
  margin: 0;
  font-size: var(--text-xs);
  color: var(--muted);
  text-align: start;
}
.tour-note button {
  background: none;
  border: none;
  padding: 0;
  color: var(--accent);
  font: inherit;
  font-weight: 600;
  cursor: pointer;
  text-decoration: underline;
  text-underline-offset: 2px;
}

/* ── Footer: skip · dots · nav ── */
.tour-footer {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-3) 1.1rem;
  border-block-start: 1px solid var(--surface-b);
  background: var(--card);
}
.tour-skip { margin-inline-end: auto; }

.tour-dots { display: flex; align-items: center; gap: var(--space-2); }

/* Dots are aria-hidden + tabindex=-1: a decorative pointer shortcut. They add
   nothing a keyboard user lacks (arrow keys, Home/End, the #tourProgress live
   region and the visible counter all cover it) and would otherwise put six
   extra tab stops inside a six-step dialog. */
.tour-dot {
  inline-size: 0.5rem;
  block-size: 0.5rem;
  padding: 0;
  border: none;
  border-radius: var(--radius-full);
  background: var(--surface-b);
  cursor: pointer;
  transition: background-color 0.15s ease, inline-size 0.15s ease;
}
.tour-dot:hover { background: var(--muted); }
.tour-dot.is-active { background: var(--accent); inline-size: 1.25rem; }
.tour-dot.is-done { background: color-mix(in srgb, var(--accent) 45%, var(--surface-b)); }

.tour-nav { display: flex; align-items: center; gap: var(--space-2); }
.tour-nav-btn { display: inline-flex; align-items: center; gap: var(--space-2); }
.tour-nav-icon { inline-size: 0.875rem; block-size: 0.875rem; flex: 0 0 auto; }
/* No logical property mirrors a path; scaleX is the standard idiom. Scoped to
   the icon so the label never mirrors with it. */
[dir="rtl"] .tour-nav-icon { transform: scaleX(-1); }

/* Reserved for the spotlight presenter: promotes an anchored element above the
   backdrop without disturbing its layout. .outlook-composer is z-index 70. */
.tour-anchor-raised { position: relative; z-index: 71; }

/* ── Responsive ── */

@media (max-width: 1023px) {
  #tourModal .tour-card { width: min(600px, 100%); }
  .tour-title { font-size: var(--text-xl); }
}

/* Phone: a bottom sheet. The .outlook-composer shell centres its card and pads
   1rem, both of which are wrong here — a centred 16:10 card leaves the footer
   floating mid-screen and wastes the safe bottom edge. */
@media (max-width: 640px) {
  #tourModal.outlook-composer { align-items: flex-end; padding: 0; }
  #tourModal .tour-card {
    width: 100%;
    max-height: 92dvh;
    border-start-start-radius: var(--radius-xl);
    border-start-end-radius: var(--radius-xl);
    border-end-start-radius: 0;
    border-end-end-radius: 0;
  }
  .tour-media { aspect-ratio: 16 / 11; }
  .tour-title { font-size: var(--text-lg); }
  .tour-footer {
    flex-wrap: wrap;
    padding-block-end: max(var(--space-3), env(safe-area-inset-bottom));
  }
  .tour-skip { order: 3; flex-basis: 100%; margin-inline-end: 0; align-self: start; }
  .tour-nav { margin-inline-start: auto; }
  .tour-nav-btn { min-height: 2.25rem; }   /* 36px touch target */
}

/* Landscape phone / short window: the image yields, never the copy. */
@media (max-height: 560px) {
  .tour-media { aspect-ratio: 21 / 9; max-block-size: 34dvh; }
}

@media (prefers-reduced-motion: reduce) {
  .tour-media-skeleton { animation: none; }
  .tour-media-img { transition: none; opacity: 1; }
  .tour-dot { transition: none; }
}

/* ── Share dialog (dashboard-share.js) ─────────────────────────────────────
   One dialog for every shareable subject (dashboards, saved analytics,
   playbooks). Lives here, not in dashboard.css, because the dialog is generic:
   a page that never loads the dashboard stylesheet must still be able to open
   it. The `--dash-*` variables are the Mission Control palette; each falls back
   to the platform token so the dialog reads correctly on any page. */
body.dv-share-locked { overflow: hidden; }
.dv-share-modal { position: fixed; inset: 0; z-index: 120; display: flex; }
.dv-share-backdrop {
  position: absolute; inset: 0;
  background: rgba(15, 23, 42, 0.55);
  -webkit-backdrop-filter: blur(2px);
  backdrop-filter: blur(2px);
  animation: dv-fade 0.16s ease-out;
}
@keyframes dv-fade { from { opacity: 0; } }
.dv-share-panel {
  position: relative; margin: auto;
  width: min(520px, calc(100vw - 32px)); max-height: min(88vh, 760px);
  display: flex; flex-direction: column;
  background: var(--dash-surface, var(--card)); color: var(--dash-text, var(--text));
  border: 1px solid var(--dash-border, var(--card-b)); border-radius: 18px;
  box-shadow: 0 24px 60px rgba(0, 0, 0, 0.32);
  animation: dv-rise 0.18s ease-out;
}
@keyframes dv-rise { from { opacity: 0; transform: translateY(10px); } }
.dv-share-head {
  display: flex; align-items: flex-start; gap: 12px;
  padding: 18px 16px 14px 20px;
  border-bottom: 1px solid var(--dash-border, var(--card-b));
}
.dv-share-head > div:first-child { flex: 1 1 auto; min-width: 0; }
.dv-share-title { margin: 0; font-size: 16px; font-weight: 700; }
.dv-share-subtitle {
  margin: 3px 0 0; font-size: 12.5px; color: var(--dash-muted, var(--muted));
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.dv-share-close {
  flex: 0 0 auto; width: 30px; height: 30px;
  display: inline-flex; align-items: center; justify-content: center;
  border: 0; border-radius: 8px; background: transparent; color: var(--dash-muted, var(--muted)); cursor: pointer;
}
.dv-share-close svg { width: 17px; height: 17px; }
.dv-share-close:hover { background: rgba(148, 163, 184, 0.14); color: var(--dash-text, var(--text)); }
.dv-share-body { flex: 1 1 auto; overflow-y: auto; padding: 16px 20px 20px; }
.dv-share-section {
  margin: 0 0 10px; font-size: 11px; font-weight: 700;
  text-transform: uppercase; letter-spacing: 0.07em; color: var(--dash-muted, var(--muted));
}
.dv-share-section-row { margin-top: 22px; }

/* visibility choice */
.dv-share-opts { display: flex; flex-direction: column; gap: 8px; }
.dv-share-opt {
  display: flex; align-items: flex-start; gap: 11px; text-align: start;
  padding: 12px 14px; font: inherit; cursor: pointer;
  border: 1px solid var(--dash-border, var(--card-b)); border-radius: 13px;
  background: color-mix(in srgb, var(--dash-bg, var(--bg)) 55%, transparent);
  color: var(--dash-text, var(--text));
  transition: border-color 0.15s ease, background 0.15s ease;
}
.dv-share-opt:hover { border-color: rgba(var(--accent-rgb), 0.4); }
.dv-share-opt.is-on {
  border-color: var(--dash-accent, var(--accent));
  background: rgba(var(--accent-rgb), 0.09);
  box-shadow: 0 0 0 1px rgba(var(--accent-rgb), 0.3);
}
.dv-share-radio {
  flex: 0 0 auto; width: 16px; height: 16px; margin-top: 2px;
  border: 2px solid var(--dash-border, var(--card-b)); border-radius: 50%;
  transition: border-color 0.15s ease;
}
.dv-share-opt.is-on .dv-share-radio {
  border-color: var(--dash-accent, var(--accent));
  box-shadow: inset 0 0 0 3px var(--dash-surface, var(--card)), inset 0 0 0 8px var(--dash-accent, var(--accent));
}
.dv-share-opt-icon {
  flex: 0 0 auto; display: inline-flex; align-items: center; justify-content: center;
  width: 30px; height: 30px; border-radius: 9px;
  color: var(--dash-muted, var(--muted)); background: color-mix(in srgb, var(--dash-muted, var(--muted)) 12%, transparent);
}
.dv-share-opt.is-on .dv-share-opt-icon { color: var(--dash-accent, var(--accent)); background: rgba(var(--accent-rgb), 0.14); }
.dv-share-opt-icon svg { width: 16px; height: 16px; }
.dv-share-opt-text { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.dv-share-opt-title { font-size: 13.5px; font-weight: 600; }
.dv-share-opt-sub { font-size: 12px; line-height: 1.5; color: var(--dash-muted, var(--muted)); }
/* Governed "everyone" option (playbooks): unavailable here, or awaiting a decision. */
.dv-share-opt.is-disabled { cursor: not-allowed; opacity: .6; }
.dv-share-opt.is-disabled:hover { border-color: var(--dash-border, var(--card-b)); }
.dv-share-opt.is-pending { cursor: default; border-style: dashed; }
.dv-share-opt-action {
  align-self: flex-start; margin-top: 4px; padding: 3px 10px; border-radius: 999px;
  font-size: 12px; font-weight: 600; cursor: pointer;
  color: var(--dash-accent, var(--accent)); background: rgba(var(--accent-rgb), 0.1);
}
.dv-share-opt-action:hover { background: rgba(var(--accent-rgb), 0.18); }

/* add + list grants */
.dv-share-add { display: flex; gap: 8px; flex-wrap: wrap; }
.dv-share-select, .dv-share-input {
  height: 36px; padding: 0 10px; border-radius: 9px; font: inherit; font-size: 13px;
  border: 1px solid var(--dash-border, var(--card-b)); background: var(--dash-bg, var(--bg)); color: var(--dash-text, var(--text));
}
.dv-share-input { flex: 1 1 160px; min-width: 0; }
.dv-share-select:focus, .dv-share-input:focus {
  outline: none; border-color: var(--dash-accent, var(--accent));
  box-shadow: 0 0 0 3px rgba(var(--accent-rgb), 0.18);
}
.dv-share-add-btn {
  height: 36px; padding: 0 16px; border: 0; border-radius: 9px;
  background: var(--dash-accent, var(--accent)); color: var(--accent-contrast, #14172E);
  font: inherit; font-size: 13px; font-weight: 600; cursor: pointer;
}
.dv-share-add-btn:hover { background: color-mix(in srgb, var(--dash-accent, var(--accent)) 84%, #000); }
.dv-share-grants { list-style: none; margin: 12px 0 0; padding: 0; display: flex; flex-direction: column; gap: 6px; }
.dv-share-grant {
  display: flex; align-items: center; gap: 11px;
  padding: 8px 10px; border-radius: 11px;
  background: color-mix(in srgb, var(--dash-bg, var(--bg)) 55%, transparent);
  border: 1px solid var(--dash-border, var(--card-b));
}
.dv-share-grant-avatar {
  flex: 0 0 auto; width: 30px; height: 30px; border-radius: 50%;
  display: inline-flex; align-items: center; justify-content: center;
  font-size: 12.5px; font-weight: 700; color: var(--accent-contrast, #14172E);
  background: linear-gradient(135deg, var(--dash-accent, var(--accent)), color-mix(in srgb, var(--dash-accent, var(--accent)) 58%, #000));
}
.dv-share-grant-avatar.is-dept {
  border-radius: 9px;
  background: linear-gradient(135deg, var(--dash-purple, var(--info)), color-mix(in srgb, var(--dash-purple, var(--info)) 58%, #000));
}
.dv-share-grant-avatar.is-unit {
  color: var(--dash-accent, var(--accent));
  background: rgba(var(--accent-rgb), 0.12);
  border-radius: 8px;
}
.dv-share-grant-text { display: flex; flex-direction: column; min-width: 0; flex: 1 1 auto; }
.dv-share-grant-name {
  font-size: 13px; font-weight: 600;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.dv-share-grant-kind { font-size: 11px; color: var(--dash-muted, var(--muted)); }
.dv-share-remove {
  flex: 0 0 auto; padding: 5px 10px; border-radius: 8px;
  border: 1px solid transparent; background: transparent;
  color: var(--dash-muted, var(--muted)); font: inherit; font-size: 12px; cursor: pointer;
}
.dv-share-remove:hover {
  color: var(--dash-rose, var(--danger));
  border-color: color-mix(in srgb, var(--dash-rose, var(--danger)) 40%, transparent);
}
.dv-share-empty, .dv-share-loading { font-size: 12.5px; color: var(--dash-muted, var(--muted)); padding: 2px 2px 0; }
/* validate-before-add feedback (typeahead adapters) */
.dv-share-hint { margin: 6px 2px 0; font-size: 12px; color: var(--dash-rose, var(--danger)); }
.dv-share-hint[hidden] { display: none; }

/* link + privacy note */
.dv-share-link {
  display: flex; align-items: center; gap: 10px; margin-top: 22px;
  padding: 9px 10px 9px 12px; border-radius: 11px;
  border: 1px solid var(--dash-border, var(--card-b));
  background: color-mix(in srgb, var(--dash-bg, var(--bg)) 55%, transparent);
}
.dv-share-link-icon { flex: 0 0 auto; display: inline-flex; color: var(--dash-muted, var(--muted)); }
.dv-share-link-icon svg { width: 16px; height: 16px; }
.dv-share-link-url {
  flex: 1 1 auto; min-width: 0; font-size: 12px; color: var(--dash-muted, var(--muted));
  font-family: "JetBrains Mono", ui-monospace, monospace;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  direction: ltr; text-align: start;
}
.dv-share-copy {
  flex: 0 0 auto; height: 30px; padding: 0 12px;
  border: 1px solid var(--dash-border, var(--card-b)); border-radius: 8px;
  background: var(--dash-surface, var(--card)); color: var(--dash-text, var(--text));
  font: inherit; font-size: 12.5px; font-weight: 600; cursor: pointer;
}
.dv-share-copy:hover { border-color: var(--dash-accent, var(--accent)); color: var(--dash-accent, var(--accent)); }
.dv-share-copy.is-done { color: var(--dash-emerald, var(--ok)); border-color: var(--dash-emerald, var(--ok)); }
.dv-share-note {
  display: flex; gap: 9px; margin: 18px 0 0;
  font-size: 11.5px; line-height: 1.6; color: var(--dash-muted, var(--muted));
}
.dv-share-note-icon { flex: 0 0 auto; color: var(--dash-emerald, var(--ok)); }
.dv-share-note-icon svg { width: 15px; height: 15px; }
.dv-share-foot {
  display: flex; justify-content: flex-end; gap: 8px;
  padding: 13px 20px; border-top: 1px solid var(--dash-border, var(--card-b));
}
.dv-share-done {
  height: 34px; padding: 0 18px; border: 0; border-radius: 9px;
  background: var(--dash-accent, var(--accent)); color: var(--accent-contrast, #14172E);
  font: inherit; font-size: 13px; font-weight: 600; cursor: pointer;
}
.dv-share-done:hover { background: color-mix(in srgb, var(--dash-accent, var(--accent)) 84%, #000); }

/* ── Connected accounts cards (js/connected-accounts-cards.js) ───────────────
   The person's OWN sign-in on a connected system. These live here, not on a
   page, because TWO surfaces render the same cards: the chat dialog and
   /profile. They were page-scoped in profile.html until the dialog existed;
   a copy in chat's stylesheet would have been a second visual language for
   one component. The `pp-` prefix is kept so the cards keep their identity
   across both hosts (and in the smoke tests that pin them). */
.pp-card-head { margin-block-end: var(--space-3); }
.pp-card-head h2 { margin: 0; font-size: var(--text-lg); font-weight: 650; }
.pp-card-head p { margin: 0.125rem 0 0; font-size: var(--text-xs); color: var(--muted); }

.pp-msg { min-height: 1.2em; margin: var(--space-2) 0 0; font-size: var(--text-xs); color: var(--danger); }
.pp-msg.is-ok { color: var(--accent); }

.pp-identity-cards { display: flex; flex-direction: column; gap: var(--space-5); }
.pp-identity-cards[hidden] { display: none; }
.pp-identity-note { margin: 0 0 var(--space-2); font-size: var(--text-xs); color: var(--muted); }
.pp-identity-status {
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2);
  margin-block-end: var(--space-3); font-size: var(--text-sm);
}
.pp-identity-pill {
  display: inline-flex; align-items: center; padding: 0.1rem 0.6rem; border-radius: 999px;
  font-size: var(--text-xs); font-weight: 600; background: var(--surface-b); color: var(--muted);
}
.pp-identity-pill.is-ok { background: var(--accent-soft); color: var(--accent); }
.pp-identity-pill.is-warn { background: color-mix(in srgb, var(--danger) 14%, transparent); color: var(--danger); }
.pp-identity-muted { color: var(--muted); }
.pp-identity-form { display: grid; gap: var(--space-2); max-inline-size: 28rem; }
.pp-identity-label { font-size: var(--text-xs); font-weight: 600; color: var(--muted); }
.pp-identity-hint { margin: 0; font-size: var(--text-xs); color: var(--muted); }
.pp-identity-actions { display: flex; flex-wrap: wrap; gap: var(--space-2); margin-block-start: var(--space-2); }
.pp-identity-card.is-target { outline: 2px solid var(--accent); outline-offset: 2px; }
/* An author `display` beats the UA's [hidden] rule; keep the attribute meaningful. */
.pp-identity-form[hidden], .pp-identity-actions[hidden], .pp-identity-hint[hidden] { display: none; }

/* ── Connected accounts, in the chat dialog ──────────────────────────────────
   Inside a dialog the card chrome is noise: the panel is already the card. The
   cards keep their own boundary only when there is more than one to tell apart,
   which is what the sibling selector below draws. */
.ca-modal-sub { margin: 0.125rem 0 0; font-size: var(--text-xs); color: var(--muted); }
.ca-modal-body .pp-identity-cards { gap: 0; }
.ca-modal-body .pp-identity-card,
.ca-modal-body .pp-identity-empty {
  background: transparent; border: 0; border-radius: 0; padding: 0;
}
.ca-modal-body .pp-identity-card + .pp-identity-card {
  margin-block-start: var(--space-5);
  padding-block-start: var(--space-5);
  border-block-start: 1px solid var(--card-b);
}
/* An OUTSET outline is clipped by the scrolling body at the panel's edge, so
   the highlight that says "this is the card you were sent to" draws inside. */
.ca-modal-body .pp-identity-card.is-target { outline-offset: -2px; }
