diff --git a/public/assets/common.js b/public/assets/common.js index 0f0e8e1..64d4213 100644 --- a/public/assets/common.js +++ b/public/assets/common.js @@ -143,5 +143,29 @@ window.IAP = (function () { el.hidden = false; } catch (e) {} } - return { getConfig, fmtPol, fmtUsd, status, renderNav, refreshNavWallet, describeEvent, feedRow, adSlot, reportAd, $ }; + // ── sign-up code request with the invisible guard fields (form age + honeypot) + // and the icon check the server asks for only after an IP trips a limit ── + const FORM_TS = Date.now(); + function iconCheck(host, ch, note) { + return new Promise(resolve => { + host.hidden = false; + host.innerHTML = '
' + (note ? esc(note) + ' ' : '') + 'Tap the ' + esc(ch.prompt) + '.
' + + '
' + ch.options.map(o => '').join('') + '
'; + host.querySelectorAll('.ic-btn').forEach(b => b.addEventListener('click', () => { host.innerHTML = ''; host.hidden = true; resolve(b.textContent); }, { once: true })); + }); + } + async function requestCode(email, opts) { + const o = opts || {}; + let pick = null; + for (let i = 0; i < 4; i++) { + const r = await (await fetch('/api/auth/email/start', { method: 'POST', headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ email, fts: FORM_TS, website: (o.honeypot && o.honeypot.value) || '', pick }) })).json(); + if (r.challenge && o.host) { pick = await iconCheck(o.host, r.challenge, r.error); continue; } + if (r.error) throw new Error(r.error); + return r; + } + throw new Error('Could not verify. Refresh the page and try again.'); + } + const esc = s => String(s == null ? '' : s).replace(/[&<>"]/g, c => ({ '&': '&', '<': '<', '>': '>', '"': '"' }[c])); + return { getConfig, fmtPol, fmtUsd, status, renderNav, refreshNavWallet, describeEvent, feedRow, adSlot, reportAd, requestCode, $ }; })(); diff --git a/public/assets/join.js b/public/assets/join.js index 9398bfe..e89d276 100644 --- a/public/assets/join.js +++ b/public/assets/join.js @@ -43,8 +43,9 @@ function busy(btn, fn) { return async () => { if (btn.disabled) return; btn.disabled = true; err(''); try { await fn(); } catch (e) { err(e.message || 'Something went wrong.'); } finally { btn.disabled = false; } }; } + const codeOpts = () => ({ honeypot: $('jnWebsite'), host: $('jnCheck') }); const send = busy($('jnSend'), async () => { - const r = await api('/api/auth/email/start', { email: $('jnEmail').value }); + const r = await IAP.requestCode($('jnEmail').value, codeOpts()); $('jnCodeRow').hidden = false; $('jnVerify').hidden = false; $('jnSend').hidden = true; $('jnResend').hidden = false; if (r.devCode) $('jnCode').value = r.devCode; // the last thing they see before the account is created: who they are joining under @@ -54,7 +55,7 @@ }); $('jnSend').addEventListener('click', send); $('jnResend').addEventListener('click', busy($('jnResend'), async () => { - const r = await api('/api/auth/email/start', { email: $('jnEmail').value }); + const r = await IAP.requestCode($('jnEmail').value, codeOpts()); if (r.devCode) $('jnCode').value = r.devCode; IAP.status('Fresh code sent.', 'ok'); })); diff --git a/public/assets/my.js b/public/assets/my.js index 11c1ff4..70dcfab 100644 --- a/public/assets/my.js +++ b/public/assets/my.js @@ -1610,8 +1610,9 @@ if (!cfg.emailAuth) return; $('passCards').hidden = true; $('magicCard').hidden = false; + const codeOpts = () => ({ honeypot: $('mcWebsite'), host: $('mcCheck') }); const start = busy($('mcSendBtn'), async () => { - const r = await api('/api/auth/email/start', { email: $('mcEmail').value }); + const r = await IAP.requestCode($('mcEmail').value, codeOpts()); $('mcCodeRow').hidden = false; $('mcVerifyBtn').hidden = false; $('mcSendBtn').hidden = true; @@ -1622,7 +1623,7 @@ }); $('mcSendBtn').addEventListener('click', start); $('mcResend').addEventListener('click', busy($('mcResend'), async () => { - const r = await api('/api/auth/email/start', { email: $('mcEmail').value }); + const r = await IAP.requestCode($('mcEmail').value, codeOpts()); if (r.devCode) $('mcCode').value = r.devCode; IAP.status('Fresh code sent.', 'ok'); })); diff --git a/public/assets/site.css b/public/assets/site.css index 0d529a7..a49ccc0 100644 --- a/public/assets/site.css +++ b/public/assets/site.css @@ -628,6 +628,10 @@ img{max-width:100%} /* ── promo tools (2026-09-09) ── */ .promo-strip{display:flex;justify-content:space-between;align-items:center;gap:14px;flex-wrap:wrap;padding:16px 20px} .angle-list{display:flex;flex-direction:column;gap:8px} +.hp-field{position:absolute;left:-9999px;top:auto;width:1px;height:1px;opacity:0;overflow:hidden} +.icon-check{display:flex;gap:8px;flex-wrap:wrap;margin:0 0 12px} +.icon-check .ic-btn{font-size:26px;line-height:1;padding:10px 14px;border-radius:12px;border:1px solid var(--line-strong);background:var(--panel);cursor:pointer} +.icon-check .ic-btn:hover{border-color:var(--mint)} .angle-row.pb-acc>summary{padding:12px 14px;font-size:14.5px} .angle-row .angle-body{display:flex;justify-content:space-between;align-items:center;gap:14px;flex-wrap:wrap;padding:0 14px 14px} .angle-row .angle-txt{flex:1 1 320px;min-width:0} diff --git a/public/join.html b/public/join.html index 2222273..1a4f070 100644 --- a/public/join.html +++ b/public/join.html @@ -111,6 +111,8 @@

Join free

Type your email and we send a 6-digit code. No password, no wallet needed today.

+ +