/* ===========================================================================
   sim.css  -  WingPC full-system simulator.

   Dark by design: the thing under test is a night-legible instrument panel,
   and a white page next to it wrecks your eyes' reference for the backlight
   preview. Layout is three columns -- bus on the left, dash in the middle,
   head-unit-local inputs on the right -- collapsing to one on narrow screens.
   =========================================================================== */

:root {
  color-scheme: dark;
  --bg: #0b0d10;
  --card: #14171c;
  --card-hi: #1b1f26;
  --line: #262b33;
  --fg: #dfe3ea;
  --fg-dim: #8b93a1;
  --accent: #38bdf8;
  --ok: #4ade80;
  --warn: #fbbf24;
  --bad: #f87171;
  --mono: 'Cascadia Mono', 'DejaVu Sans Mono', Consolas, monospace;

  /* One colour per CAN source, defined ONCE. Used by the frame monitor and by
     the left edge of the matching node card, so a card and its frames cannot
     drift apart -- which they would if the hex sat in two rules. */
  --id-ecu:     #f0abfc;
  --id-chassis: #7dd3fc;
  --id-env:     #86efac;
  --id-road:    #fcd34d;
}

* { box-sizing: border-box; }

html, body {
  margin: 0;
  background: var(--bg);
  color: var(--fg);
  font: 13px/1.45 -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;
}

/* ---- header ---- */
header {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px 18px;
  padding: 10px 16px;
  background: #101318;
  border-bottom: 1px solid var(--line);
  position: sticky;
  top: 0;
  z-index: 10;
}
.brand strong { color: var(--accent); letter-spacing: .02em; }
.brand span { color: var(--fg-dim); margin-left: 8px; }
.hdr-controls { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; }
.hdr-stats {
  margin-left: auto;
  font-family: var(--mono);
  font-size: 11.5px;
  color: var(--fg-dim);
  display: flex;
  gap: 14px;
  flex-wrap: wrap;
}
.hdr-stats b { color: var(--fg); font-weight: 600; }

button {
  background: var(--card-hi);
  color: var(--fg);
  border: 1px solid var(--line);
  border-radius: 5px;
  padding: 5px 12px;
  font: inherit;
  cursor: pointer;
}
button:hover { border-color: #3a424e; }
button.primary { border-color: var(--accent); color: var(--accent); }
button.small { padding: 2px 8px; font-size: 11.5px; }

label.inline { display: flex; align-items: center; gap: 6px; color: var(--fg-dim); }
label.inline.chk { cursor: pointer; }
select, input[type=text], input[type=number] {
  background: var(--card-hi);
  color: var(--fg);
  border: 1px solid var(--line);
  border-radius: 4px;
  padding: 3px 6px;
  font: inherit;
}

/* ---- layout ---- */
main {
  display: grid;
  grid-template-columns: minmax(280px, 1fr) minmax(500px, 1.15fr) minmax(280px, 1fr);
  gap: 12px;
  padding: 12px;
  align-items: start;
}
@media (max-width: 1180px) { main { grid-template-columns: 1fr; } }

.col { display: flex; flex-direction: column; gap: 12px; min-width: 0; }

.card {
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: 8px;
  padding: 10px 12px 12px;
}
.card h2 {
  margin: 0 0 8px;
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: .06em;
  color: var(--fg-dim);
  display: flex;
  align-items: baseline;
  gap: 8px;
}
.card h2 .sub { text-transform: none; letter-spacing: 0; font-size: 11px; opacity: .7; }

/* A collapsible card. The summary is styled as the h2 it replaces, so a
   collapsible card and a plain one read the same when open. */
details.card > summary {
  margin: 0;
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: .06em;
  color: var(--fg-dim);
  cursor: pointer;
  list-style: none;              /* the default marker, replaced below */
  display: flex;
  align-items: baseline;
  gap: 8px;
  user-select: none;
}
details.card > summary::-webkit-details-marker { display: none; }
details.card > summary .sub { text-transform: none; letter-spacing: 0; font-size: 11px; opacity: .7; }
/* Own marker, on the right, so the title still starts where every other card
   title does rather than being indented by a triangle. */
details.card > summary::after {
  content: '+';
  margin-left: auto;
  font-family: var(--mono);
  color: var(--fg-dim);
}
details.card[open] > summary::after { content: '\2212'; }   /* minus */
details.card[open] > summary { margin-bottom: 8px; }
details.card > summary:hover { color: var(--fg); }
.card h2 .badge {
  margin-left: auto;
  font-family: var(--mono);
  font-size: 10.5px;
  padding: 1px 6px;
  border-radius: 999px;
  border: 1px solid var(--line);
  text-transform: none;
  letter-spacing: 0;
}
.badge.live { color: var(--ok); border-color: #1f4d33; background: #0f2418; }
.badge.dead { color: var(--fg-dim); }
/* Powered, but nothing is arriving: 100% loss, or a node with no sensor.
   Neither green nor grey would be honest. */
.badge.mute { color: var(--warn); border-color: #4d3f1f; background: #241d0f; }

/* A node card is bracketed left and right in its bus colour, so the card and
   its frames in the monitor read as the same source at a glance. Side borders
   only, which keeps the rounded corners intact.

   Each class only supplies --id; the borders are declared once. Adding a node
   is then one line here rather than one per edge. */
#nodes .card {
  border-left:  3px solid var(--id, var(--line));
  border-right: 3px solid var(--id, var(--line));
}
#nodes .card.node-ecu     { --id: var(--id-ecu); }
#nodes .card.node-chassis { --id: var(--id-chassis); }
#nodes .card.node-env     { --id: var(--id-env); }
#nodes .card.node-road    { --id: var(--id-road); }

/* ---- node config row ---- */
.nodecfg {
  display: flex;
  flex-wrap: wrap;
  gap: 6px 12px;
  padding-bottom: 8px;
  margin-bottom: 6px;
  border-bottom: 1px dashed var(--line);
  color: var(--fg-dim);
  font-size: 12px;
}
.nodecfg label { display: flex; align-items: center; gap: 5px; }
.nodecfg input[type=number] { width: 62px; }

/* ---- signal rows ---- */
.sig {
  display: grid;
  grid-template-columns: 104px 62px 1fr 68px;
  align-items: center;
  gap: 6px;
  min-height: 24px;
}
.sig > .name { color: var(--fg-dim); font-size: 12px; }
.sig select.mode { font-size: 11px; padding: 2px 4px; }
.sig .val {
  font-family: var(--mono);
  font-size: 11.5px;
  text-align: right;
  color: var(--fg);
}
.sig input[type=range] { width: 100%; }
.sig.auto .val { color: var(--accent); }
.sig .ctl { display: flex; align-items: center; gap: 6px; min-width: 0; }
.sig .ctl input[type=text] { width: 100%; }
.sig .ctl select { width: 100%; }
.sig .period { width: 52px; }

/* ---- panel ---- */
.panelcard { display: flex; flex-direction: column; align-items: stretch; }
.panelwrap { display: flex; justify-content: center; padding: 6px 0 10px; }
#panel {
  border-radius: 50%;
  background: #000;
  box-shadow: 0 0 0 6px #191d23, 0 0 0 7px #2b323b, 0 10px 30px rgba(0, 0, 0, .6);
}

/* ---- ignition key switch ----
   Sits at the right-hand end of the panel's control row -- the top right of the
   LCD box, where the barrel is on the bike. Four detents in a row rather than a
   dropdown, because a key switch is a thing with POSITIONS: you want to see at a
   glance which one it is in and how far it is from the one that powers the dash.

   Each detent carries TWO stripes, one per circuit, so both extents are visible
   in the widget itself and not only in the lamps beside it: a left border in
   --ok for the ACC circuit (ACC and ON) and an inset bar along the bottom in
   --accent for the ignition circuit (ON alone). The one detent with the top
   stripe and not the bottom one is ACC -- a live dash over a dead bike. */
.keysw { display: flex; align-items: center; gap: 6px; margin-left: auto; }
.keysw .kslabel {
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: .08em;
  color: var(--fg-dim);
}
.ksdetents {
  display: flex;
  background: #10141a;
  border: 1px solid #39424f;
  border-radius: 5px;
  overflow: hidden;
}
.ksdet {
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: .04em;
  color: #9fb0c4;
  background: linear-gradient(#2a313b, #1d232b);
  border: 0;
  border-left: 2px solid transparent;
  padding: 4px 7px;
  cursor: pointer;
  user-select: none;
}
.ksdet + .ksdet { box-shadow: inset 1px 0 0 #39424f; }
.ksdet.hot { border-left-color: var(--ok); }
/* The ignition circuit, along the bottom edge. A separate axis from the left
   border on purpose: two circuits should not compete for one piece of the
   control, or the reader has to remember which shade meant which. */
.ksdet.ig { border-bottom: 2px solid var(--accent); padding-bottom: 2px; }
.ksdet:hover { background: linear-gradient(#333b47, #242b34); color: var(--fg); }
.ksdet:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
/* The barrel is HERE. Amber rather than the accent blue: this is the bike's
   state, not a bench control that happens to be selected. */
.ksdet.on {
  background: linear-gradient(#7a5b10, #5c440b);
  color: #ffe9b0;
  box-shadow: inset 0 0 0 1px var(--warn);
}
/* The circuits themselves -- ACC, which is what PIN_IGN_SENSE actually reads,
   and IG, which is what powers the motorcycle. One class for both, because the
   only thing that distinguishes them is which detents light them, and that
   belongs in signals.js rather than in a stylesheet. Both dark at LOCK and
   OFF; ACC alone at ACC; both at ON. The widget's whole point is that these
   are three different questions. */
.kslamp {
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: .04em;
  padding: 3px 5px;
  border-radius: 4px;
  border: 1px solid #39424f;
  color: var(--fg-dim);
  background: #10141a;
}
.kslamp.live {
  color: #0b0d10;
  background: var(--ok);
  border-color: var(--ok);
}

/* The pod is the one thing on this page you actually operate, so it should
   read as hardware rather than as another form control: raised, high enough
   contrast to find without hunting, and with a press you can see land. */
.pod { display: flex; gap: 12px; justify-content: center; padding: 14px 0 6px; }
.pod .btn {
  background: linear-gradient(#333a45, #232932);
  border: 1px solid #454e5c;
  border-top-color: #5a6575;
  border-radius: 8px;
  box-shadow: 0 2px 0 #11141a, 0 3px 6px rgba(0, 0, 0, 0.45);
  padding: 12px 18px 10px;
  font-family: var(--mono);
  font-size: 15px;
  font-weight: 600;
  letter-spacing: 0.5px;
  color: #f2f5fa;
  cursor: pointer;
  user-select: none;
  min-width: 96px;
  text-align: center;
  transition: background 60ms linear, box-shadow 60ms linear, transform 60ms linear;
}
.pod .btn:hover { border-color: #6b7789; background: linear-gradient(#3b434f, #272e38); }
.pod .btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* Pressed: the button physically sinks onto the board and lights up. Driven
   by the .down class, so the keyboard shortcuts show the same feedback the
   mouse does -- they go through the same line state. */
.pod .btn.down {
  background: linear-gradient(#0d5b78, #0a4560);
  border-color: var(--accent);
  color: #eafaff;
  box-shadow: 0 0 0 1px var(--accent), 0 0 14px rgba(56, 189, 248, 0.45);
  transform: translateY(2px);
}
.pod .btn.faulted {
  border-color: var(--bad);
  color: var(--bad);
  box-shadow: 0 2px 0 #11141a, 0 0 12px rgba(248, 113, 113, 0.35);
}
/* The key that works this button, on the button. */
.pod .btn .key {
  display: block;
  font-size: 10px;
  font-weight: 400;
  letter-spacing: 0;
  color: #9fb0c4;
  margin-top: 3px;
}
.pod .btn.down .key { color: #bfe9ff; }
.pod .stuck { display: block; font-size: 10px; color: var(--fg-dim); margin-top: 6px; }

.hint { color: var(--fg-dim); font-size: 11.5px; margin: 6px 0 0; text-align: center; }

/* ---- handlebar bindings overview ---- */
.bindings { display: flex; flex-direction: column; gap: 2px; font-family: var(--mono); }
.bindrow {
  display: grid;
  grid-template-columns: 52px 26px 40px 1fr;
  align-items: baseline;
  gap: 6px;
  padding: 3px 6px;
  border-radius: 4px;
  border: 1px solid transparent;
  font-size: 11.5px;
  transition: background 60ms linear, border-color 60ms linear;
}
.bindrow .bname  { color: var(--fg); font-weight: 600; }
.bindrow .bkey   { color: var(--accent); text-align: center; }
.bindrow .binput { color: var(--fg-dim); }
.bindrow .bwhat  { color: var(--fg); }
/* Nothing bound on this page is a real answer, not an empty row. */
.bindrow.unbound .bwhat { color: var(--fg-dim); font-style: italic; }
/* Lit while the input is actually down -- the card doubles as a live legend. */
.bindrow.active {
  background: #0a4560;
  border-color: var(--accent);
}
.bindrow.active .bname,
.bindrow.active .bwhat { color: #eafaff; }
/* While the fill-up modal is up it owns every input, so the card says so. */
.bindings.modal { outline: 1px solid #4d3f1f; outline-offset: 4px; border-radius: 4px; }
/* The commit is the only destructive action in the UI -- it gets to shout. */
.bindrow.hot { background: #241d0f; border-color: var(--warn); }
.bindrow.hot .bname, .bindrow.hot .bwhat { color: var(--warn); font-weight: 600; }
.bindrow.faulted { border-color: var(--bad); }
.bindrow.faulted .bname { color: var(--bad); }
.bindnote { text-align: left; margin-top: 8px; }
kbd {
  font-family: var(--mono);
  background: var(--card-hi);
  border: 1px solid var(--line);
  border-radius: 3px;
  padding: 0 4px;
}

.notes {
  margin-top: 8px;
  font-family: var(--mono);
  font-size: 11px;
  color: var(--fg-dim);
  min-height: 16px;
}

/* ---- stats blocks ---- */
.stats {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(128px, 1fr));
  gap: 2px 12px;
  font-family: var(--mono);
  font-size: 11.5px;
}
.stats div { display: flex; justify-content: space-between; gap: 8px; }
.stats .k { color: var(--fg-dim); }
.stats .v { color: var(--fg); }
.stats .v.ok { color: var(--ok); }
.stats .v.warn { color: var(--warn); }
.stats .v.bad { color: var(--bad); }
.stats .v.dim { color: var(--fg-dim); }
/* The parenthetical after a column name (f0-f3). A shade smaller than the key so
   it reads as an aside rather than part of the name.

   margin-right:auto is what makes the layout work: the row is flex with
   space-between, so with three children the free space would be split BETWEEN
   them and the label would float in the middle. Pushing all the slack to the
   label's right groups it against the key and leaves the value hard right, where
   every other row's value already sits. Rows with no label still have two
   children and are unaffected.

   SAME font-size and font-family as the key -- do not "improve" this by making it
   smaller. Two font sizes in one flex row means two sets of text metrics, and the
   row then has to reconcile them; every attempt to do that (align-items:baseline,
   overflow clipping) moved the f0-f3 rows off the grid's own baseline instead of
   onto it. Matching the key's metrics removes the problem rather than correcting
   for it: identical font, identical line box, identical baseline, no alignment
   property needed anywhere.

   Subtlety comes from opacity, which is a paint-time effect and touches no
   metric. Nothing here may set font-size, line-height, overflow, display or
   vertical-align.

   margin-top has to be zeroed for the same reason: the page-wide .hint is a
   standalone caption paragraph and carries margin:6px 0 0, which inside this
   flex row pushed the parenthetical 6px below the key and the value. */
.stats .hint {
  color: var(--fg-dim);
  opacity: 0.8;
  margin: 0 auto 0 0;
  white-space: nowrap;
}
/* Currently UNUSED and kept on purpose. It marked columns the simulator modelled
   before logger.cpp wrote them -- the box claims to be the uploaded row, so a
   field the firmware could not produce had to look different from one it could.
   All 32 ship now and selftest.js asserts nothing carries the class; the rule
   stays so re-marking a column is one class name rather than a style change too. */
.stats .v.soon, .stats .grp .soon { color: var(--accent); }

/* Section rule inside the telemetry inspector. The grid above is auto-fit, so
   a heading has to be told to span every column or it lands inside one of
   them and reads as a key with no value. */
.stats .grp {
  grid-column: 1 / -1;
  display: block;
  margin-top: 7px;
  padding-top: 5px;
  border-top: 1px solid var(--line);
  color: var(--fg-dim);
  font-size: 10.5px;
  letter-spacing: 0.03em;
}
.stats .grp:first-child { margin-top: 0; padding-top: 0; border-top: 0; }

/* One column. The auto-fit grid above is right for the short key/value pairs
   in the telemetry inspector, but the per-id bus rows carry a long value and
   must not be split across columns. */
#busstats { grid-template-columns: 1fr; gap: 3px; }
#busstats .v { white-space: nowrap; }

.rowcontrols { display: flex; gap: 10px; align-items: center; margin-bottom: 6px; }

pre.mono {
  font-family: var(--mono);
  font-size: 11px;
  line-height: 1.45;
  margin: 0;
  color: var(--fg-dim);
  /* Sim.MONITOR_LINES rows at 11px * 1.45 line-height, rounded up. Change
     both together or one of them wins silently. */
  max-height: 210px;
  overflow: hidden;
  white-space: pre;
}
pre.mono .id-ecu { color: var(--id-ecu); }
pre.mono .id-chassis { color: var(--id-chassis); }
pre.mono .id-env { color: var(--id-env); }
pre.mono .id-road { color: var(--id-road); }
pre.mono .drop { color: var(--bad); }
