/* Interaction patterns the tester reuses across views: filter toggles, segmented controls,
   the capability launcher, the numbered flow, the selection bar, the searchable picker and
   the command palette. Everything here is built from the tokens in tokens.css; nothing here
   knows which view it is placed in. */

/* ── one skeleton for every view ───────────────────────────────────────────────
   Consistency is the cheapest usability there is: if the primary action is top-right on one
   screen and bottom-left on the next, every screen has to be re-read. The rules below fix
   the positions once, so each view only has to declare which slot a control belongs in.

     .view__head     title, then .view__actions (primary action, then the info button)
     .card__head     section name, status pills, then .card__actions on the right
     .toolbar        filters first, view options next, .toolbar__end (exports) on the right

   Destructive controls are last within their group, everywhere. */
.view__actions,
.card__actions {
  display: flex; align-items: center; gap: .5rem; flex-wrap: wrap;
  margin-left: auto;
}
.toolbar__end { display: flex; align-items: center; gap: .5rem; flex-wrap: wrap; margin-left: auto; }

/* Navigation the current session cannot reach. Driven by the ARIA state rather than by
   inline styles written from JavaScript, so the visual and the announced state cannot
   disagree and a media query can still override the presentation. */
.rail__link[aria-disabled="true"] { opacity: .45; pointer-events: none; }
.rail__note { margin-top: .375rem; }
.rail__foot button { margin-top: .5rem; }

/* ── layout utilities ──────────────────────────────────────────────────────────
   These replace the inline `style="flex:1 1 160px"` attributes the markup used to carry.
   An inline style cannot respond to a media query and cannot be overridden by a later
   rule, so every responsive fix had to be written twice. */
.field-row { display: flex; flex-wrap: wrap; gap: .75rem; align-items: flex-start; }
.field-row > * { flex: 1 1 10rem; min-width: 0; }
.field-row > .cell--wide { flex: 2 1 15rem; }
.field-row > .cell--auto { flex: 0 0 auto; }
/* A control that sits beside labelled fields has no label of its own to push it down. */
.field-row > .cell--end { align-self: flex-end; }

.ml-auto { margin-left: auto; }
.nowrap  { flex-wrap: nowrap; white-space: nowrap; }
.grow    { flex: 1 1 auto; min-width: 0; }

/* Text that may be Hebrew, Latin, or both in one string.

   `plaintext` resolves each run's direction from its own characters, so a mixed
   "A007 אלביר אל שרקי 1" reads in the right order instead of being reordered by the page's
   direction. The alignment is pinned to the left deliberately: `dir="auto"` also flips the
   box, which left a table column where Hebrew-first names sat right and Latin-first names
   sat left — correct per string, unreadable as a column. Ordering follows the text;
   alignment follows the column. */
.bidi { unicode-bidi: plaintext; text-align: left; }

/* The same string, being typed rather than displayed. A field is not a column, so the reason
   `.bidi` pins its alignment does not apply here and the opposite is true: an operator typing
   Hebrew into a left-aligned box watches the caret run away from the edge the text belongs to,
   which reads as broken long before the first word is finished. `dir="auto"` on the element
   flips the box from the first strong character typed, and `start` lets the alignment follow
   it — Hebrew right, Latin left, in the one field, with no toggle to set and nothing to
   remember. The empty field keeps the page direction, so English placeholders stay put. */
.bidi-field { unicode-bidi: plaintext; text-align: start; }

/* ── filter toggles ────────────────────────────────────────────────────────────
   Replaces the checkbox + label pairs used for stream kinds, log levels and catalog flags.
   The checkbox had to be 24px to meet WCAG 2.5.8, which made a row of eight filters taller
   than the data it filtered. A button carries its own hit area, so the whole chip is the
   target at any size, and `aria-pressed` says the same thing to a screen reader that the
   fill says visually. */
/* Names what the control beside it changes. A segmented control shows its whole choice but
   not what the choice is about, and an aria-label says that to a screen reader and to nobody
   else. */
.card__actions-label {
  color: var(--fg-faint);
  font-size: .7rem; font-weight: 700;
  text-transform: uppercase; letter-spacing: .04em;
  white-space: nowrap;
}

.toggles { display: flex; flex-wrap: wrap; gap: .3125rem; align-items: center; }
.toggle {
  min-height: 26px;
  padding: .1875rem .5625rem;
  border: 1px solid var(--line-strong);
  border-radius: 999px;
  background: var(--surface);
  color: var(--fg-faint);
  font: 600 .6875rem/1.3 var(--sans);
  letter-spacing: .02em;
  white-space: nowrap;
}
.toggle[aria-pressed="true"] {
  background: var(--accent-soft);
  border-color: color-mix(in srgb, var(--accent) 45%, transparent);
  color: color-mix(in srgb, var(--accent) 80%, var(--fg));
}
.toggle:hover:not(:disabled) { border-color: var(--fg-faint); background: var(--surface-sunk); }
.toggle[aria-pressed="true"]:hover { background: var(--accent-soft); }
/* Tone variants let a level or kind keep its meaning while switched off, so a reader can
   still tell "errors are hidden" from "verbose is hidden". */
.toggle--ok[aria-pressed="true"]   { background: var(--ok-soft);   border-color: color-mix(in srgb, var(--ok) 45%, transparent);   color: var(--ok); }
.toggle--warn[aria-pressed="true"] { background: var(--warn-soft); border-color: color-mix(in srgb, var(--warn) 45%, transparent); color: var(--warn); }
.toggle--bad[aria-pressed="true"]  { background: var(--bad-soft);  border-color: color-mix(in srgb, var(--bad) 45%, transparent);  color: var(--bad); }
.toggle--live[aria-pressed="true"] { background: var(--live-soft); border-color: color-mix(in srgb, var(--live) 45%, transparent); color: var(--live); }
.toggle__count { margin-left: .3125rem; opacity: .7; font-weight: 500; }

/* ── segmented control ─────────────────────────────────────────────────────────
   For a two- or three-way choice that changes what the panel below *is*, rather than
   filtering it. A select hides the alternatives behind a click; a segment shows the whole
   choice, which is what makes "explicit vs authorized" legible without reading the hint. */
.seg { display: inline-flex; padding: 2px; border: 1px solid var(--line-strong); border-radius: var(--radius-s); background: var(--surface-sunk); }
.seg > button {
  min-height: 28px; padding: .25rem .625rem;
  border: 0; border-radius: calc(var(--radius-s) - 2px); background: transparent;
  color: var(--fg-muted); font-size: .75rem; font-weight: 600;
}
.seg > button[aria-pressed="true"] { background: var(--surface); color: var(--fg); box-shadow: 0 1px 2px rgb(15 27 40 / .12); }
.seg > button:hover:not([aria-pressed="true"]) { color: var(--fg); background: transparent; }

/* ── capability launcher (Overview) ────────────────────────────────────────────
   Every flow the tester drives, each showing its own current state and the one action that
   moves it forward. This is the page's answer to "what can this thing do" — a rail of names
   answers "where can I go", which is not the same question. */
.tiles { display: grid; gap: .75rem; grid-template-columns: repeat(auto-fit, minmax(min(260px, 100%), 1fr)); }
.tile {
  display: flex; flex-direction: column; gap: .5rem;
  padding: .875rem 1rem;
  border: 1px solid var(--line); border-radius: var(--radius);
  background: var(--surface);
  text-decoration: none; color: inherit;
  transition: border-color .12s ease, background-color .12s ease;
}
.tile:hover { border-color: var(--line-strong); background: var(--surface-sunk); }
.tile__top { display: flex; align-items: center; gap: .5rem; }
.tile__top svg { flex: none; width: 18px; height: 18px; color: var(--accent); }
.tile__name { font-weight: 620; font-size: .9375rem; }
.tile__state { margin-left: auto; }
.tile__what { font-size: .8125rem; color: var(--fg-muted); }
.tile__route { font-family: var(--mono); font-size: .6875rem; color: var(--fg-faint); overflow-wrap: anywhere; }
.tile__go { margin-top: auto; padding-top: .25rem; font-size: .75rem; font-weight: 600; color: var(--accent); }
.tile[aria-disabled="true"] { opacity: .55; pointer-events: none; }

/* ── numbered flow (Push) ──────────────────────────────────────────────────────
   Push is four ordered steps, not four peer cards. Numbering them and dimming the ones that
   are not reachable yet is the difference between a form and instructions. */
.flow { display: grid; gap: .75rem; }
.step { border: 1px solid var(--line); border-radius: var(--radius-l); background: var(--surface); box-shadow: var(--shadow); }
.step__head {
  display: flex; align-items: center; gap: .75rem; flex-wrap: wrap;
  padding: .875rem 1.125rem;
  border-bottom: 1px solid var(--line);
}
.step__index {
  display: grid; place-items: center;
  width: 24px; height: 24px; flex: none;
  border-radius: 999px;
  background: var(--surface-sunk); color: var(--fg-muted);
  border: 1px solid var(--line-strong);
  font: 700 .75rem/1 var(--sans);
}
.step__head h2 { margin-right: auto; font-size: 1rem; }
.step__body { padding: 1.125rem; }
.step--done > .step__head > .step__index { background: var(--ok); border-color: var(--ok); color: var(--surface); }
.step--active { border-color: color-mix(in srgb, var(--accent) 45%, var(--line)); }
.step--active > .step__head > .step__index { background: var(--accent); border-color: var(--accent); color: var(--accent-fg); }
/* Blocked, not hidden: a reader has to be able to see what comes next and why it is not
   available yet, which a hidden card cannot say. */
.step--blocked { opacity: .6; }
.step--blocked .step__body { pointer-events: none; }

/* ── selection action bar ──────────────────────────────────────────────────────
   Bulk actions appear when there is a selection to act on, instead of sitting permanently
   disabled above a list. A permanently disabled control teaches nothing; one that appears
   the moment it becomes usable teaches what enabled it. */
.bulkbar {
  display: flex; align-items: center; gap: .5rem; flex-wrap: wrap;
  padding: .5rem 1.125rem;
  border-bottom: 1px solid var(--line);
  background: color-mix(in srgb, var(--accent) 8%, var(--surface-sunk));
}
.bulkbar[hidden] { display: none; }
.bulkbar__count { font-weight: 650; font-size: .8125rem; }
.bulkbar__note { font-size: .75rem; color: var(--fg-muted); }

/* ── id summary ────────────────────────────────────────────────────────────────
   Five hundred chips is not a list, it is a wall: it buried the live data under two
   screens of identifiers nobody reads one at a time. The summary states the count, shows a
   handful for orientation, and puts the rest behind a searchable picker. */
.idsum {
  display: flex; align-items: center; gap: .5rem; flex-wrap: wrap;
  padding: .625rem .75rem;
  border: 1px solid var(--line); border-radius: var(--radius);
  background: var(--surface-sunk);
}
.idsum__count { font-weight: 650; font-size: .875rem; font-variant-numeric: tabular-nums; }
.idsum__peek {
  min-width: 0; flex: 1 1 12rem;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font-family: var(--mono); font-size: .75rem; color: var(--fg-faint);
}

/* ── searchable picker ─────────────────────────────────────────────────────────
   One dialog behind every "choose from hundreds of things" control: live window ids, push
   controllers, push event filters. They were three separate chip walls showing raw numeric
   ids; a picker can afford a search box, names beside the ids, and a running count. */
.picker { display: flex; flex-direction: column; min-height: 0; }
.picker__tools { display: flex; gap: .5rem; align-items: center; flex-wrap: wrap; padding: .75rem 1.125rem; border-bottom: 1px solid var(--line); background: var(--surface-sunk); }
/* The box keeps its size while the filter narrows it. Sized only by its rows, the dialog
   collapsed from a full list to two lines as you typed, moving the search box under the
   cursor mid-keystroke — the reader chases the control instead of reading results. */
.picker__list {
  flex: 1 1 auto;
  overflow: auto;
  padding: .375rem;
  block-size: min(52dvh, 19rem);
}
.picker__row {
  /* justify-content is set explicitly because the base button rule centres its content:
     a row whose meta is absent has nothing pushing it wide, and inherited centring left
     those rows indented out of line with the rest. */
  display: flex; align-items: center; justify-content: flex-start; gap: .625rem;
  width: 100%;
  padding: .375rem .5rem;
  border: 0; border-radius: var(--radius-s);
  background: none; text-align: left;
  font-size: .8125rem;
}
.picker__row:hover { background: var(--surface-sunk); }
.picker__box {
  display: grid; place-items: center;
  width: 18px; height: 18px; flex: none;
  border: 1px solid var(--line-strong); border-radius: 4px;
  color: transparent;
  font-size: .6875rem; font-weight: 700;
}
.picker__row[aria-pressed="true"] .picker__box { background: var(--accent); border-color: var(--accent); color: var(--accent-fg); }
.picker__id { flex: none; width: 5.5rem; font-family: var(--mono); font-size: .75rem; color: var(--fg-muted); }
.picker__label { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.picker__meta { flex: none; font-size: .6875rem; color: var(--fg-faint); }

/* One choice: a tick box would offer to tick several, and an empty one on every row reads
   as "none of these are on". The row itself is the control, and the one being read is
   marked rather than checked. */
.picker__list--single .picker__box { display: none; }
.picker__list--single .picker__row[aria-pressed="true"] {
  background: var(--accent-soft);
  color: color-mix(in srgb, var(--accent) 78%, var(--fg));
}
.picker__list--single .picker__row[aria-pressed="true"]::after {
  content: "reading";
  flex: none;
  font-size: .6875rem; font-weight: 600; letter-spacing: .02em;
}
.picker__foot { display: flex; align-items: center; gap: .5rem; flex-wrap: wrap; padding: .75rem 1.125rem; border-top: 1px solid var(--line); background: var(--surface-sunk); }

/* ── command palette ───────────────────────────────────────────────────────────
   Every action this tool performs, reachable from one key. A tester's actions are spread
   over seven views and three collapsed cards; without a palette, "connect the hub" costs a
   navigation and a scroll, and half of them are never found at all. */
#command-palette { width: min(560px, calc(100vw - 2rem)); }
#command-palette .picker__list { block-size: min(24rem, 56dvh); }
.palette__input { border: 0; border-radius: 0; min-height: 48px; font-size: .9375rem; padding: .75rem 1.125rem; }
.palette__input:focus { box-shadow: none; border-color: transparent; }
.palette__group { padding: .5rem .625rem .25rem; color: var(--fg-faint); font-size: .6875rem; letter-spacing: .08em; text-transform: uppercase; }
.palette__row[aria-selected="true"] { background: var(--accent-soft); }
.palette__hint { margin-left: auto; flex: none; display: flex; gap: .25rem; }

kbd, .kbd {
  display: inline-block;
  min-width: 1.4em;
  padding: .0625rem .3125rem;
  border: 1px solid var(--line-strong); border-bottom-width: 2px; border-radius: 4px;
  background: var(--surface-sunk); color: var(--fg-muted);
  font: 600 .6875rem/1.5 var(--mono);
  text-align: center;
}

.shortcuts { display: grid; gap: .375rem 1rem; grid-template-columns: auto minmax(0, 1fr); align-items: center; font-size: .8125rem; }
.shortcuts dt { justify-self: start; }
.shortcuts dd { margin: 0; color: var(--fg-muted); }

/* ── container queries ─────────────────────────────────────────────────────────
   The auto-fit grids already size themselves to the space available, so the containers here
   are for the pieces *inside* a cell, which a viewport query cannot reach: a live controller
   card is 190px wide in a compact grid on a wide screen and full width on a phone, and only
   the card's own width says which layout its metrics need.

   Applied to leaf components only. `container-type: inline-size` implies size containment,
   which would break the sticky table header and the fill-the-viewport panels if it were put
   on a card or a view. */
.tile, .ctl, .step__body { container-type: inline-size; }

@container (max-width: 240px) {
  .ctl__metrics { grid-template-columns: minmax(0, 1fr); }
}

@container (max-width: 420px) {
  .step__body .field-row > * { flex-basis: 100%; }
}

/* Headings and short explanatory paragraphs read better with the browser choosing the break
   points than with a ragged last word on its own line. */
h1, h2, h3, .tile__name { text-wrap: balance; }
.tile__what, .empty p, .hint, .note { text-wrap: pretty; }
