Hello, World!
import React, { useState, useEffect, useRef, useCallback } from 'react';
import { Play, Star, Settings, Home, Volume2, ArrowLeft, Brush, PenTool, Hash } from 'lucide-react';
// Custom CSS for engaging, kid-friendly animations
const CustomStyles = () => (
);
const speak = (text, type = 'normal') => {
if ('speechSynthesis' in window) {
window.speechSynthesis.cancel();
const utterance = new SpeechSynthesisUtterance(text);
utterance.rate = type === 'letter' ? 0.75 : 0.95;
utterance.pitch = type === 'cheer' ? 1.4 : 1.1;
window.speechSynthesis.speak(utterance);
}
};
const playHappySound = () => {
speak("Wow! Amazing job Luther!", "cheer");
};
// Sleek Sports Car with shiny highlights and alloy wheels
const SportsCarSVG = ({ color = "#ef4444", className = "w-32 h-32" }) => (
);
// Heavy Duty Dump Truck
const DumpTruckSVG = ({ color = "#3b82f6", className = "w-32 h-32" }) => (
);
// High-tech Space Rocket
const RocketSVG = ({ color = "#8b5cf6", className = "w-32 h-32" }) => (
);
const Scenery = () => (
{/* Sky gradient */}
{/* Moving Clouds */}
{/* Rolling Hills */}
{/* Road */}
);
const CloudSVG = ({ className }) => (
);
const Confetti = ({ active }) => {
if (!active) return null;
const shapes = ['â', 'ð', 'ðïļ', 'ð', 'ð', 'âĻ'];
return (
{[...Array(40)].map((_, i) => (
{shapes[Math.floor(Math.random() * shapes.length)]}
))}
);
};
const Header = ({ coins, onBack, onHome, title }) => (
{onBack && (
)}
{onHome && (
)}
{title}
{coins}
);
const WordGame = ({ onWin }) => {
const words = [
{ word: 'RACE', icon:
},
{ word: 'TRUCK', icon:
},
{ word: 'SPACE', icon:
}
];
const [level, setLevel] = useState(0);
const [spelled, setSpelled] = useState([]);
const [options, setOptions] = useState([]);
const [wiggleIndex, setWiggleIndex] = useState(-1);
const current = words[level];
const initLevel = useCallback(() => {
setSpelled([]);
const letters = current.word.split('');
// Add 2 random distracter letters to make it harder
const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
let distracters = [];
while(distracters.length < 2) {
const char = alphabet[Math.floor(Math.random() * alphabet.length)];
if (!letters.includes(char) && !distracters.includes(char)) {
distracters.push(char);
}
}
setOptions([...letters, ...distracters].sort(() => Math.random() - 0.5));
setTimeout(() => speak(`Can you spell ${current.word}?`), 600);
}, [current.word]); // FIXED: Depend on the string, not the changing object reference
useEffect(() => { initLevel(); }, [initLevel]);
const handleLetterClick = (letter, index) => {
const expectedLetter = current.word[spelled.length];
if (letter === expectedLetter) {
speak(letter, 'letter');
const newSpelled = [...spelled, letter];
setSpelled(newSpelled);
const newOptions = [...options];
newOptions[index] = null;
setOptions(newOptions);
if (newSpelled.join('') === current.word) {
setTimeout(() => {
speak(`You spelled ${current.word}! Awesome!`);
if (level < words.length - 1) {
setLevel(l => l + 1);
} else {
onWin();
}
}, 1000);
}
} else {
speak(`Whoops! We need ${expectedLetter}.`);
setWiggleIndex(index);
setTimeout(() => setWiggleIndex(-1), 500);
}
};
return (
{/* Target Slots */}
{current.word.split('').map((l, i) => (
{spelled[i] || l}
))}
{/* Letter Options */}
{options.map((letter, i) => (
letter ? (
) : (
)
))}
);
};
const MathGame = ({ onWin }) => {
const [level, setLevel] = useState(0);
const [targetNum, setTargetNum] = useState(3);
const [options, setOptions] = useState([]);
const [isAnimating, setIsAnimating] = useState(false);
const generateLevel = useCallback(() => {
setIsAnimating(true);
// Harder: 5 to 9 cars
const num = Math.floor(Math.random() * 5) + 5;
setTargetNum(num);
const opts = new Set([num]);
// 4 options total, with close distractors to force careful counting
while(opts.size < 4) {
let wrongNum = num + (Math.floor(Math.random() * 5) - 2); // e.g. -2 to +2
if (wrongNum > 0 && wrongNum !== num) opts.add(wrongNum);
}
setOptions(Array.from(opts).sort(() => Math.random() - 0.5));
speak("How many cars are there?");
setTimeout(() => setIsAnimating(false), 1000); // let drive animation finish
}, []);
useEffect(() => { generateLevel(); }, [generateLevel]);
const handleGuess = (num) => {
if (num === targetNum) {
speak(num.toString(), 'letter');
setTimeout(() => {
if (level < 2) {
setLevel(l => l + 1);
generateLevel();
} else {
onWin();
}
}, 800);
} else {
speak("Hmm, let's count them out loud together.");
}
};
const getVehicle = (index) => {
const colors = ['#ef4444', '#f59e0b', '#3b82f6', '#8b5cf6', '#10b981', '#ec4899', '#f97316'];
const color = colors[index % colors.length];
// Slightly smaller vehicles to fit up to 9
return index % 2 === 0 ?
:
;
};
return (
{/* Grass ground line inside box */}
{[...Array(targetNum)].map((_, i) => (
{getVehicle(i)}
))}
{options.map((num) => (
))}
);
};
const ColorGame = ({ onWin }) => {
const colors = [
{ name: 'Red', hex: '#ef4444', gradient: 'from-red-400 to-red-600', border: 'border-red-700' },
{ name: 'Blue', hex: '#3b82f6', gradient: 'from-blue-400 to-blue-600', border: 'border-blue-700' },
{ name: 'Green', hex: '#10b981', gradient: 'from-green-400 to-green-600', border: 'border-green-700' },
{ name: 'Yellow', hex: '#facc15', gradient: 'from-yellow-300 to-yellow-500', border: 'border-yellow-600' },
// Harder colors added
{ name: 'Purple', hex: '#a855f7', gradient: 'from-purple-400 to-purple-600', border: 'border-purple-700' },
{ name: 'Orange', hex: '#f97316', gradient: 'from-orange-400 to-orange-600', border: 'border-orange-700' },
{ name: 'Pink', hex: '#ec4899', gradient: 'from-pink-400 to-pink-600', border: 'border-pink-700' },
{ name: 'Black', hex: '#1e293b', gradient: 'from-slate-700 to-slate-900', border: 'border-slate-900' }
];
const [level, setLevel] = useState(0);
const [targetColor, setTargetColor] = useState(colors[0]);
const [shuffledColors, setShuffledColors] = useState([]);
const [splash, setSplash] = useState(null);
const generateLevel = useCallback(() => {
// Pick 1 target color
const target = colors[Math.floor(Math.random() * colors.length)];
setTargetColor(target);
// Pick 5 unique distractors (6 cars total on screen)
const others = colors.filter(c => c.name !== target.name).sort(() => Math.random() - 0.5).slice(0, 5);
setShuffledColors([target, ...others].sort(() => Math.random() - 0.5));
setSplash(null);
setTimeout(() => speak(`Find the ${target.name} car!`), 600);
}, [level]);
useEffect(() => { generateLevel(); }, [generateLevel]);
const handleGuess = (color) => {
if (color.name === targetColor.name) {
setSplash(color.hex);
speak(`Yes! ${color.name}!`);
setTimeout(() => {
if (level < 3) {
setLevel(l => l + 1);
} else {
onWin();
}
}, 1200);
} else {
speak(`That is ${color.name}. Where is ${targetColor.name}?`);
}
};
return (
{/* Target Color Banner */}
{/* Splash effect overlay */}
{splash && (
)}
{/* Expanded Grid from 4 to 6 items */}
{shuffledColors.map((color, i) => (
))}
);
};
const MainMenu = ({ onPlay, onDashboard }) => (
);
const MapScreen = ({ onSelectGame }) => {
const garages = [
{ id: 'word', title: 'Word Garage', icon:
, color: 'from-sky-400 to-blue-600', border: 'border-blue-800' },
{ id: 'math', title: 'Counting Yard', icon:
, color: 'from-purple-400 to-purple-600', border: 'border-purple-800' },
{ id: 'color', title: 'Paint Shop', icon:
, color: 'from-pink-400 to-rose-600', border: 'border-rose-800' }
];
return (
Where to next, Luther?
{garages.map((g) => (
))}
);
};
const Dashboard = ({ onBack }) => (
Grown-Up Dashboard
Track Luther's progress.
);
export default function App() {
const [view, setView] = useState('menu');
const [coins, setCoins] = useState(0);
const [showConfetti, setShowConfetti] = useState(false);
const handleWin = () => {
setShowConfetti(true);
playHappySound();
setCoins(c => c + 3);
setTimeout(() => {
setShowConfetti(false);
setView('map');
}, 4000);
};
const renderView = () => {
switch (view) {
case 'menu': return
setView('map')} onDashboard={() => setView('dashboard')} />;
case 'map': return setView(id)} />;
case 'dashboard': return ;
case 'word': return ;
case 'math': return ;
case 'color': return ;
default: return ;
}
};
const getTitle = () => {
switch(view) {
case 'map': return "Garage Map";
case 'dashboard': return "Settings";
case 'word': return "Word Garage";
case 'math': return "Counting Yard";
case 'color': return "Paint Shop";
default: return "";
}
}
return (
{view !== 'menu' && (
setView('map') : null}
onHome={view === 'map' ? () => setView('menu') : null}
/>
)}
{renderView()}
);
}