* { margin: 0; padding: 0; box-sizing: border-box; }
html, body { height: 100%; overscroll-behavior: none; }
body {
  background: #05070d; color: #cfd6e4;
  font-family: system-ui, -apple-system, sans-serif;
  font-size: 15px; -webkit-tap-highlight-color: transparent;
}
.hidden { display: none !important; }
.screen { position: fixed; inset: 0; display: flex; flex-direction: column; }
.center { align-items: center; justify-content: center; }
.error { color: #ff7b72; min-height: 1.2em; font-size: 13px; }
.grow { flex: 1; }
.row { display: flex; gap: 8px; align-items: center; }

h1 { font-size: 28px; line-height: 1.1; margin-bottom: 16px; color: #e8eefc; }
h2 { font-size: 14px; text-transform: uppercase; letter-spacing: .08em; color: #7f8ba3; margin-bottom: 8px; }

/* ---- Star Wars identity ---- */
/* CSS starfield for DOM screens (auth/lobby): repeating 1px radial-gradient
   "stars" over faint nebula washes — no images, no JS */
.starry::before {
  content: ''; position: absolute; inset: 0; pointer-events: none;
  background-image:
    radial-gradient(ellipse 70% 45% at 78% 12%, rgba(37, 64, 122, .30), transparent 70%),
    radial-gradient(ellipse 55% 40% at 15% 85%, rgba(85, 42, 110, .20), transparent 70%),
    radial-gradient(1px 1px at 27px 43px, rgba(255,255,255,.55), transparent),
    radial-gradient(1px 1px at 142px 117px, rgba(255,255,255,.35), transparent),
    radial-gradient(1.4px 1.4px at 86px 181px, rgba(156,195,255,.45), transparent),
    radial-gradient(1px 1px at 201px 74px, rgba(255,224,184,.4), transparent),
    radial-gradient(1px 1px at 53px 232px, rgba(255,255,255,.3), transparent),
    radial-gradient(1.6px 1.6px at 233px 205px, rgba(255,255,255,.5), transparent);
  background-repeat: no-repeat, no-repeat, repeat, repeat, repeat, repeat, repeat, repeat;
  background-size: auto, auto, 260px 260px, 260px 260px, 300px 300px, 300px 300px, 340px 340px, 340px 340px;
}
.starry > * { position: relative; }

.brand-block { text-align: center; margin-bottom: 18px; }
.kicker {
  display: block; color: #a9b6cf; font-size: 11px; font-weight: 600;
  letter-spacing: .5em; text-transform: uppercase; margin-bottom: 6px;
  padding-left: .5em; /* optically recenter the tracked-out text */
}
.brand-title {
  color: #ffe81f; font-size: 42px; font-weight: 900; line-height: .95;
  letter-spacing: .01em; text-transform: uppercase; margin: 0;
  text-shadow: 0 0 24px rgba(255, 232, 31, .25);
}
.bar .brand {
  color: #ffe81f; font-size: 12px; font-weight: 800;
  letter-spacing: .14em; text-transform: uppercase; white-space: nowrap;
}
.auth-panel { width: min(340px, calc(100vw - 32px)); padding: 22px 18px; }

/* ---- Empire at War HUD skin ----
   Panels, buttons, tabs and slot frames are the game's own art, cut out of
   its MT_COMMANDBAR atlas by scripts/extract-art.mjs into /assets/ui/ and
   worn here as CSS 9-slice borders. Slices are small (the source pieces
   are 24-256px) so the bevel stays crisp while the middle stretches. */
.panel {
  border: 3px solid transparent;
  border-image: url(/assets/ui/panel.png) 3 fill / 3px stretch;
  padding: 14px; margin: 8px; min-width: 0;
}
.starry .panel { backdrop-filter: blur(4px); }

input, select {
  font: inherit; color: inherit; width: 100%; margin-bottom: 8px;
  border-radius: 0; padding: 10px 12px;
  background: #060b16; border: 1px solid #2f4d84;
  box-shadow: inset 0 1px 3px rgba(0, 0, 0, .7);
}
input:focus, select:focus { outline: none; border-color: #6f9ff0; }

button {
  font: inherit; color: #d7e4fb; cursor: pointer; white-space: nowrap;
  background: none; border: 4px solid transparent; border-radius: 0;
  border-image: url(/assets/ui/tab_off.png) 6 10 fill / 4px 8px stretch;
  padding: 7px 8px; font-weight: 600;
  text-shadow: 0 1px 2px rgba(0, 0, 0, .8);
  transition: filter .12s, transform .06s;
}
/* the "on" tab art is a pale blue fill — EaW puts dark text on it */
button.primary {
  border-image-source: url(/assets/ui/tab_on.png);
  color: #0a1a35; text-shadow: none;
}
button.small { padding: 5px 6px; font-size: 13px; }
button:disabled { opacity: .45; cursor: default; }
button:not(:disabled):active {
  transform: translateY(1px);
  border-image-source: url(/assets/ui/tab_hover.png); color: #0a1a35; text-shadow: none;
}
button.danger { border-image-source: url(/assets/ui/tab_off.png); color: #ff9c9c; }
label.check { display: flex; gap: 6px; align-items: center; white-space: nowrap; }
label.check input { width: auto; margin: 0; }

/* the game's own command-bar icons on order buttons */
[class*='cmd-']::before {
  content: ''; display: inline-block; vertical-align: -4px;
  width: 18px; height: 18px; margin-right: 5px;
  background-size: contain; background-repeat: no-repeat; background-position: center;
}
.cmd-move::before { background-image: url(/assets/ui/cmd_move.png); }
.cmd-attack::before { background-image: url(/assets/ui/cmd_attack.png); }
.cmd-stop::before { background-image: url(/assets/ui/cmd_stop.png); }
.cmd-guard::before { background-image: url(/assets/ui/cmd_guard.png); }
.cmd-retreat::before { background-image: url(/assets/ui/cmd_retreat.png); }
.cmd-reinforce::before { background-image: url(/assets/ui/cmd_reinforce.png); }

.bar {
  display: flex; align-items: center; gap: 10px; padding: 8px 10px;
  padding-top: max(8px, env(safe-area-inset-top));
  background: #070c17 url(/assets/ui/panel.png) 0 0 / 100% 340% no-repeat;
  border-bottom: 1px solid #4d7ac4;
  box-shadow: 0 2px 10px rgba(0, 0, 0, .7);
  z-index: 5;
}

.chip {
  padding: 3px 10px 3px 8px; border-radius: 0; font-size: 13px; font-weight: 600;
  display: inline-flex; align-items: center; gap: 5px;
  border: 1px solid currentColor;
}
/* an empty chip (before the first state arrives) shouldn't draw at all */
.chip:empty { display: none; }
/* faction sigils lifted from the game's command bar (factionless chips —
   "neutral", counts — get no glyph and no gap) */
.chip.empire::before, .chip.rebels::before,
.chip.republic::before, .chip.cis::before, .chip.consortium::before {
  content: ''; width: 15px; height: 15px; flex: none;
  background-size: contain; background-repeat: no-repeat; background-position: center;
}
.chip.empire { background: #10203a; color: #9cc3ff; }
.chip.empire::before { background-image: url(/assets/ui/sigil_empire.png); }
.chip.rebels { background: #2c1216; color: #ff9c9c; }
.chip.rebels::before { background-image: url(/assets/ui/sigil_rebel.png); }
.chip.republic { background: #2c2112; color: #ffd79c; }
.chip.republic::before { background-image: url(/assets/ui/sigil_rebel.png); }
.chip.cis { background: #1e1630; color: #c3a8ff; }
.chip.cis::before { background-image: url(/assets/ui/sigil_empire.png); }
.chip.consortium { background: #0f2622; color: #86e6d3; }
/* Two layers, first on top: the Consortium's own symbol once an extraction
   run has produced it, falling back to the generic pirate sigil until then.
   A background layer that 404s is skipped rather than drawn blank, so this
   is correct before AND after the art lands. */
.chip.consortium::before {
  background-image: url(/assets/ui/sigil_underworld.png), url(/assets/ui/sigil_pirate.png);
}

/* The battle command bar carries four controls and a force tally, and on a
   folded phone (344px) that leaves the tally about 90px. `.grow` alone does
   not stop it wrapping — a flex item's min-width is auto — so "2 x 2 . LZ
   1/3" broke onto four lines and squeezed the bar to a quarter of the
   screen. Only the 3D renderer shows the tilt and bombing-run buttons, so
   this appeared the moment battle3d.js started loading again. */
#battle-hud { gap: 6px; padding: 8px; }
#bhud-forces {
  min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
#battle-hud button { flex: none; }
/* Under ~400px the decorative command glyphs on Stop/Retreat are the least
   valuable pixels in the bar: the words already say what the buttons do,
   and the space they free is what keeps "LZ 1/3" on screen — which is the
   number a ground battle is actually fought over (landing zones scale how
   much of the invasion force can be on the ground). */
@media (max-width: 400px) {
  #battle-hud .cmd-stop::before, #battle-hud .cmd-retreat::before { display: none; }
  #battle-hud button.small { padding: 5px 7px; }
  #bhud-forces { font-size: 12px; }
}

/* a locked tech is context, not an offer — it reads quieter than the ones
   you can actually start */
.tech-item.locked { opacity: .62; }

.lobby-body { overflow-y: auto; flex: 1; padding-bottom: 24px; }
.list .item {
  display: flex; align-items: center; gap: 8px; padding: 10px 4px;
  border-bottom: 1px solid #1c2740; font-size: 14px;
}
.list .item:last-child { border-bottom: 0; }
.muted { color: #7f8ba3; font-size: 13px; }

#map { flex: 1; position: relative; touch-action: none; }
#map canvas { position: absolute; inset: 0; }

/* Six-figure totals are normal late game. The amount must never wrap, and
   it must never push the HUD buttons off a folded screen — so the readout
   clips (dropping the per-minute suffix first) instead of growing. */
#hud-credits {
  display: flex; align-items: baseline; gap: 6px; min-width: 0;
  font-variant-numeric: tabular-nums; font-weight: 600; white-space: nowrap;
}
#hud-credits .amount { flex: none; }          /* the balance is never cut */
#hud-credits .inc {                            /* the rate goes first if tight */
  color: #7f8ba3; font-weight: 400; font-size: 13px;
  min-width: 0; overflow: hidden; text-overflow: ellipsis;
}
/* Folded phone: the HUD bar is exactly full at 344px and the income rate was
   the thing squeezed to zero width — the player could never see it. The
   faction sigil already says which side you are on, so drop the word and give
   the credits readout the ~60px back. */
@media (max-width: 400px) {
  #hud-faction { font-size: 0; padding: 3px 6px; gap: 0; }
  #hud-faction::before { width: 17px; height: 17px; }
  #hud-credits .inc .per { display: none; }
}
.bar > button { flex: none; }

.sheet {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 10;
  border: 3px solid transparent; border-bottom: 0;
  border-image: url(/assets/ui/panel.png) 3 fill / 3px stretch;
  max-height: 62vh; display: flex; flex-direction: column;
  padding-bottom: env(safe-area-inset-bottom);
  box-shadow: 0 -12px 40px rgba(0,0,0,.6);
}
.sheet-head {
  display: flex; align-items: center; gap: 8px; padding: 10px 12px 6px;
}
.sheet-head .title {
  font-weight: 700; font-size: 16px; color: #e8eefc;
  letter-spacing: .04em; text-transform: uppercase;
}
.sheet-tabs { display: flex; gap: 3px; padding: 0 8px; }
.sheet-tabs button { flex: 1; padding: 6px 4px; font-size: 13px; }
.sheet-tabs button.active {
  border-image-source: url(/assets/ui/tab_on.png);
  color: #0a1a35; text-shadow: none;
}
.sheet-body { overflow-y: auto; padding: 10px 12px 16px; }

.build-item, .fleet-card, .tech-item {
  display: flex; align-items: center; gap: 10px; padding: 9px 0;
  border-bottom: 1px solid #1c2740;
}
.build-item:last-child, .fleet-card:last-child, .tech-item:last-child { border-bottom: 0; }
.build-item .info, .tech-item .info { flex: 1; min-width: 0; }
/* unit icons sit in the game's own octagonal slot frame */
.bicon {
  width: 44px; height: 44px; flex: none; object-fit: contain;
  border: 5px solid transparent;
  border-image: url(/assets/ui/slot.png) 8 fill / 5px stretch;
}
.bicon.small { width: 28px; height: 28px; border-width: 4px; border-image-width: 4px; }
.build-item .name, .tech-item .name { font-size: 14px; color: #e8eefc; }
.build-item .meta, .tech-item .meta { font-size: 12px; color: #7f8ba3; }

.fleet-card { flex-direction: column; align-items: stretch; gap: 6px; }
.fleet-card .fleet-title { display: flex; align-items: center; gap: 8px; font-weight: 600; }
.fleet-card .meta { font-size: 12px; color: #a9b6cf; }
.fleet-card .meta.muted { color: #7f8ba3; font-style: italic; }

/* planet dossier: the income breakdown and slot tally read as a ledger, so
   they are label-left / figure-right rather than another card list */
.stat-row {
  display: flex; align-items: baseline; gap: 8px;
  font-size: 13px; color: #a9b6cf; padding: 5px 2px;
  border-bottom: 1px solid rgba(36, 54, 92, .5);
}
.stat-row:last-of-type { border-bottom: 0; }
.stat-row span:last-child { color: #e8eefc; font-variant-numeric: tabular-nums; }
.build-item .meta.muted { color: #6b7793; }
.unit-row { display: flex; align-items: center; gap: 8px; font-size: 13px; padding: 2px 0; }
.unit-row .hull { margin-left: auto; color: #7f8ba3; font-size: 12px; }
.unit-row .hull.hurt { color: #f0b45a; }

/* tap-to-select ship list (fleet tab redesign) */
.ships-head { display: flex; align-items: center; gap: 8px; }
.ships-head h2 { margin: 0; }
.unit-row.tappable {
  padding: 8px 10px; margin: 3px 0; border-radius: 0;
  border: 1px solid #24365c; background: rgba(10, 20, 40, .55);
  cursor: pointer; user-select: none; -webkit-tap-highlight-color: transparent;
}
/* selected rows wear the game's corner-bracket select frame */
.unit-row.tappable.selected {
  border-color: transparent; background: rgba(28, 52, 100, .6);
  border-image: url(/assets/ui/slot_select.png) 8 fill / 5px stretch;
  border-width: 5px;
}
.unit-row .sel-mark {
  width: 20px; height: 20px; border-radius: 50%; flex: none;
  border: 1.5px solid #2a3a5e; color: #6fb3ff;
  display: inline-flex; align-items: center; justify-content: center; font-size: 12px;
}
.unit-row.selected .sel-mark { border-color: #6f9ff0; background: #1b2f55; }
.unit-row .cls { color: #7f8ba3; width: 14px; text-align: center; flex: none; }
.move-btn { width: 100%; margin: 10px 0 14px; }
.move-btn[disabled] { opacity: 0.45; }

.queue-row { display: flex; gap: 8px; font-size: 13px; color: #a9b6cf; padding: 4px 0; }
.queue-row .eta { margin-left: auto; color: #7f8ba3; }

#toasts {
  position: fixed; top: calc(52px + env(safe-area-inset-top)); left: 0; right: 0;
  display: flex; flex-direction: column; align-items: center; gap: 6px;
  z-index: 20; pointer-events: none;
}
.toast {
  border: 3px solid transparent;
  border-image: url(/assets/ui/panel.png) 3 fill / 3px stretch;
  padding: 7px 14px; font-size: 13px; max-width: 92vw;
  animation: toast-in .2s ease-out;
}
.toast.bad { border-image-source: none; border-color: #7a2e2e; background: #2b1518; }
@keyframes toast-in { from { opacity: 0; transform: translateY(-6px); } }

.overlay {
  position: fixed; inset: 0; z-index: 30; background: rgba(3,5,10,.88);
  display: flex; align-items: center; justify-content: center; text-align: center;
}
.overlay .panel { padding: 28px; }
.overlay h1 { margin-bottom: 8px; }

#battle-map { flex: 1; position: relative; touch-action: none; }
#battle-map canvas { position: absolute; inset: 0; }
button.danger { border-color: #7a2e2e; background: #2b1518; color: #ff9c9c; }

.battle-status {
  position: fixed; top: calc(56px + env(safe-area-inset-top)); left: 0; right: 0;
  text-align: center; pointer-events: none; z-index: 8;
}
.battle-status span {
  background: #2b1518; border: 1px solid #7a2e2e; border-radius: 999px;
  padding: 5px 14px; font-size: 13px; color: #ff9c9c;
}

.radial {
  position: fixed; z-index: 25; transform: translate(-50%, -50%);
  display: flex; flex-direction: column; gap: 4px;
  border: 3px solid transparent;
  border-image: url(/assets/ui/panel.png) 3 fill / 3px stretch;
  padding: 8px; min-width: 150px;
  /* a Star Destroyer has 6 batteries and the Death Star 7, so the menu can
     outgrow a short viewport — scroll it rather than let it run off screen */
  max-height: 78vh; overflow-y: auto;
  box-shadow: 0 8px 30px rgba(0,0,0,.6);
}
.radial button { padding: 8px 10px; font-size: 13px; text-align: left; }
.radial .title { font-size: 12px; color: #7f8ba3; padding: 0 2px 4px; }

.update-banner {
  position: fixed; bottom: 0; left: 0; right: 0; z-index: 40;
  padding: 8px 14px; padding-bottom: max(8px, env(safe-area-inset-bottom));
  text-align: center; font-size: 13px; cursor: pointer;
  background: #14243d; border-top: 1px solid #2f6ad4; color: #9cc3ff;
}

/* A scenario that is deliberately asymmetric warns the weaker side in the
   lobby, where the side is chosen. Inline and in flow — it was briefly given
   `.hint`, which is a position:fixed in-game overlay, and the text landed on
   top of the faction picker and the Create button. */
.faction-note {
  margin: 4px 0 0; font-size: 12px; line-height: 1.35; color: #9aa6bf;
  border-left: 2px solid #7a6a2a; padding-left: 8px;
}

.hint {
  position: fixed; bottom: 14px; left: 0; right: 0; text-align: center;
  z-index: 8; pointer-events: none;
}
.hint span {
  display: inline-block; border: 3px solid transparent;
  border-image: url(/assets/ui/panel.png) 3 fill / 3px stretch;
  padding: 5px 14px; font-size: 13px;
}

/* ---- political layer (expanded Clone Wars) ---- */
/* A slim standing meter + campaign era under the command bar. Hidden
   entirely in scenarios with no political layer. */
.politics {
  display: flex; align-items: center; gap: 8px;
  padding: 4px 10px; font-size: 12px; z-index: 4;
  background: rgba(6, 11, 22, .92);
  border-bottom: 1px solid #24365c;
}
.pol-label { color: #9cc3ff; font-weight: 600; letter-spacing: .04em; text-transform: uppercase; }
.pol-meter {
  flex: 1; height: 7px; min-width: 60px;
  background: #0a1120; border: 1px solid #2a3a5e; overflow: hidden;
}
.pol-meter i { display: block; height: 100%; background: #3fd97b; transition: width .4s ease; }
.pol-meter.fair i { background: #f0b45a; }
.pol-meter.poor i { background: #ff5c5c; }
.pol-value { font-variant-numeric: tabular-nums; font-weight: 700; color: #e8eefc; }
.pol-era {
  color: #7f8ba3; text-transform: uppercase; letter-spacing: .08em;
  white-space: nowrap; font-size: 11px;
}
#btn-mute { min-width: 34px; text-align: center; }
#btn-mute.off { opacity: .5; }

/* ---- motion + waiting states ---- */
/* Screens cross-fade instead of snapping. The map/battle canvases live
   inside these, so the fade is on the section only — nothing re-renders. */
.screen { opacity: 1; transition: opacity .18s ease-out; }
.screen.hidden { display: none !important; }
.sheet { animation: sheet-up .18s ease-out; }
@keyframes sheet-up { from { transform: translateY(12px); opacity: 0; } }

/* the two waits a player actually notices: joining a game, opening a battle */
.busy {
  position: fixed; left: 50%; top: 50%; transform: translate(-50%, -50%);
  z-index: 35; pointer-events: none;      /* informational only, never traps input */
  display: flex; align-items: center; gap: 10px;
  border: 3px solid transparent;
  border-image: url(/assets/ui/panel.png) 3 fill / 3px stretch;
  padding: 12px 18px; font-size: 14px; color: #d7e4fb;
  white-space: nowrap; max-width: calc(100vw - 32px);
  box-shadow: 0 10px 40px rgba(0, 0, 0, .7);
}
.busy-spinner {
  width: 15px; height: 15px; flex: none; border-radius: 50%;
  border: 2px solid #2a3a5e; border-top-color: #6f9ff0;
  animation: spin .7s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }

@media (prefers-reduced-motion: reduce) {
  .screen, .sheet, .busy-spinner { transition: none; animation: none; }
}

/* Install offer — only rendered when the browser actually has one to give
   (shared/install.js keeps it hidden otherwise, and on an already-installed
   app it never appears at all). */
.install-panel {
  display: flex; align-items: center; gap: 12px;
}
.install-panel .install-copy { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.install-panel .install-copy strong { font-size: 15px; }
.install-panel .install-copy .muted { font-size: 12px; }
.install-panel button { flex: none; }

/* orbital strike: armed reads as live, so a stray tap is never a surprise */
#btn-b-bomb.armed {
  background: linear-gradient(180deg, #6b2020, #401212);
  border-color: #d95b5b; color: #ffd9d9;
}
