// Journey screen — stats from /api/journey const { useState: useState_J, useEffect: useEffect_J } = React; const PHASE_NAMES = { 1: "FOUNDATION", 2: "EXPLORATION", 3: "BUILDING", 4: "COMPOUND" }; const PHASE_LENGTH = 30; const prettyTheme = (t) => (t || "unknown").replace(/_/g, " ").replace(/\b\w/g, c => c.toUpperCase()); function JourneyScreen({ onSettings }) { const [journey, setJourney] = useState_J(null); const [err, setErr] = useState_J(null); useEffect_J(() => { api.getJourney().then(setJourney).catch(e => { console.warn("/api/journey failed:", e); setErr(e); }); }, []); if (err || !journey) { return (
Your journey
Loading…
); } const { streak, total_days, avg_rating, moved_pct, phase, theme_ratings, sparkline_7 } = journey; if (total_days === 0) { return (
Your journey
Just getting started
No entries yet. The first one is the hardest.
); } const phaseName = PHASE_NAMES[phase] || "FOUNDATION"; const phaseProgress = total_days % PHASE_LENGTH || (total_days === 0 ? 0 : PHASE_LENGTH); const themesSorted = Object.entries(theme_ratings || {}) .sort((a, b) => b[1] - a[1]) .slice(0, 4); const max = Math.max(5, ...(sparkline_7 || [1])); const bars = sparkline_7 || [0,0,0,0,0,0,0]; const today = new Date(); const labels = Array.from({ length: 7 }, (_, i) => { const d = new Date(today); d.setDate(today.getDate() - (6 - i)); return ["Sun","Mon","Tue","Wed","Thu","Fri","Sat"][d.getDay()]; }); return (
Your journey
{streak ? `Streak: ${streak}` : "Building"}
PHASE {phase} · {phaseName}
{phaseProgress} / {PHASE_LENGTH}
{themesSorted.length > 0 && (

What’s landing

{themesSorted.map(([t, v]) => (
{prettyTheme(t)} {v.toFixed(1)}
))}
)}
Last 7 days
{bars.map((v, i) => (
))}
{labels.map((l, i) => {l})}
); } Object.assign(window, { JourneyScreen });