/* ===== Stellar Frontier: RTS — styles ===== Palette carried over from the original Stellar Frontier's style.css so the two games read as the same universe. Everything below the :root block is new — the original's layout was built for a turn-based tab/panel UI and doesn't fit a real-time view. */ :root { --bg: #05070f; --bg-grad-1: #0a1028; --bg-grad-2: #060814; --panel: #0e1530; --panel-2: #141d42; --border: #243162; --accent: #4fd1ff; --accent-2: #8a6bff; --good: #4ade80; --bad: #f87171; --warn: #fbbf24; --text: #dce6ff; --muted: #8593c4; --gold: #ffd166; } * { box-sizing: border-box; margin: 0; padding: 0; } html, body { height: 100%; background: radial-gradient(ellipse at top, var(--bg-grad-1), var(--bg-grad-2)); color: var(--text); font-family: "Segoe UI", system-ui, sans-serif; overflow: hidden; } #app { display: flex; flex-direction: column; height: 100%; } /* TWO FIXED ROWS. The topbar carries a lot (resources, clock, chips, volume, mute, pause/galaxy/save/load/home/help). It used to be ONE wrapping row, so as the resource readout grew or shrank the control buttons flipped between line 1 and line 2 — a jarring up/down jump. Now it's a column: an INFO row (line 1) that stays a single line (the resource readout scrolls sideways instead of wrapping), and a CONTROLS row (line 2) that is always pinned below it, so the buttons never move. */ #topbar { display: flex; flex-direction: column; align-items: stretch; gap: 6px; padding: 8px 12px; background: var(--panel); border-bottom: 1px solid var(--border); } .topbar-row { display: flex; align-items: center; gap: 8px 12px; min-width: 0; /* let flex children shrink instead of forcing the row wider */ } /* Line 1 never wraps — a long commodity list scrolls within the resource readout, keeping the row's height constant so the controls below can't jump. Every fixed item keeps its size and its text on one line; only the resource readout shrinks (and scrolls) to absorb the squeeze — otherwise the title text itself would wrap to several lines and grow the row. */ .topbar-info { flex-wrap: nowrap; } .topbar-info > * { flex: 0 0 auto; white-space: nowrap; } .topbar-info > .resources { flex: 1 1 auto; min-width: 0; } /* Line 2 keeps the buttons together; on a very narrow window they may wrap among themselves, but they always live on their own row, never mixed in with the resources. */ .topbar-controls { flex-wrap: wrap; } #topbar .title { font-weight: 700; letter-spacing: 0.08em; color: var(--accent); } .resources { display: flex; gap: 14px; font-size: 0.85rem; color: var(--muted); flex: 1 1 auto; min-width: 0; /* Stay on ONE line and scroll sideways when the commodity list is long, so the info row's height never changes and the control row below it can't be shoved up/down. */ flex-wrap: nowrap; overflow-x: auto; overflow-y: hidden; scrollbar-width: thin; } .resources::-webkit-scrollbar { height: 4px; } .resources::-webkit-scrollbar-thumb { background: var(--border); border-radius: 2px; } .resources > * { flex: 0 0 auto; white-space: nowrap; } /* each readout keeps its width; the row scrolls, not wraps */ .resources span { color: var(--text); } /* Supply readout warns amber at the cap, flashes red for the ~800ms after a supply-blocked production attempt (see main.js's supplyBlockedUntil). */ .resources .supply.at-cap { color: var(--warn); } .resources .supply.blocked { color: var(--bad); } /* Odyssey universal-credit balance — galaxy-wide, so it reads gold like the clock. */ .resources .credits { color: var(--gold); font-variant-numeric: tabular-nums; } /* Odyssey industrial-Power gauge — reads like supply, warns amber when factories out-draw the Reactors and production throttles. */ .resources .power { font-variant-numeric: tabular-nums; } .resources .power.at-cap { color: var(--warn); } /* Neighbour stance chip — green when friendly, amber neutral, red hostile. */ .resources .relation { text-transform: capitalize; } .resources .relation.friendly { color: var(--good, #4ade80); } .resources .relation.neutral { color: var(--warn); } .resources .relation.hostile { color: var(--bad); } /* Commodity flow ledger (docs/improvement-proposals.md "Commodity flow ledger"): a resource span reads red only once it's BOTH net-negative and something live actually depends on it (hud.js liveConsumers) — a stockpile drifting down with nothing standing on it yet stays the ordinary color. Hover any resource for the +X/min · -X/min tooltip that drives this. */ .resources span.deficit { color: var(--bad); } /* Galaxy toasts — brief top-center alerts about worlds you're not on. #galaxyToast is a STACK (flex column) holding up to 3 toasts, so a routine "Researched…" no longer evicts a one-shot clickable "colony has fallen — click to retake" (overlays.js showGalaxyToast). */ .galaxy-toast-stack { position: absolute; top: 64px; left: 50%; transform: translateX(-50%); display: flex; flex-direction: column; align-items: center; gap: 6px; z-index: 35; pointer-events: none; /* above .starmap-overlay (30) so "colony under attack/lost" alerts stay visible with the starmap open */ } .galaxy-toast-stack.hidden { display: none; } .galaxy-toast { padding: 8px 16px; border-radius: 8px; font-size: 0.9rem; font-weight: 600; background: #12203a; border: 1px solid #2b3a5e; color: var(--text); box-shadow: 0 4px 16px rgba(0,0,0,0.5); pointer-events: none; transition: opacity 0.3s; } .galaxy-toast.warn { border-color: var(--warn); color: var(--warn); } .galaxy-toast.bad { border-color: var(--bad); color: var(--bad); } .galaxy-toast.good { border-color: var(--good, #4ade80); color: var(--good, #4ade80); } /* An actionable alert (e.g. "colony under attack — click to defend"): the base toast is pointer-events:none, so re-enable clicks and cue it as interactive. */ .galaxy-toast.clickable { pointer-events: auto; cursor: pointer; } .galaxy-toast.clickable:hover { background: #1a2c4d; box-shadow: 0 4px 20px rgba(0,0,0,0.6); } /* Control-group chips ("1:8 2:3") — top-left over the field, so bound groups are visible and (on touch, which has no number row) tappable to recall. Rebuilt by hud.js each tick. */ .group-chips { position: absolute; top: 10px; left: 10px; display: flex; gap: 6px; z-index: 15; pointer-events: none; } .group-chips.hidden { display: none; } .group-chip { pointer-events: auto; font-family: inherit; cursor: pointer; padding: 3px 9px; border-radius: 6px; font-size: 0.8rem; font-weight: 700; background: #12203a; border: 1px solid #2b3a5e; color: var(--text); } .group-chip:hover { background: #1a2c4d; border-color: var(--accent, #4fd1ff); } /* Transient UI hint (touch "why is this greyed" feedback) — bottom-center, its own channel so it never competes with the galaxy-alert stack. */ .ui-hint { position: absolute; bottom: 24px; left: 50%; transform: translateX(-50%); padding: 8px 16px; border-radius: 8px; font-size: 0.9rem; font-weight: 600; background: #12203a; border: 1px solid var(--warn); color: var(--warn); box-shadow: 0 4px 16px rgba(0,0,0,0.5); z-index: 22; pointer-events: none; max-width: 80%; } .ui-hint.hidden { display: none; } /* Starmap overlay (starmap.js): a full-screen galaxy map with world nodes on a ring, coloured by your relationship to each. */ .starmap-overlay { position: absolute; inset: 0; z-index: 30; background: rgba(3, 6, 15, 0.92); display: flex; flex-direction: column; align-items: center; } .starmap-overlay.hidden { display: none; } .chip-btn.hidden { display: none; } .starmap-head { text-align: center; margin-top: 24px; } .starmap-head h2 { margin: 0 0 4px; color: var(--accent); letter-spacing: 0.08em; } .starmap-head p { margin: 0; color: var(--muted); font-size: 0.9rem; } .starmap-field { position: relative; flex: 1; width: min(760px, 92%); align-self: center; } .starmap-world { position: absolute; transform: translate(-50%, -50%); display: flex; flex-direction: column; align-items: center; gap: 2px; min-width: 96px; padding: 8px 10px; cursor: pointer; background: #0e1830; border: 2px solid #2b3a5e; border-radius: 10px; color: var(--text); box-shadow: 0 2px 10px rgba(0,0,0,0.4); } .starmap-world:hover { border-color: var(--accent); } .starmap-world .sm-ico { font-size: 20px; line-height: 1; } /* faction emblem (data.js FACTIONS) */ .starmap-world .sm-name { font-weight: 600; font-size: 0.9rem; } .starmap-world .sm-sub { font-size: 0.72rem; color: var(--muted); } /* Industry/Tech badge — the world's economic character at a glance. */ .starmap-world .sm-stats { font-size: 0.68rem; color: var(--muted); opacity: 0.85; font-variant-numeric: tabular-nums; margin-top: 1px; } /* Deposit icons + yield, plus the world's rule-modifier label when it has one — the same geography read the skirmish select cards already give freely (setup.js's mod-note). A world's full modifier label (e.g. Glacius's "Frozen ground: all units 10% slower; ice fields flank a central lane") can run to 6+ wrapped lines at this width, which used to make that one card far taller than its neighbours on the ring layout below — tall enough to overlap and obscure them. Clamping to 2 lines bounds every card to roughly the same height regardless of label length; the full, untruncated text is still one hover away via the element's title (starmap.js). */ .starmap-world .sm-deps { font-size: 0.66rem; color: var(--muted); opacity: 0.8; margin-top: 2px; max-width: 160px; text-align: center; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; } .starmap-world.seat { border-color: var(--accent); box-shadow: 0 0 14px rgba(79,209,255,0.5); } .starmap-world.seat .sm-sub { color: var(--accent); } .starmap-world.colony { border-color: var(--good, #4ade80); } .starmap-world.colony .sm-sub { color: var(--good, #4ade80); } .starmap-world.contested { border-color: var(--bad); opacity: 0.85; } .starmap-world.contested .sm-sub { color: var(--bad); } /* Conquered — its neighbour's capital is razed (Domination win progress). */ .starmap-world.pacified { border-color: var(--gold, #fbbf24); } .starmap-world.pacified .sm-sub { color: var(--gold, #fbbf24); } /* Live colony alert badges (game.colonyAlerts, starmap.js renderStarmap/alertBadge): a world currently on fire — or one still waiting to be retaken — reads as urgent at a glance instead of looking identical to a healthy colony. Declared after the status classes above so it wins the border/glow cascade on an equally-specific selector (e.g. a colony under attack): the live alert is the more urgent fact right now. */ .starmap-world.alert-attack { border-color: var(--bad); box-shadow: 0 0 14px rgba(255,80,80,0.45); animation: sm-alert-pulse 1.6s ease-in-out infinite; } .starmap-world.alert-attack .sm-sub { color: var(--bad); font-weight: 600; } .starmap-world.alert-fallen { border-color: var(--bad); opacity: 0.9; } .starmap-world.alert-fallen .sm-sub { color: var(--bad); font-weight: 600; } @keyframes sm-alert-pulse { 0%, 100% { box-shadow: 0 0 14px rgba(255,80,80,0.45); } 50% { box-shadow: 0 0 22px rgba(255,80,80,0.8); } } /* Garrison line — CC count + combat-unit supply on a world you currently hold (starmap.js garrisonOf), styled like the industry/tech badge above it. */ .starmap-world .sm-garrison { font-size: 0.68rem; color: var(--muted); opacity: 0.9; margin-top: 1px; font-variant-numeric: tabular-nums; } /* Colony standing orders + Freight Lanes summary (starmap.js renderColonyOrders/ renderLaneOverlay) — reuse the market-head/market-row/market-btn idiom from the selection panel (hudSelection.js) so a starmap-side list needs no styling of its own beyond width/spacing. */ .starmap-side { width: min(760px, 92%); align-self: center; margin: 4px 0; } .starmap-side .market-head { margin: 10px 0 4px; } .starmap-foot { color: var(--muted); font-size: 0.8rem; margin-bottom: 18px; } /* Surrender — the only way out of the play-forever Odyssey. Muted by default (it's a rare, deliberate act), turning red once armed for the confirm click. */ .starmap-surrender { margin-bottom: 20px; padding: 7px 16px; border-radius: 8px; cursor: pointer; background: transparent; color: var(--muted); border: 1px solid #2b3a5e; font-size: 0.82rem; font-weight: 600; transition: all 0.15s; } .starmap-surrender:hover { color: var(--text); border-color: #3a4c74; } .starmap-surrender.armed { color: var(--bad); border-color: var(--bad); background: rgba(255,80,80,0.08); } /* Tech & Industry Chart overlay (techChart.js): the unlock ladder laid out as tier COLUMNS — every building, unit gate and tech/doctrine node, prereq depth left-to-right, coloured by whether it's built/researched, buildable right now, or still locked. Same full-screen overlay idiom as .starmap-overlay just above (own element, topbar button + hotkey + Esc). */ .techchart-overlay { position: absolute; inset: 0; z-index: 30; background: rgba(3, 6, 15, 0.94); display: flex; flex-direction: column; align-items: center; padding: 20px 0 0; } .techchart-overlay.hidden { display: none; } .techchart-head { text-align: center; padding: 0 20px; flex: 0 0 auto; } .techchart-head h2 { margin: 0 0 4px; color: var(--accent); letter-spacing: 0.08em; } .techchart-head p { margin: 0; color: var(--muted); font-size: 0.85rem; max-width: 720px; } /* The tier columns scroll sideways as a whole (a deep Odyssey chart easily outgrows the viewport width); each column scrolls independently on the vertical axis, so a crowded Tier 0 doesn't stretch every other column's cards down to match its height. */ .techchart-body { flex: 1 1 auto; width: 100%; margin-top: 14px; display: flex; gap: 14px; align-items: flex-start; overflow-x: auto; overflow-y: hidden; padding: 0 20px 16px; } .techchart-col { flex: 0 0 200px; max-height: 100%; display: flex; flex-direction: column; gap: 8px; overflow-y: auto; padding-right: 2px; } .techchart-col-head { flex: 0 0 auto; color: var(--muted); font-size: 0.72rem; font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase; text-align: center; border-bottom: 1px solid var(--border); padding-bottom: 6px; margin-bottom: 2px; } /* One node card. A plain
(built, or any non-building node) reads the same as a