var recoverEmail = ''; 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 openRecover() { 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 = ''; setTimeout(function() { document.getElementById('recoverEmailInput').focus(); }, 100); } function closeRecover() { document.getElementById('recoverModal').classList.remove('active'); } 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'); var rH2 = document.querySelector('#recoverResult h2'); if (rH2) { rH2.setAttribute('tabindex', '-1'); rH2.focus(); } } 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 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() { fallbackCopy(text, showCopied, showFailed); }); } else { fallbackCopy(text, showCopied, showFailed); } } catch(e) { showFailed(); } } function fallbackCopy(text, onSuccess, onFail) { try { var ta = document.createElement('textarea'); ta.value = text; ta.style.position = 'fixed'; ta.style.opacity = '0'; ta.style.top = '-9999px'; document.body.appendChild(ta); ta.focus(); ta.select(); var success = document.execCommand('copy'); document.body.removeChild(ta); success ? onSuccess() : onFail(); } catch(e) { onFail(); } } 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.'); } } // === Demo Playground === async function generateDemo() { var btn = document.getElementById('demoGenerateBtn'); var status = document.getElementById('demoStatus'); var result = document.getElementById('demoResult'); var errorEl = document.getElementById('demoError'); var html = document.getElementById('demoHtml').value.trim(); if (!html) { errorEl.textContent = 'Please enter some HTML.'; errorEl.style.display = 'block'; result.style.display = 'none'; return; } errorEl.style.display = 'none'; result.style.display = 'none'; btn.disabled = true; status.textContent = 'Generating PDF…'; try { var res = await fetch('/v1/demo/html', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ html: html }) }); if (!res.ok) { var data = await res.json(); errorEl.textContent = data.error || 'Something went wrong.'; errorEl.style.display = 'block'; btn.disabled = false; status.textContent = ''; return; } var blob = await res.blob(); var url = URL.createObjectURL(blob); var dl = document.getElementById('demoDownload'); dl.href = url; result.style.display = 'block'; status.textContent = ''; btn.disabled = false; } catch (err) { errorEl.textContent = 'Network error. Please try again.'; errorEl.style.display = 'block'; btn.disabled = false; status.textContent = ''; } } // === Init === document.addEventListener('DOMContentLoaded', function() { // BUG-068: Open change email modal if navigated via hash if (window.location.hash === '#change-email') { openEmailChange(); } // Demo playground document.getElementById('demoGenerateBtn').addEventListener('click', generateDemo); // Checkout buttons document.getElementById('btn-checkout').addEventListener('click', checkout); var heroCheckout = document.getElementById('btn-checkout-hero'); if (heroCheckout) heroCheckout.addEventListener('click', checkout); // 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(); }); }); // Smooth scroll for hash links document.querySelectorAll('a[href^="#"]').forEach(function(a) { a.addEventListener('click', function(e) { var target = this.getAttribute('href'); if (target === '#') return; e.preventDefault(); var el = document.querySelector(target); 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() { 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'); var ecH2 = document.querySelector('#emailChangeResult h2'); if (ecH2) { ecH2.setAttribute('tabindex', '-1'); ecH2.focus(); } } catch (err) { errEl.textContent = 'Network error. Please try again.'; errEl.style.display = 'block'; btn.disabled = false; } } // Email change event listeners 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 = ['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(); 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(); } } } }); })();