/* ═══════════════════════════════════════════════════════════════════════════
   Arsenal — components
   One definition per component, shared by every view. The previous build
   kept two near-identical copies (indicators.html / phishing.html), which is
   the root cause of the whole "fixed on one tab, forgotten on the other"
   class of bugs in prompts/06, 17, 18, 19, 21, 22, 24.

   Requires tokens.css + base.css.
   Selectors are kept to a single class wherever possible so nothing here
   silently outranks a view-level rule.
   ═══════════════════════════════════════════════════════════════════════════ */

/* ═══ CONTROLS ═══════════════════════════════════════════════════════════ */

/* Buttons carry no hue. Emphasis comes from weight and fill, never colour —
   a coloured call-to-action would outrank a verdict for the eye, which is
   exactly the failure of the previous design. */
.btn{
  display:inline-flex;align-items:center;justify-content:center;gap:6px;
  height:26px;padding:0 11px;
  border:1px solid var(--line-hard);border-radius:var(--r-control);
  background:transparent;color:var(--ink-2);
  font-family:var(--f-struct);text-transform:uppercase;letter-spacing:var(--tr-mid);
  font-size:var(--t-micro);white-space:nowrap;
  transition:color var(--m-fast) var(--ease),
             border-color var(--m-fast) var(--ease),
             background var(--m-fast) var(--ease);
}
.btn svg{width:12px;height:12px;flex-shrink:0}
.btn:hover:not(:disabled){color:var(--ink);border-color:var(--ink-3)}
.btn:active:not(:disabled){background:var(--raised)}
.btn:disabled{opacity:.38}

.btn--strong{background:var(--ink);color:var(--bg);border-color:var(--ink);font-weight:600}
.btn--strong:hover:not(:disabled){background:var(--ink);color:var(--bg);border-color:var(--ink);opacity:.86}
.btn--strong:active:not(:disabled){background:var(--ink);opacity:.78}

.btn--quiet{border-color:transparent;color:var(--ink-3)}
.btn--quiet:hover:not(:disabled){border-color:var(--line);color:var(--ink)}

/* The single sanctioned exception to the achromatic rule: an irreversible
   action may borrow the malicious hue, because destruction is the risk the
   colour is for. */
.btn--destructive{border-color:var(--v-malicious);color:var(--v-malicious)}
.btn--destructive:hover:not(:disabled){
  background:var(--v-malicious-wash);color:var(--v-malicious);border-color:var(--v-malicious);
}

.btn--lg{height:32px;padding:0 14px;font-size:var(--t-label)}
.btn--block{display:flex;width:100%}

.icon-btn{
  width:26px;height:26px;flex-shrink:0;
  display:grid;place-items:center;
  color:var(--ink-3);border-radius:var(--r-control);
  transition:color var(--m-fast) var(--ease),background var(--m-fast) var(--ease);
}
.icon-btn svg{width:15px;height:15px}
.icon-btn:hover:not(:disabled){color:var(--ink);background:var(--raised)}
.icon-btn:disabled{opacity:.35}
.icon-btn--danger:hover:not(:disabled){color:var(--v-malicious);background:var(--v-malicious-wash)}

/* Segmented control — TR|EN, theme, any 2-3 way exclusive choice. */
.seg{display:inline-flex;border:1px solid var(--line);border-radius:var(--r-control);overflow:hidden;flex-shrink:0}
.seg > button{
  height:22px;padding:0 8px;
  font-family:var(--f-struct);text-transform:uppercase;letter-spacing:var(--tr-wide);
  font-size:var(--t-micro);color:var(--ink-3);
  transition:color var(--m-fast) var(--ease),background var(--m-fast) var(--ease);
}
.seg > button:hover{color:var(--ink-2)}
.seg > button[aria-pressed="true"]{background:var(--raised);color:var(--ink)}

/* Fields */
.field{
  width:100%;
  background:var(--sunken);
  border:1px solid var(--line);border-radius:var(--r-control);
  color:var(--ink);
  padding:0 9px;height:28px;
  transition:border-color var(--m-fast) var(--ease);
}
.field:hover{border-color:var(--line-hard)}
.field:focus{border-color:var(--ink-3)}
.field::placeholder{color:var(--ink-3)}
.field--area{
  height:auto;min-height:112px;padding:9px 10px;resize:vertical;
  font-family:var(--f-data);font-size:var(--t-data);line-height:var(--lh-data);
}
.field--data{font-family:var(--f-data);font-size:var(--t-data);font-variant-numeric:tabular-nums}
.field--select{appearance:none;-webkit-appearance:none;padding-right:26px;cursor:pointer}

.field-l{
  display:block;margin-bottom:3px;
  font-family:var(--f-struct);text-transform:uppercase;letter-spacing:var(--tr-mid);
  font-size:var(--t-micro);color:var(--ink-3);
}
.field-row{display:grid;grid-template-columns:1fr 1fr;gap:var(--s-2)}

/* Pinned action footer below the independently scrolling intake fields. */
.intake-actions{
  display:flex;flex-wrap:wrap;gap:var(--s-2);
  padding:var(--s-3) var(--s-4);
  flex-shrink:0;
  background:var(--pane);
  border-top:1px solid var(--line);
}
.intake-actions .btn{flex:0 0 auto}
.field-row .full{grid-column:1/-1}
.field-wrap{position:relative}
.field-wrap .caret-i{
  position:absolute;right:9px;top:50%;transform:translateY(-50%);
  width:12px;height:12px;color:var(--ink-3);pointer-events:none;
}

/* Checkbox — visible border in both themes (the previous white-alpha border
   vanished on light; prompts/09). */
.check{
  appearance:none;-webkit-appearance:none;
  width:14px;height:14px;flex-shrink:0;
  border:1px solid var(--line-hard);border-radius:2px;
  background:var(--sunken);cursor:pointer;position:relative;
  transition:background var(--m-fast) var(--ease),border-color var(--m-fast) var(--ease);
}
.check:hover{border-color:var(--ink-3)}
.check:checked{background:var(--ink);border-color:var(--ink)}
.check:checked::after{
  content:'';position:absolute;left:4px;top:1px;
  width:4px;height:8px;border:solid var(--bg);border-width:0 2px 2px 0;
  transform:rotate(42deg);
}
.check:indeterminate{background:var(--ink);border-color:var(--ink)}
.check:indeterminate::after{
  content:'';position:absolute;left:2px;top:5.5px;width:8px;height:2px;background:var(--bg);
}

/* Drop zone */
.drop{
  margin-top:var(--s-2);
  display:flex;align-items:center;gap:9px;
  padding:9px 11px;
  border:1px dashed var(--line-hard);border-radius:var(--r-control);
  color:var(--ink-3);font-size:var(--t-data);
  transition:border-color var(--m-fast) var(--ease),color var(--m-fast) var(--ease),
             background var(--m-fast) var(--ease);
}
.drop svg{width:15px;height:15px;flex-shrink:0}
.drop:hover{border-color:var(--ink-3);color:var(--ink-2)}
.drop.is-over{border-color:var(--ink);color:var(--ink);background:var(--raised)}

/* ═══ SIGNATURE: CONSENSUS BAR ═══════════════════════════════════════════
   Eight cells, fixed order — VirusTotal · AbuseIPDB · IPInfo · WHOIS · OTX ·
   Shodan · MalwareBazaar · URLhaus. Cell POSITION is the provider's identity,
   so the shape of the bar is readable at a glance without labels.

   The analyst's real question is never "what does one source say", it is
   "do the sources agree, and can I defend this in a ticket". This is that
   question rendered.

   It is also the loading state: cells resolve as providers answer.
   ═══════════════════════════════════════════════════════════════════════ */
.cons{display:inline-flex;align-items:center;gap:2px}
.cell{
  width:11px;height:16px;flex-shrink:0;
  border:1px solid var(--line-hard);border-radius:1px;
  background:transparent;
}
.cons--lg .cell{width:16px;height:22px}

.cell.is-malicious {background:var(--v-malicious); border-color:transparent}
.cell.is-suspicious{background:var(--v-suspicious);border-color:transparent}
.cell.is-clean     {background:var(--v-clean);     border-color:transparent}
.cell.is-trusted   {background:var(--v-trusted);   border-color:transparent}
.cell.is-unknown   {background:var(--v-unknown);   border-color:transparent;opacity:.45}
.cell.is-error     {background:var(--v-error);     border-color:transparent;opacity:.72}

/* not applicable to this indicator type — hollow, dotted */
.cell.is-na{border-style:dotted;opacity:.5}

/* provider needs an API key and none is configured — hatched */
.cell.is-nokey{
  background:repeating-linear-gradient(45deg,
    transparent,transparent 2px,var(--line-hard) 2px,var(--line-hard) 3px);
}

/* awaiting response */
.cell.is-wait{
  background:var(--ink-3);border-color:transparent;
  animation:cellWait 1.1s ease-in-out infinite;
}
@keyframes cellWait{0%,100%{opacity:.18}50%{opacity:.42}}

/* fill-in order when a batch resolves — the one orchestrated moment */
.cons.is-filling .cell{animation:cellIn var(--m-fill) var(--ease) backwards}
.cons.is-filling .cell:nth-child(1){animation-delay:0ms}
.cons.is-filling .cell:nth-child(2){animation-delay:40ms}
.cons.is-filling .cell:nth-child(3){animation-delay:80ms}
.cons.is-filling .cell:nth-child(4){animation-delay:120ms}
.cons.is-filling .cell:nth-child(5){animation-delay:160ms}
.cons.is-filling .cell:nth-child(6){animation-delay:200ms}
.cons.is-filling .cell:nth-child(7){animation-delay:240ms}
.cons.is-filling .cell:nth-child(8){animation-delay:280ms}
@keyframes cellIn{from{opacity:0;transform:scaleY(.4)}to{opacity:1;transform:scaleY(1)}}

.cons-n{
  margin-left:6px;
  font-family:var(--f-data);font-variant-numeric:tabular-nums;
  font-size:var(--t-label);color:var(--ink-3);
}

/* Legend, used in the evidence panel and the help overlay. */
.cons-legend{display:flex;flex-wrap:wrap;gap:var(--s-2) var(--s-4);align-items:center}
.cons-legend > span{
  display:inline-flex;align-items:center;gap:5px;
  font-size:var(--t-label);color:var(--ink-3);
}

/* ═══ VERDICT ════════════════════════════════════════════════════════════
   Colour is never alone: hue + icon + word, always in the same column.
   ═══════════════════════════════════════════════════════════════════════ */
.vd{
  display:inline-flex;align-items:center;gap:6px;
  font-family:var(--f-struct);text-transform:uppercase;letter-spacing:var(--tr-mid);
  font-size:10.5px;white-space:nowrap;
}
.vd svg{width:13px;height:13px;flex-shrink:0}
.vd.is-malicious {color:var(--v-malicious)}
.vd.is-suspicious{color:var(--v-suspicious)}
.vd.is-clean     {color:var(--v-clean)}
.vd.is-trusted   {color:var(--v-trusted)}
.vd.is-unknown   {color:var(--v-unknown)}
.vd.is-error     {color:var(--v-error)}

/* Indicator type — distinguished by GLYPH, not by colour. The previous build
   spent seven hues on indicator types, which competed with the verdicts. */
.ttag{
  display:inline-flex;align-items:center;gap:4px;
  padding:1px 5px;
  border:1px solid var(--line);border-radius:var(--r-chip);
  font-family:var(--f-struct);text-transform:uppercase;letter-spacing:var(--tr-mid);
  font-size:var(--t-micro);color:var(--ink-3);white-space:nowrap;
}
.ttag svg{width:10px;height:10px;flex-shrink:0}

/* ═══ DATA DISPLAY ═══════════════════════════════════════════════════════ */
.kv{display:flex;align-items:baseline;justify-content:space-between;gap:var(--s-3);font-size:11.5px}
.kv > dt{color:var(--ink-3);flex-shrink:0}
.kv > dd{
  font-family:var(--f-data);font-variant-numeric:tabular-nums;
  color:var(--ink-2);text-align:right;
  min-width:0;overflow-wrap:anywhere;
}
.kv > dd.is-technical{
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
}

.chips{display:flex;flex-wrap:wrap;gap:4px}
.chip{
  padding:1px 5px;
  border:1px solid var(--line);border-radius:var(--r-chip);
  font-family:var(--f-data);font-size:10.5px;color:var(--ink-3);
  white-space:nowrap;
}
.chip.is-malicious {border-color:var(--v-malicious); color:var(--v-malicious)}
.chip.is-suspicious{border-color:var(--v-suspicious);color:var(--v-suspicious)}
.chip.is-clean     {border-color:var(--v-clean);     color:var(--v-clean)}
.chip.is-trusted   {border-color:var(--v-trusted);   color:var(--v-trusted)}

.meter{display:flex;height:4px;border-radius:2px;overflow:hidden;background:var(--sunken)}
.meter > i{display:block;height:100%}
.meter .seg-malicious {background:var(--v-malicious)}
.meter .seg-suspicious{background:var(--v-suspicious)}
.meter .seg-clean     {background:var(--v-clean)}
.meter .seg-unknown   {background:var(--v-unknown);opacity:.4}

.count{
  font-family:var(--f-data);font-variant-numeric:tabular-nums;
  font-size:var(--t-label);color:var(--ink-3);
}
.count b{color:var(--ink-2);font-weight:600}
.count .is-malicious{color:var(--v-malicious);font-weight:600}
.count .is-trusted{color:var(--v-trusted);font-weight:600}

/* Domain age — the WHOIS signal an analyst reads first on a phishing host. */
.age{
  display:flex;align-items:baseline;gap:7px;
  padding:7px 9px;border:1px solid var(--line);border-radius:var(--r-control);
}
.age-n{font-family:var(--f-data);font-size:22px;line-height:1;font-variant-numeric:tabular-nums}
.age-u{font-size:var(--t-label);color:var(--ink-3)}
.age.is-suspicious{border-color:var(--v-suspicious);background:var(--v-suspicious-wash)}
.age.is-suspicious .age-n{color:var(--v-suspicious)}
.age.is-malicious{border-color:var(--v-malicious);background:var(--v-malicious-wash)}
.age.is-malicious .age-n{color:var(--v-malicious)}
.age.is-clean .age-n{color:var(--v-clean)}

/* Health indicator. Two honest states plus a derived one — the broker's
   /health returns no per-provider map, so no fake "degraded" is invented. */
.health{
  display:inline-flex;align-items:center;gap:6px;flex-shrink:0;
  font-family:var(--f-struct);text-transform:uppercase;letter-spacing:var(--tr-wide);
  font-size:var(--t-micro);color:var(--ink-3);
}
.health-dot{width:6px;height:6px;border-radius:50%;background:var(--v-unknown);flex-shrink:0}
.health.is-up .health-dot{background:var(--v-clean)}
.health.is-down .health-dot{background:var(--v-malicious)}
.health.is-down{color:var(--v-malicious)}

/* Rail variant: dot only. The label stays in the DOM for screen readers and
   the state reaches a mouse user through the tooltip. */
.health--rail{
  width:32px;height:26px;flex-shrink:0;
  display:grid;place-items:center;
}
.health--rail .health-dot{width:7px;height:7px}
.health--rail.is-down .health-dot{box-shadow:0 0 0 3px var(--v-malicious-wash)}

/* ═══ QUEUE ══════════════════════════════════════════════════════════════
   One surface. The row IS the evidence container: the previous build split
   a staged-indicator table from a separate CTI list, so reading one
   indicator meant looking in two places.
   ═══════════════════════════════════════════════════════════════════════ */
/* 96px on the type column: the full Defender type names (DomainName,
   FileSha256) are what the analyst edits and what the export carries, so they
   are shown in full rather than abbreviated. 64px clipped them into the value. */
.q-head,
.q-row-main{
  display:grid;
  grid-template-columns:26px 96px minmax(0,1fr) 190px 176px 58px;
  gap:var(--s-3);align-items:center;
  padding:0 var(--s-4);
}
.q-head{
  height:28px;flex-shrink:0;
  position:sticky;top:0;z-index:var(--z-sticky);
  background:var(--pane);border-bottom:1px solid var(--line);
  font-family:var(--f-struct);text-transform:uppercase;letter-spacing:var(--tr-wide);
  font-size:var(--t-micro);color:var(--ink-3);
}
.q-row{border-bottom:1px solid var(--line-soft)}
.q-row-main{
  min-height:var(--row-h);
  cursor:pointer;
  transition:background var(--m-fast) var(--ease);
}
.q-row:hover .q-row-main,
.q-row.is-open .q-row-main{background:var(--pane)}
.q-row.is-selected .q-row-main{background:var(--raised)}
.q-row:focus-visible{outline:2px solid var(--ink);outline-offset:-2px}
.q-val{font-family:var(--f-data);font-size:var(--t-data);color:var(--ink);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
/* Holds the row's delete button and the disclosure caret side by side. Given a
   single 26px track they stacked and pushed every row to 52px tall. */
.q-caret{
  display:flex;align-items:center;justify-content:flex-end;gap:var(--s-1);
  color:var(--ink-3);
}
.q-caret svg{width:13px;height:13px;transition:transform var(--m-base) var(--ease)}
.q-row.is-open .q-caret [data-action="toggle"] svg{transform:rotate(90deg)}

/* Inline evidence, indented to align under the indicator value. */
.q-evidence{
  background:var(--pane);
  padding:0 var(--s-4) var(--s-4) calc(var(--s-4) + 26px + 96px + var(--s-3) * 2);
}

/* Queue toolbar: what is staged, and what you can do to all of it. Sits above
   the column headings so the count reads as a caption for the rows below and
   the actions sit at the end of the scan, not buried in app chrome. */
.q-toolbar{
  display:flex;align-items:center;gap:var(--s-2);
  padding:0 var(--s-4);height:38px;flex-shrink:0;
  border-bottom:1px solid var(--line-soft);
}

/* Bulk action bar, shown while rows are selected. */
.bulk{
  display:flex;align-items:center;gap:var(--s-3);
  padding:0 var(--s-4);height:32px;flex-shrink:0;
  background:var(--raised);border-bottom:1px solid var(--line);
}

/* ═══ EVIDENCE CARDS ═════════════════════════════════════════════════════
   header (provider + status) · body (type-specific) · footer (external link)
   ═══════════════════════════════════════════════════════════════════════ */
.ev-grid{
  display:grid;grid-template-columns:repeat(auto-fill,minmax(232px,1fr));
  gap:1px;background:var(--line-soft);border:1px solid var(--line-soft);
}
.ev-card{
  background:var(--bg);padding:10px 12px;
  display:flex;flex-direction:column;gap:7px;min-width:0;
}
.ev-head{display:flex;align-items:center;justify-content:space-between;gap:var(--s-2)}
.ev-name{
  font-family:var(--f-struct);text-transform:uppercase;letter-spacing:var(--tr-mid);
  font-size:var(--t-micro);color:var(--ink-2);
}
.ev-status{
  padding:1px 5px;border:1px solid currentColor;border-radius:var(--r-chip);
  font-family:var(--f-struct);text-transform:uppercase;letter-spacing:var(--tr-mid);
  font-size:9.5px;color:var(--ink-3);white-space:nowrap;
}
.ev-status.is-malicious {color:var(--v-malicious)}
.ev-status.is-suspicious{color:var(--v-suspicious)}
.ev-status.is-clean     {color:var(--v-clean)}
.ev-status.is-trusted   {color:var(--v-trusted)}
.ev-status.is-unknown   {color:var(--v-unknown)}
.ev-status.is-error     {color:var(--v-error)}
.ev-foot{margin-top:auto;padding-top:5px;border-top:1px solid var(--line-soft)}
.ev-link{
  display:inline-flex;align-items:center;gap:4px;
  font-size:var(--t-label);color:var(--ink-3);text-decoration:none;
  transition:color var(--m-fast) var(--ease);
}
.ev-link:hover{color:var(--ink)}
.ev-cache{
  font-family:var(--f-data);font-size:9.5px;color:var(--ink-3);
  border:1px solid var(--line);border-radius:var(--r-chip);padding:0 4px;
}

/* ═══ GUARDRAIL ══════════════════════════════════════════════════════════
   Stopping an analyst from blocking core infrastructure is this product's
   reason to exist, so the guardrail lives at the point of action — inside
   the row, next to the control it governs — not in a dismissible banner at
   the top of the page, and not behind a native confirmation dialog.
   ═══════════════════════════════════════════════════════════════════════ */
.guard{
  display:flex;align-items:center;gap:var(--s-3);flex-wrap:wrap;
  margin-bottom:var(--s-3);padding:10px 12px;
  border:1px solid var(--v-trusted);background:var(--v-trusted-wash);
}
.guard-ico{color:var(--v-trusted);flex-shrink:0}
.guard-ico svg{width:17px;height:17px}
.guard-t{
  font-family:var(--f-struct);text-transform:uppercase;letter-spacing:var(--tr-mid);
  font-size:var(--t-label);color:var(--v-trusted);
}
.guard-d{font-size:var(--t-data);color:var(--ink-2)}

/* A row that is BOTH flagged and trusted infrastructure. The conflict is
   shown, never collapsed — that combination is precisely what a human has
   to adjudicate. */
.guard.is-conflict{border-color:var(--v-suspicious);background:var(--v-suspicious-wash)}
.guard.is-conflict .guard-ico,
.guard.is-conflict .guard-t{color:var(--v-suspicious)}

.lock{
  display:inline-flex;align-items:center;gap:7px;
  height:26px;padding:0 10px;
  border:1px solid var(--line-hard);border-radius:var(--r-control);
  background:var(--sunken);color:var(--ink-2);font-size:var(--t-data);
}
.lock svg{width:12px;height:12px;color:var(--v-trusted)}
.guard.is-conflict .lock svg{color:var(--v-suspicious)}

/* Two-step override, in place of the old native confirmation dialog. */
.override{display:flex;align-items:center;gap:var(--s-2);flex-wrap:wrap}
.override-q{font-size:var(--t-data);color:var(--ink)}

/* ═══ KQL ════════════════════════════════════════════════════════════════ */
.kql{display:flex;min-height:100%;width:100%}
.kql-gutter{
  flex-shrink:0;min-width:38px;padding:10px 9px 10px 0;
  text-align:right;user-select:none;
  /* The gutter is newline-separated line numbers. Without pre they reflow as
     inline text and the flex item grows to the full length of the run,
     shoving the code column off screen. */
  white-space:pre;
  border-right:1px solid var(--line-soft);
  font-family:var(--f-data);font-size:11.5px;line-height:1.72;
  font-variant-numeric:tabular-nums;
  color:var(--ink-3);opacity:.6;
}
.kql-code{
  flex:1;min-width:0;padding:10px 14px;margin:0;
  font-family:var(--f-data);font-size:11.5px;line-height:1.72;
  color:var(--ink-2);white-space:pre;tab-size:2;
}
.kql-key{color:var(--k-key);font-weight:600}
.kql-str{color:var(--k-str)}
.kql-com{color:var(--k-com);font-style:italic}
.kql-op {color:var(--k-op)}
.kql-tab{color:var(--k-tab)}
.kql-ind{color:var(--k-ind)}
.kql-code ::selection,
.kql-code::selection{background:var(--ink);color:var(--bg)}

/* ═══ FEEDBACK ═══════════════════════════════════════════════════════════ */

/* Toasts — one implementation, fully tokenised, correct in both themes.
   The previous build hardcoded a dark bubble, which stayed dark on white
   (prompts/06 — still live on the phishing tab before this rewrite). */
/* Fixed width, not shrink-to-fit. A position:fixed flex column sizes itself to
   its widest child, so every toast used to re-measure whenever another one
   arrived or expired - a one-line confirmation was narrow on its own and wide
   next to a broker error. */
/* Bottom right, clearing the query bar (--toast-bottom, set in app.js). Top
   right put the stack squarely on the queue toolbar, and since a toast takes
   pointer events, Export raised a toast that then swallowed clicks on Export. */
.toasts{
  position:fixed;bottom:var(--toast-bottom,var(--s-4));right:var(--s-4);z-index:var(--z-toast);
  display:flex;flex-direction:column;gap:var(--s-2);
  pointer-events:none;
  width:340px;max-width:calc(100vw - var(--s-8));
}
.toast{
  pointer-events:auto;position:relative;overflow:hidden;width:100%;
  background:var(--raised);border:1px solid var(--line-hard);
  border-left-width:2px;border-radius:var(--r-control);
  box-shadow:var(--shadow-pop);
  padding:9px 11px;
  animation:toastIn var(--m-base) var(--ease);
}
@keyframes toastIn{from{opacity:0;transform:translateX(24px)}to{opacity:1;transform:none}}
.toast.is-out{animation:toastOut var(--m-base) var(--ease) forwards}
@keyframes toastOut{to{opacity:0;transform:translateX(24px)}}
.toast-top{display:flex;align-items:center;gap:var(--s-2);margin-bottom:2px}
.toast-t{
  flex:1;
  font-family:var(--f-struct);text-transform:uppercase;letter-spacing:var(--tr-mid);
  font-size:var(--t-micro);color:var(--ink);
}
.toast-close{width:22px;height:22px;flex:0 0 auto}
.toast-close svg{width:12px;height:12px}
/* One line is the floor, so a bare confirmation is the same height as a
   one-line message. Longer copy still wraps and grows. */
.toast-m{
  font-size:var(--t-data);line-height:var(--lh-data);color:var(--ink-2);
  min-height:calc(var(--t-data) * var(--lh-data));
}
.toast.is-ok   {border-left-color:var(--v-clean)}
.toast.is-warn {border-left-color:var(--v-suspicious)}
.toast.is-error{border-left-color:var(--v-malicious)}
.toast-bar{position:absolute;left:0;bottom:0;height:2px;width:100%;background:var(--line)}
.toast-bar > i{display:block;height:100%;background:var(--ink-3);transform-origin:left;
  animation:toastProg var(--ttl,4500ms) linear forwards}
.toast.is-paused .toast-bar > i{animation-play-state:paused}
@keyframes toastProg{from{transform:scaleX(1)}to{transform:scaleX(0)}}

/* System banner. Reserved for system-level conditions (broker unreachable).
   Verdicts live on their row, never in a page-level banner. */
.banner{
  display:flex;align-items:center;gap:var(--s-3);
  padding:9px var(--s-4);
  border-bottom:1px solid var(--v-malicious);
  background:var(--v-malicious-wash);
  font-size:var(--t-data);color:var(--ink);
}
.banner svg{width:15px;height:15px;color:var(--v-malicious);flex-shrink:0}

/* Empty states are invitations, not apologies. */
.empty{
  display:flex;flex-direction:column;align-items:center;justify-content:center;
  gap:var(--s-2);padding:var(--s-12) var(--s-6);text-align:center;color:var(--ink-3);
}
.empty-ico{
  width:40px;height:40px;display:grid;place-items:center;
  border:1px solid var(--line);border-radius:var(--r-control);color:var(--ink-3);
}
.empty-ico svg{width:18px;height:18px}
.empty-t{
  font-family:var(--f-struct);text-transform:uppercase;letter-spacing:var(--tr-mid);
  font-size:var(--t-label);color:var(--ink-2);
}
.empty-d{font-size:var(--t-data);max-width:38ch}

/* Tooltip — a real component, because native title= is unreachable by
   keyboard and cannot be styled. */
.tip{
  position:absolute;z-index:var(--z-pop);
  background:var(--raised);color:var(--ink);
  border:1px solid var(--line-hard);border-radius:var(--r-control);
  box-shadow:var(--shadow-pop);
  padding:5px 8px;font-size:var(--t-label);max-width:280px;
  pointer-events:none;white-space:pre-line;
}

/* ═══ OVERLAYS ═══════════════════════════════════════════════════════════ */
.scrim{
  position:fixed;inset:0;z-index:var(--z-modal);
  background:var(--scrim);
  display:grid;place-items:center;padding:var(--s-6);
  animation:fade var(--m-base) var(--ease);
}
@keyframes fade{from{opacity:0}to{opacity:1}}
.dialog{
  width:100%;max-width:460px;max-height:82vh;overflow:auto;
  background:var(--pane);border:1px solid var(--line-hard);
  box-shadow:var(--shadow-modal);
}
.dialog-head{
  display:flex;align-items:center;justify-content:space-between;gap:var(--s-3);
  padding:var(--s-3) var(--s-4);border-bottom:1px solid var(--line);
}
.dialog-t{
  font-family:var(--f-struct);text-transform:uppercase;letter-spacing:var(--tr-mid);
  font-size:var(--t-label);color:var(--ink);
}
.dialog-body{padding:var(--s-4)}
.dialog-foot{
  display:flex;justify-content:flex-end;gap:var(--s-2);
  padding:var(--s-3) var(--s-4);border-top:1px solid var(--line);
}

/* Command palette — Ctrl+K. Specified in the founding brief, never built
   until now. */
.palette{
  width:100%;max-width:560px;align-self:start;margin-top:12vh;
  background:var(--pane);border:1px solid var(--line-hard);
  box-shadow:var(--shadow-modal);
}
.palette-input{
  width:100%;height:44px;padding:0 var(--s-4);
  background:transparent;border:none;border-bottom:1px solid var(--line);
  color:var(--ink);font-size:var(--t-lead);
}
.palette-input::placeholder{color:var(--ink-3)}
.palette-list{max-height:44vh;overflow:auto;padding:var(--s-1) 0}
.palette-item{
  display:flex;align-items:center;gap:var(--s-3);
  width:100%;padding:0 var(--s-4);height:34px;
  color:var(--ink-2);font-size:var(--t-body);
}
.palette-item svg{width:15px;height:15px;color:var(--ink-3);flex-shrink:0}
.palette-item[aria-selected="true"]{background:var(--raised);color:var(--ink)}
.palette-item .hint{margin-left:auto;font-family:var(--f-data);font-size:var(--t-micro);color:var(--ink-3)}

.kbd{
  display:inline-block;min-width:18px;padding:1px 5px;
  border:1px solid var(--line-hard);border-bottom-width:2px;border-radius:var(--r-chip);
  background:var(--sunken);color:var(--ink-2);
  font-family:var(--f-data);font-size:10.5px;text-align:center;
}
.help-row{
  display:flex;align-items:center;justify-content:space-between;gap:var(--s-4);
  padding:6px 0;border-bottom:1px solid var(--line-soft);font-size:var(--t-data);
}
.help-row:last-child{border-bottom:none}
.help-keys{display:flex;gap:4px;flex-shrink:0}

/* ═══ HISTORY ════════════════════════════════════════════════════════════ */
.h-card{
  display:grid;grid-template-columns:minmax(0,1fr) auto auto;
  gap:var(--s-3);align-items:center;
  padding:var(--s-3);border:1px solid var(--line);
  background:var(--pane);margin-bottom:var(--s-2);
  transition:border-color var(--m-fast) var(--ease);
}
.h-card:hover{border-color:var(--line-hard)}
.h-preview{
  font-family:var(--f-data);font-size:var(--t-data);color:var(--ink-2);
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
}
.h-meta{
  font-family:var(--f-data);font-size:var(--t-label);color:var(--ink-3);
  font-variant-numeric:tabular-nums;white-space:nowrap;
}
