/* views-ssa.css — SSA venue admin + spatial monitor */
.ssa-page { padding: 16px 20px; max-width: 1500px; margin: 0 auto; }
.ssa-title { margin: 0 0 4px; font-size: 22px; }
.ssa-sub { color: var(--text-2); margin: 0 0 16px; }
/* Setup + node-add error line (event-spatial.js renderSetup + buildNodesCard).
   The class had no rule, so the message sat flush against the button above it. */
.ssa-setup-error { color: var(--danger); font-size: 13px; margin-top: 8px; }
.ssa-setup-error:empty { margin-top: 0; }
.ssa-head { display: flex; align-items: center; gap: 14px; margin-bottom: 12px; }
.ssa-head h1 { font-size: 18px; margin: 0; flex: 1; }
.ssa-monitor-link { margin-left: auto; }

.ssa-venue-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); gap: 12px; margin-bottom: 18px; }
.ssa-venue-links { display: flex; gap: 12px; margin: 8px 0 4px; }
.ssa-venue-meta { color: var(--text-2); font-size: 12px; }
.ssa-create-row { display: flex; gap: 8px; align-items: center; margin-top: 10px; flex-wrap: wrap; }
.ssa-create-row input, .ssa-create-row select { background: var(--bg-2); color: var(--text-1); border: 1px solid var(--border); border-radius: 6px; padding: 6px 8px; }

.ssa-cols { display: grid; grid-template-columns: minmax(480px, 2fr) minmax(320px, 1fr); gap: 16px; }
.ssa-canvas-wrap { position: relative; }
.ssa-canvas-wrap canvas, .ssa-spatial-canvas { width: 100%; height: auto; border: 1px solid var(--border); border-radius: 8px; background: var(--mon-1); display: block; cursor: crosshair; }
.ssa-canvas-hint { color: var(--text-2); font-size: 12px; margin-top: 6px; min-height: 16px; }

.ssa-row { display: flex; gap: 8px; align-items: center; margin-top: 8px; flex-wrap: wrap; }
.ssa-list { display: flex; flex-direction: column; gap: 4px; margin-top: 8px; }
.ssa-list-row { display: flex; gap: 8px; align-items: center; padding: 5px 8px; border-radius: 6px; background: var(--bg-2); font-size: 13px; }
.ssa-list-row span { flex: 1; }
.ssa-x { background: none; border: none; color: var(--text-2); cursor: pointer; }
.ssa-x:hover { color: var(--danger); }
/* Compact secondary button for list-row actions (e.g. node "Edit") — a real
   pill in the app's language instead of default browser chrome. */
.ssa-row-btn {
  font: inherit; font-size: 12px; line-height: 1; white-space: nowrap;
  padding: 5px 12px; border-radius: var(--r-sm); cursor: pointer;
  background: var(--bg-surface); color: var(--text-1);
  border: 1px solid var(--border);
  transition: background .12s ease, border-color .12s ease;
}
.ssa-row-btn:hover {
  border-color: var(--accent);
  background: color-mix(in srgb, var(--accent) 14%, var(--bg-surface));
}
.ssa-row-btn:active { background: color-mix(in srgb, var(--accent) 22%, var(--bg-surface)); }
.ssa-row-btn.is-on { border-color: var(--accent); color: var(--accent); background: color-mix(in srgb, var(--accent) 14%, transparent); }

/* "+ Add channels" scope picker (Locate watch list) */
.ssa-add-picker { margin-top: 10px; border-top: 1px solid var(--border-subtle); padding-top: 8px; }
.ssa-add-picker > summary { cursor: pointer; font-size: 12.5px; color: var(--accent); list-style: none; user-select: none; padding: 2px 0; }
.ssa-add-picker > summary::-webkit-details-marker { display: none; }
.ssa-add-body { margin-top: 8px; display: flex; flex-direction: column; gap: 10px; }
.ssa-cat-chips { display: flex; flex-wrap: wrap; gap: 6px; }
.ssa-chip {
  font: inherit; font-size: 11.5px; line-height: 1; padding: 4px 10px; cursor: pointer;
  border-radius: 99px; background: var(--bg-surface); color: var(--text-2);
  border: 1px solid var(--border); transition: background .12s ease, border-color .12s ease, color .12s ease;
}
.ssa-chip:hover { color: var(--text-1); border-color: var(--accent); }
.ssa-chip.is-on { color: var(--accent); border-color: var(--accent); background: color-mix(in srgb, var(--accent) 14%, transparent); }
.ssa-add-section { display: flex; flex-direction: column; gap: 4px; }
.ssa-add-group { border: 1px solid var(--border-subtle); border-radius: var(--r-sm); background: var(--bg-2); }
.ssa-add-group > summary { display: flex; align-items: center; gap: 8px; padding: 6px 8px; cursor: pointer; list-style: none; font-size: 13px; }
.ssa-add-group > summary::-webkit-details-marker { display: none; }
.ssa-add-group-name { flex: 1; min-width: 0; }
.ssa-add-group-acts { display: inline-flex; gap: 6px; flex: none; }
.ssa-add-group .ssa-list { padding: 0 8px 8px; }

/* Channels tab — full channel-selection surface. The sub-header bleeds full
   width (its own negative margin); the body is a readable, scrollable column. */
.ssa-channels-body { display: flex; flex-direction: column; gap: 14px; max-width: 760px; }
.ssa-ch-search {
  width: 100%; box-sizing: border-box; font: inherit; font-size: 13px;
  padding: 7px 10px; border-radius: var(--r-sm);
  background: var(--bg-2); color: var(--text-1); border: 1px solid var(--border);
}
.ssa-ch-search:focus { outline: none; border-color: var(--accent); }
.ssa-add-groups { display: flex; flex-direction: column; gap: 10px; }
.ssa-groupby { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.ssa-groupby-label { font-size: 12px; color: var(--text-2); }
/* The ✕ sits beside the Edit pill — give it a matching hit area + hover so the
   pair reads as a tidy action group. */
.ssa-list-row .ssa-x { padding: 4px 7px; border-radius: var(--r-sm); transition: background .12s ease, color .12s ease; }
.ssa-list-row .ssa-x:hover { background: color-mix(in srgb, var(--danger) 14%, transparent); }
.ssa-section-label { font-size: 12px; text-transform: uppercase; letter-spacing: 0.06em; color: var(--text-2); margin: 14px 0 6px; display: flex; gap: 8px; align-items: center; }
.ssa-cal-status { font-size: 13px; margin-bottom: 8px; }
.ssa-cal-form { display: grid; grid-template-columns: 1fr 1fr 1fr 1fr; gap: 6px; margin-top: 8px; }
.ssa-cal-form input { min-width: 0; }
.ssa-cal-grid-label, .ssa-cal-form > button { grid-column: 1 / -1; }
/* Node editor (Configure → Spatial nodes → Edit): every setting is one
   labelled .field row, every box is .input/.select, buttons grouped in one
   .ssa-actions row per section. The antenna table + switch schedule are grids
   so the column captions line up with the boxes. */
.ssa-node-editor { margin-top: 14px; padding-top: 12px; border-top: 1px solid var(--border-subtle); }
.ssa-ant-table, .ssa-sched { display: flex; flex-direction: column; gap: 4px; }
.ssa-ant-rows, .ssa-sched-rows { display: flex; flex-direction: column; gap: 4px; }
.ssa-ant-head, .ssa-ant-row { display: grid; grid-template-columns: 72px minmax(120px, 1fr) 84px auto auto 33px; gap: 6px; align-items: center; }
.ssa-ant-head, .ssa-sched-head { font-size: 11px; text-transform: uppercase; letter-spacing: 0.05em; color: var(--text-muted); padding: 0 2px; }
.ssa-ant-row .input-num { width: 100%; }
.ssa-ant-row .input { min-width: 0; }
.ssa-ant-row .ssa-x { justify-self: end; }
.ssa-ant-row .ssa-stage-hint { grid-column: 1 / -1; }
/* V8: an imported pattern auto-staged onto this port (not yet persisted). */
.ssa-ant-row.is-autoselected { padding: 2px 6px; margin: 0 -6px; border-radius: var(--r-sm); background: color-mix(in srgb, var(--accent) 12%, transparent); }
.ssa-ant-row.is-autoselected .select[data-ssa="ant-pattern"] { border-color: color-mix(in srgb, var(--accent) 70%, transparent); }
.ssa-stage-hint { font-size: 11px; color: var(--accent); white-space: nowrap; }
.ssa-sched-head, .ssa-sched-row { display: grid; grid-template-columns: 72px 1fr auto; gap: 6px; align-items: center; }
.ssa-sched-row .input-num { width: 72px; }
.ssa-sched-dwell { display: inline-flex; align-items: center; gap: 6px; }
.ssa-sched-tools { display: inline-flex; align-items: center; gap: 2px; justify-self: end; }
@media (max-width: 640px) {
  .ssa-ant-head { display: none; }
  .ssa-ant-row { grid-template-columns: 72px minmax(100px, 1fr) 84px 33px; }
  .ssa-ant-row .select { grid-column: span 2; justify-self: start; }
}
.ssa-unit-hint { font-size: 12px; color: var(--text-2); white-space: nowrap; }
.ssa-unit-hint:empty { display: none; }
.ssa-ack-chip { font-size: 11px; padding: 2px 8px; border-radius: 99px; background: color-mix(in srgb, var(--ok) 15%, transparent); color: var(--ok); }
.ssa-ack-chip.is-pending { background: color-mix(in srgb, var(--warn) 15%, transparent); color: var(--warn); }

/* monitor */
.ssa-monitor-cols { grid-template-columns: minmax(480px, 2fr) minmax(330px, 1fr); }
.ssa-picker-row { display: block; padding: 10px 12px; border: 1px solid var(--border); border-radius: 8px; margin-bottom: 8px; }
.ssa-underlay-controls { display: flex; gap: 16px; align-items: center; margin-top: 8px; flex-wrap: wrap; font-size: 12px; color: var(--text-2); }
.ssa-underlay-controls label { display: flex; gap: 6px; align-items: center; }
.ssa-underlay-controls select { background: var(--bg-2); color: var(--text-1); border: 1px solid var(--border); border-radius: 6px; padding: 4px 8px; }
.ssa-underlay-controls input[type="range"] { width: 88px; accent-color: var(--accent); }   /* one width, desktop + phone */
.ssa-heat-opacity-val { min-width: 36px; font-variant-numeric: tabular-nums; }
.ssa-resync-result { font-size: 12px; color: var(--text-2); margin-top: 6px; }
.ssa-resync-result ul { margin: 6px 0 0; padding-left: 18px; }
/* One action row per card section (buttons grouped; exactly one primary). The
   first row in a card body sits flush; later rows keep a small gap. */
.ssa-actions { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
.ssa-actions + .ssa-actions, * + .ssa-actions { margin-top: 10px; }
.ssa-actions .ssa-manage-link { margin-left: auto; }
/* Zones card: the "Sync zones from map" outcome — one status line + a compact
   list of skipped zones in the panel's own type, flush with the card content
   edge (no <ul> indent). Each item: zone · human reason · "fix it" link; the
   worker's raw reason lives in the item's title. */
.ssa-sync-result { margin-top: 10px; font-size: 13px; color: var(--text-2); }
.ssa-sync-result:empty { margin-top: 0; }
.ssa-sync-status { color: var(--text-1); font-variant-numeric: tabular-nums; }
.ssa-sync-status.is-error { color: var(--danger); }
.ssa-skip-list { list-style: none; margin: 6px 0 0; padding: 0; display: flex; flex-direction: column; gap: 4px; }
.ssa-skip-list li { position: relative; display: flex; flex-wrap: wrap; gap: 0 6px; align-items: baseline; padding-left: 14px; line-height: 1.45; }
.ssa-skip-list li::before { content: '•'; position: absolute; left: 2px; color: var(--text-muted); }
.ssa-skip-zone { color: var(--text-1); font-weight: 500; }
.ssa-skip-fix { color: var(--accent); text-decoration: none; white-space: nowrap; }
.ssa-skip-fix:hover { text-decoration: underline; }
.ssa-health-strip { display: flex; gap: 8px; margin-top: 10px; flex-wrap: wrap; }
.ssa-health-cell { border: 1px solid var(--border); border-left-width: 4px; border-radius: 8px; padding: 6px 10px; min-width: 150px; font-size: 12px; }
.ssa-health-cell.is-green { border-left-color: var(--ok); }
.ssa-health-cell.is-amber { border-left-color: var(--warn); }
.ssa-health-cell.is-red   { border-left-color: var(--danger); }
/* A planned node (no hardware yet — isPlannedNode): nothing measured, so neutral,
   never the red of a failing receiver (unmeasured ⇒ neutral). */
.ssa-health-cell.is-planned { border-left-color: var(--text-faint); }
.ssa-health-meta.ssa-health-planned { color: var(--text-muted); font-style: italic; }
.ssa-health-bars { display: flex; gap: 2px; align-items: flex-end; height: 18px; margin: 4px 0; }
.ssa-sweep-bar { width: 9px; background: var(--accent); height: var(--v, 0%); border-radius: 1px; }
.ssa-health-meta { color: var(--text-2); }
.ssa-clock-chip { font-family: 'IBM Plex Mono', monospace; }
.ssa-det-card { border: 1px solid var(--border); border-radius: 10px; padding: 10px 12px; margin-bottom: 10px; cursor: pointer; }
.ssa-det-card.is-selected { border-color: var(--accent); box-shadow: 0 0 0 1px color-mix(in srgb, var(--accent) 45%, transparent); }
.ssa-det-head { display: flex; justify-content: space-between; font-weight: 600; }
.ssa-det-freq { font-family: 'IBM Plex Mono', monospace; }
.ssa-det-zone { margin-top: 2px; }
.ssa-det-nodes, .ssa-det-seen { color: var(--text-2); font-size: 12px; margin-top: 2px; }
.ssa-det-comp { display: flex; gap: 4px; height: 6px; margin-top: 6px; }
.ssa-bar { flex: 1; background: var(--bg-2); border-radius: 3px; position: relative; overflow: hidden; }
.ssa-bar::after { content: ''; position: absolute; left: 0; top: 0; bottom: 0; width: var(--v, 0%); background: var(--accent); }
.ssa-det-action { margin-top: 6px; font-size: 13px; color: var(--accent); }
.ssa-replay-stub { margin-top: 10px; }
.ssa-replay-chip { font-size: 12px; color: var(--text-2); border: 1px dashed var(--border); border-radius: 99px; padding: 4px 12px; }

/* bearing-rays toggle (sits in the underlay-controls row) */
.ssa-rays-toggle { display: flex; gap: 6px; align-items: center; cursor: pointer; }
.ssa-rays-toggle input { accent-color: var(--accent); }

/* unfoldable reasoning table (per detection card; collapsed by default) */
.ssa-reasoning { margin-top: 8px; border-top: 1px solid var(--border); padding-top: 6px; }
.ssa-reasoning-summary { font-size: 12px; color: var(--text-2); cursor: pointer; list-style: none; user-select: none; }
.ssa-reasoning-summary::-webkit-details-marker { display: none; }
.ssa-reasoning-summary::before { content: '▸ '; color: var(--text-2); }
.ssa-reasoning[open] .ssa-reasoning-summary::before { content: '▾ '; }
.ssa-reasoning-body { margin-top: 6px; }
.ssa-reasoning-table { width: 100%; border-collapse: collapse; font-size: 11px; font-variant-numeric: tabular-nums; }
.ssa-reasoning-table th { text-align: left; color: var(--text-2); font-weight: 500; padding: 2px 6px 2px 0; border-bottom: 1px solid var(--border); }
.ssa-reasoning-table td { padding: 3px 6px 3px 0; color: var(--text-1); }
.ssa-reasoning-node { font-family: 'IBM Plex Mono', monospace; }
.ssa-reasoning-row.is-excluded td { color: var(--text-2); font-style: italic; }
.ssa-reasoning-combined { margin-top: 6px; font-size: 11px; color: var(--text-2); }
@media (max-width: 980px) { .ssa-cols { grid-template-columns: 1fr; } }
/* The monitor keeps its two columns down to 900 px (480 + 330 + gap fits) so
   the reference-point card in the right column is beside the canvas at every
   width spatial.js mounts it there ((min-width: 900px) — keep in sync). */
@media (max-width: 899px) { .ssa-monitor-cols { grid-template-columns: 1fr; } }

/* ── antenna orientation panel (fan + oblique + fine-tune, spec 2026-06-11) ── */
.ssa-orient-panel { display: flex; flex-direction: column; gap: 8px; }
/* fan (compass) + side view: two EQUAL cards in one grid */
.ssa-orient-cards { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px; }
.ssa-orient-card { display: flex; flex-direction: column; gap: 6px; padding: 10px 12px; border: 1px solid var(--border-subtle); border-radius: var(--r-md); background: var(--bg-2); min-width: 0; }
.ssa-orient-card-title { font-size: 12px; font-weight: 600; color: var(--text-1); }
.ssa-orient-card-sub { font-size: 11px; color: var(--text-muted); line-height: 1.4; }
.ssa-orient-card-fig { flex: 1; display: flex; align-items: center; justify-content: center; min-height: 160px; }
.ssa-orient-card-fig svg { width: 100%; height: auto; max-width: 220px; }
@media (max-width: 560px) { .ssa-orient-cards { grid-template-columns: 1fr; } }
.ssa-fan { background: var(--bg-surface); border-radius: var(--r-sm); }
.ssa-fan-dial { fill: none; stroke: var(--border, rgba(255,255,255,0.18)); stroke-width: 1; }
.ssa-fan-tick { fill: var(--text-2); font-size: 10px; }
.ssa-fan-wedge { stroke-width: 1.1; }
.ssa-fan-wedge.is-trusted { fill: rgba(52,211,153,0.18); stroke: rgba(52,211,153,0.8); stroke-dasharray: none; }
.ssa-fan-wedge.is-planned { fill: rgba(251,191,36,0.12); stroke: rgba(251,191,36,0.72); stroke-dasharray: 5 4; }
.ssa-fan-ghost { stroke: rgba(251,191,36,0.85); stroke-width: 1; stroke-dasharray: 3 3; }
.ssa-oblique { background: var(--bg-surface); border-radius: var(--r-sm); }
.ssa-oblique-ground { stroke: var(--border, rgba(255,255,255,0.25)); stroke-width: 1.2; }
.ssa-oblique-mast { stroke: var(--text-2); stroke-width: 2; }
.ssa-oblique-beam { fill: rgba(52,211,153,0.16); stroke: rgba(52,211,153,0.7); stroke-width: 1; }
.ssa-orient-rows { display: flex; flex-direction: column; gap: 6px; margin-top: 6px; }
/* one antenna per row: meta line (name · source · readout · drift), then its
   own action row (override box + buttons) */
.ssa-orient-row { display: flex; flex-direction: column; gap: 4px; padding: 8px 10px; border-radius: var(--r-sm); background: var(--bg-2); font-size: 12px; }
.ssa-orient-meta { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.ssa-orient-name { min-width: 130px; color: var(--text-1); font-weight: 500; }
.ssa-orient-badge { font-size: 10px; text-transform: uppercase; letter-spacing: 0.05em; padding: 1px 6px; border-radius: 10px; }
.ssa-orient-badge.is-manual { background: var(--accent-soft); color: var(--accent); }
.ssa-orient-badge.is-measured { background: color-mix(in srgb, var(--ok) 16%, transparent); color: var(--ok); }
.ssa-orient-badge.is-planned { background: color-mix(in srgb, var(--warn) 16%, transparent); color: var(--warn); }
.ssa-orient-readout { color: var(--text-2); flex: 1 1 200px; }
.ssa-orient-ctl.ssa-actions { margin-top: 2px; gap: 6px; }

/* ── orientation drift + coverage analysis surfaces (spec 2026-06-11 §9) ── */
.ssa-coverage-stat { font-size: 11px; color: var(--text-2); margin-left: 8px; }
.ssa-health-drift { margin-top: 4px; font-size: 11px; color: var(--warn); font-weight: 600; }
.ssa-orient-drift { font-size: 11px; color: var(--warn); font-weight: 600; }
.ssa-orient-drift-summary { font-size: 12px; color: var(--warn); font-weight: 600; margin: 2px 0 4px; }

/* ── Spatial 3D view (js/views/spatial-3d.js, #/events/:id/spatial/3d) ──────
   Immersive full-bleed WebGL scene. Header strip + per-node panel float over a
   flex viewport that owns the rest of the viewport height. */
.ssa3d-page { display: flex; flex-direction: column; height: 100%; min-height: 0; background: var(--mon-1); }
.ssa3d-head { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; padding: 12px 16px; border-bottom: 1px solid var(--border, rgba(255,255,255,0.08)); }
/* Title sizing/flex matches the 2D sub-header's .ssa-head h1 (18px, flex:1) so
   the monitor / 3D / config headers read as one family; the flexed title also
   seats the status badge right after it (consistent badge placement). */
.ssa3d-title { font-size: 18px; font-weight: 600; margin: 0; flex: 1 1 auto; color: var(--text-1, #e6e9f0); }
.ssa3d-spacer { flex: 1 1 auto; }
.ssa3d-presets { display: inline-flex; gap: 4px; }
.ssa3d-toggles { display: inline-flex; gap: 12px; align-items: center; }
.ssa3d-toggle { font-size: 12px; color: var(--text-2, #aab2c5); display: inline-flex; align-items: center; gap: 3px; cursor: pointer; }
.ssa3d-nodepanel { display: flex; gap: 12px; flex-wrap: wrap; align-items: center; padding: 4px 12px; font-size: 12px; color: var(--text-2, #aab2c5); border-bottom: 1px solid var(--border, rgba(255,255,255,0.06)); }
.ssa3d-nodepanel-title { font-weight: 600; color: var(--text-1, #e6e9f0); }
.ssa3d-node-toggle { display: inline-flex; align-items: center; gap: 3px; cursor: pointer; }
.ssa3d-viewport { position: relative; flex: 1 1 auto; min-height: 0; overflow: hidden; overscroll-behavior: none; }
.ssa3d-viewport canvas { display: block; width: 100%; height: 100%; }
.ssa3d-fallback { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 14px; height: 100%; padding: 32px; text-align: center; color: var(--text-2, #aab2c5); }
.ssa3d-fallback p { max-width: 420px; line-height: 1.5; }

/* Reference-point tool (venue field markers) — set on the 2D Spatial floor
   plan. On a two-column layout (≥ 900 px) spatial.js mounts it as a normal
   full-width card at the top of the right column, beside the canvas (nothing
   floats over the plan). Narrower, it lives inside .ssa-spatial-canvas-wrap
   (position:relative) as an overlay — the phone bottom sheet below — so the
   operator never scrolls between the pitch and the panel while placing. */
.ssa-spatial-canvas-wrap { position: relative; overscroll-behavior: none; }
.ssa-monitor-right > .ssa-refpoints {
  position: static; width: auto; max-width: none; max-height: none; overflow: visible;
  box-shadow: none; border-color: var(--border-subtle, var(--border)); border-radius: var(--r-md, 10px);
}
.ssa-monitor-right > .ssa-refpoints .ssa-refpoints-rows { grid-template-columns: 1fr; }   /* one feature per row */
/* The 2D canvas is a zoomable/pannable map (js/ssa/canvas-view.js): its CSS box
   is 3:2 of the column width (the backing store follows it × dpr, so the plan
   is crisp on 2× displays); touch-action:none keeps a finger drag / pinch on
   the map instead of scrolling the page; it is focusable for + − 0 keys.
   overscroll-behavior: none here and on the wrappers states intent — the
   rule that actually stops Chrome's swipe-back is the root one (base.css
   html,body), plus the wheel handler cancelling every event over the map. */
.ssa-spatial-canvas { aspect-ratio: 3 / 2; touch-action: none; overscroll-behavior: none; }
.ssa-spatial-canvas.is-panning { cursor: grabbing; }
/* Node drag (editors, js/views/spatial.js): a grab hand over a marker this
   user may move, a closed hand while one is in flight. */
.ssa-spatial-canvas.is-over-node { cursor: grab; }
.ssa-spatial-canvas.is-dragging-node { cursor: grabbing; }
.ssa-spatial-canvas:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.ssa-zoom-ctl { display: inline-flex; align-items: center; gap: 2px; }
.ssa-zoom-ctl .btn { min-width: 30px; padding: 3px 8px; font-size: 13px; line-height: 1.2; }
.ssa-zoom-val { min-width: 38px; text-align: center; font-variant-numeric: tabular-nums; font-family: 'IBM Plex Mono', monospace; font-size: 11px; color: var(--text-2); }
.ssa-refpoints {
  position: absolute; top: 12px; right: 12px; z-index: 20;
  width: 300px; max-width: calc(100% - 24px); max-height: calc(100% - 24px);
  overflow: auto; padding: 12px; border: 1px solid var(--border); border-radius: 10px;
  background: var(--bg-card); box-shadow: 0 8px 28px rgba(0, 0, 0, 0.5);
}
.ssa-refpoints-head { font-size: 13px; font-weight: 600; color: var(--text-1); margin-bottom: 4px; }
.ssa-refpoints-hint { font-size: 12px; color: var(--text-2); margin-bottom: 10px; min-height: 16px; }
.ssa-refpoints-rows { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 6px; }
/* One line per feature: feature button | status | ✕. A grid (not flex) so the
   status cell is the ONLY thing that gives — minmax(0, 1fr) + min-width: 0 let
   it shrink below its text and ellipsize ("129, 121.4 m · on Riverside Stadium…")
   instead of running into the ✕ or past the card; the ✕ keeps its own column
   and is never overlapped. */
.ssa-refpoints-row { display: grid; grid-template-columns: auto minmax(0, 1fr) auto; gap: 6px; align-items: center; padding: 5px 6px; border: 1px solid var(--border); border-radius: 7px; background: var(--bg-1); font-size: 12px; }
.ssa-refpoints-row.is-set { border-color: color-mix(in srgb, var(--accent) 55%, transparent); }
.ssa-refpoints-arm { background: var(--bg-2); color: var(--text-1); border: 1px solid var(--border); border-radius: 6px; padding: 4px 7px; cursor: pointer; font-size: 12px; white-space: nowrap; }
.ssa-refpoints-arm.is-armed { background: var(--accent); color: #fff; border-color: var(--accent); font-weight: 600; }
.ssa-refpoints-status { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--text-2); font-family: 'IBM Plex Mono', monospace; font-size: 11px; text-align: right; }
.ssa-refpoints-del { background: none; border: none; color: var(--text-2); cursor: pointer; font-size: 13px; line-height: 1; }
.ssa-refpoints-del:hover { color: var(--danger); }
.ssa-refpoints-actions { display: flex; gap: 10px; align-items: center; margin-top: 10px; }
.ssa-refpoints-savestatus { font-size: 12px; color: var(--text-2); }
.ssa-refpoints-savestatus.is-dirty { color: var(--warn); }
.ssa-refpoints-savestatus.is-ok { color: var(--ok); }
.ssa-refpoints-savestatus.is-err { color: var(--danger); }
.ssa-spatial-canvas.ssa-canvas-armed { cursor: cell; }
@media (max-width: 560px) {
  .ssa-refpoints-rows { grid-template-columns: 1fr; }
  /* On phones the top-right card would swallow most of the pitch — dock it as a
     bottom sheet capped to part of the canvas height (scrolls within). */
  .ssa-refpoints {
    top: auto; left: 8px; right: 8px; bottom: 8px;
    width: auto; max-width: none; max-height: 55%;
    border-radius: 12px;
  }
  /* While a feature is armed the operator must tap the plan to place it, so
     collapse the sheet to just its hint line until the marker lands (setArmed
     clears the flag), restoring the full sheet. */
  .ssa-refpoints.is-armed { max-height: none; }
  .ssa-refpoints.is-armed .ssa-refpoints-head,
  .ssa-refpoints.is-armed .ssa-refpoints-rows,
  .ssa-refpoints.is-armed .ssa-refpoints-actions { display: none; }
  .ssa-refpoints.is-armed .ssa-refpoints-hint { margin-bottom: 0; }
}

/* ── Data-source pill (Live / Simulated) — the clickable sub-header chip + its
   selector popover (DECT-monitor pattern). Replaces the old status Badge. ── */
.ssa-source-pill {
  position: relative; display: inline-flex; align-items: center; gap: 6px;
  padding: 3px 9px; border-radius: 999px; cursor: pointer; user-select: none;
  font-size: 11.5px; font-weight: 600; line-height: 1; white-space: nowrap;
  border: 1px solid var(--border); background: var(--bg-2); color: var(--text-1);
}
.ssa-source-pill:hover { border-color: color-mix(in srgb, var(--text-2) 45%, transparent); }
.ssa-source-pill:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.ssa-source-pill .badge-label { text-transform: uppercase; letter-spacing: .3px; }
.ssa-source-pill .ssp-caret { font-size: 9px; color: var(--text-2); }
.ssa-source-pill.is-open .ssp-caret { transform: rotate(180deg); }
.ssp-dot { width: 7px; height: 7px; border-radius: 50%; flex: none; background: var(--text-2); }
.ssp-dot-live { background: var(--ok); }
.ssp-dot-warn, .ssp-dot-sim { background: var(--warn); }
.ssp-dot-danger { background: var(--danger); }
.ssp-dot-neutral { background: var(--text-2); }
.ssa-source-pill.ssp-live { border-color: color-mix(in srgb, var(--ok) 45%, transparent); }
.ssa-source-pill.ssp-warn { border-color: color-mix(in srgb, var(--warn) 50%, transparent); }
.ssa-source-pill.ssp-warn .badge-label { color: var(--warn); }   /* SIMULATED reads amber */
.ssa-source-pill.ssp-danger { border-color: color-mix(in srgb, var(--danger) 50%, transparent); }
.ssa-source-menu {
  position: absolute; top: calc(100% + 6px); right: 0; z-index: 50; cursor: default;
  width: 212px; padding: 8px; border: 1px solid var(--border); border-radius: 10px;
  background: var(--bg-card); box-shadow: 0 8px 28px rgba(0, 0, 0, 0.5);
}
.ssa-source-cap { font-size: 11px; color: var(--text-2); margin: 0 0 6px 2px; }
.ssa-source-menu .segmented { width: 100%; }
.ssa-source-menu .seg-btn { flex: 1; justify-content: center; }
.ssa-source-menu .seg-btn.is-disabled { opacity: .45; cursor: not-allowed; }
.ssa-source-hint { font-size: 11px; color: var(--text-2); margin-top: 6px; }

/* ── Canvas toolbar — ONE row directly above the 2D canvas, the 3D view's
   control-row pattern (.ssa3d-head: 12px labels, ghost buttons, wrapping):
   Underlay · Heatmap · layer toggles · coverage stat · + − Fit · north chip.
   Nothing floats over the plan any more except the frame banner. ── */
.ssa-canvas-toolbar {
  display: flex; align-items: center; flex-wrap: wrap; gap: 6px 14px;
  margin: 0 0 8px; padding: 6px 10px; font-size: 12px; color: var(--text-2);
  border: 1px solid var(--border, rgba(255,255,255,0.08)); border-radius: 8px; background: var(--bg-1);
}
/* the controls body contributes its children straight into the toolbar's flex row */
.ssa-canvas-toolbar > .ssa-underlay-controls { display: contents; }
.ssa-canvas-toolbar .ssa-coverage-stat { margin-left: 0; }
.ssa-canvas-toolbar .ssa-coverage-stat:empty { display: none; }
.ssa-canvas-toolbar .ssa-zoom-ctl { margin-left: auto; }
/* "Pitch markers" — the reference-point TOOL as a pill toggle at the end of the
   row (control standard: pill = toggle), sized to the toolbar's 12px text. */
.ssa-canvas-toolbar .chip { padding: 2px 10px; font-size: 11.5px; }
.ssa-canvas-toolbar .ssa-underlay-hint { max-width: 420px; }
.ssa-north-chip { font-family: 'IBM Plex Mono', monospace; font-size: 11px; font-weight: 600; color: var(--warn); white-space: nowrap; }
.ssa-north-chip[hidden] { display: none; }
/* Floor + Underlay are the shared boxed Select (control standard: box = pick a
   value); the underlay host is rebuilt per floor and contributes its one child
   straight into the toolbar row. */
.ssa-canvas-toolbar .ssa-underlay-host { display: contents; }
/* Toolbar chips: "heatmap on <floor>" (2D), "model placed on <floor>" (3D) —
   a muted dashed pill; a button when clicking it switches floor. */
.ssa-toolbar-chip {
  font-family: var(--font-mono); font-size: 11px; font-weight: 600; color: var(--text-2);
  white-space: nowrap; background: transparent; border: 1px dashed var(--border-strong, var(--border));
  border-radius: var(--r-pill, 999px); padding: 2px 9px; line-height: 1.5; cursor: pointer;
}
.ssa-toolbar-chip:hover:not(:disabled) { color: var(--text-1); border-color: var(--accent); }
.ssa-toolbar-chip:disabled { cursor: default; }
.ssa-toolbar-chip[hidden] { display: none; }
/* "N plan pages not shown · no scale" — a LINK to the Map tab (the fix), so no
   underline and the chip's own colour, not the link blue. */
a.ssa-toolbar-chip { text-decoration: none; display: inline-flex; align-items: center; }

/* ── Node-health strip — compact node cards in a wrapping row UNDER the map. ── */
.ssa-node-strip-wrap { margin-top: 10px; }
.ssa-node-strip-wrap[hidden] { display: none; }
.ssa-node-strip-cap { font-size: 11px; color: var(--text-2); margin-bottom: 6px; text-transform: uppercase; letter-spacing: .3px; }
.ssa-node-strip { display: flex; flex-wrap: wrap; gap: 8px; margin: 0; }
.ssa-node-strip .ssa-health-cell { min-width: 118px; padding: 6px 9px; font-size: 11.5px; }

@media (max-width: 560px) {
  .ssa-node-strip .ssa-health-cell { flex: 1 1 auto; }
  /* (The heat slider is 88px at every width now — see .ssa-underlay-controls
     above — so the toolbar row "Heatmap [slider] 85%" stays on one line here
     without a phone override.) */
}

/* 3D nav hint — fades out after a few seconds (pan/zoom were undiscoverable). */
.ssa3d-navhint {
  position: absolute; left: 12px; bottom: 12px; z-index: 5; pointer-events: none;
  font-size: 11px; color: var(--text-2, #aab2c5); background: rgba(12,18,28,0.7);
  border: 1px solid var(--border, rgba(255,255,255,0.08)); border-radius: 6px;
  padding: 4px 8px; opacity: 0; animation: ssa3d-navhint-fade 6s ease forwards;
}
@keyframes ssa3d-navhint-fade {
  0% { opacity: 0; } 8% { opacity: 1; } 75% { opacity: 1; } 100% { opacity: 0; }
}

/* Embedded 3D view (in-shell state of the Spatial tab): full-bleed under the
   event header — drop the page padding/max-width so the scene fills the area.
   The .ssa3d-page height is set at runtime (event-spatial fit3d). */
.ssa-event-spatial.is-3d { padding: 0; max-width: none; }
/* …but the setup strip is page furniture, not part of the scene: keep the
   inset it has everywhere else rather than letting it run edge to edge. */
.ssa-event-spatial.is-3d .ssa-setup-strip { margin: 8px 16px; }
.ssa-event-spatial.is-3d [data-ssa="spatial-panel"] { height: 100%; display: flex; flex-direction: column; }
/* The 3D view mounts into a box of its OWN (event-spatial render3D) so its own
   remounts — a floor change is one — cannot wipe the setup strip above it. The
   column + flex:1 keeps .ssa3d-page's height:100% resolving against a real
   height after a remount, which is what panelHost used to give it directly. */
.ssa-event-spatial.is-3d .ssa3d-host { flex: 1 1 0; min-height: 0; }

/* Drone sub-tab: same full-bleed treatment as 3D (its Cesium world fills the
   area under the event header). fit3d sizes the .ssa3d-page height at runtime. */
.ssa-event-spatial.is-drone { padding: 0; max-width: none; }
.ssa-event-spatial.is-drone [data-ssa="spatial-panel"] { height: 100%; }
/* The 3D host fills the page below the sub-header + control row. */
.ssa-drone-page { display: flex; flex-direction: column; height: 100%; min-height: 0; }
.ssa-drone-head { display: flex; align-items: center; gap: 10px; padding: 6px 16px; flex-wrap: wrap; }
/* 2D map + 3D drape share one positioned area; only one is shown at a time. */
.ssa-drone-body { position: relative; flex: 1 1 0; min-height: 0; background: var(--mon-1); }
/* MapLibre stamps its own `maplibregl-map` class onto our container, and the
   vendored `.maplibregl-map { position: relative }` rule TIES this selector's
   specificity (both 0,1,0) and wins on load order — which collapses the host to
   height:0 (its only child, the canvas, is position:absolute → out of flow) and
   the whole map goes black. Scope under `.ssa-drone-body >` (0,2,0) so our
   absolute-fill beats MapLibre's relative and the host keeps the body's height. */
.ssa-drone-body > .ssa-drone-map { position: absolute; inset: 0; }
.ssa-drone-map .maplibregl-map, .ssa-drone-map canvas { position: absolute; inset: 0; }
.ssa-drone-3d { position: absolute; inset: 0; background: var(--mon-1); }
.ssa-drone-3d canvas, .ssa-drone-3d .cesium-viewer, .ssa-drone-3d .cesium-widget { width: 100%; height: 100%; }
.ssa-drone-empty { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; z-index: 4; color: var(--text-2); font-size: 14px; pointer-events: none; }
/* Flight (replay) picker uses the shared .select primitive; only the host span
   needs to sit inline in the control row. */
.ssa-drone-session-host { display: inline-flex; align-items: center; }
.ssa-drone-badge {
  font-variant-numeric: tabular-nums; padding: 2px 10px; border-radius: 999px;
  font-size: 12px; background: color-mix(in srgb, var(--accent) 15%, transparent); color: var(--accent);
}
.ssa-drone-badge.is-coarse { background: color-mix(in srgb, var(--warn) 15%, transparent); color: var(--warn); }
.ssa-drone-status {
  position: absolute; top: 12px; left: 50%; transform: translateX(-50%); z-index: 5;
  padding: 6px 14px; border-radius: 8px; background: rgba(20,24,32,.88);
  color: var(--text-2); font-size: 13px; pointer-events: none;
}
.ssa-drone-status.is-warn { color: #ffd9a0; }
.ssa-drone-status.is-error { color: #ff9d9d; }

/* 3D camera controls: toolbar presets keep a little breathing room from the
   spread badge; the on-map +/− zoom cluster is a corner overlay (top-left, the
   free corner — video = right rail, Cesium credit = bottom-left). */
.ssa-drone-presets { margin-left: 4px; }
.ssa-drone-zoom {
  position: absolute; top: 12px; left: 12px; z-index: 5;
  display: flex; flex-direction: column;
  border-radius: 8px; overflow: hidden; box-shadow: 0 1px 4px rgba(0,0,0,.4);
}
.ssa-drone-zoom-btn {
  width: 34px; height: 34px; padding: 0; border: none; cursor: pointer;
  background: rgba(20,24,32,.88); color: var(--text-1);
  font-size: 20px; font-weight: 600; line-height: 1;
  display: flex; align-items: center; justify-content: center;
}
.ssa-drone-zoom-btn + .ssa-drone-zoom-btn { border-top: 1px solid var(--border, rgba(255,255,255,.1)); }
.ssa-drone-zoom-btn:hover { background: rgba(38,44,56,.94); }
.ssa-drone-zoom-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }

/* P3 live-video panel: a collapsible right rail floating over the map (the
   panel supplies its own width/background/border; we only anchor it). */
.ssa-drone-body > .drone-video {
  position: absolute; top: 0; right: 0; bottom: 0; z-index: 6;
}

/* P3 "Recorded flights" list (revealed under the control row, like ingest). */
.ssa-drone-recorded {
  padding: 10px 16px; border-bottom: 1px solid var(--border, rgba(255,255,255,.08));
  background: var(--bg-1, #10141d);
}
.ssa-drone-recorded-empty { color: var(--text-2); font-size: 13px; }
/* Recorded-flight items are the shared .btn primitive; the list just wraps them. */
.ssa-drone-recorded-list { display: flex; flex-wrap: wrap; gap: 8px; }

/* Spatial config panel: the stacked cards (Zones / Spatial nodes / watch list)
   share the global .card (no margin), so their borders clashed into each other.
   Space them. Scoped to the event-spatial views so other .card layouts are
   untouched; suppressed in 3D (full-bleed). */
.ssa-event-spatial:not(.is-3d) .card { margin-bottom: 12px; }
.ssa-event-spatial:not(.is-3d) [data-ssa="watch-host"] { margin-top: 4px; }

/* Config form width: cap the stacked cards (Zones / Spatial nodes / Simulation /
   watch list) so their rows stop stretching edge-to-edge on a wide monitor —
   left-aligned under the full-bleed sub-header, which stays full width. Scoped
   to the config sub-view (is-config) so the monitor/3D fill the area as before. */
.ssa-event-spatial.is-config [data-ssa="spatial-panel"] > .card,
.ssa-event-spatial.is-config [data-ssa="watch-host"] { max-width: 880px; }

/* Simulation ("dummy data") — config card affordances. The synthetic-data
   signal in the monitor is the amber SIMULATED sub-header badge plus the
   compact "Stop simulation" button in the underlay-controls row (no banner). */
.ssa-sim-hint { font-size: 12px; color: var(--text-2); margin-bottom: 6px; }
/* Configure → Spatial nodes: why the source list is empty and what to do
   instead (planned nodes). Same voice as the sim hint; capped to a readable
   measure on wide screens. */
.ssa-node-add-hint { font-size: 12px; line-height: 1.45; color: var(--text-2); margin: 8px 0 0; max-width: 72ch; }
.ssa-node-add-hint[hidden] { display: none; }
.ssa-sim-on { font-size: 13px; color: var(--warn); margin-bottom: 8px; }
.ssa-sim-stop { align-self: flex-start; }

/* The 2D monitor (renderSpatialMonitor) builds its OWN .ssa-page.ssa-spatial
   wrapper; nested inside the event-spatial .ssa-page that doubles the padding
   and would inset the full-bleed sub-header differently from the config view
   (which appends straight into the host). Zero the inner wrapper's padding/width
   so the sub-header bleeds to the same edge in all three sub-views. */
.ssa-event-spatial .ssa-spatial { padding: 0; max-width: none; }

/* All three Spatial sub-views share the 3D scene's dark surface (--mon-1) so the
   2D monitor and Configure sit on the same background as the 3D view rather than
   the lighter app shell. .m-body is a flex column, so the mount grows to fill it
   (and the dark shows below short content / the empty detections column). */
.m-spatial-mount { flex: 1 1 auto; min-height: 0; background: var(--mon-1); }

/* The right column is now just the detection cards, replay stub and watch list
   (map controls float over the canvas; the node strip sits under it). Flex gap
   owns the spacing. */
.ssa-monitor-right { display: flex; flex-direction: column; gap: 12px; min-width: 0; }
.ssa-monitor-right > .ssa-replay-stub,
.ssa-monitor-right > [data-ssa="watch-host"] { margin-top: 0; }

/* Unified Spatial sub-header (ssa-nav): SSA brand mark + "Spatial Spectrum
   Awareness" + the Monitor·3D·Configure·Reference-points tab switcher. Styled
   as a real chrome bar in the app's monitor language — the same recipe as the
   event sub-header .m-subhdr (var(--bg-1) fill, hairline bottom border, 14px
   title, 20px / 11.5px tab pills) — so the 2D monitor, 3D view and config panel
   all read as one surface. Full-bleeds to the .ssa-page edges (negative margin)
   so it spans wider than the inset cards, like .m-subhdr does. */
.ssa-navbar {
  display: flex; align-items: center; gap: var(--s3); flex-wrap: wrap;
  min-height: 30px; padding: 5px 20px; margin: -16px -20px 14px;
  background: var(--bg-1); border-bottom: 1px solid var(--border-subtle);
}
.ssa-navbar-brand { display: inline-flex; align-items: center; gap: 8px; color: var(--text-1); font-weight: 600; font-size: 14px; letter-spacing: -.2px; }
.ssa-navbar-name { white-space: nowrap; }
.ssa-glyph { color: var(--accent); flex: none; }
.ssa-navbar-right { display: inline-flex; align-items: center; gap: 10px; margin-left: auto; flex-wrap: wrap; }
.ssa-navbar-tabs { flex: none; }
/* Compact tab pills, identical to the event sub-header tabs (.m-subhdr .seg-btn). */
.ssa-navbar .segmented { padding: 0; align-items: center; }
.ssa-navbar .seg-btn { padding: 0 10px; height: 20px; font-size: 11.5px; }
/* The Configure gear at the end of the row: the platform's settings-gear shape
   (ghost icon button) sized to the 20px tab pills; pressed = the Configure
   panel is showing (same treatment as an active pill). */
.ssa-navbar-gear { width: 24px; height: 24px; min-width: 0; min-height: 0; padding: 0; margin-left: 2px; }
.ssa-navbar-gear[aria-pressed="true"] { background: var(--bg-raised); color: var(--text-1); box-shadow: 0 1px 3px rgba(0,0,0,.4); }
/* Full-bleed 3D page: the bar is the scene's top chrome row — no page padding to
   bleed against, so reset the negative margin (keeps the shared bg + border). */
.ssa3d-page .ssa-navbar { margin: 0; padding: 5px 16px; }
/* Phone: the Monitor·Channels·3D·Drone·Configure·Reference pill strip is wider
   than the viewport and was cut mid-label with no scroll affordance. Same
   treatment as the monitor tab strip (.m-modewrap ≤1099): full-width row that
   pans horizontally, labels never wrap. */
@media (max-width: 760px) {
  .ssa-navbar-right { flex: 1 1 100%; min-width: 0; }
  /* The Segmented element itself carries .ssa-navbar-tabs — make IT the pan
     container (its .seg-btn children keep their min-content width). */
  .ssa-navbar-tabs {
    flex: 1 1 auto; min-width: 0; max-width: 100%;
    overflow-x: auto; -webkit-overflow-scrolling: touch; scrollbar-width: none;
  }
  .ssa-navbar-tabs::-webkit-scrollbar { display: none; }
  .ssa-navbar-tabs .seg-btn { white-space: nowrap; flex: none; }
}

/* ── Barometric elevation: node height (differential-baro sensed vs manual pin) ──
   The source chip is a fixed-height status pill so it sits centred in the
   33px Height row next to the .input and the Use-sensor button. */
.ssa-height-src {
  display: inline-flex; align-items: center; height: 24px; padding: 0 10px; border-radius: 99px;
  font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em; white-space: nowrap;
  background: color-mix(in srgb, var(--text-2) 14%, transparent); color: var(--text-2);
}
.ssa-height-src.is-sensed { background: color-mix(in srgb, var(--accent) 16%, transparent); color: var(--accent); }
.ssa-height-src.is-manual { background: color-mix(in srgb, var(--ok) 16%, transparent); color: var(--ok); }
.ssa-height-src.is-none   { background: color-mix(in srgb, var(--warn) 14%, transparent); color: var(--warn); }
/* node-strip per-node height chip */
.ssa-health-height { margin-top: 3px; font-size: 11px; color: var(--text-2); }
.ssa-health-height.is-sensed { color: var(--accent); }
.ssa-health-height.is-manual { color: var(--text-1); }
.ssa-health-height.is-none   { color: var(--warn); }

/* ── V8 antenna-pattern library + import wizard ─────────────────────────────── */
.pat-library { max-width: 980px; margin: 0 auto; padding: var(--s5) var(--s4); }
.pat-lib-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: var(--s4); }
.pat-lib-title { font-size: 20px; font-weight: 600; margin: 0; }
.pat-lib-table { width: 100%; border-collapse: collapse; }
.pat-lib-table .dt-th { text-align: left; color: var(--text-muted); font-size: 12px; text-transform: uppercase; letter-spacing: .04em; padding: 8px 10px; border-bottom: 1px solid var(--border); }
.pat-lib-table .dt-td { padding: 9px 10px; border-bottom: 1px solid var(--border-subtle); font-size: 13px; }
/* Phone: the 5-column library table's min-content exceeds narrow viewports
   (≈470px), and with no scroll container the whole document panned sideways.
   Let the table itself scroll (display:block + overflow keeps row layout). */
@media (max-width: 760px) {
  .pat-lib-table { display: block; overflow-x: auto; -webkit-overflow-scrolling: touch; }
}

.pat-wizard { display: grid; grid-template-columns: 1.1fr .9fr; gap: var(--s4); align-items: start; }
.pat-drop { border: 1.5px dashed var(--border); border-radius: var(--r-md); padding: 18px; text-align: center; color: var(--text-2); background: var(--bg-2); }
.pat-drop.is-drag { border-color: var(--accent); background: var(--accent-soft); }
.pat-drop-big { color: var(--text-1); font-weight: 500; }
.pat-drop-sub { color: var(--text-muted); font-size: 12px; margin-top: 4px; font-family: var(--font-mono); }
/* File / Format / Name are labelled .field rows (components/field.js); the
   drop zone sits above them, the preview card above the metadata. */
.pat-wizard .field-list { margin-top: 12px; }
.pat-wizard .field { grid-template-columns: 72px minmax(0, 1fr); }
.pat-wizard .field-controls > .input { flex: 1 1 160px; }
.pat-filename { color: var(--text-muted); font-size: 12px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 100%; }
.pat-preview-card { display: flex; flex-direction: column; gap: 6px; padding: 10px 12px; border: 1px solid var(--border-subtle); border-radius: var(--r-md); background: var(--bg-2); margin-bottom: 12px; }
.pat-preview-title { font-size: 12px; font-weight: 600; color: var(--text-1); }
.pat-preview-fig { display: flex; justify-content: center; }
.pat-polar { display: block; width: 100%; max-width: 230px; height: auto; background: radial-gradient(120% 120% at 50% 45%, var(--bg-2) 0%, var(--bg-0) 100%); border-radius: var(--r-md); }
.pat-meta { display: grid; grid-template-columns: auto 1fr; gap: 5px 14px; font-size: 12.5px; }
.pat-meta .pat-k { color: var(--text-faint); }
.pat-meta .pat-v { color: var(--text-1); font-family: var(--font-mono); }
.pat-warn { margin-top: 12px; font-size: 12px; color: var(--warn); background: var(--warn-dim); border: 1px solid rgba(251,191,36,.28); border-radius: var(--r-sm); padding: 8px 10px; }
.pat-footer { display: flex; gap: 10px; justify-content: flex-end; }
/* "Manage library →" is navigation: an anchor in the ghost-button shape so it
   shares the action row's height, coloured as a link. */
a.ssa-manage-link { color: var(--accent); text-decoration: none; }
a.ssa-manage-link:hover { color: var(--accent); text-decoration: underline; background: transparent; }
@media (max-width: 640px) { .pat-wizard { grid-template-columns: 1fr; } }

/* (The 761–1099 px "tablet gap" rule that narrowed the heat slider inside the
   old floating Map-controls popover is gone with the popover: the toolbar row
   above the canvas wraps instead, and the label stays on one line.) */
.ssa-canvas-toolbar label.ssa-heat-opacity { white-space: nowrap; }

/* ── Coordinate frames (js/ssa/frames.js) ─────────────────────────────────────
   Frame badge: geo / plan p.N / unknown — the same chip wherever a node or
   zone is listed (config lists, node editor header, node strip, 3D node panel). */
.ssa-frame-badge {
  display: inline-block; font-family: var(--font-mono); font-size: 10px; line-height: 1.5;
  padding: 1px 6px; border-radius: 99px; white-space: nowrap; letter-spacing: .03em;
  margin-left: 6px; vertical-align: middle;
  background: color-mix(in srgb, var(--text-2) 14%, transparent); color: var(--text-2);
}
.ssa-frame-badge.is-geo     { background: color-mix(in srgb, var(--accent) 16%, transparent); color: var(--accent); }
.ssa-frame-badge.is-plan    { background: color-mix(in srgb, var(--ok) 16%, transparent); color: var(--ok); }
.ssa-frame-badge.is-unknown { background: color-mix(in srgb, var(--warn) 14%, transparent); color: var(--warn); }
.ssa-list-row .ssa-frame-badge { flex: 0 0 auto; }   /* .ssa-list-row span would stretch it */
.ssa-health-frame { margin: 1px 0 3px; }
.ssa-health-frame .ssa-frame-badge { margin-left: 0; }
/* Frame banner: rows the scene cannot position (different frame) are named here. */
.ssa-frame-banner {
  display: flex; flex-direction: column; gap: 3px; margin: 0 0 8px; padding: 8px 12px;
  border-radius: 8px; border: 1px solid color-mix(in srgb, var(--warn) 45%, transparent);
  background: color-mix(in srgb, var(--warn) 10%, transparent);
  color: var(--text-1); font-size: 12.5px; line-height: 1.4;
}
.ssa-frame-banner[hidden] { display: none; }
.ssa-frame-banner-names { color: var(--text-2); font-size: 11.5px; }
.ssa3d-page .ssa-frame-banner { margin: 8px 16px 0; }
/* Underlay hint (replaces the select when no plan can be drawn under the rows). */
.ssa-underlay-hint { font-size: 11.5px; color: var(--text-2); line-height: 1.35; max-width: 190px; }
/* "on <floor>" beside a listed row (frames.js floorNote): a dotted link when it
   switches floor (node strip / 3D node panel), plain text in the config lists;
   amber "not on a floor" for an orphan. */
.ssa-floor-note {
  font-family: var(--font-mono); font-size: 10px; color: var(--text-2); white-space: nowrap;
  margin-left: 6px; background: none; border: none; padding: 0; line-height: 1.5;
}
button.ssa-floor-note { cursor: pointer; color: var(--accent); text-decoration: underline dotted; }
button.ssa-floor-note:hover { color: var(--text-1); }
.ssa-floor-note.is-orphan { color: var(--warn); }
.ssa-floor-note.is-current { opacity: .7; }
.ssa-health-frame .ssa-floor-note { display: inline-block; margin-left: 4px; }
.ssa-list-row .ssa-floor-note { flex: 0 0 auto; }
/* Node strip: a node on another floor (or no floor) is listed but not drawn. */
.ssa-health-cell.is-offframe { opacity: .72; border-style: dashed; }
/* 3D node panel: off-frame nodes are listed, greyed, never placed. */
.ssa3d-node-toggle.is-offframe { opacity: .6; cursor: default; }
/* Cross-frame rows (js/ssa/frame-convert.js): drawn on this floor THROUGH the
   sheet's georef — listed as drawn, "via <floor>", read-only here. */
.ssa-floor-note.is-converted { color: var(--ok); }
.ssa-health-cell.is-converted { border-style: dotted; }
.ssa3d-node-toggle.is-converted { opacity: .85; }
/* The banner slot as a HINT (a sheet without a georef while geo-placed rows
   exist): calmer than the orphan warning. */
.ssa-frame-banner.is-hint {
  border-color: color-mix(in srgb, var(--accent) 40%, transparent);
  background: color-mix(in srgb, var(--accent) 8%, transparent);
}
/* Config panel: origin follows the event pin until the first node is placed. */
.ssa-origin-note { font-size: 12px; margin: 0 0 10px; }
/* Node editor header: name · frame badge · "on <floor>" · position — ONE baseline. */
.ssa-node-editor-head { display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap; margin-bottom: 6px; }
.ssa-node-editor-name { font-weight: 600; font-size: 14px; color: var(--text-1); }
.ssa-node-editor-pos { font-variant-numeric: tabular-nums; }

/* ── Provenance / staleness / origin (geo-s3) ────────────────────────────────
   Placement section: the "where these metres came from" line sits on the same
   baseline as the "Use device GPS" un-latch button (.ssa-actions does the row). */
.ssa-placement-source { margin-bottom: 8px; }

/* Zones card: the zone-sync staleness chip beside "Sync zones from map". A
   STATUS, not a control — muted by default, amber once the map has moved on. */
.ssa-sync-chip {
  font-size: 11.5px; line-height: 1.5; padding: 2px 8px; border-radius: 99px;
  white-space: nowrap; background: color-mix(in srgb, var(--text-2) 12%, transparent);
  color: var(--text-2);
}
.ssa-sync-chip.is-stale {
  background: color-mix(in srgb, var(--warn) 14%, transparent); color: var(--warn);
}
.ssa-sync-chip[hidden] { display: none; }

/* Origin card: the snapshot's coordinates, then the divergence notice. */
.ssa-origin-coords { font-family: var(--font-mono); font-size: 12.5px; color: var(--text-1); }
.ssa-origin-diverged {
  display: flex; flex-direction: column; gap: 8px; align-items: flex-start;
  margin-top: 10px; padding: 8px 12px; border-radius: 8px; font-size: 12.5px;
  line-height: 1.4; color: var(--text-1);
  border: 1px solid color-mix(in srgb, var(--warn) 45%, transparent);
  background: color-mix(in srgb, var(--warn) 10%, transparent);
}
.ssa-origin-diverged .ssa-actions { margin-top: 0; }

/* Settings › Schedule & venue: the same divergence, said after a location save. */
.venue-origin-notice {
  margin-top: 8px; padding: 8px 12px; border-radius: 8px; font-size: 12.5px;
  line-height: 1.4; color: var(--text-1);
  border: 1px solid color-mix(in srgb, var(--warn) 45%, transparent);
  background: color-mix(in srgb, var(--warn) 10%, transparent);
}
.venue-origin-notice[hidden] { display: none; }

/* ── Venue setup checklist (js/views/spatial-setup.js) ───────────────────────
   A derived list, not a wizard: one row per step, exactly one expanded (the
   next actionable one), done rows collapsed to one line that QUOTES the value
   they achieved, blocked rows visibly inert with their reason. Nothing here is
   a gate — the styling has to read as a signpost, not a lock. */
.setup-list { list-style: none; margin: 12px 0 0; padding: 0; display: flex; flex-direction: column; }
.setup-row {
  display: flex; gap: 10px; align-items: flex-start;
  padding: 9px 2px; border-top: 1px solid var(--border-subtle);
}
.setup-row:first-child { border-top: 0; }
.setup-mark {
  flex: none; width: 18px; text-align: center; font-size: 13px; line-height: 1.5;
  font-family: var(--font-mono); color: var(--text-faint);
}
.setup-row.is-done .setup-mark { color: var(--ok); }
.setup-row.is-next .setup-mark { color: var(--accent); }
.setup-row.is-blocked .setup-mark { color: var(--text-faint); }
.setup-main { flex: 1 1 auto; min-width: 0; }
.setup-head { display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 8px; }
.setup-title { font-size: 13px; font-weight: 600; color: var(--text-1); }
.setup-row.is-blocked .setup-title,
.setup-row.is-optional .setup-title { color: var(--text-2); font-weight: 500; }
.setup-detail { font-size: 12px; color: var(--text-2); min-width: 0; overflow-wrap: anywhere; }
.setup-row.is-done .setup-detail { color: var(--text-muted); }
.setup-tag {
  font-size: 10.5px; line-height: 1.6; padding: 0 7px; border-radius: var(--r-pill);
  background: color-mix(in srgb, var(--text-2) 12%, transparent); color: var(--text-muted);
}
.setup-inline-link { font-size: 11.5px; color: var(--accent); text-decoration: none; }
.setup-inline-link:hover { text-decoration: underline; }
.setup-explain { margin: 6px 0 0; font-size: 12px; line-height: 1.5; color: var(--text-2); max-width: 62ch; }
.setup-blocked { margin: 4px 0 0; font-size: 11.5px; line-height: 1.45; color: var(--text-muted); }
.setup-row.is-blocked { opacity: .72; }
.setup-action { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin-top: 9px; }
.setup-note { font-size: 11.5px; color: var(--text-muted); }
.setup-row.is-next { background: color-mix(in srgb, var(--accent) 6%, transparent); border-radius: var(--r-sm); }
.setup-complete {
  display: flex; flex-wrap: wrap; align-items: center; gap: 10px; margin-top: 14px;
  font-size: 12.5px; color: var(--text-2);
}

/* The one-line strip above the 2D / 3D / Configure views while setup is
   incomplete. A signpost: quiet, one row high, never covering the view. */
.ssa-setup-strip {
  display: flex; flex-wrap: wrap; align-items: center; gap: 10px;
  padding: 7px 12px; margin-bottom: 8px; border-radius: var(--r-sm);
  font-size: 12px; color: var(--text-2);
  border: 1px solid color-mix(in srgb, var(--accent) 35%, transparent);
  background: color-mix(in srgb, var(--accent) 8%, transparent);
}
.setup-strip-count { font-variant-numeric: tabular-nums; color: var(--text-1); font-weight: 600; }
.setup-strip-dot { opacity: .5; }
.setup-strip-next { flex: 1 1 auto; min-width: 0; }
.ssa-setup-strip .setup-strip-go { flex: none; text-decoration: none; }
