/* ── Reset ───────────────────────────────────────────────────────────── */
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }

/* ── Root layout ─────────────────────────────────────────────────────── */
body {
  height: 100vh;
  overflow: hidden;
  font-family: 'Courier New', monospace;
  background: #0d1117;
  color: #c9d1d9;
}

#app {
  display: grid;
  grid-template-columns: 260px minmax(0, 1fr);
  height: 100vh;
  overflow: hidden;
}

/* ── AAR companion mode ──────────────────────────────────────────────────
   The report sits beside the replay rather than on top of it, so a finding
   and the thing it describes are on screen together. Three columns: the
   mission list collapses to a spine you can click to get back, the map keeps
   the middle (with its stepper and histogram still live -- that is the whole
   point), and the report takes a draggable right column. */
#app.aar-side {
  grid-template-columns: 0 minmax(0, 1fr) 5px var(--aar-w, 520px);
}
#app.aar-side #mission-panel {
  width: 0; min-width: 0; border-right: 0; overflow: hidden;
}
#app.aar-side #aar-split,
#app.aar-side #aar-pane { display: flex; }

/* Restore strip: the collapsed mission list is still reachable. */
#aar-restore {
  position: fixed; left: 0; top: 0; bottom: 0; width: 14px; z-index: 1200;
  background: #161b22; border-right: 1px solid #30363d; cursor: pointer;
  display: none; align-items: center; justify-content: center;
}
#app.aar-side #aar-restore { display: flex; }
#aar-restore:hover { background: #1f2630; }
#aar-restore span {
  writing-mode: vertical-rl; font-size: 9px; letter-spacing: 2px;
  text-transform: uppercase; color: #8b949e; user-select: none;
}
#app.aar-side.missions-open { grid-template-columns: 260px minmax(0,1fr) 5px var(--aar-w, 520px); }
#app.aar-side.missions-open #mission-panel { width: auto; min-width: 0; border-right: 1px solid #30363d; }
#app.aar-side.missions-open #aar-restore { display: none; }

/* ── Full-panel AAR ──────────────────────────────────────────────────────
   The report is meant to be the whole window here, so the mission list
   collapses to the same clickable spine the companion mode uses rather than
   keeping 260px of thumbnails beside a document that is not about them. */
#app.panel-full { grid-template-columns: 0 minmax(0, 1fr); }
#app.panel-full #mission-panel {
  width: 0; min-width: 0; border-right: 0; overflow: hidden;
}
#app.panel-full #aar-restore { display: flex; }
/* ...and the list is still one click away, same as in companion mode. */
#app.panel-full.missions-open { grid-template-columns: 260px minmax(0, 1fr); }
#app.panel-full.missions-open #mission-panel {
  width: auto; min-width: 0; border-right: 1px solid #30363d;
}
#app.panel-full.missions-open #aar-restore { display: none; }

/* Drag handle between map and report. */
#aar-split {
  display: none; background: #21262d; cursor: col-resize;
  align-items: center; justify-content: center;
}
#aar-split:hover, #aar-split.dragging { background: #58a6ff; }
body.aar-dragging { cursor: col-resize; user-select: none; }
body.aar-dragging iframe { pointer-events: none; }   /* keep the drag alive over the frame */

#aar-pane {
  display: none; flex-direction: column; min-width: 0;
  background: #0d1117; border-left: 1px solid #30363d; overflow: hidden;
}
#aar-pane-head {
  flex-shrink: 0; display: flex; align-items: baseline; gap: 10px;
  padding: 8px 10px; background: #161b22; border-bottom: 1px solid #30363d;
}
#aar-pane-title {
  font-size: 11px; letter-spacing: 2px; text-transform: uppercase; color: #58a6ff;
}
#aar-pane-hint {
  flex: 1; font-size: 10px; color: #8b949e; overflow: hidden;
  text-overflow: ellipsis; white-space: nowrap;
}
#aar-pane-count {
  font-size: 10px; color: #d29922; white-space: nowrap; flex-shrink: 0;
}
#aar-pane-hint { flex: 1; }
#aar-pane-flash {
  background: none; border: 1px solid #30363d; color: #8b949e; cursor: pointer;
  font-family: inherit; font-size: 10px; padding: 2px 7px; border-radius: 3px;
  white-space: nowrap; flex-shrink: 0;
}
#aar-pane-flash:hover { color: #d29922; border-color: #d29922; }
/* AAR findings marked on the histogram's time axis. */
#histogram .aar-mark { cursor: pointer; }
#histogram .aar-mark:hover text { text-decoration: underline; }
/* Companion mode: the histogram is not a time control, so it should not offer
   itself as one. The markers stay clickable -- they navigate the report. */
#histogram.no-seek .seekable,
#histogram.no-seek .seekable-label,
#histogram.no-seek .axis-hit { cursor: default; }
#aar-pane-close {
  background: none; border: 1px solid #30363d; color: #8b949e; cursor: pointer;
  font-size: 14px; line-height: 1; padding: 2px 7px; border-radius: 3px;
}
#aar-pane-close:hover { color: #c9d1d9; border-color: #58a6ff; }
#aar-pane-frame { flex: 1; width: 100%; border: 0; background: #e7e6df; }

/* ── Shot-list pane: shares the AAR pane's right column ──────────────────── */
#app.shots-side { grid-template-columns: 0 minmax(0, 1fr) 5px var(--aar-w, 520px); }
#app.shots-side #mission-panel { width: 0; min-width: 0; border-right: 0; overflow: hidden; }
#app.shots-side #shots-split,
#app.shots-side #shots-pane { display: flex; }
#app.shots-side #aar-restore { display: flex; }
#app.shots-side.missions-open { grid-template-columns: 260px minmax(0,1fr) 5px var(--aar-w, 520px); }
#app.shots-side.missions-open #mission-panel { width: auto; min-width: 0; border-right: 1px solid #30363d; }
#app.shots-side.missions-open #aar-restore { display: none; }
#shots-split { display: none; background: #21262d; cursor: col-resize; align-items: center; justify-content: center; }
#shots-split:hover, #shots-split.dragging { background: #58a6ff; }
#shots-pane { display: none; flex-direction: column; min-width: 0; background: #0d1117; border-left: 1px solid #30363d; overflow: hidden; }
#shots-pane-head { flex-shrink: 0; display: flex; align-items: center; gap: 8px; padding: 8px 10px; background: #161b22; border-bottom: 1px solid #30363d; }
#shots-pane-title { font-size: 11px; letter-spacing: 2px; text-transform: uppercase; color: #58a6ff; white-space: nowrap; }
#shots-pane-count { font-size: 10px; color: #d29922; white-space: nowrap; }
#shots-filter { flex: 1; min-width: 60px; background: #0d1117; border: 1px solid #30363d; color: #c9d1d9; border-radius: 4px; padding: 3px 8px; font: inherit; font-size: 12px; }
#shots-pane-close { background: none; border: 1px solid #30363d; color: #8b949e; cursor: pointer; font-size: 14px; line-height: 1; padding: 2px 7px; border-radius: 3px; flex-shrink: 0; }
#shots-pane-close:hover { color: #c9d1d9; border-color: #58a6ff; }
#shots-pane-body { flex: 1; overflow: auto; }
table.sp-tbl { width: 100%; border-collapse: collapse; font-size: 12px; }
table.sp-tbl thead th { position: sticky; top: 0; z-index: 1; background: #1c2129; color: #8b949e; text-align: left; padding: 6px 8px; border-bottom: 1px solid #30363d; font-weight: 600; }
table.sp-tbl tbody td { padding: 4px 8px; border-bottom: 1px solid #21262d; }
table.sp-tbl tr.seek { cursor: pointer; }
table.sp-tbl tbody tr:hover { background: rgba(88,166,255,.08); }
table.sp-tbl td.t { font-family: ui-monospace, SFMono-Regular, Consolas, monospace; white-space: nowrap; color: #c9d1d9; }
table.sp-tbl td.tm { font-size: 10px; font-weight: 600; }
table.sp-tbl tr.bl td.tm { color: #79c0ff; }
table.sp-tbl tr.op td.tm { color: #ff7b72; }
table.sp-tbl td.d, table.sp-tbl td.p { font-family: ui-monospace, monospace; color: #8b949e; white-space: nowrap; }
table.sp-tbl td.frat { color: #f0883e; font-weight: 700; }
table.sp-tbl td.same { color: #6e7681; }

/* The report never covers the map -- it takes width from it. On a narrow
   screen both get smaller rather than one winning: the pane width is clamped in
   aar-link.js so the map keeps a usable minimum, and the map column is
   minmax(0,1fr) so it can actually give ground instead of forcing an overflow. */

/* ── Mission panel (left column) ─────────────────────────────────────── */
#mission-panel {
  display: flex;
  flex-direction: column;
  background: #161b22;
  border-right: 1px solid #30363d;
  overflow: hidden;
}

#mission-panel-header {
  flex-shrink: 0;
  padding: 14px 16px;
  background: #0d1117;
  border-bottom: 1px solid #30363d;
}

#source-toggle {
  display: flex;
  align-items: center;
  gap: 4px;
  margin-top: 8px;
  font-size: 10px;
  letter-spacing: 1px;
  text-transform: uppercase;
  color: #8b949e;
}
#source-toggle span { margin-right: 4px; }
#source-toggle button {
  font: inherit;
  padding: 2px 8px;
  border: 1px solid #30363d;
  border-radius: 3px;
  background: #161b22;
  color: #8b949e;
  cursor: pointer;
}
#source-toggle button:hover { border-color: #58a6ff; color: #c9d1d9; }
#source-toggle button.active { background: #1f3a5f; border-color: #58a6ff; color: #58a6ff; }
#source-toggle button:focus-visible { outline: 2px solid #58a6ff; outline-offset: 1px; }

#mission-panel-header h1 {
  font-size: 13px;
  color: #58a6ff;
  letter-spacing: 2px;
  text-transform: uppercase;
}

/* Which unit's data this viewer is showing. Written in by arcsite/build.py
   from units/<unit>/unit.json -- the frontend itself stays unit-agnostic.
   Deliberately quieter than the ARC REPLAY wordmark beside it: it is an
   identifier you check, not a title you read every time. */
#mission-panel-header h1 .unit-tag {
  display: inline-block;
  margin-left: 8px;
  padding: 1px 7px;
  border: 1px solid #30363d;
  border-radius: 10px;
  background: #21262d;
  color: #c9d1d9;
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.5px;
  text-transform: none;
  vertical-align: 1px;
}

/* Environment badge (e.g. PP for preprod), added at runtime from env.json.
   Amber so it reads as a warning-ish "not production" marker distinct from the
   neutral unit tag beside it. */
#mission-panel-header h1 .env-badge {
  display: inline-block;
  margin-left: 6px;
  padding: 1px 7px;
  border-radius: 10px;
  background: #9e6a03;
  color: #fff8e6;
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.5px;
  text-transform: none;
  vertical-align: 1px;
}

#mission-panel-sub {
  font-size: 10px;
  color: #8b949e;
  margin-top: 3px;
}

#mission-list {
  flex: 1;
  overflow-y: auto;
  padding: 10px 8px;
}

/* Mission bins ("June", "Sept"). The label and the order both come from
   missionGroups in units/<unit>/unit.json -- a unit that defines none renders
   a flat list and never shows one of these. Sticky so the bin you are
   scrolled into stays named. */
.mission-group {
  position: sticky;
  top: -10px;                 /* cancels #mission-list's padding */
  z-index: 2;
  display: flex;
  align-items: center;
  gap: 6px;
  width: 100%;
  margin: 10px 0 6px;
  padding: 4px 4px 4px 2px;
  background: #161b22;
  color: #8b949e;
  font-size: 10px;
  font-weight: 600;
  font-family: inherit;
  letter-spacing: 1.2px;
  text-transform: uppercase;
  text-align: left;
  border: 0;
  border-bottom: 1px solid #30363d;
  cursor: pointer;
}

.mission-group:hover { color: #e6edf3; }
.mission-group:first-child { margin-top: 0; }

.mission-group .mg-caret {
  display: inline-block;
  font-size: 11px;
  line-height: 1;
  transform: rotate(90deg);           /* expanded: caret points down */
  transition: transform .12s ease;
}

.mission-group[aria-expanded="false"] .mg-caret {
  transform: rotate(0deg);            /* collapsed: points at the label */
}

.mission-group .mg-label { flex: 1; }

/* Collapsing a bin hides its cards. Needed because .mission-card sets
   `display: flex`, and a class rule beats the [hidden] attribute's UA
   `display: none` -- so without this the cards stayed on screen and only the
   heading appeared to do anything. */
.mission-card[hidden] { display: none; }

/* How many missions are folded away in there, so a collapsed bin still says
   how much it is hiding. */
.mission-group .mg-count {
  color: #6e7681;
  font-weight: 600;
  letter-spacing: 0;
}

/* ── Mission cards ───────────────────────────────────────────────────── */
.mission-card {
  display: flex;
  gap: 10px;
  padding: 8px;
  margin-bottom: 8px;
  background: #0d1117;
  border: 1px solid #21262d;
  border-radius: 6px;
  cursor: pointer;
  transition: border-color .15s, background .15s;
}

.mission-card:hover { border-color: #58a6ff; background: #0e1521; }

.mission-card.active {
  border-color: #58a6ff;
  background: #0e1521;
}

.mission-card.loading { opacity: .75; cursor: wait; }

/* thumbnail wrapper */
.mission-thumb {
  position: relative;
  width: 80px;
  height: 54px;
  flex-shrink: 0;
  border-radius: 3px;
  overflow: hidden;
  background: #21262d;
}

.mission-thumb img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

.mission-thumb-overlay {
  position: absolute;
  inset: 0;
  background: rgba(13, 17, 23, .8);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 4px;
  display: none;
}

.mission-card.loading .mission-thumb-overlay { display: flex; }

/* spinner */
.spinner {
  width: 18px;
  height: 18px;
  border: 2px solid #30363d;
  border-top-color: #58a6ff;
  border-radius: 50%;
  animation: spin .7s linear infinite;
}

@keyframes spin { to { transform: rotate(360deg); } }

.loading-text {
  font-size: 9px;
  color: #8b949e;
  text-align: center;
  line-height: 1.3;
  max-width: 74px;
}

/* card info */
.mission-info { flex: 1; min-width: 0; display: flex; flex-direction: column; justify-content: center; gap: 3px; }

.mission-name  { font-size: 11px; color: #f0f6fc; line-height: 1.3; }
.mission-date  { font-size: 10px; color: #8b949e; }
.mission-count { font-size: 10px; color: #3fb950; }
.mission-error { font-size: 10px; color: #f85149; }

/* ── Add-mission footer ──────────────────────────────────────────────── */
#add-mission-footer {
  flex-shrink: 0;
  padding: 8px;
  border-top: 1px solid #30363d;
}

#add-mission-footer button {
  width: 100%;
  background: #21262d;
  border-color: #444c56;
  color: #8b949e;
  font-size: 11px;
}

#add-mission-footer button:hover { color: #58a6ff; border-color: #58a6ff; }

/* ── Delete button on card ───────────────────────────────────────────── */
.mission-card { position: relative; }

.btn-delete-mission {
  position: absolute;
  top: 4px; right: 4px;
  width: 18px; height: 18px;
  padding: 0;
  font-size: 11px;
  line-height: 1;
  border-radius: 3px;
  background: transparent;
  border-color: transparent;
  color: #484f58;
  opacity: 0;
  transition: opacity .15s, color .15s;
}

.mission-card:hover .btn-delete-mission { opacity: 1; }
.btn-delete-mission:hover:not(:disabled) { color: #f85149 !important; background: transparent; border-color: transparent; }

/* ── Add-mission modal ───────────────────────────────────────────────── */
#add-modal-backdrop {
  position: fixed;
  inset: 0;
  background: rgba(0,0,0,.6);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 1000;
}

#add-modal-backdrop.hidden { display: none; }

#add-modal {
  background: #161b22;
  border: 1px solid #30363d;
  border-radius: 8px;
  padding: 20px 24px;
  width: 340px;
  display: flex;
  flex-direction: column;
  gap: 12px;
}

#add-modal h2 { font-size: 13px; color: #f0f6fc; }

#add-modal label {
  display: flex;
  flex-direction: column;
  gap: 4px;
  font-size: 10px;
  color: #8b949e;
  text-transform: uppercase;
  letter-spacing: .8px;
}

#add-modal input {
  background: #0d1117;
  border: 1px solid #30363d;
  color: #c9d1d9;
  padding: 6px 8px;
  font-size: 12px;
  font-family: inherit;
  border-radius: 4px;
  color-scheme: dark;
}

#add-modal input:focus { outline: none; border-color: #58a6ff; }

#add-modal-error { font-size: 11px; color: #f85149; min-height: 14px; }

.modal-buttons { display: flex; gap: 8px; justify-content: flex-end; }

/* ── Right panel ─────────────────────────────────────────────────────── */
#right-panel {
  display: grid;
  /* Name the column. Left implicit it sizes to max-content, which meant the map
     kept the width it was first laid out at: shrink the panel (opening the AAR
     beside it) and the map stayed wide and got clipped, so the report looked
     like it was covering the map rather than taking space from it. */
  grid-template-columns: minmax(0, 1fr);
  grid-template-rows: 1fr 48px 120px;
  overflow: hidden;
}

/* ── Leaflet map ─────────────────────────────────────────────────────── */
#map-wrap { position: relative; overflow: hidden; min-width: 0; }
#map { width: 100%; height: 100%; }

/* ── Stepper bar ─────────────────────────────────────────────────────── */
#stepper-bar {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 0 12px;
  background: #161b22;
  border-top: 1px solid #30363d;
  border-bottom: 1px solid #30363d;
  height: 48px;
}

.stepper-side { display: flex; gap: 6px; flex-shrink: 0; }

/* Two navigation groups that do different things: shot-to-shot vs clock. Each
   gets a tinted well, its own accent and a caption, because when they sat side
   by side as bare buttons there was nothing to say which was which. */
.nav-group {
  display: flex;
  align-items: center;
  gap: 5px;
  flex-shrink: 0;
  padding: 4px 8px 4px 6px;
  border-radius: 6px;
  border: 1px solid transparent;
}
.nav-group .nav-label {
  font-size: 9px;
  text-transform: uppercase;
  letter-spacing: .09em;
  font-weight: 700;
  padding-right: 3px;
  opacity: .85;
}
.nav-shots {
  background: rgba(56, 139, 253, .09);
  border-color: rgba(56, 139, 253, .28);
}
.nav-shots .nav-label { color: #6cb0ff; }
.nav-shots button { border-color: #2d4a72; }
.nav-time {
  background: rgba(210, 153, 34, .09);
  border-color: rgba(210, 153, 34, .30);
}
.nav-time .nav-label { color: #e3b341; }
.nav-time button { border-color: #6b551d; }
/* The jump buttons carry their own size ("10s"), so tabular figures keep the
   bar from twitching when the speed changes. */
#btn-back, #btn-fwd { font-variant-numeric: tabular-nums; min-width: 54px; }

#stepper-info {
  flex: 1;
  font-size: 11px;
  color: #8b949e;
  text-align: center;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* The whole time axis is a seek target now: the strip takes the click, the
   labels just advertise that it is clickable. */
#histogram .axis-hit { cursor: pointer; }
#histogram .hover-guide { pointer-events: none; }
#histogram .seekable-label { cursor: pointer; }
/* The strip sits above the labels, so a :hover sibling rule on them could
   never fire -- the affordance is the cursor plus this permanent underline. */
#histogram .seekable-label { text-decoration: underline dotted #484f58; }

#stepper-info b  { color: #f0f6fc; }
#stepper-info em { color: #f85149; font-style: normal; }

/* Initial call to action. This is the only thing on screen the user has to act
   on before anything else works, so it is deliberately loud -- and it is
   dropped for good the moment a mission is chosen.

   It sits centred on the empty map, not in the stepper bar: that bar is a flex
   row of navigation groups, and at narrow widths the prompt was pushed off the
   right-hand edge where nobody could see the one instruction on the page. */
#map-prompt {
  position: absolute;
  inset: 0;
  z-index: 400;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 16px;
  text-align: center;
  font-size: clamp(15px, 2.4vw, 22px);
  font-weight: 600;
  color: #f0f6fc;
  letter-spacing: 0.3px;
  pointer-events: none;                 /* never blocks the map underneath */
  animation: prompt-pulse 2.4s ease-in-out infinite;
}
#map-prompt[hidden] { display: none; }

@keyframes prompt-pulse {
  0%, 100% { opacity: 1;    text-shadow: 0 0 10px rgba(88, 166, 255, 0.45); }
  50%      { opacity: 0.72; text-shadow: 0 0 4px  rgba(88, 166, 255, 0.15); }
}

@media (prefers-reduced-motion: reduce) {
  #map-prompt { animation: none; }
}

/* ── Buttons ─────────────────────────────────────────────────────────── */
button {
  background: #21262d;
  border: 1px solid #30363d;
  color: #c9d1d9;
  padding: 5px 12px;
  cursor: pointer;
  font-size: 12px;
  font-family: inherit;
  border-radius: 4px;
  transition: background .12s, border-color .12s;
  white-space: nowrap;
}

button:hover:not(:disabled) { background: #30363d; border-color: #58a6ff; }
button:disabled              { opacity: .3; cursor: default; }

/* ── Play button ─────────────────────────────────────────────────────── */
/* Playback is the whole point of the viewer, so this is the one control in the
   bar styled as a primary action rather than another grey chip. */
#btn-play {
  background: #238636;
  border-color: #2ea043;
  color: #ffffff;
  font-size: 14px;
  font-weight: 700;
  padding: 7px 20px;
  letter-spacing: 0.3px;
  min-width: 104px;
  text-align: center;
  transition: background .12s, border-color .12s, box-shadow .12s, transform .12s;
}

#btn-play:hover:not(:disabled) {
  background: #2ea043;
  border-color: #3fb950;
  box-shadow: 0 0 0 3px rgba(46, 160, 67, 0.28);
}

#btn-play:active:not(:disabled) { transform: translateY(1px); }

/* Pause reads as "stop what is happening", so it drops the go-green. */
#btn-play.playing {
  background: #9e6a03;
  border-color: #bb8009;
}

#btn-play.playing:hover:not(:disabled) {
  background: #bb8009;
  border-color: #d29922;
  box-shadow: 0 0 0 3px rgba(210, 153, 34, 0.28);
}

/* Set from the moment a mission's data lands until the user actually presses
   play, which is exactly the window where they have a loaded map on screen and
   no indication that anything is meant to be pressed next. Dropped for good
   after the first press -- a control that keeps pulsing after you have used it
   is just noise. */
#btn-play.ready {
  animation: play-ready 1.6s ease-in-out infinite;
}

@keyframes play-ready {
  0%, 100% { box-shadow: 0 0 0 0   rgba(63, 185, 80, 0.55); }
  50%      { box-shadow: 0 0 0 7px rgba(63, 185, 80, 0.00); }
}

@media (prefers-reduced-motion: reduce) {
  #btn-play.ready {
    animation: none;
    box-shadow: 0 0 0 3px rgba(63, 185, 80, 0.5);
  }
}

/* ── Histogram ───────────────────────────────────────────────────────── */
#histogram-container {
  background: #0d1117;
  border-top: 1px solid #21262d;
  overflow: hidden;
}

#histogram {
  display: block;
  width: 100%;
  height: 120px;
}
#orbat-timeline { display: block; width: 100%; height: 120px; }
#orbat-timeline[hidden] { display: none; }

/* Playhead wrapper — CSS-transitioned via transform */
.playhead-group {
  transition: transform .1s linear;
}

/* Bin rects — hover cursor set per-rect in JS */
.bin-hit { cursor: default; }
.bin-hit.seekable { cursor: pointer; }

/* ── Map overlay controls (heat-map toggle) ───────────────────────────── */
/* Top-left, offset right of Leaflet's zoom control so they don't collide. */
#map-controls {
  position: absolute;
  top: 10px;
  left: 52px;
  right: 10px;
  z-index: 900;
  pointer-events: none;   /* let map interactions through the gaps */
  display: flex;
  flex-flow: row wrap;
  align-items: flex-start;
  gap: 6px;
}
#map-controls > * { pointer-events: auto; }
#map-controls #heat-note { flex-basis: 100%; }
#map-controls .map-toggle {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  background: rgba(13, 17, 23, 0.92);
  border: 1px solid #30363d;
  border-radius: 5px;
  padding: 6px 10px;
  font-size: 12px;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  color: #e6edf3;
  cursor: pointer;
  user-select: none;
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.45);
}
#map-controls .map-toggle:hover { border-color: #58a6ff; }

/* A control and its sub-options. The children are indented and tied to the
   parent by a left rule, so it reads as "these belong to that" rather than as
   three unrelated switches. */
/* The bins sit in a row across the top of the map, so each is a narrow column
   the eye can scan rather than one tall stack down the left edge. */
#map-controls { display: flex; flex-direction: row; align-items: flex-start; gap: 8px; }

/* A bin: a named set of controls that behave alike. The title is a button --
   clicking it folds the bin away, and the count keeps its state visible while
   folded so collapsing never hides what is switched on. */
#map-controls .ctl-bin {
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding: 5px;
  border-radius: 6px;
  background: rgba(13, 17, 23, 0.78);
  border: 1px solid #30363d;
}
#map-controls .ctl-bin-title {
  display: flex;
  align-items: center;
  gap: 4px;
  background: none;
  border: 0;
  padding: 1px 2px 2px;
  font: 700 9px/1.2 ui-monospace, SFMono-Regular, Menlo, monospace;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: #8b949e;
  cursor: pointer;
  white-space: nowrap;
}
#map-controls .ctl-bin-title:hover { color: #e6edf3; }
#map-controls .ctl-caret { font-size: 8px; transition: transform .12s; }
#map-controls .ctl-count { color: #58a6ff; letter-spacing: 0; }
#map-controls .ctl-bin-body { display: flex; flex-direction: column; gap: 4px; }

#map-controls .ctl-bin.collapsed .ctl-bin-body { display: none; }
#map-controls .ctl-bin.collapsed .ctl-caret { transform: rotate(-90deg); }

/* Why a bin is unavailable, said once for the group rather than left to be
   inferred from three greyed rows. */
#map-controls .ctl-bin-note {
  font-size: 9.5px;
  line-height: 1.3;
  color: #6e7681;
  padding: 1px 2px 0;
  max-width: 148px;
}
#map-controls .ctl-bin-note:empty { display: none; }

/* Unit filter. The list scrolls because a mission can carry 98 devices, and a
   control panel that grows to the height of the roster is not a control panel. */
#map-controls .ctl-filter-actions { display: flex; gap: 4px; }
#map-controls .ctl-filter-actions button {
  flex: 1;
  font: 10px/1 ui-monospace, SFMono-Regular, Menlo, monospace;
  padding: 4px 0;
  background: #21262d;
  border: 1px solid #30363d;
  border-radius: 4px;
  color: #c9d1d9;
  cursor: pointer;
}
#map-controls .ctl-filter-actions button:hover { border-color: #58a6ff; }
#map-controls .ctl-filter-list {
  display: flex;
  flex-direction: column;
  gap: 2px;
  max-height: 46vh;
  overflow-y: auto;
  padding-right: 2px;
}
#map-controls .ctl-filter-list label {
  display: flex;
  align-items: center;
  gap: 5px;
  font: 11px/1.3 ui-monospace, SFMono-Regular, Menlo, monospace;
  color: #e6edf3;
  cursor: pointer;
  padding: 1px 2px;
  border-radius: 3px;
  white-space: nowrap;
}
#map-controls .ctl-filter-list label:hover { background: rgba(88, 166, 255, .12); }
#map-controls .ctl-filter-list input { margin: 0; cursor: pointer; }
/* The swatch is the whole point of "color with role": it shows which color a
   unit will be before you go looking for it on the map. */
#map-controls .ctl-swatch {
  width: 8px; height: 8px; border-radius: 2px; flex-shrink: 0;
}
#map-controls .ctl-filter-list .ctl-shots {
  margin-left: auto; color: #8b949e; font-variant-numeric: tabular-nums;
}
/* A team heading inside the list, when the mission distinguishes teams. */
#map-controls .ctl-filter-list .ctl-team {
  font: 700 9px/1.2 ui-monospace, SFMono-Regular, Menlo, monospace;
  letter-spacing: .1em; text-transform: uppercase; color: #8b949e;
  padding: 4px 2px 1px;
}

/* ── Floating Order-of-Battle panel (division missions) ──────────────── */
#orbat-panel {
  position: fixed; top: 76px; right: 18px; z-index: 900;
  width: 360px; max-height: 74vh; display: flex; flex-direction: column;
  background: rgba(13, 17, 23, .95);
  border: 1px solid #30363d; border-radius: 9px;
  box-shadow: 0 10px 34px rgba(0, 0, 0, .55);
  color: #e6edf3; font: 13px/1.55 ui-monospace, SFMono-Regular, Menlo, monospace;
}
#orbat-panel[hidden] { display: none !important; }
#orbat-head {
  display: flex; align-items: center; gap: 8px; padding: 9px 11px;
  border-bottom: 1px solid #30363d; cursor: move; user-select: none;
  background: rgba(255, 255, 255, .03); border-radius: 9px 9px 0 0;
}
#orbat-title { font-weight: 700; letter-spacing: .05em; text-transform: uppercase; font-size: 12.5px; }
#orbat-legend { margin-left: auto; display: flex; gap: 3px; align-items: center; }
#orbat-legend .ammo-key { width: 9px; height: 9px; border-radius: 2px; display: inline-block; }
.ammo-key.ok { background: #3fb950; } .ammo-key.warn { background: #d29922; }
.ammo-key.low { background: #f85149; } .ammo-key.out { background: #8b2b2b; }
#orbat-dock, #orbat-collapse { background: none; border: none; color: #8b949e; font-size: 15px; cursor: pointer; padding: 0 2px; line-height: 1; }
#orbat-dock:hover, #orbat-collapse:hover { color: #e6edf3; }
.orb-toolbar { display: flex; gap: 4px; margin-bottom: 4px; }
.orb-toolbar .orb-popout { flex: 1; margin: 0; }
#orbat-body { overflow: auto; padding: 6px 6px 9px; scrollbar-width: thin; }

#orbat-body .orb-row {
  display: flex; align-items: center; gap: 7px; padding: 3px 8px 3px 4px;
  cursor: pointer; border-left: 2px solid transparent; white-space: nowrap; user-select: none;
}
#orbat-body .orb-row:hover { background: rgba(88, 166, 255, .10); }
#orbat-body .orb-row.sel { background: rgba(240, 165, 0, .16); border-left-color: #f0a500; }
#orbat-body .orb-row.off .orb-nm, #orbat-body .orb-row.off .orb-rc { opacity: .4; }
#orbat-body .orb-tw { width: 18px; flex: 0 0 auto; text-align: center; color: #8b949e; font-size: 14px; line-height: 1; }
#orbat-body .orb-tw.leaf { visibility: hidden; }
#orbat-body .orb-tw:hover { color: #e6edf3; }
#orbat-body .orb-eye { width: 17px; flex: 0 0 auto; text-align: center; color: #6e7681; font-size: 15px; line-height: 1; }
#orbat-body .orb-eye:hover { color: #e6edf3; }
#orbat-body .orb-chip { width: 10px; height: 10px; flex: 0 0 auto; border-radius: 2px; box-shadow: inset 0 0 0 1px rgba(0, 0, 0, .35); }
#orbat-body .orb-ech { width: 22px; flex: 0 0 auto; color: #8b949e; font-size: 10px; letter-spacing: -.5px; }
#orbat-body .orb-nm { flex: 1 1 auto; overflow: hidden; text-overflow: ellipsis; }
#orbat-body .orb-nm.lead { font-weight: 700; }
#orbat-body .orb-pct { width: 32px; flex: 0 0 auto; text-align: right; color: #8b949e; font-variant-numeric: tabular-nums; font-size: 11px; }
#orbat-body .orb-row.off .orb-pct { opacity: .4; }
#orbat-body .orb-ammo { width: 34px; flex: 0 0 auto; height: 6px; border-radius: 3px; background: #21262d; overflow: hidden; }
#orbat-body .orb-ammo > i { display: block; height: 100%; border-radius: 3px; transition: width .2s linear; }
#orbat-body .orb-ammo.na { visibility: hidden; }
#orbat-body .orb-rc { width: 46px; flex: 0 0 auto; text-align: right; color: #8b949e; font-variant-numeric: tabular-nums; font-size: 12px; }
.ammo-ok > i, .a-ok { background: #3fb950; }
.ammo-warn > i, .a-warn { background: #d29922; }
.ammo-low > i, .a-low { background: #f85149; }
.ammo-out > i, .a-out { background: #8b2b2b; }
#orbat-body .orb-row.a-out-row { background: rgba(139, 43, 43, .18); }

/* Ammo bar + pop-out — generic so they work docked in the filter bin AND in
   the floating panel. */
.orb-pct { width: 30px; flex: 0 0 auto; text-align: right; color: #8b949e;
  font-variant-numeric: tabular-nums; font-size: 11px; }
.orb-row.off .orb-pct { opacity: .4; }
.orb-ammo { width: 30px; flex: 0 0 auto; height: 6px; border-radius: 3px;
  background: #21262d; overflow: hidden; }
.orb-ammo > i { display: block; height: 100%; border-radius: 3px; transition: width .2s linear; }
.orb-ammo.na { visibility: hidden; }
.a-ok > i { background: #3fb950; } .a-warn > i { background: #d29922; }
.a-low > i { background: #f85149; } .a-out > i { background: #8b2b2b; }
.orb-row.a-out-row { background: rgba(139, 43, 43, .18); }
.orb-popout {
  display: block; width: 100%; margin: 0 0 4px; padding: 3px 6px;
  font: 600 10px/1.2 ui-monospace, SFMono-Regular, Menlo, monospace;
  letter-spacing: .08em; text-transform: uppercase; text-align: left;
  background: #21262d; color: #adbac7; border: 1px solid #30363d; border-radius: 4px; cursor: pointer;
}
.orb-popout:hover { border-color: #f0a500; color: #e6edf3; }

/* ── ORBAT echelon tree (division missions) ──────────────────────────── */
#map-controls .ctl-filter-list.orbat-tree { gap: 0; max-height: 58vh; }
#map-controls .orbat-tree .orb-row {
  display: flex; align-items: center; gap: 5px;
  font: 11px/1.4 ui-monospace, SFMono-Regular, Menlo, monospace;
  color: #e6edf3; cursor: pointer; padding: 1px 4px 1px 0;
  border-left: 2px solid transparent; white-space: nowrap; user-select: none;
}
#map-controls .orbat-tree .orb-row:hover { background: rgba(88, 166, 255, .10); }
#map-controls .orbat-tree .orb-row.sel {
  background: rgba(240, 165, 0, .16); border-left-color: #f0a500;
}
#map-controls .orbat-tree .orb-row.off .orb-nm,
#map-controls .orbat-tree .orb-row.off .orb-pct,
#map-controls .orbat-tree .orb-row.off .orb-rc { opacity: .4; }
#map-controls .orbat-tree .orb-tw {
  width: 17px; flex-shrink: 0; text-align: center; color: #8b949e; font-size: 13px;
  line-height: 1;
}
#map-controls .orbat-tree .orb-tw.leaf { visibility: hidden; }
#map-controls .orbat-tree .orb-tw:hover { color: #e6edf3; }
#map-controls .orbat-tree .orb-eye {
  width: 15px; flex-shrink: 0; text-align: center; color: #6e7681; font-size: 13px;
  line-height: 1;
}
#map-controls .orbat-tree .orb-eye:hover { color: #e6edf3; }
/* Percentage remaining, immediately left of the bar. Fixed width and
   right-aligned for the same reason .orb-rc is: 9% / 47% / 100% must stack on
   their last digit rather than drift with digit count. */
#map-controls .orbat-tree .orb-pct {
  width: 30px; flex: 0 0 auto; text-align: right; color: #8b949e;
  font-variant-numeric: tabular-nums; font-size: 10px;
}
#map-controls .orbat-tree .orb-chip {
  width: 8px; height: 8px; border-radius: 2px; flex-shrink: 0;
  box-shadow: inset 0 0 0 1px rgba(0, 0, 0, .35);
}
#map-controls .orbat-tree .orb-ech {
  width: 20px; flex-shrink: 0; color: #8b949e; font-size: 9px; letter-spacing: -.5px;
}
#map-controls .orbat-tree .orb-nm {
  flex: 1 1 auto; overflow: hidden; text-overflow: ellipsis;
}
#map-controls .orbat-tree .orb-nm.lead { font-weight: 700; }
#map-controls .orbat-tree .orb-rc {
  /* Fixed width and right-aligned, so 60 / 480 / 1,940 stack on their last
     digit instead of drifting with digit count. The floating panel's copy of
     this rule (#orbat-body .orb-rc) always had both; the docked one -- which is
     what a real unit actually sees, since only the demonstrator pops out -- had
     neither, so the column only lined up in the view nobody was using.
     tabular-nums keeps the glyphs the same width; without the fixed box that
     alone is not enough, because the element still shrink-wraps its content. */
  width: 44px;
  flex: 0 0 auto;
  text-align: right;
  color: #8b949e;
  font-variant-numeric: tabular-nums;
}

#map-controls .map-group { display: flex; flex-direction: column; gap: 4px; }
#map-controls .map-sub {
  display: flex;
  flex-direction: column;
  gap: 4px;
  margin-left: 14px;
  padding-left: 8px;
  border-left: 1px solid #30363d;
}
#map-controls .map-sub[hidden] { display: none; }
#map-controls .map-sub .map-toggle {
  font-size: 11px;
  padding: 4px 8px;
  background: rgba(13, 17, 23, 0.85);
}
#map-controls .map-toggle input { cursor: pointer; margin: 0; }
#map-controls .map-toggle.disabled {
  opacity: 0.45;
  cursor: not-allowed;
}
#map-controls .map-toggle.disabled:hover { border-color: #30363d; }
#map-controls .map-toggle.disabled input { cursor: not-allowed; }

/* Map-only modes (heat map, direction rose, AI AAR): the stepper and histogram
   don't apply, so collapse the grid to a single row and let the map area fill
   the whole panel — hiding the children alone would leave their reserved
   48px/120px rows blank. */
#right-panel.map-only { grid-template-rows: 1fr; }
#right-panel.map-only #stepper-bar,
#right-panel.map-only #histogram-container { display: none; }

/* AI AAR: an isolated iframe covers the map with the report, below the overlay
   controls (z 900) so the checkbox stays clickable. */
#map-wrap .aar-frame {
  position: absolute; inset: 0; width: 100%; height: 100%;
  border: 0; z-index: 500; background: var(--bg);
}
/* The map is hidden behind the AAR, so its zoom/layers controls shouldn't
   float over the report (our own #map-controls stays visible above it). */
#right-panel.aar-active .leaflet-control-container { display: none; }
/* Same reason for the device shot-count roster: it is a map overlay (z 900,
   above the AAR frame), so a full-panel AAR or timeline would otherwise show
   it floating over the report. Hidden while one is up; its own toggle state is
   untouched, so it returns as it was when the panel closes. `replaced` (aar OR
   timeline) drives this class, so both full-panel views are covered. */
#right-panel.aar-active #device-roster { display: none; }

/* Engagement Timeline: full-panel replacement view over the map. */
#map-wrap .tl-view {
  position: absolute; inset: 0; z-index: 500;
  display: flex; flex-direction: column;
  padding-top: 46px;                 /* clear the control row above */
  background: #0d1117; color: #c9d1d9;
}
#map-wrap .tl-view .tl-head {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: 6px 16px;
  padding: 12px 16px; border-bottom: 1px solid #21262d; background: #161b22;
}
#map-wrap .tl-view .tl-head b {
  font-size: 14px; letter-spacing: .04em; text-transform: uppercase; color: #f0f6fc;
}
#map-wrap .tl-view .tl-head span {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 12px; color: #8b949e;
}
#map-wrap .tl-view .tl-scroll { flex: 1; overflow: auto; }
#map-wrap .tl-view .tl-canvas { display: block; }
#map-controls .map-note {
  margin-top: 5px;
  max-width: 200px;
  padding: 3px 8px;
  background: rgba(13, 17, 23, 0.85);
  border-radius: 4px;
  font-size: 11px;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  color: #d29922;
  line-height: 1.35;
}
#map-controls .map-note:empty { display: none; }

/* ── Map roster legend ────────────────────────────────────────────────── */
/* Lists every rostered device, including ones that never reported a position
   and so cannot appear on the map at all. */
/* Own DOM element rather than a Leaflet control: fewer moving parts, and it
   cannot be swallowed by control-corner layout rules. z-index clears Leaflet's
   own controls, which top out around 800. */
#device-roster {
  position: absolute;
  left: 10px;
  bottom: 10px;
  z-index: 900;
  pointer-events: auto;
  background: rgba(13, 17, 23, 0.92);
  border: 1px solid #30363d;
  border-radius: 5px;
  padding: 7px 9px;
  font-size: 12px;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  color: #e6edf3;
  line-height: 1.7;
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.45);
  min-width: 232px;
  /* A 98-device roster is taller than the viewport. It used to be hidden
     outright past 10 devices; now the overlay toggle can show it at any size,
     so it has to cap itself and scroll instead of burying the map. */
  max-height: 45%;
  overflow-y: auto;
}

#device-roster .rl-title {
  font-weight: 600;
  color: #f0f6fc;
  margin-bottom: 4px;
  font-size: 10px;
  letter-spacing: 0.4px;
  text-transform: uppercase;
}

#device-roster .rl-row {
  display: flex;
  align-items: center;
  gap: 6px;
  white-space: nowrap;
}

#device-roster .rl-dot {
  width: 9px;
  height: 9px;
  border-radius: 50%;
  flex: 0 0 auto;
}

#device-roster .rl-hollow {
  background: transparent;
  border: 1.5px dashed currentColor;
}

#device-roster .rl-name { flex: 1; }

/* Shot count. Tabular figures stop the column jittering as digits change
   during playback. */
#device-roster .rl-count {
  font-variant-numeric: tabular-nums;
  font-weight: 600;
  color: #f0f6fc;
  text-align: right;
  min-width: 46px;
}

/* Running tally reads slightly quieter than a settled total. */
#device-roster .rl-count.rl-partial { color: #d2a8ff; }

#device-roster .rl-title .rl-total {
  float: right;
  font-weight: 600;
  color: #adbac7;
  font-variant-numeric: tabular-nums;
  text-transform: none;
  letter-spacing: 0;
}

/* Dismiss the panel from the panel itself, rather than hunting for the
   overlay toggle. Floated before .rl-total so it lands in the corner. */
#device-roster .rl-close {
  float: right;
  margin: -2px -3px 0 8px;
  padding: 0 3px;
  border: 0;
  background: none;
  color: #8b949e;
  font-size: 14px;
  line-height: 1.1;
  cursor: pointer;
}

#device-roster .rl-close:hover {
  color: #f0f6fc;
}

#device-roster .rl-status {
  font-size: 11px;
  color: #adbac7;
}

/* Status is carried by color and the hollow/dashed swatch, not by fading the
   row. Dimming to 55% made these rows unreadable -- and they are the ones most
   worth reading, since they are the devices with no location at all. */
#device-roster .rl-none  .rl-status { color: #ff7b72; font-weight: 600; }
#device-roster .rl-stale .rl-status { color: #e3b341; font-weight: 600; }
#device-roster .rl-ok    .rl-status { color: #56d364; }


/* ── memory probe (Ctrl+Shift+M) ──────────────────────────────────────
   Diagnostic only: hidden until asked for, and it must never sit over the
   controls it is measuring, so it docks bottom-left above the histogram. */
#arc-debug-panel {
  position: fixed;
  left: 8px;
  bottom: 8px;
  z-index: 3000;
  width: 620px;
  max-width: calc(100vw - 16px);
  background: rgba(13, 17, 23, .95);
  border: 1px solid #30363d;
  border-radius: 6px;
  color: #c9d1d9;
  font: 11px/1.45 'Courier New', monospace;
  box-shadow: 0 8px 30px rgba(0, 0, 0, .5);
}
#arc-debug-panel[hidden] { display: none; }
#arc-debug-panel .dbg-bar {
  display: flex; align-items: center; gap: 8px;
  padding: 6px 8px; border-bottom: 1px solid #21262d; color: #8b949e;
}
#arc-debug-panel .dbg-bar span { flex: 1; }
#arc-debug-panel button {
  font: inherit; padding: 2px 8px; background: #21262d;
  border: 1px solid #30363d; color: #c9d1d9; border-radius: 4px; cursor: pointer;
}
#arc-debug-panel .dbg-body { padding: 8px; max-height: 46vh; overflow: auto; }
#arc-debug-panel .dbg-head { color: #8b949e; margin-bottom: 6px; }
#arc-debug-panel b { color: #f0f6fc; }
#arc-debug-panel table { border-collapse: collapse; width: 100%; }
#arc-debug-panel th {
  text-align: left; color: #6e7681; font-weight: 400; padding: 1px 6px 3px 0;
}
#arc-debug-panel td { padding: 1px 6px 1px 0; white-space: nowrap; }
#arc-debug-panel td.n { text-align: right; font-variant-numeric: tabular-nums; }

/* Boot strip for ?debug=1 -- deliberately loud and centred at the top, since
   its whole job is to be seen when something else is not. */
#arc-debug-boot {
  position: fixed;
  top: 8px;
  left: 50%;
  transform: translateX(-50%);
  z-index: 4000;
  padding: 6px 14px;
  border-radius: 5px;
  background: #1f3b57;
  color: #f0f6fc;
  font: 12px/1.4 'Courier New', monospace;
  box-shadow: 0 6px 20px rgba(0, 0, 0, .45);
}

/* Marker animations in CSS rather than SVG SMIL.
   SMIL timelines were created and destroyed with every icon rebuild during
   playback -- the least robust animation path in the browser, and the likeliest
   thing to take a renderer down. CSS keyframes are declared once here and cost
   nothing per marker. fill-box keeps the scale centred on the circle. */
@keyframes arc-pulse-kf {
  0%   { transform: scale(1);   opacity: .9; }
  100% { transform: scale(2.3); opacity: 0; }
}
.arc-pulse {
  transform-box: fill-box;
  transform-origin: center;
  animation: arc-pulse-kf 1.1s linear infinite;
}
@keyframes arc-fire-kf {
  0%, 100% { fill-opacity: .95; }
  50%      { fill-opacity: .35; }
}
.arc-fire { animation: arc-fire-kf .9s ease-in-out infinite; }

@media (prefers-reduced-motion: reduce) {
  .arc-pulse, .arc-fire { animation: none; }
}

/* Shown only when map tiles cannot be fetched (a blocked CDN on a locked-down
   network). Sits low so it never covers the controls, and is advisory rather
   than alarming -- the replay itself is unaffected. */
#tile-note {
  position: absolute;
  left: 50%;
  bottom: 12px;
  transform: translateX(-50%);
  z-index: 800;
  max-width: min(560px, calc(100% - 32px));
  padding: 7px 14px;
  border-radius: 6px;
  background: rgba(56, 43, 12, .95);
  border: 1px solid #6b551d;
  color: #e3b341;
  font: 11px/1.45 ui-monospace, SFMono-Regular, Menlo, monospace;
  text-align: center;
  box-shadow: 0 6px 20px rgba(0, 0, 0, .45);
}
#tile-note[hidden] { display: none; }

/* ── Portal view ──────────────────────────────────────────────────────
   A second look for the replay map, matching the reference portal: the unit
   is a plain light-blue disc with its name in a black box beside it, and its
   aim is a labelled ray rather than a spear on the marker. No shot count in
   the node -- the point of this mode is the pointing picture, and a number in
   the disc is what made ours read heavy at low zoom.

   Rendered as HTML divIcons rather than SVG so the black label boxes size
   themselves to the text, which an SVG <rect> cannot do without measuring. */
.portal-node { position: relative; width: 0; height: 0; }
.portal-dot {
  position: absolute;
  /* 14 px disc inside a 2 px black ring; left/top keep the visual centre on the
     marker's anchor once the border is counted. */
  left: -9px;
  top: -9px;
  width: 14px;
  height: 14px;
  border-radius: 50%;
  background: #9ddefc;
  border: 2px solid #000;
}
.portal-name,
.portal-cone-label {
  position: absolute;
  background: #000;
  color: #fff;
  font: 700 12px/1.3 system-ui, -apple-system, "Segoe UI", sans-serif;
  padding: 1px 5px;
  white-space: nowrap;
  letter-spacing: .01em;
}
/* Name sits just under the disc, centred on it. */
.portal-name {
  left: 50%;
  top: 11px;
  transform: translateX(-50%);
}
/* The bearing/range label rides the ray, so it is rotated by the caller and
   centred on its anchor point. */
.portal-cone-label {
  left: 0;
  top: 0;
  transform-origin: 50% 50%;
}

/* Brush readout for the discharge circles: one floating box driven by a single
   mousemove handler, rather than a tooltip bound to every circle. */
#dot-brush {
  position: absolute;
  z-index: 820;
  pointer-events: none;
  padding: 4px 8px;
  border-radius: 4px;
  background: rgba(13, 17, 23, .94);
  border: 1px solid #30363d;
  color: #e6edf3;
  font: 11px/1.4 ui-monospace, SFMono-Regular, Menlo, monospace;
  white-space: nowrap;
  box-shadow: 0 4px 14px rgba(0, 0, 0, .5);
}
#dot-brush[hidden] { display: none !important; }

/* ── AI after-action reviews: the dropdown and the request form ──────────────
   Lives inside the Analysis bin, below the radios it enables. Borrows the bin's
   dark control palette rather than introducing a second one. */
#map-controls .aar-review {
  display: flex; flex-direction: column; gap: 5px;
  margin-top: 7px; padding-top: 7px; border-top: 1px solid #30363d;
}
#map-controls .aar-row { display: flex; gap: 5px; align-items: center; }
#map-controls .aar-review select {
  flex: 1 1 auto; min-width: 0;
  background: #0d1117; color: #c9d1d9; border: 1px solid #30363d;
  border-radius: 4px; padding: 4px 6px; font: inherit;
}
#map-controls .aar-review select:disabled { color: #6e7681; }
#map-controls .aar-review button {
  background: #21262d; color: #c9d1d9; border: 1px solid #30363d;
  border-radius: 4px; padding: 4px 9px; font: inherit; cursor: pointer;
}
#map-controls .aar-review button:hover { border-color: #58a6ff; }
#map-controls .aar-review button:disabled { color: #6e7681; cursor: default; border-color: #30363d; }
#map-controls .aar-review label {
  display: flex; flex-direction: column; gap: 3px; font-size: 11px; color: #8b949e;
}
#map-controls .aar-review input[type=text],
#map-controls .aar-review textarea {
  background: #0d1117; color: #c9d1d9; border: 1px solid #30363d;
  border-radius: 4px; padding: 4px 6px; font: inherit; resize: vertical;
}
#map-controls #aar-form { display: flex; flex-direction: column; gap: 6px; }
#map-controls #aar-form[hidden] { display: none !important; }
#map-controls .aar-status { font-size: 11px; color: #8b949e; line-height: 1.35; }
#map-controls .aar-status.aar-error { color: #f85149; }
#map-controls .aar-status.aar-busy { color: #d29922; }
/* "Bin at" selector at the top of the Order of Battle list. Sits in both the
   floating panel and the docked filter bin, because it is rendered as the
   first row of the tree rather than into either container's own chrome. */
.orb-bin {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 5px 6px 7px;
  margin-bottom: 4px;
  border-bottom: 1px solid #21262d;
}
.orb-bin label {
  font-size: 11px;
  color: #8b949e;
  letter-spacing: .02em;
}
.orb-bin select {
  flex: 1 1 auto;
  min-width: 0;
  background: #0d1117;
  color: #c9d1d9;
  border: 1px solid #30363d;
  border-radius: 4px;
  padding: 2px 4px;
  font-size: 11px;
  font-family: inherit;
}
.orb-bin select:hover { border-color: #8b949e; }
.orb-bin select:focus { outline: none; border-color: #58a6ff; }

/* A soldier who fired but never reported a position. The rounds are real and
   the count is true; what is missing is anywhere to draw them. Faded rather
   than hidden so the shots are still accounted for, and distinct from `.off`
   (deliberately hidden by the eye toggle) which is dimmer still.

   Units that produced neither rounds nor a position never reach the DOM --
   build.py leaves them out of the tree, so both the panel and the unit
   timeline drop them without either having to know why. */
#orbat-body .orb-row.nopos .orb-nm,
#map-controls .orbat-tree .orb-row.nopos .orb-nm { opacity: .55; font-style: italic; }
#orbat-body .orb-row.nopos .orb-chip,
#map-controls .orbat-tree .orb-row.nopos .orb-chip { opacity: .45; }
#orbat-body .orb-row.nopos .orb-rc,
#map-controls .orbat-tree .orb-row.nopos .orb-rc { opacity: .6; }

/* Contour height labels. Sit ON the line like a paper topo sheet, so they are
   dark text on a translucent plate rather than a pill -- a pill would read as
   a marker, and these are part of the line. */
.contour-label {
  font: 700 10px/1 ui-monospace, SFMono-Regular, Menlo, monospace;
  color: #2a1c05;
  background: rgba(255, 214, 150, .92);
  padding: 1px 3px;
  border-radius: 2px;
  white-space: nowrap;
  pointer-events: none;
}

/* Sight-check cross-section. Floats over the map bottom-left, narrow enough
   to leave the scale bar and attribution clear. */
#sight-panel {
  position: absolute;
  left: 12px;
  bottom: 12px;
  z-index: 800;
  width: 430px;
  max-width: calc(100% - 24px);
  background: rgba(11, 14, 20, .88);
  border: 1px solid #2b3440;
  border-radius: 6px;
  padding: 7px 9px 6px;
  color: #d8dee6;
  font: 11px/1.45 ui-monospace, SFMono-Regular, Menlo, monospace;
  pointer-events: none;
}
#sight-head { color: #9fb0c3; margin-bottom: 3px; }
#sight-head b { color: #e8eef6; }
#sight-svg { display: block; width: 100%; height: 150px; }
#sight-foot { margin-top: 3px; color: #9fb0c3; }
#sight-foot .hit { color: #ffae3d; }
#sight-foot .open { color: #6ee7b7; }

/* Overlay sub-group. Used by Terrain, which is a set of exploratory overlays
   sharing one dependency and shipped off -- grouping them keeps the main
   overlay list about things that work. */
/* The bin lays its children out as a flex column with a gap, and a <details>
   is one flex item, so its own children fell out of that and lost the
   spacing. Putting the flex column on the <details> does NOT fix it: Chrome
   wraps the children in ::details-content, so the layout applies to that box
   and the pills inside it stay inline. Hence an explicit body element, which
   needs no agreement from the browser about how <details> is boxed. */
.toggle-group { margin: 2px 0 1px; }
.toggle-group .tg-body {
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.toggle-group > summary {
  cursor: pointer;
  list-style: none;
  padding: 2px 0;
  color: #9fb0c3;
  font-size: 11px;
  letter-spacing: .04em;
  text-transform: uppercase;
  user-select: none;
}
.toggle-group > summary::-webkit-details-marker { display: none; }
/* Disclosure triangle drawn with borders, not a glyph. The first version used
   a CSS unicode escape for one, and the escape was swallowed as an OCTAL
   escape on the way into this file -- leaving a literal control byte that
   rendered as a tofu box followed by two stray letters. Borders need no font
   coverage and nothing survives being escaped twice. */
.toggle-group > summary::before {
  content: "";
  display: inline-block;
  width: 0;
  height: 0;
  margin-right: 5px;
  vertical-align: 1px;
  border-top: 4px solid transparent;
  border-bottom: 4px solid transparent;
  border-left: 5px solid #5d6b7d;
}
.toggle-group[open] > summary::before {
  border-left: 4px solid transparent;
  border-right: 4px solid transparent;
  border-top: 5px solid #5d6b7d;
  border-bottom: 0;
  vertical-align: 2px;
}
/* Indent the PILL, never its padding: padding-left moves the text inside the
   box and makes a grouped toggle a visibly different control from every
   other one. */
.toggle-group .map-toggle { margin-left: 10px; }
.toggle-group .grp-note { text-transform: none; letter-spacing: 0; color: #6b7a8c; }
/* Disabled as a set: the summary dims with the controls under it, so the
   group reads as switched off rather than as four separately broken boxes. */
.toggle-group.grp-off > summary { color: #5d6b7d; }
.toggle-group.grp-off .map-toggle { opacity: .45; }
