/* ===========================================================================
   EZEmail console — design system
   ---------------------------------------------------------------------------
   Everything here is self-hosted. The Content-Security-Policy is
   `font-src 'self'` and `script-src 'self'`, so there is no CDN, no Google
   Fonts link and no external stylesheet anywhere in this console.

   The visual idea: this is infrastructure for people who need to know whether
   mail arrived. So numbers are the loudest thing on every page, they are set in
   tabular figures so columns line up and a change in a digit does not shift the
   layout, and every colour that appears on a metric MEANS a delivery state —
   green delivered, red bounced, amber deferred, indigo accepted. Colour is
   never decoration here; if something is green it is because it landed.

   The signature component is `.pipeline` — the proportional bar showing
   accepted → delivered / bounced / deferred. It is the one picture that answers
   the question this whole product exists to answer, and it replaces a row of
   four disconnected tiles that never added up to a story.
   =========================================================================== */

/* --- self-hosted faces ---------------------------------------------------- */
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url('/assets/fonts/inter-latin-wght-normal.woff2') format('woff2-variations');
}
@font-face {
  font-family: 'JetBrains Mono';
  font-style: normal;
  font-weight: 100 800;
  font-display: swap;
  src: url('/assets/fonts/jetbrains-mono-latin-wght-normal.woff2') format('woff2-variations');
}

/* --- tokens --------------------------------------------------------------- */
:root {
  --bg:          #f5f6f8;
  --surface:     #ffffff;
  --surface-2:   #fafbfc;
  --surface-3:   #f2f4f7;
  --border:      #e4e7ec;
  --border-strong:#d0d5dd;

  --text:        #101828;
  --text-soft:   #566175;
  --text-faint:  #8792a5;

  /* Indigo. Deliberately not the cyan-blue every email product uses. */
  --accent:      #5457e5;
  --accent-hover:#4547cc;
  --accent-soft: #eeefff;
  --accent-line: #c9caf7;
  --accent-text: #ffffff;

  /* Delivery states. These are data colours before they are brand colours. */
  --ok:        #087443;
  --ok-soft:   #e6f5ed;
  --ok-line:   #a7dcc1;
  --warn:      #a45c07;
  --warn-soft: #fdf3e3;
  --warn-line: #f0d3a0;
  --bad:       #b42318;
  --bad-soft:  #fdecea;
  --bad-line:  #f4bdb7;
  --info-soft: #eaf1fd;

  /* The dark rail. */
  --rail:        #0f1219;
  --rail-2:      #171b25;
  --rail-text:   #c7cede;
  --rail-faint:  #7b849a;
  --rail-active: #ffffff;

  --radius:    10px;
  --radius-sm: 6px;
  --radius-lg: 14px;
  --radius-pill: 999px;

  --shadow-sm: 0 1px 2px rgba(16,24,40,.05);
  --shadow:    0 1px 3px rgba(16,24,40,.08), 0 1px 2px rgba(16,24,40,.04);
  --shadow-lg: 0 12px 32px -8px rgba(16,24,40,.18), 0 4px 8px -4px rgba(16,24,40,.06);

  --sidebar-w: 248px;
  --topbar-h:  60px;

  --font: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
  --mono: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;

  /* One spacing scale, so nothing is spaced by feel. */
  --s1: 4px;  --s2: 8px;  --s3: 12px; --s4: 16px;
  --s5: 20px; --s6: 24px; --s7: 32px; --s8: 40px; --s9: 56px;
}

/* Dark applies when the VIEWER asks for it, or when explicitly set. Written as
   three blocks rather than one so an explicit choice beats the system setting
   in both directions — and so no colour has its only definition inside a media
   query, which is how a theme ends up half-applied. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
  --bg:          #0c0e13;
  --surface:     #14171f;
  --surface-2:   #191d26;
  --surface-3:   #1e222c;
  --border:      #262b36;
  --border-strong:#343a47;
  --text:        #e9ecf2;
  --text-soft:   #a3abbb;
  --text-faint:  #737c8d;
  --accent:      #7b7df0;
  --accent-hover:#8f91f4;
  --accent-soft: #1c1f3d;
  --accent-line: #33377a;
  --ok:        #4ade80; --ok-soft:  #0d2a1c; --ok-line:  #1d5238;
  --warn:      #fbbf5c; --warn-soft:#2c2211; --warn-line:#59431a;
  --bad:       #f87171; --bad-soft: #2c1614; --bad-line: #5c2724;
  --info-soft: #131c2e;
  --rail:      #090b0f;
  --rail-2:    #12151d;
  --shadow-sm: 0 1px 2px rgba(0,0,0,.4);
  --shadow:    0 1px 3px rgba(0,0,0,.5);
  --shadow-lg: 0 12px 32px -8px rgba(0,0,0,.6);
}
}

:root[data-theme="dark"] {
  --bg:          #0c0e13;
  --surface:     #14171f;
  --surface-2:   #191d26;
  --surface-3:   #1e222c;
  --border:      #262b36;
  --border-strong:#343a47;
  --text:        #e9ecf2;
  --text-soft:   #a3abbb;
  --text-faint:  #737c8d;
  --accent:      #7b7df0;
  --accent-hover:#8f91f4;
  --accent-soft: #1c1f3d;
  --accent-line: #33377a;
  --ok:        #4ade80; --ok-soft:  #0d2a1c; --ok-line:  #1d5238;
  --warn:      #fbbf5c; --warn-soft:#2c2211; --warn-line:#59431a;
  --bad:       #f87171; --bad-soft: #2c1614; --bad-line: #5c2724;
  --info-soft: #131c2e;
  --rail:      #090b0f;
  --rail-2:    #12151d;
  --shadow-sm: 0 1px 2px rgba(0,0,0,.4);
  --shadow:    0 1px 3px rgba(0,0,0,.5);
  --shadow-lg: 0 12px 32px -8px rgba(0,0,0,.6);
}

@media (prefers-color-scheme: light) {
  :root:not([data-theme="dark"]) { color-scheme: light; }
}

* { box-sizing: border-box; }

html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: var(--font);
  font-size: 14px;
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  /* Numbers line up in every table and tile. */
  font-variant-numeric: tabular-nums;
  font-feature-settings: 'cv05' 1, 'ss03' 1;
}

/* --- type scale ----------------------------------------------------------- */
h1, h2, h3, h4 { margin: 0; font-weight: 600; letter-spacing: -.018em; color: var(--text); }
h1 { font-size: 24px; line-height: 1.25; letter-spacing: -.028em; }
h2 { font-size: 15px; line-height: 1.35; }
h3 { font-size: 14px; }
p  { margin: 0 0 var(--s3); }
p:last-child { margin-bottom: 0; }

a { color: var(--accent); text-decoration: none; }
a:hover { text-decoration: underline; text-underline-offset: 2px; }

.muted  { color: var(--text-soft); }
.tiny   { font-size: 12px; line-height: 1.45; }
.mono   { font-family: var(--mono); font-size: 12.5px; letter-spacing: -.01em; }
.num    { font-variant-numeric: tabular-nums; }
.truncate { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 100%; }

/* An eyebrow that labels a group. Used for nav headings and card kickers. */
.nav-heading, .legend-label, .stat-label {
  font-size: 11px;
  font-weight: 600;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--text-faint);
}

.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;
}

.skip {
  position: absolute; left: -9999px; top: 0; z-index: 100;
  background: var(--accent); color: #fff; padding: var(--s3) var(--s4);
  border-radius: 0 0 var(--radius-sm) 0;
}
.skip:focus { left: 0; }

/* --- shell ---------------------------------------------------------------- */
.shell { display: flex; min-height: 100vh; }

.main-column {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  margin-left: var(--sidebar-w);
}

.main {
  flex: 1;
  padding: var(--s6) var(--s7) var(--s9);
  max-width: 1440px;
  width: 100%;
  outline: none;
}

/* --- sidebar -------------------------------------------------------------- */
.sidebar {
  position: fixed;
  inset: 0 auto 0 0;
  width: var(--sidebar-w);
  background: var(--rail);
  display: flex;
  flex-direction: column;
  z-index: 40;
  border-right: 1px solid rgba(255,255,255,.06);
}

.brand {
  display: flex; align-items: center; gap: var(--s3);
  height: var(--topbar-h);
  padding: 0 var(--s5);
  flex-shrink: 0;
  border-bottom: 1px solid rgba(255,255,255,.06);
}

.brand-mark {
  width: 28px; height: 28px; flex-shrink: 0;
  display: grid; place-items: center;
  border-radius: 7px;
  background: linear-gradient(140deg, var(--accent) 0%, #8b5cf6 100%);
  color: #fff;
  font-size: 14px;
  box-shadow: 0 2px 8px rgba(84,87,229,.4);
}
.brand-mark svg { width: 16px; height: 16px; }

.brand-name {
  font-size: 15px; font-weight: 600; letter-spacing: -.02em;
  color: #fff;
}

.nav { flex: 1; overflow-y: auto; padding: var(--s5) var(--s3); }
.nav::-webkit-scrollbar { width: 6px; }
.nav::-webkit-scrollbar-thumb { background: rgba(255,255,255,.12); border-radius: 3px; }

.nav-group + .nav-group { margin-top: var(--s5); }
.nav-group .nav-heading { padding: 0 var(--s3) var(--s2); color: var(--rail-faint); }

.nav-item {
  display: flex; align-items: center; gap: var(--s3);
  padding: 8px var(--s3);
  margin-bottom: 1px;
  border-radius: var(--radius-sm);
  color: var(--rail-text);
  font-size: 13.5px;
  font-weight: 450;
  line-height: 1.4;
  transition: background .12s ease, color .12s ease;
}
.nav-item:hover { background: rgba(255,255,255,.06); color: #fff; text-decoration: none; }
.nav-item.is-active { background: rgba(84,87,229,.22); color: var(--rail-active); font-weight: 550; }
.nav-item.is-active .nav-icon { color: var(--accent); opacity: 1; }

.nav-icon {
  width: 16px; height: 16px; flex-shrink: 0;
  opacity: .7;
  transition: opacity .12s ease;
}
.nav-item:hover .nav-icon { opacity: 1; }

/* Sub-navigation: the drill-down under an open parent. Indented to the
   parent's LABEL rather than to its icon, so the hierarchy reads down the text
   column; the rule on the left is what carries the eye between them. */
.nav-sub {
  margin: 2px 0 var(--s2);
  padding-left: 28px;
  border-left: 1px solid rgba(255,255,255,.10);
  margin-left: 15px;
}
.nav-subitem {
  display: block;
  padding: 6px var(--s3);
  margin-bottom: 1px;
  border-radius: var(--radius-sm);
  color: var(--rail-faint);
  font-size: 12.5px;
  font-weight: 450;
  line-height: 1.4;
  transition: background .12s ease, color .12s ease;
}
.nav-subitem:hover { background: rgba(255,255,255,.06); color: #fff; text-decoration: none; }
.nav-subitem.is-active { color: var(--rail-active); font-weight: 550; }

.sidebar-foot {
  padding: var(--s4) var(--s5) var(--s5);
  border-top: 1px solid rgba(255,255,255,.06);
  flex-shrink: 0;
}
.sidebar-foot .tiny { color: var(--rail-faint); margin-top: var(--s2); }

/* --- topbar --------------------------------------------------------------- */
.topbar {
  position: sticky; top: 0; z-index: 30;
  height: var(--topbar-h);
  display: flex; align-items: center; gap: var(--s4);
  padding: 0 var(--s7);
  background: color-mix(in srgb, var(--surface) 88%, transparent);
  backdrop-filter: saturate(180%) blur(12px);
  border-bottom: 1px solid var(--border);
}

.org-name { font-weight: 600; font-size: 14px; letter-spacing: -.01em; }
.topbar-right { margin-left: auto; display: flex; align-items: center; gap: var(--s3); }

/* The search box. Sits between the organization name and the account menu,
   which is where the eye already goes, and grows into the space the topbar has
   rather than claiming a fixed width — on a narrow window it is the first
   thing that should give way. */
.topbar-search {
  display: flex; align-items: center; gap: var(--s2);
  flex: 0 1 340px;
  min-width: 0;
  padding: 0 var(--s3);
  height: 32px;
  border: 1px solid var(--border);
  border-radius: var(--radius-pill);
  background: var(--surface-3, var(--surface));
  color: var(--text-soft);
}
.topbar-search:focus-within { border-color: var(--accent-line, var(--accent)); color: var(--text); }
.topbar-search input {
  flex: 1; min-width: 0;
  border: 0; padding: 0; height: auto;
  background: none;
  font-size: 13px;
  color: inherit;
}
.topbar-search input:focus { outline: none; box-shadow: none; }
/* WebKit draws its own clear button inside a type=search; it collides with the
   pill's right edge and cannot be styled, so it is removed. */
.topbar-search input::-webkit-search-decoration,
.topbar-search input::-webkit-search-cancel-button { -webkit-appearance: none; }

/* Hidden until the breakpoint hides the box, for the same reason .menu-toggle
   is: both are single-class selectors, so the later rule wins and this stays
   out of the way on a desktop. */
.topbar .topbar-search-link { display: none; }

/* Placed after .icon-button on purpose: both are single-class selectors, so the
   later rule wins and the toggle stays hidden until the breakpoint asks for it.
   Written the other way round, the button leaks onto every desktop page. */
.topbar .menu-toggle { display: none; }

/* --- page head ------------------------------------------------------------ */
.page-head {
  display: flex; align-items: flex-start; justify-content: space-between;
  gap: var(--s5); margin-bottom: var(--s6);
}
.page-head h1 + .muted { margin-top: var(--s1); font-size: 13.5px; }
.page-actions { display: flex; gap: var(--s2); flex-shrink: 0; }

.crumbs { font-size: 12.5px; color: var(--text-faint); margin-bottom: var(--s3); }
.crumbs a { color: var(--text-soft); }

/* --- cards ---------------------------------------------------------------- */
.card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow-sm);
  overflow: hidden;
}
/* Stacked cards space themselves; cards laid out BY a grid must not, or the
   grid's own gap gets a stray margin added to every item after the first and
   each row steps downwards. Admin tiles are `.card.stat`, which is exactly
   where this showed up. */
.card + .card { margin-top: var(--s5); }
.grid > .card + .card,
.grid > .card { margin-top: 0; }

.card-head {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--s4);
  padding: var(--s4) var(--s5);
  border-bottom: 1px solid var(--border);
  background: var(--surface);
}
.card-head h2 { font-size: 14px; font-weight: 600; }

.card-body { padding: var(--s5); }
.card-body.tight { padding: 0; }

/* --- grid ----------------------------------------------------------------- */
/* align-items: start, so a short card next to a long one keeps its own height.
   Stretching leaves a half-empty panel that reads as missing content. */
.grid { display: grid; gap: var(--s5); align-items: start; }
.grid-2 { grid-template-columns: repeat(2, minmax(0,1fr)); }
.grid-3 { grid-template-columns: repeat(3, minmax(0,1fr)); }
.grid-4 { grid-template-columns: repeat(4, minmax(0,1fr)); }

/* --- stat tiles ----------------------------------------------------------- */
.stat {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: var(--s5);
  box-shadow: var(--shadow-sm);
  position: relative;
  overflow: hidden;
}

/* A hairline of the metric's own colour. Tells you what kind of number this is
   before you read the label. */
.stat::before {
  content: ''; position: absolute; inset: 0 auto 0 0; width: 3px;
  background: var(--accent);
}
.stat.is-ok::before   { background: var(--ok); }
.stat.is-warn::before { background: var(--warn); }
.stat.is-bad::before  { background: var(--bad); }
.stat.is-quiet::before{ background: var(--border-strong); }

.stat-value {
  display: block;
  font-size: 30px;
  font-weight: 600;
  letter-spacing: -.035em;
  line-height: 1.1;
  margin-top: var(--s2);
  font-variant-numeric: tabular-nums;
}
.stat-sub { display: block; margin-top: var(--s1); font-size: 12px; color: var(--text-soft); }

/* --- THE SIGNATURE: delivery pipeline ------------------------------------- */
/*
   One proportional bar for where a send actually went. Four tiles can each be
   correct and still leave you unable to say whether a campaign went well; this
   cannot — the segments are the same total, so the shape IS the answer.
*/
.pipeline { display: block; }

.pipeline-bar {
  display: flex;
  height: 10px;
  border-radius: var(--radius-pill);
  overflow: hidden;
  background: var(--surface-3);
  margin-bottom: var(--s4);
}
.pipeline-seg {
  height: 100%;
  transition: width .5s cubic-bezier(.16,1,.3,1);
  min-width: 0;
}
.pipeline-seg.is-delivered { background: var(--ok); }
.pipeline-seg.is-bounced   { background: var(--bad); }
.pipeline-seg.is-deferred  { background: var(--warn); }
.pipeline-seg.is-pending   { background: var(--accent-line); }

.pipeline-legend {
  display: flex; flex-wrap: wrap; gap: var(--s5);
}
.legend-item { display: flex; align-items: baseline; gap: var(--s2); }
.legend-dot {
  width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0;
  transform: translateY(-1px);
}
.legend-dot.is-delivered { background: var(--ok); }
.legend-dot.is-bounced   { background: var(--bad); }
.legend-dot.is-deferred  { background: var(--warn); }
.legend-dot.is-pending   { background: var(--accent-line); }
.legend-value { font-size: 15px; font-weight: 600; font-variant-numeric: tabular-nums; }
.legend-label { display: block; }

/* --- badges & pills ------------------------------------------------------- */
/* The bare .badge is the NEUTRAL state, not an unstyled one. Statuses like
   `processing` and `queued` map to no variant on purpose; if the base carries
   no background they render as plain text beside a pill and the column reads as
   though only some rows have a status. */
.badge {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 2px 9px;
  border-radius: var(--radius-pill);
  font-size: 11.5px; font-weight: 550; line-height: 1.6;
  background: var(--surface-3);
  color: var(--text-soft);
  border: 1px solid var(--border);
  white-space: nowrap;
}
.badge-ok    { background: var(--ok-soft);   color: var(--ok);   border-color: var(--ok-line); }
.badge-warn  { background: var(--warn-soft); color: var(--warn); border-color: var(--warn-line); }
.badge-bad   { background: var(--bad-soft);  color: var(--bad);  border-color: var(--bad-line); }
.badge-quiet { background: var(--surface-3); color: var(--text-soft); border-color: var(--border); }
.badge-info  { background: var(--accent-soft); color: var(--accent); border-color: var(--accent-line); }

.pill {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 5px 11px;
  border-radius: var(--radius-sm);
  font-size: 12px; font-weight: 550;
  width: 100%;
}
.pill::before { content: ''; width: 6px; height: 6px; border-radius: 50%; background: currentColor; flex-shrink: 0; }
.pill-ok   { background: rgba(8,116,67,.16);  color: #5ee9a8; }
.pill-warn { background: rgba(164,92,7,.18);  color: #fbbf5c; }

/* --- buttons -------------------------------------------------------------- */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 7px;
  padding: 8px 14px;
  border-radius: var(--radius-sm);
  border: 1px solid var(--border-strong);
  background: var(--surface);
  color: var(--text);
  font-family: inherit; font-size: 13.5px; font-weight: 550; line-height: 1.4;
  cursor: pointer;
  white-space: nowrap;
  transition: background .12s ease, border-color .12s ease, box-shadow .12s ease, transform .06s ease;
  box-shadow: var(--shadow-sm);
}
.btn:hover { background: var(--surface-3); text-decoration: none; }
.btn:active { transform: translateY(.5px); }

.btn-primary {
  background: var(--accent); border-color: var(--accent); color: var(--accent-text);
  box-shadow: 0 1px 2px rgba(16,24,40,.08), inset 0 1px 0 rgba(255,255,255,.14);
}
.btn-primary:hover { background: var(--accent-hover); border-color: var(--accent-hover); }

.btn-danger { background: var(--bad); border-color: var(--bad); color: #fff; }
.btn-danger:hover { background: #9a1e15; border-color: #9a1e15; }

.btn-sm { padding: 5px 10px; font-size: 12.5px; }
.btn[disabled], .btn:disabled { opacity: .5; cursor: not-allowed; }

.icon-button {
  display: inline-grid; place-items: center;
  width: 34px; height: 34px;
  border: 1px solid transparent; background: transparent;
  border-radius: var(--radius-sm);
  color: var(--text-soft); cursor: pointer; font-size: 16px;
}
.icon-button:hover { background: var(--surface-3); color: var(--text); }

/* --- forms ---------------------------------------------------------------- */
.field { margin-bottom: var(--s4); }
.field:last-child { margin-bottom: 0; }

label {
  display: block;
  font-size: 13px; font-weight: 550;
  margin-bottom: 6px;
  color: var(--text);
}

input[type=text], input[type=email], input[type=password], input[type=search],
input[type=number], input[type=url], input[type=date], select, textarea {
  width: 100%;
  padding: 8px 11px;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-sm);
  background: var(--surface);
  color: var(--text);
  font-family: inherit; font-size: 13.5px; line-height: 1.5;
  transition: border-color .12s ease, box-shadow .12s ease;
  box-shadow: var(--shadow-sm);
}
textarea { min-height: 120px; resize: vertical; font-family: var(--mono); font-size: 12.5px; }

input:focus, select:focus, textarea:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-soft);
}

select {
  appearance: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%23566175' stroke-width='2.5' stroke-linecap='round'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 10px center;
  padding-right: 30px;
}

.hint { font-size: 12px; color: var(--text-soft); margin-top: 5px; }
.field-error { font-size: 12px; color: var(--bad); margin-top: 5px; font-weight: 500; }
input.is-error, textarea.is-error { border-color: var(--bad); }

.filters {
  display: flex; flex-wrap: wrap; gap: var(--s3); align-items: flex-end;
  padding: var(--s4) var(--s5);
  border-bottom: 1px solid var(--border);
  background: var(--surface-2);
}
.filters .field { margin-bottom: 0; min-width: 150px; }

/* Focus ring for anything keyboard-reachable. */
a:focus-visible, button:focus-visible, summary:focus-visible, .nav-item:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: var(--radius-sm);
}

/* --- tables --------------------------------------------------------------- */
.table-wrap { overflow-x: auto; }

table { width: 100%; border-collapse: collapse; font-size: 13.5px; }

thead th {
  position: sticky; top: 0;
  text-align: left;
  padding: 9px var(--s5);
  background: var(--surface-2);
  border-bottom: 1px solid var(--border);
  font-size: 11px; font-weight: 600;
  letter-spacing: .06em; text-transform: uppercase;
  color: var(--text-faint);
  white-space: nowrap;
}

tbody td {
  padding: 11px var(--s5);
  border-bottom: 1px solid var(--border);
  vertical-align: middle;
}
tbody tr:last-child td { border-bottom: 0; }
tbody tr { transition: background .1s ease; }
tbody tr:hover { background: var(--surface-2); }

td.num, th.num { text-align: right; font-variant-numeric: tabular-nums; }

/* --- key/value ------------------------------------------------------------ */
.kv { display: grid; grid-template-columns: minmax(140px, max-content) 1fr; gap: 9px var(--s5); }
.kv dt { color: var(--text-soft); font-size: 13px; }
.kv dd { margin: 0; font-size: 13.5px; }

/* --- notices -------------------------------------------------------------- */
.notice {
  display: flex; flex-wrap: wrap; gap: var(--s2) var(--s3);
  padding: var(--s4);
  border-radius: var(--radius);
  border: 1px solid var(--border);
  background: var(--surface-2);
  margin-bottom: var(--s5);
  font-size: 13.5px;
  /* Readiness blockers are long SCREAMING_SNAKE tokens with no break
     opportunity, so on a narrow screen the notice grew past the viewport and
     clipped its own "Details" link. Let them break rather than push. */
  min-width: 0;
  overflow-wrap: anywhere;
}
.notice > * { min-width: 0; }
.notice-ok   { background: var(--ok-soft);   border-color: var(--ok-line); }
.notice-warn { background: var(--warn-soft); border-color: var(--warn-line); }
.notice-bad  { background: var(--bad-soft);  border-color: var(--bad-line); }
.notice-info { background: var(--info-soft); border-color: var(--accent-line); }

/* --- empty state ---------------------------------------------------------- */
.empty { padding: var(--s9) var(--s5); text-align: center; }
.empty h3 { font-size: 15px; margin-bottom: var(--s2); }
.empty p { color: var(--text-soft); max-width: 46ch; margin-inline: auto; }
.empty .btn { margin-top: var(--s5); }

/* --- charts --------------------------------------------------------------- */
.chart { position: relative; width: 100%; height: 200px; }
.chart canvas { display: block; width: 100% !important; }

.legend { display: flex; gap: var(--s5); flex-wrap: wrap; margin-top: var(--s4); }

/* --- pager ---------------------------------------------------------------- */
.pager {
  display: flex; align-items: center; justify-content: space-between; gap: var(--s4);
  padding: var(--s3) var(--s5);
  border-top: 1px solid var(--border);
  background: var(--surface-2);
  font-size: 13px;
}

/* --- user menu ------------------------------------------------------------ */
.avatar {
  width: 28px; height: 28px; flex-shrink: 0;
  display: grid; place-items: center;
  border-radius: 50%;
  background: linear-gradient(140deg, var(--accent), #8b5cf6);
  color: #fff; font-size: 12px; font-weight: 600;
}

.menu { position: relative; }
.menu summary { list-style: none; cursor: pointer; }
.menu summary::-webkit-details-marker { display: none; }

/* An inline drawer: a <details> in a table cell whose panel expands the row
   rather than floating over it. Distinct from .menu-panel deliberately —
   a floating panel anchored to a table cell is clipped by the scroll
   container the table already needs. */
details > summary.btn { list-style: none; cursor: pointer; display: inline-flex; }
details > summary.btn::-webkit-details-marker { display: none; }
.drawer {
  margin-top: var(--s3);
  padding: var(--s4);
  min-width: 260px;
  background: var(--surface-2, var(--surface));
  border: 1px solid var(--border);
  border-radius: var(--radius);
  text-align: left;
  white-space: normal;
}
.drawer .field { margin-bottom: var(--s3); }
.drawer .field:last-of-type { margin-bottom: var(--s3); }

/* --- the route matrix ------------------------------------------------------
   A grid of routes: rows are senders, columns are receivers, cells carry both
   a colour and a number. The colour says what state the route is in; the
   number says what it may send this minute — a matrix showing only colour
   tells an operator something is wrong and nothing about how wrong. */
.matrix th.num, .matrix td.num { text-align: center; }
.matrix th { font-size: 11px; letter-spacing: .04em; text-transform: uppercase; }

.cell {
  display: inline-block;
  min-width: 34px;
  padding: 2px 6px;
  border-radius: var(--radius-sm);
  font-size: 12px;
  font-variant-numeric: tabular-nums;
  font-weight: 550;
  text-decoration: none;
}
.cell:hover { text-decoration: none; filter: brightness(.96); }

.cell-ok    { background: var(--ok-soft);   color: var(--ok); }
.cell-warn  { background: var(--warn-soft); color: var(--warn); }
.cell-bad   { background: var(--bad-soft);  color: var(--bad); }
.cell-probe { background: var(--info-soft); color: var(--accent); }
/* A route nothing has been sent down. Deliberately almost invisible: an empty
   cell is the absence of history, not a state, and colouring it would invite
   reading it as one. */
.cell-none  { color: var(--text-soft); opacity: .4; }

td.num.warn { color: var(--warn); font-weight: 550; }

/* --- the demo banner -------------------------------------------------------
   Deliberately loud and deliberately not dismissible. Its whole purpose is to
   stop a synthetic figure being quoted as a real one, and a marker that can be
   clicked away is a marker that will be. */
.demo-banner {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--s2);
  padding: 10px var(--s7);
  background: var(--warn-soft);
  border-bottom: 1px solid var(--warn-line);
  color: var(--warn);
  font-size: 12.5px;
  line-height: 1.5;
}
.demo-banner strong { font-weight: 650; }
.demo-banner a { margin-left: auto; color: inherit; text-decoration: underline; }

@media (max-width: 860px) {
  .demo-banner { padding: 10px var(--s4); }
  .demo-banner a { margin-left: 0; }
}

.user-chip {
  display: flex; align-items: center; gap: var(--s2);
  padding: 4px 9px 4px 4px;
  border-radius: var(--radius-pill);
  border: 1px solid var(--border);
  background: var(--surface);
  font-size: 13px; font-weight: 500;
}
.user-chip:hover { background: var(--surface-3); }

.menu-panel {
  position: absolute; right: 0; top: calc(100% + 6px);
  min-width: 230px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow-lg);
  padding: var(--s2);
  z-index: 50;
}
.menu-head { padding: var(--s3); border-bottom: 1px solid var(--border); margin-bottom: var(--s2); }
.menu-item {
  display: block; width: 100%; text-align: left;
  padding: 8px var(--s3);
  border: 0; background: transparent;
  border-radius: var(--radius-sm);
  color: var(--text); font-family: inherit; font-size: 13.5px; cursor: pointer;
}
.menu-item:hover { background: var(--surface-3); text-decoration: none; }
.menu-item.danger { color: var(--bad); }

/* --- auth / public pages -------------------------------------------------- */
.auth {
  min-height: 100vh;
  display: grid; place-items: center;
  padding: var(--s6);
  background:
    radial-gradient(900px 500px at 50% -10%, var(--accent-soft) 0%, transparent 60%),
    var(--bg);
}
.auth-card {
  width: 100%; max-width: 400px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg);
  padding: var(--s7);
}
.auth-brand {
  display: flex; align-items: center; justify-content: center; gap: var(--s3);
  margin-bottom: var(--s6);
  font-size: 17px; font-weight: 600; letter-spacing: -.02em;
}
.auth-card h1 { font-size: 19px; text-align: center; margin-bottom: var(--s2); }
.auth-card .muted { text-align: center; margin-bottom: var(--s6); font-size: 13px; }
.auth-card .btn { width: 100%; margin-top: var(--s2); padding: 10px; }

.public { min-height: 100vh; display: grid; place-items: center; padding: var(--s6); }
.public-card {
  max-width: 480px; width: 100%;
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius-lg); box-shadow: var(--shadow-lg);
  padding: var(--s7); text-align: center;
}

/* --- bars (inline meters) ------------------------------------------------- */
.bar { height: 6px; background: var(--surface-3); border-radius: var(--radius-pill); overflow: hidden; }
.bar > span { display: block; height: 100%; background: var(--accent); border-radius: inherit; }

/* --- responsive ----------------------------------------------------------- */
@media (max-width: 1100px) {
  .grid-4 { grid-template-columns: repeat(2, minmax(0,1fr)); }
  .grid-3 { grid-template-columns: repeat(2, minmax(0,1fr)); }
}

@media (max-width: 860px) {
  .sidebar { transform: translateX(-100%); transition: transform .22s cubic-bezier(.16,1,.3,1); }
  .sidebar.is-open { transform: none; box-shadow: var(--shadow-lg); }
  .main-column { margin-left: 0; }
  .topbar .menu-toggle { display: inline-grid; }
  .main { padding: var(--s5) var(--s4) var(--s8); }
  .topbar { padding: 0 var(--s4); }
  /* The box gives way; the link takes its place, so search is never a feature
     that exists only on a wide screen. */
  .topbar-search { display: none; }
  .topbar .topbar-search-link { display: inline-flex; }
  .grid-2, .grid-3, .grid-4 { grid-template-columns: minmax(0,1fr); }
  .page-head { flex-direction: column; align-items: stretch; }
  h1 { font-size: 21px; }
  .stat-value { font-size: 26px; }
  .kv { grid-template-columns: minmax(0,1fr); gap: 2px var(--s4); }
  .kv dd { margin-bottom: var(--s3); }
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .01ms !important;
    transition-duration: .01ms !important;
  }
}

@media print {
  .sidebar, .topbar, .page-actions, .pager { display: none; }
  .main-column { margin-left: 0; }
  .card { box-shadow: none; break-inside: avoid; }
}

/* A figure a provider states for itself, against one it inherits from the
   platform. The distinction matters on the provider-policy table: an operator
   asking "why is Microsoft stricter" needs to see which numbers were chosen
   for Microsoft and which are simply the defaults everybody gets. */
td.is-own { font-weight: 600; color: var(--text); }
td.is-own::after { content: ' *'; color: var(--muted); font-weight: 400; }
