Files
Battle-for-Tismo/client/css/style.css
T

1837 lines
58 KiB
CSS
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
: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); }
/* The unified variable display: a unit (currency symbol or commodity icon), a
space-grouped number, then a suffix (a resource's unit or a power of ten).
A flow carries its sign and reads green as income, red as a cost. */
.value { white-space: nowrap; }
.value .value-icon { width: 13px; height: 13px; vertical-align: -2px; }
.value .value-number { font-variant-numeric: tabular-nums; }
.value .value-number.positive { color: #8fe3a8; }
.value .value-number.negative { color: #ff9a9a; }
/* 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-note {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 2px 6px;
font-size: 10.5px;
color: var(--muted);
}
.building-tile .bt-bill {
display: inline-flex;
flex-wrap: wrap;
align-items: center;
gap: 2px 6px;
}
/* The upkeep reads as a cost, in the same tan the tooltips use. */
.building-tile .bt-note .res,
.building-tile .bt-note .bt-bill-suffix { color: #e2c08a; }
.building-tile .bt-note .res { display: inline; white-space: nowrap; }
.building-tile .bt-note .res img { width: 12px; height: 12px; opacity: 0.9; vertical-align: -2px; margin-right: 2px; }
.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; }
/* A research figure on the button carries the erlenmeyer icon in the meta line. */
.building-tile .bt-meta .res { display: inline; white-space: nowrap; }
.building-tile .bt-meta .res img { width: 11px; height: 11px; vertical-align: -2px; margin-right: 3px; }
.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; white-space: nowrap; }
.card-tooltip .res img { width: 13px; height: 13px; vertical-align: -2px; margin-right: 3px; }
/* 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-sub .res { display: inline; white-space: nowrap; }
.modal-sub .res img { width: 13px; height: 13px; vertical-align: -2px; margin-right: 3px; }
.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 .effects .res { display: inline; white-space: nowrap; }
.row-card .effects .res img { width: 13px; height: 13px; vertical-align: -2px; margin-right: 3px; }
.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 the same building cards as the city tab, tiled across
the tab so many read at once, grouped by the theme sub-tabs. */
#technology-list {
display: grid;
grid-template-columns: repeat(auto-fill, 178px);
gap: 8px;
align-content: start;
}
/* A focused technology turns its action button into the progress bar: the
committed share fills behind, while the "Focused" label and the points sit on
top. The disabled dimming is lifted so the bar stays legible. */
.building-tile .bt-upgrade {
position: relative;
overflow: hidden;
}
.building-tile .bt-upgrade .bt-progress-fill {
position: absolute;
inset: 0 auto 0 0;
width: 0;
background: linear-gradient(90deg, rgba(79, 140, 255, 0.7), rgba(127, 209, 255, 0.7));
pointer-events: none;
z-index: 0;
}
.building-tile .bt-upgrade .bt-up-label,
.building-tile .bt-upgrade .bt-meta { position: relative; z-index: 1; }
.building-tile .bt-upgrade.progressing:disabled { opacity: 1; }
.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%;
overflow-x: auto;
overflow-y: hidden;
padding-bottom: 8px;
}
/* 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); }