:root { color-scheme: dark; }
* { box-sizing: border-box; }
body {
  margin: 0;
  font-family: system-ui, -apple-system, sans-serif;
  background: #11151a;
  color: #d7dee6;
  overflow: hidden;
  display: flex; flex-direction: column; height: 100vh;
}
header {
  padding: 8px 16px;
  background: #1a2027;
  border-bottom: 1px solid #2a323c;
  display: flex;
  align-items: center;
  gap: 14px;
}
h1 { font-size: 15px; margin: 0; white-space: nowrap; }
h1 a { color: inherit; text-decoration: none; }
h1 a:hover { color: #8fd3ff; }
h1 .sub { color: #6f87a0; font-weight: 400; font-size: 12px; margin-left: 8px; }
select, button {
  background: #2a3742; color: #d7dee6; border: 1px solid #3a4856;
  border-radius: 4px; padding: 5px 10px; cursor: pointer; font-size: 13px;
}
button:hover { background: #344450; }
button:disabled { opacity: .4; cursor: default; }
/* Fixed width so toggling "▶ Play" / "⏸ Pause" doesn't reflow the controls row. */
#play { min-width: 74px; text-align: center; }
select { padding: 5px 8px; }

.controls {
  display: flex; gap: 8px; align-items: center;
  padding: 7px 16px; background: #1a2027; border-bottom: 1px solid #2a323c;
}
/* Playback bar: sits below the map, so the border flips to the top edge. */
#bottombar { border-bottom: none; border-top: 1px solid #2a323c; }
.controls .grow { flex: 1; }
input[type=range] { flex: 1; min-width: 120px; accent-color: #5a9fd0; }

/* Timeline: the scrub slider with the downloaded-ranges bar underneath
   (video-player style: blue = fully loaded chunk, dim = keyframe only). */
#timeline { flex: 1; min-width: 120px; display: flex; flex-direction: column; gap: 1px; }
#timeline input[type=range] { width: 100%; margin: 0; }
#buffbar { height: 3px; border-radius: 2px; background: transparent; }
.timelabel {
  min-width: 190px; text-align: right; font-size: 12px;
  font-variant-numeric: tabular-nums; color: #9fb0bf;
}

/* ---- home view: the replay list shown when no replay is selected ----
   Toggled via body.home, which hides the playback chrome underneath. */
body.home .controls, body.home #wrap { display: none; }
#home { flex: 1; min-height: 0; overflow-y: auto; padding: 18px 24px; }
#hometable { border-collapse: collapse; width: 100%; max-width: 1650px; margin: 0 auto; font-size: 13px; }
#hometable th {
  text-align: left; font-size: 11px; text-transform: uppercase; letter-spacing: .04em;
  color: #6f87a0; font-weight: 600; padding: 4px 14px 6px 8px; border-bottom: 1px solid #2a323c;
}
/* Each cell is filled by a block-level link (the row IS a link: middle/ctrl
   click opens the replay in a new tab), so the cell padding lives on the <a>
   to keep the whole row surface clickable. */
#hometable td { padding: 0; border-bottom: 1px solid #202830; white-space: nowrap; }
#hometable td a {
  display: block; padding: 6px 14px 6px 8px;
  color: inherit; text-decoration: none;
}
#hometable td.map { white-space: normal; }
/* Settings badges: small muted pills, wrapping within the row's link. */
#hometable td.settings { white-space: normal; }
#hometable td.settings a { display: flex; flex-wrap: wrap; gap: 4px; }
#hometable .badge {
  font-size: 10px; line-height: 1; padding: 3px 6px; border-radius: 8px;
  background: #232e39; border: 1px solid #33404d; color: #9fb0bf;
  white-space: nowrap;
}
/* Per-flag badge colours: lava glows orange, mods (tweaked unit/def tables)
   warns yellow — the same yellow family as the energy bars; zombies rise in
   purple, ruins moss over in green, an explicitly unranked lobby cools blue. */
#hometable .badge-lava {
  color: #ffc46a; border-color: #b85c1a;
  /* ember gradient: hot orange rim fading into deep red coals */
  background: linear-gradient(180deg, #7a2606, #4a1204 55%, #38150a);
  text-shadow: 0 0 4px rgba(255, 140, 30, .5);
  box-shadow: 0 0 8px rgba(255, 110, 20, .35), inset 0 1px 3px rgba(255, 150, 40, .35), inset 0 -3px 6px rgba(230, 60, 0, .4);
}
#hometable .badge-mods { color: #e3c34a; border-color: #6e5c1e; background: #332b10; }
#hometable .badge-zombies { color: #c58aff; border-color: #5e3d85; background: #2a1a40; }
#hometable .badge-ruins { color: #7fd58a; border-color: #33663c; background: #16311b; }
#hometable .badge-unranked { color: #7fb2ff; border-color: #2f4d7a; background: #16243a; }
/* Players column: one span per side, "v" separators; the side that recorded
   the current upload gets a viewpoint dot. Full roster in the tooltip. */
#hometable td.players { white-space: normal; }
#hometable td.players .vs { color: #6f87a0; padding: 0 6px; font-size: 11px; }
#hometable td.players .side.uploader::before { content: '◉ '; }
/* Alternate uploads of the same game (superseded revisions keep playing). */
#hometable td.links a.alt {
  display: inline-block; padding: 6px 5px; font-size: 11px;
  color: #7fa6c4; text-decoration: none;
}
#hometable td.links a.alt:hover { text-decoration: underline; color: #8fd3ff; }
/* Admin-only columns (?admin=true): the refresh-button column and the Data
   (bundle size) column stay hidden unless the page is in admin mode. */
#hometable th.admin, #hometable td.admin,
#hometable th.data, #hometable td.data { display: none; }
body.admin-mode #hometable th.admin, body.admin-mode #hometable td.admin,
body.admin-mode #hometable th.data, body.admin-mode #hometable td.data { display: table-cell; }
#hometable td.admin button.refresh {
  font: inherit; font-size: 13px; line-height: 1; padding: 4px 7px; margin: 2px 4px;
  color: #9fb0bf; background: #232e39; border: 1px solid #33404d; border-radius: 6px;
  cursor: pointer;
}
#hometable td.admin button.refresh:hover:enabled { color: #cfe3f5; border-color: #4a5a6c; }
#hometable td.admin button.refresh:disabled { opacity: .6; cursor: default; }
/* External per-game links (gex, BAR): real links, visually distinct from the
   invisible row-link anchors filling the other cells. */
#hometable td.links a.ext {
  display: inline-block; padding: 6px 5px;
  color: #8fd3ff; text-decoration: none;
}
#hometable td.links a.ext:hover { text-decoration: underline; }
#hometable th.num, #hometable td.num { text-align: right; }
#hometable td { font-variant-numeric: tabular-nums; }
#hometable tbody tr { cursor: pointer; }
#hometable tbody tr:hover { background: #1b232c; }
#hometable td.dim { color: #6a7785; }
#homemsg { padding: 12px 8px; font-size: 13px; max-width: 1650px; margin: 0 auto; }

#wrap { display: flex; flex: 1; min-height: 0; }
#left { flex: 1; position: relative; }
canvas { display: block; width: 100%; height: 100%; background: #0c0f13; cursor: crosshair; }
/* WebGL icon overlay: sits on the 2D base canvas; input goes to #cv below. */
#glcv { position: absolute; left: 0; top: 0; background: transparent; pointer-events: none; }
#ovcv { position: absolute; left: 0; top: 0; background: transparent; pointer-events: none; }

#side {
  width: 320px; background: #161b21; border-left: 1px solid #2a323c;
  padding: 12px; overflow-y: auto; font-size: 13px;
}
#side h2 { font-size: 12px; text-transform: uppercase; letter-spacing: .04em;
  color: #6f87a0; margin: 14px 0 6px; }
#side h2:first-child { margin-top: 0; }
.stat { display: flex; justify-content: space-between; padding: 2px 0; }
.stat b { color: #fff; font-variant-numeric: tabular-nums; }

/* ---- player list ----
   One row per player: rank / flag / OS / name on the left, the metal + energy
   bars (small) on the right, laid out like BAR's in-game player list. */
#players { display: flex; flex-direction: column; }
.pgroup { border-top: 1px solid #2a323c; }
.pgroup:first-child { border-top: none; }
.prow {
  display: flex; align-items: center; gap: 6px; padding: 3px 0;
}
.prow.spectator { opacity: .55; }
.prow .phead {
  flex: 1; min-width: 0; display: flex; align-items: center; gap: 5px;
  font-size: 12px; line-height: 1.3;
}
/* rank badge: BAR's chevron/star icon (served from /ranks/), constant-size slot
   so names align whether or not a player has a rank. */
.prow .rank {
  flex: 0 0 auto; width: 18px; height: 14px; object-fit: contain;
  image-rendering: auto;
}
span.rank { width: 18px; } /* empty placeholder keeps the column when no icon */
.prow .flag { flex: 0 0 auto; width: 15px; text-align: center; font-size: 12px; line-height: 1; }
.prow .os {
  flex: 0 0 auto; min-width: 26px; text-align: right;
  color: #9fb0bf; font-variant-numeric: tabular-nums; font-size: 11px;
}
.prow .pname {
  flex: 1; min-width: 0; font-weight: 600;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* right block: the two resource meters, metal then energy, SIDE BY SIDE — one
   line per player, since stacking them made the list twice as tall as the roster
   it lists. The number sits ON its own bar — width = this player's income
   against the largest any team sustained in the last minute — so scanning the
   column ranks the economies at a glance. Hovering the row swaps BOTH halves at
   once to that resource's stock against the player's own storage cap, so the bar
   and the number under the cursor always mean the same thing; that is why all
   four elements share one fixed-width slot. Metal is silver, energy yellow (as
   in game), which is what tells the two meters apart on one line. */
.pres { flex: 0 0 auto; display: flex; align-items: center; gap: 4px; }
.resrow { display: flex; align-items: center; font-size: 10px; line-height: 1; }
.rmeter {
  position: relative; display: block; width: 70px; padding: 3px 4px;
  background: #222c36; border-radius: 3px; overflow: hidden;
  text-align: right; font-variant-numeric: tabular-nums;
}
.rmeter > i { position: absolute; inset: 0 auto 0 0; opacity: .32; } /* the bar */
.rmeter > b { position: relative; font-weight: 600; } /* above the bar */
/* Income bar + income number by default; stock bar + stock number on hover. The
   pairs are swapped together — a storage fill under an income figure would read
   as one measurement and be neither. */
.bstore, .rstore { display: none; }
.prow:hover .binc, .prow:hover .rinc { display: none; }
.prow:hover .bstore { display: block; }
.prow:hover .rstore { display: inline; }
/* Numbers follow the bar colour: metal white/silver, energy yellow. */
.resrow.metal .rmeter > i { background: #cfd6dd; }
.resrow.metal .rmeter > b { color: #cfd6dd; }
.resrow.energy .rmeter > i { background: #e3c34a; }
.resrow.energy .rmeter > b { color: #e3c34a; }

#tooltip {
  position: absolute; pointer-events: none; display: none;
  background: rgba(20,26,33,0.96); border: 1px solid #3a4856; border-radius: 4px;
  padding: 7px 9px; font-size: 12px; line-height: 1.5; color: #d7dee6;
  max-width: 240px; z-index: 10; box-shadow: 0 4px 10px rgba(0,0,0,0.5);
}
#tooltip h3 { margin: 0 0 3px; font-size: 12px; color: #fff; }
/* The internal def name next to the human one ("Construction Bot armck"). */
#tooltip h3 .code { color: #7c8b99; font-weight: normal; margin-left: 5px; }
#tooltip .row { display: flex; justify-content: space-between; gap: 12px; }
#tooltip .label { color: #8a98a6; }
#tooltip .bar { height: 5px; background: #222c36; border-radius: 3px; margin: 3px 0 1px; overflow: hidden; }
#tooltip .bar > div { height: 100%; }

#zoomlabel {
  position: absolute; left: 8px; bottom: 8px; pointer-events: none;
  background: rgba(20,26,33,0.82); border: 1px solid #2a323c; border-radius: 4px;
  padding: 3px 7px; font-size: 11px; color: #9fb0bf;
  font-variant-numeric: tabular-nums; z-index: 5;
}

/* A full-width sidebar button, for the handful of view toggles that earn a
   control rather than a constant at the top of app.js. */
.sidebtn { width: 100%; margin-bottom: 8px; font-size: 12px; }

/* ---- chat transcript ----
   The whole conversation, not a ticker: lines past the playhead stay listed but
   dimmed, so the panel reads as a transcript you can scroll and click (a click
   seeks to when the line was said). It scrolls itself to keep the playhead in
   view during playback. */
#chat {
  max-height: 240px; overflow-y: auto; overscroll-behavior: contain;
  border-top: 1px solid #2a323c; font-size: 12px; line-height: 1.4;
}
.chatrow { padding: 2px 0; cursor: pointer; word-break: break-word; }
.chatrow:hover { background: #1d242c; }
/* Not said yet at the playhead: not shown at all, so the log never spoils
   what is about to happen and the newest line is always the last one. */
.chatrow.future { display: none; }
.chattime {
  color: #6a7785; font-variant-numeric: tabular-nums; margin-right: 5px;
  font-size: 11px;
}
.chattag {
  color: #9fb0bf; background: #232b34; border-radius: 3px; padding: 0 4px;
  margin-right: 4px; font-size: 10px; text-transform: uppercase;
}

.hint { font-size: 11px; color: #6a7785; line-height: 1.5; }
#empty {
  position: absolute; inset: 0; display: flex; align-items: center; justify-content: center;
  text-align: center; color: #6a7785; font-size: 14px; padding: 24px;
}

/* Catalog filters above the list. One wrapping row of controls, sized to the
   table it filters; every control narrows the SQL query behind GET
   /api/replays rather than hiding rows client-side, so the count reported at
   the end is the true number of matching replays, not what fits on a page. */
#filters {
  max-width: 1650px; margin: 0 auto 10px; padding: 8px 12px;
  border: 1px solid #2a323c; border-radius: 6px; background: #141a20;
  display: flex; flex-wrap: wrap; align-items: center; gap: 8px 14px;
  font-size: 12px; color: #8a98a6;
}
#filters label { display: flex; align-items: center; gap: 5px; }
#filters input, #filters select {
  background: #0f141a; color: #c7d3de; border: 1px solid #2f3947;
  border-radius: 4px; padding: 3px 5px; font-size: 12px; font-family: inherit;
}
#filters input:focus, #filters select:focus { outline: none; border-color: #4a7fa8; }
#filters input[type=search] { width: 130px; }
#filters #f_map { max-width: 200px; }
#filters button {
  background: #1b232c; color: #9fb0bf; border: 1px solid #2f3947;
  border-radius: 4px; padding: 3px 9px; font-size: 12px; cursor: pointer;
}
#filters button:hover:enabled { color: #cfe3f5; border-color: #4a5a6c; }
#filters button:disabled { opacity: .45; cursor: default; }
#filters button.on { background: #24425c; border-color: #4a7fa8; color: #d6ecff; }
/* Admin-only controls in the bar (?admin=true), same gate as the list's
   per-row refresh button. */
#filters .admin { display: none; }
body.admin-mode #filters .admin { display: inline-block; }
#filters #f_count { margin-left: auto; }
/* Settings filters reuse the row badges' look, but as toggles: dim until
   picked, so the bar reads as "nothing selected" at a glance. */
#filters .chips { display: flex; flex-wrap: wrap; gap: 5px; }
#filters .chip {
  padding: 2px 7px; border-radius: 9px; font-size: 11px; cursor: pointer;
  border: 1px solid #2f3947; background: #0f141a; color: #7d8b99; user-select: none;
}
#filters .chip:hover { color: #c7d3de; border-color: #4a5a6c; }
#filters .chip.on { background: #24425c; border-color: #4a7fa8; color: #d6ecff; }

/* Drag&drop publishing (the landing view's dropzone). */
#dropzone {
  max-width: 1650px; margin: 0 auto 14px; padding: 14px 18px;
  border: 1px dashed #3a4856; border-radius: 6px;
  font-size: 12px; color: #8a98a6; text-align: center;
}
#dropzone code { color: #a9bac9; }
#dropzone.drag { border-color: #8fd3ff; background: #16202a; color: #c7d3de; }
#dropzone .browse { color: #8fd3ff; cursor: pointer; text-decoration: underline; }
#dropstatus { margin-top: 8px; font-size: 12px; color: #c7d3de; }
#dropstatus.error { color: #e2785b; }
#dropstatus.ok { color: #6fd07f; }

/* Point-of-view marking in the header. Pushed to the right so it never
   crowds the replay title, which is the thing being read. */
#viewmark {
  margin-left: auto;
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 12px;
  color: #9fb0bf;
  white-space: nowrap;
}
#viewmark select { font-size: 12px; }
/* Reserves its own width so the header does not reflow as the text changes
   between "saving…" and a result. */
#viewstatus { min-width: 8ch; font-size: 11px; color: #6f87a0; }
#viewstatus.ok { color: #7fd18b; }
#viewstatus.err { color: #ff9c8f; }
