/* The web client's visual system (docs/ui-plan.md § "Visual system"): one
   dark theme from the tokens below, the components (button, chip, switch,
   select, card, panel), then the views. Styling lives here and in the
   CSSOM only: the page's policy allows no style attributes. Shown and
   hidden is the `hidden` attribute (the tests read it), never a class. */

/* ===== Tokens ===== */
:root {
  color-scheme: dark;
  /* Surfaces, darkest first: behind the picture, dock and panels, cards and
     fields, buttons, pressed. */
  --bg-0: #07090c;
  --bg-1: #0f1217;
  --bg-2: #161a21;
  --bg-3: #1e232c;
  --bg-4: #2a303b;
  --line-1: #232833;
  --line-2: #343b48;
  /* Text: --text-3 keeps 4.5:1 on --bg-1 and --bg-2. */
  --text-1: #edf0f4;
  --text-2: #b3bbc7;
  --text-3: #8a93a1;
  --accent: #6aa8f8;
  --accent-bg: #2670cc;
  --accent-bg-hover: #2d76d3;
  --accent-soft: rgb(94 161 247 / 0.14);
  --accent-line: rgb(94 161 247 / 0.45);
  --ok: #3fca6b;
  --ok-text: #8fe3a8;
  --ok-soft: rgb(63 202 107 / 0.14);
  --warn: #f3b33d;
  --warn-text: #ffd38a;
  --warn-soft: rgb(243 179 61 / 0.13);
  --danger: #f06464;
  --danger-text: #ffb0b0;
  --danger-soft: rgb(240 100 100 / 0.13);
  --info: #6fb6ff;
  --info-text: #b4d9ff;
  --info-soft: rgb(111 182 255 / 0.11);
  /* The latency stages of the HUD's bar, in their fixed order (dark steps
     of the categorical palette), and what no stage explains. */
  --s1: #3987e5;
  --s2: #d95926;
  --s3: #199e70;
  --s4: #c98500;
  --s5: #d55181;
  --s6: #008300;
  --s7: #9085e9;
  --s8: #e66767;
  --s-rest: #5b6270;
  /* Type: 13 px is the smallest body text, 12 px only for chips, kbd,
     capitalised labels and the HUD's numbers. */
  --fs-1: 12px;
  --fs-2: 13px;
  --fs-3: 14px;
  --fs-4: 16px;
  --fs-5: 20px;
  --fs-6: 28px;
  --font: system-ui, -apple-system, "Segoe UI", Roboto, Cantarell, "Noto Sans", "Helvetica Neue", Arial, sans-serif;
  --mono: ui-monospace, "SF Mono", "Cascadia Mono", "JetBrains Mono", "DejaVu Sans Mono", Menlo, Consolas, monospace;
  --s-1: 4px;
  --s-2: 8px;
  --s-3: 12px;
  --s-4: 16px;
  --s-5: 24px;
  --s-6: 32px;
  /* Fields and small buttons, buttons and list items, panels and cards. */
  --r-s: 6px;
  --r-m: 10px;
  --r-l: 14px;
  --dock-w: 312px;
  --rail-w: 68px;
  --hud-w: 420px;
  --bar-h: 48px;
  /* The phone's bottom bar (§ "The phone's bars" below). */
  --bbar-h: 64px;
  /* The HUD's row below the session in narrow windows while it shows. */
  --hud-row: 0px;
  /* The fullscreen tab's height (controls.ts). */
  --tab-h: 28px;
  --shadow-1: 0 1px 2px rgb(0 0 0 / 0.4);
  --shadow-2: 0 12px 32px rgb(0 0 0 / 0.55), 0 2px 6px rgb(0 0 0 / 0.35);
  /* A 2 px ring in the accent outside a 2 px gap. */
  --focus: 0 0 0 2px var(--bg-1), 0 0 0 4px var(--accent);
  font: var(--fs-3) / 1.45 var(--font);
}

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

body {
  margin: 0 auto;
  max-width: 60rem;
  padding: var(--s-4);
  color: var(--text-1);
  background: radial-gradient(1000px 520px at 75% -10%, #142339 0%, transparent 62%) no-repeat, var(--bg-0);
  -webkit-font-smoothing: antialiased;
}

button,
input,
select {
  font: inherit;
  color: inherit;
}

:focus-visible {
  outline: none;
  box-shadow: var(--focus);
}

h1,
h2,
h3 {
  line-height: 1.25;
}

/* A label kept for screen readers and tests, hidden visually. */
.sr {
  position: absolute !important;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* Icons (icons.ts): 16 px, xs 12, sm 14, lg 22. */
svg.i {
  width: 16px;
  height: 16px;
  flex: none;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.8;
  stroke-linecap: round;
  stroke-linejoin: round;
  pointer-events: none;
}

svg.i.xs {
  width: 12px;
  height: 12px;
}

svg.i.sm {
  width: 14px;
  height: 14px;
}

svg.i.lg {
  width: 22px;
  height: 22px;
}

svg.logo {
  flex: none;
  display: block;
}

kbd {
  font: 600 var(--fs-1) / 1 var(--mono);
  padding: 2px 5px 1px;
  border-radius: 4px;
  border: 1px solid var(--line-2);
  border-bottom-width: 2px;
  background: var(--bg-2);
  color: var(--text-2);
  white-space: nowrap;
}

code {
  font: 0.92em var(--mono);
}

.message:empty,
.note:empty {
  display: none;
}

/* Quieter text, through the colour, not opacity (contrast). */
.details,
.empty,
.status,
.note {
  color: var(--text-3);
}

dl {
  display: grid;
  grid-template-columns: max-content minmax(0, 1fr);
  gap: var(--s-1) var(--s-4);
}

dt {
  color: var(--text-3);
}

dd {
  margin: 0;
  overflow-wrap: anywhere;
}

/* ===== Components ===== */

/* Button: 32 px, s 28 px, l 40 px. Primary, quiet, danger, icon only. */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  flex: none;
  min-height: 32px;
  padding: 0 12px;
  border-radius: var(--r-m);
  border: 1px solid var(--line-2);
  background: var(--bg-3);
  color: var(--text-1);
  font-size: var(--fs-2);
  font-weight: 600;
  line-height: 1.2;
  white-space: nowrap;
  cursor: pointer;
}

.btn:hover:not(:disabled) {
  background: var(--bg-4);
}

.btn:disabled {
  opacity: 0.45;
  cursor: not-allowed;
}

.btn.primary {
  background: var(--accent-bg);
  border-color: transparent;
  color: #fff;
}

.btn.primary:hover:not(:disabled) {
  background: var(--accent-bg-hover);
}

.btn.quiet {
  background: transparent;
  border-color: transparent;
  color: var(--text-2);
}

.btn.quiet:hover:not(:disabled) {
  background: var(--bg-3);
  color: var(--text-1);
}

.btn.danger {
  background: transparent;
  border-color: rgb(240 100 100 / 0.35);
  color: var(--danger-text);
}

.btn.danger:hover:not(:disabled) {
  background: var(--danger-soft);
}

.btn[aria-pressed="true"],
.btn[aria-expanded="true"] {
  background: var(--accent-soft);
  border-color: var(--accent-line);
  color: var(--text-1);
}

.btn.s {
  min-height: 28px;
  padding: 0 10px;
  border-radius: var(--r-s);
  font-size: var(--fs-2);
}

.btn.l {
  min-height: 40px;
  padding: 0 18px;
  font-size: var(--fs-3);
}

.btn.icon {
  width: 32px;
  padding: 0;
}

.btn.s.icon {
  width: 28px;
}

.btn.block {
  width: 100%;
}

/* Chip: a state next to a title ("Window", "Live", "View only"). */
.chip {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  flex: none;
  min-height: 24px;
  padding: 0 9px;
  border-radius: 999px;
  border: 1px solid var(--line-2);
  background: var(--bg-3);
  color: var(--text-2);
  font-size: var(--fs-1);
  font-weight: 600;
  white-space: nowrap;
}

.chip .dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: currentColor;
}

.chip.ok {
  color: var(--ok-text);
  background: var(--ok-soft);
  border-color: rgb(63 202 107 / 0.3);
}

.chip.warn {
  color: var(--warn-text);
  background: var(--warn-soft);
  border-color: rgb(243 179 61 / 0.32);
}

.chip.info {
  color: var(--info-text);
  background: var(--info-soft);
  border-color: rgb(111 182 255 / 0.3);
}

button.chip {
  cursor: pointer;
}

/* Switch: a checkbox drawn as a switch (its knob a gradient, so no extra
   element and no pseudo-element on the input). */
input.switch {
  appearance: none;
  flex: none;
  width: 38px;
  height: 22px;
  margin: 0;
  border-radius: 999px;
  border: 1px solid var(--line-2);
  background: radial-gradient(circle at 10px 50%, var(--text-2) 0 7px, transparent 7.5px) var(--bg-4);
  cursor: pointer;
}

input.switch:checked {
  border-color: transparent;
  background: radial-gradient(circle at 26px 50%, #fff 0 7px, transparent 7.5px) var(--accent-bg);
}

input.switch:disabled {
  opacity: 0.45;
  cursor: not-allowed;
}

/* Select and text field. */
.select,
.input {
  min-height: 32px;
  border-radius: var(--r-s);
  border: 1px solid var(--line-2);
  background: var(--bg-1);
  color: var(--text-1);
}

.select {
  padding: 0 8px;
  font-size: var(--fs-2);
}

.input {
  width: 100%;
  height: 42px;
  padding: 0 44px 0 12px;
  font-size: var(--fs-4);
}

.input:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-soft);
}

.input[aria-invalid="true"] {
  border-color: var(--danger);
  box-shadow: 0 0 0 3px var(--danger-soft);
}

/* A refused password keeps its red, and with the keyboard on it the ring
   says so too. */
.input[aria-invalid="true"]:focus {
  box-shadow: 0 0 0 3px var(--danger-soft), var(--focus);
}

.card {
  background: var(--bg-2);
  border: 1px solid var(--line-1);
  border-radius: var(--r-l);
}

/* ===== Login ===== */
body:has(.login-wrap) {
  max-width: none;
  padding: 0;
  background: var(--bg-0);
}

.login-wrap {
  min-height: 100vh;
  min-height: 100dvh;
  display: grid;
  place-items: center;
  padding: var(--s-5);
  background: radial-gradient(900px 500px at 50% -10%, #142339 0%, transparent 65%), var(--bg-0);
}

.login {
  display: flex;
  flex-direction: column;
  width: min(392px, 100%);
  padding: 28px 28px 24px;
  box-shadow: var(--shadow-2);
}

.login .brand {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  margin-bottom: 22px;
}

.login .brand .logo {
  width: 40px;
  height: 40px;
}

.login .brand b {
  display: block;
  font-size: 18px;
  line-height: 1.2;
}

.login .brand span {
  color: var(--text-3);
  font-size: var(--fs-2);
}

.login h1 {
  margin: 0 0 var(--s-1);
  font-size: 22px;
}

.login .lead {
  margin: 0 0 20px;
  color: var(--text-2);
}

.login .message {
  margin: 0 0 var(--s-4);
  padding: var(--s-2) var(--s-3);
  border-radius: var(--r-s);
  background: var(--info-soft);
  color: var(--info-text);
  font-size: var(--fs-2);
}

.field-label {
  display: block;
  margin-bottom: 6px;
  font-size: var(--fs-2);
  font-weight: 650;
}

.pw-wrap {
  position: relative;
}

.pw-wrap .pw-eye {
  position: absolute;
  top: 50%;
  right: 7px;
  transform: translateY(-50%);
}

.login .error {
  display: flex;
  align-items: flex-start;
  gap: 7px;
  margin: var(--s-2) 0 0;
  color: var(--danger-text);
  font-size: var(--fs-2);
}

.login .error svg {
  margin-top: 2px;
  color: var(--danger);
}

.login .btn.primary {
  margin-top: 18px;
}

/* ===== Home view ===== */

/* The home view fills the window: the pinned dock with the list, and the
   main area with what to pick, the Recent shelf, the next session's
   settings and the host's capabilities. The message about the last session
   sits above the main area (a row of its own, so phones show it first). */
body:has(.home) {
  max-width: none;
  padding: 0;
}

.home {
  position: fixed;
  inset: 0;
  display: grid;
  grid-template-columns: var(--dock-w) minmax(0, 1fr);
  grid-template-rows: auto minmax(0, 1fr);
}

.home > .dock {
  grid-column: 1;
  grid-row: 1 / 3;
  min-height: 0;
}

.home > .home-main {
  grid-column: 2;
  grid-row: 2;
  min-width: 0;
  overflow-y: auto;
  background: radial-gradient(1000px 520px at 75% -10%, #142339 0%, transparent 62%), var(--bg-0);
}

.home > .session-message {
  grid-column: 2;
  grid-row: 1;
  justify-self: center;
  width: min(812px, calc(100% - 88px));
  margin: var(--s-5) 0 0;
}

/* Why the last session ended: its plain text, "Details" with the technical
   one (docs/ui-plan.md § "Step 5", P2-20). */
.session-message {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  gap: 6px 10px;
  padding: 10px 12px 10px 14px;
  border-radius: var(--r-m);
  background: linear-gradient(var(--warn-soft), var(--warn-soft)), var(--bg-1);
  box-shadow: inset 3px 0 0 var(--warn);
  color: var(--text-1);
}

.session-message > svg.i {
  flex: none;
  margin-top: 6px;
  color: var(--warn);
}

.session-message > .text {
  flex: 1 1 16rem;
  min-width: 0;
  margin: 5px 0 0;
}

.session-message > .details {
  flex: 0 0 calc(100% - 26px);
  min-width: 0;
  margin-left: 26px;
  padding: 7px 10px;
  border-radius: var(--r-s);
  background: rgb(0 0 0 / 0.28);
}

.home-inner {
  max-width: 900px;
  margin: 0 auto;
  padding: 56px 44px 48px;
}

.home-inner h1 {
  margin: 0 0 6px;
  font-size: var(--fs-6);
  letter-spacing: -0.015em;
}

.home-inner .lead {
  max-width: 620px;
  margin: 0 0 26px;
  color: var(--text-2);
  font-size: var(--fs-4);
}

/* A capitalised label: the Recent shelf's, the technical details'. */
.recent-shelf > h2,
.home-hint.tech-h {
  display: flex;
  align-items: center;
  gap: var(--s-2);
  margin: 0 0 10px;
  color: var(--text-2);
  font-size: var(--fs-1);
  font-weight: 700;
  letter-spacing: 0.09em;
  text-transform: uppercase;
}

.recent-shelf {
  margin-bottom: var(--s-5);
}

.shelf {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
  gap: var(--s-3);
}

button.recent.card {
  display: flex;
  flex-direction: column;
  gap: var(--s-2);
  min-width: 0;
  padding: 10px;
  color: var(--text-1);
  text-align: left;
  cursor: pointer;
}

button.recent.card:hover {
  background: var(--bg-3);
  border-color: var(--line-2);
}

button.recent.card .thumb {
  width: 100%;
}

.rc-text {
  display: flex;
  flex-direction: column;
  min-width: 0;
}

.rc-text strong,
.rc-sub {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.rc-text strong {
  font-size: var(--fs-3);
  font-weight: 600;
}

.rc-sub {
  color: var(--text-3);
  font-size: var(--fs-2);
}

.explain-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 14px;
}

.explain {
  padding: 16px 18px;
}

.explain h2 {
  display: flex;
  align-items: center;
  gap: 9px;
  margin: 0 0 6px;
  font-size: var(--fs-4);
}

.explain h2 svg.i {
  color: var(--accent);
}

.explain p {
  margin: 0;
  color: var(--text-2);
}

.next {
  margin: 22px 0 0;
  color: var(--text-2);
  font-size: var(--fs-2);
}

.next b {
  color: var(--text-1);
  font-weight: 650;
}

/* A button drawn as a link ("Change", "Show technical details"). */
.linkbtn {
  padding: 0;
  border: 0;
  background: none;
  color: var(--accent);
  font: inherit;
  font-weight: 600;
  text-decoration: underline;
  text-underline-offset: 2px;
  cursor: pointer;
}

.home-hint {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 6px;
  margin: 22px 0 0;
  color: var(--text-3);
  font-size: var(--fs-2);
}

.home-hint b {
  color: var(--text-2);
}

.home-hint.tech-h {
  margin: 30px 0 0;
}

.home-hint.tech-h::after {
  content: "";
  flex: 1;
  height: 1px;
  background: var(--line-1);
}

/* The host's capabilities: "This computer", collapsed in simple mode. */
.hello {
  margin-top: 14px;
}

/* Its heading as the cards beside it draw theirs (an icon), with the HUD's
   chevron instead of the browser's marker. */
.hello > summary {
  display: flex;
  align-items: center;
  gap: var(--s-2);
  padding: 12px 16px;
  list-style: none;
  cursor: pointer;
  font-size: var(--fs-3);
  font-weight: 650;
}

.hello > summary::-webkit-details-marker {
  display: none;
}

.hello > summary svg.i {
  color: var(--text-2);
}

.hello > summary::after {
  content: "";
  width: 6px;
  height: 6px;
  margin: 0 2px 0 auto;
  border-right: 2px solid var(--text-3);
  border-bottom: 2px solid var(--text-3);
  transform: rotate(-45deg);
}

.hello[open] > summary::after {
  margin-top: -3px;
  transform: rotate(45deg);
}

.hello[open] > summary {
  padding-bottom: 6px;
}

.hello-body {
  padding: 0 16px 14px;
}

.hello-body p {
  margin: 0 0 10px;
  color: var(--text-2);
}

.hello dl {
  margin: 0;
}

/* The technical cards (technical mode): This computer, This browser, Last
   session, Copy diagnostics, two to a row where they fit. */
.tech-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
  gap: 14px;
  margin-top: 14px;
  align-items: start;
}

.tech-grid > .hello {
  margin-top: 0;
}

.tech-card {
  min-width: 0;
  padding: 12px 16px 14px;
}

.tech-card > h3 {
  display: flex;
  align-items: center;
  gap: var(--s-2);
  margin: 0 0 8px;
  font-size: var(--fs-3);
  font-weight: 650;
}

.tech-card > h3 svg.i {
  color: var(--text-2);
}

.tech-card dl,
.hello dl {
  grid-template-columns: 9.5rem minmax(0, 1fr);
  font: var(--fs-1) / 1.5 var(--mono);
}

.tech-card dd {
  overflow-wrap: anywhere;
}

.tech-card .tc-note {
  margin: 0 0 8px;
  color: var(--text-3);
  font-size: var(--fs-2);
}

.tech-card .tc-body > .btn {
  margin-top: 8px;
}

.copy-card {
  grid-column: 1 / -1;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-3);
}

.copy-card p {
  flex: 1 1 320px;
  margin: 0;
  color: var(--text-2);
}

/* ===== Dock ===== */

/* The list of windows and screens (dock.ts, docs/ui-plan.md § "The dock
   and its shapes"): pinned (312 px), the rail (68 px), over the picture
   (fullscreen, a narrow window) and a sheet from the bottom (phones). The
   list is in the DOM in every shape; the rail is drawn from it. */
.dock {
  flex: none;
  width: var(--dock-w);
  min-height: 0;
  display: flex;
  flex-direction: column;
  background: var(--bg-1);
  border-right: 1px solid var(--line-1);
  color: var(--text-1);
  z-index: 6;
}

.dock-head {
  position: relative;
  display: flex;
  align-items: center;
  gap: var(--s-2);
  min-height: 56px;
  padding: 12px 10px 8px 16px;
}

.dock-head .brand {
  flex: 1;
  min-width: 0;
  display: flex;
  align-items: center;
  gap: 10px;
}

.dock-head .logo {
  width: 30px;
  height: 30px;
}

.dock-head .brand > div {
  min-width: 0;
}

.dock-head .brand b {
  display: block;
  font-size: 15px;
  line-height: 1.2;
}

.dock-head .brand .host {
  display: flex;
  align-items: center;
  gap: 5px;
  overflow: hidden;
  color: var(--text-3);
  font-size: var(--fs-2);
  white-space: nowrap;
}

/* A long name ends in an ellipsis inside the head (its title has it whole). */
.dock-head .brand .host > span {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
}

.dock-search {
  position: relative;
  margin: 0 12px 6px;
}

.dock-search .search {
  width: 100%;
  height: 36px;
  padding: 0 34px;
  border-radius: 9px;
  border: 1px solid var(--line-2);
  background: var(--bg-2);
  color: var(--text-1);
  font-size: var(--fs-2);
}

.dock-search .search::placeholder {
  color: var(--text-3);
}

.dock-search .search::-webkit-search-cancel-button {
  appearance: none;
}

.dock-search .search:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-soft);
}

.dock-search > svg.i {
  position: absolute;
  left: 11px;
  top: 10px;
  color: var(--text-3);
}

.dock-search > kbd {
  position: absolute;
  right: 9px;
  top: 9px;
}

.dock-search:focus-within > kbd {
  display: none;
}

.dock-body {
  flex: 1 1 0;
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: 2px 8px 14px;
  scrollbar-width: thin;
  scrollbar-color: var(--bg-4) transparent;
}

.dock .sect {
  margin-top: var(--s-2);
}

.dock .sect + .sect {
  margin-top: 14px;
  padding-top: 10px;
  border-top: 1px solid var(--line-1);
}

.dock .sect-h {
  display: flex;
  align-items: center;
  gap: var(--s-2);
  padding: 4px 8px 2px;
}

.dock .sect-h h2 {
  display: flex;
  align-items: center;
  gap: 6px;
  margin: 0;
  color: var(--text-2);
  font-size: var(--fs-1);
  font-weight: 700;
  letter-spacing: 0.09em;
  text-transform: uppercase;
}

.dock .sect-h .count,
.dock .sect-h .right {
  color: var(--text-3);
  font-size: var(--fs-1);
}

.dock .sect-h .right {
  margin-left: auto;
  display: flex;
  align-items: center;
  gap: 2px;
}

.dock .sect-help,
.dock .note,
.dock .empty {
  margin: 1px 8px 6px;
  color: var(--text-3);
  font-size: var(--fs-2);
  line-height: 1.4;
}

.dock .note {
  color: var(--warn-text);
}

.dock .ws-h {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 0 6px;
  margin: 10px 8px 3px;
  color: var(--text-2);
  font-size: var(--fs-2);
  font-weight: 650;
}

.dock .ws-h .where {
  color: var(--text-3);
  font-weight: 400;
}

.dock .ws-h .wtech {
  margin-left: auto;
  color: var(--text-3);
  font: 400 var(--fs-1) var(--mono);
}

/* Workspaces nobody looks at: one dashed group. */
.dock .hidden-workspace {
  margin: 12px 0 0;
  padding: 8px 0 2px;
  border: 1px dashed var(--line-2);
  border-radius: 12px;
  background: rgb(255 255 255 / 0.018);
}

.dock .hg-h {
  display: flex;
  align-items: center;
  gap: 7px;
  margin: 0;
  padding: 2px 8px;
  font-size: var(--fs-2);
  font-weight: 650;
}

.dock .hg-h svg.i {
  color: var(--text-3);
}

.dock ul.targets {
  display: flex;
  flex-direction: column;
  gap: 2px;
  margin: 0;
  padding: 0;
  list-style: none;
}

.dock button.target {
  display: grid;
  grid-template-columns: 80px minmax(0, 1fr) auto;
  align-items: center;
  gap: 0 10px;
  width: 100%;
  padding: 6px;
  border-radius: var(--r-m);
  border: 1px solid transparent;
  background: transparent;
  color: var(--text-1);
  text-align: left;
  cursor: pointer;
}

.dock button.target:hover:not(:disabled) {
  background: var(--bg-2);
}

.dock button.target[aria-current="true"] {
  background: var(--accent-soft);
  border-color: var(--accent-line);
}

.dock button.target:disabled {
  color: var(--text-3);
  cursor: not-allowed;
}

/* A target's picture: its thumbnail, or its kind's icon. */
.thumb {
  position: relative;
  display: grid;
  place-items: center;
  aspect-ratio: 16 / 9;
  overflow: hidden;
  border-radius: var(--r-s);
  border: 1px solid var(--line-1);
  background: #0b0d10;
  color: var(--text-3);
}

.thumb > img.thumbnail {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: contain;
}

.dock .thumb {
  width: 80px;
}

.dock .thumb .hid {
  display: none;
}

.dock .t-text {
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 1px;
}

.dock .t-text strong,
.dock .t-sub {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.dock .t-text strong {
  font-size: var(--fs-2);
  font-weight: 600;
}

.dock .t-sub {
  color: var(--text-3);
  font-size: var(--fs-2);
}

.dock .t-sub > svg.i {
  margin-right: 4px;
  vertical-align: -1px;
}

.dock .badge-now {
  padding: 2px 7px;
  border-radius: 999px;
  background: rgb(94 161 247 / 0.16);
  color: var(--accent);
  font-size: var(--fs-1);
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

.dock .t-tech {
  grid-column: 2 / 4;
  margin-top: 2px;
  color: var(--text-3);
  font: var(--fs-1) / 1.35 var(--mono);
  overflow-wrap: anywhere;
}

/* The search's match: in the lightest text, so it keeps 4.5:1 on the highlight
   also in a subtitle's grey. */
.dock mark {
  border-radius: 2px;
  background: rgb(243 179 61 / 0.3);
  color: var(--text-1);
}

.dock .empty-find {
  margin: 18px 10px;
  color: var(--text-3);
  font-size: var(--fs-2);
}

/* The session's Recent row: its other targets, Ctrl+Alt+Tab's order. */
.dock .recent-row {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 6px;
  padding: 4px 6px 2px;
}

.dock button.recent {
  display: flex;
  flex-direction: column;
  gap: 4px;
  min-width: 0;
  padding: 4px;
  border-radius: 9px;
  border: 1px solid transparent;
  background: transparent;
  color: var(--text-2);
  text-align: left;
  cursor: pointer;
}

.dock button.recent:hover {
  background: var(--bg-2);
}

.dock button.recent .thumb {
  width: 100%;
}

.dock .rt-name {
  overflow: hidden;
  font-size: var(--fs-2);
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* The footer: the status, the technical switch, Settings. */
.dock-foot {
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 8px 10px 10px;
  border-top: 1px solid var(--line-1);
  background: var(--bg-1);
}

.dock .status-row {
  display: flex;
  align-items: center;
  gap: var(--s-2);
  width: 100%;
  min-height: 30px;
  padding: 0 8px;
  border: 0;
  border-radius: var(--r-s);
  background: transparent;
  color: var(--text-2);
  font-size: var(--fs-2);
  text-align: left;
}

button.status-row {
  cursor: pointer;
}

button.status-row:hover {
  background: var(--bg-2);
}

.dock .status-row .status {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  color: inherit;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.sdot {
  flex: none;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--ok);
  box-shadow: 0 0 0 3px var(--ok-soft);
}

.sdot.warn {
  background: var(--warn);
  box-shadow: 0 0 0 3px var(--warn-soft);
}

.dock .q {
  margin-left: auto;
  font-size: var(--fs-2);
  font-weight: 600;
  white-space: nowrap;
}

.dock .q[data-tone="good"] {
  color: var(--ok-text);
}

.dock .q[data-tone="fair"] {
  color: var(--warn-text);
}

.dock .q[data-tone="poor"] {
  color: var(--danger-text);
}

.dock.technical .q {
  font-family: var(--mono);
  font-size: var(--fs-1);
}

/* In technical mode in a session: the numbers in one line under the status. */
.dock .foot-summary {
  margin: -4px 0 0;
  padding: 0 8px;
  color: var(--text-2);
  font: var(--fs-1) / 1.45 var(--mono);
  overflow-wrap: anywhere;
}

.dock .quality {
  padding: 8px 10px;
  border-radius: 9px;
  border: 1px solid var(--line-1);
  background: var(--bg-2);
  color: var(--text-2);
  font-size: var(--fs-2);
  line-height: 1.45;
}

.dock .quality p {
  margin: 0 0 4px;
}

.dock .tech-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 7px 8px 7px 10px;
  border-radius: var(--r-m);
  border: 1px solid var(--line-1);
  background: var(--bg-2);
}

.dock .tech-row.on {
  border-color: var(--accent-line);
}

.dock .tech-row .tr-icon {
  display: grid;
  color: var(--text-3);
}

.dock .tech-row.on .tr-icon {
  color: var(--accent);
}

.dock .tech-row .t {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
}

.dock .tech-row b {
  font-size: var(--fs-2);
  line-height: 1.3;
}

.dock .tech-row .sub {
  color: var(--text-3);
  font-size: var(--fs-2);
  line-height: 1.3;
}

.dock .tech-row .kb {
  display: flex;
  gap: 2px;
  margin-top: 3px;
}

/* The dock's switch: a button with `role="switch"`, drawn as the inputs are. */
button.switch {
  flex: none;
  width: 38px;
  height: 22px;
  padding: 0;
  border-radius: 999px;
  border: 1px solid var(--line-2);
  background: radial-gradient(circle at 10px 50%, var(--text-2) 0 7px, transparent 7.5px) var(--bg-4);
  cursor: pointer;
}

button.switch[aria-checked="true"] {
  border-color: transparent;
  background: radial-gradient(circle at 26px 50%, #fff 0 7px, transparent 7.5px) var(--accent-bg);
}

.dock .foot-row {
  display: flex;
  gap: 6px;
}

.dock .foot-row .btn {
  flex: 1;
  justify-content: flex-start;
}

/* ===== Settings ===== */

/* The settings page (settings.ts, docs/ui-plan.md § "Step 6"): in the dock
   instead of the list, at home and in a session (beside the picture, over
   it in fullscreen, a sheet on phones). A head with "‹" and the title, then
   the groups, scrolling on their own. The footer keeps the status and
   "Settings" (pressed) while it is open; its switch is the page's own. */
.dock .settings {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
}

.settings .page-head {
  position: relative;
  display: flex;
  align-items: center;
  gap: 6px;
  min-height: 56px;
  padding: 12px 10px 6px;
}

.settings .page-head h2 {
  flex: 1;
  margin: 0;
  font-size: var(--fs-4);
}

/* Its close button only over the picture and as a sheet: beside the
   picture "‹" and "Settings" close it. */
.dock:not([data-shape="overlay"], [data-shape="sheet"]) .settings .page-head .close {
  display: none;
}

.settings .page-body {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: 0 8px 12px;
  container: settings / inline-size;
}

/* As a sheet: the grabber the list's head has. */
.dock[data-shape="sheet"] .settings .page-head {
  padding-top: 18px;
}

.dock[data-shape="sheet"] .settings .page-head::before {
  content: "";
  position: absolute;
  top: 6px;
  left: 50%;
  width: 40px;
  height: 4px;
  margin-left: -20px;
  border-radius: 2px;
  background: var(--line-2);
}

.settings .group {
  margin-bottom: 8px;
  padding: 4px 8px 12px;
  border-bottom: 1px solid var(--line-1);
}

.settings .group > h3 {
  margin: 6px 0 10px;
  color: var(--text-2);
  font-size: var(--fs-1);
  font-weight: 700;
  letter-spacing: 0.09em;
  text-transform: uppercase;
}

.settings .field + .field {
  margin-top: 14px;
}

.settings .f-label {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 8px;
  margin-bottom: 7px;
  font-size: var(--fs-2);
  font-weight: 650;
}

.settings .f-label .tag {
  margin-left: 4px;
  padding: 1px 6px;
  border-radius: 4px;
  border: 1px solid var(--accent-line);
  color: var(--accent);
  font: 600 var(--fs-1) / 1.3 var(--mono);
  letter-spacing: 0.04em;
  text-transform: uppercase;
  vertical-align: 1px;
}

.settings .now {
  color: var(--text-3);
  font-size: var(--fs-2);
  font-weight: 500;
  white-space: nowrap;
}

.settings .select {
  width: 100%;
  min-height: 36px;
  background: var(--bg-2);
}

.settings .help {
  margin: 6px 0 0;
  color: var(--text-3);
  font-size: var(--fs-2);
  line-height: 1.4;
}

.settings .help.saved {
  margin: 4px 8px 0;
  text-align: center;
}

/* A switch: its name and help first, the switch at the right. The input
   is the label's first child (its parent's text says what it does). */
.settings label.toggle {
  display: flex;
  flex-direction: row-reverse;
  align-items: flex-start;
  gap: 12px;
  padding: 2px 0 12px;
  cursor: pointer;
}

.settings label.toggle > .t {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
}

.settings label.toggle b {
  margin-bottom: 2px;
  font-size: var(--fs-2);
}

.settings label.toggle small {
  color: var(--text-3);
  font-size: var(--fs-2);
  line-height: 1.4;
}

.settings label.toggle small.kb {
  margin-top: 6px;
}

.settings label.toggle small.kb kbd + kbd,
.settings dl.shortcuts kbd + kbd {
  margin-left: 2px;
}

/* The client's own shortcuts: each key combination over what it does in
   the dock's width, beside it where the page is wider (a sheet). */
.settings dl.shortcuts {
  grid-template-columns: minmax(0, 1fr);
  gap: 3px 12px;
  align-items: center;
  margin: 0;
  padding: 10px 12px;
  border-radius: var(--r-m);
  background: var(--bg-2);
  color: var(--text-2);
  font-size: var(--fs-2);
}

.settings dl.shortcuts dt {
  color: var(--text-3);
  white-space: nowrap;
}

.settings dl.shortcuts dt:not(:first-child) {
  margin-top: 7px;
}

@container settings (min-width: 400px) {
  .settings dl.shortcuts {
    grid-template-columns: max-content minmax(0, 1fr);
    gap: 7px 14px;
  }

  .settings dl.shortcuts dt:not(:first-child) {
    margin-top: 0;
  }
}

/* A sentence and its button: "Wake screens", "Copy diagnostics". The
   button goes below the sentence where both do not fit a line. */
.settings .action-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px 12px;
}

.settings .action-row > p {
  flex: 1 1 14rem;
  min-width: 0;
  margin: 0;
  color: var(--text-3);
  font-size: var(--fs-2);
  line-height: 1.4;
}

.settings label.toggle + .action-row {
  margin-top: 2px;
}

/* The "Connect" group (connectgroup.ts): the link as a QR code, devices
   and approvals, one row each. */
.settings .connect h4 {
  margin: 14px 0 6px;
  color: var(--text-2);
  font-size: var(--fs-2);
  font-weight: 600;
}

.settings .connect .action-row + .action-row {
  margin-top: 8px;
}

.settings .connect .warn {
  color: var(--warn-text);
}

.settings .connect .qr-row {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  gap: 12px;
  margin: 8px 0;
}

/* Dark on light and large enough for a phone's camera at arm's length. */
.settings .connect svg.qr {
  width: 200px;
  height: 200px;
  border-radius: 6px;
}

.settings .connect .qr-row .actions {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.settings .connect input[name="device_name"] {
  flex: 1 1 12rem;
}

/* The copy fallback's text area (diagnostics.ts), out of sight. */
.copy-buffer {
  position: fixed;
  top: 0;
  left: -10000px;
  width: 1px;
  height: 1px;
  opacity: 0;
}

/* The rail: thumbnails with their names as tooltips, a crossed-out eye on
   windows nobody looks at, the section icons, and in the footer the
   status dot, the technical switch's pulse, Settings and the chevron. */
.dock[data-shape="rail"] {
  width: var(--rail-w);
}

.dock[data-shape="rail"] .dock-head {
  justify-content: center;
  min-height: 0;
  padding: 10px 0 6px;
}

.dock[data-shape="rail"] .rail-open {
  width: 44px;
  min-height: 44px;
}

.dock[data-shape="rail"] .dock-head .brand,
.dock[data-shape="rail"] .dock-search,
.dock[data-shape="rail"] .recent-sect,
.dock[data-shape="rail"] .sect-help,
.dock[data-shape="rail"] .note,
.dock[data-shape="rail"] .empty,
.dock[data-shape="rail"] .ws-h,
.dock[data-shape="rail"] .hg-h,
.dock[data-shape="rail"] .sect-h .count,
.dock[data-shape="rail"] .t-text,
.dock[data-shape="rail"] .badge-now,
.dock[data-shape="rail"] .t-tech,
.dock[data-shape="rail"] .status-row .status,
.dock[data-shape="rail"] .q {
  display: none;
}

.dock[data-shape="rail"] .dock-body {
  padding: 4px 0 10px;
  scrollbar-width: none;
}

.dock[data-shape="rail"] .sect + .sect {
  margin: 8px 18px 0;
  padding-top: 6px;
}

.dock[data-shape="rail"] .sect-h {
  justify-content: center;
  padding: 2px 0 6px;
}

/* The section's icon only: its name is too long for 68 px. */
.dock[data-shape="rail"] .sect-h h2 {
  font-size: 0;
}

.dock[data-shape="rail"] .hidden-workspace {
  margin: 0;
  padding: 0;
  border: 0;
  background: none;
}

.dock[data-shape="rail"] .workspace + .workspace,
.dock[data-shape="rail"] .workspace + .hidden-workspace {
  margin-top: 7px;
}

.dock[data-shape="rail"] ul.targets {
  align-items: center;
  gap: 7px;
}

.dock[data-shape="rail"] button.target {
  display: block;
  width: 52px;
  padding: 0;
  border: 0;
  border-radius: 7px;
}

.dock[data-shape="rail"] button.target .thumb {
  width: 52px;
  height: 36px;
  aspect-ratio: auto;
  border-radius: 7px;
  border-color: var(--line-2);
}

.dock[data-shape="rail"] .thumb > img.thumbnail {
  object-fit: cover;
}

.dock[data-shape="rail"] .thumb > svg.i {
  width: 16px;
  height: 16px;
}

/* A tile without a picture names its target, in the rail only (the list
   shows the name beside it): the icons alone all look the same. */
.thumb-name {
  display: none;
}

.dock[data-shape="rail"] .thumb:has(> .thumb-name) {
  align-content: center;
  gap: 1px;
}

.dock[data-shape="rail"] .thumb > .thumb-name {
  display: block;
  max-width: 46px;
  overflow: hidden;
  font-size: 9px;
  font-weight: 600;
  line-height: 1.15;
  color: var(--text-2);
  text-overflow: ellipsis;
  white-space: nowrap;
}

.dock[data-shape="rail"] button.target[aria-current="true"] {
  background: none;
  box-shadow: 0 0 0 2px var(--bg-1), 0 0 0 4px var(--accent);
}

.dock[data-shape="rail"] .thumb .hid {
  position: absolute;
  right: 1px;
  bottom: 1px;
  display: grid;
  padding: 1px;
  border-radius: 4px;
  background: rgb(0 0 0 / 0.75);
  color: var(--text-2);
}

.dock[data-shape="rail"] .dock-foot {
  align-items: center;
  gap: 4px;
  padding: 8px 0 10px;
}

.dock[data-shape="rail"] .status-row {
  justify-content: center;
  width: auto;
  min-height: 24px;
  padding: 0 8px;
}

/* The plain status beside the rail, over the picture, not squeezed into
   its 68 px. */
.dock[data-shape="rail"] .dock-foot {
  position: relative;
}

.dock[data-shape="rail"] .quality {
  position: absolute;
  left: calc(100% + 8px);
  bottom: 10px;
  width: 280px;
  box-shadow: var(--shadow-2);
}

.dock[data-shape="rail"] .foot-row .btn {
  flex: none;
  width: 32px;
  padding: 0;
  justify-content: center;
}

.dock[data-shape="rail"] .foot-row .btn > .lbl {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
}

.dock .tech-icon[aria-pressed="true"] {
  border-color: transparent;
  background: var(--accent-soft);
  color: var(--accent);
}

/* Over the picture: fullscreen, and a window too narrow to pin the list
   (its rail's room stays, so the picture does not move). */
.dock[data-shape="overlay"] {
  position: absolute;
  top: var(--bar-h);
  bottom: 0;
  left: 0;
  border-top: 1px solid var(--line-1);
  box-shadow: var(--shadow-2);
  z-index: 10;
}

.viewer.fullscreen .dock[data-shape="overlay"] {
  top: calc(var(--tab-h) + var(--bar-h));
}

/* A sheet from the bottom on phones, over a scrim a tap closes it on. */
.dock[data-shape="sheet"] {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  width: auto;
  height: 80%;
  border: 1px solid var(--line-2);
  border-bottom: 0;
  border-radius: 18px 18px 0 0;
  box-shadow: var(--shadow-2);
  z-index: 22;
}

.dock[data-shape="sheet"] .dock-head {
  padding-top: 18px;
}

.dock[data-shape="sheet"] .dock-head::before {
  content: "";
  position: absolute;
  top: 6px;
  left: 50%;
  width: 40px;
  height: 4px;
  margin-left: -20px;
  border-radius: 2px;
  background: var(--line-2);
}

.viewer > .scrim {
  position: absolute;
  inset: 0;
  background: rgb(0 0 0 / 0.55);
  z-index: 21;
}

/* ===== Viewer ===== */

/* The session fills the window. `.viewer`, the fullscreen element, is a row
   of the dock, the stage column and the docked HUD (below the stage in
   narrow windows). The stage column holds the toolbar's row and the
   viewport, the box the picture is fitted into and input and the cursor are
   mapped in (picture.ts, docs/ui-plan.md § "Layout"). The stage is the
   container of the toolbar's queries. A floating dock (over the picture, a
   sheet) takes no column. The HUD's column has a fixed width while the HUD
   shows (`data-hud`, set with the HUD), so the HUD's content never sizes
   the columns and its updates never resize the picture (docs/ui-plan.md
   § "Step 4b"). */
body:has(.viewer) {
  max-width: none;
  padding: 0;
  overflow: hidden;
}

.viewer {
  --hud-col: 0px;
  position: fixed;
  inset: 0;
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) var(--hud-col);
  grid-template-rows: minmax(0, 1fr);
  background: var(--bg-0);
  color: var(--text-1);
  overflow: hidden;
}

.viewer[data-hud] {
  --hud-col: var(--hud-w);
}

.viewer > .dock {
  grid-column: 1;
  grid-row: 1 / -1;
}

.viewer > .stage {
  grid-column: 2;
  grid-row: 1;
}

/* The HUD's cell: the HUD inside it, not a grid item itself, is laid out
   on its own (Chromium lays out a grid item only with its grid). The
   contained HUD is a stacking context: the cell keeps it beneath the stage
   as the uncontained HUD was, so a panel the stage lets out stays over it
   and the stage's layers keep their text rendering. Its negative z-index
   stays inside `.viewer` because `.viewer` is a stacking context of its own
   (`position: fixed` makes one): without one the cell would sink below
   the page's background. */
.viewer > .hud-cell {
  position: relative;
  z-index: -1;
  grid-column: 3;
  grid-row: 1;
  min-width: 0;
  min-height: 0;
}

/* The list over the picture in a narrow window keeps the rail's room, so
   the picture does not move. */
.viewer[data-dock="overlay"]:not(.fullscreen) {
  grid-template-columns: var(--rail-w) minmax(0, 1fr) var(--hud-col);
}

.stage {
  position: relative;
  flex: 1 1 0;
  min-width: 0;
  min-height: 0;
  display: flex;
  flex-direction: column;
  container: stage / inline-size;
}

.viewport {
  position: relative;
  flex: 1 1 0;
  min-height: 0;
  overflow: hidden;
  background: var(--bg-0);
}

/* The picture: a clip box at the content rect's place in the viewport. The
   media element inside is placed so that only the content rect shows
   (picture.ts). */
.viewport .picture {
  position: absolute;
  overflow: hidden;
}

.viewport .video {
  position: absolute;
  object-fit: fill;
}

.viewport canvas.video {
  image-rendering: auto;
}

.viewport .surface {
  position: absolute;
  inset: 0;
  outline: none;
  touch-action: none;
  user-select: none;
}

/* The session's column takes no browser gesture either (docs/ui-plan.md
   § "Step 14"): a pinch on the toolbar or a strip zoomed the whole page,
   and the picture's own zoom is the gestures' (input/touch.ts). The dock,
   the HUD and the keyboard's row scroll as before. */
.viewer > .stage {
  touch-action: none;
}

/* The surface shows no focus ring: the outline below says whether it has
   the keyboard. */
.viewport .surface:focus-visible {
  box-shadow: none;
}

.viewport .surface.hide-pointer {
  cursor: none;
}

/* While the capture offer stands, the browser's own arrow over the whole
   picture: an app that takes the mouse hides its cursor, so the host's is
   not drawn, and the click anywhere that takes the mouse and the way to
   the offer's chip are aimed with this one. It beats the host's shape,
   which the viewer writes into the surface's style. The host's cursor
   comes back with the offer's end. */
.viewport[data-capture-offer] > .surface {
  cursor: default !important;
}

.viewport[data-capture-offer] > .cursor {
  visibility: hidden;
}

.viewport .cursor {
  position: absolute;
  left: 0;
  top: 0;
  pointer-events: none;
  image-rendering: pixelated;
  z-index: 2;
}

/* While the session takes input and the picture does not have the
   keyboard: an amber outline around the viewport, and "Click the picture
   to type" at its top (`.type-hint`, below). Neither takes pointer
   events. */
.stage.takes-input:not(:has(.surface:focus)) > .viewport::before {
  content: "";
  position: absolute;
  inset: 0;
  box-shadow: inset 0 0 0 2px rgb(243 179 61 / 0.75);
  pointer-events: none;
  z-index: 3;
}

/* The toolbar in its own row above the viewport, so the picture is never
   under it (P1-3). One row is exactly --bar-h tall (its line is a shadow
   inside, not a border). It wraps only where even the icons do not fit
   (a column the HUD and the settings page beside it leave a few hundred
   pixels wide). Phones and narrow windows have the bars instead. */
.stage .toolbar {
  position: relative;
  flex: none;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s-1);
  min-height: var(--bar-h);
  padding: var(--s-2);
  background: var(--bg-1);
  box-shadow: inset 0 -1px 0 var(--line-1);
  z-index: 4;
}

/* The title takes the room the buttons leave, one line with an ellipsis,
   the chips after it. Its height never follows what the chips hold: a
   toolbar of another height would move the picture. */
.stage .toolbar .name {
  flex: 1 1 0;
  min-width: 4rem;
  height: 24px;
  display: flex;
  align-items: center;
  gap: 7px;
  margin: 0 6px;
  overflow: hidden;
}

.stage .toolbar .title {
  flex: 0 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: var(--fs-3);
  font-weight: 650;
}

/* A window's app ("Firefox") after its title. */
.stage .toolbar .app {
  flex: none;
  color: var(--text-3);
  font-size: var(--fs-2);
  white-space: nowrap;
}

/* Fullscreen: how to leave it. */
.stage .toolbar .tip {
  flex: none;
  margin-right: 6px;
  color: var(--text-3);
  font-size: var(--fs-2);
  white-space: nowrap;
}

.stage .toolbar .sep {
  flex: none;
  width: 1px;
  height: 22px;
  margin: 0 4px;
  background: var(--line-2);
}

/* The controls between the title and "Stop" are the row's own items here
   (`.tools` is the phone's bottom bar, § "The phone's bars"); "Switch"
   comes first, before the title. */
.stage .toolbar .tools {
  display: contents;
}

.stage .toolbar .tools > .keep {
  order: -1;
}

/* Technical mode with the HUD closed: the delay and the rate shown, a
   click opens the HUD. */
.stage .toolbar .chip-lat {
  font-variant-numeric: tabular-nums;
}

.stage .toolbar .chip-lat:hover {
  background: var(--bg-4);
  color: var(--text-1);
}

/* "Sound" says its state by its icon (the speaker, or crossed out), so
   "on" is not drawn as pressed. */
.stage .toolbar .btn.sound[aria-pressed="true"] {
  background: transparent;
  border-color: transparent;
  color: var(--text-2);
}

.stage .toolbar .btn.sound[aria-pressed="true"]:hover {
  background: var(--bg-3);
  color: var(--text-1);
}

/* "Keys"' chevron says that it opens a popover. */
.stage .toolbar .btn > svg.i.xs {
  margin-left: -2px;
  color: var(--text-3);
}

/* "Keys": a popover below its button with "Super" and "Alt+Tab". */
.stage .toolbar .keys-wrap {
  position: relative;
  flex: none;
  display: flex;
}

.stage .toolbar .keys {
  position: absolute;
  top: calc(100% + 16px);
  right: 0;
  width: 320px;
  max-width: calc(100vw - 16px);
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: var(--s-2);
  border-radius: var(--r-l);
  border: 1px solid var(--line-2);
  background: var(--bg-2);
  box-shadow: var(--shadow-2);
  z-index: 8;
}

.stage .toolbar .keys .row {
  display: grid;
  grid-template-columns: 84px minmax(0, 1fr);
  gap: 10px;
  align-items: center;
  padding: 6px;
}

.stage .toolbar .keys .row .btn {
  width: 100%;
}

.stage .toolbar .keys .row span {
  color: var(--text-3);
  font-size: var(--fs-2);
  line-height: 1.35;
}

.stage .toolbar .keys .note {
  margin: 4px 0 0;
  padding: 8px 6px 2px;
  border-top: 1px solid var(--line-1);
  font-size: var(--fs-2);
  line-height: 1.4;
}

/* Fullscreen: the toolbar hides, the tab at the top reveals it over the
   picture, hanging below the tab, which stays in its place (controls.ts,
   P1-4). */
.viewer.fullscreen .stage .toolbar {
  position: absolute;
  top: var(--tab-h);
  left: 0;
  right: 0;
  background: rgb(15 18 23 / 0.97);
  box-shadow: inset 0 -1px 0 var(--line-1), var(--shadow-2);
  z-index: 7;
}

.viewer.fullscreen:not(.controls) .stage .toolbar {
  display: none;
}

.viewport .tab {
  position: absolute;
  top: 0;
  left: 50%;
  transform: translateX(-50%);
  display: flex;
  align-items: center;
  gap: 6px;
  height: var(--tab-h);
  max-width: min(16rem, calc(100% - 2rem));
  padding: 0 14px 0 10px;
  border-radius: 0 0 12px 12px;
  border: 1px solid rgb(255 255 255 / 0.08);
  border-top: 0;
  /* Quiet through its colours, not through opacity: its text keeps its
     contrast over a bright picture, and the picture does not show through. */
  background: rgb(15 18 23 / 0.92);
  color: var(--text-2);
  font-size: var(--fs-2);
  font-weight: 650;
  white-space: nowrap;
  cursor: pointer;
  z-index: 6;
}

.viewport .tab .text {
  overflow: hidden;
  text-overflow: ellipsis;
}

.viewport .tab:hover,
.viewport .tab:focus-visible {
  border-color: var(--line-2);
  color: var(--text-1);
}

/* Revealed: the tab joins the toolbar below it. */
.viewer.controls .viewport .tab {
  border-bottom: 0;
  border-radius: 12px 12px 0 0;
  background: rgb(15 18 23 / 0.97);
  color: var(--text-1);
  opacity: 1;
}

/* ===== Messages (docs/ui-plan.md § "Step 5") ===== */

/* Strips: rows between the toolbar and the picture, so they cover nothing
   (P2-3, P2-13). The message strip holds `.banner` (the icon, the text and
   the action) with "Details" and its text outside it; a hint strip holds
   them itself, with its close button. A strip appears only at a session's
   start or a change of state. */
.stage .strips {
  position: relative;
  flex: none;
  display: flex;
  flex-direction: column;
  z-index: 3;
}

/* The confined pointer's strip (viewer.ts `over`): the app confines and
   frees the mouse as it likes (a game's menus), so its strip hangs over the
   top of the picture, below the other strips, and never resizes it. */
.viewer:not(.fullscreen) .stage .strips > .strip.over {
  position: absolute;
  top: 100%;
  left: 0;
  right: 0;
  box-shadow: inset 0 -1px 0 var(--line-1), var(--shadow-2);
}

.strip {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  gap: 6px 10px;
  padding: 8px 10px 8px 12px;
  background: var(--bg-1);
  box-shadow: inset 0 -1px 0 var(--line-1);
  font-size: var(--fs-2);
  line-height: 1.45;
  color: var(--text-2);
}

.strip.warn {
  background: linear-gradient(var(--warn-soft), var(--warn-soft)), var(--bg-1);
  box-shadow: inset 3px 0 0 var(--warn), inset 0 -1px 0 var(--line-1);
}

.strip.info {
  background: linear-gradient(var(--info-soft), var(--info-soft)), var(--bg-1);
  box-shadow: inset 3px 0 0 var(--info), inset 0 -1px 0 var(--line-1);
}

.strip .banner {
  flex: 1 1 24rem;
  min-width: 0;
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  gap: 6px 10px;
}

.strip .s-icon {
  flex: none;
  display: grid;
  padding-top: 6px;
}

.strip.warn .s-icon {
  color: var(--warn);
}

.strip.info .s-icon {
  color: var(--info);
}

.strip .text {
  flex: 1 1 16rem;
  min-width: 0;
  padding-top: 5px;
  overflow-wrap: anywhere;
}

.strip .text b,
.session-message .text b,
.toast .text b {
  color: var(--text-1);
  font-weight: 650;
}

/* The action, and what it does to the person at the computer under it. */
.strip .act {
  flex: none;
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 2px;
  max-width: 24rem;
}

.strip .act:not(:has(> .action:not([hidden]))) {
  display: none;
}

.strip .consequence {
  color: var(--text-3);
  font-size: var(--fs-1);
  line-height: 1.35;
  text-align: right;
}

.strip > .close {
  margin-left: -4px;
}

/* "Details" and its text: the technical wording, the facts, and "Always
   show technical details". */
.details-toggle > svg.i.xs {
  color: var(--text-3);
}

.details {
  font: var(--fs-1) / 1.55 var(--mono);
  color: var(--text-2);
}

.strip > .details {
  flex: 0 0 calc(100% - 26px);
  min-width: 0;
  margin: 0 0 2px 26px;
  padding: 7px 10px;
  border-radius: var(--r-s);
  background: rgb(0 0 0 / 0.28);
}

.details p {
  margin: 0;
  overflow-wrap: anywhere;
}

.details .facts {
  color: var(--text-3);
}

.details code {
  font: inherit;
  font-weight: 700;
  color: var(--warn-text);
}

.strip.info .details code {
  color: var(--info-text);
}

.details .always {
  margin-top: 4px;
  font-family: var(--font);
  font-size: var(--fs-1);
}

/* A state card's steps (the opening card's): done, under way, to come. */
.details .steps {
  margin: 6px 0 2px;
  padding: 0;
  list-style: none;
  text-align: left;
}

.details .steps li {
  display: flex;
  gap: var(--s-2);
  align-items: baseline;
  overflow-wrap: anywhere;
}

.details .steps .st {
  flex: none;
  width: 12px;
  text-align: center;
}

.details .steps .done .st {
  color: var(--ok);
}

.details .steps .now {
  color: var(--text-1);
}

.details .steps .now .st {
  color: var(--accent);
}

.details .steps .todo {
  color: var(--text-3);
}

/* Steps to come are quiet, their code too. */
.details .steps .todo code {
  color: inherit;
  font-weight: inherit;
}

/* In fullscreen the strips hang below the revealed toolbar, over the
   picture; while it is hidden the tab carries a chip instead (controls.ts),
   so a strip never moves the picture there. */
.viewer.fullscreen .stage .strips {
  position: absolute;
  top: calc(var(--tab-h) + var(--bar-h));
  left: 0;
  right: 0;
  z-index: 7;
  box-shadow: var(--shadow-2);
}

.viewer.fullscreen:not(.controls) .stage .strips {
  display: none;
}

.viewport .tab .tab-chip {
  flex: none;
  margin-left: 4px;
  padding: 1px 8px;
  border-radius: 999px;
  border: 1px solid rgb(243 179 61 / 0.32);
  background: var(--warn-soft);
  color: var(--warn-text);
  font-size: var(--fs-1);
}

/* State cards: centred over the picture while it is not live, which is
   then dimmed (P2-4, P2-5). The card lets the pointer through but for its
   controls, so a click meant for the picture still reaches it. */
.viewport.dim .picture {
  filter: grayscale(0.75);
}

.viewport.dim::after {
  content: "";
  position: absolute;
  inset: 0;
  background: rgb(7 9 12 / 0.6);
  pointer-events: none;
  z-index: 2;
}

.viewport .state-card {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  max-height: calc(100% - 24px);
  overflow: auto;
  pointer-events: none;
  z-index: 5;
}

.state-card,
.lock-card {
  width: min(420px, calc(100% - 32px));
  padding: 22px 22px 20px;
  border-radius: var(--r-l);
  border: 1px solid var(--line-2);
  background: rgb(22 26 33 / 0.95);
  box-shadow: var(--shadow-2);
  text-align: center;
  color: var(--text-1);
}

.state-card button,
.state-card .details {
  pointer-events: auto;
}

.sc-visual {
  display: grid;
  place-items: center;
  margin: 0 auto;
}

.sc-visual.sc-icon {
  width: 52px;
  height: 52px;
  border-radius: 50%;
  background: var(--bg-3);
  color: var(--text-2);
}

.state-card.warn .sc-visual.sc-icon {
  background: var(--warn-soft);
  color: var(--warn);
}

.sc-visual.sc-thumb {
  position: relative;
  width: 216px;
  max-width: 100%;
  aspect-ratio: 16 / 9;
  border-radius: var(--r-m);
  border: 1px solid var(--line-2);
  overflow: hidden;
  background: var(--bg-0);
}

.sc-visual.sc-thumb > img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  filter: brightness(0.55);
}

.sc-visual.sc-thumb > .spinner {
  position: absolute;
}

.spinner {
  width: 30px;
  height: 30px;
  border: 3px solid rgb(255 255 255 / 0.22);
  border-top-color: #fff;
  border-radius: 50%;
  animation: spin 0.9s linear infinite;
}

@keyframes spin {
  to {
    transform: rotate(360deg);
  }
}

.sc-title {
  margin: 14px 0 4px;
  font-size: var(--fs-5);
  letter-spacing: -0.01em;
  overflow-wrap: anywhere;
}

.sc-more {
  margin: 0;
  color: var(--text-2);
  overflow-wrap: anywhere;
}

.state-card > .details,
.lock-card > .details {
  margin-top: 12px;
  padding: 8px 10px;
  border-radius: var(--r-s);
  background: rgb(0 0 0 / 0.3);
  text-align: left;
  overflow-wrap: anywhere;
}

.sc-foot {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  align-items: center;
  gap: 8px;
  margin-top: 16px;
}

.sc-foot > .actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 8px;
}

/* The privacy shutter: covers the picture of a window whose stream pauses
   under the lock, with the lock card (graft 1 of docs/ui-plan.md). It
   takes the pointer: nothing behind it may be clicked. */
.viewport .shutter {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  padding: 16px;
  background: var(--bg-1);
  z-index: 4;
}

/* Toasts: short messages at the bottom, stacked, never in place of a strip
   or a card. Over everything of the session, the floating dock and the
   sheet's scrim included (the stage and the viewport are no stacking
   contexts), so a phone shows what Copy diagnostics did with the settings
   open. */
.toasts {
  position: absolute;
  left: 50%;
  bottom: 20px;
  transform: translateX(-50%);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  width: max-content;
  max-width: calc(100% - 24px);
  pointer-events: none;
  z-index: 30;
}

/* The home view's, at the bottom of the page. */
.home + .toasts {
  position: fixed;
}

.toast {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto auto;
  align-items: center;
  gap: 4px 10px;
  max-width: 40rem;
  padding: 7px 6px 7px 12px;
  border-radius: 11px;
  border: 1px solid var(--line-2);
  background: var(--bg-2);
  box-shadow: var(--shadow-2);
  color: var(--text-1);
  font-size: var(--fs-2);
  /* Its buttons and opened details take the pointer, its text lets a click
     through to the picture, as the state card does. */
  pointer-events: none;
}

.toast button,
.toast > .details {
  pointer-events: auto;
}

.toast > svg.i {
  flex: none;
  color: var(--accent);
}

.toast.error > svg.i {
  color: var(--warn);
}

.toast .text {
  min-width: 0;
}

.toast > .details {
  grid-column: 1 / -1;
  padding: 6px 8px;
  border-radius: var(--r-s);
  background: rgb(0 0 0 / 0.28);
}

/* "Click the picture to type": at the top of the picture while typing can
   reach the target and the picture does not have the keyboard (P1-1). */
.viewport .type-hint {
  display: none;
  position: absolute;
  left: 50%;
  top: 12px;
  transform: translateX(-50%);
  pointer-events: none;
  z-index: 3;
}

.stage.takes-input:not(:has(.surface:focus)) .type-hint {
  display: block;
}

.viewer.fullscreen .viewport .type-hint {
  top: calc(var(--tab-h) + 10px);
}

.stage.takes-input:not(:has(.surface:focus)) .type-hint::before {
  content: "Click the picture to type";
  display: block;
  padding: 5px 14px;
  border-radius: 999px;
  border: 1px solid rgb(243 179 61 / 0.5);
  background: rgb(22 26 33 / 0.94);
  box-shadow: var(--shadow-2);
  color: var(--warn-text);
  font-size: var(--fs-2);
  font-weight: 600;
  white-space: nowrap;
}

/* The chips after the title: the kind, the input, the state (graft 2). */
.stage .toolbar .chips {
  flex: none;
  display: flex;
  align-items: center;
  gap: 6px;
  margin-left: 4px;
}

.stage .toolbar .chip {
  height: 24px;
  min-height: 0;
}

.spin-s {
  width: 9px;
  height: 9px;
  border: 2px solid currentColor;
  border-right-color: transparent;
  border-radius: 50%;
  animation: spin 0.9s linear infinite;
}

@media (prefers-reduced-motion: reduce) {
  .spinner,
  .spin-s {
    animation: none;
  }
}

/* The capture offer (docs/ui-plan.md § "Step 10"): the app holds the
   mouse and the browser does not. No box over the picture, the browser
   shows a notice of its own once the page has the mouse. A chip after the
   title's chips, "Click to control" with its × ("Not now"), two buttons
   in one pill. The focus ring lies inside the buttons, since the title's
   row clips what lies outside it. */
.capture-offer {
  display: inline-flex;
  align-items: stretch;
  flex: none;
  height: 24px;
  border-radius: 999px;
  border: 1px solid var(--accent-line);
  background: var(--accent-soft);
  color: var(--info-text);
  font-size: var(--fs-1);
  font-weight: 600;
  white-space: nowrap;
  overflow: hidden;
}

.capture-offer > button {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  margin: 0;
  border: 0;
  background: transparent;
  color: inherit;
  font: inherit;
  cursor: pointer;
}

.capture-offer > .capture-take {
  padding: 0 8px 0 9px;
  border-radius: 999px 0 0 999px;
}

.capture-offer > .capture-later {
  padding: 0 7px 0 6px;
  border-left: 1px solid var(--accent-line);
  border-radius: 0 999px 999px 0;
  color: var(--text-2);
}

.capture-offer > button:hover {
  background: rgb(94 161 247 / 0.16);
  color: var(--text-1);
}

.capture-offer > button:focus-visible {
  box-shadow: inset 0 0 0 2px var(--accent);
}

/* In the toolbar the offer's chip stands where "You control it" was: a
   click on the picture takes the mouse then, it does not reach the app.
   Only where the chip shows (below). */
@media not all and (pointer: coarse) {
  .stage .toolbar .chips:has(> .capture-offer:not([hidden])) > .chip-input:not(.warn) {
    display: none;
  }
}

/* In fullscreen, while the toolbar is hidden, the chip hangs below the
   tab, opaque over the picture. The revealed toolbar has its own. */
.viewport > .capture-offer {
  display: none;
}

.viewer.fullscreen:not(.controls) .viewport > .capture-offer {
  display: inline-flex;
  position: absolute;
  top: calc(var(--tab-h) + 6px);
  left: 50%;
  transform: translateX(-50%);
  background: linear-gradient(var(--accent-soft), var(--accent-soft)), rgb(15 18 23 / 0.94);
  box-shadow: var(--shadow-2);
  z-index: 6;
}

/* "Click the picture to type" goes below the tab's chip while that
   shows, to the place of the first offer's hint (below). Only where the
   chip shows (a fine pointer). */
@media not all and (pointer: coarse) {
  .viewer.fullscreen:not(.controls) .viewport:has(> .capture-offer:not([hidden])) > .type-hint {
    top: calc(var(--tab-h) + 40px);
  }
}

/* The first offer's hint, once a session: one line at the top of the
   picture that fades in and out by itself (offer.ts `CAPTURE_HINT_MS`,
   as long as this animation), never over the picture's centre and never
   in the way of a click. "Click the picture to type" waits behind it. */
.viewport > .capture-hint {
  position: absolute;
  left: 50%;
  top: 12px;
  transform: translateX(-50%);
  max-width: calc(100% - 2rem);
  width: max-content;
  padding: 5px 14px;
  border-radius: 999px;
  border: 1px solid var(--accent-line);
  background: rgb(22 26 33 / 0.94);
  box-shadow: var(--shadow-2);
  color: var(--text-1);
  font-size: var(--fs-2);
  font-weight: 600;
  text-align: center;
  pointer-events: none;
  z-index: 3;
  animation: capture-hint 3.5s ease both;
}

.viewport > .capture-hint:not([hidden]) ~ .type-hint {
  visibility: hidden;
}

.viewer.fullscreen .viewport > .capture-hint {
  top: calc(var(--tab-h) + 40px);
}

.viewer.fullscreen.controls .viewport > .capture-hint {
  top: calc(var(--tab-h) + var(--bar-h) + 12px);
}

@keyframes capture-hint {
  0% {
    opacity: 0;
  }

  8% {
    opacity: 1;
  }

  86% {
    opacity: 1;
  }

  100% {
    opacity: 0;
  }
}

@media (prefers-reduced-motion: reduce) {
  .viewport > .capture-hint {
    animation: none;
  }
}

/* Ctrl+Alt+Tab: the target the switch goes to on release. */
.stage .mru {
  position: absolute;
  left: 50%;
  top: 40%;
  transform: translateX(-50%);
  padding: 14px 20px;
  border-radius: var(--r-l);
  border: 1px solid var(--line-2);
  background: rgb(22 26 33 / 0.96);
  box-shadow: var(--shadow-2);
  font-size: var(--fs-4);
  font-weight: 600;
  z-index: 6;
}


/* ===== HUD ===== */

/* Docked at the right of the session (P1-7): opaque, scrolling on its own,
   closed from its header, taking the picture's width instead of covering
   it. Its sections are collapsible (hud.ts). It fills its cell
   (`.hud-cell`, a column or row of fixed size while it shows), never sized
   by its content, and is contained: an update, four times a second, lays
   out, styles and paints the HUD alone, never the stage and the picture
   beside it (docs/ui-plan.md § "Step 4b"). */
.hud {
  contain: strict;
  height: 100%;
  overflow-y: auto;
  overscroll-behavior: contain;
  background: var(--bg-1);
  border-left: 1px solid var(--line-1);
  color: var(--text-1);
  font: var(--fs-1) / 1.45 var(--mono);
  font-variant-numeric: tabular-nums;
  scrollbar-width: thin;
  scrollbar-color: var(--bg-4) transparent;
}

.hud > .head {
  position: sticky;
  top: 0;
  z-index: 1;
  display: flex;
  align-items: center;
  gap: var(--s-2);
  min-height: var(--bar-h);
  padding: 6px 8px 6px 14px;
  background: var(--bg-1);
  box-shadow: inset 0 -1px 0 var(--line-1);
  font-family: var(--font);
}

.hud > .head .heading {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
}

.hud > .head strong {
  font-size: var(--fs-3);
  line-height: 1.25;
}

.hud > .head .heading span {
  color: var(--text-3);
  font-size: var(--fs-2);
  line-height: 1.3;
}

.hud > .section {
  margin: 0 12px 0 14px;
  padding: 2px 0 10px;
  border-bottom: 1px solid var(--line-1);
}

.hud > .section > summary {
  display: flex;
  align-items: center;
  gap: var(--s-2);
  padding: 8px 0 6px;
  list-style: none;
  cursor: pointer;
  color: var(--text-1);
  font: 700 var(--fs-2) / 1.3 var(--font);
}

.hud > .section > summary::-webkit-details-marker {
  display: none;
}

/* The section's chevron: right while collapsed, down while open. */
.hud > .section > summary::before {
  content: "";
  width: 6px;
  height: 6px;
  margin: 0 3px 0 1px;
  border-right: 2px solid var(--text-3);
  border-bottom: 2px solid var(--text-3);
  transform: rotate(-45deg);
}

.hud > .section[open] > summary::before {
  margin-top: -3px;
  transform: rotate(45deg);
}

.hud dl {
  grid-template-columns: 8.5rem minmax(0, 1fr);
  margin: 2px 0 6px;
  gap: 2px var(--s-3);
}

.hud table {
  width: 100%;
  border-collapse: collapse;
}

.hud table.frames {
  margin-top: var(--s-2);
}

.hud td,
.hud th {
  padding: 0 0 0 0.4rem;
  text-align: right;
  white-space: nowrap;
}

.hud th {
  font-weight: 600;
  color: var(--text-3);
}

.hud tr th:first-child {
  padding: 0;
  text-align: left;
  font-weight: normal;
  color: var(--text-1);
  white-space: normal;
}

.hud tr:first-child th:first-child {
  color: var(--text-3);
}

/* Input → shown (hud.ts, latency.ts): the headline with its grade and
   history, the mean per stage as a stacked bar, the stage table. The
   headline and the bar have tooltips that explain them. The grade always
   comes with its word. */
.hud .latency {
  margin: 0;
}

.hud .latency .headline {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 0 0.5rem;
  padding-left: 0.5rem;
  border-left: 3px solid var(--s-rest);
}

.hud .latency .headline.good {
  border-left-color: var(--ok);
}

.hud .latency .headline.fair {
  border-left-color: var(--warn);
}

.hud .latency .headline.poor {
  border-left-color: var(--danger);
}

.hud .latency .headline .label,
.hud .latency .headline .unit,
.hud .latency .note {
  color: var(--text-2);
}

.hud .latency .headline .p95 {
  margin-left: auto;
  color: var(--text-3);
}

.hud .latency .headline .value {
  font: 700 24px/1.1 var(--font);
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.02em;
}

.hud .latency .headline .grade {
  align-self: center;
  padding: 0 8px;
  border-radius: 999px;
  font: 700 var(--fs-1) / 1.6 var(--font);
}

.hud .latency .grade.good {
  color: var(--ok-text);
  background: var(--ok-soft);
}

.hud .latency .grade.fair {
  color: var(--warn-text);
  background: var(--warn-soft);
}

.hud .latency .grade.poor {
  color: var(--danger-text);
  background: var(--danger-soft);
}

/* The history on a row of its own, as wide as the HUD. */
.hud .latency .spark {
  flex: 1 0 100%;
  width: 100%;
  height: 32px;
  margin: 4px 0 0;
  overflow: visible;
}

.hud .latency .spark .guide {
  stroke: var(--line-2);
  stroke-width: 1;
  stroke-dasharray: 3 3;
  vector-effect: non-scaling-stroke;
}

.hud .latency .spark .line {
  fill: none;
  stroke: var(--accent);
  stroke-width: 1.5;
  stroke-linejoin: round;
  vector-effect: non-scaling-stroke;
}

.hud .latency .spark .dot {
  stroke-width: 5;
  stroke-linecap: round;
  vector-effect: non-scaling-stroke;
}

.hud .latency .spark .dot.good {
  stroke: var(--ok);
}

.hud .latency .spark .dot.fair {
  stroke: var(--warn);
}

.hud .latency .spark .dot.poor {
  stroke: var(--danger);
}

.hud .latency .bar {
  display: flex;
  gap: 2px;
  height: 8px;
  margin: 0.4rem 0;
}

.hud .latency .bar .seg {
  flex-basis: 0;
  min-width: 1px;
}

.hud .latency .bar .seg:first-child {
  border-radius: 4px 0 0 4px;
}

.hud .latency .bar .seg:last-child {
  border-radius: 0 4px 4px 0;
}

.hud .latency .swatch {
  display: inline-block;
  width: 0.6rem;
  height: 0.6rem;
  margin-right: 0.35rem;
  border-radius: 2px;
}

.hud .latency .s1 {
  background: var(--s1);
}

.hud .latency .s2 {
  background: var(--s2);
}

.hud .latency .s3 {
  background: var(--s3);
}

.hud .latency .s4 {
  background: var(--s4);
}

.hud .latency .s5 {
  background: var(--s5);
}

.hud .latency .s6 {
  background: var(--s6);
}

.hud .latency .s7 {
  background: var(--s7);
}

.hud .latency .s8 {
  background: var(--s8);
}

.hud .latency .residual {
  background: var(--s-rest);
}

.hud .latency tr.sub th,
.hud .latency tr.sub td {
  color: var(--text-3);
}

.hud .latency tr.sub th {
  padding-left: 1.35rem;
}

.hud .latency tr.sum th,
.hud .latency tr.sum td {
  border-top: 1px solid var(--line-2);
  font-weight: 700;
}

.hud .latency .note {
  margin: 0.2rem 0 0;
}

/* The HUD's summary line under its header, once a second (docs/ui-plan.md
   § "Step 7"), and each section's aside in its heading. */
.hud > .summary {
  margin: 10px 12px 4px 14px;
  padding: 7px 10px;
  border: 1px solid var(--line-1);
  border-radius: var(--r-s);
  background: var(--bg-2);
  color: var(--text-1);
  overflow-wrap: anywhere;
}

.hud > .section > summary .aside {
  margin-left: auto;
  overflow: hidden;
  color: var(--text-3);
  font: 500 var(--fs-1) / 1.3 var(--mono);
  text-overflow: ellipsis;
  white-space: nowrap;
}

.hud > .head .copy {
  flex: none;
}

/* Events, the newest first: the time, then what happened. */
.hud .events {
  margin: 2px 0 4px;
  padding: 0;
  list-style: none;
}

.hud .events li {
  display: grid;
  grid-template-columns: 4.6rem minmax(0, 1fr);
  gap: var(--s-2);
  padding: 3px 0;
  border-bottom: 1px solid var(--line-1);
}

.hud .events li:last-child {
  border-bottom: 0;
}

.hud .events time {
  color: var(--text-3);
}

.hud .events .text {
  overflow-wrap: anywhere;
}

.hud .events li.ok .text {
  color: var(--ok-text);
}

.hud .events li.warn .text {
  color: var(--warn-text);
}

.hud .events li.error .text {
  color: var(--danger-text);
}

.hud .events code {
  font: inherit;
  font-weight: 700;
}

/* ===== Narrow sessions ===== */

/* A button whose label shows only as its tooltip and to assistive
   technology (its text stays the label): the icon buttons, and the
   toolbar's buttons where the stage column is narrow. */
.btn.icon > .lbl {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
}

/* The toolbar's row as the stage column narrows (container queries on the
   stage, docs/ui-plan.md § "Step 8"): below 1180 px the fullscreen tip, a
   window's app and the latency chip go, below 1080 px the kind chip, below
   980 px the buttons with an icon show only it, below 760 px "You control
   it" goes and the capture offer's chip shows its icon and its × while
   "Live" gives way to it, below 600 px all chips but a warning ("View
   only", "Reconnecting") and "Switch" and "Stop" show only their icons
   too. The labels stay in the DOM and a title explains each. A phone or a
   narrow window has the phone's two bars instead (`data-bars`, below), a
   column narrowed by the HUD or the settings page beside it keeps this
   row. */
@container stage (max-width: 1180px) {
  .stage .toolbar .tip,
  .stage .toolbar .app,
  .stage .toolbar .chip-lat {
    display: none;
  }
}

@container stage (max-width: 1080px) {
  .stage .toolbar .chip-kind {
    display: none;
  }
}

@container stage (max-width: 760px) {
  .stage .toolbar .chip-input:not(.warn) {
    display: none;
  }
}

/* The capture offer's chip in this row (docs/ui-plan.md § "Step 10") is
   the mouse icon and the ×, "Click to control" stays its label and its
   tooltip says what it does. While it shows, "Live" gives way to it. The
   offer stands only while the input reaches the target (viewer.ts), so
   no warning shares the row with it, and a warning would stay. So the
   title keeps its room and nothing of the chip is cut off by the title's
   row. Only where the chip shows (a fine pointer, as for the input
   chip). */
@media not all and (pointer: coarse) {
  @container stage (max-width: 760px) {
    :where(.viewer:not([data-bars], [data-side])) .stage .toolbar .chips:has(> .capture-offer:not([hidden])) > .chip-state:not(.warn) {
      display: none;
    }

    :where(.viewer:not([data-bars], [data-side])) .stage .toolbar .capture-offer > .capture-take {
      padding: 0 7px 0 8px;
    }

    :where(.viewer:not([data-bars], [data-side])) .stage .toolbar .capture-offer > .capture-take > .lbl {
      position: absolute;
      width: 1px;
      height: 1px;
      margin: -1px;
      overflow: hidden;
      clip: rect(0 0 0 0);
      clip-path: inset(50%);
      white-space: nowrap;
    }
  }
}

/* "Keys", "Capture mouse", "Fullscreen" and "HUD" show their icons only,
   "Switch" and "Stop" (`keep`) their labels too. */
@container stage (max-width: 980px) {
  :where(.viewer:not([data-bars], [data-side])) .stage .toolbar .btn:has(> svg.i):not(.keep) {
    min-width: 32px;
    padding: 0 8px;
    gap: 2px;
  }

  :where(.viewer:not([data-bars], [data-side])) .stage .toolbar .btn:has(> svg.i):not(.keep) > .lbl {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    overflow: hidden;
    clip: rect(0 0 0 0);
    clip-path: inset(50%);
    white-space: nowrap;
  }
}

@container stage (max-width: 600px) {
  :where(.viewer:not([data-bars], [data-side])) .stage .toolbar .chips .chip:not(.warn) {
    display: none;
  }

  :where(.viewer:not([data-bars], [data-side])) .stage .toolbar .btn.keep:has(> svg.i) {
    min-width: 32px;
    padding: 0 8px;
  }

  :where(.viewer:not([data-bars], [data-side])) .stage .toolbar .btn.keep:has(> svg.i) > .lbl {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    overflow: hidden;
    clip: rect(0 0 0 0);
    clip-path: inset(50%);
    white-space: nowrap;
  }

  /* "Keys" opens across the toolbar's width. */
  :where(.viewer:not([data-bars], [data-side])) .stage .toolbar .keys-wrap {
    position: static;
  }

  :where(.viewer:not([data-bars], [data-side])) .stage .toolbar .keys {
    left: 8px;
    right: 8px;
    width: auto;
    top: calc(100% + 8px);
  }
}

/* Strips in a stage column of 760 px or less: the text across with a
   hint's close button at its right, the action and "Details" under the
   text, so the close button never wraps onto a row of its own. */
@container stage (max-width: 760px) {
  .strip:not(.msg) > .text {
    flex: 1 0 calc(100% - 64px);
  }

  .strip:not(.msg) > .close {
    order: 1;
    margin-left: 0;
  }

  .strip:not(.msg) > .act,
  .strip:not(.msg) > .details-toggle {
    order: 2;
  }

  .strip:not(.msg) > .details {
    order: 3;
  }

  .strip > .act,
  .strip:not(.msg) > .act:not(:has(> .action:not([hidden]))) + .details-toggle,
  .strip .banner > .act {
    margin-left: 26px;
  }

  .strip.msg > .banner {
    flex: 1 1 0;
  }

  .strip .banner > .text {
    flex: 1 0 calc(100% - 26px);
  }

  .strip .act {
    align-items: flex-start;
    max-width: none;
  }

  .strip .consequence {
    text-align: left;
  }
}

/* ===== The phone's bars ===== */

/* A phone or a narrow window (`data-bars`: the session less the rail is
   760 px wide or less, viewer.ts `BARS_PX`; the HUD and the settings page
   beside the picture do not count, so opening them keeps the toolbar as it
   is): the toolbar is two bars, its own buttons placed by CSS, so no
   button exists twice (P1-2, P2-21). The top bar holds the title with the
   stream's state (or a warning) and "Stop", the bottom bar the other
   controls side by side, icon over label, in thumb reach: "Switch",
   "Keys", "Capture mouse" (with a fine pointer), "Fullscreen", "Sound",
   "Settings" and "HUD". The picture's box ends above the bottom bar, so no
   bar covers the picture, and "Keys" opens above it. The list over the
   picture ends above it too. `:where()` keeps the rules' weight what it
   would be without `data-bars`: fullscreen's toolbar and the coarse
   pointer's sizes still win over them. */
:where(.viewer[data-bars]) .stage .toolbar {
  position: static;
  flex-wrap: nowrap;
  padding: 0 var(--s-2) 0 var(--s-3);
}

:where(.viewer[data-bars]) .stage .toolbar .name {
  margin-left: 0;
}

:where(.viewer[data-bars]) .stage .toolbar .title {
  font-size: var(--fs-4);
}

:where(.viewer[data-bars]) .stage .toolbar .tip,
:where(.viewer[data-bars]) .stage .toolbar .sep,
:where(.viewer[data-bars]) .stage .toolbar .chip-kind,
:where(.viewer[data-bars]) .stage .toolbar .chip-input:not(.warn),
:where(.viewer[data-bars]) .stage .toolbar .chip-input.warn ~ .chip-state:not(.warn) {
  display: none;
}

:where(.viewer[data-bars]) .stage .toolbar .tools {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 7;
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: minmax(0, 1fr);
  gap: 2px;
  height: var(--bbar-h);
  padding: 4px;
  background: var(--bg-1);
  box-shadow: inset 0 1px 0 var(--line-1);
}

:where(.viewer[data-bars]) .stage .toolbar .tools .keys-wrap {
  display: contents;
}

/* The icons in one line, the labels under them (a long one, "Capture
   mouse", on two lines). */
:where(.viewer[data-bars]) .stage .toolbar .tools .btn {
  flex-direction: column;
  justify-content: flex-start;
  gap: 3px;
  width: auto;
  min-width: 0;
  height: 100%;
  min-height: 0;
  padding: 6px 2px 0;
  border-color: transparent;
  font-size: var(--fs-1);
  line-height: 1.1;
  white-space: normal;
  text-align: center;
}

:where(.viewer[data-bars]) .stage .toolbar .tools .btn > svg.i {
  flex: none;
  width: 20px;
  height: 20px;
}

/* "Keys"' chevron: its popover opens above the bar. */
:where(.viewer[data-bars]) .stage .toolbar .tools .btn > svg.i.xs {
  display: none;
}

:where(.viewer[data-bars]) .stage .toolbar .tools .btn > .lbl {
  position: static;
  flex: none;
  width: auto;
  max-width: 100%;
  height: auto;
  margin: 0;
  overflow: visible;
  clip: auto;
  clip-path: none;
  white-space: normal;
}

:where(.viewer[data-bars]) .stage .toolbar .tools .btn[aria-pressed="true"]:not(.sound),
:where(.viewer[data-bars]) .stage .toolbar .tools .btn[aria-expanded="true"] {
  color: var(--accent);
}

/* Six or seven columns in a stage of 440 px or less (a phone of 360 px):
   the labels a step smaller and tighter, so "Fullscreen" and its
   neighbours keep a gap between them. */
@container stage (max-width: 440px) {
  :where(.viewer[data-bars]) .stage .toolbar .tools .btn > .lbl {
    font-size: 11px;
    letter-spacing: -0.01em;
  }
}

/* "Keys": above the bottom bar, across its width (`keys-390.png`). */
:where(.viewer[data-bars]) .stage .toolbar .keys {
  top: auto;
  bottom: calc(100% + 8px);
  left: 8px;
  right: 8px;
  width: auto;
  max-width: none;
}

:where(.viewer[data-bars]) .stage > .viewport {
  margin-bottom: var(--bbar-h);
}

/* Fullscreen: both bars over the picture while the controls show, the
   bottom one at the bottom of the session. */
.viewer.fullscreen:where([data-bars]) .stage > .viewport {
  margin-bottom: 0;
}

.viewer.fullscreen:where([data-bars]) .stage .toolbar .tools {
  position: fixed;
  right: var(--hud-col);
  bottom: var(--hud-row);
  background: rgb(15 18 23 / 0.97);
  box-shadow: inset 0 1px 0 var(--line-1), var(--shadow-2);
}

/* The toasts stay above the bottom bar there too. */
.viewer.fullscreen.controls:where([data-bars]) .viewport .toasts {
  bottom: calc(var(--bbar-h) + 20px);
}

/* The list over the picture ends above the phone's bottom bar. */
.viewer[data-bars] > .dock[data-shape="overlay"] {
  bottom: var(--bbar-h);
  border-bottom: 1px solid var(--line-1);
}

/* Windows too narrow for the HUD beside the picture: it docks below the
   session instead, still taking the picture's room (on phones a sheet,
   below). Its row has a fixed height while it shows, as its column has a
   fixed width beside the session. */
@media (max-width: 960px) {
  .viewer {
    grid-template-rows: minmax(0, 1fr) 0;
  }

  .viewer[data-hud] {
    --hud-col: 0px;
    --hud-row: 50vh;
    grid-template-rows: minmax(0, 1fr) var(--hud-row);
  }

  /* A phone's address bar comes and goes: half of what shows. */
  @supports (height: 1dvh) {
    .viewer[data-hud] {
      --hud-row: 50dvh;
    }
  }

  .viewer > .hud-cell {
    grid-column: 2 / -1;
    grid-row: 2;
  }

  .hud {
    border-left: 0;
    border-top: 1px solid var(--line-1);
  }
}

/* ===== Phones ===== */

/* The home view is the list: one column that scrolls, the message about
   the last session, the list with its footer at the bottom of the screen,
   then the rest. In a session the toolbar is the two bars (above), the
   dock and the settings page are sheets with a scrim (dock.ts, viewer.ts),
   "Keys" opens above the bottom bar, and the HUD is a sheet below the
   session that takes the picture's room instead of covering it or the
   bars. */
@media (max-width: 720px) {
  :root {
    --bar-h: 52px;
  }

  .hud {
    border: 1px solid var(--line-2);
    border-bottom: 0;
    border-radius: 18px 18px 0 0;
  }

  .hud > .head {
    padding-top: 16px;
  }

  .hud > .head::before {
    content: "";
    position: absolute;
    top: 6px;
    left: 50%;
    width: 40px;
    height: 4px;
    margin-left: -20px;
    border-radius: 2px;
    background: var(--line-2);
  }

  body:has(.home) {
    overflow: auto;
  }

  .home {
    position: static;
    display: flex;
    flex-direction: column;
    min-height: 100vh;
    min-height: 100dvh;
  }

  .home > .session-message {
    width: auto;
    margin: var(--s-3) var(--s-3) 0;
  }

  .home > .dock {
    width: auto;
    border-right: 0;
  }

  .home > .dock .dock-body,
  .home > .dock .settings,
  .home > .dock .settings .page-body {
    flex: none;
    overflow: visible;
  }

  .home > .dock .dock-foot {
    position: sticky;
    bottom: 0;
  }

  .home > .home-main {
    overflow: visible;
  }

  .home-inner {
    padding: 28px 16px 32px;
  }

  .explain-grid {
    grid-template-columns: 1fr;
  }
}

/* A landscape picture on a portrait phone: turning the phone gives a
   bigger picture. Under the picture, not over it: not while a card says
   what is going on, nor with the HUD, which leaves the picture no room
   below it. */
.viewport .rotate-hint {
  display: none;
}

@media (max-width: 720px) and (orientation: portrait) and (pointer: coarse) {
  .viewer:not(.fullscreen, [data-hud]) .viewport[data-landscape]:not(:has(> .state-card:not([hidden]), > .shutter:not([hidden]))) > .rotate-hint {
    position: absolute;
    left: 0;
    right: 0;
    bottom: 18px;
    z-index: 1;
    display: flex;
    justify-content: center;
    align-items: center;
    gap: 8px;
    color: var(--text-3);
    font-size: var(--fs-2);
    pointer-events: none;
  }
}

/* ===== Coarse pointers: 44 px controls ===== */

/* A coarse main pointer (a phone, a tablet): no "Capture mouse" (P3-5),
   and no capture offer, neither its chips nor its hint. */
@media (pointer: coarse) {
  .stage .toolbar .btn.capture {
    display: none;
  }

  .capture-offer,
  .capture-hint {
    display: none !important;
  }
}

@media (pointer: coarse) {
  /* The toolbar's row with its 44 px buttons. */
  :root {
    --bar-h: 60px;
    --tab-h: 44px;
  }

  .btn,
  .btn.s,
  .select,
  .dock-search .search {
    min-height: 44px;
  }

  .dock:not([data-shape="rail"]) button.target {
    min-height: 56px;
  }

  /* The dock's switch keeps its look (38 × 22 px), the whole row toggles
     it (dock.ts): the row gets the height, and the switch a hit area of
     44 px around it. */
  .dock .tech-row {
    min-height: 44px;
  }

  button.switch {
    position: relative;
  }

  button.switch::before {
    content: "";
    position: absolute;
    inset: -11px -3px;
  }

  /* Buttons drawn as links ("Change", "Always show technical details"). */
  .linkbtn {
    min-height: 44px;
  }

  .hud > .section > summary {
    min-height: 44px;
  }

  .btn.icon,
  .btn.s.icon {
    width: 44px;
  }

  /* The toolbar's buttons, also those that show only their icon where the
     stage column is narrow. */
  .stage .toolbar .btn,
  .stage .toolbar .btn:has(> svg.i):not(.keep),
  .stage .toolbar .btn.keep:has(> svg.i) {
    min-width: 44px;
  }
}

/* ===== Short landscape screens: the side bar ===== */

/* A phone turned sideways (`data-side`, viewer.ts `SIDE_QUERY`,
   docs/ui-plan.md § "Step 11"): the height is what the picture lacks. The
   toolbar is one bar at the right, the title and the stream's state at its
   top, "Stop" at its bottom and the other controls between them, icon over
   label, so the picture takes the whole height and no bar covers it. The
   strips stay above the picture. The dock and the settings page open over
   the picture's left (viewer.ts `#dockShape`), "Keys" opens beside the bar
   and the HUD docks beside the session. Later in the sheet than the
   coarse pointer's sizes, which the bar's buttons override. */
:where(.viewer[data-side]) {
  --side-w: 76px;
}

:where(.viewer[data-side]) > .stage {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  grid-template-rows: auto minmax(0, 1fr);
}

:where(.viewer[data-side]) > .stage > .strips {
  grid-column: 1;
  grid-row: 1;
}

:where(.viewer[data-side]) > .stage > .viewport {
  grid-column: 1;
  grid-row: 2;
}

:where(.viewer[data-side]) .stage .toolbar {
  grid-column: 2;
  grid-row: 1 / -1;
  flex-direction: column;
  flex-wrap: nowrap;
  align-items: stretch;
  gap: 4px;
  width: var(--side-w);
  min-height: 0;
  padding: 6px 4px;
  box-shadow: inset 1px 0 0 var(--line-1);
}

/* The title over the stream's state, both centred: the name on one line
   with an ellipsis (its whole text in the tooltip, viewer.ts), the state
   chip under it. A warning chip takes the state's place. */
:where(.viewer[data-side]) .stage .toolbar .name {
  flex: none;
  flex-direction: column;
  align-items: stretch;
  gap: 4px;
  height: auto;
  min-width: 0;
  margin: 0;
  padding: 2px 0 4px;
}

:where(.viewer[data-side]) .stage .toolbar .title {
  font-size: var(--fs-1);
  text-align: center;
}

:where(.viewer[data-side]) .stage .toolbar .app,
:where(.viewer[data-side]) .stage .toolbar .tip,
:where(.viewer[data-side]) .stage .toolbar .sep,
:where(.viewer[data-side]) .stage .toolbar .chip-kind,
:where(.viewer[data-side]) .stage .toolbar .chip-lat,
:where(.viewer[data-side]) .stage .toolbar .chip-input:not(.warn),
:where(.viewer[data-side]) .stage .toolbar .chip.warn ~ .chip-state:not(.warn) {
  display: none;
}

:where(.viewer[data-side]) .stage .toolbar .chips {
  justify-content: center;
  margin: 0;
}

:where(.viewer[data-side]) .stage .toolbar .chip {
  max-width: 100%;
  height: 20px;
  padding: 0 6px;
  font-size: 11px;
}

/* The capture offer's chip (a fine pointer only) in the state's place,
   its mouse icon and its ×, as in a narrow stage. */
:where(.viewer[data-side]) .stage .toolbar .chips:has(> .capture-offer:not([hidden])) > .chip-state:not(.warn) {
  display: none;
}

:where(.viewer[data-side]) .stage .toolbar .capture-offer > .capture-take {
  padding: 0 7px 0 8px;
}

:where(.viewer[data-side]) .stage .toolbar .capture-offer > .capture-take > .lbl {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
}

/* The controls between the title and "Stop": 52 px each where the height
   allows, down to 40 px (seven with a mouse in technical mode), the bar
   scrolls only below that. Centred by two spacers rather than
   `justify-content`, which would push the first control out of reach
   above the top when they overflow. */
:where(.viewer[data-side]) .stage .toolbar .tools {
  flex: 1 1 0;
  min-height: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
  overflow-y: auto;
  scrollbar-width: none;
}

:where(.viewer[data-side]) .stage .toolbar .tools::before,
:where(.viewer[data-side]) .stage .toolbar .tools::after {
  content: "";
  flex: 1 1 0;
}

/* "Switch" in its DOM place, not before the spacer (the row puts it
   before the title with `order`). */
:where(.viewer[data-side]) .stage .toolbar .tools > .keep {
  order: 0;
}

:where(.viewer[data-side]) .stage .toolbar .tools .keys-wrap {
  display: contents;
}

:where(.viewer[data-side]) .stage .toolbar .tools .btn,
:where(.viewer[data-side]) .stage .toolbar > .btn.keep {
  flex: 0 1 52px;
  flex-direction: column;
  justify-content: center;
  gap: 3px;
  width: 100%;
  min-width: 0;
  height: auto;
  min-height: 40px;
  padding: 4px 2px;
  font-size: 11px;
  line-height: 1.1;
  white-space: normal;
  text-align: center;
}

:where(.viewer[data-side]) .stage .toolbar .tools .btn {
  border-color: transparent;
}

:where(.viewer[data-side]) .stage .toolbar .btn > svg.i {
  flex: none;
  width: 20px;
  height: 20px;
}

/* "Keys"' chevron: its popover opens beside the bar. */
:where(.viewer[data-side]) .stage .toolbar .btn > svg.i.xs {
  display: none;
}

:where(.viewer[data-side]) .stage .toolbar .btn > .lbl {
  position: static;
  flex: none;
  width: auto;
  max-width: 100%;
  height: auto;
  margin: 0;
  overflow: hidden;
  clip: auto;
  clip-path: none;
  white-space: nowrap;
  text-overflow: ellipsis;
}

:where(.viewer[data-side]) .stage .toolbar .tools .btn[aria-pressed="true"]:not(.sound),
:where(.viewer[data-side]) .stage .toolbar .tools .btn[aria-expanded="true"] {
  color: var(--accent);
}

/* Below 380 px of height (Safari's bars on a phone turned sideways) the
   bar is tighter: the controls 44 px with their labels at 10 px (an icon
   alone does not say what it does), the state chip its coloured dot. Five
   controls and "Stop" then fit in 320 px, the technical mode's sixth
   scrolls. */
@media (max-height: 380px) {
  :where(.viewer[data-side]) .stage .toolbar {
    gap: 2px;
    padding: 4px;
  }

  :where(.viewer[data-side]) .stage .toolbar .name {
    gap: 2px;
    padding: 0 0 2px;
  }

  /* The state as its coloured dot (the chip's word in its tooltip). */
  :where(.viewer[data-side]) .stage .toolbar .chip-state:not(.warn) {
    height: 12px;
    padding: 0 4px;
    font-size: 0;
  }

  :where(.viewer[data-side]) .stage .toolbar .tools .btn,
  :where(.viewer[data-side]) .stage .toolbar > .btn.keep {
    flex-basis: 44px;
    min-height: 40px;
    gap: 1px;
    padding: 2px;
    font-size: 10px;
  }

  :where(.viewer[data-side]) .stage .toolbar .btn > svg.i {
    width: 18px;
    height: 18px;
  }
}

/* "Keys": left of the bar, from its top. */
:where(.viewer[data-side]) .stage .toolbar .keys {
  top: 8px;
  right: calc(100% + 8px);
  max-height: calc(100% - 16px);
  overflow-y: auto;
}

/* The list and the settings page over the picture take the whole height,
   and the dock scrolls as one, as the phone's home view does: a footer
   that stayed would leave the list room for two entries. */
.viewer[data-side] > .dock[data-shape="overlay"] {
  top: 0;
  border-top: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
}

.viewer[data-side] > .dock[data-shape="overlay"] :is(.dock-body, .settings, .settings .page-body) {
  flex: none;
  overflow: visible;
}

.viewer[data-side][data-dock="overlay"] {
  grid-template-columns: auto minmax(0, 1fr) var(--hud-col);
}

/* The HUD beside the session, not below it: below, it would take the
   height the picture lacks. At most 45 % of the width, so the picture
   keeps room on the narrowest phones. */
.viewer[data-side][data-hud] {
  --hud-col: min(var(--hud-w), 45vw);
  grid-template-rows: minmax(0, 1fr);
}

.viewer[data-side] > .hud-cell {
  grid-column: 3;
  grid-row: 1;
}

.viewer[data-side] .hud {
  border: 0;
  border-left: 1px solid var(--line-1);
  border-radius: 0;
}

.viewer[data-side] .hud > .head {
  padding-top: 6px;
}

.viewer[data-side] .hud > .head::before {
  content: none;
}

/* ===== Phones: the dock's footer ===== */

/* A coarse main pointer (a phone, a tablet, docs/ui-plan.md § "Step 15"):
   the footer is one slim row, the status beside "Settings" and "Log out",
   so the list keeps its room. The "Technical details" card goes: its
   shortcut is a hardware keyboard's and Settings has the same switch. */
@media (pointer: coarse) {
  .dock .tech-row,
  .dock .tech-icon {
    display: none;
  }

  .dock:not([data-shape="rail"]) .dock-foot {
    flex-direction: row;
    align-items: center;
    gap: 4px;
    padding: 4px 6px;
  }

  .dock:not([data-shape="rail"]) .dock-foot .status-row {
    flex: 1 1 auto;
    min-width: 0;
    padding: 0 6px;
  }

  .dock:not([data-shape="rail"]) .dock-foot .status-row .status {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  .dock:not([data-shape="rail"]) .dock-foot .foot-row {
    flex: none;
    gap: 2px;
  }

  .dock:not([data-shape="rail"]) .dock-foot .foot-row .btn {
    flex: none;
  }
}

/* A short landscape screen at home: the list and the rest share the width,
   the list scrolls as one with its footer at its end, as the session's
   dock over the picture does (§ "Step 11"), and the rest is Recent and
   the details: the welcome and its two cards say what the list's groups
   say, at a size that took most of the height. */
@media (orientation: landscape) and (max-height: 500px) {
  .home {
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  }

  .home > .dock {
    width: auto;
    overflow-y: auto;
    overscroll-behavior: contain;
  }

  .home-inner {
    padding: 16px 20px 24px;
  }

  .home-inner > .explain-grid,
  .home-inner > .next,
  .home-inner:has(> .recent-shelf:not([hidden])) > :is(h1, .lead) {
    display: none;
  }

  .home-inner > h1 {
    font-size: var(--fs-5);
  }

  .home .shelf {
    grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
  }

  .home > .dock :is(.dock-body, .settings, .settings .page-body) {
    flex: none;
    overflow: visible;
  }
}

/* ===== The phone's keyboard ===== */

/* A device without a fine pointer (`data-touch`, softkeys.ts,
   docs/ui-plan.md § "Step 12"): "Keyboard" opens the browser's on-screen
   keyboard and stands in for "Keys", whose keys its row has. A touch
   screen with a fine main pointer keeps "Keys" next to "Keyboard", a
   desktop has no "Keyboard". */
.viewer[data-touch] .stage .toolbar .keys-wrap {
  display: none;
}

/* The field the keyboard writes into: in the page, focusable, never seen.
   16 px, or iOS zooms the page in as it takes the focus. */
.softkbd-input {
  position: fixed;
  left: 0;
  bottom: 0;
  width: 1px;
  height: 1px;
  margin: 0;
  padding: 0;
  border: 0;
  opacity: 0;
  font-size: 16px;
  resize: none;
  caret-color: transparent;
  pointer-events: none;
}

/* The keys a phone's keyboard lacks, on top of it while it is open
   (`--keyrow-bottom`: the keyboard's height, from the visual viewport). */
.keyrow {
  position: fixed;
  left: 0;
  right: 0;
  bottom: var(--keyrow-bottom, 0px);
  z-index: 30;
  display: flex;
  gap: 4px;
  padding: 6px;
  overflow-x: auto;
  background: var(--bg-1);
  box-shadow: inset 0 1px 0 var(--line-2), var(--shadow-2);
  scrollbar-width: none;
}

.keyrow .btn {
  flex: none;
  min-width: 44px;
  min-height: 40px;
  padding: 0 10px;
}

.keyrow .btn.sticky[aria-pressed="true"] {
  border-color: var(--accent-line);
  background: var(--accent-soft);
  color: var(--accent);
}

.keyrow .btn.hide {
  margin-left: auto;
}

[hidden] {
  display: none !important;
}

/* The page of WebRTC Direct: the computers it knows (pages.ts). */
.machines {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 8px;
}
.machine {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: 8px;
}
