'use client'; import { useEffect, useState } from 'react'; import './typing-words.css'; const WORDS = ['plan', 'idea', 'problem', 'goals'] as const; const TYPING_MS = 90; const DELETING_MS = 55; const PAUSE_AFTER_TYPED_MS = 2000; const PAUSE_AFTER_DELETED_MS = 400; type Phase = 'typing' | 'pausing' | 'deleting' | 'waiting'; export default function TypingWords() { const [wordIndex, setWordIndex] = useState(0); const [charIndex, setCharIndex] = useState(0); const [phase, setPhase] = useState('typing'); const [prefersReducedMotion, setPrefersReducedMotion] = useState(false); useEffect(() => { const media = window.matchMedia('(prefers-reduced-motion: reduce)'); setPrefersReducedMotion(media.matches); const onChange = () => setPrefersReducedMotion(media.matches); media.addEventListener('change', onChange); return () => media.removeEventListener('change', onChange); }, []); useEffect(() => { if (prefersReducedMotion) return; const currentWord = WORDS[wordIndex]; let timeoutId: ReturnType; if (phase === 'typing') { if (charIndex < currentWord.length) { timeoutId = setTimeout(() => setCharIndex((i) => i + 1), TYPING_MS); } else { timeoutId = setTimeout(() => setPhase('pausing'), PAUSE_AFTER_TYPED_MS); } } else if (phase === 'pausing') { timeoutId = setTimeout(() => setPhase('deleting'), 0); } else if (phase === 'deleting') { if (charIndex > 0) { timeoutId = setTimeout(() => setCharIndex((i) => i - 1), DELETING_MS); } else { timeoutId = setTimeout(() => { setWordIndex((i) => (i + 1) % WORDS.length); setPhase('waiting'); }, PAUSE_AFTER_DELETED_MS); } } else if (phase === 'waiting') { timeoutId = setTimeout(() => setPhase('typing'), 0); } return () => clearTimeout(timeoutId); }, [charIndex, phase, wordIndex, prefersReducedMotion]); useEffect(() => { if (!prefersReducedMotion) return; const intervalId = setInterval(() => { setWordIndex((i) => (i + 1) % WORDS.length); }, 3000); return () => clearInterval(intervalId); }, [prefersReducedMotion]); const displayedWord = prefersReducedMotion ? WORDS[wordIndex] : WORDS[wordIndex].slice(0, charIndex); return ( {displayedWord} ); }