/**
 * Connections module — 1.0.30
 * ============================
 * Master-detail Connections workspace.
 *
 * Shared component primitives (tokens, buttons, inputs, badges, cards, menu,
 * empty/error states, motion preferences) live in cx-base.css and are loaded
 * before this file. Only what is specific to this view is below.
 */

/* Light-theme overrides for this module's own surfaces. The token flips that
   every module needs are in cx-base; these two surfaces are ours alone. */
[data-theme="light"] .cx-topo {
  background:
    radial-gradient(circle at 1px 1px, rgba(15, 23, 42, 0.07) 1px, transparent 0) 0 0 / 22px 22px,
    var(--bg-surface);
}
/* The log tail stays dark in both themes — it is a terminal surface, and
   syntax-coloured log levels need a dark ground to stay legible. */
[data-theme="light"] .cx-logs { background: #0f172a; }
[data-theme="light"] .cx-log-msg { color: #cbd5e1; }
[data-theme="light"] .cx-log-line { border-bottom-color: rgba(255, 255, 255, 0.05); }

/* ============================================================
   3. WORKSPACE SHELL — master / detail
   ============================================================ */
.cx-workspace {
  display: grid;
  grid-template-columns: var(--cx-rail-w) minmax(0, 1fr);
  gap: var(--sp-4);
  align-items: start;
  min-height: 0;
}

/* -- rail -------------------------------------------------------------- */
.cx-rail {
  display: flex;
  flex-direction: column;
  background: var(--cx-surface);
  border: 1px solid var(--cx-hairline);
  border-radius: var(--cx-r-lg);
  overflow: hidden;
  /* Sticks alongside a long detail pane rather than scrolling away.
     Header height + view padding is subtracted so the footer stays put. */
  position: sticky;
  top: var(--sp-4);
  max-height: calc(100vh - var(--header-h) - var(--sp-10));
}
.cx-rail-search { padding: 12px 12px 8px; }

/* Wrap, do not scroll.
   A horizontally scrolling strip clipped "Offline" against the rail edge with
   no scrollbar and no fade, so the fourth filter simply looked absent. Four
   short chips fit on two rows at every rail width we support. */
.cx-rail-filters {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  padding: 0 12px 10px;
}

.cx-chip {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  height: 26px;
  padding: 0 9px;
  border-radius: var(--cx-r-sm);
  border: 1px solid var(--cx-hairline);
  background: transparent;
  color: var(--cx-fg-muted);
  font-family: var(--font-ui);
  font-size: 12px;
  font-weight: 500;
  cursor: pointer;
  white-space: nowrap;
  transition: background-color var(--t-fast) var(--ease-out),
              color var(--t-fast) var(--ease-out),
              border-color var(--t-fast) var(--ease-out);
}
.cx-chip:hover { background: var(--cx-muted); color: var(--text-2); }
.cx-chip[aria-pressed="true"] {
  background: var(--cx-surface-2);
  border-color: var(--cx-hairline-strong);
  color: var(--cx-fg);
}
.cx-chip-count {
  font-family: var(--font-mono);
  font-size: 11px;
  padding: 0 4px;
  border-radius: 4px;
  background: var(--cx-muted);
  color: var(--cx-fg-muted);
}
.cx-chip[aria-pressed="true"] .cx-chip-count { background: var(--accent-dim); color: var(--accent); }

.cx-rail-list {
  flex: 1 1 auto;
  min-height: 120px;
  overflow-y: auto;
  overflow-x: hidden;
  padding: 2px 8px 8px;
  display: flex;
  flex-direction: column;
  gap: 6px;
  scroll-behavior: smooth;
}

/* -- rail item --------------------------------------------------------- */
.cx-item {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  width: 100%;
  padding: 10px 10px 10px 11px;
  border-radius: var(--cx-r-md);
  border: 1px solid transparent;
  background: transparent;
  text-align: left;
  cursor: pointer;
  position: relative;
  transition: background-color var(--t-fast) var(--ease-out),
              border-color var(--t-fast) var(--ease-out),
              transform var(--t-fast) var(--ease-out);
}
.cx-item:hover { background: var(--cx-muted); }
.cx-item[aria-selected="true"] {
  background: var(--cx-surface-2);
  border-color: var(--border-accent);
}
/* Selection is marked by a rail stripe as well as the tint — tint alone is
   too weak a signal at this contrast, and colour must not be the only cue. */
.cx-item[aria-selected="true"]::before {
  content: '';
  position: absolute;
  left: -1px; top: 10px; bottom: 10px;
  width: 2px;
  border-radius: var(--r-full);
  background: var(--accent);
}

.cx-item-icon {
  flex: 0 0 auto;
  width: 30px; height: 30px;
  display: grid;
  place-items: center;
  border-radius: var(--cx-r-sm);
  background: var(--cx-muted);
  border: 1px solid var(--cx-hairline);
  color: var(--cx-fg-subtle);
  transition: color var(--t-fast) var(--ease-out),
              border-color var(--t-fast) var(--ease-out);
}
.cx-item-icon.is-online { color: var(--success); border-color: var(--success-border); background: var(--success-bg); }
/* These are <span>s, and ellipsis truncation is inert on an inline box —
   without an explicit block display the names ran past the rail and gave the
   list a horizontal scrollbar at narrow widths. */
.cx-item-body { flex: 1 1 auto; min-width: 0; display: block; }
.cx-item-name {
  display: block;
  font-size: 13px;
  font-weight: 600;
  color: var(--cx-fg);
  line-height: 1.3;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.cx-item-host {
  display: block;
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--cx-fg-subtle);
  line-height: 1.5;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.cx-item-tags { display: flex; flex-wrap: wrap; gap: 4px; margin-top: 5px; }
.cx-item-kebab {
  flex: 0 0 auto;
  opacity: 0;
  transition: opacity var(--t-fast) var(--ease-out);
}
.cx-item:hover .cx-item-kebab,
.cx-item:focus-within .cx-item-kebab { opacity: 1; }

.cx-rail-foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-2);
  padding: 9px 12px;
  border-top: 1px solid var(--cx-hairline);
  background: var(--bg-surface);
}
.cx-page-label { font-size: 12px; color: var(--cx-fg-muted); font-variant-numeric: tabular-nums; }

/* ============================================================
   4. DETAIL PANE
   ============================================================ */
/* The detail pane is a query container.
   Its width is viewport MINUS sidebar MINUS rail, so a viewport media query
   is the wrong instrument — at 1280 CSS px the viewport clears a 1200px
   breakpoint while the pane itself is only ~640px, which is how the four
   stat cards ended up at ~145px each with every label wrapped onto four
   lines. Sizing off the container measures the thing that actually
   constrains the cards. */
.cx-detail {
  display: flex;
  flex-direction: column;
  gap: var(--sp-4);
  min-width: 0;
  container-type: inline-size;
  container-name: cxdetail;
}

.cx-detail-head {
  display: flex;
  align-items: center;
  gap: var(--sp-4);
  flex-wrap: wrap;
  padding: 14px 18px;
  background: var(--cx-surface);
  border: 1px solid var(--cx-hairline);
  border-radius: var(--cx-r-lg);
}
.cx-detail-ident { display: flex; align-items: center; gap: 12px; min-width: 0; }
.cx-detail-avatar {
  width: 42px; height: 42px;
  display: grid; place-items: center;
  border-radius: var(--cx-r-md);
  background: var(--success-bg);
  border: 1px solid var(--success-border);
  color: var(--success);
  flex: 0 0 auto;
}
.cx-detail-avatar.is-offline {
  background: var(--cx-muted);
  border-color: var(--cx-hairline);
  color: var(--cx-fg-subtle);
}
.cx-detail-name {
  font-size: 19px;
  font-weight: 600;
  letter-spacing: -0.015em;
  color: var(--cx-fg);
  line-height: 1.2;
  margin: 0;
}
.cx-detail-sub {
  font-family: var(--font-mono);
  font-size: 12px;
  color: var(--cx-fg-subtle);
  margin-top: 2px;
}
.cx-detail-badges { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }

/* KPI strip — right-aligned, pushed away from the identity block */
.cx-kpis {
  display: flex;
  gap: var(--sp-6);
  margin-left: auto;
  flex-wrap: wrap;
}
.cx-kpi { min-width: 62px; }
.cx-kpi-label {
  display: block;
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--cx-fg-subtle);
}
.cx-kpi-value {
  display: block;
  font-size: 15px;
  font-weight: 600;
  color: var(--cx-fg);
  font-variant-numeric: tabular-nums;
  margin-top: 2px;
  line-height: 1.25;
}
.cx-detail-actions { display: flex; align-items: center; gap: 8px; }

/* -- tab bar ---------------------------------------------------------- */
.cx-tabbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-4);
  flex-wrap: wrap;
  border-bottom: 1px solid var(--cx-hairline);
  padding: 0 2px;
}
.cx-tabs { display: flex; gap: 2px; overflow-x: auto; scrollbar-width: none; }
.cx-tabs::-webkit-scrollbar { display: none; }
.cx-tab {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  height: 38px;
  padding: 0 12px;
  border: 0;
  background: transparent;
  color: var(--cx-fg-muted);
  font-family: var(--font-ui);
  font-size: 13px;
  font-weight: 500;
  cursor: pointer;
  white-space: nowrap;
  transition: color var(--t-fast) var(--ease-out);
}
.cx-tab:hover { color: var(--text-2); }
.cx-tab[aria-selected="true"] { color: var(--cx-fg); font-weight: 600; }
.cx-tab[aria-selected="true"]::after {
  content: '';
  position: absolute;
  left: 8px; right: 8px; bottom: -1px;
  height: 2px;
  border-radius: var(--r-full) var(--r-full) 0 0;
  background: var(--accent);
}
.cx-tab-badge {
  font-family: var(--font-mono);
  font-size: 10px;
  padding: 1px 5px;
  border-radius: 4px;
  background: var(--cx-muted);
  color: var(--cx-fg-muted);
}

.cx-refresh-ctl {
  display: flex;
  align-items: center;
  gap: 8px;
  padding-bottom: 6px;
  font-size: 12px;
  color: var(--cx-fg-muted);
}

/* -- panel transition --------------------------------------------------
   Opt-in, NOT on the bare class. The panel element is rebuilt whenever the
   markup changes, including on a background poll, so an unconditional
   entrance animation replayed the slide-in every ten seconds — read as the
   page hiccuping. The module adds `is-entering` on a real tab switch only. */
.cx-panel.is-entering { animation: cx-panel-in var(--t-base) var(--ease-out) both; }
@keyframes cx-panel-in {
  from { opacity: 0; transform: translateY(4px); }
  to   { opacity: 1; transform: none; }
}

/* ============================================================
   5. BENTO STAT GRID (Overview)
   ============================================================ */
.cx-bento {
  display: grid;
  /* auto-fit + a 232px floor: the grid picks its own column count from the
     space the pane actually has, so this needs no breakpoint per layout. */
  grid-template-columns: repeat(auto-fit, minmax(232px, 1fr));
  gap: var(--sp-4);
}
/* ============================================================
   6. ARCHITECTURE — topology canvas
   ============================================================ */
.cx-arch { display: grid; grid-template-columns: minmax(0, 1fr) 340px; gap: var(--sp-4); align-items: start; }

.cx-topo {
  position: relative;
  height: 400px;
  background:
    radial-gradient(circle at 1px 1px, rgba(255,255,255,0.045) 1px, transparent 0) 0 0 / 22px 22px,
    var(--bg-surface);
  border-radius: 0 0 var(--cx-r-lg) var(--cx-r-lg);
  overflow: hidden;
  cursor: grab;
  touch-action: none;
}
.cx-topo.is-panning { cursor: grabbing; }
.cx-topo-stage {
  position: absolute;
  inset: 0;
  transform-origin: 0 0;
  will-change: transform;
}
.cx-topo-edges { position: absolute; inset: 0; overflow: visible; pointer-events: none; }
.cx-edge {
  fill: none;
  stroke-width: 1.75;
  stroke-linecap: round;
}
.cx-edge-sync  { stroke: var(--success); }
.cx-edge-async { stroke: var(--accent); }
.cx-edge-unknown { stroke: var(--text-4); stroke-dasharray: 4 4; }
/* Flow animation reads direction at a glance. Killed under
   prefers-reduced-motion at the bottom of this file. */
.cx-edge-flow {
  stroke-dasharray: 5 7;
  animation: cx-flow 1.1s linear infinite;
}
@keyframes cx-flow { to { stroke-dashoffset: -12; } }
.cx-edge-label {
  font-family: var(--font-ui);
  font-size: 10px;
  fill: var(--cx-fg-subtle);
  text-anchor: middle;
}

.cx-node {
  position: absolute;
  width: 190px;
  padding: 10px 12px;
  border-radius: var(--cx-r-md);
  background: var(--cx-surface);
  border: 1px solid var(--cx-hairline-strong);
  box-shadow: var(--shadow-md);
  display: flex;
  align-items: center;
  gap: 9px;
  cursor: pointer;
  transition: border-color var(--t-fast) var(--ease-out),
              transform var(--t-fast) var(--ease-out),
              box-shadow var(--t-fast) var(--ease-out);
}
.cx-node:hover { transform: translateY(-2px); box-shadow: var(--shadow-lg); border-color: var(--border-strong); }
.cx-node.is-current { border-color: var(--accent); box-shadow: var(--shadow-accent); }
.cx-node-icon {
  width: 26px; height: 26px;
  display: grid; place-items: center;
  border-radius: var(--cx-r-sm);
  flex: 0 0 auto;
}
.cx-node-primary .cx-node-icon { background: var(--success-bg); color: var(--success); }
.cx-node-sync    .cx-node-icon { background: var(--info-bg);    color: var(--info); }
.cx-node-async   .cx-node-icon { background: var(--accent-dim); color: var(--accent); }
.cx-node-unknown .cx-node-icon { background: var(--cx-muted);   color: var(--cx-fg-subtle); }
/* Same inline-span truncation trap as the rail item above. */
.cx-node-body { min-width: 0; display: block; }
.cx-node-name {
  display: block;
  font-size: 12.5px; font-weight: 600; color: var(--cx-fg);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.cx-node-host {
  display: block;
  font-family: var(--font-mono); font-size: 10.5px; color: var(--cx-fg-subtle);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.cx-node-role { display: block; margin-top: 4px; }

.cx-topo-tools {
  position: absolute;
  left: 12px; top: 12px;
  display: flex;
  flex-direction: column;
  gap: 4px;
  z-index: 3;
}
.cx-topo-tools .cx-btn {
  width: 28px; height: 28px; padding: 0;
  background: var(--cx-surface);
  border: 1px solid var(--cx-hairline);
  box-shadow: var(--shadow-sm);
  color: var(--cx-fg-muted);
}
.cx-topo-tools .cx-btn:hover { color: var(--cx-fg); border-color: var(--cx-hairline-strong); }

.cx-topo-legend {
  position: absolute;
  left: 0; right: 0; bottom: 0;
  display: flex;
  justify-content: center;
  gap: var(--sp-5);
  padding: 8px 12px;
  background: linear-gradient(to top, var(--bg-surface) 55%, transparent);
  font-size: 11px;
  color: var(--cx-fg-muted);
  z-index: 2;
  flex-wrap: wrap;
}
.cx-legend-item { display: inline-flex; align-items: center; gap: 5px; }

/* -- definition list (cluster details / features) ---------------------- */
.cx-dl { display: flex; flex-direction: column; }
.cx-dl-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-3);
  padding: 8px 0;
  font-size: 12.5px;
  border-bottom: 1px solid var(--cx-hairline);
}
.cx-dl-row:last-child { border-bottom: 0; }
.cx-dl-row > dt { color: var(--cx-fg-muted); }
.cx-dl-row > dd { margin: 0; font-weight: 600; color: var(--cx-fg); text-align: right; }

/* ============================================================
   7. TABLES (Activity, Logs, Metrics detail)
   ============================================================ */
.cx-table-wrap { overflow-x: auto; }
.cx-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 12.5px;
}
.cx-table th {
  position: sticky;
  top: 0;
  z-index: 1;
  height: 34px;
  padding: 0 10px;
  text-align: left;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--cx-fg-muted);
  background: var(--bg-surface);
  border-bottom: 1px solid var(--cx-hairline);
  white-space: nowrap;
}
.cx-table td {
  padding: 8px 10px;
  border-bottom: 1px solid var(--cx-hairline);
  color: var(--text-2);
  vertical-align: top;
}
.cx-table tbody tr { transition: background-color var(--t-fast) var(--ease-out); }
.cx-table tbody tr:hover { background: var(--cx-muted); }
.cx-table tbody tr:last-child td { border-bottom: 0; }
.cx-num { font-variant-numeric: tabular-nums; text-align: right; }
.cx-mono { font-family: var(--font-mono); font-size: 11.5px; }
.cx-truncate {
  max-width: 380px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  display: block;
}

/* -- log lines --------------------------------------------------------- */
.cx-logs {
  font-family: var(--font-mono);
  font-size: 11.5px;
  line-height: 1.65;
  max-height: 520px;
  overflow: auto;
  padding: 10px 14px;
  background: var(--bg-base);
}
.cx-log-line {
  display: grid;
  grid-template-columns: 148px 62px minmax(0, 1fr);
  gap: 10px;
  padding: 2px 0;
  border-bottom: 1px solid rgba(255, 255, 255, 0.03);
}
.cx-log-time { color: var(--cx-fg-subtle); white-space: nowrap; }
.cx-log-level { font-weight: 500; text-transform: uppercase; font-size: 10.5px; letter-spacing: 0.04em; }
.cx-log-msg { color: var(--text-2); white-space: pre-wrap; word-break: break-word; }
.cx-lvl-error, .cx-lvl-fatal, .cx-lvl-panic { color: var(--danger); }
.cx-lvl-warning { color: var(--warning); }
.cx-lvl-notice, .cx-lvl-info { color: var(--info); }
.cx-lvl-log, .cx-lvl-debug, .cx-lvl-statement { color: var(--cx-fg-subtle); }


/* ============================================================
   10. SETTINGS TAB
   ============================================================ */
.cx-form-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: var(--sp-4) var(--sp-4);
}
.cx-field { display: flex; flex-direction: column; gap: 5px; }
.cx-field-label { font-size: 12px; font-weight: 500; color: var(--text-2); }
.cx-field-hint { font-size: 11.5px; color: var(--cx-fg-subtle); line-height: 1.5; }

.cx-danger-zone {
  border-color: var(--danger-border);
}
.cx-danger-zone .cx-card-title { color: var(--danger); }

/* ============================================================
   10b. FEATURE READINESS
   ============================================================ */

/* -- summary bar ------------------------------------------------------
   One row answering "how ready is this target" before any list is read:
   a fraction, a proportional bar, the legend, what the server is, and the
   single action that changes the number. */
.cx-ready-summary {
  display: flex;
  align-items: center;
  gap: var(--sp-4);
  flex-wrap: wrap;
  padding: 12px 14px;
  margin-bottom: var(--sp-4);
  border-radius: var(--cx-r-md);
  border: 1px solid var(--cx-hairline);
  background: var(--cx-muted);
}
.cx-ready-score { flex: 0 0 auto; }
.cx-ready-score-num {
  font-size: 24px;
  font-weight: 600;
  line-height: 1;
  color: var(--cx-fg);
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.02em;
}
.cx-ready-score-num > span { font-size: 15px; font-weight: 500; color: var(--cx-fg-subtle); }
.cx-ready-score-label {
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--cx-fg-subtle);
  margin-top: 4px;
}

.cx-ready-bar {
  flex: 1 1 160px;
  min-width: 120px;
  display: flex;
  gap: 2px;
  height: 6px;
  border-radius: var(--r-full);
  overflow: hidden;
  background: var(--cx-muted);
}
.cx-ready-seg { display: block; border-radius: var(--r-full); }
.cx-ready-seg-success { background: var(--success); }
.cx-ready-seg-warning { background: var(--warning); }
.cx-ready-seg-danger  { background: var(--danger); }

.cx-ready-legend { display: flex; gap: var(--sp-4); flex-wrap: wrap; font-size: 11.5px; color: var(--cx-fg-muted); }
.cx-ready-server { display: flex; gap: 6px; flex-wrap: wrap; margin-left: auto; }

/* -- sections ---------------------------------------------------------- */
.cx-ready-section { margin-top: var(--sp-5); }
.cx-ready-section:first-child { margin-top: 0; }
.cx-ready-section-title {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 0 0 10px;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.075em;
  text-transform: uppercase;
  color: var(--cx-fg-muted);
}

/* -- module rows -------------------------------------------------------
   A left status stripe carries the state as well as the badge, so the state
   survives being read in greyscale or at a glance. */
.cx-ready-list { display: flex; flex-direction: column; gap: 4px; }
.cx-ready-row {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  padding: 9px 12px;
  border-radius: var(--cx-r-md);
  border: 1px solid var(--cx-hairline);
  border-left-width: 3px;
  border-left-color: var(--cx-hairline-strong);
  background: var(--cx-surface);
  transition: border-color var(--t-fast) var(--ease-out),
              background-color var(--t-fast) var(--ease-out);
}
.cx-ready-row:hover { background: var(--cx-surface-2); }
.cx-ready-row-success { border-left-color: var(--success); }
.cx-ready-row-warning { border-left-color: var(--warning); }
.cx-ready-row-danger  { border-left-color: var(--danger); }
.cx-ready-row-name {
  font-size: 13px;
  font-weight: 500;
  color: var(--cx-fg);
  min-width: 0;
}
.cx-ready-row-spacer { flex: 1 1 auto; }
.cx-ready-note {
  display: inline-flex;
  color: var(--cx-fg-subtle);
  cursor: help;
  transition: color var(--t-fast) var(--ease-out);
}
.cx-ready-note:hover, .cx-ready-note:focus-visible { color: var(--info); }

/* -- requirement chips -------------------------------------------------
   Three kinds, three treatments, because the remedy differs: a hard
   requirement blocks the module, an enhancer only reduces its depth, and a
   host package is installed on the IntelliDB host rather than the target. */
.cx-req-list { display: flex; gap: 5px; flex-wrap: wrap; }
.cx-req {
  display: inline-flex;
  align-items: center;
  padding: 1px 7px;
  border-radius: var(--cx-r-sm);
  border: 1px dashed var(--cx-hairline-strong);
  font-family: var(--font-mono);
  font-size: 10.5px;
  color: var(--cx-fg-muted);
  white-space: nowrap;
}
.cx-req-hard { border-color: var(--danger-border);  color: var(--danger);  border-style: solid; }
.cx-req-soft { border-color: var(--cx-hairline-strong); color: var(--cx-fg-muted); }
.cx-req-host { border-color: var(--warning-border); color: var(--warning); }

/* -- extension cards ---------------------------------------------------- */
.cx-ready-exts {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(216px, 1fr));
  gap: 10px;
}
.cx-ext {
  display: flex;
  flex-direction: column;
  gap: 5px;
  padding: 11px 13px;
  border-radius: var(--cx-r-md);
  border: 1px solid var(--cx-hairline);
  background: var(--cx-surface);
  transition: border-color var(--t-fast) var(--ease-out),
              transform var(--t-fast) var(--ease-out);
}
.cx-ext:hover { border-color: var(--cx-hairline-strong); transform: translateY(-1px); }
/* Installed is the resting state and recedes; anything else is the exception
   and keeps full contrast. */
.cx-ext.is-installed { background: var(--cx-muted); }
.cx-ext-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 8px; }
.cx-ext-name { font-size: 12.5px; font-weight: 600; color: var(--cx-fg); line-height: 1.35; }
.cx-ext-raw {
  font-family: var(--font-mono);
  font-size: 10.5px;
  color: var(--cx-fg-subtle);
  word-break: break-all;
}
.cx-ext-purpose { margin: 2px 0 0; font-size: 11.5px; line-height: 1.5; color: var(--cx-fg-muted); }
.cx-ext-reason  { margin: 2px 0 0; font-size: 11.5px; line-height: 1.5; color: var(--warning); }
.cx-ext-btn { align-self: flex-start; margin-top: 4px; }

/* -- connectivity diagnosis --------------------------------------------
   Rendered by modules/server-onboarding.js, which inserts itself as a
   sibling before #conn-readiness-cx. It is not ours to restructure, so it is
   restyled in place to sit inside this card rather than float above it. */
.cx-card .conn-diagnosis {
  margin: 0 18px 14px;
  padding: 11px 13px;
  border-radius: var(--cx-r-md);
  border: 1px solid var(--cx-hairline);
  background: var(--cx-muted);
  font-size: 12.5px;
  color: var(--cx-fg-muted);
  line-height: 1.55;
}
.cx-card .diag-head { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.cx-card .diag-badge {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 2px 8px;
  border-radius: var(--cx-r-sm);
  border: 1px solid transparent;
  font-size: 11px;
  font-weight: 500;
}
.cx-card .diag-ok   { background: var(--success-bg); border-color: var(--success-border); color: var(--success); }
.cx-card .diag-fail { background: var(--danger-bg);  border-color: var(--danger-border);  color: var(--danger); }
.cx-card .diag-msg  { margin-top: 6px; }
.cx-card .diag-muted { color: var(--cx-fg-subtle); }
.cx-card .diag-next {
  margin-top: 6px;
  padding-left: 2px;
  color: var(--text-2);
}
.cx-card .diag-fix { margin-top: 10px; }
.cx-card .diag-fix-title {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  font-size: 12px;
  font-weight: 600;
  color: var(--cx-fg);
  margin-bottom: 5px;
}
.cx-card .diag-file { font-family: var(--font-mono); font-size: 11px; font-weight: 400; color: var(--cx-fg-subtle); }
.cx-card .diag-snippet {
  margin: 0;
  padding: 9px 11px;
  border-radius: var(--cx-r-sm);
  border: 1px solid var(--cx-hairline);
  background: var(--bg-base);
  font-family: var(--font-mono);
  font-size: 11px;
  line-height: 1.6;
  color: var(--text-2);
  overflow-x: auto;
  white-space: pre;
}
/* The diagnosis panel is rendered by another module with the app-wide .btn
   classes. Pull just those two buttons into this card's grammar. */
.cx-card .conn-diagnosis .btn {
  height: 26px;
  padding: 0 9px;
  border-radius: var(--cx-r-md);
  border: 1px solid var(--cx-hairline-strong);
  background: transparent;
  color: var(--text-2);
  font-family: var(--font-ui);
  font-size: 12px;
  font-weight: 500;
  cursor: pointer;
  transition: background-color var(--t-fast) var(--ease-out), color var(--t-fast) var(--ease-out);
}
.cx-card .conn-diagnosis .btn:hover { background: var(--cx-muted); color: var(--cx-fg); }
.cx-card .diag-copy { margin-left: auto; }


/* ============================================================
   12. RESPONSIVE
   Regressions concentrate at narrow widths, so each breakpoint below
   was checked rather than assumed.
   ============================================================ */
/* Container queries first — these track the detail pane, which is what the
   cards live in. The viewport queries further down handle the shell (rail vs
   sidebar), which genuinely is a viewport-level concern. */
@container cxdetail (max-width: 900px) {
  .cx-arch { grid-template-columns: minmax(0, 1fr); }
  .cx-topo { height: 340px; }
}
@container cxdetail (max-width: 640px) {
  .cx-kpis { gap: var(--sp-4); }
  .cx-detail-head { align-items: flex-start; }
  .cx-topo { height: 300px; }
}
@container cxdetail (max-width: 520px) {
  .cx-kpis { margin-left: 0; width: 100%; order: 3; }
  .cx-detail-badges { order: 4; }
  .cx-log-line { grid-template-columns: minmax(0, 1fr); gap: 0; padding: 5px 0; }
  .cx-log-time, .cx-log-level { display: inline; margin-right: 8px; }
}
/* Fallback for engines without container queries: keep the old viewport
   breakpoints so the cards still reflow rather than staying at four columns. */
@supports not (container-type: inline-size) {
  @media (max-width: 1440px) {
    .cx-arch { grid-template-columns: minmax(0, 1fr); }
    .cx-topo { height: 340px; }
  }
}
@media (max-width: 1200px) {
  .cx-kpis { gap: var(--sp-4); }
}
@media (max-width: 1024px) {
  /* Rail stops being a sidebar and becomes a header list. Sticky is
     dropped with it — a sticky full-width block would eat the viewport. */
  .cx-workspace { grid-template-columns: minmax(0, 1fr); }
  .cx-rail { position: static; max-height: 340px; }
  .cx-detail-head { align-items: flex-start; }
  .cx-kpis { margin-left: 0; width: 100%; order: 3; }
  .cx-detail-actions { margin-left: auto; }
}
@media (max-width: 700px) {
  .cx-root { --cx-rail-w: 100%; }
  /* Below this width a 232px floor would still try for two columns inside a
     ~300px pane and overflow. Force one. */
  .cx-bento { grid-template-columns: minmax(0, 1fr) !important; }
  .cx-detail-head { padding: 12px 14px; gap: var(--sp-3); }
  .cx-detail-name { font-size: 17px; }
  .cx-kpis { gap: var(--sp-4) var(--sp-5); }
  .cx-kpi { min-width: 0; flex: 1 1 40%; }
  .cx-card-head { padding: 12px 14px 10px; }
  .cx-card-body { padding: 0 14px 14px; }
  .cx-truncate { max-width: 200px; }
  .cx-refresh-ctl { padding-bottom: 8px; }
  .cx-arch { grid-template-columns: minmax(0, 1fr); }
}

/* ============================================================
   9. RAIL FOLDERS
   ============================================================
   Grouping is a navigation device, so it is drawn as quietly as one: a
   small uppercase header, a 7px colour dot, a count. The rows inside are
   the existing .cx-item untouched — a folder changes where a connection
   sits, never how it reads.

   The rail switches to this mode only once a folder exists. An install
   that never made one keeps the flat, paginated list it always had. */

.cx-chip-action {
  margin-left: auto;
  border-style: dashed;
}
.cx-chip-action:hover {
  border-style: solid;
  border-color: var(--cx-hairline-strong);
  color: var(--cx-fg);
}

.cx-rail-error {
  margin: 0 2px 8px;
  font-size: 11.5px;
}

.cx-folder {
  display: flex;
  flex-direction: column;
  border-radius: var(--cx-r-md);
  /* The drop highlight paints on this border, so it is reserved at rest
     rather than added on hover — a border that appears mid-drag reflows
     every row beneath the cursor. */
  border: 1px solid transparent;
  transition: background 120ms ease, border-color 120ms ease;
}
.cx-folder.is-drop-target {
  border-color: var(--accent);
  background: color-mix(in srgb, var(--accent) 8%, transparent);
}

.cx-folder-head {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  padding: 7px 8px;
  border: 0;
  border-radius: var(--cx-r-sm);
  background: transparent;
  color: var(--cx-fg-muted);
  font-family: var(--font-ui);
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  cursor: pointer;
  text-align: left;
}
.cx-folder-head:hover { background: var(--cx-muted); color: var(--cx-fg); }
.cx-folder-head:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
}

.cx-folder-chevron {
  display: inline-flex;
  flex: 0 0 auto;
  transition: transform 160ms cubic-bezier(0.4, 0, 0.2, 1);
}
.cx-folder.is-collapsed .cx-folder-chevron { transform: rotate(-90deg); }

.cx-folder-dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  flex: 0 0 auto;
}
/* Ungrouped is a computed bucket, not a folder anyone chose a colour for.
   A hollow dot says "no colour", where a filled grey one would read as a
   colour that happens to be grey. */
.cx-folder-dot-none {
  background: transparent;
  box-shadow: inset 0 0 0 1px var(--cx-hairline-strong);
}

.cx-folder-name {
  flex: 1 1 auto;
  min-width: 0;
  /* Spans ignore ellipsis without an explicit block display — the rail
     item name learned this and grew a horizontal scrollbar first. */
  display: block;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.cx-folder-count {
  flex: 0 0 auto;
  font-family: var(--font-mono);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0;
  color: var(--cx-fg-muted);
}

/* The edit affordance overlays the header's right edge rather than sitting
   in its flow: nesting a button inside a button is invalid HTML, and the
   header is the toggle. */
.cx-folder { position: relative; }
.cx-folder-edit {
  position: absolute;
  top: 4px;
  right: 4px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 22px;
  height: 22px;
  padding: 0;
  border: 0;
  border-radius: var(--cx-r-sm);
  background: transparent;
  color: var(--cx-fg-muted);
  cursor: pointer;
  opacity: 0;
  transition: opacity 120ms ease, background 120ms ease;
}
.cx-folder:hover .cx-folder-edit,
.cx-folder-edit:focus-visible { opacity: 1; }
.cx-folder-edit:hover { background: var(--cx-surface-2); color: var(--cx-fg); }
/* Keyboard users get no hover, so the control must be reachable without it. */
.cx-folder-edit:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
}
/* The count would sit under the gear on hover. */
.cx-folder:hover .cx-folder-count { margin-right: 22px; }

.cx-folder-body {
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding-left: 6px;
  margin-left: 5px;
  border-left: 1px solid var(--cx-hairline);
}

.cx-folder-empty {
  padding: 10px 10px 12px;
  font-size: 11.5px;
  line-height: 1.5;
  color: var(--cx-fg-muted);
}
.cx-kbd {
  font-family: var(--font-mono);
  font-size: 10.5px;
  padding: 1px 4px;
  border-radius: 4px;
  border: 1px solid var(--cx-hairline);
  background: var(--cx-muted);
  white-space: nowrap;
}

.cx-item.is-dragging { opacity: 0.45; }
.cx-item[draggable="true"] { cursor: grab; }
.cx-item[draggable="true"]:active { cursor: grabbing; }

.cx-page-dim { opacity: 0.65; margin-left: 8px; }

/* -- kebab: move-to block ---------------------------------- */
.cx-menu-label {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 7px 10px 3px;
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--cx-fg-muted);
}
.cx-menu-sub { padding-left: 18px; }
.cx-menu-item:disabled {
  opacity: 0.55;
  cursor: default;
}

/* -- folder dialog ------------------------------------------ */
/* `.sr-only` in cx-base.css is scoped `.cx-root .sr-only`, and this dialog
   mounts on document.body — OUTSIDE that root — so the rule never matches
   and every visually-hidden label paints on screen. Measured: the seven
   colour names rendered across the swatches.

   Any body-level overlay in this module inherits the same hole (the kebab
   menu is the other one; it happens to use no sr-only text). Re-declaring
   it here rather than widening the base rule keeps cx-base scoped as its
   author intended. */
.cx-dlg-backdrop .sr-only {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

.cx-dlg-backdrop {
  position: fixed;
  inset: 0;
  z-index: 1200;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 16px;
  background: var(--bg-overlay, rgba(0, 0, 0, 0.45));
  animation: cx-dlg-fade 120ms ease-out;
}
@keyframes cx-dlg-fade { from { opacity: 0; } to { opacity: 1; } }

.cx-dlg {
  width: 100%;
  max-width: 380px;
  border-radius: var(--cx-r-lg);
  border: 1px solid var(--cx-hairline);
  background: var(--cx-surface);
  box-shadow: 0 20px 48px rgba(0, 0, 0, 0.28);
  animation: cx-dlg-rise 160ms cubic-bezier(0.4, 0, 0.2, 1);
}
@keyframes cx-dlg-rise {
  from { opacity: 0; transform: translateY(8px) scale(0.985); }
  to   { opacity: 1; transform: none; }
}
@media (prefers-reduced-motion: reduce) {
  .cx-dlg-backdrop, .cx-dlg { animation: none; }
  .cx-folder-chevron { transition: none; }
}

.cx-dlg-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  padding: 12px 12px 12px 16px;
  border-bottom: 1px solid var(--cx-hairline);
  font-size: 13px;
  font-weight: 650;
  color: var(--cx-fg);
}
.cx-dlg-body { padding: 16px; display: flex; flex-direction: column; gap: 14px; }
.cx-dlg-foot {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 12px 16px;
  border-top: 1px solid var(--cx-hairline);
}
.cx-dlg-danger { color: var(--danger); }
.cx-dlg-danger:hover { background: color-mix(in srgb, var(--danger) 12%, transparent); }

.cx-dlg-note {
  margin: 0;
  font-size: 12px;
  line-height: 1.45;
  color: var(--danger);
}

.cx-field { display: flex; flex-direction: column; gap: 6px; }
.cx-field-label {
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--cx-fg-muted);
}

.cx-swatches { display: flex; flex-wrap: wrap; gap: 8px; }
.cx-swatch {
  width: 24px;
  height: 24px;
  padding: 0;
  border-radius: 50%;
  border: 2px solid transparent;
  background: var(--sw);
  cursor: pointer;
  transition: transform 120ms ease, box-shadow 120ms ease;
}
.cx-swatch:hover { transform: scale(1.08); }
/* Selection is a ring AND a size change, so it does not rest on colour
   perception alone — the swatches differ only by colour by definition. */
.cx-swatch.is-on {
  border-color: var(--cx-fg);
  transform: scale(1.12);
  box-shadow: 0 0 0 2px var(--cx-surface) inset;
}
.cx-swatch:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

@media (max-width: 700px) {
  .cx-folder-edit { opacity: 1; }   /* no hover on touch */
  .cx-folder:hover .cx-folder-count { margin-right: 22px; }
  .cx-folder-count { margin-right: 22px; }
}
