From 3a7335ea7cd452446477f86cce42de71322b9cb2 Mon Sep 17 00:00:00 2001 From: Ernie Cook Date: Mon, 2 Mar 2026 17:44:50 -0500 Subject: [PATCH] Add night/day cycle with 4 time phases - Added time cycle system with dawn, day, dusk, and night phases - Each phase has unique gradient backgrounds and ambient orb colors - Voidlings move faster during day/dawn/dusk, slower at night - Voidlings naturally become sleepy when night falls - Added auto-cycle that progresses every ~15 seconds per phase - Added manual time control toggle for user preference - Added progress bar showing time until next phase - Time controls display current time icon and name --- WORKLOG.md | 11 ++- src/pages/Chamber.tsx | 182 +++++++++++++++++++++++++++++++++++++----- 2 files changed, 172 insertions(+), 21 deletions(-) diff --git a/WORKLOG.md b/WORKLOG.md index 4b7f512..0140030 100644 --- a/WORKLOG.md +++ b/WORKLOG.md @@ -37,13 +37,18 @@ A whimsical pocket dimension where digital companions called "Voidlings" float a - Added petting interaction (โ™ก button) - makes voidlings happy - Added achievements system with 10 unlockable achievements - Added user stats tracking (total pets, feedings, sessions) +- Added night/day cycle with 4 time phases (dawn, day, dusk, night) +- Added time-based visual themes with different gradients and ambient colors +- Added automatic time progression (1 minute per full cycle) +- Added manual time control toggle +- Added voidling speed multipliers based on time of day (slower at night) +- Voidlings naturally become sleepy at night ### Next Steps 1. Add sound effects 2. Add voidling shop/trading -3. Add night/day cycle -4. Add more voidling types -5. Add voidling minigames +3. Add more voidling types +4. Add voidling minigames --- diff --git a/src/pages/Chamber.tsx b/src/pages/Chamber.tsx index 1d1bb8d..0aabf27 100644 --- a/src/pages/Chamber.tsx +++ b/src/pages/Chamber.tsx @@ -14,6 +14,74 @@ interface Voidling { pets: number } +type TimeOfDay = 'dawn' | 'day' | 'dusk' | 'night' + +interface TimeCycle { + name: TimeOfDay + gradient: string + chamberBg: string + chamberBorder: string + orb1: string + orb2: string + orb3: string + voidlingMultiplier: number + description: string +} + +const TIME_CYCLES: TimeCycle[] = [ + { + name: 'dawn', + gradient: 'from-orange-300 via-pink-300 to-purple-400', + chamberBg: 'bg-orange-900/20', + chamberBorder: 'border-orange-700/30', + orb1: 'bg-orange-400/20', + orb2: 'bg-pink-400/20', + orb3: 'bg-purple-400/20', + voidlingMultiplier: 1.2, + description: 'The void stirs...' + }, + { + name: 'day', + gradient: 'from-cyan-300 via-blue-300 to-purple-300', + chamberBg: 'bg-cyan-900/20', + chamberBorder: 'border-cyan-700/30', + orb1: 'bg-cyan-400/20', + orb2: 'bg-blue-400/20', + orb3: 'bg-purple-400/20', + voidlingMultiplier: 1.5, + description: 'The void is bright' + }, + { + name: 'dusk', + gradient: 'from-purple-400 via-pink-400 to-orange-400', + chamberBg: 'bg-purple-900/20', + chamberBorder: 'border-purple-700/30', + orb1: 'bg-purple-400/20', + orb2: 'bg-pink-400/20', + orb3: 'bg-orange-400/20', + voidlingMultiplier: 1.2, + description: 'Shadows lengthen...' + }, + { + name: 'night', + gradient: 'from-indigo-900 via-purple-900 to-black', + chamberBg: 'bg-indigo-950/40', + chamberBorder: 'border-indigo-800/40', + orb1: 'bg-indigo-500/10', + orb2: 'bg-purple-500/10', + orb3: 'bg-pink-500/10', + voidlingMultiplier: 0.5, + description: 'The void sleeps' + }, +] + +const TIME_ICONS: Record = { + dawn: '๐ŸŒ…', + day: 'โ˜€๏ธ', + dusk: '๐ŸŒ†', + night: '๐ŸŒ™', +} + interface Particle { id: number x: number @@ -47,13 +115,13 @@ const VOIDLING_TYPES = [ ] const ACHIEVEMENTS: Achievement[] = [ - { id: 'first_pet', name: 'Gentle Touch', description: 'Pet your first voidling', icon: 'โœ‹', condition: (v, s) => s.totalPets >= 1 }, - { id: 'ten_pets', name: 'Frienemy', description: 'Pet voidlings 10 times', icon: '๐Ÿค', condition: (v, s) => s.totalPets >= 10 }, - { id: 'fifty_pets', name: 'Void Whisperer', description: 'Pet voidlings 50 times', icon: '๐Ÿ”ฎ', condition: (v, s) => s.totalPets >= 50 }, - { id: 'first_feeding', name: 'Nourisher', description: 'Feed your first voidling', icon: 'โ—•', condition: (v, s) => s.totalFeedings >= 1 }, - { id: 'collector', name: 'Collector', description: 'Have 5 different voidlings', icon: '๐Ÿ“ฆ', condition: (v, s) => v.length >= 5 }, + { id: 'first_pet', name: 'Gentle Touch', description: 'Pet your first voidling', icon: 'โœ‹', condition: (_v, s) => s.totalPets >= 1 }, + { id: 'ten_pets', name: 'Frienemy', description: 'Pet voidlings 10 times', icon: '๐Ÿค', condition: (_v, s) => s.totalPets >= 10 }, + { id: 'fifty_pets', name: 'Void Whisperer', description: 'Pet voidlings 50 times', icon: '๐Ÿ”ฎ', condition: (_v, s) => s.totalPets >= 50 }, + { id: 'first_feeding', name: 'Nourisher', description: 'Feed your first voidling', icon: 'โ—•', condition: (_v, s) => s.totalFeedings >= 1 }, + { id: 'collector', name: 'Collector', description: 'Have 5 different voidlings', icon: '๐Ÿ“ฆ', condition: (v, _s) => v.length >= 5 }, { id: 'variety', name: 'Biodiversity', description: 'Have all 4 types of voidlings', icon: '๐ŸŒˆ', condition: (v) => { const types = new Set(v.map(x => x.type)); return types.size >= 4; } }, - { id: 'parent', name: 'Protective Parent', description: 'Have 10 voidlings', icon: '๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ', condition: (v, s) => v.length >= 10 }, + { id: 'parent', name: 'Protective Parent', description: 'Have 10 voidlings', icon: '๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ', condition: (v, _s) => v.length >= 10 }, { id: 'curious', name: 'Curious Mind', description: 'Have a curious voidling', icon: 'โ“', condition: (v) => v.some(x => x.mood === 'curious') }, { id: 'sleepy_boys', name: 'Sleepy Boys', description: 'Have a sleepy voidling', icon: '๐Ÿ˜ด', condition: (v) => v.some(x => x.mood === 'sleepy') }, { id: 'happy_home', name: 'Happy Home', description: 'Have 3 happy voidlings', icon: 'โ˜บ๏ธ', condition: (v) => v.filter(x => x.mood === 'happy').length >= 3 }, @@ -115,6 +183,9 @@ export default function Chamber() { totalVoidlings: 0, sessions: 1, }) + const [timeOfDay, setTimeOfDay] = useState('night') + const [isAutoCycle, setIsAutoCycle] = useState(true) + const [cycleProgress, setCycleProgress] = useState(0) useEffect(() => { const savedVoidlings = localStorage.getItem('voidlings') @@ -170,6 +241,43 @@ export default function Chamber() { }) }, [voidlings, userStats]) + useEffect(() => { + if (!isAutoCycle) return + + const cycleDuration = 60000 + const updateInterval = 500 + + const interval = setInterval(() => { + setCycleProgress(prev => { + const newProgress = prev + (updateInterval / cycleDuration) * TIME_CYCLES.length + if (newProgress >= TIME_CYCLES.length) { + return 0 + } + return newProgress + }) + }, updateInterval) + + return () => clearInterval(interval) + }, [isAutoCycle]) + + useEffect(() => { + const currentIndex = Math.floor(cycleProgress) + if (TIME_CYCLES[currentIndex]) { + setTimeOfDay(TIME_CYCLES[currentIndex].name) + } + }, [cycleProgress]) + + useEffect(() => { + if (timeOfDay === 'night') { + setVoidlings(prev => prev.map(v => { + if (v.mood !== 'sleepy' && Math.random() > 0.7) { + return { ...v, mood: 'sleepy' as const } + } + return v + })) + } + }, [timeOfDay]) + useEffect(() => { const interval = setInterval(() => { setParticles(prev => { @@ -199,6 +307,9 @@ export default function Chamber() { }, []) useEffect(() => { + const currentTimeCycle = TIME_CYCLES.find(t => t.name === timeOfDay) || TIME_CYCLES[0] + const multiplier = currentTimeCycle.voidlingMultiplier + const interval = setInterval(() => { setVoidlings(prev => prev.map(v => { let { x, y, vx, vy } = v @@ -207,11 +318,11 @@ export default function Chamber() { vx *= 0.95 vy *= 0.95 } else if (v.mood === 'happy') { - vx += (Math.random() - 0.5) * 0.2 - vy += (Math.random() - 0.5) * 0.2 + vx += (Math.random() - 0.5) * 0.2 * multiplier + vy += (Math.random() - 0.5) * 0.2 * multiplier } else if (v.mood === 'curious') { - vx += (Math.random() - 0.5) * 0.1 - vy += (Math.random() - 0.5) * 0.1 + vx += (Math.random() - 0.5) * 0.1 * multiplier + vy += (Math.random() - 0.5) * 0.1 * multiplier } if (v.type === 'spark') { @@ -221,8 +332,8 @@ export default function Chamber() { if (Math.abs(vy) > 1) vy = vy > 0 ? 1 : -1 } - x += vx - y += vy + x += vx * multiplier + y += vy * multiplier if (x < 5 || x > 95) { vx *= -1; x = Math.max(5, Math.min(95, x)) } if (y < 10 || y > 90) { vy *= -1; y = Math.max(10, Math.min(90, y)) } @@ -231,7 +342,7 @@ export default function Chamber() { })) }, 100) return () => clearInterval(interval) - }, []) + }, [timeOfDay]) function createStarterVoidlings() { const starters: Voidling[] = [ @@ -336,8 +447,11 @@ export default function Chamber() { } } + const currentTimeCycle = TIME_CYCLES.find(t => t.name === timeOfDay) || TIME_CYCLES[0] + const progressInPhase = cycleProgress % 1 + return ( -
+
{particles.map(p => (
+
+ {TIME_ICONS[timeOfDay]} +
+ {timeOfDay} +
+
+
+
+ + {!isAutoCycle && ( +
+ {TIME_CYCLES.map((t) => ( + + ))} +
+ )} +
-
+
-
-
-
+
+
+
{voidlings.length === 0 && (