/* mobot-rcs operator console.
 *
 * The vocabulary is deliberately the one `mobot-sandbox`'s admin console uses,
 * because an operator moves between them: a left icon rail, a page head of
 * kicker + display title + one line of subtitle, summary strips, filter pills,
 * cards built from uppercase micro-labels over their values, and outlined
 * action rows. Warm off-white ground, white cards, one dark-green accent.
 *
 * Layout rules that keep it usable rather than merely tidy:
 *  - a card is NOT a link. Buttons live inside cards, and a card-wide anchor
 *    swallows every one of their clicks (it did; see `renderCard`).
 *  - facts are a GRID, never a run-on line. `.facts` gives each value its own
 *    column so the eye can scan down one label.
 *  - the detail page is two columns: what the robot is doing on the left,
 *    what you can do about it pinned on the right.
 */

:root {
  --bg: #f4f2ee;
  --panel: #ffffff;
  --panel-sunk: #faf9f7;
  --text: #1b1f1d;
  --muted: #6f7671;
  --border: #e3e0da;
  --border-strong: #d3cfc7;

  --accent: #1f5140;
  --accent-soft: #e8f0ec;
  --ok: #2f6f4f;
  --ok-bg: #e8f2ec;
  --warn: #8a5a12;
  --warn-bg: #fbf1de;
  --bad: #9c3328;
  --bad-bg: #fbeae7;
  --offline: #6f7671;
  --offline-bg: #eceae6;

  --radius: 14px;
  --radius-sm: 9px;
  --rail: 56px;
  --display: ui-serif, Georgia, "Times New Roman", serif;
  --mono: ui-monospace, SFMono-Regular, Menlo, monospace;
}

* { box-sizing: border-box; }

/* Every view below sets an explicit `display`, and a bare `display: flex` on a
   rule beats the `hidden` ATTRIBUTE -- which is how the sign-in card ended up
   painted on top of a fully rendered fleet page. `hidden` is how this app
   switches views, so it wins here, once, for everything. */
[hidden] { display: none !important; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font: 14px/1.45 -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  -webkit-font-smoothing: antialiased;
}

h1, h2, h3 { margin: 0; }
button { font: inherit; }

/* -- sign-in ----------------------------------------------------------------- */

#login-view { display: flex; align-items: center; justify-content: center; min-height: 100vh; padding: 24px; }
#login-card {
  width: 100%; max-width: 380px; background: var(--panel);
  border: 1px solid var(--border); border-radius: var(--radius); padding: 30px 28px 26px;
}
.brand { font-family: var(--display); font-size: 22px; letter-spacing: -.01em; }
.brand span { color: var(--muted); }
.kicker {
  margin: 14px 0 6px; font-size: 10px; font-weight: 700; letter-spacing: .14em;
  text-transform: uppercase; color: var(--muted);
}
#login-card h1 { font-family: var(--display); font-size: 26px; font-weight: 500; margin-bottom: 18px; }
#login-card label { display: block; font-size: 12px; color: var(--muted); margin: 14px 0 5px; }
#login-card input {
  width: 100%; padding: 9px 11px; border: 1px solid var(--border-strong);
  border-radius: var(--radius-sm); background: var(--panel-sunk); font-size: 14px;
}
#login-card input:focus { outline: 2px solid var(--accent); outline-offset: -1px; background: var(--panel); }
#login-card button[type="submit"] {
  width: 100%; margin-top: 22px; padding: 11px; border: none; border-radius: var(--radius-sm);
  background: var(--accent); color: #fff; font-weight: 600; cursor: pointer;
}
#login-card button[type="submit"]:hover { opacity: .92; }
#login-error { color: var(--bad); font-size: 13px; margin-top: 12px; min-height: 16px; }

/* -- shell ------------------------------------------------------------------- */

#app-view { display: flex; min-height: 100vh; }
#app-view[hidden] { display: none; }

#rail {
  position: fixed; top: 0; bottom: 0; left: 0; width: var(--rail);
  display: flex; flex-direction: column; align-items: center; gap: 8px;
  padding: 14px 0; background: var(--accent); color: #fff; z-index: 5;
}
/* `.rail-brand` -- an `m` in a disc at the top of the rail -- is gone. It was
   `aria-hidden`, linked nowhere and did nothing: a logo mark on a tool with
   one deployment per host, where nobody is wondering whose console this is.
   The rail now starts with the first thing you can press. */
.rail-nav { display: flex; flex-direction: column; gap: 4px; flex: 1; margin-top: 2px; }
.rail-link {
  position: relative; width: 44px; height: 44px; border-radius: 11px;
  display: grid; place-items: center; color: rgba(255,255,255,.85);
  text-decoration: none;
}
/* SVG, NOT CHARACTERS. The rail used ▦ ◷ ⚙ -- U+25A6, U+25F7 and U+2699,
   three different Unicode blocks with three different design metrics. At one
   `font-size` they rendered at three different optical sizes, and the gear was
   half again the height of the grid; nothing in CSS fixes that, because the
   size difference is inside the font.

   Drawn on one 24 viewBox at one stroke width, they are the same size by
   construction. 18px inside the same 44px target the character glyphs had;
   the button size has not moved through any of this. */
.rail-icon {
  width: 18px; height: 18px; display: block;
  fill: none; stroke: currentColor; stroke-width: 1.7;
  stroke-linecap: round; stroke-linejoin: round;
}
.rail-link:hover { background: rgba(255,255,255,.12); color: #fff; }
.rail-link.is-active { background: rgba(255,255,255,.2); color: #fff; }
.rail-label {
  position: absolute; left: 50px; white-space: nowrap; background: var(--text); color: #fff;
  font-size: 11px; padding: 4px 8px; border-radius: 6px; opacity: 0; pointer-events: none;
  transition: opacity .12s;
}
.rail-link:hover .rail-label, .rail-link:focus-visible .rail-label { opacity: 1; }
.rail-foot { display: flex; flex-direction: column; align-items: center; gap: 10px; }
#conn-badge { font-size: 15px; line-height: 1; color: rgba(255,255,255,.35); }
#conn-badge.live { color: #8fd6a9; }
#logout {
  width: 32px; height: 32px; border-radius: 50%; border: 1px solid rgba(255,255,255,.3);
  background: none; color: #fff; font-size: 10px; font-weight: 700; cursor: pointer;
  text-transform: uppercase; letter-spacing: .04em;
}
#logout:hover { background: rgba(255,255,255,.14); }

#main { flex: 1; margin-left: var(--rail); padding: 18px 20px 40px; max-width: 1500px; }

/* -- page head --------------------------------------------------------------- */

.page-head { margin-bottom: 12px; }
.page-head h1 { font-family: var(--display); font-size: 27px; font-weight: 500; letter-spacing: -.015em; }
.page-sub { margin: 4px 0 0; color: var(--muted); font-size: 13px; }
.back-link { display: inline-block; margin-bottom: 10px; font-size: 12px; color: var(--muted); text-decoration: none; }
.back-link:hover { color: var(--accent); }
#detail-heading { display: flex; align-items: flex-start; gap: 12px; flex-wrap: wrap; width: 100%; }
#detail-heading .robot-id { font-family: var(--display); font-size: 30px; font-weight: 500; }
.detail-title-block { display: flex; align-items: flex-start; gap: 14px; }
.robot-identities { display: inline-flex; align-items: center; flex-wrap: wrap; gap: 4px 10px; }
.robot-identity { color: var(--muted); font: 11px/1.2 var(--mono); white-space: nowrap; }
.detail-identities {
  display: flex; flex-direction: column; align-items: flex-start;
  gap: 3px; padding-top: 2px;
}
.heading-availability {
  display: flex; flex-direction: column; align-items: flex-end;
  gap: 5px; margin-left: auto;
}
.heading-availability-row { display: flex; align-items: center; gap: 8px; }
.heading-availability-row .switch-field {
  margin: 0 0 0 5px; padding: 0 0 0 13px;
  border-top: 0; border-left: 1px solid var(--border);
}
.offerability-reason {
  max-width: 54ch; color: var(--muted); font-size: 11px; line-height: 1.35;
  text-align: right;
}

/* -- summary strip and filter pills ------------------------------------------ */

/* `.summary-strip` is gone from both pages that had one. It printed the same
   four counts the filter pills print, immediately above them, and only the
   pills were clickable -- so the strip was a read-only echo of the row under
   it. Deleted rather than restyled: no amount of styling makes a duplicate
   worth its vertical space. */

.filter-pills {
  display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 12px;
  justify-content: center;
}
.filter-pill {
  border: 1px solid var(--border-strong); background: var(--panel); color: var(--muted);
  border-radius: 999px; padding: 5px 13px; font-size: 12px; cursor: pointer;
}
.filter-pill:hover { border-color: var(--accent); color: var(--accent); }
.filter-pill.is-active { background: var(--accent); border-color: var(--accent); color: #fff; }
.filter-pills:empty { display: none; }

/* -- cards ------------------------------------------------------------------- */

.card-grid {
  display: grid; gap: 14px;
  grid-template-columns: repeat(auto-fill, minmax(330px, 1fr));
}
.card {
  background: var(--panel); border: 1px solid var(--border);
  border-radius: var(--radius); padding: 16px 17px 14px;
  display: flex; flex-direction: column;
}
.card.is-offline { background: var(--panel-sunk); }
.card-head { display: flex; align-items: center; gap: 8px; margin-bottom: 2px; }
.card-title {
  font-family: var(--display); font-size: 19px; font-weight: 500; color: var(--text);
  text-decoration: none; margin-right: auto;
}
.card-title:hover { color: var(--accent); text-decoration: underline; }
.card-identities { margin: 2px 0 10px; }

/* -- status chips ------------------------------------------------------------ */

.pill {
  font-size: 10px; font-weight: 700; letter-spacing: .05em; text-transform: uppercase;
  padding: 3px 9px; border-radius: 999px; white-space: nowrap;
}
.pill.online { background: var(--ok-bg); color: var(--ok); }
.pill.offline { background: var(--offline-bg); color: var(--offline); }
.pill.telemetry-warn { background: var(--bad-bg); color: var(--bad); }
.pill.available { background: var(--accent-soft); color: var(--accent); }
.pill.unavailable { background: var(--warn-bg); color: var(--warn); }
.pill.outcome-good { background: var(--ok-bg); color: var(--ok); }
.pill.outcome-bad { background: var(--bad-bg); color: var(--bad); }
.pill.outcome-live { background: var(--accent-soft); color: var(--accent); }

.dot { font-size: 9px; line-height: 1; }

/* -- fact grid --------------------------------------------------------------- */
/* The single most important rule in this file. Facts used to render as one
   run-on line of `label: value  label: value  …`, which is why the page read
   as a dump. Each fact is a cell: micro-label above, value below, columns
   aligned down the card. */

.facts {
  display: grid; gap: 11px 16px; margin: 13px 0 0;
  grid-template-columns: repeat(auto-fit, minmax(88px, 1fr));
}
.facts.cols-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.fact { min-width: 0; }
.fact-label {
  font-size: 9px; font-weight: 700; letter-spacing: .09em; text-transform: uppercase;
  color: var(--muted); display: block; margin-bottom: 2px;
}
/* `display: block` is not cosmetic: `overflow`/`text-overflow` have NO effect
   on an inline box, so a long task number printed straight over the next
   column instead of ellipsing. */
.fact-value {
  display: block; font-size: 13px; color: var(--text);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* A task number is ~20 monospace characters, and a protocol-2 step is a plan
   task id of similar length. Neither fits a 1fr column in a three-across
   grid, and `auto-fit` above adds COLUMNS as the card widens instead of
   growing them, so a wider screen does not help. They get two. */
.fact.wide { grid-column: span 2; }
.fact-value.mono { font-family: var(--mono); font-size: 12px; }
.fact-value.dim { color: var(--muted); }

/* -- blockers ---------------------------------------------------------------- */

.blockers {
  margin-top: 13px; padding: 9px 12px; border-radius: var(--radius-sm);
  background: var(--warn-bg); font-size: 12px; color: var(--warn);
}
.blockers.fault { background: var(--bad-bg); color: var(--bad); }
.blockers-title {
  font-size: 9px; font-weight: 700; letter-spacing: .09em; text-transform: uppercase;
  display: block; margin-bottom: 3px; opacity: .85;
}
.blocker-list { margin: 0; padding-left: 16px; }
.blocker-list li { margin: 1px 0; }
.blocker-list li.is-cleared { opacity: .45; text-decoration: line-through; }

.fault-detail { margin-top: 10px; font-size: 12px; color: var(--bad); }

/* -- actions ----------------------------------------------------------------- */

.card-actions { margin-top: auto; padding-top: 13px; }
.actions { display: flex; flex-direction: column; gap: 14px; margin-top: 12px; }
.order-actions { align-items: flex-start; }
.order-actions .plan-confirm { align-self: stretch; }
.action-row { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; }
.action-row-label {
  font-size: 9px; font-weight: 700; letter-spacing: .09em; text-transform: uppercase;
  color: var(--muted); width: 100%; margin-bottom: 3px;
}

/* Raw actuation is fenced off, and the fence is a SAFETY property rather than
   decoration (AGENTS.md §3): `open cabin` sends `EXEC_ACTION OPEN_LID`, no
   plan gate completes, no milestone fires, JD hears nothing and the task stays
   open forever. An operator reaching for it when they meant `Remote-collect`
   leaves the delivery permanently unfinished, so it must not look like a
   sibling of the control that completes one. */
.action-row.raw {
  border: 1px dashed var(--warn); background: var(--warn-bg);
  border-radius: var(--radius-sm); padding: 9px 11px; margin-top: 2px;
}
.action-row.raw .action-row-label { color: var(--warn); }
.action-row.raw button { background: var(--panel); }
.actions button, .action-row button {
  border: 1px solid var(--border-strong); background: var(--panel); color: var(--text);
  border-radius: var(--radius-sm); padding: 6px 12px; font-size: 12.5px; cursor: pointer;
}
.actions button:hover, .action-row button:hover { border-color: var(--accent); color: var(--accent); }
.actions button:disabled, .action-row button:disabled {
  opacity: .45; cursor: not-allowed; border-color: var(--border); color: var(--muted);
}
.actions button.primary, .action-row button.primary {
  background: var(--accent); border-color: var(--accent); color: #fff;
}
.actions button.primary:hover, .action-row button.primary:hover { color: #fff; opacity: .92; }
.actions button.danger:hover, .action-row button.danger:hover { border-color: var(--bad); color: var(--bad); }
/* -- the availability switch -------------------------------------------------- */
/* Label, state and action in ONE element. A verb-only button ("Make
   unavailable") makes the reader infer the state from the verb, and that
   inference collides with the verdict pill next to it. */

.switch-field {
  display: flex; align-items: center; gap: 12px;
  margin-top: auto; padding-top: 13px; border-top: 1px solid var(--border);
}
/* In the actions rail it is one control among several rows, not the card's
   footer, so it keeps the spacing and drops the rule. */
.actions .switch-field { margin-top: 0; padding-top: 0; border-top: none; }
.switch-label {
  font-size: 9px; font-weight: 700; letter-spacing: .09em; text-transform: uppercase;
  color: var(--muted); flex: none;
}
.switch {
  display: inline-flex; align-items: center; gap: 9px;
  background: none; border: none; padding: 4px 2px; margin: 0;
  font: inherit; font-size: 12.5px; color: var(--muted); cursor: pointer;
}
.switch:hover:not(:disabled) { color: var(--text); }
.switch:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 6px; }
.switch:disabled { cursor: not-allowed; opacity: .55; }

/* The track is drawn, not an <input>: this performs an action on press rather
   than holding a value for a form, and `aria-pressed` is what announces it. */
.switch-track {
  position: relative; width: 34px; height: 19px; flex: none;
  background: var(--border-strong); border-radius: 999px;
  transition: background .14s;
}
.switch-track::after {
  content: ""; position: absolute; top: 2px; left: 2px;
  width: 15px; height: 15px; border-radius: 50%;
  background: var(--panel); box-shadow: 0 1px 2px rgba(0,0,0,.22);
  transition: transform .14s;
}
.switch.is-on .switch-track { background: var(--accent); }
.switch.is-on .switch-track::after { transform: translateX(15px); }
.switch.is-on .switch-state { color: var(--accent); font-weight: 600; }

.action-note { font-size: 11px; color: var(--muted); margin-top: 7px; min-height: 14px; }
.action-note:empty { min-height: 0; margin-top: 0; }

/* -- panels and robot detail dashboard --------------------------------------- */

.detail-dashboard { display: flex; flex-direction: column; gap: 16px; }
.detail-content { min-width: 0; }
.robot-detail-grid {
  display: grid; gap: 14px;
  grid-template-columns: minmax(0, 75fr) minmax(0, 35fr);
  align-items: start;
}
.detail-left-stack,
.detail-robot-cards { display: flex; flex-direction: column; gap: 14px; min-width: 0; }
.detail-plan { min-width: 0; }
.taskplan-card-head {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  margin-bottom: 10px;
}
.taskplan-card-head button { height: 30px; padding: 5px 10px; font-size: 11.5px; }

.panel {
  background: var(--panel); border: 1px solid var(--border);
  border-radius: var(--radius); padding: 16px 18px;
}
.panel h2 {
  font-size: 10px; font-weight: 700; letter-spacing: .12em; text-transform: uppercase;
  color: var(--muted);
}
.panel-hint { margin: 5px 0 0; font-size: 12px; color: var(--muted); }
.hollow { font-size: 13px; color: var(--muted); margin: 12px 0 0; }

/* -- the robot page ----------------------------------------------------------- */

.robot-status-card .facts { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.robot-status-card .fact-value.mono { white-space: normal; overflow-wrap: anywhere; }
.telemetry-warning {
  margin-top: 10px; padding: 9px 11px; border-radius: var(--radius-sm);
  background: var(--bad-bg); color: var(--bad); font-size: 12px; line-height: 1.4;
}
.inspect-card { min-width: 0; }
.inspect-controls {
  display: flex; align-items: center; justify-content: flex-end; gap: 7px;
  margin-top: -20px; margin-bottom: 8px;
}
.inspect-paused { color: var(--warn); font-size: 11px; margin-right: auto; }
.inspect-call {
  margin-top: 12px; border: 1px solid var(--border); border-left: 3px solid var(--ok);
  border-radius: var(--radius-sm); padding: 10px; min-width: 0;
}
.inspect-call.is-bad { border-left-color: var(--bad); }
.inspect-call.is-pending { border-left-color: var(--warn); }
.inspect-head {
  display: grid; grid-template-columns: 1fr auto 1fr; align-items: center; gap: 8px;
}
.inspect-sender { font-size: 11px; font-weight: 700; color: var(--accent); }
.inspect-time { justify-self: center; color: var(--muted); font: 11px var(--mono); }
.inspect-copy { justify-self: end; }
.inspect-path, .inspect-sign {
  margin-top: 6px; color: var(--muted); font: 11px var(--mono); overflow-wrap: anywhere;
}
.inspect-path { color: var(--text); font-weight: 600; }
.inspect-message {
  margin-top: 9px; border: 1px solid var(--border); border-radius: 5px;
  background: var(--panel-sunk); overflow: hidden;
}
.inspect-label {
  display: block; padding: 5px 8px; border-bottom: 1px solid var(--border);
  color: var(--muted); font-size: 10px; font-weight: 700;
  letter-spacing: .06em; text-transform: uppercase;
}
.inspect-message pre {
  margin: 0; padding: 8px; max-height: 260px; overflow: auto;
  color: var(--text); font: 11px/1.45 var(--mono); white-space: pre-wrap;
  overflow-wrap: anywhere;
}
.inspect-response { border-color: color-mix(in srgb, var(--accent) 25%, var(--border)); }

.actions-toolbar {
  padding: 0 0 14px; border-bottom: 1px solid var(--border);
  overflow-x: auto;
}
.actions-toolbar .actions {
  display: grid; grid-template-columns: max-content max-content;
  gap: 0 8px; margin: 0; align-items: end; justify-content: center;
  min-width: max-content;
}
.actions-toolbar .action-row {
  min-height: 50px; padding: 0; align-content: flex-end; flex-wrap: nowrap;
}
.actions-toolbar .action-row.plan-builder {
  grid-column: 1; grid-row: 1; min-height: 32px; padding: 0; border: 0; gap: 8px;
}
.actions-toolbar .action-row.raw {
  grid-column: 2; grid-row: 1; margin: 0; min-height: 32px; padding: 5px 7px;
}
.actions-toolbar .action-note,
.actions-toolbar .plan-confirm { grid-column: 1 / -1; }
.actions-toolbar .plan-confirm { margin-top: 6px; }

.plan-action-select {
  width: 132px; min-width: 0; height: 30px; box-sizing: border-box;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-sm); background: var(--panel); color: var(--text);
  padding: 5px 22px 5px 7px; font: 11.5px/1.2 inherit; cursor: pointer;
}
.plan-action-picker {
  display: inline-flex; align-items: center; gap: 5px; white-space: nowrap;
}
.plan-action-prefix { font-size: 11.5px; color: var(--text); }
.plan-action-select:hover { border-color: var(--accent); }
.plan-action-select:focus-visible {
  outline: 2px solid var(--accent); outline-offset: 2px;
}
.plan-action-select:disabled {
  opacity: .45; cursor: not-allowed; border-color: var(--border);
  color: var(--muted);
}

.plan-confirm {
  display: flex; flex-wrap: wrap; align-items: center; gap: 7px;
  min-height: 42px; padding: 8px 10px; border: 1px solid var(--border);
  border-radius: var(--radius-sm); background: var(--panel-sunk);
}
.plan-confirm-text {
  flex: 1 1 280px; min-width: 0; font-size: 12.5px; color: var(--text);
}

.actions-toolbar .action-row button {
  height: 30px; box-sizing: border-box; padding: 5px 8px;
  font-size: 11.5px; white-space: nowrap;
}

/* The 30/70 split lives inside the LEFT column, not the page, so it needs to
   collapse earlier than the page does -- 30% of a 2fr column is narrow. */

/* THE TIMELINE IS FIXED, THE TASK DETAIL IS FLUID.
   The timeline holds one column of step names and its content does not get
   wider with the window -- so a percentage gave it space it could not use at
   large widths and starved it at small ones. 13rem is measured, not guessed:
   the longest suffix `plan_builder` mints is `mission:task_received` at 144px
   in this monospace, plus the 14px mark column, the 8px gap, 20px of padding
   and the rule = 187px, so 208px leaves headroom without waste. Every extra
   pixel of the window now goes to the current task, which is the pane that
   can use it -- facts, a pickup code, and the controls.

   NO BREAKPOINT, deliberately. This used to collapse to one column below
   1280px, which put eighteen timeline steps full-width and pushed the task
   detail and its actions below the fold: the two things an operator opens the
   page for, gone, and the layout a different shape from the one they had
   learned. */
.plan-split {
  display: grid; grid-template-columns: 13rem minmax(0, 1fr);
  gap: 22px; align-items: start;
}
.plan-timeline { min-width: 0; border-right: 1px solid var(--border); padding-right: 20px; }
.plan-detail { min-width: 0; }
.pane-title {
  font-size: 9px; font-weight: 700; letter-spacing: .09em; text-transform: uppercase;
  color: var(--muted); margin-bottom: 4px;
}

.pane-head {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 8px; margin-bottom: 4px;
}
.pane-head .pane-title { margin-bottom: 0; }

/* -- the timeline ------------------------------------------------------------- */
/* ASCENDING: first step at the top, so reading down is reading forward in
   time. Built from the PLAN, so there is a past, a present and a future to
   distinguish -- which is what makes highlighting the current step mean
   anything. */

.timeline { list-style: none; margin: 10px 0 0; padding: 0; }
.timeline-plan-group { margin-top: 10px; }
.timeline-plan-node { font-weight: 700; }
.timeline-plan-node .timeline-mark { border-radius: 3px; }
.timeline-plan-children {
  margin: 2px 0 0 7px; padding-left: 13px;
  border-left: 1px solid var(--border);
}
.timeline-step {
  position: relative; display: grid;
  grid-template-columns: 14px minmax(0, 1fr); gap: 0 8px;
  padding: 5px 0 5px 0; font-size: 12px;
}
/* The connecting rule, drawn between marks rather than around them. */
.timeline-step:not(:last-child)::before {
  content: ""; position: absolute; left: 6px; top: 17px; bottom: -1px;
  width: 1px; background: var(--border);
}
.timeline-mark {
  width: 9px; height: 9px; border-radius: 50%; margin: 5px 0 0 1px;
  background: var(--panel); border: 2px solid var(--border-strong);
}
.timeline-name {
  font-family: var(--mono); font-size: 11.5px; color: var(--muted);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.timeline-type, .timeline-at {
  grid-column: 2; font-size: 10px; color: var(--muted); opacity: .75;
}

.timeline-step.is-new .timeline-name { opacity: .55; }
.timeline-step.is-new .timeline-mark { border-style: dashed; }

.timeline-step.is-completed .timeline-mark {
  background: var(--ok); border-color: var(--ok);
}
.timeline-step.is-completed .timeline-name { color: var(--text); }

.timeline-step.is-in-progress {
  background: var(--accent-soft); border-radius: 7px;
  padding-left: 4px; margin-left: -4px;
}
.timeline-step.is-in-progress .timeline-mark {
  background: var(--accent); border-color: var(--accent);
  box-shadow: 0 0 0 3px rgba(31,81,64,.16);
}
.timeline-step.is-in-progress .timeline-name { color: var(--accent); font-weight: 700; }
.timeline-step.is-in-progress .timeline-type { color: var(--accent); opacity: .9; }

.timeline-step.is-skipped .timeline-mark {
  background: var(--warn-bg); border-color: var(--warn);
}
.timeline-step.is-skipped .timeline-name,
.timeline-step.is-skipped .timeline-type { color: var(--warn); }

.timeline-step.is-failed .timeline-mark {
  background: var(--bad); border-color: var(--bad);
}
.timeline-step.is-failed .timeline-name,
.timeline-step.is-failed .timeline-type { color: var(--bad); }

.timeline-step.is-cancelled .timeline-mark {
  background: var(--offline); border-color: var(--offline);
}
.timeline-step.is-cancelled .timeline-name,
.timeline-step.is-cancelled .timeline-type { color: var(--offline); }

/* A disagreement outranks the ordinary state colour. Both values are printed
   in the row; the red inset makes it visible without relying on colour alone. */
.timeline-step.is-mismatch {
  background: var(--bad-bg); border-radius: 7px;
  box-shadow: inset 3px 0 0 var(--bad);
  padding-left: 7px; margin-left: -7px;
}
.timeline-step.is-mismatch .timeline-name,
.timeline-step.is-mismatch .timeline-type { color: var(--bad); font-weight: 700; opacity: 1; }
.timeline-step.is-mismatch .timeline-mark {
  background: var(--bad); border-color: var(--bad);
  box-shadow: 0 0 0 3px rgba(156,51,40,.14);
}

/* The 70% pane carries the task's OWN actions, inline rather than as a card:
   they are part of this task's detail, not a separate thing on the page. The
   robot's controls are their own card in the right column. */
.plan-detail .actions { margin-top: 18px; border-top: 1px solid var(--border); padding-top: 14px; }
/* Same separation as `.actions` above, and for the same reason: a sub-block
   inside the pane needs a rule between it and the pane's own facts, or the two
   fact grids read as one. */
.plan-detail .lift-crossing {
  margin-top: 18px; border-top: 1px solid var(--border); padding-top: 14px;
}

/* -- task line and code ------------------------------------------------------ */

.task-line { display: flex; align-items: center; gap: 9px; flex-wrap: wrap; margin-top: 12px; }
.task-no { font-family: var(--mono); font-size: 12.5px; }
.no-task { color: var(--muted); font-size: 13px; margin-top: 12px; }
.pickup-code-block { margin-top: 11px; }
.pickup-code-label {
  display: block; margin-bottom: 4px; color: var(--muted);
  font-size: 9px; font-weight: 700; letter-spacing: .09em; text-transform: uppercase;
}
.pickup-code {
  font-family: var(--mono); font-size: 19px; letter-spacing: .16em; color: var(--text);
  background: var(--panel-sunk); border: 1px dashed var(--border-strong);
  border-radius: var(--radius-sm); padding: 7px 12px; display: inline-block;
}

/* -- the taskplan trace ------------------------------------------------------ */

.trace { list-style: none; margin: 13px 0 0; padding: 0; }
.trace li {
  display: flex; align-items: baseline; gap: 10px; font-size: 12px;
  padding: 5px 0 5px 14px; border-left: 2px solid var(--border);
}
.trace li.now { border-left-color: var(--accent); }
.trace-step { font-family: var(--mono); color: var(--text); }
.trace li.now .trace-step { font-weight: 700; }
.trace-at { color: var(--muted); margin-left: auto; }
.trace-tag {
  font-size: 9px; font-weight: 700; text-transform: uppercase; letter-spacing: .07em;
  color: var(--accent); background: var(--accent-soft); border-radius: 999px; padding: 2px 7px;
}

/* -- job list (history) ------------------------------------------------------ */

.jobs { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 10px; }
.job {
  background: var(--panel); border: 1px solid var(--border); border-radius: var(--radius);
  padding: 13px 16px;
}
.job-head { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.job-when { font-size: 11px; color: var(--muted); font-family: var(--mono); }
.job-no { font-family: var(--mono); font-size: 13px; margin-right: auto; color: var(--text); text-decoration: none; }
.job-no:hover { color: var(--accent); text-decoration: underline; }
.job-robot { font-size: 11px; color: var(--muted); }

.trail { list-style: none; margin: 13px 0 0; padding: 0; }
.trail li {
  display: flex; align-items: baseline; gap: 12px; font-size: 12px;
  padding: 4px 0 4px 14px; border-left: 2px solid var(--border);
}
.trail-edge { font-family: var(--mono); color: var(--text); flex: none; }
.trail-cause { color: var(--muted); flex: 1 1 auto; min-width: 0; }
.trail-at { color: var(--muted); flex: none; font-family: var(--mono); }

/* -- misc -------------------------------------------------------------------- */

.load-more { margin-top: 14px; }
.spread { display: flex; align-items: center; gap: 10px; }
.spread .grow { margin-left: auto; }

/* A finding the operator must not scroll past: the robot is running a plan
   RCS did not send, so the timeline beside it is empty and no milestone of
   it will ever reach WCS. Sits above "Current task" rather than beside it,
   because it explains why that panel looks wrong. */
.banner {
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding: 10px 12px;
  margin: 0 0 12px;
  border-radius: 6px;
  border-left: 3px solid currentColor;
  font-size: 13px;
  line-height: 1.4;
}
.banner-warn {
  color: #8a5a00;
  background: #fff6e5;
}
.banner strong {
  font-weight: 600;
}
.reported-plan {
  margin-bottom: 16px;
}

/* A reported task opens for the fields the row cannot hold -- error_reason,
   remarks, the timestamps. */
.timeline-step.is-selectable { cursor: pointer; }
.timeline-step.is-selectable:hover { background: rgba(0, 0, 0, 0.03); }
.timeline-step.is-selected { background: rgba(0, 0, 0, 0.05); }
.timeline-step.is-mismatch.is-selectable:hover,
.timeline-step.is-mismatch.is-selected { background: var(--bad-bg); }
.task-detail { margin: 12px 0 4px; }
.facts-note { color: #8a5a00; }

/* SETTINGS — what this deployment is wired to.
 *
 * Built from the vocabulary already on the fleet and detail pages rather than
 * a set of its own: `.card`'s panel and radius, `.pill`'s shape and colour
 * pairs, `.action-row button`'s outline. A settings page that looked like a
 * different product would read as a different product's settings.
 *
 * One column, capped: these are short statements about the deployment, and a
 * card stretched across a wide screen puts its label and its value too far
 * apart to read as one fact. */
#settings-body {
  display: flex; flex-direction: column; gap: 14px;
  max-width: 1080px;
}
.set-card {
  background: var(--panel); border: 1px solid var(--border);
  border-radius: var(--radius); padding: 16px 17px 15px;
}
.set-card-title {
  font-family: var(--display); font-size: 17px; font-weight: 500;
  color: var(--text); margin: 0;
}
.set-card-sub { color: var(--muted); font-size: 12.5px; margin: 4px 0 0; }
.set-row {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  margin-top: 13px;
}
.set-value {
  font-family: var(--mono, ui-monospace, monospace); font-size: 12px;
  color: var(--muted); overflow-wrap: anywhere;
}
.set-address-row { align-items: flex-end; }
.set-address-label {
  display: flex; flex: 1 1 520px; flex-direction: column; gap: 5px;
  color: var(--muted); font-size: 11px; font-weight: 600;
  text-transform: uppercase; letter-spacing: .04em;
}
.set-address-input {
  width: 100%; min-width: 260px; box-sizing: border-box;
  border: 1px solid var(--border-strong); border-radius: 5px;
  background: var(--panel-sunk); color: var(--text);
  padding: 7px 9px; font: 12px var(--mono, ui-monospace, monospace);
}
.set-address-input:focus {
  border-color: var(--accent); outline: 1px solid var(--accent);
}
/* A SWITCH, NOT TWO BUTTONS.
 *
 * It was two -- "Use real WCS" and "Use mock" -- with the current one filled.
 * A filled button reads as the thing you press, so the control said the
 * opposite of what it meant, and neither label told you where you already
 * were. A switch has a POSITION, which is a state rather than an offer, and
 * the word beside it names that state.
 *
 * The colours are the console's own: green for the ordinary case, amber for
 * the one you should notice. Amber rather than red because a mock rig is a
 * legitimate thing to be, just never a thing to be by accident. */
.set-toggle {
  position: relative; flex: none;
  width: 46px; height: 26px; border-radius: 999px;
  border: 1px solid var(--border-strong); background: var(--panel-sunk);
  padding: 0; cursor: pointer; transition: background .14s, border-color .14s;
}
.set-toggle-knob {
  position: absolute; top: 2px; left: 2px; width: 20px; height: 20px;
  border-radius: 50%; background: #fff; transition: transform .14s;
  box-shadow: 0 1px 2px rgba(0,0,0,.28);
}
.set-toggle.is-real { background: var(--ok); border-color: var(--ok); }
.set-toggle.is-mock { background: var(--warn); border-color: var(--warn); }
/* Mock is the RIGHT-hand position, so "pushed over to the unusual side" is
   the thing you see without reading anything. */
.set-toggle.is-mock .set-toggle-knob { transform: translateX(20px); }
.set-toggle:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.set-toggle:disabled { opacity: .45; cursor: not-allowed; }

/* The read-out. Legible at a glance from across a desk, which is the whole
   job: whether the rig in front of you is talking to a real vendor. */
.set-mode {
  font-size: 13px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase;
}
.set-mode-real { color: var(--ok); }
.set-mode-mock { color: var(--warn); }

.set-link {
  display: inline-block; margin-top: 13px; color: var(--accent);
  font-size: 13px; font-weight: 500;
}
.set-hollow { color: var(--muted); font-size: 12.5px; margin: 9px 0 0; }
.set-note { font-size: 12.5px; margin: 11px 0 0; color: var(--ok); }
.set-note-bad { color: var(--bad); }

/* A long refusal reason must not stretch the row. */
.map-select { padding: 4px 7px; font: inherit; max-width: 30ch; }
.map-upload-field {
  display: inline-flex; flex-direction: column; align-items: flex-start;
  gap: 4px; min-width: 0;
}
.map-upload-label {
  color: var(--muted); font-size: 10.5px; font-weight: 700;
  line-height: 1; letter-spacing: .05em; text-transform: uppercase;
}
.map-poi-upload { max-width: 230px; color: var(--muted); font-size: 12px; }
.map-poi-upload::file-selector-button {
  border: 1px solid var(--border-strong); border-radius: 5px;
  background: var(--panel); color: var(--text); padding: 5px 9px;
  font: inherit; cursor: pointer; margin-right: 8px;
}
.map-upload-name { max-width: 22ch; }
.set-action {
  border: 1px solid var(--border-strong); border-radius: 5px;
  background: var(--panel); color: var(--text); padding: 5px 9px;
  font: inherit; cursor: pointer;
}
.set-action:hover { border-color: var(--accent); color: var(--accent); }
.set-action:disabled { opacity: .45; cursor: not-allowed; }

.mapping-grid {
  display: grid;
  grid-template-columns: repeat(4, minmax(145px, 1fr));
  gap: 7px 9px; margin-top: 14px; min-width: 680px;
}
.set-card:has(.mapping-grid) { overflow-x: auto; }
.mapping-label {
  color: var(--muted); font-size: 11px; font-weight: 600;
  text-transform: uppercase; letter-spacing: .04em;
}
.mapping-kind {
  grid-column: 1 / -1; color: var(--accent); font-size: 12px;
  font-weight: 600; border-bottom: 1px solid var(--border);
  padding: 6px 0 3px; margin-top: 2px;
}
.mapping-floor {
  grid-column: 1 / -1; color: var(--muted); font-size: 11px;
  font-weight: 600; padding: 5px 0 0;
}
.mapping-row { display: contents; }
.mapping-input,
.mapping-select {
  width: 100%; min-width: 0; border: 1px solid var(--border-strong);
  border-radius: 5px; background: var(--panel); color: var(--text);
  padding: 6px 7px; font: 12px/1.35 var(--mono, ui-monospace, monospace);
}
.mapping-input:focus,
.mapping-select:focus { outline: 2px solid var(--accent); outline-offset: 1px; }
