From b74acb272ef2c60021184fac00e86946d97d4dbf Mon Sep 17 00:00:00 2001 From: Ernie Cook Date: Tue, 3 Mar 2026 02:06:32 -0500 Subject: [PATCH] Add Star Catcher minigame and chamber decorations - Added new Star Catcher minigame where players catch falling stars - Golden stars worth 3 points, regular stars worth 1 point - 10 Void Essence awarded per point (higher rewards than Void Hunt) - Added 4 purchasable decorations: Crystal Orb, Void Flame, Ancient Tome, Moon Stone - Decorations appear in chamber with ambient pulsing animation - Persist decorations to localStorage --- WORKLOG.md | 10 ++- src/pages/Chamber.tsx | 182 +++++++++++++++++++++++++++++++++++++----- 2 files changed, 171 insertions(+), 21 deletions(-) diff --git a/WORKLOG.md b/WORKLOG.md index c000f81..4766712 100644 --- a/WORKLOG.md +++ b/WORKLOG.md @@ -51,12 +51,16 @@ A whimsical pocket dimension where digital companions called "Voidlings" float a - Added sound effects system using Web Audio API (pet, feed, spawn, achievement, purchase, release) - Added mute toggle button to control sounds - Added Void Hunt minigame - click targets for 30 seconds to earn Void Essence +- Added Star Catcher minigame - catch falling stars for higher rewards (10 Void Essence per star) +- Added purchasable chamber decorations: Crystal Orb, Void Flame, Ancient Tome, Moon Stone +- Decorations appear in the chamber with ambient pulsing animation ### Next Steps -1. Add more minigames +1. Add more minigames (DONE - added Star Catcher) 2. Add voidling evolution/combat system -3. Add decorations for the chamber +3. Add more chamber decorations/ways to customize chamber +4. Add achievement for catching stars in Star Catcher --- -*Last updated: Session 5 complete - Added voidling shop with Void Essence currency* +*Last updated: Session 6 complete - Added Star Catcher minigame and chamber decorations* diff --git a/src/pages/Chamber.tsx b/src/pages/Chamber.tsx index 5133be1..433e701 100644 --- a/src/pages/Chamber.tsx +++ b/src/pages/Chamber.tsx @@ -113,6 +113,7 @@ interface ShopItem { id: string name: string type: Voidling['type'] + category: 'voidling' | 'consumable' | 'decoration' color: string glow: string shape: string @@ -121,6 +122,22 @@ interface ShopItem { icon: string } +interface Decoration { + id: string + name: string + icon: string + x: number + y: number +} + +interface FallingStar { + id: number + x: number + y: number + speed: number + points: number +} + const VOIDLING_TYPES = [ { type: 'wisp', color: 'bg-purple-400', glow: 'shadow-purple-500/50', shape: 'rounded-full', description: 'Ethereal and floaty' }, { type: 'blob', color: 'bg-cyan-400', glow: 'shadow-cyan-500/50', shape: 'rounded-[40%]', description: 'Bouncy and playful' }, @@ -131,14 +148,18 @@ const VOIDLING_TYPES = [ ] const SHOP_ITEMS: ShopItem[] = [ - { id: 'wisp_egg', name: 'Wisp Egg', type: 'wisp', color: 'bg-purple-400', glow: 'shadow-purple-500/50', shape: 'rounded-full', cost: 50, description: 'Hatches into a wisp', icon: '🥚' }, - { id: 'blob_egg', name: 'Blob Egg', type: 'blob', color: 'bg-cyan-400', glow: 'shadow-cyan-500/50', shape: 'rounded-[40%]', cost: 50, description: 'Hatches into a blob', icon: '🥚' }, - { id: 'spark_egg', name: 'Spark Egg', type: 'spark', color: 'bg-amber-400', glow: 'shadow-amber-500/50', shape: 'rounded-sm', cost: 75, description: 'Hatches into a spark', icon: '⚡' }, - { id: 'ember_egg', name: 'Ember Egg', type: 'ember', color: 'bg-rose-400', glow: 'shadow-rose-500/50', shape: 'rounded-full', cost: 75, description: 'Hatches into an ember', icon: '🔥' }, - { id: 'glitch_egg', name: 'Glitch Egg', type: 'glitch', color: 'bg-green-400', glow: 'shadow-green-500/50', shape: 'rounded-lg', cost: 150, description: 'Rare unstable voidling', icon: '👾' }, - { id: 'crystal_egg', name: 'Crystal Egg', type: 'crystal', color: 'bg-blue-400', glow: 'shadow-blue-500/50', shape: 'rounded-sm', cost: 150, description: 'Rare sharp voidling', icon: '💎' }, - { id: 'treat', name: 'Void Treat', type: 'wisp', color: 'bg-yellow-400', glow: 'shadow-yellow-500/50', shape: 'rounded-full', cost: 20, description: 'All voidlings become happy', icon: '🍪' }, - { id: 'star_fragment', name: 'Star Fragment', type: 'wisp', color: 'bg-white', glow: 'shadow-white/50', shape: 'rounded-full', cost: 200, description: 'Rare: +100 Void Essence!', icon: '⭐' }, + { id: 'wisp_egg', name: 'Wisp Egg', type: 'wisp', category: 'voidling', color: 'bg-purple-400', glow: 'shadow-purple-500/50', shape: 'rounded-full', cost: 50, description: 'Hatches into a wisp', icon: '🥚' }, + { id: 'blob_egg', name: 'Blob Egg', type: 'blob', category: 'voidling', color: 'bg-cyan-400', glow: 'shadow-cyan-500/50', shape: 'rounded-[40%]', cost: 50, description: 'Hatches into a blob', icon: '🥚' }, + { id: 'spark_egg', name: 'Spark Egg', type: 'spark', category: 'voidling', color: 'bg-amber-400', glow: 'shadow-amber-500/50', shape: 'rounded-sm', cost: 75, description: 'Hatches into a spark', icon: '⚡' }, + { id: 'ember_egg', name: 'Ember Egg', type: 'ember', category: 'voidling', color: 'bg-rose-400', glow: 'shadow-rose-500/50', shape: 'rounded-full', cost: 75, description: 'Hatches into an ember', icon: '🔥' }, + { id: 'glitch_egg', name: 'Glitch Egg', type: 'glitch', category: 'voidling', color: 'bg-green-400', glow: 'shadow-green-500/50', shape: 'rounded-lg', cost: 150, description: 'Rare unstable voidling', icon: '👾' }, + { id: 'crystal_egg', name: 'Crystal Egg', type: 'crystal', category: 'voidling', color: 'bg-blue-400', glow: 'shadow-blue-500/50', shape: 'rounded-sm', cost: 150, description: 'Rare sharp voidling', icon: '💎' }, + { id: 'treat', name: 'Void Treat', type: 'wisp', category: 'consumable', color: 'bg-yellow-400', glow: 'shadow-yellow-500/50', shape: 'rounded-full', cost: 20, description: 'All voidlings become happy', icon: '🍪' }, + { id: 'star_fragment', name: 'Star Fragment', type: 'wisp', category: 'consumable', color: 'bg-white', glow: 'shadow-white/50', shape: 'rounded-full', cost: 200, description: 'Rare: +100 Void Essence!', icon: '⭐' }, + { id: 'crystal_orb', name: 'Crystal Orb', type: 'wisp', category: 'decoration', color: 'bg-blue-400', glow: 'shadow-blue-500/50', shape: 'rounded-full', cost: 100, description: 'A glowing orb for your chamber', icon: '🔮' }, + { id: 'void_flame', name: 'Void Flame', type: 'wisp', category: 'decoration', color: 'bg-purple-400', glow: 'shadow-purple-500/50', shape: 'rounded-full', cost: 120, description: 'An eternal purple flame', icon: '🕯️' }, + { id: 'ancient_book', name: 'Ancient Tome', type: 'wisp', category: 'decoration', color: 'bg-amber-600', glow: 'shadow-amber-500/50', shape: 'rounded-lg', cost: 150, description: 'Knowledge from the void', icon: '📕' }, + { id: 'moon_stone', name: 'Moon Stone', type: 'wisp', category: 'decoration', color: 'bg-zinc-300', glow: 'shadow-zinc-400/50', shape: 'rounded-full', cost: 200, description: 'A piece of the moon', icon: '🌑' }, ] const ACHIEVEMENTS: Achievement[] = [ @@ -222,9 +243,12 @@ export default function Chamber() { }) const [showGames, setShowGames] = useState(false) const [gameActive, setGameActive] = useState(false) + const [gameType, setGameType] = useState<'hunt' | 'stars' | null>(null) const [gameScore, setGameScore] = useState(0) const [gameTimeLeft, setGameTimeLeft] = useState(30) const [gameTargets, setGameTargets] = useState<{id: number, x: number, y: number}[]>([]) + const [fallingStars, setFallingStars] = useState([]) + const [decorations, setDecorations] = useState([]) useEffect(() => { soundEngine.setMuted(isMuted) @@ -248,7 +272,7 @@ export default function Chamber() { }, [gameActive]) useEffect(() => { - if (!gameActive) return + if (!gameActive || gameType !== 'hunt') return const spawnTarget = () => { const newTarget = { @@ -263,7 +287,41 @@ export default function Chamber() { const spawnInterval = setInterval(spawnTarget, 1500) return () => clearInterval(spawnInterval) - }, [gameActive]) + }, [gameActive, gameType]) + + useEffect(() => { + if (!gameActive || gameType !== 'stars') return + + const spawnStar = () => { + const newStar: FallingStar = { + id: Date.now() + Math.random(), + x: Math.random() * 80 + 10, + y: -5, + speed: Math.random() * 0.8 + 0.5, + points: Math.random() > 0.8 ? 3 : 1, + } + setFallingStars(prev => [...prev, newStar]) + } + + spawnStar() + const spawnInterval = setInterval(spawnStar, 800) + + return () => clearInterval(spawnInterval) + }, [gameActive, gameType]) + + useEffect(() => { + if (!gameActive || gameType !== 'stars') return + + const moveStars = setInterval(() => { + setFallingStars(prev => { + return prev + .map(s => ({ ...s, y: s.y + s.speed })) + .filter(s => s.y < 110) + }) + }, 50) + + return () => clearInterval(moveStars) + }, [gameActive, gameType]) useEffect(() => { const savedVoidlings = localStorage.getItem('voidlings') @@ -292,6 +350,13 @@ export default function Chamber() { setUserStats(stats) } catch {} } + + const savedDecorations = localStorage.getItem('voidling_decorations') + if (savedDecorations) { + try { + setDecorations(JSON.parse(savedDecorations)) + } catch {} + } }, []) useEffect(() => { @@ -307,6 +372,10 @@ export default function Chamber() { localStorage.setItem('voidling_achievements', JSON.stringify(unlockedAchievements)) }, [unlockedAchievements]) + useEffect(() => { + localStorage.setItem('voidling_decorations', JSON.stringify(decorations)) + }, [decorations]) + useEffect(() => { ACHIEVEMENTS.forEach(achievement => { if (!unlockedAchievements.includes(achievement.id)) { @@ -521,16 +590,23 @@ export default function Chamber() { setVoidlings(voidlings.filter(v => v.id !== id)) } - function startGame() { + function startGame(type: 'hunt' | 'stars') { + setGameType(type) setGameActive(true) setGameScore(0) setGameTimeLeft(30) setGameTargets([]) + setFallingStars([]) } function endGame() { setGameActive(false) - const reward = gameScore * 5 + let reward = 0 + if (gameType === 'hunt') { + reward = gameScore * 5 + } else if (gameType === 'stars') { + reward = gameScore * 10 + } if (reward > 0) { setUserStats(prev => { const newStats = { ...prev, voidEssence: prev.voidEssence + reward } @@ -539,6 +615,8 @@ export default function Chamber() { }) } setGameTargets([]) + setFallingStars([]) + setGameType(null) } function hitTarget(id: number) { @@ -547,6 +625,15 @@ export default function Chamber() { setGameTargets(prev => prev.filter(t => t.id !== id)) } + function catchStar(id: number) { + soundEngine.playPet() + const star = fallingStars.find(s => s.id === id) + if (star) { + setGameScore(prev => prev + star.points) + } + setFallingStars(prev => prev.filter(s => s.id !== id)) + } + function purchaseItem(item: ShopItem) { if (userStats.voidEssence < item.cost) return @@ -566,6 +653,15 @@ export default function Chamber() { localStorage.setItem('voidling_stats', JSON.stringify(newStats)) return newStats }) + } else if (item.category === 'decoration') { + const newDecoration: Decoration = { + id: generateId(), + name: item.name, + icon: item.icon, + x: Math.random() * 70 + 15, + y: Math.random() * 50 + 30, + } + setDecorations(prev => [...prev, newDecoration]) } else { const pos = getRandomPosition() const newVoidling: Voidling = { @@ -806,6 +902,20 @@ export default function Chamber() { ) })} + + {decorations.map((decoration) => ( +
+ {decoration.icon} +
+ ))}
@@ -916,12 +1026,29 @@ export default function Chamber() {

Catch as many targets as you can in 30 seconds. Each catch awards 5 Void Essence!

+ +
+
+ +
+
Star Catcher
+
Catch falling stars!
+
+
+

Catch falling stars in 30 seconds. Golden stars are worth 3 points! Each point awards 10 Void Essence!

+ +
@@ -934,11 +1061,11 @@ export default function Chamber() { ⏱️ {gameTimeLeft}s
- 🎯 {gameScore} + {gameType === 'hunt' ? '🎯' : '⭐'} {gameScore}
- {gameTargets.map(target => ( + {gameType === 'hunt' && gameTargets.map(target => ( ))} + + {gameType === 'stars' && fallingStars.map(star => ( + + ))} )} @@ -959,13 +1101,17 @@ export default function Chamber() { Game Over!

- You caught {gameScore} targets! + {gameType === 'hunt' ? ( + <>You caught {gameScore} targets! + ) : ( + <>You caught {gameScore} stars! + )}

- +{gameScore * 5} Void Essence + +{(gameType === 'hunt' ? gameScore * 5 : gameScore * 10)} Void Essence