/* ===========================================================================
   Device Manager — the one stylesheet.
   Served straight from public/ (see the <link> in layouts/application.html.erb),
   cache-busted on its own mtime, so editing this file changes every page on the
   next reload with no build step and no precompile.

   Tailwind is still loaded for LAYOUT utilities only (grid, flex, spacing,
   responsive prefixes). Anything that carries the design — colour, type, border,
   radius, elevation — belongs here, behind a token, so changing it once changes
   it everywhere. Do not reintroduce @apply component rules in the layout: they
   need the Tailwind runtime and split the system in two.
   =========================================================================== */

/* --- tokens ---------------------------------------------------------------
   Values are Tailwind's own defaults (slate for surfaces and text, blue-600 for
   the primary action, emerald/amber/red/sky for state), so the colour utilities
   still sitting in the layout and a few views line up with what is set here.   */
:root {
  color-scheme: light;

  --paper:      #f8fafc;   /* slate-50  */
  --panel:      #f1f5f9;   /* slate-100 */
  --surface:    #ffffff;
  --rule:       #e2e8f0;   /* slate-200 */
  --rule-soft:  #f1f5f9;   /* slate-100 */

  --ink:        #0f172a;   /* slate-900 */
  --ink-soft:   #475569;   /* slate-600 */
  --ink-faint:  #94a3b8;   /* slate-400 */

  --accent:      #2563eb;  /* blue-600 — primary action, links, current release */
  --accent-hover:#1d4ed8;  /* blue-700 */
  --accent-ink:  #ffffff;
  --accent-bg:   #eff6ff;  /* blue-50  */
  --accent-rule: #bfdbfe;  /* blue-200 */

  --good:      #059669; --good-bg:    #ecfdf5; --good-rule:    #a7f3d0;
  --info:      #0284c7; --info-bg:    #f0f9ff; --info-rule:    #bae6fd;
  --armed:     #b45309; --armed-bg:   #fffbeb; --armed-rule:   #fde68a;
  --refused:   #dc2626; --refused-bg: #fef2f2; --refused-rule: #fecaca;
  --stale:     #94a3b8;

  --font-sans: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto,
               "Helvetica Neue", Arial, sans-serif;
  --font-mono: ui-monospace, "SF Mono", SFMono-Regular, "JetBrains Mono", Menlo,
               Consolas, monospace;

  --text-xs:   0.75rem;
  --text-sm:   0.8125rem;
  --text-base: 0.875rem;
  --text-md:   0.9375rem;
  --text-lg:   1.125rem;
  --text-xl:   1.375rem;

  --radius:      0.5rem;
  --radius-sm:   0.375rem;
  --radius-pill: 999px;

  --shadow:    0 1px 2px 0 rgba(15, 23, 42, 0.05);
  --shadow-md: 0 4px 6px -1px rgba(15, 23, 42, 0.08), 0 2px 4px -2px rgba(15, 23, 42, 0.06);
  --focus:     0 0 0 2px #ffffff, 0 0 0 4px var(--accent);
  --focus-ring:0 0 0 3px rgba(37, 99, 235, 0.18);
}

/* --- base ----------------------------------------------------------------- */
body {
  background: var(--paper);
  color: var(--ink);
  font-family: var(--font-sans);
  font-size: var(--text-base);
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
}

img, svg, canvas { max-width: 100%; }

code, .mono {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
}
code {
  font-size: 0.875em;
  color: var(--ink);
  background: var(--panel);
  border-radius: var(--radius-sm);
  padding: 0.1em 0.3em;
}

main a:not(.btn):not(.nav-link):not(.dropdown-item) {
  color: var(--accent);
  text-decoration: none;
}
main a:not(.btn):not(.nav-link):not(.dropdown-item):hover {
  color: var(--accent-hover);
  text-decoration: underline;
  text-underline-offset: 0.15em;
}
:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* --- type ----------------------------------------------------------------- */
.h4 { font-size: var(--text-xl); font-weight: 600; color: var(--ink); letter-spacing: -0.01em; }
.h5 { font-size: var(--text-lg); font-weight: 600; color: var(--ink); }
.h6 { font-size: var(--text-md); font-weight: 600; color: var(--ink); }

/* Column heads, section labels, field captions. */
.eyebrow {
  font-size: var(--text-xs);
  font-weight: 600;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--ink-soft);
}

/* Headline values are fixed-width tokens — a version, a DID, a byte count — so
   they stay in the mono face and the digits stay in column. */
.stat {
  display: block;
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-size: clamp(1.5rem, 4vw, 1.875rem);
  font-weight: 600;
  line-height: 1.1;
  letter-spacing: -0.02em;
  margin: 0.45rem 0 0.3rem;
  color: var(--ink);
}
.stat-xl { font-size: clamp(2rem, 7vw, 3rem); letter-spacing: -0.03em; line-height: 1; }
.stat-lg { font-size: clamp(1.5rem, 4.5vw, 2.25rem); }

.sub { display: block; color: var(--ink-soft); font-size: var(--text-xs); }

/* Direction of travel next to a headline value. */
.trend {
  display: inline-flex; align-items: center; gap: 0.15rem;
  font-size: var(--text-xs); font-weight: 600;
}
.trend-up   { color: var(--good); }
.trend-down { color: var(--refused); }
.trend-flat { color: var(--ink-faint); }

/* --- sidebar / header ----------------------------------------------------- */
.nav-link {
  display: flex; align-items: center; gap: 0.625rem;
  padding: 0.5rem 0.75rem;
  border-radius: var(--radius);
  font-size: var(--text-base); font-weight: 500;
  color: var(--ink-soft); text-decoration: none;
  transition: background-color 0.12s, color 0.12s;
}
.nav-link:hover { background: var(--panel); color: var(--ink); }
.nav-link.active, .nav-link-active {
  background: var(--accent-bg); color: var(--accent-hover); font-weight: 600;
}
.nav-link .bi { font-size: var(--text-lg); opacity: 0.75; }

.dropdown-item {
  display: block; width: 100%; text-align: left; white-space: nowrap;
  padding: 0.5rem 1rem; font-size: var(--text-base);
  color: var(--ink); text-decoration: none;
}
.dropdown-item:hover { background: var(--panel); }
.dropdown-menu {
  position: absolute; left: 0; z-index: 40; margin-top: 0.25rem;
  width: max-content; min-width: 12rem; max-width: 100%;
  max-height: 20rem; overflow-y: auto;
  background: var(--surface);
  border: 1px solid var(--rule); border-radius: var(--radius);
  box-shadow: var(--shadow-md);
}

/* --- tabs ----------------------------------------------------------------- */
.tabs {
  display: flex; flex-wrap: wrap; gap: 0.25rem;
  border-bottom: 1px solid var(--rule);
  margin-bottom: 1.25rem;
}
.tab {
  display: inline-flex; align-items: center; gap: 0.5rem;
  padding: 0.5rem 0.875rem; margin-bottom: -1px;
  border: 1px solid transparent; border-bottom-color: transparent;
  border-radius: var(--radius) var(--radius) 0 0;
  background: none;
  font-family: inherit; font-size: var(--text-base); font-weight: 500;
  color: var(--ink-soft); cursor: pointer;
  transition: background-color 0.12s, color 0.12s;
}
.tab:hover { background: var(--panel); color: var(--ink); }
a.tab { text-decoration: none; }
.tab.active {
  background: var(--surface);
  border-color: var(--rule); border-bottom-color: var(--surface);
  color: var(--accent-hover); font-weight: 600;
}
.tab .bi { font-size: var(--text-lg); opacity: 0.75; }

/* --- surfaces ------------------------------------------------------------- */
.card {
  background: var(--surface);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
}
.card-header {
  display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between;
  gap: 0.5rem 0.75rem;
  padding: 0.875rem 1.25rem;
  border-bottom: 1px solid var(--rule);
  font-weight: 600; color: var(--ink);
}
.card-body { padding: 1.25rem; }
.card-footer {
  padding: 0.875rem 1.25rem;
  border-top: 1px solid var(--rule);
  color: var(--ink-soft); font-size: var(--text-xs);
}

/* A card whose children stack with even spacing and whose footer sinks to the
   bottom, so a row of them lines their controls up however tall each one grows. */
.panel {
  display: flex; flex-direction: column; gap: 1rem;
  background: var(--surface);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  padding: 1.25rem;
}
.panel-primary { border-color: var(--accent-rule); box-shadow: var(--shadow-md); }
.panel-head {
  display: flex; flex-wrap: wrap; align-items: baseline; justify-content: space-between;
  gap: 0.35rem 0.75rem;
  padding-bottom: 0.875rem; border-bottom: 1px solid var(--rule);
}
.panel-grow { flex: 1 1 auto; }
.panel-foot {
  display: flex; flex-wrap: wrap; align-items: center; gap: 0.5rem 0.75rem;
  margin-top: auto; padding-top: 0.875rem; border-top: 1px solid var(--rule);
}

/* A callout that annotates the thing above it, as opposed to an alert, which
   announces the result of something you just did. */
.note {
  margin: 0; padding: 0.75rem 0.875rem;
  background: var(--paper);
  border: 1px solid var(--rule);
  border-left: 3px solid var(--rule);
  border-radius: var(--radius-sm);
  font-size: var(--text-sm);
  color: var(--ink-soft);
}
.note p { margin: 0 0 0.5rem; }
.note p:last-child { margin-bottom: 0; }
.note-armed   { background: var(--armed-bg);   border-color: var(--armed-rule);   border-left-color: var(--armed); }
.note-refused { background: var(--refused-bg); border-color: var(--refused-rule); border-left-color: var(--refused); color: var(--refused); }
.note-accent  { background: var(--accent-bg);  border-color: var(--accent-rule);  border-left-color: var(--accent); }

/* A row of counts that belong together. */
.strip {
  display: grid; gap: 1px;
  background: var(--rule);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  overflow: hidden;
  grid-template-columns: repeat(auto-fit, minmax(12rem, 1fr));
}
.strip > * { background: var(--surface); padding: 1.125rem 1.25rem; }

/* --- buttons -------------------------------------------------------------- */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 0.4rem;
  padding: 0.5rem 0.875rem;
  background: var(--surface);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  color: var(--ink);
  font: inherit;
  font-size: var(--text-sm); font-weight: 500;
  line-height: 1.25rem;
  text-decoration: none; cursor: pointer;
  transition: background-color 0.12s, color 0.12s, border-color 0.12s;
}
.btn:hover { background: var(--panel); color: var(--ink); }
.btn:focus-visible { outline: none; box-shadow: var(--focus); }
.btn-sm { padding: 0.3rem 0.625rem; font-size: var(--text-xs); line-height: 1rem; }

/* .btn is inline-flex, so siblings in a squeezed table cell wrap one per line. */
.btn-row { display: inline-flex; gap: 0.375rem; white-space: nowrap; }

.btn-primary { background: var(--accent); border-color: var(--accent); color: var(--accent-ink); }
.btn-primary:hover { background: var(--accent-hover); border-color: var(--accent-hover); color: var(--accent-ink); }

.btn-outline-primary { border-color: var(--accent-rule); color: var(--accent); }
.btn-outline-primary:hover { background: var(--accent-bg); border-color: var(--accent); color: var(--accent-hover); }

.btn-outline-secondary { border-color: var(--rule); color: var(--ink-soft); }
.btn-outline-secondary:hover { background: var(--panel); color: var(--ink); }

.btn-outline-danger { border-color: var(--refused-rule); color: var(--refused); }
.btn-outline-danger:hover { background: var(--refused-bg); border-color: var(--refused); color: var(--refused); }
.btn-danger { background: var(--refused); border-color: var(--refused); color: #ffffff; }
.btn-danger:hover { background: #b91c1c; border-color: #b91c1c; color: #ffffff; }

.btn-link {
  border-color: transparent; background: none; box-shadow: none; color: var(--accent);
  padding-left: 0.25rem; padding-right: 0.25rem;
}
.btn-link:hover { background: none; color: var(--accent-hover); text-decoration: underline; }

.btn:disabled, .btn[disabled] {
  border-color: var(--rule); background: var(--panel);
  color: var(--ink-faint); cursor: not-allowed; box-shadow: none;
}
.btn:disabled:hover, .btn[disabled]:hover { background: var(--panel); color: var(--ink-faint); }

/* button_to emits a <form>; this keeps one sitting inline with its siblings. */
.btn-form { display: inline; }

/* --- badges --------------------------------------------------------------- */
.badge {
  display: inline-flex; align-items: center;
  padding: 0.15rem 0.55rem;
  border-radius: var(--radius-pill);
  background: var(--panel); color: var(--ink-soft);
  font-size: var(--text-xs); font-weight: 500;
  white-space: nowrap;
}
.badge.bg-secondary { background: var(--panel);      color: var(--ink-soft); }
.badge.bg-success   { background: var(--good-bg);    color: var(--good); }
.badge.bg-danger    { background: var(--refused-bg); color: var(--refused); }
.badge.bg-warning   { background: var(--armed-bg);   color: var(--armed); }
.badge.bg-info      { background: var(--info-bg);    color: var(--info); }
.badge.bg-light     { background: var(--panel);      color: var(--ink-soft); }
.badge-amber        { background: var(--armed-bg);   color: var(--armed); }

/* Phone ↔ watch direction: the watch always left, the phone always right, only the arrow turns —
   blue leaving the watch, green arriving at it — so a column of them reads at a glance. */
.sync-dir { display: inline-flex; align-items: center; gap: 0.3rem; white-space: nowrap; color: var(--ink); }
.sync-dir-out { color: var(--accent); }
.sync-dir-in { color: var(--good); }

/* The search box and the device the page is showing share one line, a quarter of it each; the
   strip sits on the input's baseline so the answer survives the dropdown closing, and the matching
   favourite chip is lit in the same colour. */
.picker-row { display: flex; align-items: flex-end; gap: 1rem; }
/* The strip is as wide as its one line (DID and tags) and wraps only when the row runs out of room. */
.picker-search { flex: 0 1 25%; min-width: min(100%, 14rem); }
.picked { flex: 0 1 auto; min-width: min(100%, 14rem); }
.picked {
  display: flex; flex-wrap: wrap; align-items: center; gap: 0.5rem;
  min-height: 2.6rem; padding: 0.5rem 0.875rem;
  background: var(--accent-bg); border: 1px solid var(--accent-rule); border-radius: var(--radius);
  color: var(--accent-hover);
}
.picked.d-none { display: none; }
.picked .did { font-family: var(--font-mono); font-size: var(--text-md); font-weight: 600; background: none; padding: 0; color: var(--accent-hover); }
.picked .eyebrow { color: var(--accent-hover); }
.btn.is-picked { background: var(--accent-bg); border-color: var(--accent); color: var(--accent-hover); }

/* Favourites take the page width, most recently shown first. Folded, the list is one chip row and
   the rest wrap out of sight below the clip (never a chip cut in half); the chevron opens it. The
   chevron only shows when there is something hidden (.has-more, set by the script). */
.favorites { display: flex; align-items: baseline; gap: 0.5rem 1rem; margin: 0 0 0.875rem; padding: 0.75rem 1rem; }
.favorites-list { display: flex; flex-wrap: wrap; gap: 0.5rem; flex: 1 1 0; min-width: 0; max-height: 2rem; overflow: hidden; }
.favorites.is-open .favorites-list { max-height: none; }
.favorites-toggle {
  flex: none; align-self: flex-start; visibility: hidden;
  width: 1.75rem; height: 1.75rem; padding: 0; border: 1px solid var(--accent-rule); border-radius: 999px;
  background: var(--accent-bg); color: var(--accent); font: inherit; cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center;
}
.favorites.has-more .favorites-toggle { visibility: visible; }
.favorites-toggle:hover { background: var(--accent); border-color: var(--accent); color: var(--accent-ink); }
.favorites-toggle .bi { transition: transform 0.15s ease; }
.favorites.is-open .favorites-toggle .bi { transform: rotate(180deg); }

/* Trace chart cards: title and subtitle on the left, the shared range menu on the right, and the
   rendered-data count under the plot. */
.trace-head { align-items: flex-start; }
.trace-title { min-width: 0; flex: 1 1 14rem; }
.trace-menu { display: flex; flex-wrap: wrap; align-items: center; gap: 0.375rem; flex: 0 1 auto; }
.trace-menu .form-control { width: auto; }
.chart-stats { font-variant-numeric: tabular-nums; white-space: nowrap; }
.trace-plot { position: relative; }
/* zrender rewrites the canvas cursor inline on every mousemove; only !important outranks it */
.trace-plot.is-selecting > div { cursor: crosshair !important; }
.trace-select {
  position: absolute; pointer-events: none;
  background: color-mix(in srgb, var(--accent) 16%, transparent);
  border-left: 1px solid var(--accent); border-right: 1px solid var(--accent);
}

/* --- text viewer --------------------------------------------------------- */
/* A log opened in place instead of downloaded: four fifths of the viewport, the mono face, the
   search hits lit and the current one darker. */
.viewer { position: fixed; inset: 0; z-index: 60; display: flex; align-items: center; justify-content: center;
          background: rgba(15, 23, 42, 0.5); padding: 1rem; }
.viewer.d-none { display: none; }
.viewer-card { width: 80vw; height: 80vh; display: flex; flex-direction: column; min-width: 0;
               background: var(--surface); border: 1px solid var(--rule); border-radius: var(--radius); box-shadow: var(--shadow-md); }
.viewer-head { display: flex; flex-wrap: wrap; align-items: center; gap: 0.5rem 0.75rem; padding: 0.625rem 1rem; border-bottom: 1px solid var(--rule); }
.viewer-head .form-control { width: 16rem; }
.viewer-body { flex: 1 1 auto; overflow: auto; margin: 0; padding: 0.75rem 1rem;
               font-family: var(--font-mono); font-size: var(--text-sm); line-height: 1.45; white-space: pre; color: var(--ink);
               font-variant-numeric: tabular-nums; }
.viewer-body mark { background: #fde68a; color: inherit; padding: 0; }
.viewer-body mark.current { background: #f59e0b; }

/* --- favorite star --------------------------------------------------------- */
.star-col { width: 2rem; padding-right: 0; }
.star {
  background: none; border: 0; padding: 0; cursor: pointer;
  font-size: var(--text-base); line-height: 1;
  filter: grayscale(1); opacity: 0.4;
}
.star:hover { opacity: 0.8; }
.star[aria-pressed="true"] { filter: none; opacity: 1; }

/* A status word belonging to one row, rather than a count. */
.state {
  display: inline-block;
  padding: 0.15rem 0.55rem;
  border: 1px solid currentColor; border-radius: var(--radius-pill);
  color: var(--ink-soft);
  font-size: var(--text-xs); font-weight: 500;
  white-space: nowrap;
}
.state-ok, .state.is-up-to-date          { color: var(--good); }
.state-armed, .state.is-pending,
.state.is-ahead                          { color: var(--armed); }
.state-refused                           { color: var(--refused); }

/* --- tables --------------------------------------------------------------- */
.table-responsive {
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  overscroll-behavior-x: contain;
}
.table { width: 100%; border-collapse: collapse; font-size: var(--text-sm); }
.table thead th {
  padding: 0.625rem 0.875rem;
  text-align: left; white-space: nowrap;
  font-size: var(--text-xs); font-weight: 600;
  letter-spacing: 0.04em; text-transform: uppercase;
  color: var(--ink-soft);
  background: var(--paper);
  border-bottom: 1px solid var(--rule);
}
.table thead th.text-end { text-align: right; }
.table tbody td {
  padding: 0.75rem 0.875rem; vertical-align: middle;
  border-bottom: 1px solid var(--rule-soft);
}
.table tbody tr:last-child td { border-bottom: 0; }
.table tfoot td { padding: 0.75rem 0.875rem; vertical-align: middle; border-top: 2px solid var(--rule); }
.table tbody tr:hover td { background: var(--panel); }
.table-sm thead th { padding: 0.5rem 0.625rem; }
.table-sm tbody td, .table-sm tfoot td { padding: 0.5rem 0.625rem; }
.table .blank { color: var(--ink-faint); }
/* A hash or a DID must not wrap mid-token; the wrapper scrolls instead. */
.table .mono, .table code { white-space: nowrap; }

/* Sortable column: the th carries aria-sort, the button in it is the control and inherits the
   head's type, so an unsorted head reads like every other one. */
.table thead th .sort {
  all: unset;
  display: inline-flex; align-items: center; gap: 0.3rem;
  cursor: pointer;
}
.table thead th .sort::after { content: "\21C5"; color: var(--ink-faint); }
.table thead th .sort:hover { color: var(--ink); }
.table thead th .sort:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: var(--radius-sm); }
.table thead th[aria-sort] .sort { color: var(--ink); }
.table thead th[aria-sort="ascending"] .sort::after  { content: "\2191"; color: var(--accent); }
.table thead th[aria-sort="descending"] .sort::after { content: "\2193"; color: var(--accent); }

/* Cursor pagination footer: count on the left, "older" on the right. */
.table-page {
  display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between;
  gap: 0.5rem 0.75rem; padding-top: 0.875rem;
}

/* --- forms ---------------------------------------------------------------- */
.form-label {
  display: block; margin-bottom: 0.35rem;
  font-size: var(--text-sm); font-weight: 500;
  color: var(--ink);
}
.form-control {
  display: block; width: 100%;
  padding: 0.5rem 0.75rem;
  background: var(--surface);
  border: 1px solid var(--rule); border-radius: var(--radius);
  box-shadow: var(--shadow);
  color: var(--ink);
  font: inherit; font-size: var(--text-base);
}
.form-control::placeholder { color: var(--ink-faint); }
/* Anywhere on a date input opens its picker (dm.js); the indicator only signals, never takes the click. */
input[type="date"], input[type="datetime-local"], input[type="month"] { cursor: pointer; }
input[type="date"]::-webkit-calendar-picker-indicator,
input[type="datetime-local"]::-webkit-calendar-picker-indicator,
input[type="month"]::-webkit-calendar-picker-indicator { pointer-events: none; }
.form-control:focus { outline: none; border-color: var(--accent); box-shadow: var(--focus-ring); }
.form-control:disabled { background: var(--panel); color: var(--ink-faint); }
.form-control-sm { padding: 0.35rem 0.55rem; font-size: var(--text-sm); }
.form-control.mono, select.form-control, input.form-control[type="text"].mono {
  font-family: var(--font-mono);
}
textarea.form-control { resize: vertical; }
.form-check { display: flex; align-items: center; gap: 0.5rem; font-size: var(--text-base); }
.form-check input[type="checkbox"], .form-check input[type="radio"] { accent-color: var(--accent); width: 1rem; height: 1rem; }
.form-hint { margin: 0.35rem 0 0; font-size: var(--text-xs); color: var(--ink-soft); }

/* A search box with its filters stacked under it, each only as wide as it needs to be. */
.toolbar { display: flex; flex-direction: column; align-items: flex-start; gap: 0.5rem; }
/* Filters only, no search box: one line. Not `.inline`: Tailwind turns that name into display:inline. */
.toolbar.toolbar-inline { flex-direction: row; flex-wrap: wrap; align-items: center; }

/* A collapsed filter under a search box; open, it takes the full width. Beside a search box
   (toolbar-inline) it sits on the search line and, open, takes the rest of it. */
.toolbar-inline:has(> .facet[open]) { align-items: flex-start; }
.toolbar-inline > .facet[open] { flex: 1 1 20rem; }
.facet {
  min-width: 0;
  background: var(--surface);
  border: 1px solid var(--rule); border-radius: var(--radius);
  box-shadow: var(--shadow);
}
.facet[open] { align-self: stretch; }
.facet > summary {
  display: flex; align-items: center; gap: 0.5rem;
  padding: 0.5rem 0.875rem; cursor: pointer; list-style: none;
  font-size: var(--text-base); font-weight: 500; color: var(--ink);
}
.facet > summary::-webkit-details-marker { display: none; }
.facet > summary::before { content: "\25B8"; color: var(--ink-faint); font-size: var(--text-xs); }
.facet[open] > summary::before { content: "\25BE"; }
.facet-body {
  display: flex; flex-direction: column; gap: 0.75rem;
  padding: 0.875rem; border-top: 1px solid var(--rule);
}
.facet-options {
  display: grid; gap: 0.4rem 1.25rem;
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 11rem), 1fr));
}
.facet-options .form-check { font-size: var(--text-sm); }
.facet-options .num { margin-left: auto; font-family: var(--font-mono); font-size: var(--text-xs); color: var(--ink-soft); }

.field { display: flex; flex-direction: column; gap: 0.3rem; }
.field-row { display: flex; flex-wrap: wrap; align-items: center; gap: 0.5rem; }
.field-row > .form-control, .field-grow { flex: 1 1 11rem; min-width: 0; }

.input-group { display: flex; align-items: stretch; min-width: 0; }
.input-group > .form-control { border-radius: 0; border-left: 0; border-right: 0; box-shadow: none; min-width: 0; }
.input-group > :first-child { border-radius: var(--radius) 0 0 var(--radius); border-left: 1px solid var(--rule); }
.input-group > :last-child  { border-radius: 0 var(--radius) var(--radius) 0; border-right: 1px solid var(--rule); }
.input-group-text {
  display: inline-flex; align-items: center; padding: 0 0.75rem;
  background: var(--panel); border: 1px solid var(--rule);
  color: var(--ink-soft); font-size: var(--text-sm); white-space: nowrap;
}

/* --- alerts --------------------------------------------------------------- */
.alert {
  padding: 0.75rem 1rem;
  border: 1px solid var(--rule); border-radius: var(--radius);
  font-size: var(--text-base);
}
.alert-success { background: var(--good-bg);    border-color: var(--good-rule);    color: #047857; }
.alert-danger  { background: var(--refused-bg); border-color: var(--refused-rule); color: #b91c1c; }
.alert-warning { background: var(--armed-bg);   border-color: var(--armed-rule);   color: var(--armed); }
.alert-info    { background: var(--info-bg);    border-color: var(--info-rule);    color: #0369a1; }

/* --- breadcrumb, spinner -------------------------------------------------- */
.breadcrumb {
  display: flex; flex-wrap: wrap; align-items: center; gap: 0.25rem;
  margin: 0; padding: 0; list-style: none;
  font-size: var(--text-base); color: var(--ink-soft);
  overflow-wrap: anywhere;
}
.breadcrumb-item { display: flex; align-items: center; gap: 0.25rem; }
.breadcrumb-item + .breadcrumb-item::before { content: "/"; margin-right: 0.25rem; color: var(--ink-faint); }
.breadcrumb-item.active { color: var(--ink); font-weight: 500; }
.breadcrumb-item a { color: var(--accent); }

.spinner-border {
  display: inline-block; width: 1rem; height: 1rem;
  border: 2px solid var(--rule); border-top-color: var(--accent);
  border-radius: var(--radius-pill);
  animation: dm-spin 0.7s linear infinite;
  vertical-align: -0.125em;
}
.spinner-border-sm { width: 0.875rem; height: 0.875rem; }
@keyframes dm-spin { to { transform: rotate(360deg); } }

/* --- modal ---------------------------------------------------------------- */
.modal {
  position: fixed; inset: 0; z-index: 50;
  display: none; place-items: center;
  padding: 1rem; background: rgba(15, 23, 42, 0.5);
}
.modal.is-open { display: grid; }
.modal-box {
  width: 100%; max-width: 34rem;
  max-height: calc(100dvh - 2rem); overflow: auto;
  background: var(--surface);
  border: 1px solid var(--rule); border-radius: var(--radius);
  box-shadow: var(--shadow-md);
}
.modal-box-wide { max-width: 60rem; }
.modal-head {
  display: flex; align-items: center; justify-content: space-between; gap: 0.75rem;
  padding: 1rem 1.25rem; border-bottom: 1px solid var(--rule);
  font-weight: 600;
}
.modal-body { padding: 1.25rem; display: flex; flex-direction: column; gap: 1rem; }
/* The Info modals built as a .card in a fixed overlay: the card never outgrows the viewport, so its
   header (Close) and footer stay on screen and the body scrolls instead. */
.overlay-card { display: flex; flex-direction: column; max-height: 100%; }
.overlay-card > .card-body { flex: 1 1 auto; min-height: 0; overflow-y: auto; }

/* --- key/value lists ------------------------------------------------------ */
/* Metadata under a headline value. Each pair is its own wrapper element in the views, so a
   pair occupies one grid cell and a label never wraps away from its value. */
.meta {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 9rem), 1fr));
  gap: 0.2rem 1.25rem;
  margin: 0.7rem 0 0; font-size: var(--text-xs);
}
.meta dt {
  display: inline; letter-spacing: 0.04em; text-transform: uppercase; color: var(--ink-soft);
}
.meta dd { display: inline; margin: 0 0 0 0.35rem; font-family: var(--font-mono); color: var(--ink); }

/* Two columns of pairs — a detail panel. */
.detail-list {
  display: grid; gap: 0.35rem 1.5rem; margin: 0 0 1rem; font-size: var(--text-sm);
  grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
}
.detail-list dt {
  display: inline; font-size: var(--text-xs);
  letter-spacing: 0.04em; text-transform: uppercase; color: var(--ink-soft);
}
.detail-list dd { display: inline; margin: 0 0 0 0.35rem; font-family: var(--font-mono); overflow-wrap: anywhere; }

/* Label left, number right, one per line. */
.tally { margin: 0.5rem 0 0; font-size: var(--text-xs); color: var(--ink-soft); }
.tally div { display: flex; justify-content: space-between; gap: 0.75rem; }
.tally dd { margin: 0; font-family: var(--font-mono); color: var(--ink); }

/* Rows of equal panels. They fold at whatever width the content stops fitting,
   rather than at one hard breakpoint, so the mid sizes are not a dead zone. */
.row { display: grid; gap: 1rem; margin-bottom: 1rem; }
.row-pair { grid-template-columns: repeat(auto-fit, minmax(min(100%, 22rem), 1fr)); }
.row-trio { grid-template-columns: repeat(auto-fit, minmax(min(100%, 18rem), 1fr)); }

.more summary { cursor: pointer; color: var(--ink-soft); }
.more[open] summary { margin-bottom: 0.6rem; }
.push-end { margin-left: auto; }

/* --- distribution band ----------------------------------------------------
   A proportional stripe: one segment per category, width by count. Used for the
   OTA fleet's version spread, and reusable for any "what is out there" split.  */
.band {
  display: flex; height: 3rem;
  border: 1px solid var(--rule); border-radius: var(--radius); overflow: hidden;
}
.band > * { min-width: 3px; border-right: 1px solid var(--surface); transform-origin: left center; }
.band > :last-child { border-right: 0; }

.seg-stale   { background: var(--stale); }
.seg-behind  { background: var(--info); }
.seg-live    { background: var(--accent); }
.seg-armed   { background: repeating-linear-gradient(135deg, var(--armed) 0 4px, #d97706 4px 8px); }
.seg-refused { background: repeating-linear-gradient(135deg, var(--refused) 0 4px, #ef4444 4px 8px); }
.seg-absent  {
  background: var(--panel);
  background-image: radial-gradient(var(--ink-faint) 1px, transparent 1px);
  background-size: 5px 5px;
}

/* The band's readout, and the only version of it a screen reader gets. Each row
   is banded in its segment's colour, so no separate swatch element is needed. */
.legend {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(min(100%, 13rem), 1fr));
  gap: 0.5rem 1.5rem; margin: 1rem 0 0; padding: 0; list-style: none;
}
.legend li {
  display: flex; align-items: baseline; gap: 0.5rem;
  padding: 0.5rem 0 0.5rem 0.7rem;
  border-left: 3px solid var(--rule);
  background: var(--panel);
  border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
}
.legend .key { font-family: var(--font-mono); font-weight: 500; }
.legend .num { margin-left: auto; padding-right: 0.6rem; font-family: var(--font-mono); font-variant-numeric: tabular-nums; }
.legend small {
  display: block; font-size: var(--text-xs);
  letter-spacing: 0.04em; text-transform: uppercase; color: var(--ink-soft);
}
.legend .is-live    { border-left-color: var(--accent); }
.legend .is-behind  { border-left-color: var(--info); }
.legend .is-armed   { border-left-color: var(--armed); }
.legend .is-refused { border-left-color: var(--refused); }

/* Per-row miniature of the band: label, proportional track, count. */
.spread { list-style: none; margin: 0.5rem 0 0; padding: 0; }
.spread li {
  display: grid; grid-template-columns: minmax(4.5rem, 7rem) 1fr auto; align-items: center;
  gap: 0.6rem; padding: 0.25rem 0;
  font-size: var(--text-sm); color: var(--ink-soft);
}
.spread li.is-live { color: var(--ink); font-weight: 500; }
.track { height: 0.5rem; background: var(--panel); border-radius: var(--radius-pill); overflow: hidden; }
.track > i { display: block; height: 100%; background: var(--stale); }
.spread .is-live .track > i  { background: var(--accent); }
.spread .is-absent .track > i { background: var(--rule); }
.spread .num { min-width: 2.5rem; text-align: right; font-family: var(--font-mono); }

/* --- sequence gate --------------------------------------------------------
   Numbered steps for a genuine one-way sequence. Only use where the order is
   real and the reader needs it — not as decoration on an unordered list.       */
.gate { display: inline-flex; align-items: center; }
.gate span {
  display: grid; place-items: center;
  width: 1.5rem; height: 1.5rem;
  background: var(--surface); border: 1px solid var(--rule);
  color: var(--ink-faint);
  font-family: var(--font-mono); font-size: var(--text-xs);
}
.gate span + span { border-left: 0; }
.gate span:first-child { border-radius: var(--radius-sm) 0 0 var(--radius-sm); }
.gate span:last-child  { border-radius: 0 var(--radius-sm) var(--radius-sm) 0; }
.gate .is-done { background: var(--accent); border-color: var(--accent); color: var(--accent-ink); }
.gate .is-next { border-color: var(--armed); color: var(--armed); }
.gate .is-void { background: var(--panel); border-color: var(--rule); color: var(--ink-faint); }

/* An ordered precedence, drawn rather than described. */
.ladder {
  display: flex; flex-wrap: wrap; align-items: center; gap: 0.5rem;
  margin: 0 0 1.5rem; padding: 0; list-style: none;
  font-family: var(--font-mono); font-size: var(--text-sm);
}
.ladder li { display: flex; align-items: center; gap: 0.5rem; }
.ladder li::after { content: "\25B8"; color: var(--ink-faint); }
.ladder li:last-child::after { content: ""; }
.ladder b {
  padding: 0.2rem 0.6rem; border: 1px solid var(--rule); border-radius: var(--radius-pill);
  background: var(--surface); font-weight: 500;
}
.ladder li:first-child b { background: var(--accent); border-color: var(--accent); color: var(--accent-ink); }

/* --- record lists --------------------------------------------------------- */
/* The lines around a parsed log line (Phone Syncs Info modal): the mono face, the event line lit. */
.snippet {
  margin: 0; padding: 0.75rem; max-height: 40vh; overflow: auto; white-space: pre;
  font-family: var(--font-mono); font-size: var(--text-xs); line-height: 1.5; color: var(--ink);
  background: var(--panel); border: 1px solid var(--rule); border-radius: var(--radius);
}
.snippet .ln { display: inline-block; min-width: 4ch; text-align: right; color: var(--ink-faint); }
.snippet .hit { display: inline-block; min-width: 100%; background: var(--accent-bg); font-weight: 600; }

/* The firmware census on Home: the table (the legend, one swatch per row) beside its pie. */
.census { display: grid; grid-template-columns: minmax(0, 1fr) 27rem; align-items: center; }
.census-chart { padding: 0.5rem 1rem; border-left: 1px solid var(--rule-soft); }
/* The narrow columns shrink to their content (w-px), and a swatch must not wrap away from its version. */
.census th, .census td { white-space: nowrap; }
.swatch { display: inline-block; width: 0.6rem; height: 0.6rem; border-radius: 999px; margin-right: 0.45rem; vertical-align: middle; }
@media (max-width: 900px) {
  .census { grid-template-columns: 1fr; }
  .census-chart { border-left: 0; border-top: 1px solid var(--rule-soft); }
}

/* A proportion in a table cell: the filled part of the track is the share, the number says it. */
.share { display: grid; grid-template-columns: minmax(6rem, 1fr) auto; align-items: center; gap: 0.5rem; }
.share-track { display: block; height: 0.5rem; border-radius: 999px; background: var(--panel); overflow: hidden; }
.share-track > i { display: block; height: 100%; background: var(--accent); border-radius: inherit; }
.share-num { font-family: var(--font-mono); font-size: var(--text-xs); color: var(--ink-soft); font-variant-numeric: tabular-nums; }

.records { list-style: none; margin: 0; padding: 0; }
.records > li { padding: 0.875rem 0; border-top: 1px solid var(--rule-soft); }
.records > li:first-child { border-top: 0; }
.records-compact > li {
  display: flex; flex-wrap: wrap; align-items: center; gap: 0.5rem 0.6rem; padding: 0.5rem 0;
}
/* A per-record control row: the select takes the slack but stops short of the full card width,
   so its buttons stay beside it rather than wrapping under. */
.records .field-row > .form-control { max-width: 22rem; }
.record-head {
  display: flex; flex-wrap: wrap; align-items: baseline; justify-content: space-between; gap: 0.35rem 0.75rem;
}
.push { margin-left: auto; color: var(--ink-soft); }
.chips {
  display: flex; flex-wrap: wrap; gap: 0.35rem; margin-top: 0.5rem;
  font-family: var(--font-mono); font-size: var(--text-xs); color: var(--ink-soft);
}

/* --- page furniture ------------------------------------------------------- */
.page-head {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: 0.5rem 1.25rem;
  padding: 1.25rem 0 1rem; margin-bottom: 1.5rem;
  border-bottom: 1px solid var(--rule);
}
.page-head h1 {
  margin: 0; font-size: var(--text-xl); font-weight: 600;
  letter-spacing: -0.01em; color: var(--ink);
}
.page-head nav { display: flex; flex-wrap: wrap; gap: 0.75rem 1rem; }
@media (min-width: 640px) { .page-head nav { margin-left: auto; } }

.section { margin-bottom: 2rem; }

/* The line above the OTA fleet band: what leads the fleet right now. */
.fleet-newest-line {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: 0.5rem;
  margin: 0 0 0.875rem;
}
.fleet-newest-line .mono { font-size: var(--text-lg); color: var(--accent); }
.orphans { margin-bottom: 1rem; }
.did { font-size: var(--text-base); font-weight: 500; overflow-wrap: anywhere; }

.section-head {
  display: flex; flex-wrap: wrap; align-items: baseline; justify-content: space-between;
  gap: 0.35rem 0.75rem; padding-bottom: 0.6rem; margin-bottom: 1rem;
  border-bottom: 1px solid var(--rule);
}
.lede { margin: 0; color: var(--ink-soft); }
.lede-wide { max-width: 68ch; margin-bottom: 1.5rem; }
.empty { margin: 0; color: var(--ink-soft); }

/* --- Bootstrap-shaped aliases --------------------------------------------
   Names the existing views and their inline JS already emit. Kept so no page has
   to be rewritten to adopt this file; prefer the semantic names above in new work. */
.d-flex { display: flex; }
.d-none { display: none; }
.d-inline { display: inline; }
.d-block { display: block; }
.flex-column { flex-direction: column; }
.justify-content-between { justify-content: space-between; }
.align-items-center { align-items: center; }
.text-end { text-align: right; }
.text-muted, .text-secondary { color: var(--ink-soft); }
.text-danger { color: var(--refused); }
.text-warning { color: var(--armed); }
.text-success { color: var(--good); }
.text-dark { color: var(--ink); }
.fw-semibold { font-weight: 600; }
.fw-normal { font-weight: 400; }
.small { font-size: var(--text-xs); }
.fs-3 { font-size: var(--text-xl); }
.border-bottom { border-bottom: 1px solid var(--rule); }
.position-relative { position: relative; }
.position-absolute { position: absolute; }
.me-1 { margin-right: 0.25rem; }
.me-2 { margin-right: 0.5rem; }
.ms-1 { margin-left: 0.25rem; }
.ms-auto { margin-left: auto; }
.ps-4 { padding-left: 1.5rem; }
.g-3 { gap: 1rem; }
.btn-secondary { background: var(--ink); border-color: var(--ink); color: #ffffff; }
.btn-secondary:hover { background: #1e293b; border-color: #1e293b; color: #ffffff; }
.badge-blue { background: var(--accent-bg); color: var(--accent-hover); }
/* Its own row under the legend: five series and the stats no longer fit one row at 1280 px.
   Selectable although zrender puts user-select: none on the chart container around it. */
.window-stats {
  position: absolute; top: 28px; z-index: 1; text-align: right;
  font-size: var(--text-xs); line-height: 1.4; color: var(--ink-soft);
  font-variant-numeric: tabular-nums; white-space: nowrap;
  -webkit-user-select: text; user-select: text; cursor: text;
}
.window-stats span { display: block; }
.window-stats b { color: var(--ink); font-weight: 600; }
/* Trace chart popups list every series; the value column keeps its width when a value is empty,
   so the box does not change size under the pointer. */
.tip-rows { display: grid; grid-template-columns: auto minmax(5rem, auto); column-gap: 1rem; row-gap: 0.15rem;
            font-variant-numeric: tabular-nums; white-space: nowrap; }
.tip-rows b { text-align: right; }
.tip-dot { display: inline-block; width: 10px; height: 10px; border-radius: 50%; margin-right: 0.4rem; vertical-align: -1px; }
/* page headers: wrap the title and its right-side note instead of cramping */
.d-flex.justify-content-between.border-bottom { flex-wrap: wrap; column-gap: 0.75rem; row-gap: 0.25rem; }

/* --- small screens --------------------------------------------------------- */
@media (max-width: 640px) {
  .card-body, .panel, .modal-body { padding: 1rem; }
  .card-header, .card-footer, .modal-head { padding-left: 1rem; padding-right: 1rem; }
  .strip > * { padding: 0.875rem 1rem; }
  .table thead th { padding: 0.5rem 0.625rem; }
  .table tbody td { padding: 0.625rem; }
  .fleet-newest { text-align: left; }
  .picker-row { flex-direction: column; align-items: stretch; }
  .picker-search, .picked { flex-basis: auto; }
  .favorites { flex-wrap: wrap; }
  .favorites-list { order: 1; flex-basis: 100%; }
  .push-end, .push { margin-left: 0; }
  .btn { width: auto; }
}

/* --- motion --------------------------------------------------------------- */
/* One orchestrated moment, used by the fleet band: it fills left to right on
   load, oldest first, which is the direction the work moves it. */
@media (prefers-reduced-motion: no-preference) {
  .band > * { animation: dm-fill 0.5s cubic-bezier(0.2, 0.7, 0.3, 1) backwards; }
  @keyframes dm-fill { from { transform: scaleX(0); } to { transform: scaleX(1); } }
}
