/* Styles shared by the gateway pages.

   It comes from the same origin, so it is not an external dependency: the page
   keeps working on a VPS with no way out to the internet. It sits outside the
   HTML so the dashboard and the policy page do not drift apart visually over
   time.
*/
/* Dark mode only. `color-scheme` also tells the browser to paint what we do
     not control dark: scrollbars and native controls. */
  :root {
    color-scheme: dark;
    /* Always reserve the width of the scrollbar. Without this, a page that does
       not overflow makes the bar disappear, the viewport widens by 15px and the
       centred content shifts 7px: turning a page made everything jump
       sideways. */
    scrollbar-gutter: stable;
    --background: #16161a; --panel: #1d1d22; --border: #2e2e35; --text: #e6e6e3;
    --muted: #9a9a92; --ok: #5bbd85; --bad: #e2776a; --waiting: #d4b24c;
    --accent: #7aa5e0;
  }
  * { box-sizing: border-box; }
  /* The browser ships `[hidden] { display: none }`, but any rule of our own
     carrying `display` beats it on specificity: `.field { display: block }` was
     making the form's hidden fields visible. */
  [hidden] { display: none !important; }
  body {
    margin: 0; padding: 1.5rem; background: var(--background); color: var(--text);
    font: 14px/1.5 ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
  }
  main { max-width: 1000px; margin: 0 auto; }
  /* `baseline` and not `center`. The row mixes sizes --title 1.05rem, status
     and buttons .78rem-- and centring the BOXES leaves each text resting on a
     different baseline: measured on the dashboard, the title fell 2.8px below
     the rest, which is enough to look untidy without it being clear why. On a
     shared baseline the spread is 0 and the header grows by 1px.
     (An earlier comment here said `baseline` left the buttons floating relative
     to the title; it was measured and it does not happen: the button rests its
     label on the same line as everything else.) */
  header { display: flex; align-items: baseline; gap: .75rem; flex-wrap: wrap; margin-bottom: 1.25rem; }

  /* Navigation between pages. It is a link, but it looks and behaves like a
     button: the border gives it a click area and separates it from the status
     text, which it used to blend into. */
  .nav {
    font-size: .78rem; line-height: 1.6; padding: .2rem .7rem; border-radius: 5px;
    color: var(--muted); background: var(--panel); border: 1px solid var(--border);
    text-decoration: none; white-space: nowrap;
  }
  .nav:hover { color: var(--accent); border-color: var(--accent); }
  .nav:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
  /* The page title. At 1.05rem it competed on equal terms with the status and
     the buttons beside it, and the header read as a row of things of the same
     weight instead of a title with accessories. */
  h1 { font-size: 1.3rem; margin: 0; letter-spacing: .01em; }
  h2 { font-size: .8rem; margin: 1.75rem 0 .6rem; color: var(--muted);
       text-transform: uppercase; letter-spacing: .08em; font-weight: 600; }
  .muted { color: var(--muted); font-size: .8rem; }
  .panel { background: var(--panel); border: 1px solid var(--border); border-radius: 8px; overflow: hidden; }
  .row { display: flex; gap: .75rem; align-items: center; padding: .6rem .85rem;
          border-top: 1px solid var(--border); }
  .row:first-child { border-top: none; }
  /* `align-self` because the spacer is empty: with no baseline of its own, the
     browser synthesises one from its 0px box and shifts the whole row. */
  .grow { flex: 1; min-width: 0; align-self: center; }

  /* No horizontal scroll, ever. It used to be `overflow-x: auto` here, which
     traded one problem for a worse one: a single long value -- a conversation
     id, an error, a match object -- made the table wider than its panel and
     the whole thing scrolled sideways. That is not a narrow-screen problem. It
     happens at any width, because nothing capped a cell.

     `table-layout: fixed` is what caps it: the table is exactly 100% of its
     panel and the column widths come from the `<colgroup>`, not from the
     content, so a long value can no longer widen anything. What it does
     instead is run out of room, and every cell is set up to end in an
     ellipsis when it does. */
  .scroll table { table-layout: fixed; }
  table { width: 100%; border-collapse: collapse; }
  th {
    text-align: left; font-weight: 600; font-size: .68rem; letter-spacing: .06em;
    text-transform: uppercase; color: var(--muted); padding: .5rem .7rem;
    border-bottom: 1px solid var(--border); white-space: nowrap;
  }
  /* `nowrap` plus these two is the ellipsis: a value too long for its column is
     cut with a `...` instead of wrapping the row to two lines. The full text is
     in the cell's `title`. */
  td { padding: .45rem .7rem; border-top: 1px solid var(--border); white-space: nowrap; }
  td, th { overflow: hidden; text-overflow: ellipsis; }
  tbody tr.clickable { cursor: pointer; }
  tbody tr.clickable:hover td { background: color-mix(in srgb, var(--border) 35%, transparent); }
  td.tag { width: 1.6rem; padding-right: 0; }
  /* Now just a marker: the column that takes whatever the sized ones leave, by
     having no width in the `<colgroup>`. It used to carry `width: 100%` and
     `max-width: 0`, which was the trick that let ONE column truncate under
     automatic layout -- and under fixed layout it would claim the whole table
     and squash every other column to nothing. Truncation is global now. */
  td.elastic { width: auto; }
  /* `th` too: without it the header of a numeric column sits on the left and
     its values on the right, misaligned with each other. */
  td.right, th.right { text-align: right; }
  tr.detail td { padding: 0 .7rem .7rem; border-top: none; white-space: normal; }
  td.error { color: var(--bad); }
  td.success { color: var(--ok); }
  td.waiting { color: var(--waiting); }
  td.info { color: var(--accent); }

  .actions { display: flex; gap: .5rem; align-items: center; padding: .5rem 0 0; }
  button {
    font: inherit; font-size: .78rem; padding: .25rem .7rem; border-radius: 5px;
    color: var(--text); background: var(--panel); border: 1px solid var(--border);
    cursor: pointer;
  }
  button:hover:not(:disabled) { border-color: var(--bad); color: var(--bad); }
  button:disabled { opacity: .5; cursor: default; }
  .hint { font-size: .78rem; color: var(--muted); }

  th.sortable { cursor: pointer; user-select: none; }
  th.sortable:hover { color: var(--text); }
  /* The arrow is ALWAYS in the DOM and only changes opacity. If it appeared
     and disappeared, its width (~8px) would widen the active column and shift
     the following ones on every click. */
  th .arrow { font-size: .7em; margin-left: .2rem; opacity: 0; }
  /* Only the active one shows. The inactive one used to be hinted at on hover
     to anticipate the first click, but with the third state that was confusing:
     on removing the sorting with the mouse over the header, the translucent
     arrow landed where the solid one had been and the sequence read
     down arrow, up arrow, down arrow, as if descending had come back. What the
     next click does is what the `title` says. */
  th .arrow.active { opacity: .7; }
  #summary { display: flex; align-items: center; gap: 1rem; padding: .45rem .7rem;
             border-top: 1px solid var(--border); color: var(--muted); font-size: .76rem; }
  .pager { display: flex; align-items: center; gap: .6rem; margin-left: auto; }
  .pager button { padding: .15rem .55rem; font-size: .76rem; }
  .pager button:hover:not(:disabled) { border-color: var(--accent); color: var(--accent); }
  .mono { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: .82rem; }
  .truncate { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  /* `inline-block` is essential: inside a cell the span is inline and
     width/height are ignored, so the dot is not drawn. In the previous flex
     layout it was not needed, because the children of a flex box are blocks. */
  .dot { display: inline-block; width: .55rem; height: .55rem; border-radius: 50%;
           flex: none; background: var(--muted); vertical-align: middle; }
  /* Scoped to `.dot`: they paint the BACKGROUND, so left loose they were
     filling any element that reused the class (a solid amber cell, for
     instance). The text colour comes from `td.success` / `td.waiting` /
     `td.error`. */
  .dot.ok { background: var(--ok); }
  .dot.bad { background: var(--bad); }
  .dot.waiting { background: var(--waiting); }
  .chip { font-size: .72rem; padding: .1rem .45rem; border-radius: 4px;
          border: 1px solid var(--border); color: var(--muted); white-space: nowrap; }
  .num { font-variant-numeric: tabular-nums; }
  a { color: var(--accent); }
  #error { display: none; margin-bottom: 1rem; padding: .6rem .85rem; border-radius: 6px;
           border: 1px solid var(--bad); color: var(--bad); font-size: .85rem; }
  .empty { padding: .85rem; color: var(--muted); font-size: .85rem; }
  details summary { cursor: pointer; }
  /* With a height cap: a long JSON must not push the rest of the table off the
     screen. */
  pre { margin: .5rem 0 0; padding: .6rem; background: var(--background);
        border-radius: 6px; overflow: auto; max-height: 22rem; font-size: .78rem; }

/* Secondary section heading, for the blocks inside a page. */
h2.sub { font-size: .72rem; margin-top: 1.1rem; }

/* --- Sign in ------------------------------------------------------------ */

.centered { display: flex; min-height: 80vh; align-items: center; justify-content: center; }
.card { width: 100%; max-width: 22rem; padding: 1.5rem; }
.card h1 { font-size: 1.3rem; margin: 0 0 .35rem; }
.card p { margin: 0 0 1.1rem; font-size: .82rem; }
.field { display: block; margin-bottom: .8rem; }
.field span { display: block; font-size: .74rem; margin-bottom: .25rem; }
.field input {
  width: 100%; padding: .4rem .55rem; font: inherit; font-size: .86rem;
  color: var(--text); background: var(--background);
  border: 1px solid var(--border); border-radius: 5px;
}
.field input:focus { outline: 2px solid var(--accent); outline-offset: -1px; }
.card button { width: 100%; padding: .45rem; margin-top: .3rem; }
.card button:hover:not(:disabled) { border-color: var(--accent); color: var(--accent); }
.notice-error {
  margin: 0 0 .8rem; padding: .5rem .6rem; border-radius: 5px;
  border: 1px solid var(--bad); color: var(--bad); font-size: .8rem;
}

/* --- Adding policy rules ------------------------------------------------ */

/* `min()` and not a fixed width: on a phone a 28rem modal runs off the screen
   and the form becomes unreachable sideways. */
.modal {
  width: min(27rem, calc(100vw - 2rem)); padding: 0;
  color: var(--text); background: var(--panel);
  border: 1px solid var(--border); border-radius: 10px;
}
.modal::backdrop { background: rgb(0 0 0 / .6); }
.modal h2 { margin: 0 0 .9rem; }
.form { padding: 1.15rem 1.25rem 1.25rem; }
/* Agent and action are short and go together; destination and note take the full width. */
.form .pair { display: flex; gap: .7rem; }
.form .pair .field { flex: 1; min-width: 0; }
/* Los controles de casilla quedan afuera a proposito: esta regla es para
   campos de texto, y aplicada a un radio o un checkbox lo estira a todo el
   ancho y le pone borde y padding de input. El sintoma es el control dibujado
   solo en el centro de una fila vacia, con su etiqueta empujada fuera del
   dialogo. */
.form select,
.form input:not([type="radio"], [type="checkbox"]) {
  width: 100%; padding: .35rem .5rem; font: inherit; font-size: .84rem;
  color: var(--text); background: var(--background);
  border: 1px solid var(--border); border-radius: 5px;
}
.form select:focus, .form input:focus {
  outline: 2px solid var(--accent); outline-offset: -1px;
}
.form .actions { padding-top: 1rem; }

/* Note: it is capped and cut with an ellipsis; the whole text goes in the
   cell's `title`. The width has to be set by the block inside -- in a table with
   automatic layout the `max-width` of a <td> does not hold it back. */
/* `max-width: 100%` and not a fixed 17rem: the column now has a width of its
   own, and a cap narrower than it would truncate earlier than it has to. */
td.note > span { display: block; max-width: 100%; overflow: hidden; text-overflow: ellipsis; }

/* What is about to be deleted, in the confirmation dialog. It sits on the page
   background and not on the panel's: sunken, it reads as "this is the data" and
   not as one more paragraph of the dialog. */
.rule-detail {
  margin: 0 0 .9rem; padding: .65rem .75rem; border-radius: 7px;
  background: var(--background); border: 1px solid var(--border);
}
.rule-detail .line { display: flex; align-items: center; gap: .5rem; flex-wrap: wrap; }
.rule-detail .arrow { color: var(--muted); }
.rule-detail .destination { color: var(--text); font-size: .88rem; word-break: break-all; }
.rule-detail .chip { text-transform: uppercase; letter-spacing: .05em; }
/* The colour says which kind of rule it is without having to read: green for
   what grants, blue for what blocks, just like Completed and Blocked on the
   dashboard. */
.chip.allow { color: var(--ok); border-color: color-mix(in srgb, var(--ok) 50%, transparent); }
.chip.deny { color: var(--accent); border-color: color-mix(in srgb, var(--accent) 50%, transparent); }

.rule-detail .note-box {
  display: flex; gap: .6rem; margin-top: .6rem; padding-top: .6rem;
  border-top: 1px solid var(--border);
}
.rule-detail .label {
  flex: none; padding-top: .1rem; font-size: .64rem; text-transform: uppercase;
  letter-spacing: .07em; color: var(--muted);
}
/* It wraps instead of truncating --this is where it is read in full-- but with
   a cap: a very long note must not push the buttons off the screen. */
#rv-note {
  font-size: .8rem; color: var(--muted); overflow-wrap: anywhere;
  max-height: 7rem; overflow-y: auto;
}
.form p.hint { margin: 0; }
/* Destructive button: red from the start and not only on hover. What deletes
   has to look different from what does not, before it is touched. */
.danger { color: var(--bad); border-color: color-mix(in srgb, var(--bad) 55%, transparent); }
.danger:hover:not(:disabled) {
  color: var(--bad); border-color: var(--bad);
  background: color-mix(in srgb, var(--bad) 14%, transparent);
}
.danger:focus-visible { outline: 2px solid var(--bad); outline-offset: 1px; }
/* The default hover is red, which is right for cancelling and wrong for the
   primary action. */
.primary:hover:not(:disabled) { border-color: var(--accent); color: var(--accent); }

/* Row action. It is still a button --with a border and a click area-- and not
   an underlined link, which inside a table reads as "go to another page". It is
   kept muted so it does not compete with the data, but visible without hover:
   if it only appeared on hover, on touch it would never appear.

   Shape and colour are separate on purpose. `.row-action` is the shape; the
   red belongs to `.remove`, which is a modifier. They used to be one rule, and
   the first row action that did not delete anything inherited the red from it
   -- which is precisely the signal the palette is supposed to reserve. */
td.row-actions { width: 1%; padding-left: 0; }
button.row-action {
  padding: .1rem .5rem; font-size: .72rem; line-height: 1.5; letter-spacing: .02em;
  color: var(--muted); background: transparent;
  border-color: color-mix(in srgb, var(--border) 70%, transparent);
}
tbody tr:hover button.row-action { border-color: var(--border); }
/* Blue: the same answer as every other thing on the page you can click. */
button.row-action:hover:not(:disabled) {
  color: var(--accent); border-color: var(--accent);
  background: color-mix(in srgb, var(--accent) 12%, transparent);
}
button.row-action:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
/* Red is what deletes, and only that. */
button.row-action.remove:hover:not(:disabled) {
  color: var(--bad); border-color: var(--bad);
  background: color-mix(in srgb, var(--bad) 12%, transparent);
}
button.row-action.remove:focus-visible { outline-color: var(--bad); }
/* The header button shares the look of `.nav`, which is an <a>. */
button.nav:hover:not(:disabled) { color: var(--accent); border-color: var(--accent); }

/* --- Account ------------------------------------------------------------ */

/* The username, next to the button that changes its password. */
.account { font-size: .78rem; }
/* Confirmation of an action that leaves no visible trace on the page: without
   this, changing the password closes a dialog and nothing else happens. */
.notice-ok {
  margin: 0 0 1rem; padding: .6rem .85rem; border-radius: 6px; font-size: .85rem;
  color: var(--ok); border: 1px solid color-mix(in srgb, var(--ok) 55%, transparent);
  background: color-mix(in srgb, var(--ok) 10%, transparent);
}

/* A freshly issued secret. It goes in a <pre> and not in a disabled input so it
   can be selected by hand where the clipboard is not available.

   Ordinary selection, deliberately. It used to be `user-select: all`, so that
   one click would take the whole value -- but a click then selected everything
   whether that was wanted or not, and a fully selected block can be dragged,
   which made reading it feel like being one gesture away from breaking it. The
   button next to it is what takes the whole value now, and selecting half of a
   connection string by hand works again. */
.secret {
  margin: 0; white-space: pre-wrap; overflow-wrap: anywhere;
  max-height: none; border: 1px solid var(--border);
}

/* Label on the left, its button on the right, on the same baseline: the button
   belongs to this block and not to the dialog, which already has its own. */
.copyable + .copyable { margin-top: .9rem; }
.copyable-head {
  display: flex; align-items: baseline; justify-content: space-between; gap: .6rem;
}
.copyable-head .cred-label { margin: 0 0 .3rem; }
/* Fixed width so "Copy" turning into "Copied" does not shift the label. */
.copyable-head button { flex: none; min-width: 7.5rem; }


/* --- Scope of an API key ------------------------------------------------ */

/* A fieldset and not loose divs: the two radios are one choice, and a legend
   is what says so to a screen reader without inventing an aria attribute. */
.scope {
  margin: 0 0 .8rem; padding: .7rem .8rem .8rem; border-radius: 7px;
  border: 1px solid var(--border); background: var(--background);
}
.scope legend {
  padding: 0 .35rem; font-size: .64rem; text-transform: uppercase;
  letter-spacing: .07em; color: var(--muted);
}
.choice { display: flex; gap: .5rem; align-items: baseline; padding: .15rem 0; cursor: pointer; }
.choice input { flex: none; }
/* `min-width: 0` porque el default de un flex item es `auto`: sin esto el
   texto no puede encogerse por debajo de su ancho natural y desborda. */
.choice > span { min-width: 0; }
/* The consequence sits under the label, muted: it is what the option MEANS,
   and putting it in a tooltip would hide it from exactly the person choosing. */
.choice em { display: block; font-style: normal; font-size: .72rem; color: var(--muted); }

/* Indented under its radio, so it reads as belonging to "Only these" and not
   as a third option. Scrolls: a deployment with thirty agents must not push
   the buttons off the screen. */
.agents {
  display: flex; flex-direction: column; gap: .1rem;
  margin: .4rem 0 0 1.45rem; max-height: 11rem; overflow-y: auto;
}
.agents .agent {
  display: flex; gap: .45rem; align-items: baseline;
  font-size: .82rem; cursor: pointer;
}
.agents .agent input { flex: none; }

/* --- The docs page ------------------------------------------------------ */

/* The only page that is a document and not a screen, so it is the only one
   that needs prose. Narrower than the rest on purpose: `main` is 1000px, which
   is right for a table and far too wide for a paragraph -- past roughly 75
   characters the eye loses the start of the next line. */
.doc { max-width: 46rem; padding-bottom: 4rem; }
.doc p { line-height: 1.65; margin: 0 0 1rem; }
/* The one-line answer to "what is this", before any heading. */
.doc .lead { font-size: 1rem; color: var(--text); margin-bottom: 1.8rem; }
/* `h2` is uppercase and tiny elsewhere, where it labels a panel. Here it opens
   a section that gets read, so it goes back to looking like a heading. */
.doc h2 {
  font-size: 1.05rem; color: var(--text); text-transform: none;
  letter-spacing: 0; margin: 2.2rem 0 .8rem;
  padding-top: 1.1rem; border-top: 1px solid var(--border);
}
.doc h3 { font-size: .9rem; color: var(--text); margin: 1.5rem 0 .5rem; }
/* Lists carry the same line height as the paragraphs around them; a denser one
   reads as a different kind of content rather than as part of the argument. */
.doc ul { margin: 0 0 1rem; padding-left: 1.15rem; }
.doc li { line-height: 1.65; margin-bottom: .5rem; }
/* The bullet is punctuation, not content. */
.doc li::marker { color: var(--muted); }
.doc strong { color: var(--text); font-weight: 600; }
/* Inline code sits inside a sentence: a border would break the line's rhythm,
   so it gets a tint instead. */
.doc code {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: .86em;
  padding: .08rem .3rem; border-radius: 4px;
  background: color-mix(in srgb, var(--muted) 16%, transparent);
}
/* A frame around every block. The background alone separated code from prose
   only where the two happened to differ in tone; a border says where the block
   starts and ends regardless, which matters most on the long examples, where
   the top and the bottom are not on screen together. */
.doc pre {
  margin: 0 0 1.2rem; padding: .9rem 1rem; line-height: 1.55; max-height: none;
  border: 1px solid var(--border); border-radius: 7px;
}
/* The diagrams are `pre` too, and they get the same frame: next to a framed
   example an unframed one reads as something that failed to render rather than
   as a different kind of thing. */
.doc pre.flow { background: color-mix(in srgb, var(--panel) 60%, var(--background)); }
/* A block already has its own background; the inline tint would double up. */
.doc pre code { padding: 0; background: none; font-size: 1em; }
/* The diagram is art, not code: no wrapping and no shrinking. */
.doc .flow { color: var(--muted); font-size: .76rem; line-height: 1.5; }
.doc table { margin: 0 0 1.2rem; }
.doc td, .doc th { white-space: normal; vertical-align: top; }
.doc td:first-child { white-space: nowrap; }
/* An aside: true, and not part of the argument you are following. Marked by a
   rule down the side rather than a box, which would read as a warning.

   Scoped to `.doc` deliberately. `.notice` on its own has no styling anywhere
   -- only `.notice-error` and `.notice-ok` do -- so the seven uses of it on
   /policy and /config render as ordinary paragraphs today. Styling the bare
   class would silently restyle those two pages as well. */
.doc .notice {
  margin: 0 0 1.2rem; padding-left: .8rem;
  border-left: 2px solid var(--border);
  color: var(--muted); font-size: .92em;
}
.doc .notice code { font-size: .9em; }

/* --- Syntax highlighting ------------------------------------------------ */

/* Its own tokens and not the status palette: `--ok` means "this finished well"
   on the dashboard, and a string is not that. Tying the two would mean a change
   to one silently repainting the other. */
:root {
  --tok-comment: #7e8a97;
  --tok-string:  #93c48b;
  --tok-keyword: #b48ce0;
  --tok-literal: #d4b24c;
  --tok-builtin: #6fb3c9;
  --tok-number:  #d99a6c;
  --tok-name:    #7aa5e0;
}
/* Comments are the part you skip on a second read; italic says so without
   spending a colour. */
.tok-com { color: var(--tok-comment); font-style: italic; }
.tok-str { color: var(--tok-string); }
.tok-kw  { color: var(--tok-keyword); }
.tok-lit { color: var(--tok-literal); }
.tok-bif { color: var(--tok-builtin); }
.tok-num { color: var(--tok-number); }
.tok-dec { color: var(--tok-literal); }
.tok-key { color: var(--tok-name); }
.tok-var { color: var(--tok-number); }
.tok-flag { color: var(--tok-builtin); }

/* --- The language picker ------------------------------------------------ */

/* Hidden, not removed: `display: none` would take them out of the tab order,
   and then the picker could only be used with a mouse.
   Not positioned either -- it sits inside its own label, so leaving it in flow
   at zero size keeps it exactly where the click lands, and the browser has
   nowhere to scroll to. */
.lang-pick {
  width: 0; height: 0; margin: 0; padding: 0; border: 0;
  opacity: 0; pointer-events: none;
}
label.lang {
  display: inline-block; margin: 0 .3rem .9rem 0;
  font-size: .76rem; padding: .2rem .7rem; border-radius: 5px;
  color: var(--muted); background: var(--panel);
  border: 1px solid var(--border); cursor: pointer; user-select: none;
}
label.lang:hover { color: var(--accent); border-color: var(--accent); }
.langs { margin: 0 0 1rem; display: flex; align-items: baseline; gap: .1rem; flex-wrap: wrap; }
.langs-legend {
  font-size: .64rem; text-transform: uppercase; letter-spacing: .07em;
  color: var(--muted); margin-right: .5rem;
}
/* The radio lives inside its label, which is also what removes the `for`/`id`
   pair: a label wrapping its control is already associated with it. */
label.lang:has(.lang-pick:checked) {
  color: var(--accent); border-color: var(--accent);
  background: color-mix(in srgb, var(--accent) 12%, transparent);
}
/* The focus ring has to land on the label, since the input itself is invisible.
   Without this the picker is unusable by keyboard: you would be moving through
   controls you cannot see. */
label.lang:has(.lang-pick:focus-visible) { outline: 2px solid var(--accent); outline-offset: 2px; }

/* One choice switches every example on the page.

   Through `:has()` on the container and not a sibling combinator from the
   input. The sibling version needed every radio at the very top of the
   document, and clicking a label focuses its radio: with the radio off-screen
   the browser scrolled the page to it, so picking a language threw the reader
   back to the top. Asking the container instead lets each radio sit where its
   label is, which is where the click already happened.

   Python shows without `:has()` having to match, so a browser that does not
   support it still renders a readable page -- one language instead of a
   picker, rather than no examples at all. */
.example .ex { display: none; }
.example .ex-python { display: block; }

.doc:has(.pick-javascript:checked) .ex-python,
.doc:has(.pick-bash:checked) .ex-python { display: none; }
.doc:has(.pick-javascript:checked) .ex-javascript,
.doc:has(.pick-bash:checked) .ex-bash { display: block; }

/* An example with no shell version falls back to Python rather than showing an
   empty gap, and a note in its place says why. It comes last, and it is more
   specific than the rule that hides Python above. */
.doc:has(.pick-bash:checked) .no-bash .ex-python { display: block; }


/* --- What a registration is still missing ------------------------------- */

/* Amber and not red: nothing is broken, something is unfinished. The same
   colour the `waiting` status uses, for the same reason. */
.setup {
  margin: 0 0 1.4rem; padding: .85rem 1rem 1rem; border-radius: 8px;
  background: var(--panel);
  border: 1px solid color-mix(in srgb, var(--waiting) 45%, transparent);
}
.setup h2 { margin: 0 0 .6rem; font-size: .95rem; color: var(--waiting); }
/* Grid and not flex: the subjects line up in a column, so the eye reads down
   the list of what is affected instead of hunting for the name in each row. */
.setup .item {
  display: grid; grid-template-columns: max-content 1fr max-content;
  align-items: baseline; gap: .55rem; padding: .3rem 0; font-size: .82rem;
}
.setup .item + .item { border-top: 1px solid var(--border); }
.setup .what {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace; color: var(--text);
  overflow-wrap: anywhere;
}
.setup .why { color: var(--muted); }
/* An agent missing several steps is one entry with several reasons, so its
   name is written once. */
.setup .reason { margin: 0; }
.setup .reason + .reason { margin-top: .25rem; }
/* The button sits on the baseline of a two-line reason too, so it does not
   drift down the row as the text wraps. */
.setup .row-action { align-self: center; }

/* One column on a phone: three of them turn the reason into a two-word
   column with the button hanging off the edge. */
@media (max-width: 34rem) {
  .setup .item { grid-template-columns: 1fr; gap: .15rem; }
  .setup .row-action { justify-self: start; margin-top: .3rem; }
}


/* --- Credentials issued with a registration ----------------------------- */

/* The name of what is below it, not a heading: two of these stack in one
   dialog and a real heading would compete with the dialog's own. */
.cred-label {
  margin: .9rem 0 .3rem; font-size: .68rem; text-transform: uppercase;
  letter-spacing: .07em; color: var(--muted);
}
.cred-label:first-child { margin-top: 0; }
/* Wider than the forms. This dialog shows configuration lines, not fields: at
   27rem each connection string wraps over three lines, which pushed the signing
   secret below the fold -- and that is the one whose absence later looks like a
   401 from a wrong secret. */
#dialog-credentials { width: min(42rem, calc(100vw - 2rem)); }
/* It still scrolls if it has to, so the button that dismisses it is always
   reachable: closing it is the only way to lose the secrets, and it must never
   be off-screen. */
/* The margin is here and not on the label: the label carries `margin-top: 0`
   as the first of a stack, and without this it sits flush against the sentence
   above it, which belongs to the dialog and not to the block. */
#cred-body { margin-top: .9rem; max-height: min(32rem, 70vh); overflow-y: auto; }


/* --- Columns a narrow screen does without ------------------------------- */

/* Not scrolling sideways is only half the fix. Nine columns in a 495px panel
   do fit -- the table caps itself -- but they fit as `J...`, `ST...` and dates
   read `2...`, which is a table that no longer says anything. So the ones that
   are historical totals, or that the row can be understood without, come out.
   `display: none` on the `<col>` is what collapses the column and hands its
   share to the one that absorbs the slack; the `th` and the `td` need it too,
   or the remaining cells shift one column to the left. */
@media (max-width: 44rem) {
  .scroll col.minor, .scroll th.minor, .scroll td.minor { display: none; }
}


/* --- What a retirement takes, and what it leaves ------------------------ */

/* No bullet and no indent: these are two short inventories inside a dialog,
   and a bulleted list there reads as prose to be skimmed rather than as the
   two columns of a decision. */
.plain { margin: 0 0 .2rem; padding: 0; list-style: none; }
.plain li { padding: .12rem 0; font-size: .82rem; color: var(--muted); }
/* The half that is irreversible gets the colour; the half that survives does
   not need one. */
#rt-gone li { color: var(--text); }
#rt-gone li::before { content: "\2212\00a0"; color: var(--bad); }
#rt-kept li::before { content: "\2713\00a0"; color: var(--ok); }


/* --- The dashboard's pointer at what is unfinished ---------------------- */

/* One line and not the panel: the panel with its buttons lives on the Agents
   page, where things are changed. What the dashboard owes is the signal. */
.setup-line {
  margin: 0 0 1.2rem; padding: .55rem .8rem; border-radius: 7px;
  font-size: .82rem; color: var(--muted); background: var(--panel);
  border: 1px solid color-mix(in srgb, var(--waiting) 45%, transparent);
}
.setup-line a { color: var(--accent); }


/* --- What feeds an agent ------------------------------------------------ */

.source-row { padding: .55rem 0; }
.source-row + .source-row { border-top: 1px solid var(--border); }
/* The name, its match and its button on one line; the secret, once asked for,
   below. */
.source-head { display: flex; align-items: center; gap: .55rem; font-size: .84rem; }
.source-head .muted { font-size: .76rem; overflow-wrap: anywhere; }
.source-head .chip { text-transform: uppercase; letter-spacing: .05em; }
#sr-body { max-height: min(26rem, 60vh); overflow-y: auto; margin-bottom: .8rem; }
#dialog-sources { width: min(40rem, calc(100vw - 2rem)); }


/* Two histories, one panel. The tab that is on reads as the heading it
   replaced -- there is no title above them, because the labels are the
   title. */
.tabs { display: flex; gap: .35rem; align-items: baseline; margin: 1.6rem 0 .6rem; }
.tabs button {
  background: none; border: none; padding: 0 0 .15rem;
  font-size: 1rem; font-weight: 600; color: var(--muted); cursor: pointer;
  border-bottom: 2px solid transparent;
}
.tabs button[aria-selected="true"] { color: var(--text); border-bottom-color: var(--accent); }
.tabs button:hover:not([aria-selected="true"]) { color: var(--text); }
.tabs button:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.tabs .divider { color: var(--muted); opacity: .5; }
