/**
 * Security & Audit — the ledger
 * =============================
 * Read-only forensic explorer over every hash chain IDBE writes.
 *
 * Shared component primitives (buttons, inputs, badges, cards, empty and
 * error states, scrollbars, motion preferences) live in cx-base.css and are
 * loaded before this file. Only what is specific to this view is below.
 *
 * THE DESIGN IDEA, and why it is this one.
 * The subject of this module is a hash chain: an append-only ledger where
 * every row commits to the one before it. Every previous version of this
 * screen rendered that as a flat table -- rows in a list, indistinguishable
 * from a log viewer, with the one structural property that makes them
 * EVIDENCE nowhere on screen. The signature element here is the SPINE: a
 * rail down the left of the evidence rows carrying a node per row, unbroken
 * while consecutive rows belong to one chain and severed where they do not.
 * It encodes the data structure rather than decorating it, which is the only
 * reason to spend the boldness there.
 *
 * Everything around the spine is deliberately quiet: one accent, hairline
 * borders, tabular numerals, and colour reserved for the two facts an
 * auditor is actually looking for -- what CLASS of thing happened, and
 * whether the chain still attests it.
 */


/* ============================================================
   1. LOCAL TOKENS
   ============================================================
   On :root rather than on the view, for the same reason cx-base gives:
   anything portalled outside `#view-security` (a menu, a dialog) resolves
   an unset custom property to the property's INITIAL value -- transparent,
   0, currentColor -- which is invisible to every source-level check and
   only appears on screen. */
:root {
  /* Spine geometry. One number, three consumers (rail, node, row inset),
     so the rail cannot drift out of alignment with the dots it threads. */
  --sa-spine-w: 2px;
  --sa-node: 9px;
  /* 32, not 26. At 26 the 24px node ring centres 13px from the table's left
     edge, which put its outer 2px OUTSIDE the scroll container -- measured
     in the real shell: ring at x=275 against a container starting at 277,
     so every node was shaved down its left side. */
  --sa-spine-col: 32px;

  /* Event CLASS colours. Derived from the verb, never from a chain name --
     nothing about a chain is hardcoded in this module and these must not
     become the exception. */
  --sa-cls-deny:   var(--danger);
  --sa-cls-grant:  var(--success);
  --sa-cls-remove: var(--warning);
  --sa-cls-change: var(--info);
  --sa-cls-read:   var(--text-3);

  /* The rail must READ. Measured at --border-mid it resolved to
     rgba(0,0,0,0.12) on a white card and the spine -- the one element that
     carries what a hash chain IS -- was invisible from a normal viewing
     distance. A border tone is right for a divider nobody should notice and
     wrong for a load-bearing one. */
  --sa-rail: color-mix(in srgb, var(--text-4) 62%, transparent);
  --sa-grid: color-mix(in srgb, var(--border) 60%, transparent);
}


/* ============================================================
   2. SHELL
   ============================================================ */
#view-security .view-header { margin-bottom: var(--sp-4); }

/* Container queries, not viewport queries. This view's width is the
   viewport MINUS the sidebar, so a viewport breakpoint fires at the wrong
   moment -- measured previously in .cx-detail at 1280px viewport / 640px
   pane. Every responsive rule below keys off the card. */
#view-security .card,
#view-security .sa-panel { container-type: inline-size; }

/* The tab MOUNTS are containers too, and this is not redundant.
   `.sa-bento` is a PARENT of the panels, so a query written against a panel
   has no container above it to resolve and silently never matches. Measured
   at 390px: `grid-template-columns` stayed `278px 0px`, i.e. the second
   column -- both leaderboards -- was zero pixels wide and invisible, with no
   error anywhere. An unmatched container query is not a fallback, it is a
   rule that does not exist. */
#sa-overview-content,
#sa-evidence-content,
#sa-activity-content { container-type: inline-size; }


/* ── tab rail ────────────────────────────────────────────────────────────
   Ten tabs. Six of them were once CLIPPED AWAY at 390px -- not scrolled
   off, unreachable -- because `width: fit-content` has no upper bound and
   `.main-content` is `overflow-x: hidden`. The cap plus `flex-shrink: 0`
   on the buttons is what makes the strip scroll instead of overflow, and
   removing either brings that back. */
#secaudit-tabs {
  display: flex;
  gap: 2px;
  padding: 4px;
  max-width: 100%;
  overflow-x: auto;
  scrollbar-width: none;
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  scroll-snap-type: x proximity;
}
#secaudit-tabs::-webkit-scrollbar { display: none; }

#secaudit-tabs .tab-btn {
  flex: 0 0 auto;
  scroll-snap-align: start;
  display: inline-flex;
  align-items: center;
  gap: 7px;
  height: 32px;
  padding: 0 12px;
  border: 0;
  border-radius: var(--r-md);
  background: transparent;
  color: var(--text-3);
  font: inherit;
  font-size: 12.5px;
  font-weight: 500;
  white-space: nowrap;
  cursor: pointer;
  transition: color var(--t-fast), background var(--t-fast);
}
#secaudit-tabs .tab-btn svg { flex: 0 0 auto; opacity: 0.75; }
#secaudit-tabs .tab-btn:hover { color: var(--text-1); background: var(--bg-card); }
#secaudit-tabs .tab-btn.active {
  color: var(--text-1);
  background: var(--bg-card);
  font-weight: 600;
  box-shadow: var(--shadow-sm);
}
#secaudit-tabs .tab-btn.active svg { opacity: 1; color: var(--accent); }

/* A divider between tab CLUSTERS. Ten equal pills read as ten unrelated
   things; three groups read as three. The rule is a pseudo-element so the
   groups need no wrapper elements, which would break the flat tablist
   semantics assistive tech walks. */
#secaudit-tabs .tab-btn[data-sa-group]::before {
  content: "";
  align-self: center;
  width: 1px;
  height: 16px;
  margin-right: 10px;
  margin-left: 4px;
  background: var(--border-mid);
}

/* The count a tab carries. Muted by default so it never competes with the
   label; it is context, not the answer. */
.sa-tab-count {
  font-size: 10.5px;
  font-variant-numeric: tabular-nums;
  color: var(--text-4);
  background: var(--bg-surface);
  border-radius: var(--r-full);
  padding: 1px 5px;
}
.tab-btn.active .sa-tab-count { color: var(--text-2); }


/* ============================================================
   3. PANEL — the card shape this view uses everywhere
   ============================================================ */
.sa-panel {
  background: var(--bg-card);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  overflow: hidden;
}
.sa-panel + .sa-panel { margin-top: var(--sp-4); }

.sa-panel-head {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  padding: 13px 16px;
  border-bottom: 1px solid var(--border);
}
.sa-panel-title {
  font-size: 13.5px;
  font-weight: 600;
  color: var(--text-1);
  display: flex;
  align-items: center;
  gap: 8px;
  min-width: 0;
}
.sa-panel-title svg { color: var(--text-3); flex: 0 0 auto; }
/* What the panel is measuring, beside the name of it. Never a second
   sentence -- one clause, and it is always a fact about the data below. */
.sa-panel-sub {
  font-size: 11.5px;
  font-weight: 400;
  color: var(--text-3);
  font-variant-numeric: tabular-nums;
}
.sa-panel-tools {
  display: flex;
  align-items: center;
  gap: 6px;
  margin-left: auto;
  flex-wrap: wrap;
  justify-content: flex-end;
  min-width: 0;
}
.sa-panel-body { padding: 16px; }
.sa-panel-body-flush { padding: 0; }

@container (max-width: 620px) {
  .sa-panel-head { flex-wrap: wrap; }
  .sa-panel-title { flex: 1 1 100%; min-width: 0; }
  .sa-panel-tools { margin-left: 0; width: 100%; justify-content: flex-start; }
  .sa-panel-tools > :is(input, select) { flex: 1 1 140px; min-width: 0; }
}


/* ============================================================
   4. THE LEDGER — evidence rows, and the spine that threads them
   ============================================================
   The signature element. A chain is a sequence where every row commits to
   its predecessor; the rail says so. Consecutive rows of ONE chain share an
   unbroken rail, a chain change severs it, and a row whose link is broken
   gets a hollow node in danger.

   The rail is drawn on the first cell rather than as a table column,
   because a real column would need a colspan on every detail row and would
   push the disclosure out of the row's reading order. */
.sa-ledger {
  width: 100%;
  /* SCROLL rather than compress. Auto table layout distributes what is
     available, so pinning three columns made the other two share the
     remainder -- measured in the real shell (viewport minus a 252px
     sidebar): the event column came out 200px and eight chips were cut
     mid-word, with `cloud_log_source.deleted` and `.disabled` truncating to
     the same fourteen characters. Two different events reading identically
     is the one thing this column must never do. */
  min-width: var(--sa-ledger-min, 880px);
  border-collapse: collapse;
  font-size: 12.5px;
}
.sa-ledger thead th {
  position: sticky;
  top: 0;
  z-index: 2;
  text-align: left;
  padding: 9px 12px;
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-4);
  background: var(--bg-elevated);
  border-bottom: 1px solid var(--border);
  white-space: nowrap;
}
.sa-ledger tbody td {
  padding: 12px;
  border-bottom: 1px solid var(--sa-grid);
  vertical-align: top;
}
.sa-ledger-wrap {
  overflow: auto;
  max-height: min(64vh, 720px);
}

/* -- day group ---------------------------------------------------------
   "Aug 15, 03:10 PM" printed on fifteen consecutive rows is fifteen
   repetitions of one fact. The day is said ONCE, as a group header, and
   the row keeps only the clock, which is the part that differs. */
.sa-day > td {
  padding: 0;
  border-bottom: 1px solid var(--border);
}
.sa-day-inner {
  display: flex;
  align-items: baseline;
  gap: 10px;
  padding: 7px 12px 7px calc(var(--sa-spine-col) + 12px);
  background: var(--bg-surface);
}
.sa-day-label {
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--text-2);
}
.sa-day-count {
  font-size: 11px;
  color: var(--text-4);
  font-variant-numeric: tabular-nums;
}

/* -- the spine ----------------------------------------------------------
   `position: relative` on the CELL, not the row: a table row is not a
   containing block for an absolutely positioned child in either engine, so
   a rail anchored to the row lands on the table instead and runs the whole
   height of it. */
.sa-ev > td:first-child {
  position: relative;
  padding-left: calc(var(--sa-spine-col) + 12px);
  white-space: nowrap;
}
/* The rail. Full-bleed through the row so consecutive rows join with no
   seam; `.is-chain-head` trims the top half and `.is-chain-tail` the
   bottom, which is what makes a chain boundary visible as a GAP rather
   than as a colour change nobody notices. */
.sa-ev > td:first-child::before {
  content: "";
  position: absolute;
  left: calc(var(--sa-spine-col) / 2);
  top: 0;
  /* Past the 1px cell border, or the rail shows a hairline gap at every row
     boundary and a continuous chain reads as nine separate ones. */
  bottom: -1px;
  width: var(--sa-spine-w);
  transform: translateX(-50%);
  background: var(--sa-rail);
}
.sa-ev.is-chain-head > td:first-child::before { top: 50%; }
.sa-ev.is-chain-tail > td:first-child::before { bottom: 50%; }
.sa-ev.is-chain-head.is-chain-tail > td:first-child::before { display: none; }

/* The disclosure, in two shapes.
   The BASE is the inline control the source-specific tabs (Cortex SQL,
   Auto-Heal, Admin Access, the Live Feed) have always used: a chevron in
   the first cell, in normal flow.

   The LEDGER variant makes it the node on the spine. That distinction is
   load-bearing and was missing: the absolute positioning was written on the
   bare class, and those tabs' cells are `position: static`, so every
   disclosure in them escaped its row and stacked against the panel --
   measured on the Cortex tab, a button at y=221 belonging to a row at
   y=316. A screenshot of the Evidence tab alone could never have shown it. */
.sa-disclose {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  /* 24x24: the minimum target, measured at 390px. An earlier disclosure in
     this module came out 23x18. Negative margins grow the target without
     growing the row. */
  width: 24px;
  height: 24px;
  margin: -3px 2px -3px -3px;
  padding: 0;
  border: 0;
  background: transparent;
  color: var(--text-3);
  cursor: pointer;
  vertical-align: middle;
}
.sa-chevron {
  display: inline-flex; color: var(--text-4);
  transition: transform var(--t-base) var(--ease-out), color var(--t-base);
}
.sa-row-expandable:hover .sa-chevron { color: var(--accent); }
.sa-row-expandable.is-open .sa-chevron { transform: rotate(90deg); color: var(--accent); }

.sa-row-expandable { cursor: pointer; transition: background var(--t-fast); }
.sa-row-expandable:hover { background: var(--bg-elevated); }
.sa-row-expandable:hover .sa-disclose { color: var(--accent); }

/* -- the ledger variant: the node IS the disclosure ---------------------
   The first version drew a decorative dot and then positioned the button on
   top of it: measured, both landed at x=47 and the chevron hid the node on
   every row that had a control -- which is every row. Merging them is also
   the better design. The thing you click to open a link in the chain should
   BE the link.

   The ring carries the state: hairline while the link holds, filled when
   the row is open, hollow in danger at the row a walk named as the first
   break, so a broken link reads as a HOLE in the ledger rather than as
   another red word among red words. */
.sa-ev .sa-disclose {
  position: absolute;
  left: calc(var(--sa-spine-col) / 2);
  top: 20px;
  transform: translate(-50%, -50%);
  z-index: 1;
  margin: 0;
  border-radius: 50%;
  background: var(--bg-card);
  border: var(--sa-spine-w) solid var(--sa-rail);
  color: var(--text-4);
  transition: border-color var(--t-fast), background var(--t-fast), color var(--t-fast);
}
.sa-ev:hover .sa-disclose { border-color: var(--accent); color: var(--accent); }
.sa-ev.is-open .sa-disclose {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--text-inverse);
}
.sa-ev.is-broken .sa-disclose {
  border-color: var(--danger);
  color: var(--danger);
  box-shadow: 0 0 0 3px var(--danger-bg);
}

/* -- column widths ------------------------------------------------------
   Measured on the first paint: SUBJECT took 431px for a truncated uuid
   while SUMMARY, which carries what the row is about, got 409 -- auto table
   layout distributes by content length, and a uuid is long content that
   says almost nothing. The three narrow columns are pinned so the two that
   carry meaning take what is left.

   `width` on a th under auto layout is a HINT, which is what is wanted
   here: a long event name may still push its column wider rather than
   clipping the one thing that identifies the row. */
/* These are EVIDENCE's five columns. On the bare class they would land on
   nth-child of every other ledger -- the eight-column Cortex audit would
   get a 148px User and a 210px Risk -- so they carry the modifier. */
.sa-ledger.is-evidence th:nth-child(1) { width: 104px; min-width: 104px; }
.sa-ledger.is-evidence th:nth-child(2) { width: 148px; min-width: 128px; }
.sa-ledger.is-evidence th:nth-child(4) { width: 210px; min-width: 160px; }
.sa-ledger.is-evidence td:nth-child(3) { white-space: nowrap; }

/* An execution audit carries more columns than Evidence does; it scrolls
   sooner rather than cutting a SQL cell or an event name mid-word. */
.sa-ledger.is-wide { --sa-ledger-min: 1040px; }
/* The chip never shrinks below its own text. As a flex item in a squeezed
   cell it would otherwise be compressed and clip the event NAME, which is
   the row's primary identifier. */
.sa-evt {
  flex: 0 0 auto;
  display: inline-block;
  padding: 1px 8px;
  border-radius: 999px;
  font-family: var(--font-mono);
  font-size: 11px;
  line-height: 1.65;
  white-space: nowrap;
  color: var(--sa-cls, var(--text-2));
  background: color-mix(in srgb, var(--sa-cls, var(--text-3)) 13%, transparent);
  border: 1px solid color-mix(in srgb, var(--sa-cls, var(--text-3)) 32%, transparent);
}
.sa-evt[data-cls="deny"]   { --sa-cls: var(--sa-cls-deny); font-weight: 600; }
.sa-evt[data-cls="remove"] { --sa-cls: var(--sa-cls-remove); }
.sa-evt[data-cls="grant"]  { --sa-cls: var(--sa-cls-grant); }
.sa-evt[data-cls="change"] { --sa-cls: var(--sa-cls-change); }
/* `read` keeps no tint at all. It is the quiet majority, and a page where
   every row is coloured says nothing about the row that matters. */
.sa-evt[data-cls="read"] {
  --sa-cls: var(--sa-cls-read);
  background: transparent;
  border-color: var(--border);
}
/* Never colour alone: a refusal is the row being scanned for, and roughly
   one reader in twelve cannot separate red from green. The weight above and
   this mark are the two signals that survive without hue. */
.sa-evt[data-cls="deny"]::before { content: "! "; font-weight: 700; }
.sa-ev-actor { font-weight: 600; color: var(--text-1); }
.sa-ev-time { font-variant-numeric: tabular-nums; color: var(--text-2); }

/* -- the LITE row --------------------------------------------------------
   Cortex SQL, Auto-Heal, Admin Access, the Live Feed and Investigation are
   all event tables, but they are not chains: there is no prev_hash to
   commit to, so they get the ledger's rhythm -- day groups, the class
   chip, the actor emphasis -- and NOT the spine. Drawing a rail there
   would claim a property those rows do not have, which is the one thing
   this module must never do. */
.sa-ev-lite > td { padding: 11px 12px; vertical-align: middle; }
.sa-ev-lite > td:first-child { white-space: nowrap; }
.sa-ev-meta { color: var(--text-3); font-size: 11.5px; }
.sa-mono {
  font-family: var(--font-mono); font-size: 11px;
  display: inline-block; max-width: 46ch;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  vertical-align: bottom;
}
.sa-ev-num { font-variant-numeric: tabular-nums; font-size: 11.5px; color: var(--text-2); }
/* Which surface produced the row -- SQL Editor, Cortex, Roles & Grants.
   Quiet: it groups rows, it is not the thing being reported. */
.sa-ev-src {
  font-size: 11px; color: var(--text-2);
  padding: 1px 7px; border-radius: 999px;
  border: 1px solid var(--border); white-space: nowrap;
}
.sa-outcome { font-size: 11.5px; font-weight: 600; }
.sa-outcome.is-ok { color: var(--success); }
/* Not colour alone: a failed execution is the row an operator is scanning
   for, and roughly one reader in twelve cannot separate these two hues. */
.sa-outcome.is-bad { color: var(--danger); }
.sa-outcome.is-bad::before { content: "\2715\00a0"; }

/* -- detail ------------------------------------------------------------
   The control above is a real button, not a row-level click handler: what
   sits behind it is the full attested payload, and a row handler is
   unreachable by keyboard and announces nothing. A role="button" on the row
   is not the fix either -- it destroys the row semantics assistive tech uses
   to read a table. */
.sa-chevron {
  display: inline-flex;
  transition: transform var(--t-fast) var(--ease-out);
}
.sa-ev.is-open .sa-chevron { transform: rotate(90deg); }

.sa-detail-row { display: none; }
.sa-detail-row.sa-row-expanded { display: table-row; }
.sa-detail-cell {
  position: relative;
  padding: 0 12px 14px calc(var(--sa-spine-col) + 12px) !important;
  background: var(--bg-elevated);
  border-bottom: 1px solid var(--border) !important;
}
/* The rail runs THROUGH an open payload. Without this the chain appears to
   be severed at exactly the row the operator is reading -- the one place the
   picture must not suggest a break that is not there. It is drawn dimmer
   than the rail proper because no node sits on this stretch: it is the wire
   between two links, not a link. */
.sa-detail-row.sa-row-expanded .sa-detail-cell::before {
  content: "";
  position: absolute;
  left: calc(var(--sa-spine-col) / 2 + 12px);
  top: 0;
  bottom: -1px;
  width: var(--sa-spine-w);
  transform: translateX(-50%);
  background: var(--sa-rail);
  opacity: 0.55;
}
/* A chain's LAST row has no wire below it, open or not. */
.sa-ev.is-chain-tail + .sa-detail-row .sa-detail-cell::before { display: none; }
.sa-detail-section { display: flex; flex-direction: column; gap: var(--sp-1); }
.sa-detail-block {
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  background: var(--bg-card);
  padding: 12px 14px;
  animation: sa-unfold var(--t-base) var(--ease-out) both;
}
@keyframes sa-unfold {
  from { opacity: 0; transform: translateY(-4px); }
  to   { opacity: 1; transform: none; }
}
.sa-detail-section + .sa-detail-section { margin-top: 12px; }
.sa-detail-label {
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-4);
  margin-bottom: 7px;
}
.sa-detail-meta {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-4);
  margin-top: 12px;
  padding-top: 10px;
  border-top: 1px solid var(--border);
  font-size: 11px;
  color: var(--text-3);
}
.sa-detail-meta strong {
  color: var(--text-2);
  font-family: var(--font-mono);
  font-weight: 500;
}

/* Attested payload: a definition list, so a key and its value are
   announced as one pair rather than as a run of unlabelled text -- which,
   for an audit payload, is the entire content of the row. */
.sa-payload { display: grid; grid-template-columns: max-content 1fr; gap: 3px var(--sp-3); margin: 0; }
.sa-payload dt {
  font-family: var(--font-mono); font-size: 11px; color: var(--text-3);
  white-space: nowrap;
}
.sa-payload dd {
  margin: 0; font-family: var(--font-mono); font-size: 11px; color: var(--text-1);
  white-space: pre-wrap; word-break: break-word; min-width: 0;
}
@container (max-width: 640px) {
  .sa-payload { grid-template-columns: 1fr; gap: 0 0; }
  .sa-payload dt { margin-top: 6px; }
}

/* -- narrow: stop being a table ----------------------------------------
   Hiding columns was the first attempt and it was wrong. Measured at 390px:
   the ledger kept TIME and EVENT on screen and pushed SUMMARY -- the thing
   that says what the row is ABOUT -- off the right edge behind a horizontal
   scroll, so a phone showed a clock and a chip and nothing else. Folding the
   quiet columns and keeping the loud one only works while the loud one still
   fits, and at 390px nothing fits.

   So the row stops being a row. Time and event on the first line, the
   summary on its own line, chain and subject last and muted. The spine
   survives intact, because it is the one thing that does not depend on
   having columns. */
@container (max-width: 720px) {
  /* Every display is set through `.sa-ledger tr.<class>`, never through a
     bare `.sa-ledger tr`. A blanket rule is (0,1,1) and beats the (0,1,0)
     class rules below it whatever the source order -- measured: `.sa-ev`
     stayed `block` so the flex ordering never applied, AND every collapsed
     payload sprang open, because `.sa-detail-row { display: none }` lost to
     it too. Two defects from one selector. */
  .sa-ledger, .sa-ledger tbody { display: block; }
  .sa-ledger thead { display: none; }
  .sa-ledger td { display: block; }
  .sa-ledger tr.sa-day { display: block; }
  .sa-ledger tr.sa-detail-row { display: none; }
  .sa-ledger tr.sa-detail-row.sa-row-expanded { display: block; }
  .sa-ledger-wrap { overflow-x: hidden; }
  .sa-ledger { min-width: 0; }

  .sa-day > td { padding: 0; }

  .sa-ledger tr.sa-ev {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 3px 9px;
    position: relative;
    padding: 11px 12px 11px calc(var(--sa-spine-col) + 12px);
    border-bottom: 1px solid var(--sa-grid);
  }
  /* (0,2,1), so it beats `.sa-ledger tbody td` at (0,1,2). The first
     version was `.sa-ev > td` at (0,1,1) and lost: every cell kept its
     bottom border and its 12px padding, so a three-line row rendered as
     five bordered strips. Same collision as the display rules above, and
     it is worth stating twice -- table selectors carry an extra element
     each and quietly outrank the class rules written to override them. */
  .sa-ledger tr.sa-ev > td { padding: 0; border: 0; }
  /* The rail and the node move to the ROW. A block-level tr IS a containing
     block; a table-row is not, which is why they live on the cell at every
     other width. */
  .sa-ledger tr.sa-ev > td:first-child { padding-left: 0; position: static; }
  .sa-ledger tr.sa-ev > td:first-child::before,
  .sa-ledger tr.sa-ev > td:first-child::after { display: none; }
  .sa-ev::before {
    content: "";
    position: absolute;
    left: calc(var(--sa-spine-col) / 2 + 12px);
    top: 0;
    bottom: -1px;
    width: var(--sa-spine-w);
    transform: translateX(-50%);
    background: var(--sa-rail);
  }
  .sa-ev.is-chain-head::before { top: 50%; }
  .sa-ev.is-chain-tail::before { bottom: 50%; }
  .sa-ev.is-chain-head.is-chain-tail::before { display: none; }
  .sa-ev .sa-disclose { left: calc(var(--sa-spine-col) / 2 + 12px); top: 22px; }

  /* Reading order, which is not column order: what happened, then what it
     was about, then where it was recorded. */
  .sa-ledger tr.sa-ev > td:nth-child(1) { order: 1; }
  .sa-ledger tr.sa-ev > td:nth-child(3) { order: 2; }
  .sa-ledger tr.sa-ev > td:nth-child(5) { order: 3; flex: 1 1 100%; }
  .sa-ledger tr.sa-ev > td:nth-child(2) { order: 4; }
  .sa-ledger tr.sa-ev > td:nth-child(4) { order: 5; flex: 1 1 auto; min-width: 0; }

  .sa-ledger .sa-col-fold { display: block; }

  .sa-ledger tr.sa-ev-lite {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 3px 9px;
    padding: 11px 12px;
    border-bottom: 1px solid var(--sa-grid);
  }
  .sa-ledger tr.sa-ev-lite > td { padding: 0; border: 0; display: inline-block; }
  /* time, actor and the class chip lead; everything else follows on its own
     line. The quiet trailing columns -- module, completed-at, row counts --
     are the ones a phone can afford to lose the least room to. */
  .sa-ledger tr.sa-ev-lite > td:nth-child(1) { order: 1; }
  .sa-ledger tr.sa-ev-lite > td:nth-child(2) { order: 2; }
  .sa-ledger tr.sa-ev-lite > td:nth-child(3) { order: 3; }
  .sa-ledger tr.sa-ev-lite > td:nth-child(n+4) { order: 4; }
  .sa-ledger tr.sa-ev-lite > td:last-child { flex: 1 1 100%; }
  .sa-ledger tr.sa-ev-lite .sa-mono { max-width: 100%; }
  /* A cell holding only the null marker is noise on a phone: it repeats the
     absence the row already shows by omission. Kept on desktop, where the
     column header gives it a meaning. */
  .sa-ledger tr.sa-ev-lite > td:has(> .sa-null:only-child),
  .sa-ledger tr.sa-ev-lite > td:has(> :only-child > .sa-null:only-child) {
    display: none;
  }
  .sa-ev-chain, .sa-ev-subject { font-size: 10.5px; color: var(--text-4); }
  .sa-ev-subject { max-width: 100%; }
  .sa-ev-summary { max-width: none; }
  .sa-detail-cell { padding-left: calc(var(--sa-spine-col) + 12px) !important; }
}


/* ============================================================
   5. ACTIVITY — distributions, drawn as distributions
   ============================================================
   Three distributions were rendered as three full-bleed tables of one to
   three rows each, so ~60% of the pane was empty and the SHAPE of the data
   -- which is the whole content of a distribution -- appeared nowhere.
   A donut for the split, ranked bars for the leaderboards, side by side. */
.sa-bento {
  display: grid;
  gap: var(--sp-4);
  grid-template-columns: minmax(0, 340px) minmax(0, 1fr);
  align-items: start;
}
@container (max-width: 860px) {
  .sa-bento { grid-template-columns: minmax(0, 1fr); }
}
/* The two leaderboards stack inside their own column so the donut stays
   beside them rather than above one and beside the other. */
.sa-bento-stack { display: grid; gap: var(--sp-4); }

.sa-donut { width: 100%; height: 232px; }

/* The legend does the labelling; the chart carries no text, because a
   donut with six labels around it is unreadable at the width this pane
   actually gets. */
.sa-legend { display: flex; flex-direction: column; gap: 7px; margin-top: 10px; }
.sa-legend-row {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 12px;
}
.sa-legend-swatch {
  width: 9px;
  height: 9px;
  border-radius: 2px;
  flex: 0 0 auto;
}
.sa-legend-name {
  color: var(--text-2);
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.sa-legend-value {
  font-variant-numeric: tabular-nums;
  color: var(--text-1);
  font-weight: 600;
}
.sa-legend-pct { font-size: 11px; color: var(--text-4); font-variant-numeric: tabular-nums; min-width: 4ch; text-align: right; }

/* -- ranked bars --------------------------------------------------------
   The bar is the row's background, not a separate track beside a number.
   One element, so the label sits ON the measurement and a long name cannot
   push the bar out of alignment with the row above it. */
.sa-rank { display: flex; flex-direction: column; gap: 4px; }
.sa-rank-row {
  position: relative;
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 11px;
  border-radius: var(--r-md);
  overflow: hidden;
  isolation: isolate;
}
.sa-rank-row::before {
  content: "";
  position: absolute;
  inset: 0 auto 0 0;
  width: var(--sa-pct, 0%);
  z-index: -1;
  background: var(--accent-dim);
  border-radius: var(--r-md);
  /* Grows from nothing on first paint. The bar IS the measurement, so
     seeing it arrive is what makes the ranking read as a ranking rather
     than as a coloured list. */
  animation: sa-grow 620ms var(--ease-out) both;
  transform-origin: left center;
}
@keyframes sa-grow {
  from { transform: scaleX(0); }
  to   { transform: scaleX(1); }
}
.sa-rank-row:hover::before { background: var(--accent-glow, var(--accent-dim)); }
.sa-rank-ord {
  font-size: 10.5px;
  font-variant-numeric: tabular-nums;
  color: var(--text-4);
  width: 2ch;
  flex: 0 0 auto;
}
.sa-rank-name {
  font-size: 12.5px;
  color: var(--text-1);
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* A resolved name reads as the answer; the raw id beside it is for the
   engineer reading a support ticket and is deliberately quieter. */
.sa-rank-id {
  font-family: var(--font-mono);
  font-size: 10.5px;
  color: var(--text-4);
  flex: 0 0 auto;
}
.sa-rank-value {
  font-size: 13px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  color: var(--text-1);
  flex: 0 0 auto;
}

@media (prefers-reduced-motion: reduce) {
  .sa-rank-row::before { animation: none; }
  .sa-detail-block { animation: none; }
}


/* ============================================================
   6. KPI STRIP
   ============================================================ */
.sa-kpi-grid {
  display: grid;
  gap: var(--sp-3);
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
}
.sa-kpi {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 7px;
  padding: 15px 16px;
  background: var(--bg-card);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  overflow: hidden;
  /* A button variant sits beside a div variant on the same row; the UA
     reset keeps the two the same shape. Measured 522 vs 522. */
  text-align: left;
  font: inherit;
  color: inherit;
  width: 100%;
}
.sa-kpi::before {
  content: "";
  position: absolute;
  inset: 0 auto 0 0;
  width: 3px;
  background: var(--border-mid);
}
.sa-kpi--success::before { background: var(--success); }
.sa-kpi--info::before    { background: var(--info); }
.sa-kpi--danger::before  { background: var(--danger); }
.sa-kpi--muted::before   { background: var(--text-4); }
.sa-kpi.is-clickable { cursor: pointer; transition: border-color var(--t-fast), transform var(--t-fast); }
.sa-kpi.is-clickable:hover { border-color: var(--accent); transform: translateY(-2px); }
.sa-kpi.is-clickable:active { transform: translateY(-1px); }
.sa-kpi-top { display: flex; align-items: center; gap: 9px; }
.sa-kpi-label {
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--text-3);
  flex: 1;
  min-width: 0;
}
.sa-kpi-icon {
  width: 26px;
  height: 26px;
  border-radius: var(--r-md);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
}
.sa-kpi--success .sa-kpi-icon { background: var(--success-bg); color: var(--success); }
.sa-kpi--info .sa-kpi-icon    { background: var(--info-bg);    color: var(--info); }
.sa-kpi--danger .sa-kpi-icon  { background: var(--danger-bg);  color: var(--danger); }
.sa-kpi--muted .sa-kpi-icon   { background: var(--bg-elevated); color: var(--text-3); }
.sa-kpi-value {
  font-size: 27px;
  font-weight: 700;
  line-height: 1.05;
  color: var(--text-1);
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.02em;
}
.sa-kpi-value.is-pill { font-size: 18px; }
.sa-kpi-sub { font-size: 11.5px; color: var(--text-3); }
.sa-kpi-sub .up { color: var(--danger); font-weight: 600; }
.sa-kpi-cta {
  font-size: 11px;
  color: var(--text-4);
  opacity: 0;
  transform: translateX(-3px);
  transition: opacity var(--t-fast), transform var(--t-fast), color var(--t-fast);
}
.sa-kpi.is-clickable:hover .sa-kpi-cta { opacity: 1; transform: none; color: var(--accent); }

/* An unread KPI. Deliberately NOT styled like a low number: the whole
   point is that it must not read as a measurement. */
#view-security .sa-kpi-value.is-unknown { color: var(--text-4); cursor: help; }
#view-security .sa-kpi-unknown { color: var(--warning, var(--text-3)); font-weight: 500; }
.sa-kpi-value, .sa-pager-info { white-space: nowrap; }


/* ============================================================
   7. CHAIN INTEGRITY — the attestation strip
   ============================================================ */
.sa-chain-strip {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 11px 14px;
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  background: var(--bg-elevated);
}
.sa-chain-strip + .sa-chain-strip { margin-top: 7px; }
/* One row per chain in the integrity attestation. Same shape as the strip
   above; it exists separately because it is rendered by the verify handler
   rather than by the overview. */
.sa-chain-line {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 12px;
  border-bottom: 1px solid var(--border);
  font-size: 12.5px;
  flex-wrap: wrap;
}
.sa-chain-line:last-child { border-bottom: 0; }
.sa-chain-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  flex: 0 0 auto;
  background: var(--text-4);
}
.sa-chain-dot.is-ok { background: var(--success); box-shadow: 0 0 0 3px var(--success-bg); }
.sa-chain-dot.is-unknown { background: var(--warning); box-shadow: 0 0 0 3px var(--warning-bg); }
.sa-chain-dot.is-empty { background: var(--text-4); }
.sa-chain-dot.is-bad { background: var(--danger); box-shadow: 0 0 0 3px var(--danger-bg); }
.sa-chain-name { font-weight: 600; color: var(--text-1); font-size: 12.5px; }
.sa-chain-icon {
  font-weight: 700; font-family: var(--font-mono);
  width: 1.1em; text-align: center; flex-shrink: 0;
}
.sa-chain-icon.is-ok { color: var(--success); }
.sa-chain-icon.is-empty { color: var(--text-3); }
.sa-chain-icon.is-unknown { color: var(--warning); }
.sa-chain-icon.is-bad { color: var(--danger); }
.sa-chain-verdict { font-size: 11.5px; font-weight: 500; }
.sa-chain-verdict.is-ok { color: var(--success); }
.sa-chain-verdict.is-unknown { color: var(--warning); }
.sa-chain-verdict.is-bad { color: var(--danger); }
.sa-chain-extent, .sa-chain-reason { font-size: 11px; color: var(--text-3); }
.sa-chain-strip .sa-chain-meta { display: flex; gap: 10px; flex-wrap: wrap; align-items: baseline; min-width: 0; }
#view-security .sa-chain-view { margin-left: auto; flex-shrink: 0; }

/* -- posture ------------------------------------------------------------
   Whether the links hold and whether the table is still append-only are
   two different facts, and the strip says both. A degraded posture is not
   a broken chain; an intact chain is not trustworthy on a writable table. */
.sa-posture {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 1px 7px;
  border-radius: var(--r-sm);
  border: 1px solid var(--border);
  font-size: 10.5px;
  font-weight: 500;
  color: var(--text-3);
  cursor: help;
}
.sa-posture[data-state="ok"] { color: var(--success); border-color: var(--success-border); background: var(--success-bg); }
.sa-posture[data-state="degraded"] { color: var(--danger); border-color: var(--danger-border); background: var(--danger-bg); }
.sa-posture[data-state="unknown"],
.sa-posture[data-state="unavailable"] { color: var(--text-3); background: var(--bg-surface); }


/* ============================================================
   8. COVERAGE GRID
   ============================================================
   Answers "what does this deployment audit". It replaced a one-line
   verdict about a set the operator could not enumerate. */
.sa-coverage { margin-top: var(--sp-5); }
.sa-coverage .sa-section-label {
  font-size: 12px; font-weight: 600; color: var(--text-2);
  margin-bottom: var(--sp-2);
}
.sa-cov-grid {
  display: grid; gap: var(--sp-3);
  grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
}
/* A card that navigates must be a real button: as a div with a click
   handler it is unreachable by keyboard and announces nothing. The UA
   button styling is reset so it still matches the surrounding cards. */
.sa-cov-card {
  display: flex; flex-direction: column; gap: 6px;
  text-align: left; font: inherit; color: inherit; width: 100%;
  background: var(--bg-elevated); border: 1px solid var(--border);
  border-radius: var(--r-md); padding: var(--sp-3);
  cursor: pointer; transition: border-color var(--t-base), background var(--t-base), transform var(--t-fast);
}
.sa-cov-card:hover { border-color: var(--accent); background: var(--bg-card); transform: translateY(-2px); }
.sa-cov-head { display: flex; align-items: center; gap: 8px; }
.sa-cov-name { font-size: 13px; font-weight: 600; flex: 1; min-width: 0; }
.sa-cov-head .sa-chain-dot { margin-left: auto; flex-shrink: 0; }
.sa-cov-attests { font-size: 11px; color: var(--text-3); line-height: 1.45; }
.sa-cov-count { font-size: 12px; color: var(--text-2); font-variant-numeric: tabular-nums; }
/* Muted, and never a number: an unread chain has not recorded nothing. */
.sa-cov-unknown { color: var(--text-4); font-style: italic; cursor: help; }
.sa-cov-alltime, .sa-cov-quiet { color: var(--text-4); font-size: 11px; }
.sa-coverage-empty {
  font-size: 12px; color: var(--text-3); padding: var(--sp-3);
  border: 1px dashed var(--border); border-radius: var(--r-md);
}


/* A toolbar row: the filters that scope the table under it.

   `.input-select { width: 100% }` is a FORM rule and every audit toolbar
   inherits it -- measured at 1600px the Cortex toolbar's two selects came
   out 470px each, one per line. Widths are capped here and the container
   query hands full width back only when the card is genuinely narrow, so
   the same markup reads as a toolbar on a desktop and as a stack on a
   phone. Scoped to this view: the form rule is correct where it was
   written. */
.sa-filter-row {
  display: flex; flex-wrap: wrap; align-items: center;
  gap: 8px; margin-bottom: var(--sp-3);
}
#view-security .sa-filter-row .input,
#view-security .sa-filter-row .input-select {
  width: auto; min-width: 132px; max-width: 200px; flex: 0 1 auto;
}
.sa-filter-row .sa-filter-spacer { flex: 1 1 auto; }
@container (max-width: 560px) {
  .sa-filter-row { gap: 6px; }
  #view-security .sa-filter-row .input,
  #view-security .sa-filter-row .input-select { max-width: none; flex: 1 1 140px; }
}

/* ============================================================
   9. STATES — empty, failed, locked, loading, paging
   ============================================================
   An empty result and a failed read are different claims about the
   operator's data and must never render the same. */
/* The pre-fetch seed row. This is NOT an empty state: nothing has been read
   yet, so it must not make a claim about the operator's data the way "No
   results" would. Quiet, centred, and it says what to do. */
.sa-table-seed.is-bad { color: var(--danger); }
.sa-table-seed {
  padding: var(--sp-8, 32px) var(--sp-4);
  text-align: center;
  font-size: 12.5px;
  color: var(--text-3);
}

.sa-empty-state {
  display: flex; flex-direction: column; align-items: center; gap: 7px;
  padding: var(--sp-8, 32px) var(--sp-4); text-align: center;
}
.sa-empty-state .sa-empty-icon {
  /* An inline SVG carrying a viewBox and NO width/height has no intrinsic
     size -- it resolves to 100% of its containing block. Inside a
     colspan=8 cell that is the entire panel, and the 1:1 viewBox makes
     the height follow it. Never leave a bare viewBox unsized. */
  width: 44px; height: 44px; flex: 0 0 auto;
  color: var(--text-4); margin-bottom: var(--sp-1);
}
.sa-empty-state .sa-empty-title { font-size: 13.5px; font-weight: 600; color: var(--text-2); }
.sa-empty-state .sa-empty-hint { font-size: 12px; color: var(--text-3); max-width: 52ch; line-height: 1.6; }

/* A banner naming the sources that dropped out of the page below it. */
.sa-banner {
  font-size: 12px; line-height: 1.5; padding: var(--sp-2) var(--sp-4);
  border-top: 1px solid var(--border); color: var(--text-2);
}
.sa-banner.is-warn {
  background: color-mix(in srgb, var(--warning) 10%, transparent);
  color: var(--text-1);
}

.sa-tier-notice {
  display: flex; align-items: center; gap: 10px;
  padding: 14px 16px; margin: var(--sp-3) 0;
  border: 1px solid var(--border-accent); border-radius: var(--r-md);
  background: var(--accent-dim); color: var(--text-2); font-size: 12.5px;
}
.sa-tier-notice svg { flex-shrink: 0; color: var(--accent); }
.sa-tier-notice strong { color: var(--accent); }

.sa-integrity-hint { font-size: 12px; color: var(--text-3); line-height: 1.6; }

.sa-skel { display: flex; flex-direction: column; gap: 9px; }
.sa-skel-line, .sa-skel-kpi {
  border-radius: var(--r-sm);
  background: linear-gradient(90deg, var(--bg-elevated) 25%, var(--bg-surface) 50%, var(--bg-elevated) 75%);
  background-size: 200% 100%;
  animation: sa-shimmer 1.4s linear infinite;
}
.sa-skel-line { height: 11px; width: 100%; }
.sa-skel-kpi { height: 92px; }
@keyframes sa-shimmer {
  from { background-position: 200% 0; }
  to   { background-position: -200% 0; }
}
@media (prefers-reduced-motion: reduce) {
  .sa-skel-line, .sa-skel-kpi { animation: none; }
}

.sa-pager-wrap { padding: 11px 14px; border-top: 1px solid var(--border); }
.sa-pager { display: flex; align-items: center; gap: 10px; justify-content: flex-end; }
.sa-pager-info { font-size: 11.5px; color: var(--text-3); font-variant-numeric: tabular-nums; margin-right: auto; }

.sa-null { color: var(--text-4); }

.sa-linkbtn {
  background: none; border: 0; padding: 0; font: inherit;
  color: var(--accent); cursor: pointer; text-decoration: underline;
  text-underline-offset: 2px;
}

/* One visible focus ring for every custom control in this view. Without
   it the disclosure, the KPI buttons and the inline links are all
   keyboard-reachable and none of them shows where the focus is. */
#view-security :is(.sa-disclose, .sa-kpi.is-clickable, .sa-linkbtn, .sa-cov-card, .sa-rank-row):focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: var(--r-sm);
}

.sa-sr-only {
  position: absolute; width: 1px; height: 1px;
  padding: 0; margin: -1px; overflow: hidden;
  clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

/* Staggered entrance, once per paint. `.sa-reveal` is added on a
   STRUCTURAL render only -- a background poll that rewrites the same rows
   must not replay it, which is what made the old board read as a hiccup
   every ten seconds. */
.sa-reveal { animation: sa-rise var(--t-slow) var(--ease-out) both; }
@keyframes sa-rise {
  from { opacity: 0; transform: translateY(6px); }
  to   { opacity: 1; transform: none; }
}
@media (prefers-reduced-motion: reduce) {
  .sa-reveal { animation: none; }
}

/* Desaturated badges inside this view: calmer than the global vivid set. */
#view-security .badge,
#view-security .severity-badge {
  background: color-mix(in srgb, var(--bg-elevated) 82%, transparent);
  border: 1px solid var(--border);
}
#view-security .badge-success { color: var(--success); border-color: var(--success-border); }
#view-security .badge-danger  { color: var(--danger);  border-color: var(--danger-border); }
#view-security .badge-warning { color: var(--warning); border-color: var(--warning-border); }
#view-security .badge-info    { color: var(--info);    border-color: var(--info-border); }

/* A toolbar control sizes to its content, not to the toolbar.
   `.input-select` carries `width: 100%` because it was written for form
   layouts, and inside a toolbar that makes every select claim the whole
   row -- measured at 1600px: two selects at 470px each, one per line, and
   three at 709px. Found by SCREENSHOT, and it affects every toolbar here. */
#view-security .view-actions .input-select,
#view-security .view-actions .input,
#view-security .sa-panel-tools .input-select,
#view-security .sa-panel-tools .input {
  width: auto;
  max-width: 100%;
}
@container (max-width: 640px) {
  /* Full width again once the card is narrow enough that side-by-side
     controls would be unusable. */
  #view-security .view-actions .input-select,
  #view-security .view-actions .input,
  #view-security .sa-panel-tools .input-select,
  #view-security .sa-panel-tools .input {
    width: 100%;
  }
}

@container (max-width: 640px) {
  #view-security .card-header { flex-wrap: wrap; gap: var(--sp-2); }
  #view-security .card-header > .card-title { flex: 1 1 100%; min-width: 0; }
  #view-security .card-header > .view-actions { flex-wrap: wrap; min-width: 0; max-width: 100%; }
  #view-security .card-header > .view-actions > :is(input, select) {
    flex: 1 1 140px; min-width: 0; max-width: 100%;
  }
  #view-security .card-header > .view-actions > button { flex: 0 0 auto; }
}

@media (max-width: 1100px) { .sa-kpi-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 560px)  { .sa-kpi-grid { grid-template-columns: 1fr; } }
