/*
 * The chrome. Four layers, in this order:
 *
 *   1. tokens        the palette and the type scale everything is built from
 *   2. the UI kit    ui.ts's components -- panel, button, plate, readout,
 *                    meter -- styled once and reused by every screen
 *   3. the screens   sidebar, overlay, mission select, Boot Hill
 *   4. the layouts   the same screens rearranged for a phone
 *
 * Nothing below layer 2 invents its own button or its own border. That is the
 * whole point of having the kit: the mission select and the end-of-mission
 * panel cannot drift apart, because they are made of the same parts.
 *
 * Layer 4 is the reason layer 1 has a type scale rather than literal pixel
 * sizes. Every readable thing sizes from a token, and the tokens are redefined
 * per layout mode, so making the game legible on a phone is one block of
 * declarations rather than a hunt through six hundred lines. `data-layout` is
 * the only thing any of it branches on; `layout.ts` is the only thing that sets
 * it, and it decides from CSS pixels and orientation rather than from a
 * user-agent string, because a narrow desktop window has the same problem a
 * phone does and deserves the same answer.
 */

:root {
  --sidebar-w: 188px;
  /* Reserved bottom bar in portrait, before the home-indicator inset. */
  --bar-h: 104px;

  /*
   * The touch-target floor. 44px is the smallest thing a finger hits reliably,
   * and every control in the game is held to it -- buttons, difficulty tabs,
   * mission cards, roster plates once they became tappable.
   */
  --tap: 44px;

  /*
   * The type scale.
   *
   * The old sheet ran from 9px to 14px with up to 0.22em of letter-spacing on
   * the smallest of it, which is unreadable on a phone at arm's length and was
   * being "solved" by a media query that set `display: none`. Hiding the thing
   * the player needed was never the answer; sizing it was.
   */
  /*
   * Every size is a multiple of ten.
   *
   * ui/pixelface.ts builds the chrome face on a grid where one glyph pixel is
   * exactly one CSS pixel at font-size 10px -- so 10, 20, 30 and 40 land every
   * glyph edge on a whole device pixel and anything between them does not,
   * which is the softness this face exists to remove. There is no 24 here and
   * asking for one gets a blurred 20.
   *
   * The ladder doubled on 1 September: everything was set for a system font
   * and never revisited when the chrome became a bitmap face, which left the
   * result panel telling you a hostage had died in ten-pixel type. Body is 20
   * now, headings 30, titles 40 -- and every box that holds text had to grow
   * with it, which is the same instruction stated twice.
   */
  --face: "Boots & Bullets Pixel", ui-monospace, "Cascadia Mono", Menlo, Consolas, monospace;
  --fs-micro: 20px;   /* keycaps, theatre notes -- decoration, not reading */
  --fs-sm: 20px;      /* hints, card meta */
  --fs-body: 20px;    /* roster plates, the objective line */
  --fs-lg: 30px;      /* button labels, the mission name */
  --fs-xl: 30px;      /* readout values */
  --fs-display: 40px; /* briefing and result titles */
  --fs-title: 40px;
  --fs-brand: 60px;

  /*
   * Tracking is a legibility cost that rises as type shrinks. Uppercase at 9px
   * with 0.22em between the letters reads as a texture rather than a word, so
   * the small tokens carry less of it and the large ones keep the look.
   */
  --track-tight: 0.06em;
  --track: 0.12em;
  --track-wide: 0.2em;

  /* Olive, not emerald. The game's ground palette is a sunlit khaki green and
     the chrome has to sit in the same family or it reads as a different game. */
  --ink: #d6dcae;
  --ink-dim: #8e9a63;
  --ink-faint: #5f6a3d;
  --gold: #f0d878;
  --blood: #b4402a;

  --plate: #2a3117;
  --plate-hi: #3d4623;
  --plate-lo: #161b0b;
  --well: #10140a;
  --edge: #0a0d05;

  /* Notch, home indicator, rounded corners. Zero on a desktop. */
  --safe-t: env(safe-area-inset-top, 0px);
  --safe-r: env(safe-area-inset-right, 0px);
  --safe-b: env(safe-area-inset-bottom, 0px);
  --safe-l: env(safe-area-inset-left, 0px);
}

* { box-sizing: border-box; }

html, body {
  margin: 0;
  /*
   * `dvh`, not `100%`. A percentage height collapses the moment a mobile
   * browser shows or hides its own chrome, which is every time the page is
   * scrolled -- and the fallback matters, because a browser without `dvh`
   * still has to get a full-height game.
   */
  height: 100%;
  height: 100dvh;
  overflow: hidden;
  overscroll-behavior: none;
  background: var(--edge);
  color: var(--ink);
  font: var(--fs-body)/1 var(--face);

  user-select: none;
  -webkit-user-select: none;
  -webkit-tap-highlight-color: transparent;
}

/* ============================================================== the UI kit */

.ui-panel {
  background: var(--plate);
  border-top: 1px solid var(--plate-hi);
  border-left: 1px solid var(--plate-hi);
  border-right: 1px solid var(--plate-lo);
  border-bottom: 1px solid var(--plate-lo);
}
.ui-panel.tone-dark { background: var(--well); }
.ui-panel.tone-warn { background: #3a2113; }
.ui-panel.tone-good { background: #26401c; }

/* --- button */
.ui-btn {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 9px 12px;
  min-height: var(--tap);
  background: var(--plate);
  border-top: 1px solid var(--plate-hi);
  border-left: 1px solid var(--plate-hi);
  border-right: 1px solid var(--plate-lo);
  border-bottom: 2px solid var(--plate-lo);
  color: var(--ink);
  font: inherit;
  text-align: left;
  cursor: pointer;
  transition: background 0.1s, transform 0.06s, border-color 0.1s;
}
.ui-btn:hover, .ui-btn:focus-visible {
  background: var(--plate-hi);
  outline: none;
}
/* Pressing pushes the plate into the bevel rather than moving the whole thing. */
.ui-btn:active { transform: translateY(1px); border-bottom-width: 1px; margin-bottom: 1px; }
.ui-btn.tone-good { background: #2f5222; border-top-color: #4a7a34; border-left-color: #4a7a34; }
.ui-btn.tone-good:hover { background: #3d6a2c; }
.ui-btn.tone-warn { background: #4a2718; border-top-color: #74412a; border-left-color: #74412a; }
.ui-btn.tone-warn:hover { background: #5e3320; }

.ui-btn-body { display: flex; flex-direction: column; gap: 4px; flex: 1; min-width: 0; }
.ui-btn-label {
  font-size: var(--fs-body);
  font-weight: 700;
  letter-spacing: var(--track);
  text-transform: uppercase;
}
.ui-btn-hint { color: var(--ink-dim); font-size: var(--fs-micro); letter-spacing: var(--track-tight); }

.ui-key {
  padding: 3px 6px;
  background: var(--well);
  border: 1px solid var(--plate-hi);
  border-radius: 2px;
  color: var(--ink-dim);
  font: inherit;
  font-size: var(--fs-micro);
  letter-spacing: var(--track-tight);
}

/* --- plate: chevrons instead of a border, which is how the original does it */
.ui-plate {
  display: flex;
  align-items: center;
  gap: 7px;
  padding: 5px 7px;
  background: linear-gradient(#333c1c, #232a12);
  border-top: 1px solid #47522a;
  border-bottom: 1px solid var(--plate-lo);
  color: var(--ink);
  font-size: var(--fs-sm);
  font-weight: 700;
  letter-spacing: var(--track);
  text-transform: uppercase;
}
.ui-plate-label { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.ui-plate-value { color: var(--gold); }

/* A dead soldier keeps his plate. That is the point of the plate. */
.ui-plate.dead {
  background: linear-gradient(#2a1613, #1a0e0b);
  border-top-color: #4a2018;
  color: #7a5148;
  text-decoration: line-through;
  text-decoration-color: var(--blood);
}

/* --- readout */
.ui-readout { display: flex; align-items: baseline; gap: 7px; padding: 2px 7px; }
.ui-readout-value { color: var(--gold); font-size: var(--fs-xl); font-weight: 700; }
.ui-readout-label {
  color: var(--ink-dim);
  font-size: var(--fs-micro);
  letter-spacing: var(--track);
  text-transform: uppercase;
}
.ui-readout-value.pulse { animation: ui-pulse 0.32s ease-out; }
@keyframes ui-pulse {
  0% { color: #fff6d0; transform: scale(1.35); }
  100% { color: var(--gold); transform: scale(1); }
}

/* --- pips */
.ui-pips { display: flex; gap: 4px; padding: 2px 7px; }
.ui-pip { width: 9px; height: 9px; background: #8fd44a; box-shadow: 0 0 0 1px var(--edge); }
.ui-pip.dead { background: #4a2020; }

/* --- meter */
.ui-meter { padding: 4px 7px; }
.ui-meter-head {
  display: flex;
  justify-content: space-between;
  margin-bottom: 4px;
  color: var(--ink-dim);
  font-size: var(--fs-micro);
  letter-spacing: var(--track);
  text-transform: uppercase;
}
.ui-meter-text { color: var(--gold); }
.ui-meter-track { height: 6px; background: var(--well); box-shadow: inset 0 0 0 1px var(--plate-lo); }
.ui-meter-fill { display: block; height: 100%; background: linear-gradient(#d8b64c, #9a7a20); }

/* --- heading */
.ui-heading {
  margin: 12px 0 5px;
  padding: 0 7px;
  color: var(--ink-faint);
  font-size: var(--fs-micro);
  letter-spacing: var(--track-wide);
  text-transform: uppercase;
}
.ui-heading::after {
  content: "";
  display: block;
  height: 1px;
  margin-top: 4px;
  background: linear-gradient(90deg, var(--plate-hi), transparent);
}

/* =============================================================== the stage */
/*
 * A grid rather than a flex row, because the three layouts are three different
 * arrangements of the same two children and a grid can express all of them
 * without the markup changing. `wide` puts the HUD in a column beside the
 * viewport, `stacked` puts it in a row beneath, and `compact` takes it out of
 * flow entirely and floats it in the corners -- see the layouts section.
 */

#stage {
  display: grid;
  height: 100%;
  grid-template-columns: var(--sidebar-w) minmax(0, 1fr);
  grid-template-rows: minmax(0, 1fr);
}

/* The canvas's positioning context, and what the action bar is anchored to. */
#viewport {
  position: relative;
  min-width: 0;
  min-height: 0;
  overflow: hidden;
}

#screen {
  display: block;
  width: 100%;
  height: 100%;
  cursor: crosshair;
  /*
   * Without this the browser claims every drag for its own scrolling and pinch
   * for its own zoom, and the game never sees either. `user-scalable=no` has
   * been ignored by Safari since iOS 10; this is what actually works.
   */
  touch-action: none;
  -webkit-touch-callout: none;
}

/* --------------------------------------------------------------- sidebar */
/*
 * Down the left edge, as in the original. The column exists mainly to hold the
 * roster: a casualty should read as a name leaving a list, not as a counter
 * going down.
 */
/*
 * The sidebar keeps the small ladder.
 *
 * When the chrome doubled to 20/30/40 this column doubled with it, and a
 * 188px rail set for ten-pixel type burst at twenty -- every hint wrapped,
 * ORDERS ran to two lines, the roster barely fit. The owner asked for half
 * again, and half of this ladder is exactly the one the sidebar was designed
 * around. Scoped here rather than changed globally, so a dialog and the rail
 * can disagree about size while reading from one set of names -- and 15 is
 * not on the bitmap grid, so 30 halves to 20, not to a blur.
 */
#hud {
  --fs-micro: 10px;
  --fs-sm: 10px;
  --fs-body: 10px;
  --fs-lg: 20px;
  --fs-xl: 20px;
  display: flex;
  flex-direction: column;
  min-width: 0;
  min-height: 0;
  padding: calc(10px + var(--safe-t)) 6px 8px calc(6px + var(--safe-l));
  background: linear-gradient(#232a13, #171d0d);
  border-right: 2px solid var(--edge);
  box-shadow: inset -1px 0 0 var(--plate-lo);
  overflow: hidden;
}

.hud-mission { padding: 0 7px 2px; }
.hud-mission b {
  display: block;
  color: var(--gold);
  font-size: var(--fs-lg);
  letter-spacing: var(--track-tight);
  text-transform: uppercase;
  line-height: 1.15;
}
.hud-diff {
  display: inline-block;
  margin-top: 6px;
  padding: 2px 6px;
  font-size: var(--fs-micro);
  font-weight: 700;
  letter-spacing: var(--track);
  text-transform: uppercase;
}
.diff-rookie { background: #2f4a24; color: #bfe39a; }
.diff-regular { background: #3c4a1c; color: #dbe89a; }
.diff-veteran { background: #5a4118; color: #f0c878; }
.diff-elite { background: #5c2018; color: #f0a08c; }

.hud-body { flex: 1; min-height: 0; overflow: hidden; }
.hud-roster { display: flex; flex-direction: column; gap: 2px; }
.hud-objective {
  padding: 0 7px;
  color: var(--ink);
  font-size: var(--fs-sm);
  line-height: 1.45;
  letter-spacing: var(--track-tight);
  text-transform: uppercase;
}

.hud-hint {
  padding: 8px 7px 0;
  border-top: 1px solid var(--plate-lo);
  color: var(--ink-faint);
  font-size: var(--fs-micro);
  line-height: 1.8;
}
.hud-hint b { color: var(--ink-dim); font-weight: 700; }

/* --------------------------------------------------------------- overlay */
/*
 * The sidebar offset used to be baked in here as `inset: 0 0 0 var(--sidebar-w)`,
 * which quietly hard-coded one of the three layouts into the overlay geometry.
 * It is now a plain full-bleed overlay, and only `wide` shifts it.
 */
/* --- the blackout: one fade for the whole screen, canvas and chrome alike.
   Above the sidebar (3), the action bar (4) and the thumbstick (7), and below
   the overlay -- which is raised past it for exactly this reason, so the
   results panel and the briefing land *on* the black rather than under it.
   Not a canvas fill: the sidebar is DOM outside the canvas and nothing painted
   in there can ever reach it, which is the whole reason this element exists. */
#blackout {
  position: fixed;
  inset: 0;
  background: #000;
  opacity: 0;
  z-index: 8;
  pointer-events: none;
}

#overlay {
  position: fixed;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: max(12px, var(--safe-t)) max(12px, var(--safe-r)) max(12px, var(--safe-b)) max(12px, var(--safe-l));
  background: rgba(8, 11, 5, 0.78);
  pointer-events: none;
  /* Above #blackout, so an end-of-mission panel is readable on the black
     rather than buried under it. */
  z-index: 12;
}
#overlay[hidden] { display: none; }
/* The briefing is a caption; the result panel is a thing you click. */
#overlay.interactive { pointer-events: auto; }

/* --- briefing */
.briefing { text-align: center; padding: 0 30px; }
.briefing-title {
  color: var(--gold);
  font-size: var(--fs-display);
  font-weight: 700;
  letter-spacing: var(--track);
  text-transform: uppercase;
  text-shadow: 0 3px 0 #5c5210;
}
.briefing-obj {
  margin-top: 14px;
  color: var(--ink);
  font-size: var(--fs-body);
  letter-spacing: 0.2em;
  text-transform: uppercase;
}
.briefing-line { margin-top: 10px; color: var(--ink-dim); font-size: var(--fs-body); }

/* --- result */
.result {
  width: min(680px, 92vw);
  padding: 34px 34px 30px;
  background: linear-gradient(#242c14, #171d0d);
  border-top: 2px solid var(--plate-hi);
  border-left: 2px solid var(--plate-hi);
  border-right: 2px solid var(--edge);
  border-bottom: 2px solid var(--edge);
  box-shadow: 0 18px 40px rgba(0, 0, 0, 0.6);
  text-align: center;
}
.result-title {
  font-size: var(--fs-title);
  font-weight: 700;
  letter-spacing: var(--track);
  text-transform: uppercase;
}
.result.win .result-title { color: var(--gold); text-shadow: 0 3px 0 #5c5210; }
.result.lose .result-title { color: #e07050; text-shadow: 0 3px 0 #52180d; }
.result-sub { margin-top: 10px; color: var(--ink-dim); font-size: var(--fs-sm); letter-spacing: var(--track-tight); }

.result-roll { margin: 20px auto 0; max-width: 420px; }
.result-roll-head {
  margin-bottom: 7px;
  color: var(--blood);
  font-size: var(--fs-micro);
  letter-spacing: var(--track-wide);
  text-transform: uppercase;
}
.result-roll .ui-plate {
  background: linear-gradient(#2a1613, #1a0e0b);
  border-top-color: #4a2018;
  color: #a8756a;
  margin-bottom: 2px;
}
.result-perfect { margin-top: 18px; color: #a8c07a; font-size: var(--fs-sm); }

.result-actions { display: flex; flex-direction: column; gap: 7px; margin-top: 22px; }
/* Back and onward, side by side and equal weight. */
.result-choice { display: flex; gap: 7px; }
.result-choice .ui-btn { flex: 1 1 0; }
/* Two buttons, an arrow and a keycap into half a panel: the label gives way. */
.result-choice .ui-btn-label {
  font-size: var(--fs-lg);
  letter-spacing: var(--track-tight);
  white-space: nowrap;
}
.ui-btn-arrow { flex: none; font-size: 0.85em; line-height: 1; color: var(--ink-dim); }
/* The gap on .ui-btn already spaces them; these only pull them to the edges. */
.ui-btn-arrow.back { margin-right: -2px; }
.ui-btn-arrow.fwd { margin-left: -2px; }

/* ============================================================ mission select */

#menu {
  position: fixed;
  inset: 0;
  z-index: 10;
  overflow-y: auto;
  background: radial-gradient(ellipse at 50% -10%, #2f3a1c 0%, #141a0b 55%, #080b04 100%);
}
#menu[hidden] { display: none; }
#menu-inner { max-width: 1000px; margin: 0 auto; padding: 48px 24px 64px; }

#menu h1 {
  margin: 0;
  font-size: var(--fs-brand);
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--gold);
  text-shadow: 0 4px 0 #5c5210, 0 5px 12px rgba(0, 0, 0, 0.6);
  text-align: center;
}
.menu-sub {
  margin: 14px 0 30px;
  text-align: center;
  color: var(--ink-dim);
  letter-spacing: var(--track);
  text-transform: uppercase;
  font-size: var(--fs-sm);
}
.menu-foot { margin: 30px 0 0; text-align: center; color: var(--ink-faint); font-size: var(--fs-sm); letter-spacing: var(--track-tight); }

#menu-difficulty { display: flex; justify-content: center; gap: 5px; margin: 0 0 12px; }
.diff {
  padding: 8px 20px;
  background: var(--plate);
  border-top: 1px solid var(--plate-hi);
  border-left: 1px solid var(--plate-hi);
  border-right: 1px solid var(--plate-lo);
  border-bottom: 2px solid var(--plate-lo);
  color: var(--ink-dim);
  font: inherit;
  font-size: var(--fs-body);
  font-weight: 700;
  letter-spacing: var(--track);
  text-transform: uppercase;
  cursor: pointer;
  transition: background 0.12s, color 0.12s;
}
.diff:hover { background: var(--plate-hi); color: var(--ink); }
.diff.on { background: var(--plate-hi); color: var(--gold); box-shadow: inset 0 2px 0 var(--gold); }
.menu-blurb {
  margin: 0 0 24px;
  text-align: center;
  color: #a3b47c;
  font-size: var(--fs-sm);
  min-height: 14px;
}

/* --- mission groups */
/*
 * Missions are grouped into theatres rather than listed flat, so the campaign
 * reads as a shape you are moving through. Later groups will lock.
 */
#menu-list { display: flex; flex-direction: column; gap: 26px; }

.theatre-head {
  display: flex;
  align-items: baseline;
  gap: 12px;
  margin-bottom: 10px;
}
.theatre-name {
  color: var(--gold);
  font-size: var(--fs-body);
  font-weight: 700;
  letter-spacing: 0.2em;
  text-transform: uppercase;
}
.theatre-note { color: var(--ink-faint); font-size: var(--fs-micro); letter-spacing: var(--track-tight); }
.theatre-rule { flex: 1; height: 1px; background: linear-gradient(90deg, var(--plate-hi), transparent); }

.theatre-grid {
  display: grid;
  /*
   * 460, not 290. The cards were measured for ten-pixel type; at twenty a
   * mission name wrapped to three lines and the meta column collided with it.
   * A box does not stay the right size when the type inside it doubles, which
   * is the half of "make it bigger" that is not a font size.
   */
  grid-template-columns: repeat(auto-fill, minmax(460px, 1fr));
  gap: 10px;
}

/*
 * No left accent bar: the cards are grouped by theatre now, so a per-card
 * colour stripe is repeating information the heading already carries.
 */
.mission {
  display: grid;
  /* number | body | meta -- a grid so the meta cannot ride over the name. */
  grid-template-columns: auto minmax(0, 1fr);
  align-items: start;
  gap: 16px;
  width: 100%;
  padding: 13px 15px;
  background: linear-gradient(#232b14, #1a2010);
  border-top: 1px solid var(--plate-hi);
  border-left: 1px solid var(--plate-hi);
  border-right: 1px solid var(--plate-lo);
  border-bottom: 2px solid var(--plate-lo);
  color: inherit;
  font: inherit;
  text-align: left;
  cursor: pointer;
  transition: background 0.12s, transform 0.08s;
}
.mission:hover, .mission:focus-visible {
  background: linear-gradient(#33401d, #232c12);
  outline: none;
  transform: translateY(-2px);
}
.mission:active { transform: translateY(0); }
.mission.last { box-shadow: inset 0 0 0 1px #6b7d40; }

.m-num { font-size: 40px; font-weight: 700; color: #56703a; min-width: 54px; line-height: 1; }
.mission:hover .m-num { color: var(--gold); }
.m-body { display: flex; flex-direction: column; gap: 5px; flex: 1; min-width: 0; }
.m-name { font-size: var(--fs-lg); font-weight: 700; letter-spacing: var(--track-tight); text-transform: uppercase; }
.m-obj { color: var(--ink-dim); font-size: var(--fs-micro); letter-spacing: var(--track-tight); text-transform: uppercase; }
.m-mech { color: #a3b47c; font-size: var(--fs-sm); line-height: 1.4; }
.m-levers { display: flex; flex-wrap: wrap; gap: 4px; margin-top: 3px; }
.lever {
  padding: 2px 6px;
  background: rgba(0, 0, 0, 0.28);
  box-shadow: inset 0 0 0 1px #3b4d26;
  color: #9db97a;
  font-size: var(--fs-micro);
  font-style: normal;
  letter-spacing: var(--track-tight);
  text-transform: uppercase;
}
/*
 * The meta runs under the card, not beside it.
 *
 * As a third column it needed a fixed width no card could spare once the type
 * doubled: it is nowrap by nature -- "GARRISON 88X56 OPEN" cannot break -- so
 * it squeezed the name column to nothing and then overflowed across it. A row
 * beneath the body has the whole card to wrap into and cannot collide with
 * anything.
 */
.m-meta {
  grid-column: 1 / -1;
  display: flex;
  flex-direction: row;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px 14px;
  color: var(--ink-faint);
  font-size: var(--fs-sm);
  letter-spacing: var(--track-tight);
  text-transform: uppercase;
  white-space: nowrap;
}
.m-doctrine { color: #b0975c; }
/* The theatre heading carries the terrain now, so the card need not repeat it. */
.m-theme { display: none; }

/* The layouts section at the foot of this file replaces what used to be a
   single `max-width: 720px` query whose whole strategy was `display: none`. */

/* ============================================================ the meta-game */
/*
 * Rank, the roster and Boot Hill.
 *
 * Nothing here invents a new button or a new plate -- it decorates the kit's
 * pieces. A grave and a living soldier are the same `plate` with different
 * classes on it, deliberately, because they are the same man at different times.
 */

/* --- rank marks, shared by the sidebar, the panel and the hill */
.ui-plate.vet .ui-plate-value { color: #f0d878; font-weight: 700; }
/* The one soldier the player named. Marked everywhere he appears. */
.ui-plate.own .ui-plate-label::before {
  content: '\2726';
  margin-right: 5px;
  color: var(--gold);
  font-size: var(--fs-micro);
}

/* --- the par line in the sidebar */
.hud-par { margin-top: 10px; }
.hud-par[hidden] { display: none; }
.hud-par .ui-plate { background: var(--well); }
.hud-par.beating .ui-plate { box-shadow: inset 0 0 0 1px #6d8a3c; }
.hud-par.beating .ui-plate-value { color: #b6e06a; }
.hud-par.behind .ui-plate-value { color: #d08a52; }

/* --- the squad roll on the briefing card */
.briefing-squad {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 5px;
  margin-top: 18px;
}
.briefing-man {
  padding: 3px 8px;
  background: var(--plate);
  box-shadow: inset 0 1px 0 var(--plate-hi);
  color: var(--ink-dim);
  font-size: var(--fs-micro);
  font-style: normal;
  letter-spacing: var(--track-tight);
}
.briefing-man.vet { color: var(--gold); }
.briefing-man.fresh { color: #8fb0d4; }
.briefing-man.own { box-shadow: inset 0 0 0 1px var(--gold); }
.briefing-hill {
  margin-top: 14px;
  color: #9a6a52;
  font-size: var(--fs-micro);
  letter-spacing: var(--track);
  text-transform: uppercase;
}

/* --- records and promotions on the end panel */
.result-records {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 5px;
  margin-top: 16px;
}
.result-ribbon {
  padding: 3px 9px;
  background: #4a3d12;
  color: var(--gold);
  font-size: var(--fs-micro);
  font-style: normal;
  letter-spacing: var(--track-tight);
  text-transform: uppercase;
}
/*
 * Promotion is good news and has to look like it. These plates inherited the
 * casualty styling -- dark red on a red gradient, struck through in spirit if
 * not in fact -- so the one screen that congratulates you did it in the colour
 * of a death notice.
 */
.result-roll.promotions .ui-plate {
  background: linear-gradient(#2f4020, #1d2a13);
  border-top-color: #4d6a2c;
  color: #d8ecb0;
}
.result-roll.promotions .ui-plate-value { color: #b6e06a; font-weight: 700; }
.result-roll.promotions .result-roll-head { color: #8fae5e; }
.result-note {
  margin-top: 10px;
  color: var(--ink-faint);
  font-size: var(--fs-micro);
  line-height: 1.5;
}

/* --- the way in, on the mission select */
#menu-actions { display: flex; flex-wrap: wrap; justify-content: center; gap: 9px; margin: 0 0 18px; }
#menu-actions .ui-btn { width: auto; min-width: 200px; }
/* Nothing is rendered into it while Boot Hill is off the front screen, and an
   empty row should not still be spending its margin. */
#menu-actions:empty { display: none; }

/* --- the music switch
 *
 * Pinned to the corner, not in the flow: it belongs to the screen rather than to
 * the list of missions, and it must not move when the list scrolls -- hence
 * `fixed` inside a container that is itself the scroller. Round, because every
 * other control in this game is a bevelled rectangle and this one is not a
 * choice about the war.
 */
#music-toggle {
  position: fixed;
  top: calc(var(--safe-t) + 14px);
  left: calc(var(--safe-l) + 14px);
  z-index: 11;
  display: grid;
  place-items: center;
  width: var(--tap);
  height: var(--tap);
  padding: 0;
  background: var(--plate);
  border: 1px solid var(--plate-hi);
  border-bottom-color: var(--plate-lo);
  border-right-color: var(--plate-lo);
  border-radius: 50%;
  color: var(--ink-faint);
  cursor: pointer;
  transition: background 0.12s, color 0.12s, border-color 0.12s;
}
#music-toggle:hover, #music-toggle:focus-visible { background: var(--plate-hi); outline: none; }
#music-toggle:active { transform: translateY(1px); }
#music-toggle svg { width: 22px; height: 22px; overflow: visible; }
/* One rule each: the cone is a solid shape, the waves and the strike are lines,
   and both take their colour from the button so the states need only set that. */
#music-toggle .cone { fill: currentColor; }
#music-toggle .wave, #music-toggle .slash {
  fill: none;
  stroke: currentColor;
  stroke-width: 1.9;
  stroke-linecap: round;
}
/* Playing: lit gold, the one warm thing in the corner. */
#music-toggle.on { color: var(--gold); border-top-color: #4a7a34; border-left-color: #4a7a34; }
/* Held back by the browser's autoplay rules -- on, but not yet audible. The
   pulse is the only honest way to say "this is waiting for you", and it stops
   the moment the first click lets the track through. */
#music-toggle.blocked { color: var(--ink-dim); animation: music-wait 1.6s ease-in-out infinite; }
@keyframes music-wait {
  0%, 100% { opacity: 0.45; }
  50% { opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
  #music-toggle.blocked { animation: none; opacity: 0.7; }
}

/* --- ribbons on a mission card */
.m-ribbons { display: flex; gap: 3px; margin-top: 6px; }
.ribbon {
  width: 13px;
  height: 6px;
  background: var(--plate-lo);
  box-shadow: inset 0 0 0 1px #000;
  opacity: 0.5;
}
/* Earned ribbons light up in their difficulty's colour; the rest stay visible
   and dark, because a gap you can see is a goal and a hidden one is not.
   Spelled out per difficulty rather than leaning on the .diff-* tabs above:
   those set a background too, and this rule is the later one. */
.ribbon.on { opacity: 1; }
.ribbon.on.diff-rookie { background: #6d9a4a; }
.ribbon.on.diff-veteran { background: #e0a63c; }
.ribbon.on.diff-elite { background: #d4503a; }

/* --- stars: one per difficulty, lit up to the highest one cleared.
   The row is the same element the ribbons used, so the card's layout is
   unchanged; only what sits in it is different. */
.m-stars { gap: 2px; align-items: center; }
.m-stars .ribbon { display: none; }
.star { width: 12px; height: 12px; line-height: 0; }
.star svg { width: 12px; height: 12px; display: block; }
/* Unearned: a hollow outline, so the gap is a shape rather than an absence. */
.star svg polygon { fill: none; stroke: #4a4a34; stroke-width: 1; }
.star.on svg polygon { stroke: #000; }
.star.on.diff-rookie svg polygon { fill: #6d9a4a; }
.star.on.diff-veteran svg polygon { fill: #e0a63c; }
.star.on.diff-elite svg polygon { fill: #d4503a; }
.m-best {
  margin-top: 5px;
  color: #b0975c;
  font-size: var(--fs-micro);
  letter-spacing: var(--track-tight);
}
.mission.cleared .m-num { color: #8a9a55; }

/* ================================================================ Boot Hill */
#hill {
  position: fixed;
  inset: 0;
  z-index: 30;
  overflow-y: auto;
  background:
    radial-gradient(ellipse 90% 60% at 50% 100%, #1b1f10 0%, transparent 70%),
    linear-gradient(#0d1108, #060803);
}
#hill[hidden] { display: none; }
#hill-inner { max-width: 980px; margin: 0 auto; padding: 40px 24px 56px; }

.hill-head { text-align: center; margin-bottom: 10px; }
.hill-head h2 {
  margin: 0;
  font-size: 40px;
  font-weight: 700;
  letter-spacing: var(--track-wide);
  text-transform: uppercase;
  color: var(--ink);
  text-shadow: 0 3px 0 #000;
}
.hill-sub {
  margin: 8px 0 0;
  color: var(--ink-faint);
  font-size: var(--fs-sm);
  letter-spacing: var(--track);
  text-transform: uppercase;
}

/* The mound. The crosses are absolutely placed from boothill.ts; this only
   provides the ground they stand on. */
.hill-mound {
  position: relative;
  height: 210px;
  margin: 22px 0 30px;
  background:
    radial-gradient(ellipse 70% 150% at 50% 130%, #2a3117 0%, #1a1f0e 55%, transparent 78%);
  border-bottom: 1px solid #11150a;
}
.hill-cross {
  position: absolute;
  width: 9px;
  height: 14px;
  background: #9aa07a;
  clip-path: polygon(
    38% 0, 62% 0, 62% 28%, 100% 28%, 100% 48%, 62% 48%, 62% 100%, 38% 100%, 38% 48%, 0 48%, 0 28%, 38% 28%);
  filter: drop-shadow(1px 2px 0 #000);
}
.hill-cross.vet { background: var(--gold); height: 18px; }
.hill-cross.own { background: #e8e4d0; height: 19px; }
.hill-empty {
  position: absolute;
  inset: auto 0 40% 0;
  text-align: center;
  color: var(--ink-faint);
  font-size: var(--fs-body);
  letter-spacing: var(--track);
  text-transform: uppercase;
}
.hill-more {
  position: absolute;
  right: 6px;
  bottom: 4px;
  color: var(--ink-faint);
  font-size: var(--fs-micro);
  letter-spacing: var(--track-tight);
}

.hill-cols {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 22px;
  align-items: start;
}
.hill-col { min-width: 0; }
.hill-list { display: flex; flex-direction: column; gap: 3px; max-height: 340px; overflow-y: auto; }

/* Inside these lists a plate carries a second line, so it wraps. The right
   chevron would land in the middle of the wrap, so it goes. */
.hill-list .ui-plate { flex-wrap: wrap; align-items: center; }
.hill-where {
  flex-basis: 100%;
  margin: 3px 0 0 13px;
  color: var(--ink-faint);
  font-size: var(--fs-micro);
  letter-spacing: var(--track-tight);
  text-transform: uppercase;
}
.hill-note {
  margin: 10px 0 0;
  color: var(--ink-faint);
  font-size: var(--fs-micro);
  line-height: 1.6;
}

.hill-rename {
  padding: 2px 7px;
  border: 0;
  background: var(--plate-hi);
  color: var(--ink-dim);
  font: inherit;
  font-size: var(--fs-micro);
  letter-spacing: var(--track-tight);
  text-transform: uppercase;
  cursor: pointer;
}
.hill-rename:hover { background: var(--gold); color: var(--plate-lo); }

.hill-editor { display: flex; flex-wrap: wrap; align-items: center; gap: 5px; width: 100%; }
.hill-input {
  flex: 1;
  min-width: 90px;
  padding: 5px 7px;
  border: 0;
  background: var(--well);
  box-shadow: inset 0 0 0 1px var(--plate-hi);
  color: var(--gold);
  font: inherit;
  font-size: var(--fs-body);
  letter-spacing: var(--track);
  text-transform: uppercase;
}
.hill-input:focus { outline: 0; box-shadow: inset 0 0 0 1px var(--gold); }
.hill-editor .ui-btn { width: auto; padding: 5px 9px; }
.hill-err { flex-basis: 100%; color: #e07050; font-size: var(--fs-micro); letter-spacing: var(--track-tight); }

.hill-actions { display: flex; justify-content: center; gap: 9px; margin-top: 32px; }
.hill-actions .ui-btn { width: auto; min-width: 170px; }

@media (max-width: 860px) {
  .hill-cols { grid-template-columns: 1fr; }
  .hill-mound { height: 170px; }
}

/* ================================================================ layer 4 */
/* ============================================================ the layouts */
/*
 * Three arrangements of the same markup, chosen by `layout.ts` and published
 * as `data-layout` on the root element.
 *
 *   wide     >= 900px and landscape. The sidebar, as the original had it.
 *   compact  landscape, narrower. The HUD leaves the flow and floats in the
 *            corners, because a 188px column out of 640px is a third of the
 *            battlefield and a phone has none to spare.
 *   stacked  portrait. Thumbs cannot reach the top of a tall screen, so the
 *            HUD is a reserved bar at the bottom where they already are.
 *
 * The rule the whole section follows: on a small screen the game gets the
 * pixels and the chrome gets out of the way, except where a thumb has to be
 * able to land on something.
 */

/* --------------------------------------------------------------- wide */

/*
 * No layout ever insets the overlay now. The rule that did -- briefing only,
 * sidebar left readable -- guarded a scene that stopped existing: the briefing
 * is shown on a full blackout (main.ts raises it before showBriefing), so the
 * sidebar it was protecting is under black anyway, and all the inset produced
 * was a bar of that black standing beside the scrim, plus a briefing centred
 * on the play area instead of the screen. Every overlay owns the whole screen.
 */

/* ------------------------------------------------------------ compact */

[data-layout="compact"] {
  --fs-micro: 12px;
  --fs-sm: 13px;
  --fs-body: 14px;
  --fs-lg: 17px;
  --fs-xl: 22px;
  --fs-display: clamp(24px, 6vw, 40px);
  --fs-title: clamp(20px, 5vw, 27px);
  --fs-brand: 40px;
  --tap: 48px;
  /* Tracking that reads at 11px is a smear at 14px on a phone held at arm's
     length, so the touch layouts tighten it as the type grows. */
  --track-wide: 0.14em;
  --track: 0.08em;
  --track-tight: 0.04em;
}

[data-layout="compact"] #stage {
  grid-template-columns: minmax(0, 1fr);
}
/*
 * Out of the grid entirely and over the canvas. `pointer-events: none` on the
 * shell with it restored per child is what stops an invisible HUD rectangle
 * eating orders meant for the ground behind it -- the single most common way a
 * floating game HUD goes wrong.
 */
[data-layout="compact"] #hud {
  position: absolute;
  inset: 0;
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  grid-template-rows: auto minmax(0, 1fr);
  align-content: start;
  gap: 6px;
  padding: calc(6px + var(--safe-t)) calc(6px + var(--safe-r)) calc(6px + var(--safe-b)) calc(6px + var(--safe-l));
  background: none;
  border: 0;
  box-shadow: none;
  pointer-events: none;
  z-index: 3;
}
[data-layout="compact"] #hud > * { pointer-events: auto; }

[data-layout="compact"] .hud-body {
  display: contents;
}
/* Anything floating over the battlefield needs its own ground to be read
   against; the sidebar had the column for that and these have nothing. */
[data-layout="compact"] .hud-mission,
[data-layout="compact"] .hud-squad,
[data-layout="compact"] .hud-supply,
[data-layout="compact"] .hud-orders {
  padding: 5px 8px;
  background: rgba(12, 16, 8, 0.82);
  box-shadow: inset 0 0 0 1px var(--plate-lo);
}
[data-layout="compact"] .hud-mission { grid-area: 1 / 1; justify-self: start; }
[data-layout="compact"] .hud-supply { grid-area: 1 / 2; justify-self: end; }
[data-layout="compact"] .hud-orders {
  grid-area: 2 / 1 / auto / span 2;
  align-self: start;
  justify-self: center;
  max-width: min(90%, 460px);
  text-align: center;
}
[data-layout="compact"] .hud-squad { grid-area: 1 / 1; justify-self: start; margin-top: 34px; }

/* The section headings are a sidebar device. Floating over the world they are
   four more words between the player and the battlefield. */
[data-layout="compact"] .ui-heading,
[data-layout="stacked"] .ui-heading { display: none; }
[data-layout="compact"] .hud-hint,
[data-layout="stacked"] .hud-hint { display: none; }
[data-layout="compact"] .hud-par,
[data-layout="stacked"] .hud-par { display: none; }

/* ------------------------------------------------------------ stacked */

[data-layout="stacked"] {
  --fs-micro: 12px;
  --fs-sm: 13px;
  --fs-body: 15px;
  --fs-lg: 18px;
  --fs-xl: 24px;
  --fs-display: clamp(26px, 8vw, 44px);
  --fs-title: clamp(22px, 6.5vw, 27px);
  --fs-brand: 30px;
  --tap: 48px;
  --track-wide: 0.14em;
  --track: 0.08em;
  --track-tight: 0.04em;
}

[data-layout="stacked"] #stage {
  grid-template-columns: minmax(0, 1fr);
  grid-template-rows: minmax(0, 1fr) auto;
}
[data-layout="stacked"] #viewport { grid-row: 1; }
[data-layout="stacked"] #hud {
  grid-row: 2;
  display: flex;
  flex-direction: row;
  align-items: center;
  gap: 10px;
  min-height: var(--bar-h);
  padding: 8px calc(10px + var(--safe-r)) calc(8px + var(--safe-b)) calc(10px + var(--safe-l));
  border-right: 0;
  border-top: 2px solid var(--edge);
  box-shadow: inset 0 1px 0 var(--plate-lo);
}
[data-layout="stacked"] .hud-body {
  display: flex;
  flex-direction: row;
  align-items: center;
  gap: 12px;
  width: 100%;
  min-width: 0;
}
[data-layout="stacked"] .hud-mission { flex: none; max-width: 34%; }
[data-layout="stacked"] .hud-orders { flex: 1; min-width: 0; }
[data-layout="stacked"] .hud-supply { flex: none; }
/* One scrollable row rather than a column: the names stay, because a name
   leaving a list is the whole point of the roster. */
[data-layout="stacked"] .hud-roster {
  flex-direction: row;
  gap: 4px;
  overflow-x: auto;
  scrollbar-width: none;
}
[data-layout="stacked"] .hud-roster::-webkit-scrollbar { display: none; }
[data-layout="stacked"] .hud-roster .ui-plate { flex: none; }

/*
 * Portrait shows about 195 world pixels across, which is narrower than
 * anything the missions were tuned against. It is playable, and the off-screen
 * indicators carry it, but it is worth saying once that turning the phone
 * gives you more ground.
 */
.rotate-nudge {
  position: absolute;
  left: 50%;
  bottom: 12px;
  transform: translateX(-50%);
  padding: 8px 14px;
  background: rgba(12, 16, 8, 0.9);
  box-shadow: inset 0 0 0 1px var(--plate-hi);
  color: var(--ink-dim);
  font-size: var(--fs-sm);
  letter-spacing: var(--track-tight);
  text-transform: uppercase;
  z-index: 6;
}
[data-layout="stacked"] .rotate-nudge { display: block; }
.rotate-nudge { display: none; }

/* ------------------------------------------------- touch, at any layout */
/*
 * Keyed off the input the player is actually using rather than the viewport,
 * because these are statements about fingers, not about width.
 */

/* A keycap reading "Enter" beside a button nobody can press is pure noise. */
[data-input="touch"] .ui-key { display: none; }
/* A crosshair cursor on a device with no cursor. */
[data-input="touch"] #screen { cursor: default; }
[data-input="touch"] .ui-plate { min-height: var(--tap); }

/* Hover states are a lie on a touchscreen: they latch on after a tap and stay
   lit until something else is touched, which reads as a stuck selection. */
@media (hover: none) {
  .ui-btn:hover { background: var(--plate); }
  .ui-btn.tone-good:hover { background: #2f5222; }
  .ui-btn.tone-warn:hover { background: #4a2718; }
  .diff:hover { background: var(--plate); color: var(--ink-dim); }
  .diff.on:hover { background: var(--plate-hi); color: var(--gold); }
  .mission:hover { background: linear-gradient(#232b14, #1a2010); transform: none; }
  .mission:hover .m-num { color: #56703a; }
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    transition-duration: 0.01ms !important;
  }
}

/* ========================================================== the action bar */
/*
 * Anchored inside #viewport, so it lands in the battlefield on a phone and
 * inside the play area beside the sidebar on a desktop.
 *
 * The shell does not receive pointer events; only the buttons do. An invisible
 * full-size rectangle over the canvas swallowing move orders is the single most
 * common way a floating game HUD goes wrong, and it is silent when it happens.
 */
#controls {
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 4;
}
#controls[hidden] { display: none; }

.controls-top {
  position: absolute;
  top: calc(8px + var(--safe-t));
  left: calc(8px + var(--safe-l));
  display: flex;
  gap: 8px;
  pointer-events: auto;
}

/* Bottom-right by default, and mirrored for a left-handed player -- who
   otherwise has the whole bar under the wrong thumb, which is not a cosmetic
   complaint. */
.controls-cluster {
  position: absolute;
  right: calc(10px + var(--safe-r));
  bottom: calc(10px + var(--safe-b));
  display: flex;
  align-items: flex-end;
  gap: 10px;
  pointer-events: auto;
}
#controls[data-hand="left"] .controls-cluster {
  right: auto;
  left: calc(10px + var(--safe-l));
  flex-direction: row-reverse;
}
#controls[data-hand="left"] .controls-top {
  left: auto;
  right: calc(8px + var(--safe-r));
}

.ui-action {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 1px;
  width: 58px;
  height: 58px;
  padding: 0;
  border: 0;
  /*
   * A plate, not a bubble.
   *
   * These were 58px circles with a translucent fill and a soft drop shadow,
   * floating over the battlefield. A critic shown a frame of the game and asked
   * for the single most un-1993 thing in it picked this without hesitation: "a
   * smooth anti-aliased circle with a soft translucent dark fill ... it reads as
   * a modern mobile-game touch control pasted onto the frame." Three separate
   * breaches of the house rules in one widget -- an anti-aliased curve, alpha,
   * and a blurred shadow -- and it is on screen in every single frame.
   *
   * So: hard corners, an opaque fill, a solid offset shadow, and the same 2px
   * inset border the name plates use, which is the idiom the reference actually
   * shows (`elements/buttons-and-troop-chevron-status.jpg`). The tap target is
   * the same size it was; only the drawing changed.
   */
  border-radius: 0;
  background: var(--plate);
  box-shadow: inset 0 0 0 2px var(--plate-hi), 3px 3px 0 var(--edge);
  color: var(--ink);
  font: inherit;
  cursor: pointer;
  touch-action: none;
  transition: background 0.1s, transform 0.06s;
}
/* The one the thumb should find without looking. */
.ui-action.primary { width: 76px; height: 76px; }
/* Pressed: the plate moves onto its own shadow, the way a physical key does. */
.ui-action:active {
  transform: translate(2px, 2px);
  box-shadow: inset 0 0 0 2px var(--plate-hi), 1px 1px 0 var(--edge);
}
.ui-action.on { background: #4c6024; box-shadow: inset 0 0 0 2px var(--gold), 3px 3px 0 var(--edge); }
/* Unavailable is a dimmer plate, not a faded one: `opacity` is a blend, and
   there is no blending anywhere else in this game. */
.ui-action.off { background: var(--plate-lo); color: var(--ink-faint); cursor: default; }
.ui-action.off .ui-action-hint { color: var(--ink-faint); }
.ui-action.tone-good { background: #2f5222; }
.ui-action.tone-warn { background: #4a2718; }
.ui-action:focus-visible { outline: 2px solid var(--gold); outline-offset: 2px; }
.ui-action[hidden] { display: none; }

.ui-action-glyph { font-size: 20px; line-height: 1; }
.ui-action.primary .ui-action-glyph { font-size: 30px; }
.ui-action-label {
  font-size: var(--fs-micro);
  font-weight: 700;
  letter-spacing: var(--track-tight);
  text-transform: uppercase;
}
/* The keyboard shortcut, for the desktop where the bar is an alternative to a
   chord rather than the only way in. */
.ui-action-hint {
  position: absolute;
  top: calc(100% + 3px);
  color: var(--ink-faint);
  font-size: var(--fs-micro);
  letter-spacing: var(--track-tight);
  white-space: nowrap;
}
#controls[data-mode="touch"] .ui-action-hint { display: none; }

.ui-action-badge {
  position: absolute;
  top: -2px;
  right: -2px;
  min-width: 20px;
  padding: 2px 4px;
  border-radius: 10px;
  background: var(--gold);
  color: var(--plate-lo);
  font-size: var(--fs-micro);
  font-weight: 700;
  font-style: normal;
  text-align: center;
}
.ui-action-badge[hidden] { display: none; }

/* The cooldown, drained as a wedge over the button. A dead press is explained
   rather than ignored, which is the difference between a control that feels
   broken and one that feels busy. */
.ui-action-ring {
  position: absolute;
  inset: 0;
  border-radius: 50%;
  opacity: 0;
  pointer-events: none;
}

/* The thumbstick nub. Touch only -- on a mouse the aim already follows the
   cursor and a stick drawn under a pointer is clutter. */
.ui-stick {
  position: fixed;
  width: 26px;
  height: 26px;
  margin: -13px 0 0 -13px;
  border-radius: 50%;
  background: rgba(240, 216, 120, 0.5);
  box-shadow: 0 0 0 2px rgba(240, 216, 120, 0.8);
  pointer-events: none;
  z-index: 7;
}
.ui-stick[hidden] { display: none; }

/* An armed grenade is a mode, so it needs a visible way out. */
.ui-cancel {
  position: absolute;
  left: 50%;
  top: calc(10px + var(--safe-t));
  transform: translateX(-50%);
  display: flex;
  align-items: center;
  min-height: var(--tap);
  padding: 9px 14px;
  background: rgba(74, 39, 24, 0.92);
  box-shadow: inset 0 0 0 1px #74412a;
  color: var(--ink);
  font-size: var(--fs-sm);
  letter-spacing: var(--track-tight);
  text-transform: uppercase;
  pointer-events: auto;
  cursor: pointer;
}
.ui-cancel[hidden] { display: none; }

/* ============================================================== the sheets */

#sheet {
  position: fixed;
  inset: 0;
  z-index: 20;
  display: flex;
  align-items: center;
  justify-content: center;
  padding:
    max(12px, var(--safe-t)) max(12px, var(--safe-r))
    max(12px, var(--safe-b)) max(12px, var(--safe-l));
  background: rgba(6, 9, 3, 0.82);
}
#sheet[hidden] { display: none; }

.sheet-card {
  width: min(420px, 100%);
  /* Scrolls inside the viewport rather than growing past it: the difference
     between a settings list that works on a 390x844 phone and one whose last
     two rows cannot be reached. */
  max-height: 88vh;
  max-height: 88dvh;
  overflow-y: auto;
  padding: 22px 20px 18px;
  background: linear-gradient(#242c14, #171d0d);
  border-top: 2px solid var(--plate-hi);
  border-left: 2px solid var(--plate-hi);
  border-right: 2px solid var(--edge);
  border-bottom: 2px solid var(--edge);
  box-shadow: 0 18px 40px rgba(0, 0, 0, 0.6);
}

.sheet-title {
  color: var(--gold);
  font-size: var(--fs-title);
  font-weight: 700;
  letter-spacing: var(--track);
  text-transform: uppercase;
  text-align: center;
}
.sheet-sub {
  margin-top: 6px;
  color: var(--ink-dim);
  font-size: var(--fs-sm);
  letter-spacing: var(--track-tight);
  text-align: center;
  text-transform: uppercase;
}
/* Part of the card, not a bar bolted to it. The sticky treatment (own
   background, upward shadow) existed for a nine-row list that scrolled; the
   trimmed sheet never does, and over the plotted frame the band read as a
   broken drop shadow floating under DONE. */
.sheet-actions {
  display: flex;
  flex-direction: column;
  gap: 7px;
  margin-top: 18px;
}
.sheet-note {
  margin: 14px 0 0;
  color: var(--ink-faint);
  font-size: var(--fs-sm);
  line-height: 1.5;
}

.sheet-rows { margin-top: 14px; }
/* A row is two columns when both fit and two lines when they do not.
   It used to be one flex line with `min-width: 0` on the label, which lets the
   label shrink to nothing while the control keeps its natural width -- so the
   five-segment Zoom control squeezed its own description into a 40px column
   setting one word per line, and then overlapped it. Measured at 217px tall
   against 61px for every other row. `wrap` plus a floor on the label is the
   whole fix: when the control will not fit beside it, it drops below it. */
.sheet-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px 12px;
  padding: 8px 0;
  border-bottom: 1px solid var(--plate-lo);
}
.sheet-row:last-child { border-bottom: 0; }
.sheet-row-text { flex: 1 1 190px; min-width: 190px; }
/* When the control has dropped to its own line it takes the whole of it, so a
   wrapped row reads as two deliberate rows rather than as a control that fell
   off the end of the first one. */
.sheet-row > .ui-segmented { flex: 0 1 auto; margin-left: auto; }
.sheet-row-text + .ui-segmented:not(:only-child) { min-width: 0; }
.sheet-row-text b {
  display: block;
  font-size: var(--fs-body);
  letter-spacing: var(--track-tight);
  text-transform: uppercase;
}
.sheet-row-text span {
  display: block;
  margin-top: 3px;
  color: var(--ink-faint);
  font-size: var(--fs-sm);
  line-height: 1.4;
}
/* On a phone the label and its control stop fitting side by side long before
   the text itself becomes the problem, so the row turns into two. */
@media (max-width: 460px) {
  /* `flex: 1 1 190px` grows along the *main* axis, and turning the row into a
     column turns that axis vertical -- so the label stretched to fill the row
     and every setting became 263px tall with a canyon of empty green between
     the words and the control. The basis has to be reset with the direction. */
  .sheet-row { flex-direction: column; align-items: stretch; gap: 8px; }
  .sheet-row-text { flex: 0 0 auto; min-width: 0; }
  /* Five segments do not fit 390px, and the fifth was simply off the edge.
     Scrolling is already what this control does when it overflows; it just
     needs to be allowed to wrap instead when there is nowhere to scroll to. */
  .ui-segmented { flex-wrap: wrap; }
}

/* --- segmented control */
.ui-segmented {
  display: flex;
  flex: none;
  gap: 3px;
  max-width: 100%;
  overflow-x: auto;
  scrollbar-width: none;
  scroll-snap-type: x mandatory;
}
.ui-segmented::-webkit-scrollbar { display: none; }
.ui-seg {
  flex: none;
  min-height: var(--tap);
  padding: 8px 14px;
  background: var(--plate);
  border-top: 1px solid var(--plate-hi);
  border-left: 1px solid var(--plate-hi);
  border-right: 1px solid var(--plate-lo);
  border-bottom: 2px solid var(--plate-lo);
  color: var(--ink-dim);
  font: inherit;
  font-size: var(--fs-body);
  font-weight: 700;
  letter-spacing: var(--track-tight);
  text-transform: uppercase;
  cursor: pointer;
  scroll-snap-align: start;
}
.ui-seg.on { background: var(--plate-hi); color: var(--gold); box-shadow: inset 0 2px 0 var(--gold); }
.ui-seg:focus-visible { outline: 2px solid var(--gold); outline-offset: -2px; }

/* --- level bar: a track, a fill, and a square bevelled handle. Hard edges
   everywhere -- it is a piece of the plate, not a browser range input. */
.ui-slider {
  flex: 1 1 180px;
  min-width: 150px;
  padding: 8px 0;
  cursor: pointer;
  /* Dragging the handle is the gesture; the page must not scroll under it. */
  touch-action: none;
}
.ui-slider-track {
  position: relative;
  height: 10px;
  background: #141a0b;
  border-top: 1px solid var(--plate-lo);
  border-left: 1px solid var(--plate-lo);
  border-bottom: 1px solid var(--plate-hi);
  border-right: 1px solid var(--plate-hi);
}
.ui-slider-fill {
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  background: #7a6028;
}
.ui-slider-handle {
  position: absolute;
  top: 50%;
  width: 16px;
  height: 24px;
  margin: -12px 0 0 -8px;
  background: #a8843c;
  border-top: 2px solid #d9c27a;
  border-left: 2px solid #d9c27a;
  border-right: 2px solid #4a3a16;
  border-bottom: 2px solid #4a3a16;
}

.ui-chip {
  padding: 2px 7px;
  background: rgba(0, 0, 0, 0.28);
  box-shadow: inset 0 0 0 1px #3b4d26;
  color: #9db97a;
  font-size: var(--fs-micro);
  font-style: normal;
  letter-spacing: var(--track-tight);
  text-transform: uppercase;
}

/* ------------------------------------------- the screens, made responsive */

#menu-inner { padding: clamp(20px, 5vw, 48px) clamp(14px, 4vw, 24px) clamp(28px, 7vw, 64px); }
#menu h1 { font-size: var(--fs-brand); }
/* `min(100%, 290px)` rather than a bare 290px: a fixed track minimum overflows
   the moment the viewport is narrower than one card plus its padding, and a
   grid overflowing is the one thing a phone cannot scroll its way out of. */
.theatre-grid { grid-template-columns: repeat(auto-fill, minmax(min(100%, 460px), 1fr)); }
.mission { min-height: var(--tap); }
#menu-difficulty { flex-wrap: nowrap; overflow-x: auto; scrollbar-width: none; padding-bottom: 2px; }
#menu-difficulty::-webkit-scrollbar { display: none; }
.diff { flex: none; min-height: var(--tap); }

.briefing { padding: 0 clamp(16px, 5vw, 30px); }
.result {
  max-height: 88vh;
  max-height: 88dvh;
  overflow-y: auto;
  padding: clamp(20px, 4vw, 34px);
}
.result-actions .ui-btn { justify-content: center; }
.hill-head h2 { font-size: clamp(30px, 6vw, 40px); }
#hill-inner { padding: clamp(20px, 5vw, 40px) clamp(14px, 4vw, 24px) 56px; }
.hill-actions { flex-wrap: wrap; }
.hill-actions .ui-btn { min-width: min(170px, 100%); }
#menu-actions .ui-btn { min-width: min(200px, 100%); }

/*
 * The card metadata used to be hidden below 720px. The shape of a map and the
 * doctrine of its garrison are *why you pick one mission over another*, so
 * hiding them was a layout dodge rather than an editorial decision. Below the
 * width where a right-hand column fits, it becomes a row under the title.
 */
@media (max-width: 560px) {
  .mission { flex-wrap: wrap; }
  .m-meta {
    flex-direction: row;
    align-items: center;
    gap: 8px;
    width: 100%;
    margin-top: 4px;
    padding-left: 44px;
  }
}

/* ------------------------------------------------- the touch type floor */
/*
 * Keyed off the input rather than the layout, and separate from the layout
 * blocks above on purpose.
 *
 * A tablet in landscape at 1024px is `wide` -- it has room for the sidebar and
 * should have it -- but it is still a device operated with a thumb, and the
 * audit caught the whole mission-select running at 10 and 11px there. The floor
 * belongs to the finger, not to the width.
 */
[data-input="touch"] {
  --fs-micro: 12px;
  --fs-sm: 13px;
  --track-wide: 0.14em;
  --track: 0.08em;
}

/*
 * `Boots & Bullets` is held on one line by non-breaking spaces -- a game title
 * that wraps reads as a sentence -- so it is 15 unbreakable cells, and monospace
 * type with 0.18em of tracking needs 11.7x its own size in width to set them.
 * The old title was 13 cells at 10.1x, and an earlier 11vw put it at 401px in a
 * 332px hole on a 360px phone: a document-level overflow, the one thing this
 * game cannot scroll its way out of, since the body does not scroll at all.
 * Two more cells buy that margin back down, hence 7vw rather than 7.5 -- 25px
 * and 294px on the same phone, and still inside the hole on a 320px one.
 */
/*
 * The brand, on the grid like everything else.
 *
 * These were clamps -- 22px at one width, 27 at another -- which is exactly
 * what a bitmap face cannot do: every size between the multiples of ten puts
 * a glyph edge between two pixels. Three fixed steps instead, and the smallest
 * is 20 because fifteen unbreakable cells of it come to 233px, which fits a
 * 390px phone beside the music button and a 320px one without it.
 */
[data-layout="stacked"] { --fs-brand: 20px; }
[data-layout="compact"] { --fs-brand: 30px; }
#menu h1 { letter-spacing: var(--track-wide); }
/* Clear of the music toggle, which is fixed to the same corner. */
[data-layout="stacked"] #menu-inner { padding-top: 72px; }

/* --- the dev panel
 *
 * Bottom right, deliberately ugly: it is scaffolding, and it should never be
 * mistaken for part of the game. Only ever in the page under `npm run dev` --
 * a production build does not contain the code that creates it.
 */
.debug-panel {
  position: fixed;
  right: 6px;
  bottom: 6px;
  z-index: 90;
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-end;
  gap: 3px;
  max-width: 190px;
  padding: 4px;
  background: rgba(10, 13, 5, 0.85);
  border: 1px solid #4a5a2a;
}
.debug-title {
  width: 100%;
  color: #8fae5e;
  font-size: 10px;
  letter-spacing: 2px;
  text-align: right;
}
.debug-btn {
  padding: 2px 5px;
  background: #2a3418;
  border: 1px solid #4a5a2a;
  color: #c9d4b8;
  font: inherit;
  font-size: 10px;
  cursor: pointer;
}
.debug-btn:hover { background: #3a4a20; }
.debug-btn.on { background: #6d9a4a; color: #10140a; }

/* --- locked missions: visible, legible, and plainly not yet yours.
   Dimmed rather than hidden, because a goal you cannot see is not a goal --
   the same argument the unlit stars on a card make. The padlock is drawn, not
   typed: a glyph from whatever font the machine supplies is a different shape
   everywhere and an anti-aliased one on all of them. */
.mission.locked {
  opacity: 0.42;
  cursor: default;
  filter: saturate(0.35);
}
.mission.locked:hover { opacity: 0.42; }
.mission.locked .m-num::after {
  content: '';
  display: inline-block;
  width: 7px;
  height: 8px;
  margin-left: 5px;
  vertical-align: -1px;
  background:
    linear-gradient(#0000 0 0) padding-box,
    var(--plate-hi);
  clip-path: polygon(
    0 3px, 2px 3px, 2px 1px, 5px 1px, 5px 3px, 7px 3px,
    7px 8px, 0 8px);
  box-shadow: none;
}
.theatre-tally {
  margin-left: auto;
  color: #b0975c;
  font-size: var(--fs-micro);
  letter-spacing: var(--track-tight);
}


/* ===========================================================================
 * The front end -- intro and level select, one screen
 *
 * Nothing in here draws its own bevel. Every plate, button, frame, banner and
 * star is the plotted sprite arriving as a sliced border-image from ui/skin.ts,
 * which is the whole reason those sprites exist: a control has to look the same
 * on canvas and in the DOM, and it only does that if there is one drawing.
 *
 * border-image rather than background, because none of these are one size --
 * a button is as wide as its label and a frame as big as the screen. Slicing
 * stretches only the middles, so a corner lands untouched at any size.
 * ======================================================================== */

#front {
  position: fixed;
  inset: 0;
  z-index: 14;
  overflow-y: auto;
  /* The ground the front end sits on. When the attract world lands it goes
     behind this and only the vignette stays. */
  background:
    radial-gradient(ellipse at 50% 38%, #1e2a12 0%, #0d1207 62%, #070a04 100%);
  opacity: 0;
  transition: opacity 0.32s ease;
}
#front.in { opacity: 1; }
#front.leaving { opacity: 0; }
#front[hidden] { display: none; }

#front-inner {
  min-height: 100%;
  max-width: 1100px;
  margin: 0 auto;
  padding: 40px 24px 56px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 28px;
}
/* The intro is a title screen: logo and buttons sit centred in the viewport
   rather than hung from the top. Only there -- the level select is taller
   than the screen, and centring a scroller reads as broken. */
#front:not(.on-select) #front-inner {
  justify-content: center;
  padding-top: 24px;
}

/* --- the logo, which never unmounts
 *
 * The brief asked for it to shrink and move up when the level select opens, and
 * that is exactly what makes the two panes read as one screen: the thing you
 * were looking at is still there, smaller, rather than a screen having been
 * replaced by another screen. Whole-number scales only -- 0.5 halves every
 * plotted pixel cleanly, where 0.62 would put a glyph edge between two of them.
 */
#front-logo {
  width: calc(var(--sk-logo-w) * 2px);
  height: calc(var(--sk-logo-h) * 2px);
  margin: 0;
  background-image: var(--sk-logo);
  background-size: 100% 100%;
  background-repeat: no-repeat;
  image-rendering: pixelated;
  flex: none;
  transition: width 0.34s ease, height 0.34s ease;
}
/* On the select pane the logo is the screen's title: half again its plotted
   size, and hanging over the frame's top edge -- the negative margin swallows
   the column gap and sinks its lower half into the card, and the frame pads
   its own top so the rail starts below the overhang. */
#front.on-select #front-logo {
  width: calc(var(--sk-logo-w) * 1.5px);
  height: calc(var(--sk-logo-h) * 1.5px);
  margin-bottom: calc(var(--sk-logo-h) * -0.75px - 28px);
  position: relative;
  z-index: 1;
  /* It is the way back to the intro now, and the hand should say so. */
  cursor: pointer;
}
#front.on-select #select-frame {
  padding-top: calc(var(--sk-logo-h) * 0.75px + 12px);
}

/* --- the small-screen gate ----------------------------------------------
 * Below this width the game is not offered: the logo, a line of marching
 * orders, and nothing to click. Driven entirely by the width query so
 * resizing a window walks in and out of it with no script involved. */
#front-mobile { display: none; }
@media (max-width: 720px) {
  #intro, #select { display: none !important; }
  #front-mobile {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 16px;
    padding: 0 12px;
    text-align: center;
  }
  /* Whatever pane state the front was in, the logo stands back up, centred,
     still bobbing -- capped to the screen, because a 2x wordmark is wider
     than the phones this gate exists for. */
  #front #front-logo,
  #front.on-select #front-logo {
    width: min(calc(var(--sk-logo-w) * 2px), 90vw);
    height: auto;
    aspect-ratio: var(--sk-logo-w) / var(--sk-logo-h);
    margin-bottom: 0;
    animation: fx-logo-idle 8s step-end infinite;
  }
  #front #front-inner,
  #front.on-select #front-inner { justify-content: center; }
  .fx-mobile-title {
    margin: 0;
    color: var(--gold);
    font-size: 30px;
    font-weight: 700;
    line-height: 1.25;
    letter-spacing: var(--track-wide);
    text-transform: uppercase;
    max-width: 420px;
  }
  .fx-mobile-sub {
    margin: 0;
    color: var(--ink);
    font-size: 17px;
    line-height: 1.6;
    letter-spacing: var(--track-tight);
  }
}
/* Restated after the gate: its logo rule re-arms the animation, and this must
   still win for anyone who asked their machine for less motion. */
@media (prefers-reduced-motion: reduce) {
  #front #front-logo,
  #front.on-select #front-logo { animation: none; }
}

/* The idle. A sprite-style bob -- whole pixels, held pose to pose with
   `step-end` the way the game animates everything else, never a smooth
   sub-pixel glide over a pixelated image. Three slow breaths, then one
   small hop with a bounce on landing, and round again. */
@keyframes fx-logo-idle {
  0%   { transform: translateY(0); }
  6%   { transform: translateY(-2px); }
  12%  { transform: translateY(-4px); }
  18%  { transform: translateY(-2px); }
  24%  { transform: translateY(0); }
  30%  { transform: translateY(-2px); }
  36%  { transform: translateY(-4px); }
  42%  { transform: translateY(-2px); }
  48%  { transform: translateY(0); }
  54%  { transform: translateY(-2px); }
  60%  { transform: translateY(-4px); }
  66%  { transform: translateY(-2px); }
  72%  { transform: translateY(0); }
  78%  { transform: translateY(-8px); }
  82%  { transform: translateY(-3px); }
  86%, 100% { transform: translateY(0); }
}
#front-logo { animation: fx-logo-idle 8s step-end infinite; }
/* Small and risen on the select pane, the logo is furniture: it holds still. */
#front.on-select #front-logo { animation: none; }
@media (prefers-reduced-motion: reduce) {
  #front-logo { animation: none; }
}
.fx-sr {
  position: absolute;
  width: 1px; height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

.fx-pane { width: 100%; display: flex; flex-direction: column; align-items: center; }
.fx-pane[hidden] { display: none; }

/* --- buttons: the plotted plate, at any width */
.fx-btn {
  min-width: 280px;
  padding: 14px 26px;
  background: none;
  border: 10px solid transparent;
  border-image: var(--sk-btn) var(--sk-slice-btn) fill / 10px round;
  color: #f6efd8;
  font: var(--fs-lg)/1 var(--face);
  letter-spacing: var(--track);
  text-transform: uppercase;
  cursor: pointer;
  image-rendering: pixelated;
}
.fx-btn:hover, .fx-btn:focus-visible {
  border-image-source: var(--sk-btn-hot);
  color: #fffbe8;
  outline: none;
}
.fx-btn:active { border-image-source: var(--sk-btn-down); }
.fx-btn:disabled { border-image-source: var(--sk-btn-off); color: #6d7254; cursor: default; }

#intro-actions { display: flex; flex-direction: column; gap: 14px; align-items: center; }

/* --- the level select frame */
#select-frame {
  width: 100%;
  border: 18px solid transparent;
  border-image: var(--sk-frame) var(--sk-slice-frame) fill / 18px round;
  image-rendering: pixelated;
  padding: 6px;
}
/* Back on the left, the banner dead-centre, a ghost column keeping it there. */
#select-head {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  gap: 12px;
  margin: -6px 0 14px;
}
#select-head .fx-back { justify-self: start; min-width: 0; padding: 10px 22px; }
.fx-head-ghost { justify-self: end; }
.fx-banner {
  padding: 10px 40px;
  border: 12px solid transparent;
  border-image: var(--sk-banner) var(--sk-slice-banner) fill / 12px round;
  width: fit-content;
  image-rendering: pixelated;
}
.fx-banner-text {
  color: #f6efd8;
  font-size: var(--fs-lg);
  letter-spacing: var(--track-wide);
}

#select-body {
  display: grid;
  grid-template-columns: 340px minmax(0, 1fr);
  gap: 16px;
  align-items: start;
}

/* --- the rail */
#select-rail { display: flex; flex-direction: column; gap: 8px; }
.fx-group {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  align-items: center;
  gap: 12px;
  min-width: 0;
  width: 100%;
  text-align: left;
  padding: 10px 14px;
}
/* Placeholder portraits, tinted per theatre. The real six are outstanding and
   the brief says a placeholder is acceptable until they land. */
.fx-group-art { width: 36px; height: 36px; flex: none; box-shadow: inset 0 0 0 2px #0a0d05; }
.fx-group-art.t-jungle { background: linear-gradient(#3f5216, #22300c); }
.fx-group-art.t-desert { background: linear-gradient(#9d8a38, #5e5322); }
.fx-group-art.t-arctic { background: linear-gradient(#a9c9d0, #5c7079); }
.fx-group-art.t-test { background: linear-gradient(#4a4c46, #26271f); }
.fx-group-body { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
.fx-group-name { font-size: var(--fs-body); letter-spacing: var(--track-tight); line-height: 1.15; }
/* nowrap on both halves: "15 MISSIONS" breaking after the number reads as a
   different number, which is worse than the line being tight. */
.fx-group-meta { display: flex; justify-content: space-between; gap: 10px; white-space: nowrap; }
.fx-group-count { color: #9db97a; font-size: var(--fs-body); }
.fx-group-done { color: var(--gold); font-size: var(--fs-body); }
.fx-group.on { border-image-source: var(--sk-btn-hot); }
/* The dim meta tone is set for the resting plate; on the lit one it vanishes. */
.fx-group.on .fx-group-count { color: #2a2408; }
.fx-group.on .fx-group-done { color: #1c1804; }
.fx-group.on .fx-group-name { color: #fffbe8; }

/* --- the mission cards */
#select-list { display: flex; flex-direction: column; gap: 8px; }
.fx-card {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: center;
  gap: 18px;
  width: 100%;
  padding: 12px 18px;
  text-align: left;
  background: none;
  border: 10px solid transparent;
  border-image: var(--sk-plate) var(--sk-slice-plate) fill / 10px round;
  color: #f6efd8;
  font: var(--fs-body)/1.3 var(--face);
  cursor: pointer;
  image-rendering: pixelated;
}
.fx-card:hover, .fx-card:focus-visible { border-image-source: var(--sk-plate-hot); outline: none; }
.fx-card.locked { border-image-source: var(--sk-plate-off); color: #6d7254; cursor: default; }
/* A big gold numeral against the name, which is the reference's arrangement. */
.fx-card-num { color: var(--gold); font-size: var(--fs-title); line-height: 1; min-width: 66px; }
.fx-card.locked .fx-card-num { color: #4e5230; }
.fx-card-body { display: flex; flex-direction: column; gap: 6px; min-width: 0; }
.fx-card-name { font-size: var(--fs-lg); letter-spacing: var(--track-tight); }
/* Not italic. The brief is explicit and a bitmap face has no italic anyway. */
.fx-card-desc { color: #a3b47c; font-size: var(--fs-body); }
.fx-card.locked .fx-card-desc { color: #5c6142; }
.fx-card-tail { display: flex; align-items: center; gap: 6px; }

.fx-stars { display: flex; gap: 4px; }
.fx-star {
  width: 34px; height: 34px;
  background-image: var(--sk-star-off);
  background-size: 100% 100%;
  image-rendering: pixelated;
}
.fx-star.on { background-image: var(--sk-star-on); }
.fx-lock {
  width: 30px; height: 34px;
  background-image: var(--sk-lock);
  background-size: 100% 100%;
  background-repeat: no-repeat;
  image-rendering: pixelated;
}

/* --- the foot: difficulty, and the way back */
#select-foot {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  align-items: center;
  justify-content: space-between;
  margin-top: 16px;
}
.fx-diffs { display: flex; gap: 8px; flex-wrap: wrap; }
.fx-diff, .fx-back { min-width: 0; padding: 10px 20px; }
.fx-diff.on { border-image-source: var(--sk-btn-hot); color: #fffbe8; }

/* The rail stacks under the list on anything narrow; a 280px column and a
   mission card cannot both have room on a phone. */
@media (max-width: 780px) {
  #select-body { grid-template-columns: minmax(0, 1fr); }
  #select-rail { flex-direction: row; overflow-x: auto; }
  .fx-group { width: auto; min-width: 220px; }
  .fx-btn { min-width: 0; width: 100%; }
}


/* --- the sidebar tools: leave, restart, settings ------------------------- */
.hud-tools {
  display: flex;
  gap: 6px;
  padding: 8px 7px 2px;
  border-top: 1px solid var(--plate-lo);
}
.hud-tool {
  width: 44px;
  height: 40px;
  background: none;
  border: 10px solid transparent;
  border-image: var(--sk-btn) var(--sk-slice-btn) fill / 10px round;
  image-rendering: pixelated;
  cursor: pointer;
  padding: 0;
}
/*
 * The glyph rides a pseudo-element, not the background. border-image with
 * `fill` paints its middle slice over the element background, so an icon set
 * there is drawn and then covered -- three plates, no pictures, which is how
 * this shipped for about four minutes.
 */
.hud-tool::after {
  content: '';
  display: block;
  width: 30px;
  height: 30px;
  margin: -3px auto 0;
  background: center / 100% 100% no-repeat;
  image-rendering: pixelated;
}
.hud-tool:hover, .hud-tool:focus-visible { border-image-source: var(--sk-btn-hot); outline: none; }
.hud-tool:active { border-image-source: var(--sk-btn-down); }
.hud-tool.t-exit::after { background-image: var(--sk-ic-door); }
.hud-tool.t-restart::after { background-image: var(--sk-ic-restart); }
.hud-tool.t-gear::after { background-image: var(--sk-ic-gear); }

/* The standing orders, under the mission name. Always on screen. */
.hud-goal {
  margin-top: 4px;
  color: #a3b47c;
  font-size: var(--fs-body);
  letter-spacing: var(--track-tight);
  text-transform: uppercase;
  line-height: 1.4;
}

/* --- the confirmation ----------------------------------------------------
 *
 * Above the pause sheet (20) and everything of the game's, below the dev
 * panel (90): a question outranks whatever raised it.
 */
.confirm-layer {
  position: fixed;
  inset: 0;
  z-index: 40;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 16px;
  background: rgba(8, 11, 5, 0.78);
}
.confirm-card {
  width: min(560px, 92vw);
  padding: 30px 30px 26px;
  border: 18px solid transparent;
  border-image: var(--sk-frame) var(--sk-slice-frame) fill / 18px round;
  image-rendering: pixelated;
  text-align: center;
}
.confirm-title {
  color: var(--gold);
  font-size: var(--fs-lg);
  letter-spacing: var(--track);
  text-transform: uppercase;
}
.confirm-body {
  margin-top: 14px;
  color: var(--ink);
  font-size: var(--fs-body);
  line-height: 1.5;
}
.confirm-actions {
  display: flex;
  gap: 12px;
  justify-content: center;
  flex-wrap: wrap;
  margin-top: 22px;
}
.confirm-btn { min-width: 170px; }


/* --- the sidebar tools fill their row, and stay square ------------------- */
.hud-tools { gap: 8px; padding: 8px 7px 4px; }
.hud-tool {
  flex: 1 1 0;
  width: auto;
  height: auto;
  aspect-ratio: 1;
  display: grid;
  place-items: center;
  padding: 0;
}
.hud-tool::after { margin: 0; }

/* --- the sheet, in the plotted chrome ------------------------------------
 *
 * The settings looked like what they were: a 420px panel of CSS borders from
 * before the chrome existed, holding twenty-pixel type it was never sized for
 * -- the five-segment zoom row ran clean off its right edge. Wider, dressed in
 * the frame, and each row stacks its control under its label instead of
 * fighting it for one line.
 */
.sheet-card {
  width: min(620px, 100%);
  background: none;
  border: 18px solid transparent;
  border-image: var(--sk-frame) var(--sk-slice-frame) fill / 18px round;
  image-rendering: pixelated;
  padding: 24px 26px 20px;
  box-shadow: none;
}
.sheet-title {
  color: var(--gold);
  font-size: var(--fs-lg);
  letter-spacing: var(--track-wide);
  text-transform: uppercase;
  text-align: center;
}
.sheet-row { display: flex; flex-direction: column; align-items: stretch; gap: 10px; padding: 14px 0; }
.sheet-row > .ui-segmented { margin-left: 0; }
.ui-segmented { flex-wrap: wrap; gap: 6px; overflow: visible; }
.ui-seg {
  background: none;
  border: 10px solid transparent;
  border-image: var(--sk-btn) var(--sk-slice-btn) fill / 10px round;
  image-rendering: pixelated;
  color: #d9d2ba;
  min-height: 0;
  padding: 6px 14px;
}
.ui-seg.on {
  border-image-source: var(--sk-btn-hot);
  color: #fffbe8;
  box-shadow: none;
}
.sheet-actions .ui-btn {
  background: none;
  border: 10px solid transparent;
  border-image: var(--sk-btn) var(--sk-slice-btn) fill / 10px round;
  image-rendering: pixelated;
}
.sheet-actions .ui-btn:hover, .sheet-actions .ui-btn:focus-visible {
  border-image-source: var(--sk-btn-hot);
}
/* No key caps in the sheets: the owner asked for the shortcut text gone, and
   a cap on DONE was the last of it. The keys themselves still work. */
.sheet-card .ui-key { display: none; }

/* Casualties of turning the row into a column: the label kept its 190px
   flex-basis and the level bar its 180px one, which in a column are pixels of
   *height*. Both corrected against the axis they actually live on now. */
.sheet-row-text { flex: 0 0 auto; min-width: 0; }
.sheet-row > .ui-slider { flex: 0 0 auto; min-width: 0; }
.sheet-rows { padding-bottom: 8px; }
.sheet-actions { padding: 4px 4px 0; }


/* --- the mission dialog's furniture -------------------------------------- */
.mi { display: flex; flex-direction: column; gap: 16px; align-items: center; }
.mi-desc { margin: 0; color: var(--ink); font-size: var(--fs-body); line-height: 1.5; }
.mi-meta { display: flex; flex-wrap: wrap; gap: 8px; justify-content: center; }
.mi-chip {
  padding: 4px 10px;
  background: rgba(0, 0, 0, 0.28);
  box-shadow: inset 0 0 0 1px #3b4d26;
  color: #9db97a;
  font-size: var(--fs-body);
  letter-spacing: var(--track-tight);
}
.mi-chip.warn { color: #e0a050; box-shadow: inset 0 0 0 1px #6b4a20; }
.mi-earned { display: flex; align-items: center; gap: 14px; }
.mi-best { color: var(--gold); font-size: var(--fs-body); letter-spacing: var(--track-tight); }

/* One line of difficulties, stars on a line of their own, no fourth button. */
/* 740, measured: the three difficulty buttons come to 629px with their gaps,
   and 680 left 584 inside the frame -- one line was the whole point. */
.confirm-card { width: min(740px, 92vw); }
.confirm-btn { min-width: 130px; }
.mi-earned { flex-direction: column; gap: 8px; }
