The city buildings tab and the HUD's tile panel now draw the same card: an icon, name and status, a bottom-anchored button carrying the estimated price and build time, a quiet remove control and a hatched locked state, with the produce/consume/cost detail in a hover tooltip. The shared card lives in client/js/ui/card.js. The tile dock is now a summary — description, production, people and their ethnic makeup, GDP and the improvements list — with a Management button that opens a full-size panel like the city and nation panels. Its Improvements tab holds the grouped Military, Transportation and Economic cards, so Road and Railway sit with the other works. City buildings sit in a two-row horizontal scroller with the built ones first, and the city panel now matches the nation panel's size. AGENTS.md now asks for a minimal visual preview to be shown before a player-visible change is committed.
1826 lines
57 KiB
CSS
1826 lines
57 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; }
|
||
|
||
/* Boot-style loading log, sitting over every screen while a world is built. */
|
||
.loading-overlay {
|
||
position: absolute;
|
||
inset: 0;
|
||
z-index: 200;
|
||
display: flex;
|
||
align-items: flex-start;
|
||
justify-content: flex-start;
|
||
padding: 28px 34px;
|
||
overflow: hidden;
|
||
}
|
||
.loading-overlay.hidden { display: none; }
|
||
/* Once finished the overlay only fades; it must not keep swallowing clicks
|
||
meant for the HUD underneath it. */
|
||
.loading-overlay.done { opacity: 0.0; transition: opacity 0.6s ease; pointer-events: none; }
|
||
.loading-panel {
|
||
width: min(720px, 100%);
|
||
max-height: 100%;
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 10px;
|
||
}
|
||
.loading-title {
|
||
font-size: 13px;
|
||
letter-spacing: 0.12em;
|
||
text-transform: uppercase;
|
||
color: var(--accent);
|
||
}
|
||
.loading-log {
|
||
overflow: hidden auto;
|
||
padding: 12px 14px;
|
||
border-radius: 10px;
|
||
border: 1px solid rgba(120, 200, 255, 0.25);
|
||
background: rgba(4, 8, 14, 0.9);
|
||
box-shadow: inset 0 0 40px rgba(0, 0, 0, 0.6);
|
||
font-family: "DejaVu Sans Mono", "Consolas", "Courier New", monospace;
|
||
font-size: 12.5px;
|
||
line-height: 1.55;
|
||
color: #b9d4e8;
|
||
white-space: pre;
|
||
}
|
||
.loading-line { color: #9fc3dc; }
|
||
.loading-line.loading-total {
|
||
margin-top: 6px;
|
||
padding-top: 6px;
|
||
border-top: 1px solid rgba(120, 200, 255, 0.2);
|
||
color: #ffd77a;
|
||
font-weight: 600;
|
||
}
|
||
|
||
/* ---------------------------------------------------------------- menus -- */
|
||
|
||
#screen-menu, #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-join.active, #screen-choose.active { display: flex; }
|
||
|
||
/* The main menu sits on one of the game's key-art pieces, dimmed so the card
|
||
stays legible; the setup, join and choose screens keep the plain gradient
|
||
above. The piece is picked at launch (client/js/menu_background.js) by the
|
||
class on <body>, and the same art stays behind the boot log while the world
|
||
loads. The default below is also what paints before the script runs. */
|
||
#screen-menu,
|
||
.loading-overlay {
|
||
background:
|
||
radial-gradient(circle at 50% 25%, rgba(60, 90, 140, 0.25), transparent 60%),
|
||
linear-gradient(180deg, rgba(5, 7, 12, 0.45), rgba(5, 7, 12, 0.85)),
|
||
url("../assets/main_menu_background.png") center / cover no-repeat;
|
||
}
|
||
body.menu-bg-1 #screen-menu,
|
||
body.menu-bg-1 .loading-overlay {
|
||
background:
|
||
radial-gradient(circle at 50% 25%, rgba(60, 90, 140, 0.25), transparent 60%),
|
||
linear-gradient(180deg, rgba(5, 7, 12, 0.45), rgba(5, 7, 12, 0.85)),
|
||
url("../assets/main_menu_background2.png") center / cover no-repeat;
|
||
}
|
||
|
||
.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; z-index: 1; }
|
||
/* The WebGL map canvas sits behind the DOM icon layers. It is viewport-sized
|
||
and drawn from the same camera, so it never scales with #map-world. */
|
||
#map-canvas {
|
||
position: absolute;
|
||
left: 0;
|
||
top: 0;
|
||
width: 100%;
|
||
height: 100%;
|
||
z-index: 0;
|
||
pointer-events: none;
|
||
}
|
||
/* When WebGL owns the map, the DOM geometry layers are left empty and hidden;
|
||
the icon layers (paths, targets, entities, labels) still render. */
|
||
#map-world.gl-render .detail-layer { display: none; }
|
||
/* 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; }
|
||
/* Ground layers follow the camera's orthographic tilt (north-south
|
||
foreshortening); the sprite and label layers stay upright on top. The factor
|
||
is set from CAMERA_TILT by MapView. */
|
||
.layer.tilted { transform: scaleY(var(--camera-tilt, 1)); transform-origin: 0 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; }
|
||
|
||
.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 {
|
||
/* Tiling land textures are painted per tile by terrain.js; the atlas only
|
||
supplies the hexagonal alpha mask here. */
|
||
background-repeat: repeat;
|
||
-webkit-mask-image: url("../assets/terrain.png");
|
||
-webkit-mask-size: 441px 56px;
|
||
-webkit-mask-repeat: no-repeat;
|
||
mask-image: url("../assets/terrain.png");
|
||
mask-size: 441px 56px;
|
||
mask-repeat: no-repeat;
|
||
}
|
||
.hex.fog {
|
||
pointer-events: none;
|
||
background-size: var(--hex-w) var(--hex-h);
|
||
}
|
||
/* The DOM fallback cannot blend fog per vertex, so blur the whole fog layer:
|
||
its edge against the visible map fades instead of stepping at the hex line.
|
||
The WebGL path hides the layer entirely and does the blur in the shader. */
|
||
#layer-fog {
|
||
filter: blur(3.5px);
|
||
-webkit-filter: blur(3.5px);
|
||
}
|
||
.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 road or railway link, running from tile centre to tile centre. The
|
||
texture (and its repeat) is set in JS; only the quad's shape is styled here. */
|
||
.road-seg,
|
||
.rail-seg {
|
||
position: absolute;
|
||
background-color: transparent;
|
||
transform-origin: 50% 0;
|
||
pointer-events: none;
|
||
image-rendering: pixelated;
|
||
}
|
||
|
||
/* A ranged unit's bombardment: a red dashed parabolic arc from the battery to
|
||
the target and a red ring around the target. Drawn on the layer below the
|
||
entity icons so the units themselves stay on top. */
|
||
.target-arcs {
|
||
position: absolute;
|
||
left: 0;
|
||
top: 0;
|
||
width: 1px;
|
||
height: 1px;
|
||
overflow: visible;
|
||
pointer-events: none;
|
||
}
|
||
.target-line {
|
||
fill: none;
|
||
stroke: #ff2b2b;
|
||
stroke-width: 3;
|
||
stroke-dasharray: 10 8;
|
||
stroke-linecap: round;
|
||
pointer-events: none;
|
||
opacity: 0.6;
|
||
filter: drop-shadow(0 0 2px rgba(0, 0, 0, 0.7));
|
||
}
|
||
.target-shell {
|
||
fill: #ffdd66;
|
||
stroke: #ff2b2b;
|
||
stroke-width: 1;
|
||
pointer-events: none;
|
||
filter: drop-shadow(0 0 3px rgba(255, 90, 40, 0.9));
|
||
}
|
||
.target-ring {
|
||
position: absolute;
|
||
width: 46px;
|
||
height: 46px;
|
||
margin-left: -23px;
|
||
margin-top: -23px;
|
||
box-sizing: border-box;
|
||
border: 3px solid #ff2b2b;
|
||
border-radius: 50%;
|
||
pointer-events: none;
|
||
opacity: 0.6;
|
||
box-shadow: 0 0 6px rgba(255, 43, 43, 0.85);
|
||
}
|
||
/* A barrage the player is looking at stands out from the rest. */
|
||
.target-line.target-selected, .target-ring.target-selected { opacity: 1; }
|
||
|
||
/* The delivery graph, shown only in the economic map modes: a white line per
|
||
delivery leg, an animated merchandise icon riding it and a dot on every
|
||
storage node. */
|
||
#layer-resources { pointer-events: none; }
|
||
.resource-mover {
|
||
position: absolute;
|
||
width: 20px;
|
||
height: 20px;
|
||
margin-left: -10px;
|
||
margin-top: -10px;
|
||
box-sizing: border-box;
|
||
transform-origin: 50% 50%;
|
||
pointer-events: none;
|
||
filter: drop-shadow(0 0 2px rgba(0, 0, 0, 0.9));
|
||
}
|
||
.resource-node {
|
||
position: absolute;
|
||
width: 10px;
|
||
height: 10px;
|
||
margin-left: -5px;
|
||
margin-top: -5px;
|
||
box-sizing: border-box;
|
||
border: 2px solid #ffffff;
|
||
border-radius: 50%;
|
||
background: #2b3550;
|
||
pointer-events: none;
|
||
box-shadow: 0 0 4px rgba(0, 0, 0, 0.8);
|
||
}
|
||
.resource-icon { vertical-align: middle; }
|
||
|
||
/* The migration graph, shown in the population map: a shaft and an arrowhead
|
||
per city pair, the thickness carrying the people who moved last month, and a
|
||
flag-textured disc per route drifting along it like the trade merchandise. */
|
||
.migration-arrow { pointer-events: none; }
|
||
.migration-arrow-head {
|
||
position: absolute;
|
||
pointer-events: none;
|
||
transform-origin: 100% 50%;
|
||
clip-path: polygon(0 0, 100% 50%, 0 100%);
|
||
}
|
||
.migrant-mover {
|
||
position: absolute;
|
||
width: 22px;
|
||
height: 22px;
|
||
margin-left: -11px;
|
||
margin-top: -11px;
|
||
box-sizing: border-box;
|
||
border: 2px solid rgba(255, 255, 255, 0.9);
|
||
border-radius: 50%;
|
||
background-size: cover;
|
||
background-position: center;
|
||
transform-origin: 50% 50%;
|
||
pointer-events: none;
|
||
opacity: 0;
|
||
box-shadow: 0 0 6px rgba(0, 0, 0, 0.85);
|
||
}
|
||
|
||
/* Outlines the selected or inspected tile. The polygon is built in JS from the
|
||
same warped hexagon as the terrain, so it follows the geography. */
|
||
.tile-selection {
|
||
position: absolute;
|
||
width: var(--hex-w);
|
||
height: var(--hex-h);
|
||
display: none;
|
||
pointer-events: none;
|
||
}
|
||
.tile-selection.active { display: block; }
|
||
.tile-selection svg { display: block; width: 100%; height: 100%; overflow: visible; }
|
||
.tile-selection polygon {
|
||
fill: rgba(255, 255, 255, 0.12);
|
||
stroke: none;
|
||
}
|
||
.tile-selection path {
|
||
fill: none;
|
||
stroke: rgba(255, 255, 255, 0.9);
|
||
stroke-width: 3;
|
||
stroke-linejoin: round;
|
||
vector-effect: non-scaling-stroke;
|
||
}
|
||
.tile-selection .tile-selection-hidden { stroke-dasharray: 6 5; }
|
||
|
||
.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); }
|
||
/* A trench: a hatched overlay marking the dug-in tile. */
|
||
.trench-marker {
|
||
position: absolute;
|
||
width: 34px;
|
||
height: 30px;
|
||
margin-left: -17px;
|
||
margin-top: -15px;
|
||
background-image: repeating-linear-gradient(
|
||
45deg, rgba(90, 60, 30, 0.55) 0 3px, rgba(40, 26, 12, 0.55) 3px 6px
|
||
);
|
||
border: 1px solid rgba(0, 0, 0, 0.4);
|
||
pointer-events: none;
|
||
z-index: 3;
|
||
}
|
||
/* A military tile improvement marker. */
|
||
.tile-improvement-marker {
|
||
position: absolute;
|
||
width: 22px;
|
||
height: 22px;
|
||
margin-left: -11px;
|
||
margin-top: -11px;
|
||
pointer-events: none;
|
||
z-index: 3;
|
||
filter: drop-shadow(0 0 2px rgba(0, 0, 0, 0.9));
|
||
}
|
||
.tile-improvement-marker img { width: 100%; height: 100%; }
|
||
|
||
.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. The
|
||
layer needs an explicit z-index because those icons give their own children
|
||
z-index 2-4, which would otherwise paint over this layer. */
|
||
/* Country names for the far-out political map, drawn as stretched SVG text on
|
||
their own layer below the city labels. The SVG viewport is sized to the world
|
||
by the renderer (a zero-sized one is not painted at all in some browsers) and
|
||
the overflow is visible so the rotated text can sit anywhere on the map. */
|
||
/* No z-index on purpose: the layer is first of the icon layers in the DOM, so
|
||
it paints beneath the selection, paths, targets, units, cities and labels. */
|
||
.country-label-layer { pointer-events: none; }
|
||
.political-label-svg {
|
||
position: absolute;
|
||
left: 0;
|
||
top: 0;
|
||
overflow: visible;
|
||
}
|
||
.political-label-svg text {
|
||
font-family: inherit;
|
||
font-weight: 700;
|
||
pointer-events: none;
|
||
}
|
||
/* On the flat political and economic maps the transport network stays visible,
|
||
but as solid strokes instead of the road/rail textures. */
|
||
#map-world.political .road-seg, #map-world.economic .road-seg {
|
||
background-image: none !important;
|
||
background-color: #3a3a3d;
|
||
}
|
||
#map-world.political .rail-seg, #map-world.economic .rail-seg {
|
||
background-image: none !important;
|
||
background-color: #8a5a2b;
|
||
}
|
||
|
||
.city-label-layer { pointer-events: none; z-index: 5; }
|
||
.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;
|
||
}
|
||
/* A discreet progress bar under the city name, showing the first queued build
|
||
(yellow) or unit (blue). Hidden while the city is idle. */
|
||
.city-progress {
|
||
position: absolute;
|
||
left: 50%;
|
||
bottom: 100%;
|
||
margin-bottom: 1px;
|
||
transform: translateX(-50%);
|
||
width: var(--city-size);
|
||
height: 3px;
|
||
box-sizing: border-box;
|
||
background: rgba(0, 0, 0, 0.6);
|
||
border: 1px solid rgba(0, 0, 0, 0.7);
|
||
border-radius: 2px;
|
||
overflow: hidden;
|
||
pointer-events: none;
|
||
z-index: 4;
|
||
}
|
||
.city-progress .fill { height: 100%; width: 0; background: #7fc4ff; }
|
||
.city-progress.building .fill { background: #ffd54a; }
|
||
/* One small square per building level, scattered around the city icon. */
|
||
.city-buildings {
|
||
position: absolute;
|
||
inset: 0;
|
||
pointer-events: none;
|
||
z-index: 1;
|
||
}
|
||
.city-building-square {
|
||
position: absolute;
|
||
width: 10%;
|
||
height: 10%;
|
||
transform: translate(-50%, -50%);
|
||
box-sizing: border-box;
|
||
background: rgba(255, 255, 255, 0.85);
|
||
border: 1px solid rgba(0, 0, 0, 0.6);
|
||
border-radius: 1px;
|
||
}
|
||
|
||
.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; z-index: 10; }
|
||
#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;
|
||
/* Lifted above the modal overlay so the nation summary — and the approval
|
||
figure it now carries — stays bright and readable while a panel is open. */
|
||
z-index: 60;
|
||
}
|
||
|
||
.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; }
|
||
/* Month-on-month change marker inside a stat: green up, red down, neutral for
|
||
unchanged, doubled for a move past the indicator's threshold. */
|
||
.stat-arrow { margin-left: 4px; font-size: 11px; font-weight: 700; letter-spacing: -1px; }
|
||
.stat-arrow.up { color: #6ee787; }
|
||
.stat-arrow.down { color: #f27272; }
|
||
.stat-arrow.equal { color: var(--muted); }
|
||
/* Approval reads white in the neutral 40–60% band, red when the people
|
||
disapprove and green when they approve. */
|
||
.stat-value.approval-neutral { color: var(--text); }
|
||
.stat-value.approval-positive { color: #6ee787; }
|
||
.stat-value.approval-negative { color: #f27272; }
|
||
|
||
#fps-label {
|
||
position: absolute;
|
||
right: 14px;
|
||
top: 12px;
|
||
font-size: 13px;
|
||
color: #8fdc8f;
|
||
text-shadow: 0 1px 2px #000;
|
||
}
|
||
|
||
/* Colour scale for the economic map modes, in its own space below the FPS
|
||
readout. The gradient itself is painted from the shared ramp in JS. */
|
||
.map-legend {
|
||
position: absolute;
|
||
right: 14px;
|
||
top: 38px;
|
||
width: 190px;
|
||
padding: 10px 12px;
|
||
border-radius: 10px;
|
||
background: var(--panel-bg);
|
||
border: 1px solid var(--panel-border);
|
||
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.55);
|
||
pointer-events: none;
|
||
z-index: 30;
|
||
}
|
||
.map-legend-title {
|
||
font-size: 11px;
|
||
text-transform: uppercase;
|
||
letter-spacing: 0.08em;
|
||
color: var(--muted);
|
||
margin-bottom: 6px;
|
||
}
|
||
.map-legend-bar {
|
||
height: 12px;
|
||
border-radius: 6px;
|
||
border: 1px solid rgba(0, 0, 0, 0.5);
|
||
}
|
||
.map-legend-scale {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
margin-top: 4px;
|
||
font-size: 11px;
|
||
color: var(--text);
|
||
font-variant-numeric: tabular-nums;
|
||
}
|
||
/* The trade-route key, shown under the colour scale in the economic modes. */
|
||
.map-legend-graph {
|
||
margin-top: 10px;
|
||
border-top: 1px solid var(--panel-border);
|
||
padding-top: 8px;
|
||
}
|
||
.map-legend-resources {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 4px;
|
||
font-size: 11px;
|
||
color: var(--text);
|
||
}
|
||
.map-legend-resource {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 6px;
|
||
}
|
||
.map-legend-icon {
|
||
width: 16px;
|
||
height: 16px;
|
||
object-fit: contain;
|
||
flex: none;
|
||
}
|
||
.map-legend-node {
|
||
width: 8px;
|
||
height: 8px;
|
||
margin: 0 4px;
|
||
border-radius: 50%;
|
||
background: #ffffff;
|
||
border: 1px solid rgba(0, 0, 0, 0.6);
|
||
flex: none;
|
||
}
|
||
/* The trade key's idle-route swatch: the same grey as the unused map lines. */
|
||
.map-legend-idle {
|
||
width: 18px;
|
||
height: 4px;
|
||
border-radius: 2px;
|
||
background: #7b7f85;
|
||
flex: none;
|
||
}
|
||
/* The migration key: a small arrow in the same hue as the map's arrows. */
|
||
.map-legend-arrow {
|
||
position: relative;
|
||
width: 18px;
|
||
height: 4px;
|
||
border-radius: 2px;
|
||
background: #ff3ec8;
|
||
flex: none;
|
||
}
|
||
.map-legend-arrow::after {
|
||
content: "";
|
||
position: absolute;
|
||
right: -4px;
|
||
top: 50%;
|
||
transform: translateY(-50%);
|
||
border-left: 6px solid #ff3ec8;
|
||
border-top: 5px solid transparent;
|
||
border-bottom: 5px solid transparent;
|
||
}
|
||
.map-legend-note {
|
||
width: 18px;
|
||
flex: none;
|
||
}
|
||
|
||
/* The inspected tile / city panel is anchored to the bottom right. */
|
||
#tile-panel {
|
||
right: 14px;
|
||
bottom: 14px;
|
||
min-width: 320px;
|
||
max-width: 560px;
|
||
padding: 12px 18px;
|
||
}
|
||
|
||
/* Tile improvements, grouped under a category heading, in the management
|
||
panel. */
|
||
.improvement-group { display: flex; flex-direction: column; gap: 4px; }
|
||
.improvement-category {
|
||
font-size: 10px;
|
||
letter-spacing: 0.08em;
|
||
text-transform: uppercase;
|
||
color: var(--muted);
|
||
}
|
||
.improvement-row { display: flex; flex-wrap: wrap; gap: 6px; }
|
||
.improvement-btn { font-size: 12px; }
|
||
.improvement-btn.current { opacity: 0.6; }
|
||
.improvement-btn.remove { border-color: rgba(242, 114, 114, 0.6); }
|
||
.cost-res { display: inline-flex; align-items: center; gap: 2px; }
|
||
.cost-icon { width: 12px; height: 12px; vertical-align: middle; }
|
||
.cost-money { color: var(--muted); }
|
||
|
||
/* The building and improvement card: icon, name and status up top, a
|
||
bottom-anchored action button carrying the estimated price and build time,
|
||
and a quiet remove control. What the work produces, consumes and costs is in
|
||
the hover tooltip so the card itself stays bare. Shared by the city
|
||
buildings tab and the HUD's tile improvement panel. */
|
||
.building-scroller { overflow-x: auto; overflow-y: hidden; padding-bottom: 8px; }
|
||
.building-grid {
|
||
display: grid;
|
||
grid-template-rows: repeat(2, auto);
|
||
grid-auto-flow: column;
|
||
grid-auto-columns: 178px;
|
||
gap: 8px;
|
||
width: max-content;
|
||
}
|
||
.building-tile {
|
||
position: relative;
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 4px;
|
||
width: 178px;
|
||
padding: 9px;
|
||
border-radius: 9px;
|
||
border: 1px solid rgba(150, 190, 240, 0.35);
|
||
background: linear-gradient(180deg, rgba(90, 130, 190, 0.3), rgba(35, 55, 90, 0.55));
|
||
}
|
||
.building-tile:hover { filter: brightness(1.15); }
|
||
.building-tile.built { border-color: rgba(143, 227, 168, 0.6); }
|
||
.building-tile.locked { opacity: 0.6; filter: none; }
|
||
/* An unavailable work is hatched, so a locked card reads at a glance. */
|
||
.building-tile.locked::after {
|
||
content: "";
|
||
position: absolute;
|
||
inset: 0;
|
||
border-radius: inherit;
|
||
pointer-events: none;
|
||
background: repeating-linear-gradient(
|
||
45deg,
|
||
rgba(255, 255, 255, 0.14) 0 2px,
|
||
transparent 2px 8px
|
||
);
|
||
}
|
||
.bt-top { display: flex; align-items: center; gap: 8px; padding-right: 16px; }
|
||
.bt-top .building-icon { width: 34px; height: 34px; flex: 0 0 auto; opacity: 0.95; }
|
||
.bt-id { min-width: 0; }
|
||
.building-tile .bt-name { font-weight: 600; font-size: 12.5px; line-height: 1.15; }
|
||
.building-tile .bt-level { font-size: 10.5px; color: var(--muted); }
|
||
.building-tile .bt-level.built { color: #8fe3a8; }
|
||
.building-tile .bt-upgrade {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: baseline;
|
||
gap: 6px;
|
||
width: 100%;
|
||
margin-top: auto;
|
||
padding: 4px 8px;
|
||
font-size: 11.5px;
|
||
text-align: left;
|
||
}
|
||
.building-tile .bt-up-label { font-weight: 600; }
|
||
.building-tile .bt-meta {
|
||
font-size: 10px;
|
||
font-weight: 400;
|
||
color: var(--muted);
|
||
font-variant-numeric: tabular-nums;
|
||
white-space: nowrap;
|
||
}
|
||
/* The money is an estimate, so it is italic and marked with a tilde. */
|
||
.building-tile .bt-price { font-style: italic; }
|
||
.building-tile .bt-free { width: 100%; font-size: 10.5px; padding: 3px 6px; }
|
||
.building-tile .bt-downgrade {
|
||
position: absolute;
|
||
top: 5px;
|
||
right: 5px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
width: 20px;
|
||
height: 20px;
|
||
padding: 0;
|
||
line-height: 1;
|
||
font-size: 11px;
|
||
border-radius: 6px;
|
||
background: transparent;
|
||
border: 1px solid transparent;
|
||
color: var(--muted);
|
||
opacity: 0.6;
|
||
}
|
||
.building-tile .bt-downgrade:hover:not(:disabled) {
|
||
opacity: 1;
|
||
color: #ff9a9a;
|
||
background: rgba(255, 255, 255, 0.06);
|
||
border-color: rgba(242, 114, 114, 0.5);
|
||
}
|
||
.building-tile .bt-downgrade:disabled { opacity: 0.2; }
|
||
|
||
/* The card's hover tooltip. Fixed to the body so a scrolling card list never
|
||
clips it. */
|
||
.card-tooltip {
|
||
position: fixed;
|
||
z-index: 1000;
|
||
pointer-events: none;
|
||
max-width: 300px;
|
||
padding: 8px 10px;
|
||
border-radius: 9px;
|
||
background: var(--panel-bg);
|
||
border: 1px solid var(--panel-border);
|
||
box-shadow: 0 16px 40px rgba(0, 0, 0, 0.7);
|
||
font-size: 11.5px;
|
||
color: var(--text);
|
||
}
|
||
.card-tooltip .tip-section + .tip-section {
|
||
margin-top: 6px;
|
||
border-top: 1px solid rgba(255, 255, 255, 0.08);
|
||
padding-top: 6px;
|
||
}
|
||
.card-tooltip .tip-head {
|
||
font-size: 9.5px;
|
||
letter-spacing: 0.08em;
|
||
text-transform: uppercase;
|
||
color: var(--muted);
|
||
margin-bottom: 2px;
|
||
}
|
||
.card-tooltip .tip-line {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 4px;
|
||
font-variant-numeric: tabular-nums;
|
||
}
|
||
.card-tooltip .tip-produce { color: #8fe3a8; }
|
||
.card-tooltip .tip-consume,
|
||
.card-tooltip .tip-cost { color: #e2c08a; }
|
||
.card-tooltip .tip-none { color: var(--muted); }
|
||
.card-tooltip .res { display: inline-flex; align-items: center; gap: 3px; }
|
||
.card-tooltip .res img { width: 13px; height: 13px; }
|
||
|
||
/* The tile panel leads with what the tile actually produces. */
|
||
#tile-production { margin: 4px 0 6px; display: flex; flex-direction: column; gap: 2px; }
|
||
.tile-prod-row {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 6px;
|
||
font-size: 13px;
|
||
font-variant-numeric: tabular-nums;
|
||
}
|
||
.tile-prod-row .tile-prod-name { color: var(--muted); }
|
||
.tile-prod-row .tile-prod-value { margin-left: auto; }
|
||
.tile-prod-empty { font-size: 12px; color: var(--muted); }
|
||
|
||
.info-section.hidden { display: none !important; }
|
||
.info-title { font-size: 17px; font-weight: 600; margin-bottom: 4px; }
|
||
.city-production-label { font-size: 12px; color: var(--muted); margin-top: 4px; }
|
||
.city-production-bar { height: 8px; margin-top: 4px; }
|
||
.city-production-bar > div { height: 100%; background: linear-gradient(180deg, #7fc4ff, #2f7fd0); }
|
||
.site-material-label { font-size: 11px; color: var(--muted); margin-top: 4px; font-variant-numeric: tabular-nums; }
|
||
.site-material-label.stalled { color: #ff8a80; }
|
||
.site-warning { color: #ff5252; font-weight: 700; }
|
||
.site-actions { display: flex; gap: 6px; margin-top: 6px; }
|
||
.site-actions .aero { font-size: 11px; padding: 3px 8px; }
|
||
.gather-spinner {
|
||
display: inline-block; width: 12px; height: 12px; margin-top: 6px;
|
||
border: 2px solid rgba(255, 255, 255, 0.25); border-top-color: #7fc4ff;
|
||
border-radius: 50%; animation: gather-spin 0.9s linear infinite;
|
||
}
|
||
@keyframes gather-spin { to { transform: rotate(360deg); } }
|
||
.construction-marker {
|
||
position: absolute; width: 18px; height: 18px; margin: -9px 0 0 -9px;
|
||
display: flex; align-items: center; justify-content: center;
|
||
pointer-events: none;
|
||
}
|
||
.construction-marker.stalled {
|
||
color: #fff; background: #d32f2f; border-radius: 50%;
|
||
font-weight: 700; font-size: 13px; box-shadow: 0 0 4px rgba(0, 0, 0, 0.6);
|
||
}
|
||
.construction-marker .gather-spinner { margin-top: 0; }
|
||
.construction-marker-bar {
|
||
width: 16px; height: 6px; background: rgba(0, 0, 0, 0.6);
|
||
border-radius: 3px; overflow: hidden;
|
||
}
|
||
.construction-marker-bar > div {
|
||
height: 100%; background: linear-gradient(180deg, #7fc4ff, #2f7fd0);
|
||
}
|
||
.tile-coords { font-size: 11px; color: var(--muted); font-variant-numeric: tabular-nums; margin-bottom: 2px; }
|
||
.tile-gdp-head { font-size: 13px; font-variant-numeric: tabular-nums; }
|
||
.tile-gdp-toggle { cursor: pointer; }
|
||
.tile-gdp-toggle:hover { color: #fff; }
|
||
.tile-gdp-caret { margin-left: 6px; color: var(--muted); font-size: 11px; }
|
||
.tile-gdp-mods { margin-top: 2px; }
|
||
.tile-gdp-mod { font-size: 12px; color: var(--muted); padding-left: 12px; font-variant-numeric: tabular-nums; }
|
||
#tile-panel .row, #unit-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-cargo { margin-top: 6px; font-size: 12px; color: var(--muted); }
|
||
.unit-stack-list { display: flex; flex-direction: column; gap: 3px; margin-top: 8px; max-height: 140px; overflow-y: auto; }
|
||
.stack-unit { display: flex; align-items: center; gap: 8px; font-size: 12px; cursor: pointer; }
|
||
.stack-unit-name { flex: 1; }
|
||
.stack-unit-hp { 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); }
|
||
|
||
/* Lower-left dock: the news feed sits above the selected-unit panel, so both
|
||
stay clear of the bottom-right tile panel. */
|
||
#left-dock {
|
||
position: absolute;
|
||
left: 14px;
|
||
bottom: 14px;
|
||
width: 320px;
|
||
max-width: calc(50vw - 30px);
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: stretch;
|
||
gap: 10px;
|
||
pointer-events: none;
|
||
z-index: 40;
|
||
}
|
||
#unit-panel {
|
||
position: relative;
|
||
width: 100%;
|
||
padding: 12px 18px;
|
||
pointer-events: auto;
|
||
}
|
||
/* Chat-like feed of short-lived news, with the News button beneath it. */
|
||
.game-corner {
|
||
position: relative;
|
||
width: 100%;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: flex-start;
|
||
gap: 8px;
|
||
pointer-events: none;
|
||
}
|
||
.game-feed {
|
||
width: 100%;
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 4px;
|
||
}
|
||
.dock-buttons { position: relative; display: flex; gap: 8px; pointer-events: auto; }
|
||
.game-corner .news-button {
|
||
pointer-events: auto;
|
||
padding: 6px 14px;
|
||
font-size: 13px;
|
||
}
|
||
/* The map-mode picker, anchored directly above the buttons. */
|
||
.map-modes-popover {
|
||
position: absolute;
|
||
left: 0;
|
||
bottom: calc(100% + 8px);
|
||
min-width: 170px;
|
||
padding: 8px;
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 2px;
|
||
border-radius: 10px;
|
||
background: var(--panel-bg);
|
||
border: 1px solid var(--panel-border);
|
||
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.55);
|
||
pointer-events: auto;
|
||
z-index: 45;
|
||
}
|
||
.map-modes-title {
|
||
padding: 2px 8px 6px;
|
||
font-size: 11px;
|
||
text-transform: uppercase;
|
||
letter-spacing: 0.08em;
|
||
color: var(--muted);
|
||
}
|
||
.map-mode-option {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
padding: 6px 8px;
|
||
border-radius: 6px;
|
||
font-size: 13px;
|
||
color: var(--text);
|
||
cursor: pointer;
|
||
}
|
||
.map-mode-option:hover { background: rgba(110, 175, 255, 0.2); }
|
||
.map-mode-option input { accent-color: #6eafff; cursor: pointer; }
|
||
.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; }
|
||
|
||
/* The city and government panels dock below the top-left nation summary
|
||
instead of covering it, so the approval figure stays in view while they are
|
||
open. The 104px clears the summary's 12px offset plus its padded height. */
|
||
#modal-city,
|
||
#modal-nation,
|
||
#modal-tile {
|
||
align-items: flex-start;
|
||
padding: 104px 16px 16px;
|
||
}
|
||
|
||
.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; }
|
||
|
||
/* The nation panel carries wide tables and long policy/campaign rows, so it
|
||
takes most of the screen. A fixed height keeps it from resizing as tabs with
|
||
wildly different content are switched. */
|
||
#modal-nation .modal-card {
|
||
width: 90vw;
|
||
max-width: 1600px;
|
||
height: min(80vh, 900px);
|
||
max-height: calc(100vh - 120px);
|
||
}
|
||
.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; }
|
||
|
||
/* Credits list opened from the main menu. */
|
||
#modal-credits .modal-card { width: 560px; }
|
||
.credits-title {
|
||
margin: 18px 0 6px;
|
||
font-size: 13px;
|
||
letter-spacing: 0.08em;
|
||
text-transform: uppercase;
|
||
color: var(--accent);
|
||
}
|
||
.credits-title:first-child { margin-top: 0; }
|
||
.credits-note { margin: 0 0 10px; color: var(--muted); font-size: 13px; }
|
||
.credits-entries { list-style: none; margin: 0 0 4px; padding: 0; }
|
||
.credits-entries li {
|
||
padding: 6px 0;
|
||
border-bottom: 1px solid rgba(255, 255, 255, 0.06);
|
||
font-size: 13px;
|
||
}
|
||
.credits-entries .credits-name { font-weight: 600; }
|
||
.credits-entries a { color: var(--text); font-weight: 600; text-decoration: none; }
|
||
.credits-entries a:hover { text-decoration: underline; }
|
||
.credits-detail { display: block; margin-top: 2px; color: var(--muted); font-size: 12px; }
|
||
|
||
/* Devlog list opened from the main menu. A commit's header is a button that
|
||
expands to show the full message, so the list stays compact. */
|
||
#modal-devlog .modal-card { width: 640px; }
|
||
.devlog-entry { border-bottom: 1px solid rgba(255, 255, 255, 0.06); }
|
||
.devlog-header {
|
||
display: flex;
|
||
align-items: baseline;
|
||
gap: 10px;
|
||
width: 100%;
|
||
padding: 10px 6px;
|
||
border: 0;
|
||
background: none;
|
||
color: var(--text);
|
||
font: inherit;
|
||
text-align: left;
|
||
cursor: pointer;
|
||
}
|
||
.devlog-header:hover { background: rgba(255, 255, 255, 0.04); }
|
||
.devlog-date {
|
||
flex: 0 0 auto;
|
||
color: var(--muted);
|
||
font-size: 12px;
|
||
font-variant-numeric: tabular-nums;
|
||
}
|
||
.devlog-subject { flex: 1 1 auto; font-size: 14px; font-weight: 600; }
|
||
.devlog-toggle { flex: 0 0 auto; color: var(--accent); font-weight: 700; }
|
||
.devlog-body { padding: 0 6px 12px; color: var(--muted); font-size: 13px; }
|
||
.devlog-message { margin: 0 0 6px; white-space: pre-wrap; }
|
||
.devlog-hash { font-family: monospace; font-size: 11px; }
|
||
|
||
/* Capability report: one row per WebGL/WASM feature with a status badge. */
|
||
.capability-row {
|
||
display: flex;
|
||
align-items: flex-start;
|
||
gap: 10px;
|
||
padding: 8px 0;
|
||
border-bottom: 1px solid rgba(255, 255, 255, 0.06);
|
||
}
|
||
.capability-status {
|
||
flex: 0 0 auto;
|
||
min-width: 68px;
|
||
text-align: center;
|
||
padding: 2px 6px;
|
||
border-radius: 6px;
|
||
font-size: 11px;
|
||
letter-spacing: 0.06em;
|
||
font-weight: 600;
|
||
background: rgba(110, 175, 255, 0.18);
|
||
color: var(--muted);
|
||
}
|
||
.capability-pass .capability-status { background: rgba(90, 200, 120, 0.2); color: #8fe0a3; }
|
||
.capability-fail .capability-status { background: rgba(220, 90, 90, 0.22); color: #ff9a9a; }
|
||
.capability-optional .capability-status { background: rgba(230, 180, 90, 0.2); color: #ffcf7a; }
|
||
.capability-info .capability-status { background: rgba(150, 160, 180, 0.18); color: var(--muted); }
|
||
.capability-label { font-weight: 600; }
|
||
.capability-detail { color: var(--muted); font-size: 12px; margin-top: 2px; }
|
||
.capability-requirements { margin: 0; padding-left: 40px; list-style: disc; }
|
||
.capability-requirements li { margin-bottom: 6px; color: var(--muted); font-size: 13px; }
|
||
/* Let the checks scroll while the card stays inside the viewport. */
|
||
#capabilities-list { flex: 1 1 auto; min-height: 0; }
|
||
#capabilities-min { flex: 0 0 auto; max-height: 28vh; }
|
||
|
||
.tabs {
|
||
display: flex;
|
||
/* Wrap rather than scroll: every tab stays visible with no clipped edge,
|
||
and the bar keeps a stable height whatever tab is selected. */
|
||
flex-wrap: wrap;
|
||
flex: 0 0 auto;
|
||
gap: 6px;
|
||
padding: 10px 18px 8px;
|
||
border-bottom: 1px solid rgba(255, 255, 255, 0.08);
|
||
}
|
||
.tab {
|
||
cursor: pointer;
|
||
flex: 0 0 auto;
|
||
white-space: nowrap;
|
||
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); }
|
||
/* Secondary tabs inside a page, e.g. the technology themes. */
|
||
.subtabs { display: flex; flex-wrap: wrap; gap: 6px; padding: 4px 18px 8px; }
|
||
.subtab {
|
||
cursor: pointer;
|
||
white-space: nowrap;
|
||
padding: 5px 12px;
|
||
border-radius: 7px;
|
||
border: 1px solid rgba(255, 255, 255, 0.12);
|
||
background: rgba(0, 0, 0, 0.25);
|
||
color: var(--muted);
|
||
font-size: 12px;
|
||
}
|
||
.subtab.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 .building-icon {
|
||
width: 46px;
|
||
height: 46px;
|
||
flex: 0 0 auto;
|
||
align-self: center;
|
||
opacity: 0.9;
|
||
}
|
||
.unit-icon {
|
||
width: 26px;
|
||
height: 26px;
|
||
flex: 0 0 auto;
|
||
opacity: 0.9;
|
||
}
|
||
.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 .consume { font-size: 12px; color: #e2c08a; margin-top: 2px; }
|
||
.row-card .cost { font-size: 12px; color: var(--muted); margin-top: 3px; }
|
||
.row-card .cost .cost-money { color: var(--text); }
|
||
.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; }
|
||
|
||
/* Technology cards are wide, short 3:1 tiles in a grid, so the tab shows many
|
||
more of them at once than a single stacked list would. */
|
||
#technology-list {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fill, minmax(360px, 1fr));
|
||
gap: 8px;
|
||
align-content: start;
|
||
}
|
||
#technology-list .row-card {
|
||
margin-bottom: 0;
|
||
aspect-ratio: 3 / 1;
|
||
overflow: hidden;
|
||
padding: 8px 10px;
|
||
gap: 10px;
|
||
}
|
||
#technology-list .row-card .desc,
|
||
#technology-list .row-card .effects {
|
||
margin: 2px 0;
|
||
overflow: hidden;
|
||
display: -webkit-box;
|
||
-webkit-line-clamp: 2;
|
||
-webkit-box-orient: vertical;
|
||
}
|
||
#technology-list .row-card .actions { justify-content: flex-start; align-items: flex-start; }
|
||
#technology-list .row-card .actions .aero { min-width: 0; padding: 3px 12px; }
|
||
/* A focused technology fills a bar with the points committed beside it. */
|
||
#technology-list .cost { display: flex; align-items: center; gap: 8px; }
|
||
.tech-progress-track {
|
||
flex: 1 1 auto;
|
||
height: 8px;
|
||
background: rgba(255, 255, 255, 0.14);
|
||
border-radius: 4px;
|
||
overflow: hidden;
|
||
}
|
||
.tech-progress-fill { height: 100%; background: linear-gradient(90deg, #4f8cff, #7fd1ff); }
|
||
.tech-progress-text { white-space: nowrap; font-variant-numeric: tabular-nums; }
|
||
|
||
.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; }
|
||
|
||
.economy-table { width: 100%; border-collapse: collapse; margin-top: 6px; }
|
||
.economy-table th,
|
||
.economy-table td { padding: 5px 8px; text-align: left; border-bottom: 1px solid rgba(255, 255, 255, 0.08); }
|
||
.economy-table th { color: var(--muted); font-weight: 600; font-size: 11px; }
|
||
.economy-table .numeric { text-align: right; font-variant-numeric: tabular-nums; }
|
||
/* Every data table reads the same way: alternate-row shading and a highlight on
|
||
the row under the pointer. Later, more specific rules (a local nation, a
|
||
sorted header) may override these. */
|
||
.economy-table tbody tr:nth-child(even),
|
||
.diplomacy-table tbody tr:nth-child(even),
|
||
.budget-table tbody tr:nth-child(even) { background: rgba(255, 255, 255, 0.045); }
|
||
.economy-table tbody tr:hover,
|
||
.diplomacy-table tbody tr:hover,
|
||
.budget-table tbody tr:hover { background: rgba(110, 175, 255, 0.12); }
|
||
/* A resource row that expands into its per-building demand. */
|
||
.resource-caret { display: inline-block; width: 12px; color: var(--muted); }
|
||
.economy-table td.clickable { cursor: pointer; }
|
||
.economy-table td.clickable:hover { color: var(--accent); }
|
||
.resource-demand td { padding-top: 2px; padding-bottom: 6px; }
|
||
.demand-line { font-size: 12px; color: var(--muted); padding: 1px 0 1px 14px; }
|
||
.central-bank-rate { display: flex; align-items: center; gap: 8px; margin: 8px 0; }
|
||
.central-bank-rate label { color: var(--muted); }
|
||
.central-bank-rate input { width: 90px; }
|
||
/* The commodity price index chart and its inflation table. */
|
||
.central-bank-prices { margin-top: 6px; }
|
||
.line-chart { display: block; width: 100%; height: auto; margin: 6px 0; }
|
||
.line-chart-line { vector-effect: non-scaling-stroke; }
|
||
.line-chart-baseline {
|
||
stroke: rgba(255, 255, 255, 0.25);
|
||
stroke-width: 1;
|
||
stroke-dasharray: 3 3;
|
||
}
|
||
.line-chart-label { fill: var(--muted); font-size: 9px; }
|
||
.inflation-table { margin-top: 4px; }
|
||
.inflation-swatch {
|
||
display: inline-block;
|
||
width: 10px;
|
||
height: 3px;
|
||
margin-right: 6px;
|
||
border-radius: 2px;
|
||
vertical-align: middle;
|
||
}
|
||
.tax-table .tax-input { width: 52px; }
|
||
.tax-table .tax-apply { padding: 2px 8px; }
|
||
.subhead { margin-top: 10px; color: var(--muted); font-weight: 600; }
|
||
|
||
.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; }
|
||
|
||
/* A foreign figure's equivalent in our money, kept quiet beside it. */
|
||
.conversion { color: var(--muted); font-size: 0.85em; }
|
||
|
||
/* 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 tr.budget-buy .budget-name { padding-left: 26px; }
|
||
.budget-table tr.budget-source .budget-name { padding-left: 44px; color: var(--muted); }
|
||
.budget-table tr.budget-source .budget-value { color: var(--muted); }
|
||
.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; }
|
||
.budget-table tr.budget-subsection td {
|
||
padding-top: 8px;
|
||
color: var(--muted);
|
||
font-size: 11px;
|
||
}
|
||
.budget-months {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 10px;
|
||
padding: 4px 18px 0;
|
||
}
|
||
.budget-months:empty { display: none; }
|
||
.budget-month-nav {
|
||
min-width: 28px;
|
||
padding: 2px 8px;
|
||
font-size: 12px;
|
||
}
|
||
.budget-month-label { color: var(--muted); font-size: 12px; min-width: 120px; text-align: center; }
|
||
|
||
.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 city and tile panels keep a fixed size whatever tab is open, so switching
|
||
between train, buildings, people and budget never resizes the card. They match
|
||
the nation panel so the three read as the same surface. The unit buttons sit in
|
||
a fixed-height box that scrolls when there are more units than fit; the queue
|
||
below is the flexible part and scrolls too, so adding units never stretches
|
||
the card or pushes the buttons out of reach. */
|
||
#modal-city .modal-card,
|
||
#modal-tile .modal-card {
|
||
width: 90vw;
|
||
max-width: 1600px;
|
||
height: min(80vh, 900px);
|
||
max-height: calc(100vh - 120px);
|
||
}
|
||
#modal-city #city-train-list {
|
||
flex: 0 0 auto;
|
||
width: 100%;
|
||
max-height: 168px;
|
||
overflow-y: auto;
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 8px;
|
||
}
|
||
#modal-city #city-train-list .train-button {
|
||
flex: 0 0 auto;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 10px;
|
||
text-align: left;
|
||
}
|
||
#city-train-list .train-name { font-weight: 600; }
|
||
#city-train-list .train-resources { display: inline-flex; align-items: center; gap: 8px; }
|
||
#city-train-list .train-price { color: var(--muted); font-size: 12px; font-variant-numeric: tabular-nums; }
|
||
#city-train-list .train-time { color: var(--muted); font-size: 12px; margin-left: auto; font-variant-numeric: tabular-nums; }
|
||
/* Local test game: the free, instant twin of an order button. */
|
||
.free-button {
|
||
border: 1px dashed rgba(130, 225, 170, 0.75) !important;
|
||
color: #b8f5cd;
|
||
}
|
||
.free-button:disabled { opacity: 0.4; }
|
||
#modal-city .training-status { flex: 0 0 auto; }
|
||
#modal-city .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; }
|
||
#city-train-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;
|
||
}
|
||
|
||
/* Politics: the nation Politics tab and the city People tab. */
|
||
.politics-card {
|
||
padding: 12px;
|
||
margin-bottom: 10px;
|
||
border-radius: 9px;
|
||
background: rgba(255, 255, 255, 0.05);
|
||
border: 1px solid rgba(255, 255, 255, 0.06);
|
||
}
|
||
.politics-card > .name { font-weight: 700; margin-bottom: 4px; }
|
||
.politics-controls {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
align-items: flex-end;
|
||
gap: 10px;
|
||
margin-top: 8px;
|
||
}
|
||
.politics-field {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 4px;
|
||
font-size: 12px;
|
||
}
|
||
.politics-field .politics-label { color: var(--muted); text-transform: uppercase; letter-spacing: 0.04em; }
|
||
.politics-select {
|
||
background: rgba(0, 0, 0, 0.4);
|
||
color: inherit;
|
||
border: 1px solid rgba(255, 255, 255, 0.15);
|
||
border-radius: 6px;
|
||
padding: 4px 6px;
|
||
}
|
||
.politics-readout { color: var(--muted); font-size: 12px; margin-left: 8px; }
|
||
.politics-policy {
|
||
padding: 8px 0;
|
||
border-top: 1px solid rgba(255, 255, 255, 0.07);
|
||
}
|
||
.politics-policy:first-of-type { border-top: none; }
|
||
.politics-row {
|
||
padding: 4px 0;
|
||
font-size: 13px;
|
||
border-top: 1px solid rgba(255, 255, 255, 0.05);
|
||
}
|
||
.politics-penalty { color: #f0b060; }
|
||
.famine-warning { color: #e05a4a; font-weight: 600; }
|
||
.ethnic-chart { display: flex; align-items: center; gap: 16px; flex-wrap: wrap; margin-top: 8px; }
|
||
.ethnic-legend { display: flex; flex-direction: column; gap: 4px; font-size: 13px; }
|
||
.ethnic-legend-row { display: flex; align-items: center; gap: 8px; }
|
||
.ethnic-swatch { width: 12px; height: 12px; border-radius: 3px; display: inline-block; }
|
||
.ethnic-donut { flex: 0 0 auto; }
|
||
.politics-policy-row { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
|
||
.politics-new { margin-top: 10px; }
|
||
.politics-wizard {
|
||
margin-top: 10px;
|
||
padding: 12px;
|
||
border-radius: 8px;
|
||
background: rgba(0, 0, 0, 0.28);
|
||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||
}
|
||
.politics-estimate { margin-top: 8px; font-size: 13px; }
|
||
.politics-cost { color: #f0b060; }
|
||
.politics-wizard .politics-controls { margin-top: 0; }
|
||
.ethnic-trend { font-weight: 700; margin-left: 2px; }
|
||
.trend-up { color: #6ee787; }
|
||
.trend-down { color: #f27272; }
|
||
#tile-ethnicity { margin-top: 6px; }
|
||
#tile-ethnicity .tile-population { font-size: 13px; font-variant-numeric: tabular-nums; }
|
||
#tile-ethnicity .ethnic-chart { margin-top: 4px; gap: 10px; }
|
||
#tile-ethnicity .ethnic-legend { font-size: 12px; }
|
||
.politics-row-actions { display: flex; justify-content: flex-end; margin-top: 4px; }
|
||
.politics-spend { display: flex; align-items: center; gap: 8px; margin-top: 6px; }
|
||
.politics-slider { flex: 1 1 auto; accent-color: #7fc4ff; }
|
||
.opinion-list { margin-top: 10px; display: flex; flex-direction: column; gap: 4px; }
|
||
.opinion-heading {
|
||
margin-top: 8px;
|
||
color: var(--muted);
|
||
text-transform: uppercase;
|
||
font-size: 11px;
|
||
letter-spacing: 0.05em;
|
||
}
|
||
.opinion-row {
|
||
display: grid;
|
||
grid-template-columns: 170px 1fr 48px;
|
||
align-items: center;
|
||
gap: 8px;
|
||
font-size: 13px;
|
||
}
|
||
.opinion-bar { height: 10px; border-radius: 5px; background: rgba(0, 0, 0, 0.4); overflow: hidden; }
|
||
.opinion-fill { height: 100%; background: #6ee787; }
|
||
.opinion-fill.negative { background: #f27272; }
|
||
.opinion-pct { text-align: right; color: var(--muted); }
|