/*
 * One page, one stylesheet, in the gateway bench's look: most of the rules
 * below are copied from it, so the two benches read as one family.
 *
 * Sizes, dims and gaps are three scales: pick a step, never write a fresh
 * number. A fact a machine wrote -- a serial, a topic, a payload -- is set in
 * the mono face.
 *
 * This file is downloaded by whoever opens the page, comments included.
 */

:root {
  --ink: #17191c;
  --ink-quiet: #4a4f57;
  --ink-faint: #767c86;
  --page: #f7f8fa;
  --card: #ffffff;
  --edge: #dfe3e8;
  --accent: #1a5fb4;
  --bad: #b3261e;
  --good: #1c7c48;
  --waiting: #8a5a00;

  --text-caption: 0.75rem;
  --text-small: 0.85rem;
  --text-body: 0.95rem;
  --text-lead: 1.1rem;
  --text-title: 1.5rem;

  /* One height for everything on a form row. Saying it once is what stops a
     button drifting away from the fields beside it. */
  --control: 34px;

  --gap-xs: 4px;
  --gap-s: 8px;
  --gap-m: 12px;
  --gap-l: 20px;
  --gap-xl: 32px;

  --font-mono: 'IBM Plex Mono', ui-monospace, SFMono-Regular, Menlo, monospace;
  --font: system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
}

*, *::before, *::after { box-sizing: border-box; }

body {
  margin: 0;
  padding: var(--gap-xl) var(--gap-l);
  background: var(--page);
  color: var(--ink);
  font-family: var(--font);
  font-size: var(--text-body);
  line-height: 1.5;
}

main, .masthead { max-width: 1400px; margin: 0 auto; }
.masthead { padding: 0 calc(var(--gap-l) + 1px); }
.masthead h1 { font-size: var(--text-title); margin: 0 0 var(--gap-xs); font-weight: 600; }

/* The link to the manual, in the title's row. Hidden until the manual
   answers, so the rule that shows it must not undo `hidden`.

   It shares a box with the account, and the box centres them. As a sibling of
   the account it sat at the top of the row, a few pixels above the name and
   the buttons beside it. The size is the account's own. */
.corner { display: flex; align-items: center; flex-wrap: wrap; justify-content: flex-end; gap: var(--gap-s) var(--gap-m); }
.manual { white-space: nowrap; font-size: var(--text-small); }
.manual[hidden] { display: none; }
.manual { color: var(--accent); text-decoration: none; }
.manual:hover { text-decoration: underline; }
.lede { margin: 0 0 var(--gap-l); color: var(--ink-quiet); max-width: 70ch; }
/* The masthead's one row: the title and the lede on the left, who you are on
   the right. `.row` ends its children at the bottom, which would drop the
   account block to the foot of a two-line title. */
.row.top { align-items: flex-start; }
/* **On a phone it goes under the title instead.** A name, a role and two
   buttons do not fit beside a title at 375px: a flex item shrinks before it
   wraps, so without this the row kept them all on one line and each button's
   label wrapped inside a box of one height, which is a label spilling out of
   its button. Giving the title the whole line is what makes the row wrap.
   The wired bench met the same thing on the same day. */
@media (max-width: 700px) {
  .masthead .grow { flex: 1 1 100%; }
  /* The space under goes on the corner, not on the account: `.corner`
     centres its items, so a margin on the account alone lifted Sign in
     half a margin above Help (seen on a phone, 2026-10-15). */
  .account { flex-wrap: wrap; }
  .corner { margin-bottom: var(--gap-m); }
}

/* **The account as one button and a menu, on a phone** (asked for on
   2026-10-04). The rule above gives a visitor's two words a line of their
   own, which is right for a visitor. Somebody signed in had four lines there:
   Help, the name and role, and two buttons that wrapped. Now the title and
   one button share a line, and the rest opens under the button.

   `.corner-items` wraps Help and the account. On a wide screen it has no box
   of its own, so the row is laid out as it was. */
.corner-items { display: contents; }
.account-open { display: none; }
@media (max-width: 700px) {
  /* The title lines up with the words of the tabs under it. */
  .masthead { padding: 0 var(--gap-m); }
  .masthead.signed-in .row.top { flex-wrap: nowrap; align-items: center; margin-bottom: var(--gap-s); }
  .masthead.signed-in .grow { flex: 1 1 0; min-width: 0; }
  .masthead.signed-in h1 { margin: 0; }
  /* The sentence under the title is for somebody who has not seen the page. */
  .masthead.signed-in .lede { display: none; }
  /* Signed in, the corner shares its line with the title, so the space
     under it that a visitor's corner has would lift it off the title's
     centre. */
  .corner.has-menu { position: relative; flex-wrap: nowrap; margin-bottom: 0; }
  .corner.has-menu .account-open { display: inline-flex; align-items: center; gap: var(--gap-s); white-space: nowrap; }
  .corner.has-menu .corner-items {
    display: none;
    position: absolute;
    right: 0;
    top: calc(100% + var(--gap-xs));
    z-index: 20;
    flex-direction: column;
    align-items: stretch;
    min-width: 13rem;
    padding: var(--gap-xs);
    background: var(--card);
    border: 1px solid var(--edge);
    border-radius: 8px;
    box-shadow: 0 8px 24px rgba(23, 25, 28, 0.14);
  }
  .corner.has-menu.is-open .corner-items { display: flex; }
  /* The account's own box goes, so its parts and Help are ordered as one list. */
  .corner.has-menu .account { display: contents; }
  .corner.has-menu .who {
    order: 1;
    margin: 0 0 var(--gap-xs);
    padding: var(--gap-s) var(--gap-m);
    border-bottom: 1px solid var(--edge);
  }
  .corner.has-menu #my-password-open { order: 2; }
  .corner.has-menu .manual { order: 3; }
  .corner.has-menu #sign-out { order: 4; }
  .corner.has-menu .corner-items button,
  .corner.has-menu .corner-items .manual {
    display: block;
    width: 100%;
    height: auto;
    padding: var(--gap-s) var(--gap-m);
    border: none;
    border-radius: 6px;
    background: none;
    color: var(--accent);
    font-size: var(--text-body);
    text-align: left;
  }
}

.panel {
  background: var(--card);
  border: 1px solid var(--edge);
  border-radius: 8px;
  padding: var(--gap-l);
  margin-bottom: var(--gap-l);
}

.field { display: block; margin-bottom: var(--gap-m); }
.label { display: block; font-size: var(--text-small); color: var(--ink-quiet); margin-bottom: var(--gap-xs); }
.hint { color: var(--ink-faint); font-weight: 400; }

textarea, input[type=text], input[type=email], input[type=password], input[type=number], input[type=date], input[type=time], select {
  width: 100%;
  font-family: var(--font-mono);
  font-size: var(--text-small);
  color: var(--ink);
  background: var(--card);
  border: 1px solid var(--edge);
  border-radius: 6px;
  padding: var(--gap-s) var(--gap-m);
}
textarea { resize: vertical; display: block; }
/* One height for a field and a button, so a row of them lines up. */
input[type=text], input[type=email], input[type=password], input[type=number], input[type=date], input[type=time], select, button {
  height: var(--control);
}
select { font-family: var(--font); cursor: pointer; }

textarea:focus-visible, input:focus-visible, select:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
  border-color: var(--accent);
}

.row { display: flex; gap: var(--gap-m); align-items: flex-end; flex-wrap: wrap; }
.row .field { margin-bottom: 0; }
.narrow { width: 160px; }
.grow { flex: 1 1 240px; }
.serial-field { width: 12rem; }
.choice-field { width: 14rem; }

/* A checkbox is not a text field and must not be stretched to look like one. */
.tick { display: flex; align-items: center; gap: var(--gap-s); height: var(--control); }
.tick input { width: auto; height: auto; }
.tick span { font-size: var(--text-small); color: var(--ink-quiet); }

button {
  font: inherit;
  font-size: var(--text-small);
  padding: 0 var(--gap-l);
  border: 1px solid transparent;
  border-radius: 6px;
  background: var(--accent);
  color: #fff;
  cursor: pointer;
}
button:hover { filter: brightness(1.08); }
button[disabled] { opacity: 0.6; cursor: default; }
button[disabled]:hover { filter: none; }
button.quiet {
  background: var(--card);
  color: var(--accent);
  border-color: var(--edge);
}
button.quiet:hover { filter: none; border-color: var(--accent); }
button.small { height: 28px; padding: 0 var(--gap-m); }
button:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
button.danger { background: var(--bad); }
button.quiet.danger { background: var(--card); color: var(--bad); }
button.quiet.danger:hover { border-color: var(--bad); }

.detail { margin: var(--gap-m) 0 var(--gap-l); font-size: var(--text-small); color: var(--ink-quiet); }
:is(h1, h2, h3) + .detail { margin-top: calc(-1 * var(--gap-s)); }
.detail:first-child { margin-top: 0; }

.refusal {
  background: var(--card);
  border: 1px solid var(--bad);
  border-left-width: 4px;
  border-radius: 6px;
  padding: var(--gap-m) var(--gap-l);
  margin-bottom: var(--gap-l);
  color: var(--bad);
}
/* Under the field it is about, in a form: the form's own space follows. */
form .refusal { margin: var(--gap-s) 0 var(--gap-m); }

/* The risk of this bench, said beside the gateway it is about. Amber: it is
   not a fault, it is a thing to know before pressing anything. */
.warning {
  border: 1px solid var(--waiting);
  border-left-width: 4px;
  border-radius: 6px;
  padding: var(--gap-s) var(--gap-m);
  margin: var(--gap-m) 0;
  color: var(--waiting);
  font-size: var(--text-small);
}

.heading { font-size: var(--text-lead); font-weight: 600; margin: var(--gap-xl) 0 var(--gap-m); }
.heading-small { font-size: var(--text-body); font-weight: 600; margin: 0 0 var(--gap-s); }
/* A panel's heading with a button beside it. The heading takes the width of
   its words and no more, so the two stay on one line while they fit and the
   button goes under only when they do not. `.row.top` with `.grow` was used
   here first, and its 240px starting width sent the button under the heading
   on every phone (seen 2026-10-15). */
.panel-head { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: var(--gap-s) var(--gap-m); margin-bottom: var(--gap-s); }
.panel-head .heading-small { margin: 0; }
:is(.heading, .heading-small) + .empty { margin-top: 0; }
.empty { color: var(--ink-faint); font-size: var(--text-small); margin: var(--gap-s) 0 var(--gap-m); }
.mono { font-family: var(--font-mono); }

/* The title goes back to the start page, and does not look like a link. */
.home-link { color: inherit; text-decoration: none; }

/* ------------------------------------------------------------ a gateway */

.gateway-head { display: flex; align-items: center; gap: var(--gap-m); flex-wrap: wrap; }
.gateway-serial { font-size: var(--text-title); font-weight: 600; margin: 0; }

/* The state's word, coloured: green awake, amber on its way, grey asleep, red
   with no network. */
.badge {
  font-size: var(--text-caption);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  padding: 1px var(--gap-s);
  border-radius: 999px;
  border: 1px solid currentColor;
  white-space: nowrap;
}
.badge.is-AWAKE, .badge.is-TASK { color: var(--good); }
.badge.is-CONNECTING { color: var(--waiting); }
.badge.is-ASLEEP, .badge.is-STOPPED { color: var(--ink-faint); }
.badge.is-NO_NETWORK { color: var(--bad); }

/* A name and its value on one line, the names in a column of their own. */
.facts, .gateway-part dl {
  display: grid;
  grid-template-columns: max-content minmax(0, 1fr);
  gap: 2px var(--gap-m);
  margin: var(--gap-m) 0 0;
  font-size: var(--text-small);
}
.facts dt, .gateway-part dt { color: var(--ink-quiet); }
.facts dd, .gateway-part dd { margin: 0; overflow-wrap: anywhere; }
.gateway-part dl { margin: 0; }

.actions-row { margin-top: var(--gap-m); align-items: center; }
.actions-row .end { margin-left: auto; }

/* No network is a state of the gateway's place, so it is a switch and not a
   button: it stays as it is set. */
.switch { display: inline-flex; align-items: center; gap: var(--gap-s); height: var(--control); cursor: pointer; font-size: var(--text-small); color: var(--ink-quiet); }
.switch input {
  appearance: none;
  width: 34px;
  height: 20px;
  margin: 0;
  border-radius: 999px;
  background: var(--edge);
  position: relative;
  cursor: pointer;
  transition: background 0.1s;
}
.switch input::after {
  content: '';
  position: absolute;
  top: 2px;
  left: 2px;
  width: 16px;
  height: 16px;
  border-radius: 50%;
  background: var(--card);
  box-shadow: 0 1px 2px rgb(0 0 0 / 0.25);
  transition: left 0.1s;
}
.switch input:checked { background: var(--bad); }
.switch input:checked::after { left: 16px; }
.switch input:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

.link-row { display: flex; gap: var(--gap-m); align-items: flex-end; margin-top: var(--gap-l); flex-wrap: wrap; }
.link-row .field { margin-bottom: 0; }

/* Four parts side by side where there is room, two and two under that, one
   under another on a phone. */
.gateway-parts {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: var(--gap-m) var(--gap-xl);
}
@media (max-width: 1100px) { .gateway-parts { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--gap-m) var(--gap-l); } }
/* Filter lists and tasks: two parts, side by side. */
.gateway-parts.two { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.gateway-part .empty { margin: 0; }
@media (max-width: 700px) { .gateway-parts, .gateway-parts.two { grid-template-columns: minmax(0, 1fr); } }
.gateway-part h3 {
  margin: 0 0 var(--gap-xs);
  font-size: var(--text-caption);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--ink-faint);
  font-weight: 600;
}

/* ------------------------------------------------------------ your gateways */

/* One card for each gateway: as many side by side as fit, one in each row on
   a phone. A card is a panel of its own, so it carries its own edge. */
.cards {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
  gap: var(--gap-m);
}
@media (max-width: 700px) { .cards { grid-template-columns: minmax(0, 1fr); } }
.card {
  display: flex;
  flex-direction: column;
  gap: var(--gap-s);
  min-width: 0;
  border: 1px solid var(--edge);
  border-radius: 8px;
  padding: var(--gap-m);
  background: var(--card);
}
.card-head { display: flex; align-items: center; justify-content: space-between; gap: var(--gap-s); }
.card-serial { font-size: var(--text-lead); font-weight: 600; color: var(--ink); text-decoration: none; }
.card-serial:hover { text-decoration: underline; }
.card .facts { margin: 0; }
/* The newest warning, in full: a sentence that wraps, never one that is cut. */
.card .card-warning { margin: 0; overflow-wrap: anywhere; }

/* Why the last wake could not connect. Red, like a refusal: the gateway did
   not reach its broker. Apart from the warnings, which are amber and are
   about the platform under test. The original text wraps anywhere: it is one
   long word, such as an MQTT return code. */
.connect-error {
  border: 1px solid var(--bad);
  border-left-width: 4px;
  border-radius: 6px;
  padding: var(--gap-s) var(--gap-m);
  margin: var(--gap-m) 0;
  color: var(--bad);
  font-size: var(--text-small);
  overflow-wrap: anywhere;
}
.connect-error .connect-error-detail { display: block; margin-top: var(--gap-xs); color: var(--ink-quiet); font-size: var(--text-caption); }
.card .card-connect-error { margin: 0; }

/* Back to the home page, above the gateway, at the left. */
.gateway-back { margin-bottom: var(--gap-m); }
/* Connect, why it cannot be pressed, and Open at the end of the row. The row
   stays at the foot of the card, so the buttons of cards side by side line up. */
.card-actions { display: flex; align-items: center; flex-wrap: wrap; gap: var(--gap-s); margin-top: auto; padding-top: var(--gap-xs); }
.card-why { font-size: var(--text-small); }
.card-open { margin-left: auto; color: var(--accent); text-decoration: none; font-size: var(--text-small); }
.card-open:hover { text-decoration: underline; }

/* The warnings of one gateway: the time does not wrap, the sentence does. */
#g-warnings .listing td.at { white-space: nowrap; }
#g-warnings .listing { margin-bottom: 0; }
#g-warnings .listing td.sentence { white-space: normal; overflow-wrap: anywhere; }
/* On a phone three columns leave the sentence no room, so each warning is
   two lines: the time and the count, then the sentence under them. */
@media (max-width: 700px) {
  #g-warnings .listing, #g-warnings .listing tbody { display: block; }
  #g-warnings .listing thead { display: none; }
  #g-warnings .listing tr { display: grid; grid-template-columns: minmax(0, 1fr) auto; border-bottom: 1px solid var(--edge); }
  #g-warnings .listing tbody tr:last-child { border-bottom: none; }
  #g-warnings .listing td { border-bottom: none; }
  #g-warnings .listing td.at { color: var(--ink-quiet); padding-bottom: 0; }
  #g-warnings .listing td.count { color: var(--ink-quiet); padding-bottom: 0; }
  #g-warnings .listing td.count::before { content: 'Count '; }
  #g-warnings .listing td.sentence { grid-column: 1 / -1; }
}

/* ---------------------------------------------------------------- tables */

.sheet { max-width: 100%; }
.listing {
  width: 100%;
  border-collapse: collapse;
  background: var(--card);
  border: 1px solid var(--edge);
  border-radius: 8px;
  font-size: var(--text-small);
  margin-bottom: var(--gap-m);
}
.listing th, .listing td {
  text-align: left;
  padding: var(--gap-s) var(--gap-m);
  border-bottom: 1px solid var(--edge);
  vertical-align: middle;
}
.listing th {
  font-size: var(--text-caption);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--ink-faint);
  font-weight: 600;
  white-space: nowrap;
}
.listing tbody tr:last-child td { border-bottom: none; }
/* On a phone the People table puts the email under the name, and hides its
   own column, so the table fits the width without being cut at the right
   (asked for on 2026-10-15). On a wide screen nothing changes. */
.listing .under { display: none; }
@media (max-width: 700px) {
  .listing .email-col { display: none; }
  .listing .under { display: block; color: var(--ink-quiet); overflow-wrap: anywhere; }
}
.listing .action { text-align: right; width: 1%; white-space: nowrap; }
.listing td button + button { margin-left: var(--gap-s); }
.listing td.bad { color: var(--bad); }
.listing tr.is-skipped td { opacity: 0.5; }
.listing a { color: var(--accent); text-decoration: none; }
.listing a:hover { text-decoration: underline; }

/* ------------------------------------------------------------------ log */

/* The heading of the log and its two buttons, on one line and centred on
   it, as a panel's heading is. `.row.top` lifted the buttons above the
   heading's middle, so they read as part of the section above (2026-10-15). */
.log-head { margin-top: var(--gap-xl); }
.log-head .heading { margin: 0; }
.head-buttons { display: flex; align-items: center; gap: var(--gap-s); }

/* Narrowing the log by topic: the gateway bench's chips. A label, its chips,
   and Clear filters at the right, level with the first row of chips. */
.sift {
  display: grid;
  grid-template-columns: max-content 1fr max-content;
  gap: var(--gap-s) var(--gap-m);
  align-items: baseline;
  margin-bottom: var(--gap-m);
}
.sift-label { grid-column: 1; font-size: var(--text-small); color: var(--ink-quiet); white-space: nowrap; }
.chips { grid-column: 2; display: flex; gap: var(--gap-xs); flex-wrap: wrap; align-items: center; }
#raw-clear { grid-column: 3; grid-row: 1; justify-self: end; align-self: center; }
/* Invisible rather than gone, so the chips do not move when the first is chosen. */
#raw-clear.idle { visibility: hidden; }
/* A chip is a toggle, so it looks pressed rather than having a tick. */
.chip {
  font: inherit;
  font-size: var(--text-caption);
  font-family: var(--font-mono);
  height: auto;
  padding: 2px var(--gap-s);
  border: 1px solid var(--edge);
  border-radius: 999px;
  background: var(--card);
  color: var(--ink-quiet);
  cursor: pointer;
}
.chip:hover { filter: none; border-color: var(--accent); }
.chip[aria-pressed="true"] { background: var(--accent); border-color: var(--accent); color: #fff; }
.chip:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.log {
  background: var(--card);
  border: 1px solid var(--edge);
  border-radius: 8px;
  font-size: var(--text-small);
  max-height: 22rem;
  overflow: auto;
}
.log table { width: 100%; border-collapse: collapse; }
.log th, .log td {
  text-align: left;
  padding: var(--gap-xs) var(--gap-m);
  border-bottom: 1px solid var(--edge);
  vertical-align: top;
}
.log tr:last-child td { border-bottom: none; }
.log th {
  position: sticky;
  top: 0;
  /* Opaque, or the rows show through it as they pass underneath. */
  background: var(--card);
  /* **And an explicit one, or being opaque is not enough.** A sticky cell is a
     positioned box, but under `border-collapse: collapse` the browser paints
     cells in the table's own order and the scrolling rows came out on top: the
     header had text bleeding through it, found by looking closely at a log long
     enough to scroll. */
  z-index: 1;
  /* The rule under the head, as a shadow rather than a border: a collapsed
     border belongs to the table rather than to the cell, so it stays behind
     while the cell travels. */
  box-shadow: inset 0 -1px 0 var(--edge);
  font-size: var(--text-caption);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--ink-faint);
  font-weight: 600;
  white-space: nowrap;
}
.log td.at, .log td.topic {
  font-family: var(--font-mono);
  color: var(--ink-faint);
  white-space: nowrap;
}
/* The payload takes the width that is left and wraps: a line that scrolled
   sideways would hide the end of the one thing the row carries. */
.log td.payload { font-family: var(--font-mono); word-break: break-all; width: 100%; }
/* The (i) of a line: as narrow as its icon, and no taller than the row's text. */
.log :is(th, td).explain { padding-left: 0; padding-right: 0; width: 1%; }
.log button.explains { height: auto; padding: 1px 2px; vertical-align: top; border: none; background: none; color: var(--ink-faint); }
.log button.explains:hover { color: var(--accent); }
/* The (i) after a field's name or a column's: what its values mean. */
/* Centred on the words beside it: the icon is taller than a label's text, so
   aligning it to the text's bottom lifted it 5px (seen 2026-10-15). A label
   with an (i) lays out its words and the icon as one centred row. */
.label:has(> button.explains) { display: inline-flex; align-items: center; gap: 2px; }
:is(.label, th) button.explains { height: auto; padding: 0 2px; line-height: 0; border: none; background: none; color: var(--ink-faint); text-transform: none; }
:is(.label, th) button.explains svg { display: block; }
th button.explains { vertical-align: middle; }
:is(.label, th) button.explains:hover { color: var(--accent); }
/* A note is not a message: Note where the topic goes, and its sentence in
   the colour of a warning and in the page's own letters, not as JSON. */
.log tr.is-note td.topic, .log tr.is-note td.payload { color: var(--waiting); }
.log tr.is-note td.payload { font-family: var(--font); }
#raw-shown { margin-top: var(--gap-s); }

/* The tooltip of a line's (i): a name and a value on each row, as in the
   gateway bench. Fixed to the window, because the log scrolls and would cut
   it. It takes no pointer, so it is never in the way of the row under it. */
.tip {
  position: fixed;
  z-index: 1100;
  max-width: min(34rem, calc(100vw - 16px));
  padding: var(--gap-s) var(--gap-m);
  border-radius: 6px;
  background: var(--ink);
  color: var(--page);
  font-size: var(--text-caption);
  line-height: 1.45;
  pointer-events: none;
  box-shadow: 0 2px 10px rgb(0 0 0 / 0.25);
}
.tip[hidden] { display: none; }
/* In the top layer a browser centres a box and gives it a border and a
   scrollbar. This one is placed by the page and has neither. */
.tip[popover] { inset: auto; margin: 0; border: 0; overflow: visible; }
.tip p { margin: 0; }
/* What the message is, in one line, over the rows of what it carries. */
.tip p.about { font-weight: 600; }
.tip p.about:not(:last-child) { margin-bottom: var(--gap-xs); }
.tip dl { display: grid; grid-template-columns: auto 1fr; gap: 2px var(--gap-m); margin: 0; }
.tip dt { opacity: 0.7; white-space: nowrap; }
.tip dd { margin: 0; overflow-wrap: anywhere; }
/* How it was delivered, under what it carries, and quieter, as the names are. */
.tip p.delivery { opacity: 0.7; }
.tip :not(.delivery) + p.delivery { margin-top: var(--gap-xs); }
.log + .empty { margin-top: var(--gap-s); }

/* ---------------------------------------------------------------- windows */

.window {
  border: 1px solid var(--edge);
  border-radius: 8px;
  padding: var(--gap-l);
  width: min(36rem, calc(100vw - 2 * var(--gap-l)));
  color: var(--ink);
  background: var(--card);
}
.window::backdrop { background: rgba(23, 25, 28, 0.35); }
.window .row.actions { justify-content: flex-end; margin-top: var(--gap-l); }
.window .refusal { margin-bottom: 0; }

/* `[hidden]` is only a `display: none` in the browser's own sheet, so a rule
   that sets `display` would cancel it silently. */
[hidden] { display: none !important; }

/* ------------------------------------------------------------- on a phone */

@media (max-width: 900px) {
  body { padding: var(--gap-l) var(--gap-m); }
  .serial-field { width: 100%; }
  .narrow { width: calc(50% - var(--gap-m) / 2); }
  /* A table wider than a phone scrolls inside its own box, never the page. */
  .listing { display: block; overflow-x: auto; white-space: nowrap; }
  .choice-field { width: calc(50% - var(--gap-m) / 2); }
}

/* On a phone a line of the log is two rows: the time, the topic and the
   (i), then the JSON under them as wide as the box. Four columns left the
   JSON a few letters wide. */
@media (max-width: 700px) {
  .log thead { display: none; }
  .log tr { display: flex; flex-wrap: wrap; column-gap: var(--gap-s); padding: var(--gap-xs) var(--gap-s); border-bottom: 1px solid var(--edge); }
  .log tr:last-child { border-bottom: none; }
  .log td { display: block; padding: 0; border: none; }
  .log td.topic { white-space: normal; word-break: break-all; }
  .log td.payload { flex: 1 1 100%; }
}

/* Who you are, in the corner of the masthead. Two words for a visitor, because
   signing in is optional and the page must not read as though it is not. */
/* **Who you are, opposite the title** (the wired bench's masthead). No bottom
   margin any more: it sits beside the lede, which carries the space under the
   whole header. */
.account { display: flex; align-items: center; gap: var(--gap-s); }
.account .who {
  display: flex;
  align-items: center;
  gap: var(--gap-s);
  font-size: var(--text-small);
  color: var(--ink-quiet);
  margin-right: var(--gap-xs);
}
/* The role, as a chip. `.role` and not `.badge`: `.badge` is a gateway's
   state, upper case and coloured. */
.role {
  font-size: var(--text-caption);
  color: var(--ink-quiet);
  border: 1px solid var(--edge);
  border-radius: 999px;
  padding: 0 var(--gap-s);
  white-space: nowrap;
}

/* ==== own tooltips: begin ==== */
/* The page's own tooltip, the same text in three benches (see app.js). Fixed
   to the window, because a table that scrolls would cut it. It takes no
   pointer, so it is never in the way of what is under it. */
.own-tip {
  position: fixed;
  z-index: 1100;
  max-width: min(26rem, calc(100vw - 16px));
  padding: var(--gap-s) var(--gap-m);
  border-radius: 6px;
  background: var(--ink);
  color: var(--page);
  font-family: var(--font);
  font-size: var(--text-caption);
  font-weight: 400;
  line-height: 1.45;
  text-align: left;
  white-space: pre-line;
  pointer-events: none;
  box-shadow: 0 2px 10px rgb(0 0 0 / 0.25);
}
.own-tip[hidden] { display: none; }
/* In the top layer a browser centres a box and gives it a border and a
   scrollbar. This one is placed by the page and has neither. */
.own-tip[popover] { inset: auto; margin: 0; border: 0; overflow: visible; }
/* ==== own tooltips: end ==== */

/* ==== own lists: begin ==== */
/* Every select is drawn by the page: a button that looks like a field, and a
   list that opens from it. Safari on a phone or a tablet draws a select's
   options in a narrow popover of its own, which no rule here can reach. The
   select itself stays in the page, hidden, and holds the value.

   This block is the same, byte for byte, in every bench. `--field` and
   `--field-pad` are what a bench sets where its own fields differ. */
select[data-own-list] { display: none !important; }
.choices { position: relative; display: block; width: 100%; }
button.choices-button {
  width: 100%;
  height: var(--control);
  display: flex;
  align-items: center;
  gap: var(--gap-s);
  padding: 0 var(--field-pad, var(--gap-m));
  font-family: var(--font);
  font-size: var(--text-small);
  font-weight: 400;
  color: var(--ink);
  background: var(--field, var(--card));
  border: 1px solid var(--edge);
  border-radius: 6px;
  text-align: left;
  cursor: pointer;
}
button.choices-button:hover { filter: none; border-color: var(--accent); }
button.choices-button[aria-expanded=true] { border-color: var(--accent); }
button.choices-button:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; border-color: var(--accent); }
button.choices-button[disabled] { cursor: default; border-color: var(--edge); }
/* `width: 0` and then grown: a long line must not make its field wider. With
   `min-width: 0` alone, a row of fields took the width of the chosen words
   and a phone scrolled sideways. */
.choices-now { flex: 1 1 0; width: 0; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.choices-caret { flex: none; color: var(--ink-quiet); }
.choices-list {
  position: fixed;
  z-index: 1000;
  box-sizing: border-box;
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: var(--gap-xs) 0;
  background: var(--card);
  border: 1px solid var(--edge);
  border-radius: 8px;
  box-shadow: 0 6px 20px rgba(0, 0, 0, 0.16);
}
.choices-group {
  padding: var(--gap-s) var(--gap-m) var(--gap-xs);
  font-size: var(--text-caption);
  color: var(--ink-faint);
  text-transform: uppercase;
  letter-spacing: 0.04em;
}
/* A line wraps and is never cut: its end is often what tells two lines apart. */
.choices-list button {
  display: block;
  width: 100%;
  height: auto;
  min-height: var(--control);
  padding: var(--gap-s) var(--gap-m);
  font-family: var(--font);
  font-size: var(--text-small);
  font-weight: 400;
  line-height: 1.35;
  color: var(--ink);
  background: none;
  border: none;
  border-radius: 0;
  text-align: left;
  overflow-wrap: anywhere;
  cursor: pointer;
}
.choices-list button:hover, .choices-list button:focus-visible { filter: none; background: var(--page); outline: none; }
.choices-list button[aria-selected=true] { color: var(--accent); font-weight: 600; }
.choices-list button[disabled] { color: var(--ink-faint); cursor: default; background: none; }
/* ==== own lists: end ==== */
