Roads are pre-generated between cities with a greedy geometric spanner and flatten unit movement cost. City improvements now only affect the tiles their region controls, with per-region tax collection and a Cities tab. The budget is a grouped, scrollable table with 25%/year tax and a 10%-of-GDP opening treasury. Also merged the tile info into the city panel, kept conflict and budget expansion state across snapshots, added join notifications, filled border corners, drew battle sides as stacks and made tab overflow scroll.
781 lines
23 KiB
CSS
781 lines
23 KiB
CSS
:root {
|
|
--hex-w: 63px;
|
|
--hex-h: 56px;
|
|
--panel-bg: linear-gradient(160deg, rgba(40, 52, 70, 0.96), rgba(18, 24, 34, 0.96));
|
|
--panel-border: rgba(150, 190, 240, 0.35);
|
|
--text: #e8eef7;
|
|
--muted: #9fb0c8;
|
|
--accent: #6cb6ff;
|
|
}
|
|
|
|
* { box-sizing: border-box; }
|
|
|
|
html, body {
|
|
margin: 0;
|
|
height: 100%;
|
|
overflow: hidden;
|
|
background: #05070c;
|
|
color: var(--text);
|
|
font-family: "Segoe UI", "DejaVu Sans", Roboto, sans-serif;
|
|
font-size: 14px;
|
|
user-select: none;
|
|
}
|
|
|
|
#screens { position: absolute; inset: 0; }
|
|
|
|
.screen { position: absolute; inset: 0; display: none; }
|
|
.screen.active { display: block; }
|
|
|
|
/* ---------------------------------------------------------------- menus -- */
|
|
|
|
#screen-menu, #screen-new, #screen-join, #screen-choose {
|
|
display: none;
|
|
align-items: center;
|
|
justify-content: center;
|
|
background:
|
|
radial-gradient(circle at 50% 20%, rgba(60, 90, 140, 0.35), transparent 60%),
|
|
linear-gradient(180deg, #0b1220, #05070c);
|
|
}
|
|
#screen-menu.active, #screen-new.active, #screen-join.active, #screen-choose.active { display: flex; }
|
|
|
|
.menu-card {
|
|
width: 380px;
|
|
padding: 28px 32px;
|
|
border-radius: 14px;
|
|
background: var(--panel-bg);
|
|
border: 1px solid var(--panel-border);
|
|
box-shadow: 0 18px 50px rgba(0, 0, 0, 0.6), inset 0 1px 0 rgba(255, 255, 255, 0.08);
|
|
text-align: center;
|
|
}
|
|
.menu-card.wide { width: 520px; text-align: left; }
|
|
.menu-card h1 { margin: 0 0 6px; font-size: 30px; letter-spacing: 1px; }
|
|
.menu-card.wide h1 { text-align: center; }
|
|
.tagline { color: var(--muted); margin: 0 0 24px; }
|
|
|
|
.field { display: block; margin: 14px 0; }
|
|
.field > span:first-child { display: block; color: var(--muted); margin-bottom: 5px; font-size: 12px; text-transform: uppercase; letter-spacing: 0.06em; }
|
|
.field .inline { display: flex; gap: 8px; }
|
|
.field .inline input { flex: 1; }
|
|
|
|
input, select {
|
|
width: 100%;
|
|
padding: 9px 11px;
|
|
border-radius: 7px;
|
|
border: 1px solid rgba(160, 190, 230, 0.3);
|
|
background: rgba(8, 12, 20, 0.85);
|
|
color: var(--text);
|
|
font-size: 14px;
|
|
outline: none;
|
|
}
|
|
input:focus, select:focus { border-color: var(--accent); }
|
|
|
|
.row { display: flex; gap: 10px; justify-content: center; margin-top: 18px; }
|
|
.menu-card.wide .row { justify-content: flex-end; }
|
|
|
|
.aero {
|
|
cursor: pointer;
|
|
padding: 9px 18px;
|
|
border-radius: 8px;
|
|
border: 1px solid rgba(150, 190, 240, 0.45);
|
|
background: linear-gradient(180deg, rgba(90, 130, 190, 0.55), rgba(35, 55, 90, 0.85));
|
|
color: var(--text);
|
|
font-size: 14px;
|
|
transition: filter 0.12s ease, transform 0.05s ease;
|
|
}
|
|
.aero:hover { filter: brightness(1.25); }
|
|
.aero:active { transform: translateY(1px); }
|
|
.aero:disabled { opacity: 0.4; cursor: default; filter: none; }
|
|
.aero.primary { background: linear-gradient(180deg, rgba(110, 175, 255, 0.7), rgba(40, 95, 175, 0.9)); }
|
|
.aero.big { display: block; width: 100%; margin: 10px 0; padding: 14px; font-size: 17px; }
|
|
|
|
.status { min-height: 20px; margin: 14px 0 0; color: #ffcf7a; text-align: center; }
|
|
|
|
.game-list {
|
|
list-style: none;
|
|
margin: 6px 0 14px;
|
|
padding: 0;
|
|
max-height: 190px;
|
|
overflow-y: auto;
|
|
border-radius: 8px;
|
|
background: rgba(0, 0, 0, 0.3);
|
|
border: 1px solid rgba(150, 190, 240, 0.15);
|
|
}
|
|
.game-list li { padding: 9px 12px; cursor: pointer; border-bottom: 1px solid rgba(255,255,255,0.05); }
|
|
.game-list li:hover { background: rgba(110, 175, 255, 0.15); }
|
|
.game-list li.selected { background: rgba(110, 175, 255, 0.3); }
|
|
|
|
/* Flag + name pickers used on the new-game and join screens. */
|
|
.civ-list {
|
|
list-style: none;
|
|
margin: 6px 0 14px;
|
|
padding: 0;
|
|
max-height: 220px;
|
|
overflow-y: auto;
|
|
border-radius: 8px;
|
|
background: rgba(0, 0, 0, 0.3);
|
|
border: 1px solid rgba(150, 190, 240, 0.15);
|
|
}
|
|
.civ-list li {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 10px;
|
|
padding: 7px 12px;
|
|
cursor: pointer;
|
|
border-bottom: 1px solid rgba(255,255,255,0.05);
|
|
}
|
|
.civ-list li:hover { background: rgba(110, 175, 255, 0.15); }
|
|
.civ-list li.selected { background: rgba(110, 175, 255, 0.3); }
|
|
.civ-list img {
|
|
width: 32px;
|
|
height: 32px;
|
|
object-fit: cover;
|
|
border-radius: 3px;
|
|
border: 1px solid rgba(255, 255, 255, 0.15);
|
|
flex: 0 0 auto;
|
|
}
|
|
|
|
/* ------------------------------------------------------------- world map -- */
|
|
|
|
#screen-game.active { display: block; }
|
|
|
|
#map-viewport {
|
|
position: absolute;
|
|
inset: 0;
|
|
overflow: hidden;
|
|
background-image: url("../assets/map_background.png");
|
|
background-size: 512px 512px;
|
|
background-color: #05070c;
|
|
cursor: grab;
|
|
}
|
|
#map-viewport.dragging { cursor: grabbing; }
|
|
|
|
#map-world { position: absolute; left: 0; top: 0; transform-origin: 0 0; }
|
|
/* Promote the map to its own layer only while panning, so a drag is a GPU
|
|
translation instead of a repaint of every hex. Keeping it off when idle
|
|
avoids holding a huge composited layer in memory. */
|
|
#map-viewport.dragging #map-world { will-change: transform; }
|
|
.layer { position: absolute; left: 0; top: 0; }
|
|
/* A materialised block of the detailed map. Positioned at its world origin and
|
|
moved with `transform`; its tiles are positioned relative to it. */
|
|
.chunk { position: absolute; left: 0; top: 0; }
|
|
|
|
/* Zoomed-out level of detail: the detailed hex/fog/border layers are hidden and
|
|
a single simplified canvas is shown instead. */
|
|
#layer-overview { display: none; }
|
|
#map-world.lod #layer-overview { display: block; }
|
|
#map-world.lod .detail-layer { display: none; }
|
|
.overview-block { position: absolute; }
|
|
|
|
.hex {
|
|
position: absolute;
|
|
width: var(--hex-w);
|
|
height: var(--hex-h);
|
|
/* Terrain and fog tiles are drawn from pre-masked art (transparent corners),
|
|
so no per-tile clip-path is needed. Clipping thousands of hexes forced a
|
|
repaint of the whole map on every pan frame. */
|
|
background-repeat: no-repeat;
|
|
}
|
|
.hex.terrain {
|
|
background-image: url("../assets/terrain.png");
|
|
background-size: 441px 56px;
|
|
}
|
|
.hex.fog {
|
|
pointer-events: none;
|
|
background-size: var(--hex-w) var(--hex-h);
|
|
}
|
|
.fog-unexplored {
|
|
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='63' height='56'%3E%3Cpolygon points='63,28 47.25,0 15.75,0 0,28 15.75,56 47.25,56' fill='%2304040a'/%3E%3C/svg%3E");
|
|
}
|
|
.fog-explored {
|
|
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='63' height='56'%3E%3Cpolygon points='63,28 47.25,0 15.75,0 0,28 15.75,56 47.25,56' fill='%23040612' fill-opacity='0.62'/%3E%3C/svg%3E");
|
|
}
|
|
|
|
.border-seg {
|
|
position: absolute;
|
|
height: 6px;
|
|
border-radius: 3px;
|
|
transform-origin: 0 50%;
|
|
pointer-events: none;
|
|
}
|
|
|
|
/* Rounds off the junction where two border edges meet, so the outline has no
|
|
notches at the corners. */
|
|
.border-corner {
|
|
position: absolute;
|
|
border-radius: 50%;
|
|
pointer-events: none;
|
|
}
|
|
|
|
/* A single pre-generated road link, running from tile centre to tile centre. */
|
|
.road-seg {
|
|
position: absolute;
|
|
background: #8a5a2b;
|
|
border-radius: 2px;
|
|
box-shadow: 0 0 1px rgba(0, 0, 0, 0.6);
|
|
transform-origin: 0 50%;
|
|
pointer-events: none;
|
|
}
|
|
|
|
.tile-selection {
|
|
position: absolute;
|
|
width: var(--hex-w);
|
|
height: var(--hex-h);
|
|
clip-path: polygon(100% 50%, 75% 0, 25% 0, 0 50%, 25% 100%, 75% 100%);
|
|
border: none;
|
|
box-shadow: inset 0 0 0 3px rgba(255, 255, 255, 0.85);
|
|
pointer-events: none;
|
|
}
|
|
|
|
.entity { position: absolute; }
|
|
.unit {
|
|
--unit-size: 38px;
|
|
width: 38px;
|
|
height: 38px;
|
|
margin-left: -19px;
|
|
margin-top: -19px;
|
|
cursor: pointer;
|
|
}
|
|
.unit .unit-statuses,
|
|
.city .city-statuses {
|
|
position: absolute;
|
|
display: flex;
|
|
gap: 1px;
|
|
pointer-events: none;
|
|
z-index: 2;
|
|
}
|
|
.unit .unit-statuses { top: -4px; right: -4px; }
|
|
.city .city-statuses { top: -6px; left: 50%; transform: translateX(-50%); }
|
|
.status-icon {
|
|
width: calc(var(--unit-size, 38px) / 5);
|
|
height: calc(var(--unit-size, 38px) / 5);
|
|
display: block;
|
|
filter: drop-shadow(0 0 1px rgba(0, 0, 0, 0.9));
|
|
}
|
|
.unit .status-icon {
|
|
width: calc(var(--unit-size, 38px) / 3);
|
|
height: calc(var(--unit-size, 38px) / 3);
|
|
}
|
|
.city .status-icon {
|
|
width: calc(var(--city-size, 26px) / 3);
|
|
height: calc(var(--city-size, 26px) / 3);
|
|
}
|
|
.unit .body {
|
|
width: 100%;
|
|
height: 100%;
|
|
border-radius: 50%;
|
|
border: 2px solid rgba(0, 0, 0, 0.65);
|
|
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.6);
|
|
overflow: hidden;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
}
|
|
.unit .body img { width: 78%; height: 78%; image-rendering: auto; }
|
|
/* Owner's flag centred on the unit's top-left corner, so only a sliver of it
|
|
overlaps the unit and the unit icon stays readable. */
|
|
.unit .unit-flag {
|
|
position: absolute;
|
|
top: 0;
|
|
left: 0;
|
|
width: 50%;
|
|
height: 50%;
|
|
transform: translate(-50%, -50%);
|
|
object-fit: cover;
|
|
border: 1px solid rgba(0, 0, 0, 0.65);
|
|
border-radius: 3px;
|
|
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.5);
|
|
pointer-events: none;
|
|
z-index: 3;
|
|
display: block;
|
|
}
|
|
.unit.selected .body { box-shadow: 0 0 0 3px rgba(255, 255, 255, 0.9), 0 2px 6px rgba(0,0,0,0.6); }
|
|
.unit .health {
|
|
position: absolute;
|
|
left: 3px;
|
|
right: 3px;
|
|
bottom: -7px;
|
|
height: 5px;
|
|
background: rgba(30, 0, 0, 0.85);
|
|
border: 1px solid rgba(0, 0, 0, 0.7);
|
|
border-radius: 3px;
|
|
overflow: hidden;
|
|
}
|
|
.unit .health .fill { height: 100%; background: #46d36a; }
|
|
/* Stack size, sitting on the lower-right corner of the unit icon. */
|
|
.unit .unit-count {
|
|
position: absolute;
|
|
right: 0;
|
|
bottom: 0;
|
|
min-width: 14px;
|
|
height: 14px;
|
|
padding: 0 3px;
|
|
box-sizing: border-box;
|
|
border-radius: 7px;
|
|
background: #000000;
|
|
color: #ffffff;
|
|
font-size: 10px;
|
|
font-weight: 700;
|
|
line-height: 14px;
|
|
text-align: center;
|
|
pointer-events: none;
|
|
z-index: 4;
|
|
}
|
|
/* On a battlefield both stacks shrink to half size and face each other across
|
|
the battle icon. */
|
|
.unit.in-battle { transform: scale(0.5); }
|
|
.battle-marker {
|
|
position: absolute;
|
|
width: 22px;
|
|
height: 22px;
|
|
margin-left: -11px;
|
|
margin-top: -11px;
|
|
z-index: 4;
|
|
pointer-events: none;
|
|
filter: drop-shadow(0 0 2px rgba(0, 0, 0, 0.9));
|
|
}
|
|
.battle-marker img { width: 100%; height: 100%; }
|
|
|
|
.city {
|
|
--city-size: 26px;
|
|
transform: translate(-50%, -50%);
|
|
text-align: center;
|
|
}
|
|
.city .marker {
|
|
width: var(--city-size);
|
|
height: var(--city-size);
|
|
margin: 0 auto;
|
|
border-radius: 5px;
|
|
border: 2px solid rgba(255, 255, 255, 0.8);
|
|
background-color: #333;
|
|
background-size: cover;
|
|
cursor: pointer;
|
|
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.7);
|
|
}
|
|
/* City names live in their own layer above every unit, marker and status icon,
|
|
so they stay legible instead of being covered by whatever is nearby. */
|
|
.city-label-layer { pointer-events: none; }
|
|
.city-label {
|
|
position: absolute;
|
|
transform: translate(-50%, -100%);
|
|
margin-top: -21px;
|
|
font-size: 11px;
|
|
color: #fff;
|
|
text-align: center;
|
|
text-shadow: 0 1px 2px #000, 0 0 4px #000;
|
|
white-space: nowrap;
|
|
pointer-events: none;
|
|
}
|
|
.city-label.capital::before {
|
|
content: "\2605";
|
|
color: #ffd54a;
|
|
margin-right: 3px;
|
|
}
|
|
|
|
.path-seg {
|
|
position: absolute;
|
|
height: 4px;
|
|
border-radius: 2px;
|
|
transform-origin: 0 50%;
|
|
pointer-events: none;
|
|
opacity: 0.85;
|
|
}
|
|
|
|
/* ------------------------------------------------------------------ HUD -- */
|
|
|
|
#hud { position: absolute; inset: 0; pointer-events: none; }
|
|
#hud .panel { pointer-events: auto; }
|
|
.hidden { display: none !important; }
|
|
|
|
.panel {
|
|
position: absolute;
|
|
background: var(--panel-bg);
|
|
border: 1px solid var(--panel-border);
|
|
border-radius: 12px;
|
|
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.55), inset 0 1px 0 rgba(255, 255, 255, 0.08);
|
|
}
|
|
|
|
#top-panel {
|
|
left: 12px;
|
|
top: 12px;
|
|
min-width: 420px;
|
|
padding: 12px 16px;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 14px;
|
|
}
|
|
|
|
.flag-button {
|
|
width: 64px;
|
|
height: 64px;
|
|
padding: 0;
|
|
border-radius: 10px;
|
|
border: 1px solid var(--panel-border);
|
|
background: rgba(0, 0, 0, 0.3);
|
|
cursor: pointer;
|
|
overflow: hidden;
|
|
flex: 0 0 auto;
|
|
}
|
|
.flag-button img { width: 100%; height: 100%; object-fit: cover; }
|
|
.flag-button:hover { filter: brightness(1.2); }
|
|
|
|
.top-text { flex: 0 0 auto; min-width: 150px; }
|
|
.civ-name { font-size: 19px; font-weight: 600; }
|
|
.player-name { color: var(--muted); }
|
|
.admin-label { color: #ffcf7a; font-size: 12px; }
|
|
|
|
.top-stats { display: flex; gap: 16px; margin-left: auto; }
|
|
.stat { display: flex; flex-direction: column; align-items: flex-end; }
|
|
.stat-label { font-size: 10px; text-transform: uppercase; letter-spacing: 0.08em; color: var(--muted); }
|
|
.stat-value { font-size: 15px; font-variant-numeric: tabular-nums; }
|
|
|
|
#fps-label {
|
|
position: absolute;
|
|
right: 14px;
|
|
top: 12px;
|
|
font-size: 13px;
|
|
color: #8fdc8f;
|
|
text-shadow: 0 1px 2px #000;
|
|
}
|
|
|
|
#bottom-panel {
|
|
left: 50%;
|
|
bottom: 14px;
|
|
transform: translateX(-50%);
|
|
min-width: 420px;
|
|
max-width: 720px;
|
|
padding: 12px 18px;
|
|
}
|
|
.info-section.hidden { display: none !important; }
|
|
.info-title { font-size: 17px; font-weight: 600; margin-bottom: 4px; }
|
|
.tile-gdp-mod { font-size: 12px; color: var(--muted); padding-left: 12px; font-variant-numeric: tabular-nums; }
|
|
#bottom-panel .row { justify-content: flex-start; margin-top: 10px; }
|
|
.health-row { display: flex; align-items: center; gap: 8px; }
|
|
.health-bar { width: 220px; height: 12px; background: rgba(30, 0, 0, 0.8); border-radius: 6px; overflow: hidden; margin-top: 6px; }
|
|
#unit-health-fill { height: 100%; background: linear-gradient(180deg, #6fe08a, #2fa04d); }
|
|
#unit-health-label { margin-top: 6px; font-size: 12px; color: var(--muted); font-variant-numeric: tabular-nums; }
|
|
|
|
.unit-stats { display: flex; gap: 16px; margin-top: 6px; }
|
|
.unit-stat {
|
|
font-size: 13px;
|
|
color: var(--muted);
|
|
border-bottom: 1px dotted rgba(159, 176, 200, 0.5);
|
|
cursor: help;
|
|
}
|
|
.unit-stat:hover { color: var(--text); }
|
|
|
|
/* Chat-like feed of short-lived news in the lower-left corner, with the News
|
|
button anchored beneath it. */
|
|
.game-corner {
|
|
position: absolute;
|
|
left: 14px;
|
|
bottom: 14px;
|
|
width: 320px;
|
|
max-width: calc(50vw - 30px);
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: flex-start;
|
|
gap: 8px;
|
|
pointer-events: none;
|
|
z-index: 40;
|
|
}
|
|
.game-feed {
|
|
width: 100%;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 4px;
|
|
}
|
|
.game-corner .news-button {
|
|
pointer-events: auto;
|
|
padding: 6px 14px;
|
|
font-size: 13px;
|
|
}
|
|
.feed-line {
|
|
align-self: flex-start;
|
|
max-width: 100%;
|
|
padding: 6px 10px;
|
|
border-radius: 7px;
|
|
background: rgba(8, 12, 20, 0.86);
|
|
border: 1px solid rgba(150, 190, 240, 0.35);
|
|
color: var(--text);
|
|
font-size: 12px;
|
|
line-height: 1.35;
|
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.5);
|
|
opacity: 1;
|
|
transform: translateY(0);
|
|
transition: opacity 0.4s ease, transform 0.4s ease;
|
|
animation: feed-in 0.18s ease;
|
|
}
|
|
.feed-line.leaving { opacity: 0; transform: translateY(8px); }
|
|
.feed-line.feed-alert {
|
|
background: rgba(80, 14, 14, 0.92);
|
|
border-color: #ff6a6a;
|
|
color: #ffd9d9;
|
|
font-weight: 600;
|
|
}
|
|
@keyframes feed-in {
|
|
from { opacity: 0; transform: translateY(8px); }
|
|
to { opacity: 1; transform: translateY(0); }
|
|
}
|
|
|
|
.tooltip {
|
|
position: absolute;
|
|
z-index: 80;
|
|
max-width: 280px;
|
|
padding: 6px 10px;
|
|
border-radius: 7px;
|
|
background: rgba(8, 12, 20, 0.97);
|
|
border: 1px solid var(--panel-border);
|
|
color: var(--text);
|
|
font-size: 12px;
|
|
white-space: nowrap;
|
|
pointer-events: none;
|
|
box-shadow: 0 6px 18px rgba(0, 0, 0, 0.6);
|
|
}
|
|
|
|
/* Right-click menu for the units sharing a tile. */
|
|
.stack-menu {
|
|
position: absolute;
|
|
z-index: 70;
|
|
min-width: 200px;
|
|
padding: 4px;
|
|
border-radius: 8px;
|
|
background: var(--panel-bg);
|
|
border: 1px solid var(--panel-border);
|
|
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.55);
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 2px;
|
|
}
|
|
.stack-item {
|
|
text-align: left;
|
|
padding: 7px 10px;
|
|
border: none;
|
|
border-radius: 5px;
|
|
background: transparent;
|
|
color: var(--text);
|
|
font-size: 13px;
|
|
cursor: pointer;
|
|
}
|
|
.stack-item:hover { background: rgba(110, 175, 255, 0.2); }
|
|
.stack-item.stack-all {
|
|
margin-top: 2px;
|
|
border-top: 1px solid rgba(255, 255, 255, 0.12);
|
|
color: #ffcf7a;
|
|
}
|
|
|
|
/* --------------------------------------------------------------- modals -- */
|
|
|
|
.modal {
|
|
position: absolute;
|
|
inset: 0;
|
|
background: rgba(0, 0, 0, 0.55);
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
z-index: 50;
|
|
}
|
|
.modal.hidden { display: none; }
|
|
|
|
.modal-card {
|
|
width: 620px;
|
|
max-height: 80vh;
|
|
display: flex;
|
|
flex-direction: column;
|
|
border-radius: 14px;
|
|
background: var(--panel-bg);
|
|
border: 1px solid var(--panel-border);
|
|
box-shadow: 0 24px 70px rgba(0, 0, 0, 0.7);
|
|
overflow: hidden;
|
|
}
|
|
.modal-card-small { width: 420px; }
|
|
.modal-card-small .row { padding-bottom: 18px; }
|
|
.modal-head {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
padding: 14px 18px;
|
|
font-size: 19px;
|
|
font-weight: 600;
|
|
border-bottom: 1px solid rgba(255, 255, 255, 0.08);
|
|
}
|
|
.modal-sub { padding: 10px 18px; color: var(--muted); }
|
|
.modal-body { padding: 12px 18px 18px; overflow-y: auto; }
|
|
|
|
.modal-close { padding: 4px 11px; }
|
|
|
|
.tabs { display: flex; gap: 6px; padding: 10px 18px 0; }
|
|
.tab {
|
|
cursor: pointer;
|
|
padding: 8px 16px;
|
|
border-radius: 8px 8px 0 0;
|
|
border: 1px solid transparent;
|
|
border-bottom: none;
|
|
background: rgba(0, 0, 0, 0.25);
|
|
color: var(--muted);
|
|
}
|
|
.tab.active { background: rgba(110, 175, 255, 0.22); color: var(--text); }
|
|
.tab-page { display: none; }
|
|
/* The active tab fills the rest of the card and lets its body scroll: without
|
|
min-height: 0 the flex item would grow past the card and be clipped by the
|
|
card's overflow: hidden, hiding e.g. long diplomacy tables. */
|
|
.tab-page.active { display: flex; flex-direction: column; flex: 1 1 auto; min-height: 0; }
|
|
.tab-page.active > .modal-sub { flex: 0 0 auto; }
|
|
.tab-page.active > .modal-body { flex: 1 1 auto; min-height: 0; }
|
|
|
|
.row-card {
|
|
display: flex;
|
|
gap: 14px;
|
|
padding: 10px 12px;
|
|
margin-bottom: 8px;
|
|
border-radius: 9px;
|
|
background: rgba(255, 255, 255, 0.05);
|
|
border: 1px solid rgba(255, 255, 255, 0.05);
|
|
}
|
|
.row-card .info { flex: 1; }
|
|
.row-card .name { font-weight: 600; }
|
|
.row-card .desc { color: var(--muted); font-size: 12px; margin: 3px 0; }
|
|
.row-card .effects { font-size: 12px; color: #bcd4ff; }
|
|
.row-card .cost { font-size: 12px; color: var(--muted); margin-top: 3px; }
|
|
.row-card .actions { display: flex; flex-direction: column; gap: 6px; justify-content: center; }
|
|
.row-card .actions .aero { min-width: 170px; }
|
|
.row-card .value {
|
|
display: flex;
|
|
align-items: flex-start;
|
|
justify-content: flex-end;
|
|
min-width: 140px;
|
|
font-weight: 600;
|
|
}
|
|
.row-card .value.positive { color: #8fe3a8; }
|
|
.row-card .value.negative { color: #ff9a9a; }
|
|
.row-card .budget-detail { margin-top: 4px; }
|
|
.row-card .budget-line { font-size: 12px; color: var(--muted); margin-top: 2px; }
|
|
|
|
.row-card.conflict-card { display: block; cursor: pointer; }
|
|
.row-card.conflict-card:hover { background: rgba(255, 255, 255, 0.09); }
|
|
.conflict-detail {
|
|
margin-top: 8px;
|
|
padding-top: 8px;
|
|
border-top: 1px solid rgba(255, 255, 255, 0.08);
|
|
}
|
|
.conflict-detail .conflict-line { font-size: 12px; color: var(--muted); }
|
|
.conflict-detail .conflict-side { font-size: 13px; margin-top: 4px; }
|
|
|
|
.diplomacy-table { width: 100%; border-collapse: collapse; }
|
|
.diplomacy-table th,
|
|
.diplomacy-table td {
|
|
padding: 8px 10px;
|
|
border-bottom: 1px solid rgba(255, 255, 255, 0.08);
|
|
text-align: left;
|
|
}
|
|
.diplomacy-table th {
|
|
cursor: pointer;
|
|
user-select: none;
|
|
color: var(--muted);
|
|
font-weight: 600;
|
|
white-space: nowrap;
|
|
}
|
|
.diplomacy-table th:hover { color: var(--text); }
|
|
.diplomacy-table th.sorted-asc,
|
|
.diplomacy-table th.sorted-desc { color: var(--text); }
|
|
.diplomacy-table th.numeric,
|
|
.diplomacy-table td.numeric { text-align: right; }
|
|
.diplomacy-table .sort-arrow { font-size: 10px; margin-left: 4px; }
|
|
.diplomacy-table tbody tr:hover { background: rgba(255, 255, 255, 0.05); }
|
|
.diplomacy-table tbody tr.local-nation { background: rgba(120, 170, 255, 0.16); }
|
|
.diplomacy-table tbody tr.local-nation:hover { background: rgba(120, 170, 255, 0.24); }
|
|
.diplomacy-table tbody tr.local-nation td.name { font-weight: 600; }
|
|
.diplomacy-table td.status { white-space: nowrap; }
|
|
.diplomacy-table td.status .aero { margin-left: 8px; padding: 4px 10px; }
|
|
|
|
/* Budget table: income green, every cost red, alternating rows and the net
|
|
total pinned to the last row. */
|
|
.budget-table { width: 100%; border-collapse: collapse; }
|
|
.budget-table th,
|
|
.budget-table td {
|
|
padding: 7px 10px;
|
|
border-bottom: 1px solid rgba(255, 255, 255, 0.06);
|
|
text-align: left;
|
|
}
|
|
.budget-table th { color: var(--muted); font-weight: 600; }
|
|
.budget-table td.numeric,
|
|
.budget-table th.numeric { text-align: right; }
|
|
.budget-table tbody tr:nth-child(even) { background: rgba(255, 255, 255, 0.045); }
|
|
.budget-table tbody tr:hover { background: rgba(110, 175, 255, 0.12); }
|
|
.budget-table .budget-value,
|
|
.budget-table td.numeric { text-align: right; font-variant-numeric: tabular-nums; }
|
|
.budget-table tr.income .budget-value,
|
|
.budget-table tr.income-detail .budget-value,
|
|
.budget-table td.income { color: #8fe3a8; }
|
|
.budget-table tr.budget-group .budget-value,
|
|
.budget-table tr.expense .budget-value,
|
|
.budget-table tr.expense-detail .budget-value { color: #ff9a9a; }
|
|
.budget-table tr.budget-section td {
|
|
padding-top: 14px;
|
|
color: var(--muted);
|
|
font-size: 11px;
|
|
letter-spacing: 0.08em;
|
|
text-transform: uppercase;
|
|
}
|
|
.budget-table tr.budget-group .budget-name.clickable { cursor: pointer; }
|
|
.budget-table tr.budget-group .budget-name.clickable:hover { color: var(--accent); }
|
|
.budget-caret { display: inline-block; width: 12px; color: var(--muted); }
|
|
.budget-note { margin-top: 2px; color: var(--muted); font-size: 11px; }
|
|
.budget-table tfoot .budget-total td {
|
|
padding-top: 10px;
|
|
border-top: 2px solid rgba(255, 255, 255, 0.25);
|
|
font-weight: 700;
|
|
}
|
|
.budget-table tfoot .budget-total .budget-value { color: #fff; }
|
|
|
|
.training-status { padding: 0 18px 16px; }
|
|
.training-queue { padding-top: 0; }
|
|
.training-queue .queue-empty { padding: 4px 0; }
|
|
.training-queue .queue-row { padding: 8px 12px; }
|
|
.training-queue .queue-row .actions .aero { min-width: 110px; }
|
|
|
|
/* The training modal keeps a fixed, slightly larger size. The unit buttons sit
|
|
in a fixed, non-scrolling box; the queue is the flexible part and scrolls, so
|
|
adding units never stretches the card or pushes the buttons out of reach. */
|
|
#modal-train .modal-card {
|
|
width: 680px;
|
|
height: min(80vh, 740px);
|
|
}
|
|
#modal-train #train-list {
|
|
flex: 0 0 auto;
|
|
width: 100%;
|
|
height: 120px;
|
|
max-height: none;
|
|
overflow: hidden;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 8px;
|
|
}
|
|
#modal-train .training-status { flex: 0 0 auto; }
|
|
#modal-train .training-queue { flex: 1 1 auto; min-height: 0; }
|
|
.progress { height: 14px; margin-top: 8px; background: rgba(0, 0, 0, 0.5); border-radius: 7px; overflow: hidden; }
|
|
#train-progress, #buildings-progress { height: 100%; width: 0; background: linear-gradient(180deg, #7fc4ff, #2f7fd0); }
|
|
|
|
.news-item {
|
|
padding: 10px 12px;
|
|
margin-bottom: 8px;
|
|
border-radius: 9px;
|
|
background: rgba(255, 255, 255, 0.05);
|
|
border: 1px solid rgba(255, 255, 255, 0.05);
|
|
}
|
|
.news-item .news-text { font-weight: 600; }
|
|
.news-item .news-date { color: var(--muted); font-size: 12px; margin-top: 3px; }
|
|
.news-empty { color: var(--muted); padding: 8px 0; }
|
|
#news-button { flex: 0 0 auto; }
|
|
|
|
.banner {
|
|
position: absolute;
|
|
left: 50%;
|
|
top: 12px;
|
|
transform: translateX(-50%);
|
|
padding: 10px 20px;
|
|
border-radius: 8px;
|
|
background: rgba(140, 20, 20, 0.9);
|
|
border: 1px solid rgba(255, 120, 120, 0.6);
|
|
z-index: 60;
|
|
}
|