fix: draw street map ground and repair mobile layout
CI/CD / test (push) Successful in 57s
CI/CD / publish (push) Successful in 1m8s
CI/CD / deploy (push) Successful in 6s

- Turn the satellite ground off behind SATELLITE_GROUND so OSM streets,
  parks and land use are drawn; the imagery index is no longer fetched.
- Restore phone rules lost in the CSS-to-JS theme migration (#rank
  height, toolbar button size) and replace invalid -var() values.
- Add a landscape-phone layout and make the briefing scrollable.
- Add two-finger pinch zoom to the big map.
- Add regression tests for the theme and the skipped imagery request.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-29 07:51:40 +03:30
co-authored by Claude Opus 5.5
parent df930694f7
commit 526796126d
9 changed files with 200 additions and 27 deletions
+68 -15
View File
@@ -25,6 +25,10 @@ import { createTraffic } from '../systems/traffic.js';
import { createSportSedan } from '../models/carModels.js';
import { createMapAssets } from '../world/mapRender.js';
import { formatDistance, toLocal } from '../../utils/geo.js';
// City map zoom limits in metres per screen pixel (closest and furthest).
const MAP_ZOOM_MIN = 0.35;
const MAP_ZOOM_MAX = 30;
/**
* Initializes timers in dependency order.
* @param {*} context - context.
@@ -863,24 +867,72 @@ export async function registerMapTabs(context) {
* @returns {*} Operation result, when applicable.
*/
export async function registerMapDragging(context) {
// Every finger (or the mouse) on the map, so two fingers can pinch to zoom on phones.
context.mapPointers = new Map();
context.mapPinch = null;
context.listen(context.mapCanvas, 'pointerdown', (e) => {
context.mapDrag = {
x: e.clientX,
y: e.clientY,
moved: 0,
};
context.mapPointers.set(e.pointerId, { x: e.clientX, y: e.clientY });
context.mapCanvas.setPointerCapture(e.pointerId);
// A second finger turns the gesture into a pinch; it never counts as a tap on a machine.
if (context.mapPointers.size > 1) {
context.mapDrag = null;
context.mapPinch = pointerSpread(context.mapPointers);
return;
}
context.mapDrag = { x: e.clientX, y: e.clientY, moved: 0 };
});
context.listen(context.mapCanvas, 'pointermove', (e) => {
if (!context.mapDrag) return;
context.mapView.cx -= (e.clientX - context.mapDrag.x) * context.mapView.mpp;
context.mapView.cz -= (e.clientY - context.mapDrag.y) * context.mapView.mpp;
context.mapDrag.moved +=
Math.abs(e.clientX - context.mapDrag.x) + Math.abs(e.clientY - context.mapDrag.y);
context.mapDrag.x = e.clientX;
context.mapDrag.y = e.clientY;
context.renderMap();
if (!context.mapPointers.has(e.pointerId)) return;
context.mapPointers.set(e.pointerId, { x: e.clientX, y: e.clientY });
if (context.mapPinch) pinchMap(context);
else if (context.mapDrag) dragMap(context, e);
});
const release = (e) => {
context.mapPointers.delete(e.pointerId);
if (context.mapPointers.size < 2) context.mapPinch = null;
};
context.listen(context.mapCanvas, 'pointercancel', (e) => {
release(e);
context.mapDrag = null;
});
context.mapRelease = release;
}
/**
* Measures the distance between the first two pointers on the map.
* @param {Map<number, {x: number, y: number}>} pointers - Active pointers by id.
* @returns {number} Distance in CSS pixels.
*/
function pointerSpread(pointers) {
const [a, b] = [...pointers.values()];
return Math.hypot(a.x - b.x, a.y - b.y) || 1;
}
/**
* Zooms the city map by how much the two fingers spread or closed since the last move.
* @param {*} context - Game context with the map view and active pointers.
*/
function pinchMap(context) {
const spread = pointerSpread(context.mapPointers);
context.mapView.mpp = THREE.MathUtils.clamp(
(context.mapView.mpp * context.mapPinch) / spread,
MAP_ZOOM_MIN,
MAP_ZOOM_MAX,
);
context.mapPinch = spread;
context.renderMap();
}
/**
* Pans the city map with a one-finger or mouse drag.
* @param {*} context - Game context with the map view and drag state.
* @param {PointerEvent} e - The move event.
*/
function dragMap(context, e) {
context.mapView.cx -= (e.clientX - context.mapDrag.x) * context.mapView.mpp;
context.mapView.cz -= (e.clientY - context.mapDrag.y) * context.mapView.mpp;
context.mapDrag.moved +=
Math.abs(e.clientX - context.mapDrag.x) + Math.abs(e.clientY - context.mapDrag.y);
context.mapDrag.x = e.clientX;
context.mapDrag.y = e.clientY;
context.renderMap();
}
/**
* Runs the register map selection and zoom operation for this subsystem.
@@ -899,6 +951,7 @@ export async function registerMapSelectionAndZoom(context) {
if (hit) context.selectMachine(hit.def.id);
}
context.mapDrag = null;
context.mapRelease(e);
});
context.listen(
context.mapCanvas,
@@ -907,8 +960,8 @@ export async function registerMapSelectionAndZoom(context) {
e.preventDefault();
context.mapView.mpp = THREE.MathUtils.clamp(
context.mapView.mpp * (e.deltaY > 0 ? 1.15 : 1 / 1.15),
0.35,
30,
MAP_ZOOM_MIN,
MAP_ZOOM_MAX,
);
context.renderMap();
},