final / src /components /CalibrationOverlay.jsx
k22056537
feat: UI nav, onboarding, L2CS weights path + torch.load; trim dev files
a75bb5a
import React, { useState, useEffect, useRef, useCallback } from 'react';
const COLLECT_MS = 2000;
const CENTER_MS = 3000;
const VERIFY_MS = 3000;
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.verifying ? VERIFY_MS : (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) {
const success = calibration.success;
return (
<div ref={overlayRef} className="cal-overlay">
<div className={`cal-done-card ${success ? 'cal-done-success' : 'cal-done-fail'}`}>
<div className="cal-done-eyebrow">
{success ? 'Complete' : 'Failed'}
</div>
<h2 className="cal-done-title">
{success ? 'Calibration Complete' : 'Calibration Failed'}
</h2>
<p className="cal-done-subtitle">
{success
? 'Gaze tracking is now active.'
: 'Not enough samples collected. Try again.'}
</p>
</div>
</div>
);
}
const [tx, ty] = calibration.target || [0.5, 0.5];
const isVerifying = calibration.verifying;
const accent = isVerifying ? '#007BFF' : '#28a745';
const glow = isVerifying ? 'rgba(0, 123, 255, 0.6)' : 'rgba(40, 167, 69, 0.6)';
return (
<div ref={overlayRef} className="cal-overlay">
<div className="cal-header">
{isVerifying ? (
<>
<span className="cal-eyebrow cal-eyebrow-verify">Verification</span>
<p className="cal-instruction">
Look at the dot to confirm calibration accuracy
</p>
</>
) : (
<>
<span className="cal-eyebrow cal-eyebrow-collect">
Point {calibration.index + 1} of {calibration.numPoints}
</span>
<p className="cal-instruction">
{calibration.index === 0
? 'Look at the center dot \u2014 this sets your baseline'
: 'Hold your gaze steady on the target'}
</p>
</>
)}
</div>
<div
className="cal-target"
style={{ left: `${tx * 100}%`, top: `${ty * 100}%` }}
>
<svg width="60" height="60" className="cal-ring">
<circle cx="30" cy="30" r="24" fill="none" stroke="rgba(255,255,255,0.12)" strokeWidth="3" />
<circle
cx="30" cy="30" r="24" fill="none" stroke={accent} strokeWidth="3"
strokeDasharray={`${progress * 150.8} 150.8`} strokeLinecap="round"
transform="rotate(-90, 30, 30)"
/>
</svg>
<div
className="cal-dot"
style={{
background: `radial-gradient(circle, #fff 30%, ${accent} 100%)`,
boxShadow: `0 0 24px ${glow}`,
}}
/>
</div>
<button onClick={handleCancel} className="cal-cancel">
Cancel Calibration
</button>
</div>
);
}
export default CalibrationOverlay;