import React, { useState, useEffect, useRef, useCallback } from 'react'; const COLLECT_MS = 2000; const CENTER_MS = 3000; // centre point gets extra time (bias reference) function CalibrationOverlay({ calibration, videoManager }) { const [progress, setProgress] = useState(0); const timerRef = useRef(null); const startRef = useRef(null); const overlayRef = useRef(null); const enterFullscreen = useCallback(() => { const el = overlayRef.current; if (!el) return; const req = el.requestFullscreen || el.webkitRequestFullscreen || el.msRequestFullscreen; if (req) req.call(el).catch(() => {}); }, []); const exitFullscreen = useCallback(() => { if (document.fullscreenElement || document.webkitFullscreenElement) { const exit = document.exitFullscreen || document.webkitExitFullscreen || document.msExitFullscreen; if (exit) exit.call(document).catch(() => {}); } }, []); useEffect(() => { if (calibration && calibration.active && !calibration.done) { const t = setTimeout(enterFullscreen, 100); return () => clearTimeout(t); } }, [calibration?.active]); useEffect(() => { if (!calibration || !calibration.active) exitFullscreen(); }, [calibration?.active]); useEffect(() => { if (!calibration || !calibration.collecting || calibration.done) { setProgress(0); if (timerRef.current) cancelAnimationFrame(timerRef.current); return; } startRef.current = performance.now(); const duration = calibration.index === 0 ? CENTER_MS : COLLECT_MS; const tick = () => { const pct = Math.min((performance.now() - startRef.current) / duration, 1); setProgress(pct); if (pct >= 1) { if (videoManager) videoManager.nextCalibrationPoint(); startRef.current = performance.now(); setProgress(0); } timerRef.current = requestAnimationFrame(tick); }; timerRef.current = requestAnimationFrame(tick); return () => { if (timerRef.current) cancelAnimationFrame(timerRef.current); }; }, [calibration?.index, calibration?.collecting, calibration?.done]); const handleCancel = () => { if (videoManager) videoManager.cancelCalibration(); exitFullscreen(); }; if (!calibration || !calibration.active) return null; if (calibration.done) { return (

{calibration.success ? 'Calibration Complete' : 'Calibration Failed'}

{calibration.success ? 'Gaze tracking is now active.' : 'Not enough samples collected. Try again.'}

); } const [tx, ty] = calibration.target || [0.5, 0.5]; return (
Look at the dot ({calibration.index + 1}/{calibration.numPoints})
{calibration.index === 0 ? 'Look at the center dot - this sets your baseline' : 'Hold your gaze steady on the target'}
); } const overlayStyle = { position: 'fixed', top: 0, left: 0, width: '100vw', height: '100vh', background: 'rgba(0, 0, 0, 0.92)', zIndex: 10000, display: 'flex', alignItems: 'center', justifyContent: 'center', }; const messageBoxStyle = { textAlign: 'center', padding: '30px 40px', background: 'rgba(30, 30, 50, 0.9)', borderRadius: '16px', border: '1px solid rgba(255,255,255,0.1)', }; export default CalibrationOverlay;