﻿/* View-specific pieces: the live monitor's tabs, toolbars, controller grid and streams. */

/* ── tabs ──────────────────────────────────────────────────────────────────── */
/* Three surfaces over one connection. Stacking them made the page scroll past whatever
   was being watched; the counters keep the hidden ones visibly alive. */
.card__head--tabs { padding: 0 .5rem; gap: 0; }
.tabs { display: flex; gap: .125rem; flex-wrap: wrap; }
.tab {
  border: 0; border-bottom: 2px solid transparent; border-radius: 0;
  background: none;
  padding: .75rem .875rem;
  color: var(--fg-muted); font-weight: 550;
}
.tab:hover:not([aria-selected="true"]) { background: var(--surface-sunk); color: var(--fg); }
.tab[aria-selected="true"] { color: var(--fg); border-bottom-color: var(--accent); }
.tab[aria-selected="true"] .pill { background: var(--accent-soft); color: color-mix(in srgb, var(--accent) 78%, var(--fg)); }
[role="tabpanel"]:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }

/* ── panel toolbars ────────────────────────────────────────────────────────── */
.toolbar {
  display: flex; align-items: center; flex-wrap: wrap; gap: .5rem;
  padding: .625rem 1.125rem;
  border-bottom: 1px solid var(--line);
  background: var(--surface-sunk);
}
.toolbar--chips { padding-block: .5rem; }
.toolbar__grow { flex: 2 1 240px; min-width: 0; }
.toolbar__sep { width: 1px; align-self: stretch; margin-inline: .25rem; background: var(--line-strong); }
.toolbar input, .toolbar select { min-height: 32px; padding-block: .3125rem; font-size: .8125rem; }
/* A toolbar's own cells size to their content; only the search field is told to grow. */
.toolbar > .cell--auto { flex: 0 1 auto; }
.toolbar > .cell--auto select { width: auto; min-width: 8rem; }

/* A metadata strip under a toolbar: what the filters currently leave visible. Placed
   outside the toolbar so it never competes with a control for the same row. */
.hint--strip {
  margin: 0;
  padding: .5rem 1.125rem;
  border-bottom: 1px solid var(--line);
  background: var(--surface-sunk);
}

/* A select that sits in a card head beside a button, rather than in a form column. */
.select--auto { width: auto; min-width: 10rem; }

/* A window id is an opaque 36-character identifier: it belongs in the stat row as evidence,
   not as a headline number, so it is sized to read rather than to be seen from across it. */
.stat__value--id { font-size: .8125rem; }

/* ── live state ────────────────────────────────────────────────────────────── */
.stats--tight { gap: .5rem; grid-template-columns: repeat(auto-fit, minmax(104px, 1fr)); margin-bottom: .75rem; }
.stats--tight .stat { padding: .5rem .625rem; }
.stats--tight .stat__value { font-size: 1.0625rem; }
/* Tone is the answer, not decoration: "two offline" has to read differently from "none". */
.stat--ok   { border-color: color-mix(in srgb, var(--ok) 35%, var(--line)); }
.stat--ok   .stat__value { color: var(--ok); }
.stat--warn { border-color: color-mix(in srgb, var(--warn) 40%, var(--line)); }
.stat--warn .stat__value { color: var(--warn); }
.stat--bad  { border-color: color-mix(in srgb, var(--bad) 40%, var(--line)); }
.stat--bad  .stat__value { color: var(--bad); }

.live-grid {
  display: grid; gap: .75rem;
  grid-template-columns: repeat(auto-fill, minmax(min(250px, 100%), 1fr));
}
.live-grid--compact { grid-template-columns: repeat(auto-fill, minmax(min(190px, 100%), 1fr)); }

.ctl {
  padding: .75rem .875rem;
  border: 1px solid var(--line); border-radius: var(--radius);
  background: var(--surface-sunk);
  cursor: pointer;
  transition: border-color .12s ease, background-color .12s ease;
  /* An authorized window is nine hundred cards. Skipping layout and paint for the ones off
     screen is what keeps that a grid rather than a freeze; the placeholder size is a
     full-density card, so the scrollbar stays honest before anything is painted. */
  content-visibility: auto;
  contain-intrinsic-size: auto 116px;
}
.ctl:hover { border-color: var(--line-strong); background: var(--surface); }
.ctl:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.ctl__more { margin-top: .5rem; font-size: .6875rem; color: var(--fg-faint); }
.ctl.flash { animation: cardflash 1.1s ease-out; }
@keyframes cardflash {
  from { border-color: var(--live); background: color-mix(in srgb, var(--live) 14%, var(--surface-sunk)); }
}
.ctl__top { display: flex; align-items: baseline; gap: .5rem; margin-bottom: .5rem; }
.ctl__name { font-weight: 600; font-size: .875rem; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ctl__id { margin-left: auto; font-family: var(--mono); font-size: .6875rem; color: var(--fg-faint); }
.ctl__metrics { display: grid; grid-template-columns: repeat(auto-fit, minmax(84px, 1fr)); gap: .375rem .75rem; font-size: .75rem; }
.ctl__metric span { display: block; color: var(--fg-faint); font-size: .6875rem; }
.ctl__metric b { font-weight: 600; font-variant-numeric: tabular-nums; }

tbody tr.flash td { animation: flash 1.1s ease-out; }
@keyframes flash {
  from { background: color-mix(in srgb, var(--live) 26%, transparent); }
  to   { background: transparent; }
}

.log {
  max-height: 52dvh; overflow: auto;
  font-family: var(--mono); font-size: .75rem;
  /* The stream grows from the bottom; reserving the gutter stops the whole row grid
     shifting sideways the moment the first scrollbar appears. */
  scrollbar-gutter: stable;
  overscroll-behavior: contain;
}
.log__row {
  /* Time, tag, source, message. The source column was 74px, which truncated every .NET
     logger category to "RequestLo…" — a value that identifies nothing. 11rem fits the last
     segment of the categories this server actually emits (RequestLoggingMiddleware is the
     longest of them), which is the part that names the emitter. */
  display: grid; grid-template-columns: 78px 56px 11rem minmax(0, 1fr); gap: .625rem;
  padding: .3125rem .875rem;
  border-bottom: 1px solid var(--line);
  align-items: baseline;
  /* Thousands of rows, most of them off-screen: skip the ones nobody is looking at. */
  content-visibility: auto; contain-intrinsic-size: auto 24px;
}
/* A row that carries a payload is a control: it opens what the hub actually sent. */
.log__row--open { cursor: pointer; }
.log__row--open:hover { background: var(--surface-sunk); }
.log__row--open:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.log__more {
  margin-left: .5rem; padding: 0 .25rem;
  border: 1px solid var(--line-strong); border-radius: 3px;
  font-size: .625rem; color: var(--fg-faint);
  opacity: 0; transition: opacity .12s ease;
}
.log__row--open:hover .log__more,
.log__row--open:focus-visible .log__more { opacity: 1; }
.log__row:nth-child(even) { background: color-mix(in srgb, var(--fg) 2.5%, transparent); }

/* On a phone the three fixed columns leave the message a sliver, so the row stacks: the
   metadata reads as one line and the text it labels gets the full width below it. */
@media (max-width: 700px) {
  .log__row { grid-template-columns: auto auto minmax(0, 1fr); row-gap: .25rem; }
  .log__msg { grid-column: 1 / -1; }
}
.log__time { color: var(--fg-faint); }
.log__ctl  { color: var(--fg-muted); }
.log__msg  { overflow-wrap: anywhere; }
/* Values substituted into the message template, distinguished from the literal text so a
   log line still reads as the template the code declares. */
.log__val {
  color: var(--accent);
  background: color-mix(in srgb, var(--accent) 10%, transparent);
  border-radius: 3px;
  padding: 0 .1875rem;
}

/* An exception keeps its own line breaks; a correlation id is metadata, not message. */
.log__exception { white-space: pre-wrap; margin-top: .25rem; }
.log__trace { font-size: .6875rem; opacity: .8; }

.tag {
  display: inline-block; padding: 0 .375rem;
  border-radius: 4px;
  font-size: .6875rem; font-weight: 600;
  background: var(--idle-soft); color: var(--idle);
  text-align: center;
}
.tag--in   { background: var(--live-soft); color: var(--live); }
.tag--out  { background: var(--accent-soft); color: color-mix(in srgb, var(--accent) 80%, var(--fg)); }
.tag--sys  { background: var(--warn-soft); color: var(--warn); }
.tag--elog { background: var(--bad-soft); color: var(--bad); }
.tag--ok   { background: var(--ok-soft); color: var(--ok); }

/* An ordered procedure, numbered by the browser: the numbers are the content, so they are
   not painted on with ::before. */
.steps { margin: 0; padding-left: 1.25rem; font-size: .8125rem; color: var(--fg-muted); }
.steps li + li { margin-top: .3125rem; }

/* Firebase Web Push diagnostic */
.push-token {
  min-height: 5rem;
  margin: 0;
  padding: .75rem;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface-sunk);
  color: var(--fg-muted);
  overflow-wrap: anywhere;
  white-space: pre-wrap;
}
.push-messages { display: grid; gap: .625rem; }
.push-message { padding: .625rem .75rem; border: 1px solid var(--line); border-radius: var(--radius); background: var(--surface-sunk); }
.push-message__top { display: flex; gap: .5rem; align-items: baseline; }
.push-message__top time { margin-left: auto; color: var(--fg-faint); font: .6875rem var(--mono); }
.push-message p { margin-top: .375rem; color: var(--fg-muted); font-size: .8125rem; }
.push-message code { display: inline-block; margin-top: .375rem; font-size: .6875rem; overflow-wrap: anywhere; }
.push-subscriptions { display: grid; gap: .625rem; }
.push-subscription { padding: .75rem; border: 1px solid var(--line); border-radius: var(--radius); background: var(--surface-sunk); }
.push-subscription p { margin-top: .375rem; color: var(--fg-muted); font-size: .8125rem; }
.push-subscription .row { margin-top: .625rem; }
#push-controller-selection, #push-event-selection { max-height: 12rem; overflow: auto; padding: .25rem; }
#push-event-selection { max-height: 18rem; }
.chips__label { flex-basis: 100%; margin-top: .375rem; color: var(--fg-faint); font-size: .6875rem; text-transform: uppercase; letter-spacing: .04em; }
.chips__label:first-child { margin-top: 0; }
.push-message__detail { margin-top: .375rem; color: var(--fg-muted); font: .6875rem var(--mono); overflow-wrap: anywhere; }

/* ---- Raw register file (controller detail dialog) ---- */
/* Grouped by zone like the legacy backdoor tabs; the server pre-joined names, so these
   rules only lay out rows. */
.registers-scroll { max-height: 40vh; overflow-y: auto; display: grid; gap: 2px; }
.register-row { display: grid; grid-template-columns: 1fr auto; gap: 0 12px; padding: 2px 4px; border-radius: 4px; }
.register-row:nth-child(odd) { background: color-mix(in srgb, currentColor 4%, transparent); }
.register-row--digital { grid-template-columns: 1fr auto; }
.register-bit { grid-column: 1 / -1; display: flex; gap: 8px; align-items: center; padding-inline-start: 16px; font-size: 0.85em; }
.register-bit[data-set="false"] { opacity: 0.6; }

/* State wiki
   ------------------------------------------------------------------------------------
   A reference tree over the published contract: model, value, and the nested types a
   value expands into. Built from <details> so expanding costs no script and the keyboard
   behaviour is the browser's own. */

.wiki {
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 10px 14px 14px;
}

.wiki__model-links {
  display: flex;
  gap: 6px;
  padding: 10px 14px 4px;
  overflow-x: auto;
  scrollbar-width: thin;
}

.wiki__model-link {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  min-height: 32px;
  padding: 4px 9px;
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-s);
  color: var(--fg-muted);
  font-size: .76rem;
  font-weight: 600;
  text-decoration: none;
  white-space: nowrap;
}

.wiki__model-link:hover { background: var(--surface-sunk); color: var(--fg); }
.wiki__model-link[aria-current="page"] {
  border-color: color-mix(in srgb, var(--accent) 45%, var(--line));
  background: var(--accent-soft);
  color: var(--accent);
}

.wiki__model {
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface);
}

.wiki__model-head {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 12px;
  cursor: pointer;
  font-weight: 600;
}

.wiki__model-name { flex: 1; }

.wiki__rows {
  border-top: 1px solid var(--line);
  padding: 6px 12px 10px;
}

.wiki__columns {
  display: grid;
  grid-template-columns: minmax(14rem, 1.6fr) minmax(8rem, 1fr) minmax(7rem, .7fr);
  gap: 8px 12px;
  padding: 7px 12px;
  border-top: 1px solid var(--line);
  background: var(--surface-sunk);
  color: var(--fg-faint);
  font-size: .7rem;
  font-weight: 600;
  text-transform: uppercase;
}

.wiki__row {
  display: grid;
  /* A fixed lead column for the +/- marker, so paths at one depth align whether or not
     the row opens; the marker itself is transparent on a leaf. */
  grid-template-columns: 1.45rem minmax(14rem, 1.6fr) minmax(8rem, 1fr) minmax(7rem, .7fr);
  align-items: baseline;
  gap: 8px 12px;
  padding: 6px 0;
  border-bottom: 1px solid var(--line);
}

.wiki__row:last-child { border-bottom: 0; }

.wiki__row--group {
  cursor: pointer;
  font-weight: 600;
}

.wiki__path {
  width: fit-content;
  font-family: var(--mono);
  font-size: .86em;
  color: var(--fg);
  white-space: normal;
  overflow-wrap: anywhere;
  text-decoration-color: transparent;
}

.wiki__path:hover, .wiki__path:focus-visible { color: var(--accent); text-decoration-color: currentColor; }

.wiki__row--group .wiki__path { font-weight: 600; }
.wiki__row--leaf .wiki__path { color: var(--fg-muted); }

.wiki__contract {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 4px;
}

.wiki__type {
  display: flex;
  flex-direction: column;
  gap: 1px;
  font-family: var(--mono);
  font-size: 0.86em;
  color: var(--fg-muted);
  overflow-wrap: anywhere;
}

.wiki__type-primary { color: var(--fg); font-weight: 600; }
.wiki__type-schema { color: var(--fg-faint); font-size: .8em; }

/* Below the head, spanning the row: the addresses belong with the explanation. */
.wiki__chips {
  grid-column: 1 / -1;
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
  margin-top: 4px;
}

.wiki__chip {
  font-family: var(--mono);
  font-size: 0.76em;
  padding: 1px 6px;
  border: 1px solid var(--line);
  border-radius: var(--radius-s);
  color: var(--fg-muted);
  white-space: nowrap;
}

.wiki__chip--event {
  border-color: var(--accent);
  color: var(--accent);
}

.wiki__chip--soft { color: var(--fg-faint); }

/* The explanation spans the full row: it is the point of the view, not a column in it. */
.wiki__desc {
  grid-column: 1 / -1;
  margin: 2px 0 0;
  font-size: 0.9em;
  color: var(--fg-muted);
}

.wiki__desc--missing { color: var(--bad); }

/* The semantic panel: what the value is, before where it came from. */
.wiki__badges {
  grid-column: 1 / -1;
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
  margin-top: 4px;
}

.wiki__badge {
  font-size: 0.74em;
  letter-spacing: 0.02em;
  padding: 1px 7px;
  border-radius: 999px;
  background: var(--surface-sunk);
  color: var(--fg-muted);
  white-space: nowrap;
}

/* An unconfirmed unit is a published gap, so it reads as one rather than as a value. */
.wiki__badge--warn {
  background: var(--warn-soft);
  color: var(--warn);
}

.wiki__badge--shape {
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-s);
  background: transparent;
  color: var(--fg);
}

.wiki__facts {
  grid-column: 1 / -1;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
  gap: 2px 14px;
  margin: 5px 0 0;
  font-size: 0.86em;
}

.wiki__fact dt {
  color: var(--fg-faint);
  font-size: 0.86em;
  text-transform: lowercase;
}

.wiki__fact dd {
  margin: 0;
  font-family: var(--mono);
  color: var(--fg-muted);
  overflow-wrap: anywhere;
}

.wiki__gap {
  grid-column: 1 / -1;
  margin: 4px 0 0;
  font-size: 0.86em;
  color: var(--fg-faint);
}

.wiki__group {
  border-bottom: 1px solid var(--line);
  padding-bottom: 4px;
}

.wiki__group-meta {
  display: grid;
  /* Indented past the marker column so a group's prose starts under its own path. */
  grid-template-columns: minmax(14rem, 1.6fr) minmax(8rem, 1fr) minmax(7rem, .7fr);
  gap: 2px 12px;
  padding: 0 0 8px 1.45rem;
}

.wiki__children {
  margin-left: 14px;
  padding-left: 10px;
  border-left: 2px solid var(--line);
}

.wiki__shapes {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(17rem, 1fr));
  gap: 12px;
}

.wiki__members {
  margin: 6px 0 0;
  padding-left: 18px;
  font-size: 0.88em;
  color: var(--fg-muted);
}

@media (max-width: 720px) {
  .wiki__model-links { padding-inline: 8px; }
  .wiki { padding-inline: 8px; }
  .wiki__columns { display: none; }
  .wiki__rows { padding-inline: 8px; }
  .wiki__row, .wiki__group-meta { grid-template-columns: auto 1fr; gap: 4px; }
  .wiki__group-meta { grid-template-columns: 1fr; padding-inline-start: 1.45rem; }
  .wiki__type { grid-row: 2; }
  .wiki__contract { grid-row: 3; }
  .wiki__children { margin-left: 6px; padding-left: 8px; }
  .wiki__path { line-height: 1.45; }
}

/* A resolved address, as opposed to one read out of the description's prose. */
.wiki__chip--address {
  border-color: var(--line-strong);
  color: var(--fg);
  font-variant-numeric: tabular-nums;
}

/* ── Assistant ─────────────────────────────────────────────────────────────────
   A chat surface built from the same tokens as every other view, so it follows
   both themes without a rule of its own. The composer is pinned below a scrolling
   transcript: the question box is the one control that must never scroll away. */

.assistant {
  display: grid;
  grid-template-rows: 1fr auto;
  gap: .75rem;
  min-height: 0;
  height: 100%;
}

.assistant__feed {
  display: flex;
  flex-direction: column;
  gap: .75rem;
  overflow-y: auto;
  min-height: 0;
  padding-right: .25rem;
}

.assistant__feed:empty { display: none; }

/* Shown only while the transcript is empty, so the view opens by explaining itself
   rather than as a blank box with a text field under it. */
.assistant__empty {
  display: grid;
  gap: .5rem;
  /* Top-aligned rather than centred: the panel is as tall as the view, and centring left
     the welcome floating in the middle of an empty box, far from the heading it belongs to
     and far from the composer it is telling you to use. */
  align-content: start;
  justify-items: start;
  padding: 1.25rem;
  border: 1px dashed var(--line-strong);
  border-radius: var(--radius-l);
  background: var(--surface-sunk);
}

.assistant__empty h2 { margin: 0; font-size: 1.05rem; }

/* Grouped rather than a flat row of chips: the labels are what make the set read as the range
   of what can be asked, instead of four questions someone happened to write down. */
.assistant__suggestions {
  display: flex;
  flex-direction: column;
  gap: .6rem;
  margin-top: .25rem;
}

.assistant__suggestion-row { display: flex; flex-wrap: wrap; gap: .4rem; }

.assistant__suggestion-label {
  margin: 0 0 .3rem;
  font-size: .75rem;
  font-weight: 600;
  letter-spacing: .04em;
  text-transform: uppercase;
  color: var(--fg-muted);
}

/* Two menus rather than a row of chips: the set grew past what a row can hold without either
   wrapping over the composer or scrolling out of sight, and the grouping is the point. Fixed
   height whatever the tools do next. */
.assistant__quick {
  display: flex;
  flex-wrap: wrap;
  gap: .35rem;
  margin-top: .4rem;
}

.assistant__quick select { max-width: 16rem; }

/* On a phone the two menus wrapped onto separate lines, spending height directly above the
   keyboard on nothing. Sharing one row keeps the composer where a thumb expects it. */
@media (max-width: 560px) {
  .assistant__quick { flex-wrap: nowrap; }
  .assistant__quick select { flex: 1 1 0; max-width: none; min-width: 0; }
}

.assistant__composer {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: .5rem;
  align-items: end;
}

.assistant__composer textarea {
  width: 100%;
  resize: vertical;
  min-height: 2.75rem;
  max-height: 12rem;
  font: inherit;
  padding: .55rem .7rem;
  color: var(--fg);
  background: var(--surface);
  border: 1px solid var(--line-strong);
  border-radius: var(--radius);
}

.assistant__composer textarea:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
}

.chat {
  display: grid;
  gap: .4rem;
  padding: .7rem .85rem;
  border: 1px solid var(--line);
  border-radius: var(--radius-l);
  background: var(--surface);
  max-width: 62rem;
}

/* The operator's own words sit on the sunk surface and to the right; the model's sit
   on the raised one. The asymmetry is what makes a transcript scannable at a glance. */
.chat--question {
  background: var(--surface-sunk);
  justify-self: end;
}

.chat--answer { border-left: 3px solid var(--accent); }
.chat--pending { opacity: .75; }
.chat--failure { border-left: 3px solid var(--bad); background: var(--bad-soft); }

.chat__role {
  display: flex;
  align-items: center;
  gap: .4rem;
  font-size: .75rem;
  font-weight: 600;
  letter-spacing: .02em;
  text-transform: uppercase;
  color: var(--fg-muted);
}

.chat__text {
  margin: 0;
  /* The answer is model prose and may carry its own line breaks; honouring them keeps a
     list the model wrote as a list. */
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

.chat__text--muted { color: var(--fg-muted); }

/* A prepared service call sits between the answer and its subject table: it is the one thing
   in a turn the operator acts on, so it is not allowed to scroll away inside either. */
.chat__draft { margin-block: .5rem; }

/* The subject table is data, not the answer: seventy-nine rows inline pushed the text that
   explains them off the top of the screen and buried the composer. It scrolls in its own box
   instead, on both axes, so the turn stays the size of a turn. */
.chat__controllers {
  overflow: auto;
  max-height: 22rem;
  border: 1px solid var(--line);
  border-radius: var(--radius);
}

.chat__controllers > summary {
  padding: .45rem .6rem;
  cursor: pointer;
  color: var(--fg-muted);
  font-size: .85rem;
}

.chat__trace { font-size: .85rem; color: var(--fg-muted); }
.chat__trace summary { cursor: pointer; }

.chat__trace-list {
  margin: .35rem 0 0;
  padding-left: 1.1rem;
  font-family: var(--mono);
  font-size: .8rem;
}

.chat__actions { display: flex; gap: .4rem; }

@media (max-width: 720px) {
  .assistant__composer { grid-template-columns: 1fr; }
  .chat { max-width: 100%; }
}

/* The progress line of a streaming answer. It retires the moment the first text fragment
   arrives, so the two never compete for the reader's attention. */
.chat__status {
  margin: 0;
  font-size: .9rem;
  color: var(--fg-muted);
}

.chat__status::after {
  content: '';
  display: inline-block;
  width: .5rem;
  height: .5rem;
  margin-left: .4rem;
  border-radius: 50%;
  background: var(--accent);
  animation: chat-pulse 1s ease-in-out infinite;
}

@keyframes chat-pulse {
  0%, 100% { opacity: .25; }
  50%      { opacity: 1; }
}

/* Respect a reader who has asked for less motion; the text still updates. */
@media (prefers-reduced-motion: reduce) {
  .chat__status::after { animation: none; opacity: .6; }
}

/* Formatted model answers. The text is built as real nodes by core/answer-format.js, so
   these style block children rather than a single pre-wrap paragraph. */
.chat__text > .chat__paragraph {
  margin: 0 0 .45rem;
  overflow-wrap: anywhere;
}

.chat__text > .chat__paragraph:last-child { margin-bottom: 0; }

/* A markdown heading, rendered as an emphasised line: a real heading inside a chat turn
   would break the page outline for a screen reader. */
.chat__paragraph--lead { font-weight: 600; }

/* Controller names are frequently Hebrew, and an answer mixes them into English prose.
   Isolating each block lets the bidi algorithm resolve a line from its own content instead
   of dragging a right-to-left name's trailing punctuation to the wrong end of it. */
.chat__paragraph,
.chat__list li,
.chat__controllers td { unicode-bidi: plaintext; }

.chat__list {
  margin: 0 0 .45rem;
  padding-left: 1.15rem;
  display: grid;
  gap: .2rem;
}

.chat__list:last-child { margin-bottom: 0; }

.chat__text code {
  font-family: var(--mono);
  font-size: .9em;
  padding: .05rem .3rem;
  border-radius: var(--radius-s);
  background: var(--surface-sunk);
}

/* The "no project chosen" notice in the assistant's empty state. */
.assistant__project-hint {
  margin: 0;
  padding: .5rem .65rem;
  border-left: 3px solid var(--warn);
  border-radius: var(--radius-s);
  background: var(--warn-soft);
  color: var(--fg);
}

/* The scope pill doubles as the project switch, so it has to read as a control rather than
   as a label that happens to be coloured. */
.pill--action {
  text-decoration: none;
  cursor: pointer;
  border-color: var(--line-strong);
}

.pill--action:hover,
.pill--action:focus-visible {
  border-color: var(--accent);
  color: var(--accent);
}

/* The problem code under a refusal: the contract, kept on screen but subordinate to the
   sentence above it that says what actually happened. */
.chat__code {
  margin: 0;
  font-family: var(--mono);
}

/* A button that reads as the link it replaced. The project hint used to navigate to the
   Projects view; it now opens the switcher dialog in place, and the affordance has to stay
   the same or the sentence stops offering anything. */
.linkish {
  padding: 0;
  border: 0;
  background: none;
  font: inherit;
  color: var(--accent);
  text-decoration: underline;
  cursor: pointer;
}

.linkish:hover { text-decoration: none; }

/* ── Assistant panel ───────────────────────────────────────────────────────────
   The Assistant view's own surface, hosted over another view instead of beside it.

   Fixed and full height rather than a floating window with a position to remember: the chat
   is a column — transcript above, composer pinned below — and a column wants the tallest
   box on screen, not the one the operator last dragged it into. The handle sits on the edge
   mid-height, clear of the toast corner it would otherwise sit under. */
.dock {
  position: fixed;
  z-index: 45;
  inset-block: 0;
  inline-size: min(460px, 94vw);
  display: flex;
  flex-direction: column;
  background: var(--surface);
  box-shadow: var(--shadow);
}

.dock--right { inset-inline-end: 0; border-inline-start: 1px solid var(--line-strong); }
.dock--left  { inset-inline-start: 0; border-inline-end: 1px solid var(--line-strong); }

.dock__head {
  display: flex;
  align-items: center;
  gap: .25rem;
  flex: none;
  padding: .5rem .625rem;
  border-block-end: 1px solid var(--line);
  background: var(--surface-sunk);
}

.dock__title { flex: 1 1 auto; margin: 0; font-size: .9375rem; }

.dock__body {
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  flex-direction: column;
  padding: .625rem;
}

/* The surface arrives carrying the view's own fill rules, including the narrow-screen
   override that turns the card into a block. In here it is always the whole panel. */
.dock__body > .card {
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  flex-direction: column;
}

/* A drawer handle, not a floating action button: it says which edge the panel comes from. */
.dock-launcher {
  position: fixed;
  z-index: 44;
  inset-inline-end: 0;
  inset-block-start: 50%;
  translate: 0 -50%;
  display: grid;
  place-items: center;
  inline-size: 2.5rem;
  block-size: 3rem;
  padding: 0;
  border: 1px solid var(--line-strong);
  border-inline-end: 0;
  border-radius: var(--radius-l) 0 0 var(--radius-l);
  background: var(--surface);
  color: var(--accent);
  box-shadow: var(--shadow);
  cursor: pointer;
}

.dock-launcher:hover { background: var(--surface-sunk); }

/* On a phone the panel is the screen, and a 460px column beside a 380px viewport is not a
   panel at all. */
@media (max-width: 40rem) {
  .dock { inline-size: 100vw; }
}

/* ── wiki tree affordance ──────────────────────────────────────────────────────
   The view was already a tree of nested <details>, and read as a flat list of rows
   because nothing said which rows opened. A disclosure triangle is the browser's
   answer and it is easy to miss at this row height, beside a monospace path that is
   itself the most eye-catching thing on the line.

   So every expandable node carries an explicit [+] / [−], in a fixed-width box that
   also reserves the same space on leaf rows — without that reservation the paths at
   one depth do not line up, and a column of paths that almost aligns is harder to
   scan than one that does not try. */
.wiki__row--group,
.wiki__model-head { list-style: none; }

.wiki__row--group::-webkit-details-marker,
.wiki__model-head::-webkit-details-marker { display: none; }

.wiki__toggle {
  display: inline-grid;
  place-items: center;
  inline-size: 1.05rem;
  block-size: 1.05rem;
  flex: none;
  border: 1px solid var(--line-strong);
  border-radius: 4px;
  background: var(--surface-sunk);
  color: var(--fg-muted);
  font-family: var(--mono);
  font-size: .72rem;
  line-height: 1;
  user-select: none;
}

.wiki__group[open] > .wiki__row--group .wiki__toggle,
.wiki__model[open] > .wiki__model-head .wiki__toggle {
  border-color: var(--accent);
  color: var(--accent);
}

/* One glyph, swapped by the open state, so nothing has to keep script and DOM in step. */
.wiki__toggle::after { content: '+'; }
.wiki__group[open] > .wiki__row--group .wiki__toggle::after,
.wiki__model[open] > .wiki__model-head .wiki__toggle::after { content: '−'; }

/* The same width a toggle occupies, so leaf paths align with the ones above them. */
.wiki__toggle--leaf {
  border-color: transparent;
  background: none;
}
.wiki__toggle--leaf::after { content: ''; }

/* The guide is what makes depth legible once several levels are open; it brightens
   with the branch the pointer is in so a deep row can be traced back to its parent. */
.wiki__children { transition: border-color .12s ease; }
.wiki__group:hover > .wiki__children { border-color: var(--line-strong); }

.wiki__count {
  flex: none;
  padding: 0 .3rem;
  border-radius: 999px;
  background: var(--surface-sunk);
  color: var(--fg-faint);
  font-size: .72rem;
  font-variant-numeric: tabular-nums;
}

/* ── controller state reference: outline and density ───────────────────────────
   The tree publishes 252 values. At full detail each one is a path, an explanation, three
   badges and four provenance facts, so a model's worth of values is several screens of
   scroll and nothing tells a reader where they are in it. Two things fix that without
   hiding anything: a density that trades the prose for a line, and an outline that names
   the groups actually on screen. */
.wiki-layout {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  align-items: start;
}

/* The outline earns a column only where one does not squeeze the tree. Below this the tree
   is already the whole width it needs, and a rail would take the space the paths use. */
@media (min-width: 1180px) {
  .wiki-layout:has(.wiki-outline__link) { grid-template-columns: 15rem minmax(0, 1fr); }
}

.wiki-outline {
  display: none;
  position: sticky;
  top: 10px;
  /* Two ceilings, and it needs both: the viewport, so a long model's outline scrolls
     inside itself rather than off the screen, and the grid row, so a short model's outline
     cannot stand taller than the tree beside it and hang out of the card. */
  max-height: min(calc(100dvh - 9rem), 100%);
  overflow-y: auto;
  scrollbar-width: thin;
  padding: 10px 10px 14px 14px;
  border-inline-end: 1px solid var(--line);
  /* Its own ground, not the card's: a sticky panel that borrows the background behind it
     shows whatever has scrolled under it once the card's own edge passes. */
  background: var(--surface);
}

@media (min-width: 1180px) {
  .wiki-outline:has(.wiki-outline__link) { display: block; }
}

.wiki-outline__model {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 8px 6px 4px;
  color: var(--fg-faint);
  font-size: .7rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .04em;
}

.wiki-outline__link {
  display: flex;
  align-items: baseline;
  gap: 8px;
  width: 100%;
  padding: 5px 8px;
  border: 0;
  border-radius: var(--radius-s);
  background: transparent;
  color: var(--fg-muted);
  font-family: var(--mono);
  font-size: .74rem;
  text-align: start;
}

.wiki-outline__link:hover { background: var(--surface-sunk); color: var(--fg); }
.wiki-outline__link[aria-current="true"] {
  background: var(--accent-soft);
  color: var(--accent);
  font-weight: 600;
}

.wiki-outline__name { flex: 1; overflow-wrap: anywhere; }
.wiki-outline__count { flex: none; color: var(--fg-faint); font-family: var(--sans, inherit); font-size: .7rem; }

/* A model heading that scrolls away takes with it the only thing saying which model the
   rows below belong to, which is the one fact a reader of five stacked models needs. */
.wiki__model-head {
  position: sticky;
  /* A sticky offset is measured from the scrollport's padding edge, and `.main` pads itself
     by this clamp - so `top: 0` would pin the header a padding's worth below the top and
     leave rows visible in the gap above it. Pulling it back by the same amount lands it
     flush, and the value is the layout's own rather than a second guess at it. */
  top: calc(-1 * clamp(1rem, .6rem + 1.2vw, 1.75rem));
  z-index: 2;
  background: var(--surface);
  border-radius: var(--radius) var(--radius) 0 0;
  /* Rows scroll behind it rather than under a hard edge, which is what says the header is
     pinned rather than that the list starts here. */
  box-shadow: 0 6px 10px -8px rgb(15 27 40 / .35);
}

/* A jump from the outline lands the row below the pinned header rather than under it. The
   offset is the header's own height plus the scrollport's padding. */
.wiki__row, .wiki__model { scroll-margin-top: 5rem; }

/* Compact: the path, its type and its provenance badges stay; the prose and the fact list
   fold away. Nothing is removed from the contract - Detailed is one click away, and the
   filter still searches the hidden text, because a reader looking for an encoding should
   still find the row that has it. */
.wiki--compact .wiki__desc,
.wiki--compact .wiki__facts { display: none; }

.wiki--compact .wiki__row { padding: 3px 0; }
.wiki--compact .wiki__badges { gap: 4px; }
.wiki--compact .wiki__group-meta { padding-block: 0; }

/* The address chips are the reason a reader opens this view at all, so density never hides
   them: an event number without its register and bit is the number they already had. */
.wiki--compact .wiki__chips { margin-top: 2px; }

/* Facet chips sit on their own row under the search: two groups of eight and three, which
   is more than a toolbar row can hold beside a text field without either wrapping into an
   unreadable ladder or squeezing the field that gets used most. */
.wiki-facets {
  display: flex;
  flex-wrap: wrap;
  gap: 6px 22px;
  padding: 0 14px 10px;
}

.wiki-facet {
  display: flex;
  align-items: center;
  gap: 8px;
  min-width: 0;
}

.wiki-facet__label {
  flex: none;
  color: var(--fg-faint);
  font-size: .7rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .04em;
}

/* A chip whose count is zero stays visible and stops being a target: "nothing here has an
   unconfirmed unit" is an answer, and a chip that vanished would read as a broken filter. */
.wiki-facet .toggle:disabled { opacity: .45; }

/* Each provenance badge names the question it answers. Without the key, "number",
   "register pair" and "unconfirmed" sit in a row reading like one sentence about the value,
   and they are three separate facts - the last of which is about the unit, not the value. */
.wiki__badge-key {
  margin-inline-end: .35em;
  color: var(--fg-faint);
  font-weight: 500;
  opacity: .85;
}

.wiki__badge--warn .wiki__badge-key { color: inherit; }

/* The badge glossary uses the value-shape layout: same kind of content - a name, why it
   exists, and what its members are - so it should not look like a different thing. */
.wiki__glossary {
  display: grid;
  gap: 14px 26px;
  grid-template-columns: repeat(auto-fit, minmax(min(260px, 100%), 1fr));
  margin-bottom: 18px;
}

.wiki__glossary .wiki__shape h3 { margin-bottom: 4px; }

/* The device address rides in the badge row, so it survives compact density. It is the fact
   the page exists to give; folding it away left "from one register" answering "which one?"
   with nothing, and left the firmware-layout selector looking inert. */
.wiki__badge--source {
  background: var(--surface);
  border: 1px solid var(--line);
  color: var(--fg-muted);
  font-family: var(--mono);
  font-size: .7em;
  white-space: normal;
}

/* ---------------------------------------------------------------- commands */
/* The command reference. Deliberately quieter than the state tree: a reader
   should be able to scan for the two things that decide whether a row is safe
   to act on — how well evidenced it is, and whether anything may send it. */

/* min-width:0 on the grid and every descendant that holds a table: a grid or flex child
   defaults to min-content width, so one wide address table stretches the whole card past the
   viewport instead of scrolling inside its own box. */
.commands { display: grid; gap: 1.25rem; min-width: 0; }
.commands > * { min-width: 0; }

/* Jumping between models beats scrolling 74 rows to reach the last one. Sticky, so the jump
   stays reachable from anywhere in the list rather than only from the top. */
.commands-jump {
  position: sticky;
  top: 0;
  z-index: 2;
  display: flex;
  flex-wrap: wrap;
  gap: .375rem;
  padding: .5rem 0;
  margin-bottom: .25rem;
  background: var(--surface);
  border-bottom: 1px solid var(--line);
}

.commands-jump a {
  display: inline-flex;
  align-items: center;
  gap: .375rem;
  padding: .1875rem .5rem;
  border: 1px solid var(--line);
  border-radius: var(--radius-s);
  color: var(--fg-muted);
  font-size: .75rem;
  text-decoration: none;
}

.commands-jump a:hover,
.commands-jump a:focus-visible { border-color: var(--accent); color: var(--accent); }
.commands-jump a span { color: var(--fg-faint); font-variant-numeric: tabular-nums; }

.command-model-group { min-width: 0; scroll-margin-top: 3rem; }

.command-model-group > h3 {
  display: flex;
  align-items: center;
  gap: .5rem;
  margin: 0 0 .5rem;
  padding-bottom: .375rem;
  border-bottom: 1px solid var(--line);
  font-size: .9375rem;
}

.command-model-count {
  padding: .0625rem .375rem;
  border-radius: var(--radius-s);
  background: var(--surface-sunk);
  color: var(--fg-muted);
  font-size: .75rem;
  font-weight: 500;
}

.command {
  border: 1px solid var(--line);
  border-radius: var(--radius-s);
  background: var(--surface);
  margin-bottom: .375rem;
  min-width: 0;
}

.command > summary {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: .5rem;
  padding: .5rem .625rem;
  cursor: pointer;
  font-size: .8125rem;
}

.command[open] > summary { border-bottom: 1px solid var(--line); }

.command-name code { font-size: .8125rem; font-weight: 600; }

.command-model,
.command-effect,
.command-target,
.command-status,
.command-flag {
  padding: .0625rem .375rem;
  border-radius: var(--radius-s);
  font-size: .6875rem;
  white-space: nowrap;
}

.command-model { background: var(--surface-sunk); color: var(--fg-muted); font-family: var(--mono); }
.command-effect { background: var(--idle-soft); color: var(--idle); }
.command-target { color: var(--fg-faint); }

/* Evidence and sendability carry colour; nothing else on the row does, so the
   two facts that decide whether a row is safe are the two that stand out. */
.command-status-published { background: var(--ok-soft); color: var(--ok); }
.command-status-confirmedNotPublished { background: var(--live-soft); color: var(--live); }
.command-status-contradicted { background: var(--warn-soft); color: var(--warn); }
.command-status-unconfirmed { background: var(--bad-soft); color: var(--bad); }
.command-status-notCommand { background: var(--surface-sunk); color: var(--fg-faint); }

.command-flag-sendable { background: var(--accent-soft); color: var(--accent); }
.command-flag-blocked {
  background: transparent;
  color: var(--fg-faint);
  border: 1px dashed var(--line-strong);
}

.command-detail { padding: .625rem; display: grid; gap: .625rem; min-width: 0; }
.command-detail > * { min-width: 0; }
.command-detail h5 { margin: 0; font-size: .75rem; color: var(--fg-muted); text-transform: uppercase; letter-spacing: .04em; }

/* Sits inside an uppercase heading and is a sentence, not a label. */
.command-detail h5 .command-stride { text-transform: none; letter-spacing: 0; }
.command-summary { margin: 0; font-size: .8125rem; text-wrap: pretty; }
.command-note { margin: 0; font-size: .8125rem; color: var(--fg-muted); }

.command-facts { display: grid; grid-template-columns: auto 1fr; gap: .1875rem .75rem; margin: 0; font-size: .8125rem; }
.command-facts dt { color: var(--fg-muted); }
.command-facts dd { margin: 0; }
.command-unit { color: var(--fg-muted); font-family: var(--mono); font-size: .75rem; }

.command-preconditions,
.command-steps { margin: 0; padding-left: 1.125rem; font-size: .8125rem; display: grid; gap: .1875rem; }

/* The address table is the one thing here that can genuinely be wider than the card, so it
   scrolls inside its own box and the page never does. */
.command-layout { min-width: 0; max-width: 100%; overflow-x: auto; }

/* Hugs its content rather than stretching: seven columns spread across the full card width
   put a finger-width of nothing between a register and the layout it belongs to. The wrapper
   scrolls when the content is genuinely wider than the card. */
.command-addresses {
  width: auto;
  border-collapse: collapse;
  font-size: .75rem;
  font-variant-numeric: tabular-nums;
}
.command-addresses th {
  text-align: left;
  padding: .1875rem 1.25rem .1875rem 0;
  color: var(--fg-muted);
  font-weight: 500;
  white-space: nowrap;
  border-bottom: 1px solid var(--line);
}

.command-addresses td {
  padding: .25rem 1.25rem .25rem 0;
  white-space: nowrap;
  border-bottom: 1px solid var(--line);
}

.command-addresses th:last-child,
.command-addresses td:last-child { padding-right: 0; }
.command-addresses tr:last-child td { border-bottom: 0; }

.command-stride { color: var(--fg-muted); font-size: .75rem; font-weight: 400; }

.command-absent { color: var(--fg-faint); font-style: italic; }
