
:root {
  --bg: #12141a; --panel: #1a1d26; --panel-2: #21252f; --line: #2c313d;
  --text: #e6e8ee; --dim: #9aa2b1; --accent: #6ea8fe;
  --ok: #4ade80; --warn: #fbbf24; --bad: #f87171;
  --mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
}
/* Follow the OS, unless the config says otherwise. The :not() is what lets an
   explicit dark choice win on a machine set to light. */
@media (prefers-color-scheme: light) {
  :root:not([data-theme="dark"]) {
  --bg: #f6f7f9; --panel: #fff; --panel-2: #f0f2f5; --line: #d9dee7;
  --text: #1a1d26; --dim: #5b6472; --accent: #1a56db;
  --ok: #15803d; --warn: #b45309; --bad: #b91c1c;
}
}
:root[data-theme="light"] {
  --bg: #f6f7f9; --panel: #fff; --panel-2: #f0f2f5; --line: #d9dee7;
  --text: #1a1d26; --dim: #5b6472; --accent: #1a56db;
  --ok: #15803d; --warn: #b45309; --bad: #b91c1c;
}
* { box-sizing: border-box; }
body {
  margin: 0; background: var(--bg); color: var(--text);
  font: 15px/1.5 system-ui, -apple-system, Segoe UI, Roboto, sans-serif;
}
a { color: var(--accent); }
header {
  display: flex; align-items: center; gap: 1.5rem; flex-wrap: wrap;
  padding: .9rem 1.25rem; background: var(--panel); border-bottom: 1px solid var(--line);
}
header h1 { font-size: 1rem; margin: 0; letter-spacing: .02em; display: flex; align-items: center; gap: .5rem; }
header h1 span { color: var(--dim); font-weight: 400; }
.logo { width: 22px; height: 22px; flex: none; color: var(--accent); }
nav { display: flex; gap: .25rem; flex: 1; flex-wrap: wrap; }
nav a {
  padding: .35rem .7rem; border-radius: 6px; text-decoration: none;
  color: var(--dim); font-size: .9rem;
}
nav a:hover { background: var(--panel-2); color: var(--text); }
nav a.on { background: var(--panel-2); color: var(--text); }
main { max-width: 1100px; margin: 0 auto; padding: 1.5rem 1.25rem 2rem; }
footer {
  max-width: 1100px; margin: 0 auto; padding: 1rem 1.25rem 2.5rem;
  border-top: 1px solid var(--line); color: var(--dim); font-size: .85rem;
  display: flex; flex-wrap: wrap; gap: .3rem 1.1rem; align-items: baseline;
}
h2 { font-size: 1.05rem; margin: 2rem 0 .75rem; }
h2:first-child { margin-top: 0; }
p.note { color: var(--dim); font-size: .9rem; margin: .35rem 0 1rem; }
.panel {
  background: var(--panel); border: 1px solid var(--line);
  border-radius: 10px; padding: 1rem 1.1rem; margin-bottom: 1rem;
}
.grid { display: grid; gap: .85rem; grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); }
.card { background: var(--panel); border: 1px solid var(--line); border-radius: 10px; padding: .9rem 1rem; }
.card h3 { margin: 0 0 .4rem; font-size: .95rem; display: flex; align-items: center; gap: .5rem; }
.card dl { margin: .5rem 0 0; display: grid; grid-template-columns: auto 1fr; gap: .2rem .75rem; font-size: .88rem; }
.card dt { color: var(--dim); }
.card dd { margin: 0; word-break: break-word; }
/* The structured fields under a log message. Dimmer and smaller than the
   message so a scan still reads as one line per event. */
.fields { margin: .25rem 0 0; display: grid; grid-template-columns: auto 1fr; gap: .1rem .6rem; font-size: .8rem; color: var(--dim); }
.fields dt { opacity: .8; }
.fields dd { margin: 0; word-break: break-word; }
.svc-icon { width: 20px; height: 20px; flex: none; color: var(--dim); }
/* Collapsed, a card is one row: mark, id, host, what it may write. Open, it is
   the form it always was. The marker is drawn here because a summary set to
   display:flex loses the browser's own triangle. */
.svc-title { display: flex; align-items: center; gap: .5rem; margin: 0; cursor: pointer; list-style: none; }
.svc-title::-webkit-details-marker { display: none; }
.svc-title::after {
  content: ''; flex: none; width: .45rem; height: .45rem; margin-left: .25rem;
  border-right: 2px solid var(--dim); border-bottom: 2px solid var(--dim);
  transform: rotate(45deg); transition: transform .15s ease;
}
.svc[open] > .svc-title { margin-bottom: .75rem; }
.svc[open] > .svc-title::after { transform: rotate(-135deg); }
@media (prefers-reduced-motion: reduce) { .svc-title::after { transition: none; } }
.svc-host {
  color: var(--dim); font-size: .85em; margin-left: auto;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0;
}
.svc-writes {
  flex: none; font-size: .75rem; padding: .1rem .45rem; border-radius: 999px;
  border: 1px solid var(--line); background: var(--panel-2); color: var(--dim);
}
/* Status to the right edge, so the dots line up down the grid rather than
   sitting at eight different offsets after eight different service names. */
.card h3 .dot { margin-left: auto; }
.dot { width: .6rem; height: .6rem; border-radius: 50%; display: inline-block; flex: none; }
.dot.ok { background: var(--ok); } .dot.bad { background: var(--bad); } .dot.off { background: var(--dim); }
.remedy {
  margin-top: .6rem; padding: .5rem .6rem; border-left: 3px solid var(--warn);
  background: var(--panel-2); border-radius: 0 6px 6px 0; font-size: .85rem; color: var(--text);
}
table { width: 100%; border-collapse: collapse; font-size: .87rem; }
/* A health message, a mount path, a URL: one long unbroken value in a cell is
   enough to push the whole page sideways on a phone. overflow-wrap is
   "anywhere" rather than "break-word" because only that value lets the cell's
   min-content width shrink, which is the part that stops the overflow. */
th, td {
  text-align: left; padding: .45rem .6rem; border-bottom: 1px solid var(--line);
  vertical-align: top; overflow-wrap: anywhere;
}
th { color: var(--dim); font-weight: 500; position: sticky; top: 0; background: var(--panel); }
td.mono, .mono { font-family: var(--mono); font-size: .85em; }
tr.lvl-40 td { color: var(--warn); } tr.lvl-50 td, tr.lvl-60 td { color: var(--bad); }
.scroll { max-height: 65vh; overflow: auto; border: 1px solid var(--line); border-radius: 10px; background: var(--panel); }
form.inline { display: flex; gap: .5rem; flex-wrap: wrap; align-items: end; margin-bottom: 1rem; }
label { display: block; font-size: .85rem; color: var(--dim); margin-bottom: .2rem; }
input, select, textarea {
  background: var(--panel-2); color: var(--text); border: 1px solid var(--line);
  border-radius: 7px; padding: .45rem .6rem; font: inherit; min-width: 0;
}
input:focus, select:focus, textarea:focus { outline: 2px solid var(--accent); outline-offset: -1px; }
input[type=checkbox] { min-width: auto; }
button {
  background: var(--accent); color: #fff; border: 0; border-radius: 7px;
  padding: .5rem .9rem; font: inherit; cursor: pointer;
}
button.ghost { background: var(--panel-2); color: var(--text); border: 1px solid var(--line); }
button:hover { filter: brightness(1.1); }
.row { display: flex; gap: .5rem; align-items: center; flex-wrap: wrap; }
.msg { padding: .7rem .9rem; border-radius: 8px; margin-bottom: 1rem; font-size: .9rem; }
.msg.ok { background: #14351f; color: #b7f7cd; }
.msg.err { background: #3a1618; color: #ffc9cc; white-space: pre-wrap; }
@media (prefers-color-scheme: light) {
  .msg.ok { background: #dcfce7; color: #14532d; }
  .msg.err { background: #fee2e2; color: #7f1d1d; }
}
.login { max-width: 380px; margin: 12vh auto; }
.login .panel { padding: 1.5rem; }
.field { margin-bottom: .9rem; }
.field input { width: 100%; }
.repair textarea { width: 100%; min-height: 26rem; font-family: var(--mono); font-size: .8rem; }
fieldset { border: 1px solid var(--line); border-radius: 10px; padding: .9rem 1rem; margin: 0 0 1rem; }
legend { padding: 0 .4rem; color: var(--dim); font-size: .85rem; }
.svc-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); gap: .75rem; }
/* What type=password used to do, minus the part that made every password
   manager fill the card as a login form. Chrome, Edge and Safari have had the
   prefixed property for years; Firefox since 116. Where it is unsupported the
   field is a plain text input, which is the honest failure — the server never
   renders a secret into it, so nothing is ever on screen that was not just
   typed. */
.secret { -webkit-text-security: disc; }
dialog {
  background: var(--panel); color: var(--text); border: 1px solid var(--line);
  border-radius: 10px; padding: 0; max-width: 560px; width: calc(100% - 2rem);
}
dialog::backdrop { background: rgba(0, 0, 0, .6); }
dialog .panel { margin: 0; border: 0; border-radius: 10px; max-height: 82vh; overflow: auto; }
.token { display: flex; gap: .5rem; align-items: center; margin-bottom: .5rem; flex-wrap: wrap; }
/* Wraps rather than shrinking: squeezed onto one phone-width line, the field
   holding the token is narrower than the two buttons beside it, and a token
   you cannot see any of is worse than one on its own line. */
.token input { flex: 1 1 14rem; font-family: var(--mono); font-size: .8rem; }
#mcp-config { width: 100%; margin-top: .6rem; font-size: .78rem; white-space: pre; }
.dim { color: var(--dim); }

/* --- the write audit ---------------------------------------------------
   Seven columns, one of them a JSON blob of arguments, was unreadable before
   it was unresponsive — no width makes that table scannable. An entry per
   attempt puts the answer on the first line (what happened, which tool, when)
   and moves the arguments underneath, where their length costs nothing. */
.trail { display: grid; gap: .6rem; }
.entry {
  background: var(--panel); border: 1px solid var(--line);
  border-radius: 10px; padding: .75rem .9rem;
}
.entry-top { display: flex; align-items: baseline; gap: .5rem; flex-wrap: wrap; }
.entry-top .tool { font-weight: 600; }
.entry-top time { margin-left: auto; color: var(--dim); font-size: .8rem; }
.entry dl {
  margin: .55rem 0 0; display: grid; grid-template-columns: auto 1fr;
  gap: .15rem .75rem; font-size: .86rem;
}
.entry dt { color: var(--dim); }
.entry dd { margin: 0; overflow-wrap: anywhere; }
.badge {
  font-size: .72rem; text-transform: uppercase; letter-spacing: .04em;
  padding: .1rem .45rem; border-radius: 999px; white-space: nowrap;
  border: 1px solid var(--line); background: var(--panel-2); color: var(--dim);
}
/* Only the outcomes worth stopping on are coloured. A preview and a dry run
   are the system working, and colouring those too would leave nothing for a
   refusal or a half-finished write to stand out against. */
.badge.applied { color: var(--ok); border-color: currentColor; }
.badge.denied, .badge.attempted { color: var(--warn); border-color: currentColor; }
.badge.failed { color: var(--bad); border-color: currentColor; }

/* --- small screens -----------------------------------------------------
   The body is one column at any width already. What breaks on a phone is the
   header, where a title, four nav links and Sign out share one wrapping flex
   row and land wherever they happen to fall. */
@media (max-width: 700px) {
  header { gap: .6rem .75rem; padding: .75rem .9rem; }
  header h1 { flex: 1; }
  /* A row of its own, under the title and Sign out. Moved with "order" rather
     than in the markup, because on a wide screen the nav genuinely does belong
     between those two, and the source order is the reading order. */
  nav { order: 1; flex-basis: 100%; gap: .3rem; }
  /* Stretched to fill the line: four links of unequal length wrapping across
     two rows read as debris otherwise. Smaller and tighter so that on most
     phones they fit on one row at all. */
  nav a { flex: 1 1 auto; text-align: center; padding: .55rem .55rem; font-size: .85rem; }
  main { padding: 1.25rem .9rem 1.5rem; }
  footer { padding: 1rem .9rem 2.5rem; }
  .panel, .card, .entry { padding: .8rem .85rem; }
  /* 16px is the threshold under which iOS Safari zooms the page in on focus —
     and it does not zoom back out, so one tap on a field leaves you scrolled
     sideways across a form you were only trying to type in. */
  input, select, textarea, .token input, #mcp-config { font-size: 16px; }

  /* Tables whose last column is prose, which is the logs and the dashboard's
     problems and nothing else. Four columns split about 340px on a phone, so
     the three narrow headings wrap letter by letter — "Le/ve/l" — and the
     message keeps what is left. The write audit reached this first and
     answered it by leaving the table for cards; this is that answer applied
     without a second set of markup, because the log table is rendered twice
     (server-side here, client-side in JS) and any card built out of elements
     would have to be built twice too.

     Opt-in by class, not a rule on every table: disks, scans and the IMDb
     status are three short columns each, already readable, worse as cards. */
  .stacked, .stacked tbody, .stacked tr, .stacked td { display: block; }
  .stacked thead { display: none; }
  .stacked tr { padding: .7rem .85rem; border-bottom: 1px solid var(--line); }
  .stacked tr:last-child { border-bottom: 0; }
  .stacked td { border: 0; padding: 0; }
  /* The short cells rejoined into one meta line above the message. Selected by
     position rather than by a class, because "the prose column is the last
     one" is true of both tables and putting it in the markup would hand the
     two renderers another thing to disagree about.

     The :where wrapper keeps this at zero specificity so tr.lvl-40 td still
     colours a warning row. Without it the level cell of a warning renders
     dim, and the level is the one word on that line worth colouring. */
  .stacked td:where(:not(:last-child)) { display: inline; font-size: .8rem; color: var(--dim); }
  .stacked td:where(:not(:last-child)) + td:where(:not(:last-child))::before { content: ' · '; }
  .stacked td:where(:last-child) { margin-top: .35rem; }
}
