/* ── tokens ───────────────────────────────────────────────────────────
   Ground is a blue-biased near-black rather than neutral grey; the accent
   is a brass/signal amber borrowed from terminal warning text. Semantic
   colours are deliberately separate from the accent, so "this is
   interactive" and "this is dangerous" never collapse into one signal. */
:root {
  --ground:   #F6F7F9;
  --panel:    #FFFFFF;
  --panel-2:  #EFF2F5;
  --line:     #D8DEE5;
  --ink:      #111922;
  --ink-2:    #46545F;
  --muted:    #6C7A87;
  --accent:   #9A6F14;
  --ok:       #2F7D57;
  --ok-bg:    #E4F1EA;
  --warn:     #9A6F14;
  --warn-bg:  #F7EEDA;
  --crit:     #A83A31;
  --crit-bg:  #F8E5E3;
  --shadow:   0 1px 2px rgba(16,24,32,.06), 0 4px 14px rgba(16,24,32,.05);

  --mono: ui-monospace, "SF Mono", "Cascadia Mono", "Roboto Mono", Menlo, Consolas, monospace;
  --sans: ui-sans-serif, system-ui, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
}

@media (prefers-color-scheme: dark) {
  :root {
    --ground:  #0D131A;
    --panel:   #141D26;
    --panel-2: #1B2732;
    --line:    #26333F;
    --ink:     #E3EAF1;
    --ink-2:   #A9B7C4;
    --muted:   #778795;
    --accent:  #D9AE52;
    --ok:      #5FBE8B;
    --ok-bg:   #16301F;
    --warn:    #D9AE52;
    --warn-bg: #322709;
    --crit:    #E0776C;
    --crit-bg: #351A17;
    --shadow:  0 1px 2px rgba(0,0,0,.4), 0 6px 18px rgba(0,0,0,.32);
  }
}
:root[data-theme="dark"] {
  --ground:#0D131A; --panel:#141D26; --panel-2:#1B2732; --line:#26333F;
  --ink:#E3EAF1; --ink-2:#A9B7C4; --muted:#778795; --accent:#D9AE52;
  --ok:#5FBE8B; --ok-bg:#16301F; --warn:#D9AE52; --warn-bg:#322709;
  --crit:#E0776C; --crit-bg:#351A17;
  --shadow:0 1px 2px rgba(0,0,0,.4), 0 6px 18px rgba(0,0,0,.32);
}
:root[data-theme="light"] {
  --ground:#F6F7F9; --panel:#FFFFFF; --panel-2:#EFF2F5; --line:#D8DEE5;
  --ink:#111922; --ink-2:#46545F; --muted:#6C7A87; --accent:#9A6F14;
  --ok:#2F7D57; --ok-bg:#E4F1EA; --warn:#9A6F14; --warn-bg:#F7EEDA;
  --crit:#A83A31; --crit-bg:#F8E5E3;
  --shadow:0 1px 2px rgba(16,24,32,.06), 0 4px 14px rgba(16,24,32,.05);
}

*, *::before, *::after { box-sizing: border-box; }

/* Class rules below set `display` on elements that are also toggled with the `hidden`
   attribute (.banner, .live, .panel). A class selector outbids the user-agent's
   `[hidden] { display: none }`, so without this the shadow-mode banner, the error banner and the
   inspection panel are all visible before there is anything in them. */
[hidden] { display: none !important; }

body {
  margin: 0;
  background: var(--ground);
  color: var(--ink);
  font-family: var(--sans);
  font-size: 15px;
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
}
/* Fluid rather than capped at a fixed column width. This console is a data tool: the detections
   table carries nine columns and the domain roster carries nine more, and on a wide display the
   old 1180px cap left half the screen empty while the subject and sender columns ellipsed. Width
   here buys readable rows, so the page takes what the display offers.

   The cap exists only to stop the layout becoming absurd on an ultrawide; prose is constrained
   separately below, because a 2000px line of body text is genuinely harder to read than a 700px
   one and that argument does not apply to a table. */
.wrap { max-width: 2200px; margin: 0 auto; padding: 32px 24px 72px; display: flex; flex-direction: column; gap: 28px; }

/* Body copy stays at a readable measure however wide the page gets. ch units track the font, so
   this holds its meaning if the type scale changes. */
.banner p, .prose { max-width: 92ch; }
.dim { color: var(--muted); }
.prose { margin: 0; font-size: 13px; color: var(--ink-2); }

/* ── masthead ─────────────────────────────────────────────── */
.masthead { display: flex; flex-wrap: wrap; gap: 16px; align-items: baseline; justify-content: space-between; border-bottom: 1px solid var(--line); padding-bottom: 18px; }
.brand { display: flex; align-items: baseline; gap: 12px; flex-wrap: wrap; }
.brand h1 { font-family: var(--mono); font-size: 21px; font-weight: 600; letter-spacing: -.02em; margin: 0; }
.brand .env { font-family: var(--mono); font-size: 11px; letter-spacing: .1em; text-transform: uppercase; color: var(--muted); border: 1px solid var(--line); border-radius: 3px; padding: 2px 7px; }

/* Which customer is on screen.
   Deliberately the loudest thing in the masthead after the product name. Every number on this page
   belongs to exactly one customer, and for an MSP the failure mode of a quiet tenant label is
   triaging Chord's mail believing it is UTEX's — so it is set in the accent, at nearly heading
   size, and the <select> beside it is a control rather than the label itself. */
.brand .env.tenant {
  font-size: 14px; letter-spacing: .06em; font-weight: 600;
  color: var(--ink); background: color-mix(in srgb, var(--accent) 14%, transparent);
  border-color: color-mix(in srgb, var(--accent) 55%, var(--line)); padding: 3px 10px;
}
.tenant-select {
  font-family: var(--mono); font-size: 12px; color: var(--ink); background: var(--panel);
  border: 1px solid var(--line); border-radius: 4px; padding: 4px 8px; max-width: 220px;
}
.tenant-select:focus { outline: 2px solid color-mix(in srgb, var(--accent) 55%, transparent); outline-offset: 1px; }
.tenant-select:disabled { color: var(--muted); }

/* ── domain status control ────────────────────────────────────
   Next to the tenant picker, because it is the second thing that is true of exactly one customer.

   Nothing here is distinguished by colour alone, and the redundancy is threefold rather than
   twofold: a hue, a glyph whose SHAPE differs at every rung (filled square / filled diamond /
   tick / question mark), and a word. Take the colour away entirely — a greyscale screenshot pasted
   into a ticket, which is how this control will most often be read by the person who has to act on
   it — and the glyph and the word both still say which state it is in.

   The unknown state is deliberately the quietest thing in the masthead. It is not an alarm; it is
   an absence, and dressing it up as a warning would train operators to ignore the colour that
   actually means something. */
.dh-status { display: inline-flex; align-items: center; gap: 9px; }
.dh-status-ind { display: inline-flex; align-items: center; gap: 5px; padding-left: 9px; border-left: 1px solid var(--line); }
.dh-status-g { font-family: var(--mono); font-size: 12px; line-height: 1; }
.dh-status-w { font-family: var(--mono); font-size: 10.5px; letter-spacing: .04em; }

/* Qualified with .btn deliberately. These rules set the same three properties .btn does, and .btn
   is declared further down the file — at equal specificity the later rule wins, so an unqualified
   .dh-status-red would have painted the panel colour over the red. Verified in a browser: the
   symptom was a correctly classed, correctly glyphed button with no colour at all, which is the
   one failure mode a colour-plus-glyph design is meant to survive and is exactly why it does. */
.btn.dh-status-red    { border-color: color-mix(in srgb, var(--crit) 60%, var(--line)); background: var(--crit-bg); color: var(--crit); }
.btn.dh-status-red .dh-status-ind    { border-left-color: color-mix(in srgb, var(--crit) 40%, transparent); }
.btn.dh-status-amber  { border-color: color-mix(in srgb, var(--warn) 60%, var(--line)); background: var(--warn-bg); color: var(--warn); }
.btn.dh-status-amber .dh-status-ind  { border-left-color: color-mix(in srgb, var(--warn) 40%, transparent); }
.btn.dh-status-green  { border-color: color-mix(in srgb, var(--ok) 50%, var(--line)); background: var(--ok-bg); color: var(--ok); }
.btn.dh-status-green .dh-status-ind  { border-left-color: color-mix(in srgb, var(--ok) 35%, transparent); }
/* Neutral, and neutral in both themes: an unread report must not look like a passing one at a
   glance, and must not look like a failing one either. */
.btn.dh-status-unknown, .btn.dh-status-loading { color: var(--muted); background: var(--panel); }
.btn.dh-status-red:hover, .btn.dh-status-amber:hover, .btn.dh-status-green:hover { color: inherit; }
.dh-status:focus-visible { outline: 2px solid color-mix(in srgb, var(--accent) 55%, transparent); outline-offset: 1px; }

/* Visible to a screen reader, not to the eye. The tenant picker's label is the only control on the
   page whose purpose is not obvious from the value inside it. */
.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;
}
.stamp { font-family: var(--mono); font-size: 12px; color: var(--muted); text-align: right; }
.masthead-actions { display: flex; gap: 8px; justify-content: flex-end; margin-top: 8px; }

.live { display: inline-flex; align-items: center; gap: 7px; font-family: var(--mono); font-size: 12px; color: var(--ok); }
.dot { width: 7px; height: 7px; border-radius: 50%; background: var(--ok); box-shadow: 0 0 0 3px color-mix(in srgb, var(--ok) 18%, transparent); }

.btn {
  font-family: var(--mono); font-size: 11.5px; letter-spacing: .04em; text-transform: uppercase;
  color: var(--ink-2); background: var(--panel); border: 1px solid var(--line);
  border-radius: 4px; padding: 5px 10px; cursor: pointer; text-decoration: none; display: inline-block;
}
.btn:hover { border-color: var(--accent); color: var(--ink); }
.btn:disabled { opacity: .45; cursor: default; border-color: var(--line); }
.btn-primary { color: var(--ink); border-color: color-mix(in srgb, var(--accent) 55%, var(--line)); }

/* ── banners ──────────────────────────────────────────────── */
.banner { display: flex; gap: 14px; align-items: flex-start; background: var(--warn-bg); border: 1px solid color-mix(in srgb, var(--warn) 35%, transparent); border-radius: 6px; padding: 14px 16px; }
.banner .k { font-family: var(--mono); font-size: 11px; letter-spacing: .09em; text-transform: uppercase; color: var(--warn); white-space: nowrap; padding-top: 2px; }
.banner p { margin: 0; font-size: 14px; color: var(--ink-2); }
.banner strong { color: var(--ink); font-weight: 600; }
.banner-error { background: var(--crit-bg); border-color: color-mix(in srgb, var(--crit) 35%, transparent); }
.banner-error .k { color: var(--crit); }

/* ── stat row ─────────────────────────────────────────────── */
.stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(168px, 1fr)); gap: 1px; background: var(--line); border: 1px solid var(--line); border-radius: 6px; overflow: hidden; }
.stat { background: var(--panel); padding: 16px 18px; display: flex; flex-direction: column; gap: 5px; }
.stat .lbl { font-family: var(--mono); font-size: 10.5px; letter-spacing: .1em; text-transform: uppercase; color: var(--muted); }
.stat .val { font-family: var(--mono); font-size: 27px; font-weight: 600; letter-spacing: -.02em; font-variant-numeric: tabular-nums; line-height: 1.1; }
.stat .sub { font-size: 12.5px; color: var(--muted); }
.stat .val.ok { color: var(--ok); } .stat .val.crit { color: var(--crit); } .stat .val.warn { color: var(--warn); }

/* ── panels ───────────────────────────────────────────────── */
.panel { background: var(--panel); border: 1px solid var(--line); border-radius: 6px; box-shadow: var(--shadow); overflow: hidden; }
.panel > header { padding: 13px 18px; border-bottom: 1px solid var(--line); display: flex; align-items: baseline; justify-content: space-between; gap: 12px; background: var(--panel-2); }
.panel > header h2 { margin: 0; font-family: var(--mono); font-size: 12px; letter-spacing: .09em; text-transform: uppercase; font-weight: 600; color: var(--ink-2); }
.panel > header .note { font-family: var(--mono); font-size: 11.5px; color: var(--muted); font-variant-numeric: tabular-nums; }
.panel .body { padding: 16px 18px; display: flex; flex-direction: column; gap: 14px; }

/* ── verdict bar ──────────────────────────────────────────── */
.vbar { display: flex; height: 9px; border-radius: 5px; overflow: hidden; background: var(--panel-2); }
.vbar i { display: block; }
.vkey { display: flex; flex-wrap: wrap; gap: 18px; }
.vkey div { display: flex; align-items: baseline; gap: 7px; font-size: 13px; color: var(--ink-2); }
.vkey b { font-family: var(--mono); font-variant-numeric: tabular-nums; color: var(--ink); font-weight: 600; }
.swatch { width: 9px; height: 9px; border-radius: 2px; align-self: center; }

/* ── filters ──────────────────────────────────────────────── */
.filters { display: flex; flex-wrap: wrap; gap: 12px 16px; align-items: flex-end; }
.filters label { display: flex; flex-direction: column; gap: 4px; }
.filters label span { font-family: var(--mono); font-size: 10.5px; letter-spacing: .09em; text-transform: uppercase; color: var(--muted); }
.filters input, .filters select {
  font-family: var(--mono); font-size: 12.5px; color: var(--ink);
  background: var(--panel); border: 1px solid var(--line); border-radius: 4px; padding: 5px 8px;
}
.filters input:focus, .filters select:focus { outline: 2px solid color-mix(in srgb, var(--accent) 55%, transparent); outline-offset: 1px; }
/* A checkbox reads as a switch, not a field: laid out on the baseline with the other controls
   rather than stacked label-over-input like the text inputs. */
.filters label.check { flex-direction: row; align-items: center; gap: 7px; padding-bottom: 6px; }
.filters label.check input { margin: 0; accent-color: var(--accent); }
.filters label.check span { text-transform: none; letter-spacing: .02em; font-size: 12px; color: var(--ink-2); }
.filter-actions { display: flex; gap: 8px; }
.pager { display: flex; align-items: center; gap: 14px; }
.pager .note { font-family: var(--mono); font-size: 11.5px; color: var(--muted); font-variant-numeric: tabular-nums; }

/* ── tables ───────────────────────────────────────────────── */
.scroll { overflow-x: auto; }
table { width: 100%; border-collapse: collapse; font-size: 13px; }
th { text-align: left; font-family: var(--mono); font-size: 10.5px; letter-spacing: .09em; text-transform: uppercase; color: var(--muted); font-weight: 600; padding: 0 10px 8px 0; border-bottom: 1px solid var(--line); white-space: nowrap; }
td { padding: 9px 10px 9px 0; border-bottom: 1px solid color-mix(in srgb, var(--line) 55%, transparent); vertical-align: top; }
tr:last-child td { border-bottom: 0; }
td.m, th.m { font-family: var(--mono); font-variant-numeric: tabular-nums; }
td.num, th.num { text-align: right; padding-right: 14px; }
tbody tr.row { cursor: pointer; }
tbody tr.row:hover td { background: var(--panel-2); }
tbody tr.row.selected td { background: color-mix(in srgb, var(--accent) 10%, transparent); }
/* Scales with the viewport instead of sitting at a fixed width. The old flat 260px ellipsed
   subjects on a display with room to spare; the clamp keeps that value as the floor on a laptop
   and lets a wide screen spend its extra pixels on the two columns an operator actually reads. */
td.truncate { max-width: clamp(260px, 24vw, 620px); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Identifier columns. Held narrower than a subject line because a mailbox address and a raw
   object id are both fixed-shape values an operator scans rather than reads, and at equal width
   the recipient and mailbox pair pushes the verdict off the end of the table. */
td.truncate.id { max-width: clamp(190px, 16vw, 420px); }

/* Timestamps are a single unit of meaning and must not wrap mid-value. "7/27/26, 7:53 PM" broken
   across two lines doubles every row's height and reads as two separate facts. */
td.when { white-space: nowrap; }

.pill { display: inline-block; font-family: var(--mono); font-size: 10.5px; letter-spacing: .05em; text-transform: uppercase; padding: 2px 7px; border-radius: 3px; font-weight: 600; white-space: nowrap; }
.pill.junk { background: var(--crit-bg); color: var(--crit); }
.pill.none { background: var(--panel-2); color: var(--muted); }
.pill.flag { background: var(--warn-bg); color: var(--warn); }
.pill.ok   { background: var(--ok-bg);   color: var(--ok); }

.auth { font-family: var(--mono); }
.auth.fail { color: var(--crit); }
.auth.pass { color: var(--ok); }
.auth.soft { color: var(--warn); }
.auth.unknown { color: var(--muted); }

.score { font-family: var(--mono); font-weight: 600; font-variant-numeric: tabular-nums; }
.score.hi { color: var(--crit); } .score.mid { color: var(--warn); }

/* ── attachment state ─────────────────────────────────────────
   Three states, and the third one is the point. "Carried files", "carried none" and "was never
   measured" have to be told apart at a glance down a column of fifty rows, so they differ in
   GLYPH, in WORD and in SHAPE — the unrecorded state is the only one in a dashed box, which reads
   as a gap in the data even in greyscale and even to somebody skimming.

   Deliberately not toned with --crit or --warn. Carrying an attachment is not a finding; the score
   and verdict columns are where this row's danger is stated, and a second alarming colour beside
   them would compete with the one that means something. */
.attx { font-family: var(--mono); font-size: 11px; white-space: nowrap; }
.attx-yes { color: var(--ink-2); font-weight: 600; }
.attx-no { color: var(--muted); }
.attx-unknown {
  color: var(--muted); border: 1px dashed color-mix(in srgb, var(--muted) 50%, transparent);
  border-radius: 3px; padding: 0 5px;
}

/* The same fact restated where it governs a whole list rather than one row. */
.attach-caveat { border-left: 3px solid color-mix(in srgb, var(--muted) 50%, transparent); padding-left: 12px; }
.attach-caveat b { color: var(--ink); }

/* ── direction, protection, recipients ────────────────────────
   The same encoding rule the attachment cell established, applied to the three dimensions that
   arrived after it: an unmeasured value is a dashed outline, a question mark and the words "not
   recorded", so it survives greyscale, a screenshot and a reader who does not see the colour.

   None of the "not recorded" states is coloured as a warning. They are absences of measurement,
   not findings, and painting a third of a legacy roster amber would train operators to ignore
   amber on the rows where it means something. */
.dirx { font-family: var(--mono); font-size: 11px; white-space: nowrap; }
.dirx-in { color: var(--ink-2); }
.dirx-out { color: var(--accent); font-weight: 600; }
.dirx-unknown, .rcpx-unknown {
  color: var(--muted);
  border: 1px dashed color-mix(in srgb, var(--muted) 45%, transparent);
  border-radius: 3px; padding: 0 5px;
}

.secx { font-family: var(--mono); font-size: 11px; white-space: nowrap; }
.secx-p { display: inline-block; min-width: 1.1em; text-align: center; }
.secx-yes { color: var(--ok); font-weight: 600; }
.secx-no { color: var(--muted); }
/* Concealed is neither a pass nor a failure — the property is real and sealed where nothing here
   can read it — so it takes the accent rather than either semantic colour. */
.secx-concealed { color: var(--accent); font-weight: 600; }
.secx-unknown { color: var(--muted); }
.secx-note { color: var(--muted); font-size: 10.5px; }

.rcpx { font-family: var(--mono); font-size: 10.5px; color: var(--muted); }

/* ── self-send ────────────────────────────────────────────────
   Never crit. Red in this console means a conviction — a junked message, a domain anyone can spoof
   today — and a self-send match is a name that resembles another name. The flag takes the accent
   and the strength word rides beside it at all three rungs, so the cell cannot be read at a glance
   as anything stronger than "look at this". */
.ssx { font-family: var(--mono); font-size: 11px; white-space: nowrap; }
.ssx-no { color: var(--muted); }
.ssx-unknown {
  color: var(--muted);
  border: 1px dashed color-mix(in srgb, var(--muted) 45%, transparent);
  border-radius: 3px; padding: 0 5px;
}
.ssx-hit { color: var(--accent); }
.ssx-conf { font-size: 10px; letter-spacing: .04em; text-transform: uppercase; }
/* The rungs differ in weight as well as in the word, so the difference survives a greyscale
   screenshot. A weak match is deliberately the quietest thing here. */
.ssx-weak { opacity: .8; }
.ssx-weak .ssx-conf { color: var(--muted); }
.ssx-moderate .ssx-conf { color: var(--accent); }
.ssx-strong { font-weight: 600; }
.ssx-strong .ssx-conf { color: var(--accent); font-weight: 600; }

/* ── inspection ───────────────────────────────────────────── */
.kv { display: grid; grid-template-columns: repeat(auto-fit, minmax(210px, 1fr)); gap: 10px 20px; }
.kv div { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.kv .k { font-family: var(--mono); font-size: 10.5px; letter-spacing: .09em; text-transform: uppercase; color: var(--muted); }
.kv .v { font-family: var(--mono); font-size: 12.5px; word-break: break-all; }

.subhead { font-family: var(--mono); font-size: 11px; letter-spacing: .09em; text-transform: uppercase; color: var(--muted); font-weight: 600; margin: 6px 0 0; }

.find { border-left: 3px solid var(--line); padding: 0 0 0 14px; display: flex; flex-direction: column; gap: 6px; }
.find.crit { border-left-color: var(--crit); }
.find.warn { border-left-color: var(--warn); }
.find.info { border-left-color: var(--ok); }
.find h3 { margin: 0; font-size: 14px; font-weight: 600; display: flex; gap: 10px; align-items: baseline; }
.find p { margin: 0; font-size: 13px; color: var(--ink-2); }
.find .evidence { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 2px 18px; font-family: var(--mono); font-size: 11.5px; color: var(--ink-2); }
.find .evidence b { color: var(--muted); font-weight: 600; }

pre.hdr { font-family: var(--mono); font-size: 11.5px; line-height: 1.65; background: var(--panel-2); border: 1px solid var(--line); border-radius: 4px; padding: 12px 14px; overflow-x: auto; margin: 0; color: var(--ink-2); white-space: pre-wrap; word-break: break-word; }

details > summary { font-family: var(--mono); font-size: 11px; letter-spacing: .09em; text-transform: uppercase; color: var(--muted); cursor: pointer; }
details[open] > summary { margin-bottom: 10px; }

/* The rendered message.
   This was 55vh here and 62vh in the overlay, which put the actual mail — the thing the analyst
   opened the overlay to read — into a small scrolling box beneath a taller stack of the console's
   own furniture: the sanitiser's counters above it, the explanatory prose and the raw-headers
   disclosure below.

   It is now given as much height as the overlay can spare, with a large floor. Both bounds are
   viewport-relative as well as fixed: a frame that is taller than the screen cannot be read in one
   piece, and nests a scrollbar inside a scrollbar — the operator scrolls the overlay to reach the
   bottom of a box that is itself scrolling. So the fixed values are the ceiling and the floor, and
   vh decides which of them applies. On the 900px-tall laptop this has to survive, min(1240px, 86vh)
   resolves to 774px inside a body area of roughly 848px: the whole frame fits on screen at once,
   with nothing overflowing.

   `flex: none` is load-bearing and was the larger half of the problem. Both the overlay body and
   the panel body are flex COLUMNS, and a flex item shrinks below its declared height by default —
   so the frame was being squeezed down to whatever min-height said, 320px, no matter what height
   asked for. Raising the height alone would have changed nothing; measured in a browser, the frame
   rendered at exactly its min-height until this line was added. */
.bodyframe {
  flex: none;
  width: 100%; height: min(1100px, 78vh); min-height: min(560px, 62vh);
  border: 1px solid var(--line); border-radius: 4px; background: #fff;
}
.blocked { display: flex; flex-wrap: wrap; gap: 8px 16px; font-family: var(--mono); font-size: 11.5px; color: var(--muted); }
.blocked b { color: var(--ink-2); font-variant-numeric: tabular-nums; }

.empty { font-family: var(--mono); font-size: 12.5px; color: var(--muted); padding: 8px 0; }

/* ── attachments ──────────────────────────────────────────────
   Metadata only. There is deliberately no rule here for rendering attachment content — no
   preview, no thumbnail, no object or embed — because there is deliberately no such element. */
.att { width: 100%; }
.att td { vertical-align: middle; }
.att .name { font-family: var(--mono); font-size: 12.5px; word-break: break-all; max-width: 320px; }
.att .kindtag { display: inline-block; font-family: var(--mono); font-size: 10px; letter-spacing: .06em; text-transform: uppercase; padding: 1px 6px; border-radius: 3px; background: var(--panel-2); color: var(--muted); border: 1px solid var(--line); white-space: nowrap; }
.att .kindtag.item { color: var(--warn); border-color: color-mix(in srgb, var(--warn) 40%, var(--line)); }
.att .kindtag.reference { color: var(--crit); border-color: color-mix(in srgb, var(--crit) 40%, var(--line)); }

/* The hash is the most useful thing on the row: it identifies the file to VirusTotal or an
   internal corpus without the file leaving the tenant. Full 64 characters, monospace, selectable,
   with its own copy control — never truncated with an ellipsis. */
.hash { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.hash code { font-family: var(--mono); font-size: 11.5px; color: var(--ink); background: var(--panel-2); border: 1px solid var(--line); border-radius: 3px; padding: 2px 6px; word-break: break-all; user-select: all; }
.hash .btn { padding: 2px 8px; font-size: 10.5px; }

/* Why there is no hash, said in words.
   A blank cell used to mean one thing and now could mean two, and "we declined to transfer 25 MB to
   print 64 characters" and "this thing has no bytes at all" are opposite situations: one is a real
   file the analyst can still pull and hash themselves. Both get a word and a glyph. */
.hashstate { font-family: var(--mono); font-size: 11px; letter-spacing: .02em; white-space: nowrap; }
.hashstate.skipped { color: var(--warn); background: var(--warn-bg); border: 1px solid color-mix(in srgb, var(--warn) 35%, transparent); border-radius: 3px; padding: 1px 6px; }
.hashstate.none { color: var(--muted); }

/* A reference attachment's URL, shown exactly as a body link is: the real destination, printed as
   inert text. Not an anchor, so there is nothing to click and nothing to fetch. */
.defanged { font-family: var(--mono); font-size: 11.5px; color: var(--crit); background: var(--crit-bg); border: 1px dashed color-mix(in srgb, var(--crit) 40%, transparent); border-radius: 3px; padding: 1px 6px; word-break: break-all; user-select: all; }

/* ── domain health ────────────────────────────────────────────
   Thirty rows that have to be scannable at a glance, so the matrix is dense: monospace, tabular,
   fixed-width status tokens, one line per domain.

   Nothing here is distinguished by colour alone. The severity ramp varies HUE, FILL and GLYPH
   together — critical is a filled red square, high an outlined red triangle, medium a filled amber
   diamond, low an outlined amber circle, informational a bare grey ring — so the ordering survives
   a greyscale screenshot and a colourblind reader, both of which this table will meet. */
.dh-chips { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
.chip {
  font-family: var(--mono); font-size: 11px; letter-spacing: .06em; text-transform: uppercase;
  border: 1px solid var(--line); border-radius: 3px; padding: 3px 9px; background: var(--panel);
  color: var(--ink-2); cursor: pointer; display: inline-flex; align-items: center; gap: 6px;
}
.chip b { font-variant-numeric: tabular-nums; color: var(--ink); font-weight: 600; }
.chip:hover { border-color: var(--accent); }
.chip:focus-visible { outline: 2px solid color-mix(in srgb, var(--accent) 55%, transparent); outline-offset: 1px; }
.chip-zero { opacity: .5; }
.chip-flat { cursor: default; color: var(--muted); }
.chip-flat:hover { border-color: var(--line); }
.chip-crit .chip-g, .chip-high .chip-g { color: var(--crit); }
.chip-med  .chip-g, .chip-low  .chip-g { color: var(--warn); }
.chip-info .chip-g { color: var(--muted); }

.sev {
  display: inline-block; font-family: var(--mono); font-size: 10.5px; letter-spacing: .06em;
  font-weight: 600; white-space: nowrap; border-radius: 3px; padding: 1px 6px;
  border: 1px solid transparent;
}
.sev-crit  { background: var(--crit-bg); color: var(--crit); border-color: color-mix(in srgb, var(--crit) 45%, transparent); }
.sev-high  { background: transparent;    color: var(--crit); border-color: color-mix(in srgb, var(--crit) 45%, transparent); }
.sev-med   { background: var(--warn-bg); color: var(--warn); border-color: color-mix(in srgb, var(--warn) 40%, transparent); }
.sev-low   { background: transparent;    color: var(--warn); border-color: color-mix(in srgb, var(--warn) 40%, transparent); }
.sev-info  { background: transparent;    color: var(--muted); border-color: transparent; }
.sev-clean { background: var(--ok-bg);   color: var(--ok);   border-color: color-mix(in srgb, var(--ok) 35%, transparent); }

.st { font-family: var(--mono); font-size: 11.5px; white-space: nowrap; }
.st-ok   { color: var(--ok); }
.st-warn { color: var(--warn); }
.st-bad  { color: var(--crit); font-weight: 600; }
.st-na   { color: var(--muted); }

table.dh td { vertical-align: middle; }
table.dh .dh-name { font-size: 12.5px; word-break: break-all; max-width: 260px; }
.dh-toggle { padding: 2px 8px; font-size: 10.5px; }
tbody tr.dh-row:hover td { background: var(--panel-2); }

/* The expanded row. Set apart from the matrix above it, because it is prose and paste-ready records
   rather than something to scan. */
tr.dh-detail > td { background: var(--panel-2); padding: 14px 14px 18px 0; }
.dh-detail-head { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; margin-bottom: 12px; }
.dh-detail-name { font-family: var(--mono); font-size: 13px; font-weight: 600; word-break: break-all; }
.dh-find { margin-bottom: 14px; }
.dh-find h3 { align-items: center; }
.dh-find .dh-code { font-family: var(--mono); font-size: 10.5px; letter-spacing: .06em; margin: 0; }
.dh-crit  { border-left-color: var(--crit); }
.dh-high  { border-left-color: var(--crit); }
.dh-med   { border-left-color: var(--warn); }
.dh-low   { border-left-color: var(--warn); }
.dh-info  { border-left-color: var(--line); }

/* The records are the deliverable — somebody pastes these into a DNS provider — so they are set
   full width, unwrapped where possible, selectable, and each has its own copy control. */
.zone-list { display: flex; flex-direction: column; gap: 6px; margin-top: 8px; }
.zone-row { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.zone-row .btn { padding: 2px 8px; font-size: 10.5px; }
code.zone {
  font-family: var(--mono); font-size: 11.5px; color: var(--ink); background: var(--panel);
  border: 1px solid var(--line); border-radius: 3px; padding: 3px 8px;
  word-break: break-all; user-select: all;
}
.zone-purpose { font-size: 11.5px; }

/* A report older than the backend's own cache window is either one nobody re-ran or a scheduled
   refresh that stopped. Either way the records below it may already be fixed, or newly broken.
   Written against .note directly rather than against the panel header it used to sit in, so moving
   the report into an overlay did not silently take the staleness warning with it. */
.note.stale { color: var(--crit); font-weight: 600; }

#dh-legend .sev, #dh-legend .st { margin-right: 4px; }

/* ── full-screen inspection overlay ───────────────────────────
   Fixed to the viewport rather than laid out inside .wrap, so it keeps the whole display
   regardless of the page's own gutters. The header analysis grid and the Received chain are both
   wide tables and both read far better edge to edge. */
.overlay { position: fixed; inset: 0; z-index: 60; display: flex; background: color-mix(in srgb, #05080B 62%, transparent); }
.overlay-dialog { display: flex; flex-direction: column; width: 100%; height: 100%; background: var(--panel); }
.overlay-dialog:focus { outline: none; }
.overlay-head { display: flex; align-items: center; justify-content: space-between; gap: 20px; padding: 12px 28px; border-bottom: 1px solid var(--line); background: var(--panel-2); flex: none; }
.overlay-title { display: flex; align-items: baseline; gap: 14px; flex-wrap: wrap; min-width: 0; }
.overlay-title h2 { margin: 0; font-family: var(--mono); font-size: 12px; letter-spacing: .09em; text-transform: uppercase; font-weight: 600; color: var(--ink-2); }
.overlay-title .note { font-family: var(--mono); font-size: 11.5px; color: var(--muted); }
/* Only the inspection subtitle needs this: it carries a mailbox address or a raw object id, either
   of which is one long unbreakable token. The domain-status subtitle is a sentence and would be
   mangled by it. */
#inspect-note { word-break: break-all; }
.overlay-body { flex: 1; min-height: 0; overflow-y: auto; padding: 22px 28px 56px; display: flex; flex-direction: column; gap: 14px; }

/* Background scroll lock. Without it the page behind keeps scrolling under the overlay and the
   analyst loses their place in the detection list they came from. */
body.overlay-open { overflow: hidden; }

/* With the whole width available the identity and header-analysis grids can breathe instead of
   wrapping every value onto its own line. */
.overlay-body .kv { grid-template-columns: repeat(auto-fit, minmax(230px, 1fr)); gap: 12px 28px; }
.overlay-body .bodyframe { height: min(1240px, 86vh); min-height: min(640px, 70vh); }

/* ── sender blocks ────────────────────────────────────────────
   Declared here, after .btn and after the overlay rules, because several of these are single-class
   selectors that have to outbid earlier single-class ones — .btn-danger against .btn is the same
   trap the domain-status colours hit, where a correctly classed button rendered with no colour at
   all because .btn was declared later in the file.

   The masthead indicator borrows the domain-status geometry (.dh-status-ind and its children are
   reused) and differs only in what its states mean. There is deliberately NO green: domain status
   can honestly say "healthy", and this button cannot say anything of the kind, because the ledger
   it counts knows only what THIS console did — both live customers carry entries somebody made by
   hand in the Defender portal. "None from here" is muted rather than reassuring, and the only state
   that raises its voice is the one asking for work. */
.blk-status { display: inline-flex; align-items: center; gap: 9px; }
.btn.blk-status-check { border-color: color-mix(in srgb, var(--warn) 60%, var(--line)); background: var(--warn-bg); color: var(--warn); }
.btn.blk-status-check .dh-status-ind { border-left-color: color-mix(in srgb, var(--warn) 40%, transparent); }
/* In force is a statement, not an alarm. Somebody meant to do it. */
.btn.blk-status-active { border-color: color-mix(in srgb, var(--accent) 50%, var(--line)); color: var(--ink); }
.btn.blk-status-none, .btn.blk-status-unknown, .btn.blk-status-loading { color: var(--muted); background: var(--panel); }
.btn.blk-status-check:hover, .btn.blk-status-active:hover { color: inherit; }
.blk-status:focus-visible { outline: 2px solid color-mix(in srgb, var(--accent) 55%, transparent); outline-offset: 1px; }

/* The destructive control. Distinct from .btn-primary, which means "this is the main action here";
   this one means "this changes a customer's mail". Colour AND weight, so the difference between
   Apply and Block does not rest on hue alone. */
.btn-danger { color: var(--crit); border-color: color-mix(in srgb, var(--crit) 55%, var(--line)); font-weight: 600; }
.btn-danger:hover { border-color: var(--crit); color: var(--crit); background: var(--crit-bg); }
.btn-danger:disabled { color: var(--muted); border-color: var(--line); font-weight: 400; }

.overlay-head-actions { display: flex; gap: 8px; align-items: center; flex: none; }

/* Which customer this ledger belongs to, restated inside the overlay that covers the masthead. */
.blk-scope { display: flex; align-items: baseline; gap: 12px; flex-wrap: wrap; padding-bottom: 4px; }
.blk-scope-k { font-family: var(--mono); font-size: 10.5px; letter-spacing: .09em; text-transform: uppercase; color: var(--muted); }
.blk-scope-name {
  font-family: var(--mono); font-size: 17px; font-weight: 600; letter-spacing: -.01em; color: var(--ink);
  background: color-mix(in srgb, var(--accent) 14%, transparent);
  border: 1px solid color-mix(in srgb, var(--accent) 55%, var(--line));
  border-radius: 4px; padding: 2px 12px;
}
.blk-scope-slug { font-family: var(--mono); font-size: 11.5px; color: var(--muted); }

.blk-authority { border-left: 3px solid color-mix(in srgb, var(--muted) 50%, transparent); padding-left: 12px; }
.blk-authority b { color: var(--ink); }
.blk-caveat { border-left: 3px solid color-mix(in srgb, var(--muted) 50%, transparent); padding-left: 12px; }

table.blk td { vertical-align: top; }
.blk-target { font-size: 12.5px; font-weight: 600; word-break: break-all; }
.blk-sub { font-family: var(--mono); font-size: 10.5px; color: var(--muted); margin-top: 2px; }
.blk-check { font-family: var(--mono); font-size: 10.5px; color: var(--warn); font-weight: 600; margin-top: 3px; }
/* The consequence of a block already in force, on the row it belongs to. The operator reading this
   row is often not the operator who made it. */
.blk-warn { font-size: 11.5px; color: var(--ink-2); margin-top: 5px; max-width: 46ch; border-left: 2px solid color-mix(in srgb, var(--warn) 55%, transparent); padding-left: 8px; }
.blk-tiny, .blk-revoke { padding: 2px 8px; font-size: 10.5px; }
/* Unknown rows are the ones somebody has to act on, so the ROW carries the marker rather than only
   the status cell — it survives being scrolled sideways past that column. */
.blk-row-check td { background: color-mix(in srgb, var(--warn) 7%, transparent); }
.blk-row-check td:first-child { box-shadow: inset 3px 0 0 var(--warn); }

.blkst { font-family: var(--mono); font-size: 11px; white-space: nowrap; display: inline-block; }
.blkst-on { color: var(--ink); font-weight: 600; }
.blkst-off { color: var(--muted); }
.blkst-bad { color: var(--crit); font-weight: 600; }
/* Neither success nor failure, and the styling has to say so. Warn hue, a question mark, and the
   only dashed border in the table: the point is that it is unresolved, not that it is bad. */
.blkst-check {
  color: var(--warn); font-weight: 600;
  border: 1px dashed color-mix(in srgb, var(--warn) 55%, transparent);
  border-radius: 3px; padding: 0 6px;
}

/* ── the confirmation dialog ──────────────────────────────────
   Centred and bounded rather than full screen. It is asked ON TOP of the ledger or the message the
   operator was reading, and taking the whole viewport would remove the thing they are deciding
   about at the moment they decide. */
.overlay-centre { align-items: center; justify-content: center; padding: 20px; }
.overlay-modal {
  width: min(680px, 100%); height: auto; max-height: 100%;
  border: 1px solid var(--line); border-radius: 8px; box-shadow: var(--shadow); overflow: hidden;
}
.overlay-modal .overlay-body { padding: 20px 24px 24px; gap: 16px; }

/* The customer, first and largest. Applying one customer's block to another is the worst outcome
   this dialog has, and the defence that works is making the name impossible not to read. */
.blk-plate {
  display: flex; align-items: baseline; gap: 12px; flex-wrap: wrap;
  background: color-mix(in srgb, var(--accent) 10%, transparent);
  border: 1px solid color-mix(in srgb, var(--accent) 45%, var(--line));
  border-radius: 6px; padding: 12px 16px;
}
.blk-plate-k { font-family: var(--mono); font-size: 10.5px; letter-spacing: .09em; text-transform: uppercase; color: var(--muted); }
.blk-plate-name { font-family: var(--mono); font-size: 22px; font-weight: 600; letter-spacing: -.02em; color: var(--ink); }
.blk-plate-slug { font-family: var(--mono); font-size: 11.5px; color: var(--muted); }

.blk-sec { display: flex; flex-direction: column; gap: 8px; }
.blk-sec .subhead { margin: 0; }
.blk-input {
  font-family: var(--mono); font-size: 13px; color: var(--ink); background: var(--panel);
  border: 1px solid var(--line); border-radius: 4px; padding: 7px 10px; width: 100%;
}
.blk-input:focus { outline: 2px solid color-mix(in srgb, var(--accent) 55%, transparent); outline-offset: 1px; }
.blk-reason { resize: vertical; min-height: 48px; font-family: var(--sans); }
.blk-expiry { width: auto; }
.blk-kind { font-family: var(--mono); font-size: 11.5px; }
.blk-until { font-size: 12px; }
/* Shown only when the target contains characters that do not display as themselves — a right-to-left
   override pasted out of hostile mail makes the input show one address while another is sent. */
.blk-echo { margin: 0; font-size: 12px; color: var(--crit); }
.blk-echo code { font-family: var(--mono); background: var(--crit-bg); border-radius: 3px; padding: 1px 5px; user-select: all; }

/* The consequence. Deliberately the loudest prose in the dialog, and deliberately ABOVE the reason
   field rather than below it, so it is read on the way to committing rather than after the operator
   has already decided. */
.blk-consequence {
  margin: 0; font-size: 13.5px; color: var(--ink); background: var(--warn-bg);
  border: 1px solid color-mix(in srgb, var(--warn) 40%, transparent);
  border-left: 4px solid var(--warn);
  border-radius: 4px; padding: 12px 14px; max-width: none;
}

.blk-target-big { font-family: var(--mono); font-size: 16px; font-weight: 600; margin: 0; word-break: break-all; }
.blk-error { margin: 0; font-size: 13px; color: var(--crit); background: var(--crit-bg); border: 1px solid color-mix(in srgb, var(--crit) 35%, transparent); border-radius: 4px; padding: 10px 12px; }
.blk-actions { display: flex; gap: 10px; justify-content: flex-end; padding-top: 4px; }

.blk-result { margin: 0; font-family: var(--mono); font-size: 15px; font-weight: 600; }
.blk-result-on { color: var(--ink); }
.blk-result-off { color: var(--muted); }
.blk-result-bad { color: var(--crit); }
.blk-result-check { color: var(--warn); }

/* ── rules ────────────────────────────────────────────────────
   Declared after .btn, .btn-primary and .btn-danger for the reason written twice already in this
   file: several of these are single-class selectors that have to outbid earlier single-class ones,
   and at equal specificity the later rule wins. A .rul-status-active declared above .btn renders
   as a correctly classed, correctly glyphed button with no colour at all.

   The indicator has NO green state and no reassuring tone anywhere. Domain status can honestly say
   "healthy"; this cannot say anything of the kind. Six enabled rules is not good news — a rule
   flagging a hundred and ten purchase orders is six enabled rules too — and none enabled is not
   good news either. It reports a count, and the judgement stays with the operator. */
.rul-status { display: inline-flex; align-items: center; gap: 9px; }
.btn.rul-status-active { border-color: color-mix(in srgb, var(--accent) 50%, var(--line)); color: var(--ink); }
.btn.rul-status-check { border-color: color-mix(in srgb, var(--warn) 60%, var(--line)); background: var(--warn-bg); color: var(--warn); }
.btn.rul-status-check .dh-status-ind { border-left-color: color-mix(in srgb, var(--warn) 40%, transparent); }
.btn.rul-status-off, .btn.rul-status-empty,
.btn.rul-status-unknown, .btn.rul-status-loading { color: var(--muted); background: var(--panel); }
.btn.rul-status-active:hover, .btn.rul-status-check:hover { color: inherit; }
.rul-status:focus-visible { outline: 2px solid color-mix(in srgb, var(--accent) 55%, transparent); outline-offset: 1px; }

/* ── the roster ─────────────────────────────────────────────── */
.rul-what b { color: var(--ink); }

table.rul td { vertical-align: top; }
.rul-name { font-size: 12.5px; font-weight: 600; word-break: break-word; }
.rul-desc { font-size: 12px; color: var(--ink-2); margin-top: 2px; max-width: 46ch; }
/* Which conditions actually fired on one message. Prose in a table, so it is held to a measure. */
.rul-why { font-size: 11.5px; color: var(--ink-2); max-width: 40ch; }
.rul-when { font-size: 12px; color: var(--ink-2); max-width: 52ch; }
.rul-actions { white-space: nowrap; }
.rul-actions .btn { margin: 0 4px 4px 0; }
.rul-tiny { padding: 2px 8px; font-size: 10.5px; }

/* An enabled rule is the only kind affecting mail, so the ROW carries the marker rather than only
   the state cell — it survives being scrolled sideways past that column, exactly like an unknown
   block. Deliberately the accent and not a semantic colour: a rule being on is a thing somebody
   meant to do, not a warning and not an all-clear. */
.rul-row-on td:first-child { box-shadow: inset 3px 0 0 var(--accent); }

.rulst { font-family: var(--mono); font-size: 11px; white-space: nowrap; display: inline-block; }
.rulst-on { color: var(--ink); font-weight: 600; }
.rulst-off { color: var(--muted); }
/* Neither on nor off. Same dashed treatment every unmeasured value in this console gets, for the
   same reason: a state nobody recorded must not be able to pass for a state somebody set. */
.rulst-check {
  color: var(--warn); font-weight: 600;
  border: 1px dashed color-mix(in srgb, var(--warn) 55%, transparent);
  border-radius: 3px; padding: 0 6px;
}

/* A rule that declares an action other than Flag. This console's entire vocabulary rests on rules
   not touching mail, so a rule that says otherwise gets the loudest treatment on the roster —
   it is the one row where the surrounding prose has stopped being true. */
.rul-action-odd {
  font-size: 11.5px; color: var(--crit); background: var(--crit-bg);
  border: 1px solid color-mix(in srgb, var(--crit) 40%, transparent);
  border-radius: 3px; padding: 3px 7px; margin-top: 5px; max-width: 44ch;
}

/* ── the editor ─────────────────────────────────────────────── */
.rul-conds { display: flex; flex-direction: column; gap: 10px; }
.rul-cond {
  border: 1px solid var(--line); border-radius: 6px; padding: 12px 14px;
  background: var(--panel-2); display: flex; flex-direction: column; gap: 10px;
}
/* A condition this build cannot read. Dashed, like every other "we do not know" in this console. */
.rul-cond-opaque { border-style: dashed; border-color: color-mix(in srgb, var(--warn) 45%, var(--line)); }
.rul-cond-head { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.rul-and {
  font-family: var(--mono); font-size: 10.5px; letter-spacing: .1em; font-weight: 600;
  color: var(--muted); min-width: 3.5em;
}
.rul-cond-type { width: auto; max-width: 100%; flex: 1 1 260px; }
.rul-cond-unknown { font-family: var(--mono); font-size: 12px; color: var(--warn); flex: 1 1 auto; }
.rul-cond-head .btn { margin-left: auto; }
.rul-cond-fields { display: flex; flex-wrap: wrap; gap: 10px 16px; }
.rul-field { display: flex; flex-direction: column; gap: 4px; flex: 1 1 220px; min-width: 0; }
.rul-field-wide { flex: 1 1 100%; }
.rul-field > span:first-child {
  font-family: var(--mono); font-size: 10.5px; letter-spacing: .09em; text-transform: uppercase;
  color: var(--muted);
}
.rul-field-hint { font-size: 11.5px; color: var(--muted); }
/* The hint carries the measured reasoning behind a condition — why the mismatch list is three
   entries long, what a group name means — and those are paragraphs with deliberate breaks in them,
   so newlines in the text have to survive. */
.rul-cond-hint { white-space: pre-line; }
.rul-raw { max-height: 220px; overflow: auto; }

/* A closed vocabulary gets checkboxes rather than a comma-separated box. Ten spellings typed by
   hand is ten chances to be refused for a trailing space, and the refusal arrives a round trip
   later. */
.rul-choices { display: flex; flex-wrap: wrap; gap: 6px 14px; padding: 4px 0; }
.rul-choice { display: inline-flex; align-items: center; gap: 6px; font-size: 12.5px; color: var(--ink-2); }
.rul-choice input { margin: 0; accent-color: var(--accent); }

.rul-add { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
.rul-add-select { width: auto; max-width: 100%; flex: 1 1 320px; }
.rul-score-input { width: auto; max-width: 140px; }

/* The preview window sits WITH the preview button, not in a settings panel. Narrowing the dates is
   the answer to a scan that hit its ceiling, and that answer has to be within reach of the notice
   that raises the problem. */
.rul-window { display: flex; gap: 12px; align-items: flex-end; flex-wrap: wrap; }
.rul-date { width: auto; }

/* ── the preview ────────────────────────────────────────────────
   The centre of the whole feature, so it is given the visual weight of one: the count is set at
   stat-tile size because "130" and "20" are the two numbers the decision turns on, and a figure an
   operator has to hunt for is a figure they will skip. */
.rul-preview-btn { font-size: 12px; padding: 8px 14px; }
.rul-preview { display: flex; flex-direction: column; gap: 14px; }

.rul-head { display: flex; flex-wrap: wrap; gap: 12px; }
.rul-figure {
  display: flex; flex-direction: column; gap: 3px; padding: 12px 18px;
  border: 1px solid color-mix(in srgb, var(--accent) 45%, var(--line));
  background: color-mix(in srgb, var(--accent) 10%, transparent);
  border-radius: 6px; min-width: 200px;
}
.rul-figure-quiet { border-color: var(--line); background: var(--panel-2); }
.rul-figure-n {
  font-family: var(--mono); font-size: 27px; font-weight: 600; letter-spacing: -.02em;
  font-variant-numeric: tabular-nums; line-height: 1.1; color: var(--ink);
}
.rul-figure-l { font-size: 12.5px; color: var(--ink-2); }
.rul-figure-quiet .rul-figure-n { color: var(--ink-2); }

/* A scan that did not reach the end of the history. This is the single most consequential sentence
   in the feature — a preview that examined half the mail and came back clean looks exactly like a
   preview of a harmless rule — so it gets the same treatment as the block dialog's consequence:
   the loudest prose on the screen, and impossible to scroll past. */
.rul-cap {
  font-size: 13.5px; color: var(--ink); background: var(--warn-bg);
  border: 1px solid color-mix(in srgb, var(--warn) 40%, transparent);
  border-left: 4px solid var(--warn);
  border-radius: 4px; padding: 12px 14px;
}
/* An unstated truncation is still worth saying and is not the same claim, so it is quieter — but
   it is present, because silence here would be this console deciding the scan was complete. */
.rul-cap-quiet {
  background: var(--panel-2); color: var(--ink-2);
  border-color: var(--line); border-left-color: color-mix(in srgb, var(--muted) 55%, transparent);
}

.rul-stale {
  margin: 0; font-size: 13px; color: var(--crit); background: var(--crit-bg);
  border: 1px solid color-mix(in srgb, var(--crit) 35%, transparent);
  border-radius: 4px; padding: 10px 12px;
}

/* The breakdown by sender domain. The bar is what makes "110 of these are one system" land before
   anybody reads a number, so it is wide and the table is sorted by it. */
table.rul-break td { vertical-align: middle; }
.rul-bar {
  display: block; height: 9px; min-width: 120px; width: 100%;
  background: var(--panel-2); border-radius: 5px; overflow: hidden;
}
.rul-bar-fill { display: block; height: 100%; background: var(--accent); }

/* The gate. Its job is to be the sentence an operator reads on the way to the buttons, so it sits
   directly above them and changes colour with what it is saying. */
.rul-gate {
  margin: 0; font-size: 13.5px; border-radius: 4px; padding: 12px 14px; max-width: none;
  border: 1px solid var(--line); background: var(--panel-2); color: var(--ink-2);
}
.rul-gate b { color: var(--ink); }
.rul-gate-blocked {
  background: var(--warn-bg); color: var(--ink);
  border-color: color-mix(in srgb, var(--warn) 40%, transparent);
  border-left: 4px solid var(--warn);
}
.rul-gate-ready {
  background: var(--ok-bg); color: var(--ink);
  border-color: color-mix(in srgb, var(--ok) 35%, transparent);
  border-left: 4px solid var(--ok);
}

/* ── the match log ──────────────────────────────────────────── */
table.rlg td { vertical-align: top; }
/* The one word on the row that says what happened, and it says flagged. Deliberately the accent
   rather than a semantic colour: this is a record, not a verdict, and red here would read as the
   message having been dealt with. */
.rlg-flag { font-family: var(--mono); font-size: 11px; color: var(--accent); white-space: nowrap; }
.rul-renamed { font-family: var(--mono); font-size: 10.5px; color: var(--warn); margin-top: 2px; }
tr.rlg-detail > td { background: var(--panel-2); padding: 12px 14px 16px 0; }

footer { border-top: 1px solid var(--line); padding-top: 16px; font-size: 12.5px; color: var(--muted); display: flex; flex-wrap: wrap; gap: 6px 20px; justify-content: space-between; }
footer code { font-family: var(--mono); }

/* Laptop widths: the overlay keeps the full viewport, it just stops spending it on margins. */
@media (max-width: 1100px) {
  .overlay-head { padding: 10px 16px; }
  .overlay-body { padding: 16px 16px 40px; }
  .overlay-body .kv { grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); }
  .att .name { max-width: 200px; }
}

@media (max-width: 720px) {
  .wrap { padding: 20px 14px 48px; }
  .masthead, .stamp { text-align: left; }
  .masthead-actions { justify-content: flex-start; }
  /* A phone has no room to spend and the surrounding chrome is proportionally larger, so the frame
     takes what is left rather than what it would like. */
  .overlay-body .bodyframe { height: 72vh; min-height: 0; }
  .dh-status, .blk-status, .rul-status { width: 100%; justify-content: space-between; }

  /* The condition rows stop being side-by-side fields and become a stack. Two operands squeezed
     into a phone's width are two operands nobody can read the labels of, and the labels are what
     say which of "check" and "result" is which. */
  .rul-cond-fields { flex-direction: column; }
  .rul-cond-head .btn { margin-left: 0; }
  .rul-actions { white-space: normal; }
  .rul-head { flex-direction: column; }
  .rul-figure { min-width: 0; }
  .rul-when { max-width: none; }

  /* The overlay heads now carry two or three controls. On a phone they wrap under the title rather
     than squeezing it, and the confirmation gives up its margins so the plate and the consequence
     keep their size — those two are the whole point of the dialog. */
  .overlay-head { flex-wrap: wrap; }
  .overlay-head-actions { flex-wrap: wrap; }
  .overlay-centre { padding: 0; }
  .overlay-modal { width: 100%; height: 100%; max-height: none; border: 0; border-radius: 0; }
  .overlay-modal .overlay-body { padding: 16px 14px 32px; }
  .blk-plate-name { font-size: 18px; }
  /* Stacked, and the destructive one last: on a narrow screen the thumb rests at the bottom of the
     screen, and Cancel is the one that should be under it. */
  .blk-actions { flex-direction: column-reverse; }
  .blk-actions .btn { width: 100%; text-align: center; }
}
