:root {
  --bg: #0f172a; --panel: #1e293b; --panel2: #273449; --border: #334155;
  --text: #e2e8f0; --muted: #94a3b8; --accent: #818cf8; --accent2: #34d399;
  --code: #0b1220; --danger: #ef4444; --danger-dim: #7f1d1d; --warn: #fcd34d;
  --key: #7dd3fc; --str: #86efac; --num: #fca5a5; --bool: #fcd34d; --null: #94a3b8;
  --row-hover: rgba(129,140,248,0.08);
  color-scheme: dark;
}
/* ── Shiny controls theme ──────────────────────────────────────────────────
   Shiny.Blazor.Controls draws from its own token set (shiny-theme.css), which is a light palette
   that flips to its own dark one on prefers-color-scheme. This admin is dark whatever the OS says,
   so the seeds are repointed at the palette above and everything the controls derive follows.

   The selector is deliberately heavier than the one it overrides: shiny-theme.css's dark block is
   ":root:not(.shiny-theme-light)", so a plain ":root" here would lose to it on a dark-mode machine. */
html:root:not(.shiny-theme-light) {
  --shiny-background: var(--bg);
  --shiny-foreground: var(--text);
  --shiny-card: var(--panel);
  --shiny-popover: var(--panel2);
  --shiny-muted: var(--panel2);
  --shiny-muted-foreground: var(--muted);
  --shiny-primary: var(--accent);
  --shiny-primary-foreground: #0b1020;
  --shiny-secondary: var(--panel2);
  --shiny-secondary-foreground: var(--text);
  --shiny-accent: var(--accent2);
  --shiny-accent-foreground: #0b1020;
  --shiny-destructive: var(--danger);
  --shiny-destructive-foreground: #fff;
  --shiny-success: var(--accent2);
  --shiny-success-foreground: #0b1020;
  --shiny-info: var(--key);
  --shiny-info-foreground: #0b1020;
  --shiny-warning: var(--warn);
  --shiny-warning-foreground: #0b1020;
  --shiny-caution: var(--warn);
  --shiny-caution-foreground: #0b1020;
  --shiny-critical: var(--danger);
  --shiny-critical-foreground: #fff;
  --shiny-border: var(--border);
  --shiny-input: var(--border);
  --shiny-ring: var(--accent);
  --shiny-font-sans: inherit;
  --shiny-radius: 8px;
}

* { box-sizing: border-box; }
html, body { height: 100%; }
body {
  margin: 0; font-family: system-ui, -apple-system, "Segoe UI", sans-serif;
  background: var(--bg); color: var(--text); line-height: 1.5;
  -webkit-text-size-adjust: 100%;
}
#app { min-height: 100vh; display: flex; flex-direction: column; }
.muted { color: var(--muted); }
h1:focus { outline: none; }

/* ── Boot / spinners ───────────────────────────────────────────────────── */
.boot { flex: 1; display: grid; place-content: center; justify-items: center; gap: 18px; min-height: 100vh; }
.spinner {
  display: inline-block; width: 28px; height: 28px; border-radius: 50%;
  border: 3px solid var(--border); border-top-color: var(--accent);
  animation: spin 0.8s linear infinite; vertical-align: middle;
}
.spinner.sm { width: 14px; height: 14px; border-width: 2px; border-color: rgba(11,16,32,0.25); border-top-color: #0b1020; }
@keyframes spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) { .spinner { animation-duration: 2.4s; } }

.brand-mark {
  display: inline-grid; place-items: center; width: 44px; height: 44px; border-radius: 12px;
  background: linear-gradient(135deg, #a5b4fc, var(--accent)); color: #0b1020;
  font-weight: 800; font-size: 0.95rem; letter-spacing: -0.02em; flex-shrink: 0;
  box-shadow: 0 6px 24px rgba(129,140,248,0.35);
}
.brand-mark.sm { width: 32px; height: 32px; border-radius: 9px; font-size: 0.75rem; box-shadow: none; }

/* ── Buttons ───────────────────────────────────────────────────────────── */
button, .btn-link {
  font-family: inherit; font-size: 0.82rem; border-radius: 6px; cursor: pointer;
  border: 1px solid transparent; padding: 7px 12px; transition: background 0.12s, border-color 0.12s;
  display: inline-flex; align-items: center; justify-content: center; gap: 8px; text-decoration: none;
}
button:disabled { opacity: 0.5; cursor: not-allowed; }
button:focus-visible, a:focus-visible, input:focus-visible, select:focus-visible, summary:focus-visible {
  outline: 2px solid var(--accent); outline-offset: 2px;
}
.btn-primary { background: var(--accent); color: #0b1020; font-weight: 700; }
.btn-primary:hover:not(:disabled) { background: #a5b4fc; }
.btn-ghost { background: var(--panel2); color: var(--text); border-color: var(--border); }
.btn-ghost:hover:not(:disabled) { background: #334155; }
.btn-danger-ghost { background: transparent; color: #fca5a5; border-color: var(--danger-dim); }
.btn-danger-ghost:hover:not(:disabled) { background: var(--danger-dim); color: #fff; }
.btn-sm { padding: 4px 9px; font-size: 0.78rem; }
.btn-block { width: 100%; padding: 11px 14px; font-size: 0.92rem; }

/* ── Inputs ────────────────────────────────────────────────────────────── */
input[type="search"], input[type="password"], input[type="text"], input[type="number"],
input[type="datetime-local"], select, textarea {
  background: var(--code); color: var(--text); border: 1px solid var(--border);
  border-radius: 6px; padding: 8px 10px; font-size: 0.85rem; font-family: inherit;
  max-width: 100%;
}
textarea { resize: vertical; }
input:focus, select:focus, textarea:focus { outline: none; border-color: var(--accent); }
textarea:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
input[type="checkbox"] { accent-color: var(--accent); width: 16px; height: 16px; }
label.check { display: inline-flex; align-items: center; gap: 6px; color: var(--muted); font-size: 0.85rem; cursor: pointer; }

/* ── Chrome ────────────────────────────────────────────────────────────── */
.topbar {
  padding: max(12px, env(safe-area-inset-top, 0px)) 24px 12px;
  border-bottom: 1px solid var(--border);
  background: linear-gradient(135deg, #1e1b4b, #0f172a);
  display: flex; align-items: center; justify-content: space-between; gap: 16px; flex-wrap: wrap;
}
.brand-sm { display: flex; align-items: center; gap: 10px; }
.brand-sm .title { font-weight: 650; font-size: 1rem; letter-spacing: -0.01em; }
.brand-sm .sub { color: var(--muted); font-size: 0.76rem; font-family: ui-monospace, monospace; }
nav.tabs { display: flex; gap: 4px; }
nav.tabs a { color: var(--muted); text-decoration: none; font-size: 0.85rem; padding: 6px 12px; border-radius: 6px; }
nav.tabs a:hover { color: var(--text); background: var(--panel2); }
nav.tabs a.active { color: var(--text); background: var(--panel2); box-shadow: inset 0 -2px 0 var(--accent); }
.head-actions { display: flex; align-items: center; gap: 12px; }
.user { display: inline-flex; align-items: center; gap: 8px; font-size: 0.84rem; color: var(--muted); }
.avatar {
  width: 26px; height: 26px; border-radius: 50%; display: inline-grid; place-items: center;
  background: var(--panel2); border: 1px solid var(--border); color: var(--text); font-size: 0.75rem; font-weight: 700;
}

.toolbar {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  padding: 12px 24px; border-bottom: 1px solid var(--border); background: var(--panel);
}
.toolbar .search { min-width: 240px; flex: 1; max-width: 420px; }
.custom-range { display: inline-flex; gap: 6px; align-items: center; color: var(--muted); font-size: 0.8rem; flex-wrap: wrap; }

/* A <details> used as a dropdown of checkboxes - the traffic page's status filter. */
.multi { position: relative; }
.multi > summary {
  list-style: none; cursor: pointer; user-select: none; white-space: nowrap;
  background: var(--code); color: var(--text); border: 1px solid var(--border);
  border-radius: 6px; padding: 8px 10px; font-size: 0.85rem;
}
.multi > summary::-webkit-details-marker { display: none; }
.multi > summary::after { content: " \25be"; color: var(--muted); }
.multi[open] > summary { border-color: var(--accent); }
.multi .menu {
  position: absolute; z-index: 20; top: calc(100% + 4px); left: 0;
  min-width: 200px; max-height: 320px; overflow-y: auto; padding: 8px;
  display: flex; flex-direction: column; gap: 2px;
  background: var(--panel2); border: 1px solid var(--border); border-radius: 8px;
  box-shadow: 0 12px 32px rgba(0,0,0,0.45);
}
.multi .menu-head {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  padding: 2px 4px 8px; margin-bottom: 4px; border-bottom: 1px solid var(--border);
  font-size: 0.76rem; text-transform: uppercase; letter-spacing: 0.06em;
}
.multi .menu label.check { gap: 8px; padding: 4px 6px; border-radius: 5px; }
.multi .menu label.check:hover { background: var(--row-hover); }
.multi .menu .n { margin-left: auto; color: var(--muted); font-size: 0.78rem; font-variant-numeric: tabular-nums; }
.multi .menu .none { color: var(--muted); font-size: 0.8rem; padding: 4px 6px; }

.stat { color: var(--muted); font-size: 0.82rem; }
.stat strong { color: var(--text); font-variant-numeric: tabular-nums; }
.spacer { flex: 1; }

main.content { flex: 1; padding: 0 24px max(32px, env(safe-area-inset-bottom, 0px)); min-width: 0; }

/* ── Grids ─────────────────────────────────────────────────────────────── */
/* The three data grids are Shiny.Blazor.Controls <DataGrid>s, so the table chrome - header, sort
   affordances, filter menus, resize handles, pager - comes from the control and is coloured by the
   tokens mapped at the top of this file. Nothing here styles a bare <table> any more: such a rule
   would bleed straight into the grid's own markup. What is left is the wrapper, a few trims that
   keep the grid looking like the rest of the admin, and the classes cell templates use. */
.grid-wrap { margin-top: 12px; transition: opacity 0.15s; }
.grid-wrap.stale { opacity: 0.55; }

.admin-grid { font-size: 0.84rem; }

/* Deliberately heavier than the control's own ".shiny-dg-table thead th[b-hash]" (0,2,2): the admin
   has always had a raised, small-caps header strip and the grid paints its header on the surface. */
.admin-grid.shiny-dg .shiny-dg-table thead th {
  background: var(--panel2); color: var(--muted);
  font-size: 0.74rem; text-transform: uppercase; letter-spacing: 0.05em;
}
.admin-grid.shiny-dg .shiny-dg-table tbody td { color: var(--text); vertical-align: top; }
.admin-grid.shiny-dg .shiny-dg-detail > td { background: rgba(0,0,0,0.18); }
.admin-grid .shiny-dg-detail-inner { padding: 6px 12px 18px; }

/* ── Cell content ──────────────────────────────────────────────────────── */
/* Each of these goes on an element *inside* a cell - the grid owns the <td> itself. */
.cell-when { white-space: nowrap; color: var(--muted); font-variant-numeric: tabular-nums; }
.cell-site { white-space: nowrap; font-family: ui-monospace, monospace; font-size: 0.78rem; color: var(--accent2); }
.cell-who strong { display: block; }
.cell-who span { color: var(--muted); font-size: 0.8rem; }
.cell-path { font-family: ui-monospace, monospace; font-size: 0.78rem; white-space: normal; word-break: break-all; }
.cell-ip { font-family: ui-monospace, monospace; font-size: 0.78rem; white-space: nowrap; }
.cell-trunc { display: block; color: var(--muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cell-actions { display: flex; gap: 6px; justify-content: flex-end; white-space: nowrap; }
.snippet {
  display: -webkit-box; -webkit-line-clamp: 2; line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden; white-space: normal; color: var(--muted);
}
a.email { color: var(--key); text-decoration: none; }
a.email:hover { text-decoration: underline; }

.status { font-family: ui-monospace, monospace; font-size: 0.78rem; padding: 1px 6px; border-radius: 4px; }
.status.ok { background: rgba(52,211,153,0.12); color: var(--accent2); }
.status.warn { background: rgba(252,211,77,0.12); color: var(--warn); }
.status.err { background: rgba(239,68,68,0.15); color: #fca5a5; }
.bot { font-size: 0.68rem; color: var(--warn); border: 1px solid #854d0e; border-radius: 4px; padding: 0 4px; margin-left: 6px; }

/* ── Row detail ────────────────────────────────────────────────────────── */
.message-box {
  background: var(--code); border: 1px solid var(--border); border-radius: 8px;
  padding: 14px 16px; white-space: pre-wrap; word-break: break-word; font-size: 0.87rem; max-width: 900px;
}
.detail-actions { margin-top: 12px; display: flex; gap: 8px; flex-wrap: wrap; }
.message-box + .detail-actions + .meta-strip { margin-top: 14px; padding-top: 12px; border-top: 1px solid var(--border); }
.meta-strip { display: flex; flex-wrap: wrap; gap: 8px 22px; font-size: 0.76rem; }
.meta-strip .item { min-width: 0; max-width: 100%; }
.meta-strip .label { display: block; color: var(--muted); text-transform: uppercase; letter-spacing: 0.05em; font-size: 0.66rem; }
/* The grid puts white-space:nowrap on every cell, and a detail row is a cell - so a user agent or a
   referrer would be one very long line, and break-all alone cannot undo that. */
.meta-strip .value { font-family: ui-monospace, monospace; white-space: normal; word-break: break-all; }

.ip-locate {
  font: inherit; font-size: 0.7rem; margin-left: 8px; padding: 1px 7px; border-radius: 4px;
  background: var(--panel2); color: var(--text); border: 1px solid var(--border);
}
.ip-locate:hover:not(:disabled) { background: #334155; }
.ip-locate:disabled { cursor: progress; }
.ip-map { color: var(--accent); margin-left: 8px; white-space: nowrap; }
.ip-err { color: #fca5a5; }

.pager { display: flex; align-items: center; gap: 10px; margin-top: 12px; color: var(--muted); font-size: 0.82rem; }
.pager strong { color: var(--text); font-variant-numeric: tabular-nums; }

.empty { text-align: center; padding: 56px 20px; color: var(--muted); }
.empty .big, .big { font-size: 2.4rem; margin-bottom: 8px; }
.banner {
  margin-top: 12px; padding: 9px 13px; border-radius: 6px; font-size: 0.82rem;
  background: #422006; border: 1px solid #854d0e; color: #fcd34d;
}
.banner.err { background: rgba(127,29,29,0.6); border-color: #b91c1c; color: #fca5a5; }

/* ── Summary tiles ─────────────────────────────────────────────────────── */
.summary { display: grid; gap: 12px; grid-template-columns: repeat(auto-fit, minmax(170px, 1fr)); }
.summary .lists { grid-column: span 2; }
@media (max-width: 720px) { .summary .lists { grid-column: auto; } }

/* Collapsed: the two totals only, sized to their content instead of stretched across the row. */
.summary.collapsed { display: flex; flex-wrap: wrap; }
.summary.collapsed .tile { flex: 0 1 190px; padding: 8px 12px; }
.summary.collapsed .tile .value { font-size: 1.25rem; }

.panel { margin-top: 16px; }
.panel-toggle {
  padding: 4px 6px 4px 0; background: none; border: 0; color: var(--muted);
  font-size: 0.7rem; text-transform: uppercase; letter-spacing: 0.06em; gap: 6px;
}
.panel-toggle:hover { color: var(--text); }
.panel-toggle .chevron { transition: transform 0.12s; font-size: 0.8rem; line-height: 1; }
.panel-toggle[aria-expanded="true"] .chevron { transform: rotate(90deg); }
@media (prefers-reduced-motion: reduce) { .panel-toggle .chevron { transition: none; } }
.tile { background: var(--panel); border: 1px solid var(--border); border-radius: 8px; padding: 12px 14px; min-width: 0; }
.tile .label { color: var(--muted); font-size: 0.7rem; text-transform: uppercase; letter-spacing: 0.06em; }
.tile .value { font-size: 1.7rem; font-weight: 650; font-variant-numeric: tabular-nums; margin-top: 2px; }
.top-list { list-style: none; margin: 6px 0 0; padding: 0; font-size: 0.8rem; }
.top-list li { display: flex; justify-content: space-between; gap: 10px; padding: 3px 0; border-bottom: 1px dashed rgba(51,65,85,0.6); }
.top-list li:last-child { border-bottom: 0; }
.top-list .k { font-family: ui-monospace, monospace; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
.top-list .n { color: var(--muted); font-variant-numeric: tabular-nums; flex-shrink: 0; }
.top-list .none { color: var(--muted); border: 0; }

/* ── Raw OData ─────────────────────────────────────────────────────────── */
details.advanced { margin-top: 26px; border-top: 1px solid var(--border); padding-top: 14px; }
details.advanced summary { cursor: pointer; color: var(--muted); font-size: 0.8rem; text-transform: uppercase; letter-spacing: 0.06em; user-select: none; }
details.advanced summary:hover { color: var(--text); }
.adv-body { margin-top: 12px; }
.adv-row { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
.raw-query { flex: 1; min-width: 260px; font-family: ui-monospace, monospace; }
pre.json { background: var(--code); padding: 14px; border-radius: 8px; overflow: auto; font-size: 0.78rem; max-height: 420px; margin-top: 10px; }
.hint { color: var(--muted); font-size: 0.76rem; margin: 8px 0 0; }
.warn-text { color: var(--warn); }

/* How a contact submission identified itself. Deliberately quiet - it is a triage hint, not a verdict. */
.chip {
  display: inline-block; font-size: 0.72rem; padding: 1px 7px; border-radius: 999px;
  border: 1px solid transparent; white-space: nowrap;
}
.chip.ok { background: rgba(52,211,153,0.10); color: var(--accent2); border-color: rgba(52,211,153,0.25); }
.chip.app { background: rgba(129,140,248,0.12); color: var(--accent); border-color: rgba(129,140,248,0.3); }
.chip.warn { background: rgba(252,211,77,0.12); color: var(--warn); border-color: rgba(252,211,77,0.3); }
.chip.none { color: var(--muted); }

.hint.err { color: #fca5a5; }
code.inline { background: var(--code); padding: 1px 5px; border-radius: 4px; font-size: 0.76rem; }
.tok-key { color: var(--key); } .tok-str { color: var(--str); }
.tok-num { color: var(--num); } .tok-bool { color: var(--bool); } .tok-null { color: var(--null); }

/* ── Login ─────────────────────────────────────────────────────────────── */
main.login {
  flex: 1; display: grid; place-items: center; padding: 32px 16px;
  background:
    radial-gradient(900px 500px at 15% -10%, rgba(129,140,248,0.22), transparent 60%),
    radial-gradient(700px 480px at 110% 110%, rgba(52,211,153,0.10), transparent 60%),
    var(--bg);
}
.login-card {
  width: 100%; max-width: 400px; padding: 30px 28px 24px;
  background: rgba(30,41,59,0.85); backdrop-filter: blur(8px);
  border: 1px solid var(--border); border-radius: 16px;
  box-shadow: 0 24px 60px rgba(0,0,0,0.45);
}
.login-card.center { text-align: center; }
.login-card.center .btn-link { margin-top: 12px; }
.login-card .brand { display: flex; align-items: center; gap: 14px; margin-bottom: 18px; }
.login-card h1 { margin: 0; font-size: 1.35rem; letter-spacing: -0.02em; }
.login-card .brand p { margin: 2px 0 0; font-size: 0.82rem; }
.login-card .banner { margin: 0 0 16px; }
.login-card form { display: grid; gap: 16px; margin-top: 4px; }
.field { display: grid; gap: 6px; }
/* The login page's fields sit in a <form>, which spaces them; the sites editor reuses the card
   without one, so its fields need the same air between them. */
.login-card > .field { margin-top: 16px; }
.field label { font-size: 0.8rem; font-weight: 600; color: var(--text); }
.field input { width: 100%; padding: 10px 12px; font-size: 0.95rem; }
.field input.invalid { border-color: var(--danger); }
.field-error { margin: 0; color: #fca5a5; font-size: 0.78rem; }
.password { position: relative; }
.password input { padding-right: 64px; }
.password .reveal {
  position: absolute; right: 6px; top: 50%; transform: translateY(-50%);
  padding: 4px 9px; font-size: 0.74rem; background: transparent; color: var(--muted); border-color: var(--border);
}
.password .reveal:hover:not(:disabled) { color: var(--text); background: var(--panel2); }
.caps-hint { margin: 0; color: var(--warn); font-size: 0.76rem; }
.remember { display: flex; align-items: flex-start; gap: 10px; cursor: pointer; font-size: 0.88rem; }
.remember input { margin-top: 3px; flex-shrink: 0; }
.remember small { display: block; color: var(--muted); font-size: 0.76rem; margin-top: 1px; }
.fineprint { margin: 18px 0 0; color: var(--muted); font-size: 0.74rem; text-align: center; }

/* ── Error UI (Blazor) ─────────────────────────────────────────────────── */
#blazor-error-ui {
  display: none; position: fixed; bottom: 0; left: 0; width: 100%; z-index: 1000;
  padding: 10px 24px max(10px, env(safe-area-inset-bottom, 0px));
  background: var(--danger-dim); color: #fff; font-size: 0.85rem;
  box-shadow: 0 -4px 16px rgba(0,0,0,0.4);
}
#blazor-error-ui .reload { color: #fff; margin-left: 10px; }
#blazor-error-ui .dismiss { cursor: pointer; position: absolute; right: 18px; top: 8px; }

/* ── Phone row cards ───────────────────────────────────────────────────── */
/* What a grid row collapses into on a phone: one cell carrying the fields the dropped columns were
   showing, stacked instead of laid out sideways. Tapping the row still opens the detail underneath,
   which is where everything else lives - so nothing is actually lost by narrowing to this.
   See Viewport.cs for why the columns are dropped in C# rather than hidden with a media query. */
.cell-card { display: flex; flex-direction: column; gap: 3px; min-width: 0; white-space: normal; }
.cell-card .headline { display: flex; align-items: baseline; gap: 10px; }
.cell-card .who { font-weight: 650; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cell-card .when {
  margin-left: auto; flex-shrink: 0; color: var(--muted);
  font-size: 0.74rem; font-variant-numeric: tabular-nums;
}
.cell-card .sub {
  display: flex; flex-wrap: wrap; align-items: center; gap: 4px 8px;
  min-width: 0; color: var(--muted); font-size: 0.76rem;
}
.cell-card .sub > * { min-width: 0; max-width: 100%; }
.cell-card .snippet { margin-top: 2px; }
.cell-card .actions { display: flex; gap: 8px; margin-top: 10px; }
.cell-card .actions > button { flex: 1 1 auto; }

/* ── Touch ─────────────────────────────────────────────────────────────── */
/* A mouse hits a 28px button; a thumb does not. This is on the input device rather than the window
   width, because a tablet in landscape is wide and still all thumbs. */
@media (pointer: coarse) {
  button, .btn-link { min-height: 40px; }
  .btn-sm { min-height: 34px; padding: 6px 11px; }
  .ip-locate { min-height: 30px; }
  .panel-toggle, .password .reveal { min-height: 0; }
  input[type="search"], input[type="password"], input[type="text"], input[type="number"],
  input[type="datetime-local"], select, .multi > summary { min-height: 40px; }
  input[type="checkbox"] { width: 20px; height: 20px; }
  nav.tabs a { padding: 9px 14px; }
  details.advanced summary, .multi .menu label.check { padding-top: 8px; padding-bottom: 8px; }
}

/* ── Narrow windows ────────────────────────────────────────────────────── */
/* Matches Viewport.PhoneMax, which is what the pages branch on - the two have to agree, or a grid
   folds to its card column while the chrome around it is still laid out for a desktop. */
@media (max-width: 760px) {
  /* Under 16px iOS zooms the page in when a field takes focus, and never zooms back out. */
  input[type="search"], input[type="password"], input[type="text"], input[type="number"],
  input[type="datetime-local"], select, textarea { font-size: 16px; }

  /* Topbar: the brand and the account share the first row, the sections get their own underneath. */
  .topbar { padding-left: max(16px, env(safe-area-inset-left, 0px)); padding-right: max(16px, env(safe-area-inset-right, 0px)); gap: 10px 12px; }
  .brand-sm { flex: 1 1 auto; min-width: 0; }
  .brand-sm > div { min-width: 0; }
  .brand-sm .title { font-size: 0.95rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .brand-sm .sub { display: none; }
  .user .uname { display: none; }
  nav.tabs { order: 3; width: 100%; gap: 6px; }
  nav.tabs a { flex: 1 1 0; text-align: center; }

  /* Toolbars: the search takes a row of its own, the filters pair up, the actions share the last row. */
  .toolbar {
    padding: 10px max(16px, env(safe-area-inset-right, 0px)) 10px max(16px, env(safe-area-inset-left, 0px));
    gap: 8px;
  }
  .toolbar .search { flex: 1 1 100%; min-width: 0; max-width: none; }
  .toolbar > select, .toolbar > .multi { flex: 1 1 45%; min-width: 0; }
  .toolbar > .multi > summary { width: 100%; }
  .toolbar > label.check { flex: 1 1 auto; }
  .toolbar .custom-range { flex: 1 1 100%; }
  .toolbar .custom-range input { flex: 1 1 40%; min-width: 0; }
  .toolbar .stat { flex: 1 1 100%; }
  .toolbar .spacer { display: none; }
  .toolbar > button { flex: 1 1 auto; }
  .multi .menu { max-width: min(78vw, 300px); }

  main.content {
    padding: 0 max(16px, env(safe-area-inset-right, 0px)) max(24px, env(safe-area-inset-bottom, 0px))
             max(16px, env(safe-area-inset-left, 0px));
  }

  /* Statistics: the two totals share a row, and a top-N list gets the whole of one - half a phone's
     width is not enough for a path and its count. */
  .summary { grid-template-columns: repeat(auto-fit, minmax(136px, 1fr)); }
  .summary .lists { grid-column: 1 / -1; }
  .summary.collapsed .tile { flex: 1 1 136px; }
  .tile .value { font-size: 1.4rem; }

  /* Row detail: one value per line. A user agent or a referrer has no business in a column here. */
  .meta-strip { display: grid; grid-template-columns: 1fr; gap: 10px; }
  .message-box { padding: 12px 13px; }
  .detail-actions > button { flex: 1 1 auto; }

  /* The grid keeps its own sideways scroller for the odd wide cell; the page must not gain one too. */
  .grid-wrap { margin-top: 10px; }
  .admin-grid .shiny-dg-filter-menu { min-width: 0; max-width: min(78vw, 300px); }
  .admin-grid .shiny-dg-pager { flex-wrap: wrap; justify-content: space-between; gap: 8px 14px; }
  .empty { padding: 36px 16px; }

  /* Raw OData: the query gets a full row, the entity set and the two buttons share the one above it. */
  details.advanced { margin-top: 20px; }
  .raw-query { flex: 1 1 100%; min-width: 0; }
  .adv-row > select { flex: 1 1 100%; }
  .adv-row > button { flex: 1 1 auto; }
  pre.json { font-size: 0.72rem; max-height: 300px; }

  main.login { padding: 24px 16px max(24px, env(safe-area-inset-bottom, 0px)); }
  .login-card { padding: 24px 20px 20px; border-radius: 14px; }
  .login-card h1 { font-size: 1.2rem; }
}
