// ===== SCREENS: Home, Traffic, Courses, Laws, Emergency ===== // ---------- News Carousel ---------- function NewsCarousel({ t, news, onRefresh, loading }) { const [idx, setIdx] = useState(0); const [drag, setDrag] = useState(0); const startX = useRef(null); const wrapRef = useRef(null); const timer = useRef(null); useEffect(() => { if (idx > news.length - 1) setIdx(0); }, [news.length]); const clamp = (n) => Math.max(0, Math.min(news.length - 1, n)); const go = (n) => { setIdx(clamp(n)); restart(); }; const restart = () => { clearInterval(timer.current); timer.current = setInterval(() => setIdx(i => (i + 1) % Math.max(1, news.length)), 5000); }; useEffect(() => { restart(); return () => clearInterval(timer.current); }, [news.length, loading]); const onDown = (x) => { startX.current = x; }; const onMove = (x) => { if (startX.current != null) setDrag(x - startX.current); }; const onUp = () => { if (startX.current == null) return; if (drag < -40) go(idx + 1); else if (drag > 40) go(idx - 1); startX.current = null; setDrag(0); }; if (loading) { return (
🤖
Claude is generating fresh news…
{[0, 1, 2].map(i => )}
); } const item = news[idx] || news[0]; return (
onDown(e.clientX)} onMouseMove={e => startX.current != null && onMove(e.clientX)} onMouseUp={onUp} onMouseLeave={onUp} onTouchStart={e => onDown(e.touches[0].clientX)} onTouchMove={e => onMove(e.touches[0].clientX)} onTouchEnd={onUp} style={{ touchAction: 'pan-y', cursor: 'grab' }}>
{item.tag} {item.time}
{item.icon}

{item.title}

{item.summary}

{/* Thumbnail strip */}
{news.map((n, i) => ( ))}
); } // ---------- Auto-rotating Ad Banner (half the height of a news card) ---------- function AdCarousel({ t }) { const [idx, setIdx] = useState(0); const timer = useRef(null); const start = () => { clearInterval(timer.current); timer.current = setInterval(() => setIdx(i => (i + 1) % ads.length), 5000); }; useEffect(() => { start(); return () => clearInterval(timer.current); }, []); const ad = ads[idx]; return (
{ad.icon}
{ad.tag}
{ad.title}
{ad.sub}
); } // ---------- Home ---------- function HomeScreen({ t, go, news, onRefreshNews, newsLoading, mood, user }) { const firstName = (user && user.name ? String(user.name).split(' ')[0] : 'driver'); return (
{/* Hero */}
Good drive, {firstName}
Your Safety Score
87 ▲ Excellent
87%
{/* Quick access grid */}
Quick Access
{quick_access.map(q => ( ))}
{/* Feature pills */}
{feature_pills.map(f => ( ))}
{/* Sponsored auto-rotating ad */} {/* News */}
✨ AI Refresh }>Road Safety News
{/* Programs & Donate boxes */}
); } // ---------- Live Traffic ---------- function TrafficScreen({ t, go }) { const [heatmap, setHeatmap] = useState(true); const sevColor = { high: t.danger, med: t.warn, low: t.success }; return (
{/* Live conditions summary — matches Home/Learn opening card rhythm */}
Live Conditions
LIVE
{traffic_stats.map((s, i) => (
0 ? `1px solid ${t.border}` : 'none' }}>
{s.value}
{s.unit}
{s.label}
))}
{/* Map */}
{[['Heavy', t.danger], ['Moderate', t.warn], ['Light', t.success]].map(([l, c]) => (
{l}
))}
{/* Incidents */}
LIVE}>Active Incidents
{incidents.map(i => (
{i.type} {i.sev === 'high' ? 'Severe' : i.sev === 'med' ? 'Moderate' : 'Minor'}
{i.desc}
{i.loc} ·{i.time}
))}
); } function TrafficMap({ t, heatmap }) { const road = t.dark ? '#2a3344' : '#dde3ef'; const land = t.dark ? '#11161f' : '#eef2f8'; return ( {/* blocks */} {[[20,20],[120,30],[230,18],[40,130],[180,140],[280,120]].map(([x,y],i)=>( ))} {/* roads */} {/* heat segments */} {heatmap && } {/* incident pins */} {[[140,110,t.danger],[120,150,t.danger],[250,170,t.warn],[120,40,t.success]].map(([x,y,c],i)=>( ))} ); } // ---------- Courses ---------- function CoursesScreen({ t, go }) { const totalDone = courses.reduce((a, c) => a + c.done, 0); const totalLessons = courses.reduce((a, c) => a + c.lessons, 0); const pct = Math.round((totalDone / totalLessons) * 100); return (
{pct}% COMPLETE
Keep it up!
{totalDone} of {totalLessons} lessons completed across {courses.length} courses.
My Courses
{courses.map(c => { const p = Math.round((c.done / c.lessons) * 100); return (

{c.title}

{c.level} {c.rating} {c.done}/{c.lessons}
{p}%
{c.done === 0 ? 'Start Course' : c.done === c.lessons ? 'Review' : 'Continue Learning'}
); })}
); } // ---------- Traffic Laws ---------- function LawsScreen({ t, go }) { const [region, setRegion] = useState('Asia'); const [query, setQuery] = useState(''); const [open, setOpen] = useState(1); const filtered = laws.filter(l => !query || l.cat.toLowerCase().includes(query.toLowerCase()) || l.rules.some(r => r.toLowerCase().includes(query.toLowerCase())) ); return (
setQuery(e.target.value)} placeholder="Search laws, e.g. seatbelt, speed…" style={{ width: '100%', border: `1px solid ${t.border}`, background: t.inputBg, color: t.text, borderRadius: 14, padding: '13px 14px 13px 42px', outline: 'none', fontWeight: 500, }} />
{law_regions.map(r => setRegion(r)} color={t.mid}>{r})}
{region !== 'Asia' && ( 🌐 Showing Philippine-aligned regulations. Switch to Asia for full local detail. )}
{filtered.map(l => { const isOpen = open === l.id; return (
{l.rules.map((r, i) => (
{r}
))}
); })} {filtered.length === 0 &&
No laws match "{query}"
}
); } // ---------- Emergency / SOS ---------- function EmergencyScreen({ t, go }) { return (
Emergency Contacts
{emergency.map(e => (
{e.name}
{e.desc}
{e.num} Call
))}
In an accident?
Stay safe, switch on hazards, and use the Evidence Logger to document the scene with GPS tags.
); } Object.assign(window, { HomeScreen, TrafficScreen, CoursesScreen, LawsScreen, EmergencyScreen, NewsCarousel, AdCarousel });