:root {
  --concrete: #e7eaec;
  --panel: #f6f7f8;
  --ink: #1d2730;
  --steel: #5d6b77;
  --frame: #2a333b;
  --line: #c9d0d5;
  --signal: #2563d9;
  --live: #23845a;
  --fault: #b7791f;
  --dead: #9aa5ae;
  --font: "Archivo", system-ui, sans-serif;
  --narrow: "Archivo Narrow", "Archivo", system-ui, sans-serif;
  color-scheme: light;
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --concrete: #161b20;
    --panel: #1e252b;
    --ink: #e4e9ed;
    --steel: #93a1ad;
    --frame: #0c1014;
    --line: #2f3941;
    --signal: #6b9bff;
    --live: #4cc38a;
    --fault: #e0a43c;
    --dead: #56626c;
    color-scheme: dark;
  }
}
:root[data-theme="dark"] {
  --concrete: #161b20;
  --panel: #1e252b;
  --ink: #e4e9ed;
  --steel: #93a1ad;
  --frame: #0c1014;
  --line: #2f3941;
  --signal: #6b9bff;
  --live: #4cc38a;
  --fault: #e0a43c;
  --dead: #56626c;
  color-scheme: dark;
}

* { box-sizing: border-box; }
[hidden] { display: none !important; }
body {
  margin: 0;
  background: var(--concrete);
  color: var(--ink);
  font: 15px/1.45 var(--font);
}
button, input { font: inherit; color: inherit; }
button {
  cursor: pointer;
  border: 1px solid var(--line);
  background: var(--panel);
  border-radius: 6px;
  padding: 6px 12px;
}
button:focus-visible, input:focus-visible, canvas:focus-visible {
  outline: 2px solid var(--signal);
  outline-offset: 2px;
}
button.quiet { border-color: transparent; background: none; color: var(--steel); }
button.quiet:hover { color: var(--ink); }

.bar {
  display: flex;
  align-items: center;
  gap: 16px;
  padding: 12px 16px;
  border-bottom: 1px solid var(--line);
}
.bar h1 {
  margin: 0;
  font: 600 18px/1 var(--narrow);
  letter-spacing: 0.01em;
}
.views { display: flex; gap: 4px; margin-left: 8px; }
.views button[aria-pressed="true"] { border-color: var(--ink); }
#sign-out { margin-left: auto; }

main { padding: 20px 16px 40px; }
.message { color: var(--steel); margin: 0 0 16px; min-height: 1.45em; }

.sign-in {
  display: grid;
  gap: 8px;
  max-width: 320px;
  margin: 10vh auto 0;
}
.sign-in input {
  padding: 8px 10px;
  border: 1px solid var(--line);
  border-radius: 6px;
  background: var(--panel);
}
.hint { color: var(--steel); font-size: 13px; margin: 0; }

/* The wall: a section per group, in which phones stand side by side at their own proportions. */
.wall { display: grid; gap: 28px; }
.group { border-radius: 10px; }
.group-head { display: flex; flex-wrap: wrap; align-items: center; gap: 4px 12px; margin-bottom: 12px; }
.group-name, .group-title { margin: 0; font: 600 17px/1.25 var(--narrow); }
.group-name { field-sizing: content; min-width: 6em; max-width: 100%; margin-left: -5px; padding: 2px 4px; }
.group .remove { color: var(--fault); }
.group.drop-target { outline: 2px dashed var(--signal); outline-offset: 8px; }
.wall:not(.dragging) .group.unused, .wall:not(.dragging) .new-group-drop { display: none; }
.new-group-drop {
  display: grid;
  place-items: center;
  min-height: 96px;
  border: 2px dashed var(--line);
  color: var(--steel);
}
.group-row th { padding-top: 14px; text-align: left; font: 600 15px/1.25 var(--narrow); }
.group-select {
  padding: 3px 6px;
  border: 1px solid var(--line);
  border-radius: 4px;
  background: var(--concrete);
  color: var(--ink);
  font: inherit;
}
.phones {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
  gap: 28px 20px;
  align-items: end;
}
.tile { margin: 0; min-width: 0; }
.tile .name {
  display: block;
  width: calc(100% + 5px);
  margin: 8px 0 0 -5px;
  padding: 2px 4px;
  font: 600 15px/1.25 var(--narrow);
  text-overflow: ellipsis;
}
.state { margin: 2px 0 0; color: var(--steel); font-size: 13px; }

.handset {
  position: relative;
  display: block;
  width: 100%;
  padding: 10px 6px 14px;
  border: 0;
  border-radius: 18px;
  background: var(--frame);
  box-shadow: inset 0 0 0 1px rgb(255 255 255 / 0.06);
}
.handset .led {
  position: absolute;
  top: 4px;
  left: 50%;
  width: 28px;
  height: 3px;
  margin-left: -14px;
  border-radius: 2px;
  background: var(--dead);
}
.tile[data-state="live"] .led, .focus[data-state="live"] .handset.large .led { background: var(--live); }
.tile[data-state="fault"] .led, .focus[data-state="fault"] .handset.large .led { background: var(--fault); }
.handset canvas {
  display: block;
  width: 100%;
  /* Until the first frame says otherwise; the viewer sets the stream's real ratio. */
  aspect-ratio: 9 / 19.5;
  border-radius: 10px;
  background: #000;
}
.tile button.handset:hover { box-shadow: inset 0 0 0 1px rgb(255 255 255 / 0.06), 0 0 0 2px var(--signal); }
.offline {
  position: absolute;
  inset: 10px 6px 14px;
  display: grid;
  place-items: center;
  padding: 12px;
  color: #c3ccd3;
  font-size: 13px;
  text-align: center;
}
.offline:empty { display: none; }

/* Focus: one phone, large, with its buttons where a phone has them. */
.focus {
  width: 100vw;
  height: 100vh;
  max-width: none;
  max-height: none;
  margin: 0;
  padding: 16px;
  border: 0;
  background: var(--concrete);
  color: var(--ink);
}
.focus::backdrop { background: var(--concrete); }
.focus-top { display: flex; align-items: baseline; gap: 16px; flex-wrap: wrap; }
.focus-top h2 { margin: 0; font: 600 20px/1.2 var(--narrow); }
.focus-body {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 14px;
  margin-top: 12px;
}
.handset.large {
  width: auto;
  padding: 14px 10px 20px;
  border-radius: 28px;
}
.handset.large canvas {
  width: auto;
  height: calc(100vh - 190px);
  max-width: calc(100vw - 120px);
  border-radius: 14px;
  cursor: crosshair;
  touch-action: none;
}
.side-keys {
  position: absolute;
  top: 18%;
  right: -58px;
  display: grid;
  gap: 8px;
}
.side-keys button {
  width: 52px;
  padding: 8px 0;
  border-radius: 0 6px 6px 0;
  border-left: 4px solid var(--frame);
  font-size: 12px;
}
.nav-keys { display: flex; gap: 8px; }
.nav-keys button { min-width: 88px; }
.stage { display: flex; flex-direction: column; align-items: center; gap: 14px; }

/* Mirroring: the phones that copy the focused one's input, small and live beside it. */
#mirror-toggle { margin-left: auto; }
#mirror-toggle[aria-pressed="true"] { border-color: var(--signal); color: var(--signal); }
.focus[data-mirror] .focus-body {
  flex-direction: row;
  align-items: flex-start;
  justify-content: center;
  /* Room for the side buttons, which stick out past the handset. */
  gap: 14px 80px;
}
.focus[data-mirror] .handset.large canvas { max-width: calc(100vw - 480px); }
.mirrors {
  width: 260px;
  max-height: calc(100vh - 110px);
  overflow-y: auto;
}
.mirrors .hint { margin: 0 0 10px; }
.mirror-phones {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 14px 10px;
  align-items: end;
}
.mirror-phone { display: block; min-width: 0; cursor: pointer; }
.mirror-phone .handset { padding: 6px 4px 8px; border-radius: 12px; }
.mirror-phone .handset canvas { border-radius: 6px; }
.mirror-phone:has(input:not(:checked)) .handset { opacity: 0.4; }
.mirror-phone[data-state="live"] .led { background: var(--live); }
.mirror-phone[data-state="fault"] .led { background: var(--fault); }
.mirror-name {
  display: flex;
  align-items: center;
  gap: 4px;
  margin-top: 6px;
  font: 500 13px/1.2 var(--narrow);
}
.mirror-name .name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.grid-keys {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
  margin-bottom: 16px;
}
.grid-keys button { padding: 4px 12px; }
.grid-keys button[aria-disabled="true"] { opacity: 0.6; cursor: progress; }

.details { border-collapse: collapse; width: 100%; }
#list { overflow-x: auto; }
.details th, .details td {
  text-align: left;
  padding: 8px;
  border-bottom: 1px solid var(--line);
  white-space: nowrap;
}
.details th { color: var(--steel); font-weight: 500; }
/* The row under the pointer, or holding focus, is the one its buttons act on. */
.details tbody tr:hover, .details tbody tr:focus-within { background: var(--panel); }
.details tbody tr:hover .name-input:hover, .details tbody tr:focus-within .name-input:focus { background: var(--concrete); }
.details tbody tr:hover button, .details tbody tr:focus-within button { background: var(--concrete); }
.sub { color: var(--steel); font-size: 12px; }
.details tbody tr.openable { cursor: pointer; }
.details tbody tr.device:focus-visible { outline: 2px solid var(--signal); outline-offset: -2px; }
/* A long error shows its first 40 or so characters, and all of it while the cell is hovered or the
   row has the keyboard's focus, wrapped to the same fixed width: a width that followed the text
   would narrow the column as it wraps and take the cell from under the pointer. A clicked row keeps
   the focus, so focus alone would leave it tall. */
.sub.clip { width: 40ch; overflow: hidden; text-overflow: ellipsis; }
.details td:hover > .sub.clip, .details tbody tr:focus-visible .sub.clip { white-space: normal; overflow-wrap: anywhere; }
.name-input {
  width: 14em;
  padding: 3px 6px;
  border: 1px solid transparent;
  border-radius: 4px;
  background: none;
  font-weight: 500;
}
.name-input:hover, .name-input:focus { border-color: var(--line); background: var(--panel); }
.name-input::placeholder { color: var(--ink); opacity: 1; }
.name-input:focus::placeholder { color: var(--steel); }
.details .actions button { padding: 3px 10px; }
.details .actions button + button { margin-left: 6px; }
.details .remove { color: var(--fault); }
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}
.ok { color: var(--live); }
.bad { color: var(--fault); }

@media (max-width: 600px) {
  .phones { grid-template-columns: repeat(2, 1fr); }
  .side-keys { position: static; display: flex; justify-content: center; margin-top: 10px; }
  .side-keys button { border-left-width: 1px; border-radius: 6px; }
  .handset.large canvas { height: calc(100vh - 260px); max-width: calc(100vw - 52px); }
  .focus[data-mirror] .focus-body { flex-direction: column; align-items: center; }
  .focus[data-mirror] .handset.large canvas { height: calc(100vh - 420px); max-width: calc(100vw - 52px); }
  .mirrors { width: 100%; max-height: none; }
  .mirror-phones { grid-template-columns: repeat(4, 1fr); }
}

/* Setup: the page a phone opens from a setup link. */
.setup { max-width: 560px; margin: 0 auto; }
.setup h2 { margin: 0 0 12px; font: 600 22px/1.2 var(--narrow); }
.steps { margin: 0; padding-left: 1.4em; display: grid; gap: 22px; }
.steps h3 { margin: 0 0 8px; font: 600 17px/1.2 var(--narrow); }
.steps p { margin: 0 0 8px; }
.action {
  display: inline-block;
  padding: 10px 18px;
  border-radius: 6px;
  background: var(--signal);
  color: var(--concrete);
  font-weight: 500;
  text-decoration: none;
}
.action[aria-disabled="true"] { background: var(--dead); pointer-events: none; }

/* Add phones: make a setup link and show it as a QR code. */
#new-group { margin-left: auto; }
#add-phone { margin-left: auto; }
#new-group:not([hidden]) + #add-phone { margin-left: 0; }
#add-phone + #sign-out { margin-left: 0; }
.enroll {
  width: min(420px, calc(100vw - 32px));
  padding: 20px;
  border: 1px solid var(--line);
  border-radius: 10px;
  background: var(--panel);
  color: var(--ink);
}
.enroll::backdrop { background: rgb(0 0 0 / 0.4); }
.enroll h2 { margin: 0 0 12px; font: 600 20px/1.2 var(--narrow); }
.enroll input {
  padding: 8px 10px;
  border: 1px solid var(--line);
  border-radius: 6px;
  background: var(--concrete);
}
.enroll .row { display: flex; justify-content: flex-end; gap: 8px; margin-top: 12px; }
.enroll #enroll-link { width: 100%; margin-top: 12px; font-size: 13px; }
/* QR codes need dark modules on light, whatever the theme. */
.qr { width: min(260px, 100%); margin: 12px auto 0; background: #fff; }
.qr svg { display: block; width: 100%; height: auto; }

/* Recording: the focused phone's input kept as steps. */
#record-toggle { margin-left: auto; }
#record-toggle + #mirror-toggle { margin-left: 0; }
#record-toggle[aria-pressed="true"] { border-color: var(--fault); color: var(--fault); }
.focus[data-recording] .handset.large { box-shadow: inset 0 0 0 1px rgb(255 255 255 / 0.06), 0 0 0 3px var(--fault); }
.enroll form > label { display: block; margin-bottom: 4px; }
.enroll form > input { width: 100%; }

/* Recordings: the list, and the one open for editing and playing. */
.recordings { overflow-x: auto; }
.recordings > .hint { margin-bottom: 12px; }
button.link {
  padding: 0;
  border: 0;
  background: none;
  color: var(--signal);
  font-weight: 500;
  text-align: left;
}
button.link:hover { text-decoration: underline; }
#recording-list tbody tr { cursor: pointer; }
/* The whole row is the target, so the name shows no button under the pointer. */
#recording-list tbody tr:hover button.link, #recording-list tbody tr:focus-within button.link { background: none; }
#recording-list tbody tr[data-open] { background: var(--panel); box-shadow: inset 3px 0 0 var(--signal); }
#recording-runs tbody tr[data-linked] { background: var(--panel); box-shadow: inset 3px 0 0 var(--signal); }
.recording-detail {
  margin-top: 20px;
  padding: 16px;
  border: 1px solid var(--line);
  border-radius: 10px;
  background: var(--panel);
}
.recording-detail h3 { margin: 22px 0 8px; font: 600 16px/1.2 var(--narrow); }
.recording-detail .row { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 10px; }
.recording-detail button:disabled { opacity: 0.5; cursor: default; }
.recording-detail .details tbody tr:hover, .recording-detail .details tbody tr:focus-within { background: var(--concrete); }
.recording-head { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
.recording-head #recording-close { margin-left: auto; }
.recording-name { width: min(24em, 100%); font: 600 18px/1.25 var(--narrow); }
.recording-detail .scroll { overflow-x: auto; }
.target-groups { display: grid; gap: 10px; }
.targets { display: flex; flex-wrap: wrap; gap: 6px 16px; }
.target-all { font-weight: 600; margin-bottom: 4px; }
.target-all + .targets { padding-left: 20px; }
.target { display: inline-flex; align-items: center; gap: 4px; cursor: pointer; }
.steps-table .fields { display: flex; flex-wrap: wrap; align-items: center; gap: 6px 12px; white-space: normal; }
.field { display: inline-flex; align-items: center; gap: 4px; color: var(--steel); font-size: 13px; }
.recording-detail input, .recording-detail select {
  padding: 3px 6px;
  border: 1px solid var(--line);
  border-radius: 4px;
  background: var(--concrete);
  color: var(--ink);
  font: inherit;
}
.field input { width: 6.5em; }
.text-field { width: min(22em, 60vw); }
.steps-table .actions button { min-width: 2.2em; }
.details .actions button + button { margin-left: 4px; }
/* A row grows while its clipped line is read. Its buttons stay at its top, where they were, so they don't
   slide away and leave the next row's button under a pointer on its way to them. */
#recording-runs td { vertical-align: top; }
/* A run's row can't take the focus itself, so its buttons show the keyboard the whole of a clipped line. */
#recording-runs tbody tr:has(:focus-visible) .sub.clip { white-space: normal; overflow-wrap: anywhere; }
.shot { margin: 12px 0 0; }
.shot img { display: block; max-width: min(360px, 100%); max-height: 70vh; border-radius: 8px; background: #000; }
.shot figcaption { margin-top: 6px; }
/* One line whatever it says, so a message after Play again doesn't move the rows under the pointer. */
.runs-status { min-height: 1.5em; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Lies over the table without moving its rows, and lets the pointer reach what is under it. */
.shot-preview {
  position: fixed;
  z-index: 1000;
  margin: 0;
  padding: 4px;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--panel);
  pointer-events: none;
}
.shot-preview img { display: block; max-width: min(360px, calc(100vw - 32px)); max-height: 80vh; border-radius: 4px; }
.shot-preview img:not([src]) { display: none; }
.shot-preview figcaption { padding: 4px 8px; }
.shot-preview figcaption:empty { display: none; }
