/* ==========================================================================
   Konquest — "Deep-Space Plotting Board"
   A tactical star-chart aesthetic: engraved cartouche titles, brass
   instrument accents, illustrated worlds, a living starfield.
   Single deliberate dark theme (chart under lamplight), not adaptive.
   ========================================================================== */

:root {
  /* ground */
  --void: #0a1020;
  --panel: #131b2e;
  --panel-raised: #182242;
  --line: #2a3552;
  --grid-line: rgba(42, 53, 82, 0.4);

  /* ink */
  --parchment: #e9e4d6;
  --muted: #7c869c;
  --muted-dim: #545e78;

  /* the one accent */
  --brass: #c99a52;
  --brass-bright: #e0b876;
  --brass-dim: #8a6c3d;

  /* semantic, kept apart from the accent */
  --good: #7fae6f;
  --danger: #c15a3a;
  --danger-bright: #de7250;

  --radius: 0.125rem;
  --serif: 'Fraunces', 'Iowan Old Style', Georgia, serif;
  --sans: 'Public Sans', system-ui, -apple-system, sans-serif;
  --mono: 'IBM Plex Mono', 'SFMono-Regular', Consolas, monospace;
}

* { box-sizing: border-box; }

html {
  height: 100%;
}

body {
  margin: 0;
  min-height: 100%;
  color: var(--parchment);
  font-family: var(--sans);
  background:
    radial-gradient(ellipse 56rem 34rem at 12% 8%, rgba(95, 168, 176, 0.11), transparent 60%),
    radial-gradient(ellipse 50rem 40rem at 88% 78%, rgba(163, 110, 182, 0.09), transparent 62%),
    radial-gradient(ellipse 40rem 26rem at 65% 12%, rgba(201, 154, 82, 0.07), transparent 58%),
    var(--void);
}

button, input, select { font-family: inherit; }

::selection { background: var(--brass-dim); color: var(--parchment); }

:focus-visible {
  outline: 0.125rem solid var(--brass);
  outline-offset: 0.125rem;
}

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

/* ---------- living starfield (page-level, sits behind everything) ---------- */

#starfield {
  position: fixed;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  overflow: hidden;
}

.star {
  position: absolute;
  border-radius: 50%;
  background: var(--parchment);
  animation: twinkle ease-in-out infinite;
  will-change: opacity, transform;
}

@keyframes twinkle {
  0%, 100% { opacity: var(--star-min, 0.15); transform: scale(0.8); }
  50% { opacity: var(--star-max, 0.9); transform: scale(1.2); }
}

@media (prefers-reduced-motion: reduce) {
  .star { animation: none; opacity: 0.45; }
}

/* ---------- screens ---------- */

.screen[hidden] { display: none !important; }

.screen {
  min-height: 100vh;
  min-height: 100dvh;
  display: flex;
  align-items: center;
  justify-content: center;
  position: relative;
  z-index: 1;
  padding: 2rem 0;
}

.cartouche {
  position: relative;
  padding: 0.5rem 1.5rem;
  width: 100%;
  display: flex;
  justify-content: center;
}

.cartouche-frame {
  background: var(--panel);
  border: 1px solid var(--line);
  box-shadow: 0 0 0 5px var(--void), 0 0 0 6px var(--line), 0 20px 60px #00000080;
  padding: 1.85rem 2.75rem;
  width: min(90vw, 27.5rem);
  text-align: center;
}

.cartouche-eyebrow {
  margin: 0 0 0.6rem;
  font-family: var(--serif);
  font-style: italic;
  font-size: 0.92rem;
  color: var(--brass-bright);
}

.cartouche-frame h1 {
  margin: 0 0 0.5rem;
  font-family: var(--serif);
  font-weight: 600;
  font-optical-sizing: auto;
  font-size: 2.6rem;
  letter-spacing: 0.02em;
  color: var(--parchment);
  text-wrap: balance;
}

.tagline {
  color: var(--muted);
  margin: 0 0 1.25rem;
  font-size: 0.92rem;
  font-style: italic;
  font-family: var(--serif);
}

/* ---------- form (requisition-line styling) ---------- */

.cartouche-form { text-align: left; }

.cartouche-form label {
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
  margin-bottom: 0.8rem;
  font-size: 0.72rem;
}

.cartouche-form label > span:first-child {
  color: var(--muted);
  font-weight: 600;
  font-size: 0.78rem;
}

.field-row {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 1rem;
}

.field-hint {
  color: var(--muted);
  font-size: 0.68rem;
  font-weight: 400;
}

.cartouche-form input[type="text"],
.cartouche-form input[type="number"],
.cartouche-form select {
  appearance: none;
  background: transparent;
  border: none;
  border-bottom: 1px solid var(--line);
  color: var(--parchment);
  padding: 0.4rem 0.1rem;
  font-size: 0.95rem;
  font-family: var(--sans);
  border-radius: 0;
  width: 100%;
}

.cartouche-form select {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6'%3E%3Cpath d='M0,0 L5,6 L10,0' fill='none' stroke='%237c869c' stroke-width='1.4'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 0.2rem center;
  padding-right: 1.1rem;
}

.cartouche-form input[type="text"]:focus,
.cartouche-form input[type="number"]:focus,
.cartouche-form select:focus {
  outline: none;
  border-bottom-color: var(--brass);
}

/* The two "I already have a code" disclosures share styling; keep them
   selected as a group so a third never silently misses out. */
#join-details,
#resume-details {
  margin-top: 0.35rem;
  border-top: 1px solid var(--line);
  padding-top: 0.75rem;
}
#join-details summary,
#resume-details summary {
  cursor: pointer;
  text-align: center;
  color: var(--muted);
  font-size: 0.8rem;
  list-style: none;
}
#join-details summary::-webkit-details-marker,
#resume-details summary::-webkit-details-marker { display: none; }
#join-details summary::marker,
#resume-details summary::marker { content: ''; }
#join-details summary:hover,
#join-details summary:focus-visible,
#resume-details summary:hover,
#resume-details summary:focus-visible { color: var(--brass); }
#join-details[open] summary,
#resume-details[open] summary { color: var(--brass); }
#join-game-form,
#resume-game-form { margin-top: 0.85rem; }

/* ---------- lobby screen ---------- */

.lobby-qr {
  width: 8.75rem;
  height: 8.75rem;
  margin: 0 auto 0.6rem;
  padding: 0.35rem;
  background: var(--parchment);
  border: 1px solid var(--brass-dim);
  box-shadow: 0 0 0 3px var(--void), 0 0 0 4px var(--line);
}
.lobby-qr svg { display: block; width: 100%; height: 100%; }

.join-code-display {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  background: var(--void);
  border: 1px solid var(--line);
  padding: 0.6rem 1rem;
  margin-bottom: 0.6rem;
}
.join-code-label { color: var(--muted); font-size: 0.75rem; font-weight: 600; }
.join-code-value {
  font-family: var(--mono, monospace);
  font-size: 1.3rem;
  letter-spacing: 0.15em;
  color: var(--brass-bright);
}

.lobby-players {
  list-style: none;
  margin: 0 0 1rem;
  padding: 0;
  text-align: left;
}
.lobby-players li {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  padding: 0.5rem 0;
  border-bottom: 1px solid var(--line);
  font-size: 0.9rem;
  color: var(--parchment);
}
.lobby-players li.open { color: var(--muted); font-style: italic; }

.waiting-banner {
  flex: 0 0 auto;
  background: var(--panel);
  border-bottom: 1px solid var(--brass-dim);
  color: var(--brass-bright);
  font-size: 0.8rem;
  font-weight: 600;
  text-align: center;
  padding: 0.5rem 1rem;
}

/* ---------- buttons ---------- */

button {
  cursor: pointer;
  border: 1px solid transparent;
  border-radius: var(--radius);
  padding: 0.65rem 1.15rem;
  font-size: 0.85rem;
  font-weight: 600;
  font-family: var(--sans);
  white-space: nowrap;
  transition: filter 0.12s ease, transform 0.08s ease, border-color 0.12s ease;
}
button:active { transform: scale(0.98); }
button:disabled { opacity: 0.5; cursor: default; }

button.primary {
  background: var(--brass);
  color: #1a1204;
  width: 100%;
}
button.primary:hover:not(:disabled) { background: var(--brass-bright); }

button.ghost {
  background: transparent;
  color: var(--muted);
  border-color: var(--line);
}
button.ghost:hover:not(:disabled) { border-color: var(--brass-dim); color: var(--parchment); }

.hint { margin-top: 1.25rem; font-size: 0.8rem; color: var(--muted); }
.hint a { color: var(--brass); }

/* Fixed corner toggle for start/end screens, which have no topbar to dock
   into. Hidden on the game screen (where the topbar has its own compact
   version below) via a sibling rule off the same [hidden] attribute
   showScreen() already manages — same pattern as .screen[hidden] elsewhere. */
.mute-toggle-fixed {
  position: fixed;
  top: 0.85rem;
  right: 1.1rem;
  z-index: 20;
  padding: 0.4rem 0.75rem;
  font-size: 0.72rem;
  background: rgba(19, 27, 46, 0.7);
  backdrop-filter: blur(2px);
}
#game-screen:not([hidden]) ~ .mute-toggle-fixed { display: none; }

/* Compact icon-only version docked in the game screen's topbar. */
.mute-toggle-inline {
  padding: 0.65rem 0.8rem;
  font-size: 1rem;
  line-height: 1;
}
.mute-toggle-inline[aria-pressed="true"] { opacity: 0.45; }

/* ---------- game screen: chart title block ---------- */

#game-screen {
  display: flex;
  flex-direction: column;
  /* .screen (shared with the start/end cartouche cards) sets align-items:center
     and justify-content:center to center a small modal-like box; reset both
     here or topbar/board shrink-wrap their own content instead of filling
     the window, leaving big dead margins on wide screens */
  align-items: stretch;
  justify-content: flex-start;
  min-height: 0;
  height: 100vh;
  height: 100dvh;
  padding: 0;
}

#topbar {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: 1.5rem;
  padding: 0.85rem 1.4rem;
  background: var(--panel);
  border-bottom: 1px solid var(--brass-dim);
  box-shadow: 0 1px 0 var(--line);
}

.brand {
  font-family: var(--serif);
  font-weight: 600;
  letter-spacing: 0.06em;
  color: var(--brass);
  font-size: 1.15rem;
}

.turn-readout {
  font-family: var(--mono);
  font-size: 0.8rem;
  letter-spacing: 0.08em;
  color: var(--muted);
  flex: 1;
  font-variant-numeric: tabular-nums;
}

#topbar-actions { display: flex; gap: 0.6rem; }
#topbar-actions button { width: auto; }

#board {
  flex: 1 1 auto;
  min-height: 0;
  display: grid;
  /* A flat 20rem was 38% of the viewport at 850px (just above the mobile
     breakpoint below) vs 25% at 1280px+ - nothing eased that transition, so
     a tablet-landscape or small-laptop window got a disproportionately
     dominant sidebar. Scales smoothly instead: saturates back to today's
     exact 320px at ~1231px+ (large desktop, where this app has actually
     been played, is pixel-identical to before), narrows to a 224px floor by
     ~862px - only the untested 800-1231px band changes. */
  grid-template-columns: 1fr clamp(14rem, 26vw, 20rem);
  gap: 0;
}

/* ---------- starmap / plotting board ---------- */

#map-wrap {
  display: flex;
  flex-direction: column;
  min-height: 0;
}

.map-viewport {
  position: relative;
  flex: 1 1 auto;
  min-height: 0;
  overflow: hidden;
  /* deliberately no opaque fill: the page-level #starfield shows through here */
  background-image:
    linear-gradient(var(--grid-line) 1px, transparent 1px),
    linear-gradient(90deg, var(--grid-line) 1px, transparent 1px);
  background-size: 4rem 4rem, 4rem 4rem;
  background-position: -1px -1px, -1px -1px;
}

.map-viewport::before {
  content: "";
  position: absolute;
  inset: 0.625rem;
  pointer-events: none;
  z-index: 2;
  background-repeat: no-repeat;
  background-size: 1.375rem 1.375rem;
  background-image:
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='22' height='22'%3E%3Cpath d='M1,21 L1,1 L21,1' fill='none' stroke='%23c99a52' stroke-width='1.6'/%3E%3C/svg%3E"),
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='22' height='22'%3E%3Cpath d='M1,1 L21,1 L21,21' fill='none' stroke='%23c99a52' stroke-width='1.6'/%3E%3C/svg%3E"),
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='22' height='22'%3E%3Cpath d='M1,1 L1,21 L21,21' fill='none' stroke='%23c99a52' stroke-width='1.6'/%3E%3C/svg%3E"),
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='22' height='22'%3E%3Cpath d='M21,1 L21,21 L1,21' fill='none' stroke='%23c99a52' stroke-width='1.6'/%3E%3C/svg%3E");
  background-position: top left, top right, bottom left, bottom right;
  opacity: 0.75;
}

#starmap { width: 100%; height: 100%; display: block; position: relative; z-index: 0; }

.planet-label-name {
  fill: var(--parchment);
  font-family: var(--sans);
  font-size: 13px;
  font-weight: 600;
  text-anchor: middle;
  pointer-events: none;
  letter-spacing: 0.02em;
}
.planet-label-stats {
  fill: var(--muted);
  font-family: var(--mono);
  font-size: 10.5px;
  text-anchor: middle;
  pointer-events: none;
}
.planet-ships {
  fill: #fff;
  font-family: var(--mono);
  font-size: 14px;
  font-weight: 600;
  text-anchor: middle;
  pointer-events: none;
  paint-order: stroke fill;
  stroke: #000a;
  stroke-width: 3px;
  stroke-linejoin: round;
}

.planet circle.body {
  stroke: #ffffff35;
  stroke-width: 1;
  cursor: pointer;
  transition: filter 0.15s ease;
}
.planet:hover circle.body { filter: brightness(1.2); }
.planet circle.orbit-ring {
  fill: none;
  stroke-dasharray: 2 3;
  stroke-width: 1;
  opacity: 0.6;
}
.planet.selected circle.selection-ring {
  fill: none;
  stroke: var(--brass-bright);
  stroke-width: 1.5;
}
.selection-tick {
  stroke: var(--brass-bright);
  stroke-width: 1.5;
}

.fleet-path {
  stroke: #e9e4d64a;
  stroke-width: 1.2;
  stroke-dasharray: 1 4;
  stroke-linecap: round;
  fill: none;
}
.fleet-marker {
  stroke: #000a;
  stroke-width: 1;
  stroke-linejoin: round;
}
.fleet-label {
  fill: var(--parchment);
  font-family: var(--mono);
  font-size: 10.5px;
  text-anchor: middle;
}

/* ---------- order bar: docked, never overlaps the map ---------- */

.order-bar {
  /* stays present and animates between a slim idle strip and a slightly
     taller active state, rather than the old approach of a fixed tall
     min-height at all times (that ate too much of the map, especially on
     mobile) or popping the whole element in/out (which read as the UI
     lurching). the size difference between idle/active is now small and
     transitions smoothly instead of jumping */
  flex: 0 0 auto;
  width: 100%;
  min-height: 3.25rem;
  transition: min-height 0.2s ease;
  box-sizing: border-box;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.4rem 1.5rem;
  background: var(--panel);
  border-top: 1px solid var(--brass-dim);
  box-shadow: 0 -0.25rem 1rem #00000066;
  padding: 0.55rem 1.1rem;
  position: relative;
  z-index: 3;
}
.order-bar.active { min-height: 3.75rem; }

.order-bar-placeholder {
  width: 100%;
  margin: 0;
  text-align: center;
  color: var(--muted);
  font-size: 0.85rem;
  font-style: italic;
  font-family: var(--serif);
}

.order-bar-info, .order-bar-controls, .order-actions { display: none; }
.order-bar.active .order-bar-placeholder { display: none; }
.order-bar.active .order-bar-info { display: block; }
.order-bar.active .order-bar-controls { display: flex; }
.order-bar.active .order-actions { display: flex; }

.order-bar-info { flex: 1 1 16rem; min-width: 0; }
.order-bar-controls { flex: 1 1 12rem; min-width: 0; align-items: center; gap: 0.6rem; }

.order-row { display: flex; flex-wrap: wrap; align-items: baseline; gap: 0.4rem; font-size: 0.92rem; color: var(--parchment); font-weight: 600; }
.order-row .order-arrow { color: var(--muted); font-weight: 400; }

/* Arrival time and the "enough to take it" estimate: the two numbers you
   actually decide on, previously either absent or only shown after launch. */
.order-metrics { gap: 0.75rem; margin-top: 0.15rem; }
.order-metric {
  font-size: 0.74rem;
  color: var(--muted);
  font-family: var(--mono);
  font-weight: 400;
}
#order-eta { color: var(--brass-bright); }
#order-estimate.insufficient { color: var(--danger-bright); }

.ships-label {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: 0.35rem;
  white-space: nowrap;
  font-size: 0.78rem;
  color: var(--muted);
}
.ships-number {
  width: 4.5rem;
  padding: 0.2rem 0.35rem;
  background: var(--void);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  color: var(--brass-bright);
  font-family: var(--mono);
  font-size: 0.95rem;
  text-align: right;
}
.ships-number:focus { border-color: var(--brass); outline: none; }

.ship-presets { flex: 0 0 auto; display: flex; gap: 0.3rem; }
.chip {
  padding: 0.2rem 0.5rem;
  background: transparent;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  color: var(--muted);
  font-size: 0.7rem;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  cursor: pointer;
  width: auto;
}
.chip:hover { border-color: var(--brass-dim); color: var(--brass-bright); }

#order-panel input[type="range"] {
  flex: 1 1 auto;
  min-width: 0;
  accent-color: var(--brass);
}

.order-actions { flex: 0 0 auto; gap: 0.6rem; }
.order-actions button { width: auto; }

@media (max-width: 50em) {
  .order-bar {
    flex-direction: column;
    flex-wrap: nowrap;
    align-items: stretch;
    min-height: 3.5rem;
    max-height: 45vh;
    overflow-y: auto;
  }
  .order-bar.active { min-height: 8rem; }
  .order-bar-placeholder { display: flex; align-items: center; justify-content: center; flex: 1; }
  .order-bar.active .order-bar-placeholder { display: none; }
  /* the 16rem/12rem flex-basis above is a *width* hint for the row layout;
     in column mode flex-basis governs height instead, so it must be reset
     here or the panel balloons and spills out of #map-wrap */
  .order-bar-info, .order-bar-controls { flex: 0 0 auto; width: 100%; }
  .order-actions { width: 100%; }
  .order-actions button { flex: 1; }
}

/* ---------- sidebar: ship's log ---------- */

#sidebar {
  border-left: 1px solid var(--line);
  background: var(--panel);
  padding: 0 1.1rem 1.1rem;
  min-height: 0;
  /* Only the log scrolls, not the whole column. Previously the sidebar itself
     scrolled, so on a phone the log's own length pushed the sections beneath
     it hundreds of pixels out of reach - the resume code sat at 737px in a
     210px window, effectively invisible. */
  display: flex;
  flex-direction: column;
  overflow: hidden;
}

#standings, #fleets-en-route, #resume-section { flex: 0 0 auto; }

/* Standings and Fleets have no natural ceiling - a full 8-player roster or
   several active fleets can grow tall enough to push #event-log to 0px and
   silently clip everything below it (#sidebar has overflow:hidden, no
   scrollbar to fall back on). Capping and scrolling them internally, same
   idea as the log itself already does, keeps every section reachable
   regardless of roster size. */
#standings-list, #fleets-list {
  max-height: 11rem;
  overflow-y: auto;
}

#event-log {
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  flex-direction: column;
}
#event-log #log-list {
  flex: 1 1 auto;
  min-height: 2.5rem;
  overflow-y: auto;
}

#sidebar h2 {
  font-family: var(--serif);
  font-style: italic;
  font-weight: 500;
  font-size: 1rem;
  color: var(--brass-bright);
  margin: 1.3rem 0 0.6rem;
  padding-bottom: 0.5rem;
  border-bottom: 1px solid var(--line);
}
#sidebar section:first-child h2 { margin-top: 1.1rem; }

#standings-list, #fleets-list, #log-list { list-style: none; margin: 0; padding: 0; }

#standings-list li {
  display: flex;
  align-items: center;
  gap: 0.55rem;
  padding: 0.45rem 0;
  font-size: 0.85rem;
  border-bottom: 1px solid var(--grid-line);
}
#standings-list li:last-child { border-bottom: none; }
#standings-list li.eliminated { opacity: 0.4; text-decoration: line-through; }
.color-dot { width: 0.5625rem; height: 0.5625rem; border-radius: 50%; flex-shrink: 0; box-shadow: 0 0 0 1px #00000060; }
.standing-name { flex: 1; }
.standing-stats { color: var(--muted); font-size: 0.75rem; font-family: var(--mono); }

#fleets-list li, #log-list li {
  font-size: 0.78rem;
  color: var(--muted);
  padding: 0.45rem 0;
  border-bottom: 1px solid var(--grid-line);
  line-height: 1.4;
  font-family: var(--mono);
}
#fleets-list li:last-child, #log-list li:last-child { border-bottom: none; }
#fleets-list li button {
  margin-top: 0.35rem;
  padding: 0.25rem 0.55rem;
  font-size: 0.65rem;
  background: var(--danger);
  color: #fff;
}
#fleets-list li button:hover { background: var(--danger-bright); }

/* ---------- end overlay ---------- */

#end-overlay .cartouche-frame { max-width: 23.75rem; }
#end-message { color: var(--muted); margin-bottom: 1.75rem; font-family: var(--sans); font-style: normal; font-size: 0.92rem; }
#end-title.victory { color: var(--brass-bright); }
#end-title.defeat { color: var(--muted); }

@media (max-width: 50em) {
  #board { grid-template-columns: 1fr; grid-template-rows: 1fr auto; }
  #sidebar { border-left: none; border-top: 1px solid var(--line); max-height: 30vh; }
  /* The resume code is reference material, not something consulted mid-turn;
     on a phone it earns less vertical space than the log. */
  #resume-section .resume-hint { display: none; }
  #resume-section h2 { margin-bottom: 0.35rem; }
  .field-row { grid-template-columns: 1fr; gap: 0; }

  /* Below ~40em the centered cartouche card (up to 90vw wide) reaches far
     enough right to sit under .mute-toggle-fixed; on short viewports the
     card also sits at this minimum padding (nothing taller pushes it down),
     so the two collide by a few px at the corner. Extra top padding here
     clears it on every screen that uses .cartouche (start/lobby/end). */
  .screen { padding-top: 3rem; }

  /* comfortable tap targets once a mouse can no longer be assumed */
  button { padding: 0.8rem 1.2rem; font-size: 0.9rem; }
  #fleets-list li button { padding: 0.5rem 0.85rem; font-size: 0.78rem; }

  #topbar { padding: 0.7rem 0.85rem; gap: 0.6rem; flex-wrap: wrap; row-gap: 0.5rem; }
  .brand { font-size: 1rem; }
  .turn-readout { font-size: 0.74rem; }
  /* the header has the least room of anywhere in the app to spend on padding */
  #topbar-actions button { padding: 0.55rem 0.85rem; font-size: 0.78rem; }
}

@media (max-height: 43.75em) {
  .cartouche-frame { padding: 1.75rem 2rem; }
  .cartouche-frame h1 { font-size: 2.1rem; }
  .tagline { margin-bottom: 1.25rem; }
  #new-game-form label { margin-bottom: 0.75rem; }
}

/* ==========================================================================
   Additions: toasts, in-page confirm, turn clock, player flags, utilities
   ========================================================================== */

/* ---------- toasts (replacing alert()) ---------- */

.toasts {
  position: fixed;
  left: 50%;
  /* On the game screen, --order-bar-clearance (set in game.js, on <body>
     since #toasts isn't a descendant of #order-panel) tracks the order
     bar's real current height, so a toast never lands on top of it - the
     bar's height varies with content and breakpoint, so a fixed guess here
     would only be safe some of the time. Elsewhere it's unset, so this is
     just the original fixed offset. */
  bottom: calc(1.25rem + var(--order-bar-clearance, 0px));
  transform: translateX(-50%);
  z-index: 60;
  display: flex;
  flex-direction: column-reverse;
  align-items: center;
  gap: 0.4rem;
  width: min(28rem, calc(100vw - 2rem));
  pointer-events: none;
}

.toast {
  pointer-events: auto;
  width: 100%;
  padding: 0.6rem 0.9rem;
  background: var(--panel-raised);
  border: 1px solid var(--line);
  border-left: 0.1875rem solid var(--muted);
  border-radius: var(--radius);
  box-shadow: 0 0.5rem 1.5rem #00000088;
  color: var(--parchment);
  font-size: 0.85rem;
  line-height: 1.35;
  cursor: pointer;
  animation: toast-in 0.18s ease-out;
}
.toast.leaving { opacity: 0; transition: opacity 0.25s ease; }
.toast-ok { border-left-color: var(--good); }
.toast-warn { border-left-color: var(--brass); }
.toast-error { border-left-color: var(--danger-bright); }

@keyframes toast-in {
  from { opacity: 0; transform: translateY(0.5rem); }
  to { opacity: 1; transform: none; }
}

@media (prefers-reduced-motion: reduce) {
  .toast { animation: none; }
}

/* ---------- in-page confirm (replacing confirm()) ---------- */

.modal {
  position: fixed;
  inset: 0;
  z-index: 70;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 1.5rem;
  background: rgba(6, 10, 20, 0.72);
  backdrop-filter: blur(0.125rem);
}
.modal[hidden] { display: none; }

.modal-card {
  width: min(24rem, 100%);
  padding: 1.4rem;
  background: var(--panel);
  border: 1px solid var(--brass-dim);
  border-radius: var(--radius);
  box-shadow: 0 1rem 3rem #000000aa;
}
.modal-card p {
  margin: 0 0 1.1rem;
  font-family: var(--serif);
  font-size: 1rem;
  line-height: 1.45;
}
.modal-actions { display: flex; gap: 0.6rem; }
.modal-actions button { flex: 1 1 0; width: auto; }

/* ---------- turn clock ---------- */

.turn-clock {
  font-family: var(--mono);
  font-size: 0.9rem;
  font-variant-numeric: tabular-nums;
  color: var(--muted);
  padding: 0.15rem 0.5rem;
  border: 1px solid var(--line);
  border-radius: var(--radius);
}
.turn-clock.urgent {
  color: var(--danger-bright);
  border-color: var(--danger);
}
.turn-clock[hidden] { display: none; }

/* ---------- standings: ready / offline markers ---------- */

.player-flag {
  margin-left: 0.35rem;
  font-size: 0.65rem;
  font-family: var(--mono);
  letter-spacing: 0.05em;
}
.player-flag.ready { color: var(--good); }
.player-flag.idle {
  color: var(--danger-bright);
  text-transform: uppercase;
}

/* ---------- start-screen additions ---------- */

/* .cartouche-form label sets its own display:flex, which (like .screen
   elsewhere) beats the [hidden] UA rule - without this, the field stays
   visible for every game regardless of num_human. */
#turn-timer-field[hidden] { display: none; }

.checkbox-field {
  flex-direction: row !important;
  align-items: flex-start;
  gap: 0.55rem;
}
.checkbox-field input[type="checkbox"] {
  margin-top: 0.15rem;
  accent-color: var(--brass);
  width: 0.95rem;
  height: 0.95rem;
  flex: 0 0 auto;
}
.checkbox-field > span { display: flex; flex-direction: column; gap: 0.15rem; }

/* ---------- small utilities (were inline style attributes) ---------- */

.full-width { width: 100%; }
.mt-sm { margin-top: 0.6rem; }
.mb-lg { margin-bottom: 1.25rem; }
.uppercase { text-transform: uppercase; }

/* ---------- keyboard access on the map ---------- */

/* Planets are focusable now, so they need a visible focus ring; the default
   outline is drawn outside the <svg>'s clip on some engines. */
.planet:focus { outline: none; }
.planet:focus-visible .body {
  stroke: var(--brass-bright);
  stroke-width: 3;
  paint-order: stroke;
}

/* ---------- resume code ---------- */

#resume-section { margin-top: auto; }
.resume-hint {
  margin: 0 0 0.45rem;
  color: var(--muted);
  font-size: 0.72rem;
  font-style: italic;
  font-family: var(--serif);
}
.resume-row {
  display: flex;
  align-items: center;
  gap: 0.4rem;
}
.resume-code {
  flex: 1 1 auto;
  min-width: 0;
  /* Nobody reads this character by character - they press Copy. Showing the
     whole 40-character string only crowded the column. */
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  padding: 0.3rem 0.45rem;
  background: var(--void);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  color: var(--brass-bright);
  font-family: var(--mono);
  font-size: 0.68rem;
}
#resume-section[hidden] { display: none; }

/* ---------- map pan / zoom ---------- */

.map-viewport { position: relative; }

#starmap { touch-action: none; cursor: default; }
#starmap.pannable { cursor: grab; }
#starmap.panning { cursor: grabbing; }
#starmap .planet { cursor: pointer; }

.map-controls {
  position: absolute;
  right: 0.6rem;
  bottom: 0.6rem;
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
  z-index: 2;
}

.map-btn {
  width: 2rem;
  height: 2rem;
  padding: 0;
  display: grid;
  place-items: center;
  /* Solid first: an engine without color-mix() drops the whole declaration,
     which would leave these floating transparent over the map. */
  background: var(--panel);
  background: color-mix(in srgb, var(--panel) 88%, transparent);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  color: var(--muted);
  font-family: var(--mono);
  font-size: 1rem;
  line-height: 1;
  backdrop-filter: blur(0.125rem);
}
.map-btn:hover:not(:disabled) { color: var(--brass-bright); border-color: var(--brass-dim); }
.map-btn:disabled { opacity: 0.3; cursor: default; }
.map-btn[hidden] { display: none; }

/* ---------- sidebar tabs (narrow screens only) ----------
   A phone gives the sidebar about 30vh. Standings and Fleets alone can fill
   that, which pushed the Log and the resume code off the bottom entirely.
   Rather than shrink four sections until none of them is readable, show one
   at a time and give it the whole panel. Desktop is unaffected: the tab strip
   is not rendered and every section stays stacked and visible. */

.sidebar-tabs { display: none; }

@media (max-width: 50em) {
  .sidebar-tabs {
    display: flex;
    gap: 0.25rem;
    position: sticky;
    top: 0;
    z-index: 2;
    padding: 0.5rem 0 0.4rem;
    margin-bottom: 0.2rem;
    background: var(--panel);
    border-bottom: 1px solid var(--line);
  }

  .sidebar-tab {
    flex: 1 1 0;
    padding: 0.45rem 0.3rem;
    background: transparent;
    border: 1px solid transparent;
    border-radius: var(--radius);
    color: var(--muted);
    font-size: 0.72rem;
    font-weight: 600;
    letter-spacing: 0.03em;
    white-space: nowrap;
  }
  .sidebar-tab[aria-selected="true"] {
    color: var(--brass-bright);
    border-color: var(--brass-dim);
    background: var(--panel-raised);
  }

  /* One section at a time; the active one takes the whole panel. */
  #sidebar > section { display: none; }
  /* One tab may reveal more than one section: "Share" carries both the
     resume code and the spectator link, which are the same kind of thing -
     something you hand to another person. A section with no tab is simply
     unreachable on a phone, which is what happened when Spectators was added
     as a fifth section against four tabs. */
  #sidebar[data-tab="standings"] #standings,
  #sidebar[data-tab="fleets-en-route"] #fleets-en-route,
  #sidebar[data-tab="event-log"] #event-log,
  #sidebar[data-tab="share"] #spectate-section,
  #sidebar[data-tab="share"] #resume-section {
    display: flex;
    flex-direction: column;
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
  }

  /* The heading is redundant when the tab above already names the section -
     except under Share, which shows two and needs to tell them apart. */
  #sidebar > section h2 { display: none; }
  #sidebar[data-tab="share"] #spectate-section h2,
  #sidebar[data-tab="share"] #resume-section h2 { display: block; }
  #sidebar[data-tab="share"] #spectate-section,
  #sidebar[data-tab="share"] #resume-section { flex: 0 0 auto; }
  #resume-section .resume-hint { display: block; margin-top: 0.2rem; }
}

/* ---------- connection warning ---------- */

/* Distinct from the brass "waiting on other players" banner: that one is a
   normal part of a turn, this one means the board you are looking at may be
   stale. */
.connection-banner {
  border-bottom-color: var(--danger);
  color: var(--danger-bright);
}
.connection-banner[hidden] { display: none; }

/* ---------- fog of war indicator ---------- */

/* Deliberately quiet and permanent. It is not an alert, it is a caption for
   the thing the player is not seeing. */
.fog-indicator {
  font-size: 0.68rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--muted);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 0.15rem 0.45rem;
  white-space: nowrap;
  cursor: help;
}
.fog-indicator[hidden] { display: none; }

@media (max-width: 50em) {
  /* The topbar has the least room anywhere in the app; the log line and the
     order panel still carry the message. */
  .fog-indicator { display: none; }
}

/* ---------- log announcements over the map ----------
   The log carries the entire narrative - captures, repelled attacks, AI
   taunts, neutral worlds grumbling - and a playtester finished a whole
   session without noticing the sidebar existed. New lines now surface over
   the map itself and fade; the sidebar remains the full history. */

.log-flash {
  position: absolute;
  left: 0.6rem;
  bottom: 0.6rem;
  right: 3rem; /* clear of the zoom controls */
  /* Without a cap this just fills map-width-minus-insets, which on a wide
     desktop map measured out to 902px (94% of a 960px map) for one line -
     a corner notification, not a banner. */
  max-width: 26rem;
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
  pointer-events: none;
  z-index: 2;
}

.log-flash-line {
  align-self: flex-start;
  max-width: 100%;
  padding: 0.3rem 0.6rem;
  background: color-mix(in srgb, var(--panel) 92%, transparent);
  background-color: var(--panel);
  border-left: 0.125rem solid var(--brass-dim);
  border-radius: var(--radius);
  color: var(--parchment);
  font-family: var(--serif);
  font-size: 0.78rem;
  line-height: 1.35;
  opacity: 0.94;
  animation: log-flash-in 0.22s ease-out;
  /* Server-generated text has no length cap - a multi-party combat clash
     lists every fighting player's name and can run past 200 characters.
     Fallback first: max-height/overflow alone hard-clips on an engine
     without line-clamp support, instead of an unbounded box; line-clamp
     then gives it a clean truncating ellipsis. Full text is always still in
     the permanent sidebar log either way. */
  max-height: 2.7em;
  overflow: hidden;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
}
.log-flash-line.leaving { opacity: 0; transition: opacity 0.4s ease; }

@keyframes log-flash-in {
  from { opacity: 0; transform: translateY(0.35rem); }
  to { opacity: 0.94; transform: none; }
}

@media (prefers-reduced-motion: reduce) {
  .log-flash-line { animation: none; }
}

/* Unread marker on the Log tab, for narrow screens where only one sidebar
   section is visible at a time. */
.sidebar-tab.has-unread::after {
  content: attr(data-unread);
  margin-left: 0.3rem;
  padding: 0 0.25rem;
  border-radius: 0.6rem;
  background: var(--brass-dim);
  color: var(--void);
  font-size: 0.62rem;
  font-weight: 700;
}

/* ---------- spectating ---------- */

.spectator-badge {
  font-size: 0.68rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--brass-bright);
  border: 1px solid var(--brass-dim);
  border-radius: var(--radius);
  padding: 0.15rem 0.45rem;
  white-space: nowrap;
}
.spectator-badge[hidden] { display: none; }

#spectate-section[hidden] { display: none; }
#order-panel[hidden] { display: none; }
