var signupEmail = ''; var recoverEmail = ''; function showState(state) { ['signupInitial', 'signupLoading', 'signupVerify', 'signupResult'].forEach(function(id) { var el = document.getElementById(id); if (el) el.classList.remove('active'); }); document.getElementById(state).classList.add('active'); } function showRecoverState(state) { ['recoverInitial', 'recoverLoading', 'recoverVerify', 'recoverResult'].forEach(function(id) { var el = document.getElementById(id); if (el) el.classList.remove('active'); }); document.getElementById(state).classList.add('active'); } function openSignup() { document.getElementById('signupModal').classList.add('active'); showState('signupInitial'); document.getElementById('signupError').style.display = 'none'; document.getElementById('verifyError').style.display = 'none'; document.getElementById('signupEmail').value = ''; document.getElementById('verifyCode').value = ''; signupEmail = ''; } function closeSignup() { document.getElementById('signupModal').classList.remove('active'); } function openRecover() { closeSignup(); document.getElementById('recoverModal').classList.add('active'); showRecoverState('recoverInitial'); var errEl = document.getElementById('recoverError'); if (errEl) errEl.style.display = 'none'; var verifyErrEl = document.getElementById('recoverVerifyError'); if (verifyErrEl) verifyErrEl.style.display = 'none'; document.getElementById('recoverEmailInput').value = ''; document.getElementById('recoverCode').value = ''; recoverEmail = ''; } function closeRecover() { document.getElementById('recoverModal').classList.remove('active'); } async function submitSignup() { var errEl = document.getElementById('signupError'); var btn = document.getElementById('signupBtn'); var emailInput = document.getElementById('signupEmail'); var email = emailInput.value.trim(); if (!email || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) { errEl.textContent = 'Please enter a valid email address.'; errEl.style.display = 'block'; return; } errEl.style.display = 'none'; btn.disabled = true; showState('signupLoading'); try { var res = await fetch('/v1/signup/free', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email: email }) }); var data = await res.json(); if (!res.ok) { showState('signupInitial'); errEl.textContent = data.error || 'Something went wrong. Please try again.'; errEl.style.display = 'block'; btn.disabled = false; return; } signupEmail = email; document.getElementById('verifyEmailDisplay').textContent = email; showState('signupVerify'); document.getElementById('verifyCode').focus(); btn.disabled = false; } catch (err) { showState('signupInitial'); errEl.textContent = 'Network error. Please try again.'; errEl.style.display = 'block'; btn.disabled = false; } } async function submitVerify() { var errEl = document.getElementById('verifyError'); var btn = document.getElementById('verifyBtn'); var codeInput = document.getElementById('verifyCode'); var code = codeInput.value.trim(); if (!code || !/^\d{6}$/.test(code)) { errEl.textContent = 'Please enter a 6-digit code.'; errEl.style.display = 'block'; return; } errEl.style.display = 'none'; btn.disabled = true; try { var res = await fetch('/v1/signup/verify', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email: signupEmail, code: code }) }); var data = await res.json(); if (!res.ok) { errEl.textContent = data.error || 'Verification failed.'; errEl.style.display = 'block'; btn.disabled = false; return; } document.getElementById('apiKeyText').textContent = data.apiKey; showState('signupResult'); } catch (err) { errEl.textContent = 'Network error. Please try again.'; errEl.style.display = 'block'; btn.disabled = false; } } async function submitRecover() { var errEl = document.getElementById('recoverError'); var btn = document.getElementById('recoverBtn'); var emailInput = document.getElementById('recoverEmailInput'); var email = emailInput.value.trim(); if (!email || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) { errEl.textContent = 'Please enter a valid email address.'; errEl.style.display = 'block'; return; } errEl.style.display = 'none'; btn.disabled = true; showRecoverState('recoverLoading'); try { var res = await fetch('/v1/recover', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email: email }) }); var data = await res.json(); if (!res.ok) { showRecoverState('recoverInitial'); errEl.textContent = data.error || 'Something went wrong.'; errEl.style.display = 'block'; btn.disabled = false; return; } recoverEmail = email; document.getElementById('recoverEmailDisplay').textContent = email; showRecoverState('recoverVerify'); document.getElementById('recoverCode').focus(); btn.disabled = false; } catch (err) { showRecoverState('recoverInitial'); errEl.textContent = 'Network error. Please try again.'; errEl.style.display = 'block'; btn.disabled = false; } } async function submitRecoverVerify() { var errEl = document.getElementById('recoverVerifyError'); var btn = document.getElementById('recoverVerifyBtn'); var codeInput = document.getElementById('recoverCode'); var code = codeInput.value.trim(); if (!code || !/^\d{6}$/.test(code)) { errEl.textContent = 'Please enter a 6-digit code.'; errEl.style.display = 'block'; return; } errEl.style.display = 'none'; btn.disabled = true; try { var res = await fetch('/v1/recover/verify', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email: recoverEmail, code: code }) }); var data = await res.json(); if (!res.ok) { errEl.textContent = data.error || 'Verification failed.'; errEl.style.display = 'block'; btn.disabled = false; return; } if (data.apiKey) { document.getElementById('recoveredKeyText').textContent = data.apiKey; showRecoverState('recoverResult'); } else { errEl.textContent = data.message || 'No key found for this email.'; errEl.style.display = 'block'; btn.disabled = false; } } catch (err) { errEl.textContent = 'Network error. Please try again.'; errEl.style.display = 'block'; btn.disabled = false; } } function copyKey() { var key = document.getElementById('apiKeyText').textContent; var btn = document.getElementById('copyBtn'); doCopy(key, btn); } function copyRecoveredKey() { var key = document.getElementById('recoveredKeyText').textContent; var btn = document.getElementById('copyRecoveredBtn'); doCopy(key, btn); } function doCopy(text, btn) { function showCopied() { btn.textContent = '\u2713 Copied!'; setTimeout(function() { btn.textContent = 'Copy'; }, 2000); } function showFailed() { btn.textContent = 'Failed'; setTimeout(function() { btn.textContent = 'Copy'; }, 2000); } try { if (navigator.clipboard && window.isSecureContext) { navigator.clipboard.writeText(text).then(showCopied).catch(function() { // Fallback to execCommand try { var ta = document.createElement('textarea'); ta.value = text; ta.style.position = 'fixed'; ta.style.opacity = '0'; ta.style.top = '-9999px'; ta.style.left = '-9999px'; document.body.appendChild(ta); ta.focus(); ta.select(); var success = document.execCommand('copy'); document.body.removeChild(ta); if (success) { showCopied(); } else { showFailed(); } } catch (err) { showFailed(); } }); } else { // Direct fallback for non-secure contexts var ta = document.createElement('textarea'); ta.value = text; ta.style.position = 'fixed'; ta.style.opacity = '0'; ta.style.top = '-9999px'; ta.style.left = '-9999px'; document.body.appendChild(ta); ta.focus(); ta.select(); var success = document.execCommand('copy'); document.body.removeChild(ta); if (success) { showCopied(); } else { showFailed(); } } } catch(e) { showFailed(); } } async function checkout() { try { var res = await fetch('/v1/billing/checkout', { method: 'POST' }); var data = await res.json(); if (data.url) window.location.href = data.url; else alert('Checkout is not available yet. Please try again later.'); } catch (err) { alert('Something went wrong. Please try again.'); } } document.addEventListener('DOMContentLoaded', function() { document.getElementById('btn-signup').addEventListener('click', openSignup); document.getElementById('btn-signup-2').addEventListener('click', openSignup); document.getElementById('btn-checkout').addEventListener('click', checkout); document.getElementById('btn-close-signup').addEventListener('click', closeSignup); document.getElementById('signupBtn').addEventListener('click', submitSignup); document.getElementById('verifyBtn').addEventListener('click', submitVerify); document.getElementById('copyBtn').addEventListener('click', copyKey); // Recovery modal document.getElementById('btn-close-recover').addEventListener('click', closeRecover); document.getElementById('recoverBtn').addEventListener('click', submitRecover); document.getElementById('recoverVerifyBtn').addEventListener('click', submitRecoverVerify); document.getElementById('copyRecoveredBtn').addEventListener('click', copyRecoveredKey); document.getElementById('recoverModal').addEventListener('click', function(e) { if (e.target === this) closeRecover(); }); // Open recovery from links document.querySelectorAll('.open-recover').forEach(function(el) { el.addEventListener('click', function(e) { e.preventDefault(); openRecover(); }); }); document.getElementById('signupModal').addEventListener('click', function(e) { if (e.target === this) closeSignup(); }); document.querySelectorAll('a[href^="#"]').forEach(function(a) { a.addEventListener('click', function(e) { e.preventDefault(); var el = document.querySelector(this.getAttribute('href')); if (el) el.scrollIntoView({ behavior: 'smooth' }); }); }); }); // --- Email Change --- var emailChangeApiKey = ''; var emailChangeNewEmail = ''; function showEmailChangeState(state) { ['emailChangeInitial', 'emailChangeLoading', 'emailChangeVerify', 'emailChangeResult'].forEach(function(id) { var el = document.getElementById(id); if (el) el.classList.remove('active'); }); document.getElementById(state).classList.add('active'); } function openEmailChange() { closeSignup(); closeRecover(); document.getElementById('emailChangeModal').classList.add('active'); showEmailChangeState('emailChangeInitial'); var errEl = document.getElementById('emailChangeError'); if (errEl) errEl.style.display = 'none'; var verifyErrEl = document.getElementById('emailChangeVerifyError'); if (verifyErrEl) verifyErrEl.style.display = 'none'; document.getElementById('emailChangeApiKey').value = ''; document.getElementById('emailChangeNewEmail').value = ''; document.getElementById('emailChangeCode').value = ''; emailChangeApiKey = ''; emailChangeNewEmail = ''; } function closeEmailChange() { document.getElementById('emailChangeModal').classList.remove('active'); } async function submitEmailChange() { var errEl = document.getElementById('emailChangeError'); var btn = document.getElementById('emailChangeBtn'); var apiKey = document.getElementById('emailChangeApiKey').value.trim(); var newEmail = document.getElementById('emailChangeNewEmail').value.trim(); if (!apiKey) { errEl.textContent = 'Please enter your API key.'; errEl.style.display = 'block'; return; } if (!newEmail || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(newEmail)) { errEl.textContent = 'Please enter a valid email address.'; errEl.style.display = 'block'; return; } errEl.style.display = 'none'; btn.disabled = true; showEmailChangeState('emailChangeLoading'); try { var res = await fetch('/v1/email-change', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ apiKey: apiKey, newEmail: newEmail }) }); var data = await res.json(); if (!res.ok) { showEmailChangeState('emailChangeInitial'); errEl.textContent = data.error || 'Something went wrong.'; errEl.style.display = 'block'; btn.disabled = false; return; } emailChangeApiKey = apiKey; emailChangeNewEmail = newEmail; document.getElementById('emailChangeEmailDisplay').textContent = newEmail; showEmailChangeState('emailChangeVerify'); document.getElementById('emailChangeCode').focus(); btn.disabled = false; } catch (err) { showEmailChangeState('emailChangeInitial'); errEl.textContent = 'Network error. Please try again.'; errEl.style.display = 'block'; btn.disabled = false; } } async function submitEmailChangeVerify() { var errEl = document.getElementById('emailChangeVerifyError'); var btn = document.getElementById('emailChangeVerifyBtn'); var code = document.getElementById('emailChangeCode').value.trim(); if (!code || !/^\d{6}$/.test(code)) { errEl.textContent = 'Please enter a 6-digit code.'; errEl.style.display = 'block'; return; } errEl.style.display = 'none'; btn.disabled = true; try { var res = await fetch('/v1/email-change/verify', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ apiKey: emailChangeApiKey, newEmail: emailChangeNewEmail, code: code }) }); var data = await res.json(); if (!res.ok) { errEl.textContent = data.error || 'Verification failed.'; errEl.style.display = 'block'; btn.disabled = false; return; } document.getElementById('emailChangeNewDisplay').textContent = data.newEmail || emailChangeNewEmail; showEmailChangeState('emailChangeResult'); } catch (err) { errEl.textContent = 'Network error. Please try again.'; errEl.style.display = 'block'; btn.disabled = false; } } // Add event listeners for email change (append to DOMContentLoaded) document.addEventListener('DOMContentLoaded', function() { var closeBtn = document.getElementById('btn-close-email-change'); if (closeBtn) closeBtn.addEventListener('click', closeEmailChange); var changeBtn = document.getElementById('emailChangeBtn'); if (changeBtn) changeBtn.addEventListener('click', submitEmailChange); var verifyBtn = document.getElementById('emailChangeVerifyBtn'); if (verifyBtn) verifyBtn.addEventListener('click', submitEmailChangeVerify); var modal = document.getElementById('emailChangeModal'); if (modal) modal.addEventListener('click', function(e) { if (e.target === this) closeEmailChange(); }); document.querySelectorAll('.open-email-change').forEach(function(el) { el.addEventListener('click', function(e) { e.preventDefault(); openEmailChange(); }); }); }); // === Accessibility: Escape key closes modals, focus trapping === (function() { function getActiveModal() { var modals = ['signupModal', 'recoverModal', 'emailChangeModal']; for (var i = 0; i < modals.length; i++) { var m = document.getElementById(modals[i]); if (m && m.classList.contains('active')) return m; } return null; } function closeActiveModal() { var m = getActiveModal(); if (!m) return; m.classList.remove('active'); } document.addEventListener('keydown', function(e) { if (e.key === 'Escape') closeActiveModal(); // Focus trap inside active modal if (e.key === 'Tab') { var modal = getActiveModal(); if (!modal) return; var focusable = modal.querySelectorAll('button:not([disabled]), input:not([disabled]), a[href], [tabindex]:not([tabindex="-1"])'); if (focusable.length === 0) return; var first = focusable[0], last = focusable[focusable.length - 1]; if (e.shiftKey) { if (document.activeElement === first) { e.preventDefault(); last.focus(); } } else { if (document.activeElement === last) { e.preventDefault(); first.focus(); } } } }); })();