/* Shadowtrace dashboard — Pico.css v2 overrides + small custom styles.
   Kept tiny and hand-written so the full CSS story is: one vendored
   framework file + this. No build step, no preprocessor. */

:root {
  --pico-font-size: 15px;

  /* Font stack — Segoe UI Variable is genuinely great (Windows 11
     ships it system-wide, and this is a Windows-native tool) so we
     don't need to pull a webfont. Inter sits below it as a cross-OS
     fallback in case someone views the dashboard from a Mac/Linux
     browser; system-ui + -apple-system cover everyone else. Display
     variant is used for headings (tighter, more geometric), Text
     variant for body (better at small sizes). */
  --font-ui: "Segoe UI Variable Text", "Segoe UI", Inter, system-ui, -apple-system, "Helvetica Neue", Arial, sans-serif;
  --font-display: "Segoe UI Variable Display", "Segoe UI", Inter, system-ui, -apple-system, "Helvetica Neue", Arial, sans-serif;
  --font-mono: "Cascadia Code", "JetBrains Mono", Consolas, "Liberation Mono", "Courier New", monospace;

  --pico-font-family: var(--font-ui);
  --pico-font-family-sans-serif: var(--font-ui);
  --pico-font-family-monospace: var(--font-mono);

  /* Status palette — one source of truth for every chip, badge, row
     highlight, and border accent that conveys state. Keeping these
     as CSS variables instead of hard-coding hex across 40+ call sites
     means a re-tune of the risk colour updates everywhere at once,
     and lets a future dark/light theme switch swap the palette
     centrally. Intensity rules of thumb:
       --status-danger   = red, "this is actively bad"
       --status-warning  = amber, "this wants eyes on it"
       --status-success  = green, filled — "this is healthy/ack'd"
       --status-muted    = green, outline — "nothing flagged"
       --status-info     = grey outline — neutral count / context */
  --status-danger: #c0392b;
  --status-warning: #d9822b;
  --status-success: #27ae60;
  --status-success-fg: #6ddf9f;
  --status-success-tint: rgba(109, 223, 159, 0.12);
  --status-success-border: rgba(109, 223, 159, 0.4);

  /* Severity palette — four-tier SOC ladder (critical/high/medium/low)
     that replaces the previous binary "flagged-or-clean" tint. Every
     triage surface in the app (row accent on the sessions table, the
     severity chip on the session hero, the Needs-review stack order,
     KPI strip) should read from these so an operator sees the same
     colour for the same tier no matter which page they're on.
     Convention is "traffic light" — red/orange/yellow/blue — which
     is the same visual language every enterprise SOC console (Crowd-
     Strike, SentinelOne, Wiz) uses, so a CISO demo doesn't need an
     explainer for what the colours mean.
       -fg:     the foreground/stroke colour the chip text uses
       -tint:   a low-alpha wash for row backgrounds + chip fills
       -border: the line colour for chip borders + row accents */
  --severity-critical-fg: #ff5a4d;
  --severity-critical-tint: rgba(255, 90, 77, 0.14);
  --severity-critical-border: rgba(255, 90, 77, 0.55);
  --severity-high-fg: #ff9b3d;
  --severity-high-tint: rgba(255, 155, 61, 0.14);
  --severity-high-border: rgba(255, 155, 61, 0.55);
  --severity-medium-fg: #f5c84b;
  --severity-medium-tint: rgba(245, 200, 75, 0.14);
  --severity-medium-border: rgba(245, 200, 75, 0.55);
  --severity-low-fg: #5fb3ff;
  --severity-low-tint: rgba(95, 179, 255, 0.12);
  --severity-low-border: rgba(95, 179, 255, 0.45);
}

body {
  font-family: var(--font-ui);
  font-feature-settings: "ss01", "ss02", "cv11"; /* Segoe UI Variable stylistic sets — subtle but helps legibility */
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}
h1, h2, h3, h4, h5, h6 { font-family: var(--font-display); }
code, kbd, pre, samp { font-family: var(--font-mono); }

/* Tabular numerics everywhere numbers appear in scannable columns —
   tables, code (session ids, paths), stat-card totals, small meta
   strings. Prose body text stays proportional (default). This is
   the single biggest "enterprise feel" lever: columns of numbers
   stop wiggling under the reader's eye. */
table,
code,
kbd,
samp,
.stat-card strong,
.agent-count,
.risk-chip strong,
.proc-count strong {
  font-variant-numeric: tabular-nums;
}

/* Right-align numeric table columns — opt-in via .num class on the
   matching <th> and <td>. CSS can't derive "this column is numbers"
   from the <th data-sort="number"> attribute (no parent→children
   selector across siblings), so templates explicitly mark cells
   they want right-aligned. */
th.num,
td.num {
  text-align: right;
}

/* Inline SVG icons (Lucide) — replace the emoji (⚠ 📄 ⇔ ✓) that
   previously stood in for icons. Sized to match the surrounding
   font via em, vertically aligned so baseline reads as a single
   line with the adjacent text. currentColor lets a chip's text
   colour paint the glyph too — the red-on-red alert icon inside
   .section-heading-alert, the green check inside
   .risk-chip-reviewed, etc. */
.icon {
  display: inline-block;
  width: 1em;
  height: 1em;
  vertical-align: -0.15em;
  flex-shrink: 0;
}
.icon-alert { width: 1.1em; height: 1.1em; vertical-align: -0.2em; }
.icon-check { width: 1em; height: 1em; vertical-align: -0.15em; }

body > main.container { padding-top: 1.5rem; }

/* ==========================================================================
   App shell — persistent left sidebar + main column. Every enterprise
   security console (CrowdStrike/SentinelOne/Wiz/Datadog) uses this
   silhouette; a visible, always-on nav rail signals "platform" rather
   than "page." The sidebar spans the full viewport height, the main
   column scrolls independently with its own footer and content width.
   ========================================================================= */
:root {
  --sidebar-width: 232px;
}
body {
  margin: 0;
}
.app-sidebar {
  position: fixed;
  top: 2px; /* sit below the .top-accent 2px strip */
  left: 0;
  bottom: 0;
  width: var(--sidebar-width);
  display: flex;
  flex-direction: column;
  background: linear-gradient(180deg,
      color-mix(in srgb, var(--pico-primary) 4%, var(--pico-card-background-color)) 0%,
      var(--pico-card-background-color) 100%);
  border-right: 1px solid var(--pico-muted-border-color);
  z-index: 40;
  overflow-y: auto;
}
.app-shell {
  margin-left: var(--sidebar-width);
  min-height: calc(100vh - 2px);
  display: flex;
  flex-direction: column;
}
.app-shell > main.container,
.app-shell > footer.container {
  max-width: none;
  width: auto;
  margin-left: 1.75rem;
  margin-right: 1.75rem;
}
.app-shell > main.container {
  flex: 1 0 auto;
  padding-top: 1.25rem;
  padding-bottom: 1.5rem;
}

/* Sidebar brand — same mark as the old header, positioned at the top
   of the rail. Slightly larger wordmark than when it was in a header
   row because vertical real estate is free here; sidebar top reads
   like a product-page masthead. */
.sidebar-brand {
  padding: 1rem 0.9rem 0.75rem;
  border-bottom: 1px solid var(--pico-muted-border-color);
}
.sidebar-brand .brand {
  display: inline-flex;
  align-items: center;
  gap: 0.55rem;
  color: var(--pico-color);
  text-decoration: none;
}
.sidebar-brand .brand:hover { color: var(--pico-color); }
.sidebar-brand .brand-logo {
  width: 26px;
  height: 26px;
  color: var(--pico-primary);
  flex-shrink: 0;
}

/* Sidebar nav groups. Two visual groups — Monitor (operational) and
   Configure (admin). Each group has a muted uppercase label so a
   first-time visitor can read the product's information architecture
   without clicking. Links are full-width rows with icon + label; the
   active item picks up a left accent bar + tinted background. */
.sidebar-nav {
  flex: 1 0 auto;
  padding: 0.75rem 0.4rem;
  display: flex;
  flex-direction: column;
  gap: 1.1rem;
}
.sidebar-group {
  display: flex;
  flex-direction: column;
  gap: 0.1rem;
}
.sidebar-group-label {
  padding: 0.25rem 0.75rem 0.35rem;
  font-size: 0.68rem;
  font-family: var(--font-display);
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--pico-muted-color);
}
a.sidebar-link {
  display: flex;
  align-items: center;
  gap: 0.65rem;
  padding: 0.45rem 0.75rem;
  border-radius: 6px;
  border-left: 2px solid transparent;
  color: var(--pico-muted-color);
  font-size: 0.9rem;
  font-weight: 500;
  text-decoration: none;
  transition: background 120ms ease, color 120ms ease, border-color 120ms ease;
}
a.sidebar-link:hover {
  background: color-mix(in srgb, var(--pico-primary) 5%, transparent);
  color: var(--pico-color);
}
a.sidebar-link[aria-current="page"] {
  background: color-mix(in srgb, var(--pico-primary) 10%, transparent);
  border-left-color: var(--pico-primary);
  color: var(--pico-color);
}
a.sidebar-link[aria-current="page"] .sidebar-icon {
  color: var(--pico-primary);
}
/* Nested entry — indented + slightly muted so the parent/child
   relationship reads at a glance without needing a bullet or tree
   glyph. Icon shrinks a hair to underline the hierarchy. */
a.sidebar-link.sidebar-sublink {
  padding-left: 1.75rem;
  font-size: 0.85rem;
}
a.sidebar-link.sidebar-sublink .sidebar-icon {
  width: 14px;
  height: 14px;
  opacity: 0.85;
}
.sidebar-icon {
  width: 16px;
  height: 16px;
  flex-shrink: 0;
  color: currentColor;
}

/* Sidebar foot — the Live chip sits at the bottom of the rail as a
   persistent "the server is up" signal. Small gap above so it reads as
   its own band, not an extension of the Configure group. */
.sidebar-foot {
  padding: 0.75rem 0.9rem 1rem;
  border-top: 1px solid var(--pico-muted-border-color);
}
.sidebar-foot .status-chip {
  width: auto;
}

/* Mobile: collapse the sidebar. At <800px the rail becomes a horizontal
   strip across the top — not perfect but it keeps all nav reachable
   without a hamburger-button build. Enterprise buyers look at desktop;
   mobile is a "doesn't break" pass, not a destination experience. */
@media (max-width: 800px) {
  .app-sidebar {
    position: static;
    width: auto;
    bottom: auto;
    flex-direction: row;
    flex-wrap: wrap;
    padding: 0.5rem 0.75rem;
    gap: 0.75rem;
  }
  .app-shell { margin-left: 0; }
  .sidebar-brand, .sidebar-foot {
    flex: 0 0 auto;
    padding: 0;
    border: none;
  }
  .sidebar-nav {
    flex: 1 1 100%;
    flex-direction: row;
    flex-wrap: wrap;
    padding: 0;
    gap: 0.25rem 0.5rem;
  }
  .sidebar-group { flex-direction: row; gap: 0.15rem; }
  .sidebar-group-label { display: none; }
  a.sidebar-link { padding: 0.35rem 0.55rem; border-left: none; }
  a.sidebar-link[aria-current="page"] { border-left: none; }
}

/* Top accent strip — sits above the header. A 2px gradient from the
   primary colour fading to transparent; the tiny visual cue that
   makes the page read as "product surface" immediately. */
.top-accent {
  height: 2px;
  background: linear-gradient(
    to right,
    var(--pico-primary) 0%,
    var(--pico-primary) 35%,
    rgba(var(--pico-primary-rgb, 52, 152, 219), 0.4) 65%,
    transparent 100%
  );
}

/* Header band — wraps the nav so we can give it a subtle darker
   background + bottom separator without touching every nav rule. The
   layered gradients (card-bg over a faint primary glow on the left)
   give the header the faint "dashboard chrome" texture that distinguishes
   a product from a plain page. */
.app-header {
  background:
    linear-gradient(to right, rgba(var(--pico-primary-rgb, 52, 152, 219), 0.05), transparent 40%),
    var(--pico-card-background-color);
  border-bottom: 1px solid var(--pico-muted-border-color);
  margin-bottom: 1rem;
}
.app-header nav {
  padding-top: 0.4rem;
  padding-bottom: 0.4rem;
}

/* Status chips — the right side of the header. Pill-shaped, muted,
   with the live variant carrying a pulsing green dot so the eye sees
   "yes, something is alive on the other end". */
.app-status { gap: 0.5rem; display: flex; align-items: center; }
.status-chip {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0.2rem 0.65rem;
  border: 1px solid var(--pico-muted-border-color);
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.02);
  font-size: 0.72rem;
  font-family: var(--font-display);
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--pico-muted-color);
  white-space: nowrap;
}
.status-chip.status-meta { font-family: var(--font-mono); text-transform: none; letter-spacing: 0; }
.status-chip.status-live {
  color: #6ddf9f;
  border-color: rgba(109, 223, 159, 0.35);
  background: rgba(109, 223, 159, 0.08);
}
.status-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: #6ddf9f;
  box-shadow: 0 0 0 0 rgba(109, 223, 159, 0.7);
  animation: pulse-dot 2s ease-out infinite;
}
@keyframes pulse-dot {
  0%   { box-shadow: 0 0 0 0 rgba(109, 223, 159, 0.55); }
  70%  { box-shadow: 0 0 0 6px rgba(109, 223, 159, 0); }
  100% { box-shadow: 0 0 0 0 rgba(109, 223, 159, 0); }
}
/* Kill the default Pico bullet spacing on the status <ul>. */
.app-status { list-style: none; padding: 0; margin: 0; }

/* Settings dropdown. Sits in the header's status corner next to the
   LIVE chip, anchored to the right edge. The <summary> is rendered as
   a square icon button (gear) that reads as "configuration" in every
   dashboard convention; a floating panel drops below it on click.
   Click-outside-to-close is wired in app.js — without it, native
   <details> only closes on re-click of the summary, which feels
   broken on a menu. Using a plain <details class="settings-menu">
   (not Pico's .dropdown) so Pico's default dropdown CSS stays out of
   the way — the panel geometry is entirely ours. */
.app-settings {
  position: relative;
  padding: 0 !important;
  margin: 0 0 0 0.1rem !important;
}
.app-settings details.settings-menu {
  margin: 0;
  padding: 0;
  border: none;
  background: transparent;
}
.app-settings details.settings-menu > summary {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2rem;
  height: 2rem;
  padding: 0;
  border: 1px solid var(--pico-muted-border-color);
  border-radius: 6px;
  background: transparent;
  /* Gear is a wayfinding control — it should read at the same weight
     as the LIVE chip's text, not fade into the header chrome. Using
     --pico-contrast (the theme's brightest foreground) gives the
     icon a clear silhouette against the dark header without tipping
     into "this is the primary action" territory. */
  color: var(--pico-contrast);
  cursor: pointer;
  list-style: none;
  transition: color 120ms ease, border-color 120ms ease, background 120ms ease;
}
/* Kill the default triangle marker so the gear icon stands alone. */
.app-settings details.settings-menu > summary::after,
.app-settings details.settings-menu > summary::before,
.app-settings details.settings-menu > summary::-webkit-details-marker {
  display: none !important;
  content: none !important;
}
.app-settings details.settings-menu > summary:hover,
.app-settings details.settings-menu > summary:focus-visible {
  color: var(--pico-color);
  border-color: var(--pico-primary);
  background: color-mix(in srgb, var(--pico-primary) 10%, transparent);
  outline: none;
}
.app-settings details.settings-menu[open] > summary {
  color: var(--pico-primary);
  border-color: var(--pico-primary);
  background: color-mix(in srgb, var(--pico-primary) 10%, transparent);
}
.app-settings .gear-icon {
  width: 1.05rem;
  height: 1.05rem;
  display: block;
}
/* Floating settings panel. Anchored to the right edge of the gear
   button so the menu grows leftward into the page (button sits on
   the right of the viewport — growing rightward would push it off-
   screen). The panel uses a <div>, not a <ul>, so Pico's nav/list
   styling can't reach in and nudge it. min-width keeps short entries
   from collapsing the box; box-shadow + card background lift it
   above page content so it reads as a floating menu. */
.app-settings details.settings-menu > .settings-panel {
  position: absolute;
  left: auto;
  right: 0;
  top: calc(100% + 0.4rem);
  min-width: 12rem;
  margin: 0;
  padding: 0.35rem;
  background: var(--pico-card-background-color);
  border: 1px solid var(--pico-muted-border-color);
  border-radius: 8px;
  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.45);
  z-index: 60;
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
}
/* Section label above the menu items. Small uppercase, muted —
   reads as "you are in the settings menu" and gives the panel
   visual weight even when there's only one item (which there is
   today — more will slot in as Retention / Auth / Alerting land). */
.app-settings .settings-panel-label {
  font-family: var(--font-display);
  font-size: 0.62rem;
  font-weight: 600;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--pico-muted-color);
  padding: 0.35rem 0.55rem 0.25rem;
  margin: 0;
  border-bottom: 1px solid var(--pico-muted-border-color);
  margin-bottom: 0.2rem;
}
.app-settings .settings-panel a.nav-admin {
  display: block;
  padding: 0.45rem 0.6rem;
  font-family: var(--font-display);
  font-size: 0.78rem;
  font-weight: 600;
  letter-spacing: 0.08em;
  color: var(--pico-color);
  text-decoration: none;
  background: transparent;
  border-radius: 5px;
  transition: background 120ms ease, color 120ms ease;
}
.app-settings .settings-panel a.nav-admin:hover,
.app-settings .settings-panel a.nav-admin:focus-visible {
  background: color-mix(in srgb, var(--pico-primary) 14%, transparent);
  color: var(--pico-color);
  text-decoration: none;
}
.app-settings .settings-panel a.nav-admin[aria-current="page"] {
  color: var(--pico-primary);
  background: color-mix(in srgb, var(--pico-primary) 10%, transparent);
}

footer.container {
  margin-top: 3rem;
  padding-top: 1rem;
  border-top: 1px solid var(--pico-muted-border-color);
  color: var(--pico-muted-color);
}

/* Footer chrome — two-row-on-mobile / single-row-on-desktop layout.
   Left block carries the product tagline; right block carries build
   identity (commit + schema version). Build chips are intentionally
   lighter-weight than the header's status-chip language so they read
   as "meta info" rather than actionable state. */
.app-footer {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem 1.5rem;
}
.app-footer-build {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  font-variant-numeric: tabular-nums;
}
.app-footer-build .build-chip {
  white-space: nowrap;
}
.app-footer-build .build-chip code {
  font-size: 0.85em;
  color: var(--pico-color);
  background: transparent;
  padding: 0;
}
.app-footer-build .build-sep {
  opacity: 0.5;
}

nav ul li small { color: var(--pico-muted-color); }

/* Session tab nav — the row of links under the session header that
   switches between Overview / Processes / Activity / Events. Rendered
   as links (not JS tabs) so each tab is its own URL, bookmarkable and
   back-button friendly. Active tab gets a solid bottom border to match
   the "physical tab" affordance without being garish. */
/* Session notes thread — sits between hero and tab nav. Card-shaped to
   match the hero's visual weight without competing with it (softer
   border, smaller title). The composer stays visible even in the empty
   state so leaving the first note is one click, not two. */
.session-notes {
  border: 1px solid var(--pico-muted-border-color);
  border-radius: var(--pico-border-radius);
  padding: 0.75rem 1rem;
  margin: 0.5rem 0 1rem 0;
  background: var(--pico-card-background-color);
}
.session-notes-head {
  display: flex;
  align-items: baseline;
  gap: 0.5rem;
  margin-bottom: 0.5rem;
}
.session-notes-title { font-size: 0.95rem; }
.session-notes-count {
  color: var(--pico-muted-color);
  font-variant-numeric: tabular-nums;
}
.session-notes-empty {
  margin: 0 0 0.75rem 0;
  color: var(--pico-muted-color);
}
.session-notes-list {
  list-style: none;
  padding: 0;
  margin: 0 0 0.75rem 0;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}
.session-note {
  border-left: 3px solid var(--pico-primary);
  padding: 0.35rem 0.65rem;
  background: var(--pico-background-color);
  border-radius: 0 4px 4px 0;
}
.session-note-meta {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  margin-bottom: 0.15rem;
  font-size: 0.85em;
}
.session-note-author { color: var(--pico-color); }
.session-note-author-anon { color: var(--pico-muted-color); font-weight: 500; }
.session-note-ts {
  color: var(--pico-muted-color);
  font-variant-numeric: tabular-nums;
}
.session-note-del-form {
  margin: 0 0 0 auto;
  display: inline;
}
.session-note-del {
  background: transparent;
  border: none;
  color: var(--pico-muted-color);
  padding: 0 0.25rem;
  font-size: 1.1em;
  line-height: 1;
  cursor: pointer;
  border-radius: 3px;
}
.session-note-del:hover {
  color: var(--severity-critical-fg, var(--pico-color));
  background: var(--pico-muted-border-color);
}
.session-note-body {
  margin: 0;
  white-space: pre-wrap;
  word-break: break-word;
}
.session-note-form {
  display: block;
  margin: 0;
}
.session-note-input {
  display: block;
  width: 100%;
  box-sizing: border-box;
  resize: vertical;
  margin: 0 0 0.5rem 0;
  min-height: 3rem;
  line-height: 1.4;
}
.session-note-submit {
  display: inline-block;
  width: auto;
  padding: 0.5rem 1rem;
  margin: 0;
  white-space: nowrap;
}

nav.session-tabs {
  display: flex;
  gap: 0.25rem;
  margin: 0 0 1.25rem 0;
  border-bottom: 1px solid var(--pico-muted-border-color);
}
nav.session-tabs .tab {
  display: inline-block;
  padding: 0.5rem 1rem;
  color: var(--pico-muted-color);
  text-decoration: none;
  border-bottom: 2px solid transparent;
  margin-bottom: -1px;
}
nav.session-tabs .tab:hover {
  color: var(--pico-color);
}
nav.session-tabs .tab.active {
  color: var(--pico-color);
  border-bottom-color: var(--pico-primary);
  font-weight: 600;
}
nav.session-tabs .tab small {
  color: var(--pico-muted-color);
  margin-left: 0.25rem;
}

/* Session table rows keep the monospace session id left-aligned and the
   timestamps compact. */
table code { font-size: 0.85em; }

/* Top-N tables sit inside <article> grid cells. Compact them so several
   fit without dominating the page. */
article > table {
  margin: 0;
  font-size: 0.9em;
}
article > table td:first-child { width: 80%; }
article > table td:last-child  { text-align: right; color: var(--pico-muted-color); }

/* Long paths wrap rather than force horizontal scroll. */
code.wrap {
  word-break: break-all;
  white-space: normal;
}

/* Decoded PowerShell payload shown under a base64 blob. Collapsed by
   default so the table stays scannable; opens to show the recovered
   script inline without losing the row context. */
details.decoded-cmdline {
  margin-top: 0.4rem;
}
details.decoded-cmdline summary {
  cursor: pointer;
  color: var(--pico-muted-color);
}
details.decoded-cmdline pre {
  margin: 0.3rem 0 0;
  padding: 0.5rem 0.65rem;
  background: color-mix(in srgb, var(--pico-primary, #06b6d4) 6%, transparent);
  border-left: 2px solid color-mix(in srgb, var(--pico-primary, #06b6d4) 40%, transparent);
  border-radius: 3px;
  max-height: 14rem;
  overflow: auto;
}
details.decoded-cmdline pre code {
  white-space: pre-wrap;
  word-break: break-word;
  font-size: 0.88em;
}

/* Print-friendly variant for the forensic report — always expanded, no
   summary chrome, darker border so it survives greyscale printing. */
pre.report-decoded {
  margin: 0.35rem 0 0;
  padding: 0.5rem 0.65rem;
  background: #f6f8fa;
  border-left: 3px solid #94a3b8;
  border-radius: 2px;
  white-space: pre-wrap;
  word-break: break-word;
  font-size: 0.88em;
}

/* Sensitive-hit panel stands out without going Full Red Alert — this is a
   review tool, not a pager. */
article.sensitive-hits {
  border-left: 4px solid var(--pico-del-color, #c0392b);
  background: var(--pico-card-background-color);
}
article.sensitive-hits header strong { color: var(--pico-del-color, #c0392b); }

/* Needs-review checklist — the Flagged-sessions table wrapped in a
   bulk-ack form. Checkboxes sit in a narrow first column; the footer
   carries a live "N of M selected" count and a submit button that
   disables until at least one row is checked, so the reviewer gets
   feedback that the batch action exists without committing to it.
   Inherits the surrounding .sensitive-hits card chrome — this is the
   same panel, just with the extra affordance. */
.needs-review-form { margin: 0; }
.needs-review-table th.needs-review-check,
.needs-review-table td.needs-review-check {
  width: 2.25rem;
  padding-right: 0;
  text-align: center;
  vertical-align: middle;
}
.needs-review-table td.needs-review-check input,
.needs-review-table th.needs-review-check input {
  margin: 0;
  cursor: pointer;
}
.needs-review-actions {
  display: flex;
  justify-content: flex-end;
  align-items: center;
  gap: 0.75rem;
  margin-top: 0.5rem;
  padding-top: 0.75rem;
  border-top: 1px solid var(--pico-muted-border-color);
}
.needs-review-count { color: var(--pico-muted-color); }
button.needs-review-submit {
  margin: 0;
  padding: 0.35rem 0.9rem;
  font-size: 0.85em;
  font-weight: 600;
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
}
button.needs-review-submit[disabled] {
  opacity: 0.55;
  cursor: not-allowed;
}

/* Evidence table: each sensitive-hit row is the reviewer's gateway
   into the timeline. The path cell is the primary click target so we
   style it as a strong underlined link that highlights on hover —
   much more affordant than the old "plain code" styling which hid
   the interactivity. Image + pid column is the attribution (who did
   it); timestamp anchors the "when". Muted dash for the probe-
   originated hits that couldn't be enriched (old rollups). */
table.evidence-table a.evidence-link {
  color: var(--pico-primary, #06b6d4);
  text-decoration: underline dotted;
  text-underline-offset: 2px;
}
table.evidence-table a.evidence-link:hover {
  text-decoration-style: solid;
  background: color-mix(in srgb, var(--pico-primary, #06b6d4) 12%, transparent);
  border-radius: 2px;
}
table.evidence-table a.evidence-link code {
  color: inherit;
  background: transparent;
  padding: 0;
}
code.evidence-image {
  display: inline-block;
  padding: 0.05rem 0.35rem;
  background: color-mix(in srgb, var(--pico-card-background-color, #111) 60%, transparent);
  border-radius: 2px;
  font-size: 0.78em;
  margin-right: 0.3rem;
}
small.evidence-pid {
  color: var(--pico-muted-color, #888);
  font-variant-numeric: tabular-nums;
}

article.overflow-warn {
  padding: 0.5rem 1rem;
  background: var(--pico-card-background-color);
  border-left: 4px solid var(--pico-mark-background-color, #f39c12);
}
article.overflow-warn kbd { margin-right: 0.4rem; }

/* Process tree. Pico sets sensible <ul> indentation; we just tighten the
   spacing and add a faint left border to make the hierarchy obvious. */
ul.tree, ul.tree ul {
  list-style: none;
  padding-left: 1.2rem;
  margin: 0;
  border-left: 1px dashed var(--pico-muted-border-color);
}
ul.tree > li, ul.tree ul > li {
  padding: 0.25rem 0;
}
.proc code.wrap { font-size: 0.9em; }
.proc .proc-meta {
  margin-left: 0.5rem;
  color: var(--pico-muted-color);
}
.proc mark {
  margin-right: 0.4rem;
  padding: 0 0.4rem;
  font-size: 0.75em;
  text-transform: uppercase;
}
.proc details { margin-top: 0.25rem; }
.proc details pre {
  margin: 0.25rem 0 0 0;
  padding: 0.4rem 0.6rem;
  font-size: 0.85em;
}

/* Inline cmdline: the most load-bearing piece of evidence on the
   Processes tab for AI-agent triage — "what did node.exe actually
   invoke". Rendered flush under the image line in a subtly tinted
   code block so it reads as a direct annotation of its process, not
   a separate UI element. Horizontal overflow rather than wrap so a
   long node bootstrap stays one copy-pasteable line instead of
   ballooning the row height. */
.proc-cmdline {
  margin: 0.25rem 0 0 0;
  padding: 0.35rem 0.55rem;
  background: color-mix(in srgb, var(--pico-card-background-color, #111) 60%, transparent);
  border-left: 2px solid var(--pico-muted-border-color, #333);
  border-radius: 0 2px 2px 0;
  max-width: 100%;
  overflow-x: auto;
}
.proc-cmdline code {
  font-size: 0.82em;
  color: var(--pico-color, #ddd);
  background: transparent;
  padding: 0;
  white-space: pre;
}

/* Lifespan chip on the proc-meta line. Dead processes read in muted
   text alongside pid/ppid; still-running processes get a green accent
   so the eye lands on "who's still alive" when scanning a tree with
   dozens of ended children. Not a full badge — just inline colour on
   a single word, matching the understatement of the surrounding meta. */
.proc-lifespan {
  font-family: var(--pico-font-family-monospace, monospace);
}
.proc-lifespan-running {
  color: #10b981;
  font-weight: 500;
}

/* Event-filter pills above the Recent events table. Rendered as links so
   state lives in the URL (bookmark-friendly) rather than in JS state. */
nav.filter-pills {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
  margin: 0 0 0.75rem 0;
}
nav.filter-pills .pill {
  display: inline-block;
  padding: 0.2rem 0.75rem;
  border: 1px solid var(--pico-muted-border-color);
  border-radius: 999px;
  font-size: 0.85em;
  color: var(--pico-muted-color);
  text-decoration: none;
  background: transparent;
}
nav.filter-pills .pill.active {
  color: var(--pico-primary);
  border-color: var(--pico-primary);
  background: var(--pico-primary-background, transparent);
}
nav.filter-pills .pill:hover {
  border-color: var(--pico-primary);
}

/* Fixed two-column grid used by the Activity tab on session detail.
   Each cell holds one topn card and keeps a half-width slot even when
   the row has only one card in it — avoids the empty spacer-article
   hack we previously used with Pico's flex .grid. Drops to a single
   column on narrow viewports so cards stay readable. */
.grid-two-cols {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--pico-grid-column-gap, 1rem) var(--pico-grid-row-gap, 1rem);
  margin-bottom: var(--pico-spacing, 1rem);
}
@media (max-width: 768px) {
  .grid-two-cols { grid-template-columns: 1fr; }
}

/* Stat cards — the "at a glance" row across the top of the triage
   landing page. Kept flat and compact; a big number + a small label is
   the whole job. stat-card-flag colours non-zero risk counters so the
   reviewer's eye lands on the card that matters. */
.stat-cards article.stat-card {
  margin: 0;
  padding: 0.75rem 1rem;
  text-align: left;
}
.stat-cards article.stat-card small {
  display: block;
  color: var(--pico-muted-color);
  font-size: 0.8em;
  text-transform: uppercase;
  letter-spacing: 0.04em;
}
.stat-cards article.stat-card strong {
  font-size: 1.8rem;
  line-height: 1.2;
}

/* Review-posture micro row inside the Flagged stat-card. Three
   compact label/value pairs — Reviewed, Open, MTTA — laid as a
   small dl grid below the severity chip row. Small + quiet so it
   reads as context under the main number, not a competing headline. */
.stat-posture {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 0.35rem 0.75rem;
  margin: 0.65rem 0 0 0;
  padding-top: 0.55rem;
  border-top: 1px solid var(--pico-muted-border-color);
}
.stat-posture > div {
  display: flex;
  flex-direction: column;
  min-width: 0;
}
.stat-posture dt {
  color: var(--pico-muted-color);
  font-size: 0.66em;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  font-family: var(--font-display);
  margin: 0;
}
.stat-posture dd {
  margin: 0;
  font-size: 0.95rem;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  color: var(--pico-color);
}
.stat-posture-alert { color: var(--severity-critical-fg); }

/* Severity tier strip under the Flagged stat-card's big number.
   Replaces the sparkline when any tier is non-zero — the per-tier
   count is a better "is this an emergency" signal than a 14-day
   shape line. Wraps onto two lines on narrow cards without forcing
   horizontal scroll. */
.stat-severity {
  display: flex;
  flex-wrap: wrap;
  gap: 0.3rem;
  margin-top: 0.45rem;
}
.stat-severity .severity-chip {
  font-size: 0.66em;
  padding: 0.18rem 0.5rem;
}
article.stat-card-flag {
  border-left: 4px solid var(--pico-del-color, #c0392b);
}
article.stat-card-flag strong {
  color: var(--pico-del-color, #c0392b);
}
article.stat-card-zero {
  border-left-color: var(--pico-muted-border-color);
}
article.stat-card-zero strong {
  color: var(--pico-muted-color);
}

/* Flagged row tinting in the main sessions table — a faint left accent
   so a reviewer scanning the list can spot risky sessions without
   reading the Risk column. Using the same del-colour as the Needs-review
   panel so the two signals share a visual language. */
tr.row-flagged td:first-child {
  border-left: 3px solid var(--pico-del-color, #c0392b);
}
/* Severity-keyed row accents — override the generic red row-flagged
   accent when a specific tier class is present. A CISO scanning the
   table can triage top-down: reds first, oranges next, yellows as
   time allows. Matches the severity-chip palette one-for-one. */
tr.row-severity-critical td:first-child {
  border-left: 3px solid var(--severity-critical-fg);
}
tr.row-severity-high td:first-child {
  border-left: 3px solid var(--severity-high-fg);
}
tr.row-severity-medium td:first-child {
  border-left: 3px solid var(--severity-medium-fg);
}
tr.row-severity-low td:first-child {
  border-left: 3px solid var(--severity-low-fg);
}
/* Reviewed row — replaces the red accent with a muted green so a
   reviewed-but-still-risky session reads as "handled" not "pending",
   without disappearing from the list entirely. */
tr.row-reviewed td:first-child {
  border-left: 3px solid rgba(109, 223, 159, 0.5);
}
tr.row-reviewed {
  opacity: 0.72;
}
.reviewed-tag {
  color: #6ddf9f;
  margin-left: 0.35rem;
  font-weight: 700;
}

/* Risk badges in the session table. Kept small; they share the pill
   shape used elsewhere so the page doesn't feel like four different
   designers worked on it. */
mark.risk {
  display: inline-block;
  padding: 0.05rem 0.4rem;
  margin-right: 0.25rem;
  border-radius: 4px;
  font-size: 0.75em;
  text-transform: uppercase;
  letter-spacing: 0.03em;
}
/* Explicit colours (not Pico vars) — Pico's dark-theme
   --pico-mark-background-color is actually dark blue, which renders
   dark-on-dark and is unreadable. Hard-coding keeps the signal
   consistent across themes. */
mark.risk-sensitive {
  background: #c0392b;
  color: #fff;
}
mark.risk-shell {
  background: #d9822b;
  color: #fff;
}
small.risk-network {
  color: var(--pico-muted-color);
  font-size: 0.8em;
}

/* Per-process activity in the tree. proc-counts is the inline summary
   line ("32 opens · 4 writes · 2 DNS") so a reviewer gets a read on
   each node at a glance without expanding anything. The proc-activity
   <details> is the drill-down when they want the actual paths. */
.proc-counts {
  display: flex;
  flex-wrap: wrap;
  gap: 0.6rem;
  margin: 0.2rem 0 0 0;
  font-size: 0.85em;
  color: var(--pico-muted-color);
}
.proc-counts .proc-count strong {
  color: var(--pico-color);
  font-weight: 600;
  margin-right: 0.2rem;
}
.proc-counts .proc-count-flag strong {
  color: var(--pico-del-color, #c0392b);
}

.proc-activity {
  margin-top: 0.25rem;
}
.proc-activity summary {
  color: var(--pico-muted-color);
  cursor: pointer;
}
.proc-activity-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: 0.5rem 1rem;
  margin-top: 0.4rem;
}
.proc-activity-grid table {
  margin: 0.2rem 0 0 0;
  font-size: 0.85em;
}
.proc-activity-grid table td:last-child {
  text-align: right;
  color: var(--pico-muted-color);
}

/* Badges on the process-tree row: root (existing neutral), shell, and
   sensitive. Hard-coded colours because Pico's dark theme maps
   --pico-mark-background-color to a dark blue — readable in light
   mode, invisible here. */
.proc mark {
  background: #6b7280; /* slate — neutral "root" badge */
  color: #fff;
}
.proc mark.shell {
  background: #d9822b;
  color: #fff;
}
.proc mark.sensitive {
  background: #c0392b;
  color: #fff;
}
.proc.proc-flagged {
  border-left: 2px solid var(--pico-del-color, #c0392b);
  padding-left: 0.5rem;
}

/* Session timeline — shorter, denser cousin of the trend chart. One
   bar per time bin across a session's [first, last] window; the
   sensitive overlay reuses the same red as the rest of the risk
   language. Ticks sit inside the viewBox so the article stays the
   height it reserved. */
article.timeline {
  margin: 1.75rem 0 1rem 0;
  padding: 0.75rem 1rem 0.5rem 1rem;
  background: var(--pico-card-background-color);
  border: 1px solid var(--pico-muted-border-color);
  border-radius: var(--pico-border-radius);
}
article.timeline header {
  margin-bottom: 0.25rem;
  padding: 0;
  border: none;
  background: transparent;
}
article.timeline header small { color: var(--pico-muted-color); }
article.timeline svg {
  display: block;
  width: 100%;
  max-width: 640px;
  height: auto;
  aspect-ratio: 10 / 1;
}
article.timeline .timeline-axis {
  stroke: var(--pico-muted-border-color);
  stroke-width: 1;
}
article.timeline .timeline-bar {
  fill: var(--pico-primary);
  opacity: 0.7;
}
article.timeline .timeline-bar-sensitive {
  fill: #c0392b;
}
article.timeline .timeline-tick {
  fill: var(--pico-muted-color);
  font-size: 9px;
  font-family: var(--pico-font-family);
}

/* Hero stat cards — the top row of the landing page. Each card is
   "big number" + a small 14-day sparkline pinned to the bottom so the
   cards keep a uniform height regardless of whether the sparkline
   renders. display:flex with space-between pushes label to the top,
   number to the middle, trend to the bottom.
   align-items:stretch + height:100% on the row ensures the Flagged
   card (which is tallest, having severity chips + review-posture row)
   doesn't leave a shorter Probes / Sessions / Sensitive card dangling
   beneath it. Without this, the Probes <a> card was sizing to its own
   content and hanging into the Agents panel below. */
.hero-stats {
  align-items: stretch;
  /* Clear air between the hero row and the fleet-breakdown panels
     below. Without this the bottom edge of the hero tiles sits
     directly against the "AGENTS / TOP SENSITIVE PATHS / TOP
     SUSPICIOUS HOSTS" headers, which reads as the hero row
     blending into the breakdown row — the two are conceptually
     different (hero = fleet macro, breakdown = triage-queue
     micro) and should read as different bands. */
  margin-bottom: 1.25rem;
}
.hero-stats article.stat-card {
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  min-height: 110px;
  height: 100%;
  padding: 0.75rem 1rem;
}
.hero-stats article.stat-card header {
  margin: 0;
  padding: 0;
  border: none;
  background: transparent;
}
.hero-stats article.stat-card header small {
  color: var(--pico-muted-color);
  font-size: 0.75em;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  font-weight: 500;
}
.hero-stats article.stat-card strong {
  display: block;
  font-size: 2.1rem;
  line-height: 1.1;
  font-weight: 600;
  margin: 0.2rem 0 0.1rem 0;
  font-variant-numeric: tabular-nums;
}
.hero-stats .spark-subtitle {
  color: var(--pico-muted-color);
  font-size: 0.75em;
}

/* Link variant of a stat card — used on the landing-page Hosts tile
   to send the reviewer into /ui/probes. We let Pico's <a> styles
   handle colour inheritance, then re-apply the card's flex layout
   (none of the base .stat-card rules target <a>, they all qualify on
   article.stat-card) and strip the default link underline/decoration
   so the card reads as a card, not as a link-in-a-card. */
.hero-stats a.stat-card,
a.stat-card-link {
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  min-height: 110px;
  height: 100%;
  padding: 0.75rem 1rem;
  border: var(--pico-border-width, 1px) solid var(--pico-card-border-color, rgba(115, 130, 140, 0.2));
  border-radius: var(--pico-border-radius, 0.5rem);
  background: var(--pico-card-background-color, transparent);
  color: inherit;
  text-decoration: none;
  transition: border-color 0.15s, transform 0.15s;
}
.hero-stats a.stat-card header,
a.stat-card-link header {
  margin: 0;
  padding: 0;
  border: none;
  background: transparent;
}
.hero-stats a.stat-card header small,
a.stat-card-link header small {
  color: var(--pico-muted-color);
  font-size: 0.75em;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  font-weight: 500;
}
.hero-stats a.stat-card strong,
a.stat-card-link strong {
  display: block;
  font-size: 2.1rem;
  line-height: 1.1;
  font-weight: 600;
  margin: 0.2rem 0 0.1rem 0;
  font-variant-numeric: tabular-nums;
}
.hero-stats a.stat-card:hover,
a.stat-card-link:hover {
  border-color: var(--pico-primary);
  transform: translateY(-1px);
}
/* The stat-card-flag border-left applies to <article> only in the
   existing rule — repeat it for the link variant so a silent-probe
   count on the Hosts card gets the same red accent treatment as the
   Flagged-sessions card. */
a.stat-card-link.stat-card-flag {
  border-left: 4px solid var(--pico-del-color, #c0392b);
}
a.stat-card-link.stat-card-flag strong {
  color: var(--pico-del-color, #c0392b);
}
.probe-silent-subtitle {
  color: var(--pico-del-color, #c0392b) !important;
  font-weight: 600;
}
/* Tiny status dot that sits before the Probes stat-card subtitle so
   the card has a bottom-edge visual accent matching the sparklines on
   its peer cards — without it the probes tile reads as "empty" next
   to the neighbouring Sessions / Flagged / Sensitive cards and visually
   bleeds into whatever sits below it. */
.probe-pip {
  display: inline-block;
  width: 0.5rem;
  height: 0.5rem;
  border-radius: 50%;
  margin-right: 0.4rem;
  vertical-align: baseline;
}
.probe-pip-ok {
  background: #27ae60;
  box-shadow: 0 0 0 2px color-mix(in srgb, #27ae60 22%, transparent);
}
.probe-pip-alert {
  background: var(--pico-del-color, #c0392b);
  box-shadow: 0 0 0 2px color-mix(in srgb, var(--pico-del-color, #c0392b) 22%, transparent);
}

/* All-sessions page — dedicated browse view for the full fleet. Hero
   mirrors the probes page so the two "listing" pages share shape. */
.all-sessions-hero { margin-bottom: 1.25rem; }
.all-sessions-title { margin: 0.25rem 0 0.4rem 0; }
.all-sessions-subtitle { color: var(--pico-muted-color); max-width: 70ch; }
.all-sessions-count { margin: 0.5rem 0 0.75rem 0; color: var(--pico-muted-color); }

/* Activity-tab summary strip. One line of tallies under the hero so
   the reviewer reads the rough shape of the session before scrolling
   into the per-category cards below. Subdued tone — this is context,
   not a headline. */
.activity-summary {
  margin: 0.25rem 0 1rem 0;
  padding: 0.4rem 0.75rem;
  border-left: 3px solid var(--pico-muted-border-color, #333);
  color: var(--pico-muted-color);
  line-height: 1.5;
}
.activity-summary strong {
  color: var(--pico-color);
  font-variant-numeric: tabular-nums;
}

/* Dashboard CTA linking to the full list — quiet button sitting
   under the fleet panels so a reviewer who finished triage has an
   obvious "now browse everything" next step without a second scan. */
.dashboard-allsessions-cta {
  margin: 1.25rem 0 0 0;
  text-align: right;
}

/* Probe health panel — the /ui/probes page. Table with a status chip
   in col 1 (silent rows float to the top via sort, so the chip column
   is the scan axis). The chip shares pill geometry with risk-chip but
   uses its own colour palette: green/amber/red for fresh/stale/silent. */
.probes-hero { margin-bottom: 1.5rem; }
.probes-title { margin: 0.25rem 0 0.4rem 0; }
.probes-subtitle { color: var(--pico-muted-color); max-width: 70ch; }
.probes-stats { margin-bottom: 1.5rem; }
.probes-table-wrap { overflow-x: auto; }
.probes-table {
  width: 100%;
  border-collapse: collapse;
}
.probes-table th, .probes-table td {
  padding: 0.55rem 0.75rem;
  border-bottom: 1px solid var(--pico-card-border-color, rgba(115, 130, 140, 0.15));
  text-align: left;
  vertical-align: middle;
}
.probes-table th {
  font-size: 0.75em;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--pico-muted-color);
  font-weight: 500;
}
.probe-chip {
  display: inline-block;
  padding: 0.15rem 0.6rem;
  border-radius: 999px;
  font-size: 0.72em;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  font-weight: 600;
  color: #fff;
}
.probe-chip-fresh  { background: var(--status-success); }
.probe-chip-stale  { background: var(--status-warning); }
.probe-chip-silent { background: var(--status-danger); }
.probe-row-silent td:first-child {
  border-left: 3px solid var(--status-danger);
}
.probe-row-stale td:first-child {
  border-left: 3px solid var(--status-warning);
}
.probe-silence { font-variant-numeric: tabular-nums; }
.probe-silence-fresh { color: var(--pico-muted-color); }
.probe-recent { color: var(--pico-muted-color); margin-left: 0.35rem; }
.probe-noname { color: var(--pico-muted-color); font-style: italic; }
.probe-agents code.probe-agent {
  font-size: 0.85em;
  padding: 0.05rem 0.35rem;
  background: var(--pico-code-background-color, rgba(115, 130, 140, 0.08));
  border-radius: 4px;
  margin-right: 0.2rem;
}

/* Sparkline SVG — fills the width of the card at a fixed height, no
   aspect-ratio constraint since the stretching is fine here (the
   shape is the signal, not the exact slope). spark-neutral uses
   pico-primary for the non-risk metrics; spark-flag uses the same
   del-red as the rest of the risk visual language. */
svg.sparkline {
  display: block;
  width: 100%;
  height: 32px;
  margin-top: 0.5rem;
}
svg.sparkline .sparkline-line {
  fill: none;
  stroke-width: 1.5;
  vector-effect: non-scaling-stroke;
}
svg.sparkline .sparkline-area {
  stroke: none;
  opacity: 0.22;
}
svg.sparkline.spark-neutral .sparkline-line  { stroke: var(--pico-primary); }
svg.sparkline.spark-neutral .sparkline-area  { fill:   var(--pico-primary); }
svg.sparkline.spark-flag    .sparkline-line  { stroke: #c0392b; }
svg.sparkline.spark-flag    .sparkline-area  { fill:   #c0392b; }

/* Fleet-level breakdown row — the two-column strip between the hero
   cards and the Needs-review panel. Agents on the left, sensitive
   paths on the right; each side is its own article so Pico's card
   chrome gives them matching backgrounds + borders. */
.fleet-grid {
  margin: 0 0 1rem 0;
}
.fleet-grid article {
  margin: 0;
  padding: 0.75rem 1rem;
}
.fleet-grid article header {
  margin-bottom: 0.5rem;
  padding: 0;
  border: none;
  background: transparent;
}
.fleet-grid article header small { color: var(--pico-muted-color); }

/* Per-agent tiles. Horizontal flex grid so N agents wrap cleanly at
   narrow widths. Each tile carries a left accent that goes red when
   the agent has any flagged sessions — same vocabulary as the
   row-flagged accent in the main table. */
.agent-tiles {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
}
.agent-tile {
  flex: 1 1 180px;
  min-width: 160px;
  padding: 0.6rem 0.75rem;
  background: var(--pico-background-color);
  border: 1px solid var(--pico-muted-border-color);
  border-left: 3px solid var(--pico-muted-border-color);
  border-radius: var(--pico-border-radius);
}
.agent-tile-flag {
  border-left-color: #c0392b;
}
.agent-tile .agent-name {
  margin-bottom: 0.3rem;
  font-size: 0.9em;
}
.agent-tile .agent-name code {
  background: transparent;
  padding: 0;
  font-weight: 600;
}
.agent-tile .agent-metrics {
  display: flex;
  align-items: baseline;
  gap: 0.4rem;
  flex-wrap: wrap;
}
.agent-tile .agent-count {
  font-size: 1.4rem;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}
.agent-tile .agent-label { color: var(--pico-muted-color); }
.agent-tile .agent-flag {
  margin-left: auto;
  padding: 0.1rem 0.4rem;
  border-radius: 4px;
  background: rgba(192, 57, 43, 0.18);
  color: #e27063;
  font-size: 0.75em;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.03em;
}
.agent-tile .agent-lastseen {
  display: block;
  margin-top: 0.3rem;
  color: var(--pico-muted-color);
  font-size: 0.75em;
}

/* Top sensitive paths panel. Tight table — the interesting columns
   are path, hit count, and distinct-host count (the cross-machine
   signal). Multi-host rows get a subtle red tint as the "this touched
   more than one machine" affordance. */
.sensitive-panel table {
  margin: 0;
  font-size: 0.9em;
}
.sensitive-panel table code.wrap {
  font-size: 0.9em;
}
.sensitive-panel table td:nth-child(3),
.sensitive-panel table td:nth-child(4),
.sensitive-panel table th:nth-child(3),
.sensitive-panel table th:nth-child(4) {
  text-align: right;
  width: 4rem;
  font-variant-numeric: tabular-nums;
}
.sensitive-panel tr.multi-host td:first-child {
  border-left: 2px solid #c0392b;
}
.sensitive-panel.sensitive-panel-empty p {
  margin: 0;
  color: var(--pico-muted-color);
}

/* Dashboard filter bar — sits above the "All sessions" table. Labels
   stack above their <select> so the bar reads like a form row at a
   glance. The search input grows to fill remaining space so a reviewer
   can type a long fragment without the field feeling cramped. */
form.dashboard-filters {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem 1rem;
  align-items: flex-end;
  margin: 0 0 1rem 0;
}
form.dashboard-filters label {
  display: flex;
  flex-direction: column;
  gap: 0.2rem;
  margin: 0;
  font-size: 0.85em;
}
form.dashboard-filters label small {
  color: var(--pico-muted-color);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  font-size: 0.75em;
}
form.dashboard-filters select,
form.dashboard-filters input[type="search"] {
  margin: 0;
  min-width: 10rem;
  padding: 0.4rem 0.6rem;
  font-size: 0.9em;
}
form.dashboard-filters label.dashboard-search {
  flex: 1 1 16rem;
}
form.dashboard-filters label.dashboard-search input[type="search"] {
  width: 100%;
}
a.dashboard-clear,
a.dashboard-export {
  align-self: flex-end;
  margin: 0;
  padding: 0.35rem 0.8rem;
  font-size: 0.85em;
}
a.dashboard-export {
  margin-left: auto;
}

/* Top-of-dashboard tabbed tools panel — Search events / Search
   ancestry / Compare. Tab buttons sit flush to the bottom of a card
   edge so the active tab appears to belong to the panel below; the
   panel itself is a bordered card holding one form at a time. The
   tabs are keyboard-navigable (ArrowLeft/Right) via the JS in
   initDashboardTabs; visuals here mirror Pico's form field styling
   so the inputs blend with the rest of the app. */
section.dashboard-tools {
  margin: 0 0 1.25rem 0;
}
.tools-tablist {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: 0.15rem;
  margin: 0;
  padding: 0;
  border-bottom: 1px solid var(--pico-card-border-color, var(--pico-muted-border-color));
}
/* Shortcut hint chip. Sits at the right edge of the tablist as a
   terse kbd badge so the "/" shortcut is discoverable without a
   help menu. Muted by default so it doesn't compete with the active
   tab; still readable. */
/* Search sub-tabs. Shared strip at the top of /ui/search, /ui/ancestry,
   /ui/diff so the three related hunt tools read as one product surface
   (sidebar has one "Search" entry; these tabs are the within-surface
   navigation). Icon + label per tab, pill-shaped active item, muted
   inactive. Matches the session-tabs language used inside a session so
   sub-nav feels familiar across the app. */
.searchtabs {
  display: flex;
  justify-content: flex-start;
  gap: 0.25rem;
  margin: 0 0 1.25rem 0;
  border-bottom: 1px solid var(--pico-muted-border-color);
}
a.searchtab {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0.5rem 0.9rem;
  color: var(--pico-muted-color);
  font-size: 0.9em;
  font-weight: 500;
  text-decoration: none;
  border-bottom: 2px solid transparent;
  margin-bottom: -1px;
  transition: color 120ms ease, border-color 120ms ease;
}
a.searchtab .sidebar-icon {
  width: 14px;
  height: 14px;
}
a.searchtab:hover {
  color: var(--pico-color);
}
a.searchtab.searchtab-active {
  color: var(--pico-color);
  border-bottom-color: var(--pico-primary);
  font-weight: 600;
}
a.searchtab.searchtab-active .sidebar-icon {
  color: var(--pico-primary);
}

.tools-shortcut-hint {
  margin: 0 0.25rem 0.25rem auto;
  padding: 0.1rem 0.45rem;
  font-size: 0.72em;
  font-family: var(--font-mono, ui-monospace, Cascadia Code, Consolas, monospace);
  color: var(--pico-muted-color);
  background: var(--pico-code-background-color, transparent);
  border: 1px solid var(--pico-muted-border-color);
  border-radius: 0.25rem;
  line-height: 1;
  pointer-events: none;
  user-select: none;
}
button.tools-tab {
  margin: 0;
  padding: 0.4rem 0.85rem;
  border: 1px solid transparent;
  border-bottom: none;
  border-radius: var(--pico-border-radius, 0.375rem) var(--pico-border-radius, 0.375rem) 0 0;
  background: transparent;
  color: var(--pico-muted-color);
  font-size: 0.82em;
  font-weight: 500;
  line-height: 1.2;
  cursor: pointer;
  transform: translateY(1px); /* so active tab's bottom edge overlaps the panel's top border */
}
button.tools-tab:hover {
  color: var(--pico-color);
}
button.tools-tab:focus-visible {
  outline: 2px solid var(--pico-primary-focus, var(--pico-primary));
  outline-offset: 2px;
}
button.tools-tab.tools-tab-active {
  background: var(--pico-card-background-color, var(--pico-background-color, transparent));
  border-color: var(--pico-card-border-color, var(--pico-muted-border-color));
  border-bottom-color: var(--pico-card-background-color, var(--pico-background-color, transparent));
  color: var(--pico-color);
}
.tools-panel {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem 0.75rem;
  align-items: flex-end;
  margin: 0;
  padding: 0.65rem 0.8rem;
  border: 1px solid var(--pico-card-border-color, var(--pico-muted-border-color));
  border-top: none;
  border-radius: 0 var(--pico-border-radius, 0.375rem) var(--pico-border-radius, 0.375rem) var(--pico-border-radius, 0.375rem);
  background: var(--pico-card-background-color, transparent);
}
.tools-panel[hidden] {
  display: none;
}
.tools-field {
  display: flex;
  flex-direction: column;
  gap: 0.2rem;
  margin: 0;
  font-size: 0.85em;
}
.tools-field-grow {
  flex: 1 1 16rem;
}
.tools-field small {
  color: var(--pico-muted-color);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  font-size: 0.75em;
}
.tools-field input[type="search"],
.tools-field select {
  margin: 0;
  padding: 0.4rem 0.6rem;
  font-size: 0.9em;
  width: 100%;
}
/* Pico paints a magnifying-glass SVG into every input[type="search"]
   via background-image. At the tools-panel's compact sizing it reads
   as a stray circle floating over the placeholder, not a glyph — kill
   it here and let the placeholder text carry the affordance. Same
   override for the webkit cancel-button that appears once you type. */
.tools-field input[type="search"] {
  background-image: none;
  padding-left: 0.6rem;
}
.tools-field input[type="search"]::-webkit-search-cancel-button,
.tools-field input[type="search"]::-webkit-search-decoration {
  -webkit-appearance: none;
  appearance: none;
}
button.tools-submit,
a.tools-submit {
  margin: 0;
  padding: 0.4rem 1rem;
  font-size: 0.9em;
  align-self: flex-end;
}
.tools-empty {
  flex: 1 1 100%;
  margin: 0;
  color: var(--pico-muted-color);
}

/* Sortable column headers. cursor:pointer signals the click affordance;
   the arrow indicator only appears on the active column (aria-sort set
   by app.js) so inactive headers don't look like a legend. */
table[data-sortable] th.sortable-col {
  cursor: pointer;
  user-select: none;
}
table[data-sortable] th.sortable-col:hover {
  color: var(--pico-primary);
}
table[data-sortable] th[aria-sort="ascending"]::after {
  content: " ▲";
  font-size: 0.7em;
  color: var(--pico-primary);
}
table[data-sortable] th[aria-sort="descending"]::after {
  content: " ▼";
  font-size: 0.7em;
  color: var(--pico-primary);
}

/* Session totals — the big top-N grid that used to be the whole page.
   Now collapsed by default so the tree is the star of the show. */
details.session-totals {
  margin: 1rem 0;
  padding: 0.5rem 1rem;
  background: var(--pico-card-background-color);
  border: 1px solid var(--pico-muted-border-color);
  border-radius: var(--pico-border-radius);
}
details.session-totals summary {
  cursor: pointer;
  padding: 0.25rem 0;
}
details.session-totals > .grid {
  margin-top: 0.75rem;
}

/* Brand mark in the top nav. The SVG uses currentColor so it picks up
   the link colour; we just fix its size and give the wordmark a subtle
   weight contrast (bold "Sigtrace" company name + pill-tagged "AI.Trace"
   product) to avoid the flat all-bold look Pico gives <strong> by
   default. */
nav ul li a.brand {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  text-decoration: none;
}
.brand-logo {
  width: 30px;
  height: 30px;
  color: var(--pico-primary);
  flex-shrink: 0;
}

/* Two-face wordmark. Bahnschrift (Windows-native variable DIN-derived
   sans) carries "Sigtrace" (company) with a condensed semibold/bold
   profile — that's what makes the name stop reading as generic Segoe
   UI. "AI.Trace" (product line) is Cascadia Code (Windows-native tech
   mono), uppercase and wide-tracked, sitting as a smaller tag pill
   next to it. The contrast between the two faces is the whole design;
   it lets the mark read instantly as a techy/observability product
   rather than a plain sans wordmark. Fallbacks degrade cleanly to
   Segoe UI Variable + Consolas on older/other systems. */
.brand-word {
  display: inline-flex;
  align-items: baseline;
  gap: 0.55rem;
  color: var(--pico-color);
  line-height: 1;
}
.brand-name {
  font-family: "Bahnschrift", "Segoe UI Variable Display", Inter, system-ui, -apple-system, sans-serif;
  font-stretch: 85%;
  font-weight: 700;
  font-size: 1.4rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--pico-color);
}
/* Colour-split: SIG (the signal prefix) in neutral white, TRAP (the
   action the product performs) in primary. The break lands exactly
   where the real-world signal name breaks — SIG- prefix + the verb —
   so the typography carries meaning, not just contrast. */
.brand-name-prefix {
  color: var(--pico-color);
  opacity: 0.92;
}
.brand-name-verb {
  color: var(--pico-primary);
}
.brand-line {
  font-family: var(--font-mono);
  font-size: 0.72rem;
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.24em;
  color: var(--pico-muted-color);
  padding: 0.15rem 0.45rem;
  border: 1px solid var(--pico-muted-border-color);
  border-radius: 3px;
  background: rgba(255, 255, 255, 0.02);
  position: relative;
  top: -1px;
}

/* Triage hero panel — the landing page's top-of-fold band. This is
   the piece that has to sell the page as "real product chrome" in the
   first glance. Techniques:
     - Bordered card with a 3px primary accent on the left (matches
       the section-heading language below it — one design system).
     - Radial primary-colour glow in the top-left corner, so the eye
       starts there and sweeps right through the content.
     - A faint dot-grid background on the right half (SVG-in-CSS via
       a tiled radial-gradient) — just enough texture to make the
       panel feel like instrumentation, not a blank rectangle.
     - Decorative hero-trace sparkline on the right, echoing the
       stat-card sparklines so the hero feels of-a-piece with the
       data below. */
.triage-hero {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 2rem;
  padding: 1.25rem 1.75rem;
  margin: 0 0 1.75rem 0;
  background:
    radial-gradient(circle at 0% 0%, rgba(var(--pico-primary-rgb, 52, 152, 219), 0.14) 0%, transparent 55%),
    radial-gradient(circle at 70% 50%, rgba(255, 255, 255, 0.02) 1px, transparent 1.5px) 0 0 / 14px 14px,
    linear-gradient(135deg, rgba(255, 255, 255, 0.015) 0%, transparent 60%),
    var(--pico-card-background-color);
  border: 1px solid var(--pico-muted-border-color);
  border-left: 3px solid var(--pico-primary);
  border-radius: var(--pico-border-radius);
  overflow: hidden;
}
.triage-hero::before {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 1px;
  background: linear-gradient(
    to right,
    var(--pico-primary) 0%,
    rgba(var(--pico-primary-rgb, 52, 152, 219), 0.2) 40%,
    transparent 80%
  );
  opacity: 0.6;
  pointer-events: none;
}
.triage-hero-body {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}
.triage-hero .eyebrow {
  font-family: var(--font-display);
  font-size: 0.78rem;
  font-weight: 700;
  letter-spacing: 0.26em;
  text-transform: uppercase;
  color: var(--pico-primary);
}
.triage-hero .subtitle {
  color: var(--pico-color);
  margin: 0;
  font-size: 1rem;
  max-width: 60ch;
  line-height: 1.45;
}

.triage-hero-side {
  display: flex;
  align-items: center;
  flex-shrink: 0;
}
.hero-trace {
  width: 220px;
  height: 52px;
  color: var(--pico-primary);
  opacity: 0.45;
}

@media (max-width: 768px) {
  .triage-hero {
    flex-direction: column;
    align-items: flex-start;
    gap: 1rem;
    padding: 1.1rem 1.25rem;
  }
  .hero-trace { width: 160px; height: 40px; }
}

/* Section headings — the uppercase labels above each major block
   (Agents, Top sensitive paths, Needs review, All sessions). The
   layout is: solid left accent bar + tight uppercase title + optional
   meta small + a hairline rule that extends across the remaining
   width. That trailing rule is the thing that makes this feel like an
   enterprise dashboard chrome rather than just styled text — it ties
   every section visually and fills the horizontal dead space that
   small headings leave behind. */
.section-heading {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  padding: 0.15rem 0 0.15rem 0.9rem;
  margin: 0 0 1rem 0;
  border-left: 3px solid var(--pico-primary);
  line-height: 1.2;
}
.section-heading .section-title {
  text-transform: uppercase;
  letter-spacing: 0.18em;
  font-family: var(--font-display);
  font-size: 0.78rem;
  font-weight: 700;
  color: var(--pico-color);
  white-space: nowrap;
}
.section-heading small {
  color: var(--pico-muted-color);
  text-transform: none;
  letter-spacing: 0;
  font-size: 0.78rem;
  white-space: nowrap;
}
.section-heading::after {
  content: "";
  flex: 1;
  height: 1px;
  min-width: 2rem;
  background: linear-gradient(to right, var(--pico-muted-border-color) 0%, var(--pico-muted-border-color) 30%, transparent 100%);
}
/* <h3> variant — kill default heading margins so the accent bar sits
   flush and we don't get double spacing above All sessions. */
h3.section-heading {
  margin: 2rem 0 1rem 0;
  font-weight: 700;
}
/* Alert tone — used for the Needs review header. Swaps the accent bar
   and title colour to the error red, and fades the trailing rule
   through red instead of neutral so the whole strip reads as a
   warning band. */
.section-heading-alert {
  border-left-color: var(--pico-del-color, #c0392b);
}
.section-heading-alert .section-title {
  color: var(--pico-del-color, #c0392b);
}
.section-heading-alert::after {
  background: linear-gradient(to right, rgba(192, 57, 43, 0.45) 0%, rgba(192, 57, 43, 0.15) 30%, transparent 100%);
}

/* Host-sessions heading — trade the trailing rule for an inline CSV
   export button. Suppressing ::after lets margin-left: auto on the
   button anchor it to the right edge of the heading strip. */
.host-sessions-heading {
  margin: 2rem 0 0.5rem 0;
}
.host-sessions-heading::after {
  content: none;
}
.host-sessions-heading a.dashboard-export {
  margin: 0 0 0 auto;
  align-self: center;
}

/* Empty state. First-run landing (zero rollups ever) and "filter
   matches nothing" both fall back to this block — centered, generous
   padding, a faint decorative sparkline at the top that animates a
   single dot so the page doesn't look frozen while we wait. The
   snippet inside is a concrete call to action: here's the exact HTTP
   shape a probe must send. */
.empty-state {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: 0.75rem;
  margin: 3rem auto;
  max-width: 640px;
  padding: 2.5rem 2rem;
  border: 1px solid var(--pico-muted-border-color);
  border-radius: 12px;
  background:
    radial-gradient(ellipse at top, rgba(var(--pico-primary-rgb, 1, 170, 255), 0.06), transparent 60%),
    var(--pico-card-background-color, transparent);
}
.empty-state-art {
  width: 160px;
  height: 48px;
  color: var(--pico-primary);
}
.empty-state h3 {
  margin: 0;
  font-family: var(--font-display, inherit);
  font-size: 1.25rem;
  font-weight: 700;
  letter-spacing: 0.02em;
}
.empty-state p {
  margin: 0;
  max-width: 48ch;
  color: var(--pico-muted-color);
}
.empty-state-snippet {
  width: 100%;
  margin: 0.5rem 0 0 0;
  padding: 1rem 1.25rem;
  text-align: left;
  font-family: var(--font-mono, ui-monospace, monospace);
  font-size: 0.85rem;
  line-height: 1.55;
  background: var(--pico-code-background-color, rgba(0, 0, 0, 0.2));
  border: 1px solid var(--pico-muted-border-color);
  border-radius: 8px;
  overflow-x: auto;
}
.empty-state-snippet code {
  background: transparent;
  padding: 0;
  color: var(--pico-color);
}
.empty-state-foot {
  color: var(--pico-muted-color);
  opacity: 0.8;
}
/* Two-path onboarding list — "install a probe" vs "try demo data". Ordered
   list because the two paths ARE ordered (install is the production path,
   demo is the fallback), but rendered with light typography so it reads as
   guidance rather than a procedure. Each li stacks a bold heading over a
   short explanation; a seed-dashboard snippet slots under path 2 inline. */
.empty-state-paths {
  width: 100%;
  margin: 0.5rem 0 0 0;
  padding: 0;
  list-style: none;
  counter-reset: empty-state-path;
  text-align: left;
  display: flex;
  flex-direction: column;
  gap: 1rem;
}
.empty-state-paths > li {
  counter-increment: empty-state-path;
  padding: 0.9rem 1rem 0.9rem 2.5rem;
  position: relative;
  border: 1px solid var(--pico-muted-border-color);
  border-radius: 8px;
  background: var(--pico-card-background-color, transparent);
}
.empty-state-paths > li::before {
  content: counter(empty-state-path);
  position: absolute;
  left: 0.9rem;
  top: 0.85rem;
  width: 1.25rem;
  height: 1.25rem;
  border-radius: 50%;
  background: var(--pico-primary);
  color: var(--pico-primary-inverse, #fff);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 0.75rem;
  font-weight: 700;
}
.empty-state-paths > li strong {
  display: block;
  margin-bottom: 0.15rem;
}
.empty-state-paths > li span {
  display: block;
  color: var(--pico-muted-color);
  font-size: 0.9rem;
}
.empty-state-paths .empty-state-snippet {
  margin-top: 0.6rem;
  font-size: 0.78rem;
}
/* Wire-format disclosure — collapsed by default so first-time operators
   aren't confronted with an HTTP shape they don't need. Expands on click
   for anyone writing a custom shipper. */
.empty-state-wire {
  width: 100%;
  margin-top: 0.5rem;
  text-align: left;
}
.empty-state-wire summary {
  cursor: pointer;
  color: var(--pico-muted-color);
  font-size: 0.85rem;
  padding: 0.25rem 0;
}
.empty-state-wire[open] summary {
  margin-bottom: 0.35rem;
}
/* Paragraphs inside the disclosure render left-aligned and at the
   same muted weight as the summary, so the probes-page troubleshooting
   notes don't inherit the centered-text layout of the surrounding
   empty-state card. */
.empty-state-wire p {
  max-width: none;
  margin: 0.35rem 0;
  text-align: left;
}
/* Filter-match-empty variant — tighter, no decorative art. */
.empty-state-filter {
  padding: 2rem;
  margin: 1.5rem auto;
}

/* Session hero. Cousin of the landing-page triage-hero but denser —
   a session page is answering specifics, not setting scene, so we
   load the card with the identifying meta (agent, host, duration,
   first/last seen) and push risk chips hard right so a reviewer's
   first glance lands on "is this session risky". */
.session-hero {
  position: relative;
  display: flex;
  flex-wrap: wrap;
  gap: 1rem 2rem;
  align-items: flex-start;
  justify-content: space-between;
  margin: 0.5rem 0 1.25rem 0;
  padding: 1.25rem 1.5rem;
  border: 1px solid var(--pico-muted-border-color);
  border-left: 3px solid var(--pico-primary);
  border-radius: 10px;
  background:
    radial-gradient(circle at 0% 0%, rgba(var(--pico-primary-rgb, 1, 170, 255), 0.08), transparent 45%),
    var(--pico-card-background-color, transparent);
}
/* Flagged session — left border swaps to the error red + a faint red
   glow takes over, so the card itself telegraphs risk before the
   reviewer reads a single word. */
.session-hero-flagged {
  border-left-color: var(--pico-del-color, #c0392b);
  background:
    radial-gradient(circle at 0% 0%, rgba(192, 57, 43, 0.08), transparent 45%),
    var(--pico-card-background-color, transparent);
}
.session-hero-body {
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
  min-width: 0;
  flex: 1 1 420px;
}
.session-hero-id {
  margin: 0;
  font-family: var(--font-display, inherit);
  font-size: 1.5rem;
  font-weight: 700;
  letter-spacing: 0.01em;
}
.session-hero-id code {
  background: transparent;
  padding: 0;
  color: var(--pico-color);
  font-family: var(--font-mono, ui-monospace, monospace);
}
.session-hero-meta {
  display: flex;
  flex-wrap: wrap;
  gap: 0.15rem 0.75rem;
  color: var(--pico-muted-color);
  font-size: 0.9em;
}
.session-hero-meta code {
  background: transparent;
  padding: 0;
  color: var(--pico-color);
}
.session-hero-agent strong {
  color: var(--pico-color);
  font-weight: 600;
}
.session-hero-risk {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem;
  flex-shrink: 0;
}
/* Risk chips on the session hero. Bigger than the table-row risk
   marks — this is the answer to "is this session worth looking at",
   not a compact sort key. strong holds the count; the label follows. */
.risk-chip {
  display: inline-flex;
  align-items: baseline;
  gap: 0.35rem;
  padding: 0.35rem 0.75rem;
  border-radius: 999px;
  font-size: 0.85em;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  font-weight: 600;
  color: #fff;
}
.risk-chip strong {
  font-size: 1.15em;
  font-weight: 700;
}
.risk-chip-sensitive { background: var(--status-danger); }
.risk-chip-shell     { background: var(--status-warning); }
.risk-chip-net {
  background: transparent;
  color: var(--pico-muted-color);
  border: 1px solid var(--pico-muted-border-color);
}
.risk-chip-clean {
  background: transparent;
  color: var(--status-success-fg);
  border: 1px solid var(--status-success-border);
}
/* Reviewed — greyed green so it reads as "acknowledged, handled" not
   "everything's fine". Separate tone from risk-chip-clean which only
   means "nothing flagged". */
.risk-chip-reviewed {
  background: var(--status-success-tint);
  color: var(--status-success-fg);
  border: 1px solid var(--status-success-border);
}

/* Severity chip — the 4-tier ladder chip. Compact tinted pill with
   coloured border + matching foreground. Unlike the category risk-
   chips above (which say *what kind* of signal), this chip answers
   *how bad* on a single traffic-light scale. Used on the session
   hero, session table rows, and the stat cards.

   We use tint + border + fg from the same palette so the chip reads
   at a glance without shouting — the row accent and stat-card border
   already paint strongly in the matching colour. */
.severity-chip {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  padding: 0.2rem 0.55rem;
  border-radius: 999px;
  font-size: 0.72em;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  line-height: 1;
  border: 1px solid transparent;
  white-space: nowrap;
}
.severity-chip::before {
  content: "";
  display: inline-block;
  width: 0.45rem;
  height: 0.45rem;
  border-radius: 50%;
  background: currentColor;
}
.severity-chip-critical {
  color: var(--severity-critical-fg);
  background: var(--severity-critical-tint);
  border-color: var(--severity-critical-border);
}
.severity-chip-high {
  color: var(--severity-high-fg);
  background: var(--severity-high-tint);
  border-color: var(--severity-high-border);
}
.severity-chip-medium {
  color: var(--severity-medium-fg);
  background: var(--severity-medium-tint);
  border-color: var(--severity-medium-border);
}
.severity-chip-low {
  color: var(--severity-low-fg);
  background: var(--severity-low-tint);
  border-color: var(--severity-low-border);
}
.severity-chip-none {
  color: var(--status-success-fg);
  background: var(--status-success-tint);
  border-color: var(--status-success-border);
}

/* Review form — the Mark reviewed / Undo buttons on the session hero.
   Pico's default button is way too big for an inline header
   affordance, so we shrink it to chip size and strip the default
   margin. The form itself is flex so button + chip sit inline. */
.rules-form-actions {
  display: flex;
  gap: 0.6rem;
  align-items: center;
  flex-wrap: wrap;
  margin-top: 0.5rem;
}
.rules-form-actions button {
  margin: 0;
  width: auto;
}
.rules-preview-card {
  margin-top: 1rem;
}
.rules-preview-card figcaption {
  margin-top: 0.5rem;
  color: var(--pico-muted-color);
}
.review-form {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  margin: 0;
}
.review-form button {
  margin: 0;
  padding: 0.35rem 0.9rem;
  font-size: 0.85em;
  font-weight: 600;
  line-height: 1;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  border-radius: 999px;
  width: auto;
}
.review-mark {
  background: var(--pico-primary);
  border-color: var(--pico-primary);
  color: #fff;
}
.review-undo {
  font-size: 0.75em;
  padding: 0.25rem 0.6rem;
}

/* Forensic-report link in the session hero. Sized to match the chip
   row so it lives comfortably alongside risk chips and the review
   button without stealing emphasis from either — but visible enough
   that a compliance reviewer lands on it without scrolling. */
.session-hero-report {
  margin: 0;
  padding: 0.35rem 0.9rem;
  font-size: 0.8em;
  font-weight: 600;
  line-height: 1;
  letter-spacing: 0.03em;
  border-radius: 999px;
  width: auto;
  white-space: nowrap;
  text-decoration: none;
}

/* Breadcrumb on session pages — tighten spacing so it doesn't take
   up visual real estate above the hero. */
nav.session-breadcrumb {
  margin-bottom: 0.25rem;
}
nav.session-breadcrumb ul {
  margin: 0;
  padding: 0;
}

/* Posted timestamp dropped to a muted footer line below the overview
   content — not top-of-page real estate, but still findable. */
.meta-foot {
  color: var(--pico-muted-color);
  font-size: 0.85em;
  text-align: right;
  margin: 0.5rem 0 0 0;
}

/* Bare-chrome main — used on pages rendered without the sidebar
   (login). Pico's .container default gutters are fine here; restore
   the centred max-width the sidebar layout overrides elsewhere. */
main.bare-main {
  max-width: var(--pico-container-max-width, 72rem);
  margin-left: auto;
  margin-right: auto;
}

/* Login page — centred, narrow card. Intentionally unstyled beyond
   what Pico gives us: nothing about this page should feel like the
   rest of the app, it's a thin gate. */
.login-wrap {
  max-width: 22rem;
  margin: 4rem auto 2rem auto;
}
.login-wrap hgroup { text-align: center; margin-bottom: 1.5rem; }
.login-wrap hgroup h1 { margin: 0.1rem 0 0.35rem; }
.login-wrap hgroup p { margin: 0; color: var(--pico-muted-color); font-size: 0.9em; }
.login-eyebrow {
  display: block;
  font-size: 0.72rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--pico-muted-color);
}
.login-foot { text-align: center; margin: 1rem 0 0; color: var(--pico-muted-color); }
.login-wrap .login-error {
  background: color-mix(in srgb, var(--pico-del-color, #b33) 15%, transparent);
  border: 1px solid var(--pico-del-color, #b33);
  padding: 0.5rem 0.75rem;
  font-size: 0.9em;
  margin-bottom: 1rem;
}
.login-form button { width: 100%; }

/* --- Session timeline (Timeline tab) ------------------------------------
   Vertical strip: a column of minute headers (HH:MM) down the left, with
   the events that happened in that minute listed to the right of each
   header. A thin vertical rule threads the minute labels together so the
   whole thing reads as one chronological spine. Per-event left border is
   coloured by category (files / network / processes) so a scroll gives
   an at-a-glance sense of "what kind of thing was happening when".
   Sensitive-path hits get a warn tint + left stripe to grab the eye. */

.timeline {
  list-style: none;
  padding: 0;
  margin: 1rem 0 2rem 0;
  position: relative;
}
.timeline::before {
  /* The spine — a faint vertical rule the minute markers sit on. */
  content: "";
  position: absolute;
  left: 3.25rem;
  top: 0;
  bottom: 0;
  width: 1px;
  background: var(--pico-muted-border-color, #333);
}
.timeline-day {
  list-style: none;
  margin: 1rem 0 0.5rem 0;
  padding-left: 5.5rem;
  font-size: 0.75rem;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--pico-muted-color, #888);
}
.timeline-day span {
  background: var(--pico-background-color, #0d0d0d);
  padding: 0 0.4rem;
}
.timeline-bucket {
  list-style: none;
  display: grid;
  grid-template-columns: 5.5rem 1fr;
  align-items: start;
  margin: 0.35rem 0;
}
.timeline-minute {
  position: relative;
  font-family: var(--pico-font-family-monospace, monospace);
  font-size: 0.85rem;
  color: var(--pico-muted-color, #888);
  padding-top: 0.1rem;
}
.timeline-minute::after {
  /* The dot where this minute meets the spine. */
  content: "";
  position: absolute;
  right: -0.3rem;
  top: 0.35rem;
  width: 0.55rem;
  height: 0.55rem;
  border-radius: 50%;
  background: var(--pico-muted-color, #888);
  box-shadow: 0 0 0 3px var(--pico-background-color, #0d0d0d);
}
.timeline-events {
  list-style: none;
  padding: 0;
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
}
.timeline-event {
  list-style: none;
  display: grid;
  grid-template-columns: 5rem 5.5rem auto 1fr auto auto;
  align-items: baseline;
  gap: 0.5rem;
  padding: 0.25rem 0.5rem;
  border-left: 3px solid var(--pico-muted-border-color, #333);
  background: color-mix(in srgb, var(--pico-card-background-color, #111) 60%, transparent);
  border-radius: 0 3px 3px 0;
  font-size: 0.9rem;
}
.timeline-event .timeline-time code {
  font-size: 0.8rem;
  color: var(--pico-muted-color, #888);
  background: transparent;
  padding: 0;
}
.timeline-event .timeline-type {
  font-family: var(--pico-font-family-monospace, monospace);
  font-size: 0.7rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--pico-muted-color, #888);
  padding: 0.05rem 0.35rem;
  border: 1px solid var(--pico-muted-border-color, #333);
  border-radius: 2px;
  white-space: nowrap;
}
.timeline-event .timeline-verb {
  color: var(--pico-color, #eee);
  white-space: nowrap;
}
.timeline-event .timeline-detail code {
  font-size: 0.82rem;
  color: var(--pico-muted-color, #aaa);
  background: transparent;
  padding: 0;
}
.timeline-event .timeline-pid {
  color: var(--pico-muted-color, #888);
  font-size: 0.75rem;
  white-space: nowrap;
}
.timeline-event .timeline-sensitive {
  background: color-mix(in srgb, var(--pico-del-color, #c06) 25%, transparent);
  color: var(--pico-del-color, #c06);
  padding: 0.05rem 0.4rem;
  border-radius: 2px;
  font-size: 0.7rem;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  font-weight: 600;
}

/* Category stripes — left border colour picks up the eventCategory()
   bucket. Order deliberately: files are the most common and read in a
   neutral blue; network lookups pop in cyan; process lifecycle events
   in green so the tree-like events feel like scaffolding; session
   start/end events have no category set so they fall through to the
   default muted border. */
.timeline-cat-files     { border-left-color: #3b82f6; }
.timeline-cat-network   { border-left-color: #06b6d4; }
.timeline-cat-processes { border-left-color: #10b981; }

/* Sensitive override: always wins regardless of category, because it's
   the one signal a reviewer most cares about. Combines a warn stripe
   with a subtle background tint so flagged rows stand out while
   scrolling past a wall of neutral file_opens. */
.timeline-flagged {
  border-left-color: var(--pico-del-color, #c06) !important;
  background: color-mix(in srgb, var(--pico-del-color, #c06) 8%, transparent);
}

/* Deep-link target: when the Overview sends the reviewer to a specific
   timeline event via #ev-<ts>, the :target selector fires and pulses
   the row so it's visible the moment the browser scrolls into place.
   No JS — the scroll-into-view is free from the fragment navigation,
   and CSS :target keeps the highlight until the reviewer clicks away. */
@keyframes timeline-target-pulse {
  0%   { box-shadow: 0 0 0 0 color-mix(in srgb, var(--pico-primary, #06b6d4) 55%, transparent); }
  70%  { box-shadow: 0 0 0 10px transparent; }
  100% { box-shadow: 0 0 0 0 transparent; }
}
.timeline-event:target {
  outline: 2px solid var(--pico-primary, #06b6d4);
  outline-offset: 2px;
  background: color-mix(in srgb, var(--pico-primary, #06b6d4) 14%, transparent);
  animation: timeline-target-pulse 1.4s ease-out 2;
  scroll-margin-top: 120px;
}
/* Flagged + :target combine — keep the red stripe but swap the pulse
   colour so it reads as "here's the sensitive event you clicked". */
.timeline-event.timeline-flagged:target {
  outline-color: var(--pico-del-color, #c06);
  background: color-mix(in srgb, var(--pico-del-color, #c06) 16%, transparent);
}

/* Narrow-viewport fallback: collapse the grid so long file paths have
   room to wrap on a phone-sized viewport without clipping the verb. */
@media (max-width: 640px) {
  .timeline-event {
    grid-template-columns: 1fr;
    gap: 0.15rem;
  }
}

/* Network tab: two tables of DNS queries + TCP connects. The tables
   are the reviewer's "who called what, how often" view — the cyan
   accent matches the timeline's network stripe so a reviewer
   jumping between Timeline and Network sees the same visual key.
   Wrapped in .network-table-wrap so very wide rows (multi-image
   columns on busy agents) scroll horizontally instead of breaking
   the card layout. */
.network-table-wrap {
  overflow-x: auto;
  margin-bottom: 1rem;
}
table.network-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.85rem;
}
table.network-table thead th {
  text-align: left;
  font-size: 0.75rem;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--pico-muted-color, #888);
  border-bottom: 1px solid var(--pico-muted-border-color, #333);
  padding: 0.3rem 0.5rem;
  font-weight: 500;
}
table.network-table tbody td {
  padding: 0.3rem 0.5rem;
  border-bottom: 1px solid color-mix(in srgb, var(--pico-muted-border-color, #333) 40%, transparent);
  vertical-align: top;
}
table.network-table tbody tr:hover td {
  background: color-mix(in srgb, #06b6d4 5%, transparent);
}
table.network-table td.net-count,
table.network-table th.net-count {
  text-align: right;
  font-variant-numeric: tabular-nums;
  width: 4rem;
}
/* Image and pid chips: compact, one per line so a row with N pids
   shows them stacked rather than spilling into an unreadable wrap.
   Matches the subdued code-chip look used elsewhere so they feel like
   attribution, not emphasis. */
code.net-image,
code.net-pid {
  display: inline-block;
  margin: 0 0.25rem 0.15rem 0;
  padding: 0.05rem 0.35rem;
  background: color-mix(in srgb, var(--pico-card-background-color, #111) 60%, transparent);
  border-radius: 2px;
  font-size: 0.78em;
}
code.net-image {
  color: var(--pico-color, #ddd);
}
code.net-pid {
  color: var(--pico-muted-color, #888);
}

/* Suspicious-host chip on the DNS row. Sits inline with the query
   code chip so the reason surfaces without demanding its own column.
   Row itself also picks up .row-flagged so the eye is already on it
   when the chip is read. */
.net-flag-chip {
  display: inline-block;
  margin-left: 0.4rem;
  padding: 0.05rem 0.4rem;
  border-radius: 2px;
  background: color-mix(in srgb, var(--pico-del-color, #c0392b) 22%, transparent);
  color: var(--pico-del-color, #c0392b);
  font-size: 0.7em;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  vertical-align: middle;
}

@media (max-width: 640px) {
  table.network-table thead {
    display: none;
  }
  table.network-table tbody td {
    display: block;
    border-bottom: none;
    padding: 0.1rem 0.5rem;
  }
  table.network-table tbody tr {
    display: block;
    padding: 0.4rem 0;
    border-bottom: 1px solid var(--pico-muted-border-color, #333);
  }
}

/* File-access heatmap. Each row is a directory cluster with a
   proportional intensity bar so the eye pre-attentively picks out
   the hot spots before reading any counts. Two-tone bar (subtle
   track + bright fill) matches the event-timeline strip on Overview
   so the two "intensity" visuals read as the same design language.
   Flagged directories (sensitive hits) get a red border + tint,
   overriding the neutral card chrome — the consistent promotion
   pattern used across the app (proc-flagged, timeline-flagged). */
ol.heatmap {
  list-style: none;
  padding: 0;
  margin: 0.5rem 0 1.5rem 0;
}
.heatmap-dir {
  padding: 0.6rem 0.75rem;
  margin-bottom: 0.45rem;
  border: 1px solid var(--pico-muted-border-color, #333);
  border-radius: 3px;
  background: color-mix(in srgb, var(--pico-card-background-color, #111) 40%, transparent);
}
.heatmap-dir-flagged {
  border-color: var(--pico-del-color, #c06);
  background: color-mix(in srgb, var(--pico-del-color, #c06) 6%, transparent);
}
.heatmap-row {
  display: grid;
  grid-template-columns: 8rem 1fr;
  gap: 0.75rem;
  align-items: center;
}
.heatmap-bar-wrap {
  height: 0.75rem;
  background: color-mix(in srgb, var(--pico-muted-color, #888) 15%, transparent);
  border-radius: 2px;
  overflow: hidden;
}
.heatmap-bar {
  height: 100%;
  background: linear-gradient(
    90deg,
    #3b82f6 0%,
    #06b6d4 60%,
    #10b981 100%
  );
  border-radius: 2px;
  transition: width 120ms ease-out;
}
.heatmap-dir-flagged .heatmap-bar {
  background: linear-gradient(
    90deg,
    var(--pico-del-color, #c06) 0%,
    #f59e0b 100%
  );
}
.heatmap-meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem;
  min-width: 0;
}
.heatmap-path {
  font-size: 0.85rem;
  flex: 1 1 auto;
  min-width: 0;
}
.heatmap-counts {
  display: inline-flex;
  flex-wrap: wrap;
  gap: 0.3rem;
  flex-shrink: 0;
}
.heatmap-count {
  font-size: 0.75rem;
  padding: 0.1rem 0.4rem;
  border-radius: 2px;
  background: color-mix(in srgb, var(--pico-card-background-color, #111) 70%, transparent);
  color: var(--pico-muted-color, #aaa);
}
.heatmap-count strong {
  color: var(--pico-color, #ddd);
}
.heatmap-count-muted {
  opacity: 0.65;
}
.heatmap-count-flag {
  background: color-mix(in srgb, var(--pico-del-color, #c06) 20%, transparent);
}
.heatmap-count-flag strong {
  color: var(--pico-del-color, #c06);
}

.heatmap-drill {
  margin-top: 0.4rem;
  padding-top: 0.3rem;
  border-top: 1px dashed color-mix(in srgb, var(--pico-muted-border-color, #333) 60%, transparent);
}
.heatmap-drill summary {
  cursor: pointer;
  color: var(--pico-muted-color, #888);
}
table.heatmap-files {
  width: 100%;
  border-collapse: collapse;
  margin-top: 0.3rem;
  font-size: 0.82rem;
}
table.heatmap-files thead th {
  text-align: left;
  font-size: 0.7rem;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--pico-muted-color, #888);
  border-bottom: 1px solid var(--pico-muted-border-color, #333);
  padding: 0.2rem 0.4rem;
  font-weight: 500;
}
table.heatmap-files thead th.num,
table.heatmap-files tbody td.num {
  text-align: right;
  font-variant-numeric: tabular-nums;
  width: 4rem;
}
table.heatmap-files tbody td {
  padding: 0.2rem 0.4rem;
  border-bottom: 1px solid color-mix(in srgb, var(--pico-muted-border-color, #333) 30%, transparent);
}
tr.heatmap-file-flagged td {
  background: color-mix(in srgb, var(--pico-del-color, #c06) 8%, transparent);
}
.heatmap-flag-chip {
  display: inline-block;
  margin-left: 0.3rem;
  padding: 0.05rem 0.35rem;
  background: var(--pico-del-color, #c06);
  color: #fff;
  border-radius: 2px;
  font-size: 0.7rem;
  text-transform: uppercase;
  letter-spacing: 0.05em;
}
.muted {
  color: var(--pico-muted-color, #888);
}

@media (max-width: 640px) {
  .heatmap-row {
    grid-template-columns: 1fr;
    gap: 0.3rem;
  }
}

/* Cross-session search page (/ui/search). The hero reuses the page-
   level breadcrumb + title pattern from the session subtabs so the
   chrome feels native rather than bolted-on. The search input is
   deliberately large and primary-coloured on focus: this page's only
   real UI is that input, so it earns the emphasis. */
.search-hero {
  margin-bottom: 1.5rem;
  padding: 1rem 0;
  border-bottom: 1px solid var(--pico-muted-border-color, #333);
}
.search-title {
  margin: 0.2rem 0 0.4rem 0;
  font-size: 1.5rem;
}
.search-subtitle {
  margin: 0 0 1rem 0;
  color: var(--pico-muted-color, #888);
  font-size: 0.9rem;
}
.search-form {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: 0.6rem;
}
.search-input-label {
  flex: 1 1 20rem;
  margin-bottom: 0;
}
.search-input-label input[type="search"] {
  font-size: 1.05rem;
  padding: 0.55rem 0.8rem;
  background-image: none;
}
.search-input-label input[type="search"]::-webkit-search-decoration,
.search-input-label input[type="search"]::-webkit-search-cancel-button {
  -webkit-appearance: none;
  appearance: none;
}
.search-form button[type="submit"] {
  margin-bottom: 0;
}
.search-form a[role="button"] {
  margin-bottom: 0;
}

/* Summary line under the form — one sentence, muted-card background
   so it reads as an info strip rather than another content block. */
.search-summary {
  margin-bottom: 1.2rem;
  padding: 0.55rem 0.85rem;
  background: color-mix(in srgb, var(--pico-card-background-color, #111) 70%, transparent);
  border-left: 3px solid var(--pico-primary, #06b6d4);
  border-radius: 2px;
  font-size: 0.9rem;
}
.search-summary code {
  background: transparent;
  padding: 0 0.15rem;
  color: var(--pico-primary, #06b6d4);
}

/* Results list. Each session is a card with header (id + agent +
   count) + category chips + sample rows. Numbered <ol> so a reviewer
   can reference "the third result" verbally. */
ol.search-results {
  list-style: none;
  counter-reset: sr;
  padding: 0;
  margin: 0;
  display: grid;
  gap: 0.8rem;
}
ol.search-results li.search-result {
  counter-increment: sr;
  position: relative;
  padding: 0.8rem 1rem 0.8rem 2.4rem;
  background: var(--pico-card-background-color, #111);
  border: 1px solid var(--pico-muted-border-color, #333);
  border-radius: 4px;
}
ol.search-results li.search-result::before {
  content: counter(sr);
  position: absolute;
  left: 0.8rem;
  top: 0.8rem;
  font-family: var(--pico-font-family-monospace, monospace);
  font-size: 0.85rem;
  color: var(--pico-muted-color, #888);
  font-variant-numeric: tabular-nums;
}
.search-result-head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 1rem;
  flex-wrap: wrap;
  margin-bottom: 0.4rem;
}
.search-result-id a {
  text-decoration: none;
}
.search-result-id code {
  font-size: 0.95rem;
}
.search-result-agent,
.search-result-host {
  color: var(--pico-muted-color, #888);
  margin-left: 0.4rem;
}
.search-result-meta {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 0.1rem;
}
.search-result-count {
  color: var(--pico-primary, #06b6d4);
  font-variant-numeric: tabular-nums;
}
.search-result-when {
  color: var(--pico-muted-color, #888);
  font-variant-numeric: tabular-nums;
}

/* Category chips — fixed 4-5 colours so each category reads the same
   across every result card. Each chip is count + label, colour-coded
   so a reviewer scanning down the list can spot "all DNS" vs "all
   file" patterns without reading the text. */
.search-result-cats {
  display: flex;
  flex-wrap: wrap;
  gap: 0.35rem;
  margin-bottom: 0.6rem;
}
.search-cat {
  padding: 0.1rem 0.5rem;
  border-radius: 10px;
  font-size: 0.75rem;
  background: color-mix(in srgb, var(--pico-muted-color, #888) 15%, transparent);
  color: var(--pico-muted-color, #888);
}
.search-cat strong {
  color: inherit;
  margin-right: 0.2rem;
}
.search-cat-process { background: color-mix(in srgb, #a78bfa 20%, transparent); color: #c4b5fd; }
.search-cat-file    { background: color-mix(in srgb, #06b6d4 20%, transparent); color: #67e8f9; }
.search-cat-dns     { background: color-mix(in srgb, #22c55e 20%, transparent); color: #86efac; }
.search-cat-network { background: color-mix(in srgb, #f59e0b 20%, transparent); color: #fcd34d; }

/* Sample rows — each one is a whole-row click target that deep-links
   to the matching event on the session's Events tab. Field pill names
   *why* the event matched (path / image / cmdline / query / daddr) so
   a reviewer knows what field the query hit without having to re-parse
   the snippet. */
ul.search-samples {
  list-style: none;
  padding: 0;
  margin: 0 0 0.3rem 0;
  display: grid;
  gap: 0.2rem;
}
a.search-sample-link {
  display: grid;
  grid-template-columns: 5rem 1fr auto;
  gap: 0.5rem;
  align-items: baseline;
  padding: 0.25rem 0.5rem;
  border-radius: 3px;
  color: inherit;
  text-decoration: none;
  border: 1px solid transparent;
}
a.search-sample-link:hover {
  background: color-mix(in srgb, var(--pico-primary, #06b6d4) 8%, transparent);
  border-color: color-mix(in srgb, var(--pico-primary, #06b6d4) 30%, transparent);
}
.search-sample-field {
  font-size: 0.7rem;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--pico-muted-color, #888);
  background: color-mix(in srgb, var(--pico-card-background-color, #111) 60%, transparent);
  padding: 0.05rem 0.35rem;
  border-radius: 2px;
  text-align: center;
}
code.search-sample-snippet {
  font-size: 0.82rem;
  color: var(--pico-color, #ddd);
  background: transparent;
  padding: 0;
  word-break: break-all;
}
.search-sample-when {
  color: var(--pico-muted-color, #888);
  font-variant-numeric: tabular-nums;
}
.search-sample-more {
  display: block;
  margin-top: 0.3rem;
  color: var(--pico-muted-color, #888);
  font-style: italic;
}

/* Mode-hint line under the search form — the "try the other search
   mode" nudge that links between /ui/search and /ui/ancestry. Muted
   so it doesn't compete with the primary form. */
.search-mode-hint {
  margin-top: 0.5rem;
  margin-bottom: 0;
  color: var(--pico-muted-color, #888);
}

/* Ancestry search — two-input form needs a sane layout. Grid with
   auto-sized columns so the image + ancestor inputs sit side-by-side
   on desktop and stack on narrow screens (handled by the same
   @media block below). */
.ancestry-form {
  display: grid;
  grid-template-columns: 1fr 1fr auto auto;
  gap: 0.6rem;
  align-items: end;
}

/* Ancestry match list — one process per <li>, each rendering the
   spawn chain as "root › a › b › self". Chain wraps naturally; pid
   + cmdline fall on a second muted line so a reviewer scanning 20
   matches can eyeball the chains without a second click. */
.ancestry-matches {
  list-style: none;
  padding: 0;
  margin: 0.5rem 0 0;
  display: grid;
  gap: 0.35rem;
}
.ancestry-match-link {
  display: block;
  padding: 0.4rem 0.6rem;
  border: 1px solid var(--pico-muted-border-color, #333);
  border-radius: 0.3rem;
  text-decoration: none;
  color: inherit;
}
.ancestry-match-link:hover {
  border-color: var(--pico-primary, #01aaff);
}
.ancestry-chain {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.25rem 0.4rem;
}
.ancestry-sep {
  color: var(--pico-muted-color, #888);
  font-weight: 600;
}
.ancestry-step {
  font-size: 0.85em;
  padding: 0.1rem 0.4rem;
  border-radius: 0.25rem;
  background: rgba(255, 255, 255, 0.05);
}
.ancestry-step-root {
  background: rgba(1, 170, 255, 0.15);
  color: var(--pico-primary, #01aaff);
}
.ancestry-step-self {
  background: rgba(192, 57, 43, 0.2);
  color: #ff8f85;
  font-weight: 600;
}
.ancestry-match-meta {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin-top: 0.25rem;
  color: var(--pico-muted-color, #888);
  font-size: 0.8em;
}
.ancestry-match-cmdline {
  font-size: 0.85em;
  color: var(--pico-color, #eee);
  opacity: 0.75;
}

@media (max-width: 640px) {
  a.search-sample-link {
    grid-template-columns: 1fr;
    gap: 0.1rem;
  }
  .search-result-head {
    flex-direction: column;
    align-items: flex-start;
  }
  .search-result-meta {
    align-items: flex-start;
  }
  .ancestry-form {
    grid-template-columns: 1fr;
  }
  .diff-meta {
    grid-template-columns: 1fr !important;
  }
  .diff-grid {
    grid-template-columns: 1fr !important;
  }
  .diff-picker {
    grid-template-columns: 1fr !important;
  }
}

/* Session diff — /ui/diff. Three visual zones:
   1. Hero: breadcrumbs + title + picker form
   2. Meta strip: two session identity cards side-by-side
   3. Diff categories: per-category 3-column grid (only-left / both /
      only-right) with coloured columns so the reviewer's eye can
      pick out deltas without reading the column headers. */
.diff-hero { margin-bottom: 1.5rem; }
.diff-title { margin: 0.25rem 0 0.4rem 0; }
.diff-subtitle { color: var(--pico-muted-color); max-width: 72ch; }
.diff-error {
  padding: 0.75rem 1rem;
  border-left: 3px solid var(--pico-del-color, #c0392b);
  background: rgba(192, 57, 43, 0.08);
  margin: 1rem 0;
}
.diff-error strong { margin-right: 0.25rem; }
.diff-error small { display: block; margin-top: 0.25rem; color: var(--pico-muted-color); }

.diff-picker {
  display: grid;
  grid-template-columns: 1fr 1fr auto auto;
  gap: 0.75rem;
  align-items: end;
  margin-top: 0.75rem;
}
.diff-picker-label small {
  display: block;
  color: var(--pico-muted-color);
  font-size: 0.75em;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  margin-bottom: 0.25rem;
}
.diff-picker select {
  width: 100%;
  font-family: var(--font-mono);
  font-size: 0.85em;
}

.diff-meta {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 1rem;
  margin: 1.5rem 0;
}
.diff-meta-card {
  padding: 0.75rem 1rem;
  border-radius: var(--pico-border-radius, 0.5rem);
  border: 1px solid var(--pico-card-border-color, rgba(115, 130, 140, 0.2));
}
.diff-meta-card header {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 0.5rem;
  flex-wrap: wrap;
  margin-bottom: 0.5rem;
  padding: 0;
  background: transparent;
  border: none;
}
.diff-side-label {
  text-transform: uppercase;
  letter-spacing: 0.05em;
  font-size: 0.7em;
  font-weight: 600;
  padding: 0.1rem 0.5rem;
  border-radius: 999px;
}
.diff-meta-left  .diff-side-label { background: rgba(232, 76, 61, 0.18); color: #e84c3d; }
.diff-meta-right .diff-side-label { background: rgba(39, 174, 96, 0.18); color: #27ae60; }

/* Severity accent on a meta card — a coloured left border matching
   the severity palette, so a reviewer looking at two cards side by
   side sees "this side is critical, this side is medium" before
   reading the chips. Clean sessions get no accent (default border). */
.diff-meta-sev-critical { border-left: 4px solid var(--severity-critical-fg); }
.diff-meta-sev-high     { border-left: 4px solid var(--severity-high-fg); }
.diff-meta-sev-medium   { border-left: 4px solid var(--severity-medium-fg); }
.diff-meta-sev-low      { border-left: 4px solid var(--severity-low-fg); }
.diff-meta-dl {
  display: grid;
  grid-template-columns: max-content 1fr;
  gap: 0.15rem 0.75rem;
  margin: 0;
}
.diff-meta-dl dt {
  color: var(--pico-muted-color);
  font-size: 0.8em;
  text-transform: uppercase;
  letter-spacing: 0.03em;
}
.diff-meta-dl dd { margin: 0; }
.diff-meta-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 0.35rem;
  margin-top: 0.6rem;
}

/* Diff-summary bar — one-line verdict that sits above the per-category
   grid. Layout: big percentage on the left, stacked proportion bar in
   the middle (three flex-weighted segments so a reviewer can "feel"
   the balance without reading numbers), count dl on the right. Palette
   mirrors the diff-cat-count chips (red=left, muted=both, green=right)
   so the summary reads as a condensed preview of everything below. */
.diff-summary {
  display: grid;
  grid-template-columns: auto 1fr auto;
  align-items: center;
  gap: 1.25rem;
  margin: 0 0 1.5rem 0;
  padding: 0.85rem 1.1rem;
  border: 1px solid var(--pico-card-border-color, rgba(115, 130, 140, 0.2));
  border-radius: var(--pico-border-radius, 0.5rem);
  background: var(--pico-card-background-color);
}
.diff-summary-pct {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  line-height: 1;
}
.diff-summary-pct strong {
  font-size: 1.8rem;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}
.diff-summary-pct small {
  color: var(--pico-muted-color);
  text-transform: uppercase;
  letter-spacing: 0.05em;
  font-size: 0.7em;
  margin-top: 0.2rem;
}
.diff-summary-bar {
  display: flex;
  list-style: none;
  margin: 0;
  padding: 0;
  height: 0.6rem;
  border-radius: 999px;
  overflow: hidden;
  background: var(--pico-muted-border-color);
}
.diff-summary-seg { display: block; min-width: 2px; }
.diff-summary-seg-left  { background: #e84c3d; }
.diff-summary-seg-both  { background: var(--pico-muted-color); opacity: 0.5; }
.diff-summary-seg-right { background: #27ae60; }
.diff-summary-counts {
  display: grid;
  grid-template-columns: repeat(3, auto);
  gap: 0.15rem 1rem;
  margin: 0;
}
.diff-summary-counts > div { display: flex; flex-direction: column; }
.diff-summary-counts dt {
  color: var(--pico-muted-color);
  font-size: 0.7em;
  text-transform: uppercase;
  letter-spacing: 0.05em;
}
.diff-summary-counts dd {
  margin: 0;
  font-size: 1rem;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}
@media (max-width: 768px) {
  .diff-summary {
    grid-template-columns: 1fr;
    gap: 0.75rem;
  }
}

.diff-categories { display: flex; flex-direction: column; gap: 1.25rem; }
.diff-cat {
  border: 1px solid var(--pico-card-border-color, rgba(115, 130, 140, 0.2));
  border-radius: var(--pico-border-radius, 0.5rem);
  padding: 0.85rem 1rem;
}
.diff-cat-head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  margin-bottom: 0.6rem;
  padding: 0;
  background: transparent;
  border: none;
}
.diff-cat-head h3 { margin: 0; font-size: 1.05rem; }
.diff-cat-count {
  display: inline-block;
  padding: 0.05rem 0.45rem;
  border-radius: 999px;
  font-size: 0.75em;
  margin-left: 0.35rem;
  font-weight: 500;
}
.diff-cat-count-left  { background: rgba(232, 76, 61, 0.15); color: #e84c3d; }
.diff-cat-count-both  { background: rgba(115, 130, 140, 0.15); color: var(--pico-muted-color); }
.diff-cat-count-right { background: rgba(39, 174, 96, 0.15); color: #27ae60; }

.diff-grid {
  display: grid;
  grid-template-columns: 1fr 1fr 1fr;
  gap: 0.75rem;
}
.diff-col {
  border-radius: var(--pico-border-radius, 0.35rem);
  padding: 0.5rem 0.65rem;
  background: rgba(115, 130, 140, 0.05);
}
.diff-col header {
  margin-bottom: 0.4rem;
  padding: 0;
  background: transparent;
  border: none;
}
.diff-col header small {
  text-transform: uppercase;
  letter-spacing: 0.05em;
  font-size: 0.7em;
  color: var(--pico-muted-color);
  font-weight: 600;
}
.diff-col-left  { border-left: 3px solid #e84c3d; }
.diff-col-both  { border-left: 3px solid rgba(115, 130, 140, 0.4); }
.diff-col-right { border-left: 3px solid #27ae60; }
.diff-list {
  list-style: none;
  padding: 0;
  margin: 0;
}
.diff-entry {
  display: flex;
  flex-direction: column;
  gap: 0.1rem;
  padding: 0.35rem 0;
  border-bottom: 1px dashed rgba(115, 130, 140, 0.15);
  font-size: 0.85em;
}
.diff-entry:last-child { border-bottom: none; }
.diff-key { word-break: break-all; font-size: 0.85em; }
.diff-label { color: var(--pico-muted-color); font-style: italic; }
.diff-count { color: var(--pico-muted-color); font-variant-numeric: tabular-nums; }
.diff-count-left  { color: #e84c3d; font-weight: 500; }
.diff-count-right { color: #27ae60; font-weight: 500; }
.diff-empty { color: var(--pico-muted-color); font-style: italic; display: block; padding: 0.25rem 0; }
.diff-more { display: block; color: var(--pico-muted-color); margin-top: 0.35rem; font-style: italic; }
.diff-cat-empty { color: var(--pico-muted-color); margin: 0; }

/* ---------------------------------------------------------------
   Demo mode — banner shown on every page of the sigtrace.ai Live
   Demo deployment, and the 403 page emitted by the write-guard when
   a visitor tries to POST a mutation. Both surfaces only appear when
   SHADOWTRACE_DEMO_MODE=1 is set on the server; normal installs never
   load this state.
   --------------------------------------------------------------- */
.demo-banner {
  background: linear-gradient(
    to right,
    rgba(var(--pico-primary-rgb, 52, 152, 219), 0.18) 0%,
    rgba(var(--pico-primary-rgb, 52, 152, 219), 0.08) 100%
  );
  border-bottom: 1px solid rgba(var(--pico-primary-rgb, 52, 152, 219), 0.35);
  color: var(--pico-color);
  font-size: 0.9em;
}
.demo-banner-inner {
  max-width: 1400px;
  margin: 0 auto;
  padding: 0.55rem 1rem;
  display: flex;
  align-items: center;
  gap: 0.75rem;
  flex-wrap: wrap;
}
.demo-banner-badge {
  font-family: var(--pico-font-family-monospace, ui-monospace, Menlo, Consolas, monospace);
  font-size: 0.75em;
  font-weight: 700;
  letter-spacing: 0.12em;
  padding: 0.15rem 0.5rem;
  border-radius: 3px;
  background: var(--pico-primary);
  color: #0c121a;
  flex-shrink: 0;
}
.demo-banner-text {
  flex: 1 1 auto;
  min-width: 12rem;
  color: var(--pico-muted-color);
}
.demo-banner-cta {
  flex-shrink: 0;
  font-weight: 500;
  text-decoration: none;
  color: var(--pico-primary);
  border-bottom: 1px solid transparent;
}
.demo-banner-cta:hover {
  border-bottom-color: var(--pico-primary);
}

/* The standalone 403 page rendered when a mutation slips past the
   disabled UI and hits the server's write-guard. No sidebar, just a
   centered explanation + return link. Mostly inherits Pico defaults;
   the rules below just reclaim some vertical breathing room. */
.demo-denied {
  max-width: 40rem;
  margin: 4rem auto;
  text-align: center;
}
.demo-denied h1 {
  font-size: 1.6rem;
  margin-bottom: 1.25rem;
}
.demo-denied p {
  color: var(--pico-muted-color);
  line-height: 1.55;
}
.demo-denied p:last-of-type {
  margin-top: 1.75rem;
  display: flex;
  gap: 0.75rem;
  justify-content: center;
  flex-wrap: wrap;
}
.demo-denied [role="button"] {
  margin: 0;
}

/* Mutating buttons across the UI are styled-disabled in demo mode so
   visitors see at a glance that the action won't go through. The
   server still enforces this with the write-guard — this is only the
   visual cue. Covers <button type=submit>, <input type=submit>, and
   any anchor explicitly tagged as a write action with .demo-disable.
   Read-only form elements (search inputs, filter dropdowns, diff
   pickers) are left alone because those trigger GET requests. */
html[data-demo-mode] button[type="submit"],
html[data-demo-mode] input[type="submit"],
html[data-demo-mode] .demo-disable {
  opacity: 0.45;
  cursor: not-allowed;
  pointer-events: none;
  filter: saturate(0.6);
}
/* Any form posting state to the server gets a small "disabled in demo"
   hint underneath, so a confused visitor understands *why* the button
   is greyed out rather than assuming a client bug. The server-rendered
   title attr on the form element drives the tooltip without inflating
   every template. */
html[data-demo-mode] form[method="post" i]::after,
html[data-demo-mode] form[method="POST"]::after {
  content: "Disabled in demo — install to enable";
  display: block;
  font-size: 0.75em;
  color: var(--pico-muted-color);
  font-style: italic;
  margin-top: 0.35rem;
}
