/* Fonts (IBM Plex Sans / Mono, Space Grotesk) load via <link rel="preconnect"> +
   <link rel="stylesheet"> in each page <head> (index / share / demo) instead of a
   render-blocking @import, so the CSS and the fonts fetch in parallel. display=swap
   is preserved on the <link>. */

/* ------------------------------------------------------------------
   Design tokens — 8 px spacing scale, calmer palette, larger radii.
   Surfaces lean on soft glass; dense tables / log lists fall back to
   solid white so legibility never depends on the backdrop blur.
   ------------------------------------------------------------------ */
:root {
  --bg: #eef3fb;
  --bg-soft: #f6f9ff;
  --surface: rgba(255, 255, 255, 0.72);
  --surface-strong: rgba(255, 255, 255, 0.92);
  --surface-solid: #ffffff;
  --border: rgba(148, 163, 184, 0.22);
  --border-strong: rgba(59, 130, 246, 0.18);
  --ink: #0f172a;
  --ink-soft: #5b6b82;
  --ink-mute: #7c8aa1;
  --accent: #2563eb;
  --accent-strong: #1d4ed8;
  --accent-soft: #e2ecff;
  --accent-soft-2: #eff6ff;
  --good: #0f9f6e;
  --warn: #d97706;
  --bad: #dc2626;

  --s-1: 4px;
  --s-2: 8px;
  --s-3: 12px;
  --s-4: 16px;
  --s-5: 20px;
  --s-6: 24px;
  --s-7: 32px;
  --s-8: 40px;

  --radius-sm: 12px;
  --radius-md: 18px;
  --radius-lg: 24px;

  /* Motion — one decelerating "settle" curve (the same one share.css already
     uses) so hovers, drawers and entrances share a single coherent feel. */
  --ease: cubic-bezier(0.22, 1, 0.36, 1);

  /* Elevation ramp — sm (hairline lift) → soft (resting cards) → elev (lifted). */
  --shadow-sm: 0 4px 12px rgba(15, 23, 42, 0.06);
  --shadow-soft: 0 14px 36px rgba(15, 23, 42, 0.08);
  --shadow-elev: 0 22px 60px rgba(37, 99, 235, 0.12);

  /* Drawer width is shared by both side drawers (Activity + Targeting). */
  --drawer-width: 520px;
}

* { box-sizing: border-box; }
html, body { height: 100%; }

body {
  margin: 0;
  color: var(--ink);
  font-family: 'IBM Plex Sans', 'Segoe UI', sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  background:
    radial-gradient(110% 90% at 0% 0%, rgba(37, 99, 235, 0.14), transparent 60%),
    radial-gradient(90% 90% at 100% 0%, rgba(59, 130, 246, 0.12), transparent 55%),
    linear-gradient(180deg, #f5f9ff 0%, #eef3fb 60%, #f7faff 100%);
  min-height: 100vh;
  /* Animate the side padding when a drawer pushes the layout so the reflow is smooth. */
  transition: padding-left 260ms var(--ease), padding-right 260ms var(--ease);
}

h1, h2, h3, p { margin: 0; }
h1, h2, h3 { font-family: 'Space Grotesk', 'IBM Plex Sans', sans-serif; }

button, input, select, textarea { font: inherit; }

/* Keeps elements queryable by JS while removing them from the visual layout — used for
   the connection / cluster / service chips that controls.js still writes into. */
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* ------------------------------------------------------------------
   Shell & top bar.
   ------------------------------------------------------------------ */
.app-shell {
  width: 100%;
  max-width: 1320px;
  margin: 0 auto;
  padding: var(--s-6) var(--s-6) var(--s-7);
  display: flex;
  flex-direction: column;
  gap: var(--s-5);
}

.topbar {
  /* Width is 800 px — aligned with the hero card below. Kept narrow on purpose:
     the return feed is a low-bitrate WebRTC stream, so a tighter column keeps the
     preview small + sharp instead of upscaling it, and closes the action-row gaps. */
  width: 100%;
  max-width: 800px;
  margin: 0 auto;
  /* 2026-06-17 — Topbar is a flex row that WRAPS instead of a 2-track grid.
     The old `minmax(0,1fr) auto` grid let the brand track collapse to 0 while
     the `auto` actions track took max-content, so on a full super_admin row the
     nowrap wordmark spilled (overflow:visible) under the action pills. As flex
     with flex-wrap, the action row drops below the brand when it no longer fits
     rather than overlapping it. */
  /* 2026-06-17 — Two-row header: brand on its own line, the action row beneath it.
     align-items:stretch makes the action row full-width so the pipe (auto-margins,
     below) can pin the identity/admin cluster left and the nav cluster right. */
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: var(--s-3);
  padding: var(--s-4) var(--s-5);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  backdrop-filter: blur(18px);
  box-shadow: var(--shadow-soft);
}

/* Updated 2026-05-15 Europe/Paris — Wordmark is now a deliberate two-line logo:
   "InStream Ad" / "Control Room". Tighter line-height and bolder weight give it
   a logotype feel instead of a wrapping sentence. The <br /> in index.html is
   what splits the lines; CSS just styles the result. */
.topbar-brand { min-width: 0; flex: 0 0 auto; }

.topbar-brand h1 {
  font-size: clamp(1.15rem, 1.4vw, 1.3rem);
  line-height: 1.1;
  letter-spacing: -0.015em;
  font-weight: 700;
  color: var(--ink);
}

/* 2026-06-17 — Wordmark is a single line ("InStream Ad Control Room"): the two
   spans sit inline, the second in the accent colour. (Was a stacked 2-line logo.)
   On a narrow viewport the line may still break between the two phrases. */
.topbar-brand h1 .brand-line {
  white-space: nowrap;
}

.topbar-brand h1 .brand-line + .brand-line {
  margin-left: 0.4em;
  color: var(--accent-strong);
}

/* 2026-06-17 — Operator topbar drops the "IN-STREAM CONSOLE" kicker so the brand
   is a single line that sits level with the nav row (premium single-row header).
   Scoped to `.topbar .topbar-brand` so it hits ONLY the operator header — the admin
   console (.admin-topbar > .topbar-brand, "Super Admin Console") and the share/demo
   eyebrows still show. Copy stays in the markup, so this is one line to undo. */
.topbar .topbar-brand .eyebrow { display: none; }

.eyebrow {
  display: block;
  font-size: 0.66rem;
  font-weight: 700;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--accent-strong);
  margin-bottom: 2px;
  white-space: nowrap;
}

.topbar-actions {
  display: flex;
  flex: 1 1 auto;
  min-width: 0;
  gap: var(--s-2);
  flex-wrap: wrap;
  justify-content: flex-start;
}

/* Action buttons keep their intrinsic width and wrap to a new row rather than clipping. */
.topbar-actions .btn,
.admin-topbar-actions .btn { flex: 0 0 auto; }

/* Status / meta chips kept for the (hidden) DOM nodes the controllers write into. */
.status-badge,
.meta-badge {
  display: inline-flex;
  align-items: center;
  gap: var(--s-2);
  padding: 5px 10px;
  border-radius: 999px;
  font-size: 0.74rem;
  font-weight: 700;
  border: 1px solid transparent;
}

.status-idle        { background: #e9f1ff; color: #2751a3; border-color: rgba(37, 99, 235, 0.14); }
.status-connecting,
.status-novideo     { background: #fff4de; color: #975c07; border-color: rgba(217, 119, 6, 0.16); }
.status-connected   { background: #e8faf3; color: #0f7e58; border-color: rgba(15, 159, 110, 0.16); }
.status-disconnected{ background: #eef2f8; color: #4d6078; border-color: rgba(100, 116, 139, 0.18); }
.status-failed      { background: #feebef; color: #b4233c; border-color: rgba(220, 38, 38, 0.18); }
.meta-badge         { background: var(--accent-soft-2); color: #274c95; border-color: rgba(37, 99, 235, 0.14); }

/* ------------------------------------------------------------------
   Stage layout — single centered Hero card. The Activity Center and
   the Service Targeting panels are slide-in drawers (left and right
   respectively), so the main stage stays focused on the video and
   the two trigger buttons.
   ------------------------------------------------------------------ */
.stage {
  display: flex;
  justify-content: center;
}

/* Hero card width tracks the topbar above (800 px). Deliberately narrow: the
   return feed is a low-bitrate WebRTC stream, so a tighter card keeps the picture
   small and sharp instead of upscaling it. */
.stage > .hero {
  width: 100%;
  max-width: 800px;
}

.card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  backdrop-filter: blur(18px);
  /* Soft drop shadow + a hairline top highlight so the glass card edge catches light. */
  box-shadow: var(--shadow-soft), inset 0 1px 0 rgba(255, 255, 255, 0.6);
  padding: var(--s-5);
}

.card-head { display: grid; gap: var(--s-1); margin-bottom: var(--s-3); }

.section-head {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: var(--s-3);
  flex-wrap: wrap;
}

.section-actions { display: flex; gap: var(--s-2); flex-wrap: wrap; }

.section-note {
  display: block;
  margin-top: 2px;
  font-size: 0.85rem;
  line-height: 1.45;
  color: var(--ink-soft);
}

.helper          { color: var(--ink-soft); font-size: 0.82rem; line-height: 1.45; }
.helper.compact  { margin-top: var(--s-2); }

/* ── Hero / video ────────────────────────────────────────────── */
.hero {
  display: flex;
  flex-direction: column;
  gap: var(--s-3);
}

.hero-head {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: var(--s-3);
  flex-wrap: wrap;
}

.hero-head h2 { font-size: 1.05rem; }
.hero-actions { display: flex; gap: var(--s-2); flex-wrap: wrap; }

.video-wrap {
  position: relative;
  width: 100%;
  aspect-ratio: 16 / 9;
  min-height: 240px;
  border-radius: var(--radius-md);
  overflow: hidden;
  border: 1px solid rgba(15, 23, 42, 0.12);
  background:
    radial-gradient(circle at top, rgba(37, 99, 235, 0.22), transparent 35%),
    linear-gradient(180deg, #040b16, #05101f 48%, #02070f);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.08);
}

#previewVideo {
  width: 100%;
  height: 100%;
  display: block;
  object-fit: contain;
  background: #000;
}

.video-overlay {
  /* 2026-06-17 — Compact status chip pinned bottom-left (no right anchor → auto
     width), with tighter padding + type, so it reads as a quiet badge over the
     video instead of a full-width bar. */
  position: absolute;
  left: var(--s-3);
  bottom: var(--s-3);
  max-width: calc(100% - var(--s-3) * 2);
  padding: 5px 9px;
  border-radius: 10px;
  color: #e8f2ff;
  background: rgba(6, 17, 31, 0.62);
  border: 1px solid rgba(148, 163, 184, 0.16);
  backdrop-filter: blur(8px);
  font-size: 0.74rem;
  transition: opacity 0.6s var(--ease), transform 0.6s var(--ease);
}

.video-overlay.overlay-hidden {
  opacity: 0;
  pointer-events: none;
  transform: translateY(8px);
}

.preview-summary {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: var(--s-3);
  flex-wrap: wrap;
}

.preview-hint { margin: 0; }

.disclosure-panel { margin-top: var(--s-2); }
.disclosure-panel[hidden] { display: none; }

.metadata-grid,
.beacon-context-grid {
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-md);
  background: linear-gradient(180deg, #f9fbff, #eef5ff);
  padding: var(--s-3);
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--s-3);
}

.meta-key {
  display: block;
  color: #5a6f92;
  font-size: 0.7rem;
  text-transform: uppercase;
  letter-spacing: 0.08em;
}

.metadata-grid strong,
.beacon-context-grid strong {
  display: block;
  margin-top: var(--s-1);
  font-size: 0.9rem;
  line-height: 1.35;
  word-break: break-word;
}

/* ------------------------------------------------------------------
   Inventory triggers — two big primary buttons, side by side, sitting
   directly under the video. No card chrome, no titles, no badges.
   ------------------------------------------------------------------ */
.quick-action-list {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--s-3);
  margin-top: var(--s-2);
}

.quick-action-list .helper { grid-column: 1 / -1; }

/* Each preset is wrapped in a slim div so the lockout progress bar can sit beneath
   its own button. The wrapper stays invisible — only the button reads as a control. */
.quick-card {
  display: flex;
  flex-direction: column;
  gap: var(--s-2);
  background: transparent;
  border: 0;
  padding: 0;
  cursor: default;
  box-shadow: none;
}

.quick-card.locked { opacity: 0.85; }
.quick-card-head, .quick-card-meta, .quick-card h3, .quick-card p { display: none; }

.quick-card .btn {
  width: 100%;
  min-height: 52px;
  font-size: 0.95rem;
  font-weight: 700;
  border-radius: var(--radius-sm);
  letter-spacing: 0.01em;
}

.quick-card-progress {
  position: relative;
  height: 5px;
  border-radius: 999px;
  background: rgba(148, 163, 184, 0.22);
  overflow: hidden;
}

.quick-card-progress-fill {
  position: absolute;
  inset: 0;
  width: 0%;
  background: linear-gradient(90deg, var(--accent), #3b82f6);
  border-radius: inherit;
  animation: quickActionLockoutFill linear forwards;
  animation-duration: 30000ms;
}

.quick-card-progress.quick-card-progress-idle .quick-card-progress-fill {
  background: rgba(148, 163, 184, 0.45);
}

@keyframes quickActionLockoutFill {
  from { width: 0%; }
  to   { width: 100%; }
}

/* ------------------------------------------------------------------
   Activity Center — lives inside the left drawer. Two tabs
   (Beacon Events, Operator Timeline). The drawer body becomes a
   vertical flex container with overflow hidden so the only scrollbar
   in the drawer is the one inside the beacon table.
   ------------------------------------------------------------------ */
.activity-drawer-body {
  padding-top: var(--s-3);
  display: flex;
  flex-direction: column;
  overflow: hidden;
  min-height: 0;
}

.activity-drawer-body .tab-panel {
  flex: 1 1 auto;
  min-height: 0;
  overflow: hidden;
}

.activity-drawer-body .tab-panel.is-active { display: flex; }

/* Inside the activity drawer the table fills whatever vertical space remains,
   and the wrap is the only element that scrolls. */
.activity-drawer-body .beacon-table-wrap {
  flex: 1 1 auto;
  min-height: 0;
  max-height: none;
}

/* Give the Operator Timeline log list the same stretch behaviour so it fills
   the panel instead of being capped by the global max-height: 360px rule. */
.activity-drawer-body .log-list {
  flex: 1 1 auto;
  min-height: 0;
  max-height: none;
}

/* The full User Agent string never fits a 520 px drawer column without truncation,
   so it is dropped here. The data is still rendered into the row (and surfaced via
   the "See All Logs" modal) — only the visible column inside the drawer is hidden. */
.activity-drawer-body .beacon-table th:nth-child(7),
.activity-drawer-body .beacon-table td:nth-child(7) { display: none; }

/* Asset and Session are the columns the operator actually scans, so they get the
   biggest widths and their text is allowed to wrap (instead of ellipsising) so
   the full id is always readable. Service and IP stay narrow as requested — the
   wrap scrolls horizontally a touch when the user wants to see them. */
.activity-drawer-body .beacon-table { min-width: 660px; }
.activity-drawer-body .beacon-table th,
.activity-drawer-body .beacon-table td { padding: 8px 10px; }
.activity-drawer-body .beacon-table th:nth-child(1),
.activity-drawer-body .beacon-table td:nth-child(1) { width: 78px; }    /* Time (HH:MM:SS) */
.activity-drawer-body .beacon-table th:nth-child(2),
.activity-drawer-body .beacon-table td:nth-child(2) { width: 100px; }   /* Event badge */
.activity-drawer-body .beacon-table th:nth-child(3),
.activity-drawer-body .beacon-table td:nth-child(3) { width: 184px; }   /* Asset (mediaId) — fits ~22-char ids on one line */
.activity-drawer-body .beacon-table th:nth-child(4),
.activity-drawer-body .beacon-table td:nth-child(4) { width: 160px; }   /* Session (sessid) */
.activity-drawer-body .beacon-table th:nth-child(5),
.activity-drawer-body .beacon-table td:nth-child(5) { width: 70px; }    /* Service — minimised */
.activity-drawer-body .beacon-table th:nth-child(6),
.activity-drawer-body .beacon-table td:nth-child(6) { width: 80px; white-space: nowrap; } /* IP — minimised */

/* Allow the asset / session strong text to wrap so long ids never truncate. */
.activity-drawer-body .beacon-table td:nth-child(3) .table-strong,
.activity-drawer-body .beacon-table td:nth-child(4) .table-strong {
  white-space: normal;
  overflow: visible;
  text-overflow: clip;
  word-break: break-word;
}

/* Subtle scrollbars on the scrollable surfaces inside the drawer + log views.
   Native scrollbars are too loud against the soft surfaces; these are thin,
   slightly muted, and only become more visible on hover. */
.beacon-table-wrap,
.log-list,
.drawer-body {
  scrollbar-width: thin;
  scrollbar-color: rgba(148, 163, 184, 0.35) transparent;
}

.beacon-table-wrap::-webkit-scrollbar,
.log-list::-webkit-scrollbar,
.drawer-body::-webkit-scrollbar { width: 8px; height: 8px; }

.beacon-table-wrap::-webkit-scrollbar-track,
.log-list::-webkit-scrollbar-track,
.drawer-body::-webkit-scrollbar-track { background: transparent; }

.beacon-table-wrap::-webkit-scrollbar-thumb,
.log-list::-webkit-scrollbar-thumb,
.drawer-body::-webkit-scrollbar-thumb {
  background: rgba(148, 163, 184, 0.32);
  border-radius: 999px;
  border: 2px solid transparent;
  background-clip: content-box;
}

.beacon-table-wrap::-webkit-scrollbar-thumb:hover,
.log-list::-webkit-scrollbar-thumb:hover,
.drawer-body::-webkit-scrollbar-thumb:hover {
  background: rgba(100, 116, 139, 0.5);
  background-clip: content-box;
}

.beacon-table-wrap::-webkit-scrollbar-corner,
.log-list::-webkit-scrollbar-corner,
.drawer-body::-webkit-scrollbar-corner { background: transparent; }

.tablist {
  display: flex;
  gap: var(--s-2);
  padding-top: var(--s-4);
  flex-wrap: wrap;
}

.tab {
  border: 1px solid var(--border);
  background: var(--surface-solid);
  color: var(--ink-soft);
  padding: 8px 14px;
  border-radius: 999px;
  font-size: 0.82rem;
  font-weight: 600;
  cursor: pointer;
  transition: color 140ms var(--ease), background-color 140ms var(--ease), border-color 140ms var(--ease);
}

.tab:hover { color: var(--ink); border-color: var(--border-strong); }

.tab.is-active {
  background: var(--accent);
  color: #fff;
  border-color: transparent;
}

.tab-panel { padding-top: var(--s-4); display: flex; flex-direction: column; gap: var(--s-3); }
.tab-panel[hidden] { display: none; }

.monitor-toolbar {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--s-3);
}

.toolbar-field { min-width: 0; }
.toolbar-utility { display: flex; gap: var(--s-2); align-items: center; flex-wrap: wrap; justify-content: space-between; }

.beacon-table-wrap {
  position: relative;
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  background: var(--surface-solid);
  overflow: auto;
  /* Fill the available drawer height — caps high so longer logs lists stay scannable. */
  max-height: calc(100vh - 320px);
  min-height: 420px;
}

.beacon-table {
  width: 100%;
  border-collapse: collapse;
  table-layout: fixed;
  min-width: 620px;
}

.beacon-table thead {
  position: sticky;
  top: 0;
  background: #f7fbff;
  border-bottom: 1px solid var(--border);
  z-index: 1;
}

.beacon-table th, .beacon-table td {
  padding: 10px 9px;
  text-align: left;
  vertical-align: top;
}

/* Time column needs room for the full "DD/MM/YYYY, HH:MM:SS" string; the asset
   column then takes whatever flexes after the fixed-width columns. */
.beacon-table th:nth-child(1), .beacon-table td:nth-child(1) { width: 138px; white-space: nowrap; }
.beacon-table th:nth-child(2), .beacon-table td:nth-child(2) { width: 96px; text-align: center; }
.beacon-table th:nth-child(6), .beacon-table td:nth-child(6) { width: 100px; white-space: nowrap; }
.beacon-table th:nth-child(7), .beacon-table td:nth-child(7) { width: 160px; }

.beacon-table th {
  font-size: 0.74rem;
  color: #60728d;
  text-transform: uppercase;
  letter-spacing: 0.08em;
}

.beacon-table td { font-size: 0.82rem; line-height: 1.2; }
.beacon-table tbody tr { border-bottom: 1px solid rgba(148, 163, 184, 0.16); }
.beacon-table tbody tr:last-child { border-bottom: none; }
.beacon-table tbody tr:hover { background: rgba(37, 99, 235, 0.03); }

.beacon-empty { display: none; padding: var(--s-4); }
.beacon-empty.visible { display: block; }

.table-strong {
  display: block;
  font-weight: 700;
  color: var(--ink);
  font-size: 0.82rem;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.table-subtext {
  display: block;
  margin-top: 2px;
  color: var(--ink-soft);
  font-size: 0.72rem;
  line-height: 1.2;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.table-user-agent {
  font-size: 0.78rem;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.event-badge {
  display: inline-flex;
  align-items: center;
  padding: 4px 8px;
  border-radius: 999px;
  font-size: 0.68rem;
  font-weight: 700;
  white-space: nowrap;
}

.event-badge.impression { color: #1d4ed8; background: #dbeafe; }
.event-badge.playback   { color: #0f766e; background: #d1fae5; }
.event-badge.interaction{ color: #7c3aed; background: #ede9fe; }
.event-badge.error      { color: #b4233c; background: #fee2e2; }
.event-badge.default    { color: #36527c; background: #e9f1ff; }

.monitor-footer {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: var(--s-3);
  flex-wrap: wrap;
}

/* ── Operator timeline ──────────────────────────────────────── */
.log-list {
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  background: var(--surface-solid);
  overflow: auto;
  max-height: 360px;
}

.log-item       { border-bottom: 1px solid rgba(148, 163, 184, 0.18); }
.log-item:last-child { border-bottom: none; }
.log-item:hover { background: rgba(37, 99, 235, 0.03); }
.log-item.open  { background: rgba(37, 99, 235, 0.05); }

.log-head {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: var(--s-2);
  padding: 12px 14px;
}

.log-line {
  min-width: 0;
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-2);
  align-items: baseline;
  font-size: 0.78rem;
  line-height: 1.5;
}

.log-time    { color: #4b5f7c; font-weight: 700; }
.log-type    { color: #36527c; font-weight: 600; }
.log-status  { font-weight: 700; }
.log-status.success { color: var(--good); }
.log-status.error   { color: var(--bad); }
.log-status.info    { color: #36527c; }

.log-message {
  color: var(--ink);
  flex: 1 1 240px;
  min-width: 0;
  word-break: break-word;
}

.log-toggle {
  border: 1px solid rgba(37, 99, 235, 0.16);
  background: var(--accent-soft);
  color: #274c95;
  padding: 6px 10px;
  border-radius: 999px;
  font-size: 0.76rem;
  font-weight: 700;
  cursor: pointer;
}

.log-details {
  margin: 0 14px 12px;
  padding-left: 12px;
  border-left: 2px solid rgba(59, 130, 246, 0.2);
  max-height: 0;
  opacity: 0;
  overflow: hidden;
  transition: max-height 220ms var(--ease), opacity 160ms var(--ease), padding-top 160ms var(--ease);
}

.log-item.open .log-details {
  max-height: 320px;
  opacity: 1;
  padding-top: var(--s-1);
}

.log-details p   { color: #4b5f7c; font-size: 0.76rem; }
.log-details pre {
  margin: var(--s-2) 0 0;
  max-height: 220px;
  overflow: auto;
  white-space: pre-wrap;
  word-break: break-word;
  font-size: 0.75rem;
  line-height: 1.45;
  padding: 10px;
  border-radius: 12px;
  background: var(--surface-solid);
  border: 1px solid var(--border);
}

/* ------------------------------------------------------------------
   Buttons.
   ------------------------------------------------------------------ */
.btn {
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  padding: 10px 14px;
  cursor: pointer;
  font-size: 0.86rem;
  font-weight: 600;
  min-height: 40px;
  /* Long labels truncate with an ellipsis instead of overflowing / clipping the control. */
  max-width: 100%;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  transition: transform 140ms var(--ease), border-color 140ms var(--ease), background-color 140ms var(--ease), box-shadow 140ms var(--ease);
}

.btn:hover    { transform: translateY(-1px); }
/* Unified, clearly-distinct disabled treatment (matches .share-btn): muted, desaturated, flat. */
.btn:disabled,
.btn[disabled] { opacity: 0.5; filter: saturate(0.6); cursor: not-allowed; transform: none; box-shadow: none; }

.btn-primary {
  color: #fff;
  /* 2026-06-17 — Richer vertical gradient + a top inner highlight give the primary
     buttons (incl. the Trigger Inventory presets) a dimensional, premium finish. */
  background: linear-gradient(180deg, #4f8bf7, var(--accent) 55%, var(--accent-strong));
  box-shadow: 0 8px 22px rgba(37, 99, 235, 0.32), inset 0 1px 0 rgba(255, 255, 255, 0.25);
  font-weight: 700;
}

/* Hover deepens the lift; disabled / locked presets stay flat (see .btn:disabled). */
.btn-primary:not(:disabled):hover {
  box-shadow: 0 12px 30px rgba(37, 99, 235, 0.40), inset 0 1px 0 rgba(255, 255, 255, 0.30);
}

.btn-soft {
  color: #1d4ed8;
  background: var(--accent-soft);
  border-color: rgba(37, 99, 235, 0.18);
}

.btn-ghost {
  color: #36527c;
  background: rgba(255, 255, 255, 0.7);
  border-color: var(--border);
}

/* 2026-06-17 — In the operator topbar, ghost buttons read as a lightweight premium
   link row: transparent + muted by default, brightening on hover, instead of a row
   of bordered white pills. Scoped to .topbar-actions, so .btn-ghost everywhere else
   (drawers, modals, share / admin) is untouched. The aria-expanded "drawer open"
   state below still wins via higher specificity. */
.topbar-actions .btn-ghost {
  background: transparent;
  border-color: transparent;
  color: var(--ink-soft);
  transition: background-color 140ms var(--ease), color 140ms var(--ease), transform 140ms var(--ease);
}

.topbar-actions .btn-ghost:hover {
  background: var(--accent-soft-2);
  color: var(--accent-strong);
}

.topbar-actions .btn-sm { padding: 7px 10px; }

/* Top-bar drawer toggles read as ghost (white) when their drawer is closed and as
   bluish-soft when open, so the operator can see at a glance which panels are out. */
.topbar-actions .btn[aria-expanded="true"] {
  color: #1d4ed8;
  background: var(--accent-soft);
  border-color: rgba(37, 99, 235, 0.22);
}

.btn-link {
  background: transparent;
  border: 0;
  color: var(--accent-strong);
  padding: 6px 0;
  min-height: 0;
  font-weight: 600;
}

.btn-link:hover { transform: none; text-decoration: underline; }

.btn-sm { padding: 7px 12px; font-size: 0.8rem; min-height: 34px; }

.danger {
  color: var(--bad);
  background: #fff0f3;
  border-color: rgba(220, 38, 38, 0.18);
}

/* 2026-06-01 — Client share-link manager result row (the one-time link). */
.share-link-result {
  margin-top: var(--s-3);
  padding: var(--s-3);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-md);
  background: var(--accent-soft-2);
}
.share-link-copy-row {
  display: flex;
  gap: var(--s-2);
  align-items: center;
  margin: var(--s-2) 0;
}
.share-link-copy-row .field-input { flex: 1; }

/* ── Form primitives ────────────────────────────────────────── */
.field-label {
  display: block;
  margin: var(--s-3) 0 var(--s-1);
  color: var(--ink-soft);
  font-size: 0.78rem;
  font-weight: 600;
}

.field-label.inline { margin: 0 0 var(--s-1); }

.field-input {
  width: 100%;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--surface-solid);
  color: var(--ink);
  padding: 10px 12px;
  transition: border-color 140ms var(--ease), box-shadow 140ms var(--ease);
}

.field-input:hover { border-color: var(--border-strong); }
.field-input:focus {
  outline: none;
  border-color: rgba(37, 99, 235, 0.55);
  box-shadow: 0 0 0 4px rgba(37, 99, 235, 0.12);
}

/* 2026-06-17 — Token-based keyboard focus ring. :focus-visible shows only for keyboard / assistive
   navigation, so mouse clicks stay clean while keyboard users always get a clear, accessible ring. */
.btn:focus-visible,
.btn-link:focus-visible,
.field-input:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.service-dual-select {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--s-3);
  margin-top: var(--s-2);
}

.check-wrap {
  display: inline-flex;
  align-items: center;
  gap: var(--s-2);
  color: #425b81;
  font-size: 0.85rem;
  font-weight: 600;
}

.check-wrap.compact { padding-bottom: 2px; }

.code,
.log-line,
.log-details p,
.log-details pre,
.beacon-url-preview { font-family: 'IBM Plex Mono', 'Consolas', 'Courier New', monospace; }

.beacon-url-preview { min-height: 120px; resize: vertical; }

.event-chip-list {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-2);
  margin-top: var(--s-2);
}

.event-chip {
  border: 1px solid rgba(59, 130, 246, 0.16);
  background: #f8fbff;
  color: #36527c;
  border-radius: 999px;
  padding: 6px 12px;
  font-size: 0.78rem;
  font-weight: 600;
  cursor: pointer;
  transition: transform 140ms var(--ease), border-color 140ms var(--ease), background-color 140ms var(--ease), color 140ms var(--ease);
}

.event-chip:hover { transform: translateY(-1px); border-color: rgba(37, 99, 235, 0.32); }

.event-chip.active {
  background: linear-gradient(135deg, var(--accent-strong), #3b82f6);
  color: #fff;
  border-color: transparent;
}

/* ------------------------------------------------------------------
   Targeting drawer.
   On desktop the drawer is non-modal: no backdrop, no blur, page
   content shifts left via body padding so the WebRTC video and the
   trigger buttons stay visible AND interactive while the drawer is
   open. On narrow viewports it falls back to an overlay sheet.
   ------------------------------------------------------------------ */
.drawer-backdrop {
  position: fixed;
  inset: 0;
  background: rgba(15, 23, 42, 0.32);
  backdrop-filter: blur(6px);
  opacity: 0;
  pointer-events: none;
  transition: opacity 220ms var(--ease);
  z-index: 40;
  display: none;
}

.drawer-backdrop.is-open { opacity: 1; pointer-events: auto; }

.drawer {
  position: fixed;
  top: 0;
  height: 100vh;
  width: var(--drawer-width);
  background: var(--surface-strong);
  transition: transform 260ms var(--ease);
  z-index: 41;
  display: flex;
  flex-direction: column;
}

/* Default (right-anchored) drawer — used by Service Targeting. */
.drawer,
.drawer.drawer-right {
  right: 0;
  left: auto;
  border-left: 1px solid var(--border);
  border-right: 0;
  box-shadow: -22px 0 60px rgba(15, 23, 42, 0.18);
  transform: translateX(100%);
}

/* Left-anchored drawer — used by Activity Center. Mirrors the right drawer. */
.drawer.drawer-left {
  left: 0;
  right: auto;
  border-left: 0;
  border-right: 1px solid var(--border);
  box-shadow: 22px 0 60px rgba(15, 23, 42, 0.18);
  transform: translateX(-100%);
}

.drawer.is-open { transform: translateX(0); }

.drawer-head {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: var(--s-3);
  padding: var(--s-4) var(--s-5);
  border-bottom: 1px solid var(--border);
}

.drawer-body {
  padding: var(--s-4) var(--s-5) var(--s-6);
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: var(--s-4);
  flex: 1 1 auto;
  min-height: 0;
}

.drawer-card { padding: var(--s-4); }
.drawer-card.beacon-lab[hidden] { display: none !important; }

/* Body padding shifts the page content out of the way when a drawer opens, so the
   video and the trigger buttons stay visible AND interactive. The activity drawer
   pushes from the left, the targeting drawer pushes from the right. */
body.drawer-activity-open  { padding-left: var(--drawer-width); }
body.drawer-targeting-open { padding-right: var(--drawer-width); }

/* ── Modals ─────────────────────────────────────────────────── */
.modal-backdrop {
  position: fixed;
  inset: 0;
  display: none;
  align-items: center;
  justify-content: center;
  padding: var(--s-5);
  background: rgba(15, 23, 42, 0.4);
  backdrop-filter: blur(12px);
  z-index: 50;
}

.modal-backdrop.open { display: flex; animation: modalFade 180ms var(--ease) both; }

/* 2026-06-17 — Soft modal entrance: the backdrop fades in, the panel lifts + settles. */
.modal-backdrop.open .modal-panel { animation: modalPop 220ms var(--ease) both; }

@keyframes modalFade { from { opacity: 0; } to { opacity: 1; } }
@keyframes modalPop {
  from { opacity: 0; transform: translateY(10px) scale(0.985); }
  to   { opacity: 1; transform: translateY(0) scale(1); }
}

.modal-panel {
  width: min(1120px, 100%);
  max-height: calc(100vh - 40px);
  display: grid;
  grid-template-rows: auto 1fr auto;
  overflow: hidden;
  padding: 0;
}

.log-history-modal-panel { width: min(1180px, 100%); }

.modal-header,
.modal-footer {
  padding: var(--s-4) var(--s-5);
  display: flex;
  justify-content: space-between;
  gap: var(--s-3);
  align-items: center;
}

.modal-header { border-bottom: 1px solid var(--border); }
.modal-footer { border-top: 1px solid var(--border); justify-content: flex-end; }

.modal-body {
  padding: var(--s-5);
  display: grid;
  grid-template-columns: 1fr 1.3fr;
  gap: var(--s-4);
  overflow: auto;
}

.modal-section {
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  background: var(--surface-solid);
  padding: var(--s-4);
}

.log-history-modal-body { grid-template-columns: 1fr; }
.log-history-modal-section { min-height: 0; }
.all-log-list { max-height: 60vh; overflow: auto; }

.cluster-list { margin-top: var(--s-3); display: grid; gap: var(--s-2); }

.cluster-row {
  border: 1px solid var(--border);
  border-radius: 14px;
  background: #ffffff;
  padding: var(--s-3);
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: var(--s-2);
}

.cluster-row-info { display: grid; gap: var(--s-1); min-width: 0; }
.cluster-row-meta { color: var(--ink-soft); font-size: 0.8rem; word-break: break-word; }

.cluster-row-config {
  background: linear-gradient(180deg, #f3f7ff, #eaf1ff);
  border-color: rgba(37, 99, 235, 0.18);
}

.cluster-source-badge {
  display: inline-flex;
  align-self: flex-start;
  align-items: center;
  padding: 2px 8px;
  border-radius: 999px;
  background: #e0e7ff;
  color: #3730a3;
  font-size: 0.7rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

.cluster-mode-toggle {
  display: flex;
  align-items: center;
  gap: var(--s-2);
  margin: var(--s-2) 0 var(--s-3);
  flex-wrap: wrap;
}

.cluster-mode-toggle .btn.cluster-mode-active {
  background: linear-gradient(135deg, var(--accent-strong), #3b82f6);
  color: #fff;
  border-color: transparent;
}

.cluster-mode-hint { flex: 1 1 240px; margin: 0; }
.cluster-row-actions { display: flex; gap: var(--s-2); flex-wrap: wrap; }

.form-error {
  margin: var(--s-2) 0 var(--s-3);
  padding: 10px 12px;
  border-radius: var(--radius-sm);
  color: var(--bad);
  background: #fff0f3;
  border: 1px solid rgba(220, 38, 38, 0.16);
  font-size: 0.86rem;
}

.grid-2 {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--s-3);
}

.compact-fields { margin-top: var(--s-2); }
.action-row     { display: flex; gap: var(--s-2); flex-wrap: wrap; margin-top: var(--s-4); }

/* Breathing room between an action row (e.g. Connect Preview) and a metadata grid
   that follows it inside the targeting drawer — without this they sit flush. */
.action-row + .metadata-grid { margin-top: var(--s-4); }

/* ------------------------------------------------------------------
   Breakpoints.
   ------------------------------------------------------------------ */
@media (max-width: 1279px) {
  .app-shell { padding: var(--s-5); }
  /* Tighten the header action row so all controls fit on the laptop width without clipping. */
  .topbar-actions { gap: var(--s-1); }
  .topbar-actions .btn-sm { padding: 7px 10px; }
}

@media (max-width: 1023px) {
  /* Drawers revert to overlay sheets — the layout can no longer afford the extra
     460 px on either side, so the body padding shift is disabled and the backdrop
     comes back to receive click-to-close. */
  .drawer { width: min(440px, 94vw); }
  .drawer-backdrop { display: block; }
  body.drawer-activity-open,
  body.drawer-targeting-open { padding-left: 0; padding-right: 0; }
  /* Header gets a touch tighter so the brand + action controls share the row comfortably. */
  .topbar { gap: var(--s-3); padding: var(--s-3) var(--s-4); }
}

@media (max-width: 767px) {
  .app-shell { padding: var(--s-3); gap: var(--s-3); }

  /* Topbar is already a column at base now; just tighten padding and let the pipe
     sit inline when the action row wraps. */
  .topbar { padding: var(--s-3); }
  .topbar-actions::before { margin-left: 0; margin-right: 0; }

  .card        { padding: var(--s-4); }
  .drawer-card { padding: var(--s-3); }

  .quick-action-list { grid-template-columns: 1fr 1fr; }
  .quick-card .btn   { min-height: 56px; font-size: 1rem; }

  .service-dual-select,
  .grid-2,
  .metadata-grid,
  .beacon-context-grid { grid-template-columns: 1fr; }

  .modal-body { grid-template-columns: 1fr; }
  .hero-head  { flex-direction: column; align-items: flex-start; }
}

.animate-up { animation: fadeUp 320ms var(--ease) both; }

@keyframes fadeUp {
  from { opacity: 0; transform: translateY(8px); }
  to   { opacity: 1; transform: translateY(0); }
}

/* ==================================================================
   Updated 2026-05-15 Europe/Paris — Enterprise identity surfaces.
   The login gate, the in-topbar user menu, and the Super Admin
   console all reuse the operator-page tokens (--accent, --surface,
   --radius-*, --s-*) so the whole product reads as one design system
   instead of a dark admin shell tacked onto a light operator UI.
   ================================================================== */

/* ── Login gate (LoginGate.js) ─────────────────────────────────
   Soft, calm overlay that matches the operator background instead
   of the previous near-black scrim. */
.login-gate-overlay {
  position: fixed;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--s-5);
  z-index: 9000;
  color: var(--ink);
  font-family: 'IBM Plex Sans', 'Segoe UI', sans-serif;
  background:
    radial-gradient(110% 90% at 0% 0%, rgba(37, 99, 235, 0.18), transparent 60%),
    radial-gradient(90% 90% at 100% 0%, rgba(59, 130, 246, 0.16), transparent 55%),
    linear-gradient(180deg, #f5f9ff 0%, #eef3fb 60%, #f7faff 100%);
  backdrop-filter: blur(2px);
}

.login-gate-card {
  width: min(440px, 100%);
  background: var(--surface-strong);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-elev);
  padding: var(--s-7) var(--s-6);
  text-align: center;
  backdrop-filter: blur(18px);
}

.login-gate-eyebrow {
  display: block;
  font-size: 0.66rem;
  font-weight: 700;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--accent-strong);
  margin-bottom: var(--s-3);
}

.login-gate-card h2 {
  font-size: 1.35rem;
  margin: 0 0 var(--s-2) 0;
  letter-spacing: -0.01em;
}

.login-gate-card .login-gate-message {
  color: var(--ink-soft);
  font-size: 0.92rem;
  line-height: 1.5;
  margin: 0 0 var(--s-5) 0;
}

.login-gate-card .login-gate-setup {
  margin-top: var(--s-4);
  font-size: 0.78rem;
  color: var(--ink-mute);
  line-height: 1.4;
}

/* ── User menu (UserMenu.js) ────────────────────────────────────
   Lives inside `.topbar-actions` so it inherits the pill rhythm.
   Identity shows up as a small pill instead of bare text. */
.user-menu {
  /* 2026-06-17 — display:contents promotes the injected Admin link, identity pill
     and Sign-out button into the .topbar-actions flex row directly, so they can be
     ordered (below) alongside the static nav buttons. No JS / DOM / id change. */
  display: contents;
}

/* Topbar action order (left → right): identity pill, Admin, Client Links, then the
   three drawer/cluster toggles, then Sign out. Flex `order` only — DOM untouched. */
.topbar-actions .admin-link           { order: 1; }
.topbar-actions .identity-pill        { order: 2; }
.topbar-actions #openShareLinksButton { order: 3; }
.topbar-actions #openActivityButton   { order: 5; }
.topbar-actions #openTargetingButton  { order: 6; }
.topbar-actions #manageClustersButton { order: 7; }
.topbar-actions #signout-btn          { order: 8; }

/* Pipe divider between the identity/admin/links cluster (orders 1–3) and the
   operational nav + Sign out (orders 5–8). A flex container's ::before is itself a
   flex item, so order:4 drops it cleanly between the two groups. */
.topbar-actions::before {
  content: "";
  order: 4;
  align-self: center;
  width: 1px;
  height: 18px;
  background: rgba(148, 163, 184, 0.55);
  /* Auto margins keep the pipe centered in the free space between the identity/admin
     cluster (left) and the nav + Sign-out cluster (right). */
  margin-left: auto;
  margin-right: auto;
}

/* Client Links reads as a blue pill like the Admin link (same identity/admin
   cluster), not as a light nav link. */
.topbar-actions #openShareLinksButton {
  background: var(--accent-soft);
  border-color: rgba(37, 99, 235, 0.18);
  color: var(--accent-strong);
  font-weight: 700;
  border-radius: 999px;
}

.topbar-actions #openShareLinksButton:hover {
  background: #d6e6ff;
  color: var(--accent-strong);
}

.user-menu .identity-pill {
  display: inline-flex;
  align-items: center;
  gap: var(--s-1);
  padding: 5px 8px 5px 10px;
  border-radius: 999px;
  background: var(--surface-strong);
  border: 1px solid var(--border);
  font-size: 0.78rem;
  font-weight: 600;
  color: var(--ink-soft);
  max-width: 180px;
  min-width: 0;
  overflow: hidden;
  white-space: nowrap;
}

/* 2026-06-17 — Role badge leads, email follows (a swap of the DOM order, which is
   email-then-badge), so the chip reads first. Applies to every role. CSS `order`
   only — UserMenu.js is untouched. */
.user-menu .identity-pill .role-badge { order: -1; }

/* Operator + admin have fewer toolbar items, so their pill shows more of the email
   to fill the empty space. super_admin keeps the original 180px — at this 800px
   column its row is already full, so a wider pill there wraps to a second line. */
.user-menu .identity-pill:has(.role-operator),
.user-menu .identity-pill:has(.role-admin) { max-width: 260px; }

.user-menu .identity-email {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  min-width: 0;
}

.role-badge {
  display: inline-flex;
  align-items: center;
  padding: 4px 10px;
  border-radius: 999px;
  font-size: 0.68rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  border: 1px solid transparent;
}

.role-badge.role-super_admin { background: var(--accent-soft); color: var(--accent-strong); border-color: rgba(37, 99, 235, 0.18); }
.role-badge.role-admin       { background: #ede9fe; color: #6d28d9; border-color: rgba(124, 58, 237, 0.18); }
.role-badge.role-operator    { background: #d1fae5; color: #0f766e; border-color: rgba(15, 159, 110, 0.16); }
.role-badge.role-user        { background: #eef2f8; color: #4d6078; border-color: rgba(100, 116, 139, 0.18); }

/* Admin link inside the topbar reads as a pill button to match the rest of the row. */
.user-menu .admin-link {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 7px 12px;
  border-radius: 999px;
  font-size: 0.8rem;
  font-weight: 700;
  text-decoration: none;
  color: var(--accent-strong);
  background: var(--accent-soft);
  border: 1px solid rgba(37, 99, 235, 0.18);
  transition: background-color 140ms var(--ease), transform 140ms var(--ease);
}

.user-menu .admin-link:hover { transform: translateY(-1px); background: #d6e6ff; }

/* Impersonation banner — calm amber strip, not the previous brick orange. */
.impersonation-banner {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 9100;
  display: none;
  align-items: center;
  justify-content: center;
  gap: var(--s-3);
  padding: 10px var(--s-5);
  background: linear-gradient(135deg, #fff4de, #ffe5b3);
  color: #7a4500;
  border-bottom: 1px solid rgba(217, 119, 6, 0.32);
  font-size: 0.85rem;
  font-weight: 600;
  box-shadow: 0 6px 18px rgba(217, 119, 6, 0.16);
}

.impersonation-banner.is-open { display: flex; }
.impersonation-banner strong { color: #5b3300; }

.impersonation-banner .impersonation-stop {
  background: #fff;
  color: #7a4500;
  border: 1px solid rgba(217, 119, 6, 0.32);
  padding: 6px 12px;
  border-radius: 999px;
  font-size: 0.78rem;
  font-weight: 700;
  cursor: pointer;
  transition: transform 140ms var(--ease), background-color 140ms var(--ease);
}

.impersonation-banner .impersonation-stop:hover { transform: translateY(-1px); background: #fff8e7; }
.impersonation-banner .impersonation-stop:disabled { opacity: 0.6; cursor: default; transform: none; }

/* 2026-06-18 — Surfaced failure when "Return to Super Admin" cannot complete (no longer swallowed). */
.impersonation-banner .impersonation-error {
  color: #8a1c1c;
  background: #fdecec;
  border: 1px solid rgba(138, 28, 28, 0.28);
  padding: 4px 10px;
  border-radius: 999px;
  font-size: 0.74rem;
  font-weight: 600;
}

/* ── Super Admin console (AdminShell.js) ───────────────────────
   Full-page shell that mirrors the operator UI: same body
   background, same `.app-shell` rhythm, same topbar pattern. The
   centered max-width is wider (1280 px) so the dashboard grid
   has room to breathe without leaving large dead zones. */
.admin-shell {
  position: fixed;
  inset: 0;
  overflow: auto;
  z-index: 8000;
  color: var(--ink);
  font-family: 'IBM Plex Sans', 'Segoe UI', sans-serif;
  background:
    radial-gradient(110% 90% at 0% 0%, rgba(37, 99, 235, 0.14), transparent 60%),
    radial-gradient(90% 90% at 100% 0%, rgba(59, 130, 246, 0.12), transparent 55%),
    linear-gradient(180deg, #f5f9ff 0%, #eef3fb 60%, #f7faff 100%);
}

/* Stage 24 — tighter vertical rhythm so the status bar + tabs sit high on the page (less dead space). */
.admin-shell .app-shell { max-width: 1280px; gap: var(--s-3); padding: var(--s-5) var(--s-6) var(--s-7); }

/* Topbar inside the admin shell — same chrome as `.topbar`, just wider. */
.admin-topbar {
  width: 100%;
  max-width: 1280px;
  margin: 0 auto;
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: center;
  gap: var(--s-4);
  padding: var(--s-3) var(--s-5);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  backdrop-filter: blur(18px);
  box-shadow: var(--shadow-soft);
}

.admin-topbar .topbar-brand h1 { font-size: clamp(1.05rem, 1.4vw, 1.35rem); line-height: 1.2; letter-spacing: -0.01em; }

.admin-topbar-actions {
  display: flex;
  gap: var(--s-2);
  flex-wrap: wrap;
  justify-content: flex-end;
  align-items: center;
}

/* Page header above the dashboard grid — title + description + identity strip. */
/* 2026-06-18 Stage 24 — compact professional status bar (identity + collapsible stat chips) above the tabs. */
.admin-statusbar {
  width: 100%;
  max-width: 1280px;
  margin: 0 auto;
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: var(--s-3);
  padding: 7px 14px;
  background: var(--surface-strong);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-soft);
}
.admin-statusbar-id { display: flex; align-items: center; gap: var(--s-3); flex-wrap: wrap; min-width: 0; }
.admin-statusbar-title { font-size: 0.95rem; font-weight: 700; color: var(--ink); letter-spacing: -0.01em; }
.admin-statusbar-right { display: flex; align-items: center; gap: var(--s-2); flex-wrap: wrap; }
.admin-stats-toggle {
  appearance: none;
  cursor: pointer;
  border: 1px solid var(--border);
  background: var(--surface-solid);
  border-radius: 999px;
  color: var(--ink-soft);
  font: inherit;
  font-size: 0.7rem;
  font-weight: 600;
  letter-spacing: 0.02em;
  padding: 4px 10px;
  white-space: nowrap;
}
.admin-stats-toggle:hover { color: var(--accent); border-color: var(--accent-soft); }

.admin-identity-strip {
  display: inline-flex;
  align-items: center;
  gap: var(--s-2);
  padding: 4px 10px;
  border-radius: 999px;
  background: var(--surface-solid);
  border: 1px solid var(--border);
  font-size: 0.76rem;
  color: var(--ink-soft);
}
.admin-identity-strip strong { color: var(--ink); }

/* ── Stat chips (compact, inline, collapsible via the status-bar toggle) ──── */
.admin-stat-grid {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-2);
  margin: 0;
}

.admin-stat-card {
  display: inline-flex;
  align-items: baseline;
  gap: 6px;
  padding: 5px 12px;
  border: 1px solid var(--border);
  border-radius: 999px;
  background: var(--surface-solid);
}

.admin-stat-card .stat-value {
  font-family: 'Space Grotesk', 'IBM Plex Sans', sans-serif;
  font-size: 1.05rem;
  font-weight: 700;
  color: var(--ink);
  letter-spacing: -0.01em;
  line-height: 1;
}

.admin-stat-card .stat-label {
  font-size: 0.68rem;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ink-soft);
  white-space: nowrap;
}

.admin-stat-card.stat-error .stat-value { color: var(--bad); font-size: 0.8rem; }

/* ── Dashboard grid ──────────────────────────────────────────── */
.admin-grid {
  width: 100%;
  max-width: 1280px;
  margin: 0 auto;
  display: grid;
  grid-template-columns: minmax(0, 1.35fr) minmax(0, 1fr);
  gap: var(--s-4);
  align-items: start;
}

/* Cards on the admin shell reuse the operator `.card` look but tighten the
   internal rhythm because they hold tables/lists, not free-form forms. */
.admin-card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  backdrop-filter: blur(18px);
  box-shadow: var(--shadow-soft);
  padding: var(--s-5);
  display: flex;
  flex-direction: column;
  gap: var(--s-3);
  min-width: 0;
}

.admin-card-head {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: var(--s-3);
  flex-wrap: wrap;
}

.admin-card-head .admin-card-title {
  display: grid;
  gap: 2px;
  min-width: 0;
}

.admin-card-head h3 {
  margin: 0;
  font-size: 0.95rem;
  font-weight: 700;
  letter-spacing: -0.005em;
  color: var(--ink);
}

.admin-card-head .admin-card-eyebrow {
  font-size: 0.66rem;
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--accent-strong);
}

.admin-card-head .admin-card-actions { display: flex; gap: var(--s-2); flex-wrap: wrap; }

/* ── Tabbed console (Stage 11): tablist + tab buttons + panels ───── */
.admin-tablist {
  width: 100%;
  max-width: 1280px;
  margin: 0 auto;
  display: flex;
  gap: var(--s-1);
  flex-wrap: wrap;
  align-items: flex-end;
  border-bottom: 1px solid var(--border);
}

.admin-tab {
  appearance: none;
  border: 1px solid transparent;
  border-bottom: none;
  background: transparent;
  color: var(--ink-soft);
  font: inherit;
  font-size: 0.82rem;
  font-weight: 600;
  white-space: nowrap;
  padding: 10px 16px;
  min-height: 40px;
  border-radius: var(--radius-md) var(--radius-md) 0 0;
  cursor: pointer;
  transition: background-color 140ms var(--ease), color 140ms var(--ease);
}
.admin-tab:hover { color: var(--ink); background: rgba(37, 99, 235, 0.06); }
.admin-tab:focus-visible { outline: 2px solid var(--accent-strong); outline-offset: -2px; }
.admin-tab[aria-selected='true'] {
  color: var(--accent-strong);
  background: var(--surface);
  border-color: var(--border);
  box-shadow: inset 0 2px 0 var(--accent-strong);
}
/* Reserved (Coming soon) tabs read as quieter, with a hollow marker. */
.admin-tab-soon { color: var(--ink-mute); font-weight: 500; }
.admin-tab-soon::before { content: '○ '; opacity: 0.65; }

/* Span both columns — used for the sticky impersonation form / wide tables. */
.admin-grid .admin-card.admin-card-wide { grid-column: 1 / -1; }

.admin-panels {
  width: 100%;
  max-width: 1280px;
  margin: var(--s-4) auto 0;
  min-width: 0;
}
.admin-panel { outline: none; min-width: 0; }
.admin-panel[hidden] { display: none; }
.admin-panel-body { display: flex; flex-direction: column; gap: var(--s-4); min-width: 0; }

/* ── Admin tables ───────────────────────────────────────────── */
.admin-table-wrap {
  position: relative;
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  background: var(--surface-solid);
  overflow: auto;
  max-height: 420px;
}

.admin-table {
  width: 100%;
  border-collapse: collapse;
  table-layout: fixed;
  min-width: 540px;
}

.admin-table thead {
  position: sticky;
  top: 0;
  background: #f7fbff;
  border-bottom: 1px solid var(--border);
  z-index: 1;
}

.admin-table th {
  font-size: 0.7rem;
  color: #60728d;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  text-align: left;
  padding: 10px 12px;
  font-weight: 700;
}

.admin-table td {
  font-size: 0.82rem;
  color: var(--ink);
  padding: 10px 12px;
  vertical-align: top;
  word-break: break-word;
}

.admin-table tbody tr { border-bottom: 1px solid rgba(148, 163, 184, 0.14); }
.admin-table tbody tr:last-child { border-bottom: none; }
.admin-table tbody tr:hover { background: rgba(37, 99, 235, 0.03); }

.admin-table .cell-strong { font-weight: 700; color: var(--ink); }
.admin-table .cell-mute   { color: var(--ink-soft); font-size: 0.76rem; margin-top: 2px; display: block; }
.admin-table .cell-mono   { font-family: 'IBM Plex Mono', 'Consolas', monospace; font-size: 0.78rem; }

/* Outcome / status pills inside admin tables. */
.outcome-badge {
  display: inline-flex;
  align-items: center;
  padding: 3px 10px;
  border-radius: 999px;
  font-size: 0.68rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

.outcome-badge.outcome-success { background: #d1fae5; color: #0f6647; }
.outcome-badge.outcome-denied,
.outcome-badge.outcome-error   { background: #fee2e2; color: #b4233c; }
.outcome-badge.outcome-info    { background: #e9f1ff; color: #2751a3; }

.admin-empty {
  padding: var(--s-5) var(--s-4);
  color: var(--ink-mute);
  font-size: 0.85rem;
  text-align: center;
}

/* ── Impersonation form ───────────────────────────────────────── */
.admin-impersonation-form {
  display: grid;
  grid-template-columns: minmax(0, 1.2fr) minmax(0, 1fr) auto;
  gap: var(--s-3);
  align-items: end;
}

.admin-impersonation-form .field-label { margin-top: 0; }

.admin-callout {
  margin-top: var(--s-3);
  padding: var(--s-3) var(--s-4);
  border-radius: var(--radius-md);
  background: linear-gradient(180deg, #f9fbff, #eef5ff);
  border: 1px solid var(--border-strong);
  color: var(--ink-soft);
  font-size: 0.82rem;
  line-height: 1.5;
}

/* 2026-06-18 Stage 18 → Stage 23 — Reference "what everything means" guide. Native <details> accordion,
   closed by default, styled to MATCH the Diagnostics terminal stream (Stage 22): one continuous surface,
   line-by-line collapsible rows with a leading ▸/▾ caret, click a row to expand. Plain-language (not mono). */
.guide {
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--surface-solid);
  overflow: hidden;
}
.guide-section { border-bottom: 1px solid var(--border); }
.guide-section:last-child { border-bottom: 0; }
.guide-section > summary {
  cursor: pointer;
  list-style: none;
  display: flex;
  align-items: center;
  gap: var(--s-2);
  font-weight: 600;
  color: var(--ink);
  padding: 9px 14px;
}
.guide-section > summary::-webkit-details-marker { display: none; }
.guide-section > summary::before { content: '▸'; color: var(--ink-mute); font-size: 0.7rem; line-height: 1; }
.guide-section[open] > summary::before { content: '▾'; }
.guide-section > summary:hover { background: var(--accent-soft-2); }
.guide-dl { margin: 0; padding: 8px 14px 14px 30px; background: var(--bg-soft); border-top: 1px dashed var(--border); }
.guide-dl dt { font-weight: 600; color: var(--ink); margin-top: var(--s-2); font-size: 0.85rem; }
.guide-dl dt:first-child { margin-top: 0; }
.guide-dl dd { margin: 2px 0 0; color: var(--ink-soft); font-size: 0.82rem; line-height: 1.45; }

/* ── "Coming soon" placeholder card ───────────────────────────
   Used for features the backend doesn't expose yet (per-user logs,
   IP allowlist editor, etc.). Clearly marked, never faked. */
.admin-soon {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-direction: column;
  gap: var(--s-2);
  padding: var(--s-6) var(--s-4);
  text-align: center;
  border-radius: var(--radius-md);
  background: repeating-linear-gradient(
    135deg,
    rgba(37, 99, 235, 0.04),
    rgba(37, 99, 235, 0.04) 10px,
    rgba(37, 99, 235, 0.07) 10px,
    rgba(37, 99, 235, 0.07) 20px
  );
  border: 1px dashed rgba(37, 99, 235, 0.24);
  color: var(--ink-soft);
  font-size: 0.85rem;
}

.admin-soon .admin-soon-label {
  display: inline-flex;
  align-items: center;
  padding: 3px 10px;
  border-radius: 999px;
  background: var(--accent-soft);
  color: var(--accent-strong);
  font-size: 0.66rem;
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

/* Footer link inside an admin card. */
.admin-card-foot {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: var(--s-2);
  font-size: 0.78rem;
  color: var(--ink-mute);
}

/* ── Admin shell breakpoints ─────────────────────────────────── */
@media (max-width: 1199px) {
  .admin-grid      { grid-template-columns: 1fr; }
}

@media (max-width: 767px) {
  .admin-shell .app-shell      { padding: var(--s-3); gap: var(--s-3); }
  .admin-topbar                { grid-template-columns: 1fr; padding: var(--s-3); }
  .admin-topbar-actions        { justify-content: flex-start; }
  .admin-statusbar             { align-items: flex-start; }
  .admin-impersonation-form    { grid-template-columns: 1fr; }
  .admin-card                  { padding: var(--s-4); }

  /* User menu collapses gracefully on narrow viewports. */
  .user-menu                   { margin-left: 0; padding-left: 0; border-left: 0; flex-wrap: wrap; }
  .user-menu .identity-pill    { max-width: 100%; }
}

/* ── Super Admin diagnostics console ─────────────────────────── */
.diag-body { display: flex; flex-direction: column; gap: var(--s-3); }
.diag-controls { display: flex; flex-wrap: wrap; gap: var(--s-2); align-items: center; }
.diag-controls .diag-search { flex: 1 1 240px; min-width: 200px; }
.diag-controls select.field-input { flex: 0 0 auto; width: auto; }
.diag-results { min-height: 60px; }
/* Stage 22 — terminal-style stream: one continuous monospace surface, line-by-line, click a line to expand. */
.diag-list {
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--surface-solid);
  overflow: hidden;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
}
.diag-item { border: 0; border-bottom: 1px solid var(--border); background: transparent; }
.diag-item:last-child { border-bottom: 0; }
.diag-row {
  display: grid;
  grid-template-columns: 16px 78px auto 132px 1fr auto;
  gap: var(--s-2);
  align-items: center;
  width: 100%;
  text-align: left;
  background: transparent;
  border: 0;
  padding: 7px 12px;
  cursor: pointer;
  font: inherit;
  font-size: 0.78rem;
  color: var(--ink);
}
.diag-caret { color: var(--ink-mute); font-size: 0.68rem; line-height: 1; }
.diag-row:hover { background: var(--accent-soft-2); }
.diag-time { color: var(--ink-mute); font-size: 0.78rem; white-space: nowrap; }
.diag-component { color: var(--ink-soft); font-size: 0.8rem; font-weight: 600; }
.diag-message { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.diag-corr { color: var(--ink-mute); font-size: 0.72rem; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; white-space: nowrap; }
/* Pipeline error taxonomy (Stage 13): the error code chip in the row + the stage/side/upstream tags. */
.diag-code { color: var(--bad); font-size: 0.68rem; font-weight: 700; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; letter-spacing: 0.03em; white-space: nowrap; }
.diag-classify { display: flex; flex-wrap: wrap; gap: var(--s-2); }
.diag-classify .diag-tag { font-size: 0.72rem; font-weight: 600; color: var(--ink-soft); background: var(--surface-solid); border: 1px solid var(--border); border-radius: 999px; padding: 2px 10px; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; }
/* Share Links tab (Stage 14): live-watched badges + per-link activity timeline. */
.share-live { color: #0f7b3f; font-size: 0.74rem; font-weight: 700; white-space: nowrap; }
.share-idle { color: var(--ink-mute); font-size: 0.74rem; font-weight: 600; white-space: nowrap; }
.share-timeline { margin-top: var(--s-2); }
.share-events { display: flex; flex-direction: column; gap: 4px; }
.share-event { display: flex; align-items: center; gap: var(--s-2); padding: 6px 10px; border: 1px solid var(--border); border-radius: var(--radius-sm); background: var(--surface-solid); font-size: 0.8rem; }
.share-event .diag-message { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Cluster Diagnostics / Smoke Tests tab (Stage 15). */
.smoke-controls { display: flex; gap: var(--s-2); align-items: center; flex-wrap: wrap; margin: var(--s-2) 0; }
.smoke-controls .field-input { max-width: 360px; }
.smoke-results { display: flex; flex-direction: column; gap: var(--s-2); margin-top: var(--s-2); }
.smoke-stage { border: 1px solid var(--border); border-radius: var(--radius-sm); background: var(--surface-solid); padding: 8px 12px; display: flex; flex-direction: column; gap: 4px; }
.smoke-stage-head { display: flex; align-items: center; gap: var(--s-2); flex-wrap: wrap; }
.smoke-stage-name { font-weight: 700; text-transform: capitalize; }
.smoke-stage-msg { font-size: 0.82rem; color: var(--ink-soft); }
/* Expanded detail: indented under the line (aligned past the caret) for a terminal "tree" feel. */
.diag-detail { padding: 10px 14px 14px 34px; border-top: 1px dashed var(--border); background: var(--bg-soft); display: flex; flex-direction: column; gap: var(--s-2); }
/* Collapsed by default: this attribute rule outranks the `display: flex` above so the `hidden` attribute
   actually hides the detail (UA `[hidden]{display:none}` alone is overridden by the class's display rule). */
.diag-detail[hidden] { display: none; }
.diag-diagnosis { font-size: 0.82rem; }
.diag-steps { margin: 0; padding-left: 1.1rem; color: var(--ink-soft); font-size: 0.8rem; display: flex; flex-direction: column; gap: 2px; }
.diag-json { margin: 0; max-height: 260px; overflow: auto; background: var(--surface-solid); border: 1px solid var(--border); border-radius: var(--radius-sm); padding: 10px; font-size: 0.74rem; white-space: pre-wrap; word-break: break-word; }
.diag-sev { display: inline-block; padding: 2px 8px; border-radius: 999px; font-size: 0.7rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.03em; }
.diag-sev-info { background: #eef2f8; color: #4d6078; }
.diag-sev-warning { background: #fef3c7; color: #92660b; }
.diag-sev-error { background: #fee2e2; color: #b91c1c; }
.diag-sev-critical { background: #7f1d1d; color: #fff; }
.diag-pager { display: flex; align-items: center; justify-content: space-between; gap: var(--s-2); flex-wrap: wrap; }
.diag-pager-buttons { display: flex; gap: var(--s-2); }
.diag-error { color: var(--bad); }

@media (max-width: 767px) {
  .diag-row { grid-template-columns: 16px 1fr auto; row-gap: 4px; }
  .diag-time, .diag-corr { grid-column: 1 / -1; }
}

/* 2026-06-17 — Honor an OS "reduce motion" preference everywhere (previously only
   share.css did). Collapses transitions/animations to near-instant so motion-sensitive
   users get a still UI, without changing any layout or behaviour. */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}
