feat(ui): map canvas follows light/dark theme; fix invisible gear control
Tests · UI / test (push) Waiting to run
Tests · UI / test (pull_request) Successful in 2m45s

The map view now selects a DARK_THEME or LIGHT_THEME palette from the
resolved app theme and threads it through every primitive builder, so
the canvas, planets, ship groups, cargo routes, battle/bombing markers,
fog, reach + selection rings, pending-Send tracks, and the pick overlay
all switch with the rest of the chrome. A theme flip remounts the
renderer preserving the camera — Pixi bakes the background at init and
every primitive bakes its colour at build, so a live re-tint is not
possible on the same instance.

This also fixes the reported bug: the gear-popover trigger and the
loading overlay hardcoded a dark navy background, so in light theme the
gear was invisible (dark icon on dark chip) until hover flipped it to a
white chip. Both now use the --color-surface-overlay token and read
correctly in both themes.

The light palette mirrors the dark one role-for-role, darkened /
saturated for contrast on a light background while keeping the incoming,
battle, and bombing accents vivid. The values are a first pass meant to
be refined during the F8 manual-QA loop.

Removes the now-dead "Phase 35" references from the code and lifts the
map-recoloring prohibition from the design-system / renderer docs; the
battle scene stays a fixed-palette data-viz surface.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Ilia Denisov
2026-05-24 08:49:37 +02:00
parent d44ad9b6eb
commit f6e4a4f6bd
27 changed files with 631 additions and 230 deletions
+59 -45
View File
@@ -38,7 +38,14 @@ import type {
} from "../api/game-state";
import type { ShipGroupRef } from "../lib/selection.svelte";
import { torusShortestDelta } from "./math";
import type { LinePrim, PointPrim, PrimitiveID, Style } from "./world";
import {
DARK_THEME,
type LinePrim,
type PointPrim,
type PrimitiveID,
type Style,
type Theme,
} from "./world";
/**
* SHIP_GROUP_ID_OFFSETS partitions the primitive-id namespace so a
@@ -56,43 +63,46 @@ export const SHIP_GROUP_ID_OFFSETS = {
unidentified: 400_000_000,
} as const;
const STYLE_LOCAL_GROUP: Style = {
fillColor: 0xfff176,
fillAlpha: 0.95,
pointRadiusPx: 3,
};
const STYLE_LOCAL_INSPACE_LINE: Style = {
strokeColor: 0xfff176,
strokeAlpha: 0.7,
strokeWidthPx: 1,
strokeDashPx: 4,
};
const STYLE_OTHER_GROUP: Style = {
fillColor: 0xff6f40,
fillAlpha: 0.9,
pointRadiusPx: 3,
};
const STYLE_INCOMING_GROUP: Style = {
fillColor: 0xff5252,
fillAlpha: 1,
pointRadiusPx: 4,
};
const STYLE_INCOMING_LINE: Style = {
strokeColor: 0xff5252,
strokeAlpha: 0.85,
strokeWidthPx: 1,
strokeDashPx: 4,
};
const STYLE_UNIDENTIFIED_GROUP: Style = {
fillColor: 0x9aa3a8,
fillAlpha: 0.65,
pointRadiusPx: 3,
};
// shipGroupStyles builds the per-variant `Style` objects for the
// active theme. Only the colours are theme-driven; the alpha, radius,
// and dash spacing are fixed emphasis values. The in-space track
// reuses the own-group colour and the incoming trajectory line reuses
// the incoming colour so each pair reads as one entity.
function shipGroupStyles(theme: Theme): {
local: Style;
localLine: Style;
other: Style;
incoming: Style;
incomingLine: Style;
unidentified: Style;
} {
return {
local: { fillColor: theme.shipLocal, fillAlpha: 0.95, pointRadiusPx: 3 },
localLine: {
strokeColor: theme.shipLocal,
strokeAlpha: 0.7,
strokeWidthPx: 1,
strokeDashPx: 4,
},
other: { fillColor: theme.shipOther, fillAlpha: 0.9, pointRadiusPx: 3 },
incoming: {
fillColor: theme.shipIncoming,
fillAlpha: 1,
pointRadiusPx: 4,
},
incomingLine: {
strokeColor: theme.shipIncoming,
strokeAlpha: 0.85,
strokeWidthPx: 1,
strokeDashPx: 4,
},
unidentified: {
fillColor: theme.shipUnidentified,
fillAlpha: 0.65,
pointRadiusPx: 3,
},
};
}
// Priority order inside `hit-test`: ship groups outrank planets so a
// hyperspace group landing on top of an unidentified planet is
@@ -146,7 +156,11 @@ function addDependent(
set.add(primitiveId);
}
export function shipGroupsToPrimitives(report: GameReport): ShipGroupPrimitives {
export function shipGroupsToPrimitives(
report: GameReport,
theme: Theme = DARK_THEME,
): ShipGroupPrimitives {
const styles = shipGroupStyles(theme);
const primitives: (PointPrim | LinePrim)[] = [];
const lookup = new Map<PrimitiveID, ShipGroupRef>();
const categories = new Map<PrimitiveID, ShipGroupCategory>();
@@ -163,7 +177,7 @@ export function shipGroupsToPrimitives(report: GameReport): ShipGroupPrimitives
const pos = computeInSpacePosition(group, planetIndex, w, h);
if (pos === null) continue;
const id = SHIP_GROUP_ID_OFFSETS.local + i;
primitives.push(makePoint(id, pos.x, pos.y, PRIORITY_LOCAL, STYLE_LOCAL_GROUP));
primitives.push(makePoint(id, pos.x, pos.y, PRIORITY_LOCAL, styles.local));
lookup.set(id, { variant: "local", id: group.id });
categories.set(id, "hyperspaceGroup");
addDependent(planetDependents, group.destination, id);
@@ -183,7 +197,7 @@ export function shipGroupsToPrimitives(report: GameReport): ShipGroupPrimitives
kind: "line",
id: lineId,
priority: PRIORITY_LOCAL_LINE,
style: STYLE_LOCAL_INSPACE_LINE,
style: styles.localLine,
hitSlopPx: 0,
x1: origin.x,
y1: origin.y,
@@ -200,7 +214,7 @@ export function shipGroupsToPrimitives(report: GameReport): ShipGroupPrimitives
const pos = computeInSpacePosition(group, planetIndex, w, h);
if (pos === null) continue;
const id = SHIP_GROUP_ID_OFFSETS.other + i;
primitives.push(makePoint(id, pos.x, pos.y, PRIORITY_OTHER, STYLE_OTHER_GROUP));
primitives.push(makePoint(id, pos.x, pos.y, PRIORITY_OTHER, styles.other));
lookup.set(id, { variant: "other", index: i });
categories.set(id, "hyperspaceGroup");
addDependent(planetDependents, group.destination, id);
@@ -225,7 +239,7 @@ export function shipGroupsToPrimitives(report: GameReport): ShipGroupPrimitives
kind: "line",
id: lineId,
priority: PRIORITY_INCOMING_LINE,
style: STYLE_INCOMING_LINE,
style: styles.incomingLine,
hitSlopPx: 0,
x1: origin.x,
y1: origin.y,
@@ -242,7 +256,7 @@ export function shipGroupsToPrimitives(report: GameReport): ShipGroupPrimitives
pos.x,
pos.y,
PRIORITY_INCOMING_POINT,
STYLE_INCOMING_GROUP,
styles.incoming,
/*hitSlopPx*/ 4,
),
);
@@ -261,7 +275,7 @@ export function shipGroupsToPrimitives(report: GameReport): ShipGroupPrimitives
group.x,
group.y,
PRIORITY_UNIDENTIFIED,
STYLE_UNIDENTIFIED_GROUP,
styles.unidentified,
),
);
lookup.set(id, { variant: "unidentified", index: i });