// ===== 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 */}
{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 (
);
}
// ---------- 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}
{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 => (
))}
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 });