/* The Fires Lab — Copyright (c) 2026 Catherine Lake Creations LLC. All rights reserved.
   Build reference: CLC-RG-7F61363DDFE5 */
/* =========================================================
   Fire Mission Sim — layout only.

   This file styles sim.html and nothing else. The sim is deliberately
   decoupled from the D3A course: it is not in PAGES in tools/tag-i18n.js,
   it does not call D3A.registerPage, and it is absent from PHASES and
   SUBPAGES in js/app.js. Keep new sim rules here, not in css/style.css.

   It does borrow style.css for the design tokens (--ink, --panel, --ok,
   --bad, the fonts) and for the .asset-status / .as-pie dial, which is the
   same component the Deliver timeline uses.

   Shape: a timeline strip across the top, then the feed filling the full
   window width — width is what sets how large the ground is drawn, so the feed
   is width-first and takes whatever height its aspect gives it. The panels
   dock at the bottom of the window and expand upward over the feed.

   The feed's rendered size depends only on the window, never on panel state.
   That is deliberate: a constant feed size keeps the screen-to-grid mapping
   constant. See js/sim-camera.js.

   Layout and appearance only — behaviour lives in js/sim-*.js. The two places
   this file is driven from script are the timeline track (inline width / left)
   and the time-on-target field's hidden attribute.
   ========================================================= */

body.sim {
  margin: 0;
  height: 100dvh;
  display: grid;
  grid-template-rows: auto minmax(0, 1fr);
  gap: 8px;
  padding: 8px;
  background: var(--accent-dark);
  color: var(--ink);
  font-family: var(--font-body);
  overflow: hidden;
}

/* ---------- the feed ---------- */
/* top-aligned, so the spare height collects at the bottom where the dock sits */
.sim-stage { display: grid; align-items: start; justify-items: center; min-height: 0; }
/* Width-first: filling the window width is what makes the ground as large as
   it can be. Never crop or rescale img/sim-pov.jpg — the calibration in
   js/sim-camera.js is tied to those exact 1860 x 707 pixels. */
.sim-frame {
  position: relative;
  width: 100%;
  aspect-ratio: 1860 / 707;
  max-height: 100%;
  border-radius: var(--radius-sm);
  overflow: hidden;
}
.sim-feed { width: 100%; height: 100%; object-fit: contain; display: block; }

/* Everything the sim draws sits here, over the picture and under the credit.
   Sized and transformed in js/sim-render.js — the canvas covers the whole
   frame, but the drawing is inset to wherever the image actually rendered. */
.sim-canvas {
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
}

/* dev only, sim.html?dev=1 */
.sim-devgrid {
  position: absolute;
  left: 0;
  bottom: 0;
  z-index: 2;
  padding: .2rem .45rem;
  font-family: var(--font-mono);
  font-size: .7rem;
  color: #fff;
  background: rgba(0, 0, 0, .55);
  pointer-events: none;
}

/* The fires log (Lee, 2026-09-27): every round's effect, bottom-right of the
   feed. Scrolls, collapses to its header, opens again on every volley. */
.sim-fireslog {
  position: absolute;
  right: 0;
  bottom: 0;
  z-index: 3;
  max-width: calc(100% - 1rem);
  font-family: var(--font-mono);
  color: #fff;
  background: rgba(0, 0, 0, .62);
}
.sim-fireslog[hidden] { display: none; }
.sim-fireslog .fl-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: .75rem;
  width: 100%;
  margin: 0;
  padding: .2rem .45rem;
  border: 0;
  background: rgba(0, 0, 0, .35);
  color: #fff;
  font: 600 .7rem/1.2 var(--font-head);
  letter-spacing: .08em;
  text-transform: uppercase;
  cursor: pointer;
}
.sim-fireslog .fl-caret::after { content: '\25BE'; }
.sim-fireslog.is-collapsed .fl-caret::after { content: '\25B8'; }
.sim-fireslog .fl-body {
  margin: 0;
  padding: .25rem .45rem;
  max-height: 9.6rem;
  overflow-y: auto;
  font-size: .65rem;
  line-height: 1.35;
  white-space: pre;
}
.sim-fireslog.is-collapsed .fl-body { display: none; }

/* The score (Lee, 2026-09-28): ZBDs through the delay line. Flush in the top
   right corner, above the HUD (Lee moved it there); one line until clicked. */
.sim-score {
  position: absolute;
  right: 0;
  top: 0;
  z-index: 3;
  font-family: var(--font-mono);
  color: #fff;
  background: rgba(0, 0, 0, .62);
}
.sim-score[hidden] { display: none; }
.sim-score .sc-head {
  display: flex;
  align-items: center;
  gap: .6rem;
  width: 100%;
  margin: 0;
  padding: .2rem .45rem;
  border: 0;
  background: rgba(0, 0, 0, .35);
  color: #fff;
  font: 600 .7rem/1.2 var(--font-head);
  letter-spacing: .08em;
  text-transform: uppercase;
  cursor: pointer;
}
.sim-score .sc-num { margin-left: auto; font-family: var(--font-mono); letter-spacing: 0; }
.sim-score .sc-caret::after { content: '\25BE'; }
.sim-score.is-collapsed .sc-caret::after { content: '\25B8'; }
.sim-score .sc-body {
  margin: 0;
  padding: .25rem .45rem;
  font-size: .65rem;
  line-height: 1.35;
  white-space: pre;
}
.sim-score.is-collapsed .sc-body { display: none; }

/* Google Earth imagery credit. Required on screen whenever the frame is, so it
   is anchored to the frame rather than the page, and sits top-left: the dock is
   fixed to the bottom of the window and open panels grow upward over the
   picture. Shadowed rather than boxed so it stays legible over water or sand
   without putting a slab on the feed. */
.sim-attribution {
  position: absolute;
  top: 0;
  left: 0;
  z-index: 2;
  max-width: calc(100% - 1rem);
  margin: 0;
  padding: .25rem .5rem;
  font-size: .68rem;
  line-height: 1.3;
  color: #fff;
  text-shadow: 0 1px 2px rgba(0, 0, 0, .95), 0 0 6px rgba(0, 0, 0, .7);
  pointer-events: none;
}

/* The feed's copy of the credit sits bottom-left. The base rule above anchors
   it top-left, which the map's copy still overrides for itself; only this
   one moves.

   It sits UNDER the overlay (z 0 against the canvas's 1). From about T+8:30
   the D corridor, and later the S, drive along the bottom edge, and over the
   credit the text was covering their footprints and leaders. Now the contacts
   draw over the credit instead: the credit is still there, and a vehicle is
   never hidden by it. */
.sim-frame > .sim-attribution { top: auto; bottom: 0; z-index: 0; }

/* The top-left corner of the feed: the sensor mode switch. */
.sim-corner {
  position: absolute;
  top: .4rem;
  left: 0;
  z-index: 2;
  pointer-events: none;
}
.feed-mode {
  pointer-events: auto;
  display: inline-flex;
  margin: 0 0 0 .5rem;
  padding: 0;
  font-family: var(--font-mono);
  font-size: .68rem;
  line-height: 1.5;
  letter-spacing: .06em;
  color: #fff;
  background: rgba(0, 0, 0, .35);
  border: 1px solid rgba(255, 255, 255, .8);
  border-radius: 2px;
  cursor: pointer;
  overflow: hidden;
}
/* A two-way switch: both modes always visible, the live one lit. */
.fm-opt {
  padding: 0 .4rem;
  color: rgba(255, 255, 255, .6);
  text-shadow: 0 1px 2px rgba(0, 0, 0, .95);
}
.fm-opt + .fm-opt { border-left: 1px solid rgba(255, 255, 255, .8); }
.feed-mode[data-mode="tv"] .fm-opt[data-m="tv"],
.feed-mode[data-mode="ir"] .fm-opt[data-m="ir"] {
  color: #111;
  background: rgba(255, 255, 255, .9);
  text-shadow: none;
}
.feed-mode:hover { background: rgba(0, 0, 0, .55); }
.feed-mode:focus-visible { outline: 2px solid #fff; outline-offset: 1px; }

/* Hovering the feed, the mouse IS the reticle (drawn on the canvas), so the
   system cursor goes. Once a grid is locked the cursor comes back, so the
   student can see their way to the chat. */
.sim-frame.is-designating { cursor: none; }
.sim-frame.is-designating .feed-mode { cursor: pointer; }
/* Panels laid over the feed keep the ordinary cursor: the crosshair is for the
   ground, and a hidden pointer over the brief read as broken (Lee, 2026-09-28). */
.sim-frame.is-designating .sim-brief,
.sim-frame.is-designating .sim-score,
.sim-frame.is-designating .sim-fireslog { cursor: default; }

/* IR. A first guess at the look, to be tuned against the picture by eye —
   which is why it is one custom property. On the image only: the credit and
   the readout sit outside it and stay exactly as they are in TV. */
.sim-frame { --ir-filter: grayscale(1) contrast(1.4); }
.sim-frame.is-ir .sim-feed { filter: var(--ir-filter); }

/* ---------- shared surface ---------- */
.sim-timeline,
.dock-panel,
.rail-btn {
  border: 1px solid rgba(23, 27, 18, .22);
  border-radius: var(--radius-sm);
  background: var(--panel);
}

.region-label {
  font-family: var(--font-head);
  font-size: .8rem;
  font-weight: 600;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--muted);
}

/* ---------- timeline ---------- */
.sim-timeline {
  display: flex;
  align-items: center;
  gap: .9rem;
  padding: .4rem .7rem;
}
.sim-timeline .region-label { flex: none; }
.tl-transport { display: flex; gap: .25rem; }
.tl-btn {
  font-family: var(--font-mono);
  font-size: .85rem;
  line-height: 1;
  padding: .4rem .6rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--panel);
  color: var(--ink);
  cursor: default;
}
.tl-btn:not([disabled]) { cursor: pointer; }
.tl-btn:not([disabled]):hover { background: var(--border-soft); }
/* The transport pair reads as a state, not just two buttons: whichever of
   play / pause describes what the sim is doing now sits lit. */
.tl-transport .tl-btn[aria-pressed="true"] { background: var(--gold-fill); color: var(--gold-on-fill); }
.tl-speeds { display: flex; border: 1px solid var(--border); border-radius: var(--radius-sm); overflow: hidden; }
.tl-speeds .tl-btn { border: 0; border-radius: 0; border-left: 1px solid var(--border); }
.tl-speeds .tl-btn:first-child { border-left: 0; }
.tl-speeds .tl-btn[aria-pressed="true"] { background: var(--gold-fill); color: var(--gold-on-fill); }
.tl-track {
  flex: 1; min-width: 0;
  height: 10px; border-radius: 5px;
  background: var(--border-soft);
  position: relative;
}
/* Both are driven from js/sim-clock.js through inline width / left. They start
   at zero so the track does not flash a false position before the first paint. */
.tl-fill { position: absolute; inset: 0 auto 0 0; width: 0%; border-radius: 5px; background: var(--gold-fill); }
.tl-head { position: absolute; top: 50%; left: 0%; width: 3px; height: 20px; transform: translate(-50%, -50%); background: var(--accent-dark); }
.tl-clock { font-family: var(--font-mono); font-size: .9rem; flex: none; }
/* DEV ONLY (sim.html?dev=1): the track becomes a seek bar. Delete with the
   dev block in sim.html. */
.tl-track.tl-dev-seek { cursor: pointer; height: 14px; border-radius: 7px; }
.sim-exit { flex: none; font-size: .8rem; color: var(--accent); }

/* ---------- call for fire: the time-on-target field ----------
   Only shown for TOT. A clock time read off the timeline, not a countdown. */
.cff-tot-wrap { display: inline-flex; align-items: center; gap: .3rem; }
.cff-tot {
  font-family: var(--font-mono);
  width: 5.2em;
  text-align: center;
}

/* The command for an at-my-command mission. Deliberately the loudest control
   in the dock: it is the one thing that is time-critical to press. */
.mission-fire {
  font-family: var(--font-head);
  font-size: .75rem;
  font-weight: 600;
  letter-spacing: .08em;
  margin-left: .5rem;
  padding: .2rem .55rem;
  border: 1px solid var(--accent-dark);
  border-radius: var(--radius-sm);
  background: var(--accent-dark);
  color: #fff;
  cursor: pointer;
}
.mission-fire:hover { filter: brightness(1.12); }

/* The words come from the markup (data-empty / data-label) so the sim's
   dictionary can translate them; CSS cannot. */
.mission-list:empty::after {
  content: attr(data-empty);
  color: var(--muted);
  font-size: .85rem;
}

/* ---------- the engagement area map ----------
   It sits OVER the feed and BESIDE the chat, not across the whole window: the
   student reads the map and the net together, then closes it. So its box is
   bounded on three sides by things that must stay visible —

     top     the timeline, so T+ is never lost mid-correlation
     left    the chat panel, so the two sit side by side
     bottom  the dock rail, so the MAP button that opened it stays reachable

   --map-left and --map-bottom are measured in sim.html, because both depend on
   rendered heights CSS cannot know. The fallbacks degrade to a sane full-width
   box if that script never runs.

   THE BOX IS THE IMAGE'S OWN SHAPE. An earlier version nested percentage
   max-heights inside an auto-height wrapper, which does not constrain, so the
   picture overflowed and the bottom of the map was cut off. Giving the wrapper
   the image's aspect-ratio and a definite height removes the broken link
   entirely: the box can only ever be map-shaped. */
.sim-map {
  position: fixed;
  left: var(--map-left, 8px);
  right: 8px;
  top: var(--dock-top, 45vh);
  bottom: var(--map-bottom, 8px);
  z-index: 4;
  display: grid;
  place-items: center;
  /* No backdrop: the chat beside it stays fully usable, and dimming it would
     defeat the point. The empty margin around the map passes clicks through to
     the feed underneath. */
  pointer-events: none;
}
.sim-map[hidden] { display: none; }

.map-inner {
  position: relative;
  /* Kept in step with img/sim-map.jpg — the suite parses the JPEG header and
     fails if the two disagree. */
  aspect-ratio: var(--map-aspect, 1800 / 1718);
  height: 100%;
  width: auto;
  max-width: 100%;
  max-height: 100%;
  pointer-events: auto;
  border-radius: var(--radius-sm);
  overflow: hidden;
  box-shadow: 0 10px 34px rgba(8, 14, 6, .55);
}
.map-img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: fill;
}

/* The MAP rail button lights while the map is up. With no close button on the
   picture, this is the only thing telling the student the map is open — and it
   is the control they will press to close it. */
.rail-btn[aria-expanded="true"] {
  background: var(--gold-fill);
  color: var(--gold-on-fill);
  border-color: var(--gold-fill);
}

/* THE CREDIT GOES TOP-RIGHT, over open water.

   It is a licensing requirement and has to be legible whenever the map is on
   screen, which leaves only one place it can go: the grid labels run down the
   LEFT edge and along the BOTTOM, and the map is read by those labels. Sitting
   at the bottom — where it started — it covered the eastings and the scale bar,
   which is precisely the information the map exists to give.

   Top-right is the only large area carrying neither labels nor obstacle
   belts. It starts below the north arrow (which ends 11.4% down) and is
   right-aligned so it grows leftwards over the sea rather than down over
   TAI 1. */
.map-credit {
  /* BOTTOM-RIGHT, over open water. The credit has to be legible whenever the
     map is on screen, and the map is read by its grid labels: northings down
     the left edge, eastings along the bottom. Bottom-LEFT, where it started,
     covered the eastings and the scale bar — the very information the map
     exists to give.

     5% up from the bottom clears the easting chips (they occupy the bottom
     1.7%-3.8%); 34% wide over the right-hand ocean keeps it off the TAI 2
     corner above and the scale bar away to the left. Checked by compositing the
     text onto the map at these figures, not estimated. */
  top: auto;
  bottom: 5%;
  left: auto;
  right: 1.5%;
  max-width: 34%;
  text-align: right;
  font-size: .6rem;
}

/* ---------- dock ----------
   Pinned to the bottom of the window. Collapsed it is a row of title strips in
   the dead space under the feed, covering nothing; opened, a panel grows
   upward over the picture. Either way the feed does not move. */
.sim-dock {
  position: fixed;
  left: 8px; right: 8px; bottom: 8px;
  /* The dock reaches all the way up to just under the timeline, so an open
     panel has that whole height to grow into. --dock-top is measured from the
     timeline in sim.html, because the timeline's height depends on its own
     content and CSS cannot know it. The fallback keeps the layout sane if that
     script never runs. */
  top: var(--dock-top, 45vh);
  /* ABOVE the map. The asset board and the mission list are glanced at, not
     worked in, so they open OVER the map for as long as they are needed and
     then get out of the way — the map never shrinks to make room for them. */
  z-index: 5;
  display: flex;
  align-items: flex-end;
  gap: 8px;
  /* The dock now covers the picture, so it must not swallow clicks over the
     empty space above the panels — the dev readout listens on the frame. */
  pointer-events: none;
}
.dock-panel,
.dock-rail { pointer-events: auto; }

.dock-panel {
  flex: 0 0 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}

/* ---------- how the bottom row divides ----------
   The chat is HALF THE SCREEN and the map fills the other half flush to the
   edge. Those two are the work.

   The asset board and the mission list are references the student glances at,
   so they are title strips sized to their own labels, pushed right with the
   rail. They are not competing for width with the two things actually being
   used, and opening one costs the map nothing because the dock sits ABOVE the
   map in z-order — it opens over it and then gets out of the way.

   The chat's width is fixed open or closed because the map is positioned
   against its right edge: a chat that resized on opening would drag the map
   sideways every time the student toggled either one.

   The chat is the ONLY thing in the row allowed to give way, and only when the
   window is too narrow to seat everything else — shrink, never grow, with a
   floor. That is what guarantees the rail stays on the page. */
#dock-chat { flex: 0 1 calc(50% - 4px); min-width: 240px; }

/* WIDTHS THAT DO NOT CHANGE WHEN A PANEL IS TOGGLED. These used to widen on
   opening, which shoved the rail along the row — far enough, with both of them
   open, to push the MAP button off the edge of the window. Nothing in the
   bottom row may move when a panel is toggled: the map is positioned against
   measurements taken from this row, and a control that relocates when you use
   it is worse than one that is simply small.

   They are clamped to the viewport rather than pinned to one number, because
   pinning them was the SECOND way the MAP button went off the edge: 344 + 328
   was measured against a 1920 px viewport, and at 150% display scaling the
   same monitor reports about 1272 CSS px. 20vw / 17vw sit at the old figures
   on a wide window and stand down on a narrow one. A clamp depends on the
   window, not on the panel's state, so the row still never moves on a toggle.

   The maximum is set by the TITLE STRIP, which is the widest thing either
   panel has to show. The open content is laid out to fit inside it rather than
   the other way round. */
#dock-assets   { margin-left: auto; flex: 0 0 clamp(230px, 20vw, 344px); }
#dock-missions { flex: 0 0 clamp(200px, 17vw, 300px); }

/* The dials stack — six units down the panel rather than across it, which is
   what lets the panel stay narrow. */
.dock-body .asset-status { flex-direction: column; align-items: stretch; }
.dock-body .as-item { width: 100%; }
.dock-body .as-note { white-space: normal; }

/* Ammunition on the right of each row (Lee, 2026-09-27). Fixed small width,
   so the panel does not change size: the note beside it wraps instead. */
.as-ammo {
  margin-left: auto;
  flex: 0 0 3.6rem;
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: .15rem;
  font-family: var(--font-mono);
  font-size: .66rem;
  line-height: 1.1;
}
.as-ammo[hidden] { display: none; }
.as-ammo-bar {
  display: block;
  width: 100%;
  height: .3rem;
  background: rgba(0, 0, 0, .12);
  border-radius: 2px;
  overflow: hidden;
}
.as-ammo-bar > i { display: block; height: 100%; background: var(--ok); }
.as-ammo.is-low .as-ammo-bar > i { background: var(--bad); }
.as-ammo.is-low .as-ammo-num { color: var(--bad); }

/* A mission row wraps inside the fixed width instead of stretching it: target
   number and status on the first line, the detail beneath. */
.mission-list li {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 0 .4rem;
  align-items: baseline;
}
.mission-list li > :nth-child(2) { grid-column: 1 / -1; font-size: .78rem; opacity: .8; }
.mission-list li > .st { grid-column: 2; grid-row: 1; justify-self: end; }
.mission-list li > .mission-fire { grid-column: 1 / -1; justify-self: start; margin: .2rem 0 0; }
.dock-panel { box-shadow: 0 6px 18px rgba(18, 27, 15, .3); }

.dock-head {
  display: flex;
  align-items: center;
  gap: .5rem;
  width: 100%;
  padding: .4rem .6rem;
  border: 0;
  background: none;
  text-align: left;
  cursor: pointer;
  font: inherit;
  color: inherit;
  flex: none;
}
.dock-head:hover { background: rgba(63, 107, 44, .08); }
.dock-head .region-label { flex: none; }
.dock-hint {
  flex: 1; min-width: 0;
  font-size: .72rem;
  color: var(--muted);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* chevron up when closed (it opens upward), down when open */
.chev {
  flex: none;
  width: 0; height: 0;
  border-left: 5px solid transparent;
  border-right: 5px solid transparent;
  border-bottom: 6px solid var(--muted);
}
.dock-panel.is-open .chev { border-bottom: 0; border-top: 6px solid var(--muted); }

.dock-body { display: none; padding: 0 .6rem .5rem; min-height: 0; }
.dock-panel.is-open .dock-body {
  display: flex;
  flex-direction: column;
  min-height: 0;
  overflow: auto;
}

/* THE CHAT TAKES THE FULL HEIGHT WHEN OPEN, and nothing else does — the other
   panels are short and would just leave a tall empty box. The point is a fixed
   layout under speed: the log scrolls in the space above and the fire mission
   block stays pinned at the bottom, so Send is in the same place on screen
   whether the log holds three transmissions or thirty. */
#dock-chat.is-open { height: 100%; }
#dock-chat.is-open .dock-body { flex: 1 1 auto; overflow: hidden; }
#dock-chat.is-open .chat-log { flex: 1 1 auto; min-height: 0; overflow: auto; }
#dock-chat.is-open .cff-builder { flex: none; }

/* Everything else is capped so a long list cannot push a panel off the top. */
#dock-assets.is-open .dock-body,
#dock-missions.is-open .dock-body { max-height: 100%; overflow: auto; }

/* asset status board — same dial component as the Deliver timeline */
.dock-body .asset-status { margin: .1rem 0 0; gap: .55rem; }

/* ---------- fires coordination chat ----------
   Every transmission is its own container. Under load the student is reading
   this at a glance while a contact moves and a clock runs, so the traffic has
   to separate into discrete messages rather than running together as a
   paragraph — and it has to be obvious at a glance which ones are theirs.

   Two shades, one job each:
     incoming (FSCC)   the panel's own surface, a muted left rule
     outgoing (YOU)    a tinted card with an accent rule
   Neither is loud: this sits under a live picture and must not compete with
   it. */
.chat-log {
  font-size: .82rem;
  display: flex;
  flex-direction: column;
  gap: .3rem;
}
.chat-line {
  margin: 0;
  padding: .3rem .5rem .32rem;
  border-radius: var(--radius-sm);
  border: 1px solid var(--border-soft);
  border-left: 3px solid var(--border);
  background: color-mix(in srgb, var(--panel) 88%, var(--ink) 12%);
  line-height: 1.35;
}
.chat-line.is-self {
  border-left-color: var(--accent);
  background: color-mix(in srgb, var(--panel) 82%, var(--accent) 18%);
}
/* The callsign leads the card rather than sitting inline, so the eye can run
   down the left edge and pick out who said what without reading. */
.chat-line .who {
  display: block;
  font-family: var(--font-mono);
  font-size: .68rem;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--muted);
  margin-bottom: .1rem;
}
.chat-line.is-self .who { color: var(--accent); }

/* ---------- the command waiting to be given ----------
   An at-my-command mission is laid and waiting, and the only thing between it
   and the guns is the student pressing one button. That button has to be
   findable without opening another panel, so the transmission is written out in
   the log with FIRE in the middle of it.

   Dashed like the typing bubble, because neither has been said yet — but in the
   student's own green, because this one is theirs to send. */
.chat-line.chat-prompt {
  border-style: dashed;
  border-left-color: var(--bad);
}

.cmd-fire {
  font-family: var(--font-head);
  font-size: .78rem;
  font-weight: 600;
  letter-spacing: .1em;
  margin: 0 .15rem;
  padding: .1rem .6rem;
  border: 1px solid var(--bad);
  border-radius: var(--radius-sm);
  background: var(--bad);
  color: #fff;
  cursor: pointer;
  /* The one red thing on the screen. Everything else in the dock is green, gold
     or grey, so this reads at a glance from across the feed. */
  animation: cmd-pulse 1.8s infinite ease-in-out;
}
.cmd-fire:hover { filter: brightness(1.12); }
.cmd-fire:focus-visible { outline: 2px solid var(--ink); outline-offset: 2px; }
@keyframes cmd-pulse {
  0%, 100% { box-shadow: 0 0 0 0 rgba(163, 53, 42, .6); }
  55%      { box-shadow: 0 0 0 5px rgba(163, 53, 42, 0); }
}
/* A paused sim is not waiting on the student for anything. */
body.is-paused .cmd-fire { animation-play-state: paused; }
@media (prefers-reduced-motion: reduce) { .cmd-fire { animation: none; } }

/* ---------- "<callsign> is typing" ----------
   The beat between the student's transmission and the acknowledgement. Quieter
   than a real transmission — it is not one yet — so it is unshaded and italic
   with a dashed rule, and it can never be mistaken for traffic that arrived. */
.chat-line.chat-typing {
  background: none;
  border-style: dashed;
  border-left-color: var(--border);
  color: var(--muted);
  font-style: italic;
}
.typing-dots { display: inline-block; margin-left: .25rem; }
.typing-dots i {
  display: inline-block;
  width: 3px; height: 3px;
  margin-right: 2px;
  border-radius: 50%;
  background: currentColor;
  animation: typing-dot 1.2s infinite ease-in-out;
}
.typing-dots i:nth-child(2) { animation-delay: .15s; }
.typing-dots i:nth-child(3) { animation-delay: .3s; }
@keyframes typing-dot {
  0%, 60%, 100% { opacity: .25; }
  30%           { opacity: 1; }
}
/* A paused sim is not waiting for anything: the reply sits on the clock's
   queue, frozen. The dots stop with it rather than implying progress. */
body.is-paused .typing-dots i { animation-play-state: paused; }
@media (prefers-reduced-motion: reduce) {
  .typing-dots i { animation: none; opacity: .6; }
}

/* Older browsers without color-mix still need the two shades to differ, so the
   distinction falls back to the border rule and a flat tint. */
@supports not (background: color-mix(in srgb, red 50%, blue)) {
  .chat-line { background: var(--panel); }
  .chat-line.is-self { background: var(--border-soft); }
}

/* ---------- the fire mission ----------
   Everything above is a transcript the student reads. THIS is the only part
   they act on, so it is lifted off the log as its own block rather than
   continuing the flow of it: a heavier surface, a full border, and a label.
   Under time pressure the student should never have to work out where the
   controls start. */
.cff-builder {
  margin-top: .5rem;
  padding: .45rem .55rem .5rem;
  border: 1px solid var(--border);
  border-left: 3px solid var(--accent);
  border-radius: var(--radius-sm);
  /* THE SAME GREEN AS THE STUDENT'S OWN TRANSMISSIONS. Their outgoing bubbles
     and the block they compose in are one surface: everything on this side of
     the net is theirs. Kept identical to .chat-line.is-self on purpose — if one
     changes, change both. */
  background: color-mix(in srgb, var(--panel) 82%, var(--accent) 18%);
  display: flex;
  flex-direction: column;
  gap: .3rem;
}
@supports not (background: color-mix(in srgb, red 50%, blue)) {
  .cff-builder { background: var(--border-soft); }
}
.cff-builder::before {
  content: attr(data-label);
  color: var(--accent);
  font-family: var(--font-head);
  font-size: .7rem;
  font-weight: 600;
  letter-spacing: .1em;
  text-transform: uppercase;
}
.cff-line {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: .35rem;
}
.cff-line select,
.cff-line input {
  font-family: var(--font-body);
  font-size: .8rem;
  padding: .2rem .3rem;
  border: 1px solid var(--border);
  border-radius: 4px;
  background: var(--panel);
  color: var(--ink);
}
.cff-line select:disabled,
.cff-line input:disabled { color: var(--muted); opacity: 1; }
.cff-fixed { font-size: .8rem; }
.cff-or { font-size: .75rem; color: var(--muted); }
.cff-num { width: 3ch; text-align: center; }

/* the one live field: a grid, easting then northing, four digits each */
.cff-sq { font-family: var(--font-mono); font-size: .78rem; color: var(--muted); }
/* Wide enough for five digits: six-digit grids are standard, ten must fit. */
.cff-grid {
  width: 8ch;
  font-family: var(--font-mono) !important;
  letter-spacing: .06em;
  text-align: center;
}
.cff-grid:focus { outline: 2px solid var(--accent); outline-offset: 1px; }

.cff-send {
  font-family: var(--font-head);
  font-weight: 600;
  letter-spacing: .06em;
  font-size: .8rem;
  padding: .28rem .9rem;
  border: 1px solid var(--border);
  border-radius: 4px;
  background: var(--gold-fill);
  color: var(--gold-on-fill);
  cursor: pointer;
}
.cff-send:hover { filter: brightness(1.06); }
.cff-status { font-size: .74rem; color: var(--muted); }
.cff-status.is-bad { color: var(--bad); }

/* active missions */
.mission-list { list-style: none; margin: 0; padding: 0; font-size: .8rem; }
.mission-list li {
  display: flex; align-items: baseline; gap: .5rem;
  padding: .28rem 0;
  border-bottom: 1px solid var(--border-soft);
}
.mission-list .mn { font-family: var(--font-mono); font-size: .74rem; }
.mission-list .st { margin-left: auto; font-size: .7rem; letter-spacing: .05em; text-transform: uppercase; }
.mission-list .st.is-firing { color: var(--bad); }
.mission-list .st.is-queued { color: var(--muted); }

/* right rail — each opens a modal over the whole sim */
.dock-rail { display: flex; gap: 8px; flex: none; }
.rail-btn {
  padding: .45rem .9rem;
  box-shadow: 0 6px 18px rgba(18, 27, 15, .3);
  font-family: var(--font-head);
  font-weight: 600;
  letter-spacing: .06em;
  font-size: .95rem;
  color: var(--ink);
  cursor: pointer;
}

/* Narrow screens: the sim needs width. Say so rather than reflowing into
   something that cannot be flown. */
.sim-too-narrow { display: none; }
@media (max-width: 860px) {
  body.sim { display: grid; place-items: center; background: var(--bg); }
  body.sim > :not(.sim-too-narrow) { display: none; }
  .sim-too-narrow { display: block; padding: 2rem; text-align: center; color: var(--muted); }
}

/* The Stop confirmation (Lee, 2026-09-27). Its own classes, not the course's
   .modal (the sim stays out of the course's modal and i18n rules); the
   buttons borrow .btn. */
.sim-confirm {
  display: none;
  position: fixed;
  inset: 0;
  z-index: 60;
  background: rgba(28, 30, 24, .55);
  padding: 24px;
}
.sim-confirm.show { display: flex; align-items: center; justify-content: center; }
.sim-confirm-box {
  background: var(--panel);
  border: 1px solid var(--border);
  border-top: 5px solid var(--bad);
  border-radius: var(--radius);
  padding: 22px 26px;
  max-width: 26em;
  box-shadow: 0 18px 44px rgba(0, 0, 0, .3);
}
.sim-confirm-box p { margin: 0 0 18px; font-size: 1.05rem; }
.sim-confirm-actions { display: flex; justify-content: flex-end; gap: .6rem; }

/* Predator hides parts of the call for fire (Lee, 2026-09-28). The rows are
   flex, which would otherwise override the hidden attribute. */
.cff-builder [hidden] { display: none !important; }

/* The brief (Lee, 2026-09-28): over the feed, not filling it — a light scrim
   so the ground shows behind. X closes it; the arrow walks the slides. */
.sim-brief {
  display: none;
  position: absolute;
  inset: 0;
  z-index: 8;
  background: rgba(10, 12, 8, .28);
  align-items: center;
  justify-content: center;
  padding: 2% 4%;
  /* Type scales with the feed's width (the feed's height follows it), so a
     slide always fits: no scroll bar at any supported width (Lee). */
  font-size: clamp(11px, 1.12vw, 16px);
}
.sim-brief.show { display: flex; }
.sim-brief { container-type: size; }
.brief-box {
  position: relative;
  width: min(52em, 80%);
  max-height: 100%;
  overflow: hidden;
  padding: .9em 1.4em .7em;
  background: rgba(20, 22, 18, .82);
  color: #f2f1ea;
  border: 1px solid rgba(255, 255, 255, .18);
  border-top: 4px solid #c9a227;
  border-radius: var(--radius);
  box-shadow: 0 18px 44px rgba(0, 0, 0, .35);
}
.brief-title {
  margin: 0 2em .3em 0;
  padding-bottom: .3em;
  font: 700 .95em/1.2 var(--font-head);
  letter-spacing: .12em;
  text-transform: uppercase;
  color: #c9a227;
}
.brief-slide h3 {
  margin: .6em 0 .15em;
  font: 600 .85em/1.2 var(--font-head);
  letter-spacing: .1em;
  text-transform: uppercase;
  color: #d8d6ca;
}
.brief-slide p, .brief-slide li { margin: 0; font-size: 1em; line-height: 1.4; }
.brief-slide ol { margin: 0; padding-left: 1.3em; }
.brief-slide li + li { margin-top: .2em; }
.brief-x {
  position: absolute;
  top: .3em;
  right: .4em;
  width: 2em;
  height: 2em;
  border: 0;
  background: transparent;
  color: #f2f1ea;
  font-size: 1.5em;
  line-height: 1;
  cursor: pointer;
}
.brief-x:hover, .brief-x:focus-visible { color: #c9a227; }
.brief-nav {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 1em;
  margin-top: .7em;
}
.brief-count { font-family: var(--font-mono); font-size: .8em; color: #b9b7aa; }
.brief-arrow {
  min-width: 2.6em;
  padding: .25em .7em;
  border: 1px solid rgba(255, 255, 255, .35);
  border-radius: var(--radius-sm);
  background: rgba(255, 255, 255, .08);
  color: #f2f1ea;
  font: 600 1em/1 var(--font-head);
  cursor: pointer;
}
.brief-arrow:disabled { opacity: .3; cursor: default; }
.brief-arrow.is-begin { background: #c9a227; border-color: #c9a227; color: #14160f; letter-spacing: .06em; }
/* Slide 4, the terrain analysis (Lee, 2026-09-28): the map centred and as
   tall as the feed allows (so no scroll bar); the key on the open water at
   the map's right; the slide's name beside the header. */
.brief-sub { color: #d8d6ca; }
.brief-terrain:not([hidden]) { display: flex; justify-content: center; margin-top: .4em; }
.terrain-map { position: relative; }
.terrain-map img {
  display: block;
  height: calc(100cqh - 7em);
  max-height: 36em;
  width: auto;
  border: 1px solid rgba(255, 255, 255, .25);
  border-radius: var(--radius-sm);
}
.brief-key {
  position: absolute;
  right: 4%;
  bottom: 14%;
  list-style: none;
  margin: 0;
  padding: .45em .6em;
  background: rgba(20, 22, 18, .78);
  border: 1px solid rgba(255, 255, 255, .2);
  border-radius: var(--radius-sm);
}
.brief-key li { display: flex; align-items: center; gap: .5em; font-size: .78em; line-height: 1.2; }
.brief-key li + li { margin-top: .35em; }
.key-sw { flex: 0 0 auto; width: 1.9em; height: 1.1em; }
.key-restricted {
  border: 2px solid #c8102e;
  background-color: rgba(200, 16, 46, .15);
  background-image: repeating-linear-gradient(45deg, rgba(200, 16, 46, .8) 0 1px, transparent 1px 6px),
                    repeating-linear-gradient(-45deg, rgba(200, 16, 46, .8) 0 1px, transparent 1px 6px);
}
.key-open { border: 1.5px dashed #b9b7aa; }

/* ---------- language switch (Lee, 2026-09-28) ----------
   The course's EN / 繁中 switch (js/i18n.js), twice: top left of the
   timeline, ahead of its label, and on the first slide of the brief. The
   course draws it for a dark header; the timeline is a light panel, so it is
   re-inked here. The brief is dark and takes the course look as it is. */
.sim-timeline .lang-toggle {
  flex: none;
  margin: 0;
  border-color: var(--border);
}
.sim-timeline .lang-toggle button {
  color: var(--ink);
  font-size: .8rem;
  padding: .3rem .65rem;
  line-height: 1.2;
}
.sim-timeline .lang-toggle button:hover { background: var(--border-soft); color: var(--ink); }
.sim-timeline .lang-toggle button.on { background: var(--gold-fill); color: var(--gold-on-fill); }
/* Slide 1 only: beside the X, on the title's line. */
.brief-lang {
  position: absolute;
  top: .75em;
  right: 3.4em;
  margin: 0;
}
.brief-lang button { font-size: .8em; padding: .2em .8em; line-height: 1.3; }

/* ---------- Chinese ----------
   The course switches every button, heading and the body to the CJK face with
   a taller line in Chinese (css/style.css). The sim's controls are sized
   tight and must not grow: the transport keeps its mono glyphs, the dock
   keeps its heights, and the logs keep monospaced columns. */
html:lang(zh-Hant) body.sim { line-height: 1.5; }
html:lang(zh-Hant) body.sim .tl-btn { font-family: var(--font-mono); line-height: 1; }
html:lang(zh-Hant) body.sim button,
html:lang(zh-Hant) body.sim select { line-height: 1.3; }
html:lang(zh-Hant) body.sim .region-label,
html:lang(zh-Hant) body.sim .brief-title,
html:lang(zh-Hant) body.sim .brief-slide h3 { letter-spacing: .04em; text-transform: none; }
html:lang(zh-Hant) body.sim .fl-body,
html:lang(zh-Hant) body.sim .sc-body {
  font-family: 'IBM Plex Mono', Consolas, "Microsoft JhengHei", "PingFang TC", "Noto Sans TC", monospace;
}
/* The course caps Chinese paragraphs at 40em and resizes every h2 / h3 for
   its own pages; neither suits the sim's panels. The imagery credit keeps
   the Latin face so it stays on one line. */
html:lang(zh-Hant) body.sim p { max-width: none; }
html:lang(zh-Hant) body.sim .sim-attribution { max-width: calc(100% - 1rem); font-family: var(--font-body); }
html:lang(zh-Hant) body.sim .brief-title { font-size: 1.05em; }
html:lang(zh-Hant) body.sim .brief-slide h3 { font-size: .9em; }
