Digimon Virtual Pet https://unpkg.com/react@18/umd/react.production.min.js https://unpkg.com/react-dom@18/umd/react-dom.production.min.js https://unpkg.com/@babel/standalone/babel.min.js https://cdn.tailwindcss.com body { margin: 0; font-family: ‘Segoe UI’, sans-serif; background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%); min-height: 100vh; } @keyframes petHand { 0%, 100% { transform: translateY(0) rotate(-5deg); } 50% { transform: translateY(3px) rotate(5deg); } } @keyframes floatHeart { 0% { opacity: 1; transform: translateY(0) scale(0.5); } 50% { opacity: 1; transform: translateY(-10px) scale(1); } 100% { opacity: 0; transform: translateY(-20px) scale(1.2); } } @keyframes twinkle { 0%, 100% { opacity: 1; } 50% { opacity: 0.3; } } @keyframes lightbulbGlow { 0%, 100% { filter: drop-shadow(0 0 4px #ffff60) brightness(1.1); } 50% { filter: drop-shadow(0 0 8px #ffff90) brightness(1.3); } } @keyframes barbellPump { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-4px); } } @keyframes weightStruggle { 0%, 100% { transform: translateX(0); } 25% { transform: translateX(-1px); } 75% { transform: translateX(1px); } } @keyframes digiStruggle { 0%, 100% { transform: scaleY(1); } 50% { transform: scaleY(0.92); } } @keyframes digiJump { 0% { transform: translateY(0); } 40% { transform: translateY(-28px); } 50% { transform: translateY(-30px); } 60% { transform: translateY(-28px); } 100% { transform: translateY(0); } } @keyframes runBounce { 0%, 100% { transform: translateY(0) rotate(12deg); } 50% { transform: translateY(-3px) rotate(15deg); } } @keyframes hurdleScroll { 0% { transform: translateX(100px); } 100% { transform: translateX(-30px); } } @keyframes playerLunge { 0% { transform: translateX(0); } 30% { transform: translateX(12px); } 100% { transform: translateX(0); } } @keyframes enemyLunge { 0% { transform: translateX(0); } 30% { transform: translateX(-12px); } 100% { transform: translateX(0); } } @keyframes playerStagger { 0% { transform: translateX(0); } 15% { transform: translateX(-8px); } 30% { transform: translateX(-6px); } 45% { transform: translateX(-8px); } 60% { transform: translateX(-4px); } 100% { transform: translateX(0); } } @keyframes enemyStagger { 0% { transform: translateX(0); } 15% { transform: translateX(8px); } 30% { transform: translateX(6px); } 45% { transform: translateX(8px); } 60% { transform: translateX(4px); } 100% { transform: translateX(0); } }
const { useState, useEffect, useCallback, useRef } = React; const DIGIMON_DATA = { egg: { name: ‘Egg’, stage: ‘egg’, evolvesTo: [‘botamon’, ‘poyomon’, ‘punimon’, ‘yuramon’], evolveTime: 0 }, botamon: { name: ‘Botamon’, stage: ‘fresh’, evolvesTo: [‘koromon’], evolveTime: 3 }, poyomon: { name: ‘Poyomon’, stage: ‘fresh’, evolvesTo: [‘tokomon’], evolveTime: 3 }, punimon: { name: ‘Punimon’, stage: ‘fresh’, evolvesTo: [‘tsunomon’], evolveTime: 3 }, yuramon: { name: ‘Yuramon’, stage: ‘fresh’, evolvesTo: [‘tanemon’], evolveTime: 3 }, koromon: { name: ‘Koromon’, stage: ‘intraining’, evolvesTo: [‘agumon’], evolveTime: 12 }, tokomon: { name: ‘Tokomon’, stage: ‘intraining’, evolvesTo: [‘patamon’], evolveTime: 12 }, tsunomon: { name: ‘Tsunomon’, stage: ‘intraining’, evolvesTo: [‘gabumon’], evolveTime: 12 }, tanemon: { name: ‘Tanemon’, stage: ‘intraining’, evolvesTo: [‘palmon’], evolveTime: 12 }, agumon: { name: ‘Agumon’, stage: ‘rookie’, type: ‘Vaccine’, evolvesTo: [‘greymon’, ‘tyrannomon’], evolveTime: 36, attacks: [‘Pepper Breath’, ‘Claw Attack’] }, gabumon: { name: ‘Gabumon’, stage: ‘rookie’, type: ‘Vaccine’, evolvesTo: [‘garurumon’], evolveTime: 36, attacks: [‘Blue Blaster’, ‘Horn Attack’] }, patamon: { name: ‘Patamon’, stage: ‘rookie’, type: ‘Vaccine’, evolvesTo: [‘angemon’], evolveTime: 36, attacks: [‘Boom Bubble’, ‘Wing Slap’] }, palmon: { name: ‘Palmon’, stage: ‘rookie’, type: ‘Data’, evolvesTo: [‘togemon’], evolveTime: 36, attacks: [‘Poison Ivy’, ‘Root Break’] }, greymon: { name: ‘Greymon’, stage: ‘champion’, type: ‘Vaccine’, evolvesTo: [‘metalgreymon’], evolveTime: 72, attacks: [‘Nova Blast’, ‘Tail Crash’] }, garurumon: { name: ‘Garurumon’, stage: ‘champion’, type: ‘Vaccine’, evolvesTo: [‘weregarurumon’], evolveTime: 72, attacks: [‘Howling Blaster’, ‘Ice Wall’] }, tyrannomon: { name: ‘Tyrannomon’, stage: ‘champion’, type: ‘Virus’, evolvesTo: [‘mastertyrannomon’], evolveTime: 72, attacks: [‘Fire Breath’, ‘Slash’] }, angemon: { name: ‘Angemon’, stage: ‘champion’, type: ‘Vaccine’, evolvesTo: [‘holyangemon’], evolveTime: 72, attacks: [‘Hand of Fate’, ‘Angel Rod’] }, togemon: { name: ‘Togemon’, stage: ‘champion’, type: ‘Data’, evolvesTo: [‘lilymon’], evolveTime: 72, attacks: [‘Needle Spray’, ‘Jab’] }, metalgreymon: { name: ‘MetalGreymon’, stage: ‘ultimate’, type: ‘Vaccine’, evolvesTo: [], evolveTime: 999, attacks: [‘Giga Blaster’, ‘Metal Slash’] }, weregarurumon: { name: ‘WereGarurumon’, stage: ‘ultimate’, type: ‘Vaccine’, evolvesTo: [], evolveTime: 999, attacks: [‘Wolf Claw’, ‘Garuru Kick’] }, mastertyrannomon: { name: ‘MasterTyrannomon’, stage: ‘ultimate’, type: ‘Virus’, evolvesTo: [], evolveTime: 999, attacks: [‘Master Fire’, ‘Master Claw’] }, holyangemon: { name: ‘HolyAngemon’, stage: ‘ultimate’, type: ‘Vaccine’, evolvesTo: [], evolveTime: 999, attacks: [‘Gate of Destiny’, ‘Excalibur’] }, lilymon: { name: ‘Lilymon’, stage: ‘ultimate’, type: ‘Data’, evolvesTo: [], evolveTime: 999, attacks: [‘Flower Cannon’, ‘Sun Crescent’] }, numemon: { name: ‘Numemon’, stage: ‘champion’, type: ‘Virus’, evolvesTo: [], evolveTime: 72, attacks: [‘Poop Throw’, ‘Tackle’] }, }; const ENEMIES = { rookie: [‘agumon’, ‘gabumon’, ‘patamon’, ‘palmon’], champion: [‘greymon’, ‘garurumon’, ‘tyrannomon’, ‘angemon’, ‘togemon’, ‘numemon’], ultimate: [‘metalgreymon’, ‘weregarurumon’, ‘holyangemon’, ‘lilymon’, ‘mastertyrannomon’] }; // NES-style pixel heart sprite component const PixelHeart = ({ size = 16 }) => ( ); // NES-style steak sprite with bite states (0=full, 1=one bite, 2=two bites, 3=gone) const PixelSteak = ({ size = 24, bites = 0 }) => ( {bites < 3 && {/* Bone */} {/* Meat – reduces with bites */} {bites < 1 && } {bites === 1 && } {bites === 2 && } } ); // NES-style barbell sprite const PixelBarbell = ({ size = 28, frame = 0 }) => { const yOffset = frame % 2 === 0 ? 0 : -3; return ( {/* Left weight */} {/* Left inner plate */} {/* Bar */} {/* Right inner plate */} {/* Right weight */} ); }; // NES-style heavy weight sprite for weight training mini-game const PixelHeavyWeight = ({ size = 36, liftPosition = 0 }) => { // liftPosition: 0 = on ground, 1-4 = stages of lift (4 = overhead) const yOffset = liftPosition * -7.5; return ( {/* Left heavy plate */} {/* Left inner plate */} {/* Bar */} {/* Right inner plate */} {/* Right heavy plate */} ); }; const PixelBook = ({ size = 24 }) => ( {/* Book cover */} {/* Pages */} {/* Spine */} {/* Text lines */} ); // NES-style lightbulb sprite const PixelLightbulb = ({ size = 20, lit = false }) => ( {/* Glow effect when lit */} {lit && } {/* Bulb outline */} {/* Bulb fill */} {/* Highlight */} {/* Base */} ); // NES-style hurdle sprite for running minigame (taller) const PixelHurdle = ({ size = 28 }) => ( {/* Left post */} {/* Right post */} {/* Top bar */} {/* Middle bar */} ); // Digital Monster Color style sprites – 16×16, bold outlines, flat colors, iconic shapes const Sprite = ({ id, size = 48, walkFrame = 0, isWalking = false, stage = null }) => { const s = size; const legOffset = isWalking ? [0, 2, 0, -2][walkFrame] : 0; const altLegOffset = isWalking ? [0, -2, 0, 2][walkFrame] : 0; const sprites = { // Egg – oval with spots (default purple – Punimon) egg: , // Botamon Egg – dark gray with yellow/orange spots egg_botamon: , // Poyomon Egg – white/light blue with blue spots egg_poyomon: , // Punimon Egg – cream with purple spots egg_punimon: , // Yuramon Egg – pale green with green spots egg_yuramon: , // Botamon – black blob with yellow eyes botamon: , // Poyomon – white ghost blob poyomon: , // Punimon – purple blob with antenna punimon: , // Yuramon – green ball with sprout yuramon: , // Koromon – pink ball with ears and fangs koromon: , // Tokomon – cream with big ears tokomon: , // Tsunomon – orange with horn tsunomon: , // Tanemon – green bulb tanemon: , // Agumon – iconic orange dinosaur with clear DMC style agumon: , // Gabumon – blue fur pelt creature gabumon: , // Patamon – orange with wing ears patamon: , // Palmon – green plant with flower palmon: , // Greymon – dinosaur with helmet horns greymon: , // Garurumon – blue wolf garurumon: , // Tyrannomon – red dinosaur tyrannomon: , // Angemon – angel with helmet and wings angemon: , // Togemon – green cactus with boxing gloves togemon: , // MetalGreymon – cyborg dinosaur metalgreymon: , // WereGarurumon – humanoid wolf in jeans weregarurumon: , // MasterTyrannomon – armored red dinosaur mastertyrannomon: , // HolyAngemon – armored angel holyangemon: , // Lilymon – flower fairy lilymon: , // Numemon – purple slug numemon: , // Refuse face – angry/refusing refuse: , }; return
{sprites[id] || sprites.egg}
; }; // NES-style Background scene component – chunky pixels, limited palette const SceneBackground = ({ isNight = false, anim = 0 }) => { // NES color palette approximation const p = isNight ? { sky1: ‘#000058’, sky2: ‘#000038’, sky3: ‘#000028’, mountain1: ‘#383878’, mountain2: ‘#282858’, hill1: ‘#185818’, hill2: ‘#084808’, hill3: ‘#003800’, grass: ‘#003800’, grassLight: ‘#005800’, star: ‘#fcfcfc’, moon: ‘#fcfc68’ } : { sky1: ‘#68a8fc’, sky2: ‘#88b8fc’, sky3: ‘#a8c8fc’, mountain1: ‘#787878’, mountain2: ‘#585858’, snow: ‘#fcfcfc’, hill1: ‘#00a800’, hill2: ‘#008800’, hill3: ‘#006800’, grass: ‘#00a800’, grassLight: ‘#00c800’, grassDark: ‘#006800’, sun: ‘#fcfc68’, sunOrange: ‘#fca044’ }; // Pixel size for NES chunky look const px = 5; if (isNight) { return ( {/* Night sky – solid colors in bands */} {/* Stars – single pixels */} {[[15,10],[45,20],[80,8],[120,25],[140,12],[25,35],[60,30],[100,18],[135,38],[50,45],[90,40],[20,50],[70,15],[110,42]].map(([x,y], i) => ( ))} {/* Moon – pixelated circle */} {/* Moon shadow for crescent */} {/* Mountains – stepped pixels */} {[…Array(32)].map((_, i) => { const h = Math.max(0, 15 – Math.abs(i – 16)) * px; return ; })} {[…Array(20)].map((_, i) => { const h = Math.max(0, 10 – Math.abs(i – 10)) * px; return ; })} {/* Rolling hills – NES style stepped */} {[…Array(32)].map((_, i) => { const h = Math.sin(i * 0.3) * 8 + Math.sin(i * 0.5) * 4; return ; })} {[…Array(32)].map((_, i) => { const h = Math.sin(i * 0.4 + 1) * 6 + Math.sin(i * 0.6) * 3; return ; })} {/* Ground */} {/* Grass tufts */} {[5,25,45,65,85,105,125,145].map((x, i) => ( ))} ); } return ( {/* Sky – NES blue gradient bands */} {/* Sun – chunky pixel circle */} {/* Sun rays */} {/* Clouds – chunky NES style */} {/* Far Mountain – stepped pixel art */} {[…Array(28)].map((_, i) => { const h = Math.max(0, 14 – Math.abs(i – 14)) * px; return ; })} {/* Second mountain peak */} {[…Array(18)].map((_, i) => { const h = Math.max(0, 9 – Math.abs(i – 9)) * px; return ; })} {/* Snow caps – white pixels at peak */} {/* Rolling hills – multiple layers with NES-style stepping */} {/* Back hill */} {[…Array(32)].map((_, i) => { const h = Math.sin(i * 0.25) * 10 + Math.sin(i * 0.4) * 5; return ; })} {/* Front hill */} {[…Array(32)].map((_, i) => { const h = Math.sin(i * 0.35 + 2) * 8 + Math.sin(i * 0.55) * 4; return ; })} {/* Ground */} {/* Grass texture – NES style repeating pattern */} {[0,20,40,60,80,100,120,140].map((x, i) => ( ))} {/* Ground detail – darker grass patches */} {[10,35,55,75,95,115,135].map((x, i) => ( ))} ); }; const SAVE_KEY = ‘digimon_v6’; const save = (s) => { try { localStorage.setItem(SAVE_KEY, JSON.stringify(s)); } catch(e) {} }; const load = () => { try { const s = localStorage.getItem(SAVE_KEY); return s ? JSON.parse(s) : null; } catch(e) { return null; } }; function App() { const [gs, setGs] = useState(‘welcome’); const [digi, setDigi] = useState(null); const [bp, setBp] = useState(0); const [gameHours, setGameHours] = useState(0); const [st, setSt] = useState({ hp:100, mHp:100, off:10, def:10, spd:10, brn:10, wt:15, hap:50, dis:50, hun:50, eng:100, mis:0, bat:0, age:0 }); const [poop, setPoop] = useState(0); const [slp, setSlp] = useState(false); const [sick, setSick] = useState(false); const [menu, setMenu] = useState(‘main’); const [msg, setMsg] = useState(”); const [anim, setAnim] = useState(0); const [battle, setBattle] = useState(null); const [atk, setAtk] = useState(0); const [playerAttacking, setPlayerAttacking] = useState(false); const [enemyAttacking, setEnemyAttacking] = useState(false); const [evoT, setEvoT] = useState(null); const [ref, setRef] = useState(false); const [refAct, setRefAct] = useState(null); const [hunT, setHunT] = useState(0); const [dirT, setDirT] = useState(0); const [tirT, setTirT] = useState(0); const [sickT, setSickT] = useState(0); const [opC, setOpC] = useState(0); const [hatchTaps, setHatchTaps] = useState(0); const [eggPos, setEggPos] = useState(0); const [petting, setPetting] = useState(false); const [walkX, setWalkX] = useState(0); const [walkDir, setWalkDir] = useState(1); const [isWalking, setIsWalking] = useState(false); const [walkFrame, setWalkFrame] = useState(0); const [idleTime, setIdleTime] = useState(0); const [eating, setEating] = useState(false); const [eatBites, setEatBites] = useState(0); const [training, setTraining] = useState(null); // ‘str’, ‘spd’, ‘brn’ const [trainFrame, setTrainFrame] = useState(0); const [runX, setRunX] = useState(0); const [showLightbulb, setShowLightbulb] = useState(false); // Strength mini-game state const [strGame, setStrGame] = useState({ active: false, presses: 0, lifts: 0, timeLeft: 5, started: false }); const [strResults, setStrResults] = useState(null); // { lifts, offGain, defGain, message } // Speed mini-game state const [spdGame, setSpdGame] = useState({ active: false, timeLeft: 5, started: false, hurdles: [], jumped: 0, missed: 0, isJumping: false, bgOffset: 0, hurdleCount: 0 }); const [spdResults, setSpdResults] = useState(null); // { jumped, missed, spdGain, brnGain, message, perfect, passed } // Focus mini-game state (brain training) const [focusGame, setFocusGame] = useState({ active: false, started: false, paddlePos: -45, direction: 1 }); const [focusResults, setFocusResults] = useState(null); // { accuracy, brnGain, spdGain, message, isPerfect, passed } const focusAnimRef = useRef(null); const [selectedEgg, setSelectedEgg] = useState(0); // 0-3 for botamon, poyomon, punimon, yuramon const tickRef = useRef(0); const EGG_TYPES = [ { id: ‘egg_botamon’, baby: ‘botamon’, name: ‘Fire’, color: ‘#f8a030’ }, { id: ‘egg_poyomon’, baby: ‘poyomon’, name: ‘Holy’, color: ‘#4878c8’ }, { id: ‘egg_punimon’, baby: ‘punimon’, name: ‘Beast’, color: ‘#d088d0’ }, { id: ‘egg_yuramon’, baby: ‘yuramon’, name: ‘Nature’, color: ‘#40b040’ }, ]; useEffect(() => { if (gs === ‘playing’ && digi) save({ digi, bp, gameHours, st, poop, sick, hunT, dirT, tirT, sickT, opC }); }, [gs, digi, st, poop, gameHours, sick, sickT]); const snd = useCallback((t) => { try { const c = new (window.AudioContext || window.webkitAudioContext)(); const o = c.createOscillator(); const g = c.createGain(); o.connect(g); g.connect(c.destination); g.gain.value = 0.08; if (t === ‘beep’) { o.frequency.value = 800; o.start(); o.stop(c.currentTime + 0.08); } else if (t === ‘ok’) { o.frequency.value = 523; o.start(); setTimeout(() => o.frequency.value = 784, 100); o.stop(c.currentTime + 0.2); } else if (t === ‘hit’) { o.type = ‘sawtooth’; o.frequency.value = 150; o.start(); o.stop(c.currentTime + 0.1); } else if (t === ‘evo’) { o.frequency.value = 440; o.start(); setTimeout(() => o.frequency.value = 880, 300); o.stop(c.currentTime + 0.6); } else if (t === ‘no’) { o.type = ‘square’; o.frequency.value = 200; o.start(); o.stop(c.currentTime + 0.15); } else if (t === ‘grunt’) { o.type = ‘sine’; o.frequency.value = 280 + Math.random() * 80; g.gain.value = 0.12; o.start(); setTimeout(() => o.frequency.value = 200 + Math.random() * 60, 50); o.stop(c.currentTime + 0.12); } } catch(e) {} }, []); const start = useCallback((ld = false) => { if (ld) { const s = load(); if (s) { setDigi(s.digi); setBp(s.bp); setGameHours(s.gameHours || 0); setSt(s.st); setPoop(s.poop); setSick(s.sick); setHunT(s.hunT||0); setDirT(s.dirT||0); setTirT(s.tirT||0); setSickT(s.sickT||0); setOpC(s.opC||0); setMsg(‘Welcome back!’); setGs(‘playing’); snd(‘ok’); return; } } setSelectedEgg(0); setGs(‘eggSelect’); snd(‘ok’); }, [snd]); const confirmEggSelection = useCallback(() => { setDigi(‘egg’); setBp(0); setGameHours(0); setSt({ hp:100, mHp:100, off:10, def:10, spd:10, brn:10, wt:15, hap:50, dis:50, hun:50, eng:100, mis:0, bat:0, age:0 }); setPoop(0); setSlp(false); setSick(false); setMenu(‘main’); setMsg(‘Tap to hatch!’); setHunT(0); setDirT(0); setTirT(0); setSickT(0); setOpC(0); setHatchTaps(0); setEggPos(0); tickRef.current = 0; setGs(‘hatching’); snd(‘ok’); }, [snd]); const digimonDeath = useCallback(() => { snd(‘no’); setMsg(‘Died…’); setTimeout(() => { setSelectedEgg(0); setGs(‘eggSelect’); }, 2000); }, [snd]); const tapEgg = useCallback(() => { snd(‘grunt’); setEggPos(p => p === 0 ? 1 : p === 1 ? -1 : 1); setHatchTaps(t => { const newT = t + 1; if (newT >= 15) { setTimeout(() => { const freshDigimon = EGG_TYPES[selectedEgg].baby; setEvoT(freshDigimon); setGs(‘evolving’); snd(‘evo’); }, 300); } return newT; }); }, [snd, selectedEgg]); useEffect(() => { if (gs !== ‘playing’ || slp) return; const t = setInterval(() => { tickRef.current += 1; if (tickRef.current >= 150) { tickRef.current = 0; setGameHours(h => h + 1); setSt(s => ({ …s, age: s.age + 1, hun: Math.max(0, s.hun – 5), eng: Math.max(0, s.eng – 3), hap: Math.max(0, s.hap – 1) })); if (Math.random() Math.min(p + 1, 8)); } setSt(s => { if (s.hun t + 1); else setHunT(0); if (s.eng t + 1); else setTirT(0); return s; }); if (poop >= 3) setDirT(t => t + 1); else setDirT(0); if (sick) setSickT(t => t + 1); else setSickT(0); }, 1000); return () => clearInterval(t); }, [gs, slp, poop, sick]); useEffect(() => { if (gs !== ‘playing’) return; if (hunT >= 60) { setSt(s => ({ …s, mis: s.mis + 1 })); setHunT(0); setMsg(‘Mistake!’); if (!sick && Math.random() = 60) { setSt(s => ({ …s, mis: s.mis + 1 })); setDirT(0); setMsg(‘Mistake!’); if (!sick && Math.random() = 60) { setSt(s => ({ …s, mis: s.mis + 1 })); setTirT(0); setMsg(‘Mistake!’); } if (st.eng ({ …s, mis: s.mis + 1, eng: 5 })); setMsg(‘Exhausted!’); } if (st.mis >= 10 && DIGIMON_DATA[digi]?.stage === ‘rookie’) { setDigi(‘numemon’); setMsg(‘Devolved!’); snd(‘hit’); } // Death: hungry for 5 game hours (750 seconds) if (hunT >= 750 && digi !== ‘egg’) { digimonDeath(); return; } // Death: sick for 3 game hours (450 seconds) if (sickT >= 450 && digi !== ‘egg’) { digimonDeath(); return; } // Numemon: 6+ poop AND dirty for 1 game hour (150 seconds) if (poop >= 6 && dirT >= 150 && digi !== ‘numemon’ && digi !== ‘egg’ && DIGIMON_DATA[digi]?.stage !== ‘fresh’ && DIGIMON_DATA[digi]?.stage !== ‘intraining’) { setDigi(‘numemon’); setMsg(‘Too dirty!’); snd(‘hit’); setPoop(0); setDirT(0); } }, [hunT, dirT, tirT, sickT, st.eng, st.mis, gs, digi, sick, poop, snd, digimonDeath]); useEffect(() => { const a = setInterval(() => { setAnim(f => (f + 1) % 2); setWalkFrame(f => (f + 1) % 4); }, 300); return () => clearInterval(a); }, []); // Idle walking behavior useEffect(() => { if (gs !== ‘playing’ || slp || ref || petting || menu !== ‘main’) { setIdleTime(0); return; } const t = setInterval(() => { setIdleTime(it => { const newTime = it + 1; // Start walking after 5 seconds of idle if (newTime >= 5 && !isWalking) { setIsWalking(true); setWalkDir(Math.random() clearInterval(t); }, [gs, slp, ref, petting, menu, isWalking]); // Walking movement useEffect(() => { if (!isWalking || gs !== ‘playing’ || slp) return; const t = setInterval(() => { setWalkX(x => { const newX = x + walkDir * 2; // Turn around at edges or randomly if (Math.abs(newX) > 35 || Math.random() -d); } return Math.max(-40, Math.min(40, newX)); }); }, 150); return () => clearInterval(t); }, [isWalking, walkDir, gs, slp]); // Return to center on interaction const returnToCenter = useCallback(() => { setWalkX(0); setIsWalking(false); setIdleTime(0); }, []); useEffect(() => { if (ref) { const t = setTimeout(() => { setRef(false); setRefAct(null); }, 1500); return () => clearTimeout(t); } }, [ref]); const willRef = useCallback(() => st.hap < 30 && Math.random() { if (!digi || gs !== ‘playing’) return; const d = DIGIMON_DATA[digi]; if (!d || d.evolvesTo.length === 0) return; if (digi === ‘egg’ && bp >= 15) { setEvoT(d.evolvesTo[Math.floor(Math.random() * d.evolvesTo.length)]); setGs(‘evolving’); snd(‘evo’); return; } if (st.age >= d.evolveTime) { const t = st.mis = 50 ? d.evolvesTo[0] : d.evolvesTo[Math.floor(Math.random() * d.evolvesTo.length)]; setEvoT(t); setGs(‘evolving’); snd(‘evo’); } }, [digi, bp, st, gs, snd]); useEffect(() => { checkEvo(); }, [st.age, bp, checkEvo]); const completeEvo = useCallback(() => { if (!evoT) return; setDigi(evoT); setSt(s => ({ …s, age: 0, mHp: s.mHp + 50, hp: s.mHp + 50, off: s.off + 20, def: s.def + 20, spd: s.spd + 15, brn: s.brn + 15 })); setMsg(`${DIGIMON_DATA[evoT].name}!`); setEvoT(null); setGs(‘playing’); }, [evoT]); const feed = useCallback((type, force = false) => { returnToCenter(); snd(‘beep’); setBp(p => p + 1); if (st.hun >= 90 && !force) { setRef(true); setRefAct(‘feed’); setMsg(“Full!”); snd(‘no’); return; } if (st.hun >= 90 && force) { setSt(s => ({ …s, hun: Math.min(100, s.hun + 30), mis: s.mis + 1, hap: Math.max(0, s.hap – 10) })); setMsg(‘Mistake!’); return; } // Start eating animation for meat if (type === ‘meat’) { setEating(true); setEatBites(0); const biteInterval = setInterval(() => { setEatBites(b => { if (b >= 3) { clearInterval(biteInterval); setEating(false); return 0; } snd(‘beep’); return b + 1; }); }, 400); setSt(s => ({ …s, hun: Math.min(100, s.hun + 30), wt: s.wt + 1, hp: Math.min(s.mHp, s.hp + 10), hap: Math.min(100, s.hap + 3) })); } else { setSt(s => ({ …s, hun: Math.min(100, s.hun + 10), off: s.off + 1, def: s.def + 1, hap: Math.min(100, s.hap + 2) })); } setMsg(‘Yum!’); }, [st.hun, snd, returnToCenter]); const train = useCallback((type, force = false) => { returnToCenter(); if (st.eng p + 1); // Start training animation setTraining(type); setTrainFrame(0); setShowLightbulb(false); if (type === ‘str’) { // Strength: Weight lifting mini-game setStrGame({ active: true, presses: 0, lifts: 0, timeLeft: 5, started: false }); setMsg(‘MASH!’); } else if (type === ‘spd’) { // Speed: Hurdles mini-game setSpdGame({ active: true, timeLeft: 5, started: false, hurdles: [], jumped: 0, missed: 0, isJumping: false, bgOffset: 0, hurdleCount: 0 }); setMsg(‘JUMP!’); } else { // Brain: Focus mini-game setFocusGame({ active: true, started: false, paddlePos: -45, direction: 1 }); setMsg(‘FOCUS!’); } setMsg(‘Trained!’); }, [st.eng, willRef, snd, returnToCenter]); // Get stage multiplier for rewards (25% increase per stage advancement) const getStageMultiplier = useCallback(() => { const stage = DIGIMON_DATA[digi]?.stage; switch(stage) { case ‘fresh’: return 1.0; case ‘intraining’: return 1.25; case ‘rookie’: return 1.5; case ‘champion’: return 1.75; case ‘ultimate’: return 2.0; default: return 1.0; } }, [digi]); // Handle strength mini-game button press const handleStrPress = useCallback(() => { if (!strGame.active) return; // Start timer on first press if (!strGame.started) { setStrGame(g => ({ …g, started: true })); } snd(‘beep’); setStrGame(g => { const newPresses = g.presses + 1; const newLifts = Math.min(4, Math.floor(newPresses / 10)); return { …g, presses: newPresses, lifts: newLifts }; }); }, [strGame.active, strGame.started, snd]); // Strength mini-game timer useEffect(() => { if (!strGame.active || !strGame.started) return; if (strGame.timeLeft = 4) { message = ‘PERFECT!’; snd(‘evo’); } else if (lifts >= 3) { message = ‘Great!’; snd(‘ok’); } else if (lifts >= 1) { message = ‘OK!’; snd(‘ok’); } else { message = ‘Weak…’; snd(‘no’); } // Store results and show results screen setStrResults({ lifts, offGain, defGain, message }); setStrGame({ active: false, presses: 0, lifts: 0, timeLeft: 5, started: false }); return; } const timer = setTimeout(() => { setStrGame(g => ({ …g, timeLeft: g.timeLeft – 1 })); }, 1000); return () => clearTimeout(timer); }, [strGame.active, strGame.started, strGame.timeLeft, strGame.lifts, getStageMultiplier, snd]); // Confirm strength results and apply stats const confirmStrResults = useCallback(() => { if (!strResults) return; setSt(s => ({ …s, off: s.off + strResults.offGain, def: s.def + strResults.defGain, eng: s.eng – 20, hap: Math.max(0, s.hap – 5), hun: Math.max(0, s.hun – 8) })); setMsg(strResults.message); setStrResults(null); setTraining(null); snd(‘ok’); }, [strResults, snd]); // Handle speed mini-game jump const handleSpdJump = useCallback(() => { if (!spdGame.active) return; // Start timer on first press and spawn first hurdle if (!spdGame.started) { setSpdGame(g => ({ …g, started: true, hurdles: [{ x: 100, id: Date.now(), cleared: false }], hurdleCount: 1 })); return; } // Don’t allow jumping while already jumping if (spdGame.isJumping) return; snd(‘beep’); setSpdGame(g => ({ …g, isJumping: true })); // Land after jump animation – longer jump window for easier gameplay setTimeout(() => { setSpdGame(g => ({ …g, isJumping: false })); }, 500); }, [spdGame.active, spdGame.started, spdGame.isJumping, snd]); // Speed mini-game timer and hurdle spawning useEffect(() => { if (!spdGame.active || !spdGame.started) return; if (spdGame.timeLeft = 3; // Need 3+ to pass (easier) const perfect = spdGame.missed === 0 && spdGame.jumped >= 3; const multiplier = getStageMultiplier(); // Base rewards: 3 speed, 1 brain for passing // Failing (less than 5 jumps): just +1 speed let spdGain, brnGain; if (passed) { const baseSpd = 3; const baseBrn = 1; spdGain = Math.max(1, Math.round(baseSpd * multiplier)); brnGain = Math.round(baseBrn * multiplier); } else { spdGain = 1; brnGain = 0; } let message = ”; if (perfect) { message = ‘PERFECT!’; snd(‘evo’); } else if (passed) { message = ‘Great!’; snd(‘ok’); } else { message = ‘Failed…’; snd(‘no’); } // Store results and show results screen setSpdResults({ jumped: spdGame.jumped, missed: spdGame.missed, spdGain, brnGain, message, perfect, passed }); setSpdGame({ active: false, timeLeft: 5, started: false, hurdles: [], jumped: 0, missed: 0, isJumping: false, bgOffset: 0, hurdleCount: 0 }); return; } // Timer countdown const timer = setTimeout(() => { setSpdGame(g => ({ …g, timeLeft: g.timeLeft – 1 })); }, 1000); return () => clearTimeout(timer); }, [spdGame.active, spdGame.started, spdGame.timeLeft, spdGame.jumped, spdGame.missed, getStageMultiplier, snd]); // Hurdle spawning and movement useEffect(() => { if (!spdGame.active || !spdGame.started || spdGame.timeLeft { setSpdGame(g => { // Move background const newBgOffset = (g.bgOffset + 3) % 160; // Move hurdles – moderate speed let newHurdles = g.hurdles.map(h => ({ …h, x: h.x – 4 })); let jumped = g.jumped; let missed = g.missed; // Check for cleared/missed hurdles – WIDE collision zone for forgiveness (x=5-40) newHurdles = newHurdles.map(h => { // Hurdle is in the jump zone – very wide window if (h.x 0 && !h.cleared) { // Check if player was jumping when hurdle was in zone if (g.isJumping) { jumped++; shouldPlayJump = true; return { …h, cleared: true }; } } return h; }); // Check for hurdles that passed without being cleared newHurdles = newHurdles.filter(h => { if (h.x <= -5 && !h.cleared) { missed++; shouldPlayMiss = true; return false; } // Remove hurdles that went off screen if (h.x 0 ? Math.max(…newHurdles.map(h => h.x)) : -100; const canSpawn = hurdleCount < 8 && rightmostHurdle !h.cleared).length < 3; // Random spawn – more frequent to ensure enough hurdles if (canSpawn && Math.random() clearInterval(gameLoop); }, [spdGame.active, spdGame.started, spdGame.timeLeft, snd]); // Confirm speed results and apply stats const confirmSpdResults = useCallback(() => { if (!spdResults) return; setSt(s => ({ …s, spd: s.spd + spdResults.spdGain, brn: s.brn + spdResults.brnGain, eng: s.eng – 20, hap: Math.max(0, s.hap – 5), hun: Math.max(0, s.hun – 8) })); setMsg(spdResults.message); setSpdResults(null); setTraining(null); snd(‘ok’); }, [spdResults, snd]); // Focus mini-game – start the paddle movement const startFocusGame = useCallback(() => { if (!focusGame.active) return; snd(‘beep’); setFocusGame(g => ({ …g, started: true })); }, [focusGame.active, snd]); // Focus mini-game animation loop useEffect(() => { if (!focusGame.active || !focusGame.started) return; const speed = 4; // pixels per frame const animate = () => { setFocusGame(g => { let newPos = g.paddlePos + (speed * g.direction); let newDir = g.direction; // Bounce off walls if (newPos >= 45) { newPos = 45; newDir = -1; } else if (newPos { if (focusAnimRef.current) { cancelAnimationFrame(focusAnimRef.current); } }; }, [focusGame.active, focusGame.started]); // Focus mini-game – stop paddle and calculate results const handleFocusStop = useCallback(() => { if (!focusGame.active || !focusGame.started) return; // Stop animation if (focusAnimRef.current) { cancelAnimationFrame(focusAnimRef.current); } // Calculate accuracy based on distance from center const distance = Math.abs(focusGame.paddlePos); let accuracy; if (distance <= 3) accuracy = 100; else if (distance <= 8) accuracy = 100 – (distance – 3) * 4; else if (distance <= 15) accuracy = 80 – (distance – 8) * 3; else if (distance = 95) { brnGain = Math.max(1, Math.round(5 * multiplier)); spdGain = Math.round(2 * multiplier); message = ‘PERFECT!’; isPerfect = true; passed = true; snd(‘evo’); } else if (accuracy >= 80) { brnGain = Math.max(1, Math.round(4 * multiplier)); spdGain = Math.round(1 * multiplier); message = ‘Great!’; isPerfect = false; passed = true; snd(‘ok’); } else if (accuracy >= 60) { brnGain = Math.max(1, Math.round(3 * multiplier)); spdGain = Math.round(1 * multiplier); message = ‘Good!’; isPerfect = false; passed = true; snd(‘ok’); } else if (accuracy >= 40) { brnGain = Math.max(1, Math.round(2 * multiplier)); spdGain = 0; message = ‘OK…’; isPerfect = false; passed = true; snd(‘beep’); } else { brnGain = 1; spdGain = 0; message = ‘Missed…’; isPerfect = false; passed = false; snd(‘no’); } // Show results after brief pause setTimeout(() => { setFocusResults({ accuracy: Math.round(accuracy), brnGain, spdGain, message, isPerfect, passed }); setFocusGame({ active: false, started: false, paddlePos: -45, direction: 1 }); }, 300); }, [focusGame.active, focusGame.started, focusGame.paddlePos, getStageMultiplier, snd]); // Confirm focus results and apply stats const confirmFocusResults = useCallback(() => { if (!focusResults) return; setSt(s => ({ …s, brn: s.brn + focusResults.brnGain, spd: s.spd + focusResults.spdGain, eng: s.eng – 15, hap: Math.max(0, s.hap – 4), hun: Math.max(0, s.hun – 6) })); setMsg(focusResults.message); setFocusResults(null); setTraining(null); snd(‘ok’); }, [focusResults, snd]); const clean = useCallback(() => { returnToCenter(); if (poop > 0) { setPoop(0); setDirT(0); setMsg(‘Clean!’); snd(‘ok’); } }, [poop, snd, returnToCenter]); const doSleep = useCallback((force = false) => { returnToCenter(); if (willRef() && !force) { setRef(true); setRefAct(‘sleep’); setMsg(“Won’t!”); snd(‘no’); return; } setSlp(true); setTirT(0); setMsg(‘Zzz…’); setTimeout(() => { setSlp(false); setSt(s => ({ …s, eng: 100, age: s.age + 0.25 })); setGameHours(h => h + 0.25); setMsg(‘Rested!’); }, 5000); }, [willRef, snd, returnToCenter]); const heal = useCallback((force = false) => { returnToCenter(); if (!sick) return; if (willRef() && !force) { setRef(true); setRefAct(‘heal’); setMsg(“Won’t!”); snd(‘no’); return; } setSick(false); setSickT(0); setSt(s => ({ …s, hp: s.mHp })); setMsg(‘Healed!’); snd(‘ok’); }, [sick, willRef, snd, returnToCenter]); const praise = useCallback(() => { returnToCenter(); snd(‘beep’); setBp(p => p + 1); if (st.hap >= 100) { setOpC(c => { const n = c + 1; if (n > 5) { setSt(s => ({ …s, mis: s.mis + 1 })); setMsg(‘Mistake!’); return 0; } setMsg(`(${n}/5)`); return n; }); return; } setOpC(0); setPetting(true); setTimeout(() => setPetting(false), 1200); setSt(s => ({ …s, hap: Math.min(100, s.hap + 10) })); setMsg(‘Happy!’); }, [st.hap, snd, returnToCenter]); const scold = useCallback(() => { returnToCenter(); snd(‘beep’); setBp(p => p + 1); if (st.hap >= 100) { setSt(s => ({ …s, mis: s.mis + 1, hap: Math.max(0, s.hap – 15) })); setMsg(‘Mistake!’); return; } if (ref && refAct) { setRef(false); setSt(s => ({ …s, dis: Math.min(100, s.dis + 5), hap: Math.min(100, s.hap + 5) })); setMsg(‘OK!’); setTimeout(() => { if (refAct === ‘train’) train(‘str’, true); else if (refAct === ‘sleep’) doSleep(true); else if (refAct === ‘heal’) heal(true); else if (refAct === ‘feed’) feed(‘meat’, true); setRefAct(null); }, 500); return; } if (st.hun >= 90) { setMsg(‘Force…’); setTimeout(() => feed(‘meat’, true), 500); return; } setSt(s => ({ …s, dis: Math.min(100, s.dis + 10), hap: Math.max(0, s.hap – 5) })); setMsg(‘Scolded!’); }, [st.hap, st.hun, ref, refAct, snd, train, doSleep, heal, feed, returnToCenter]); const startBattle = useCallback(() => { returnToCenter(); const d = DIGIMON_DATA[digi]; if (!d?.attacks) { setMsg(“Can’t!”); return; } // Energy cost based on stage const energyCost = d.stage === ‘rookie’ ? 30 : d.stage === ‘champion’ ? 20 : 10; if (st.eng ({ …s, eng: s.eng – energyCost })); const eP = ENEMIES[d.stage] || ENEMIES.rookie; const eId = eP[Math.floor(Math.random() * eP.length)]; setBattle({ enemy: eId, eHp: st.mHp * 0.8, eMax: st.mHp * 0.8, pHp: st.hp, turn: ‘p’, log: [`${DIGIMON_DATA[eId].name}!`] }); setAtk(0); setGs(‘battle’); snd(‘beep’); }, [digi, st, snd, returnToCenter]); const doAttack = useCallback(() => { if (!battle || battle.turn !== ‘p’) return; const d = DIGIMON_DATA[digi]; const dmg = Math.floor(st.off * (1 + Math.random() * 0.5)); setPlayerAttacking(true); setTimeout(() => setPlayerAttacking(false), 400); snd(‘hit’); const nE = Math.max(0, battle.eHp – dmg); const nL = […battle.log, `${dmg}dmg!`]; if (nE ({ …s, bat: s.bat + 1, hap: Math.min(100, s.hap + 10), off: s.off + 2 })); setBattle({ …battle, eHp: 0, log: […nL, ‘WIN!’], turn: ‘end’ }); snd(‘ok’); setTimeout(() => { setGs(‘playing’); setBattle(null); setMsg(‘Won!’); }, 2000); } else { setBattle({ …battle, eHp: nE, log: nL, turn: ‘e’ }); setTimeout(() => { const eDmg = Math.floor(st.off * 0.6 * (1 + Math.random() * 0.3)); const nP = Math.max(0, battle.pHp – eDmg); setEnemyAttacking(true); setTimeout(() => setEnemyAttacking(false), 400); snd(‘hit’); if (nP ({ …s, hp: Math.floor(s.mHp * 0.3), hap: Math.max(0, s.hap – 20), mis: s.mis + 1 })); setBattle(pr => ({ …pr, pHp: 0, log: […pr.log, ‘LOSE’], turn: ‘end’ })); setTimeout(() => { setGs(‘playing’); setBattle(null); setMsg(‘Lost…’); }, 2000); } else { setBattle(pr => ({ …pr, pHp: nP, log: […pr.log, `${eDmg}dmg!`], turn: ‘p’ })); } }, 1000); } }, [battle, digi, st, snd]); const run = useCallback(() => { if (Math.random() < 0.5) { setGs('playing'); setBattle(null); setMsg('Escaped!'); } else setMsg("Can't!"); }, []); const hasSave = load() !== null; const d = DIGIMON_DATA[digi]; const warn = st.hun <= 20 || st.eng = 3 || sick || poop >= 6; const BrickShell = ({ children }) => (
{/* Keychain loop at top – PS1 style chunky metal */}
{/* Main brick body – PS1/32-bit realistic red brick texture */}
{/* 32-bit brick texture – chunky PS1 style stone blocks */} {/* PS1 style chunky brick pattern */} {/* Individual brick blocks with beveled edges */} {/* Row 1 */} {/* Row 2 – offset */} {/* Mortar lines */} {/* Surface wear pattern */} {/* PS1 dithered shadow overlay */}
{children}
); const LCDScreen = ({ children, size = ‘normal’, showScene = false, isNight = false }) => (
{/* Outer broken cage frame – dark metal with missing bars */}
{/* PS1 style metal texture */}
{/* Broken cage bars – vertical bars with gaps (missing bars) */} {/* Top horizontal bar */} {/* Bottom horizontal bar */} {/* Left vertical bars – some broken/missing */} {/* Broken stub at top */} {/* Right vertical bars – some broken/missing */} {/* Broken stub */}
{/* Icon row above screen – carved into metal */}
{/* Inner screen bezel – dark weathered metal */}
{/* The actual LCD screen – kept at original resolution */}
{showScene && } {!showScene && (
)} {children}
{/* Bottom icons row */}
); // 16-bit style button icons const ButtonIcon = ({ type, size = 20 }) => { const icons = { // 32-bit Steak – detailed juicy T-bone with rich shading meat: {/* Bone – detailed with depth */} {/* Bone marrow detail */} {/* Main meat – gradient base */} {/* Highlight edge – top sear */} {/* Deep shadow edge */} {/* Fat marbling – realistic distribution */} {/* Grill marks – dark char lines */} {/* Juice/glisten highlights */} , // 32-bit Vitamin Capsule – detailed with 3D shading and shine vitamin: {/* Shadow underneath */} {/* Red half – back shadow */} {/* Red half – main body */} {/* Red half – highlight */} {/* Red half – top shine */} {/* Blue half – back shadow */} {/* Blue half – main body */} {/* Blue half – highlight */} {/* Blue half – top shine */} {/* Center band – metallic separator */} {/* Center shine line */} {/* Rounded end caps */} , // Train icon – now a dumbbell train: {/* Left weight plates */} {/* Right weight plates */} {/* Bar */} {/* Inner plates */} , // Strength – same dumbbell style strength: , // Running man speed: , // Open Book instead of brain brain: {/* Left page */} {/* Right page */} {/* Spine */} {/* Text lines on left page */} {/* Text lines on right page */} {/* Page highlight */} , // Red cross (hospital) care: , // Syringe for healing heal: , // Smiley face for praise praise: , // Sad face for scold scold: , // Angry face (when refusing) angry: , // Shower head for cleaning clean: {/* Shower head */} {/* Shower holes */} {/* Pipe/arm */} {/* Water drops */} {/* More drops */} , // Three Z’s for sleep sleep: , // Stats/Chart icon stats: , // 32-bit Sword – detailed with metallic blade, ornate hilt, gleam fight: {/* Blade shadow */} {/* Blade – main steel body */} {/* Blade – edge highlight (left edge) */} {/* Blade – center fuller (groove) */} {/* Blade – bright edge highlights */} {/* Blade tip */} {/* Cross-guard – ornate gold */} {/* Cross-guard gems */} {/* Grip – wrapped leather */} {/* Grip wrap lines */} {/* Pommel – gold cap */} {/* Pommel gem */} {/* Gleam effect on blade */} , // Back arrow back: , // Run icon run: , // New game icon – plus sign newgame: , // Load game icon load: , // OK/Confirm icon – checkmark ok: , // Left arrow for navigation arrowLeft: , // Right arrow for navigation arrowRight: , }; return icons[type] || null; }; const RoundButton = ({ onClick, icon, disabled = false, pulse = false }) => ( ); if (gs === ‘welcome’) return (
DIGIMON
setGs(‘title’)} icon=”ok” />
); if (gs === ‘title’) return (

DIGITAL

MONSTER

start(false)} icon=”newgame” /> {hasSave && start(true)} icon=”load” />}
); if (gs === ‘eggSelect’) return (

SELECT EGG

{/* Egg carousel display */}
{/* Left preview egg (smaller, faded) */}
{/* Center selected egg (large, animated) */}
{/* Right preview egg (smaller, faded) */}
{/* Egg type name with colored background */}

{EGG_TYPES[selectedEgg].name}

{/* Egg selection dots */}
{EGG_TYPES.map((egg, i) => (
))}
{ setSelectedEgg(e => (e + 3) % 4); snd(‘beep’); }} icon=”arrowLeft” /> { setSelectedEgg(e => (e + 1) % 4); snd(‘beep’); }} icon=”arrowRight” />
); if (gs === ‘hatching’) return (

TAP TO HATCH!

{hatchTaps}/15

{hatchTaps > 0 && hatchTaps < 15 &&

*crack crack*

}
); if (gs === ‘evolving’) return (

EVOLUTION!

{evoT && DIGIMON_DATA[evoT]?.name}

); if (gs === ‘battle’ && battle) { const enemyData = DIGIMON_DATA[battle.enemy]; const atkPower = Math.floor(st.off * 1.25); return (
{d?.name}
{battle.pHp}/{st.mHp}
VS
{enemyData?.name}
{Math.floor(battle.eHp)}/{Math.floor(battle.eMax)}
{battle.log.slice(-3).map((l, i) =>
{l}
)}
{battle.turn === ‘p’ && ( {d?.attacks?.slice(0, 2).map((a, i) => ( ))} )} {battle.turn === ‘e’ &&
}
); } return (
{gameHours.toFixed(1)}h {d?.name || ‘Egg’} ❌{st.mis}
{warn &&
{st.hun <= 20 && πŸ–{Math.max(0, Math.floor((750-hunT)/150))}h}{st.eng <= 20 && 😴{Math.max(0, 60-tirT)}}{poop >= 3 && πŸ’©{poop >= 6 ? `!${Math.max(0, Math.floor((150-dirT)/60))}m` : Math.max(0, 60-dirT)}}{sick && πŸ€’{Math.max(0, Math.floor((450-sickT)/150))}h}
}
{/* Speed Mini-Game Track with Hurdles */} {spdGame.active && spdGame.started && (
{/* Scrolling ground/track */}
{/* Hurdles – positioned on the track, taller */} {spdGame.hurdles.map(hurdle => (
{/* Success indicator when cleared */} {hurdle.cleared && (
βœ“
)}
))}
)} {(() => { const stage = d?.stage || ‘fresh’; const isEarlyStage = stage === ‘fresh’ || stage === ‘intraining’; // Squash and stretch for early stages const squashX = isEarlyStage && isWalking ? (walkFrame % 2 === 0 ? 1.15 : 0.9) : 1; const squashY = isEarlyStage && isWalking ? (walkFrame % 2 === 0 ? 0.85 : 1.1) : 1; // Bounce for all stages const bounceY = anim * 3; // Walking tilt for later stages const tilt = !isEarlyStage && isWalking ? (walkFrame % 2 === 0 ? 3 : -3) * walkDir : 0; // Flip sprite based on direction const flipX = walkDir === -1 ? -1 : 1; // Speed training position – stay on left during minigame, positioned lower on track const isInSpeedGame = spdGame.active && spdGame.started; const trainXOffset = training === ‘spd’ ? (spdGame.active ? -25 : runX) : 0; // Speed minigame jumping – clear jump height above hurdles const isSpeedJumping = spdGame.active && spdGame.isJumping; const jumpHeight = isSpeedJumping ? -45 : 0; // Position Digimon much lower during speed game so it’s on the track with hurdles const speedGameYOffset = isInSpeedGame ? 20 : 0; return (
{/* Strength training weight in front (mini-game) */} {training === ‘str’ && strGame.active && (
)} {/* Brain training lightbulb above head */} {training === ‘brn’ && showLightbulb && (
)} {ref ? : slp ?
πŸ’€
: sick ?
πŸ€’
: } {/* Eating steak animation */} {eating && (
)} {/* Brain training book in front */} {training === ‘brn’ && !showLightbulb && (
)} {petting && (
)}
); })()}
{[…Array(poop)].map((_, i) => πŸ’©)}
{msg &&
{msg}
} {/* Strength mini-game overlay */} {strGame.active && (
⏱️{strGame.timeLeft}
πŸ‹οΈ{strGame.lifts}/4
)} {/* Focus mini-game overlay */} {focusGame.active && (
{/* Focus game area */}
{/* Target zones background */}
{/* Center line */}
{/* Moving paddle */}
{/* Status text */}
{focusGame.started ? ‘⚑ STOP! ⚑’ : ‘🧠 FOCUS!’}
)}
❀️{st.hp}/{st.mHp}
πŸ–{st.hun}%
<div className={`h-full rounded ${st.hun
⚑{st.eng}%
<div className={`h-full rounded ${st.eng
😊{st.hap}%
<div className={`h-full rounded ${st.hap
{/* Keyhole element – PS1 style metallic plate with keyhole cutout */}
{/* Keyhole shape – classic skeleton key style */}
{/* Circle part of keyhole */}
{/* Slot part of keyhole */}
{/* Metal highlight */}
{/* Strength Mini-Game Button */} {strGame.active && (
{/* Timer and Lifts display */}
⏱️ {strGame.timeLeft}s πŸ‹οΈ {strGame.lifts}/4
{/* Lift progress bar */}
{10 – (strGame.presses % 10)} more to lift!
{/* MASH button */} {/* Instructions */} {!strGame.started && (
Press to start!
)}
)} {/* Strength Results Screen */} {strResults && (
{/* Title */}
= 4 ? ‘#ffd700’ : strResults.lifts >= 3 ? ‘#90EE90’ : strResults.lifts >= 1 ? ‘#87CEEB’ : ‘#ff6b6b’ }}> {strResults.message}
{/* Lifts completed */}
πŸ‹οΈ {strResults.lifts}/4 Lifts
{/* Stat gains */}
STAT GAINS
βš”οΈ +{strResults.offGain}
πŸ›‘οΈ +{strResults.defGain}
{/* OK Button */}
)} {/* Speed Mini-Game Button */} {spdGame.active && (
{/* Timer and Jumps display */}
⏱️ {spdGame.timeLeft}s βœ… {spdGame.jumped}/3+
{/* Progress bar showing jumps toward goal */}
= 3 ? ‘bg-gradient-to-r from-green-500 to-green-400’ : ‘bg-gradient-to-r from-yellow-500 to-orange-500’}`} style={{ width: `${Math.min(100, (spdGame.jumped / 3) * 100)}%` }} />
{spdGame.started ? (spdGame.isJumping ? ‘🦘 JUMPING!’ : `πŸƒ ${3 – spdGame.jumped > 0 ? `${3 – spdGame.jumped} more to pass!` : ‘βœ“ Keep going!’}`) : ‘Press START!’}
{/* JUMP button */} {/* Instructions */} {!spdGame.started && (
Jump 3+ hurdles to pass!
)}
)} {/* Speed Results Screen */} {spdResults && (
{/* Title */}
{spdResults.message}
{/* Hurdles jumped */}
πŸƒ {spdResults.jumped}/3+ Cleared ({spdResults.missed} missed)
{/* Stat gains */}
STAT GAINS
πŸ’¨ +{spdResults.spdGain}
🧠 +{spdResults.brnGain}
{/* Perfect bonus indicator */} {spdResults.perfect && (
⭐ PERFECT BONUS! ⭐
)} {!spdResults.passed && (
Need 3+ jumps to pass!
)}
{/* OK Button */}
)} {/* Focus Mini-Game Button */} {focusGame.active && (
{/* Status display */}
🧠 FOCUS!
{focusGame.started ? ‘Press STOP!’ : ‘Align paddle with center’}
{/* START/STOP button */} {/* Instructions */} {!focusGame.started && (
Press to start!
)}
)} {/* Focus Results Screen */} {focusResults && (
{/* Title */}
{focusResults.message}
{/* Accuracy */}
🎯 {focusResults.accuracy}% Accuracy
{/* Stat gains */}
STAT GAINS
🧠 +{focusResults.brnGain}
πŸ’¨ +{focusResults.spdGain}
{/* Perfect bonus indicator */} {focusResults.isPerfect && (
⭐ PERFECT BONUS! ⭐
)} {!focusResults.passed && (
Need 40%+ to pass!
)}
{/* OK Button */}
)} {/* Buttons area */} {menu === ‘main’ && !strGame.active && !strResults && !spdGame.active && !spdResults && !focusGame.active && !focusResults && (
{ returnToCenter(); setMenu(‘feed’); }} icon=”meat” /> { returnToCenter(); setMenu(‘train’); }} icon=”train” /> { returnToCenter(); setMenu(‘care’); }} icon=”care” /> { returnToCenter(); setMenu(‘stats’); }} icon=”stats” /> doSleep()} pulse={st.eng
)} {menu === ‘feed’ && !strGame.active && !strResults && !spdGame.active && !spdResults && !focusGame.active && !focusResults && (
feed(‘meat’)} icon=”meat” /> feed(‘vit’)} icon=”vitamin” /> setMenu(‘main’)} icon=”back” />
)} {menu === ‘train’ && !strGame.active && !strResults && !spdGame.active && !spdResults && !focusGame.active && !focusResults && (
train(‘str’)} icon=”strength” /> train(‘spd’)} icon=”speed” /> train(‘brn’)} icon=”brain” /> setMenu(‘main’)} icon=”back” />
)} {menu === ‘care’ && !strGame.active && !strResults && !spdGame.active && !spdResults && !focusGame.active && !focusResults && (
= 3} icon=”clean” /> heal()} disabled={!sick} icon=”heal” /> setMenu(‘main’)} icon=”back” />
)} {menu === ‘stats’ && !strGame.active && !strResults && !spdGame.active && !spdResults && !focusGame.active && !focusResults && (
βš”οΈ {st.off}
πŸ›‘οΈ {st.def}
πŸ’¨ {st.spd}
🧠 {st.brn}
Age: {st.age}h
setMenu(‘main’)} icon=”back” />
)}
); } ReactDOM.render(, document.getElementById(‘root’));