:root {
  --bg: #15171c;
  --panel: #1d2027;
  --panel2: #242832;
  --line: #2f3440;
  --text: #e8e6e1;
  --muted: #9aa0ab;
  --green: #3ddc84;
  --amber: #f5b342;
  --red: #ff5d5d;
  --blue: #5ea8ff;
  --grey: #7b8290;
  --accent: #7c6cff;
}
* {
  box-sizing: border-box;
}
html,
body {
  margin: 0;
  height: 100%;
  background: var(--bg);
  color: var(--text);
  font:
    14px / 1.4 -apple-system,
    BlinkMacSystemFont,
    "Segoe UI",
    Inter,
    Roboto,
    sans-serif;
}
header {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 10px 16px;
  /* home-screen app: the status bar is translucent, keep the bar clear of the notch */
  padding-top: calc(10px + env(safe-area-inset-top));
  padding-left: calc(16px + env(safe-area-inset-left));
  padding-right: calc(16px + env(safe-area-inset-right));
  border-bottom: 1px solid var(--line);
  background: var(--panel);
}
header h1 {
  font-size: 16px;
  margin: 0;
  letter-spacing: 0.3px;
}
header .stats {
  color: var(--muted);
  font-size: 13px;
}
header .stats b {
  color: var(--text);
  font-weight: 600;
}
.spacer {
  flex: 1;
}
.btn {
  background: var(--panel2);
  border: 1px solid var(--line);
  color: var(--text);
  padding: 6px 10px;
  border-radius: 8px;
  font-size: 13px;
  cursor: pointer;
}
.btn:hover {
  border-color: #4a5160;
}
.btn.danger {
  border-color: #5a2f2f;
  color: #ffb3b3;
}
/* Header "⋯" menu: rarely-used room ops, kept out of the main row. */
.more {
  position: relative;
}
.more-toggle {
  color: var(--muted);
  padding: 6px 8px;
}
.more-menu {
  position: absolute;
  right: 0;
  top: calc(100% + 4px);
  z-index: 50;
  min-width: 180px;
  background: var(--panel2);
  border: 1px solid var(--line);
  border-radius: 8px;
  padding: 4px;
}
.more-menu[hidden] {
  display: none;
}
.more-menu button {
  display: block;
  width: 100%;
  text-align: left;
  background: none;
  border: 0;
  color: var(--text);
  padding: 6px 10px;
  border-radius: 6px;
  font-size: 13px;
  cursor: pointer;
  white-space: nowrap;
}
.more-menu button:hover {
  background: var(--panel);
}
.more-menu button.danger-hover:hover {
  color: #ffb3b3;
}
.prompt {
  display: flex;
  gap: 6px;
  min-width: 340px;
}
.prompt input {
  flex: 1;
  background: #12141a;
  border: 1px solid var(--line);
  color: var(--text);
  padding: 6px 10px;
  border-radius: 8px;
  font-size: 13px;
}
.prompt .btn {
  background: var(--accent);
  border-color: var(--accent);
  color: #fff;
}
.pane .prompt {
  min-width: 0;
}
main {
  display: grid;
  grid-template-columns: 1fr 340px;
  height: calc(100% - 53px);
}
.mapwrap {
  padding: 16px;
  overflow: auto;
  display: flex;
  flex-direction: column;
  gap: 10px;
  min-height: 0;
}
/* The map is sized by the space it has, not by a fixed cap. It is a portrait
   aspect, so height is the binding constraint on most displays; a 700px cap
   threw away half the available size on a large monitor or a TV, and
   with it the detail of everything drawn small - the monitor screens most of
   all, since one is only 7% of the map's width. */
.stage {
  position: relative;
  flex: 1 1 auto;
  min-height: 0;
  height: 100%;
  width: auto;
  max-width: 100%;
  max-height: 100%;
  margin: 0 auto;
  aspect-ratio: 336 / 394;
  user-select: none;
}
canvas {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  image-rendering: pixelated;
  image-rendering: crisp-edges;
  border-radius: 6px;
  cursor: default;
}
.ov {
  position: absolute;
  transform: translate(-50%, -100%);
  white-space: nowrap;
  pointer-events: none;
}
.tag {
  background: #1b1d22e6;
  color: #fff;
  border-radius: 10px;
  padding: 3px 9px 3px 7px;
  font-size: 12px;
  font-weight: 600;
  display: flex;
  align-items: center;
  gap: 6px;
  box-shadow: 0 1px 2px #0006;
  pointer-events: auto;
  cursor: pointer;
}
.tag .dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--grey);
  flex: none;
}
.tag .game {
  color: #c9cdd6;
  font-weight: 500;
}
.tag.you {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
}
.desk {
  background: #f4f1ea;
  color: #3a3d45;
  border-radius: 8px;
  padding: 2px 7px;
  font-size: 11px;
  font-weight: 600;
  box-shadow: 0 1px 2px #0004;
  pointer-events: auto;
  cursor: pointer;
}
/* On the case: an invisible hit target over the PC's number, which the map
 * canvas draws as pixel digits (numberPlate in room.js). */
.desk:focus-visible,
.tag:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
.desk.on-box {
  background: transparent;
  color: transparent;
  border: 0;
  border-radius: 0;
  padding: 0;
  min-width: 0;
  text-shadow: none;
  box-shadow: none;
}
.badge {
  background: #eef3ff;
  color: #2a3f7a;
  border: 1px solid #b9c8f2;
  border-radius: 8px;
  padding: 2px 7px;
  font-size: 11px;
  font-weight: 600;
  display: flex;
  gap: 5px;
  align-items: center;
  box-shadow: 0 1px 2px #0004;
}
.bubble {
  background: #fff;
  color: #1b1d22;
  border-radius: 10px;
  padding: 5px 9px;
  font-size: 12px;
  font-weight: 600;
  box-shadow: 0 2px 6px #0005;
  transition: opacity 0.4s;
}
.bubble:after {
  content: "";
  position: absolute;
  left: 50%;
  bottom: -6px;
  transform: translateX(-50%);
  border: 6px solid transparent;
  border-top-color: #fff;
  border-bottom: 0;
}
.bubble b {
  color: var(--accent);
}
.zzz {
  color: #fff;
  font-weight: 800;
  font-size: 13px;
  text-shadow: 0 1px 2px #000a;
  animation: float 2s ease-in-out infinite;
}
.sign {
  background: #3b2a1f;
  color: #f7e6c8;
  border: 2px solid #6b4a33;
  border-radius: 4px;
  padding: 3px 8px;
  font-size: 11px;
  font-weight: 600;
  transform: translate(-50%, 0);
  line-height: 1.25;
  box-shadow:
    inset 0 0 0 1px #21160f,
    0 2px 0 #21160f;
  pointer-events: auto;
  cursor: pointer;
}
.ov.onair {
  pointer-events: auto;
  transform: translate(0, -50%);
  font: 700 10px/1 monospace;
  letter-spacing: 0;
  padding: 3px 4px;
  border: 2px solid #3a3d45;
  background: #1b1d22;
  color: #6b6f78;
  text-decoration: none;
  display: flex;
  align-items: center;
  gap: 3px;
  image-rendering: pixelated;
}
.ov.onair:focus-visible {
  outline: 2px solid #7c6cff;
  outline-offset: 2px;
}
.ov.onair.live {
  color: #fff;
  background: #b3161b;
  border-color: #ff4d4d;
  box-shadow: 0 0 8px 2px #ff2d2d99;
  animation: onair-pulse 2s ease-in-out infinite;
}
.ov.onair i {
  width: 5px;
  height: 5px;
  background: #fff;
}
@keyframes onair-pulse {
  50% { box-shadow: 0 0 14px 4px #ff2d2dcc; }
}
.ov.rec {
  width: 6px;
  height: 6px;
  background: #ff4d4d;
  box-shadow: 0 0 4px #ff2d2d;
  animation: rec-blink 1s steps(1) infinite;
}
@keyframes rec-blink {
  50% { background: #7a1f1f; box-shadow: none; }
}
@media (prefers-reduced-motion: reduce) {
  .ov.onair.live,
  .ov.rec {
    animation: none;
  }
}
.signs {
  display: flex;
  gap: 6px;
}
.signs .sign {
  transform: none;
}
.ov.ac-climate {
  font: 700 10px/1 ui-monospace, SFMono-Regular, Menlo, monospace;
  color: #e8f4ff;
  background: #0b0d12e6;
  border: 1px solid #4aa8ff;
  border-radius: 3px;
  padding: 2px 4px;
}
.ov.ac-climate small {
  font-size: 8px;
  margin-left: 3px;
  opacity: 0.75;
}
.ov.ac-climate.stale {
  opacity: 0.45;
  filter: grayscale(1);
}
.sign small {
  display: block;
  color: #d9b98c;
  font-weight: 700;
  letter-spacing: 0.6px;
  font-size: 9px;
}
@keyframes pop {
  from {
    transform: translate(-50%, -90%) scale(0.8);
    opacity: 0;
  }
  to {
    transform: translate(-50%, -100%) scale(1);
    opacity: 1;
  }
}
@keyframes float {
  0%,
  100% {
    transform: translate(-50%, -100%);
  }
  50% {
    transform: translate(-50%, -130%);
  }
}
aside {
  border-left: 1px solid var(--line);
  background: var(--panel);
  display: flex;
  flex-direction: column;
  min-height: 0;
}
.tabs {
  display: flex;
  border-bottom: 1px solid var(--line);
}
.tabs button {
  flex: 1;
  background: none;
  border: 0;
  color: var(--muted);
  padding: 10px 0;
  font-size: 13px;
  cursor: pointer;
  border-bottom: 2px solid transparent;
}
.tabs button.on {
  color: var(--text);
  border-bottom-color: var(--accent);
}
.tabs button[data-t='device'] {
  max-width: 9em;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.pane {
  display: none;
  padding: 12px 14px;
  overflow: auto;
  flex: 1;
}
.pane.on {
  display: block;
}
.feed li {
  list-style: none;
  padding: 7px 0;
  border-bottom: 1px solid var(--line);
  font-size: 13px;
  display: flex;
  gap: 8px;
}
.feed ul {
  margin: 0;
  padding: 0;
}
.feed time {
  color: var(--muted);
  font-variant-numeric: tabular-nums;
  flex: none;
  font-size: 12px;
  padding-top: 1px;
}
.feed .k {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  flex: none;
  margin-top: 6px;
}
h3 {
  margin: 0 0 8px;
  font-size: 14px;
}
.card h2 {
  margin: 0;
  font-size: 18px;
  display: flex;
  gap: 8px;
  align-items: center;
}
.card .sub {
  color: var(--muted);
  font-size: 12px;
  margin-bottom: 10px;
}
.kv {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 4px 12px;
  font-size: 13px;
  margin: 8px 0;
}
.kv div:nth-child(odd) {
  color: var(--muted);
}
.gauge {
  height: 8px;
  background: #12141a;
  border-radius: 4px;
  overflow: hidden;
  margin: 2px 0 6px;
}
.gauge i {
  display: block;
  height: 100%;
  background: var(--blue);
}
.row {
  display: flex;
  gap: 6px;
  flex-wrap: wrap;
  margin: 8px 0;
}
.chip {
  background: var(--panel2);
  border: 1px solid var(--line);
  border-radius: 8px;
  padding: 4px 8px;
  font-size: 12px;
}
.chip.red {
  border-color: #5a2f2f;
  color: #ffb3b3;
}
.chip.blue {
  border-color: #2f4b7a;
  color: #bcd2ff;
}
.games li {
  list-style: none;
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 6px 0;
  border-bottom: 1px solid var(--line);
  font-size: 13px;
}
.games ul {
  margin: 0;
  padding: 0;
}
.prank .targets {
  display: flex;
  gap: 6px;
  flex-wrap: wrap;
  margin-bottom: 10px;
}
.prank .targets button {
  background: var(--panel2);
  border: 1px solid var(--line);
  color: var(--text);
  border-radius: 8px;
  padding: 5px 9px;
  font-size: 12px;
  cursor: pointer;
}
.prank .targets button.on {
  border-color: var(--accent);
  background: #2a2650;
}
.prank .targets button:disabled {
  opacity: 0.4;
  cursor: not-allowed;
}
.pr {
  display: grid;
  grid-template-columns: 1fr auto auto;
  gap: 6px 10px;
  align-items: center;
  padding: 8px 0;
  border-bottom: 1px solid var(--line);
  font-size: 13px;
}
.pr small {
  display: block;
  color: var(--muted);
  font-size: 11px;
}
.pr .cost {
  color: var(--amber);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}
.pr .btn {
  padding: 4px 9px;
}
.pr .btn:disabled {
  opacity: 0.4;
  cursor: not-allowed;
}
.balance {
  display: flex;
  justify-content: space-between;
  align-items: center;
  background: #12141a;
  border: 1px solid var(--line);
  border-radius: 8px;
  padding: 8px 10px;
  margin-bottom: 10px;
  font-size: 13px;
}
.balance b {
  color: var(--amber);
  font-size: 16px;
  font-variant-numeric: tabular-nums;
}
.note {
  color: var(--muted);
  font-size: 12px;
  margin-top: 10px;
}
@media (max-width: 980px) {
  main {
    grid-template-columns: 1fr;
    height: auto;
  }
  aside {
    border-left: 0;
    border-top: 1px solid var(--line);
  }
  .prompt {
    min-width: 0;
    width: 100%;
  }
  header {
    flex-wrap: wrap;
  }
}

/* ---- added for the live app (the mockup had no server-driven panels) ---- */
header .stats.bad,
.bad {
  color: var(--red);
}
.grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 8px;
  margin: 10px 0;
}
.grid div {
  background: var(--panel2);
  border: 1px solid var(--line);
  border-radius: 8px;
  padding: 6px 8px;
}
.grid b {
  display: block;
  font-size: 14px;
  font-variant-numeric: tabular-nums;
}
.grid span {
  color: var(--muted);
  font-size: 11px;
}
/* The TV tab is skinned like a late-90s desktop player: bevelled grey chassis,
 * a sunken LCD readout, chunky square transport keys, and a volume track that
 * runs green to red. It sits next to a pixel-art room map on a 16px grid, so
 * crisp 1px edges belong here in a way soft shadows and rounded corners do not.
 * Every rule is scoped under .winamp - the other tabs keep the flat dark panel.
 * Nothing here is a Winamp asset; the look is drawn in CSS. */
.winamp {
  --wa-face: #32323c;
  --wa-face-2: #3b3b47;
  --wa-hi: #6f6f82;
  --wa-lo: #15151c;
  --wa-lcd-bg: #0a1310;
  --wa-lcd: #46f296;
  --wa-lcd-dim: #2a9c63;
  margin: 8px 0 12px;
  padding: 7px;
  background: var(--wa-face);
  border-top: 1px solid var(--wa-hi);
  border-left: 1px solid var(--wa-hi);
  border-right: 1px solid var(--wa-lo);
  border-bottom: 1px solid var(--wa-lo);
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
}
.winamp .wa-bar {
  display: flex;
  justify-content: space-between;
  gap: 8px;
  align-items: baseline;
  padding: 3px 5px 4px;
  margin-bottom: 6px;
  font-size: 10px;
  letter-spacing: 0.09em;
  color: #cfd3dd;
  background: linear-gradient(to bottom, var(--wa-face-2), var(--wa-face));
  border-top: 1px solid var(--wa-hi);
  border-bottom: 1px solid var(--wa-lo);
}
.winamp .wa-bar span {
  color: var(--muted);
  text-transform: uppercase;
}
/* The readout is a well: light on the outside edges, dark on the inside. */
.winamp .wa-display {
  padding: 5px 7px 6px;
  background: var(--wa-lcd-bg);
  border-top: 1px solid var(--wa-lo);
  border-left: 1px solid var(--wa-lo);
  border-right: 1px solid var(--wa-hi);
  border-bottom: 1px solid var(--wa-hi);
}
.winamp .wa-display {
  display: flex;
  align-items: center;
  gap: 10px;
}
/* Winamp's big elapsed counter, left of the title. */
.winamp .wa-time {
  flex: none;
  min-width: 4.2ch;
  font-size: 22px;
  line-height: 1;
  font-variant-numeric: tabular-nums;
  text-align: right;
  color: var(--wa-lcd);
  text-shadow: 0 0 6px rgba(70, 242, 150, 0.35);
}
/* Paused: the counter blinks, as Winamp's did. */
.winamp .wa-time.held {
  animation: wa-blink 1s steps(1) infinite;
}
@keyframes wa-blink {
  50% {
    opacity: 0.25;
  }
}
@media (prefers-reduced-motion: reduce) {
  .winamp .wa-time.held {
    animation: none;
    opacity: 0.6;
  }
}
/* Dragging the position bar: the counter shows where it will land. */
.winamp .wa-time.seeking {
  color: #e8b93a;
  text-shadow: 0 0 6px rgba(232, 185, 58, 0.35);
}
.winamp .wa-text {
  flex: 1;
  min-width: 0;
}
/* Winamp's position bar: a thin sunken groove under the display and a flat
 * rectangular thumb, wider than tall, riding it. */
.winamp .wa-seek {
  margin-top: 6px;
  padding: 0 1px;
}
.winamp .wa-seek[hidden] {
  display: none;
}
.winamp .seek-range {
  display: block;
  width: 100%;
  height: 14px;
  margin: 0;
  appearance: none;
  -webkit-appearance: none;
  background: transparent;
  cursor: pointer;
}
.winamp .seek-range::-webkit-slider-runnable-track {
  height: 6px;
  background: #101016;
  border-top: 1px solid var(--wa-lo);
  border-left: 1px solid var(--wa-lo);
  border-right: 1px solid var(--wa-hi);
  border-bottom: 1px solid var(--wa-hi);
}
.winamp .seek-range::-moz-range-track {
  height: 6px;
  background: #101016;
  border-top: 1px solid var(--wa-lo);
  border-left: 1px solid var(--wa-lo);
  border-right: 1px solid var(--wa-hi);
  border-bottom: 1px solid var(--wa-hi);
}
.winamp .seek-range::-webkit-slider-thumb {
  appearance: none;
  -webkit-appearance: none;
  width: 28px;
  height: 12px;
  margin-top: -4px;
  background: linear-gradient(to bottom, #d7dae2, #8d919c);
  border-top: 1px solid #f2f4f8;
  border-left: 1px solid #f2f4f8;
  border-right: 1px solid var(--wa-lo);
  border-bottom: 1px solid var(--wa-lo);
  border-radius: 0;
}
.winamp .seek-range::-moz-range-thumb {
  width: 28px;
  height: 12px;
  background: linear-gradient(to bottom, #d7dae2, #8d919c);
  border-top: 1px solid #f2f4f8;
  border-left: 1px solid #f2f4f8;
  border-right: 1px solid var(--wa-lo);
  border-bottom: 1px solid var(--wa-lo);
  border-radius: 0;
}
.winamp .wa-marquee {
  overflow: hidden;
  font-size: 13px;
  line-height: 1.5;
  color: var(--wa-lcd);
  text-shadow: 0 0 6px rgba(70, 242, 150, 0.35);
}
.winamp .wa-track {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.winamp .wa-marquee.scrolling .wa-track {
  display: inline-flex;
  overflow: visible;
  text-overflow: clip;
  animation: wa-scroll var(--marquee-s, 12s) linear infinite;
}
/* Two identical copies, each carrying its own trailing gap, so translating the
 * track by exactly half its width lands on the second copy with no seam. */
.winamp .wa-marquee.scrolling .wa-track > span {
  padding-right: 3.5rem;
}
@keyframes wa-scroll {
  from {
    transform: translateX(0);
  }
  to {
    transform: translateX(-50%);
  }
}
.winamp .wa-sub {
  margin-top: 1px;
  font-size: 10px;
  letter-spacing: 0.05em;
  color: var(--wa-lcd-dim);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* Real players used tiny keys. These keep the square bevelled look but stay big
 * enough to hit on a TV. */
.winamp .wa-transport {
  display: flex;
  gap: 3px;
  margin-top: 7px;
}
.winamp .wa-transport button {
  flex: 1;
  min-width: 34px;
  height: 32px;
  font-size: 11px;
  font-family: inherit;
  color: #d8dbe3;
  background: linear-gradient(to bottom, var(--wa-face-2), #2b2b34);
  border-top: 1px solid var(--wa-hi);
  border-left: 1px solid var(--wa-hi);
  border-right: 1px solid var(--wa-lo);
  border-bottom: 1px solid var(--wa-lo);
  border-radius: 0;
  cursor: pointer;
}
/* A press on its way to the set: the keys wait for it (tvSend). */
.winamp .wa-transport button:disabled {
  opacity: 0.55;
  cursor: progress;
}
.winamp .wa-transport button:active {
  background: linear-gradient(to bottom, #2b2b34, var(--wa-face-2));
  border-top-color: var(--wa-lo);
  border-left-color: var(--wa-lo);
  border-right-color: var(--wa-hi);
  border-bottom-color: var(--wa-hi);
}
.winamp .wa-transport button.lit {
  color: var(--wa-lcd);
}
.winamp button:focus-visible,
.winamp input:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
}
.winamp .wa-vol {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-top: 8px;
  padding: 5px 5px 6px;
  border-top: 1px solid var(--wa-lo);
  border-left: 1px solid var(--wa-lo);
  border-right: 1px solid var(--wa-hi);
  border-bottom: 1px solid var(--wa-hi);
  background: #22222a;
}
.winamp .wa-vol output {
  min-width: 2.4em;
  text-align: right;
  font-size: 11px;
  font-variant-numeric: tabular-nums;
  color: var(--wa-lcd);
}
/* A native range keeps arrow keys, focus and touch for free; only its own
 * background can carry the graded track. The track is a fixed scale and the
 * handle rides it, as the original did, so nothing has to redraw on drag. The
 * red end begins at the server's ceiling, which is why that stop is a variable
 * rather than a number written here. */
.winamp .vol-range {
  flex: 1;
  appearance: none;
  -webkit-appearance: none;
  height: 10px;
  background:
    linear-gradient(
      to right,
      #2ecc71 0%,
      #b6d94c calc(var(--ceil-pct, 70%) * 0.62),
      #e8b93a var(--ceil-pct, 70%),
      #7a2626 var(--ceil-pct, 70%),
      #c0392b 100%
    )
    no-repeat center / 100% 6px;
  cursor: pointer;
}
.winamp .vol-range::-webkit-slider-thumb {
  appearance: none;
  -webkit-appearance: none;
  width: 11px;
  height: 20px;
  background: linear-gradient(to bottom, #d7dae2, #8d919c);
  border-top: 1px solid #f2f4f8;
  border-left: 1px solid #f2f4f8;
  border-right: 1px solid var(--wa-lo);
  border-bottom: 1px solid var(--wa-lo);
  border-radius: 0;
}
.winamp .vol-range::-moz-range-thumb {
  width: 11px;
  height: 20px;
  background: linear-gradient(to bottom, #d7dae2, #8d919c);
  border-top: 1px solid #f2f4f8;
  border-left: 1px solid #f2f4f8;
  border-right: 1px solid var(--wa-lo);
  border-bottom: 1px solid var(--wa-lo);
  border-radius: 0;
}
/* The amp climbs over seconds, so the control says it is working rather than
 * looking stuck between the ask and the arrival. */
.winamp .vol-range.ramping {
  animation: wa-pulse 900ms ease-in-out infinite;
}
@keyframes wa-pulse {
  0%,
  100% {
    opacity: 1;
  }
  50% {
    opacity: 0.4;
  }
}
.winamp .wa-note {
  margin: 6px 2px 0;
  font-size: 10px;
  color: #e8b93a;
}
.winamp .wa-search {
  display: flex;
  gap: 4px;
  margin-top: 8px;
}
.winamp .wa-search input {
  flex: 1;
  min-width: 0;
  padding: 7px 8px;
  font-family: inherit;
  font-size: 11px;
  color: var(--wa-lcd);
  background: var(--wa-lcd-bg);
  border-top: 1px solid var(--wa-lo);
  border-left: 1px solid var(--wa-lo);
  border-right: 1px solid var(--wa-hi);
  border-bottom: 1px solid var(--wa-hi);
  border-radius: 0;
}
.winamp .wa-search input::placeholder {
  color: var(--wa-lcd-dim);
}
.winamp .wa-search button {
  padding: 7px 12px;
  font-family: inherit;
  font-size: 10px;
  letter-spacing: 0.09em;
  color: #d8dbe3;
  background: linear-gradient(to bottom, var(--wa-face-2), #2b2b34);
  border-top: 1px solid var(--wa-hi);
  border-left: 1px solid var(--wa-hi);
  border-right: 1px solid var(--wa-lo);
  border-bottom: 1px solid var(--wa-lo);
  border-radius: 0;
  cursor: pointer;
}
.winamp .wa-hint {
  margin: 7px 2px 0;
  font-size: 10px;
  color: var(--muted);
}
@media (prefers-reduced-motion: reduce) {
  .winamp .wa-marquee.scrolling .wa-track,
  .winamp .vol-range.ramping {
    animation: none;
  }
  .winamp .vol-range.ramping {
    outline: 1px solid var(--wa-lcd);
    outline-offset: 2px;
  }
}
.chip {
  display: inline-block;
  background: var(--panel2);
  border: 1px solid var(--line);
  border-radius: 999px;
  padding: 2px 9px;
  font-size: 11px;
  color: var(--muted);
}
.chip.bad {
  background: #3a2226;
  border-color: #5a2f2f;
  color: #ffb3b3;
}
.flags {
  display: flex;
  gap: 5px;
  flex-wrap: wrap;
  margin: 8px 0;
}
.bar {
  display: grid;
  grid-template-columns: 22px 1fr auto;
  gap: 8px;
  align-items: center;
  font-size: 12px;
  margin: 4px 0;
}
.bar i {
  display: block;
  height: 8px;
  background: var(--blue);
  border-radius: 4px;
  min-width: 2px;
}
.bar > span:first-child {
  color: var(--muted);
}
.bar b {
  color: var(--muted);
  font-weight: 500;
  font-variant-numeric: tabular-nums;
}
.bar:has(i[style*="9"]) i {
  background: var(--amber);
}
.ops {
  display: flex;
  gap: 6px;
  flex-wrap: wrap;
  margin: 6px 0 10px;
}
.ops button {
  background: var(--panel2);
  border: 1px solid var(--line);
  color: var(--text);
  padding: 6px 10px;
  border-radius: 8px;
  font-size: 12px;
  cursor: pointer;
}
.ops button:hover {
  border-color: #4a5160;
}
.ops button.warn {
  border-color: #5a2f2f;
  color: #ffb3b3;
}
.maint {
  background: #1b2a3f;
  border: 1px solid #2f4d74;
  color: #cfe3ff;
  border-radius: 8px;
  padding: 6px 9px;
  font-size: 12px;
}
.hint {
  color: var(--muted);
  font-size: 12px;
}
.hint a {
  color: var(--blue);
}
#feed .row {
  display: flex;
  gap: 8px;
  margin: 0;
  padding: 7px 0;
  border-bottom: 1px solid var(--line);
  font-size: 13px;
  flex-wrap: nowrap;
}
#feed .row time {
  color: var(--muted);
  font-variant-numeric: tabular-nums;
  flex: none;
  font-size: 12px;
}
#feed .row em {
  color: var(--accent);
  font-style: normal;
}
#feed .row.note {
  color: var(--amber);
}
#feed .row.note.bad {
  color: var(--red);
}
#feed .row .times {
  color: var(--muted);
  flex: none;
}
#feed .feed-empty {
  margin: 8px 0;
}
/* The All PCs table: one row per PC, a click (or Enter) opens it. */
table.all-pcs {
  margin-top: 10px;
  font-size: 12px;
}
table.all-pcs td,
table.all-pcs th {
  padding: 5px 6px;
  white-space: nowrap;
}
table.all-pcs td:nth-child(3),
table.all-pcs td:nth-child(4) {
  max-width: 7em;
  overflow: hidden;
  text-overflow: ellipsis;
}
table.all-pcs tbody tr {
  cursor: pointer;
}
table.all-pcs tbody tr:hover,
table.all-pcs tbody tr:focus-visible {
  background: var(--panel2);
  outline: none;
}
table.all-pcs .dot {
  display: inline-block;
  width: 7px;
  height: 7px;
  border-radius: 50%;
}
/* "Couldn't load — retry": a button that reads as a link. */
button.linkish {
  background: none;
  border: 0;
  padding: 0;
  color: var(--blue);
  font: inherit;
  text-decoration: underline;
  cursor: pointer;
}
/* The header pill while the live connection is down. */
.reconnect-pill {
  background: #3a2a12;
  border: 1px solid #6a4a1a;
  color: var(--amber);
  border-radius: 999px;
  padding: 2px 10px;
  font-size: 12px;
}
.reconnect-pill[hidden] {
  display: none;
}
h4 {
  margin: 12px 0 4px;
  font-size: 12px;
  color: var(--muted);
  text-transform: uppercase;
  letter-spacing: 0.5px;
}
.page {
  padding: 18px;
  max-width: 1000px;
  margin: 0 auto;
}
table.grid2 {
  width: 100%;
  border-collapse: collapse;
  font-size: 13px;
}
table.grid2 th,
table.grid2 td {
  text-align: left;
  padding: 6px 8px;
  border-bottom: 1px solid var(--line);
}
table.grid2 th {
  color: var(--muted);
  font-weight: 500;
  font-size: 11px;
  text-transform: uppercase;
}
/* The port that leaves the switch, so the topology reads at a glance. */
#pane-net tr.uplink-row td {
  color: var(--muted);
}
#pane-net tr.uplink-row td:first-child {
  box-shadow: inset 2px 0 0 var(--accent);
}

/* The monitor screens are HTML over the canvas, for the same reason the name tags
   are: the canvas is upscaled with image-rendering: pixelated, which is what the
   room wants and the opposite of what a photograph wants. */
.ov.screen {
  transform: none;
  overflow: hidden;
}
.ov.screen img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  image-rendering: auto;
}
.ov.screen.game img {
  width: auto;
  height: 85%;
  margin: 3.75% auto 0;
  object-fit: contain;
}
.badge-count {
  color: var(--accent);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}
.task .r.bad {
  color: #ffb3b3;
}
#pane-tasks .prompt {
  margin-bottom: 6px;
}

/* Sign-in: the one page that renders before anything else is authorised. */
.signin {
  max-width: 320px;
  margin: 12vh auto;
  padding: 0 16px;
}
.signin h1 {
  font-size: 20px;
  margin: 0 0 6px;
}
.signin form {
  display: flex;
  flex-direction: column;
  gap: 4px;
  margin-top: 18px;
}
.signin label {
  color: var(--muted);
  font-size: 12px;
  margin-top: 8px;
}
.signin input {
  background: #12141a;
  border: 1px solid var(--line);
  color: var(--text);
  padding: 8px 10px;
  border-radius: 8px;
  font-size: 14px;
}
.signin button {
  margin-top: 16px;
  background: var(--accent);
  border: 1px solid var(--accent);
  color: #fff;
  padding: 9px 10px;
  border-radius: 8px;
  font-size: 14px;
  cursor: pointer;
}
.signin-error {
  background: #3a2226;
  border: 1px solid #5a2f2f;
  color: #ffb3b3;
  border-radius: 8px;
  padding: 8px 10px;
  font-size: 13px;
}
.whoami {
  color: var(--muted);
  font-size: 12px;
}
.whoami form {
  display: inline;
}
.whoami button {
  background: none;
  border: 0;
  color: var(--blue);
  font-size: 12px;
  cursor: pointer;
  padding: 0;
}

/* The room camera modal: a small security-monitor tile. The frame is a webcam
   still scaled up crisply, the one <img> allowed to look pixelated, like the
   monitor wallpapers on the map. */
.modal {
  position: fixed;
  inset: 0;
  background: rgba(8, 9, 12, 0.72);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 1010; /* above the console (1000) */
}
.modal[hidden] {
  display: none;
}
.modal-card {
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 12px;
  padding: 14px;
  box-shadow: 0 12px 40px rgba(0, 0, 0, 0.5);
  max-width: 90vw;
  max-height: 92vh;
  overflow-y: auto;
}
.modal-head {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 10px;
}
.modal-head .spacer {
  flex: 1;
}
.camera-screen {
  width: 100%;
  aspect-ratio: 16 / 9;
  background: #0a0b0d;
  border: 2px solid #0a0b0d;
  border-radius: 6px;
  overflow: hidden;
  display: flex;
  align-items: center;
  justify-content: center;
}
.camera-screen img {
  /* The browser's own image drag would steal the press; the panel moves instead. */
  -webkit-user-drag: none;
  user-select: none;
  touch-action: none;
  cursor: grab;
  width: 100%;
  height: 100%;
  object-fit: cover;
  image-rendering: pixelated;
  display: block;
}
.camera-screen.empty img {
  display: none;
}
.camera-screen.empty::after {
  content: attr(data-note);
  color: var(--muted);
  font-size: 13px;
}
/* A specs list, not a gauge: the label column is fixed so CPU, GPU and RAM line
 * up down the panel however long the part names run. */
.specs {
  margin: 4px 0 0;
  font-size: 13px;
}
.specs > div {
  display: grid;
  grid-template-columns: 52px 1fr;
  gap: 8px;
  padding: 5px 0;
  border-bottom: 1px solid var(--line);
}
.specs > div:last-child {
  border-bottom: 0;
}
.specs dt {
  color: var(--muted);
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.5px;
  padding-top: 2px;
}
.specs dd {
  margin: 0;
  min-width: 0;
  overflow-wrap: anywhere;
}
.specs dd span {
  display: block;
  color: var(--muted);
  font-size: 11px;
  font-variant-numeric: tabular-nums;
}

/* A panel hangs over the thing it controls. Not a modal: the room stays live
   underneath and it closes only on its own button. */
/* Fixed to the window rather than the map, so a panel can be parked anywhere on
   screen, including the margins beside the room. */
.pin {
  position: fixed;
  transform: translate(-50%, -50%);
  z-index: 20;
}
.pin[hidden] {
  display: none;
}
/* The player was the width of the side panel it used to live in, and looked
   right there. Free-floating it grew to whatever the skin wanted, so it keeps
   the width it had. */
#tv-player {
  width: 320px;
}
/* On a short screen the remote scrolls inside rather than past the fold. The
 * scroll sits on the body, not the panel: a scrolling panel clips the close
 * button, which hangs over its corner. */
#ac-panel .ac {
  max-height: calc(100vh - 80px);
  overflow-y: auto;
}
#camera-panel {
  width: min(420px, 80vw);
  /* .pin.card caps every panel at 420px; the camera is the one that grows. */
  max-width: 96vw;
}
/* Dragging the corner grip sizes the camera between its normal width and twice
   that. A button, so the panel's own drag handler leaves it alone. */
.pin-resize {
  position: absolute;
  right: 2px;
  bottom: 2px;
  width: 14px;
  height: 14px;
  padding: 0;
  border: 0;
  background: linear-gradient(
    135deg,
    transparent 45%,
    var(--muted) 45% 52%,
    transparent 52% 65%,
    var(--muted) 65% 72%,
    transparent 72%
  );
  opacity: 0.6;
  cursor: nwse-resize !important;
  touch-action: none;
}
.pin-resize:hover {
  opacity: 1;
}
.pin.card {
  padding: 10px 12px 12px;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 10px;
  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.55);
  max-width: min(420px, 80vw);
}
.pin-head {
  display: flex;
  align-items: baseline;
  gap: 8px;
  margin-bottom: 8px;
}
.pin-close {
  position: absolute;
  top: -10px;
  right: -10px;
  z-index: 1;
  width: 22px;
  height: 22px;
  padding: 0;
  font:
    700 12px / 1 ui-monospace,
    monospace;
  color: #d6d9e0;
  background: #3a3d44;
  border: 1px solid #55585f;
  border-top-color: #6e727a;
  border-radius: 3px;
  cursor: pointer;
}
.pin-close:hover {
  background: #4a4d55;
}

/* The club's own sign, in the pixel idiom the map already uses. */
header h1.brand {
  display: flex;
  align-items: center;
  gap: 10px;
}
header h1.brand img {
  display: block;
  image-rendering: pixelated;
}
/* Both glyph grids sit on whole pixels (3px per cell for the sign, 2px for the
   place) so neither smears, and share a baseline: the place reads as a quieter
   second voice of the sign, lit in the night palette's cool neon. */
header h1.brand {
  align-items: flex-end;
  min-height: 29px; /* the bar keeps the height the old sign gave it */
}
header h1.brand img.logo {
  filter: drop-shadow(0 0 6px rgba(94, 200, 255, 0.35));
}
header h1.brand img.place {
  opacity: 0.85;
  filter: drop-shadow(0 0 4px rgba(124, 108, 255, 0.3));
}

/* A pinned panel is dragged by anything on it that is not itself a control, so
   neither panel needs a title bar reserved as a handle. On touch only the title
   bar drags it (makeDraggable), so the rest of the panel scrolls as a page does. */
.pin {
  cursor: grab;
}
.pin .pin-head,
.pin .wa-bar,
.pin .prank .balance,
.pin .prank h3 {
  touch-action: none;
}
.pin button,
.pin input,
.pin select,
.pin textarea {
  cursor: auto;
  touch-action: auto;
}
.pin.dragging {
  cursor: grabbing;
  opacity: 0.92;
  user-select: none;
}

/* The menu is as wide as its widest row needs and no wider: it hangs over the
   machine it is aimed at, so every extra pixel covers the room. */
#prank-panel {
  width: max-content;
  max-width: min(300px, 80vw);
}

/* The hit list keeps the LCD idiom: green on black, square edges, one bevel. At
   320px the thumbnail has to stay small enough to leave the title two readable
   lines beside it. */
.winamp .wa-results {
  max-height: 232px;
  margin-top: 6px;
  overflow-y: auto;
  background: var(--wa-lcd-bg);
  border-top: 1px solid var(--wa-lo);
  border-left: 1px solid var(--wa-lo);
  border-right: 1px solid var(--wa-hi);
  border-bottom: 1px solid var(--wa-hi);
}
.winamp .wa-results[hidden] {
  display: none;
}
.winamp .wa-empty {
  margin: 0;
  padding: 9px;
  font-size: 10px;
  letter-spacing: 0.06em;
  color: var(--wa-lcd-dim);
}
.winamp .wa-hit {
  display: flex;
  gap: 7px;
  width: 100%;
  padding: 5px 6px;
  font-family: inherit;
  text-align: left;
  background: none;
  border: 0;
  border-bottom: 1px solid #14231c;
  cursor: pointer;
}
.winamp .wa-hit:last-child {
  border-bottom: 0;
}
/* A result is the play button plus a queue button beside it. */
.winamp .wa-hit-row {
  display: flex;
  border-bottom: 1px solid #14231c;
}
.winamp .wa-hit-row:last-child {
  border-bottom: 0;
}
.winamp .wa-hit-row .wa-hit {
  flex: 1;
  min-width: 0;
  /* A <button> sizes to its text in Safari despite min-width, so a long title
     pushed the + and the note out of the row; clip it here instead. */
  overflow: hidden;
  border-bottom: 0;
}
.winamp .wa-add,
.winamp .wa-del {
  flex: none;
  width: 28px;
  font: inherit;
  font-size: 15px;
  color: var(--wa-lcd);
  background: none;
  border: 0;
  border-left: 1px solid #14231c;
  cursor: pointer;
}
.winamp .wa-add:hover,
.winamp .wa-del:hover {
  background: #13291f;
}
.winamp .wa-add:disabled {
  cursor: default;
  color: var(--wa-lcd-dim);
}
/* ♫ beside a song's +: save it to a named playlist. */
.winamp .wa-save {
  flex: none;
  width: 24px;
  font: inherit;
  font-size: 13px;
  color: var(--wa-lcd-dim);
  background: none;
  border: 0;
  border-left: 1px solid #14231c;
  cursor: pointer;
}
.winamp .wa-save:hover {
  color: var(--wa-lcd);
  background: #0f1f18;
}
/* The PLAYLISTS tab's top row: its buttons, or the open list's name and back. */
.winamp .wa-rows li.wa-pl-head {
  gap: 4px;
  padding: 3px 4px;
  background: #0f1f18;
}
.winamp .wa-pl-btn {
  flex: none;
  padding: 2px 6px;
  font: inherit;
  font-size: 9px;
  letter-spacing: 0.06em;
  color: var(--wa-lcd);
  background: none;
  border: 1px solid #14231c;
  cursor: pointer;
}
.winamp .wa-pl-btn:hover {
  background: #13291f;
}
.winamp .wa-pl-btn:disabled {
  cursor: default;
  color: var(--wa-lcd-dim);
}

/* The playlist editor: up next and what played, each naming who chose it. */
.winamp .wa-list {
  position: relative;
  margin-top: 6px;
  background: var(--wa-lcd-bg);
  border-top: 1px solid var(--wa-lo);
  border-left: 1px solid var(--wa-lo);
  border-right: 1px solid var(--wa-hi);
  border-bottom: 1px solid var(--wa-hi);
}
.winamp .wa-list-tabs {
  display: flex;
  border-bottom: 1px solid #14231c;
}
.winamp .wa-list-tabs button {
  flex: 1;
  padding: 4px 0;
  font: inherit;
  font-size: 10px;
  letter-spacing: 0.08em;
  color: var(--wa-lcd-dim);
  background: none;
  border: 0;
  cursor: pointer;
}
.winamp .wa-list-tabs button.on {
  color: var(--wa-lcd);
  background: #0f1f18;
}
/* The playlist editor's corner grip: drag to show up to 12 songs (app.js). */
.winamp .wa-grip {
  position: absolute;
  right: 0;
  bottom: 0;
  z-index: 1;
  width: 12px;
  height: 12px;
  padding: 0;
  border: 0;
  background: linear-gradient(
    135deg,
    transparent 40%,
    var(--wa-hi) 40% 48%,
    var(--wa-lo) 48% 56%,
    transparent 56% 64%,
    var(--wa-hi) 64% 72%,
    var(--wa-lo) 72% 80%,
    transparent 80%
  );
  cursor: ns-resize;
  touch-action: none;
}
.winamp .wa-rows {
  max-height: 180px;
  margin: 0;
  padding: 0;
  overflow-y: auto;
  list-style: none;
}
.winamp .wa-rows li {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 3px 0 3px 6px;
  font-size: 11px;
  border-bottom: 1px solid #14231c;
}
.winamp .wa-rows li:last-child {
  border-bottom: 0;
}
.winamp .wa-rows li[data-video]:not([data-video=""]) {
  cursor: pointer;
}
.winamp .wa-rows li[data-title]:hover {
  background: #13291f;
}
.winamp .wa-rows li.wa-empty {
  display: block;
  padding: 8px;
}
.winamp .wa-n {
  flex: none;
  min-width: 3ch;
  font-variant-numeric: tabular-nums;
  color: var(--wa-lcd-dim);
}
.winamp .wa-row-text {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
}
.winamp .wa-row-text b,
.winamp .wa-row-text small {
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}
.winamp .wa-row-text b {
  font-weight: 400;
  color: var(--wa-lcd);
}
.winamp .wa-row-text small {
  font-size: 9px;
  color: var(--wa-lcd-dim);
}
.winamp .wa-by {
  flex: none;
  max-width: 9ch;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: 9px;
  color: #d8c25a;
  padding-right: 6px;
}
/* Hover is only a hint. The selection (the arrows, or a click) is the strong
 * mark, so the mouse resting over the list never looks like a second
 * selection when the list scrolls under it. */
.winamp .wa-hit:hover,
.winamp .wa-add:hover,
.winamp .wa-rows li[data-title]:hover {
  background: #0f1f18;
}
.winamp .wa-hit:focus,
.winamp .wa-results .wa-add:focus {
  outline: none;
}
/* The outline marks the cell Enter will act on: .sel in the search results,
 * focus in the played list, whether a click or the keyboard put it there. */
.winamp .wa-hit.sel,
.winamp .wa-add.sel,
.winamp .wa-save.sel,
.winamp .wa-rows .wa-add:focus-visible,
.winamp .wa-rows .wa-save:focus-visible,
.winamp .wa-rows li[tabindex]:focus {
  background: #13291f;
  outline: 1px solid var(--wa-lcd);
  outline-offset: -1px;
}
.winamp .wa-hit img {
  flex: none;
  width: 58px;
  height: 33px;
  object-fit: cover;
  background: #0d1a14;
}
.winamp .wa-hit-text {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.winamp .wa-hit-text b {
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  font-size: 11px;
  font-weight: 400;
  line-height: 1.25;
  color: var(--wa-lcd);
}
.winamp .wa-hit-text small {
  overflow: hidden;
  font-size: 9px;
  letter-spacing: 0.05em;
  white-space: nowrap;
  text-overflow: ellipsis;
  color: var(--wa-lcd-dim);
}

/* The developer panel: dense key/value tables, colour only for good and bad. */
#dev-panel {
  width: min(460px, 92vw);
  max-width: none;
  /* Above every other floating panel: it is opened to look at the room's
     controls, so it must not end up underneath one of them. */
  z-index: 30;
}
#pane-dev-camera {
  font-size: 12px;
}
#pane-dev-camera input[type='range'] {
  width: 100%;
}
#pane-dev-camera .btn.on {
  border-color: var(--accent);
  color: var(--accent);
}
#pane-dev-camera .saved {
  color: var(--green);
}
#dev-panel .dev {
  max-height: min(70vh, 640px);
  overflow-y: auto;
  font-size: 12px;
}
#dev-panel h4 {
  margin: 10px 0 4px;
  font-size: 11px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--muted);
}
#dev-panel table {
  width: 100%;
  border-collapse: collapse;
}
#dev-panel th,
#dev-panel td {
  padding: 2px 6px 2px 0;
  text-align: left;
  vertical-align: top;
  overflow-wrap: anywhere;
}
#dev-panel th {
  width: 34%;
  font-weight: 400;
  color: var(--muted);
}
#dev-panel code {
  font-size: 11px;
}
#dev-panel .ok {
  color: var(--green);
}
#dev-panel .warn {
  color: var(--amber);
}
#dev-panel .bad {
  color: var(--red);
}

/* Twitch chat pane: a scrolling log with the reply box pinned under it. */
#pane-chat.on {
  display: flex;
  flex-direction: column;
  gap: 8px;
  min-height: 0;
}
#pane-chat .chat {
  flex: 1;
  min-height: 160px;
  max-height: 60vh;
  margin: 0;
  padding: 0;
  overflow-y: auto;
  list-style: none;
  font-size: 13px;
}
#pane-chat .chat li {
  padding: 3px 0;
  overflow-wrap: anywhere;
}
#pane-chat .chat li.mine {
  opacity: 0.8;
  font-style: italic;
}
#pane-chat .ok {
  color: var(--green);
}
#pane-chat .bad {
  color: var(--red);
}
.ov.bubble.twitch {
  border-color: #9146ff;
}

/* The AC Remote: a round power key, a big set point between − and +,
 * segmented mode and fan, then the timers. */
#ac-panel {
  width: 340px;
  max-width: min(420px, 92vw);
}
#ac-sub.bad {
  color: #ffb3b3;
}
.ac {
  display: flex;
  flex-direction: column;
  gap: 12px;
  font-size: 13px;
}
.ac-top {
  display: flex;
  align-items: center;
  gap: 12px;
}
.ac-power {
  width: 52px;
  height: 52px;
  border-radius: 50%;
  border: 2px solid var(--line);
  background: var(--panel2);
  color: var(--muted);
  font-size: 24px;
  line-height: 1;
  cursor: pointer;
  transition: box-shadow 0.15s, color 0.15s, border-color 0.15s;
}
.ac-power.on {
  color: var(--green);
  border-color: var(--green);
  box-shadow: 0 0 14px rgba(61, 220, 132, 0.45), inset 0 0 8px rgba(61, 220, 132, 0.2);
}
.ac-power.off {
  opacity: 0.7;
}
.ac-power:hover {
  opacity: 1;
  border-color: var(--muted);
}
.ac-power.on:hover {
  border-color: var(--green);
}
.ac-state {
  font-size: 16px;
}
.ac-controls {
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: 12px;
  border-radius: 10px;
  background: var(--panel2);
  border: 1px solid var(--line);
  transition: opacity 0.15s;
}
/* Off, the set point, mode and fan keys are disabled (renderAc), and dimmed to say so. */
.ac-controls.off {
  opacity: 0.5;
}
.ac button:disabled {
  cursor: not-allowed;
}
.ac-room {
  display: flex;
  justify-content: center;
  align-items: baseline;
  gap: 10px;
  font-size: 13px;
}
.ac-room b {
  font-size: 18px;
  color: #e8f4ff;
}
.ac-room.stale {
  opacity: 0.5;
}
.ac-room .ac-dir.cool { color: #4aa8ff; }
.ac-room .ac-dir.heat { color: #ff5a4a; }
.ac-room .ac-dir.at { color: var(--green); }
.ac-chart { margin: 18px 0 2px; }
.ac-chart-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; margin-bottom: 6px; }
.ac-chart-read { color: #ffb04a; font-size: 16px; font-weight: 600; font-variant-numeric: tabular-nums; white-space: nowrap; }
.ac-chart-read.past { color: #ffd59a; }
.ac-chart-tabs { display: flex; gap: 3px; }
.ac-chart-tabs .btn.chip { padding: 1px 7px; font-size: 10px; border-radius: 6px; line-height: 1.5; }
.ac-chart-tabs .chip.on { background: #2a4a6a; color: #e8f4ff; }
.ac-chart-svg { display: block; width: 100%; height: auto; overflow: visible; }
.ac-chart-svg path, .ac-chart-svg line { fill: none; vector-effect: non-scaling-stroke; }
.ac-chart-svg .temp { stroke: #ffb04a; stroke-width: 2; }
.ac-chart-svg .hum { stroke: #4aa8ff; stroke-width: 1; opacity: 0.45; }
.ac-chart-svg .set { stroke: #e8f4ff; stroke-width: 1; opacity: 0.6; }
.ac-chart-svg .set.dashed { stroke-dasharray: 4 3; }
.ac-chart-svg { touch-action: pan-y; cursor: crosshair; }
.ac-chart-svg text { fill: #ffb04a; font-size: 9px; }
.ac-chart-svg .grid { stroke: #e8f4ff; stroke-width: 1; opacity: 0.08; }
.ac-chart-svg .yax { fill: #ffb04a; opacity: 0.7; font-size: 8px; text-anchor: end; dominant-baseline: middle; }
.ac-chart-svg .cur-line { stroke: #e8f4ff; stroke-width: 1; opacity: 0.35; }
.ac-chart-svg .cur-dot { fill: #ffb04a; }
.ac-chart-foot { display: flex; justify-content: space-between; font-size: 11px; }
.ac-chart-foot .hum-key { color: #4aa8ff; opacity: 0.7; }
.ac-temp {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 18px;
}
.ac-round {
  width: 44px;
  height: 44px;
  border-radius: 50%;
  border: 1px solid var(--line);
  background: var(--panel);
  color: var(--text);
  font-size: 22px;
  line-height: 1;
  cursor: pointer;
}
.ac-round:hover:not(:disabled) {
  border-color: var(--accent);
}
.ac-round:disabled {
  opacity: 0.35;
  cursor: default;
}
.ac-set {
  font-size: 44px;
  font-weight: 600;
  line-height: 1;
  min-width: 110px;
  text-align: center;
  font-variant-numeric: tabular-nums;
}
.ac-set small {
  font-size: 16px;
  font-weight: 500;
  color: var(--muted);
  margin-left: 2px;
}
.ac-label {
  margin-bottom: -6px;
}
.ac-seg {
  display: flex;
  border: 1px solid var(--line);
  border-radius: 8px;
  overflow: hidden;
}
.ac-seg-btn {
  flex: 1 1 0;
  min-width: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 1px;
  padding: 5px 2px;
  border: 0;
  border-left: 1px solid var(--line);
  background: var(--panel);
  color: var(--muted);
  font: inherit;
  font-size: 11px;
  cursor: pointer;
}
.ac-seg-btn:first-child {
  border-left: 0;
}
.ac-seg-btn:hover {
  color: var(--text);
}
.ac-ico {
  font-size: 15px;
  line-height: 1.1;
}
.ac-seg-btn.on {
  color: var(--accent);
  background: rgba(124, 108, 255, 0.16);
  box-shadow: inset 0 -2px 0 var(--accent);
}
/* Heat reads red and cool blue, on the mode key and the set temperature alike. */
.ac-seg-btn.on[data-ac-mode='heat'],
.ac-set.mode-heat {
  color: #ff5a4a;
}
.ac-seg-btn.on[data-ac-mode='heat'] {
  background: rgba(255, 90, 74, 0.14);
  box-shadow: inset 0 -2px 0 #ff5a4a;
}
.ac-seg-btn.on[data-ac-mode='cool'],
.ac-set.mode-cool {
  color: #4aa8ff;
}
.ac-seg-btn.on[data-ac-mode='cool'] {
  background: rgba(74, 168, 255, 0.14);
  box-shadow: inset 0 -2px 0 #4aa8ff;
}
.ac-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
}
.ac-timers {
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.ac .btn.chip {
  padding: 3px 10px;
  font-size: 12px;
}
.ac-timers input[type='time'] {
  min-width: 0;
}
.ac-timers ul {
  list-style: none;
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
  margin: 0;
  padding: 0;
}
.ac-pill {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 2px 4px 2px 9px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--panel2);
  font-size: 12px;
}
.ac-pill-x {
  border: 0;
  background: none;
  color: var(--muted);
  font-size: 14px;
  line-height: 1;
  padding: 0 4px;
  cursor: pointer;
}
.ac-pill-x:hover {
  color: var(--red);
}

/* The AC panel, skinned as a chunky handheld remote like the camera and the
 * Winamp player: dark rubbery plastic with a fine grain, a raised bevel, a
 * moulded close on the top edge, the set temperature and mode in a green-grey
 * LCD, and rubber keys that sink when pressed. CSS only, no images. */
.acr {
  --acr-body: #232428;
  --acr-hi: #55575f;
  --acr-lo: #0b0b0d;
  --acr-key: #3a3b41;
  --acr-lcd: #a9b89a;
  --acr-ink: #1d2419;
  padding: 0 14px 16px;
  color: #d4d6dd;
  background:
    repeating-linear-gradient(45deg, rgba(255, 255, 255, 0.018) 0 1px, transparent 1px 3px),
    repeating-linear-gradient(-45deg, rgba(0, 0, 0, 0.12) 0 1px, transparent 1px 3px),
    linear-gradient(to bottom, #2c2d32, var(--acr-body) 40%, #1b1c1f);
  border: 1px solid var(--acr-lo);
  border-radius: 22px 22px 30px 30px;
  box-shadow:
    inset 2px 2px 0 rgba(255, 255, 255, 0.08),
    inset -2px -3px 0 rgba(0, 0, 0, 0.45),
    0 12px 32px rgba(0, 0, 0, 0.6);
}
.acr .acr-plate {
  align-items: center;
  gap: 6px;
  margin: 0 -14px 12px;
  padding: 8px 10px 7px 18px;
  background: linear-gradient(to bottom, #3a3b41, #2a2b30);
  border-bottom: 1px solid var(--acr-lo);
  border-radius: 21px 21px 0 0;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 #3c3d44;
}
.acr .acr-plate b {
  font: 700 10px / 1 ui-monospace, SFMono-Regular, Menlo, monospace;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: #c9ccd4;
}
.acr #ac-sub {
  font-size: 10px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: #8f929d;
}
/* Rubber keys: a domed face, dark under-edge, and :active pushes them in. */
.acr .acr-close,
.acr .ac-round,
.acr .ac-seg-btn,
.acr .btn.chip,
.acr .ac-pill-x,
.acr .ac-power {
  color: #dcdee4;
  background: radial-gradient(ellipse at 50% 30%, #4a4b52, var(--acr-key) 60%, #2b2c31);
  border: 1px solid var(--acr-lo);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.14), 0 2px 0 #0a0a0c, 0 3px 4px rgba(0, 0, 0, 0.4);
  cursor: pointer;
}
.acr .acr-close:active,
.acr .ac-round:active:not(:disabled),
.acr .ac-seg-btn:active,
.acr .btn.chip:active,
.acr .ac-pill-x:active,
.acr .ac-power:active {
  transform: translateY(2px);
  box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.6), 0 0 0 #0a0a0c;
}
.acr .acr-close:focus-visible,
.acr .ac-round:focus-visible,
.acr .ac-seg-btn:focus-visible,
.acr .btn.chip:focus-visible,
.acr .ac-pill-x:focus-visible,
.acr .ac-power:focus-visible,
.acr select:focus-visible,
.acr input:focus-visible {
  outline: 2px solid #8fd3ff;
  outline-offset: 2px;
}
.acr .acr-close {
  flex: none;
  width: 26px;
  height: 22px;
  margin-left: auto;
  padding: 0;
  font: 700 14px / 1 ui-monospace, monospace;
  border-radius: 8px 8px 5px 5px;
}
.acr .acr-close:hover {
  color: #fff;
}
.acr .ac-power {
  width: 44px;
  height: 44px;
  border-radius: 50%;
  font-size: 20px;
  color: #fff;
}
.acr .ac-power.on {
  background: radial-gradient(circle at 40% 30%, #ff8a80, #d32f2f 55%, #8e1414);
  border-color: #4a0808;
}
.acr .ac-power.off {
  background: radial-gradient(circle at 40% 30%, #9be29f, #2e9d3a 55%, #17561d);
  border-color: #0c3310;
}
.acr .ac-state {
  font: 700 11px / 1 ui-monospace, monospace;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: #9ea1ab;
}
.acr .ac-round {
  width: 38px;
  height: 38px;
  border-radius: 12px;
  font-size: 20px;
}
.acr .ac-round:disabled {
  opacity: 0.4;
  cursor: default;
}
/* The LCD: a recessed green-grey window with dark segment-ish digits. */
.acr .ac-set {
  flex: 1;
  display: flex;
  align-items: baseline;
  justify-content: center;
  gap: 3px;
  position: relative;
  min-height: 56px;
  padding: 16px 10px 6px;
  font: 700 34px / 1 'DSEG7 Classic', 'Courier New', ui-monospace, monospace;
  letter-spacing: 0.04em;
  color: var(--acr-ink);
  background:
    repeating-linear-gradient(0deg, rgba(0, 0, 0, 0.035) 0 1px, transparent 1px 2px),
    linear-gradient(to bottom, #9aa98b, var(--acr-lcd) 30%, #b4c2a5);
  border: 2px solid #121315;
  border-radius: 6px;
  box-shadow: inset 0 2px 5px rgba(0, 0, 0, 0.45), 0 1px 0 rgba(255, 255, 255, 0.08);
  text-shadow: 1px 1px 0 rgba(0, 0, 0, 0.12);
}
.acr .ac-set small {
  font-size: 14px;
  color: var(--acr-ink);
}
.acr .ac-set::before {
  position: absolute;
  top: 5px;
  left: 8px;
  font: 700 9px / 1 ui-monospace, monospace;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}
.acr .ac-set.mode-auto::before { content: 'mode auto'; }
.acr .ac-set.mode-cool::before { content: 'mode cool'; }
.acr .ac-set.mode-dry::before { content: 'mode dry'; }
.acr .ac-set.mode-fan::before { content: 'mode fan'; }
.acr .ac-set.mode-heat::before { content: 'mode heat'; }
.acr .ac-controls.off .ac-set {
  color: rgba(29, 36, 25, 0.25);
}
/* Mode and fan: rows of small separate rubber keys, the active one lit. */
.acr .ac-seg {
  gap: 5px;
  overflow: visible;
  padding-bottom: 3px;
  border: 0;
  border-radius: 0;
  background: none;
}
.acr .ac-seg-btn {
  min-height: 30px;
  border-radius: 8px;
  font: 600 10px / 1.1 ui-monospace, monospace;
  text-transform: uppercase;
}
.acr .ac-seg-btn:first-child {
  border-left: 1px solid var(--acr-lo);
}
.acr .ac-seg-btn.on,
.acr .ac-seg-btn.on[data-ac-mode='heat'],
.acr .ac-seg-btn.on[data-ac-mode='cool'] {
  color: #10240f;
  background: radial-gradient(ellipse at 50% 30%, #d9ffb8, #8fdc5c 55%, #4f9a2c);
  border-color: #1c3a0f;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.4), 0 2px 0 #0a0a0c, 0 0 8px rgba(143, 220, 92, 0.55);
}
.acr .ac-seg-btn.on[data-ac-mode='heat'] {
  color: #2a0d00;
  background: radial-gradient(ellipse at 50% 30%, #ffd3a8, #ff8a3d 55%, #b0480c);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.4), 0 2px 0 #0a0a0c, 0 0 8px rgba(255, 138, 61, 0.55);
}
.acr .ac-seg-btn.on[data-ac-mode='cool'] {
  color: #001d2e;
  background: radial-gradient(ellipse at 50% 30%, #c8ecff, #4fb4ff 55%, #1a6aa8);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.4), 0 2px 0 #0a0a0c, 0 0 8px rgba(79, 180, 255, 0.55);
}
.acr .ac-label,
.acr .hint {
  color: #8f929d;
}
.acr .btn.chip {
  min-height: 26px;
  min-width: 34px;
  border-radius: 7px;
  font: 600 11px / 1 ui-monospace, monospace;
}
.acr .ac-timers {
  padding-top: 10px;
  border-top: 1px solid #111214;
  box-shadow: 0 -1px 0 rgba(255, 255, 255, 0.05);
}
.acr select,
.acr input[type='time'] {
  min-height: 26px;
  color: #dcdee4;
  background: #17181b;
  border: 1px solid var(--acr-lo);
  border-radius: 6px;
  box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.6);
}
.acr .ac-pill {
  color: var(--acr-ink);
  background: var(--acr-lcd);
  border-color: #121315;
  box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.35);
}
.acr .ac-pill-x {
  min-width: 26px;
  min-height: 22px;
  border-radius: 999px;
}
.acr .ac-pill-x:hover {
  color: #ff8a80;
}

/* The WHAT'S NEW panel: the hand-curated HIGHLIGHTS list in full, opened by
 * clicking the small board on the map (see highlightsBoardRect() in room.js)
 * instead of the old always-visible wall text. Each entry is name + date on
 * one row and a muted one-line blurb below, the same title/meta/detail
 * hierarchy as .task .h/.p - these are short capability descriptions, not
 * markdown. */
/* Scrolls inside the panel rather than running past the bottom of the window. */
.highlights {
  max-height: min(70vh, 560px);
  overflow-y: auto;
}
.highlights ul {
  list-style: none;
  margin: 0;
  padding: 0;
  font-size: 13px;
}
.highlights li {
  display: flex;
  align-items: flex-start;
  gap: 8px;
  padding: 6px 0;
  border-bottom: 1px solid var(--line);
}
/* Each entry's icon: 48 px art shown at 24, so the pixel art keeps the map's
 * 2 px blocks. Top-aligned with the name row, not centred on the whole entry. */
.highlights li .icon {
  flex: none;
  width: 24px;
  height: 24px;
  image-rendering: pixelated;
  margin-top: -3px;
}
.highlights li .text {
  flex: 1;
  min-width: 0;
}
.highlights li:last-child {
  border-bottom: 0;
}
.highlights li .h {
  display: flex;
  justify-content: space-between;
  gap: 8px;
}
.highlights li .h span {
  color: var(--muted);
  font-size: 12px;
  white-space: nowrap;
}
.highlights li .blurb {
  color: var(--muted);
  font-size: 12px;
  margin-top: 2px;
}

/* WHAT'S NEW as a TCTF terminal from Bungie's Oni, matching the mailbox and
 * answering machine - near-black panel, faint scanline, chamfered corners,
 * teal hairlines, monospace labels. Palette vars are shared with
 * #voicemail-panel. */
#highlights-panel.tctf {
  width: 352px;
  padding: 0 10px 12px;
  color: var(--cl-label);
  background:
    repeating-linear-gradient(0deg, rgba(63, 208, 201, 0.05) 0 1px, transparent 1px 3px),
    linear-gradient(var(--cl-bg2), var(--cl-bg));
  border: 1px solid var(--cl-dim);
  border-radius: 0;
  clip-path: polygon(14px 0, 100% 0, 100% calc(100% - 14px), calc(100% - 14px) 100%, 0 100%, 0 14px);
  font-family: ui-monospace, Menlo, monospace;
}
#highlights-panel .tctf-title {
  display: flex;
  align-items: center;
  margin: 0 -10px 8px;
  padding: 9px 40px 8px 18px;
  border-bottom: 1px solid var(--cl-dim);
  background: rgba(63, 208, 201, 0.06);
}
#highlights-panel .tctf-title b {
  font: 700 14px / 1 ui-monospace, Menlo, monospace;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--cl-teal);
  text-shadow: 0 0 6px rgba(63, 208, 201, 0.5);
}
#highlights-panel .tctf-title .cl-kanji {
  margin-left: 6px;
  font-size: 10px;
  color: var(--cl-dim);
  letter-spacing: 0.2em;
}
#highlights-panel .tctf-close {
  position: absolute;
  top: 6px;
  right: 8px;
  width: 22px;
  height: 22px;
  padding: 0;
  font: 400 18px / 1 ui-monospace, Menlo, monospace;
  color: var(--cl-teal);
  background: transparent;
  border: 1px solid var(--cl-dim);
  border-radius: 0;
  cursor: pointer;
  z-index: 1;
}
#highlights-panel .tctf-close:hover { border-color: var(--cl-teal); background: rgba(63, 208, 201, 0.12); }
#highlights-panel.tctf .highlights {
  scrollbar-width: thin;
  scrollbar-color: var(--cl-dim) transparent;
}
#highlights-panel.tctf .highlights li {
  padding: 7px 6px;
  border-bottom: 1px solid rgba(31, 111, 108, 0.45);
  transition: background-color 0.12s;
}
#highlights-panel.tctf .highlights li:last-child { border-bottom: 0; }
#highlights-panel.tctf .highlights li:hover { background: rgba(63, 208, 201, 0.06); }
#highlights-panel.tctf .highlights li .h b {
  font: 700 12px / 1.3 ui-monospace, Menlo, monospace;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--cl-label);
}
#highlights-panel.tctf .highlights li .h span {
  font: 10px / 1.6 ui-monospace, Menlo, monospace;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: #4fa9a4;
}
#highlights-panel.tctf .highlights li .h .upd { font-style: normal; color: var(--cl-dim); }
#highlights-panel.tctf .highlights li .blurb {
  font: 12px / 1.45 ui-monospace, Menlo, monospace;
  color: #a9b8bd;
}
#highlights-panel.tctf .highlights li .icon {
  margin-top: 0;
  padding: 2px;
  background: #060a0d;
  border: 1px solid var(--cl-dim);
}
@media (prefers-reduced-motion: reduce) {
  #highlights-panel.tctf .highlights li { transition: none; }
}

/* The bug/feature mailbox: one free-text box, filed as a GitHub issue. */
#mailbox-panel {
  width: 300px;
}
.mailbox {
  display: flex;
  flex-direction: column;
  gap: 8px;
  font-size: 13px;
}
.mailbox textarea {
  resize: vertical;
  background: #12141a;
  border: 1px solid var(--line);
  color: var(--text);
  padding: 6px 10px;
  border-radius: 8px;
  font: inherit;
}
.mailbox .btn {
  align-self: flex-end;
  background: var(--accent);
  border-color: var(--accent);
  color: #fff;
}
.mailbox .hint {
  margin: 0;
}
.mailbox .hint.bad {
  color: var(--red);
}
/* The newest few notes under the box, with the Tasks tab's status words. */
.mailbox-recent {
  margin-top: 10px;
  border-top: 1px solid var(--line);
  padding-top: 8px;
  font-size: 12px;
}
.mailbox-recent .hint {
  margin: 0 0 4px;
}
.mailbox-recent ol {
  list-style: none;
  margin: 0;
  padding: 0;
}
.mailbox-recent li {
  padding: 3px 0;
  overflow-wrap: anywhere;
}
/* A pending plan is clickable to expand in place - the mailbox popup's own
 * Approve/Reject, replacing the old separate Approvals tab. */
.mailbox-recent li.expandable {
  cursor: pointer;
}
.mailbox-recent .caret {
  display: inline-block;
  width: 10px;
  color: var(--muted);
}
.mailbox-recent .plan {
  background: #12141a;
  border-radius: 6px;
  padding: 8px;
  margin: 6px 0 2px;
  cursor: default;
}
.mailbox-recent .run {
  color: var(--amber);
}
.mailbox-recent .done {
  color: var(--green);
}
.mailbox-recent .bad {
  color: #ffb3b3;
}
.mailbox-recent a {
  color: var(--blue);
}

/* Mailbox skin: a TCTF terminal from Bungie's Oni, matching the answering
 * machine - near-black panel, cold teal hairlines, chamfered corners, a faint
 * scanline and an orange TRANSMIT-style Send. Scoped to #mailbox-panel; ids
 * and behaviour are unchanged. Palette vars are shared with #voicemail-panel. */
#mailbox-panel.mbx {
  width: 320px;
  padding: 0 14px 14px;
  color: var(--cl-label);
  background:
    repeating-linear-gradient(0deg, rgba(63, 208, 201, 0.05) 0 1px, transparent 1px 3px),
    linear-gradient(var(--cl-bg2), var(--cl-bg));
  border: 1px solid var(--cl-dim);
  border-radius: 0;
  clip-path: polygon(14px 0, 100% 0, 100% calc(100% - 14px), calc(100% - 14px) 100%, 0 100%, 0 14px);
  font-family: ui-monospace, Menlo, monospace;
}
#mailbox-panel .mbx-title {
  align-items: center;
  margin: 0 -14px 10px;
  padding: 9px 40px 8px 18px;
  flex-wrap: wrap;
  row-gap: 2px;
  border-bottom: 1px solid var(--cl-dim);
  background: rgba(63, 208, 201, 0.06);
}
#mailbox-panel .mbx-title b {
  font: 700 14px / 1 ui-monospace, Menlo, monospace;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--cl-teal);
  text-shadow: 0 0 6px rgba(63, 208, 201, 0.5);
}
#mailbox-panel .mbx-title .cl-kanji {
  margin-left: 6px;
  font-size: 10px;
  color: var(--cl-dim);
  letter-spacing: 0.2em;
}
#mailbox-panel .mbx-title .hint {
  flex-basis: 100%;
  font-size: 10px;
}
#mailbox-panel .mbx-title .hint:empty { display: none; }
#mailbox-panel .mbx-glyph { flex: none; }
#mailbox-panel .mbx-close {
  position: absolute;
  top: 6px;
  right: 8px;
  width: 22px;
  height: 22px;
  padding: 0;
  font: 400 18px / 1 ui-monospace, Menlo, monospace;
  color: var(--cl-teal);
  background: transparent;
  border: 1px solid var(--cl-dim);
  border-radius: 0;
  cursor: pointer;
}
#mailbox-panel .mbx-close:hover { border-color: var(--cl-teal); background: rgba(63, 208, 201, 0.12); }
#mailbox-panel .mbx-section {
  margin: 0 0 6px;
  font: 700 11px / 1.2 ui-monospace, Menlo, monospace;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--cl-teal);
  display: flex;
  align-items: center;
  gap: 6px;
}
#mailbox-panel .mbx-section::before { content: "//"; color: var(--cl-dim); }
#mailbox-panel .mbx-section::after {
  content: "";
  flex: 1;
  height: 1px;
  background: linear-gradient(90deg, var(--cl-dim), transparent);
}
#mailbox-panel .mailbox textarea {
  background: #060a0d;
  color: var(--cl-label);
  border: 1px solid var(--cl-dim);
  border-radius: 0;
  font: 13px / 1.45 ui-monospace, Menlo, monospace;
}
#mailbox-panel .mailbox textarea::placeholder { color: #6f8a8e; }
#mailbox-panel .mailbox textarea:focus { outline: 1px solid var(--cl-teal); border-color: var(--cl-teal); }
#mailbox-panel .mailbox .btn {
  min-width: 96px;
  padding: 6px 18px;
  font: 700 12px / 1.2 ui-monospace, Menlo, monospace;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: #fff;
  background: linear-gradient(#ff6a3a, #c23a14);
  border: 0;
  border-radius: 0;
  clip-path: polygon(8px 0, 100% 0, 100% calc(100% - 8px), calc(100% - 8px) 100%, 0 100%, 0 8px);
  cursor: pointer;
}
#mailbox-panel .mailbox .btn:hover { filter: brightness(1.12); }
#mailbox-panel .mailbox .btn:active { transform: translateY(1px); }
#mailbox-panel .mailbox .btn:disabled { filter: grayscale(0.7); }
#mailbox-panel .hint {
  color: #7fa3a6;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  font-size: 10px;
}
#mailbox-panel .mailbox .hint.bad,
#mailbox-panel .mailbox-recent .bad { color: var(--cl-hot); font-weight: 700; }
#mailbox-panel .mailbox-recent {
  margin-top: 12px;
  border-top: 0;
  padding-top: 0;
}
#mailbox-panel .mailbox-recent li {
  padding: 5px 6px 5px 10px;
  margin-bottom: 2px;
  color: var(--cl-label);
  border-bottom: 1px solid rgba(31, 111, 108, 0.45);
  border-left: 2px solid var(--cl-dim);
}
#mailbox-panel .mailbox-recent li.hint { border-left: 0; text-align: center; }
#mailbox-panel .mailbox-recent li:has(.done) { border-left-color: var(--cl-teal); }
#mailbox-panel .mailbox-recent li:has(.run) { border-left-color: var(--cl-hot); }
#mailbox-panel .mailbox-recent .done { color: var(--cl-teal); font-weight: 700; }
#mailbox-panel .mailbox-recent .run { color: var(--cl-hot); font-weight: 700; }
#mailbox-panel .mailbox-recent .caret { color: var(--cl-dim); }
#mailbox-panel .mailbox-recent .plan {
  background: #060a0d;
  border: 1px solid var(--cl-dim);
  border-radius: 0;
  color: var(--cl-label);
}
#mailbox-panel .mailbox-recent a { color: var(--cl-teal); }
#mailbox-panel button:focus-visible,
#mailbox-panel textarea:focus-visible,
#mailbox-panel a:focus-visible,
#mailbox-panel li.expandable:focus-visible {
  outline: 1px solid var(--cl-teal);
  outline-offset: 2px;
}

/* ---- the OpenClaw console (docs/CONSOLE.md) ----
   A drawer at the bottom of the page. It pushes the map up rather than covering
   it, so the rings on the machines a task touches stay on screen. */
body.has-console {
  display: flex;
  flex-direction: column;
}
body.has-console > header {
  flex: none;
}
body.has-console > main {
  flex: 1 1 auto;
  height: auto;
  min-height: 0;
}
.console {
  --con-bg: #0b0d12;
  --con-mono: ui-monospace, SFMono-Regular, "JetBrains Mono", Menlo, Consolas, monospace;
  --console-h: 50vh;
  --bar-h: 30px;
  position: relative;
  flex: none;
  height: var(--bar-h);
  display: flex;
  flex-direction: column;
  background:
    linear-gradient(to bottom, #7c6cff14, transparent 120px),
    var(--con-bg);
  border-top: 1px solid var(--accent);
  box-shadow: 0 -10px 40px -16px #7c6cff88;
  font: 13px / 1.5 var(--con-mono);
  color: #d9dbe3;
  transition:
    height 0.18s cubic-bezier(0.2, 0.8, 0.2, 1),
    border-color 0.9s ease-out,
    box-shadow 0.9s ease-out;
  /* Above every floating panel (Winamp, video, the developer panel), however
     often they are raised (app.js keeps them under CONSOLE_Z); only modal
     dialogs and toasts sit higher. */
  z-index: 1000;
}
.console.open {
  height: var(--console-h);
}
.console.dragging {
  transition: none;
  user-select: none;
}
.con-grip {
  position: absolute;
  top: -4px;
  left: 0;
  right: 0;
  height: 8px;
  cursor: ns-resize;
  display: none;
}
.console.open .con-grip {
  display: block;
}
.con-bar {
  flex: none;
  height: var(--bar-h);
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 0 14px;
  background: none;
  border: 0;
  border-bottom: 1px solid transparent;
  color: var(--muted);
  font: inherit;
  font-size: 12px;
  text-align: left;
  cursor: pointer;
}
a.con-bar {
  text-decoration: none;
}
.console.open .con-bar {
  border-bottom-color: #1d212b;
}
.con-bar:hover {
  color: var(--text);
}
.con-bar kbd {
  font: 700 12px / 1 var(--con-mono);
  color: var(--accent);
  border: 1px solid #3a3470;
  border-bottom-width: 2px;
  border-radius: 4px;
  padding: 2px 6px;
  background: #15132a;
}
.con-bar b {
  color: var(--text);
  letter-spacing: 1.5px;
  text-transform: uppercase;
  font-size: 11px;
}
.con-agent {
  color: #6b7080;
}
/* Feature requests waiting on the owner: a quiet amber chip, never blinking.
   A click prints them with /req. */
.con-req {
  display: inline-flex;
  align-items: center;
  padding: 1px 7px;
  border: 1px solid color-mix(in srgb, var(--amber) 45%, transparent);
  border-radius: 4px;
  color: var(--amber);
  background: color-mix(in srgb, var(--amber) 8%, transparent);
  font: 600 11px / 1.4 var(--con-mono);
  cursor: pointer;
}
.con-req:hover {
  background: color-mix(in srgb, var(--amber) 16%, transparent);
}
.con-req[hidden] {
  display: none;
}
.con-sum {
  margin-left: auto;
  color: #b9bccb;
}
.console.busy .con-sum {
  color: var(--amber);
}
.con-flash:empty {
  display: none;
}
.con-flash {
  padding: 1px 8px;
  border-radius: 4px;
  font-weight: 700;
  animation: con-flash 1s ease-in-out 3;
}
.console.flash-done .con-flash {
  color: #0b0d12;
  background: var(--green);
}
.console.flash-bad .con-flash {
  color: #0b0d12;
  background: var(--red);
}
.console.flash-ask .con-flash {
  color: #0b0d12;
  background: var(--amber);
}
@keyframes con-flash {
  50% {
    opacity: 0.45;
  }
}

/* Dormant: while Enter Oni is due (at most every 15 minutes, see app.js) the
   shut bar is only the word ONI at the bottom left: no key cap, counts, hint or
   flashes, no accent line, no glow, no purple. It is still the handle: a click
   or ~ opens it and plays Enter Oni, and the colour and the rest of the bar fade
   in from then on. */
.console.oni-dormant {
  background: var(--con-bg);
  border-top-color: #232733;
  box-shadow: none;
}
.console.oni-dormant:not(.open) .con-bar > :not(b) {
  display: none;
}
.console.oni-dormant .con-bar b {
  color: #8a8fa0;
  letter-spacing: 3px;
}
.console.oni-dormant .con-bar:hover b {
  color: #c9ccd6;
}
/* The one thing besides the name a dormant bar still shows - and a shipped
   feature request, for as long as it is announced. */
.console.oni-dormant .con-bar > .con-req:not([hidden]) {
  display: inline-flex;
}
.console.oni-dormant .con-bar > .con-flash.shipped:not(:empty) {
  display: inline;
}
@media (prefers-reduced-motion: reduce) {
  /* It wakes at once. */
  .console {
    transition: height 0.18s cubic-bezier(0.2, 0.8, 0.2, 1);
  }
}
.con-hint {
  display: none;
  color: #555b6b;
}
.console.open .con-hint {
  display: inline;
}
.con-body {
  flex: 1 1 auto;
  min-height: 0;
  display: none;
  grid-template-columns: minmax(0, 1fr) 320px;
}
.console.open .con-body {
  display: grid;
}
.con-main {
  position: relative;
  display: flex;
  flex-direction: column;
  min-height: 0;
  min-width: 0;
}
.con-log {
  flex: 1 1 auto;
  overflow-y: auto;
  padding: 10px 16px 4px;
  scrollbar-color: #2b2f3a transparent;
}
.con-empty {
  color: #6b7080;
  margin: 8px 0;
}
.con-empty::before {
  content: "# ";
  color: var(--accent);
}

/* One task = one block: the prompt echoed like a shell line, then its own
   output. The rule on the left is the task's colour. */
.con-task {
  --hue: var(--accent);
  border-left: 2px solid color-mix(in srgb, var(--hue) 55%, transparent);
  padding: 4px 0 6px 12px;
  margin: 0 0 10px;
  border-radius: 0 4px 4px 0;
}
.con-task.s-running,
.con-task.s-queued,
.con-task.s-pending_approval {
  border-left-color: var(--hue);
  background: linear-gradient(to right, color-mix(in srgb, var(--hue) 7%, transparent), transparent 60%);
}
.con-task.pinged {
  animation: con-ping 1.2s ease-out;
}
@keyframes con-ping {
  0% {
    background-color: color-mix(in srgb, var(--hue) 30%, transparent);
  }
}
.con-task .con-head {
  display: flex;
  align-items: baseline;
  gap: 8px;
  flex-wrap: wrap;
}
.con-who {
  color: var(--hue);
  font-weight: 700;
}
.con-caret {
  color: #555b6b;
}
.con-cmd {
  color: #f1f1f4;
  flex: 1 1 240px;
  min-width: 0;
  overflow-wrap: anywhere;
}
.s-rejected .con-cmd {
  text-decoration: line-through;
  color: #6b7080;
}
.con-meta {
  display: flex;
  align-items: baseline;
  gap: 8px;
  color: #6b7080;
  font-size: 12px;
  margin-left: auto;
}
.con-meta > b {
  color: #8a8fa0;
}
.con-el {
  font-variant-numeric: tabular-nums;
  color: #8a8fa0;
}
.con-tag {
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.8px;
  font-weight: 700;
}
.con-tag.t-run {
  color: var(--amber);
}
.con-tag.t-done {
  color: var(--green);
}
.con-tag.t-bad {
  color: var(--red);
}
.s-rejected .con-tag,
.s-cancelled .con-tag {
  color: #6b7080;
}
.con-pc {
  font: 600 11px / 1 var(--con-mono);
  color: var(--hue);
  background: color-mix(in srgb, var(--hue) 12%, transparent);
  border: 1px solid color-mix(in srgb, var(--hue) 40%, transparent);
  border-radius: 3px;
  padding: 2px 5px;
  cursor: pointer;
}
.con-pc:hover {
  background: color-mix(in srgb, var(--hue) 25%, transparent);
}
/* A braille spinner: no image, and it reads as a terminal's. */
.con-spin {
  font-style: normal;
  display: inline-block;
  width: 1.1em;
}
.con-spin::before {
  content: "⠋";
  animation: con-spin 0.9s steps(1) infinite;
}
@keyframes con-spin {
  0% { content: "⠋"; }
  10% { content: "⠙"; }
  20% { content: "⠹"; }
  30% { content: "⠸"; }
  40% { content: "⠼"; }
  50% { content: "⠴"; }
  60% { content: "⠦"; }
  70% { content: "⠧"; }
  80% { content: "⠇"; }
  90% { content: "⠏"; }
}
.con-line {
  display: flex;
  gap: 10px;
  color: #a9adbb;
  font-size: 12px;
  padding-left: 2px;
}
.con-line time {
  color: #4f5566;
  flex: none;
  font-variant-numeric: tabular-nums;
}
.con-line b {
  color: var(--hue);
}
.con-reply {
  margin: 6px 0 2px;
  padding: 8px 12px;
  background: #12151c;
  border: 1px solid #1f2430;
  border-radius: 4px;
  color: #e6e7ec;
  max-width: 110ch;
}
.con-reply p {
  margin: 0 0 6px;
}
.con-reply p:last-child {
  margin-bottom: 0;
}
.con-reply ul {
  margin: 0 0 6px;
  padding-left: 18px;
}
.con-reply code {
  color: #c9c2ff;
  background: #1b1930;
  border-radius: 3px;
  padding: 0 4px;
}
.con-reply.bad {
  border-color: #4a2328;
  color: #ffb3b3;
}
.con-plan {
  margin: 6px 0;
  padding: 8px 12px;
  border: 1px dashed #5b4a1f;
  border-radius: 4px;
  color: #e8dcc0;
}
.con-plan small {
  display: block;
  color: var(--amber);
  text-transform: uppercase;
  letter-spacing: 0.8px;
  font-size: 10px;
}
.console .ops {
  margin: 6px 0 2px;
}
.console .ops button {
  font: 600 12px / 1 var(--con-mono);
  padding: 6px 12px;
  border-radius: 4px;
}
.console .ops button[data-approve] {
  background: #173a27;
  border-color: #2c6b47;
  color: #bff5d6;
}

/* The prompt: a shell line, not a form. */
.con-prompt {
  flex: none;
  display: flex;
  align-items: flex-start;
  gap: 8px;
  flex-wrap: wrap;
  padding: 8px 16px 10px;
  border-top: 1px solid #1d212b;
  background: #090b0f;
}
.con-prompt label {
  color: var(--accent);
  font-weight: 700;
  white-space: nowrap;
}
.con-prompt label span {
  color: #8a8fa0;
  font-weight: 400;
}
.con-prompt label {
  line-height: 28px;
}
.con-prompt textarea {
  flex: 1;
  height: 28px;
  max-height: calc(8 * 1.4em + 8px);
  resize: none;
  overflow-y: auto;
  scrollbar-width: thin;
  min-width: 200px;
  background: none;
  border: 0;
  outline: 0;
  color: #f1f1f4;
  font: 14px / 1.4 var(--con-mono);
  caret-color: var(--accent);
  padding: 4px 0;
}
.con-prompt textarea::placeholder {
  color: #474c5a;
}
/* Fast / Smart: a segmented switch beside the prompt. One highlight slides
 * under the active half: amber for Fast, violet for Smart. 28px tall, the
 * prompt input's height, so the two share a line. */
.con-mode {
  --mode: var(--amber);
  flex: none;
  position: relative;
  display: inline-grid;
  grid-template-columns: 1fr 1fr;
  height: 28px;
  padding: 2px;
  border: 1px solid #252a35;
  border-radius: 4px;
  background: #0e1117;
  box-shadow: inset 0 1px 0 #00000080;
}
.con-mode.smart {
  --mode: #a397ff;
}
.con-mode::before {
  content: "";
  position: absolute;
  inset: 2px auto 2px 2px;
  width: calc(50% - 2px);
  border: 1px solid color-mix(in srgb, var(--mode) 55%, transparent);
  border-radius: 3px;
  background: color-mix(in srgb, var(--mode) 16%, #0e1117);
  box-shadow: 0 0 10px -2px color-mix(in srgb, var(--mode) 45%, transparent);
  transition:
    transform 180ms cubic-bezier(0.3, 0.7, 0.4, 1),
    border-color 180ms,
    background-color 180ms,
    box-shadow 180ms;
}
.con-mode.smart::before {
  transform: translateX(100%);
}
/* Until "power overwhelming" is typed, only Fast shows. */
.con-mode.locked {
  grid-template-columns: 1fr;
}
.con-mode.locked::before {
  width: calc(100% - 4px);
}
.con-mode.locked button[data-mode="smart"] {
  display: none;
}
.con-mode button {
  position: relative;
  padding: 0 10px;
  border: 0;
  border-radius: 3px;
  background: none;
  color: #5d6272;
  font: 600 11px / 1 var(--con-mono);
  letter-spacing: 0.3px;
  white-space: nowrap;
  cursor: pointer;
  transition: color 180ms;
}
.con-mode button:hover {
  color: #c9ccd6;
}
.con-mode button[aria-pressed="true"] {
  color: var(--mode);
  cursor: default;
}
.con-mode button:focus-visible {
  outline: 1px solid var(--mode);
  outline-offset: 1px;
}
@media (prefers-reduced-motion: reduce) {
  .con-mode::before,
  .con-mode button {
    transition: none;
  }
}
/* Which mode a task ran in: a small quiet badge in the row's meta. */
.con-mode-mark {
  align-self: center;
  font: normal 10px / 1 var(--con-mono);
  padding: 2px 3px;
  border: 1px solid #252a35;
  border-radius: 3px;
  opacity: 0.6;
  filter: saturate(0.6);
  cursor: default;
}
.con-mode-mark.m-fast {
  border-color: color-mix(in srgb, var(--amber) 25%, transparent);
}
.con-mode-mark.m-smart {
  border-color: color-mix(in srgb, #a397ff 30%, transparent);
  color: #a397ff;
}
/* Smart's mark: the StarCraft "SC" emblem (starcraft.blizzard.com's favicon),
 * painted in the text colour. */
.sc-icon {
  display: inline-block;
  width: 22px;
  height: 8px;
  vertical-align: 0;
  background: currentColor;
  -webkit-mask: url("/static/img/starcraft.png") center / contain no-repeat;
  mask: url("/static/img/starcraft.png") center / contain no-repeat;
}
.con-mode button .sc-icon {
  margin-right: 5px;
}
.con-mode button[aria-pressed="true"] .sc-icon {
  color: #a397ff;
}
.con-fixer {
  align-self: center;
  font: normal 10px / 1 var(--con-mono);
  padding: 2px 5px;
  border: 1px solid #2b3240;
  border-radius: 3px;
  color: #8fa3b8;
  cursor: default;
}
.con-requests {
  margin: 8px 0;
  padding: 8px 12px;
  border-left: 2px solid var(--amber);
  color: #c9ccd6;
}
.con-requests p {
  margin: 4px 0;
}
.con-note-h {
  color: var(--amber);
  font-weight: 700;
}
.con-request {
  margin: 8px 0 12px;
}
.con-request-h {
  color: #b9bccb;
}
.con-request-h a {
  color: inherit;
}
.con-task:hover .con-mode-mark {
  opacity: 0.9;
  filter: none;
}
.con-err:empty {
  display: none;
}
.con-err {
  flex-basis: 100%;
  color: var(--red);
  font-size: 12px;
}

/* "copied · 42 chars": a status line by the prompt, gone after a moment. */
.con-copied {
  position: absolute;
  right: 16px;
  bottom: 50px;
  padding: 3px 8px;
  border: 1px solid #3ddc8455;
  border-radius: 3px;
  background: #090b0fe6;
  color: var(--green);
  font: 600 11px / 1.3 var(--con-mono);
  letter-spacing: 0.5px;
  pointer-events: none;
  opacity: 0;
  transform: translateY(3px);
  transition:
    opacity 0.3s ease-out,
    transform 0.3s ease-out;
  z-index: 2;
}
.con-copied::before {
  content: "✓ ";
}
.con-copied.shown {
  opacity: 1;
  transform: none;
  transition-duration: 0.08s;
}
.con-copied.bad {
  border-color: #ff5d5d55;
  color: var(--red);
}
.con-copied.bad::before {
  content: "✗ ";
}

/* The rail: one lane per slot the server allows at once. */
.con-rail {
  border-left: 1px solid #1d212b;
  padding: 10px 12px;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: 8px;
  background: #090b0f;
}
.con-rail-h {
  color: #6b7080;
  text-transform: uppercase;
  letter-spacing: 1.5px;
  font-size: 10px;
}
.con-rail-h b {
  color: #b9bccb;
}
.con-lane {
  --hue: var(--accent);
  border: 1px solid color-mix(in srgb, var(--hue) 45%, #1d212b);
  border-left: 3px solid var(--hue);
  border-radius: 4px;
  padding: 7px 9px;
  background: color-mix(in srgb, var(--hue) 6%, #0d1016);
  cursor: pointer;
  font-size: 12px;
}
.con-lane .h {
  display: flex;
  gap: 8px;
  align-items: baseline;
}
.con-lane .h b {
  color: var(--hue);
}
.con-lane .h .con-el {
  margin-left: auto;
}
.con-lane .cmd {
  color: #e6e7ec;
  margin: 3px 0;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.con-lane .pcs {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
}
.con-lane .pcs:empty {
  display: none;
}
.con-lane .last {
  color: #8a8fa0;
  margin-top: 4px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.con-bar-pct {
  height: 3px;
  background: #1d212b;
  border-radius: 2px;
  margin-top: 6px;
  overflow: hidden;
}
.con-bar-pct i {
  display: block;
  height: 100%;
  background: var(--hue);
  transition: width 0.4s;
}
.con-lane.idle {
  border: 1px dashed #262a35;
  background: none;
  color: #3f4452;
  text-align: center;
  cursor: default;
  padding: 12px;
}

/* On the map: the machines a live task is working on. */
.ov.ai-ring {
  transform: none;
  border: 2px solid var(--hue);
  border-radius: 4px;
  box-shadow: 0 0 10px var(--hue), inset 0 0 6px var(--hue);
  animation: ai-ring 1.6s ease-in-out infinite;
}
@keyframes ai-ring {
  50% {
    opacity: 0.35;
  }
}
.ov.ai-chip {
  transform: translate(0, -50%);
  font: 700 10px / 1 ui-monospace, SFMono-Regular, Menlo, monospace;
  background: #0b0d12e6;
  color: var(--hue);
  border: 1px solid var(--hue);
  border-radius: 3px;
  padding: 2px 4px;
}

@media (max-width: 980px) {
  /* The page scrolls on a phone, so the console floats over it instead. */
  body.has-console {
    display: block;
    padding-bottom: 30px;
  }
  .console {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
  }
  .console.open {
    height: min(var(--console-h), 75vh);
  }
  .con-body {
    grid-template-columns: 1fr;
    grid-template-rows: auto minmax(0, 1fr);
  }
  .con-rail {
    order: -1;
    flex-direction: row;
    overflow-x: auto;
    border-left: 0;
    border-bottom: 1px solid #1d212b;
    max-height: 120px;
  }
  .con-lane {
    min-width: 220px;
  }
  .con-hint,
  .con-agent {
    display: none !important;
  }
}

/* ---- Enter Oni: the console's entrance (playIntro in app.js), ~7 s ----
   Transforms and opacity only, so the TV and a mid-range laptop both keep up:
   a beam along the console's edge, a bloom rising from it, a scan line through
   the map that locks onto each PC, and a card over the transcript. The timings
   below follow the constants in app.js: light 0-2.5 s, the name 0.25-1.15 s,
   status lines from 1.3 s, the greeting from 2.45 s, held until 4.6 s, gone by 5 s. */
.console.waking {
  transition-duration: 0.32s;
}
.oni-glow {
  position: absolute;
  left: 0;
  right: 0;
  top: 0;
  height: 0;
  pointer-events: none;
  z-index: 3;
}
.oni-beam {
  position: absolute;
  left: 0;
  right: 0;
  top: -2px;
  height: 3px;
  background: linear-gradient(90deg, transparent, #7c6cff 18%, #4fd1c5 42%, #fff 50%, #ff7ab6 58%, #7c6cff 82%, transparent);
  box-shadow: 0 0 12px #7c6cff, 0 0 30px #4fd1c5aa;
  transform: scaleX(0);
  will-change: transform, opacity;
  animation: oni-beam 2.1s cubic-bezier(0.2, 0.8, 0.2, 1) both;
}
@keyframes oni-beam {
  0% {
    transform: scaleX(0);
    opacity: 1;
  }
  24% {
    transform: scaleX(1);
    opacity: 1;
  }
  55% {
    transform: scaleX(1);
    opacity: 0.8;
  }
  100% {
    transform: scaleX(1);
    opacity: 0;
  }
}
.oni-bloom {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 70vh;
  background:
    radial-gradient(ellipse 55% 100% at 50% 100%, #7c6cff80, #4fd1c52e 45%, transparent 72%),
    radial-gradient(ellipse 25% 45% at 50% 100%, #ff7ab64d, transparent 70%);
  transform-origin: 50% 100%;
  will-change: transform, opacity;
  animation: oni-bloom 2.5s ease-out both;
}
@keyframes oni-bloom {
  0% {
    opacity: 0;
    transform: scaleY(0.15);
  }
  20% {
    opacity: 1;
    transform: scaleY(1);
  }
  45% {
    opacity: 0.85;
    transform: scaleY(1.04);
  }
  100% {
    opacity: 0;
    transform: scaleY(1.1);
  }
}

/* The map: a line rising from the console, and each PC locking on as it passes. */
.oni-scan {
  position: absolute;
  inset: 0;
  overflow: hidden;
  pointer-events: none;
}
.oni-scan::before {
  content: "";
  position: absolute;
  inset: 0;
  background: repeating-linear-gradient(to bottom, #7c6cff1a 0 1px, transparent 1px 4px);
  animation: oni-fade 2.4s ease-out both;
}
@keyframes oni-fade {
  0%,
  100% {
    opacity: 0;
  }
  20% {
    opacity: 1;
  }
}
/* 30% of the stage tall, bright leading edge on top. -333% puts that edge at
   the top of the map, so 2.02 s linear = the 1.55 s sweep in app.js. */
.oni-sweep {
  position: absolute;
  left: 0;
  right: 0;
  top: 100%;
  height: 30%;
  background: linear-gradient(to top, transparent, #7c6cff1c 55%, #4fd1c55e 95%, #e9fffc 99%, transparent);
  will-change: transform;
  animation: oni-sweep 2.02s linear 0.2s both;
}
@keyframes oni-sweep {
  from {
    transform: translateY(0);
  }
  to {
    transform: translateY(-433%);
  }
}
.oni-lock {
  --hue: var(--accent);
  --b: linear-gradient(var(--hue), var(--hue));
  position: absolute;
  border-radius: 3px;
  background:
    var(--b) left top / 32% 2px,
    var(--b) left top / 2px 36%,
    var(--b) right top / 32% 2px,
    var(--b) right top / 2px 36%,
    var(--b) left bottom / 32% 2px,
    var(--b) left bottom / 2px 36%,
    var(--b) right bottom / 32% 2px,
    var(--b) right bottom / 2px 36%,
    color-mix(in srgb, var(--hue) 14%, transparent);
  background-repeat: no-repeat;
  box-shadow: 0 0 14px color-mix(in srgb, var(--hue) 70%, transparent);
  will-change: transform, opacity;
  animation: oni-lock 1.8s ease-out both;
}
@keyframes oni-lock {
  0% {
    opacity: 0;
    transform: scale(1.7);
  }
  12% {
    opacity: 1;
    transform: scale(1);
  }
  70% {
    opacity: 1;
    transform: scale(1);
  }
  100% {
    opacity: 0;
    transform: scale(1);
  }
}

/* The console: its name out of noise, a few boot lines, then the greeting. */
.oni-card {
  position: absolute;
  inset: 0;
  z-index: 2;
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding: 12px 28px;
  overflow: hidden;
  pointer-events: none;
  contain: strict;
  background:
    radial-gradient(ellipse 60% 80% at 22% 45%, #7c6cff26, transparent 70%),
    var(--con-bg);
  animation: oni-in 0.3s ease-out both;
  transition: opacity 0.4s ease-in;
}
.oni-card.out,
.oni-glow.out,
.oni-scan.out {
  opacity: 0;
  transition: opacity 0.4s ease-in;
}
/* The card's own oni-in holds its opacity (fill both), which a transition
   cannot move: it fades out by animation instead, or it would just vanish. */
.oni-card.out {
  animation: oni-out 0.4s ease-in both;
}
@keyframes oni-in {
  from {
    opacity: 0;
  }
}
@keyframes oni-out {
  from {
    opacity: 1;
  }
  to {
    opacity: 0;
  }
}
.oni-mark {
  align-self: flex-start;
  font: 800 clamp(34px, 9vh, 88px) / 1 var(--con-mono);
  letter-spacing: 0.35em;
  background: linear-gradient(100deg, #7c6cff 0%, #4fd1c5 35%, #ffffff 50%, #ff7ab6 65%, #7c6cff 100%) 100% 0 / 260% 100%;
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  filter: drop-shadow(0 0 18px #7c6cff80);
  animation: oni-shine 1.55s ease-in-out 1.1s both;
}
@keyframes oni-shine {
  to {
    background-position: 0 0;
  }
}
.oni-boot {
  list-style: none;
  margin: 14px 0 0;
  padding: 0;
  font-size: 12px;
  color: #8a8fa0;
}
.oni-boot li {
  display: grid;
  grid-template-columns: 64px minmax(0, 1fr) auto;
  gap: 10px;
  max-width: 460px;
  animation: oni-line 0.25s ease-out both;
}
@keyframes oni-line {
  from {
    opacity: 0;
    transform: translateX(-8px);
  }
}
.oni-boot b {
  color: var(--accent);
  font-weight: 700;
}
.oni-boot span {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.oni-boot i {
  font-style: normal;
  font-weight: 700;
  color: var(--green);
}
.oni-say {
  margin: 16px 0 0;
  min-height: 1.4em;
  font: 17px / 1.4 var(--con-mono);
  color: #f1f1f4;
}
.oni-say i {
  display: inline-block;
  width: 0.6em;
  height: 1.05em;
  margin-left: 2px;
  vertical-align: -0.15em;
  background: #4fd1c5;
  box-shadow: 0 0 8px #4fd1c5;
  animation: oni-blink 0.9s steps(1) infinite;
}
@keyframes oni-blink {
  50% {
    opacity: 0;
  }
}

/* A known visitor, greeted by name once per browser (typeGreeting in app.js):
   "Hello <name>." with the name decoding out of glyphs, a rose bloom
   as it settles, then a second line. Held to 6.6 s rather than 4.6 s. */
.oni-card.guest {
  background:
    radial-gradient(ellipse 60% 80% at 22% 45%, #7c6cff26, transparent 70%),
    radial-gradient(ellipse 45% 70% at 60% 85%, #ff7ab61a, transparent 70%),
    var(--con-bg);
}
.oni-name {
  position: relative;
  display: inline-block;
  font-weight: 800;
  letter-spacing: 0.04em;
  background: linear-gradient(100deg, #ff7ab6 0%, #ffffff 30%, #4fd1c5 50%, #7c6cff 70%, #ff7ab6 100%) 100% 0 / 260% 100%;
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}
.oni-name::after {
  content: "";
  position: absolute;
  inset: -8px -12px;
  border-radius: 8px;
  background: radial-gradient(ellipse at center, #ff7ab655, transparent 70%);
  opacity: 0;
  pointer-events: none;
}
.oni-name.lit {
  animation: oni-shine 1.1s ease-in-out both;
}
.oni-name.lit::after {
  animation: oni-name-lit 1.25s ease-out both;
}
@keyframes oni-name-lit {
  0% {
    opacity: 0;
    transform: scale(0.6);
  }
  25% {
    opacity: 1;
    transform: scale(1.1);
  }
  100% {
    opacity: 0.35;
    transform: scale(1);
  }
}
.oni-say.oni-after {
  margin-top: 6px;
  font-size: 15px;
  color: #c9cbd6;
}
.oni-after i,
.oni-card.second .oni-hello i {
  display: none;
}
.oni-card.second .oni-after i {
  display: inline-block;
}
.oni-bloom-guest {
  height: 55vh;
  background:
    radial-gradient(ellipse 45% 100% at 50% 100%, #ff7ab666, #7c6cff26 45%, transparent 72%);
  animation-duration: 2.1s;
}

/* Reduced motion: no beam, no scan, no typing; the card and a soft glow fade in
   and out. */
.oni-card.calm {
  animation-duration: 0.4s;
}
.oni-card.calm *,
.oni-card.calm .oni-boot li {
  animation: none;
}
.oni-card.calm .oni-mark {
  background-position: 50% 0;
}
.oni-glow.calm .oni-beam {
  display: none;
}
.oni-glow.calm .oni-bloom {
  transform: none;
  animation: oni-fade 5s ease-in-out both;
}
@media (prefers-reduced-motion: reduce) {
  .oni-scan {
    display: none;
  }
}
.oni-card.calm .oni-name::after {
  opacity: 0.3;
}

/* "power overwhelming" (playOverwhelming in app.js): Smart switches on inside
   a psionic storm. "Cheat enabled." in the game's yellow, lightning across the
   transcript, the words decoding out of noise and blazing, boot lines for the
   bigger model, the SC emblem lit as the Archon line types. Held to 4.6 s. */
.po-card {
  background:
    radial-gradient(ellipse 70% 90% at 30% 45%, #a397ff33, transparent 70%),
    radial-gradient(ellipse 40% 60% at 75% 70%, #4f8cff22, transparent 70%),
    var(--con-bg);
  animation: oni-in 0.2s ease-out both, po-shake 0.45s linear 0.3s both;
}
@keyframes po-shake {
  0%, 100% { transform: none; }
  15% { transform: translate(-3px, 1px); }
  30% { transform: translate(3px, -2px); }
  45% { transform: translate(-2px, 2px); }
  60% { transform: translate(2px, 0); }
  80% { transform: translate(-1px, -1px); }
}
.po-storm {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
}
.po-storm path {
  fill: none;
  stroke: #e6e1ff;
  stroke-width: 1.6px;
  vector-effect: non-scaling-stroke;
  stroke-linejoin: bevel;
  filter: drop-shadow(0 0 4px #a397ff) drop-shadow(0 0 12px #6d5cff);
  opacity: 0;
  animation: po-bolt 0.32s steps(4, end) both;
}
@keyframes po-bolt {
  0% { opacity: 0; }
  25% { opacity: 1; }
  50% { opacity: 0.2; }
  75% { opacity: 0.9; }
  100% { opacity: 0; }
}
.po-cheat {
  position: relative;
  margin: 0 0 10px;
  font: 600 14px / 1.3 var(--con-mono);
  color: #e8e84a;
  text-shadow: 0 0 8px #e8e84a88;
  animation: oni-line 0.2s ease-out both;
}
.po-words {
  position: relative;
  align-self: flex-start;
  min-height: 1em;
  font: 800 clamp(26px, 6.5vh, 64px) / 1 var(--con-mono);
  letter-spacing: 0.14em;
  white-space: nowrap;
  color: #cfc8ff;
  text-shadow: 0 0 14px #a397ff;
}
.po-words.lit {
  background: linear-gradient(100deg, #a397ff 0%, #6fb6ff 35%, #ffffff 50%, #c9a6ff 65%, #a397ff 100%) 100% 0 / 260% 100%;
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  text-shadow: none;
  filter: drop-shadow(0 0 16px #a397ffaa);
  animation: oni-shine 1.2s ease-in-out both, po-surge-text 1.2s ease-out both;
}
@keyframes po-surge-text {
  0% { transform: scale(1.08); filter: drop-shadow(0 0 40px #ffffff); }
  100% { transform: none; filter: drop-shadow(0 0 16px #a397ffaa); }
}
.po-boot b {
  color: #a397ff;
}
.po-say {
  position: relative;
  color: #e9e6ff;
}
.po-say .po-caret {
  background: #a397ff;
  box-shadow: 0 0 8px #a397ff;
}
.oni-say .po-emblem {
  width: 36px;
  height: 13px;
  margin: 0 10px 0 0;
  vertical-align: -0.05em;
  background: #a397ff;
  box-shadow: none;
  opacity: 0.25;
  animation: none;
}
.po-card.say .po-emblem {
  opacity: 1;
  filter: drop-shadow(0 0 6px #a397ff) drop-shadow(0 0 14px #6d5cff);
  animation: po-emblem 0.6s ease-out both;
}
@keyframes po-emblem {
  0% { opacity: 0.25; transform: scale(1); }
  40% { opacity: 1; transform: scale(1.35); background: #fff; }
  100% { opacity: 1; transform: scale(1); }
}
.po-glow .oni-beam {
  background: linear-gradient(90deg, transparent, #6d5cff 15%, #a397ff 40%, #fff 50%, #6fb6ff 60%, #6d5cff 85%, transparent);
  box-shadow: 0 0 14px #a397ff, 0 0 34px #6d5cffaa;
  animation-duration: 1.6s;
}
.po-glow .oni-bloom {
  height: 60vh;
  background:
    radial-gradient(ellipse 55% 100% at 50% 100%, #a397ff80, #6d5cff2e 45%, transparent 72%),
    radial-gradient(ellipse 22% 40% at 50% 100%, #ffffff40, transparent 70%);
  animation-duration: 2s;
}
/* The mode switch itself surges as it lands on Smart. */
.con-mode.po-surge {
  animation: po-switch 1.4s ease-out both;
}
@keyframes po-switch {
  0% { box-shadow: 0 0 0 0 #a397ff00; }
  20% { box-shadow: 0 0 0 3px #a397ffcc, 0 0 24px 6px #a397ff99; }
  100% { box-shadow: 0 0 0 0 #a397ff00; }
}
.po-card.calm .po-emblem {
  opacity: 1;
}
.po-card.calm .po-caret {
  display: none;
}
@media (prefers-reduced-motion: reduce) {
  .con-mode.po-surge {
    animation: none;
  }
}
.po-card.calm {
  animation: oni-in 0.4s ease-out both;
}

/* Oni's answering machine, skinned as a TCTF comm-link terminal from Bungie's
   Oni: near-black panels, cold teal lines, orange for transmit and alerts,
   chamfered corners and a faint scanline. The meter pulses while audio moves. */
#voicemail-panel,
#mailbox-panel,
#highlights-panel {
  --cl-bg: #0b1014;
  --cl-bg2: #121a20;
  --cl-teal: #3fd0c9;
  --cl-dim: #1f6f6c;
  --cl-hot: #ff5a2a;
  --cl-label: #e6ecef;
}
#voicemail-panel {
  width: 340px;
  background:
    repeating-linear-gradient(0deg, rgba(63, 208, 201, 0.05) 0 1px, transparent 1px 3px),
    linear-gradient(var(--cl-bg2), var(--cl-bg));
  border: 1px solid var(--cl-dim);
  border-radius: 0;
  clip-path: polygon(14px 0, 100% 0, 100% calc(100% - 14px), calc(100% - 14px) 100%, 0 100%, 0 14px);
  color: var(--cl-label);
  font-family: ui-monospace, Menlo, monospace;
}
#voicemail-panel .pin-head b {
  color: var(--cl-teal);
  letter-spacing: 0.12em;
  white-space: nowrap;
  text-shadow: 0 0 6px rgba(63, 208, 201, 0.5);
}
#voicemail-panel .cl-kanji {
  margin-left: 6px;
  font-size: 10px;
  color: var(--cl-dim);
  letter-spacing: 0.2em;
  white-space: nowrap;
}
#voicemail-panel .hint {
  color: var(--cl-dim);
  text-transform: uppercase;
  letter-spacing: 0.12em;
  font-size: 11px;
}
#voicemail-panel .pin-close {
  color: var(--cl-teal);
}
.deck {
  display: flex;
  flex-direction: column;
  gap: 8px;
  font-size: 12px;
}
.deck-window {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 8px 12px;
  background: #060a0d;
  border: 1px solid var(--cl-dim);
}
/* corner brackets */
.deck-window::before,
.deck-window::after {
  content: '';
  position: absolute;
  width: 8px;
  height: 8px;
  border: 1px solid var(--cl-teal);
}
.deck-window::before {
  top: -3px;
  left: -3px;
  border-right: 0;
  border-bottom: 0;
}
.deck-window::after {
  bottom: -3px;
  right: -3px;
  border-left: 0;
  border-top: 0;
}
/* the level meter: bars that jump while a message plays or records */
.deck .cl-meter {
  display: flex;
  align-items: flex-end;
  gap: 2px;
  height: 26px;
  flex: 1;
}
.deck .cl-meter i {
  flex: 1;
  height: 15%;
  background: var(--cl-dim);
}
.deck-window.spinning .cl-meter i {
  background: var(--cl-teal);
  box-shadow: 0 0 4px rgba(63, 208, 201, 0.6);
  animation: cl-level 0.9s ease-in-out infinite alternate;
}
.deck-window.spinning .cl-meter i:nth-child(3n) { animation-duration: 0.55s; }
.deck-window.spinning .cl-meter i:nth-child(3n + 1) { animation-duration: 0.7s; animation-delay: -0.3s; }
.deck-window.spinning .cl-meter i:nth-child(4n) { animation-duration: 0.45s; animation-delay: -0.2s; }
@keyframes cl-level {
  from { height: 12%; }
  to { height: 100%; }
}
.deck-counter {
  display: flex;
  flex-direction: column;
  align-items: center;
  font:
    700 18px / 1 ui-monospace,
    Menlo,
    monospace;
  color: var(--cl-dim);
  letter-spacing: 2px;
}
.deck-counter small {
  font-size: 8px;
  letter-spacing: 0.12em;
  margin-top: 2px;
}
.deck-counter.incoming {
  color: var(--cl-hot);
  text-shadow: 0 0 6px rgba(255, 90, 42, 0.7);
  animation: cl-blink 1s steps(2) infinite;
}
@keyframes cl-blink {
  50% { opacity: 0.45; }
}
.vm-list {
  list-style: none;
  margin: 0;
  padding: 0;
  max-height: 220px;
  overflow-y: auto;
}
.vm-list li {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: 0 6px;
  padding: 5px 6px;
  border-bottom: 1px solid rgba(31, 111, 108, 0.45);
}
.vm-list li.vm-empty {
  display: block;
  text-align: center;
  padding: 10px;
}
.vm-list li.vm-new {
  border-left: 2px solid var(--cl-hot);
}
.vm-list li.vm-on {
  background: rgba(63, 208, 201, 0.1);
}
.vm-list .vm-who b {
  color: var(--cl-label);
  text-transform: uppercase;
  letter-spacing: 0.08em;
}
.vm-list .vm-meta {
  grid-column: 1;
  color: var(--cl-teal);
  font-size: 11px;
  letter-spacing: 0.06em;
}
.vm-list .vm-meta em {
  color: var(--cl-hot);
  font-style: normal;
}
.vm-list .vm-keys {
  grid-column: 2;
  grid-row: 1 / span 2;
  display: flex;
  gap: 3px;
  align-items: center;
}
#voicemail-panel .vm-keys .btn {
  background: transparent;
  border: 1px solid var(--cl-dim);
  border-radius: 0;
  color: var(--cl-teal);
}
.vm-badge {
  color: var(--cl-dim);
}
.vm-rec {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
}
.vm-record {
  flex: 1 0 100%;
  padding: 10px;
  font:
    700 13px ui-monospace,
    Menlo,
    monospace;
  letter-spacing: 0.14em;
  color: #fff;
  background: linear-gradient(#ff6a3a, #c23a14);
  border: 0;
  clip-path: polygon(10px 0, 100% 0, 100% calc(100% - 10px), calc(100% - 10px) 100%, 0 100%, 0 10px);
  cursor: pointer;
  user-select: none;
  touch-action: none;
}
.vm-record.armed {
  filter: brightness(0.85);
}
.vm-record.recording {
  animation: vm-rec 0.9s ease-in-out infinite;
}
@keyframes vm-rec {
  50% {
    filter: brightness(1.35);
  }
}
.vm-timer {
  color: var(--cl-teal);
  text-transform: uppercase;
  letter-spacing: 0.1em;
  font-size: 11px;
}

/* /giphy: the pick-list at the bottom of a log, and the GIF over the carpet. */
.con-giphy {
  padding: 0;
  border: 0;
  background: none;
}
.giphy-grid {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 2px;
}
.giphy-grid li {
  margin: 0;
  padding: 0;
}
.giphy-grid .hint {
  grid-column: 1 / -1;
}
.giphy-grid button {
  display: block;
  width: 100%;
  padding: 0;
  border: 0;
  background: #0006;
  cursor: pointer;
}
.giphy-grid button:hover,
.giphy-grid button:focus-visible,
.giphy-grid button.sel {
  outline: 2px solid #8fb8e8;
  outline-offset: -2px;
}
.giphy-grid img {
  display: block;
  width: 100%;
  height: 110px;
  object-fit: cover;
}
#gif-show {
  position: absolute;
  margin: 0;
  transform: translate(-50%, -50%) scale(1.2);
  pointer-events: none;
  z-index: 5;
  display: flex;
  flex-direction: column;
  align-items: center;
  opacity: 1;
  transition: opacity 0.6s ease;
}
#gif-show.fading {
  opacity: 0;
}
#gif-show img {
  display: block;
  max-width: 100%;
  min-height: 0;
  flex: 1 1 auto;
  object-fit: contain;
  border-radius: 6px;
  box-shadow: 0 4px 18px #000a;
}
#gif-show figcaption {
  margin-top: 4px;
  padding: 1px 8px;
  border-radius: 10px;
  background: #1b1d22e6;
  color: #fff;
  font-size: 12px;
}

/* LED ticker on the far brick wall (app.js tickerNext). #led-wall is the brick
   band (the size container, so the text keeps its 60cqh size); the panel is a
   black dot-matrix sign centred in it, always shown. */
#led-wall {
  position: absolute;
  pointer-events: none;
  z-index: 4;
  container-type: size;
  display: flex;
  align-items: center;
  justify-content: flex-start;
}
#led-wall .led-panel {
  position: relative;
  overflow: hidden;
  margin-left: 2.5%;
  width: calc(81% + 5px);
  max-width: 655px;
  height: calc(60cqh + 6px);
  box-sizing: border-box;
  background-color: #0a0a0a;
  background-image: radial-gradient(circle, rgba(255, 40, 40, 0.08) 0.8px, transparent 1.2px);
  background-size: 3px 3px;
  border: 1px solid #2a2a2a;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.55);
}
#led-wall span {
  position: absolute;
  left: 0;
  width: max-content;
  top: 50%;
  transform: translate(100vw, -50%);
  white-space: nowrap;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-weight: 700;
  font-size: 60cqh;
  line-height: 1;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: #ff2a2a;
  text-shadow: 0 0 3px #ff2a2a;
}
/* The dot grid in front of the text, so the letters break into LED pixels. */
#led-wall .led-panel::after {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 1;
  background-image:
    repeating-linear-gradient(90deg, transparent 0 2px, #0a0a0a 2px 3px),
    repeating-linear-gradient(0deg, transparent 0 2px, #0a0a0a 2px 3px);
}
#led-wall span.still {
  left: 50%;
  transform: translate(-50%, -50%);
  max-width: calc(100% - 8px);
  overflow: hidden;
  text-overflow: ellipsis;
}

/* In-app confirmation (confirmAction) and the toasts that report PC ops. */
.confirm-dialog {
  color: var(--text);
  width: min(380px, 90vw);
  padding: 16px;
}
.confirm-dialog::backdrop {
  background: rgba(8, 9, 12, 0.72);
}
.confirm-dialog h3 {
  margin: 0;
  font-size: 15px;
}
.confirm-body p {
  margin: 0 0 8px;
  font-size: 13px;
  color: var(--muted);
}
.confirm-body .confirm-warn {
  background: #3a1f1f;
  border: 1px solid #5a2f2f;
  color: #ffd0d0;
  border-radius: 8px;
  padding: 8px 10px;
  font-weight: 600;
}
.confirm-input {
  width: 100%;
  box-sizing: border-box;
  margin-top: 4px;
  padding: 7px 9px;
  border-radius: 6px;
  border: 1px solid var(--line);
  background: var(--panel2);
  color: var(--text);
  font-size: 16px; /* under 16 px, iOS zooms the page on focus */
}
.confirm-actions {
  justify-content: flex-end;
  margin: 12px 0 0;
}
.confirm-actions button.primary {
  border-color: var(--blue);
  color: var(--blue);
}
.confirm-actions button.warn {
  background: #5a2f2f;
  color: #fff;
}
.ops button:disabled {
  opacity: 0.6;
  cursor: progress;
}
#toasts {
  position: fixed;
  bottom: 20px;
  left: 50%;
  transform: translateX(-50%);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  z-index: 1020; /* above the console (1000) */
  pointer-events: none;
}
/* An error stays up longer and goes on a click, so it takes the pointer. */
.toast.bad {
  pointer-events: auto;
  cursor: pointer;
}
.toast {
  background: var(--panel);
  border: 1px solid var(--line);
  border-left: 3px solid #3ddc84;
  color: var(--text);
  padding: 8px 14px;
  border-radius: 8px;
  font-size: 13px;
  box-shadow: 0 6px 20px rgba(0, 0, 0, 0.45);
  animation: toast-in 0.18s ease-out;
}
.toast.bad {
  border-left-color: #ff6b6b;
}
@keyframes toast-in {
  from { opacity: 0; transform: translateY(8px); }
}

/* Room camera sound: a speaker that greys out and crosses through when muted. */
.sound-btn { display: inline-flex; align-items: center; justify-content: center; padding: 2px 8px; }
.sound-btn[hidden] { display: none; }
.sound-btn.muted { opacity: 0.5; }

/* The room camera's "live": a red dot, pulsing gently. */
.live-dot {
  display: inline-block;
  width: 8px;
  height: 8px;
  margin-right: 2px;
  border-radius: 50%;
  background: #e53935;
  vertical-align: middle;
  animation: live-pulse 1.6s ease-in-out infinite;
}
@keyframes live-pulse {
  50% { opacity: 0.4; }
}

/* "● live" sits at the far end of the camera's title bar, clear of the close button. */
#camera-sub {
  margin-left: auto;
  margin-right: 6px;
  white-space: nowrap;
}

/* The console bar: shut, the ~ that opens it and Oni's face; open, the face and
   the name, the ~ gone. */
.con-bar .con-oni {
  display: inline-block;
  width: 18px;
  height: 18px;
  image-rendering: pixelated;
  border-radius: 3px;
  vertical-align: middle;
}
.console.open .con-bar kbd {
  display: none;
}
.console:not(.open) .con-bar b,
.console:not(.open) .con-bar .con-agent {
  display: none;
}
/* Dormant hides all but the name; shut, it is the ~ and the face instead. */
.console.oni-dormant:not(.open) .con-bar > kbd,
.console.oni-dormant:not(.open) .con-bar > .con-oni {
  display: inline-block;
}

/* Winamp 2's titlebar close: a tiny bevelled square at the right end of the bar. */
.winamp .wa-bar {
  align-items: center;
}
.winamp .wa-bar span {
  margin-left: auto;
}
.winamp .wa-close {
  flex: none;
  width: 10px;
  height: 10px;
  padding: 0;
  font: 700 10px / 7px ui-monospace, monospace;
  color: #d8dbe3;
  background: var(--wa-face-2);
  border-top: 1px solid var(--wa-hi);
  border-left: 1px solid var(--wa-hi);
  border-right: 1px solid var(--wa-lo);
  border-bottom: 1px solid var(--wa-lo);
  border-radius: 0;
  cursor: pointer !important;
}
.winamp .wa-close:hover {
  color: #fff;
}
.winamp .wa-close:active {
  background: var(--wa-face);
  border-top-color: var(--wa-lo);
  border-left-color: var(--wa-lo);
  border-right-color: var(--wa-hi);
  border-bottom-color: var(--wa-hi);
}

/* The room camera, skinned like a camera the way the player is skinned like
 * Winamp: a graphite body, a top plate with the controls and the close, the
 * picture behind a recessed lens bezel with viewfinder brackets, and a red
 * tally light for live. Drawn in CSS with crisp 1px bevels, no images. */
.cam {
  --cam-body: #26272b;
  --cam-plate: #34353b;
  --cam-hi: #5c5e67;
  --cam-lo: #0e0e11;
  max-width: 96vw;
  padding: 0 8px 10px;
  background: repeating-linear-gradient(90deg, var(--cam-body) 0 2px, #242529 2px 4px);
  border-top: 1px solid var(--cam-hi);
  border-left: 1px solid var(--cam-hi);
  border-right: 1px solid var(--cam-lo);
  border-bottom: 1px solid var(--cam-lo);
  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.55);
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
}
.cam .cam-plate {
  align-items: center;
  gap: 6px;
  margin: 0 -8px 8px;
  padding: 5px 6px 5px 10px;
  background: linear-gradient(to bottom, #3e3f46, var(--cam-plate));
  border-bottom: 1px solid var(--cam-lo);
  box-shadow: inset 0 -1px 0 #45464e;
}
.cam .cam-plate b {
  font-size: 10px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: #c9ccd4;
}
/* Small camera buttons: square, bevelled, pressed inverts the bevel. */
.cam .cam-plate .btn,
.cam .cam-close {
  height: 20px;
  padding: 0 7px;
  font: 600 10px / 1 ui-monospace, monospace;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: #d8dbe3;
  background: linear-gradient(to bottom, #45464e, #2e2f35);
  border-top: 1px solid var(--cam-hi);
  border-left: 1px solid var(--cam-hi);
  border-right: 1px solid var(--cam-lo);
  border-bottom: 1px solid var(--cam-lo);
  border-radius: 0;
  cursor: pointer;
}
.cam .cam-plate .btn:active,
.cam .cam-close:active {
  background: linear-gradient(to bottom, #2e2f35, #45464e);
  border-top-color: var(--cam-lo);
  border-left-color: var(--cam-lo);
  border-right-color: var(--cam-hi);
  border-bottom-color: var(--cam-hi);
}
.cam .cam-plate .sound-btn {
  padding: 0 6px;
}
.cam .cam-plate .sound-btn[hidden] {
  display: none;
}
.cam .cam-close {
  flex: none;
  width: 20px;
  padding: 0;
  font-size: 13px;
}
.cam #camera-sub {
  margin-right: 2px;
  font-size: 10px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: #9a9daa;
}
/* The tally light: a red lamp with a lit glow. */
.cam .live-dot {
  width: 7px;
  height: 7px;
  margin-right: 4px;
  border: 1px solid #5a1010;
  background: radial-gradient(circle at 35% 35%, #ff8a80, #e53935 55%, #a31515);
  box-shadow: 0 0 5px rgba(255, 60, 60, 0.8);
}
/* The lens bezel: a recessed black well with viewfinder corner brackets. */
.cam .camera-screen {
  position: relative;
  background: #050506;
  border: 4px solid #111114;
  border-radius: 0;
  outline: 1px solid var(--cam-lo);
  box-shadow: 0 0 0 1px var(--cam-hi), inset 0 0 0 1px #000;
}
.cam .camera-screen::before {
  content: '';
  position: absolute;
  inset: 8px;
  pointer-events: none;
  background:
    linear-gradient(#e6e6e6cc, #e6e6e6cc) top left / 14px 1px,
    linear-gradient(#e6e6e6cc, #e6e6e6cc) top left / 1px 14px,
    linear-gradient(#e6e6e6cc, #e6e6e6cc) top right / 14px 1px,
    linear-gradient(#e6e6e6cc, #e6e6e6cc) top right / 1px 14px,
    linear-gradient(#e6e6e6cc, #e6e6e6cc) bottom left / 14px 1px,
    linear-gradient(#e6e6e6cc, #e6e6e6cc) bottom left / 1px 14px,
    linear-gradient(#e6e6e6cc, #e6e6e6cc) bottom right / 14px 1px,
    linear-gradient(#e6e6e6cc, #e6e6e6cc) bottom right / 1px 14px;
  background-repeat: no-repeat;
}
.cam .camera-screen.empty::before {
  opacity: 0.35;
}

/* The ♫ dialog, skinned as a small Winamp window: the player's title bar and
 * bevelled close, the playlists as LCD rows, and the name box and buttons of
 * its search strip. Its own class, so confirmAction's dialog is untouched. */
.winamp.wa-dialog {
  width: min(340px, calc(100vw - 32px));
  margin: auto;
  padding: 3px 7px 7px;
  color: var(--wa-lcd);
}
.winamp.wa-dialog::backdrop {
  background: rgb(0 0 0 / 0.45);
}
.winamp.wa-dialog .wa-bar {
  margin: 0 -4px 6px;
}
.winamp.wa-dialog .wa-bar b {
  font-weight: 400;
}
.winamp.wa-dialog .wa-song {
  margin: 0 0 6px;
  padding: 5px 6px;
  font-size: 11px;
  color: #d8c25a;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}
.winamp.wa-dialog .wa-rows {
  background: var(--wa-lcd-bg);
  border-top: 1px solid var(--wa-lo);
  border-left: 1px solid var(--wa-lo);
  border-right: 1px solid var(--wa-hi);
  border-bottom: 1px solid var(--wa-hi);
}
.winamp.wa-dialog .wa-rows li {
  padding-right: 6px;
  cursor: pointer;
}
.winamp.wa-dialog .wa-rows li .wa-n {
  text-align: right;
}
.winamp.wa-dialog .wa-rows li:hover {
  background: #0f1f18;
}
.winamp.wa-dialog .wa-rows li:focus {
  outline: none;
}
.winamp.wa-dialog .wa-rows li.sel {
  background: #13291f;
  outline: 1px solid var(--wa-lcd);
  outline-offset: -1px;
}
.winamp.wa-dialog .wa-search[hidden] {
  display: none;
}
.winamp.wa-dialog .wa-actions {
  justify-content: flex-end;
}
.winamp.wa-dialog .wa-search button:focus-visible,
.winamp.wa-dialog .wa-search input:focus-visible {
  outline: 1px solid var(--wa-lcd);
  outline-offset: 1px;
}

/* The pin beside the player's close: the same 10px bevel, pressed in while on. */
.winamp .wa-pin {
  flex: none;
  width: 10px;
  height: 10px;
  padding: 0;
  font: 700 10px / 7px ui-monospace, monospace;
  color: #8a8d99;
  background: var(--wa-face-2);
  border-top: 1px solid var(--wa-hi);
  border-left: 1px solid var(--wa-hi);
  border-right: 1px solid var(--wa-lo);
  border-bottom: 1px solid var(--wa-lo);
  border-radius: 0;
  cursor: pointer !important;
}
.winamp .wa-pin:hover {
  color: #fff;
}
.winamp .wa-pin.on,
.winamp .wa-pin:active {
  color: var(--wa-lcd);
  background: var(--wa-face);
  border-top-color: var(--wa-lo);
  border-left-color: var(--wa-lo);
  border-right-color: var(--wa-hi);
  border-bottom-color: var(--wa-hi);
}
.winamp .wa-pin + .wa-close {
  margin-left: -5px;
}

/* ---- phones ----
   The desktop layout sizes the map by the window's height; a phone page scrolls
   instead, so the map takes the width at its own aspect, the side panel follows
   it, and the map's panels open full screen above the Oni bar. Desktop widths
   never match this block. */
@media (max-width: 700px) {
  header {
    gap: 8px;
    padding: 8px 12px;
  }
  .mapwrap {
    padding: 8px;
    overflow: visible;
  }
  .stage {
    flex: none;
    width: 100%;
    height: auto;
    max-height: none;
  }
  aside .tabs {
    position: sticky;
    top: 0;
    z-index: 5;
    background: var(--panel);
  }
  .pane {
    overflow: visible;
  }
  /* placePin writes left/top inline for a floating panel; here they are pinned. */
  .pin.card,
  #camera-panel,
  #ac-panel {
    left: 0 !important;
    top: 0 !important;
    right: 0;
    bottom: var(--bar-h, 30px);
    transform: none;
    width: auto !important;
    max-width: none !important;
    max-height: none;
    overflow-y: auto;
    border-radius: 0;
    -webkit-overflow-scrolling: touch;
  }
  #ac-panel {
    background: var(--panel);
  }
  #ac-panel .ac {
    max-height: none;
  }
  .pin-resize {
    display: none;
  }
  /* The prank menu closes on a tap outside it, so it stays a sheet, not a page. */
  #prank-panel.pin.card {
    top: auto !important;
    max-height: 70vh;
    border-top: 1px solid var(--line);
  }
  #tv-player {
    left: 50% !important;
    top: 8px !important;
    transform: translateX(-50%);
    max-width: calc(100vw - 16px);
  }
  /* The close button hangs over a floating panel's corner; on a full-screen one
     it stays in the top corner while the panel scrolls. */
  .pin.card .pin-close {
    position: fixed;
    top: 8px;
    right: 8px;
    z-index: 2;
  }
  #prank-panel .pin-close {
    position: absolute;
  }
  /* Close buttons big enough for a thumb. */
  .pin-close,
  .cam-close,
  .acr-close {
    min-width: 36px;
    min-height: 36px;
  }
}
