* {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

@font-face {
  font-family: "PiratesBay";
  src: url("/fonts/PiratesBay.ttf") format("truetype");
  font-display: swap;
}

html,
body {
  width: 100%;
  height: 100%;
  background: #0b1f2a;
  color: #f4ead0;
  font-family: "Segoe UI", system-ui, sans-serif;
  overscroll-behavior: none;
  -webkit-user-select: none;
  user-select: none;
}

.hidden {
  display: none !important;
}

/* The React mount point itself should never participate in layout --
   body's flex rules (controller) and the individual view elements' own
   sizing (screen) are both written assuming they're direct children of
   body, exactly as they were before React owned rendering. */
#root {
  display: contents;
}

/* Screen page */
body.screen {
  overflow: hidden;
}

.lobby-view {
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 2vh;
  padding: 4vh 4vw;
  text-align: center;
}

.lobby-title {
  font-family: "PiratesBay", Georgia, serif;
  font-size: clamp(28px, 5vw, 56px);
  font-weight: 700;
  letter-spacing: 0.06em;
  color: #d4af37;
}

.lobby-hint {
  font-size: clamp(16px, 1.6vw, 22px);
  color: rgba(244, 234, 208, 0.6);
}

.lobby-roster {
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: 1vh;
  min-width: 280px;
}

.lobby-roster-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding: 10px 18px;
  background: rgba(244, 234, 208, 0.06);
  border: 1px solid rgba(244, 234, 208, 0.15);
  border-radius: 10px;
  font-size: clamp(16px, 1.8vw, 22px);
}

.ready-badge {
  font-size: 0.7em;
  font-weight: 700;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: rgba(244, 234, 208, 0.5);
}

.ready-badge.ready {
  color: #4cd964;
}

.ship-windows {
  width: 100%;
  height: 100vh;
  display: flex;
  flex-wrap: wrap;
  align-content: center;
  justify-content: center;
  align-items: center;
  gap: 16px;
  padding: 16px;
  overflow: hidden;
}

.ship-window {
  position: relative;
  flex: 0 0 auto;
  overflow: hidden;
  border: 3px solid #8b5a2b;
  border-radius: 10px;
  background: #000;
  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.55);
}

.ship-window canvas {
  display: block;
  width: 100%;
  height: 100%;
}

.ship-window-systems-ui {
  position: absolute;
  bottom: 0;
  left: 0;
  pointer-events: none;
}

.ship-window-hull-gauge {
  position: absolute;
  bottom: 10px;
  right: 10px;
  pointer-events: none;
}


.ship-window-combat {
  position: absolute;
  bottom: 10px;
  left: 50%;
  transform: translateX(-50%);
  padding: 4px 12px;
  font-size: clamp(12px, 1.2vw, 16px);
  font-weight: 700;
  color: #f4ead0;
  background: rgba(160, 39, 31, 0.85);
  border-radius: 6px;
  pointer-events: none;
  white-space: nowrap;
}

/* Enemy frame, shown alongside a captain's own ship-window while they're in
   combat -- same chrome as .ship-window (border/shadow/rounding), just its
   own content: a tinted placeholder ship image and a hull gauge. */
.combat-window-ship-img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* Placeholder recolor so it doesn't look identical to the player's own
     ship -- drop once real enemy art exists. */
  filter: sepia(0.5) hue-rotate(-50deg) saturate(2.2) brightness(0.75);
}

.combat-window-label {
  position: absolute;
  bottom: 10px;
  left: 10px;
  max-width: calc(100% - 20px);
  box-sizing: border-box;
  padding: 4px 12px;
  font-family: "PiratesBay", Georgia, serif;
  font-size: clamp(15px, 1.6vw, 20px);
  font-weight: 700;
  letter-spacing: 0.04em;
  color: #ff9a8a;
  background: rgba(11, 31, 42, 0.72);
  border-radius: 6px;
  pointer-events: none;
}

.combat-window-hull-gauge {
  position: absolute;
  bottom: 10px;
  right: 10px;
  pointer-events: none;
}

.dialog-overlay {
  position: fixed;
  inset: 0;
  z-index: 50;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 4vh 4vw;
  background: rgba(0, 0, 0, 0.72);
}

.dialog-box {
  max-width: 640px;
  padding: 32px 36px;
  background: #e8d4a8;
  color: #2b1b0e;
  border: 3px solid #8b5a2b;
  border-radius: 10px;
  box-shadow: 0 20px 60px rgba(0, 0, 0, 0.6);
  font-family: "PiratesBay", Georgia, "Times New Roman", serif;
}

.dialog-text {
  font-size: clamp(16px, 2vw, 22px);
  line-height: 1.55;
}

.dialog-hint {
  margin-top: 18px;
  font-family: "Segoe UI", system-ui, sans-serif;
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  text-align: right;
  opacity: 0.6;
}

.event-title {
  font-size: clamp(18px, 2.4vw, 26px);
  font-weight: 700;
  margin-bottom: 10px;
  color: #6b3f1d;
}

.event-choices {
  display: flex;
  flex-direction: column;
  gap: 10px;
  margin-top: 18px;
}

.event-choice-btn {
  padding: 14px 18px;
  font: 600 clamp(14px, 3.8vw, 17px)/1.3 "Segoe UI", sans-serif;
  text-align: left;
  color: #2b1b0e;
  background: rgba(139, 90, 43, 0.15);
  border: 2px solid #8b5a2b;
  border-radius: 10px;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}

.event-choice-btn:active {
  background: rgba(139, 90, 43, 0.3);
}

.event-choice-btn:disabled {
  opacity: 0.4;
  cursor: default;
}

.event-outcome {
  margin-top: 18px;
}

.event-combat-note {
  margin-top: 12px;
  font-family: "Segoe UI", system-ui, sans-serif;
  font-weight: 700;
  color: #a0271f;
}

/* Bottom-toolbar combat panel -- docks in SailPanel's flex slot (see
   App.tsx) instead of a blocking dialog, so it has to stay compact: thin
   hull bars plus a row of small fire buttons, not the old full-size
   modal layout. */
.combat-bar {
  flex: 0 0 auto;
  width: 100%;
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 8px 16px;
  background: rgba(244, 234, 208, 0.06);
  border-top: 1px solid rgba(244, 234, 208, 0.15);
  font-family: "Segoe UI", system-ui, sans-serif;
}

.combat-bar-resolution {
  margin: 0;
  text-align: center;
  font-weight: 700;
  font-size: 14px;
}

.combat-bar-hulls {
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.combat-bar-hull-row {
  display: flex;
  align-items: center;
  gap: 8px;
}

.combat-bar-hull-label {
  flex: 0 0 auto;
  width: 130px;
  font-size: 11px;
  font-weight: 700;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.combat-hull-bar {
  flex: 1 1 auto;
  height: 10px;
  background: rgba(43, 27, 14, 0.15);
  border: 1px solid rgba(43, 27, 14, 0.4);
  border-radius: 6px;
  overflow: hidden;
}

.combat-hull-fill {
  height: 100%;
  width: 0%;
  transition: width 0.2s linear;
}

.combat-hull-fill.enemy {
  background: #a0271f;
}

.combat-hull-fill.own {
  background: #2f7d3a;
}

.combat-bar-weapons {
  display: flex;
  gap: 6px;
}

.combat-bar-weapon-btn {
  flex: 1 1 0;
  /* Flex items default to min-width: auto, which floors shrinking at the
     content's own min-content width -- without this, a long weapon name
     stops the row from fitting all 4 buttons and pushes the last one out. */
  min-width: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  padding: 6px 4px;
  background: #3a4250;
  color: #f4ead0;
  border: 1px solid rgba(244, 234, 208, 0.25);
  border-radius: 8px;
  cursor: default;
  -webkit-tap-highlight-color: transparent;
}

.combat-bar-weapon-btn.active {
  background: #d4af37;
  color: #0b1f2a;
  cursor: pointer;
}

.combat-bar-weapon-btn.unmanned {
  opacity: 0.5;
}

.combat-bar-weapon-name {
  width: 100%;
  min-width: 0;
  font-size: 10px;
  font-weight: 700;
  text-align: center;
  line-height: 1.15;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.combat-bar-weapon-charge {
  display: block;
  width: 100%;
  height: 6px;
  background: rgba(0, 0, 0, 0.3);
  border-radius: 3px;
  overflow: hidden;
}

.combat-bar-weapon-charge-fill {
  display: block;
  height: 100%;
  width: 0%;
  background: #d4af37;
  transition: width 0.2s linear;
}

.combat-bar-weapon-btn.active .combat-bar-weapon-charge-fill {
  background: #0b1f2a;
}

.combat-bar-weapon-status {
  font-size: 10px;
  font-weight: 700;
  min-height: 12px;
}

/* Controller page */
body.controller {
  display: flex;
  flex-direction: column;
  width: 100%;
  height: 100%;
  overflow: hidden;
}

body.controller .lobby-view {
  flex: 1 1 auto;
  min-height: 0;
}

.status {
  font-size: clamp(16px, 4vw, 22px);
  color: rgba(244, 234, 208, 0.75);
}

.name-label {
  font-size: 13px;
  font-weight: 700;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: rgba(244, 234, 208, 0.5);
}

.name-input {
  font: 600 20px/1.2 "Segoe UI", sans-serif;
  text-align: center;
  padding: 12px 20px;
  width: min(280px, 70vw);
  border-radius: 12px;
  border: 2px solid rgba(244, 234, 208, 0.25);
  background: rgba(244, 234, 208, 0.06);
  color: inherit;
}

.name-input:focus {
  outline: none;
  border-color: #d4af37;
}

.ready-btn {
  margin-top: 8px;
  padding: 18px 48px;
  font-size: 22px;
  font-weight: 700;
  color: #0b1f2a;
  background: #d4af37;
  border: none;
  border-radius: 12px;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}

.ready-btn.active {
  background: #4cd964;
}

.ready-btn:disabled {
  background: #3a4250;
  color: #aab2bf;
  cursor: default;
}

.game-view {
  flex: 1 1 auto;
  min-height: 0;
  width: 100%;
  display: flex;
  flex-direction: column;
}

.crew-bar {
  flex: 0 0 auto;
  width: 100%;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 10px 16px;
  background: rgba(244, 234, 208, 0.06);
  border-bottom: 1px solid rgba(244, 234, 208, 0.15);
}

.crew-nav {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  align-items: center;
  gap: 12px;
}

.crew-name {
  flex: 1 1 auto;
  text-align: center;
  font-size: clamp(18px, 5vw, 24px);
  font-weight: 700;
  color: #d4af37;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Consolidated burger menu (see controller/components/DropdownMenu.tsx) --
   replaces the old standalone RefreshButton/FullscreenButton and the
   top-bar's own map/systems toggle buttons with one list. Fixed to the
   viewport corner, not the layout flow, so it sits in the same spot
   regardless of whatever's currently in the header (or lobby, which has no
   .top-bar at all). z-index above .dialog-overlay so it -- Refresh
   especially, the escape hatch for a captain stuck in a bad state -- still
   works with a dialog/event up. */
.dropdown-menu {
  position: fixed;
  top: 10px;
  left: 10px;
  z-index: 60;
}

.dropdown-menu-toggle {
  position: relative;
  display: block;
  width: 48px;
  height: 48px;
  padding: 0;
  border: none;
  background: none;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}

/* Shared by the toggle and every expanded item -- menuIcon (the circular
   medallion) sits full-bleed behind whichever glyph (burger, map, ship...)
   is drawn centered on top of it at a slight inset. */
.dropdown-menu-icon-frame {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
}

.dropdown-menu-icon-glyph {
  position: absolute;
  inset: 0;
  width: 62%;
  height: 62%;
  margin: auto;
}

.dropdown-menu-list {
  position: absolute;
  top: calc(100% + 8px);
  left: 0;
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding: 8px;
  background: rgba(11, 31, 42, 0.92);
  border: 1px solid rgba(244, 234, 208, 0.2);
  border-radius: 12px;
  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5);
}

.dropdown-menu-item {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 4px 14px 4px 4px;
  border: none;
  border-radius: 8px;
  background: none;
  color: #f4ead0;
  font: 600 15px/1.2 "Segoe UI", sans-serif;
  white-space: nowrap;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}

.dropdown-menu-item:active {
  background: rgba(244, 234, 208, 0.1);
}

.dropdown-menu-item.active {
  color: #4cd964;
}

.dropdown-menu-item-icon {
  position: relative;
  flex: 0 0 auto;
  width: 40px;
  height: 40px;
}

/* Sail-cooldown readout, absorbed into the menu toggle itself now that
   there's no top-bar SailPanel to show it (see DropdownMenu.tsx) -- a green
   overlay on the medallion, blended so it tints rather than just sitting on
   top, whose opacity DropdownMenu.tsx drives directly from cooldown
   progress (0 at the start of the recharge, up to 0.7 as it nears ready).
   Once ready, .ready takes over with its own pulsing opacity/glow instead
   of the inline progress opacity. */
.dropdown-menu-sail-tint {
  position: absolute;
  inset: 0;
  border-radius: 50%;
  background: #34d058;
  mix-blend-mode: color;
  pointer-events: none;
}

.dropdown-menu-sail-tint.ready {
  animation: dropdown-menu-sail-pulse 1.1s ease-in-out infinite;
}

@keyframes dropdown-menu-sail-pulse {
  0%,
  100% {
    opacity: 0.4;
    box-shadow: 0 0 0 0 rgba(76, 217, 100, 0.6);
  }
  50% {
    opacity: 0.85;
    box-shadow: 0 0 14px 4px rgba(76, 217, 100, 0.6);
  }
}

/* Sail vote button (see controller/components/SailVoteButton.tsx), pinned
   to the map view's bottom-center via .map-sail-vote-anchor below -- only
   rendered once every captain's destination pick matches. Same
   sail-ready/sail-not-ready plaque art the old top-bar SailPanel button
   used (see SailStatusBar.tsx's identical use on the screen side), without
   that widget's overlapping progress-bar image since the cooldown readout
   itself lives in DropdownMenu's toggle/Map-item glow instead. */
.map-sail-btn {
  display: block;
  width: min(140px, 38vw);
  padding: 0;
  border: none;
  background: none;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}

.map-sail-btn:disabled {
  cursor: default;
  opacity: 0.6;
}

.map-sail-btn-img {
  display: block;
  width: 100%;
  height: auto;
}

.map-sail-btn.ready {
  animation: map-sail-btn-pulse 1.1s ease-in-out infinite;
}

@keyframes map-sail-btn-pulse {
  0%,
  100% {
    filter: drop-shadow(0 0 0 rgba(76, 217, 100, 0));
  }
  50% {
    filter: drop-shadow(0 0 8px rgba(76, 217, 100, 0.9));
  }
}

.crew-nav-btn {
  flex: 0 0 auto;
  width: 44px;
  height: 44px;
  font-size: 18px;
  color: #0b1f2a;
  background: #d4af37;
  border: none;
  border-radius: 10px;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}

.crew-nav-btn:active {
  background: #b8952c;
}

/* Position indicator for PageSwiper's pages -- swiping the mid section is
   the main way to switch, these dots are secondary (tap to jump directly). */
.page-dots {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: 8px;
}

.page-dot {
  width: 9px;
  height: 9px;
  padding: 0;
  border: none;
  border-radius: 50%;
  background: rgba(244, 234, 208, 0.3);
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}

.page-dot.active {
  background: #d4af37;
}

.systems-panel {
  width: 100%;
  height: 100%;
  overflow-y: auto;
  padding: 16px;
}

.resource-summary {
  display: flex;
  justify-content: center;
  gap: 10px;
  flex-wrap: wrap;
  font-size: clamp(12px, 3.2vw, 15px);
  font-weight: 600;
  margin-bottom: 12px;
}

.resource-item {
  padding: 4px 10px;
  background: rgba(244, 234, 208, 0.06);
  border: 1px solid rgba(244, 234, 208, 0.15);
  border-radius: 8px;
}

.manpower-summary {
  font-size: clamp(16px, 4vw, 20px);
  font-weight: 700;
  color: #d4af37;
  text-align: center;
  margin-bottom: 16px;
}

.systems-list {
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.system-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 14px;
  background: rgba(244, 234, 208, 0.06);
  border: 1px solid rgba(244, 234, 208, 0.15);
  border-radius: 10px;
}

.system-name {
  flex: 1 1 auto;
  font-size: clamp(14px, 3.6vw, 17px);
  font-weight: 600;
}

.power-pips {
  flex: 0 0 auto;
  display: flex;
  gap: 4px;
}

.power-pip {
  width: 14px;
  height: 14px;
  border-radius: 3px;
  background: rgba(244, 234, 208, 0.15);
  border: 1px solid rgba(244, 234, 208, 0.3);
}

.power-pip.filled {
  background: #d4af37;
  border-color: #d4af37;
}

.power-controls {
  flex: 0 0 auto;
  display: flex;
  gap: 6px;
}

.power-btn {
  width: 32px;
  height: 32px;
  font-size: 16px;
  font-weight: 700;
  color: #0b1f2a;
  background: #d4af37;
  border: none;
  border-radius: 8px;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}

.power-btn:active {
  background: #b8952c;
}

.power-btn:disabled {
  background: #3a4250;
  color: #aab2bf;
  cursor: default;
}

.ship-viewport {
  width: 100%;
  height: 100%;
  position: relative;
  overflow: hidden;
  touch-action: none;
}

/* Combat cue on the controller's own ship view (see App.tsx's
   state.activeCombat / BoatPage.tsx) -- a red inset glow around the boat
   page's edges so a captain notices they're under attack immediately,
   without needing to read the hull bars. Its own layer, on top of
   .ship-viewport's canvas, rather than a box-shadow directly on
   .ship-viewport -- that element's canvas child paints over its own
   background/shadow, which would otherwise hide it completely. */
.combat-glow {
  position: absolute;
  inset: 0;
  pointer-events: none;
  box-shadow: inset 0 0 0 0 rgba(220, 20, 20, 0);
  transition: box-shadow 0.3s ease;
}

.combat-glow.active {
  box-shadow: inset 0 0 60px 12px rgba(220, 20, 20, 0.7);
}

/* This captain's own combat glow, global (see App.tsx) -- same effect as
   .combat-glow above, fixed to the viewport instead of one boat page's own
   canvas so it stays up regardless of which view/boat is on screen. Roughly
   half the alpha of .combat-glow.active (0.35 vs 0.7) -- less overpowering
   for something that's now always-on rather than only while looking at
   your own ship. */
.combat-glow-global {
  position: fixed;
  inset: 0;
  z-index: 45;
  pointer-events: none;
  box-shadow: inset 0 0 0 0 rgba(220, 20, 20, 0);
  transition: box-shadow 0.3s ease;
}

.combat-glow-global.active {
  box-shadow: inset 0 0 60px 12px rgba(220, 20, 20, 0.35);
}

/* Travel cutscene (see TravelCutscene.tsx): fullscreen, non-interactive --
   sits above ordinary content/the combat glow but below .dialog-overlay
   (z-index 50) so an arrival dialog, if any, stays readable/usable rather
   than getting covered by the cutscene. Opacity is driven frame-by-frame
   from JS (fade in/out), not a CSS transition -- it shares the same rAF
   loop already redrawing the canvas each frame. The inner canvas is sized
   to the map's native resolution and framed via a JS-computed CSS
   transform (fitNodesFrame), the same translate+scale approach
   panZoomViewport.ts uses for the interactive map. */
.travel-cutscene {
  position: fixed;
  inset: 0;
  z-index: 46;
  pointer-events: none;
  overflow: hidden;
  /* Wood table underneath the map -- fitNodesFrame rarely scales the map
     canvas to exactly cover the viewport (its 1448x1086 aspect ratio only
     matches the screen/controller by coincidence), so this shows through
     past the map's own edges rather than flat black. */
  background-image: linear-gradient(rgba(0, 0, 0, 0.3), rgba(0, 0, 0, 0.3)), url("/img/map/table.png");
  background-size: cover;
  background-position: center;
  opacity: 0;
}

.travel-cutscene-canvas {
  position: absolute;
  top: 0;
  left: 0;
  transform-origin: 0 0;
  box-shadow: 0 20px 60px rgba(0, 0, 0, 0.65);
}

/* Boat page: ShipViewport plus SystemsUI overlaid on top of it (see
   BoatPage.tsx/SystemsUIOverlay.tsx), instead of side by side, with the
   manpower bar stacked below that whole stack as its own row. */
.boat-page {
  width: 100%;
  height: 100%;
  display: flex;
  flex-direction: column;
}

/* Holds ShipViewport/combat-glow/systems-ui-overlay -- the part of
   .boat-page that behaves as before (systems UI floats over the canvas,
   pinned to this box's own bottom, rather than taking its own space).
   Flexes to fill whatever height .manpower-bar-row below doesn't need. */
.boat-viewport-stack {
  position: relative;
  flex: 1 1 auto;
  min-height: 0;
}

/* Pinned to the bottom, full width -- height isn't fixed, it follows from
   SystemsUI's own rendered height once its width is measured (same
   UNBOUNDED_HEIGHT approach as LoadoutBar/LoadoutGrid), so it can run
   taller than the boat view itself rather than being shrunk to fit.
   pointer-events: auto (and touch-action: none) since a vertical swipe on
   a system's icon column adjusts its manpower -- see
   SystemUIOverlayController.tsx. That does mean taps landing in this
   overlay's box no longer reach the ship canvas underneath for crew
   movement, even over its transparent gaps -- an accepted tradeoff of
   making this area interactive rather than a pure status display. */
.systems-ui-overlay {
  position: absolute;
  left: 0;
  bottom: 0;
  width: 100%;
  /* Avoids the few px of baseline gap a bare inline <canvas> leaves below
     itself in a block container -- the screen's own fork sidesteps this
     with a dedicated CSS-module wrapper div, but the controller doesn't
     use CSS modules elsewhere, so this is done directly on the shared class. */
  display: flex;
  pointer-events: auto;
  touch-action: none;
}

/* Viewing a teammate's ship (see TeammateBoatPage.tsx) -- no swipe gesture
   to claim here, so pointer events fall through to the ship viewport
   underneath instead, keeping pan/zoom usable even over this box. */
.systems-ui-overlay.read-only {
  pointer-events: none;
  touch-action: auto;
}

/* The paged mid-section (map/boat/systems) -- takes over the same
   fixed-header/variable-mid/fixed-bottom slot .ship-viewport used to own
   directly. All pages stay mounted side by side and are shown one at a
   time via .page-swiper-track's transform, not display:none, so each
   page's own state (e.g. the map's pan/zoom) survives switching away.
   Purely controlled by activeIndex (see PageSwiper.tsx) -- no gesture
   handling of its own, so no touch-action override needed either. */
.page-swiper {
  flex: 1 1 auto;
  min-height: 0;
  width: 100%;
  position: relative;
  overflow: hidden;
}

.page-swiper-track {
  display: flex;
  height: 100%;
}

.page-swiper-page {
  flex: 0 0 auto;
  height: 100%;
  overflow: hidden;
}

/* Floating counterpart to .dropdown-menu (top-left) -- boat view only, same
   fixed-to-the-corner treatment now that there's no .top-bar row for either
   of them to sit in (see App.tsx/DropdownMenu.tsx, which absorbed the old
   SailPanel). Replaces the old .boat-swipe-toggle-float (arrows + label) --
   see FleetBar.tsx. Runs vertically down the right edge, one icon per boat
   in the scene. */
.fleet-bar {
  position: fixed;
  top: 10px;
  right: 10px;
  z-index: 60;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
}

.fleet-bar-icon {
  position: relative;
  flex: 0 0 auto;
  width: 40px;
  height: 40px;
  padding: 0;
  border-radius: 50%;
  border: 2px solid transparent;
  background: rgba(11, 31, 42, 0.55);
  cursor: pointer;
  overflow: hidden;
  -webkit-tap-highlight-color: transparent;
}

.fleet-bar-icon-self,
.fleet-bar-icon-ally {
  border-color: rgba(76, 217, 100, 0.5);
}

.fleet-bar-icon-enemy {
  border-color: rgba(220, 20, 20, 0.5);
}

.fleet-bar-icon.active {
  border-width: 3px;
}

.fleet-bar-icon-self.active,
.fleet-bar-icon-ally.active {
  border-color: #4cd964;
}

.fleet-bar-icon-enemy.active {
  border-color: #dc1414;
}

.fleet-bar-icon-img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* Locked-destination badge -- "M" (marked), same green the hull pips use,
   rather than new art (see FleetShipView.destinationLocked). */
.fleet-bar-icon-locked {
  position: absolute;
  right: -1px;
  bottom: -1px;
  width: 14px;
  height: 14px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  background: #4cd964;
  border: 2px solid #0b1f2a;
  font: 700 9px/1 "Segoe UI", sans-serif;
  color: #0b1f2a;
}

/* Pause/play toggle (see controller/components/PauseButton.tsx) -- top
   center, reachable from every viewMode, same z-index tier as
   .dropdown-menu/.fleet-bar. Any captain can tap it; the effect is global. */
.pause-btn {
  position: fixed;
  top: 10px;
  left: 50%;
  transform: translateX(-50%);
  z-index: 60;
  width: 48px;
  height: 48px;
  padding: 0;
  border: none;
  background: none;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}

.pause-btn-img {
  display: block;
  width: 100%;
  height: 100%;
}

/* Wraps SailVoteButton.tsx's own content -- pinned bottom-center of the map
   view via .map-sail-vote-anchor below, its only placement now that the
   fleet bar no longer shows it. */
.sail-vote {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
}

.sail-vote-hint {
  max-width: 120px;
  text-align: center;
  font: 600 11px/1.3 "Segoe UI", sans-serif;
  color: #f4ead0;
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.8);
}

/* Pins SailVoteButton.tsx to the map view's own bottom-center, same spot
   the original map-only SailButton used to sit. */
.map-sail-vote-anchor {
  position: absolute;
  left: 50%;
  bottom: 16px;
  transform: translateX(-50%);
  z-index: 20;
}

/* Always-on hull HUD (see App.tsx/HullPipColumn.tsx) -- pinned to the
   vertical center of each edge regardless of viewMode, one column per ship.
   Below .dropdown-menu/.fleet-bar's z-index (60) so those still win if a
   column ever ran tall enough to reach the top corners; pointer-events:
   none since this is a pure status readout, not something a tap should
   ever land on instead of the boat view underneath it. */
.hull-pips-left,
.hull-pips-right {
  position: fixed;
  top: 50%;
  transform: translateY(-50%);
  z-index: 55;
  display: flex;
  gap: 6px;
  pointer-events: none;
}

.hull-pips-left {
  left: 6px;
}

.hull-pips-right {
  right: 6px;
}

.hull-pip-column {
  display: flex;
  flex-direction: column;
  gap: clamp(1px, 0.3vh, 3px);
  align-items: center;
}

.hull-pip {
  width: clamp(5px, 1.4vh, 9px);
  height: clamp(5px, 1.4vh, 9px);
  box-sizing: border-box;
  border-radius: 50%;
  background: transparent;
}

.hull-pip-column-green .hull-pip {
  border: 1px solid rgba(76, 217, 100, 0.5);
}

.hull-pip-column-green .hull-pip.filled {
  background: #4cd964;
  border-color: #4cd964;
}

.hull-pip-column-red .hull-pip {
  border: 1px solid rgba(220, 20, 20, 0.5);
}

.hull-pip-column-red .hull-pip.filled {
  background: #dc1414;
  border-color: #dc1414;
}

/* PageSwiper (see App.tsx's boatPages) nested inside .view-stack-page,
   which is position:absolute/inset:0 rather than the flex column
   .page-swiper's own flex:1 1 auto normally expects to fill -- so unlike
   its other use (the map/boat/systems swiper that never shipped), this one
   needs an explicit height to actually fill that box. */
.boat-swiper {
  width: 100%;
  height: 100%;
}

/* Takes over the flex:1 1 auto "variable middle section" role .page-swiper
   used to have -- each view is absolutely positioned to fill it, with only
   the active one shown (.hidden), instead of being laid out side by side
   and swiped between. */
.view-stack {
  flex: 1 1 auto;
  min-height: 0;
  width: 100%;
  position: relative;
  overflow: hidden;
}

.view-stack-page {
  position: absolute;
  inset: 0;
}

.sail-widget {
  display: flex;
  flex-direction: column;
  align-items: center;
}

.sail-icon {
  display: block;
  width: min(100px, 27.5vw);
  height: auto;
}

.sail-progress {
  position: relative;
  width: min(50px, 14vw);
  margin-top: -10px;
}

.sail-progress-img {
  display: block;
  width: 100%;
  height: auto;
}

.sail-progress-fill {
  position: absolute;
  top: 0;
  left: 0;
}

/* Screen page only now -- the controller's own sail readout moved into
   DropdownMenu's toggle/Map-item glow (dropdown-menu-sail-tint) plus the
   map-pinned Set Sail button (map-sail-btn below), neither of which use
   this icon+progress-bar combo, just the plaque art directly. Floats
   independently at the bottom-right of the screen. */
.sail-status-bar {
  position: fixed;
  right: 16px;
  bottom: 16px;
  z-index: 10;
  padding: 10px 16px 6px;
}

/* Per-player weapons loadout panel (weapons-panel.png), pinned to the top
   of that player's own ship-window frame -- one per player, sized to a
   fraction of the frame the same way .ship-window-systems-ui is (see
   WEAPONS_LOADOUT_*_RATIO in ShipWindow.tsx). Flush with the top edge --
   full width, no inset. */
.ship-window-weapons-loadout {
  position: absolute;
  top: 0;
  left: 50%;
  transform: translateX(-50%);
  pointer-events: none;
}

.weapons-loadout {
  position: relative;
}

/* Fleet-wide manpower bar (see controller/components/BoatPage.tsx/
   ManpowerBar.tsx) -- its own row below .boat-viewport-stack (and so below
   .systems-ui-overlay, which floats pinned to that stack's bottom edge),
   taking its own space rather than overlaying the ship canvas. */
.manpower-bar-row {
  flex: 0 0 auto;
  width: 100%;
}

/* A visibly darker panel than .top-bar/.loadout-bar's own barely-there
   divider tint -- this row has no art of its own (just bare pills), so it
   needs a backdrop of its own to read as a section rather than floating
   loose over the ship canvas. */
.manpower-bar {
  width: 100%;
  display: flex;
  align-items: center;
  gap: 4px;
  padding: 8px 16px;
  background: rgba(11, 31, 42, 0.55);
}

.manpower-bar-pill {
  display: block;
  flex: 1 1 0;
  min-width: 0;
  aspect-ratio: 69 / 26;
}

/* Controller's bottom bar (see LoadoutGrid.tsx) -- height isn't fixed here,
   it just follows from the grid's own rendered height once its width is
   measured, same "fixed" row role .sail-panel used to have at the bottom. */
.loadout-bar {
  flex: 0 0 auto;
  width: 100%;
  display: flex;
  justify-content: center;
  background: rgba(244, 234, 208, 0.06);
  border-top: 1px solid rgba(244, 234, 208, 0.15);
}

/* 2x2 alternative to the loadout-slots strip -- see LoadoutGrid.tsx. Sits
   inside the same .loadout-bar wrapper/slot as LoadoutBar. */
.loadout-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  grid-template-rows: repeat(2, 1fr);
}

.loadout-grid-cell {
  position: relative;
  padding: 0;
  border: none;
  background: none;
  font: inherit;
  color: #f4ead0;
  cursor: default;
  touch-action: none;
  -webkit-tap-highlight-color: transparent;
}

.loadout-grid-cell.ready {
  cursor: pointer;
  filter: drop-shadow(0 0 6px rgba(76, 217, 100, 0.75));
}

.loadout-grid-cell-bg {
  display: block;
  width: 100%;
  height: 100%;
}

/* Tune inset against the actual art -- loadout-slot.png's gold frame eats
   into the edges, so the charge bar/name/markers sit inside that border. */
.loadout-grid-cell-content {
  position: absolute;
  inset: 11% 7% 18% 7%;
  display: flex;
  align-items: stretch;
  justify-content: space-between;
  gap: 6%;
}

.loadout-grid-charge-bar {
  flex: 0 0 auto;
  width: clamp(5px, 4%, 12px);
  display: flex;
  flex-direction: column-reverse;
  background: rgba(0, 0, 0, 0.5);
  border: 1px solid rgba(212, 175, 55, 0.6);
  border-radius: 3px;
  overflow: hidden;
  margin: 6% 0% 4% 0%;
}

.loadout-grid-charge-fill {
  width: 100%;
  background: #d4af37;
  transition: height 0.2s linear;
}

.loadout-grid-cell.ready .loadout-grid-charge-fill {
  background: #4cd964;
}

.loadout-grid-info {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
}

.loadout-grid-name {
  font-family: "Segoe UI", system-ui, sans-serif;
  font-size: clamp(10px, 3.4vw, 15px);
  font-weight: 700;
  line-height: 1.2;
}

.loadout-grid-cell.ready .loadout-grid-name {
  color: #4cd964;
}

/* Locked-target highlight (see CombatWeaponState.targeted) -- takes over
   .ready's green the instant a target's been picked for this slot, whether
   it's about to fire right away or is still charging and will fire on its
   own once it finishes (see SetWeaponTargetMessage). Rules come after
   .ready's own so they win on the (momentary, never actually broadcast)
   overlap where both classes could apply. */
.loadout-grid-cell.targeted {
  cursor: pointer;
  filter: drop-shadow(0 0 6px rgba(255, 214, 10, 0.85));
}

.loadout-grid-cell.targeted .loadout-grid-charge-fill {
  background: #ffd60a;
}

.loadout-grid-cell.targeted .loadout-grid-name {
  color: #ffd60a;
}

/* Armed, waiting on a room tap over on an enemy's own boat page (see
   LoadoutGrid.tsx's armingSystemId/App.tsx's targetingSystemId) -- a
   pulsing white glow, distinct from .ready's static green and .targeted's
   yellow plaque swap, since this is neither "can act" nor "locked", just
   "waiting on you specifically, go look at an enemy". */
.loadout-grid-cell.arming {
  animation: loadout-grid-cell-arming-pulse 1.1s ease-in-out infinite;
}

@keyframes loadout-grid-cell-arming-pulse {
  0%,
  100% {
    filter: drop-shadow(0 0 0 rgba(244, 234, 208, 0));
  }
  50% {
    filter: drop-shadow(0 0 8px rgba(244, 234, 208, 0.95));
  }
}

.loadout-grid-manpower {
  flex: 0 0 auto;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 4px;
}

.loadout-grid-manpower-marker {
  flex: none;
  width: clamp(6px, 2vw, 11px);
  height: clamp(6px, 2vw, 11px);
  border-radius: 50%;
  border: 1px solid #d4af37;
  background: rgba(0, 0, 0, 0.4);
}

.loadout-grid-manpower-marker.filled {
  background: #4cd964;
  border-color: #4cd964;
}

.hull-gauge canvas {
  display: block;
}

.weapons-loadout-img {
  display: block;
  width: 100%;
  height: 100%;
}

/* Tune this against the actual art to align with weapons-panel.png's 4 dark
   slots -- top/left/width/height only, the 4 children below always split
   it into exact quarters. */
.weapons-loadout-slots {
  position: absolute;
  top: 12%;
  left: 3%;
  width: 93%;
  height: 46%;
  display: flex;
}

.weapons-loadout-slot {
  flex: 0 0 25%;
  width: 25%;
  display: flex;
  flex-direction: row;
  align-items: stretch;
  justify-content: space-between;
  /* gap: 4%; */
  padding: 2.2% 2.2%;
  color: #f4ead0;
  pointer-events: none;
  border-radius: 6px;
}

/* Controller only (see WeaponsLoadout's onSlotClick/LoadoutBar.tsx) --
   pointer-events stays "none" everywhere else (the screen's own copy is a
   pure status display). */
.weapons-loadout-slot.clickable {
  pointer-events: auto;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}

.weapons-loadout-slot.clickable.ready {
  background: rgba(212, 175, 55, 0.35);
  box-shadow: 0 0 0 2px #d4af37 inset;
}

/* Locked-target highlight (see CombatWeaponState.targeted) -- unlike
   .clickable.ready above, this isn't gated on .clickable: it's meant to be
   visible on the screen's own (non-interactive) copy just as much as the
   controller's, since that's the one place a captain's targeting isn't
   hidden behind TargetSelectOverlay's full-screen modal while it's armed
   and waiting to charge. Comes after .ready's own rules so it wins on the
   (momentary, never actually broadcast) overlap where both could apply. */
.weapons-loadout-slot.targeted {
  background: rgba(255, 214, 10, 0.35);
  box-shadow: 0 0 0 2px #ffd60a inset;
}

.weapons-loadout-slot.targeted .weapons-loadout-charge-fill {
  background: #ffd60a;
}

.weapons-loadout-slot.targeted .weapons-loadout-slot-name {
  color: #ffd60a;
}

/* Vertical gauge, same fill-from-bottom idea as .combat-charge-fill, just
   oriented for a narrow slot instead of a wide bar. */
.weapons-loadout-charge-bar {
  flex: 0 0 auto;
  width: clamp(4px, 1.1vw, 10px);
  display: flex;
  flex-direction: column-reverse;
  background: rgba(0, 0, 0, 0.5);
  border: 1px solid rgba(212, 175, 55, 0.6);
  border-radius: 3px;
  overflow: hidden;
}

.weapons-loadout-charge-fill {
  width: 100%;
  background: #d4af37;
  transition: height 0.2s linear;
}

.weapons-loadout-slot-info {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
}

/* Segoe UI, not PiratesBay -- the decorative font loses its edges at this
   small a size (see weapon-slot-name font size history), and Segoe UI is
   already the game's go-to legible sans for small UI text elsewhere
   (.dialog-hint, .event-combat-note, .combat-ships, .name-label). */
.weapons-loadout-slot-name {
  font-family: "Segoe UI", system-ui, sans-serif;
  font-size: clamp(7.2px, 1.008vw, 12.19px);
  font-weight: 600;
  line-height: 1.2;
}

.weapons-loadout-manpower-markers {
  flex: 0 0 auto;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: clamp(1px, 0.3vw, 3px);
}

.weapons-loadout-manpower-marker {
  flex: none;
  width: clamp(4px, 0.8vw, 9px);
  height: clamp(4px, 0.8vw, 9px);
  border-radius: 50%;
  border: 1px solid #d4af37;
  background: rgba(0, 0, 0, 0.4);
}

.weapons-loadout-manpower-marker.filled {
  background: #4cd964;
  border-color: #4cd964;
}

