/*
 * Agent Console — retro pixel theme.
 *
 * One committed dark CRT look (no light variant: a terminal console is dark by
 * identity). Shape lock: radius 0 everywhere, 2px borders, hard offset shadows.
 * Motion lock: steps() timing only, so even movement feels pixelated; press
 * feedback is instant, the way a sprite would move.
 *
 * Type: "Press Start 2P" (self-hosted, CSP blocks external fonts) is for short
 * uppercase labels only. Transcript and any real content stay in the system
 * monospace stack for readability during an actual incident.
 */

@font-face {
  font-family: "Press Start 2P";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("/assets/fonts/press-start-2p-latin.woff2") format("woff2");
}

:root {
  color-scheme: dark;
  --bg: #0a0d12;
  --panel: #10141c;
  --panel-2: #161c26;
  --border: #2b3444;
  --border-strong: #3d4a5e;
  --text: #d7e0ea;
  --muted: #8a97a8;
  --accent: #46e07c;
  --accent-ink: #06130a;   /* text on top of the accent */
  --accent-soft: #12291c;  /* selected-row background   */
  --accent-edge: #1f8a4c;  /* darker accent for borders */
  --ok: #46e07c;
  --warn: #f4b64a;
  --bad: #ff5d5d;
  --prod: #ff5d5d;
  --shadow: #04060a;
  --pixel: "Press Start 2P", ui-monospace, monospace;
  --mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
  /* Theme primitives: the pixel look is the default; html.theme-minimal
     (bottom of this file) redefines these for the modern minimal theme. */
  --radius: 0;
  --bw: 2px;
  --shadow-btn: 3px 3px 0 var(--shadow);
  --shadow-sm: 2px 2px 0 var(--shadow);
  --shadow-md: 4px 4px 0 var(--shadow);
  --shadow-lg: 6px 6px 0 var(--shadow);
}

* {
  box-sizing: border-box;
}

/* An author `display` rule beats the UA rule for [hidden], so state it here. */
[hidden] {
  display: none !important;
}

html,
body {
  height: 100%;
}

/*
 * The shell is a flex column filling the viewport exactly, so the page itself
 * never scrolls on desktop. Sized with flex rather than a hard-coded header
 * height, which drifts whenever the topbar's real height changes.
 */
body {
  margin: 0;
  /* dvh tracks the real visible height on mobile as browser chrome collapses;
     the 100% from the shared rule above is the fallback. */
  height: 100dvh;
  display: flex;
  flex-direction: column;
  overscroll-behavior: none;
  background: var(--bg);
  color: var(--text);
  font: 13.5px/1.6 var(--mono);
}

::selection {
  background: var(--accent);
  color: var(--accent-ink);
}

/* Faint CRT scanlines over everything. Static, so it costs nothing to keep. */
.crt {
  position: fixed;
  inset: 0;
  z-index: 999;
  pointer-events: none;
  background: repeating-linear-gradient(
    to bottom,
    rgb(0 0 0 / 14%) 0 1px,
    transparent 1px 3px
  );
}

h1,
h2,
h3 {
  margin: 0;
  font-weight: 400;
}

.muted {
  color: var(--muted);
}

.small {
  font-size: 12px;
}

.pad {
  padding: 10px 12px;
}

.spacer {
  flex: 1;
}

/* ---- pixel icons ---- */

/* The inline symbol sprite. Hidden here rather than with a style attribute,
   which the CSP blocks; without this rule it renders as an empty box that
   pushes the header down. */
.sprite {
  display: none;
}

/* No shape-rendering here: the px- symbols carry crispEdges as an attribute,
   and forcing it from CSS would also jag the mn- smooth stroke icons. */
.icon {
  width: 16px;
  height: 16px;
  flex: none;
  fill: currentColor;
}

.icon-lg {
  width: 64px;
  height: 64px;
  fill: currentColor;
}

/* ---- controls ---- */

button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  font-family: var(--pixel);
  font-size: 9px;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  line-height: 1;
  cursor: pointer;
  border: var(--bw) solid var(--accent-edge);
  border-radius: var(--radius);
  padding: 10px 14px 9px;
  background: var(--accent);
  color: var(--accent-ink);
  box-shadow: var(--shadow-btn);
}

/* Press feedback is instant on purpose: sprites do not tween. */
button:active:not(:disabled) {
  transform: translate(3px, 3px);
  box-shadow: 0 0 0 var(--shadow);
}

button:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

button.ghost {
  background: var(--panel-2);
  border-color: var(--border-strong);
  color: var(--text);
  box-shadow: var(--shadow-sm);
}

button.ghost:active:not(:disabled) {
  transform: translate(2px, 2px);
  box-shadow: 0 0 0 var(--shadow);
}

/* Hover eases in over two frames; press stays instant. */
@media (hover: hover) and (pointer: fine) {
  button {
    transition: filter 120ms steps(2);
  }

  button:hover:not(:disabled) {
    filter: brightness(1.08);
  }
}

button.small {
  padding: 7px 10px 6px;
  font-size: 8px;
}

button.danger {
  color: var(--bad);
  border-color: var(--border-strong);
}

:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

input,
select,
textarea {
  font: 13.5px/1.5 var(--mono);
  width: 100%;
  padding: 9px 11px;
  border-radius: var(--radius);
  border: var(--bw) solid var(--border);
  background: var(--bg);
  color: var(--text);
}

input:focus,
select:focus,
textarea:focus {
  outline: none;
  border-color: var(--accent);
}

::placeholder {
  color: var(--muted);
  opacity: 0.7;
}

.error {
  color: var(--bad);
  font-size: 13px;
  margin: 6px 0 0;
}

.hint {
  color: var(--muted);
  font-size: 12px;
  margin: 4px 0 0;
}

/* ---- scrollbars ---- */

* {
  scrollbar-width: thin;
  scrollbar-color: var(--border-strong) var(--panel);
}

::-webkit-scrollbar {
  width: 12px;
  height: 12px;
}

::-webkit-scrollbar-track {
  background: var(--panel);
}

::-webkit-scrollbar-thumb {
  background: var(--border-strong);
  border: 3px solid var(--panel);
}

/* ---- sign in ---- */

.centred {
  display: grid;
  place-items: center;
  padding: 24px;
}

.card {
  background: var(--panel);
  border: var(--bw) solid var(--border-strong);
  border-radius: var(--radius);
  padding: 28px;
  box-shadow: var(--shadow-lg);
}

.auth-card {
  width: min(440px, 100%);
}

.auth-card h1 {
  font-family: var(--pixel);
  font-size: 15px;
  line-height: 1.7;
  text-transform: uppercase;
  color: var(--accent);
  display: flex;
  align-items: center;
  gap: 12px;
}

.auth-card h1 .icon {
  width: 24px;
  height: 24px;
}

.auth-card p.muted {
  margin: 10px 0 20px;
}

.auth-card label {
  display: block;
  margin: 16px 0 6px;
  font-family: var(--pixel);
  font-size: 8px;
  text-transform: uppercase;
  color: var(--muted);
}

.auth-card button[type="submit"] {
  width: 100%;
  margin-top: 22px;
}

/* ---- shell ---- */

.topbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding: 12px 16px;
  background: var(--panel);
  border-bottom: var(--bw) solid var(--border);
}

.brand {
  display: flex;
  align-items: center;
  gap: 10px;
  font-family: var(--pixel);
  font-size: 11px;
  text-transform: uppercase;
  color: var(--text);
}

.brand-mark {
  color: var(--accent);
  display: inline-flex;
}

.brand-mark .icon {
  width: 20px;
  height: 20px;
}

.topbar-right {
  display: flex;
  align-items: center;
  gap: 12px;
}

.badge {
  font-family: var(--pixel);
  font-size: 8px;
  text-transform: uppercase;
  padding: 5px 8px 4px;
  border-radius: var(--radius);
  background: var(--accent-soft);
  color: var(--accent);
  border: 1px solid var(--accent-edge);
  cursor: help;
}

.topbar {
  flex: none;
}

.layout {
  display: grid;
  grid-template-columns: 300px 1fr;
  flex: 1;
  min-height: 0;
}

.sidebar {
  border-right: var(--bw) solid var(--border);
  background: var(--panel);
  display: flex;
  flex-direction: column;
  overflow: hidden;
}

.sidebar-section {
  display: flex;
  flex-direction: column;
  min-height: 0;
  border-bottom: var(--bw) solid var(--border);
}

.sidebar-section.grow {
  flex: 1;
  border-bottom: none;
}

.sidebar-heading {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 12px 12px 8px;
}

.sidebar-heading h2 {
  display: flex;
  align-items: center;
  gap: 6px;
  font-family: var(--pixel);
  font-size: 8px;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--muted);
}

.heading-icon {
  color: var(--accent);
}

.session-list {
  list-style: none;
  margin: 0;
  padding: 0 8px 10px;
  overflow-y: auto;
}

/* ---- server dropdown ---- */

.server-picker {
  position: relative;
  padding: 0 12px 12px;
}

.server-picker-toggle {
  width: 100%;
  justify-content: flex-start;
  gap: 8px;
  background: var(--bg);
  border: var(--bw) solid var(--border);
  box-shadow: none;
  color: var(--text);
  font-family: var(--mono);
  font-size: 13px;
  text-transform: none;
  letter-spacing: 0;
  padding: 9px 10px;
  text-align: left;
}

.server-picker-toggle:active:not(:disabled) {
  transform: none;
  box-shadow: none;
}

.picker-current {
  display: flex;
  align-items: center;
  gap: 8px;
  min-width: 0;
  flex: 1;
}

.picker-current .server-label {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.picker-current .server-meta {
  margin-left: auto;
}

.server-picker-toggle .caret {
  flex: none;
  color: var(--muted);
}

.server-picker-toggle[aria-expanded="true"] .caret {
  transform: rotate(180deg);
}

/* The open menu floats over the sessions list below it. */
.server-list {
  list-style: none;
  position: absolute;
  left: 12px;
  right: 12px;
  top: 100%;
  z-index: 50;
  margin: 2px 0 0;
  padding: 6px;
  background: var(--panel);
  border: var(--bw) solid var(--border-strong);
  box-shadow: var(--shadow-md);
  max-height: min(340px, 55vh);
  overflow-y: auto;
}

.server-list:not([hidden]) {
  animation: px-rise 120ms steps(2);
}

.server-button,
.session-button {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  text-align: left;
  background: transparent;
  color: var(--text);
  border: var(--bw) solid transparent;
  box-shadow: none;
  padding: 8px 10px;
  font-family: var(--mono);
  font-size: 13px;
  text-transform: none;
  letter-spacing: 0;
  line-height: 1.4;
}

.server-button:active:not(:disabled),
.session-button:active:not(:disabled) {
  transform: none;
  box-shadow: none;
  background: var(--panel-2);
}

@media (hover: hover) and (pointer: fine) {
  .server-button,
  .session-button {
    transition: background-color 120ms steps(2), border-color 120ms steps(2);
  }

  .server-button:hover,
  .session-button:hover {
    background: var(--panel-2);
    filter: none;
  }
}

/* Rows step in one after another when the probe finishes. Only .stagger lists
   animate, so re-renders from clicking around do not replay it. */
.server-list.stagger .server-item {
  animation: px-rise 160ms steps(3) backwards;
}

.server-list.stagger .server-item:nth-child(2) { animation-delay: 40ms; }
.server-list.stagger .server-item:nth-child(3) { animation-delay: 80ms; }
.server-list.stagger .server-item:nth-child(4) { animation-delay: 120ms; }
.server-list.stagger .server-item:nth-child(5) { animation-delay: 160ms; }
.server-list.stagger .server-item:nth-child(6) { animation-delay: 200ms; }
.server-list.stagger .server-item:nth-child(7) { animation-delay: 240ms; }
.server-list.stagger .server-item:nth-child(n + 8) { animation-delay: 280ms; }

@keyframes px-rise {
  from {
    opacity: 0;
    transform: translateY(6px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

.server-item.selected .server-button,
.session-item.selected .session-button {
  background: var(--accent-soft);
  border-color: var(--accent-edge);
}

.server-label {
  font-weight: 600;
}

.server-meta {
  margin-left: auto;
  color: var(--muted);
  font-size: 11px;
}

.server-error {
  margin: 2px 10px 8px;
  font-size: 12px;
  color: var(--bad);
}

/* Status dots are squares here: green = agent ready, amber = no agent that can
   answer (none installed, or its login needs renewing), red = no SSH. */
.dot {
  width: 8px;
  height: 8px;
  border-radius: var(--radius);
  flex: none;
  background: var(--muted);
}

.dot.ok {
  background: var(--ok);
}

.dot.warn {
  background: var(--warn);
}

.dot.bad {
  background: var(--bad);
}

.tag {
  font-family: var(--pixel);
  font-size: 7px;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  padding: 4px 5px 3px;
  border-radius: var(--radius);
  background: var(--panel-2);
  color: var(--muted);
  border: 1px solid var(--border-strong);
}

.tag.prod {
  background: transparent;
  color: var(--prod);
  border-color: var(--prod);
}

.tag.busy {
  color: var(--accent);
  border-color: var(--accent-edge);
  display: inline-flex;
  align-items: center;
  gap: 5px;
}

.tag.busy::before {
  content: "";
  width: 6px;
  height: 6px;
  background: var(--accent);
  animation: px-blink 1s steps(1) infinite;
}

.session-button {
  flex-direction: column;
  align-items: flex-start;
  gap: 3px;
}

.session-title {
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  font-size: 12.5px;
}

.session-meta {
  font-size: 11px;
  color: var(--muted);
}

/* Skeleton rows shown while the server probe runs. */
.skeleton-row {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 10px;
}

.skeleton-row .sk {
  height: 8px;
  background: var(--panel-2);
  animation: px-pulse 1s steps(2) infinite;
}

.skeleton-row .sk-dot {
  width: 8px;
  flex: none;
}

.skeleton-row .sk-bar {
  flex: 1;
}

.skeleton-row .sk-end {
  width: 48px;
  flex: none;
}

.skeleton-row:nth-child(2) .sk {
  animation-delay: 0.2s;
}

.skeleton-row:nth-child(3) .sk {
  animation-delay: 0.4s;
}

/* ---- chat ---- */

.chat {
  display: flex;
  flex-direction: column;
  min-width: 0;
  overflow: hidden;
}

/* Header and composer appear once per server pick; step them in briefly. */
.chat-header:not([hidden]),
.composer:not([hidden]) {
  animation: px-rise 140ms steps(3) backwards;
}

.chat-header {
  position: relative;
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 16px;
  padding: 12px 20px;
  border-bottom: var(--bw) solid var(--border);
  background: var(--panel);
}

/* Production gets hazard stripes; nobody should mistake which box this is. */
.chat-header.prod::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: -2px;
  height: 4px;
  background: repeating-linear-gradient(
    -45deg,
    var(--prod) 0 6px,
    var(--bg) 6px 12px
  );
}

/* Full Auto: solid red underline on top of whatever else is showing. */
.chat-header.unrestricted {
  box-shadow: inset 0 -3px 0 var(--bad);
}

.chat-header h2 {
  font-family: var(--pixel);
  font-size: 10px;
  line-height: 1.7;
  text-transform: uppercase;
  overflow: hidden;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
}

.chat-header p {
  margin: 4px 0 0;
}

/* The title takes what the buttons leave, down to a readable minimum; past
   that the buttons wrap onto a second row instead of running off the edge. */
.chat-header > div:first-child {
  flex: 1 1 0;
  min-width: 180px;
}

.chat-header-actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-end;
  gap: 8px;
  flex: 0 1 auto;
  align-items: center;
}

.transcript {
  flex: 1;
  overflow-y: auto;
  /* Streamed blocks glide into view instead of teleporting the scrollbar. */
  scroll-behavior: smooth;
  padding: 20px;
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.empty {
  margin: auto;
  max-width: 460px;
  text-align: center;
}

.empty-art {
  color: var(--accent);
  margin-bottom: 18px;
}

/* Classic two-frame sprite idle so an empty screen still feels alive. */
.empty-art .icon-lg {
  animation: px-bob 1.6s steps(1) infinite;
}

@keyframes px-bob {
  0%, 49% { transform: translateY(0); }
  50%, 100% { transform: translateY(-4px); }
}

.empty h2 {
  font-family: var(--pixel);
  font-size: 12px;
  line-height: 1.8;
  text-transform: uppercase;
  margin-bottom: 10px;
}

.bubble {
  max-width: min(760px, 92%);
  padding: 10px 14px;
  border-radius: var(--radius);
  border: var(--bw) solid var(--border);
  background: var(--panel);
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

.bubble.user {
  align-self: flex-end;
  background: var(--accent-soft);
  border-color: var(--accent-edge);
}

.bubble.error {
  border-color: var(--bad);
  color: var(--bad);
}

.bubble-author {
  font-family: var(--pixel);
  font-size: 7px;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--muted);
  margin-bottom: 6px;
}

.status-line {
  font-size: 12px;
  color: var(--muted);
  padding-left: 2px;
}

.status-line::before {
  content: "> ";
}

.tool-block {
  border: var(--bw) solid var(--border);
  border-radius: var(--radius);
  background: var(--panel-2);
  max-width: min(760px, 92%);
  /* Lets the height below interpolate to auto (Chrome 131+; opens instantly
     elsewhere, which is the pixel-correct fallback anyway). */
  interpolate-size: allow-keywords;
}

.tool-block::details-content {
  height: 0;
  overflow: clip;
  transition: height 180ms steps(4), content-visibility 180ms allow-discrete;
}

.tool-block[open]::details-content {
  height: auto;
}

.tool-block summary {
  cursor: pointer;
  padding: 8px 12px;
  display: flex;
  gap: 10px;
  align-items: baseline;
  list-style: none;
}

.tool-block summary::-webkit-details-marker {
  display: none;
}

.tool-block summary::before {
  content: "+";
  font-family: var(--pixel);
  font-size: 9px;
  color: var(--muted);
  flex: none;
}

.tool-block[open] summary::before {
  content: "-";
}

.tool-name {
  font-family: var(--pixel);
  font-size: 8px;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--accent);
  flex: none;
}

.tool-detail {
  font-family: var(--mono);
  font-size: 12.5px;
  color: var(--text);
  overflow-wrap: anywhere;
}

.tool-output {
  margin: 0;
  padding: 10px 12px;
  border-top: var(--bw) solid var(--border);
  font-family: var(--mono);
  font-size: 12.5px;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  max-height: 340px;
  overflow-y: auto;
}

.tool-output.bad {
  color: var(--bad);
}

/* Entries step in rather than fade in; three frames, done. */
.bubble,
.tool-block,
.status-line,
.approval,
.signin-card {
  transition:
    opacity 140ms steps(3),
    transform 140ms steps(3);
  @starting-style {
    opacity: 0;
    transform: translateY(6px);
  }
}

/* ---- thinking indicator ---- */

/*
 * Shown while a turn is running. The label tracks what the agent is actually
 * doing (thinking / running a tool / waiting for approval) and the counter
 * shows elapsed time, so a long silence never looks like a hang.
 */
.thinking {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 10px 14px;
  border: 2px dashed var(--border-strong);
  background: var(--panel);
  max-width: min(760px, 92%);
}

.px-spinner {
  display: grid;
  grid-template-columns: 6px 6px;
  grid-template-rows: 6px 6px;
  gap: 2px;
  flex: none;
}

.px-spinner b {
  background: var(--accent);
  opacity: 0.2;
  animation: px-blink-strong 0.8s steps(1) infinite;
}

/* Cells light up clockwise: top-left, top-right, bottom-right, bottom-left. */
.px-spinner b:nth-child(1) { animation-delay: 0s; }
.px-spinner b:nth-child(2) { animation-delay: 0.2s; }
.px-spinner b:nth-child(4) { animation-delay: 0.4s; }
.px-spinner b:nth-child(3) { animation-delay: 0.6s; }

.thinking-label {
  font-family: var(--pixel);
  font-size: 9px;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--accent);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.thinking.waiting .thinking-label {
  color: var(--warn);
}

.thinking.waiting .px-spinner b {
  background: var(--warn);
}

.thinking-dots {
  display: flex;
  gap: 3px;
}

.thinking-dots b {
  width: 4px;
  height: 4px;
  background: var(--accent);
  opacity: 0.2;
  animation: px-blink-strong 1.2s steps(1) infinite;
}

.thinking-dots b:nth-child(1) { animation-delay: 0s; }
.thinking-dots b:nth-child(2) { animation-delay: 0.3s; }
.thinking-dots b:nth-child(3) { animation-delay: 0.6s; }

.thinking.waiting .thinking-dots b {
  background: var(--warn);
}

.thinking-time {
  margin-left: auto;
  font-size: 12px;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}

@keyframes px-blink {
  0%, 49% { opacity: 1; }
  50%, 100% { opacity: 0.15; }
}

@keyframes px-blink-strong {
  0%, 24% { opacity: 1; }
  25%, 100% { opacity: 0.2; }
}

@keyframes px-pulse {
  0%, 49% { opacity: 1; }
  50%, 100% { opacity: 0.4; }
}

/* ---- composer ---- */

.composer {
  border-top: var(--bw) solid var(--border);
  background: var(--panel);
  padding: 12px 20px 16px;
}

.composer textarea {
  resize: vertical;
  min-height: 62px;
}

.composer-actions {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 10px;
  margin-top: 10px;
}

/* The send button turns into Stop while a turn runs; destructive dialog
   confirms share the same red. */
button.stop,
button.destructive {
  background: var(--bad);
  border-color: #7e2727;
  color: #1a0606;
}

/* Three-frame flicker on the Send/Stop morph, marking the state change. */
#send.swap > * {
  animation: px-swap 160ms steps(3);
}

@keyframes px-swap {
  from {
    opacity: 0;
    transform: translateY(3px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

@media (max-width: 700px) {
  #composerHint {
    display: none;
  }
}

/* ---- dialog ---- */

dialog {
  border: var(--bw) solid var(--border-strong);
  border-radius: var(--radius);
  background: var(--panel);
  color: var(--text);
  padding: 24px;
  width: min(460px, 92vw);
  box-shadow: var(--shadow-lg);
}

/* The dialog steps in from slightly below; closing stays instant. */
dialog[open] {
  animation: px-rise 160ms steps(3);
}

dialog::backdrop {
  background: rgb(0 0 0 / 60%);
}

dialog[open]::backdrop {
  animation: px-fade 160ms steps(3);
}

@keyframes px-fade {
  from { opacity: 0; }
  to { opacity: 1; }
}

dialog h2 {
  font-family: var(--pixel);
  font-size: 11px;
  line-height: 1.7;
  text-transform: uppercase;
  margin-bottom: 8px;
}

dialog label {
  display: block;
  margin: 16px 0 6px;
  font-family: var(--pixel);
  font-size: 8px;
  text-transform: uppercase;
  color: var(--muted);
}

.dialog-actions {
  display: flex;
  justify-content: flex-end;
  gap: 10px;
  margin-top: 22px;
}

/* ---- confirm / prompt / alert dialog ---- */

.confirm-dialog {
  width: min(420px, 92vw);
}

.confirm-dialog h2 {
  display: flex;
  align-items: center;
  gap: 10px;
  color: var(--accent);
}

/* A danger ask (production, Full Auto, delete) speaks in red. */
.confirm-dialog.danger h2 {
  color: var(--bad);
}

.confirm-message {
  margin: 10px 0 0;
  color: var(--text);
}

.confirm-dialog input {
  margin-top: 14px;
}

/* ---- modified files ("cart") ---- */

.count-badge {
  font-family: var(--pixel);
  font-size: 8px;
  padding: 3px 5px 2px;
  background: var(--accent);
  color: var(--accent-ink);
  border: 1px solid var(--accent-edge);
}

.changes-dialog {
  width: min(760px, 94vw);
  max-height: 84vh;
  display: none;
  flex-direction: column;
}

.changes-dialog[open] {
  display: flex;
}

.changes-head {
  display: flex;
  align-items: center;
  gap: 12px;
}

.changes-head h2 {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  min-width: 0;
}

.changes-subtitle {
  margin: 8px 0 12px;
}

.changes-body {
  overflow-y: auto;
  min-height: 0;
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.changes-actions {
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
}

/*
 * The entries below are a record of what the agent did, not a diff, so a change
 * it later undid itself is still listed. This line carries the answer the list
 * cannot give: whether the file on the server still differs.
 */
.change-status {
  margin: 0;
  padding: 8px 10px;
  border-left: 3px solid var(--border);
  background: var(--panel-2);
  font-size: 12.5px;
  line-height: 1.55;
  color: var(--text);
}

.change-status.good {
  border-left-color: var(--ok);
  color: var(--ok);
}

.change-status.muted {
  color: var(--muted);
}

.file-row {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  text-align: left;
  background: var(--panel-2);
  color: var(--text);
  border: var(--bw) solid var(--border);
  box-shadow: none;
  padding: 10px 12px;
  font-family: var(--mono);
  font-size: 12.5px;
  text-transform: none;
  letter-spacing: 0;
}

a.file-row {
  text-decoration: none;
  box-sizing: border-box;
}

/* A picture opens for a look rather than saving, as a thumbnail would. */
.file-row.image {
  cursor: zoom-in;
}

.file-row:active:not(:disabled) {
  transform: none;
  box-shadow: none;
}

@media (hover: hover) and (pointer: fine) {
  .file-row:hover {
    border-color: var(--accent-edge);
    filter: none;
  }
}

.file-path {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  min-width: 0;
  flex: 1;
}

.file-meta {
  flex: none;
  font-size: 11px;
  color: var(--muted);
}

.change-entry {
  border: var(--bw) solid var(--border);
  padding: 10px 12px;
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.change-entry-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
}

.change-entry p {
  margin: 0;
}

/* The preview: removed text reads red, added text reads green. */
.diff {
  margin: 0;
  padding: 8px 10px;
  font-family: var(--mono);
  font-size: 12px;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  max-height: 220px;
  overflow-y: auto;
}

.diff-old {
  background: rgb(255 93 93 / 8%);
  border-left: 3px solid var(--bad);
}

.diff-new {
  background: rgb(70 224 124 / 8%);
  border-left: 3px solid var(--ok);
}

.file-content {
  margin: 0;
  padding: 10px 12px;
  border: var(--bw) solid var(--border);
  background: var(--bg);
  font-family: var(--mono);
  font-size: 12px;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  overflow-y: auto;
  min-height: 0;
}

/* ---- mobile ---- */

/*
 * Under 860px the shell stacks: topbar, then the server picker with a capped
 * sessions list, then the chat filling whatever height remains. The page
 * itself still never scrolls; the transcript and the sessions list do.
 */
@media (max-width: 860px) {
  .layout {
    display: flex;
    flex-direction: column;
  }

  .sidebar {
    flex: none;
    border-right: none;
    border-bottom: var(--bw) solid var(--border);
    /* The open server menu must overlay the chat, not be clipped. */
    overflow: visible;
  }

  .sidebar-section {
    border-bottom: none;
  }

  .sidebar-section.grow {
    flex: none;
  }

  .session-list {
    max-height: 24dvh;
  }

  .chat {
    flex: 1;
    min-height: 0;
  }

  .chat-header,
  .chat-header-actions {
    flex-wrap: wrap;
  }

  /* The permission badge is a nicety; space for the name matters more. */
  .badge {
    display: none;
  }

  #whoami {
    max-width: 90px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  .model-picker select {
    max-width: 42vw;
  }

  /* 16px stops iOS Safari zooming the page every time a field is focused. */
  input,
  select,
  textarea {
    font-size: 16px;
  }

  .transcript {
    padding: 12px;
  }

  .composer {
    padding: 10px 12px 12px;
  }

  dialog {
    padding: 18px;
  }
}

/* Touch: comfortable target sizes, no hover-dependent affordances. */
@media (pointer: coarse) {
  button {
    padding: 12px 16px 11px;
  }

  button.small {
    padding: 10px 12px 9px;
  }

  .server-button,
  .session-button,
  .file-row {
    padding: 12px;
  }

  .server-picker-toggle {
    padding: 12px 10px;
  }

  .effort-slider input[type="range"]::-webkit-slider-thumb {
    width: 18px;
    height: 18px;
    margin-top: -5px;
  }

  .effort-slider input[type="range"]::-moz-range-thumb {
    width: 18px;
    height: 18px;
  }
}

.model-picker select {
  width: auto;
  padding: 6px 8px;
  font-size: 12px;
}

/* ---- effort slider ---- */

.effort-slider {
  display: inline-flex;
  align-items: center;
  gap: 8px;
}

.effort-name {
  font-family: var(--pixel);
  font-size: 8px;
  text-transform: uppercase;
  color: var(--muted);
  /* Longest label reserves the space, so the row never shifts mid-drag. */
  min-width: 58px;
  text-align: right;
}

.effort-slider.wide {
  display: flex;
}

.effort-slider.wide input[type="range"] {
  flex: 1;
  width: auto;
}

.effort-slider.wide .effort-name {
  text-align: left;
}

.effort-slider input[type="range"] {
  appearance: none;
  -webkit-appearance: none;
  width: 110px;
  height: 16px;
  padding: 0;
  margin: 0;
  border: none;
  background: transparent;
  cursor: pointer;
}

.effort-slider input[type="range"]:disabled {
  opacity: 0.4;
  cursor: not-allowed;
}

.effort-slider input[type="range"]::-webkit-slider-runnable-track {
  height: 12px;
  background: var(--bg);
  border: var(--bw) solid var(--border);
}

.effort-slider input[type="range"]::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  width: 12px;
  height: 12px;
  margin-top: -2px;
  border-radius: var(--radius);
  background: var(--accent);
  border: var(--bw) solid var(--accent-edge);
  box-shadow: var(--shadow-sm);
}

/* Grabbing the thumb presses it flat, same as the buttons. */
.effort-slider input[type="range"]:active::-webkit-slider-thumb {
  box-shadow: 0 0 0 var(--shadow);
}

.effort-slider input[type="range"]::-moz-range-track {
  height: 12px;
  background: var(--bg);
  border: var(--bw) solid var(--border);
}

.effort-slider input[type="range"]::-moz-range-thumb {
  width: 12px;
  height: 12px;
  border-radius: var(--radius);
  background: var(--accent);
  border: var(--bw) solid var(--accent-edge);
  box-shadow: var(--shadow-sm);
}

.effort-slider input[type="range"]:active::-moz-range-thumb {
  box-shadow: 0 0 0 var(--shadow);
}

.effort-slider input[type="range"]:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
}

/* ---- approval requests ---- */

.approval {
  border: var(--bw) solid var(--warn);
  border-radius: var(--radius);
  background: var(--panel);
  padding: 12px 14px;
  max-width: min(760px, 92%);
  /* The agent is blocked on this; the border blinks until someone answers. */
  animation: px-attention 1.2s steps(1) infinite;
}

@keyframes px-attention {
  0%, 49% { border-color: var(--warn); }
  50%, 100% { border-color: var(--border-strong); }
}

.approval.approved,
.approval.refused {
  animation: none;
  opacity: 0.75;
  /* The blink ends and the verdict color settles in over a few frames. */
  transition: opacity 240ms steps(3), border-color 240ms steps(3);
}

.approval.approved {
  border-color: var(--ok);
}

.approval.refused {
  border-color: var(--bad);
}

.approval-title {
  display: flex;
  align-items: center;
  gap: 8px;
  font-family: var(--pixel);
  font-size: 8px;
  line-height: 1.6;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--warn);
  margin-bottom: 10px;
}

.approval.approved .approval-title {
  color: var(--ok);
}

.approval.refused .approval-title {
  color: var(--bad);
}

.approval-command {
  margin: 0 0 10px;
  padding: 9px 11px;
  border-radius: var(--radius);
  border: var(--bw) solid var(--border);
  background: var(--bg);
  font-family: var(--mono);
  font-size: 12.5px;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  max-height: 220px;
  overflow-y: auto;
}

.approval-actions {
  display: flex;
  align-items: center;
  gap: 10px;
}

/* ---- reduced motion ---- */

@media (prefers-reduced-motion: reduce) {
  .px-spinner b,
  .thinking-dots b,
  .tag.busy::before,
  .skeleton-row .sk {
    animation: none;
    opacity: 0.8;
  }

  .approval,
  .empty-art .icon-lg,
  .server-list:not([hidden]),
  .server-list.stagger .server-item,
  .chat-header:not([hidden]),
  .composer:not([hidden]),
  dialog[open],
  dialog[open]::backdrop,
  #send.swap > * {
    animation: none;
  }

  .transcript {
    scroll-behavior: auto;
  }

  .tool-block::details-content {
    transition: none;
  }

  .bubble,
  .tool-block,
  .status-line,
  .approval,
  .signin-card {
    transition: opacity 140ms steps(3);
    @starting-style {
      opacity: 0;
      transform: none;
    }
  }
}

.mode-warning {
  margin: 6px 0 0;
  padding: 6px 10px;
  border-left: 3px solid var(--warn);
  background: var(--panel-2);
  border-radius: 0 6px 6px 0;
  font-size: 12.5px;
  color: var(--warn);
  max-width: 60ch;
}

/* ==== minimal theme =======================================================
 * A modern minimal dark alternative to the pixel default, selected per
 * browser from the topbar and stored with the other preferences. Same
 * structure and colors for status; different voice: system sans labels,
 * hairline borders, rounded corners, soft shadows, smooth easing.
 */

html.theme-minimal {
  --bg: #101216;
  --panel: #171a20;
  --panel-2: #1e222a;
  --border: #272c36;
  --border-strong: #39404e;
  --text: #e7eaef;
  --muted: #939cab;
  --accent: #2f6feb;
  --accent-ink: #ffffff;
  --accent-soft: #16233f;
  --accent-edge: #2456bd;
  --radius: 10px;
  --bw: 1px;
  --shadow-btn: 0 1px 2px rgb(0 0 0 / 50%);
  --shadow-sm: 0 1px 2px rgb(0 0 0 / 40%);
  --shadow-md: 0 8px 24px rgb(0 0 0 / 45%);
  --shadow-lg: 0 16px 40px rgb(0 0 0 / 50%);
  /* Every label set in the pixel face falls back to system sans. */
  --pixel: -apple-system, "Segoe UI", system-ui, Roboto, sans-serif;
}

html.theme-minimal .crt {
  display: none;
}

/* Label sizes: the pixel face reads large per point, the sans face does not. */
html.theme-minimal button {
  font-size: 12px;
  font-weight: 600;
  text-transform: none;
  letter-spacing: 0;
}

html.theme-minimal button.small,
html.theme-minimal .sidebar-heading h2,
html.theme-minimal .badge,
html.theme-minimal .auth-card label,
html.theme-minimal dialog label,
html.theme-minimal .tool-name,
html.theme-minimal .approval-title,
html.theme-minimal .effort-name,
html.theme-minimal .count-badge {
  font-size: 11px;
}

html.theme-minimal .tag,
html.theme-minimal .bubble-author {
  font-size: 10px;
}

html.theme-minimal .brand {
  font-size: 14px;
  font-weight: 600;
  text-transform: none;
}

html.theme-minimal .chat-header h2 {
  font-size: 13.5px;
  font-weight: 600;
  text-transform: none;
}

html.theme-minimal dialog h2 {
  font-size: 15px;
  font-weight: 600;
  text-transform: none;
}

html.theme-minimal .empty h2 {
  font-size: 15px;
  font-weight: 600;
  text-transform: none;
}

html.theme-minimal .auth-card h1 {
  font-size: 18px;
  font-weight: 600;
  text-transform: none;
}

html.theme-minimal .thinking-label {
  font-size: 12px;
  font-weight: 600;
}

html.theme-minimal .tool-block summary::before {
  font-size: 12px;
}

/* Press feedback: a soft scale instead of the sprite jump. */
html.theme-minimal button:active:not(:disabled),
html.theme-minimal button.ghost:active:not(:disabled) {
  transform: scale(0.97);
  box-shadow: var(--shadow-btn);
}

html.theme-minimal .thinking {
  border-style: solid;
  border-color: var(--border);
}

html.theme-minimal .px-spinner b,
html.theme-minimal .thinking-dots b {
  border-radius: 50%;
  animation-timing-function: ease-in-out;
}

/* Motion: same choreography, smooth curve instead of stepped frames. */
html.theme-minimal .bubble,
html.theme-minimal .tool-block,
html.theme-minimal .status-line,
html.theme-minimal .approval,
html.theme-minimal .signin-card {
  transition:
    opacity 200ms cubic-bezier(0.16, 1, 0.32, 1),
    transform 200ms cubic-bezier(0.16, 1, 0.32, 1);
}

html.theme-minimal dialog[open],
html.theme-minimal dialog[open]::backdrop,
html.theme-minimal .server-list:not([hidden]),
html.theme-minimal .chat-header:not([hidden]),
html.theme-minimal .composer:not([hidden]),
html.theme-minimal .server-list.stagger .server-item,
html.theme-minimal #send.swap > * {
  animation-timing-function: cubic-bezier(0.16, 1, 0.32, 1);
}

html.theme-minimal .empty-art .icon-lg {
  animation: px-bob-smooth 2.4s ease-in-out infinite;
}

@keyframes px-bob-smooth {
  0%,
  100% {
    transform: translateY(0);
  }
  50% {
    transform: translateY(-5px);
  }
}

html.theme-minimal .effort-slider input[type="range"]::-webkit-slider-runnable-track {
  border-radius: 99px;
}

html.theme-minimal .effort-slider input[type="range"]::-webkit-slider-thumb {
  border-radius: 50%;
  box-shadow: var(--shadow-btn);
}

html.theme-minimal .effort-slider input[type="range"]::-moz-range-track {
  border-radius: 99px;
}

html.theme-minimal .effort-slider input[type="range"]::-moz-range-thumb {
  border-radius: 50%;
  box-shadow: var(--shadow-btn);
}

html.theme-minimal .dot {
  border-radius: 50%;
}

html.theme-minimal .mode-warning {
  border-radius: var(--radius);
}

@media (prefers-reduced-motion: reduce) {
  html.theme-minimal .empty-art .icon-lg {
    animation: none;
  }
}

/* ==== light mode ==========================================================
 * The second appearance axis: html.theme-light re-colors the pixel theme
 * (paper-terminal pale green) and, combined with .theme-minimal, the minimal
 * theme (clean white). Structure tokens are untouched; the hard pixel
 * shadows pick up the light --shadow automatically.
 */

html.theme-light {
  color-scheme: light;
  --bg: #e9ece5;
  --panel: #f4f6f0;
  --panel-2: #e0e5da;
  --border: #b7c1ae;
  --border-strong: #94a189;
  --text: #17241b;
  --muted: #55654c;
  --accent: #177a3c;
  --accent-ink: #f4fff6;
  --accent-soft: #d3ead9;
  --accent-edge: #0e5228;
  --ok: #177a3c;
  --warn: #8a5a00;
  --bad: #b52f2f;
  --prod: #b52f2f;
  --shadow: #a9b39c;
}

html.theme-light .crt {
  background: repeating-linear-gradient(
    to bottom,
    rgb(0 0 0 / 6%) 0 1px,
    transparent 1px 3px
  );
}

html.theme-light.theme-minimal {
  --bg: #f5f6f8;
  --panel: #ffffff;
  --panel-2: #edf0f4;
  --border: #dde2e9;
  --border-strong: #c2cad5;
  --text: #171a1f;
  --muted: #5d6878;
  --accent: #2f6feb;
  --accent-ink: #ffffff;
  --accent-soft: #e7effe;
  --accent-edge: #2456bd;
  --ok: #1f9254;
  --warn: #8a5a00;
  --bad: #c0392b;
  --prod: #c0392b;
  --shadow-btn: 0 1px 2px rgb(16 24 40 / 12%);
  --shadow-sm: 0 1px 2px rgb(16 24 40 / 10%);
  --shadow-md: 0 8px 24px rgb(16 24 40 / 12%);
  --shadow-lg: 0 16px 40px rgb(16 24 40 / 14%);
}

/* ---- settings dialog ---- */

.settings-dialog {
  width: min(440px, 92vw);
}

.setting-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  margin-top: 18px;
}

.setting-label {
  font-family: var(--pixel);
  font-size: 8px;
  text-transform: uppercase;
  color: var(--muted);
}

html.theme-minimal .setting-label {
  font-size: 11px;
}

/* Segmented control: joined options, the active one wears the accent. */
.seg {
  display: inline-flex;
  border: var(--bw) solid var(--border-strong);
  border-radius: var(--radius);
  overflow: hidden;
}

.seg-option {
  border: none;
  border-radius: 0;
  box-shadow: none;
  background: var(--panel-2);
  color: var(--text);
  padding: 8px 12px 7px;
  font-size: 8px;
}

.seg-option + .seg-option {
  border-left: var(--bw) solid var(--border-strong);
}

.seg-option:active:not(:disabled) {
  transform: none;
  box-shadow: none;
}

.seg-option.active {
  background: var(--accent);
  color: var(--accent-ink);
}

/* ---- service status tags ---- */

.tag.down {
  background: transparent;
  color: var(--bad);
  border-color: var(--bad);
}

.tag.maint {
  background: transparent;
  color: var(--warn);
  border-color: var(--warn);
}

/* ---- remote access list (settings) ---- */

.setting-section {
  margin-top: 22px;
  padding-top: 16px;
  border-top: var(--bw) solid var(--border);
}

.setting-section .hint {
  margin: 8px 0 10px;
}

.access-list {
  list-style: none;
  margin: 0 0 10px;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 6px;
  max-height: 180px;
  overflow-y: auto;
}

.access-item {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 6px 8px;
  border: var(--bw) solid var(--border);
  border-radius: var(--radius);
}

.access-cidr {
  font-family: var(--mono);
  font-size: 12.5px;
  flex: 1;
}

.access-add {
  display: flex;
  gap: 8px;
}

.access-add input {
  flex: 1;
  width: auto;
}

/* ---- terminal ---- */

.terminal-dialog {
  width: min(1100px, 96vw);
  height: min(720px, 88dvh);
  max-width: none;
  padding: 0;
  display: none;
  flex-direction: column;
}

.terminal-dialog[open] {
  display: flex;
}

.terminal-head {
  display: flex;
  align-items: center;
  gap: 10px;
  flex: none;
  padding: 12px 14px;
  border-bottom: var(--bw) solid var(--border);
  background: var(--panel);
}

.terminal-head .icon {
  color: var(--accent);
}

.terminal-head h2 {
  font-family: var(--mono);
  font-size: 13px;
  text-transform: none;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  min-width: 0;
}

.term-state {
  font-family: var(--pixel);
  font-size: 7px;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  padding: 4px 6px 3px;
  border: 1px solid var(--border-strong);
  color: var(--muted);
  flex: none;
}

.term-state.ok {
  color: var(--ok);
  border-color: var(--accent-edge);
}

.term-state.bad {
  color: var(--bad);
  border-color: var(--bad);
}

html.theme-minimal .term-state {
  font-size: 10px;
}

/* The emulator paints its own background; this is just the frame. */
.terminal-host {
  flex: 1;
  min-height: 0;
  padding: 8px 10px;
  background: var(--bg);
  overflow: hidden;
}

.terminal-host .xterm {
  height: 100%;
}

.terminal-hint {
  flex: none;
  margin: 0;
  padding: 8px 14px;
  border-top: var(--bw) solid var(--border);
  background: var(--panel);
  color: var(--muted);
  font-size: 12px;
  min-height: 33px;
}

@media (max-width: 860px) {
  .terminal-dialog {
    width: 100vw;
    height: 100dvh;
    max-height: none;
  }

  .terminal-head {
    flex-wrap: wrap;
  }
}

/* ---- agent sign-in ---- */

.term-state.warn {
  color: var(--warn);
  border-color: var(--warn);
}

/* Offered wherever a dead login shows: under the server in the picker, */
.server-signin {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 2px 10px 8px;
  font-size: 12px;
  color: var(--warn);
}

.server-signin span {
  flex: 1;
  min-width: 0;
}

/* ...in the chat header, for the agent in use, */
.signin-warning {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 10px;
}

.signin-warning > span {
  flex: 1;
  min-width: 20ch;
}

/* ...and in the transcript, right where a turn failed on it. */
.signin-card {
  border: var(--bw) solid var(--warn);
  border-radius: var(--radius);
  background: var(--panel);
  padding: 12px 14px;
  max-width: min(760px, 92%);
}

.signin-card.signed-in {
  border-color: var(--ok);
}

.signin-card-title {
  display: flex;
  align-items: center;
  gap: 8px;
  font-family: var(--pixel);
  font-size: 8px;
  line-height: 1.6;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--warn);
  margin-bottom: 10px;
}

.signin-card.signed-in .signin-card-title {
  color: var(--ok);
  margin-bottom: 8px;
}

.signin-card p {
  margin: 0 0 10px;
}

.signin-card-detail {
  font-size: 12px;
  color: var(--muted);
  overflow-wrap: anywhere;
}

/* A server with nothing signed in gets the way out on its empty page. */
.empty-actions {
  display: flex;
  justify-content: center;
  flex-wrap: wrap;
  gap: 10px;
  margin-top: 18px;
}

.signin-dialog {
  width: min(640px, 94vw);
  max-height: 90dvh;
  display: none;
  flex-direction: column;
}

.signin-dialog[open] {
  display: flex;
}

.signin-body {
  overflow-y: auto;
  min-height: 0;
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.signin-steps {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.signin-step {
  display: flex;
  gap: 12px;
  padding: 12px;
  border: var(--bw) solid var(--border);
  border-radius: var(--radius);
  background: var(--panel-2);
}

/* The step waiting on you wears the accent; a finished one keeps its number green. */
.signin-step.current {
  border-color: var(--accent-edge);
}

.signin-step-num {
  display: grid;
  place-items: center;
  width: 24px;
  height: 24px;
  flex: none;
  font-family: var(--pixel);
  font-size: 9px;
  border: var(--bw) solid var(--border-strong);
  border-radius: var(--radius);
  background: var(--bg);
  color: var(--muted);
}

.signin-step.current .signin-step-num {
  background: var(--accent);
  border-color: var(--accent-edge);
  color: var(--accent-ink);
}

.signin-step.done .signin-step-num {
  color: var(--ok);
  border-color: var(--accent-edge);
}

.signin-step-body {
  flex: 1;
  min-width: 0;
}

.signin-step-title {
  margin: 2px 0 0;
  font-family: var(--pixel);
  font-size: 9px;
  font-weight: 400;
  line-height: 1.7;
  text-transform: uppercase;
}

.signin-link-row,
.signin-paste {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 10px;
}

.signin-paste input {
  flex: 1;
  width: auto;
  min-width: 12ch;
  font-size: 12.5px;
}

/* A device sign-in's one-time code, big enough to read across to the page. */
.signin-device {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 10px;
  margin-top: 10px;
}

.signin-device-code {
  font-family: var(--mono);
  font-size: 20px;
  letter-spacing: 0.12em;
  padding: 6px 12px;
  border: var(--bw) solid var(--accent-edge);
  border-radius: var(--radius);
  background: var(--bg);
  color: var(--accent);
  user-select: all;
}

/* The link dressed as the primary button. It stays a real link: it opens a
   new tab, and can be middle-clicked or copied like one. */
a.button-link {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-family: var(--pixel);
  font-size: 9px;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  line-height: 1;
  text-decoration: none;
  border: var(--bw) solid var(--accent-edge);
  border-radius: var(--radius);
  padding: 10px 14px 9px;
  background: var(--accent);
  color: var(--accent-ink);
  box-shadow: var(--shadow-btn);
}

a.button-link:active {
  transform: translate(3px, 3px);
  box-shadow: 0 0 0 var(--shadow);
}

@media (hover: hover) and (pointer: fine) {
  a.button-link {
    transition: filter 120ms steps(2);
  }

  a.button-link:hover {
    filter: brightness(1.08);
  }
}

/* The same link in the quieter ghost dress and the small size, for a bar that
   already holds ghost buttons and where it is one of several things to do. */
a.button-link.ghost {
  background: var(--panel-2);
  border-color: var(--border-strong);
  color: var(--text);
  box-shadow: var(--shadow-sm);
}

a.button-link.ghost:active {
  transform: translate(2px, 2px);
  box-shadow: 0 0 0 var(--shadow);
}

a.button-link.small {
  padding: 7px 10px 6px;
  font-size: 8px;
}

@media (pointer: coarse) {
  a.button-link.small {
    padding: 10px 12px 9px;
  }
}

.signin-status {
  margin: 0;
  padding: 8px 10px;
  border-left: 3px solid var(--border);
  background: var(--panel-2);
  font-size: 12.5px;
  line-height: 1.55;
}

.signin-status:empty {
  display: none;
}

.signin-status.good {
  border-left-color: var(--ok);
  color: var(--ok);
}

.signin-status.bad {
  border-left-color: var(--bad);
  color: var(--bad);
}

/* The PTY is 1000 columns wide so the link never wraps; the frame scrolls
   sideways instead, and what matters sits at the left edge anyway. */
.signin-term {
  flex: none;
  max-height: 340px;
  overflow: auto;
  padding: 6px;
  border: var(--bw) solid var(--border);
  background: var(--bg);
}

.signin-actions {
  align-items: center;
}

/* Settings: every agent login, renewable from one place. */
.agent-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 6px;
  max-height: 260px;
  overflow-y: auto;
}

.agent-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px;
  border: var(--bw) solid var(--border);
  border-radius: var(--radius);
}

.agent-row-text {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.agent-row-title {
  font-size: 12.5px;
  font-weight: 600;
}

.agent-row-meta,
.agent-row-state {
  font-size: 11px;
  color: var(--muted);
  overflow-wrap: anywhere;
}

.agent-row-state.expired,
.agent-row-state.missing {
  color: var(--warn);
}

html.theme-minimal .signin-card-title,
html.theme-minimal .signin-step-num {
  font-size: 11px;
}

html.theme-minimal .signin-card-title,
html.theme-minimal .signin-step-title {
  font-weight: 600;
  text-transform: none;
  letter-spacing: 0;
}

html.theme-minimal .signin-step-title {
  font-size: 13px;
}

html.theme-minimal .signin-step-num {
  border-radius: 50%;
}

html.theme-minimal a.button-link {
  font-size: 12px;
  font-weight: 600;
  text-transform: none;
  letter-spacing: 0;
}

html.theme-minimal a.button-link.small {
  font-size: 11px;
}

html.theme-minimal a.button-link:active {
  transform: scale(0.97);
  box-shadow: var(--shadow-btn);
}

/* ==== view tabs (topbar) ==================================================
 * Console / Games switcher. Same joined construction as the settings
 * segmented control; the Console tab carries the agent-done notification dot.
 */

.view-tabs {
  display: inline-flex;
  border: var(--bw) solid var(--border-strong);
  border-radius: var(--radius);
  overflow: hidden;
}

.view-tab {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  border: none;
  border-radius: 0;
  box-shadow: none;
  background: var(--panel-2);
  color: var(--muted);
  padding: 8px 14px 7px;
  font-size: 8px;
}

.view-tab + .view-tab {
  border-left: var(--bw) solid var(--border-strong);
}

.view-tab:active:not(:disabled) {
  transform: none;
  box-shadow: none;
}

.view-tab.active {
  background: var(--accent);
  color: var(--accent-ink);
}

/* Blinks a few times to catch the eye, then holds steady until visited. */
.tab-dot {
  width: 7px;
  height: 7px;
  border-radius: var(--radius);
  background: var(--accent);
  flex: none;
  animation: px-blink 0.9s steps(1) 4;
}

/* ==== games / arcade ======================================================
 * The Games tab: guideline Tetris drawn by tetris.js. Layout is HOLD and
 * score on the left, the well in the middle, NEXT and keys on the right.
 * Everything reads the theme tokens; the canvases re-read them on theme
 * change via Arcade.retheme().
 */

:root {
  /* Tetromino colors, tuned to sit with the dark palettes. */
  --tz-i: #3fc9dc;
  --tz-j: #5f8df0;
  --tz-l: #e8964e;
  --tz-o: #e6c84e;
  --tz-s: #52d072;
  --tz-t: #b57ce8;
  --tz-z: #ef5d5d;
  --tz-dead: #39424f;
}

html.theme-light {
  /* Darker set so pieces hold contrast on the pale well. */
  --tz-i: #0f8ba0;
  --tz-j: #2b5cc4;
  --tz-l: #ad5c14;
  --tz-o: #9a7d10;
  --tz-s: #1c8c3e;
  --tz-t: #7a3fb8;
  --tz-z: #c03030;
  --tz-dead: #aab3a0;
}

.games-view {
  position: relative;
  flex: 1;
  min-height: 0;
  overflow: auto;
  display: flex;
  flex-direction: column;
}

/* margin auto centers both axes and still allows scrolling when cramped. */
.arcade {
  margin: auto;
  padding: 18px 16px 26px;
}

/*
 * Three tracks: the Back button sits in the first, the title in the middle,
 * the third stays empty. The outer tracks are equal, so the title is centered
 * without taking the button out of flow (an absolutely positioned button
 * layers under the friends panel and is easy to mis-hit).
 */
.arcade-marquee {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  gap: 10px;
  margin-bottom: 16px;
  color: var(--accent);
}

.marquee-title {
  grid-column: 2;
  display: flex;
  align-items: center;
  gap: 10px;
}

.marquee-back {
  grid-column: 1;
  justify-self: start;
}

.marquee-title .icon {
  width: 20px;
  height: 20px;
}

.arcade-marquee h2 {
  font-family: var(--pixel);
  font-size: 13px;
  text-transform: uppercase;
  letter-spacing: 0.1em;
}

.arcade-grid {
  display: grid;
  grid-template-columns: auto auto auto;
  gap: 18px;
  align-items: start;
  justify-content: center;
}

.arcade-col {
  display: flex;
  flex-direction: column;
  gap: 14px;
  width: 122px;
}

.arcade-box {
  background: var(--panel);
  border: var(--bw) solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow-sm);
  padding: 10px 12px 12px;
}

.arcade-label {
  font-family: var(--pixel);
  font-size: 8px;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--muted);
  margin: 0 0 8px;
}

.mini-canvas {
  display: block;
  margin: 0 auto;
  width: 72px;
  height: auto;
  image-rendering: pixelated;
}

.stat-value {
  font-family: var(--pixel);
  font-size: 9px;
  color: var(--text);
  margin: 0;
}

.stat-score {
  font-size: 11px;
  line-height: 1.5;
  color: var(--accent);
  margin: 0 0 6px;
}

.stat-row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 8px;
  margin-top: 8px;
}

.stat-row .arcade-label {
  margin: 0;
}

/* ---- the well ---- */

.board-col {
  display: flex;
  flex-direction: column;
  align-items: center;
}

.board-well {
  position: relative;
  line-height: 0;
  background: var(--bg);
  border: var(--bw) solid var(--border-strong);
  border-radius: var(--radius);
  box-shadow: var(--shadow-md);
  overflow: hidden;
}

/* Solo gets the big screen; a battle splits it into two smaller wells. */
#boardCanvas,
#oppCanvas {
  display: block;
  image-rendering: pixelated;
  height: max(min(600px, calc(100dvh - 250px)), 300px);
  aspect-ratio: 1 / 2;
  width: auto;
}

.games-view.battle #boardCanvas,
#oppCanvas {
  height: max(min(460px, calc(100dvh - 290px)), 280px);
}

.board-overlay {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 14px;
  padding: 20px;
  text-align: center;
  line-height: 1.5;
  background: color-mix(in srgb, var(--bg) 82%, transparent);
}

.overlay-title {
  font-family: var(--pixel);
  font-size: 16px;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--accent);
}

.overlay-title.bad {
  color: var(--bad);
}

.overlay-sub {
  margin: 0;
  font-size: 12px;
  color: var(--muted);
  max-width: 26ch;
}

.overlay-key {
  margin: 0;
  font-family: var(--pixel);
  font-size: 7px;
  letter-spacing: 0.08em;
  color: var(--muted);
  animation: px-blink 1s steps(1) infinite;
}

/*
 * Tetris names its lessons on its overlay buttons, and on a narrow well a
 * name wraps. Give those labels real leading; the padding gives back what
 * the leading adds, so a one-line button stays the height it always was.
 */
.board-overlay[data-kind] button:not(.small) {
  padding-top: 8px;
  padding-bottom: 7px;
  line-height: 1.4;
}

/* The solo tempo window: a draining bar beside the score stats. */
.tz-tempo {
  display: inline-block;
  width: 64px;
  height: 8px;
  background: var(--bg);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
  overflow: hidden;
}

.tz-tempo-fill {
  display: block;
  height: 100%;
  width: 100%;
  background: var(--accent);
}

.tz-tempo-fill.low {
  background: var(--bad);
}

/* Tutorials: a lesson's coaching line, pinned over the top of the well. */
.tz-tutor {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  margin: 0;
  padding: 8px 10px;
  font-size: 11.5px;
  line-height: 1.45;
  text-align: center;
  color: var(--text);
  background: color-mix(in srgb, var(--panel) 88%, transparent);
  border-bottom: var(--bw) solid var(--border-strong);
}

/* Headed by the lesson's name and its place in the tutorial. */
.tz-tutor::before {
  content: attr(data-lesson);
  display: block;
  margin-bottom: 5px;
  font-family: var(--pixel);
  font-size: 7px;
  line-height: 1.4;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--accent);
}

/* ---- tutorial menu ---- */

/*
 * The tallest overlay there is. On a short well it scrolls from the top
 * rather than sitting centered with both ends cut off; the auto margins do
 * the centering whenever there is room for it.
 */
.board-overlay[data-kind="tutorials"] {
  justify-content: flex-start;
  gap: 10px;
  overflow-y: auto;
}

.board-overlay[data-kind="tutorials"] > :first-child {
  margin-top: auto;
}

.board-overlay[data-kind="tutorials"] > :last-child {
  margin-bottom: auto;
}

.tz-picks {
  display: flex;
  flex-direction: column;
  gap: 6px;
  width: 100%;
  max-width: 230px;
}

/* One row per tutorial, with its piece's color down the left edge. */
.tz-pick {
  justify-content: flex-start;
  width: 100%;
  border-left-width: 6px;
}

/* Eight entries: a little less padding each keeps the list on a laptop-sized
   well without scrolling. (Matches the overlay-button rule's weight; later.) */
.board-overlay[data-kind="tutorials"] button.tz-pick {
  padding-top: 6px;
  padding-bottom: 5px;
}

.tz-pick.sel {
  background: var(--accent-soft);
  border-color: var(--accent-edge);
  color: var(--accent);
}

.tz-pick[data-piece="I"] { border-left-color: var(--tz-i); }
.tz-pick[data-piece="J"] { border-left-color: var(--tz-j); }
.tz-pick[data-piece="L"] { border-left-color: var(--tz-l); }
.tz-pick[data-piece="O"] { border-left-color: var(--tz-o); }
.tz-pick[data-piece="S"] { border-left-color: var(--tz-s); }
.tz-pick[data-piece="T"] { border-left-color: var(--tz-t); }
.tz-pick[data-piece="Z"] { border-left-color: var(--tz-z); }

/* What the entry under the cursor teaches; reserved so the menu holds still. */
.tz-about {
  max-width: 30ch;
  min-height: 4.5em;
}

/* ---- tutorial pages: ways to improve, and the note on the O ---- */

/*
 * A page is a card over the top of the well, leaving the example stack on
 * the floor beneath it in view. How far down the card may reach depends on
 * how tall that stack is (data-rows; a well row is a twentieth of the
 * height), and past that the card scrolls.
 */
.board-overlay[data-kind="pages"] {
  justify-content: flex-start;
  padding: 10px;
  background: none;
}

.tz-card {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
  width: 100%;
  max-height: 66%;
  overflow-y: auto;
  padding: 14px 12px;
  background: color-mix(in srgb, var(--panel) 94%, transparent);
  border: var(--bw) solid var(--border-strong);
  border-radius: var(--radius);
  box-shadow: var(--shadow-sm);
}

.tz-card[data-rows="0"] { max-height: 100%; }
.tz-card[data-rows="3"] { max-height: 81%; }
.tz-card[data-rows="4"] { max-height: 76%; }
.tz-card[data-rows="5"] { max-height: 71%; }

.tz-card .overlay-title {
  font-size: 11px;
}

/* Page turners at either end, the page number between them. */
.tz-nav {
  display: flex;
  align-items: center;
  justify-content: space-between;
  width: 100%;
}

.tz-page-count {
  font-family: var(--pixel);
  font-size: 8px;
  letter-spacing: 0.08em;
  color: var(--muted);
}

.tz-page-name {
  margin: 0;
  font-family: var(--pixel);
  font-size: 9px;
  font-weight: 400;
  line-height: 1.5;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--text);
}

.tz-page-text {
  margin: 0;
  font-size: 12px;
  line-height: 1.55;
  text-align: left;
  color: var(--text);
}

/* Reserved height, so awards never shift the board when they appear. */
.action-callout {
  min-height: 44px;
  margin: 10px 0 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  text-align: center;
  opacity: 0;
}

.action-callout b {
  font-family: var(--pixel);
  font-size: 10px;
  font-weight: 400;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--accent);
}

.action-callout span {
  font-size: 11px;
  color: var(--muted);
}

.action-callout.show {
  animation: arcade-callout 1.5s steps(3) forwards;
}

@keyframes arcade-callout {
  0%, 75% { opacity: 1; }
  100% { opacity: 0; }
}

/* ---- keys legend and toggles ---- */

.key-row {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-top: 7px;
  font-size: 11.5px;
  color: var(--muted);
}

.key-row kbd {
  font-family: var(--mono);
  font-size: 10.5px;
  color: var(--text);
  background: var(--bg);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
  padding: 2px 5px;
  min-width: 38px;
  text-align: center;
  flex: none;
}

/* Sound toggles ride in the marquee's third track, opposite the Back button. */
.marquee-toggles {
  grid-column: 3;
  justify-self: end;
  display: flex;
  gap: 8px;
}

.marquee-toggles button[aria-pressed="false"] {
  color: var(--muted);
}

/* ---- touch controls ---- */

.touch-pad {
  display: none;
  grid-template-columns: repeat(7, 1fr);
  gap: 8px;
  margin-top: 16px;
}

@media (pointer: coarse) {
  .touch-pad {
    display: grid;
  }

  /* Keyboard hints mean nothing to a touch player. */
  .arcade-keys {
    display: none;
  }
}

.touch-pad button {
  padding: 13px 0 12px;
  font-size: 12px;
  touch-action: none;
  user-select: none;
  -webkit-user-select: none;
}

/* ---- agent-replied toast ---- */

.arcade-toast {
  position: absolute;
  top: 14px;
  right: 16px;
  z-index: 60;
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 14px;
  text-align: left;
  text-transform: none;
  letter-spacing: 0;
  background: var(--panel);
  color: var(--text);
  border: var(--bw) solid var(--accent-edge);
  border-radius: var(--radius);
  box-shadow: var(--shadow-md);
}

.arcade-toast:not([hidden]) {
  animation: px-rise 160ms steps(3);
}

.arcade-toast .icon {
  color: var(--accent);
}

.toast-text {
  display: flex;
  flex-direction: column;
  gap: 3px;
}

.toast-text b {
  font-family: var(--pixel);
  font-size: 8px;
  font-weight: 400;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--accent);
}

.toast-text > span {
  font-family: var(--mono);
  font-size: 11px;
  color: var(--muted);
}

/* ---- small screens ---- */

@media (max-width: 860px) {
  .arcade-grid {
    grid-template-columns: 1fr;
    justify-items: center;
    gap: 14px;
  }

  /* The well leads; the side panels become rows under it. */
  .board-col {
    order: -1;
  }

  .arcade-col {
    flex-direction: row;
    align-items: stretch;
    width: auto;
    max-width: 100%;
    flex-wrap: wrap;
    justify-content: center;
  }

  #boardCanvas {
    height: max(min(440px, calc(100dvh - 330px)), 240px);
  }

  /* The marquee stacks so the toggles never squeeze the title. */
  .arcade-marquee {
    grid-template-columns: auto 1fr;
    row-gap: 10px;
  }

  .marquee-title {
    grid-column: 2;
    justify-self: start;
  }

  .marquee-toggles {
    grid-column: 1 / -1;
    justify-self: stretch;
  }

  .marquee-toggles button {
    flex: 1;
  }
}

@media (max-width: 700px) {
  /* The tabs earn their room from the wordmark and tab labels. */
  .brand > span:not(.brand-mark) {
    display: none;
  }

  .view-tab-text {
    display: none;
  }

  .view-tab {
    padding: 8px 12px 7px;
  }
}

/* ---- reduced motion (games) ---- */

@media (prefers-reduced-motion: reduce) {
  .tab-dot,
  .overlay-key,
  .arcade-toast:not([hidden]) {
    animation: none;
  }

  /* Awards hold steady instead of blinking out. */
  .action-callout.show {
    animation: none;
    opacity: 1;
  }
}

/* ---- minimal theme (games) ---- */

html.theme-minimal .view-tab {
  font-size: 12px;
  font-weight: 600;
  text-transform: none;
  letter-spacing: 0;
}

html.theme-minimal .tab-dot {
  border-radius: 50%;
}

html.theme-minimal .arcade-marquee h2 {
  font-size: 15px;
  font-weight: 600;
  text-transform: none;
  letter-spacing: 0;
}

html.theme-minimal .arcade-label {
  font-size: 10.5px;
  letter-spacing: 0.04em;
}

html.theme-minimal .stat-value {
  font-size: 13px;
  font-weight: 600;
}

html.theme-minimal .stat-score {
  font-size: 16px;
}

html.theme-minimal .overlay-title {
  font-size: 20px;
  font-weight: 700;
  text-transform: none;
  letter-spacing: 0;
}

html.theme-minimal .overlay-key,
html.theme-minimal .tz-tutor::before,
html.theme-minimal .tz-page-count {
  font-size: 10.5px;
  letter-spacing: 0.03em;
}

html.theme-minimal .tz-card .overlay-title {
  font-size: 14px;
}

html.theme-minimal .tz-page-name {
  font-size: 12.5px;
  font-weight: 600;
  text-transform: none;
  letter-spacing: 0;
}

html.theme-minimal .action-callout b,
html.theme-minimal .toast-text b {
  font-size: 12.5px;
  font-weight: 600;
  text-transform: none;
  letter-spacing: 0;
}

html.theme-minimal .action-callout.show {
  animation-timing-function: ease;
}

html.theme-minimal .arcade-toast:not([hidden]) {
  animation-timing-function: cubic-bezier(0.16, 1, 0.32, 1);
}

/* ==== battles and lobby ===================================================
 * Two-player mode: the arena splits into You and the opponent's mirrored
 * board; the Players box lists everyone whose Games tab is open.
 */

:root {
  --tz-wall: #4d5568;
}

html.theme-light {
  --tz-wall: #8f987f;
}

.board-label {
  font-family: var(--pixel);
  font-size: 9px;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--muted);
  margin: 0 0 8px;
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  text-align: center;
}

.board-label.you {
  color: var(--accent);
}

.opp-stats {
  margin: 10px 0 0;
  font-size: 11.5px;
  color: var(--muted);
  text-align: center;
  font-variant-numeric: tabular-nums;
}

.overlay-count {
  margin: 0;
  font-family: var(--pixel);
  font-size: 40px;
  line-height: 1;
  color: var(--accent);
}

/* ---- friends panel ---- */

/*
 * The lobby is a friends list docked to the LEFT EDGE of the view. It is
 * absolutely positioned so it takes no part in the centering math: the
 * arcade's margin:auto centers the board on the screen itself, not on the
 * space left over beside the rail. Battles hide it; nobody in it is
 * challengeable then.
 */
.friends-panel {
  position: absolute;
  left: 16px;
  top: 18px;
  bottom: 18px;
  width: 250px;
  z-index: 5;
  display: flex;
  flex-direction: column;
}

.friends-panel .arcade-lobby {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
}

.games-view.battle .friends-panel {
  display: none;
}

.lobby-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 8px;
  flex: 1;
  min-height: 0;
  overflow-y: auto;
}

.lobby-empty {
  margin: auto;
  padding: 16px 10px;
  text-align: center;
  font-size: 11.5px;
  color: var(--muted);
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.lobby-empty b {
  font-weight: 600;
  color: var(--text);
}

.lobby-item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 8px 10px;
  border: var(--bw) solid var(--border);
  border-radius: var(--radius);
}

.lobby-item button {
  flex: none;
}

.lobby-who {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
  flex: 1;
}

.lobby-you {
  flex: none;
  margin: 10px 0 0;
  padding-top: 9px;
  border-top: var(--bw) solid var(--border);
  font-size: 10.5px;
  color: var(--muted);
}

/* Too narrow for a docked rail beside a screen-centered board: the panel
   rejoins the flow below the arcade instead of overlapping it. */
@media (max-width: 1150px) {
  .friends-panel {
    position: static;
    width: min(460px, calc(100% - 32px));
    margin: 0 auto 26px;
  }

  .friends-panel .arcade-lobby {
    flex: none;
  }

  .lobby-list {
    flex: none;
    max-height: 240px;
  }
}

.lobby-who b {
  font-size: 12px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.lobby-meta {
  font-size: 10.5px;
  color: var(--muted);
}

.lobby-meta.blink {
  color: var(--accent);
  animation: px-blink 1s steps(1) infinite;
}

@media (prefers-reduced-motion: reduce) {
  .lobby-meta.blink {
    animation: none;
  }
}

/* ---- minimal theme (battles) ---- */

html.theme-minimal .board-label {
  font-size: 12px;
  font-weight: 600;
  text-transform: none;
  letter-spacing: 0;
}

html.theme-minimal .overlay-count {
  font-size: 44px;
  font-weight: 700;
}

/* ==== game picker =========================================================
 * The Games tab opens on a cartridge shelf. The lobby channel only exists
 * after a cartridge is slotted in; Back ejects it and closes the channel.
 */

.game-select {
  margin: auto;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 20px;
  padding: 24px 16px;
  text-align: center;
}

.select-title {
  font-family: var(--pixel);
  font-size: 14px;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--accent);
}

.cartridge {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
  width: 250px;
  padding: 24px 18px 18px;
  background: var(--panel);
  border: var(--bw) solid var(--border-strong);
  border-radius: var(--radius);
  box-shadow: var(--shadow-md);
  color: var(--text);
  font-family: var(--mono);
  text-transform: none;
  letter-spacing: 0;
}

.cart-art {
  image-rendering: pixelated;
  width: 72px;
  height: 48px;
}

.cart-name {
  font-family: var(--pixel);
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--text);
}

.cart-meta {
  font-size: 11.5px;
  color: var(--muted);
}

.cart-top {
  font-family: var(--pixel);
  font-size: 8px;
  letter-spacing: 0.05em;
  color: var(--accent);
}

.select-hint {
  margin: 0;
  max-width: 44ch;
  font-size: 11.5px;
  color: var(--muted);
}

html.theme-minimal .select-title {
  font-size: 17px;
  font-weight: 600;
  text-transform: none;
  letter-spacing: 0;
}

html.theme-minimal .cart-name {
  font-size: 14px;
  font-weight: 600;
  text-transform: none;
  letter-spacing: 0;
}

html.theme-minimal .cart-top {
  font-size: 11px;
}

/* ==== arcade shell chrome =================================================
 * Pieces owned by arcade.js rather than any one game: the cartridge shelf,
 * the notice line under the marquee, and the per-game roots.
 */

.cart-shelf {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 16px;
}

.game-root {
  display: flex;
  flex-direction: column;
  align-items: center;
}

/* Reserved height, so a notice appearing never nudges the table down. */
.arcade-notice {
  min-height: 20px;
  margin: -6px 0 10px;
  text-align: center;
  font-size: 11.5px;
  color: var(--warn);
  opacity: 0;
}

.arcade-notice.show {
  opacity: 1;
  transition: opacity 140ms steps(3);
}

/* ==== pong ================================================================
 * One shared table rather than two boards: in a battle both players hit the
 * same ball, so there is nothing to mirror side by side. Each player sits on
 * the left of their own screen, which is why the guest's view is flipped.
 */

:root {
  --pg-rival: #ef5d5d;
  --pg-ball: #d7e0ea;
}

html.theme-light {
  --pg-rival: #c03030;
  --pg-ball: #17241b;
}

.pong-scores {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 20px;
  width: 100%;
  max-width: 720px;
  margin-bottom: 12px;
}

.pong-side {
  display: flex;
  align-items: baseline;
  gap: 10px;
  min-width: 0;
}

.pong-side.rival {
  justify-content: flex-end;
}

.pong-name {
  font-family: var(--pixel);
  font-size: 9px;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  max-width: 16ch;
}

.pong-name.you {
  color: var(--accent);
}

.pong-score {
  font-family: var(--pixel);
  font-size: 22px;
  line-height: 1;
  color: var(--pg-rival);
  font-variant-numeric: tabular-nums;
}

.pong-score.you {
  color: var(--accent);
}

.pong-target {
  font-family: var(--pixel);
  font-size: 8px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted);
  flex: none;
}

.pong-well {
  position: relative;
  line-height: 0;
  background: var(--bg);
  border: var(--bw) solid var(--border-strong);
  border-radius: var(--radius);
  box-shadow: var(--shadow-md);
  overflow: hidden;
}

#pongCanvas {
  display: block;
  image-rendering: pixelated;
  /* The table leads the layout: as wide as the view allows, 5:3, and never
     so tall that the scores or the key legend fall off the screen. */
  width: min(880px, calc(100vw - 340px));
  max-width: 100%;
  height: auto;
  aspect-ratio: 5 / 3;
}

.pong-foot {
  display: flex;
  gap: 20px;
  margin-top: 12px;
}

.pong-foot .key-row {
  margin-top: 0;
}

.pong-pad {
  grid-template-columns: repeat(2, minmax(0, 1fr));
  width: min(360px, 100%);
}

@media (max-width: 1150px) {
  #pongCanvas {
    width: min(880px, calc(100vw - 48px));
  }
}

@media (max-width: 700px) {
  .pong-target {
    display: none;
  }

  .pong-foot {
    display: none;
  }
}

html.theme-minimal .pong-name {
  font-size: 12px;
  font-weight: 600;
  text-transform: none;
  letter-spacing: 0;
}

html.theme-minimal .pong-score {
  font-size: 26px;
  font-weight: 700;
}

html.theme-minimal .pong-target {
  font-size: 11px;
  letter-spacing: 0.03em;
}

/* ==== air force ===========================================================
 * Vertical shooter on a portrait canvas: the board leads with its height, so
 * the HUD, the sky and the key legend all stay on one screen. Everything on
 * the canvas paints with the shared palette tokens; the wingman's plane
 * borrows the Pong rival color so both themes already cover it.
 */

.af-hud {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: center;
  column-gap: 18px;
  row-gap: 4px;
  width: 100%;
  max-width: 560px;
  margin-bottom: 10px;
}

.af-stat {
  display: flex;
  align-items: baseline;
  gap: 6px;
}

.af-stat .stat-value {
  font-size: 12px;
}

/* Reserved height, so the wingman line appearing never nudges the sky. */
.af-wing {
  min-height: 16px;
  margin: 0 0 8px;
  font-size: 11.5px;
  color: var(--muted);
  text-align: center;
}

.af-wing[hidden] {
  display: block;
  visibility: hidden;
}

.af-well {
  position: relative;
  line-height: 0;
  background: var(--bg);
  border: var(--bw) solid var(--border-strong);
  border-radius: var(--radius);
  box-shadow: var(--shadow-md);
  overflow: hidden;
}

#afCanvas {
  display: block;
  image-rendering: pixelated;
  height: min(640px, calc(100vh - 300px));
  min-height: 360px;
  aspect-ratio: 3 / 4;
  width: auto;
  max-width: calc(100vw - 48px);
}

.af-foot {
  display: flex;
  gap: 20px;
  margin-top: 12px;
}

.af-foot .key-row {
  margin-top: 0;
}

.af-pad {
  grid-template-columns: repeat(4, minmax(0, 1fr));
  width: min(420px, 100%);
}

@media (max-width: 700px) {
  .af-foot {
    display: none;
  }
}

/* ==== bubble shooter ======================================================
 * A portrait field like Air Force, but battles mirror a second board beside
 * yours the way Tetris does. Bubbles paint with the shared piece tokens, so
 * both themes already cover every color.
 */

.bub-hud {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: center;
  column-gap: 18px;
  row-gap: 4px;
  width: 100%;
  max-width: 520px;
  margin-bottom: 10px;
}

.bub-stat {
  display: flex;
  align-items: baseline;
  gap: 6px;
}

.bub-stat .stat-value {
  font-size: 12px;
}

.bub-arena {
  display: flex;
  align-items: flex-start;
  justify-content: center;
  gap: 24px;
}

.bub-col {
  display: flex;
  flex-direction: column;
  align-items: center;
}

.bub-well {
  position: relative;
  line-height: 0;
  background: var(--bg);
  border: var(--bw) solid var(--border-strong);
  border-radius: var(--radius);
  box-shadow: var(--shadow-md);
  overflow: hidden;
}

/* Solo gets the big field; a battle shrinks both so they sit side by side. */
#bubCanvas,
#bubOppCanvas {
  display: block;
  image-rendering: pixelated;
  height: max(min(640px, calc(100dvh - 300px)), 380px);
  aspect-ratio: 27 / 40;
  width: auto;
  max-width: calc(100vw - 48px);
}

.games-view.battle #bubCanvas,
#bubOppCanvas {
  height: max(min(520px, calc(100dvh - 320px)), 320px);
}

.bub-foot {
  display: flex;
  gap: 20px;
  margin-top: 12px;
}

.bub-foot .key-row {
  margin-top: 0;
}

.bub-pad {
  grid-template-columns: repeat(3, minmax(0, 1fr));
  width: min(400px, 100%);
}

@media (max-width: 700px) {
  .bub-foot {
    display: none;
  }
}

/* ==== stack tower =========================================================
 * A portrait tower like the bubble field; battles mirror the opponent's
 * tower beside yours. Blocks cycle through the shared piece tokens, so both
 * themes already cover every color.
 */

.stk-hud {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: center;
  column-gap: 18px;
  row-gap: 4px;
  width: 100%;
  max-width: 520px;
  margin-bottom: 10px;
}

.stk-stat {
  display: flex;
  align-items: baseline;
  gap: 6px;
}

.stk-stat .stat-value {
  font-size: 12px;
}

.stk-arena {
  display: flex;
  align-items: flex-start;
  justify-content: center;
  gap: 24px;
}

.stk-col {
  display: flex;
  flex-direction: column;
  align-items: center;
}

.stk-well {
  position: relative;
  line-height: 0;
  background: var(--bg);
  border: var(--bw) solid var(--border-strong);
  border-radius: var(--radius);
  box-shadow: var(--shadow-md);
  overflow: hidden;
}

/* Solo gets the big tower; a battle shrinks both so they sit side by side. */
#stkCanvas,
#stkOppCanvas {
  display: block;
  image-rendering: pixelated;
  height: max(min(640px, calc(100dvh - 300px)), 380px);
  aspect-ratio: 27 / 40;
  width: auto;
  max-width: calc(100vw - 48px);
}

.games-view.battle #stkCanvas,
#stkOppCanvas {
  height: max(min(520px, calc(100dvh - 320px)), 320px);
}

.stk-foot {
  display: flex;
  gap: 20px;
  margin-top: 12px;
}

.stk-foot .key-row {
  margin-top: 0;
}

.stk-pad {
  grid-template-columns: minmax(0, 1fr);
  width: min(260px, 100%);
}

@media (max-width: 700px) {
  .stk-foot {
    display: none;
  }
}

/* ==== air hockey ==========================================================
 * A portrait table sharing Pong's score-strip classes; only the canvas
 * sizing and the four-way touch pad are its own.
 */

#ahCanvas {
  display: block;
  image-rendering: pixelated;
  height: max(min(680px, calc(100dvh - 260px)), 400px);
  aspect-ratio: 3 / 5;
  width: auto;
  max-width: calc(100vw - 48px);
}

.ah-foot {
  display: flex;
  gap: 20px;
  margin-top: 12px;
}

.ah-foot .key-row {
  margin-top: 0;
}

.ah-pad {
  grid-template-columns: repeat(4, minmax(0, 1fr));
  width: min(420px, 100%);
}

@media (max-width: 700px) {
  .ah-foot {
    display: none;
  }
}

/* ==== terra ===============================================================
 * A landscape sandbox view with a DOM hotbar and health bar; the canvas
 * paints the world with the shared palette tokens.
 */

.tr-hud {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 22px;
  width: 100%;
  max-width: 620px;
  margin-bottom: 8px;
}

.tr-stat {
  display: flex;
  align-items: center;
  gap: 8px;
}

.tr-stat .stat-value {
  font-size: 12px;
}

.tr-hp {
  display: inline-block;
  width: 120px;
  height: 10px;
  background: var(--bg);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
  overflow: hidden;
}

.tr-hp-fill {
  display: block;
  height: 100%;
  width: 100%;
  background: var(--accent);
}

/* Reserved height, so the party line appearing never nudges the world. */
.tr-party {
  min-height: 16px;
  margin: 0 0 6px;
  font-size: 11.5px;
  color: var(--muted);
  text-align: center;
}

.tr-party[hidden] {
  display: block;
  visibility: hidden;
}

.tr-well {
  position: relative;
  line-height: 0;
  background: var(--bg);
  border: var(--bw) solid var(--border-strong);
  border-radius: var(--radius);
  box-shadow: var(--shadow-md);
  overflow: hidden;
}

#trCanvas {
  display: block;
  image-rendering: pixelated;
  width: min(880px, calc(100vw - 340px));
  max-width: 100%;
  height: auto;
  aspect-ratio: 45 / 28;
}

.tr-hotbar {
  display: flex;
  gap: 6px;
  margin-top: 10px;
  flex-wrap: wrap;
  justify-content: center;
}

.tr-slot {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  min-width: 52px;
  padding: 6px 8px;
  font-family: var(--pixel);
  font-size: 8px;
  color: var(--muted);
  background: var(--panel);
  border: var(--bw) solid var(--border-strong);
  border-radius: var(--radius);
  cursor: pointer;
}

.tr-slot b {
  font-weight: 400;
  letter-spacing: 0.05em;
}

.tr-slot span {
  font-size: 9px;
  color: var(--text);
}

.tr-slot.active {
  color: var(--accent);
  border-color: var(--accent);
}

/* Item icons: pixel art canvases in the hotbar and the crafting cards. */
.tr-icon {
  image-rendering: pixelated;
  flex: none;
}

.tr-slot .tr-icon {
  order: -1; /* the picture leads; the label and count sit under it */
}

/* Crafting: a card per recipe, with the item's picture and its costs. */
.tr-craft {
  margin-top: 10px;
  padding: 10px 12px;
  max-width: 780px;
  background: var(--panel);
  border: var(--bw) solid var(--border-strong);
  border-radius: var(--radius);
  box-shadow: var(--shadow-sm);
}

.tr-craft .arcade-label {
  text-align: center;
  margin: 0 0 8px;
}

.tr-recipes {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 8px;
}

.tr-card {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  width: 108px;
  padding: 8px 6px;
  font-family: var(--pixel);
  font-size: 7.5px;
  color: var(--text);
  background: var(--bg);
  border: var(--bw) solid var(--border-strong);
  border-radius: var(--radius);
  cursor: pointer;
}

.tr-card:not(:disabled):hover {
  border-color: var(--accent);
}

.tr-card b {
  font-weight: 400;
  letter-spacing: 0.04em;
  text-align: center;
}

.tr-card:disabled {
  opacity: 0.5;
  cursor: default;
}

.tr-card.owned {
  opacity: 0.6;
  border-color: var(--accent);
}

.tr-cost {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 4px 8px;
}

.tr-ing {
  display: flex;
  align-items: center;
  gap: 3px;
}

.tr-ing i {
  font-family: var(--mono);
  font-style: normal;
  font-size: 10px;
  color: var(--accent);
}

.tr-ing.short i {
  color: var(--bad);
}

.tr-owned {
  position: absolute;
  top: 4px;
  right: 4px;
  font-size: 6px;
  color: var(--accent);
}

.tr-foot {
  display: flex;
  gap: 18px;
  margin-top: 10px;
  flex-wrap: wrap;
  justify-content: center;
}

.tr-foot .key-row {
  margin-top: 0;
}

.tr-pad {
  grid-template-columns: repeat(3, minmax(0, 1fr));
  width: min(360px, 100%);
}

@media (max-width: 1150px) {
  #trCanvas {
    width: min(880px, calc(100vw - 48px));
  }
}

@media (max-width: 700px) {
  .tr-foot {
    display: none;
  }
}

/* ---- pasted images ---- */

.composer.dropping {
  outline: 2px dashed var(--accent);
  outline-offset: -6px;
}

.attachments {
  list-style: none;
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin: 0 0 10px;
  padding: 0;
}

.attach-chip {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 5px 8px 5px 5px;
  border: var(--bw) solid var(--border);
  border-radius: var(--radius);
  background: var(--panel-2);
  max-width: 240px;
}

.attach-chip.uploading {
  opacity: 0.7;
}

.attach-chip img {
  width: 34px;
  height: 34px;
  object-fit: cover;
  flex: none;
  border: 1px solid var(--border-strong);
}

.attach-name {
  font-size: 12px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  min-width: 0;
}

.attach-status {
  font-family: var(--pixel);
  font-size: 7px;
  text-transform: uppercase;
  color: var(--accent);
  flex: none;
  animation: px-blink 1s steps(1) infinite;
}

html.theme-minimal .attach-status {
  font-size: 10px;
}

/* A small square button: the icon carries the meaning, so no label padding. */
.attach-remove {
  padding: 4px;
  flex: none;
  background: transparent;
  border: none;
  box-shadow: none;
  color: var(--muted);
}

.attach-remove:active:not(:disabled) {
  transform: none;
  box-shadow: none;
}

@media (hover: hover) and (pointer: fine) {
  .attach-remove:hover {
    color: var(--bad);
    filter: none;
  }
}

.attach-error {
  margin: 0 0 8px;
}

/* Thumbnails inside a sent message. */
.bubble-images {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-bottom: 8px;
}

.bubble-images img {
  max-width: 220px;
  max-height: 160px;
  border: var(--bw) solid var(--border-strong);
  border-radius: var(--radius);
  cursor: zoom-in;
  display: block;
}

/* A document has no thumbnail, so it reads as a chip that saves on click. */
.attach-file {
  display: flex;
  align-items: center;
  gap: 8px;
  max-width: 240px;
  padding: 8px 10px;
  border: var(--bw) solid var(--border-strong);
  border-radius: var(--radius);
  background: var(--panel-2);
  color: var(--text);
  text-decoration: none;
  font-size: 12px;
}

@media (hover: hover) and (pointer: fine) {
  .attach-file:hover {
    border-color: var(--accent-edge);
  }
}

/* Stands in for the preview thumbnail on a composer chip with no image. */
.attach-icon {
  flex: none;
  color: var(--muted);
}

@media (prefers-reduced-motion: reduce) {
  .attach-status {
    animation: none;
  }
}

/* ---- image lightbox ---- */

.lightbox {
  width: min(1200px, 94vw);
  max-width: none;
  height: min(860px, 92dvh);
  padding: 0;
  display: none;
  flex-direction: column;
  background: var(--panel);
}

.lightbox[open] {
  display: flex;
}

.lightbox::backdrop {
  background: rgb(0 0 0 / 78%);
}

.lightbox-bar {
  display: flex;
  align-items: center;
  gap: 10px;
  flex: none;
  padding: 10px 12px;
  border-bottom: var(--bw) solid var(--border);
  /* A picture taller than the stage scrolls the dialog; the bar, and the
     way out and the download it holds, stay in reach above it. */
  position: sticky;
  top: 0;
  z-index: 1;
  background: var(--panel);
}

.lightbox-count {
  font-family: var(--pixel);
  font-size: 8px;
  text-transform: uppercase;
  color: var(--muted);
}

html.theme-minimal .lightbox-count {
  font-size: 11px;
}

/* A file from the agent is named in the bar; an attached image shows none.
   The name is what gives way when the bar runs short, never the buttons. */
.lightbox-name {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: 12.5px;
}

.lightbox-count,
.lightbox-bar button,
.lightbox-bar .button-link {
  flex: none;
}

/* One arrow glyph, mirrored for "next". */
.lightbox-next-icon {
  transform: scaleX(-1);
}

/* The stage is the click-to-dismiss area; the image itself is not. */
.lightbox-stage {
  flex: 1;
  min-height: 0;
  display: grid;
  place-items: center;
  padding: 12px;
  background: var(--bg);
  cursor: zoom-out;
}

.lightbox-stage img {
  max-width: 100%;
  max-height: 100%;
  object-fit: contain;
  cursor: default;
  /* Checkerboard behind transparent PNGs, so alpha reads as alpha. */
  background-image:
    linear-gradient(45deg, var(--panel-2) 25%, transparent 25%),
    linear-gradient(-45deg, var(--panel-2) 25%, transparent 25%),
    linear-gradient(45deg, transparent 75%, var(--panel-2) 75%),
    linear-gradient(-45deg, transparent 75%, var(--panel-2) 75%);
  background-size: 16px 16px;
  background-position: 0 0, 0 8px, 8px -8px, -8px 0;
}

/* A vector has no pixels to keep 1:1, so it is drawn as large as fits. */
.lightbox-stage img.vector {
  width: 100%;
  height: 100%;
}

/* Stands in for the picture while it is fetched, or when it cannot be shown.
   Not part of the click-to-dismiss area, so its words can be selected. */
.lightbox-status {
  margin: 0;
  max-width: 60ch;
  text-align: center;
  overflow-wrap: anywhere;
  cursor: auto;
}

/* Held back a moment, so a picture already to hand never flashes the label. */
.lightbox-status.loading {
  font-family: var(--pixel);
  font-size: 8px;
  text-transform: uppercase;
  color: var(--accent);
  opacity: 0;
  animation: px-blink 1s steps(1) 300ms infinite;
}

html.theme-minimal .lightbox-status.loading {
  font-size: 11px;
}

.lightbox-status.failed {
  color: var(--bad);
}

@media (prefers-reduced-motion: reduce) {
  /* Still held back, just without the blink. */
  .lightbox-status.loading {
    animation: px-fade 1ms 300ms both;
  }
}

@media (max-width: 860px) {
  .lightbox {
    width: 100vw;
    height: 100dvh;
  }
}

/* A phone's bar has no room for a file name beside its buttons: the name
   drops to a line of its own, and Download keeps only its icon. */
@media (max-width: 700px) {
  .lightbox-bar {
    flex-wrap: wrap;
  }

  .lightbox-name {
    order: 1;
    flex-basis: 100%;
  }

  .lightbox-download-label {
    display: none;
  }
}

/* ---- markdown in the transcript -------------------------------------------
 *
 * The agent answers in Markdown, so assistant bubbles are rendered rather than
 * printed raw. Everything below is written in the theme tokens only, so the
 * minimal theme and the light mode pick it up without a single override: radius
 * and border width come from --radius/--bw, so headings and tables are hard
 * pixel boxes in the pixel theme and soft cards in the minimal one.
 *
 * Body text stays in the monospace stack, the same as the raw transcript did.
 * The pixel face appears only on the code block's two short uppercase labels.
 */

.markdown {
  /* The bubble is pre-wrap for raw text; rendered blocks lay themselves out. */
  white-space: normal;
}

.markdown > :first-child {
  margin-top: 0;
}

.markdown > :last-child {
  margin-bottom: 0;
}

.md-p {
  margin: 0 0 10px;
}

.md-h {
  margin: 18px 0 8px;
  line-height: 1.4;
  font-weight: 700;
}

.md-h1 {
  font-size: 17px;
  color: var(--accent);
  border-bottom: var(--bw) solid var(--border);
  padding-bottom: 6px;
}

.md-h2 {
  font-size: 15.5px;
  border-bottom: var(--bw) solid var(--border);
  padding-bottom: 5px;
}

.md-h3 {
  font-size: 14.5px;
  color: var(--accent);
}

.md-h4 {
  font-size: 13.5px;
}

.md-h5,
.md-h6 {
  font-size: 13px;
  color: var(--muted);
}

.md-hr {
  border: 0;
  border-top: var(--bw) solid var(--border);
  margin: 14px 0;
}

.md-strong {
  font-weight: 700;
  color: var(--text);
}

.md-em {
  font-style: italic;
}

.md-del {
  color: var(--muted);
  text-decoration-thickness: 1px;
}

.md-link {
  color: var(--accent);
  text-decoration: underline;
  text-underline-offset: 2px;
  overflow-wrap: anywhere;
}

.md-link:hover {
  background: var(--accent-soft);
}

/* Inline code carries no border: at 2px it would outweigh the word inside. */
.md-inline-code {
  font-family: var(--mono);
  font-size: 0.94em;
  padding: 1px 4px;
  border-radius: var(--radius);
  background: var(--panel-2);
  color: var(--accent);
  overflow-wrap: anywhere;
}

.md-codeblock {
  margin: 0 0 10px;
  border: var(--bw) solid var(--border);
  border-radius: var(--radius);
  background: var(--panel-2);
  overflow: hidden;
}

.md-codebar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 5px 6px 5px 10px;
  border-bottom: var(--bw) solid var(--border);
  background: var(--panel);
}

.md-lang {
  font-family: var(--pixel);
  font-size: 7px;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--muted);
}

/* Quiet until wanted: a copy button should not compete with the code. */
.md-copy {
  font-size: 7px;
  padding: 5px 7px 4px;
  background: transparent;
  border-color: var(--border);
  color: var(--muted);
  box-shadow: none;
}

.md-copy:hover:not(:disabled) {
  color: var(--accent);
  border-color: var(--accent-edge);
}

.md-copy:active:not(:disabled) {
  transform: translate(1px, 1px);
}

.md-pre {
  margin: 0;
  padding: 10px 12px;
  overflow-x: auto;
  font-family: var(--mono);
  font-size: 12.5px;
  line-height: 1.55;
  /* Code keeps its own columns: it scrolls sideways instead of wrapping. */
  white-space: pre;
}

.md-pre code {
  font: inherit;
}

.md-list {
  margin: 0 0 10px;
  padding-left: 22px;
}

.md-item {
  margin: 2px 0;
}

/* A blank line between items in the source means the author wanted air. */
.md-loose > .md-item {
  margin: 8px 0;
}

.md-item > .md-list,
.md-item > .md-codeblock,
.md-item > .md-tablewrap {
  margin-top: 6px;
}

.md-item > .md-p:last-child {
  margin-bottom: 0;
}

.md-task {
  list-style: none;
  margin-left: -20px;
}

.md-check {
  width: auto;
  padding: 0;
  margin: 0 7px 0 0;
  vertical-align: -1px;
  accent-color: var(--accent);
}

.md-quote {
  margin: 0 0 10px;
  padding: 2px 0 2px 12px;
  border-left: 3px solid var(--accent-edge);
  color: var(--muted);
}

.md-quote > :last-child {
  margin-bottom: 0;
}

/* Wide tables scroll inside the bubble rather than stretching the transcript. */
.md-tablewrap {
  margin: 0 0 10px;
  overflow-x: auto;
  border: var(--bw) solid var(--border);
  border-radius: var(--radius);
}

.md-table {
  border-collapse: collapse;
  width: 100%;
  font-size: 12.5px;
}

.md-cell {
  padding: 6px 10px;
  text-align: left;
  vertical-align: top;
  border-bottom: var(--bw) solid var(--border);
}

.md-table th.md-cell {
  background: var(--panel);
  color: var(--accent);
  font-weight: 700;
  white-space: nowrap;
}

.md-table tbody tr:last-child .md-cell {
  border-bottom: 0;
}

.md-table tbody tr:nth-child(even) {
  background: var(--panel-2);
}

.md-left { text-align: left; }
.md-center { text-align: center; }
.md-right { text-align: right; }

/* ==== errors tab ===========================================================
 * The error watch's findings (errors.js): a list of errors on the left, the
 * one picked on the right, the sites being watched along the bottom. Each
 * pane scrolls on its own; the page itself never does, like the console.
 */

.errors-view {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
  background: var(--bg);
}

.errors-bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 12px 20px;
  padding: 14px 18px 12px;
  border-bottom: var(--bw) solid var(--border);
  background: var(--panel);
}

.errors-heading h2 {
  display: flex;
  align-items: center;
  gap: 8px;
  font-family: var(--pixel);
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: 0.06em;
}

.errors-heading p {
  margin: 7px 0 0;
}

.errors-controls,
.errors-auto,
.errors-filters,
.errors-actions,
.errors-detail-tags {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
}

.errors-controls {
  gap: 12px;
}

.errors-auto {
  gap: 8px;
}

.errors-auto-label {
  font-family: var(--pixel);
  font-size: 8px;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--muted);
}

.errors-notice {
  margin: 0;
  padding: 8px 18px;
  border-bottom: var(--bw) solid var(--border);
  background: var(--panel-2);
}

.errors-notice.warn {
  color: var(--warn);
}

.errors-filters {
  gap: 10px 14px;
  padding: 10px 18px;
  border-bottom: var(--bw) solid var(--border);
}

.errors-search {
  flex: 1 1 260px;
  max-width: 420px;
}

.errors-search input {
  padding: 6px 10px;
  font-size: 12.5px;
}

/* A multi-select filter: a toggle naming the pick, over a menu of ticks.
   Nothing ticked means no filter; each count is what that option would show
   with the other filters as they are. */
.ms {
  position: relative;
}

.ms-toggle {
  gap: 7px;
  padding: 7px 9px 6px;
  font-size: 8px;
  background: var(--panel-2);
  color: var(--text);
  border-color: var(--border-strong);
  box-shadow: var(--shadow-sm);
}

.ms-toggle:active:not(:disabled) {
  transform: translate(2px, 2px);
  box-shadow: 0 0 0 var(--shadow);
}

.ms-name {
  color: var(--muted);
}

.ms-toggle.filtered {
  border-color: var(--accent-edge);
  background: var(--accent-soft);
}

.ms-toggle.filtered .ms-value {
  color: var(--accent);
}

.ms-toggle .caret {
  color: var(--muted);
}

.ms-toggle[aria-expanded="true"] .caret {
  transform: rotate(180deg);
}

.ms-menu {
  position: absolute;
  top: calc(100% + 6px);
  left: 0;
  z-index: 30;
  min-width: 230px;
  max-width: calc(100vw - 16px);
  max-height: min(380px, 60vh);
  overflow-y: auto;
  padding: 8px;
  background: var(--panel);
  border: var(--bw) solid var(--border-strong);
  border-radius: var(--radius);
  box-shadow: var(--shadow-md);
}

.ms-presets {
  display: flex;
  gap: 6px;
  margin: 0 0 6px;
  padding: 0 0 8px;
  border-bottom: 1px solid var(--border);
}

.ms-preset {
  padding: 5px 8px 4px;
  font-size: 8px;
  background: var(--panel-2);
  color: var(--text);
  border: 1px solid var(--border-strong);
  box-shadow: none;
}

.ms-preset:active:not(:disabled) {
  transform: none;
  box-shadow: none;
}

.ms-options {
  display: flex;
  flex-direction: column;
}

.ms-option {
  display: flex;
  align-items: center;
  gap: 9px;
  padding: 6px;
  font-size: 12.5px;
  border-radius: var(--radius);
  cursor: pointer;
}

.ms-option:hover {
  background: var(--panel-2);
}

/* The global input rule makes fields full width; a tick box is not one. */
.ms-option input {
  flex: none;
  width: 14px;
  height: 14px;
  margin: 0;
  padding: 0;
  accent-color: var(--accent);
}

.ms-label {
  flex: 1;
}

.ms-count {
  color: var(--muted);
  font-size: 11.5px;
}

/* Would show nothing with the other filters as they are. */
.ms-option.none {
  opacity: 0.5;
}

.sev-text.sev-critical,
.sev-text.sev-high {
  color: var(--bad);
}

.sev-text.sev-medium {
  color: var(--warn);
}

.errors-sort {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-left: auto;
}

.errors-sort-label {
  margin-right: 6px;
  font-family: var(--pixel);
  font-size: 8px;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--muted);
}

.errors-result {
  margin: 0;
  padding: 7px 18px;
  border-bottom: var(--bw) solid var(--border);
}

/* Reads as a link inside the result line, not as one more button. */
.errors-clear {
  display: inline;
  padding: 0;
  font: inherit;
  text-transform: none;
  letter-spacing: 0;
  text-decoration: underline;
  color: var(--accent);
  background: none;
  border: none;
  box-shadow: none;
}

.errors-clear:active:not(:disabled) {
  transform: none;
  box-shadow: none;
}

.errors-panes {
  flex: 1;
  min-height: 0;
  display: grid;
  grid-template-columns: minmax(300px, 42%) minmax(0, 1fr);
}

.errors-list {
  list-style: none;
  margin: 0;
  padding: 10px;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: 7px;
  border-right: var(--bw) solid var(--border);
  background: var(--panel);
}

/* A row is a button for the keyboard, but reads as a card, not a control. */
.errors-row {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 6px;
  width: 100%;
  text-align: left;
  padding: 10px 11px 9px;
  font: 12.5px/1.4 var(--mono);
  text-transform: none;
  letter-spacing: 0;
  color: var(--text);
  background: var(--panel-2);
  border: var(--bw) solid var(--border);
  box-shadow: none;
}

.errors-row:active:not(:disabled) {
  transform: none;
  box-shadow: none;
}

.errors-row.prod {
  border-left-color: var(--prod);
}

.errors-row.selected {
  border-color: var(--accent);
  background: var(--accent-soft);
}

.errors-row-top,
.errors-row-meta {
  display: flex;
  align-items: center;
  gap: 8px;
  min-width: 0;
}

.errors-row-meta {
  flex-wrap: wrap;
  font-size: 11.5px;
}

.errors-row-title {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-weight: 600;
}

.errors-row-count {
  flex: none;
  color: var(--muted);
  font-size: 11.5px;
}

.errors-row-summary {
  font-size: 12px;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.errors-empty {
  padding: 22px 10px;
  text-align: center;
}

.errors-detail {
  min-width: 0;
  overflow-y: auto;
  padding: 18px 22px 28px;
}

.errors-detail-empty {
  max-width: 440px;
  margin: 40px auto;
  text-align: center;
}

.errors-detail-empty h2 {
  margin: 14px 0 8px;
  font-family: var(--pixel);
  font-size: 10px;
  text-transform: uppercase;
}

.errors-detail-head h3 {
  margin: 0 0 9px;
  font-size: 15px;
  font-weight: 600;
  line-height: 1.4;
  overflow-wrap: anywhere;
}

.errors-detail-head p {
  margin: 7px 0 0;
}

.errors-detail-tags {
  gap: 6px;
}

.errors-actions {
  gap: 8px;
  margin: 14px 0 16px;
}

.errors-progress {
  margin: 0 0 14px;
  padding: 8px 10px;
  border: 1px dashed var(--accent-edge);
  color: var(--accent);
}

.errors-failure {
  margin: 0 0 14px;
  padding: 8px 10px;
  border: 1px solid var(--bad);
}

.errors-flagged {
  margin: 0 0 16px;
  padding: 10px 12px;
  border: var(--bw) solid var(--warn);
  background: var(--panel-2);
}

.errors-flagged p {
  margin: 0 0 6px;
}

.errors-flagged-title {
  display: flex;
  align-items: center;
  gap: 8px;
  color: var(--warn);
  font-weight: 600;
}

.errors-flagged ul {
  list-style: none;
  margin: 10px 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 7px;
}

.errors-flagged li {
  display: flex;
  align-items: baseline;
  gap: 8px;
}

.errors-flagged .tag {
  flex: none;
}

.errors-flagged code {
  font: 12px/1.45 var(--mono);
  overflow-wrap: anywhere;
}

.errors-summary {
  margin: 0 0 16px;
  padding-left: 11px;
  border-left: 3px solid var(--accent);
  font-size: 14px;
  line-height: 1.5;
}

.errors-section {
  margin: 0 0 16px;
}

.errors-section h4 {
  margin: 0 0 7px;
  font-family: var(--pixel);
  font-size: 8px;
  font-weight: 400;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--muted);
}

.errors-facts {
  display: grid;
  grid-template-columns: max-content minmax(0, 1fr);
  gap: 5px 16px;
  margin: 16px 0;
}

.errors-facts dt {
  color: var(--muted);
}

.errors-facts dd {
  margin: 0;
  overflow-wrap: anywhere;
}

.errors-traceback {
  border: var(--bw) solid var(--border);
  background: var(--panel);
}

.errors-traceback summary {
  cursor: pointer;
  padding: 9px 11px 8px;
  font-family: var(--pixel);
  font-size: 8px;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--muted);
}

.errors-traceback pre {
  margin: 0;
  padding: 10px 12px;
  max-height: 440px;
  overflow: auto;
  border-top: 1px solid var(--border);
  font: 12px/1.45 var(--mono);
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

.errors-sites {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 18px;
  padding: 8px 18px;
  border-top: var(--bw) solid var(--border);
  background: var(--panel);
  font-size: 11.5px;
}

.errors-site {
  display: inline-flex;
  align-items: center;
  gap: 6px;
}

/* Severity and state tags, shared by the list and the detail. */
.tag.sev-critical {
  background: var(--bad);
  color: var(--bg);
  border-color: var(--bad);
}

.tag.sev-high {
  background: transparent;
  color: var(--bad);
  border-color: var(--bad);
}

.tag.sev-medium {
  background: transparent;
  color: var(--warn);
  border-color: var(--warn);
}

.tag.sev-none {
  border-style: dashed;
}

.tag.status-analysed,
.tag.status-resolved {
  color: var(--accent);
  border-color: var(--accent-edge);
}

.tag.status-failed {
  color: var(--bad);
  border-color: var(--bad);
}

/* The Errors tab's count: open critical and high errors. */
.tab-count {
  background: var(--bad);
  border-color: var(--bad);
  color: var(--bg);
}

/*
 * Under 860px the panes stack and the view scrolls as one column, as the
 * Games view does: a capped list, then the picked error at full height.
 */
@media (max-width: 860px) {
  .errors-view {
    overflow-y: auto;
  }

  .errors-panes {
    flex: none;
    display: flex;
    flex-direction: column;
  }

  .errors-list {
    flex: none;
    max-height: 42vh;
    border-right: none;
    border-bottom: var(--bw) solid var(--border);
  }

  .errors-detail {
    overflow: visible;
    padding: 14px 16px 22px;
  }

  .errors-bar,
  .errors-filters,
  .errors-notice,
  .errors-result,
  .errors-sites {
    padding-left: 16px;
    padding-right: 16px;
  }

  .errors-search {
    flex-basis: 100%;
    max-width: none;
  }

  .errors-sort {
    margin-left: 0;
  }
}

/* ---- minimal theme (errors) ---- */

html.theme-minimal .errors-heading h2,
html.theme-minimal .errors-detail-empty h2 {
  font-size: 15px;
  font-weight: 600;
  text-transform: none;
  letter-spacing: 0;
}

html.theme-minimal .errors-auto-label,
html.theme-minimal .errors-section h4,
html.theme-minimal .errors-traceback summary,
html.theme-minimal .errors-sort-label,
html.theme-minimal .ms-toggle,
html.theme-minimal .ms-preset {
  font-size: 11px;
  font-weight: 600;
  text-transform: none;
  letter-spacing: 0;
}

html.theme-minimal .errors-row,
html.theme-minimal .errors-flagged,
html.theme-minimal .errors-traceback,
html.theme-minimal .errors-progress,
html.theme-minimal .errors-failure {
  border-radius: var(--radius);
}

/* ---- responsive shell: leave room for the conversation on phones ---- */
.layout { grid-template-columns: 300px minmax(0, 1fr); }
.layout, .sidebar, .transcript, .composer, .errors-view, .games-view { min-width: 0; min-height: 0; }
.composer, .chat-header { flex-shrink: 0; }
.composer-options { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; }
.mobile-navigation, .mobile-navigation-close, .mobile-navigation-backdrop, #composerOptionsToggle { display: none; }
.bubble { flex-shrink: 0; min-width: 0; }
.composer textarea { max-height: min(240px, 28dvh); }
dialog { max-height: calc(100dvh - 32px); overflow-y: auto; overscroll-behavior: contain; }
.auth-card, .setting-row, .signin-step, .errors-detail, .changes-body { min-width: 0; overflow-wrap: anywhere; }
.centred { min-height: 100dvh; height: auto; overflow-y: auto; }
:where(button, a, input, select, textarea) { touch-action: manipulation; }

@media (max-width: 1100px) and (min-width: 861px) {
  .layout { grid-template-columns: 250px minmax(0, 1fr); }
  .topbar { gap: 10px; padding: 10px 12px; }
  .badge { display: none; }
  .chat-header { flex-wrap: wrap; }
  .chat-header-actions { justify-content: flex-start; }
}

@media (max-width: 860px) {
  body:not(.centred) { height: var(--app-height, 100dvh); }
  body { padding-left: env(safe-area-inset-left); padding-right: env(safe-area-inset-right); }
  .topbar { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 6px 10px; padding: max(6px, env(safe-area-inset-top)) 12px 6px; }
  .brand { min-width: 0; }
  .brand > span:not(.brand-mark) { display: none; }
  .topbar-right { gap: 8px; min-width: 0; }
  #whoami { max-width: 140px; font-size: 12px; }
  .view-tabs { grid-column: 1 / -1; grid-row: 2; display: flex; width: 100%; }
  .view-tab { flex: 1; min-width: 0; min-height: 40px; padding: 8px; }
  .view-tab-text { display: inline; font-size: 9px; }
  .layout { position: relative; isolation: isolate; }
  .mobile-navigation { flex: none; display: flex; align-items: center; justify-content: space-between; gap: 8px; min-height: 50px; padding: 4px 12px; border-bottom: var(--bw) solid var(--border); background: var(--panel); }
  .mobile-navigation > span { white-space: nowrap; font-size: 11px; }
  .mobile-navigation-backdrop:not([hidden]) { display: block; position: absolute; inset: 50px 0 0; width: 100%; border: 0; border-radius: 0; padding: 0; box-shadow: none; transform: none; background: rgb(0 0 0 / 55%); z-index: 39; }
  .sidebar { display: none; }
  .navigation-open .sidebar { display: flex; flex-direction: column; position: absolute; z-index: 40; top: 50px; bottom: 0; left: 0; width: min(360px, calc(100% - 32px)); overflow-y: auto; overscroll-behavior: contain; border-right: var(--bw) solid var(--border-strong); background: var(--panel); box-shadow: var(--shadow-lg); }
  .mobile-navigation-close { display: inline-flex; align-self: flex-end; margin: 10px 12px 0; }
  .sidebar-section.grow { min-height: 0; flex: 1; }
  .session-list { max-height: none; overflow-y: auto; }
  .server-list { max-height: min(340px, 45dvh); }
  .chat-header { display: block; padding: 8px 12px; }
  .chat-header > div:first-child { min-width: 0; }
  .chat-header h2 { font-size: 9px; -webkit-line-clamp: 1; }
  .chat-header p { font-size: 11px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .chat-header-actions { flex-wrap: nowrap; justify-content: flex-start; overflow-x: auto; gap: 8px; margin-top: 8px; padding: 0 3px 4px; scrollbar-width: thin; }
  .chat-header-actions > button { flex: none; white-space: nowrap; }
  .transcript { padding: 12px; overscroll-behavior-y: contain; }
  .bubble { max-width: 100%; padding: 10px 12px; }
  .bubble.user { max-width: 94%; }
  .md-tablewrap, .md-codeblock, .md-pre { max-width: 100%; }
  .composer { padding: 8px 12px max(10px, env(safe-area-inset-bottom)); }
  .composer textarea { min-height: 64px; height: 74px; max-height: min(160px, 25dvh); resize: none; }
  .composer-options { display: none; }
  .composer-options.expanded { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); margin-top: 8px; gap: 8px; max-height: 26dvh; overflow-y: auto; }
  .composer-options .model-picker { min-width: 0; width: 100%; }
  .composer-options .model-picker select { width: 100%; max-width: none; min-width: 0; }
  .composer-options .effort-slider { min-width: 0; width: auto; }
  .composer-actions { flex-wrap: nowrap; gap: 8px; margin-top: 8px; }
  #composerOptionsToggle { display: inline-flex; }
  #send { margin-left: auto; flex-shrink: 0; min-width: 72px; }
  #composerHint, .composer-actions .spacer { display: none; }
  button, button.small, .seg-option, a.button-link.small { min-height: 44px; }
  input:not([type=range]), select, textarea, .model-picker select { font-size: 16px; }
  input[type=range] { min-height: 32px; }
  dialog { width: calc(100% - 24px); max-width: calc(100% - 24px); max-height: calc(var(--app-height, 100dvh) - 24px); padding: 16px; margin: auto; }
  .dialog-actions { flex-wrap: wrap; gap: 8px; }
  .dialog-actions > button { flex: 1 1 auto; }
  .setting-row { flex-wrap: wrap; gap: 8px; }
  .setting-row > .seg { max-width: 100%; }
  .seg-option { padding-inline: 10px; }
  .changes-dialog, .signin-dialog { width: calc(100% - 24px); height: calc(var(--app-height, 100dvh) - 24px); }
  .changes-head, .changes-actions { flex-wrap: wrap; }
  .terminal-dialog { width: 100%; max-width: 100%; height: var(--app-height, 100dvh); max-height: var(--app-height, 100dvh); margin: 0; padding: 0; }
  .terminal-head { padding-top: max(8px, env(safe-area-inset-top)); gap: 8px; }
  .terminal-hint { padding-bottom: max(8px, env(safe-area-inset-bottom)); }
  .errors-bar { flex-wrap: wrap; gap: 12px; }
  .errors-controls { width: 100%; justify-content: space-between; gap: 8px; }
  .errors-filters { gap: 8px; }
  .errors-filters > .ms { flex: 1 1 calc(50% - 8px); min-width: 0; }
  .ms-toggle { width: 100%; min-width: 0; }
  .ms-menu { min-width: min(230px, calc(100vw - 40px)); max-width: calc(100vw - 40px); }
  .errors-filters > .ms:nth-of-type(even) .ms-menu { left: auto; right: 0; }
  .errors-sort { width: 100%; }
  .errors-sort .model-picker { flex: 1; min-width: 0; }
  .errors-sort select { width: 100%; max-width: none; }
  .errors-list { max-height: 35dvh; }
  .errors-detail { padding: 12px; }
  .errors-traceback pre { max-width: 100%; overflow-x: auto; }
  .games-view { padding-bottom: env(safe-area-inset-bottom); }
  .game-select { padding: 16px 12px; }
  .cart-shelf { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px; }
  .keyboard-open .topbar, .keyboard-open .chat-header-actions { display: none; }
  .keyboard-open .composer-options.expanded { max-height: 100px; }
  .centred { padding: max(16px, env(safe-area-inset-top)) 16px max(16px, env(safe-area-inset-bottom)); }
  .auth-card { padding: 22px; }
}

@media (max-width: 380px) {
  #whoami { max-width: 118px; }
  .composer-actions { gap: 6px; }
  .composer-actions button.small, #send { padding-inline: 8px; font-size: 8px; gap: 5px; }
  .mobile-navigation { padding-inline: 10px; }
  .mobile-navigation > span { display: none; }
  .auth-card { padding: 18px; }
  .auth-card h1 { font-size: 12px; }
  .cart-shelf { grid-template-columns: minmax(0, 1fr); }
}

@media (max-width: 860px) and (max-height: 500px) {
  .topbar { display: flex; padding-block: 4px; }
  .topbar .brand, .topbar #whoami { display: none; }
  .view-tabs { width: auto; }
  .chat-header { padding-block: 4px; }
  .chat-header-actions { display: none; }
  .composer textarea { height: 54px; min-height: 44px; }
  .composer { padding-block: 6px; }
}

#chatActionsToggle { display: none; }
.access-add input { min-width: 0; }
@media (max-width: 860px) {
  .chat-header { display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: center; gap: 6px 8px; }
  #chatActionsToggle { display: inline-flex; }
  .chat-header-actions { display: none; grid-column: 1 / -1; margin-top: 0; }
  .chat-header-actions.expanded { display: flex; }
  .agent-row { flex-wrap: wrap; }
  .agent-row-text { flex-basis: 140px; }
  .signin-paste input { min-width: 0; font-size: 16px; }
  .cart-shelf { display: grid; }
  .cartridge { width: 100%; min-width: 0; padding: 16px 12px; }
  .cart-name { overflow-wrap: anywhere; }
  .keyboard-open .chat-header-actions { display: none; }
}

@media (max-width: 860px) {
  .board-overlay { justify-content: flex-start; overflow-y: auto; overscroll-behavior: contain; padding: 14px; gap: 10px; }
  .board-overlay > * { flex-shrink: 0; max-width: 100%; }
  .board-overlay > :first-child { margin-top: auto; }
  .board-overlay > :last-child { margin-bottom: auto; }
  /* Menu copy needs readable width; the playing field keeps its compact fit. */
  .board-well:has(.board-overlay:not([hidden])) #boardCanvas { width: min(240px, calc(100vw - 64px)); height: auto; }
}
