/* ============================================================
   views-monitor.css — screen-specific styling for the Monitor.
   Builds on the global :root tokens (tokens.css) + components.css.
   The monitor keeps a BLACKER theme (var(--mon-0)/--mon-1/--mon-2).
   All class names are prefixed `m-` (or scoped under .monitor-view)
   to avoid colliding with the shared component classes.
   ============================================================ */

.monitor-view{
  display:flex;flex-direction:column;min-height:100dvh;height:100%;
  background:var(--bg-0);color:var(--text-1);
}

/* ── A. Sub-header ─────────────────────────────────────────── */
/* Compact single row: back · status dot · title + LIVE · tabs · icon cluster.
   (Venue·region meta line was removed; padding trimmed to suit one line.)
   Sticky: the event header (tabs + icon cluster) stays pinned to the top of
   the viewport when the page scrolls (long detections list, spatial config,
   embedded usage…). The intended internal scroller (.m-body) never engages
   because the %-height chain above it isn't definite, so the DOCUMENT scrolls
   — sticky covers both cases. z-index 100: above canvases, sticky table heads
   (z 1–5), map-area overlays (#map-canvas is isolated) and the settings
   backdrop (z 60 — the header intentionally stays usable above it), below
   popovers (800), modals (900+) and toasts (1000). */
.m-subhdr{
  display:flex;align-items:center;gap:var(--s4);
  /* Safe-area aware (immersive views have no global topbar, so this sticky
     header is the element that must clear the iPhone status bar / dynamic
     island in standalone mode and the landscape notch). 0px off-device. */
  padding:calc(5px + var(--safe-top)) calc(16px + var(--safe-right)) 5px calc(16px + var(--safe-left));
  background:var(--bg-1);border-bottom:1px solid var(--border-subtle);
  flex-wrap:wrap;
  position:sticky;top:0;z-index:100;
}
.m-subhdr .m-evtitle{
  font-size:14px;font-weight:600;letter-spacing:-.2px;color:var(--text-1);line-height:1.2;
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:42vw;
  text-decoration:none;
}
/* The event title is the (only) link back to the events list — subtle until hover. */
a.m-evtitle:hover{text-decoration:underline;color:var(--text-0,#fff)}
/* compact controls so the bar stays a short single row */
.m-subhdr .segmented{padding:0;align-items:center}
.m-subhdr .seg-btn,.m-subhdr .m-tabghost{padding:0 10px;height:20px;font-size:11.5px}
.m-subhdr .seg-count{padding:0 6px}
.m-subhdr .spacer{margin-left:auto}
.m-iconcluster{display:flex;align-items:center;gap:4px;flex-wrap:wrap}
.m-iconbtn{
  width:28px;height:22px;display:inline-flex;align-items:center;justify-content:center;
  border-radius:var(--r-sm);background:transparent;border:1px solid transparent;
  color:var(--text-2);cursor:pointer;transition:.13s;
}
.m-iconbtn svg{width:13px;height:13px}
.m-iconbtn.m-countbtn svg{width:15px;height:15px} /* Sources dish a touch larger for clarity */
.m-iconbtn:hover{background:var(--bg-raised);color:var(--text-1);border-color:var(--border)}
.m-iconbtn.on{color:var(--accent);border-color:var(--accent-soft);background:var(--accent-soft)}
.m-iconbtn:focus-visible{outline:none;box-shadow:var(--ring)}
/* Replay scrubber rail, window presets: five stacked buttons on desktop; where
   the touch floor below applies they give way to ONE select (replay.js builds
   both faces of the same setting). Five 44px buttons stacked in the rail took
   the portrait phone's scrubber down to its 80px min-height (MEASURED, 390×844
   coarse: 226 → 80px, document 844 → 912px tall) — the scrubber is the rail's
   main control. The .select pair out-specifies components.css .select. */
.replay-rail-presets{display:flex;flex-direction:column;gap:3px;align-items:center}
.select.replay-rail-select{display:none}
/* Touch + phone width: the header icons, the source-health dot, the Replay bar's
   Manage button and REC / SPEED boxes, and the scrubber rail's Live + window
   presets reach --tap-min. MEASURED (Chromium, 390×844 and 844×390, coarse
   pointer, mock event 11) before this block: .m-iconbtn 28×22, .m-aggdot 22×22,
   .replay-ctrl-btn 26px tall, REC / SPEED 24px, rail Live 38×22, rail presets
   36×18 — while Pause and Export in the same bar were already 44px
   (components.css .btn). min-* rather than width/height: monitor.js and
   replay.js inject their width/height rules at runtime, AFTER this file, and a
   min-* beats a width/height whatever the specificity. The cluster drops its
   4px gap so the eight 44px icons (354px) still share one row on a 390px phone
   (358px of header). The empty .spacer that right-aligns them then no longer
   fit on that row — 354 + the header's 10px phone gap — and wrapped onto a line
   of its own (+10px of header, MEASURED); a -10px right margin cancels the gap
   it brings, so it stays on the cluster's row and every width keeps the icon
   placement it had (at wider widths the gap is 16px and this only makes the
   invisible spacer 10px easier to fit). Narrower phones: the next block. The
   dot is a 44px box, not an invisible ::before: at the header's top edge a
   hit-expander would lose its upper half off screen. REC / SPEED: the native <select> overlays the box's
   padding box, so a 44px box gave a 42px control (MEASURED); the overlay spans
   the 1px border too — the rail's window select likewise. Cost, MEASURED: the
   sticky header grows 115→159px at 390×844 and 127→171px at 844×390 (two 44px
   rows instead of 22px ones). */
@media (pointer:coarse), (max-width:760px){
  .m-iconbtn,.m-aggdot{min-width:var(--tap-min);min-height:var(--tap-min)}
  .m-iconcluster{gap:0}
  .m-subhdr > .spacer{margin-right:-10px}
  .replay-toolbar .replay-ctrl-btn,.replay-toolbar .select{min-height:var(--tap-min)}
  .replay-toolbar .select .select-ctrl,.replay-rail-select .select-ctrl{inset:-1px;width:calc(100% + 2px);height:calc(100% + 2px)}
  .replay-rail-btn{min-width:var(--tap-min);min-height:var(--tap-min)}
  .replay-rail-presets{display:none}
  .select.replay-rail-select{display:inline-flex;width:100%;min-height:var(--tap-min);justify-content:center;padding:0 4px;gap:4px}
}
/* Phones narrower than ~386px (375px iPhone SE / mini, 360px Android). Two
   rows the 44px targets above would otherwise add, MEASURED at 375×667:
   header 115px on main, 233px with the targets alone.
   - Icons: the Sources button + seven 44px icons (354px) no longer fit the
     343px row and the cluster wrapped the last one onto a second 44px row.
     Below 760px the cluster is alone on its row (the tab strip above takes a
     full row), so it fills that row and the plain icons share it: each up to
     44px, narrower only when the row is short (≈42px at 375, ≈40px at 360),
     always 44px tall and edge to edge. Where 354px fits, every icon is 44px
     and right-aligned: the placement it had.
   - Title: the 44px source-health dot + 10px gap + the title row (291px on
     mock event 11) no longer fit 343px, so the title dropped to a row of its
     own (+30px). The dot's 44px box already brings 17px of blank either side
     of its 10px dot, so it gives the header gap back (-10px: the title sits
     17px from the visible dot, 16px beside the old 22px box); the title row takes
     what is left beside the dot, a longer name ellipsizing (.m-evtitle already
     clips with an ellipsis) instead of claiming a row. Adjacent-sibling
     selector: Usage's header reuses the .m-evtitle-row markup without the
     .m-aggdot and keeps its own layout. */
@media (max-width:760px){
  .m-subhdr > .m-aggdot{margin-right:-10px}
  .m-subhdr > .m-aggdot + .m-evtitle-row{flex:1 1 0}
  .m-subhdr > .m-iconcluster{flex:1 1 0;min-width:0;justify-content:flex-end}
  .m-iconcluster > .m-iconbtn:not(.m-countbtn){flex:1 1 0;min-width:32px;max-width:var(--tap-min)}
}
/* Usage tab inside the Live/Replay/Analyzers segmented control — native seg-btn
   styling; its count font is monospace to match the old chip's "0 / 251". */
.m-segusage .seg-count{font-family:var(--font-mono)}

/* ── Scroll body ───────────────────────────────────────────── */
.m-body{flex:1;min-height:0;overflow:auto;padding:8px 12px 20px;display:flex;flex-direction:column;gap:8px}

/* ── Live chrome fill chain (issue 1 — v1 parity) ──────────────
   The Live spectrum chart must grow to fill the viewport with the
   DETECTIONS/INTERFERENCE/UNIFIED/CLEAN-FREQ bar docked at the bottom,
   instead of the chart sitting at a fixed ~240px with dead space below.
   This mirrors v1's .chart-wrap-outer / .chart-wrap / .chart-panel /
   .chart-panel-canvas-wrap flex chain: every link is flex:1; min-height:0
   so the chart panel(s) absorb the available height and the pane bar +
   alerts stay pinned below. The panel only takes a fixed pixel height when
   the operator picks a >1× size (buildChartCanvas sets the inline height
   then); at 1× it flex-fills. */
.m-livechrome{flex:1;min-height:0;display:flex;flex-direction:column}
.m-chart-wrap-outer{flex:1;min-height:0;position:relative;display:flex}
.m-scrollhost{flex:1;min-height:0;display:flex;flex-direction:column;gap:8px;overflow-y:auto;padding-right:14px;scrollbar-width:none}
.m-scrollhost::-webkit-scrollbar{display:none;width:0;height:0}
/* Custom always-visible scroll indicator — overlays the right edge (absolute)
   so it never consumes flex width from the chart. Hidden by JS when no overflow. */
.m-scrolltrack{position:absolute;top:0;right:0;width:10px;height:100%;background:#0a0a0a;border-left:1px solid #1c1c1c;z-index:5;user-select:none;cursor:pointer}
.m-scrollthumb{position:absolute;left:1px;right:1px;background:#3a3a3a;border-radius:4px;min-height:28px}
.m-scrollthumb:hover{background:#6a6a6a}

/* ── Chart / detections splitter ───────────────────────────────
   The bottom detections section docks below the chart stack. Default COLLAPSED:
   .m-detsection is natural height (just the pane bar) and the chart stack
   flex-fills the whole live chrome, so the spectrum monitor takes the full
   viewport. When a pane is EXPANDED, JS adds .expanded and sets an explicit,
   clamped section height (monitor-split.js); the chart stack flex-fills the rest
   and the alerts body scrolls internally. The drag bar (.m-split) is shown only
   while expanded (and never in embedded monitors). */
.m-detsection{flex:0 0 auto;min-height:0;display:flex;flex-direction:column}
.m-detsection.expanded{overflow:hidden}
.m-detsection.expanded .m-alerts{flex:1;min-height:0;overflow-y:auto}
/* The active pane body must keep its natural content height so .m-alerts can
   scroll it. Without this it's a flex child of the column and shrinks to the
   clamped section height; combined with .m-subpanel's own overflow:hidden that
   CLIPPED the overflow instead of scrolling — tall panes (Clean Freq results +
   ranked alternatives, long Detections lists) had their tail unreachable. */
.m-detsection.expanded .m-alerts > .m-subpanel{flex:0 0 auto}
/* Unified overlay pane — the OPPOSITE of the list panes: the chart flex-fills
   the expanded section height (the splitter resizes it) instead of a
   natural-height body that clips/scrolls. Without this chain the chart wrap's
   flex:1 1 0 sits in plain block containers, the canvas keeps an arbitrary
   height, and .m-subpanel's overflow:hidden cuts the bottom of the bars off.
   monitor.js toggles .m-alerts--fill on for the unified pane only. */
.m-detsection.expanded .m-alerts.m-alerts--fill{display:flex;flex-direction:column;overflow:hidden}
.m-detsection.expanded .m-alerts.m-alerts--fill > .m-subpanel--fill{flex:1 1 0;min-height:0}
.m-subpanel--fill{display:flex;flex-direction:column}
.m-unified-legend{flex:0 0 auto;display:flex;align-items:center;gap:14px;flex-wrap:wrap;padding:6px 10px}
.m-unified-chart{flex:1 1 0;min-height:0;display:flex;flex-direction:column;position:relative}
.m-split{
  flex:none;height:8px;position:relative;cursor:row-resize;
  background:var(--bg-1);border-top:1px solid var(--border-subtle);border-bottom:1px solid var(--border-subtle);
  touch-action:none;user-select:none;
}
/* grab-handle pip — brightens on hover/drag */
.m-split::before{
  content:"";position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);
  width:36px;height:3px;border-radius:2px;background:var(--border-strong);transition:background .12s;
}
.m-split:hover::before,.m-split.dragging::before{background:var(--accent)}
.m-split:focus-visible{outline:none;box-shadow:var(--ring)}
/* Phone + touch: the 8px splitter bar is a hard drag target for a finger.
   Invisible ::after hit-slop widens the touch zone to ~24px without moving
   any pixels (layout and rendering unchanged everywhere). */
@media (pointer:coarse) and (max-width:760px){
  .m-split::after{content:"";position:absolute;left:0;right:0;top:-8px;bottom:-8px}
}

/* ── B. Chart panel ────────────────────────────────────────── */
.m-chartpanel{
  background:var(--mon-1);border:1px solid #161b22;border-radius:var(--r-sm);overflow:hidden;
  /* Flex-fill the scroll host so a single Live trace fills the viewport.
     A >1× panel switches to an explicit pixel height (set inline by
     buildChartCanvas) — .m-chartpanel.accented opts out of flex-grow so the
     fixed height wins and the stack scrolls, matching v1's applyPanelSizing. */
  flex:1 1 0;min-height:180px;display:flex;flex-direction:column;
}
/* A sized (>1×) panel uses its inline pixel height and does not stretch. */
.m-chartpanel.accented{flex:0 0 auto;min-height:0}

/* ── Live trim grips (issue 2) ─────────────────────────────────
   The draggable per-trace display-offset handle (DOM overlay; most styling is
   inline in trim-handles.js so the trace color drives the fill). These add the
   hover/drag affordances + reveal the dB readout. */
.trim-grip{box-shadow:0 1px 3px rgba(0,0,0,.5);transition:transform .08s,box-shadow .1s}
.trim-grip:hover{transform:scaleX(1.12)}
.trim-grip.dragging{box-shadow:0 0 0 2px rgba(255,255,255,.35),0 2px 6px rgba(0,0,0,.6);z-index:7}
.trim-grip:hover .trim-grip-readout,
.trim-grip.dragging .trim-grip-readout,
.trim-grip:focus-visible .trim-grip-readout{opacity:1 !important}
.trim-grip:focus-visible{outline:none;box-shadow:0 0 0 2px var(--accent)}
.m-panelhdr{
  display:flex;align-items:center;gap:6px;padding:3px 10px;flex-wrap:wrap;row-gap:4px;min-height:26px;
  border-bottom:1px solid #14181f;
}
/* Every interactive control in the chart-panel header shares ONE height +
   font so the bar reads as a single, evenly-sized row (matches the original
   app's uniform ~one-line header). Tune --m-ctrl-h to rescale the whole bar. */
.m-panelhdr{--m-ctrl-h:20px}
.m-panelhdr .m-cyc,
.m-panelhdr .m-panelcyc,
.m-panelhdr .m-jump,
.m-panelhdr .m-histtoggle,
.m-panelhdr .m-star,
.m-panelhdr .btn{
  /* min-height:0 is LOAD-BEARING. components.css gives every .btn
     `min-height:33px`, and min-height BEATS height — so this rule asked for 20px
     and the browser rendered 33px next to 20px chips. Measured on prod
     2026-09-19: computed height 33px, min-height 33px, --m-ctrl-h 20px. Same
     trap as the map sidebar's Place button and .form-input--grow. */
  height:var(--m-ctrl-h);min-height:0;box-sizing:border-box;flex:none;
}
/* View ▾ is a full-size .btn (.btn-sm is never defined) — pull its font + side
   padding down to the bar's scale so it sits level with the toggle controls
   instead of towering over them. Height comes from the shared rule above. */
.m-panelhdr .btn{font-size:11px;padding:0 12px;gap:5px}
.m-panelhdr.accented{border-left:3px solid var(--accent)}
.m-disclosure{
  background:none;border:none;color:var(--text-muted);cursor:pointer;font-size:12px;
  padding:2px 3px;border-radius:var(--r-sm);line-height:1;
}
.m-disclosure:hover{color:var(--text-1)}
.m-srcdot{width:8px;height:8px;border-radius:50%;background:var(--live);box-shadow:0 0 8px var(--live);flex:none}
.m-srcname{font-family:var(--font-mono);font-weight:600;font-size:12.5px;white-space:nowrap;color:var(--text-1)}
/* Trace not currently delivering (status stale/down/paused) → the name itself
   dims; detail moves to the name's title tooltip. Replaces the old inline
   .m-feedstatus "feed dropped — reconnecting…" text (redundant next to the
   aggregate status dot + offline toasts). */
.m-srcname.is-offline{color:var(--text-faint)}
/* Zone chip — the pane's zone(s); markers follow their zone (marker-zones.js). */
.m-zonechip{font-family:var(--font-mono);font-size:10px;letter-spacing:.04em;text-transform:uppercase;color:var(--text-muted);border:1px solid var(--border);border-radius:var(--r-sm);padding:1px 6px;white-space:nowrap;flex:none}
.m-zonechip.is-nozone{color:var(--text-faint);border-style:dashed}
.m-srcstate{font-family:var(--font-mono);font-size:9.5px;letter-spacing:.05em;color:var(--live);text-transform:uppercase;flex:none}
.m-cyc{
  font-family:var(--font-mono);font-size:11px;font-weight:600;color:#bfdbfe;
  background:var(--accent-soft);border:1px solid var(--accent-soft);border-radius:var(--r-sm);
  padding:0 10px;cursor:pointer;display:inline-flex;align-items:center;gap:5px;flex:none;transition:.13s;line-height:1;
}
.m-cyc:hover{border-color:var(--accent);color:#fff}
.m-cyc:focus-visible{outline:none;box-shadow:var(--ring)}
.m-cyc.def{color:var(--text-2);background:var(--bg-input);border-color:var(--border)}
.m-panelhdr .spacer{margin-left:auto}
.m-jump{
  background:var(--bg-input);border:1px solid var(--border);color:var(--text-2);
  border-radius:var(--r-sm);padding:0 10px;font-size:11px;width:104px;font-family:var(--font-mono);line-height:1;
}
.m-jump::placeholder{color:var(--text-faint)}
.m-jump:focus-visible{outline:none;border-color:var(--accent);box-shadow:none}
.m-hdrgrp{display:flex;gap:6px;align-items:center;padding-left:10px;border-left:1px solid var(--border-subtle)}
.m-star{
  width:28px;height:28px;display:inline-flex;align-items:center;justify-content:center;border-radius:var(--r-sm);
  background:transparent;border:1px solid transparent;color:var(--text-2);cursor:pointer;font-size:14px;line-height:1;
}
.m-star:hover{background:var(--bg-raised);color:var(--text-1);border-color:var(--border)}
.m-star.on{color:var(--warn);border-color:var(--warn-dim)}
.m-star:focus-visible{outline:none;box-shadow:var(--ring)}

/* chart canvas — edge-to-edge, no inset gap. Flex-fills the panel below the
   header so the chart grows to the panel's height (issue 1). When the panel is
   sized (>1×) buildChartCanvas sets an explicit inline height that wins. */
/* Waterfall stack — display:contents keeps .m-chartwrap's layout relationship
   with its host untouched; the strip is just an extra block above it. */
.m-wfstack{display:contents}
/* Opt-in waterfall strip (View ▾ → "Waterfall history"): newest sweep at the
   top, scrolling down. The canvas is absolutely inset to the chart's plot area
   (left/right set per frame from chartArea) so columns line up with the
   spectrum's frequency axis; the bitmap is native-resolution, CSS-stretched. */
.m-wfpane{position:relative;height:96px;background:var(--mon-0);border-top:1px solid #10141b;overflow:hidden}
/* canvas is a replaced element — width/height must be EXPLICIT (auto resolves
   to the intrinsic bitmap size, ignoring a left/right or top/bottom pair).
   setInsetsFromChart overrides left/width inline to track the chart plot area. */
.m-wfpane canvas{position:absolute;top:0;left:0;width:100%;height:100%;cursor:crosshair}
/* Frequency cursor line — a continuation of the chart's tap crosshair up through
   the waterfall, frequency-aligned to the strip's full-span mapping. 1px dashed
   [4,4] to match the chart line; positioned by setCursor (left set inline).
   The .preview variant (hover, not yet locked) is dimmer. pointer-events:none so
   the strip below still receives clicks. */
.m-wf-cursor{position:absolute;top:0;height:100%;width:1px;pointer-events:none;z-index:3;
  background:repeating-linear-gradient(to bottom,rgba(255,255,255,.6) 0 4px,transparent 4px 8px)}
.m-wf-cursor.preview{opacity:.5}
.m-chartwrap{position:relative;background:var(--mon-0);border-top:1px solid #10141b;margin:0;padding:0;flex:1 1 0;min-height:0}
.m-chartwrap svg{display:block;width:100%;height:100%}
.m-chartwrap .m-chart-svg{cursor:crosshair}
/* Ported Chart.js canvas — fill the wrap (Chart.js sizes it via ResizeObserver;
   display:block avoids the inline-canvas baseline gap). crosshair matches the
   old SVG affordance for the tap-readout. */
.m-chartwrap canvas{display:block;cursor:crosshair}
/* Detection-threshold drag handles (chart/threshold-handles.js): a 24px hit
   box (touch-action:none so a touch drag never scrolls the page) around a small
   tab in the trace's colour, at the left edge of the plot area. */
.thr-handle-layer{position:absolute;inset:0;pointer-events:none;z-index:7}
.thr-handle{position:absolute;width:24px;height:24px;display:flex;align-items:center;justify-content:flex-start;pointer-events:auto;cursor:ns-resize;touch-action:none;user-select:none;-webkit-user-select:none;-webkit-tap-highlight-color:transparent;border-radius:4px;outline:none}
/* clip-path would cut a box-shadow off, so the dark edge that keeps the tab
   visible on any trace colour is a drop-shadow filter (drawn after clipping). */
.thr-handle-vis{display:block;width:14px;height:10px;margin-left:1px;background:var(--thr-color,#ffc800);clip-path:polygon(0 0,62% 0,100% 50%,62% 100%,0 100%)}
.thr-handle{filter:drop-shadow(0 0 1px rgba(0,0,0,.9))}
.thr-handle .thr-handle-vis{opacity:.92;transition:transform .08s}
.thr-handle:hover .thr-handle-vis,.thr-handle.dragging .thr-handle-vis{opacity:1;transform:scale(1.2)}
.thr-handle:focus-visible{outline:2px solid var(--thr-color,#ffc800);outline-offset:1px}
@media (forced-colors:active){
  .thr-handle-vis{background:CanvasText;forced-color-adjust:none}
  .thr-handle:focus-visible{outline:2px solid Highlight}
}
.thr-handle.off-scale .thr-handle-vis{opacity:.55}
.thr-handle.saving{cursor:progress}
.thr-handle.saving .thr-handle-vis{opacity:.6}
.thr-handle-label{position:absolute;display:none;white-space:nowrap;font:11px ui-monospace,SFMono-Regular,Menlo,monospace;padding:1px 6px;border-radius:4px;border:1px solid currentColor;background:rgba(14,17,22,.92);pointer-events:none}
.thr-handle-label.on{display:block}
.m-tapcallout{
  position:absolute;z-index:5;transform:translate(-50%,-100%);
  background:var(--bg-raised);border:1px solid var(--border-strong);border-radius:var(--r-sm);
  padding:4px 8px;font-family:var(--font-mono);font-size:10.5px;color:var(--text-1);white-space:nowrap;
  box-shadow:0 6px 18px rgba(0,0,0,.55);pointer-events:none;
}
.m-tapcallout .f{color:var(--live)}
.m-tapcallout .d{color:var(--warn)}

/* Unified tap readout (chart/tap-readout.js) — the card a tap or click anywhere
   on the chart opens: frequency, the level per visible trace, and a chip per
   marker covering that frequency. position:fixed, body-mounted and clamped into
   the chart's own box by clampReadoutBox, exactly as the popovers are clamped
   into the viewport — a card clipped at a panel edge is as unreadable as no card.
   Touch targets are 28px+ high: the operator asking for this works the chart
   with a finger. */
.m-tapreadout{
  position:fixed;z-index:60;min-width:172px;max-width:min(300px,calc(100vw - 16px));
  background:var(--bg-raised);border:1px solid var(--border-strong);border-radius:var(--r-sm);
  box-shadow:0 10px 28px rgba(0,0,0,.6);padding:6px 8px 7px;
  font-family:var(--font-mono);font-size:11px;color:var(--text-1);
}
.m-tro-head{display:flex;align-items:center;gap:8px;justify-content:space-between}
.m-tro-freq{color:var(--live);font-weight:600;font-size:12px;letter-spacing:.01em}
.m-tro-x{
  flex:0 0 auto;width:22px;height:22px;display:flex;align-items:center;justify-content:center;
  background:transparent;border:1px solid var(--border);border-radius:var(--r-sm);
  color:var(--text-3);font-size:11px;line-height:1;cursor:pointer;padding:0;
}
.m-tro-x:hover{color:var(--text-1);border-color:var(--border-strong)}
.m-tro-levels{display:flex;flex-direction:column;gap:1px;margin-top:5px}
.m-tro-row{display:flex;align-items:center;gap:6px;min-height:18px}
.m-tro-dot{flex:0 0 auto;width:7px;height:7px;border-radius:50%;background:var(--text-3)}
.m-tro-name{flex:1 1 auto;color:var(--text-2);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.m-tro-dbm{flex:0 0 auto;color:var(--warn);font-variant-numeric:tabular-nums}
/* Unmeasured reads neutral, never as a number the operator could act on. */
.m-tro-dbm.unmeasured{color:var(--text-muted)}
.m-tro-empty{color:var(--text-muted)}
.m-tro-chips{display:flex;flex-direction:column;gap:3px;margin-top:6px;padding-top:6px;border-top:1px solid var(--border)}
.m-tro-chip{
  display:flex;align-items:center;gap:6px;width:100%;min-height:28px;
  background:var(--bg-sunken,rgba(255,255,255,.04));border:1px solid var(--mk,var(--border));
  border-left:3px solid var(--mk,var(--border-strong));border-radius:var(--r-sm);
  padding:4px 6px;font-family:inherit;font-size:10.5px;color:var(--text-1);
  cursor:pointer;text-align:left;
}
.m-tro-chip:hover{background:rgba(255,255,255,.08)}
.m-tro-chip-text{flex:1 1 auto;min-width:0;display:flex;flex-direction:column;gap:1px}
.m-tro-chip-name{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
/* Same-name chips: the short line that tells them apart (location / time slot / ch #). */
.m-tro-chip-sub{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--text-3);font-size:10px}
.m-tro-chip-go{flex:0 0 auto;color:var(--text-3)}
/* Outside the pane's active time slot — listed after the in-slot ones, dimmed like its band. */
.m-tro-chip.dimmed{opacity:.55}
/* hint row removed — the text lives behind the panel header's "?" (.m-charthelp) */
.m-chart-hint{display:none}

/* View ▾ popover panel */
.m-viewmenu{display:flex;flex-direction:column;gap:2px;min-width:248px;font-size:12px}
/* The View ▾ card is wider than the default .popover cap (components.css,
   min(320px, 100vw - 16px)): at 320px the Time slots rows cut the name and
   showed only "Mon 14 …" of the window (owner 2026-09-21). 440px holds a long
   slot name and a two-date window; phones keep a 16px gutter each side.
   A fixed WIDTH, not only a cap: openPopover pins the card's left edge to the
   View button, and a shrink-to-fit card near the right edge would size itself
   to the room left of the viewport edge before being slid back into view. */
.popover:has(.m-viewmenu){width:min(440px,calc(100vw - 32px));max-width:min(440px,calc(100vw - 32px))}
.m-viewmenu .grp-h{
  font-family:var(--font-mono);font-size:9px;letter-spacing:.1em;text-transform:uppercase;
  color:var(--text-muted);margin:8px 2px 4px;
}
.m-viewmenu .grp-h:first-child{margin-top:0}
.m-checkrow{
  display:flex;align-items:center;gap:9px;padding:5px 6px;border-radius:var(--r-sm);cursor:pointer;color:var(--text-1);
}
.m-checkrow:hover{background:var(--bg-raised)}
.m-checkrow input{width:14px;height:14px;accent-color:var(--accent);cursor:pointer;flex:none}
.m-checkrow .sub{margin-left:auto;font-family:var(--font-mono);font-size:10px;color:var(--text-muted)}
.m-viewmenu .divider{height:1px;background:var(--border-subtle);margin:7px 0}
.m-viewmenu .m-viewsearch{padding:2px 4px 6px}
.m-viewmenu .m-viewsearch .input{width:100%;min-height:28px;padding:4px 8px;font-size:12px}
/* Action rows ("Reset max hold" / "Reset my max hold", view-prefs-popup.js
   buildActionRow) HUG their icon + label. The old width:100% stretched the
   button across the whole 248px menu while the label filled about a third of
   it (owner report 2026-09-18). The button stays on its own line because
   buildActionRow wraps it in a plain block <div> — do not make that wrapper a
   flex row, or the button starts stretching again. Icon↔label spacing comes
   from `gap` ALONE: the text node carries no leading space (dropping it is
   part of this fix — with both, the gap doubles). */
.m-viewmenu .menu-btn{
  width:auto;display:inline-flex;align-items:center;gap:6px;
  text-align:left;background:var(--bg-input);border:1px solid var(--border);color:var(--text-2);
  border-radius:var(--r-sm);padding:6px 9px;font-size:11.5px;cursor:pointer;font-family:var(--font-ui);
}
.m-viewmenu .menu-btn:hover{border-color:var(--border-strong);color:var(--text-1)}
/* In-place disclosure (spec E §2, replacing the drill-in stack): the root list
   is never hidden — a section's body unfolds directly beneath its own row. See
   js/chart/view-prefs-popup.js. */
.m-viewmenu-body,.m-viewmenu-root,.m-viewbody{display:flex;flex-direction:column;gap:2px;min-width:0}
/* `display:flex` above out-specifies the UA `[hidden]` rule, so a folded body
   would otherwise still render (same trap as .select[hidden] in components.css). */
.m-viewbody[hidden]{display:none}
/* The unfolded body is indented and hung off a rule, so its rows read as
   BELONGING to the row above rather than as more root-level entries. */
.m-viewbody{margin:1px 0 6px 10px;padding-left:8px;border-left:1px solid var(--border-subtle)}
/* Root row: label (+ optional note) left, value summary right, › / ⌄ last. */
.m-viewrow{
  display:flex;align-items:center;gap:8px;width:100%;text-align:left;
  background:none;border:0;color:var(--text-1);font:inherit;
  padding:5px 6px;border-radius:var(--r-sm);cursor:pointer;
}
.m-viewrow:hover,.m-viewrow[aria-expanded="true"]{background:var(--bg-raised)}
.m-viewrow .m-viewrow-main{display:flex;flex-direction:column;gap:1px;flex:1;min-width:0}
.m-viewrow .note{font-size:10px;line-height:1.3;color:var(--text-muted)}
.m-viewrow .val{font-family:var(--font-mono);font-size:10px;color:var(--text-muted);white-space:nowrap;flex:none;max-width:45%;overflow:hidden;text-overflow:ellipsis}
.m-viewrow .chev{color:var(--text-faint);flex:none}
/* Quiet outside-marker count under the Slot row — text, not a boxed chip. */
.m-viewnote{font-size:10px;line-height:1.3;color:var(--text-muted);padding:0 6px 5px}
.m-viewnote[hidden]{display:none}
.m-viewhint{margin:0;padding:2px 6px 7px;font-size:10.5px;line-height:1.4;color:var(--text-muted)}
.m-viewbody .segmented{margin:0 6px 7px}
/* A Slot row whose slot was deleted server-side while the body was unfolded
   (time_slots_changed). The row is disabled rather than removed — pulling rows
   out from under the pointer would move everything below them. */
.m-viewbody .m-checkrow.is-stale{opacity:.4;cursor:not-allowed;text-decoration:line-through}
/* Time-slot rows (owner 2026-09-21, "text density is confusing"; revised
   2026-09-21 — every row must stay on ONE line, never wrapping the date
   under the name): the name (full name in the row tooltip), the compact
   window as quiet mono text (full text in the tooltip), a small dot when the
   slot is live now. A selected slot that belongs to other zones only is kept
   but muted.
   Name + window sit in ONE non-wrapping row (.m-slotrow-text): the name
   ellipsizes down to ~55% of the row before the date gives up any more of
   its own 45% share — neither ever drops to a second line (#1046 squeezed
   the name to "2…" with flex:none; #1047 wrapped the date under the name —
   both regressed the "one line" rule). Measured in Chrome, harness in the PR
   body. */
.m-checkrow.m-slotrow{min-width:0}
.m-slotrow .m-slotrow-text{flex:1 1 0;min-width:0;display:flex;flex-wrap:nowrap;white-space:nowrap;align-items:baseline;column-gap:10px}
.m-slotrow .m-slotrow-name{flex:1 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.m-slotrow .m-slotrow-when{flex:0 1 auto;min-width:0;max-width:45%;overflow:hidden;text-overflow:ellipsis;font-family:var(--font-mono);font-size:10px;color:var(--text-muted);white-space:nowrap}
.m-slotrow .m-slotrow-live{flex:none;width:6px;height:6px;border-radius:50%;background:var(--live)}
.m-slotrow.is-foreign .m-slotrow-name{color:var(--text-muted)}
/* Touch: the menu opens from a panel header that is reachable on a phone, so
   its rows need a 44px hit area. These are this file's own elements (no
   components.css change needed) — mirrors the .map-sidebar .source-row .btn
   precedent in styles/views-map.css. */
@media (pointer: coarse), (max-width: 760px) {
  /* .m-viewmenu .m-checkrow was missed on the first pass: MEASURED at 375px
     with pointer:coarse the four Display toggles rendered 40px against a
     44px --tap-min, while the section rows and buttons already met it.
     Scoped to the View menu so the other .m-checkrow users (the marker-info
     popup, the re-arm row) keep their current density. */
  .m-viewrow,.m-viewmenu .menu-btn,.m-viewmenu .m-checkrow{min-height:var(--tap-min)}
  /* Same rule for the trace picker's rows (spec I §1): the New / Edit panel
     dialog is reachable from the same phone-sized View ▾ menu, and its rows are
     checkboxes of exactly the same density. */
  .mp-body .mp-tracerow{min-height:var(--tap-min)}
}

/* The pane bar's own buttons sit beside `.select.compact` boxes (BAND / SCAN),
   which render ~27px. A bare .btn is 33px because components.css sets
   `min-height:33px`, so "Scan now" and "+ Add" towered over the boxes to their
   left (owner, 2026-09-19; measured on prod: selects 26.8px, buttons 33px).
   min-height:0 is load-bearing — min-height BEATS height, which is why simply
   setting a height here would change nothing. Same trap as the panel header's
   View button and the map sidebar's Place button. */
.m-panebar-right-inner .btn{
  min-height:0;height:27px;box-sizing:border-box;padding:0 11px;font-size:12px;
}

/* ── C. Bottom panels — single merged pane bar ─────────────────── */
/* One flex row: pane toggles interleaved with their inline sub-controls */
.m-panebar{
  display:flex;align-items:center;gap:2px;flex-wrap:wrap;
  padding:3px 6px;background:var(--bg-1);border-bottom:1px solid var(--border-subtle);
  min-height:32px;
}
/* Pane toggle buttons (▼/▶ LABEL count) — bolder/boxed */
.m-paneltoggle{
  display:inline-flex;align-items:center;gap:5px;background:var(--bg-card);border:1px solid var(--border-subtle);
  border-radius:var(--r-sm);padding:3px 9px;cursor:pointer;color:var(--text-2);font-size:11.5px;font-weight:600;
  transition:.13s;line-height:1;flex:none;white-space:nowrap;
}
.m-paneltoggle:hover{border-color:var(--border);color:var(--text-1)}
/* Open pane reads as the system's "active" state — accent border + soft accent
   wash, matching Chip.active / Select active elsewhere in the control standard. */
.m-paneltoggle.open{color:var(--text-1);border-color:var(--accent);background:var(--accent-soft)}
.m-paneltoggle .car{color:var(--text-muted);font-size:10px;width:10px}
.m-paneltoggle .cnt{
  font-family:var(--font-mono);font-size:10px;font-weight:600;background:var(--border);color:var(--text-2);
  border-radius:var(--r-pill);padding:1px 7px;
}
.m-paneltoggle.open .cnt{background:var(--accent-soft);color:var(--accent)}

/* Inline slot — sub-tabs/controls that immediately follow the open pane's toggle */
.m-panebar-inline{
  display:flex;align-items:center;gap:2px;flex-wrap:wrap;
  border-left:1px solid var(--border-subtle);padding-left:6px;margin-left:2px;
}
/* Right-side slot — auto-margin pushes it to far end */
.m-panebar-right{margin-left:auto;display:flex;align-items:center;gap:0}
.m-panebar-right-inner{display:flex;align-items:center;gap:4px;flex-wrap:wrap}

/* Interference scan-source / trace pickers were the bespoke `.m-iface-pill` native
   <select>s; the control-standard reskin (PR2) moved them onto the shared Select()
   primitive (`.select` in components.css), so the `.m-iface-pill` block was retired.
   Nothing else referenced the class. */

/* Sub-panel body — head bar lives in the pane bar now */
.m-subpanel{
  background:var(--mon-1);border:1px solid var(--border-subtle);border-radius:var(--r-md);overflow:hidden;
}
/* .m-subpanel-head kept for any future use but suppressed */
.m-subpanel-head{display:none}
.m-sort{
  display:inline-flex;align-items:center;gap:5px;font-family:var(--font-mono);font-size:11px;
  background:var(--bg-input);border:1px solid var(--border);color:var(--text-2);border-radius:var(--r-sm);
  padding:3px 8px;cursor:pointer;line-height:1;
}
.m-sort:hover{border-color:var(--border-strong);color:var(--text-1)}

/* detections table */
.m-dt-wrap{overflow-x:auto}
/* Sticky column header. .m-alerts is the list's real scroller; any ancestor
   between it and the <thead> whose overflow is not visible becomes the sticky
   containing scrollport instead and the header scrolls away with the rows. So
   in the Detections pane .m-dt-wrap does not scroll (the wide table scrolls
   .m-alerts sideways instead), and the pane body grows to the table's width so
   its border still wraps it; it drops its top border so the header sits at the
   scroller's top edge before scrolling as well as after. z-index keeps the Recent group row and any
   positioned row content under the header; the bottom rule is a shadow because
   a collapsed border does not travel with a sticky cell. */
.m-subpanel--dt{overflow:visible;width:max-content;min-width:100%;box-sizing:border-box;
  border-top:0;border-top-left-radius:0;border-top-right-radius:0}
.m-subpanel--dt .m-dt-wrap{overflow:visible}
.m-dt thead th{position:sticky;top:0;z-index:3;background:var(--mon-2);box-shadow:inset 0 -1px 0 var(--border-subtle)}
.m-dt{width:100%;border-collapse:collapse;font-size:12.5px}
.m-dt th{
  font-family:var(--font-mono);font-size:12px;letter-spacing:.04em;text-transform:uppercase;color:var(--text-2);
  text-align:left;padding:8px 10px;background:var(--mon-2);border-bottom:1px solid var(--border-subtle);
  font-weight:600;white-space:nowrap;user-select:none;vertical-align:middle;
}
.m-dt th.sortable{cursor:pointer}
.m-dt th.sortable:hover{color:var(--text-1)}
.m-dt th .sar{color:var(--accent);margin-left:3px}
.m-dt th.num,.m-dt td.num{text-align:right}
/* Top-align row cells so the checkbox + line-1 values sit on the FREQUENCY line;
   a row's comment hangs below (line 2) only inside the freq cell. */
.m-dt td{padding:10px;border-bottom:1px solid #11151c;vertical-align:top;color:var(--text-1)}
.m-dt tbody tr.det-row{transition:background .1s}
.m-dt tbody tr.det-row:hover{background:#0e141d}
.m-dt tbody tr.det-row.sel{background:#0e1c33}
.m-dt-freq{font-family:var(--font-mono);font-weight:600;color:var(--text-1)}
/* The frequency button is the row's zoom + cursor action, but it renders 16px
   tall against a 44px --tap-min. Same treatment as the map sidebar's Place
   button (styles/views-map.css): a transparent ::before hit-expander spanning
   the button's width and --tap-min tall, rather than a 44px box, so the row
   keeps its density. It belongs to the button, so a tap on it activates it. */
@media (pointer:coarse), (max-width:760px){
  .m-dt .m-dt-freq{position:relative}
  .m-dt .m-dt-freq::before{
    content:'';position:absolute;left:0;right:0;top:50%;
    height:var(--tap-min);transform:translateY(-50%);
  }
}
.m-dt-tagrow{display:flex;gap:4px;margin-top:3px;flex-wrap:wrap}
.m-dt-tagrow.inline{margin-top:0}
.m-dt-tag{
  font-family:var(--font-mono);font-size:8.5px;letter-spacing:.06em;text-transform:uppercase;
  padding:1px 5px;border-radius:var(--r-sm);border:1px solid;line-height:1.5;
}
.m-dt-lv{font-family:var(--font-mono);color:var(--text-2);font-size:11.5px;white-space:nowrap}
.m-dt-lv .pk{color:var(--text-1)}
.m-dt-lv .drop{color:var(--danger)}
.m-dt-seen{font-family:var(--font-mono);font-size:11px;color:var(--text-muted);white-space:nowrap}
.m-dt-trace{
  font-family:var(--font-mono);font-size:11px;color:var(--text-1);background:none;border:1px solid transparent;
  border-radius:var(--r-sm);cursor:pointer;display:inline-flex;align-items:center;gap:6px;padding:3px 6px;margin:-3px -6px;
}
/* SOURCES cell label "<analyzer> · <trace>" (owner 2026-09-21): when space
   is short the ANALYZER part ellipsizes; the TRACE part (antenna) is flex:none
   and never clipped. The name block is capped so a long analyzer name can't
   blow the column out. */
.m-dt-trace{max-width:100%;min-width:0}
.m-dt-trace .lead,.m-dt-trace .tl,.m-dt-trace .more,.m-dt-trace .car{flex:none}
.m-dt-trace .src-nm{display:inline-flex;align-items:baseline;min-width:0;max-width:260px;white-space:nowrap;overflow:hidden}
.m-dt-trace .src-an{flex:0 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.m-dt-trace .src-sep{flex:none;color:var(--text-faint);white-space:pre}
.m-dt-trace .src-tr{flex:none;white-space:nowrap;overflow:visible;font-weight:600}
/* "Not shown" eye after the trace name (owner decision 2026-09-22): the
   detection's trace is not drawn on this viewer's screen; click shows it.
   flex:none like the trace part, so it is never the thing that gets clipped. */
.m-dt-trace .src-hidden{flex:none;display:inline-flex;align-items:center;align-self:center;margin-left:4px;padding:1px;border-radius:var(--r-sm);color:var(--warn);cursor:pointer}
.m-dt-trace .src-hidden:hover{color:var(--text-1);background:var(--bg-raised)}
.m-dt-trace .src-hidden:focus-visible{outline:none;box-shadow:var(--ring)}
.m-dt-trace .src-hidden svg{display:block;width:12px;height:12px}
.m-dt-trace .tl{color:var(--text-2)}
.m-dt-trace .more{
  color:var(--text-muted);font-size:10px;background:var(--mon-2);border:1px solid var(--border-subtle);
  border-radius:var(--r-pill);padding:0 5px;
}
.m-dt-trace .car{color:var(--text-faint)}
.m-dt-trace:hover{border-color:var(--border);color:#fff}
.m-dt-trace:hover .more{color:var(--accent);border-color:var(--accent-soft)}
.m-dt-trace:focus-visible{outline:none;box-shadow:var(--ring)}
.m-dt-note{display:inline-flex;align-items:center;gap:5px;font-size:11px;color:var(--warn);margin-top:4px}
/* Per-row actions are ALWAYS visible (not hover-gated) — required for touch/mobile,
   where there is no hover. Quiet by default (transparent icon buttons), brighten on
   hover/focus. */
.m-dt-actions{display:flex;gap:3px;justify-content:flex-end;opacity:1}
.m-rowbtn{
  width:26px;height:26px;display:inline-flex;align-items:center;justify-content:center;border-radius:var(--r-sm);
  background:transparent;border:1px solid transparent;color:var(--text-2);cursor:pointer;font-size:13px;line-height:0;
}
.m-rowbtn svg{display:block;width:14px;height:14px}
.m-rowbtn:hover{background:var(--bg-raised);color:var(--text-1);border-color:var(--border)}
.m-rowbtn:focus-visible{outline:none;box-shadow:var(--ring)}
.m-check{width:14px;height:14px;accent-color:var(--accent);cursor:pointer;margin:0}
/* checkbox column: identical left padding on header + body cells so the
   tri-state select-all sits directly above the per-row checkboxes. */
.m-dt th.m-cbcol,.m-dt td.m-cbcol{padding-left:12px;padding-right:6px}
.m-dt td.m-cbcol .m-check{display:block}
/* nudge the per-row checkbox to sit centered on the frequency line (line 1) */
.m-dt tbody td.m-cbcol .m-check{margin-top:4px}

/* dot status mini */
.m-statdot{width:7px;height:7px;border-radius:50%;display:inline-block;flex:none}
.m-statdot.active{background:var(--live);box-shadow:0 0 6px var(--live)}
.m-statdot.paused{background:var(--warn)}
.m-statdot.muted{background:var(--text-muted)}
.m-statdot.finished{background:var(--text-faint)}

/* bulk-action bar */
.m-bulk{
  display:flex;align-items:center;gap:12px;padding:9px 12px;background:var(--accent-soft);
  border-bottom:1px solid #24487e;font-size:12px;
}
.m-bulk .n{font-weight:600;color:#bfdbfe;font-family:var(--font-mono)}
.m-bulk .spacer{margin-left:auto}

/* triangulation popover — reads as a real panel */
/* The .popover card is capped at max-width:320px (components.css), but this
   content's widest line (the header) needs ~500px. Without lifting the cap the
   card stays 320px and the bars/hint/header spill past its right border. So:
   lift the cap only for popovers holding a .m-tri, and give .m-tri a fixed
   510px so the header fits on one line and the card hugs the content exactly. */
.popover:has(.m-tri){max-width:none}
/* Sources popover holds wide rows (name + tag/band chips + 4 dots + status word);
   the default .popover max-width:320px clipped it (its content wants 340–440px).
   Lift the cap so .m-sources' own width governs. */
.popover:has(.m-sources){max-width:none}
.m-tri{width:510px;max-width:510px;padding:2px}
.m-tri .tri-h{
  font-family:var(--font-mono);font-size:10.5px;letter-spacing:.08em;text-transform:uppercase;color:var(--text-muted);
  margin-bottom:13px;line-height:1.45;
}
.m-tri .tri-row{display:flex;align-items:center;gap:10px;margin-bottom:11px;font-family:var(--font-mono);font-size:12px}
/* name = analyzer → trace: the analyzer (b) ellipsizes first, the trace (.tr)
   is flex:none and always fully visible (owner 2026-09-21). The block grows
   from 158px up to 220px before the analyzer starts to ellipsize. */
.m-tri .tri-row .nm{display:inline-flex;align-items:center;gap:4px;flex:0 1 auto;width:auto;min-width:158px;max-width:220px;color:var(--text-1);white-space:nowrap;overflow:hidden}
.m-tri .tri-row .nm .lead{display:inline-flex;align-items:center;flex:none}
.m-tri .tri-row .nm .lead svg{display:block;width:14px;height:14px}
.m-tri .tri-row .nm b{flex:0 1 auto;min-width:0;font-weight:600;overflow:hidden;text-overflow:ellipsis}
.m-tri .tri-row .nm .arr{color:var(--text-faint);flex:none}
.m-tri .tri-row .nm .tr{flex:none;color:var(--text-muted);overflow:visible}
.m-tri .tri-row .lv{width:54px;color:var(--text-2);text-align:right;flex:none}
.m-tri .tri-row .pk{width:62px;color:var(--text-muted);text-align:right;flex:none}
.m-tri .tri-row .age{width:40px;color:var(--text-faint);text-align:right;flex:none}
.m-tri .tri-row .bar{flex:1;min-width:48px;height:8px;border-radius:4px;background:#16202f;overflow:hidden}
.m-tri .tri-row .bar i{display:block;height:100%;background:linear-gradient(90deg,var(--accent),#60a5fa)}
.m-tri .tri-row .recdot{flex:none;width:14px;height:14px;border-radius:50%;border:1px solid var(--border);background:transparent;cursor:pointer;padding:0;position:relative}
.m-tri .tri-row .recdot.on{background:#ef4444;border-color:#ef4444;box-shadow:0 0 0 2px color-mix(in srgb,#ef4444 28%,transparent)}
.m-tri .tri-row .recdot:not(.on){cursor:default;opacity:.5}
.m-tri .tri-row .recdot:focus-visible{outline:none;box-shadow:var(--ring)}
.m-tri .tri-recs{margin:-4px 0 11px 24px;display:flex;flex-direction:column;gap:3px}
.m-tri .tri-rec{font-family:var(--font-mono);font-size:10.5px;color:var(--text-muted);line-height:1.4}
.m-tri .tri-row.top .nm b{color:#6ee7b7}
.m-tri .tri-row.top .bar i{background:linear-gradient(90deg,#22c55e,#4ade80)}
.m-tri .tri-hint{font-family:var(--font-mono);font-size:11.5px;color:var(--text-2);margin-top:13px;line-height:1.5;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.m-tri .tri-hint b{color:#6ee7b7}
/* The hint wraps instead of clipping: when "near <analyzer> → <trace>" can't
   share the line, it moves to its own line and only the analyzer ellipsizes. */
.m-tri .tri-hint{display:flex;flex-wrap:wrap;align-items:baseline;min-width:0}
.m-tri .tri-hint .pre,.m-tri .tri-hint .post{flex:none;white-space:pre}
.m-tri .tri-hint .src-nm{display:inline-flex;min-width:0;max-width:100%;flex:0 1 auto;overflow:hidden}
.m-tri .tri-hint .src-an{flex:0 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.m-tri .tri-hint .src-sep{flex:none;white-space:pre}
.m-tri .tri-hint .src-tr{flex:none;white-space:nowrap}

/* interference sub-panel (lighter) — sub-tabs use the shared .m-statuschip
   styling (see renderStatusTabs / monitor.js injected CSS) to match Detections. */
.m-iface-list{display:flex;flex-direction:column}
.m-iface-row{
  display:flex;align-items:center;gap:12px;padding:10px 12px;border-bottom:1px solid #11151c;font-size:12px;
}
.m-iface-row:last-child{border-bottom:none}
.m-iface-row .freq{font-family:var(--font-mono);font-weight:600;color:var(--text-1);width:96px}
.m-iface-row .desc{color:var(--text-2);flex:1;min-width:0}
.m-iface-row .desc .sub{color:var(--text-muted);font-size:11px;font-family:var(--font-mono)}
.m-iface-row .acts{display:flex;gap:6px;flex-shrink:0}
.m-iface-empty{padding:18px 12px;text-align:center;color:var(--text-muted);font-size:12px}

/* responsive: mobile-first compatibility */
@media(max-width:760px){
  .m-subhdr .m-evtitle{max-width:100%;font-size:15px}
  .m-subhdr{gap:10px}
  /* chart-panel header: ~10 controls crammed into one row clip/overflow on
     phones. The bar already declares flex-wrap:wrap — make sure it wraps
     CLEANLY: drop the margin-left:auto spacer (it forces a full-width row and
     pushes the right-hand group off-screen) so controls flow onto extra rows
     instead of overflowing, and neutralize the inner group's left border. */
  .m-panelhdr{gap:5px 6px}
  .m-panelhdr .spacer{margin-left:0;flex-basis:0}
  .m-panelhdr .m-hdrgrp{padding-left:0;border-left:none}
  .m-jump{width:84px}
  .m-tri{width:510px;min-width:0;max-width:88vw}
  .m-sources{min-width:0;max-width:92vw}
  /* triangulation rows: let the name+stats wrap above the bar on phone widths so
     the analyzer→trace label and age column stay legible in a narrow card. */
  .m-tri .tri-row{flex-wrap:wrap;gap:8px 10px}
  .m-tri .tri-row .nm{width:auto;flex:1 1 100%;min-width:0;max-width:none}
  .m-tri .tri-row .bar{flex:1 1 100px;order:5}
  .m-tri .tri-row .recdot{order:6}
  /* Pane bar wraps naturally on narrow widths */
  .m-panebar{gap:3px;padding:3px 4px}
  .m-panebar-inline{border-left:none;padding-left:0;margin-left:0;flex-wrap:wrap}
  .m-panebar-right{margin-left:0;flex-wrap:wrap}
  .m-cf-target{min-width:120px}

  /* Replay detection feed: the 5-column grid (Time/Freq/Peak/Source/Band ≈
     406px) overflows phones. Reflow each detection into a compact stacked card
     with inline field labels. The grid is declared inline in replay.js, so the
     override needs !important. The column header row is hidden — each card now
     carries its own labels via data-label. */
  .rd-feed-head{display:none !important;}
  .rd-feed-row{
    display:flex !important;
    flex-direction:column;
    gap:2px;
    padding:8px 10px !important;
    grid-template-columns:none !important;
  }
  .rd-feed-row>span{
    white-space:normal !important;
    overflow:visible !important;
    text-overflow:clip !important;
    display:flex;
    justify-content:space-between;
    gap:10px;
    min-width:0;
  }
  .rd-feed-row>span[data-label]::before{
    content:attr(data-label);
    color:var(--text-muted);
    font-weight:600;
    font-size:0.66rem;
    text-transform:uppercase;
    letter-spacing:0.05em;
    flex:0 0 auto;
  }
  /* Source/Band can be long — let their value wrap & stay readable */
  .rd-feed-row>span{text-align:right;}
  .rd-feed-row>span[data-label]::before{text-align:left;}
}

/* ── Marker-info popup ───────────────────────────────────────────────────────
   Ported from the original SPA's callout: a floating card keyed to the clicked
   marker's color (--mk, set inline; falls back to the accent). The marker's own
   color rings the card (border + glow) so it reads as "this marker", on the
   app's dark surface, opened beside the clicked marker (see openMarkerInfoPopup).
   Values are clean read-only text that swap to an input on click. */
.popover.popover--marker{
  --mk:var(--accent);
  border:1.5px solid var(--mk);
  /* Deep marker-tinted card (the original's saturated callout) over a near-black
     base, so the marker color IS the card's identity — not a faint wash. */
  background:
    linear-gradient(180deg, color-mix(in srgb, var(--mk) 7%, transparent), transparent 70px),
    color-mix(in srgb, var(--mk) 15%, #070a09);
  /* Crisp marker ring + a soft colored glow so the card visibly pops off the chart. */
  box-shadow:
    0 0 0 1px var(--mk),
    0 0 22px -3px color-mix(in srgb, var(--mk) 60%, transparent),
    0 14px 40px rgba(0,0,0,.72);
  min-width:222px; max-width:min(344px, calc(100vw - 16px));
}
.m-markerinfo{ display:flex; flex-direction:column; gap:var(--s2); }
.m-markerinfo .mi-name{ font-weight:700; font-size:15px; line-height:1.2; letter-spacing:.1px; word-break:break-word; }
.popover--marker .m-markerinfo .mi-name{ text-shadow:0 0 16px color-mix(in srgb, var(--mk) 45%, transparent); }
.m-markerinfo .mi-meta{ color:var(--text-1); font-size:12px; margin-top:-4px; font-family:var(--font-mono); }
.m-markerinfo .mi-meta span+span{ margin-left:4px; }
.m-markerinfo .mi-sync{ display:flex; align-items:center; gap:6px; font-size:11px; color:var(--text-2); }
.m-markerinfo .mi-sync .dot{ width:7px; height:7px; border-radius:50%; background:var(--ok); flex:0 0 auto; }
.m-markerinfo .mi-sync .mini{ background:none; border:none; color:var(--accent); cursor:pointer; font:inherit; font-size:11px; display:inline-flex; align-items:center; gap:3px; padding:0; }
.m-markerinfo .mi-sync .mini:hover{ text-decoration:underline; }
.m-markerinfo .mi-cat{ margin:1px 0; }
/* Category chip — a vivid marker-colored pill (the original's filled chip) so
   the card's identity reads at a glance, instead of the muted grey band chip. */
.popover--marker .mi-cat .m-bandchip{
  font-size:10px; font-weight:700; letter-spacing:.05em; padding:2px 8px;
  color:var(--mk);
  background:color-mix(in srgb, var(--mk) 20%, transparent);
  border:1px solid color-mix(in srgb, var(--mk) 55%, transparent);
}
.m-markerinfo .mi-linkrow{ display:flex; align-items:center; gap:var(--s2); flex-wrap:wrap; }
.m-markerinfo .mi-extlink{ font-size:12px; color:var(--accent); text-decoration:none; display:inline-flex; align-items:center; gap:4px; }
.m-markerinfo .mi-extlink:hover{ text-decoration:underline; }
.m-markerinfo .mi-details{ border-top:1px solid var(--border-subtle); padding-top:var(--s2); margin-top:2px; }
.m-markerinfo .mi-details>summary{ cursor:pointer; font-size:12px; color:var(--text-2); list-style:none; user-select:none; }
.m-markerinfo .mi-details>summary::-webkit-details-marker{ display:none; }
.m-markerinfo .mi-details>summary::before{ content:'▸ '; color:var(--text-muted); }
.m-markerinfo .mi-details[open]>summary::before{ content:'▾ '; }
.m-markerinfo .mi-details>summary:focus-visible{ outline:1px solid var(--accent); outline-offset:2px; border-radius:3px; }
.m-markerinfo .mi-field{ display:grid; grid-template-columns:84px 1fr auto; align-items:center; gap:3px 10px; font-size:12px; min-height:22px; margin-top:4px; }
.m-markerinfo .mi-field .k{ color:var(--text-muted); font-family:var(--font-mono); font-size:10px; letter-spacing:.02em; }
.m-markerinfo .mi-field .v{ color:var(--text-1); word-break:break-word; }
.m-markerinfo .mi-field .v.mi-editable{ cursor:pointer; border-radius:4px; padding:1px 4px; margin:-1px -4px; }
.m-markerinfo .mi-field .v.mi-editable:hover{ background:color-mix(in srgb, var(--mk, #fff) 14%, transparent); }
.m-markerinfo .mi-field .v.mi-editable:focus-visible{ outline:1px solid var(--mk, var(--accent)); outline-offset:0; }
.m-markerinfo .mi-inp,
.m-markerinfo .mi-sel{ width:100%; padding:2px 6px; background:var(--bg-input); border:1px solid var(--border-strong); border-radius:5px; color:var(--text-1); font:inherit; font-size:12px; }
.m-markerinfo .mi-sel{ cursor:pointer; }
.m-markerinfo .mi-inp:focus,
.m-markerinfo .mi-sel:focus{ outline:none; border-color:var(--mk, var(--accent)); box-shadow:0 0 0 2px color-mix(in srgb, var(--mk, var(--accent)) 30%, transparent); }
.m-markerinfo .mi-resetdot{ background:none; border:none; color:transparent; cursor:pointer; font-size:16px; line-height:1; padding:0 2px; width:auto; }
.m-markerinfo .mi-resetdot.on{ color:var(--warn); }
.m-markerinfo .mi-resetdot:hover{ color:var(--text-1); }
/* 🗑 Delete marker (owner 2026-10-03): a bin the size of pin / eye / locate at
   the far RIGHT of the icon row, behind a hairline divider, so aiming at the
   other three can't hit it. Danger colour on hover/focus only. */
.m-markerinfo .mi-linkrow .mi-delgrp{ margin-left:auto; padding-left:var(--s2); border-left:1px solid var(--border-subtle); }
.m-markerinfo .mi-linkrow .mi-extlink{ flex:1 0 100%; }
.m-markerinfo .mi-delgrp .pw.mi-del:hover:not(:disabled),
.m-markerinfo .mi-delgrp .pw.mi-del:focus-visible{ opacity:1; color:var(--danger); border-color:color-mix(in srgb, var(--danger) 55%, transparent); background:color-mix(in srgb, var(--danger) 12%, transparent); box-shadow:none; }
.m-markerinfo .mi-delgrp .pw.mi-del:disabled{ opacity:.35; cursor:not-allowed; }

/* Interference band edge editor (owner feedback 2026-10-03) — in the band's
   inspector and in a confirmed band's red-marker popup. START / STOP with
   −/+ one-bin nudges (Shift = 10), the derived BW and Undo. */
.m-iface-edges{ display:flex; flex-direction:column; gap:4px; border-top:1px solid var(--border-subtle); padding-top:var(--s2); }
.m-iface-edge{ display:flex; align-items:center; gap:4px; font-size:12px; }
.m-iface-edge .lbl{ width:40px; flex:0 0 auto; color:var(--text-muted); font-family:var(--font-mono); font-size:10px; letter-spacing:.02em; }
.m-iface-edges .m-iface-edge input.m-num{ width:100px; padding:3px 6px; font-family:var(--font-mono); font-size:12px; text-align:right;
  background:var(--bg-input); color:var(--text-1); border:1px solid var(--border); border-radius:var(--r-sm);
  -moz-appearance:textfield; appearance:textfield; }
.m-iface-edge .m-num::-webkit-inner-spin-button,
.m-iface-edge .m-num::-webkit-outer-spin-button{ -webkit-appearance:none; margin:0; }
.m-iface-edge .m-num:focus{ outline:none; border-color:var(--accent); box-shadow:0 0 0 2px var(--accent-soft); }
.m-iface-edge .m-num:disabled{ opacity:.6; }
.m-iface-edge .unit{ color:var(--text-muted); font-size:11px; }
.m-iface-nudge{ width:24px; height:24px; flex:0 0 auto; display:inline-flex; align-items:center; justify-content:center; padding:0;
  background:var(--bg-raised); border:1px solid var(--border); border-radius:var(--r-sm);
  color:var(--text-1); font:600 14px/1 var(--font-mono); cursor:pointer; }
.m-iface-nudge:hover:not(:disabled){ border-color:var(--accent); color:var(--accent); }
.m-iface-nudge:focus-visible{ outline:none; box-shadow:var(--ring); }
.m-iface-nudge:disabled{ opacity:.4; cursor:not-allowed; }
.m-iface-edgefoot{ display:flex; align-items:center; justify-content:space-between; gap:var(--s2); font-size:11px; color:var(--text-2); }
.m-iface-undo{ display:inline-flex; align-items:center; gap:4px; background:none; border:1px solid var(--border); border-radius:var(--r-sm);
  color:var(--text-1); font-size:11px; padding:3px 8px; cursor:pointer; }
.m-iface-undo:hover:not(:disabled){ border-color:var(--accent); color:var(--accent); }
.m-iface-undo:focus-visible{ outline:none; box-shadow:var(--ring); }
.m-iface-undo:disabled{ opacity:.4; cursor:default; }
.m-iface-edgehint{ font-size:10.5px; color:var(--text-muted); line-height:1.35; }
@media (pointer:coarse), (max-width:760px){
  .m-iface-nudge,.m-iface-undo{ min-width:var(--tap-min); min-height:var(--tap-min); }
  /* 16px keeps iOS from zooming the page when the field takes focus. */
  .m-iface-edges .m-iface-edge input.m-num{ min-height:var(--tap-min); font-size:16px; width:112px; }
}

/* ── A1 signal-label chip + explain popover ─────────────────────────────── */
.siglabel{display:inline-flex;align-items:center;gap:6px;border-radius:999px;
  padding:2px 4px 2px 9px;font-size:11.5px;font-weight:500;border:1px solid var(--border);
  background:var(--bg-raised);cursor:default;line-height:1.5;white-space:nowrap}
.siglabel .sig-icn{font-family:var(--font-mono);opacity:.8}
/* Guard: never let a long classifier label blow out its host row — the compact
   class label is short, but ellipsise anything unexpectedly long (full detail is
   on the chip's title tooltip + the row's editable name field). */
.siglabel .sig-text{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:180px}
.siglabel .sig-conf{font-family:var(--font-mono);font-size:10px;font-weight:600;padding:0 3px}
.siglabel .sig-qm{width:16px;height:16px;border-radius:50%;border:1px solid var(--border);
  display:inline-flex;align-items:center;justify-content:center;font-size:10px;
  color:var(--text-muted);cursor:pointer;background:var(--bg-1);padding:0;line-height:1}
.siglabel .sig-qm:hover{color:var(--accent);border-color:var(--accent)}
.siglabel.high{border-color:rgba(52,211,153,.4);color:#bff3df;background:rgba(15,59,48,.5)}
.siglabel.high .sig-conf{color:var(--ok)}
.siglabel.med{border-color:rgba(251,191,36,.38);color:#f5e2b0;background:rgba(61,47,8,.5)}
.siglabel.med .sig-conf{color:var(--warn)}
.siglabel.low{border-color:var(--border);color:var(--text-2);background:var(--bg-raised)}
.siglabel.low .sig-conf{color:var(--text-muted)}
.siglabel.det{border-color:rgba(94,106,210,.4);color:#c7ccf2;background:var(--accent-soft)}
.siglabel.det .sig-conf{color:var(--accent)}
.popover.sig-pop{width:320px}
.sig-pop .sig-prov{font-size:10.5px;color:var(--text-muted);margin:0 0 10px;font-family:var(--font-mono)}
.sig-pop .sig-why{font-size:10px;text-transform:uppercase;letter-spacing:.06em;color:var(--text-muted);margin:0 0 6px}
.sig-pop .sig-reasons{list-style:none;margin:0 0 4px;padding:0;display:flex;flex-direction:column;gap:5px}
.sig-pop .sig-reasons li{display:flex;gap:8px;font-size:12px;color:var(--text-2);align-items:baseline}
.sig-pop .sig-k{font-family:var(--font-mono);font-size:10px;color:var(--accent);flex:none;width:92px}
.sig-pop .sig-actions{display:flex;gap:8px;border-top:1px solid var(--border);padding-top:10px;margin-top:8px}
.sig-pop .sig-btn{font-size:11.5px;border-radius:6px;padding:5px 10px;cursor:pointer;
  border:1px solid var(--accent);background:var(--accent-soft);color:#c7ccf2}
/* A1 operator relabel menu — a compact vertical family picker in a popover. */
.popover.m-sigcorrect-pop{width:180px;padding:4px}
.m-sigcorrect{display:flex;flex-direction:column;gap:1px}
.m-sigcorrect .m-menu-item{display:block;width:100%;text-align:left;font-size:12px;
  border:0;background:transparent;color:var(--text-1);padding:6px 9px;border-radius:5px;cursor:pointer}
.m-sigcorrect .m-menu-item:hover,
.m-sigcorrect .m-menu-item:focus-visible{background:var(--accent-soft);color:#c7ccf2;outline:none}

/* A3 Ask Your Spectrum */
.ask-root{display:grid;grid-template-columns:1.4fr 1fr;gap:16px;align-items:start}
@media(max-width:820px){.ask-root{grid-template-columns:1fr}}
.ask-card{background:var(--bg-card);border:1px solid var(--border);border-radius:10px;overflow:hidden}
.ask-card-hd{padding:12px 15px;border-bottom:1px solid var(--border-subtle);font-size:13px;font-weight:600}
.ask-card-bd{padding:14px 15px}
.ask-box{display:flex;gap:10px;align-items:center;background:var(--bg-input,var(--bg-2));border:1px solid var(--border-strong,var(--border));border-radius:22px;padding:6px 6px 6px 14px}
.ask-input{flex:1;background:transparent;border:0;color:var(--text-1);font-size:14px;outline:none}
.ask-send{flex:0 0 auto;width:30px;height:30px;border-radius:50%;background:var(--accent);color:#fff;border:0;cursor:pointer}
.ask-scope{font-size:11px;color:var(--text-muted);margin:6px 0 12px 4px}
.ask-answer{display:flex;gap:11px;align-items:flex-start;padding-top:6px}
.ask-spark{flex:0 0 auto;width:30px;height:30px;border-radius:8px;background:var(--accent-soft);display:grid;place-items:center;color:var(--accent)}
.ask-answer-body p{margin:0;font-size:14.5px;color:var(--text-1)}
.ask-stream{color:var(--text-2);font-size:12px;margin-top:8px;display:flex;gap:6px;align-items:center;flex-wrap:wrap}
.llm-badge{font-family:var(--font-mono);font-size:10px;color:var(--accent);border:1px solid var(--accent-soft);border-radius:4px;padding:1px 5px}
.ask-ungrounded{margin-top:8px;font-size:11.5px;color:var(--warn)}
.ask-prov{margin-top:14px;padding-top:13px;border-top:1px solid var(--border-subtle)}
.ask-prov-lbl{font-size:10.5px;text-transform:uppercase;letter-spacing:.05em;color:var(--text-muted);margin-bottom:8px}
.ask-srcs{display:flex;flex-wrap:wrap;gap:7px}
.ask-src{font-size:11.5px;padding:4px 9px;border-radius:16px;background:var(--bg-2);border:1px solid var(--border-subtle);color:var(--text-2)}
.ask-suggest{margin-top:14px}
.ask-suggest-lbl{font-size:11px;color:var(--text-muted);margin-bottom:7px}
.ask-qchips{display:flex;flex-wrap:wrap;gap:7px}
.ask-qchip{font-size:12px;color:var(--text-2);background:var(--bg-2);border:1px solid var(--border);border-radius:16px;padding:5px 11px;cursor:pointer}
.ask-fallback,.ask-refusal{margin-top:12px;font-size:12px;color:var(--text-muted);border:1px dashed var(--border);border-radius:7px;padding:8px 11px;background:var(--bg-1,var(--bg-2))}
.ask-loading{font-size:12px;color:var(--text-muted)}
.ask-sec{margin-bottom:13px;padding-bottom:13px;border-bottom:1px solid var(--border-subtle)}
.ask-sec-k{font-family:var(--font-mono);font-size:10px;letter-spacing:.05em;text-transform:uppercase;color:var(--accent);margin-bottom:5px}
.ask-sec-prose{width:100%;background:var(--bg-2);border:1px solid var(--border-subtle);border-radius:6px;color:var(--text-1);font:inherit;font-size:12.5px;padding:6px}
.ask-mini{margin-top:6px;display:flex;gap:6px;flex-wrap:wrap}
.ask-m{font-size:10px;color:var(--text-muted);background:var(--bg-2);border:1px solid var(--border-subtle);border-radius:10px;padding:1px 7px}
.ask-edit{margin-top:2px;font-size:10.5px;color:var(--text-faint,var(--text-muted))}

/* ─────────── Pin / Watch one-click icons + threshold popover ───────────
   The 📌 Pin / 👁 Watch (+ optional → Locate) icon group sits inline on each
   detection row and marker-info row (see pinWatchGroup() in monitor.js).
   Ported from v1 (spectrum-platform-pages) .pin-watch-icons/.pw-icon +
   .pin-watch-popover, re-themed onto the next-SPA design tokens (indigo
   accent instead of v1 amber). Without these rules the buttons rendered as
   near-invisible unstyled boxes — this is the "watch got dropped" regression.
   The icons are dim by default and brighten on hover; the SSA-locate toggle
   lights up (accent) when active (.is-on). */
.m-pinwatch{display:inline-flex;align-items:center;gap:2px;margin-left:var(--s2);vertical-align:middle}
.m-pinwatch .pw{
  display:inline-flex;align-items:center;justify-content:center;
  width:24px;height:22px;padding:0;
  background:transparent;border:1px solid transparent;border-radius:var(--r-sm);
  color:var(--text-muted);opacity:.7;cursor:pointer;transition:.13s;
}
.m-pinwatch .pw svg{width:13px;height:13px}
.m-pinwatch .pw:hover{opacity:1;color:var(--text-1);background:var(--bg-raised);border-color:var(--border)}
.m-pinwatch .pw:focus-visible{outline:none;opacity:1;color:var(--text-1);border-color:var(--accent);box-shadow:0 0 0 2px var(--accent-soft)}
/* SSA-locate toggle in the "on" state — persistently lit accent. */
.m-pinwatch .pw.is-on{opacity:1;color:var(--accent);border-color:var(--accent-soft);background:var(--accent-soft)}

/* Popover body (wrapped by the shared .popover card via openClampedPopover). */
.m-pw-pop{display:flex;flex-direction:column;gap:var(--s2);min-width:230px}
.m-pw-pop .h{font-weight:600;font-size:13px;color:var(--text-1);
  padding-bottom:var(--s2);border-bottom:1px solid var(--border-subtle)}
.m-pw-pop .rad{display:flex;flex-direction:column;gap:2px}
.m-pw-pop .radrow{display:flex;align-items:center;gap:var(--s2);
  padding:4px 5px;border-radius:var(--r-sm);cursor:pointer;color:var(--text-1);font-size:12.5px}
.m-pw-pop .radrow:hover{background:var(--bg-raised)}
.m-pw-pop .radrow input{width:14px;height:14px;accent-color:var(--accent);cursor:pointer;flex:none}
.m-pw-pop .m-pw-field{display:flex;align-items:center;gap:var(--s2);
  font-size:12.5px;color:var(--text-1);padding:2px 5px}
.m-pw-pop .m-pw-field .m-num{
  width:66px;padding:4px 6px;font-family:var(--font-mono);font-size:12px;
  background:var(--bg-input);color:var(--text-1);
  border:1px solid var(--border);border-radius:var(--r-sm)}
.m-pw-pop .m-pw-field .m-num:focus{outline:none;border-color:var(--accent);box-shadow:0 0 0 2px var(--accent-soft)}
.m-pw-pop .hint{font-size:11px;color:var(--text-muted);line-height:1.35;font-style:italic;
  margin:0 0 0 5px}
.m-pw-pop .m-pw-foot{display:flex;justify-content:flex-end;align-items:center;
  gap:var(--s2);margin-top:var(--s1);padding-top:var(--s2);border-top:1px solid var(--border-subtle)}
.m-pw-pop .m-pw-foot .btn-ghost{margin-right:auto}

/* Mobile: keep the icons tap-friendly without disturbing the wide desktop row. */
@media (pointer:coarse), (max-width:760px){
  .m-pinwatch .pw{min-width:var(--tap-min);min-height:var(--tap-min)}
  .m-pw-pop{min-width:220px}
}

/* ── Narrow-viewport tab strip (≤1099px; phones + iPads) ──────────────────────
   Below desktop width the monitor tab strip's multi-word labels ("DECT
   Monitor", "Channel Health", "War room") wrap to two lines and the Usage
   count badge splits from its label. Give the tab strip its own full-width row
   (it already wraps below the event title at these widths), keep every label
   on one line, and let the strip pan horizontally when it exceeds the viewport
   (scrollbar hidden, touch-pannable). Originally 761–1099 (iPad pass); the
   iPhone pass extended it to ≤760 — phones had NO scroll container at all, so
   the 600px strip made the whole document pan sideways (ovX≈240 at 390px).
   Desktop (≥1100) is untouched. */
@media (max-width:1099px){
  .m-subhdr .m-modewrap{
    flex:1 1 100%;margin-left:0;min-width:0;
    overflow-x:auto;-webkit-overflow-scrolling:touch;scrollbar-width:none;
  }
  .m-subhdr .m-modewrap::-webkit-scrollbar{display:none}
  .m-subhdr .m-modewrap .segmented{flex:none}
  .m-subhdr .seg-btn,.m-subhdr .m-tabghost{white-space:nowrap}
}

/* ── Custom panels + "own window" pane (design 2026-09-18 §2) ──────── */
.m-panelname{font-weight:600;font-size:12px;padding:0 6px;border-left:2px solid var(--accent);line-height:var(--m-ctrl-h);white-space:nowrap}
/* ("New panel…", the Slot box and the outside-marker chip used to sit in the
   top bar and pushed the icon cluster onto a second line. They moved into the
   View ▾ menu (spec D §4/§6); their styling went with them — see .m-viewrow /
   .m-viewnote above. */
.m-ownwindow{padding:0 5px;display:inline-flex;align-items:center}
/* Chart help "?" (monitor.js chartHelpButton): click-to-reveal, never a hover
   title over the spectrum (owner report 2026-10-03). */
.m-charthelp{padding:0 5px;display:inline-flex;align-items:center}
/* A fixed WIDTH, not only a cap — same reason as the View ▾ card below: the
   "?" sits near the right edge, and a shrink-to-fit card sized itself to the
   room right of the button (measured 200px wide, flush against the viewport
   edge) before being slid back into view. */
.popover:has(.m-charthelp-pop){width:min(320px,calc(100vw - 32px));max-width:min(320px,calc(100vw - 32px))}
.m-charthelp-pop{
  margin:0;padding:2px 2px 2px 16px;
  font-size:12px;line-height:1.45;color:var(--text-2);
  display:flex;flex-direction:column;gap:5px;
}

/* ── Panels open in ANOTHER window (spec K, owner 2026-09-19) ─────────────────
   A panel a second window is drawing is REMOVED from this stack (not collapsed —
   a stack of dead headers was the thing the owner asked us to get rid of), and
   ALL of them are represented by this one row at the top of the stack. It is a
   summary, not a panel: `flex:0 0 auto` keeps it out of the `.m-chartpanel`
   `flex:1 1 0` height budget so it never steals chart height.
   Touch: the toggle and every "Bring back" are plain `.btn`s with NO height of
   their own, so components.css's `(pointer:coarse),(max-width:760px)
   .btn{min-height:var(--tap-min)}` already gives them the 44px target — nothing
   to add here, and components.css stays untouched. (Setting a `height` here is
   the trap that rule keeps catching: min-height BEATS height.) */
.m-otherwin{flex:0 0 auto;display:flex;flex-direction:column;gap:4px;
  background:var(--mon-1);border:1px dashed var(--border);border-radius:var(--r-md);padding:4px 6px}
.m-otherwin-pill{align-self:flex-start;gap:6px;font-size:12px;color:var(--text-2)}
.m-otherwin-pill:hover{color:var(--text-1)}
.m-otherwin-car{font-size:10px;color:var(--text-muted)}
.m-otherwin-list{display:flex;flex-direction:column;gap:3px;padding:0 4px 3px}
.m-otherwin-list[hidden]{display:none}
.m-otherwin-row{display:flex;align-items:center;justify-content:space-between;gap:10px;
  border-top:1px solid var(--border-subtle);padding-top:3px}
.m-otherwin-name{min-width:0;flex:1 1 auto;font-family:var(--font-mono);font-size:11px;
  color:var(--text-1);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.m-otherwin-back{flex:0 0 auto;font-size:12px}
/* Own-window render: the one pane owns the viewport — no body padding, no
   inner scrollbar chrome, square edges. */
.monitor-view.m-solo .m-body{padding:0}
.monitor-view.m-solo .m-scrollhost{padding-right:0;gap:0}
.monitor-view.m-solo .m-scrolltrack{display:none}
.monitor-view.m-solo .m-chartpanel{border-radius:0;border:0;min-height:0}
/* New / edit panel dialog.
   openPanelDialog calls ensureSettingsStyle() (spec E §3), so the .form-* rules
   are present wherever the dialog is opened from. That stylesheet is INJECTED
   into <head> at runtime, i.e. after this file in document order — so every rule
   below that has to beat a .form-* declaration of the SAME specificity is
   written `.mp-body .mp-*`. A bare `.mp-notice{color:…}` loses to `.form-hint`. */
.mp-body{display:flex;flex-direction:column;gap:10px}
.mp-field{display:flex;align-items:center;gap:8px}
/* The trace picker is a scrollable LIST OF CHECKBOX ROWS (spec I §1), not a
   native <select multiple>: the list box needed a ⌘/Ctrl sentence to be usable
   and a stray plain click inside it dropped the whole selection. It is no longer
   a .form-select either, so the box itself is drawn here. Label on its own line
   above a full-width, scrollable box — the shape the surrounding modals use. */
.mp-field--traces{flex-direction:column;align-items:stretch;gap:5px}
.mp-body .mp-traces{
  width:100%;min-width:0;max-width:100%;max-height:16rem;overflow-y:auto;padding:4px;
  display:flex;flex-direction:column;gap:1px;
  background:var(--bg-input);border:1px solid var(--border);border-radius:var(--r-sm);
}
/* The zone eyebrow the <optgroup> label used to be. */
.mp-body .mp-tracegrp{
  font-family:var(--font-mono);font-size:10px;letter-spacing:.06em;text-transform:uppercase;
  color:var(--text-muted);padding:6px 6px 2px;
}
.mp-body .mp-tracegrp:first-child{padding-top:2px}
.mp-body .mp-tracerow{
  display:flex;align-items:center;gap:9px;padding:4px 6px;border-radius:var(--r-sm);
  cursor:pointer;color:var(--text-1);font-size:12.5px;
}
.mp-body .mp-tracerow:hover{background:var(--bg-raised)}
.mp-body .mp-tracerow input{width:14px;height:14px;accent-color:var(--accent);cursor:pointer;flex:none}
.mp-body .mp-tracerow .mp-tracename{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
/* A trace that already lives in ANOTHER custom panel: disabled, and it says which
   panel has it — the one-panel-per-trace rule before the Worker 409s it. */
.mp-body .mp-tracerow.is-taken,.mp-body .mp-tracerow.is-taken input{cursor:not-allowed}
.mp-body .mp-tracerow.is-taken{color:var(--text-faint)}
.mp-body .mp-tracerow.is-taken:hover{background:none}
.mp-body .mp-taken{margin-left:auto;font-family:var(--font-mono);font-size:10px;color:var(--text-faint);flex:none}
/* Lead paragraph stating a consequence of saving (View ▾ → Traces on an
   auto-grouped panel: saving converts it to a custom panel). A soft-warn
   callout, the platform's shape for this (styles/views-ssa.css
   .venue-origin-notice) — it used to render as raw amber text. */
.mp-body .mp-notice{
  margin:0;max-width:44ch;padding:8px 12px;border-radius:var(--r-sm);
  font-size:12.5px;line-height:1.45;color:var(--text-1);
  border:1px solid color-mix(in srgb, var(--warn) 45%, transparent);
  background:color-mix(in srgb, var(--warn) 10%, transparent);
}
.mp-body .mp-error{color:var(--danger);font-size:12px}
.mp-footer{display:flex;gap:8px;justify-content:flex-end}
/* Delete-a-custom-panel dialog — "where do its traces go?" (owner 2026-09-18).
   Built on openModal, so .modal-body/.modal-footer own the padding; these rules
   only lay out the radio choice. Deliberately NOT in components.css: it is this
   one dialog's shape, not a shared component. */
.m-pdel{display:flex;flex-direction:column;gap:10px}
.m-pdel .m-pdel-traces{margin:0;font-family:var(--font-mono);font-size:11px;letter-spacing:.04em;color:var(--text-muted)}
.m-pdel-opts{display:flex;flex-direction:column;gap:6px}
/* Each option is a <label>: the whole row is the radio's hit area. */
.m-pdel-opt{
  display:flex;gap:9px;align-items:flex-start;cursor:pointer;
  padding:9px 11px;border:1px solid var(--border-subtle);border-radius:var(--r-sm);
  background:var(--bg-2);
}
.m-pdel-opt:hover{border-color:var(--border)}
.m-pdel-opt.on{border-color:var(--accent);background:var(--accent-soft)}
.m-pdel-radio{margin:2px 0 0;accent-color:var(--accent);flex:0 0 auto}
.m-pdel-text{display:flex;flex-direction:column;gap:3px;min-width:0}
.m-pdel-title{font-size:12.5px;font-weight:600;color:var(--text-1)}
.m-pdel-hint{font-size:12px;line-height:1.45;color:var(--text-2);max-width:52ch}
.m-pdel-target{margin-top:4px}
/* The no-chart-left-at-all warning. Same soft-warn callout as .mp-notice. */
.m-pdel-warn{
  margin:0;max-width:52ch;padding:8px 12px;border-radius:var(--r-sm);
  font-size:12.5px;line-height:1.45;color:var(--text-1);
  border:1px solid color-mix(in srgb, var(--warn) 45%, transparent);
  background:color-mix(in srgb, var(--warn) 10%, transparent);
}
.m-pdel-warn[hidden]{display:none}

/* ── The embedded Event-settings panel's CLOSE path while the header WRAPS ──
   MEASURED in Chromium on this branch, settings panel open, elementFromPoint at
   the ×'s centre (CSS viewport widths, backdrop z-index 60 throughout):

     375 → header 115px, hit DIV.m-subhdr      1024 → header 109px, hit DIV.m-iconcluster
     768 → header 109px, hit BUTTON.m-iconbtn  1099 → header 109px, hit DIV.m-iconcluster
     800 → header 109px, hit BUTTON.m-iconbtn  1100 → header  71px, hit the × itself
     900 → header 109px, hit BUTTON.m-iconbtn  1280 → header  71px, hit the × itself

   The sticky .m-subhdr is z-index 100 and the backdrop pads 60px down, so the
   panel's head sits at 61 → 104.5 and the × at 75.25 → 89.25: a header taller
   than ~75px covers the button, a 71px one does not. The × could not be tapped
   at ANY of the wrapped widths — iPad portrait and landscape included, coarse
   pointer, no Escape key — leaving a 16px strip of backdrop as the only way out.

   Two things, both of this file's own elements (components.css unchanged):

   1. Where the header COVERS the panel's ×, the backdrop goes ABOVE the header.
      The header's z-index 100 is deliberate — it keeps the tabs readable and
      clickable above the backdrop — but that intent holds only where the header
      is the one short row that never reaches the panel, and nothing changes
      there. 101 is the smallest value that clears the header and stays below
      popovers (800), modals (900+) — the coverage dialog opens over this panel
      — and toasts.

      This used to be keyed on a viewport WIDTH (max-width:1099px), the width at
      which the tab-strip rule above puts the strip on its own row. That is a
      PROXY for the wrap, and it is wrong at the boundary: the measured
      clearance at 1100px is 4px (header bottom 71, × top 75.25), so an event
      whose tab strip is wide enough to push the icon cluster onto a third row
      covers the × at 1100 while the rule calls that width safe. So the raise is
      keyed on the OVERLAP ITSELF: monitor.js measures the header's bottom edge
      against the × on every header/panel resize and sets data-hdr-covers-panel
      on .monitor-view. No width appears here at all, and widening the tab-strip
      rule can no longer make the two drift apart — there is nothing left to
      keep in step (tests/monitor-settings-close-reach.test.js).
   2. The × itself renders 14×14 against a 44px --tap-min. Same treatment as
      the map sidebar's Place button (styles/views-map.css): a transparent
      ::before hit-expander rather than a 44px box, so the head keeps its
      density. It belongs to the button, so a tap on it activates the button.
      right:-8px carries it into the head's 16px right padding and still stops
      8px short of the panel edge (measured: button right 342, panel right 358),
      which matters because .m-settings-panel is overflow:hidden and would clip
      a hit area that ran past it. `position` is not set inline — no
      !important is needed, and .m-settings-head raises the specificity over the
      bare .m-sheet-x rule the view injects at runtime. Keyed on the pointer, or
      on a phone-width viewport: it is about the finger, not about the wrap. */
.monitor-view[data-hdr-covers-panel] .m-settings-backdrop{z-index:101}
@media (pointer:coarse), (max-width:760px){
  .m-settings-head .m-sheet-x{position:relative}
  .m-settings-head .m-sheet-x::before{
    content:'';position:absolute;top:50%;right:-8px;
    width:var(--tap-min);height:var(--tap-min);transform:translateY(-50%);
  }
}

/* ── PERF 2026-09-22: progressive loading states ────────────────────────────
   Popovers now open IMMEDIATELY from what the view already has (a detection
   row's sources_top; the per-event freq-history cache) and fill in when the
   fetch lands. The quiet "updating…" line is what stops that from reading as
   "this is the final answer" — deliberately low-contrast and out of the way,
   never a spinner that pulls the eye off the data already on screen. */
.m-srcpop-updating,
.m-histupdating{
  margin-top:6px;padding-top:6px;
  border-top:1px solid var(--border);
  font-size:11px;color:var(--text-muted);font-style:italic;
}

/* One failed read must not blank the monitor (partialLoadFailures). Names what
   is missing — an empty Detections pane is otherwise indistinguishable from a
   quiet event — and offers the same re-mount the fatal error state offers. */
.m-partial-banner{
  display:flex;align-items:center;gap:10px;flex-wrap:wrap;
  margin:8px 12px;padding:8px 12px;border-radius:6px;
  background:var(--warn-bg,rgba(255,176,32,.12));
  border:1px solid var(--warn,rgba(255,176,32,.45));
  font-size:12px;color:var(--text);
}
.m-partial-banner .msg{flex:1 1 auto;min-width:0}
/* Ask: availability notice (not available / capped / hourly limit) + usage line */
.ask-notice{margin:0 0 10px;font-size:12.5px;color:var(--text-2);border:1px solid var(--warn);border-radius:7px;padding:8px 11px;background:var(--bg-1,var(--bg-2))}
.ask-usage{margin:-6px 0 10px 4px;font-size:11px;color:var(--text-muted)}
.ask-card-bd > .btn{margin-top:8px}
.ask-input:disabled,.ask-send:disabled,.ask-qchip:disabled{opacity:.5;cursor:not-allowed}
