diff --git a/station-navigation/app.js b/station-navigation/app.js
index e038809e..5289672b 100644
--- a/station-navigation/app.js
+++ b/station-navigation/app.js
@@ -1,4 +1,4 @@
-const state = { stations: [], query: '', searchText: '', locationFilter: { province: '', city: '', district: '' }, suggestions: [], activeSuggestion: -1, searchDebounce: null, stationFilter: 'all', selected: null, userLocation: null, map: null, markers: [], userMarker: null, pitch: true };
+const state = { stations: [], query: '', searchText: '', locationFilters: [], suggestions: [], activeSuggestion: -1, searchDebounce: null, stationFilter: 'all', selected: null, userLocation: null, map: null, markers: [], userMarker: null, pitch: true };
document.addEventListener('DOMContentLoaded', () => { initClock(); initMap(); loadStations(); });
document.addEventListener('pointerdown', event => { if (!event.target?.closest?.('.map-explore-toolbar')) closeStationSearchSuggestions(); });
@@ -31,12 +31,17 @@ function updateSummary(summary) {
const total = Number(summary.totalStations || 0), partner = Number(summary.cooperativeStations || 0), external = Math.max(0, total - partner);
setHTML('kpiTotal', `${formatNumber(total)} 座`); setHTML('kpiPartner', `${formatNumber(partner)} 座`);
setHTML('statusTotal', `${formatNumber(total)} 座`); setHTML('statusPartner', `${formatNumber(partner)} 座`); setHTML('statusExternal', `${formatNumber(external)} 座`);
+ setHTML('mobileStatusTotal', `${formatNumber(total)} 座`); setHTML('mobileStatusPartner', `${formatNumber(partner)} 座`); setHTML('mobileStatusExternal', `${formatNumber(external)} 座`);
document.getElementById('partnerSegment').style.width = `${total ? partner * 100 / total : 0}%`;
document.getElementById('externalSegment').style.width = `${total ? external * 100 / total : 0}%`;
+ document.getElementById('mobilePartnerSegment').style.width = `${total ? partner * 100 / total : 0}%`;
+ document.getElementById('mobileExternalSegment').style.width = `${total ? external * 100 / total : 0}%`;
}
function stationMatchesQuery(station, query = state.query) { const needle = normalize(query); return !needle || [station.name, station.shortName, station.address].some(value => normalize(value).includes(needle)); }
-function stationMatchesLocation(station) { const filter = state.locationFilter; return (!filter.province || station.province === filter.province) && (!filter.city || station.city === filter.city) && (!filter.district || stationDistrictName(station) === filter.district); }
+function locationFilterKey(filter) { return [filter.level || 'location', filter.province || '', filter.city || '', filter.district || ''].join('|'); }
+function stationMatchesLocationFilter(station, filter) { return (!filter.province || station.province === filter.province) && (!filter.city || station.city === filter.city) && (!filter.district || stationDistrictName(station) === filter.district); }
+function stationMatchesLocation(station) { return !state.locationFilters.length || state.locationFilters.some(filter => stationMatchesLocationFilter(station, filter)); }
function stationTypeScope() { return state.stations.filter(station => state.stationFilter !== 'partner' || station.cooperative); }
function filteredStations() { return stationTypeScope().filter(station => stationMatchesLocation(station) && stationMatchesQuery(station)); }
function stationSearchScore(station, query) {
@@ -101,7 +106,7 @@ function renderList() {
document.getElementById('rankViewportMeta').textContent = `当前视野 ${formatNumber(visibleNodes.length)} · 全部 ${formatNumber(allNodes.length)}`;
document.getElementById('panelRankTitle').textContent = state.userLocation && level === 'station' ? `附近${state.stationFilter === 'partner' ? '合作' : ''}加氢站` : level === 'province' ? '加氢站省份 TOP 排名' : level === 'city' ? '加氢站城市 TOP 排名' : '加氢站 TOP 排名';
if (!display.length) { box.innerHTML = '
未找到匹配站点
请尝试名称、地址、城市或拼音首字母
'; return; }
- box.innerHTML = display.slice(0, 100).map((node, index) => { const distance = node.kind === 'station' ? stationDistance(node.station) : Number.POSITIVE_INFINITY, partnerTag = node.kind === 'station' && node.cooperative ? '合作' : ''; return ``; }).join('');
+ box.innerHTML = display.slice(0, 100).map((node, index) => { const distance = node.kind === 'station' ? stationDistance(node.station) : Number.POSITIVE_INFINITY, partnerTag = node.kind === 'station' ? `${node.cooperative ? '合作' : ''}` : '', rowValue = Number.isFinite(distance) ? `${formatNumber(distance, 1)} km` : node.kind === 'station' ? '' : `${formatNumber(node.count)} 座`; return ``; }).join('');
box.querySelectorAll('[data-node-id]').forEach(button => button.addEventListener('click', () => { const node = display.find(item => item.id === button.dataset.nodeId); if (node?.kind === 'station') selectStation(node.station, true); else if (node) state.map?.setZoomAndCenter(nextZoomForLevel(node.level), node.lnglat); }));
}
function compareNodes(left, right) { if (state.userLocation && left.kind === 'station' && right.kind === 'station') return stationDistance(left.station) - stationDistance(right.station); return right.count - left.count || String(left.name || '').localeCompare(String(right.name || ''), 'zh-CN'); }
@@ -150,6 +155,22 @@ function renderStationSearchSuggestions() {
}
function openStationSearchSuggestions() { const box = document.getElementById('stationSearchSuggestions'); if (!box) return; box.hidden = false; renderStationSearchSuggestions(); }
function closeStationSearchSuggestions() { const box = document.getElementById('stationSearchSuggestions'); if (box) box.hidden = true; state.activeSuggestion = -1; }
+function locationFilterLabel(filter) { return filter.label || (filter.district ? stationGroupName({ city: filter.district }, 'city') : filter.city ? stationGroupName({ city: filter.city }, 'city') : stationGroupName({ province: filter.province }, 'province')); }
+function renderLocationFilterChips() {
+ const rail = document.getElementById('selectedLocationFilters'); if (!rail) return;
+ rail.hidden = !state.locationFilters.length;
+ rail.innerHTML = state.locationFilters.map(filter => { const key = locationFilterKey(filter), label = locationFilterLabel(filter); return ``; }).join('');
+}
+function removeLocationFilter(key) {
+ state.locationFilters = state.locationFilters.filter(filter => locationFilterKey(filter) !== key);
+ state.selected = null; closeDetail(); renderLocationFilterChips(); render(); renderStationSearchSuggestions();
+ document.getElementById('selectedRegionHint').textContent = state.locationFilters.length ? `视角: 已筛选 ${state.locationFilters.map(locationFilterLabel).join('、')}` : '视角: 全国加氢站网络';
+}
+function addLocationFilter(option) {
+ const filter = { level: option.level, province: option.province, city: option.city, district: option.district, label: option.label };
+ if (!state.locationFilters.some(current => locationFilterKey(current) === locationFilterKey(filter))) state.locationFilters.push(filter);
+ renderLocationFilterChips();
+}
function focusSearchResults() {
const stations = filteredStations(); if (!state.map || !stations.length) return;
const longitudes = stations.map(station => Number(station.longitude)), latitudes = stations.map(station => Number(station.latitude));
@@ -161,11 +182,11 @@ function focusSearchResults() {
function handleStationSearchInput(value) { state.searchText = value; document.getElementById('clearSearch').hidden = !normalize(value); state.activeSuggestion = -1; openStationSearchSuggestions(); window.clearTimeout(state.searchDebounce); state.searchDebounce = window.setTimeout(() => { state.query = value; state.selected = null; closeDetail(); render(); if (normalize(value)) focusSearchResults(); }, 180); }
function selectStationSearchSuggestion(index) {
const item = state.suggestions[index]; if (!item) return;
- if (item.type === 'location') { state.locationFilter = { province: item.province, city: item.city, district: item.district }; state.query = ''; state.searchText = ''; document.getElementById('stationSearch').value = ''; document.getElementById('clearSearch').hidden = true; state.map?.setZoomAndCenter(item.level === 'province' ? 7.2 : item.level === 'city' ? 10.5 : 13, item.lnglat); document.getElementById('selectedRegionHint').textContent = `视角: ${item.path}`; closeStationSearchSuggestions(); render(); return; }
+ if (item.type === 'location') { addLocationFilter(item); state.query = ''; state.searchText = ''; document.getElementById('stationSearch').value = ''; document.getElementById('clearSearch').hidden = true; state.map?.setZoomAndCenter(item.level === 'province' ? 7.2 : item.level === 'city' ? 10.5 : 13, item.lnglat); document.getElementById('selectedRegionHint').textContent = `视角: 已筛选 ${state.locationFilters.map(locationFilterLabel).join('、')}`; closeStationSearchSuggestions(); render(); return; }
closeStationSearchSuggestions(); selectStation(item.station, true);
}
function setStationFilter(filter) { if (!['all', 'partner'].includes(filter) || state.stationFilter === filter) return; state.stationFilter = filter; state.selected = null; closeDetail(); document.querySelectorAll('[data-station-filter]').forEach(button => button.classList.toggle('is-active', button.dataset.stationFilter === filter)); render(); renderStationSearchSuggestions(); }
-function clearSearch() { window.clearTimeout(state.searchDebounce); document.getElementById('stationSearch').value = ''; state.query = ''; state.searchText = ''; state.locationFilter = { province: '', city: '', district: '' }; closeStationSearchSuggestions(); closeDetail(); resetMap(); render(); }
+function clearSearch() { window.clearTimeout(state.searchDebounce); document.getElementById('stationSearch').value = ''; state.query = ''; state.searchText = ''; state.locationFilters = []; renderLocationFilterChips(); closeStationSearchSuggestions(); closeDetail(); resetMap(); render(); }
function handleSearchKeydown(event) { if (event.key === 'Escape') { closeStationSearchSuggestions(); event.currentTarget.blur(); return; } if (!['ArrowDown', 'ArrowUp', 'Enter'].includes(event.key)) return; const box = document.getElementById('stationSearchSuggestions'); if (box?.hidden) openStationSearchSuggestions(); if (event.key === 'Enter' && state.activeSuggestion >= 0) { event.preventDefault(); selectStationSearchSuggestion(state.activeSuggestion); return; } if (event.key !== 'Enter') { event.preventDefault(); const direction = event.key === 'ArrowDown' ? 1 : -1; state.activeSuggestion = (state.activeSuggestion + direction + state.suggestions.length) % Math.max(state.suggestions.length, 1); renderStationSearchSuggestions(); } }
function selectStation(station, fit = false) {
diff --git a/station-navigation/index.html b/station-navigation/index.html
index 69a6c06c..decae925 100644
--- a/station-navigation/index.html
+++ b/station-navigation/index.html
@@ -33,12 +33,15 @@
+
+
@@ -53,7 +56,7 @@
地图引擎: 羚牛氢能 GIS (AMap 3D Engine)站点数据服务 · 公开目录视角: 全国加氢站网络
diff --git a/station-navigation/server.py b/station-navigation/server.py
index 6831ed60..96037190 100644
--- a/station-navigation/server.py
+++ b/station-navigation/server.py
@@ -125,6 +125,11 @@ def _prewarm_station_directory_cache():
class StationNavigationHandler(SimpleHTTPRequestHandler):
server_version = "LingniuStationNavigation/1.0"
+ def __init__(self, *args, **kwargs):
+ # Static files must resolve from this release directory even when the
+ # local launcher is invoked from the repository root.
+ super().__init__(*args, directory=str(ROOT), **kwargs)
+
def do_GET(self):
path = urlparse(self.path).path
if path in {"/", "/index.html"}:
diff --git a/station-navigation/styles.css b/station-navigation/styles.css
index 9a1ee725..ce36b532 100644
--- a/station-navigation/styles.css
+++ b/station-navigation/styles.css
@@ -1,8 +1,17 @@
/* Keep the public service visually identical to the main map shell. */
-.station-navigation-shell .header-kpi-group { display: flex; }
+.station-navigation-shell .station-summary-strip { display: none; }
+.station-navigation-shell .station-summary-strip .panel-header { margin-bottom: 8px; }
+.station-navigation-shell .station-summary-strip .status-glass-grid3 { margin-bottom: 7px; }
+.station-navigation-shell .station-summary-strip .glass-cell { padding: 6px 9px; }
.station-navigation-shell .map-filter-chip { min-height: 27px; padding: 0 9px; border: 1px solid var(--glass-border); border-radius: 8px; background: var(--pill-bg); color: var(--text-muted); font-size: 10px; font-weight: 700; cursor: pointer; }
.station-navigation-shell .map-filter-chip.is-active { border-color: rgba(0,113,67,.24); background: rgba(0,113,67,.1); color: var(--accent-primary); }
.station-navigation-shell .filter-chip-rail { display: flex; gap: 6px; }
+.station-navigation-shell .selected-location-filter-rail { display: flex; align-items: center; gap: 6px; min-width: 0; max-width: min(360px, 30vw); overflow-x: auto; scrollbar-width: none; }
+.station-navigation-shell .selected-location-filter-rail::-webkit-scrollbar { display: none; }
+.station-navigation-shell .selected-location-filter-rail[hidden] { display: none; }
+.station-navigation-shell .selected-location-filter-chip { display: inline-flex; flex: 0 0 auto; align-items: center; gap: 7px; min-height: 29px; max-width: 132px; padding: 0 8px 0 10px; border: 1px solid rgba(0,113,67,.22); border-radius: 999px; background: rgba(0,113,67,.09); color: var(--accent-primary); font-size: 10px; font-weight: 700; cursor: pointer; }
+.station-navigation-shell .selected-location-filter-chip span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
+.station-navigation-shell .selected-location-filter-chip b { font: 700 15px/1 -apple-system, BlinkMacSystemFont, sans-serif; }
.station-navigation-shell .province-info-card.is-station-point { min-width: 92px; }
.station-navigation-shell .province-info-card.is-station-point .p-total { color: var(--accent-primary); }
.station-navigation-shell .province-info-card.is-external .p-total { color: var(--text-muted); }
@@ -10,11 +19,136 @@
.station-navigation-shell .rank-glass-row.is-selected { background: rgba(0,113,67,.09); border-color: rgba(0,113,67,.2); }
.station-navigation-shell .r-name { display: flex; min-width: 0; align-items: flex-start; gap: 5px; line-height: 1.35; }
.station-navigation-shell .station-list-name { min-width: 0; overflow-wrap: anywhere; white-space: normal; }
-.station-navigation-shell .station-list-partner-tag { flex: 0 0 auto; margin-top: 1px; padding: 2px 5px; border-radius: 4px; background: rgba(0,113,67,.1); color: var(--accent-primary); font-size: 9px; font-weight: 700; line-height: 1; }
+.station-navigation-shell .station-list-partner-tag { flex: 0 0 30px; min-width: 30px; margin-top: 1px; padding: 2px 5px; border-radius: 4px; background: rgba(0,113,67,.1); color: var(--accent-primary); font-size: 9px; font-weight: 700; line-height: 1; text-align: center; }
+.station-navigation-shell .station-list-partner-tag.is-placeholder { visibility: hidden; }
.station-navigation-shell .rank-empty { display: grid; min-height: 160px; place-items: center; color: var(--text-muted); font-size: 12px; text-align: center; }
.station-navigation-shell .map-detail-card .detail-grid { margin-bottom: 0; }
@media (max-width: 760px) {
- .station-navigation-shell .header-kpi-group { display: flex; }
- .station-navigation-shell .filter-chip-rail { max-width: 124px; overflow: hidden; }
- .station-navigation-shell .map-filter-chip { padding: 0 7px; font-size: 9px; }
+ /* Mobile is navigation-first: keep the first screen focused on map,
+ search and the next action, rather than dashboard chrome. */
+ .station-navigation-shell .liquid-header {
+ position: relative;
+ padding: 9px 10px 10px;
+ gap: 8px;
+ }
+
+ .station-navigation-shell .header-left {
+ width: calc(100% - 78px);
+ gap: 8px;
+ }
+
+ .station-navigation-shell .brand-block { flex-basis: 118px; min-width: 96px; }
+ .station-navigation-shell .brand-logo-svg { max-width: 118px; }
+ .station-navigation-shell .brand-divider { height: 18px; }
+ .station-navigation-shell .cockpit-title { font-size: 14px; }
+
+ .station-navigation-shell .header-controls {
+ position: absolute;
+ top: 8px;
+ right: 8px;
+ width: auto;
+ order: initial;
+ }
+
+ .station-navigation-shell .theme-switcher-bw { padding: 2px; border-radius: 9px; }
+ .station-navigation-shell .theme-switcher-bw .bw-btn { display: grid; width: 31px; min-height: 28px; padding: 4px; place-items: center; }
+ .station-navigation-shell .theme-switcher-bw .bw-btn span { display: none; }
+ .station-navigation-shell .header-kpi-group,
+ .station-navigation-shell .station-status-card { display: none; }
+ .station-navigation-shell .station-summary-strip { display: block; padding: 9px 11px; border-radius: 14px; flex: 0 0 auto; }
+ .station-navigation-shell .station-summary-strip .panel-header { margin-bottom: 7px; padding-bottom: 5px; }
+ .station-navigation-shell .station-summary-strip .status-glass-grid3 { gap: 5px; margin-bottom: 6px; }
+ .station-navigation-shell .station-summary-strip .glass-cell { padding: 6px; }
+
+ .station-navigation-shell .map-spatial-container {
+ height: clamp(430px, 58dvh, 540px);
+ min-height: clamp(430px, 58dvh, 540px);
+ }
+
+ .station-navigation-shell .map-top-bar { display: none; }
+
+ .station-navigation-shell .map-explore-toolbar {
+ top: 8px;
+ left: 8px;
+ right: 8px;
+ gap: 6px;
+ padding: 5px;
+ border-radius: 14px;
+ }
+
+ .station-navigation-shell .locate-btn {
+ flex-basis: 36px;
+ width: 36px;
+ min-height: 36px;
+ border-radius: 10px;
+ }
+
+ .station-navigation-shell .entity-search-field {
+ height: 36px;
+ border-radius: 10px;
+ }
+
+ .station-navigation-shell .entity-search-field input { font-size: 12px; }
+ .station-navigation-shell .selected-location-filter-rail { order: 3; flex: 1 0 100%; max-width: none; padding-bottom: 1px; }
+ .station-navigation-shell .filter-chip-rail { order: 4; max-width: none; overflow: visible; }
+ .station-navigation-shell .map-filter-chip { min-height: 30px; padding: 0 10px; font-size: 10px; }
+ .station-navigation-shell .filter-result-meta { order: 5; padding-right: 5px; font-size: 9px; }
+
+ .station-navigation-shell .explore-suggestions {
+ width: calc(100vw - 32px);
+ max-height: min(300px, calc(100dvh - 248px));
+ border-radius: 14px;
+ }
+
+ .station-navigation-shell .map-action-controls {
+ right: 8px;
+ bottom: 31px;
+ }
+
+ .station-navigation-shell .map-action-controls .glass-btn:nth-child(1),
+ .station-navigation-shell .map-action-controls .glass-btn:nth-child(2) { display: none; }
+
+ .station-navigation-shell .map-action-controls .glass-btn {
+ min-height: 32px;
+ padding: 6px 10px;
+ border-radius: 10px;
+ font-size: 10px;
+ }
+
+ .station-navigation-shell .map-detail-card {
+ bottom: 46px;
+ max-height: min(42dvh, 300px);
+ padding: 12px;
+ border-radius: 17px;
+ }
+
+ .station-navigation-shell .detail-title-wrap h2 {
+ display: -webkit-box;
+ overflow: hidden;
+ white-space: normal;
+ -webkit-box-orient: vertical;
+ -webkit-line-clamp: 2;
+ }
+
+ .station-navigation-shell .detail-navigate-btn,
+ .station-navigation-shell .detail-close-btn {
+ width: 32px;
+ height: 32px;
+ flex-basis: 32px;
+ }
+
+ .station-navigation-shell .detail-navigate-btn svg { width: 15px; height: 15px; }
+ .station-navigation-shell .sidebar-operations { gap: 8px; }
+ .station-navigation-shell .sidebar-card { padding: 10px 11px; border-radius: 14px; }
+ .station-navigation-shell .status-glass-grid3 { gap: 5px; margin-bottom: 7px; }
+ .station-navigation-shell .glass-cell { padding: 6px; }
+ .station-navigation-shell .rank-glass-row { min-height: 42px; padding: 7px 8px; }
+}
+
+@media (max-width: 390px) {
+ .station-navigation-shell .header-left { width: calc(100% - 76px); }
+ .station-navigation-shell .brand-block { flex-basis: 104px; min-width: 88px; }
+ .station-navigation-shell .brand-logo-svg { max-width: 104px; }
+ .station-navigation-shell .cockpit-title { font-size: 13px; }
+ .station-navigation-shell .brand-divider { margin-left: -2px; }
}
diff --git a/station-navigation/tests/test_app.mjs b/station-navigation/tests/test_app.mjs
index 333fa69e..1f9415ba 100644
--- a/station-navigation/tests/test_app.mjs
+++ b/station-navigation/tests/test_app.mjs
@@ -3,6 +3,7 @@ import fs from 'node:fs';
import vm from 'node:vm';
const source = fs.readFileSync(new URL('../app.js', import.meta.url), 'utf8');
+const styles = fs.readFileSync(new URL('../styles.css', import.meta.url), 'utf8');
const sandbox = {
console,
document: { addEventListener() {}, getElementById() { return null; } },
@@ -48,7 +49,7 @@ assert.equal(sandbox.stationNodeCount, 3);
assert.equal(sandbox.locationOptionCount, 8);
vm.runInNewContext(`
globalThis.inferredDistrict = stationDistrictName({ province: '浙江省', city: '嘉兴市', address: '嘉兴市海盐县海盐经济开发区' });
- state.locationFilter = { province: '', city: '', district: '' };
+ state.locationFilters = [];
state.query = '广州';
globalThis.queryMatches = stationMatchesQuery({ name: '广州合作站' });
globalThis.queryMisses = stationMatchesQuery({ name: '杭州合作站' });
@@ -56,18 +57,47 @@ vm.runInNewContext(`
assert.equal(sandbox.inferredDistrict, '海盐县');
assert.equal(sandbox.queryMatches, true);
assert.equal(sandbox.queryMisses, false);
+vm.runInNewContext(`
+ state.query = '';
+ state.locationFilters = [
+ { level: 'city', province: '广东省', city: '广州市', district: '', label: '广州' },
+ { level: 'city', province: '浙江省', city: '嘉兴市', district: '', label: '嘉兴' }
+ ];
+ globalThis.multiLocationMatches = [
+ stationMatchesLocation({ province: '广东省', city: '广州市', district: '白云区' }),
+ stationMatchesLocation({ province: '浙江省', city: '嘉兴市', district: '海盐县' }),
+ stationMatchesLocation({ province: '广东省', city: '深圳市', district: '南山区' })
+ ];
+`, sandbox);
+assert.equal(sandbox.multiLocationMatches.join(','), 'true,true,false');
assert.ok(Math.abs(sandbox.distanceKm([113, 23], [114, 23]) - 102.4) < 1);
assert.match(source, /zoom < 11\) return 'city'/);
assert.match(source, /state\.userLocation && left\.kind === 'station'/);
assert.doesNotMatch(source, /rankSecondaryTab|setRank\(/);
assert.match(source, /stationSearchSuggestions/);
assert.match(source, /handleStationSearchInput/);
+assert.match(source, /locationFilters/);
+assert.match(source, /renderLocationFilterChips/);
+assert.match(source, /kpiTotal|kpiPartner/);
+const index = fs.readFileSync(new URL('../index.html', import.meta.url), 'utf8');
+assert.match(index, /station-summary-strip/);
+assert.match(index, /station-status-card/);
+assert.equal((index.match(/id="statusTotal"/g) || []).length, 1);
+assert.equal((index.match(/id="mobileStatusTotal"/g) || []).length, 1);
assert.match(source, /window\.setTimeout\(\(\) => \{ state\.query = value/);
assert.match(source, /focusSearchResults\(\)/);
assert.match(source, /visibleOnly && !normalize\(state\.query\)/);
assert.match(source, /station-list-partner-tag/);
+assert.match(source, /station-list-partner-tag\$\{node\.cooperative \? '' : ' is-placeholder'\}/);
+assert.match(source, /node\.kind === 'station' \? '' : `\$\{formatNumber\(node\.count\)\} 座`/);
assert.match(source, /node\.cooperative \? 'H₂ · 合作' : ''/);
assert.doesNotMatch(source, /在线 \$\{formatNumber\(node\.online\)\}/);
assert.doesNotMatch(source, /monthlyHydrogenKg|totalHydrogenKg|vehicle/);
assert.match(source, /uri\.amap\.com\/navigation/);
+assert.equal((source.match(/function navigateToStation\(/g) || []).length, 1);
+assert.match(styles, /station-summary-strip/);
+assert.match(styles, /header-kpi-group,\n \.station-navigation-shell \.station-status-card/);
+assert.match(styles, /theme-switcher-bw \.bw-btn span \{ display: none; \}/);
+assert.match(styles, /height: clamp\(430px, 58dvh, 540px\)/);
+assert.match(styles, /map-action-controls \.glass-btn:nth-child\(2\)/);
console.log('station navigation app tests: ok');
diff --git a/station-navigation/tests/test_server.py b/station-navigation/tests/test_server.py
index 625dd4c8..358abb19 100644
--- a/station-navigation/tests/test_server.py
+++ b/station-navigation/tests/test_server.py
@@ -30,6 +30,9 @@ class StationNavigationTest(unittest.TestCase):
def test_static_assets_are_fingerprinted(self):
self.assertRegex(server.STATIC_ASSET_VERSION, r"^[0-9a-f]{16}$")
+ def test_static_assets_resolve_from_the_release_directory(self):
+ self.assertIn('directory=str(ROOT)', Path(__file__).parents[1].joinpath('server.py').read_text(encoding='utf-8'))
+
def test_default_cache_window_is_two_minutes(self):
self.assertEqual(server.STATION_CACHE_SECONDS, 120)