/** * 3-2-1 카운트다운 오버레이 (PRD §3.1 공통 규칙) * * 주의: onDone은 ref로 보관한다. 부모(TestRunner)가 usePlayTime에 의해 30ms마다 * 재렌더링되면 인라인 onDone 참조가 매번 바뀌는데, effect 의존성에 넣으면 * setTimeout이 계속 초기화되어 카운트다운이 멈춘다. */ import { useEffect, useRef, useState } from 'react'; import styled, { keyframes } from 'styled-components'; const fade = keyframes` from { transform: scale(1.6); opacity: 0.4; } to { transform: scale(1); opacity: 1; } `; const Overlay = styled.div` position: fixed; top: 0; left: 0; right: 0; height: var(--test-vh, 100dvh); z-index: 200; display: flex; align-items: center; justify-content: center; background: #1f2430; color: #fff; `; const Num = styled.div` font-size: 96px; font-weight: 800; animation: ${fade} 0.8s ease-out; `; export default function CountdownOverlay({ onDone }: { onDone: () => void }) { const [n, setN] = useState(3); const onDoneRef = useRef(onDone); onDoneRef.current = onDone; useEffect(() => { if (n === 0) { const t = setTimeout(() => onDoneRef.current(), 500); return () => clearTimeout(t); } const t = setTimeout(() => setN(n - 1), 800); return () => clearTimeout(t); }, [n]); return ( {n > 0 ? n : '시작!'} ); }