/*
 * Colours are the deck's, from client/js/deck.js — cold steel, and every warm
 * accent belonging to something powered. The UI and the world should not look
 * like they came from different games.
 */
:root {
  --bg: #0a0d16;
  --accent: #4fb6d8;      /* cyan: corridor guides, terminals, coolant */
  --hazard: #c9a63e;      /* hazard stripe: thresholds and loading edges */
  --amber: #e3963c;       /* the reactor, and things that are running hot */
  --bio: #4fd39a;         /* hydroponics */
  --steel: #2a3346;       /* bulkhead face */
  --steel-lit: #3d4a63;   /* bulkhead cap */
  --panel: rgba(17, 23, 34, 0.94);
  --line: rgba(79, 182, 216, 0.22);
  --text: #e8eef7;
  --muted: rgba(232, 238, 247, 0.55);
  --font: "Segoe UI", system-ui, -apple-system, Roboto, sans-serif;
  /* Readouts, designations and labels. A terminal's own typeface. */
  --mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
}

* { box-sizing: border-box; }

html, body {
  margin: 0;
  padding: 0;
  height: 100%;
  overflow: hidden;
  background: var(--bg);
  color: var(--text);
  font-family: var(--font);
  /* A twin-stick game must not let a drag select text or bounce the page. */
  -webkit-user-select: none;
  user-select: none;
  -webkit-tap-highlight-color: transparent;
  overscroll-behavior: none;
}

#game {
  display: block;
  position: fixed;
  inset: 0;
  cursor: none;
  touch-action: none;
}

/* ------------------------------------------------------------------ HUD */

#stats {
  position: fixed;
  top: 12px;
  left: 12px;
  display: flex;
  gap: 6px;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.04em;
  pointer-events: none;
  z-index: 5;
}
#stats span {
  background: rgba(10, 14, 24, 0.72);
  border: 1px solid rgba(255, 255, 255, 0.08);
  border-radius: 4px;
  padding: 4px 8px;
  color: var(--muted);
}
#stats .good { color: #7bed9f; }
#stats .ok { color: #ffd166; }
#stats .bad { color: #ff6b6b; }

/* The board and the feed share the top-right corner, stacked. Laying them out
   in a flow column rather than pinning both to the same corner means the feed
   follows the board's height instead of needing an offset that would be wrong
   for any other player count. */
#rightColumn {
  position: fixed;
  top: 12px;
  right: 12px;
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 8px;
  /* Transparent to the mouse by default; the scoreboard opts back in so it can
     be scrolled. Anything else here must never swallow a click meant for the
     arena underneath. */
  pointer-events: none;
  z-index: 6;
}

#killfeed {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 4px;
}
.kf-row {
  background: rgba(10, 14, 24, 0.78);
  border: 1px solid rgba(255, 255, 255, 0.08);
  border-left: 3px solid var(--accent);
  border-radius: 4px;
  padding: 5px 10px;
  font-size: 12px;
  font-weight: 700;
  display: flex;
  align-items: center;
  gap: 8px;
  animation: slideIn 0.18s ease-out;
  transition: opacity 0.35s ease, transform 0.35s ease;
}
.kf-row.fade { opacity: 0; transform: translateX(20px); }
.kf-icon { color: var(--muted); font-size: 10px; }

@keyframes slideIn {
  from { opacity: 0; transform: translateX(24px); }
  to { opacity: 1; transform: translateX(0); }
}

/* ----------------------------------------------------------- scoreboard */

/* Always on screen, so it is deliberately faint: a translucent ground and
   blurred backdrop keep the arena readable through it. Everything here is
   tuned to be glanceable without competing with the game. */
#scoreboard {
  /* Makes this the offset parent of its rows, so the auto-scroll in game.js
     can measure a row's position against the scroll box directly. */
  position: relative;
  width: 250px;
  /* Capped so a full lobby cannot grow a wall down the side of the screen.
     Roughly six rows plus the header; the rest scrolls. */
  max-height: 208px;
  overflow-y: auto;
  overflow-x: hidden;
  /* Only the board takes the mouse, and only so the wheel can scroll it. */
  pointer-events: auto;
  background: rgba(10, 14, 24, 0.42);
  border: 1px solid rgba(255, 255, 255, 0.09);
  border-radius: 8px;
  padding: 0 12px 8px;
  backdrop-filter: blur(3px);
  /* Firefox; the ::-webkit rules below cover the rest. */
  scrollbar-width: thin;
  scrollbar-color: rgba(255, 255, 255, 0.16) transparent;
}
#scoreboard::-webkit-scrollbar { width: 6px; }
#scoreboard::-webkit-scrollbar-track { background: transparent; }
#scoreboard::-webkit-scrollbar-thumb {
  background: rgba(255, 255, 255, 0.16);
  border-radius: 3px;
}

#scoreboard table { width: 100%; border-collapse: collapse; font-size: 12px; }
#scoreboard th {
  text-align: left;
  font-size: 9px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--muted);
  padding: 9px 0 6px;
  border-bottom: 1px solid rgba(255, 255, 255, 0.1);
  /* Column labels stay put while the rows scroll under them. The header needs
     a near-opaque ground of its own, or scrolled rows read through it. */
  position: sticky;
  top: 0;
  background: rgba(11, 15, 25, 0.93);
  z-index: 1;
}
#scoreboard th:not(:first-child), #scoreboard td:not(:first-child) {
  text-align: right;
  width: 42px;
}
#scoreboard td {
  padding: 5px 0;
  font-weight: 600;
  color: rgba(233, 237, 245, 0.86);
}
/* Names can be long; truncate rather than wrap, so a row is always one line
   and the visible row count stays predictable. */
#scoreboard td:first-child {
  max-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
#scoreboard tr.me td {
  background: rgba(90, 200, 250, 0.12);
  color: #fff;
}

/* ---------------------------------------------------------------- toast */

#toast {
  position: fixed;
  bottom: 90px;
  left: 50%;
  transform: translateX(-50%) translateY(10px);
  background: rgba(10, 14, 24, 0.9);
  border: 1px solid var(--line);
  border-radius: 6px;
  padding: 9px 18px;
  font-size: 13px;
  font-weight: 600;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.2s ease, transform 0.2s ease;
  z-index: 9;
}
#toast.visible { opacity: 1; transform: translateX(-50%) translateY(0); }

/* ----------------------------------------------------- the field briefing */

/*
 * Two instruments, both children of the menu's home screen: the scan log
 * (what is happening right now, in the viewport's own voice) and the briefing
 * band (who is out here and why any of it is worth doing).
 *
 * The styles here are the shared ones and the narrow layout, where both sit in
 * the menu's own scrolling flow. The wide layout that lifts them onto the
 * console is with the rest of the menu's layout, further down.
 */

.scan-log {
  margin-top: 22px;
  padding: 12px 14px 12px;
  background: rgba(8, 12, 19, 0.55);
  border: 1px solid rgba(140, 175, 215, 0.14);
  border-left: 2px solid rgba(79, 182, 216, 0.45);
  border-radius: 3px;
}
.log-head {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 10px;
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--accent);
}
.log-mark { font-size: 9px; }
.log-line {
  flex: 1;
  height: 1px;
  background: linear-gradient(90deg, rgba(79, 182, 216, 0.35), transparent);
}

/* A log line: terse, mono, and marked. Prose set loose over artwork was the
   old briefing's failure, so every line here belongs to an instrument. */
.beat {
  margin: 0 0 8px;
  /* A hanging indent, so a wrapped line falls under its own text rather than
     under the mark — the difference between a log and a paragraph. */
  padding-left: 14px;
  text-indent: -14px;
  font-family: var(--mono);
  font-size: 12px;
  line-height: 1.55;
  color: rgba(205, 222, 242, 0.82);
}
.beat:last-child { margin-bottom: 0; }
.beat::before { content: "\00B7  "; color: rgba(79, 182, 216, 0.6); }
.beat em { color: var(--accent); font-style: normal; font-weight: 700; }
.beat.warn { color: #ffd9a8; }
.beat.warn::before { color: var(--hazard); }

/* ---- the briefing band ---- */

.brief-band {
  margin-top: 14px;
  padding: 12px 14px 13px;
  background: rgba(8, 12, 19, 0.55);
  border: 1px solid rgba(140, 175, 215, 0.14);
  border-radius: 3px;
  position: relative;
}
/* The hazard strip along its top edge: a threshold, in the same paint the
   loadout terminal and the deck's own doorways use. */
.brief-band::before {
  content: "";
  position: absolute;
  top: 0; left: 0; right: 0;
  height: 3px;
  background: repeating-linear-gradient(
    -45deg, var(--hazard) 0 6px, rgba(43, 36, 19, 0.95) 6px 12px);
  opacity: 0.45;
  border-radius: 3px 3px 0 0;
}

.band-head {
  display: flex;
  align-items: baseline;
  gap: 10px;
  margin: 4px 0 12px;
  font-family: var(--mono);
  font-size: 9.5px;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  white-space: nowrap;
}
.brief-title { color: rgba(200, 226, 250, 0.85); }
.brief-tag { color: var(--hazard); }
.band-head .head-rule {
  flex: 1;
  height: 1px;
  background: linear-gradient(90deg, rgba(140, 175, 215, 0.22), transparent);
}
.brief-class { color: rgba(140, 175, 215, 0.45); letter-spacing: 0.12em; }

/* One column narrow; the wide layout turns this into three. */
.band-cols { display: flex; flex-direction: column; gap: 12px; }
.band-cols h4 {
  margin: 0 0 5px;
  font-family: var(--mono);
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--accent);
}
.band-cols p {
  margin: 0;
  font-size: 12px;
  line-height: 1.55;
  color: rgba(210, 222, 238, 0.72);
}

/* The turn. It is the point of the whole panel, so it is the one warm thing
   in it. */
.brief-kicker {
  margin: 12px 0 0;
  padding-top: 10px;
  border-top: 1px solid rgba(140, 175, 215, 0.14);
  font-size: 12px;
  line-height: 1.5;
  color: #ffd9a8;
}

/* --------------------------------------------------------- join screen */

#join {
  position: fixed;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  background: #04060b;
  z-index: 20;
  transition: opacity 0.3s ease;
}

/* The starfield. Behind everything and inert to the pointer — it is scenery,
   and a click meant for a card must never land on it. */
#joinBg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
}
/* visibility, not just opacity: a merely-transparent panel keeps its focusable
   controls in the tab order, so pressing Tab in game would put the caret in the
   callsign field and WASD would type into it instead of moving. Delayed to the
   end of the fade so the transition still plays. */
#join.hidden {
  opacity: 0;
  pointer-events: none;
  visibility: hidden;
  transition: opacity 0.3s ease, visibility 0s linear 0.3s;
}

/*
 * The terminal. A frame that does not scroll, wrapping a panel that does, so
 * the corner brackets and the hazard strip stay bolted to the housing while its
 * contents move.
 *
 * Wide and short rather than a tall column: the loadouts are the real content
 * and want to sit side by side where they can be compared at a glance.
 */
.console {
  position: relative;
  width: min(960px, calc(100vw - 40px));
  max-height: calc(100vh - 36px);
  display: flex;
  background:
    linear-gradient(150deg, rgba(61, 74, 99, 0.35), transparent 42%),
    var(--panel);
  border: 1px solid rgba(140, 175, 215, 0.22);
  border-radius: 5px;
  box-shadow:
    0 30px 70px rgba(0, 0, 0, 0.7),
    inset 0 0 0 1px rgba(0, 0, 0, 0.55),
    inset 0 1px 0 rgba(160, 200, 245, 0.10);
}

/* Hazard strip along the top edge, the same marking every threshold on the deck
   carries. Drawn rather than tiled from an image: one gradient, no asset.
   Shared with the menu's housing, so the two read as the same fitting. */
.console::before,
.menu-frame::before {
  content: "";
  position: absolute;
  left: 0; right: 0; top: 0;
  height: 4px;
  border-radius: 5px 5px 0 0;
  background: repeating-linear-gradient(
    -45deg,
    var(--hazard) 0 7px,
    rgba(43, 36, 19, 0.95) 7px 14px);
  opacity: 0.55;
}

/* Corner brackets, so the housing reads as bolted to something. */
.console::after,
.menu-frame::after {
  content: "";
  position: absolute;
  inset: 7px;
  pointer-events: none;
  border-radius: 2px;
  background:
    linear-gradient(var(--accent), var(--accent)) 0 0 / 14px 1px no-repeat,
    linear-gradient(var(--accent), var(--accent)) 0 0 / 1px 14px no-repeat,
    linear-gradient(var(--accent), var(--accent)) 100% 0 / 14px 1px no-repeat,
    linear-gradient(var(--accent), var(--accent)) 100% 0 / 1px 14px no-repeat,
    linear-gradient(var(--accent), var(--accent)) 0 100% / 14px 1px no-repeat,
    linear-gradient(var(--accent), var(--accent)) 0 100% / 1px 14px no-repeat,
    linear-gradient(var(--accent), var(--accent)) 100% 100% / 14px 1px no-repeat,
    linear-gradient(var(--accent), var(--accent)) 100% 100% / 1px 14px no-repeat;
  opacity: 0.5;
}

.panel {
  flex: 1;
  min-width: 0;
  overflow-y: auto;
  padding: 18px 30px 22px;
  text-align: center;
  scrollbar-width: thin;
  scrollbar-color: rgba(140, 175, 215, 0.22) transparent;
}
.panel::-webkit-scrollbar { width: 7px; }
.panel::-webkit-scrollbar-track { background: transparent; }
.panel::-webkit-scrollbar-thumb {
  background: rgba(140, 175, 215, 0.22);
  border-radius: 3px;
}

/* ---- status strip ---- */

.console-head {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 16px;
  padding-bottom: 9px;
  border-bottom: 1px solid rgba(140, 175, 215, 0.14);
  font-family: var(--mono);
  font-size: 9.5px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: rgba(140, 175, 215, 0.5);
  white-space: nowrap;
}
.console-head .lamp {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--bio);
  box-shadow: 0 0 8px var(--bio);
  animation: lampPulse 2.6s ease-in-out infinite;
}
@keyframes lampPulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.35; } }
.console-head .desig { color: rgba(200, 226, 250, 0.75); }
.console-head .head-rule {
  flex: 1;
  height: 1px;
  background: linear-gradient(90deg, rgba(140, 175, 215, 0.28), transparent);
}
.console-head .head-note { overflow: hidden; text-overflow: ellipsis; }

/* ---- title, with the suit you are about to wear ---- */

.title-row {
  display: flex;
  align-items: center;
  gap: 20px;
  margin-bottom: 22px;
}
#suitPreview {
  flex: 0 0 auto;
  width: 116px;
  height: 116px;
  border-radius: 4px;
  background:
    radial-gradient(circle at 50% 42%, rgba(79, 182, 216, 0.13), transparent 66%),
    rgba(0, 0, 0, 0.34);
  border: 1px solid rgba(140, 175, 215, 0.16);
}
.title-text { text-align: left; min-width: 0; }

.panel h1 {
  margin: 0;
  font-size: 34px;
  font-weight: 900;
  letter-spacing: 0.1em;
  line-height: 1.05;
}
.panel h1 span { color: var(--accent); }
.tagline {
  margin: 7px 0 0;
  font-size: 11.5px;
  color: var(--muted);
  letter-spacing: 0.05em;
  line-height: 1.5;
}

.panel label {
  display: block;
  text-align: left;
  font-family: var(--mono);
  font-size: 9.5px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: rgba(140, 175, 215, 0.6);
  margin-bottom: 7px;
}
/* A tick before each section label, so the sections read as rows on a readout
   rather than as free-floating headings. */
.panel label::before {
  content: "\25B8\00a0\00a0";
  color: var(--accent);
}
.panel input {
  width: 100%;
  padding: 12px 14px;
  font-family: var(--mono);
  font-size: 14px;
  font-weight: 600;
  letter-spacing: 0.06em;
  color: var(--text);
  background: rgba(0, 0, 0, 0.46);
  border: 1px solid rgba(140, 175, 215, 0.18);
  border-radius: 4px;
  outline: none;
  transition: border-color 0.15s ease, box-shadow 0.15s ease;
}
.panel input:focus {
  border-color: var(--accent);
  box-shadow: inset 0 0 0 1px rgba(79, 182, 216, 0.25);
}

/* ------------------------------------------------------- skill picker */

.skill-label { margin-top: 4px; }

/* The rack, above the loadout because the weapon is the choice you make first
   and change least. Four across on a wide panel; two-by-two once a column would
   be too narrow for the descriptions, which are the point of having cards. */
#weapons {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 10px;
  margin-bottom: 22px;
}
@media (max-width: 1000px) {
  #weapons { grid-template-columns: repeat(2, 1fr); }
}
/*
 * Cards are console switches: recessed, with a dark inset ground and an
 * indicator bar down the left edge that lights when the switch is thrown. The
 * shared look lives here so weapons and modules cannot drift apart.
 */
button.weapon, button.skill, button.slot {
  position: relative;
  overflow: hidden;
  font-family: inherit;
  color: var(--text);
  background:
    linear-gradient(180deg, rgba(140, 175, 215, 0.05), transparent 55%),
    rgba(0, 0, 0, 0.42);
  border: 1px solid rgba(140, 175, 215, 0.16);
  border-radius: 4px;
  cursor: pointer;
  transition: border-color 0.12s ease, background 0.12s ease, transform 0.08s ease;
}
/* The indicator, dark until the card is chosen. */
button.weapon::before, button.skill::before, button.slot::before {
  content: "";
  position: absolute;
  left: 0; top: 0; bottom: 0;
  width: 3px;
  background: rgba(140, 175, 215, 0.16);
  transition: background 0.12s ease, box-shadow 0.12s ease;
}
button.weapon:hover, button.skill:hover, button.slot:hover {
  border-color: rgba(140, 175, 215, 0.4);
  background:
    linear-gradient(180deg, rgba(140, 175, 215, 0.09), transparent 55%),
    rgba(0, 0, 0, 0.34);
}
button.weapon:active, button.skill:active { transform: scale(0.985); }

button.weapon {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 4px;
  padding: 10px 12px 11px;
  text-align: left;
}
button.weapon.selected {
  border-color: rgba(201, 166, 62, 0.65);
  background:
    linear-gradient(180deg, rgba(201, 166, 62, 0.16), transparent 60%),
    rgba(0, 0, 0, 0.34);
}
button.weapon.selected::before {
  background: var(--hazard);
  box-shadow: 0 0 10px rgba(201, 166, 62, 0.8);
}

/* The gun itself, drawn from the same art the game puts in your hands. */
.weapon-art {
  width: 100%;
  height: 52px;
  margin-bottom: 2px;
  border-radius: 3px;
  background:
    linear-gradient(180deg, rgba(140, 175, 215, 0.07), transparent),
    rgba(0, 0, 0, 0.3);
}

.weapon-name {
  font-size: 12px;
  font-weight: 800;
  letter-spacing: 0.07em;
  text-transform: uppercase;
}
.weapon-stat {
  font-family: var(--mono);
  font-size: 9.5px;
  font-weight: 700;
  letter-spacing: 0.07em;
  color: var(--hazard);
}
.weapon-desc {
  font-size: 11px;
  line-height: 1.42;
  color: var(--muted);
}

/* The two slots. Clicking one arms it; the next card click fills it. The armed
   slot is the only modal state on this screen, so it is drawn loudly. */
#slots {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 10px;
  margin-bottom: 12px;
}
button.slot {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 9px 12px 9px 15px;
}
button.slot.armed {
  border-color: rgba(79, 182, 216, 0.6);
  background:
    linear-gradient(180deg, rgba(79, 182, 216, 0.17), transparent 60%),
    rgba(0, 0, 0, 0.34);
}
button.slot.armed::before {
  background: var(--accent);
  box-shadow: 0 0 10px rgba(79, 182, 216, 0.85);
}
.slot-key {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 24px;
  height: 24px;
  border-radius: 3px;
  font-family: var(--mono);
  font-size: 12px;
  font-weight: 800;
  background: rgba(140, 175, 215, 0.1);
  border: 1px solid rgba(140, 175, 215, 0.22);
}
button.slot.armed .slot-key {
  background: var(--accent);
  border-color: var(--accent);
  color: #06202b;
}
.slot-skill {
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

/* Six loadouts across three columns rather than one long row — at six abreast
   the cards get too narrow for their descriptions to be readable, which is the
   whole reason the picker exists. */
#skills {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 10px;
}

/* Which key a card is bound to, so the answer to "what is on Q" is on the card
   itself rather than only in the slot row above. */
.skill-slot {
  position: absolute;
  top: 9px;
  right: 10px;
  min-width: 18px;
  height: 18px;
  line-height: 18px;
  text-align: center;
  border-radius: 4px;
  font-size: 10px;
  font-weight: 800;
  color: transparent;
}
.skill-slot.on {
  color: #06202b;
  background: var(--accent);
  font-family: var(--mono);
}

button.skill {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 3px;
  padding: 11px 12px 11px 15px;
  text-align: left;
}
button.skill.selected {
  border-color: rgba(79, 182, 216, 0.55);
  background:
    linear-gradient(180deg, rgba(79, 182, 216, 0.15), transparent 60%),
    rgba(0, 0, 0, 0.34);
}
button.skill.selected::before {
  background: var(--accent);
  box-shadow: 0 0 10px rgba(79, 182, 216, 0.85);
}
.skill-icon { font-size: 17px; line-height: 1; color: var(--accent); }
.skill-name {
  font-size: 12px;
  font-weight: 800;
  letter-spacing: 0.07em;
  text-transform: uppercase;
}
.skill-desc {
  font-size: 10px;
  line-height: 1.4;
  color: var(--muted);
}

/* The counterplay clause on a card. Loud enough to be read before you commit to
   a loadout, since it is the part that decides how the thing has to be played. */
.warn { color: #ffd166; }

/* Callsign and the commit button share a row — neither deserves its own line. */
.enter-row {
  display: flex;
  align-items: flex-end;
  gap: 12px;
  margin-top: 18px;
}
.enter-row .field { flex: 1; text-align: left; }
.enter-row label { margin-bottom: 6px; }

/* The commit. The one control on the panel that does something irreversible, so
   it takes the hazard colour every other "this is a threshold" marking on the
   deck uses, rather than the cyan of things you are merely inspecting. */
.panel button#joinBtn {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 9px;
  min-width: 236px;
  padding: 13px 22px;
  font-size: 13.5px;
  font-family: inherit;
  font-weight: 800;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: #17130a;
  background: linear-gradient(180deg, #e6bf55, var(--hazard));
  border: 1px solid rgba(255, 235, 170, 0.5);
  border-radius: 4px;
  cursor: pointer;
  box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.5), 0 6px 18px rgba(201, 166, 62, 0.22);
  transition: transform 0.1s ease, filter 0.15s ease, box-shadow 0.15s ease;
}
.panel button#joinBtn:hover {
  filter: brightness(1.1);
  box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.5), 0 8px 26px rgba(201, 166, 62, 0.42);
}
.panel button#joinBtn:active { transform: scale(0.98); }
.btn-mark { font-size: 10px; opacity: 0.75; }

.controls {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 8px 20px;
  margin-top: 20px;
  padding-top: 16px;
  border-top: 1px solid rgba(140, 175, 215, 0.14);
  font-size: 11px;
}
.controls div {
  display: flex;
  align-items: center;
  gap: 5px;
  color: var(--muted);
}
.controls span { margin-left: auto; }

kbd {
  display: inline-block;
  padding: 2px 6px;
  font-family: var(--mono);
  font-size: 10px;
  font-weight: 700;
  color: var(--text);
  background: rgba(140, 175, 215, 0.1);
  border: 1px solid rgba(140, 175, 215, 0.2);
  border-bottom-width: 2px;
  border-radius: 3px;
}

.touchhint {
  margin: 16px 0 0;
  font-size: 10px;
  color: var(--muted);
  line-height: 1.5;
}

/* Fold the wide layout down in stages rather than jumping straight to a column. */
@media (max-width: 900px) {
  #skills { grid-template-columns: 1fr 1fr; }
  .controls { grid-template-columns: 1fr 1fr; }
}

@media (max-width: 700px) {
  #skills { grid-template-columns: 1fr 1fr; }
  .skill-desc { display: none; }
  .enter-row { flex-direction: column; align-items: stretch; }
  .panel button#joinBtn { min-width: 0; width: 100%; }
  /* The suit preview is the first thing to go: it is a flourish, and the
     loadout descriptions underneath are not. The weapon blurb goes with the
     skill blurbs — at three columns on a phone it is a word per line. The art,
     the name and the stat line still say which gun is which. */
  #suitPreview { display: none; }
  .weapon-desc { display: none; }
  .weapon-art { height: 40px; }
  .console-head .head-note { display: none; }
  .panel { padding: 14px 16px 18px; }
  /* Smaller, and back to transparent for the mouse: the right half of a phone
     screen is the aim/fire pad, and a scrollable panel there would eat touches
     meant for the arena. */
  #scoreboard {
    width: 156px;
    max-height: 132px;
    pointer-events: none;
    font-size: 11px;
  }
  #scoreboard th:not(:first-child), #scoreboard td:not(:first-child) { width: 30px; }
  #stats { font-size: 10px; }
  .controls { display: none; }
}

/* ==================================================================== menu */

/*
 * The view out there, behind every screen that is not the game.
 *
 * One canvas and one loop. The cold open owned this artwork and played it once;
 * now the menu is laid over the same drifting hauler with the briefing read
 * either side of it, and it is parked outright whenever something opaque covers
 * it — an animation nobody can see is competing with the game for the same
 * frame budget.
 */
#backdrop {
  position: fixed;
  inset: 0;
  background: #04060b;
  z-index: 15;
}
#backdrop canvas { position: absolute; inset: 0; width: 100%; height: 100%; }
/* The deck is below this in the stack, so playing has to remove it outright
   rather than merely stop drawing to it. */
body.playing #backdrop { display: none; }

/* The HUD belongs to a match. Visibility rather than display, so nothing has to
   remember which layout each panel wanted. */
#stats, #rightColumn { visibility: hidden; }
body.playing #stats, body.playing #rightColumn { visibility: visible; }

#menu {
  position: fixed;
  inset: 0;
  z-index: 20;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
}
/* `display: flex` above beats the default `hidden` gives, so say it again. */
#menu[hidden] { display: none; }

/* Same housing as the loadout terminal: a frame that does not scroll wrapping a
   body that does, so the hazard strip and corner brackets stay bolted on. */
.menu-frame {
  position: relative;
  width: min(660px, calc(100vw - 40px));
  max-height: calc(100vh - 48px);
  display: flex;
  flex-direction: column;
  background:
    linear-gradient(150deg, rgba(61, 74, 99, 0.35), transparent 42%),
    var(--panel);
  border: 1px solid rgba(140, 175, 215, 0.22);
  border-radius: 5px;
  box-shadow:
    0 30px 70px rgba(0, 0, 0, 0.7),
    inset 0 0 0 1px rgba(0, 0, 0, 0.55),
    inset 0 1px 0 rgba(160, 200, 245, 0.10);
}
.menu-frame > .console-head { margin: 0; padding: 16px 26px 9px; }
.menu-body { padding: 4px 26px 24px; overflow-y: auto; }

/*
 * The column: the menu frame and, on the home screen, the two panels beside
 * it — identity and the record. One flex column, so on a narrow screen they
 * stack and scroll together, and on a wide one the whole column is what gets
 * pinned to the right (see the 1080 rule).
 */
.menu-column {
  display: flex;
  flex-direction: column;
  gap: 12px;
  width: min(660px, calc(100vw - 40px));
  max-height: calc(100vh - 48px);
  min-height: 0;
}
.menu-column > .menu-frame { width: 100%; flex: 0 0 auto; min-height: 0; }
.menu-column > .menu-frame[hidden] { display: none; }
/* Below the wide layout the whole column scrolls as a page, not each frame. */
@media (max-width: 1079px) {
  #menu { align-items: flex-start; overflow-y: auto; }
  .menu-column { max-height: none; }
  .menu-column > .menu-frame { max-height: none; }
  .menu-column > .menu-frame > .menu-body { overflow: visible; }
}

/* The side panels: the same housing, less air. */
.menu-frame.aux > .console-head { padding: 12px 22px 7px; }
.aux-body { padding: 4px 22px 16px; display: flex; flex-direction: column; gap: 8px; }
.aux-note { margin: 0; }
/* Identity: the line and the button share a row when there is room. */
#accountPanel .aux-body { flex-direction: row; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 8px 16px; }
#accountPanel .aux-note { flex: 1 1 260px; }
#accountPanel .menu-row { margin: 0; }
.menu-frame.record > .record-body { flex: 1 1 auto; min-height: 0; overflow: hidden; display: flex; flex-direction: column; }
.menu-frame.record .record-body > * { flex: 0 0 auto; }
.menu-frame.record .record-body > .board { flex: 1 1 auto; overflow-y: auto; min-height: 0; }
.chips-row { display: flex; flex-wrap: wrap; gap: 6px 14px; align-items: center; }
.chips-row .chips + .chips { padding-left: 14px; border-left: 1px solid var(--line); }
.board-gap { text-align: center; font-family: var(--mono); font-size: 10px; letter-spacing: 0.4em; color: var(--muted); padding: 0; line-height: 10px; }
/* Your row, pinned under the board when it is below the fold: outside the
   scroll, so a short screen never hides the one row the panel is about. */
.board-pin { max-height: none; overflow: visible; }
.board-pin[hidden] { display: none; }
.board-row.me .board-name { color: var(--hazard); }
.board-row.me .board-value { color: var(--hazard); }

/* Your own line: readouts in a row, wrapping when the panel is narrow. */
.me-line { display: flex; flex-wrap: nowrap; gap: 6px; }
.me-line .me-cell { flex: 1 1 0; min-width: 0; padding: 6px 8px; }
.me-line .me-v { font-size: 13px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

.menu-title {
  margin: 8px 0 2px;
  font-size: 30px;
  font-weight: 800;
  letter-spacing: 0.09em;
}
.menu-title span { color: var(--accent); }
.menu-tagline {
  margin: 0 0 20px;
  max-width: 46ch;
  font-size: 12px;
  line-height: 1.5;
  color: var(--muted);
}

.menu-screen { display: flex; flex-direction: column; gap: 10px; }
.menu-screen[hidden] { display: none; }

/*
 * A mode. Numbered down the left like a bulkhead sign, because the three of
 * them are a list of doors rather than a list of settings.
 */
.mode {
  display: grid;
  grid-template-columns: 30px 1fr;
  grid-template-areas: "mark name" "mark desc";
  gap: 3px 12px;
  padding: 12px 14px;
  text-align: left;
  font: inherit;
  color: var(--text);
  background: rgba(20, 27, 40, 0.7);
  border: 1px solid var(--line);
  border-left: 2px solid rgba(79, 182, 216, 0.35);
  border-radius: 4px;
  cursor: pointer;
  transition: background 0.15s ease, border-color 0.15s ease, transform 0.15s ease;
}
.mode:hover:not(:disabled) {
  background: rgba(31, 42, 61, 0.85);
  border-left-color: var(--accent);
  transform: translateX(2px);
}
.mode:disabled { opacity: 0.45; cursor: not-allowed; }
.mode-mark {
  grid-area: mark;
  align-self: center;
  font-family: var(--mono);
  font-size: 11px;
  color: var(--hazard);
}
.mode-name { grid-area: name; font-weight: 700; letter-spacing: 0.04em; }
.mode-desc { grid-area: desc; font-size: 11.5px; line-height: 1.45; color: var(--muted); }

.menu-label {
  display: block;
  margin-top: 10px;
  font-family: var(--mono);
  font-size: 9.5px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: rgba(140, 175, 215, 0.75);
}

/* A row of counts, not a number field: one click, one decision, and no state in
   which the input says something the game cannot honour. */
.chips { display: flex; flex-wrap: wrap; gap: 6px; }
.chip {
  min-width: 44px;
  padding: 7px 10px;
  font-family: var(--mono);
  font-size: 13px;
  color: var(--muted);
  background: rgba(20, 27, 40, 0.7);
  border: 1px solid var(--line);
  border-radius: 4px;
  cursor: pointer;
}
.chip:hover { color: var(--text); }
.chip.on {
  color: var(--text);
  background: rgba(79, 182, 216, 0.16);
  border-color: var(--accent);
}


/* A side's header row on the scoreboard: the running total, which is the
 * only number a team match is actually played for. */
#scoreboard tr.team-head td {
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  padding-top: 7px;
  border-bottom: 1px solid rgba(120, 160, 210, 0.18);
}

.menu-row { display: flex; align-items: center; gap: 10px; margin-top: 16px; }
.menu-row.tight { margin-top: 8px; }

.commit {
  margin-left: auto;
  padding: 10px 18px;
  font: inherit;
  font-weight: 700;
  letter-spacing: 0.06em;
  color: var(--text);
  background: linear-gradient(180deg, rgba(79, 182, 216, 0.22), rgba(79, 182, 216, 0.09));
  border: 1px solid rgba(79, 182, 216, 0.5);
  border-radius: 4px;
  cursor: pointer;
}
.commit:hover:not(:disabled) { filter: brightness(1.18); }
.commit:disabled { opacity: 0.4; cursor: not-allowed; }

/* Specific enough to beat `.panel button`, since one of these lives on the
   loadout console. */
.ghost,
.panel button.ghost {
  padding: 9px 14px;
  font: inherit;
  color: var(--muted);
  background: transparent;
  border: 1px solid var(--line);
  border-radius: 4px;
  cursor: pointer;
}
.ghost:hover,
.panel button.ghost:hover { color: var(--text); border-color: rgba(140, 175, 215, 0.45); }

.menu-field { display: flex; gap: 8px; margin-top: 8px; }
.menu-field input {
  flex: 1;
  min-width: 0;
  padding: 9px 11px;
  font: inherit;
  font-family: var(--mono);
  color: var(--text);
  background: rgba(8, 12, 20, 0.85);
  border: 1px solid var(--line);
  border-radius: 4px;
}
.menu-field input:focus { outline: none; border-color: var(--accent); }
.menu-field .commit { margin-left: 0; }

.menu-note {
  margin: 6px 0 0;
  font-size: 11.5px;
  line-height: 1.5;
  color: var(--muted);
}
.menu-note.good { color: #7bed9f; }
.menu-note.warn { color: #ffd9a8; }

.server {
  display: flex;
  align-items: center;
  gap: 12px;
  width: 100%;
  padding: 12px 14px;
  text-align: left;
  font: inherit;
  color: var(--text);
  background: rgba(20, 27, 40, 0.7);
  border: 1px solid var(--line);
  border-left: 2px solid rgba(79, 182, 216, 0.35);
  border-radius: 4px;
  cursor: pointer;
}
.server:hover:not(:disabled) {
  background: rgba(31, 42, 61, 0.85);
  border-left-color: var(--accent);
}
.server:disabled { opacity: 0.5; cursor: not-allowed; }
.server-name { font-weight: 700; }
.server-region {
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.15em;
  color: var(--hazard);
}
.server-stat { margin-left: auto; font-family: var(--mono); font-size: 11px; color: var(--muted); }
.server-stat.good { color: #7bed9f; }
.server-stat.bad { color: #ff6b6b; }

/* ------------------------------------------------------------- the record */

/* A board: rank, name, matches, value. The same housing as a server row, read
   rather than pressed, and the player's own line marked in the hazard colour. */
.board { display: flex; flex-direction: column; gap: 3px; max-height: 38vh; overflow-y: auto; }
.board-row {
  display: flex;
  align-items: baseline;
  gap: 12px;
  padding: 6px 10px;
  background: rgba(20, 27, 40, 0.55);
  border: 1px solid var(--line);
  border-left: 2px solid rgba(79, 182, 216, 0.25);
  border-radius: 4px;
  font-size: 13px;
}
.board-row.me { border-left-color: var(--hazard); background: rgba(201, 166, 62, 0.08); }
.board-rank { font-family: var(--mono); font-size: 11px; color: var(--muted); min-width: 2ch; }
.board-name { font-weight: 700; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.board-matches { font-family: var(--mono); font-size: 10px; letter-spacing: 0.08em; color: var(--muted); }
.board-value { margin-left: auto; font-family: var(--mono); font-size: 12px; color: var(--accent); }

/* Your own line, as a grid of readouts. */
.me-cell {
  display: flex; flex-direction: column; gap: 2px;
  padding: 8px 10px;
  background: rgba(20, 27, 40, 0.55);
  border: 1px solid var(--line);
  border-radius: 4px;
}
.me-k { font-family: var(--mono); font-size: 9.5px; letter-spacing: 0.14em; text-transform: uppercase; color: var(--muted); }
.me-v { font-weight: 700; font-size: 14px; }

/* -------------------------------------------------- the bridge console */

/*
 * The screen-wide head. It belongs to the screen rather than to the panel:
 * everything on the front of the game is one console, and this is its top
 * edge. Fixed at every width — on narrow screens it is simply a slim strip
 * above the centred panel.
 */
.menu-tophead {
  position: fixed;
  top: 0; left: 0; right: 0;
  z-index: 22;
  margin: 0;
  padding: 13px 22px 10px;
  background: rgba(6, 9, 15, 0.78);
  border-bottom: 1px solid rgba(140, 175, 215, 0.16);
}
#menu { padding-top: 64px; }

/*
 * Above 1080 the front of the game is an instrument cluster: the head across
 * the top, the menu panel a full-height column on the right, the briefing a
 * dashboard band along the bottom, and the region left over is the viewport —
 * the ship's window, which nothing is ever drawn over. The scan log sits in
 * the viewport's lower corner like the readout it is.
 *
 * The geometry here has a twin in intro.js (`panelSpan`, `HEAD_H`, `BAND_H`,
 * `LAYOUT_AT`): the canvas centres the hull in the viewport this layout
 * leaves. If the two disagree, the ship slides under the panel or the band —
 * a drawing fault caused by a stylesheet, which is the worst kind to find.
 */
@media (min-width: 1080px) {
  #menu {
    display: block;
    padding: 0;
  }

  /* Content-fit, not stretched to the band: a column of empty housing under
     the Settings button reads as a panel waiting for features. Tall screens
     get a shorter panel and more viewport; the cap keeps long screens (the
     bindings list) scrolling inside it instead of under the band.

     Scoped to #menu, and that scope was learned: an unscoped `.menu-frame`
     here pinned every *dialog* frame — the transmission, the leave-match
     confirm, the gate — to the menu panel's corner on wide screens, because
     they all share the housing class. The housing is shared; the console
     layout is the menu's own. */
  #menu .menu-column {
    position: fixed;
    top: 58px;
    right: 20px;
    width: min(580px, 38vw);
    max-height: calc(100vh - 58px - 224px - 18px);
    /* Frames never shrink; on a short screen the column scrolls as one. */
    overflow-y: auto;
  }
  #menu .menu-frame { width: 100%; max-height: none; min-height: 0; }
  /* The menu keeps its own height; the record takes what is left, and its
     board scrolls inside it rather than under the band. */
  #menu .menu-frame.record { flex: 1 1 auto; overflow: hidden; min-height: 150px; }
  /* The board is the point of the panel: it never collapses below two rows,
     and takes every spare pixel above that. */
  #menu .menu-frame.record .record-body > .board { min-height: 66px; }
  #menu .menu-frame.record .board { max-height: none; }
  #menu .menu-body { padding-bottom: 20px; }

  /* The dashboard band, spanning the whole width under viewport and panel
     alike — it is the console's lower edge, not the home screen's footer. */
  .brief-band {
    position: fixed;
    left: 0; right: 0; bottom: 0;
    height: 204px;
    margin: 0;
    padding: 14px 26px 14px;
    border: 0;
    border-top: 1px solid rgba(140, 175, 215, 0.18);
    border-radius: 0;
    background: rgba(7, 10, 17, 0.88);
    display: flex;
    flex-direction: column;
  }
  .brief-band::before { border-radius: 0; }

  .band-cols {
    flex-direction: row;
    gap: clamp(24px, 3.5vw, 64px);
    min-height: 0;
  }
  .band-cols section { flex: 1; min-width: 0; }
  .band-cols p { font-size: clamp(11.5px, 0.85vw, 13px); }
  .brief-kicker { margin-top: auto; font-size: clamp(11.5px, 0.85vw, 13px); }

  /* The scan log, in the viewport's lower-left corner, above the band. */
  .scan-log {
    position: fixed;
    left: 26px;
    bottom: 228px;
    width: min(440px, 34vw);
    margin: 0;
    background: rgba(6, 9, 15, 0.72);
  }

  /* Arriving, once, on the way in — a stagger of delays rather than a clock. */
  .scan-log, .brief-band {
    animation: briefIn 0.6s ease both;
  }
  .brief-band { animation-delay: 0.15s; }
  .scan-log { animation-delay: 0.35s; }
  .scan-log .beat { opacity: 0; animation: briefIn 0.5s ease forwards; }
  .scan-log .beat:nth-child(2) { animation-delay: 0.55s; }
  .scan-log .beat:nth-child(3) { animation-delay: 0.75s; }
  .scan-log .beat:nth-child(4) { animation-delay: 0.95s; }
  .scan-log .beat:nth-child(5) { animation-delay: 1.15s; }
  .scan-log .beat:nth-child(6) { animation-delay: 1.35s; }
}

/* A short window — 720 is the common case — has a short viewport, and the log
   at full size eats the hull's stern. Compact, matching the `short` branch of
   `layout()` in intro.js, which slides the ship up and right of the log. */
@media (min-width: 1080px) and (max-height: 900px) {
  .scan-log { width: min(370px, 30vw); padding: 10px 12px; }
  .scan-log .beat { font-size: 11px; margin-bottom: 6px; }
  /* No briefing band on a short screen: the scan log carries the story, and
     the 188px it took is what puts the record above the fold on a 720
     window — the panel that is meant to convert should not be under it.
     intro.js's bandSpan() knows (0 at 900 and below). */
  .brief-band { display: none; }
  #menu .menu-column { max-height: calc(100vh - 58px - 18px); }
  .scan-log { bottom: 24px; }
  /* And the menu gives back the tagline: on 720 the record needs the rows more
     than the panel needs its subtitle. The record keeps a floor so its footer
     — Show top 100, Read again — is never the part that gets clipped; past
     that the column scrolls as one. */
  #menu .menu-tagline { display: none; }
  #menu .menu-title { margin-bottom: 14px; }
  #menu .menu-frame.record { min-height: 236px; }
  #accountPanel .aux-body { padding-bottom: 12px; gap: 6px 16px; }
}

@keyframes briefIn {
  from { opacity: 0; transform: translateY(10px); }
  to { opacity: 1; transform: translateY(0); }
}

/* Somebody who has asked for less movement gets the instruments and none of
   the arrival. */
@media (prefers-reduced-motion: reduce) {
  .scan-log, .brief-band, .scan-log .beat { animation: none; opacity: 1; }
}

/*
 * Large displays. The app opens fullscreen, so a panel sized for a 1280 window
 * is a small island on a 2560 one — the housing grows and the type with it,
 * rather than the whole thing being scaled, which would blur the hairlines the
 * corner brackets and hazard strip are drawn from.
 */
@media (min-width: 1800px) {
  #menu .menu-column { width: min(660px, 34vw); }
  .menu-frame > .console-head { padding: 20px 34px 11px; }
  .menu-body { padding: 8px 34px 30px; }
  .menu-title { font-size: 38px; }
  .menu-tagline { font-size: 14px; margin-bottom: 26px; }
  .mode { padding: 15px 18px; }
  .mode-name { font-size: 17px; }
  .mode-desc { font-size: 13px; }
  .menu-note { font-size: 13px; }
  .server { padding: 15px 18px; }

  /* The loadout is the other screen a player reads at rest. */
  .console { width: min(1200px, calc(100vw - 40px)); }
}

/* ================================================================= results */

/*
 * The end of a match. Over the deck rather than instead of it — play does not
 * stop during the intermission, and blanking the arena would hide a world
 * people are still walking around in. So: a panel, off to one side of nothing,
 * with the game visible around it.
 *
 * Below the menu in the stack and above the HUD, because leaving mid-results
 * has to put the menu in front of this.
 */
#results {
  position: fixed;
  inset: 0;
  z-index: 12;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
  pointer-events: none;   /* the deck underneath stays live */
  transition: opacity 0.25s ease;
}
#results.hidden { opacity: 0; visibility: hidden; transition: opacity 0.25s ease, visibility 0s linear 0.25s; }

.results-frame {
  position: relative;
  width: min(520px, calc(100vw - 40px));
  max-height: calc(100vh - 60px);
  display: flex;
  flex-direction: column;
  padding: 16px 24px 20px;
  background:
    linear-gradient(150deg, rgba(61, 74, 99, 0.4), transparent 42%),
    rgba(10, 14, 22, 0.93);
  border: 1px solid rgba(140, 175, 215, 0.24);
  border-radius: 5px;
  box-shadow: 0 30px 70px rgba(0, 0, 0, 0.75), inset 0 1px 0 rgba(160, 200, 245, 0.1);
  /* The deck stays visible through this, and the reactor is the brightest thing
     on it — a flat translucent ground put its glow straight through the final
     table. Blurring what is behind keeps the sense of a panel over a running
     world while leaving the numbers readable, the same trick the scoreboard
     uses for the same reason. */
  backdrop-filter: blur(7px);
}
/* The same hazard strip every threshold on the deck carries. */
.results-frame::before {
  content: "";
  position: absolute;
  left: 0; right: 0; top: 0;
  height: 4px;
  border-radius: 5px 5px 0 0;
  background: repeating-linear-gradient(
    -45deg, var(--hazard) 0 7px, rgba(43, 36, 19, 0.95) 7px 14px);
  opacity: 0.55;
}
.results-frame .console-head { margin-bottom: 12px; }
.results-frame .console-head .lamp { background: var(--hazard); box-shadow: 0 0 8px var(--hazard); }

.results-winner {
  margin: 2px 0 14px;
  font-size: 20px;
  font-weight: 800;
  letter-spacing: 0.02em;
}
.results-winner em {
  display: block;
  margin-top: 2px;
  font-size: 11.5px;
  font-weight: 500;
  font-style: normal;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--muted);
}

/* A full lobby must not grow the panel past the viewport. */
.results-scroll {
  overflow-y: auto;
  pointer-events: auto;
  scrollbar-width: thin;
  scrollbar-color: rgba(255, 255, 255, 0.16) transparent;
}
#resultsTable { width: 100%; border-collapse: collapse; font-size: 12.5px; }
#resultsTable th {
  position: sticky;
  top: 0;
  padding: 6px 0;
  text-align: left;
  font-size: 9px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--muted);
  background: rgba(12, 17, 26, 0.97);
  border-bottom: 1px solid rgba(140, 175, 215, 0.18);
}
#resultsTable th:not(:nth-child(2)), #resultsTable td:not(:nth-child(2)) {
  text-align: right;
  width: 44px;
}
#resultsTable th:first-child, #resultsTable td:first-child { text-align: left; width: 26px; }
#resultsTable td {
  padding: 6px 0;
  font-weight: 600;
  color: rgba(233, 237, 245, 0.86);
}
#resultsTable td:nth-child(2) {
  max-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
#resultsTable tr.me td { background: rgba(90, 200, 250, 0.12); color: #fff; }

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

.menu-slider { display: flex; align-items: center; gap: 12px; margin-top: 8px; }
.menu-slider input[type="range"] {
  flex: 1;
  min-width: 0;
  height: 4px;
  appearance: none;
  background: rgba(79, 182, 216, 0.2);
  border-radius: 2px;
  outline: none;
}
.menu-slider input[type="range"]::-webkit-slider-thumb {
  appearance: none;
  width: 14px; height: 14px;
  border-radius: 50%;
  background: var(--accent);
  box-shadow: 0 0 8px rgba(79, 182, 216, 0.6);
  cursor: pointer;
}
.slider-value {
  min-width: 42px;
  font-family: var(--mono);
  font-size: 12px;
  color: var(--muted);
  text-align: right;
}
.ghost.small { padding: 6px 11px; font-size: 12px; }
.ghost.on { color: var(--text); border-color: var(--hazard); }

/* One action per row, its keys on the right. Always two slots, so a primary and
   an alternate read as a pair rather than the second appearing from nowhere. */
.binds { margin-top: 8px; display: flex; flex-direction: column; gap: 4px; }
.bind-row {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 5px 8px;
  border-radius: 3px;
}
.bind-row:nth-child(odd) { background: rgba(255, 255, 255, 0.025); }
.bind-label { flex: 1; font-size: 12.5px; color: var(--muted); }
.bind-key {
  min-width: 72px;
  padding: 5px 8px;
  font-family: var(--mono);
  font-size: 11px;
  color: var(--text);
  background: rgba(20, 27, 40, 0.85);
  border: 1px solid var(--line);
  border-radius: 3px;
  cursor: pointer;
}
.bind-key:hover { border-color: rgba(140, 175, 215, 0.5); }
.bind-key.empty { color: rgba(232, 238, 247, 0.28); }
/* Waiting for a key. The hazard colour, because it is the one state on this
   screen where the next thing you press does something other than what it
   normally does. */
.bind-key.listening {
  color: #0a0d16;
  background: var(--hazard);
  border-color: var(--hazard);
}

/* ----------------------------------------------------------- leave a match */

/*
 * "Are you sure" over a running deck. Above the results panel — you can be
 * asked this while the results stand.
 *
 * It takes the pointer across the whole screen, not just on its buttons: the
 * mouse that clicks "Leave" is the mouse that fires the weapon, and a click
 * landing in the gap between the buttons must not go through to the arena.
 */
#confirmLeave {
  position: fixed;
  inset: 0;
  z-index: 24;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
  background: rgba(4, 6, 11, 0.55);
  backdrop-filter: blur(2px);
  transition: opacity 0.15s ease;
}
#confirmLeave.hidden {
  opacity: 0;
  pointer-events: none;
  visibility: hidden;
  transition: opacity 0.15s ease, visibility 0s linear 0.15s;
}
#confirmLeave .menu-frame { width: min(440px, calc(100vw - 40px)); }
#confirmLeave .menu-title { font-size: 26px; }
#confirmLeave .menu-title span { color: var(--hazard); }
#confirmLeave .console-head .lamp { background: var(--hazard); box-shadow: 0 0 8px var(--hazard); }
#confirmLeave .menu-row { margin-top: 20px; }

/* The arena hides the cursor; a dialog needs it back. */
body.confirming #game { cursor: default; }

/* ----------------------------------------------------------- transmission */

/*
 * The message of the day, in the same housing as everything else — it is a
 * note from the operator, and it should arrive looking like the console
 * received it rather than like a website interrupted the game.
 */
#motd {
  position: fixed;
  inset: 0;
  z-index: 26;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
  background: rgba(4, 6, 11, 0.55);
  backdrop-filter: blur(2px);
  transition: opacity 0.15s ease;
}
#motd.hidden {
  opacity: 0;
  pointer-events: none;
  visibility: hidden;
  transition: opacity 0.15s ease, visibility 0s linear 0.15s;
}
#motd .menu-frame { width: min(520px, calc(100vw - 40px)); }
#motd .console-head .lamp { animation-duration: 1.1s; }

/* The words themselves, plain and readable — the chrome is the terminal's,
   the message is a person's. `pre-line` keeps the sender's line breaks. */
.motd-text {
  margin: 18px 0 4px;
  font-size: 15px;
  line-height: 1.65;
  color: rgba(232, 238, 247, 0.94);
  white-space: pre-line;
}
#motd .menu-row { margin-top: 20px; }

/* --------------------------------------------------------------- the muster */

/*
 * The waiting room.
 *
 * Built from the same housing as the airlock — same console, same panel, same
 * head strip — because it is the same terminal showing a different page, and a
 * second visual language for the screen immediately before a match would read
 * as a different game.
 */
#room {
  position: fixed;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  background: #04060b;
  z-index: 20;
}
#room.hidden { display: none; }

#roomBg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
}

.room-panel { padding-top: 14px; }

/* Why the button is dead, in the server's own words — see Room.canStart. Goes
   warm when it stops being a reason and becomes an announcement. */
.room-note {
  min-height: 18px;
  margin: 10px 0 14px;
  font: 600 12px/1.4 var(--mono);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--muted);
}
.room-note.go { color: var(--bio); }

/* One column per side, side by side: a team match is a comparison, and stacked
   rows would make it a scroll. */
.room-sides {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  align-items: flex-start;
}
.room-side {
  flex: 1 1 200px;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding: 0 0 8px;
  background: rgba(20, 27, 40, 0.7);
  border: 1px solid var(--line);
  /* The side's own colour, set inline from Sim.TEAM_COLORS and nowhere else —
     a second copy of the palette here would be a second thing to keep in step
     with the deck the player then walks onto. */
  border-top: 3px solid;
  border-radius: 4px;
}
.room-side.mine { background: rgba(28, 40, 58, 0.85); }

/* The header is the switch: to join a side you click the side. */
.room-side-head {
  padding: 8px 10px;
  font: 700 12px/1 var(--mono);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  text-align: left;
  background: none;
  border: 0;
  border-bottom: 1px solid var(--line);
  cursor: pointer;
}
.room-side-head:disabled { cursor: default; opacity: 0.75; }
.room-side-head:not(:disabled):hover { background: rgba(79, 182, 216, 0.12); }

.room-seat {
  display: grid;
  grid-template-columns: 16px 1fr auto;
  align-items: baseline;
  gap: 6px;
  padding: 5px 10px;
  text-align: left;
  font-size: 13px;
  color: var(--text);
}
.room-seat.me { background: rgba(79, 182, 216, 0.10); }
.room-seat .room-tick { font-family: var(--mono); color: var(--muted); }
.room-seat.ready .room-tick { color: var(--bio); }
.room-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* What a teammate is carrying. Blank for everybody else, because it never
   arrived — see Room.payloadFor on the server. */
.room-kit {
  grid-column: 2 / -1;
  font: 500 11px/1.3 var(--mono);
  letter-spacing: 0.03em;
  color: var(--muted);
}

.room-kick {
  grid-row: 1;
  grid-column: 3;
  width: 18px;
  height: 18px;
  padding: 0;
  font: 700 13px/1 var(--mono);
  color: var(--muted);
  background: none;
  border: 1px solid transparent;
  border-radius: 3px;
  cursor: pointer;
}
.room-kick:hover { color: #ff8b8b; border-color: rgba(255, 139, 139, 0.4); }

.room-row {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  justify-content: flex-end;
  margin: 16px 0 6px;
}
.room-row .ghost.on {
  color: var(--text);
  background: rgba(79, 211, 154, 0.16);
  border-color: var(--bio);
}

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

/*
 * Two surfaces, one set of rules for a line.
 *
 * Cold by default, like everything structural: the ground is the same steel
 * the killfeed and the scoreboard sit on, and the only warm marks are the ones
 * that are actually powered — a side's own colour on a callsign, and the same
 * colour again on the TEAM tag that says who else read it.
 */
#chat {
  position: fixed;
  left: 22px;
  /* Set from script against the deck plan's real footprint — see `place()` in
     chat.js. This is only what the corner looks like before the first frame. */
  bottom: 60px;
  width: 429px;
  max-width: 46vw;
  display: flex;
  flex-direction: column;
  gap: 6px;
  z-index: 6;
  /* Transparent to the mouse. A click meant for the arena that landed on a
     chat row instead would be a death; the room panel is where a callsign is
     clickable, because there is a pointer to spare there. */
  pointer-events: none;
}
#chat.off { display: none; }

.chat-log {
  display: flex;
  flex-direction: column;
  gap: 3px;
  align-items: flex-start;
}

.chat-line {
  max-width: 100%;
  background: rgba(10, 14, 24, 0.78);
  border: 1px solid rgba(255, 255, 255, 0.08);
  border-left: 3px solid var(--line);
  border-radius: 4px;
  padding: 4px 9px;
  font-size: 12.5px;
  line-height: 1.35;
  color: var(--text);
  display: flex;
  align-items: baseline;
  gap: 6px;
  animation: slideIn 0.18s ease-out;
  /* A sentence wraps rather than being clipped: half a message is worse than
     a tall box. */
  overflow-wrap: anywhere;
}
/* Said to one side only, and marked so — you can tell at a glance whether the
   other four read it, which is the whole reason the scope exists. */
.chat-line.team { border-left-color: currentColor; }
.chat-line.me { background: rgba(79, 182, 216, 0.12); }
/* The server talking, and this client talking to itself. Both quieter than
   anybody's words, and neither carries a callsign that could be mistaken for
   one. */
.chat-line.sys, .chat-line.note {
  color: var(--muted);
  font-style: italic;
  border-left-color: rgba(255, 255, 255, 0.08);
}
.chat-line.note { border-left-color: var(--amber); font-style: normal; }

/*
 * Who could read this, on every line.
 *
 * ALL is the cold, ordinary case and is drawn as such; TEAM takes the side's
 * own colour, set inline from Sim.TEAM_COLORS. That split is the art
 * direction's rule applied to text: the warm mark belongs to the narrowed
 * case, so the eye still goes to the restricted lines rather than to all of
 * them equally.
 */
.chat-tag {
  flex: none;
  min-width: 30px;
  text-align: center;
  padding: 1px 4px;
  border-radius: 2px;
  font: 700 9px/1.5 var(--mono);
  letter-spacing: 0.1em;
  color: rgba(232, 238, 247, 0.42);
  background: rgba(255, 255, 255, 0.05);
  align-self: center;
}
.chat-tag.team {
  color: var(--accent);
  background: rgba(255, 255, 255, 0.09);
}

.chat-who {
  font-weight: 700;
  white-space: nowrap;
  color: var(--text);
}
/* A button on the room panel only. Stripped back to looking like the text it
   replaces, because it is the callsign that is clickable, not a control beside
   one. */
button.chat-who {
  padding: 0;
  font: inherit;
  font-weight: 700;
  background: none;
  border: 0;
  border-bottom: 1px dashed transparent;
  cursor: pointer;
}
button.chat-who:hover { border-bottom-color: currentColor; }
button.chat-who:disabled { cursor: default; }

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

/*
 * The entry. Absent when closed rather than dimmed: while it is up the
 * keyboard belongs to it and the player is not driving, so which state it is
 * in must never be a matter of reading a shade.
 */
.chat-entry {
  display: flex;
  align-items: stretch;
  gap: 0;
  background: rgba(10, 14, 24, 0.92);
  border: 1px solid var(--line);
  border-radius: 4px;
  overflow: hidden;
  pointer-events: auto;
}
.chat-entry.hidden { display: none; }
.chat-entry input {
  flex: 1 1 auto;
  min-width: 0;
  padding: 6px 9px;
  font: 500 13px/1.2 var(--font);
  color: var(--text);
  background: none;
  border: 0;
  outline: none;
}
.chat-entry input::placeholder { color: rgba(232, 238, 247, 0.38); }

/*
 * Who is about to read this — and, in a game with sides, the control that
 * changes it.
 *
 * Warm only when it is not everybody: an "ALL" that glowed would be claiming a
 * restriction that is not there. In a free-for-all it is not a control at all,
 * so it goes flat rather than inviting a click that would do nothing.
 */
.chat-scope {
  padding: 0 9px;
  font: 700 10px/1 var(--mono);
  letter-spacing: 0.12em;
  color: var(--muted);
  background: rgba(255, 255, 255, 0.04);
  border: 0;
  border-right: 1px solid var(--line);
  cursor: default;
}
.sided .chat-scope {
  cursor: pointer;
  /* It is a switch, so it says so before it is hovered — a control nobody
     knows is a control is a control nobody uses. */
  box-shadow: inset 0 -2px 0 rgba(79, 182, 216, 0.45);
}
.sided .chat-scope:hover { background: rgba(79, 182, 216, 0.16); }

/* While the entry is up you are reading the log rather than glancing at it,
   so it stops being something the arena shows through. */
#chat.open .chat-line { background: rgba(10, 14, 24, 0.92); }

/* Belongs to a match, like the rest of the HUD. */
#chat { visibility: hidden; }
body.playing #chat { visibility: visible; }

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

.room-chat {
  margin-top: 14px;
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.room-chat.off { display: none; }
/* Tall enough to hold a conversation and capped so it cannot push the sides
   and the buttons off a short screen — the muster is what this panel is for. */
.room-chat .chat-log {
  height: 132px;
  overflow-y: auto;
  padding: 6px;
  background: rgba(20, 27, 40, 0.7);
  border: 1px solid var(--line);
  border-radius: 4px;
}
.room-chat .chat-line {
  background: none;
  border: 0;
  border-left: 3px solid transparent;
  border-radius: 0;
  padding: 2px 6px;
  animation: none;
}
.room-chat .chat-line.me { background: rgba(79, 182, 216, 0.10); }
.room-chat .chat-entry { pointer-events: auto; }

@media (max-width: 820px) {
  /* The plan takes a bigger share of a narrow screen and the corner is
     tighter, so the deck's log gives up rows rather than overlapping it. */
  #chat { max-width: 62vw; font-size: 11.5px; }
  .chat-line { padding: 3px 7px; }
  .room-chat .chat-log { height: 96px; }
}

/* -------------------------------------------------------------- keycard */

/*
 * The controls card.
 *
 * Its own corner rather than a third block under the killfeed: that column
 * changes height every time somebody dies, and a reference that moves is a
 * reference you have to hunt for. Cold throughout — it is a label, not a
 * signal, and nothing on it is powered.
 */
#keycard {
  position: fixed;
  right: 12px;
  bottom: 12px;
  width: 232px;
  padding: 8px 10px 9px;
  background: rgba(10, 14, 24, 0.72);
  border: 1px solid rgba(255, 255, 255, 0.08);
  border-radius: 4px;
  z-index: 5;
  /* Never takes a click. One meant for the arena that this swallowed would be
     a death — the same rule the rest of the HUD keeps. */
  pointer-events: none;
}
#keycard.off { display: none; }

.keycard-head {
  display: flex;
  align-items: center;
  gap: 6px;
  margin-bottom: 6px;
}
.keycard-head .desig {
  font: 700 9px/1 var(--mono);
  letter-spacing: 0.18em;
  color: rgba(140, 175, 215, 0.75);
}
.keycard-head .head-rule {
  flex: 1;
  height: 1px;
  background: rgba(255, 255, 255, 0.1);
}
.keycard-head .head-note {
  font: 700 9px/1 var(--mono);
  letter-spacing: 0.1em;
  color: var(--muted);
}
.keycard-head .lamp {
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: rgba(140, 175, 215, 0.5);
}

.keycard-row {
  display: flex;
  align-items: baseline;
  gap: 7px;
  padding: 1.5px 0;
  font-size: 11px;
  color: var(--muted);
}
/* One column for the keys so the labels line up: a ragged left edge makes a
   list of thirteen rows read as thirteen separate things. */
.keycard-keys {
  flex: none;
  width: 78px;
  display: flex;
  flex-wrap: wrap;
  gap: 2px;
}
.keycard-keys kbd {
  min-width: 15px;
  padding: 1px 3px;
  font: 600 9.5px/1.35 var(--mono);
  text-align: center;
  color: rgba(232, 238, 247, 0.82);
  background: rgba(255, 255, 255, 0.07);
  border: 1px solid rgba(255, 255, 255, 0.12);
  border-radius: 3px;
}
.keycard-what { min-width: 0; }
.keycard-rule {
  height: 1px;
  margin: 5px 0;
  background: rgba(255, 255, 255, 0.07);
}

/* Belongs to a match, like the rest of the HUD. */
#keycard { visibility: hidden; }
body.playing #keycard { visibility: visible; }

/*
 * Off on a phone, and on anything short.
 *
 * Key names mean nothing to a thumb, and the airlock already explains the two
 * halves of the screen there. The width test matches the one that hides the
 * join screen's key list, so the two agree about what "small" means.
 */
@media (max-width: 820px), (max-height: 620px), (pointer: coarse) {
  #keycard { display: none; }
}
