// UI.jsx — shared components: Logo, LineChip, StatusBar, BottomNav, AppBar, SchematicMap, Skeleton
function Logo({ size = 30, variant = 'default' }) {
return (
alSubte
);
}
function LineChip({ id, size = 'md', onClick, style = {} }) {
const cls = ['line-chip', 'solid-' + id.toLowerCase(), size, id === 'H' ? 'h' : ''].filter(Boolean).join(' ');
return (
{id === 'P' ? 'P' : id}
);
}
function StatusBar() {
return (
);
}
function BottomNav({ active, onChange, t }) {
const items = [
{ id: 'main', label: t('home'), icon: 'home' },
{ id: 'map', label: t('map'), icon: 'map' },
{ id: 'incidentReport', label: t('report'), icon: 'warning' },
{ id: 'news', label: t('news'), icon: 'news' },
{ id: 'settings', label: t('settings'), icon: 'settings'},
];
return (
);
}
function AppBar({ title, subtitle, left, right, large = false }) {
return (
);
}
function Skeleton({ w = '100%', h = 16, r = 6 }) {
return ;
}
// Schematic map — draws SCHEMATIC.lines as SVG, animates trains along the paths
function SchematicMap({ tick = 0, selectedLines = ['A','B','C','D','E','H','P'], onSelect, highlightedLine = null, showTrains = true, showUserLocation = false, userPos = [490, 440] }) {
const lines = Object.entries(SCHEMATIC.lines).filter(([id]) => selectedLines.includes(id));
return (
);
}
function SchematicTrain({ path, color, tick, seed }) {
// Simulate trains moving along the polyline in both directions
const segments = [];
for (let i = 0; i < path.length - 1; i++) {
const [x1, y1] = path[i], [x2, y2] = path[i + 1];
const len = Math.hypot(x2 - x1, y2 - y1);
segments.push({ x1, y1, x2, y2, len });
}
const totalLen = segments.reduce((a, s) => a + s.len, 0);
const speed = 60; // units per tick
const offset1 = ((tick * speed + seed * 37) % totalLen);
const offset2 = (totalLen - ((tick * speed + seed * 29) % totalLen));
const getPos = (d) => {
let remain = d;
for (const s of segments) {
if (remain <= s.len) {
const f = remain / s.len;
return [s.x1 + (s.x2 - s.x1) * f, s.y1 + (s.y2 - s.y1) * f];
}
remain -= s.len;
}
return [path[path.length - 1][0], path[path.length - 1][1]];
};
const p1 = getPos(offset1);
const p2 = getPos(offset2);
return (
);
}
// Geographic map: uses the uploaded real SVG via