import React, { useEffect, useRef, useState } from 'react' /** * TypewriterRotator * * Cycles through a list of phrases with a typing-and-deleting effect. * Designed for the soryu landing hero — sits inside a centred container, * so the caller controls position and the component just renders text. * * Honours `prefers-reduced-motion` by rendering the first phrase only, * statically. */ interface TypewriterRotatorProps { phrases: string[] /** ms per character while typing */ typeMs?: number /** ms per character while deleting */ deleteMs?: number /** ms to hold a fully-typed phrase before deleting */ holdMs?: number /** ms to wait after deletion before the next phrase types in */ gapMs?: number className?: string } export function TypewriterRotator({ phrases, typeMs = 55, deleteMs = 28, holdMs = 2200, gapMs = 320, className, }: TypewriterRotatorProps) { const [text, setText] = useState('') const [index, setIndex] = useState(0) const [phase, setPhase] = useState<'typing' | 'holding' | 'deleting' | 'gap'>('typing') const reduceMotion = useRef( typeof window !== 'undefined' && window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches, ) useEffect(() => { if (reduceMotion.current) { setText(phrases[0] ?? '') return } const current = phrases[index % phrases.length] ?? '' let timer: number if (phase === 'typing') { if (text.length < current.length) { timer = window.setTimeout(() => setText(current.slice(0, text.length + 1)), typeMs) } else { timer = window.setTimeout(() => setPhase('holding'), 0) } } else if (phase === 'holding') { // Skip the hold-then-delete cycle entirely if there's only one phrase if (phrases.length <= 1) return timer = window.setTimeout(() => setPhase('deleting'), holdMs) } else if (phase === 'deleting') { if (text.length > 0) { timer = window.setTimeout(() => setText(current.slice(0, text.length - 1)), deleteMs) } else { timer = window.setTimeout(() => setPhase('gap'), 0) } } else if (phase === 'gap') { timer = window.setTimeout(() => { setIndex((i) => (i + 1) % phrases.length) setPhase('typing') }, gapMs) } return () => window.clearTimeout(timer) }, [text, phase, index, phrases, typeMs, deleteMs, holdMs, gapMs]) return ( {text} ) }