/* ======================================================================================
   The design system. One file, every page.

   Served same-origin at /static/app.css — no CDN, no build step, no fonts fetched, so
   the hub still works from a locked-down network (and from a du datacentre link that
   only ever needed to reach this host). Every template links this sheet; a page keeps
   at most ~30 lines of its own rules in a {% block page_style %}.

   Ported from the telematics-troubleshooter's app.css: the oklch token palette, the
   light/dark handling via `color-scheme` + `prefers-color-scheme`, the base elements,
   the brand header, the nav, the user menu, the buttons, the chips and the print rules
   are that file's, so the two internal tools read as one product.

   Extended here for what a SIM fleet needs and a diagnosis tool did not:

     * the du message-kind colours (reply / receipt / wakeup / noise), which the old
       gateway UI only had in dark, re-expressed as light+dark pairs;
     * the device timeline (.tl);
     * three new pill families — SIM status, connectivity, provider;
     * a usage meter whose fill colour is the comparable dimension across providers
       that measure usage in incompatible units;
     * a description grid, a filter bar, a sticky bulk-action bar and a wide layout
       for the tables.

   These custom properties are THE definition of the palette — no template may declare
   --brand or its siblings. tests/test_web_hub.py enforces that.
   ====================================================================================== */

:root {
  color-scheme: light dark;
  --bg: oklch(0.98 0 78);
  --surface: #ffffff;
  --surface-2: oklch(0.965 0.01 74);
  --border: oklch(0.92 0.012 85);
  --text: oklch(0.205 0.012 60);
  --muted: oklch(0.52 0.02 66);
  --brand: oklch(0.702 0.158 72);
  --brand-2: oklch(0.65 0.15 72);
  --brand-fg: oklch(0.99 0.005 85);
  --brand-soft: oklch(0.96 0.016 80);
  --critical: oklch(0.577 0.245 27.325);
  --critical-soft: oklch(0.96 0.02 27);
  --warn: oklch(0.6 0.15 75);
  --warn-soft: oklch(0.965 0.02 80);
  --info: oklch(0.596 0.145 163.225);
  --info-soft: oklch(0.96 0.02 163);
  --shadow: 0 1px 2px rgba(26, 22, 14, 0.06), 0 6px 18px rgba(26, 22, 14, 0.05);
  --mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;

  /* Layout width. `wide` widens the shell for the SIM and audit tables without any
     page having to restate a pixel figure. */
  --shell: 66rem;

  /* --- semantic accents beyond the base three ----------------------------------- */
  --good: oklch(0.55 0.14 152);
  --good-soft: oklch(0.955 0.03 152);
  --accent: oklch(0.55 0.17 258);          /* blue: device replies, "pending" */
  --accent-soft: oklch(0.955 0.03 258);
  --violet: oklch(0.52 0.12 305);          /* purple: rejected / parse-error noise */
  --violet-soft: oklch(0.955 0.025 305);
  --neutral-soft: oklch(0.955 0.004 80);

  /* --- du message kinds (light) -------------------------------------------------- */
  --k-reply: oklch(0.55 0.17 258);
  --k-reply-soft: oklch(0.955 0.03 258);
  --k-receipt: oklch(0.52 0.13 152);
  --k-receipt-soft: oklch(0.955 0.03 152);
  --k-wake: oklch(0.58 0.13 75);
  --k-wake-soft: oklch(0.96 0.035 80);
  --k-noise: oklch(0.52 0.12 305);
  --k-noise-soft: oklch(0.955 0.025 305);
  --k-other: oklch(0.55 0.02 80);
  --k-other-soft: oklch(0.955 0.004 80);

  /* --- provider tints: distinguishable, deliberately quiet ------------------------ */
  --p-du: oklch(0.5 0.11 20);
  --p-du-soft: oklch(0.96 0.022 20);
  --p-tm: oklch(0.5 0.1 195);
  --p-tm-soft: oklch(0.96 0.022 195);
  --p-nce: oklch(0.5 0.11 300);
  --p-nce-soft: oklch(0.96 0.022 300);
}

@media (prefers-color-scheme: dark) {
  :root {
    --bg: oklch(0.17 0.006 70);
    --surface: oklch(0.19 0.007 70);
    --surface-2: oklch(0.262 0.005 74);
    --border: oklch(1 0 0 / 14%);
    --text: oklch(0.97 0.006 85);
    --muted: oklch(0.72 0.012 80);
    --brand: oklch(0.769 0.188 70.08);
    --brand-2: oklch(0.8 0.185 74);
    --brand-fg: oklch(0.19 0.02 70);
    --brand-soft: oklch(0.32 0.02 74);
    --critical: oklch(0.704 0.191 22.216);
    --critical-soft: oklch(0.25 0.04 25);
    --warn: oklch(0.769 0.188 70.08);
    --warn-soft: oklch(0.28 0.03 74);
    --info: oklch(0.696 0.17 162.48);
    --info-soft: oklch(0.22 0.03 163);
    --shadow: none;

    --good: oklch(0.75 0.15 152);
    --good-soft: oklch(0.24 0.04 152);
    --accent: oklch(0.75 0.13 258);
    --accent-soft: oklch(0.24 0.05 258);
    --violet: oklch(0.75 0.1 305);
    --violet-soft: oklch(0.24 0.04 305);
    --neutral-soft: oklch(0.26 0.004 80);

    --k-reply: oklch(0.78 0.12 258);
    --k-reply-soft: oklch(0.25 0.055 258);
    --k-receipt: oklch(0.78 0.14 152);
    --k-receipt-soft: oklch(0.24 0.045 152);
    --k-wake: oklch(0.8 0.13 78);
    --k-wake-soft: oklch(0.27 0.05 78);
    --k-noise: oklch(0.76 0.09 305);
    --k-noise-soft: oklch(0.25 0.04 305);
    --k-other: oklch(0.72 0.012 80);
    --k-other-soft: oklch(0.26 0.004 80);

    --p-du: oklch(0.78 0.11 20);
    --p-du-soft: oklch(0.26 0.045 20);
    --p-tm: oklch(0.78 0.1 195);
    --p-tm-soft: oklch(0.25 0.04 195);
    --p-nce: oklch(0.78 0.1 300);
    --p-nce-soft: oklch(0.25 0.04 300);
  }
}

/* ---------------- base ---------------- */

* { box-sizing: border-box; }
html { scroll-behavior: smooth; }
body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;
  line-height: 1.55;
  font-size: 15px;
  /* The app-wide treatment: one solid brand rule across the top of every page. */
  border-top: 3px solid var(--brand);
}
body.wide { --shell: 92rem; }

main { max-width: var(--shell); margin: 0 auto; padding: 1.5rem 1.25rem 4rem; }
main.wide { max-width: 92rem; }

h1 { font-size: 1.22rem; margin: 0; letter-spacing: -0.01em; }
h2 { font-size: 0.95rem; margin: 2rem 0 0.75rem; text-transform: uppercase;
     letter-spacing: 0.07em; color: var(--muted); }
h3 { font-size: 0.9rem; margin: 0 0 0.6rem; }
p.sub { margin: 0.1rem 0 0; color: var(--muted); font-size: 0.86rem; }
a { color: var(--brand); }
a.plain { color: var(--brand); text-decoration: none; }
a.plain:hover { text-decoration: underline; }
code, .mono { font-family: var(--mono); font-size: 0.86em; }
code { background: var(--surface-2); border: 1px solid var(--border);
       border-radius: 5px; padding: 0.05rem 0.3rem; }
pre {
  background: var(--surface-2); border: 1px solid var(--border); border-radius: 8px;
  padding: 0.8rem 0.9rem; overflow: auto; white-space: pre-wrap; word-break: break-word;
  font-family: var(--mono); font-size: 0.78rem; line-height: 1.45; margin: 0;
}
.hidden { display: none !important; }
.muted { color: var(--muted); }
.nowrap { white-space: nowrap; }
.right { text-align: right; }
.small { font-size: 0.84rem; }
.tiny { font-size: 0.74rem; }

/* ---------------- brand header (server-rendered in base.html) ---------------- */

.topbar { border-bottom: 1px solid var(--border); background: var(--surface); }
.topbar-in {
  max-width: var(--shell); margin: 0 auto; padding: 0.85rem 1.25rem;
  display: flex; align-items: center; justify-content: space-between;
  gap: 1.25rem; flex-wrap: wrap;
}
header.page {
  display: flex; align-items: center; justify-content: space-between;
  gap: 1.25rem; flex-wrap: wrap; margin-bottom: 1.5rem;
}
.brand { display: flex; align-items: center; gap: 1rem; min-width: 0; }
/* Truckoom mark: currentColor glyphs follow the theme; amber accents are literal in
   the artwork. Height only — width follows the viewBox. */
.brand > svg { height: 24px; width: auto; flex: 0 0 auto; color: var(--text); }
.brand .divider { width: 1px; align-self: stretch; background: var(--border); }
.brand h1 { font-size: 1.02rem; }
/* margin-left:auto keeps the row right-aligned when the header wraps. */
.controls { display: flex; gap: 0.6rem; align-items: center; flex-wrap: wrap; margin-left: auto; }

/* Page navigation: quiet text links, not buttons — the active page carries the brand
   colour and an underline. */
.nav { display: flex; gap: 0.25rem; align-items: center; flex-wrap: wrap; }
.nav a {
  color: var(--muted); text-decoration: none; font-weight: 600; font-size: 0.92rem;
  padding: 0.35rem 0.65rem; border-radius: 8px; border-bottom: 2px solid transparent;
}
.nav a:hover { color: var(--text); background: var(--surface-2); }
.nav a.active { color: var(--brand); border-bottom-color: var(--brand); border-radius: 8px 8px 0 0; }

/* Signed-in user menu: name opens a small dropdown with account + admin actions. */
.usermenu { position: relative; }
.usermenu > summary {
  list-style: none; cursor: pointer; display: inline-flex; align-items: center; gap: 0.4rem;
  padding: 0.35rem 0.75rem; border: 1px solid var(--border); border-radius: 999px;
  font-weight: 600; font-size: 0.9rem; color: var(--text); background: var(--surface);
}
.usermenu > summary::-webkit-details-marker { display: none; }
.usermenu > summary::after { content: "▾"; color: var(--muted); font-size: 0.75em; }
.usermenu[open] > summary { border-color: var(--brand); }
.usermenu .menu {
  position: absolute; right: 0; top: calc(100% + 6px); min-width: 13rem; z-index: 50;
  background: var(--surface); border: 1px solid var(--border); border-radius: 10px;
  box-shadow: var(--shadow); padding: 0.3rem; display: flex; flex-direction: column;
}
.usermenu .menu a {
  color: var(--text); text-decoration: none; font-size: 0.9rem;
  padding: 0.45rem 0.7rem; border-radius: 7px;
}
.usermenu .menu a:hover { background: var(--surface-2); }
.usermenu .menu a.danger { color: var(--critical); }
.usermenu .menu .who {
  padding: 0.45rem 0.7rem 0.3rem; font-size: 0.78rem; color: var(--muted);
  border-bottom: 1px solid var(--border); margin-bottom: 0.25rem;
}
.usermenu .menu .sect {
  padding: 0.5rem 0.7rem 0.2rem; font-size: 0.68rem; color: var(--muted);
  text-transform: uppercase; letter-spacing: 0.07em; font-weight: 700;
  border-top: 1px solid var(--border); margin-top: 0.25rem;
}

/* ---------------- cards, tiles ---------------- */

.card {
  background: var(--surface); border: 1px solid var(--border);
  border-radius: 12px; box-shadow: var(--shadow); padding: 1.1rem 1.25rem;
}
.card.flush { padding: 0; }
.card + .card { margin-top: 1rem; }
.card > h3 { margin-top: 0; }
.card .cardhead {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 0.8rem; flex-wrap: wrap; margin-bottom: 0.7rem;
}
.empty { padding: 1.1rem 1.15rem; color: var(--muted); }
.tablewrap { overflow-x: auto; }

/* A tile is a number and a noun, and it is quiet.
   `auto-fill` rather than `auto-fit` on purpose: auto-fit collapses the empty tracks, so
   a fifth tile in a four-column row would stretch across the whole width and read as a
   section of its own. auto-fill keeps the tracks, and the orphan stays one tile wide. */
.tiles { display: grid; gap: 0.7rem; grid-template-columns: repeat(auto-fill, minmax(9.5rem, 1fr)); }
.tile {
  display: flex; flex-direction: column; gap: 0.1rem;
  text-decoration: none; color: inherit;
  background: var(--surface); border: 1px solid var(--border);
  border-left: 3px solid var(--border); border-radius: 10px;
  padding: 0.7rem 0.9rem;
}
a.tile:hover { border-color: var(--border); border-left-color: var(--brand); background: var(--surface-2); }
.tile .n { font-size: 1.45rem; font-weight: 650; line-height: 1.2; letter-spacing: -0.02em; }
.tile .t { font-size: 0.72rem; color: var(--muted); text-transform: uppercase;
           letter-spacing: 0.06em; font-weight: 600; }
/* Trouble is a coloured value and a thin rule down the side — never a full outline.
   A grid of red boxes is a grid nobody reads twice. */
.tile.warn { border-left-color: var(--warn); }
.tile.warn .n { color: var(--warn); }
.tile.critical { border-left-color: var(--critical); }
.tile.critical .n { color: var(--critical); }
a.tile.warn:hover { border-left-color: var(--warn); }
a.tile.critical:hover { border-left-color: var(--critical); }

.cols { display: grid; gap: 1rem; grid-template-columns: repeat(auto-fit, minmax(21rem, 1fr)); align-items: start; }

/* ---------------- forms ---------------- */

label.field { font-size: 0.78rem; color: var(--muted); display: block; margin-bottom: 0.25rem; }
/* Deliberately typed rather than a bare `input`: a checkbox or a file picker must keep
   the platform control, not grow a text field's border and padding. */
select, textarea, button,
input[type="text"], input[type="email"], input[type="password"],
input[type="number"], input[type="date"], input[type="search"] {
  font: inherit; color: var(--text); background: var(--surface);
  border: 1px solid var(--border); border-radius: 8px; padding: 0.45rem 0.7rem;
}
select:focus-visible, button:focus-visible, input:focus-visible, textarea:focus-visible,
a:focus-visible, summary:focus-visible {
  outline: 2px solid var(--brand); outline-offset: 1px;
}
button { cursor: pointer; }
button.primary {
  background: var(--brand); border-color: var(--brand); color: var(--brand-fg);
  font-weight: 600; padding: 0.45rem 1rem;
}
button.primary:hover:not(:disabled) { background: var(--brand-2); }
/* Disabled reads as an inactive neutral control, not dimmed amber mud. */
button.primary:disabled, button:disabled {
  background: var(--surface-2); border-color: var(--border);
  color: var(--muted); cursor: not-allowed;
}
button.ghost, a.ghost {
  background: transparent; color: var(--brand); border: 1px solid var(--border);
  border-radius: 8px; padding: 0.45rem 0.7rem; text-decoration: none;
  display: inline-block; cursor: pointer; font: inherit;
}
button.ghost:hover:not(:disabled), a.ghost:hover { background: var(--brand-soft); }
button.danger, a.danger {
  color: var(--critical); border-color: var(--border); background: transparent;
  border-radius: 8px; padding: 0.45rem 0.7rem; text-decoration: none; display: inline-block;
  font: inherit; cursor: pointer;
}
button.danger:hover:not(:disabled), a.danger:hover { background: var(--critical-soft); border-color: var(--critical); }
button.small, a.small.ghost { font-size: 0.84rem; padding: 0.28rem 0.65rem; }
form.inline { display: inline; }
.formrow { display: flex; gap: 0.6rem; align-items: end; flex-wrap: wrap; }
.formrow > div { min-width: 8rem; }
.stack > * + * { margin-top: 0.9rem; }
fieldset { border: 1px solid var(--border); border-radius: 10px; padding: 0.8rem 1rem; }
legend { font-size: 0.78rem; color: var(--muted); padding: 0 0.35rem; }

/* ---------------- chips & pills ---------------- */

.chip, .pill {
  display: inline-block; font-size: 0.72rem; font-weight: 600; letter-spacing: 0.05em;
  text-transform: uppercase; padding: 0.1rem 0.55rem; border-radius: 999px;
  border: 1px solid var(--border); background: var(--surface-2); color: var(--muted);
  white-space: nowrap; vertical-align: middle;
}
.chip.critical { color: var(--critical); border-color: var(--critical); background: var(--critical-soft); }
.chip.warn { color: var(--warn); border-color: var(--warn); background: var(--warn-soft); }
.chip.info { color: var(--info); border-color: var(--info); background: var(--info-soft); }
.chip.good { color: var(--good); border-color: var(--good); background: var(--good-soft); }
.chip.brand { color: var(--brand); border-color: var(--brand); background: var(--brand-soft); }

/* SIM lifecycle state. Six named values plus the honest seventh. */
.pill.s-active       { color: var(--good);     border-color: var(--good);     background: var(--good-soft); }
.pill.s-blocked      { color: var(--critical); border-color: var(--critical); background: var(--critical-soft); }
.pill.s-suspended    { color: var(--warn);     border-color: var(--warn);     background: var(--warn-soft); }
.pill.s-deactivated  { color: var(--muted);    border-color: var(--border);   background: var(--neutral-soft); }
.pill.s-expired      { color: var(--critical); border-color: var(--border);   background: var(--neutral-soft); }
.pill.s-pending      { color: var(--accent);   border-color: var(--accent);   background: var(--accent-soft); }
.pill.s-unknown      { color: var(--muted);    border-color: var(--border);   background: var(--neutral-soft); }

/* Reachability right now. "attached" is amber on purpose: registered on the network
   but not passing data is the state that most often precedes a support ticket. */
.pill.c-online   { color: var(--good);     border-color: var(--good);   background: var(--good-soft); }
.pill.c-attached { color: var(--warn);     border-color: var(--warn);   background: var(--warn-soft); }
.pill.c-offline  { color: var(--critical); border-color: var(--border); background: var(--neutral-soft); }
.pill.c-unknown  { color: var(--muted);    border-color: var(--border); background: var(--neutral-soft); }
.pill .rat { font-weight: 500; opacity: 0.8; }

/* Provider identity: three quiet tints, told apart by hue rather than by loudness —
   the provider is context, never the thing the eye should land on first. */
.pill.p-du           { color: var(--p-du);  border-color: var(--p-du);  background: var(--p-du-soft); }
.pill.p-thingsmobile { color: var(--p-tm);  border-color: var(--p-tm);  background: var(--p-tm-soft); }
.pill.p-1nce         { color: var(--p-nce); border-color: var(--p-nce); background: var(--p-nce-soft); }

/* Roles. */
.pill.r-admin { color: var(--brand); border-color: var(--brand); background: var(--brand-soft); }
.pill.r-invited { color: var(--accent); border-color: var(--accent); background: var(--accent-soft); }

/* Action audit outcomes. */
.pill.a-ok { color: var(--good); border-color: var(--good); background: var(--good-soft); }
.pill.a-failed { color: var(--critical); border-color: var(--critical); background: var(--critical-soft); }
.pill.a-pending { color: var(--warn); border-color: var(--warn); background: var(--warn-soft); }

/* ---------------- du message kinds ---------------- */
/* The old gateway UI's colours, kept so a reader who knows the tool still recognises
   a receipt at a glance — but as light+dark pairs rather than dark-only hex. */

.tag { display: inline-block; padding: 0.05rem 0.45rem; border-radius: 999px;
       font-size: 0.7rem; font-weight: 600; margin-right: 0.2rem; white-space: nowrap; }
.t-reply   { background: var(--k-reply-soft);   color: var(--k-reply); }
.t-receipt { background: var(--k-receipt-soft); color: var(--k-receipt); }
.t-wake    { background: var(--k-wake-soft);    color: var(--k-wake); }
.t-noise   { background: var(--k-noise-soft);   color: var(--k-noise); }
.t-other   { background: var(--k-other-soft);   color: var(--k-other); }
.t-retry   { background: var(--warn-soft);      color: var(--warn); }
.t-ok      { background: var(--good-soft);      color: var(--good); }
.t-bad     { background: var(--critical-soft);  color: var(--critical); }

/* A coloured left edge on the row: the kind is readable while scanning a long list
   without the eye having to reach the tag column. */
tr.k-reply   > td:first-child { box-shadow: inset 3px 0 0 var(--k-reply); }
tr.k-receipt > td:first-child { box-shadow: inset 3px 0 0 var(--k-receipt); }
tr.k-wake    > td:first-child { box-shadow: inset 3px 0 0 var(--k-wake); }
tr.k-noise   > td:first-child { box-shadow: inset 3px 0 0 var(--k-noise); }
tr.k-other   > td:first-child { box-shadow: inset 3px 0 0 var(--k-other); }

/* ---------------- tables ---------------- */

table { width: 100%; border-collapse: collapse; font-size: 0.88rem; }
thead th {
  text-align: left; font-size: 0.7rem; text-transform: uppercase; letter-spacing: 0.06em;
  color: var(--muted); font-weight: 700; padding: 0.5rem 0.6rem;
  border-bottom: 1px solid var(--border); white-space: nowrap;
}
thead th a { color: inherit; text-decoration: none; }
thead th a:hover { color: var(--brand); }
tbody td { padding: 0.55rem 0.6rem; border-bottom: 1px solid var(--border); vertical-align: top; }
tbody tr:last-child td { border-bottom: none; }
tbody tr:hover { background: var(--surface-2); }
tr.clickable { cursor: pointer; }
td.actions { text-align: right; white-space: nowrap; }
td.actions > * + * { margin-left: 0.35rem; }
/* Identity cell: what the SIM is, then how it is addressed. */
.ident { display: block; font-weight: 600; }
.ident + .sub2 { display: block; font-size: 0.76rem; color: var(--muted); font-family: var(--mono); }

/* ---------------- description grid ---------------- */

dl.dl-grid { display: grid; grid-template-columns: max-content 1fr; gap: 0.3rem 1.2rem; margin: 0; }
dl.dl-grid dt { color: var(--muted); font-size: 0.8rem; }
dl.dl-grid dd { margin: 0; font-size: 0.9rem; word-break: break-word; }

/* ---------------- faceted filtering ---------------- */
/* Built for a fleet of thousands, where the filters are the navigation. Three bands,
   in the order an operator reads them: search, what is currently applied, what else is
   available — then the result count immediately above the table it describes. */

.searchrow { display: flex; gap: 0.5rem; align-items: center; margin-bottom: 0.7rem; }
.searchrow .grow { flex: 1; min-width: 12rem; }

.activefilters { display: flex; gap: 0.45rem; align-items: center; flex-wrap: wrap;
                 margin-bottom: 0.6rem; }
.activefilters .x { font-weight: 700; opacity: 0.7; margin-left: 0.15rem; }
.activefilters .fchip:hover .x { opacity: 1; }

.facets { display: flex; flex-direction: column; gap: 0.4rem; margin-bottom: 0.9rem; }
.facet { display: flex; gap: 0.4rem; align-items: baseline; flex-wrap: wrap; }
/* A fixed-width label column keeps the chip rows left-aligned with each other, so the
   eye runs down the values rather than hunting for where each row starts. */
.facet-label { flex: 0 0 6.5rem; font-size: 0.7rem; text-transform: uppercase;
               letter-spacing: 0.06em; color: var(--muted); font-weight: 700; }
/* The count is the point of the chip, so it is set apart rather than blended in — but
   in a text token, never in a data colour. */
.facet .fchip b, .facets .fchip b { font-weight: 700; margin-left: 0.15rem;
                                    font-variant-numeric: tabular-nums; }
.facet .fchip:not(.on) b { color: var(--muted); }

.resultline { display: flex; gap: 1rem; align-items: baseline; flex-wrap: wrap;
              margin: 0 0 0.7rem; font-size: 0.86rem; color: var(--muted); }
.resultline b { color: var(--text); font-variant-numeric: tabular-nums; }
.resultline .sizes { margin-left: auto; }
.resultline .sizes a, .resultline .sizes b { margin-left: 0.4rem; }

/* ---------------- filter bar ---------------- */

.filterbar {
  display: flex; gap: 0.45rem; align-items: center; flex-wrap: wrap;
  margin: 0 0 0.9rem;
}
.filterbar form { display: flex; gap: 0.45rem; align-items: center; flex-wrap: wrap; }
/* One chip appearance everywhere a chip appears — filter bar, facet rows, active
   filters. Scoping this to .filterbar once left every facet chip rendering as a bare
   orange underlined link, which read as a link dump rather than a control surface. */
.fchip {
  display: inline-block; text-decoration: none; font-size: 0.78rem; font-weight: 600;
  padding: 0.25rem 0.7rem; border-radius: 999px; border: 1px solid var(--border);
  background: var(--surface); color: var(--muted);
}
.fchip:hover { border-color: var(--brand); color: var(--text); text-decoration: none; }
.fchip.on { background: var(--brand-soft); border-color: var(--brand); color: var(--brand); }
/* The tag facet is a dropdown, not chips: real accounts carry dozens of long free-text
   tags, and a chip per tag is a wall. */
.facet select.tagpick { max-width: 26rem; font-size: 0.82rem; padding: 0.25rem 0.4rem; }
.filterbar .sep { width: 1px; height: 1.4rem; background: var(--border); margin: 0 0.2rem; }

/* ---------------- usage meter ---------------- */
/* Providers measure usage in units that do not compare: Things Mobile counts MB left
   against a threshold, 1NCE a percentage of a lifetime quota, du nothing at all. The
   number is per-provider; the COLOUR is the comparable dimension, and that is the
   whole point of the component. */

.meter { min-width: 6.5rem; }
.meter .bar {
  height: 6px; border-radius: 999px; background: var(--surface-2);
  border: 1px solid var(--border); overflow: hidden;
}
.meter .fill { display: block; height: 100%; background: var(--good); border-radius: 999px; }
.meter.amber .fill { background: var(--warn); }
.meter.red .fill { background: var(--critical); }
.meter .lbl { display: block; font-size: 0.74rem; color: var(--muted); margin-top: 0.15rem; }
.meter.na .lbl { font-style: italic; }
/* The same component at reading size, for a bulk send's progress. */
.meter .bar.tall { height: 10px; }

/* Tabs are the filter bar with a rule under it: the three messaging surfaces are one
   section, and a border says so without a second navigation idiom. */
.filterbar.tabs { border-bottom: 1px solid var(--border); padding-bottom: 0.6rem; margin-bottom: 1rem; }

/* ---------------- charts ---------------- */
/*
   One series, so no legend: the heading above the figure names what is plotted.

   `--series` is NOT the brand amber. Validated against the chart surface with the
   data-viz palette checks: the brand's light step lands at 2.7:1 on white (a contrast
   WARN) and its dark step at L 0.77 falls outside the dark mark band entirely. These
   two steps come off the same amber ramp and pass both — and the dark one is *chosen*
   against the dark surface rather than being the light value flipped.
     light  oklch(0.58 0.15 72)  = #af6700  — in band, >= 3:1 on #ffffff
     dark   oklch(0.66 0.16 72)  = #cd7e00  — in band, >= 3:1 on the dark card
*/
:root { --series: oklch(0.58 0.15 72); --series-wash: oklch(0.58 0.15 72 / 12%); }
@media (prefers-color-scheme: dark) {
  :root { --series: oklch(0.66 0.16 72); --series-wash: oklch(0.66 0.16 72 / 18%); }
}

.chart { margin: 0; }
/* The container is sized to include the axis band, so the labels are never the thing
   that gets cropped or spawns a nested scrollbar. */
.chart .plot { position: relative; height: 88px; padding-left: 3.6rem; }
/* SVG is a replaced element: absolutely positioned with left+right and width:auto it
   keeps its INTRINSIC width (300px) and ignores the right inset — the plot huddled in
   the left third of a wide card. The width must be explicit. */
.chart .plot svg { position: absolute; top: 0; left: 3.6rem; height: 100%;
                   width: calc(100% - 3.6rem); overflow: visible; }
/* Axis text is HTML, at real font sizes and in text tokens — never in the series
   colour, and never inside an SVG that gets stretched. */
.chart .ytick { position: absolute; left: 0; width: 3.1rem; text-align: right;
                font-size: 0.72rem; color: var(--muted); font-variant-numeric: tabular-nums; }
.chart .ytick.top { top: -0.35rem; }
.chart .ytick.zero { bottom: -0.55rem; }
.chart .xaxis { display: flex; justify-content: space-between; margin: 0.35rem 0 0 3.6rem;
                font-size: 0.72rem; color: var(--muted); }
.chart figcaption { margin-top: 0.5rem; font-size: 0.8rem; color: var(--muted); }

/* Recessive, solid hairlines — a dashed grid reads as a threshold it is not. */
.chart .grid, .chart .axis { stroke: var(--border); stroke-width: 1;
                             vector-effect: non-scaling-stroke; }
.chart .line { fill: none; stroke: var(--series); stroke-width: 2;
               stroke-linejoin: round; stroke-linecap: round;
               vector-effect: non-scaling-stroke; }
.chart .wash { fill: var(--series-wash); stroke: none; }
/* Markers are zero-length round-capped lines, not circles: the plot is stretched to the
   card's width and a circle would arrive as an ellipse. A non-scaling round cap is a
   true 8px dot at any width. The ring is the surface showing through where a marker
   crosses the line, and it is part of the hit target. */
.chart .dot { stroke: var(--series); stroke-width: 8; stroke-linecap: round;
              vector-effect: non-scaling-stroke; }
.chart .ring { stroke: var(--surface); stroke-width: 12; stroke-linecap: round;
               vector-effect: non-scaling-stroke; }
/* A full-height strip per day: the pointer has to be near the right day, not on the
   marker. It carries the day's <title>, which is the tooltip. */
.chart .hit { fill: transparent; }
.chart .hit:hover { fill: var(--surface-2); opacity: 0.55; }

h4.subhead { font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.06em;
             color: var(--muted); font-weight: 700; margin: 1.1rem 0 0.5rem; }
h4.subhead:first-of-type { margin-top: 0.2rem; }

/* ---------------- banners & flashes ---------------- */

.banner {
  border-radius: 10px; padding: 0.6rem 0.85rem; font-size: 0.88rem;
  border: 1px solid var(--border); background: var(--surface); margin-bottom: 0.9rem;
}
.banner.ok { background: var(--good-soft); border-color: var(--good); color: var(--good); }
.banner.info { background: var(--info-soft); border-color: var(--info); color: var(--info); }
.banner.warn { background: var(--warn-soft); border-color: var(--warn); color: var(--warn); }
.banner.error { background: var(--critical-soft); border-color: var(--critical); color: var(--critical); }
.banner a { color: inherit; font-weight: 600; }

/* Site-wide provider-degraded strip: slim, full width, above the page — it must be
   noticed on every screen without stealing the first inch of the content. */
.sitebanner {
  background: var(--warn-soft); border-bottom: 1px solid var(--warn); color: var(--warn);
  font-size: 0.84rem; font-weight: 600;
}
.sitebanner .in { max-width: var(--shell); margin: 0 auto; padding: 0.4rem 1.25rem; }
.sitebanner a { color: inherit; }

.flashes { max-width: var(--shell); margin: 1rem auto 0; padding: 0 1.25rem; }
.flashes .banner:last-child { margin-bottom: 0; }

/* ---------------- device timeline ---------------- */

.tl { list-style: none; margin: 0; padding: 0; }
.tl > li { position: relative; margin-left: 6px; padding: 0 0 1rem 1.1rem;
           border-left: 2px solid var(--border); }
.tl > li:last-child { border-left-color: transparent; }
.tl > li::before {
  content: ''; position: absolute; left: -6px; top: 6px; width: 9px; height: 9px;
  border-radius: 50%; background: var(--k-reply); border: 2px solid var(--bg);
}
.tl > li.e-receipt::before { background: var(--k-receipt); }
.tl > li.e-wake::before    { background: var(--k-wake); }
.tl > li.e-noise::before   { background: var(--k-noise); }
.tl > li.e-other::before   { background: var(--k-other); }
.tl .when { font-size: 0.78rem; color: var(--muted); margin-bottom: 0.3rem; }
.bubble {
  background: var(--surface); border: 1px solid var(--border); border-radius: 10px;
  padding: 0.6rem 0.75rem; font-size: 0.9rem;
}
.bubble.mt { background: var(--brand-soft); border-color: var(--brand); }
.rcpt { font-size: 0.78rem; color: var(--good); margin: 0 0 0.45rem; }

/* ---------------- stat strip ---------------- */

.stats { display: flex; flex-wrap: wrap; gap: 0.35rem 1.6rem; }
.stat { font-size: 0.9rem; }
.stat b { display: block; color: var(--muted); font-size: 0.68rem; letter-spacing: 0.06em;
          text-transform: uppercase; font-weight: 700; }

/* ---------------- staleness stamp ---------------- */

.stamp { font-size: 0.72rem; color: var(--muted); }
.stamp.stale { color: var(--warn); font-weight: 600; }

/* ---------------- pagination ---------------- */

.pager { display: flex; gap: 0.5rem; align-items: center; justify-content: flex-end;
         margin-top: 0.9rem; font-size: 0.84rem; color: var(--muted); }
.pager a, .pager span.cur {
  border: 1px solid var(--border); border-radius: 8px; padding: 0.2rem 0.6rem;
  text-decoration: none; color: var(--text);
}
.pager span.cur { background: var(--brand-soft); border-color: var(--brand); color: var(--brand); font-weight: 700; }
.pager .off { opacity: 0.4; }

/* ---------------- sticky bulk-action bar ---------------- */

.bulkbar {
  position: sticky; bottom: 0; z-index: 40; margin: 0.6rem -0.25rem 0;
  display: flex; gap: 0.6rem; align-items: center; flex-wrap: wrap;
  background: var(--surface); border: 1px solid var(--brand); border-radius: 12px;
  box-shadow: var(--shadow); padding: 0.6rem 0.9rem;
}
.bulkbar .count { font-weight: 700; }
.bulkbar[hidden] { display: none; }

/* ---------------- spinner & misc ---------------- */

.spinner {
  display: inline-block; width: 0.85em; height: 0.85em; margin-right: 0.5em;
  border: 2px solid var(--border); border-top-color: var(--brand);
  border-radius: 50%; animation: spin 0.8s linear infinite; vertical-align: -1px;
}
@keyframes spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) {
  .spinner { animation-duration: 3s; }
  html { scroll-behavior: auto; }
}

details.raw > summary { cursor: pointer; font-size: 0.84rem; color: var(--muted); font-weight: 600; }
details.raw[open] > summary { margin-bottom: 0.6rem; }
.reveal {
  background: var(--brand-soft); border: 1px solid var(--brand); border-radius: 8px;
  padding: 0.7rem 0.8rem; margin-top: 0.9rem; font-size: 0.84rem;
  word-break: break-all; font-family: var(--mono);
}

/* Sign-in / invite: a single centred card, no navigation. */
.authcard { max-width: 22rem; margin: 4rem auto; }

/* ---------------- print ---------------- */
/* On paper the header is the mark and the page title, nothing else: navigation and an
   account dropdown are screen affordances and printing them wastes the first inch of
   every report. */
@media print {
  /* Forced light: a dark-mode browser must not print pale grey on white. */
  :root {
    --bg: #ffffff; --surface: #ffffff; --surface-2: #f2f2f2;
    --border: #bbbbbb; --text: #111111; --muted: #444444;
    --brand: #7a5410; --brand-2: #7a5410; --brand-fg: #ffffff; --brand-soft: #f4efe4;
    --critical: #8c2b20; --critical-soft: #ffffff;
    --warn: #7a5410; --warn-soft: #ffffff;
    --info: #1e5a45; --info-soft: #ffffff;
    --good: #1e5a45; --good-soft: #ffffff;
    --accent: #23407a; --accent-soft: #ffffff;
    --violet: #5b3a6b; --violet-soft: #ffffff;
    --neutral-soft: #ffffff;
    --k-reply: #23407a; --k-reply-soft: #eef1f7;
    --k-receipt: #1e5a45; --k-receipt-soft: #eaf3ef;
    --k-wake: #7a5410; --k-wake-soft: #f6f0e4;
    --k-noise: #5b3a6b; --k-noise-soft: #f2eef5;
    --k-other: #444444; --k-other-soft: #f2f2f2;
    --shadow: none;
  }
  .topbar .controls, .nav, .usermenu, .bulkbar, .filterbar, .sitebanner { display: none !important; }
  .topbar { border-bottom: 1px solid var(--border); }
  .brand > svg { height: 20px; }
  .card { break-inside: avoid; }
  a { text-decoration: none; }
}
