:root {
  /* The playground is dark-only; saying so gives native widgets — scroll
     bars, the select's dropdown, date pickers — dark chrome to match
     instead of the browser's light default. */
  color-scheme: dark;
  --bg: #0f1115;
  --panel: #171a21;
  --panel-2: #1d212b;
  --border: #2a3040;
  --text: #e6e9ef;
  --muted: #8b93a7;
  --accent: #4f8cff;
  --accent-soft: rgba(79, 140, 255, 0.14);
  --error: #ff6b6b;
  --radius: 10px;
  /* Data reads in monospace — channel names, timestamps, structure rows.
     Prose stays in the system sans. One deliberate voice for numbers. */
  --mono: ui-monospace, "SF Mono", "Cascadia Mono", Menlo, Consolas, monospace;
}

* { box-sizing: border-box; }

html, body { height: 100%; }

body {
  margin: 0;
  display: flex;
  flex-direction: column;
  background: var(--bg);
  color: var(--text);
  font: 16px/1.5 system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
}

header, main, footer { padding: 0 24px; width: 100%; }
main { flex: 1; }

header { padding-top: 20px; }
.header-row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 16px;
  flex-wrap: wrap;
}
h1 { font-size: 22px; margin: 0; font-weight: 650; letter-spacing: 0.2px; }
h1 .sub {
  font-size: 13px;
  font-weight: 400;
  color: var(--muted);
  margin-left: 6px;
}
nav { display: flex; gap: 16px; }
nav a { color: var(--muted); text-decoration: none; font-size: 13px; }
nav a:hover { color: var(--accent); }
.tagline { color: var(--muted); margin: 6px 0 0; font-size: 14px; }

#landing {
  max-width: 560px;
  margin: 0 auto;
  /* One quiet screen: the drop zone is the whole page, centered in what's
     left of the viewport instead of floating at the top of dead space. */
  min-height: calc(100vh - 260px);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
}
/* The flex display above beats the hidden attribute's UA style — without
   this the landing would stay on screen behind the viewer. */
#landing[hidden] { display: none; }

#dropzone {
  display: flex;
  flex-direction: column;
  gap: 8px;
  align-items: center;
  justify-content: center;
  padding: 52px 48px;
  border: 2px dashed var(--border);
  border-radius: var(--radius);
  background: var(--panel);
  cursor: pointer;
  transition: border-color 0.15s, background 0.15s;
}
.dropwave { color: var(--accent); opacity: 0.55; margin-bottom: 4px; }
#dropzone span { color: var(--muted); font-size: 13px; }
#dropzone:hover, #dropzone:focus-visible, #dropzone.dragover {
  border-color: var(--accent);
  background: var(--accent-soft);
  outline: none;
}
#dropzone:hover .dropwave, #dropzone.dragover .dropwave { opacity: 1; }
#dropzone.dragover { transform: scale(1.01); }
.dropfacts {
  margin: 14px 0 0;
  color: var(--muted);
  font-size: 12.5px;
  font-family: var(--mono);
}

.urlform {
  display: flex;
  gap: 8px;
  width: min(560px, 100%);
  margin: 14px auto 0;
}
.urlform input {
  flex: 1;
  min-width: 0;
  padding: 7px 10px;
  font: inherit;
  font-family: var(--mono);
  font-size: 12.5px;
  color: var(--text);
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 6px;
}
.urlform input:focus { outline: none; border-color: var(--accent); }
.urlform button {
  padding: 7px 12px;
  font: inherit;
  font-size: 13px;
  color: var(--accent);
  background: transparent;
  border: 1px solid var(--border);
  border-radius: 6px;
  cursor: pointer;
}

#sample-btn {
  margin-top: 16px;
  padding: 8px 14px;
  font: inherit;
  font-size: 13px;
  color: var(--accent);
  background: transparent;
  border: 1px solid var(--border);
  border-radius: 6px;
  cursor: pointer;
}
#sample-btn:hover { border-color: var(--accent); background: var(--accent-soft); }

#status { color: var(--muted); font-size: 14px; min-height: 22px; }
.error {
  color: var(--error);
  font-size: 14px;
  white-space: pre-wrap;
}

#viewer { margin: 18px 0 24px; }

/* Once a file is open the page is a tool, not a brochure: the site header
   and footer step aside and the recording is the context (the filebar names
   it). The viewer then fills the window exactly, with no page scroll. */
body:has(#viewer:not([hidden])) header,
body:has(#viewer:not([hidden])) footer { display: none; }
body:has(#viewer:not([hidden])) main { padding-top: 18px; }

#filebar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 10px 14px;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  font-size: 13px;
  color: var(--muted);
  flex-wrap: wrap;
}
#fileinfo b { color: var(--text); font-weight: 600; }
#fileinfo .sep { margin: 0 8px; opacity: 0.5; }
#close-btn {
  padding: 6px 12px;
  font: inherit;
  font-size: 13px;
  color: var(--text);
  background: var(--panel-2);
  border: 1px solid var(--border);
  border-radius: 6px;
  cursor: pointer;
}
#close-btn:hover { border-color: var(--accent); }
#fileinfo .cmp {
  margin-left: 10px;
  padding: 1px 8px;
  border: 1px solid var(--border);
  border-radius: 999px;
  color: var(--accent);
  background: var(--accent-soft);
}

/* The side panel's width is a variable the resizer drags, so long channel and
   group names can be read in full without a tooltip. The splitter column sits
   in the gap between the panels; its own width replaces the old gap. */
#layout {
  --side-w: 280px;
  display: grid;
  grid-template-columns: var(--side-w) 14px minmax(0, 1fr);
  /* A definite height, the viewport below the file bar (104 px above it,
     26 px of margins and borders below): the plot gets the whole window,
     and a file with thousands of channels scrolls inside the list instead
     of stretching the row — and with it the plot canvas — to the list's
     full length. */
  grid-template-rows: minmax(0, 1fr);
  height: max(520px, calc(100dvh - 130px));
  margin-top: 14px;
}
#side-resizer {
  position: relative;
  cursor: col-resize;
  touch-action: none;
}
#side-resizer::after {
  content: "";
  position: absolute;
  top: 0;
  bottom: 0;
  left: 6px;
  width: 2px;
  border-radius: 1px;
  background: transparent;
  transition: background 0.15s;
}
#side-resizer:hover::after,
#side-resizer:focus-visible::after,
#side-resizer.dragging::after { background: var(--accent); }
#side-resizer:focus-visible { outline: none; }
body.side-resizing { cursor: col-resize; user-select: none; }
/* A file dragged over the open viewer: dropping it opens it in place of the
   current one, and the outline says the drop will be taken. */
body.filedrag #layout {
  outline: 2px dashed var(--accent);
  outline-offset: 4px;
  border-radius: var(--radius);
}

aside {
  display: flex;
  flex-direction: column;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  overflow: hidden;
}
/* Sidebar tabs (structure vs channels): the same two-pane split the desktop
   viewer's left panel has; the panes share the aside's height. */
#side-tabs {
  display: flex;
  gap: 4px;
  padding: 8px 8px 0;
}
.side-tab {
  flex: 1;
  padding: 6px 8px;
  font: inherit;
  font-size: 13px;
  color: var(--muted);
  background: transparent;
  border: 1px solid transparent;
  border-radius: 6px;
  cursor: pointer;
}
.side-tab:hover { color: var(--text); background: var(--panel-2); }
.side-tab.active {
  color: var(--accent);
  background: var(--accent-soft);
  border-color: var(--border);
}
#channellist {
  display: flex;
  flex-direction: column;
  flex: 1;
  min-height: 0;
}
#channellist[hidden], #structure[hidden] { display: none; }
/* The filter and how it matches sit on one row: the mode is part of the
   query, not a separate control further down. */
#filterrow { display: flex; gap: 6px; margin: 10px 10px 0; }
#filter {
  flex: 1;
  min-width: 0;
  margin: 0;
  padding: 8px 10px;
  font: inherit;
  font-size: 13px;
  color: var(--text);
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 6px;
}
#filter:focus { outline: none; border-color: var(--accent); }
#computedbar { display: flex; gap: 4px; margin: 8px 10px 8px; }
#computedbar input {
  flex: 1;
  min-width: 0;
  padding: 5px 6px;
  font: inherit;
  font-size: 12px;
  color: var(--text);
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 6px;
}
#computedbar button {
  padding: 5px 8px;
  font: inherit;
  font-size: 12px;
  background: var(--panel-2);
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: 6px;
  cursor: pointer;
}
#search-mode {
  flex: none;
  margin: 0;
  padding: 5px 6px;
  font: inherit;
  font-size: 12px;
  color: var(--text);
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 6px;
}
#channels {
  list-style: none;
  margin: 0;
  padding: 0 6px 10px;
  overflow-y: auto;
  flex: 1;
  min-height: 0;
  text-align: left;
}
#channels li {
  padding: 7px 10px;
  font-size: 13px;
  border-radius: 6px;
  cursor: pointer;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  /* Virtualized rendering (plan 4.7): the browser skips layout/paint for
     off-screen rows, so a 20k-channel file scrolls like a 20-row one. The
     intrinsic size keeps the scrollbar stable while rows are skipped. */
  content-visibility: auto;
  contain-intrinsic-size: auto 31px;
}
#channels li:hover { background: var(--panel-2); }
#channels li.selected {
  background: var(--accent-soft);
  color: var(--accent);
}
#channels li.unplotable {
  color: var(--muted);
  cursor: not-allowed;
}
#channels li.unplotable:hover { background: transparent; }
/* A text channel announces itself: bands, not lines, once plotted. */
#channels li.istext::after {
  content: "Aa";
  margin-left: 6px;
  padding: 0 4px;
  font-size: 10px;
  border: 1px solid var(--muted);
  border-radius: 4px;
  color: var(--muted);
}
/* An array channel: plottable per element (stepper in the legend). */
#channels li.isarray::after {
  content: "[]";
  margin-left: 6px;
  padding: 0 4px;
  font-size: 10px;
  border: 1px solid var(--muted);
  border-radius: 4px;
  color: var(--muted);
}
#channels .empty { color: var(--muted); cursor: default; }
#channels .empty:hover { background: transparent; }

/* Structure tree (plan 4.8): the file's outline as one scrollable pane —
   sections collapse, channels toggle like the channel list's rows do. */
#structtoolbar {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
  margin: 10px;
}
/* The filter takes a row of its own: sharing one with the two buttons left
   it ~60 px wide at the default panel width, too narrow to show a name. */
#structtoolbar input {
  flex: 1 1 100%;
  min-width: 0;
  padding: 6px 8px;
  font: inherit;
  font-size: 12px;
  color: var(--text);
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 6px;
}
#structtoolbar input:focus { outline: none; border-color: var(--accent); }
#structtoolbar button {
  padding: 5px 8px;
  font: inherit;
  font-size: 12px;
  background: var(--panel-2);
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: 6px;
  cursor: pointer;
  white-space: nowrap;
}
#structtoolbar button:hover { border-color: var(--accent); }
#structtree {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  padding: 0 6px 12px;
  font-size: 13px;
  text-align: left;
}
#structtree .filehead {
  padding: 6px 8px 0;
  font-weight: 600;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
#structtree .filesub {
  padding: 0 8px 6px;
  color: var(--muted);
  font-size: 12px;
}
#structtree .row {
  display: flex;
  align-items: baseline;
  gap: 6px;
  padding: 5px 8px;
  border-radius: 6px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
#structtree .row.static { color: var(--text); }
#structtree .row.clickable { cursor: pointer; }
#structtree .row.clickable:hover { background: var(--panel-2); }
#structtree .row.muted { color: var(--muted); }
#structtree .caret {
  width: 12px;
  flex: none;
  color: var(--muted);
  font-size: 10px;
}
#structtree .sectionlabel {
  /* Shrinkable with ellipsis, so a long group name never pushes the Plot
     all button out of the row. */
  flex: 0 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
}
#structtree .row .fill { flex: 1; }
#structtree .row .plotall {
  flex: none;
  padding: 1px 7px;
  font: inherit;
  font-size: 11px;
  color: var(--accent);
  background: transparent;
  border: 1px solid var(--border);
  border-radius: 5px;
  cursor: pointer;
}
#structtree .row .plotall:hover { border-color: var(--accent); background: var(--accent-soft); }
/* Nesting: each level indents one step, mirroring the desktop tree. */
#structtree .children { margin-left: 14px; }
#structtree .chrow { cursor: pointer; }
#structtree .chrow:hover { background: var(--panel-2); }
#structtree .chrow.selected { background: var(--accent-soft); color: var(--accent); }
#structtree .chrow.unplotable { color: var(--muted); cursor: not-allowed; }
#structtree .chrow.unplotable:hover { background: transparent; }
#structtree .chrow .dot {
  flex: none;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  align-self: center;
}
#structtree .chrow input[type="checkbox"] {
  flex: none;
  margin: 0 2px;
  accent-color: var(--accent);
}
#structtree .master-tag, #structtree .kind-tag {
  margin-left: 6px;
  padding: 0 4px;
  font-size: 10px;
  border: 1px solid var(--muted);
  border-radius: 4px;
  color: var(--muted);
}
#structtree .unreadable-tag {
  margin-left: 6px;
  color: var(--error);
  font-size: 11px;
  cursor: help;
}
/* Same badges as the channel list: bands vs per-element lines. */
#structtree .chrow.istext .kind-tag::after { content: "Aa"; }
#structtree .chrow.isarray .kind-tag::after { content: "[]"; }

/* Sample table (plan 2.2): a virtualized list — the scroll body is a spacer
   sized to the full sample count, one page of rows is absolutely positioned
   at its start index. Grid columns keep # / t / value aligned. */
#tablepanel {
  border: 1px solid var(--panel-2);
  border-radius: 8px;
  margin: 8px 0 0;
  background: var(--panel);
  font-size: 12.5px;
}
#tablehead, .tablerow {
  display: grid;
  grid-template-columns: 90px 130px 1fr;
  gap: 12px;
  padding: 0 12px;
  line-height: 22px;
}
#tablehead {
  color: var(--muted);
  border-bottom: 1px solid var(--panel-2);
  font-weight: 600;
  white-space: nowrap;
  overflow: hidden;
}
#tablescroll {
  position: relative;
  max-height: 40vh;
  overflow-y: auto;
  overscroll-behavior: contain;
}
#tablebody { position: relative; }
.tablepage { position: absolute; left: 0; right: 0; }
.tablerow {
  height: 22px;
  white-space: nowrap;
  overflow: hidden;
  font-variant-numeric: tabular-nums;
}
.tablerow:nth-child(odd) { background: rgba(255, 255, 255, 0.02); }
#tablefoot {
  color: var(--muted);
  padding: 4px 12px 6px;
  border-top: 1px solid var(--panel-2);
}

/* X-Y panel (plan 4.1) */
#xypanel {
  border: 1px solid var(--panel-2);
  border-radius: 8px;
  margin: 8px 0 0;
  background: var(--panel);
  padding: 8px;
}
#xyhead {
  display: flex;
  gap: 16px;
  align-items: center;
  padding: 0 4px 6px;
  color: var(--muted);
  font-size: 12.5px;
}
#xyhead select {
  font: inherit;
  color: var(--text);
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 6px;
}
#xycanvas { width: 100%; display: block; }

/* GPS panel (plan 3.4): the track canvas plus a small read-only legend. */
#gpspanel {
  border: 1px solid var(--panel-2);
  border-radius: 8px;
  margin: 8px 0 0;
  background: var(--panel);
  padding: 8px;
}
#gpsplot { width: 100%; display: block; border-radius: 6px; }
#gpslegend {
  color: var(--muted);
  font-size: 11.5px;
  padding: 4px 4px 0;
}

/* Bus frame panel (plan 3.3): same virtualized grid as the sample table,
   wider for the payload hex. */
#buspanel {
  border: 1px solid var(--panel-2);
  border-radius: 8px;
  margin: 8px 0 0;
  background: var(--panel);
  font-size: 12.5px;
}
#bushead {
  display: flex;
  gap: 12px;
  align-items: center;
  padding: 6px 12px;
  border-bottom: 1px solid var(--panel-2);
  color: var(--muted);
}
#bushead .bushint { margin-left: auto; font-size: 11px; }
#busscroll {
  position: relative;
  max-height: 40vh;
  overflow-y: auto;
  overscroll-behavior: contain;
}
#busbody { position: relative; }
.buspage { position: absolute; left: 0; right: 0; }
.busrow {
  display: grid;
  grid-template-columns: 80px 90px 70px 50px 1fr 50px 40px;
  gap: 10px;
  height: 22px;
  line-height: 22px;
  white-space: nowrap;
  overflow: hidden;
  font-variant-numeric: tabular-nums;
  cursor: pointer;
}
.busrow:nth-child(odd) { background: rgba(255, 255, 255, 0.02); }
.busrow:hover { background: var(--panel-2); }
.busrow.hl { background: var(--accent-soft); }

/* Details panel (plan 2.5): label/value rows of the selected channel's
   metadata, straight from the Rust-built JSON. */
#detailspanel {
  border: 1px solid var(--panel-2);
  border-radius: 8px;
  margin: 8px 0 0;
  background: var(--panel);
  font-size: 12.5px;
  padding: 8px 12px 10px;
}
#detailshead { font-weight: 600; margin-bottom: 4px; }
.detailrow {
  display: grid;
  grid-template-columns: 120px 1fr;
  gap: 12px;
  line-height: 20px;
}
.detailrow > span:first-child { color: var(--muted); }

#chartarea {
  display: flex;
  flex-direction: column;
  /* The plot shrinks first (to #plotwrap's minimum) when the table or
     details panel opens; past that the column scrolls in place. */
  min-height: 0;
  overflow-y: auto;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 12px 14px;
  min-width: 0;
}
/* Only the plot gives up height when the column is full; the toolbar, stats
   strip and panels keep their natural size (several clip their overflow,
   which would otherwise let flex squash them under their neighbours). */
#chartarea > * { flex-shrink: 0; }
#chartarea > #plotwrap { flex-shrink: 1; }
#toolbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  flex-wrap: wrap;
  min-height: 34px;
  margin-bottom: 8px;
}
#legend {
  display: flex;
  align-items: center;
  gap: 6px;
  flex-wrap: wrap;
  min-width: 0;
}
#legend .hint { color: var(--muted); font-size: 13px; }
.leg {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  max-width: 300px;
  padding: 4px 8px;
  font: inherit;
  font-size: 12.5px;
  color: var(--text);
  background: var(--panel-2);
  border: 1px solid var(--border);
  border-radius: 6px;
  cursor: pointer;
}
.leg:hover { border-color: var(--accent); }
.leg.sel { background: var(--accent-soft); border-color: var(--accent); }
.leg .dot, #readout .dot {
  width: 9px;
  height: 9px;
  border-radius: 50%;
  flex: none;
}
.leg-name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.leg-unit { color: var(--muted); flex: none; }
.leg-range {
  color: var(--muted);
  font-size: 11.5px;
  flex: none;
}
.leg .rm {
  color: var(--muted);
  padding: 0 2px;
  flex: none;
}
.leg .rm:hover { color: var(--error); }
#tools {
  display: flex;
  align-items: center;
  gap: 8px;
  flex: 1 1 auto;
  min-width: 0;
  flex-wrap: wrap;
}
/* One voice for every toolbar button — before this rule the unstyled ones
   (Attach DBC, Compare file…) fell back to the browser's light default and
   split the row into two mismatched halves. */
#tools > button {
  padding: 6px 12px;
  font: inherit;
  font-size: 13px;
  color: var(--text);
  background: var(--panel-2);
  border: 1px solid var(--border);
  border-radius: 6px;
  cursor: pointer;
}
#tools > button:hover:not(:disabled) { border-color: var(--accent); }
#tools > button:disabled { opacity: 0.45; cursor: default; }
#tools > button[aria-pressed="true"] {
  background: var(--accent-soft);
  color: var(--accent);
  border-color: var(--accent);
}
.toolsep {
  width: 1px;
  height: 20px;
  background: var(--border);
  flex: none;
}
#csv-btn { max-width: 320px; }
#shared-y-label {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  font-size: 12.5px;
  color: var(--muted);
  cursor: pointer;
  white-space: nowrap;
}
#shared-y-label.off { opacity: 0.5; cursor: default; }
#mode-toggle, #time-toggle {
  display: inline-flex;
  border: 1px solid var(--border);
  border-radius: 6px;
  overflow: hidden;
}
#mode-toggle .mode, #time-toggle .mode {
  padding: 5px 10px;
  font: inherit;
  font-size: 12.5px;
  color: var(--muted);
  background: var(--panel-2);
  border: none;
  cursor: pointer;
}
#mode-toggle .mode + .mode, #time-toggle .mode + .mode {
  border-left: 1px solid var(--border);
}
#mode-toggle .mode.active, #time-toggle .mode.active {
  background: var(--accent-soft);
  color: var(--accent);
}
#time-toggle .mode:disabled { opacity: 0.5; cursor: default; }
/* The generic #tools > button rule carries the button chrome; the CSV button
   only adds its own overflow behaviour. */
#csv-btn {
  max-width: 320px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

#statsbar {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 4px 16px;
  margin: 0 0 8px;
  padding: 6px 10px;
  background: var(--panel-2);
  border: 1px solid var(--border);
  border-radius: 6px;
  font-size: 12px;
  color: var(--muted);
}
/* display:flex above would override the hidden attribute's display:none */
#statsbar[hidden] { display: none; }
#statsbar .st-head { color: var(--text); }
#statsbar .st-ch {
  display: inline-flex;
  align-items: baseline;
  gap: 6px;
  white-space: nowrap;
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
}
#statsbar .st-name {
  color: var(--text);
  overflow: hidden;
  text-overflow: ellipsis;
}
#statsbar .dot {
  width: 9px;
  height: 9px;
  border-radius: 50%;
  flex: none;
  align-self: center;
}

#plotwrap { flex: 1; min-height: 320px; position: relative; }
#plot { width: 100%; height: 100%; display: block; cursor: crosshair; touch-action: none; }
/* tabindex=0 makes the canvas the keyboard-navigation target (main.js);
   the faint inset ring marks where the navigation keys now go. */
#plot:focus {
  outline: 1px solid var(--accent);
  outline-offset: -1px;
}
#readout {
  position: absolute;
  top: 8px;
  left: 64px;
  max-width: calc(100% - 84px);
  padding: 8px 12px;
  background: rgba(15, 17, 21, 0.92);
  border: 1px solid var(--border);
  border-radius: 8px;
  font-size: 12px;
  line-height: 1.6;
  pointer-events: none;
}
#readout .ro-t { color: var(--muted); margin-bottom: 2px; }
/* Wall-clock companions to the seconds line (absolute mode only) */
#readout .ro-abs { color: var(--muted); font-size: 11px; }
#readout .ro-row { display: flex; align-items: baseline; gap: 6px; }
#readout .ro-name {
  color: var(--muted);
  max-width: 220px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
#readout b { font-weight: 600; }
#readout .ro-unit { color: var(--muted); }
#plotmsg { margin: 8px 0 0; font-size: 13px; }
#hint {
  margin-top: 10px;
  font-size: 12px;
  color: var(--muted);
  opacity: 0.85;
}

footer {
  padding: 14px 24px 20px;
  color: var(--muted);
  font-size: 12.5px;
}
footer a { color: var(--muted); }
footer a:hover { color: var(--accent); }

@media (max-width: 820px) {
  #layout { grid-template-columns: 1fr; grid-template-rows: none; height: auto; gap: 14px; }
  #side-resizer { display: none; }
  /* The tabs, filter and computed rows take ~130 px before the first
     channel; the list needs room for several rows beneath them. */
  aside { height: min(360px, 50dvh); }
}

/* Everything that names or measures a signal uses the data face: the file
   line, channel and structure lists, legend, readout, stats and table.
   Prose (titles, hints, buttons) stays sans — the contrast is the point. */
#fileinfo,
#channels li,
#structtree,
#legend,
#readout,
#statsbar,
.tablerow,
#tablehead,
#bushead,
#busbody {
  font-family: var(--mono);
  font-size: 12.5px;
}
#fileinfo b { font-size: 13px; }

/* Keyboard reachability is part of the floor, not an extra: a visible ring
   wherever focus lands, in the same accent the traces use. */
:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

@media (prefers-reduced-motion: reduce) {
  * {
    transition: none !important;
    animation: none !important;
  }
  #dropzone.dragover { transform: none; }
}
