/* @component-map * App — Main container, handles view switching and state * Header — Top bar with logo, streak, and accuracy * IntroScreen — Landing page with ripple animation * FeedScreen — Scenario browsing with category filters * PredictScreen — Cause-and-effect chain input * LoadingScreen — Animated thinking state * RevealScreen — Score and AI evaluation results * @end-component-map */ import { useCollection, useAI } from '@deplixo/sdk'; import { Header } from './components/Header.jsx'; import { IntroScreen } from './components/IntroScreen.jsx'; import { FeedScreen } from './components/FeedScreen.jsx'; import { PredictScreen } from './components/PredictScreen.jsx'; import { LoadingScreen } from './components/LoadingScreen.jsx'; import { RevealScreen } from './components/RevealScreen.jsx'; import { useCallback, useEffect, useState } from 'react'; import { SCENARIOS } from './components/scenarios.js'; function App() { const [screen, setScreen] = useState("intro"); const [scenario, setScenario] = useState(null); const [result, setResult] = useState(null); const [apiError, setApiError] = useState(null); const { items: scoreItems, loading: scoresLoading, add: addScore } = useCollection("ripple_scores", { personal: true }); const { items: streakItems, loading: streakLoading, add: addStreak, update: updateStreak } = useCollection("ripple_streak", { personal: true }); const { generate } = useAI(); const streak = streakItems.length > 0 ? (streakItems[0].value.count || 0) : 0; const scores = scoreItems.map(item => item.value.score); const accuracy = scores.length > 0 ? Math.round(scores.reduce((a, b) => a + b, 0) / scores.length) : 0; const updateCurrentStreak = useCallback(async () => { if (streakLoading) return; if (streakItems.length === 0) { await addStreak({ count: 1 }); } else { await updateStreak(streakItems[0].id, { count: streakItems[0].value.count + 1 }); } }, [streakItems, streakLoading, addStreak, updateStreak]); const handleSelect = s => { setScenario(s); setApiError(null); setScreen("predict"); }; const handleSubmit = async preds => { setScreen("loading"); setApiError(null); try { const chainText = preds.filter(p => p.trim()).map((p, i) => `${i + 1}. ${p}`).join("\n"); const prompt = `You are an expert systems thinker evaluating a player's cause-and-effect predictions.\n\nSCENARIO: "${scenario.title}"\nCONTEXT: ${scenario.hook}\n\nPLAYER'S CHAIN:\n${chainText}\n\nReturn ONLY a JSON object with no markdown:\n{"score":<0-100>,"hits":[{"prediction":"","annotation":""}],"misses":[{"prediction":"","annotation":""}],"blind_spots":[{"effect":"","order":"2nd order","why":""}],"insight":"<2 provocative sentences about the system dynamics at play>"}\n\nScore: 90+=exceptional, 70+=good 2nd-order thinking, 50+=basic 1st-order, <50=linear only.`; const r = await generate({ prompt, json: true }); if (!r || typeof r.score !== 'number') throw new Error('Unexpected AI response format'); setResult(r); await addScore({ score: r.score, scenarioId: scenario.id, date: new Date().toISOString() }); await updateCurrentStreak(); setScreen("reveal"); } catch (err) { setApiError(`Could not score predictions: ${err.message}`); setScreen("predict"); } }; const handleNext = () => { const idx = SCENARIOS.findIndex(s => s.id === scenario?.id); setScenario(SCENARIOS[(idx + 1) % SCENARIOS.length]); setResult(null); setApiError(null); setScreen("predict"); }; if (scoresLoading || streakLoading) return ; if (screen === "intro") return setScreen("feed")} />; if (screen === "feed") return ; if (screen === "loading") return ; if (screen === "predict") return setScreen("feed")} streak={streak} accuracy={accuracy} initialError={apiError} />; if (screen === "reveal") return setScreen("feed")} streak={streak} accuracy={accuracy} />; return null; } ReactDOM.createRoot(document.getElementById("root")).render();