Create Account

Already have one? Log in

Shown on your gallery emails, signup forms, and sponsor portal
At least 8 characters
// Show/hide password — mobile typo insurance (no confirm field to catch them) const pwInput = document.getElementById('pwInput'); const pwToggle = document.getElementById('pwToggle'); pwToggle.addEventListener('click', () => { const show = pwInput.type === 'password'; pwInput.type = show ? 'text' : 'password'; pwToggle.textContent = show ? 'Hide' : 'Show'; pwToggle.setAttribute('aria-label', show ? 'Hide password' : 'Show password'); }); document.getElementById('registerForm').addEventListener('submit', async (e) => { e.preventDefault(); const btn = document.getElementById('submitBtn'); const error = document.getElementById('error'); btn.disabled = true; btn.textContent = 'Creating...'; error.style.display = 'none'; try { const form = new FormData(e.target); const res = await fetch('/api/auth/register', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email: form.get('email'), password: form.get('password'), firstName: form.get('firstName'), lastName: form.get('lastName'), companyName: form.get('companyName'), // Where they came from. Server sanitizes and may discard it; // a missing or unreadable stash must never block signup. firstTouch: (function () { try { return JSON.parse(localStorage.getItem('hsFirstTouch') || 'null'); } catch (_) { return null; } })(), }), }); const data = await res.json(); if (!res.ok) throw new Error(data.error); // Two-stage confirmation: verify email first, then admin // approval. The card swaps to a "check your inbox" state. document.getElementById('card').innerHTML = `

Check your email

We sent a verification link to ${form.get('email')}. ${data.needsApproval ? `Click it to confirm your address — then an admin will review your account and you'll get a second email when you're approved.` : `Click it to confirm your address, then log in and you're ready to get started.`}

Didn't get it? Check spam, or log in to resend.

While you're here — how did you find us? (optional)

${['Google search','YouTube / a blog','A photographer friend','Social media','A conference','Other'].map(s => `` ).join('')}
Back to login `; // One-tap attribution — set-once server-side; failure never // disturbs the signup flow. document.getElementById('srcAsk').addEventListener('click', async (ev) => { const b = ev.target.closest('button[data-src]'); if (!b) return; try { fetch('/api/auth/signup-source', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email: form.get('email'), source: b.dataset.src }), }); } catch (_) {} document.getElementById('srcAsk').innerHTML = '

Thanks — good luck out there. 📸

'; }); } catch (err) { error.textContent = err.message; error.style.display = 'block'; btn.disabled = false; btn.textContent = 'Create Account'; } });