const video = document.getElementById('videoFeed'); const empIdInput = document.getElementById('empId'); const empNameInput = document.getElementById('empName'); const btnRegister = document.getElementById('btnRegister'); const statusText = document.getElementById('statusText'); const progressCircle = document.getElementById('progressCircle'); const spoofToast = document.getElementById('spoofToast'); const spoofToastMessage = document.getElementById('spoofToastMessage'); let capturedFrames = []; let isCapturing = false; const REQUIRED_FRAMES = 5; let toastDismissTimer = null; // Start Camera navigator.mediaDevices.getUserMedia({ video: { facingMode: 'user', width: 640, height: 480 } }) .then(stream => { video.srcObject = stream; }) .catch(err => console.error(err)); // Monitor inputs to enable capture [empIdInput, empNameInput].forEach(input => { input.addEventListener('input', checkInputs); }); function checkInputs() { const valid = empIdInput.value.trim().length > 0 && empNameInput.value.trim().length > 0; if (valid && !isCapturing && capturedFrames.length === 0) { btnRegister.disabled = false; btnRegister.textContent = "START CAPTURE"; btnRegister.onclick = startCaptureProcess; } else if (capturedFrames.length === REQUIRED_FRAMES) { btnRegister.disabled = false; btnRegister.textContent = "Register"; btnRegister.onclick = submitRegistration; } else { btnRegister.disabled = true; } } function startCaptureProcess() { isCapturing = true; btnRegister.disabled = true; capturedFrames = []; statusText.textContent = "Keep face steady..."; let count = 0; const interval = setInterval(() => { if (count >= REQUIRED_FRAMES) { clearInterval(interval); finishCapture(); return; } const canvas = document.createElement('canvas'); canvas.width = video.videoWidth; canvas.height = video.videoHeight; canvas.getContext('2d').drawImage(video, 0, 0); capturedFrames.push(canvas.toDataURL('image/jpeg', 0.8)); count++; updateProgress(count / REQUIRED_FRAMES); statusText.textContent = `Scanning... ${Math.round((count/REQUIRED_FRAMES)*100)}%`; }, 600); } function updateProgress(percent) { const offset = 113 - (113 * percent); progressCircle.style.strokeDashoffset = offset; } function finishCapture() { isCapturing = false; statusText.textContent = "Face captured ✓"; statusText.style.color = "var(--color-primary)"; checkInputs(); // Re-enable button for submit } async function submitRegistration() { btnRegister.disabled = true; btnRegister.textContent = "REGISTERING..."; const payload = { employee_id: empIdInput.value.trim(), name: empNameInput.value.trim(), frames: capturedFrames }; try { const res = await fetch('/api/register', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) }); const data = await res.json(); if (data.status === 'registered') { document.getElementById('registerForm').style.display = 'none'; document.getElementById('successState').style.display = 'block'; document.getElementById('successChip').textContent = `${payload.name} — ${payload.employee_id}`; } else if (data.status === 'spoof') { showSpoofToast(data); btnRegister.disabled = false; btnRegister.textContent = "Register"; } else { // Duplicate user or other error → snackbar var msg = data.message || "Unknown error"; if (msg.indexOf("already registered") !== -1 || msg.indexOf("already registered to") !== -1) { if (typeof showSnackbar === 'function') showSnackbar("Duplicate registration — this face is already registered.", 'error'); } else if (msg.indexOf("No face detected") !== -1 || msg.indexOf("no face") !== -1) { if (typeof showSnackbar === 'function') showSnackbar("Clear photo — ensure your face is visible and well lit.", 'info'); } else { if (typeof showSnackbar === 'function') showSnackbar(msg, 'error'); } btnRegister.disabled = false; btnRegister.textContent = "Register"; } } catch (e) { if (typeof showSnackbar === 'function') showSnackbar("Network error. Please try again.", 'error'); btnRegister.disabled = false; btnRegister.textContent = "Register"; } } function showSpoofToast(data) { const msg = data.reason || data.message || "Use a live face, not a photo or screen."; spoofToastMessage.textContent = msg; spoofToast.classList.add('show'); if (toastDismissTimer) clearTimeout(toastDismissTimer); toastDismissTimer = setTimeout(() => { spoofToast.classList.remove('show'); toastDismissTimer = null; }, 4500); }