// UI.jsx — shared components: Logo, LineChip, StatusBar, BottomNav, AppBar, SchematicMap, Skeleton function Logo({ size = 30, variant = 'default' }) { return (
alSubte
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 (
9:41
); } 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 (
{left}
{/* font-size reset so callers can pass arbitrary JSX without inheriting h1 size */}

{title}

{subtitle &&
{subtitle}
}
{right}
); } 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 ( {/* Río de la Plata hint */} {/* Schematic lines */} {lines.map(([id, data]) => { const dim = highlightedLine && highlightedLine !== id ? 0.25 : 1; const d = data.path.map((p, i) => (i === 0 ? 'M' : 'L') + p[0] + ' ' + p[1]).join(' '); return ( onSelect && onSelect(id)}> {data.path.map((p, i) => ( ))} {/* Animated train */} {showTrains && } ); })} {/* Line labels at ends */} {lines.map(([id, data]) => { const start = data.path[0], end = data.path[data.path.length - 1]; return ( {id} ); })} {/* User location */} {showUserLocation && ( )} ); } 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 or tile placeholder function GeographicMap({ selectedLines = ['A','B','C','D','E','H','P'], userPos = { x: 52, y: 48 } }) { return (
{/* Fake map tile background */} {/* Rio de la Plata */} {/* Avenidas */} {/* Subway lines (real-ish curved paths) */} {selectedLines.includes('A') && } {selectedLines.includes('B') && } {selectedLines.includes('C') && } {selectedLines.includes('D') && } {selectedLines.includes('E') && } {selectedLines.includes('H') && } {selectedLines.includes('P') && } {/* User location */}
); } Object.assign(window, { Logo, LineChip, StatusBar, BottomNav, AppBar, Skeleton, SchematicMap, GeographicMap });