'use client'; import { useState, useEffect, useCallback } from 'react'; import { useIsMobile } from './hooks/useIsMobile'; import Image from 'next/image'; import { FloatingHearts } from './components/FloatingHearts'; import { ThemeToggle } from './components/ThemeToggle'; import { SpeechBubble } from './components/SpeechBubble'; import { shakeConfig } from './config/shake'; export default function Home() { const [isShaken, setIsShaken] = useState(false); const [shakeIntensity, setShakeIntensity] = useState(0); const [lastUpdate, setLastUpdate] = useState(0); const [shakeCount, setShakeCount] = useState(0); const [motionPermission, setMotionPermission] = useState('prompt'); const isMobile = useIsMobile(); // Check if device motion is available and handle permissions const requestMotionPermission = async () => { if (typeof window === 'undefined') return; // Check if device motion is available if (!('DeviceMotionEvent' in window)) { setMotionPermission('denied'); return; } // Request permission on iOS devices if ('requestPermission' in DeviceMotionEvent) { try { // @ts-expect-error - TypeScript doesn't know about requestPermission const permission = await DeviceMotionEvent.requestPermission(); setMotionPermission(permission); } catch (err) { console.error('Error requesting motion permission:', err); setMotionPermission('denied'); } } else { // Android or desktop - no permission needed setMotionPermission('granted'); } }; const triggerShake = useCallback((intensity: number) => { // Increment shake counter to trigger new message setShakeCount(count => count + 1); // Start shake animation setIsShaken(true); // Reset shake after configured duration setTimeout(() => { setIsShaken(false); }, shakeConfig.animations.shakeReset); // Trigger hearts with configured duration setShakeIntensity(intensity); setTimeout(() => setShakeIntensity(0), shakeConfig.animations.heartsReset); }, []); useEffect(() => { const handleKeyPress = (event: KeyboardEvent) => { if (event.code === 'Space') { triggerShake(shakeConfig.defaultTriggerIntensity); } }; const handleMotion = (event: DeviceMotionEvent) => { const acceleration = event.accelerationIncludingGravity; if (!acceleration) return; const currentTime = new Date().getTime(); const timeDiff = currentTime - lastUpdate; if (timeDiff > shakeConfig.debounceTime) { setLastUpdate(currentTime); const speed = Math.abs(acceleration.x || 0) + Math.abs(acceleration.y || 0) + Math.abs(acceleration.z || 0); if (speed > shakeConfig.threshold) { triggerShake(speed); } } }; // Only add motion listener if permission is granted if (typeof window !== 'undefined') { if (motionPermission === 'granted' && 'DeviceMotionEvent' in window) { window.addEventListener('devicemotion', handleMotion); } window.addEventListener('keydown', handleKeyPress); } return () => { if (typeof window !== 'undefined') { if (motionPermission === 'granted') { window.removeEventListener('devicemotion', handleMotion); } window.removeEventListener('keydown', handleKeyPress); } }; }, [lastUpdate, motionPermission, triggerShake]); // Initial permission check useEffect(() => { requestMotionPermission(); }, []); const handleClick = () => { triggerShake(shakeConfig.defaultTriggerIntensity); }; return (
Frog

{motionPermission === 'prompt' ? ( ) : motionPermission === 'granted' ? ( `Shake your device${!isMobile ? ', press spacebar,' : ''} or click/tap frog!` ) : ( `${!isMobile ? 'Press spacebar or ' : ''}Click/tap frog!` )}

); }