// RM Circle: required member profile gate (Marty, 2026-09-16). // // The member area only opens once a position has a username and a VERIFIED email. // It fires the moment ownership is proved (wallet personal_sign, or the Telegram // Mini App bridge) and cannot be dismissed, because the whole point is that a // leader can reach every member. The public /my/ page is untouched: anyone // can still read the chain data there, and nobody can write a profile from it. // // Usage: await window.RMCProfile.require(); // resolves once the profile is complete (function () { 'use strict'; var back = null, resolveDone = null, state = null; var GOLD = '#d4af37', TEAL = '#4ed6cb'; function el(tag, css, html) { var e = document.createElement(tag); if (css) e.style.cssText = css; if (html != null) e.innerHTML = html; return e; } function esc(s) { return String(s == null ? '' : s).replace(/[&<>"]/g, function (c) { return ({ '&': '&', '<': '<', '>': '>', '"': '"' })[c]; }); } async function api(path, body) { var r = await fetch(path, body ? { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) } : {}); var j = null; try { j = await r.json(); } catch (e) { j = {}; } if (!r.ok || j.error) throw new Error(j.error || 'Something went wrong. Try again.'); return j; } function shell() { back = el('div', 'position:fixed;inset:0;z-index:2147483100;display:flex;align-items:center;justify-content:center;' + 'padding:20px;background:rgba(3,7,14,.88);backdrop-filter:blur(6px);-webkit-backdrop-filter:blur(6px);overflow:auto;'); back.setAttribute('role', 'dialog'); back.setAttribute('aria-label', 'Finish setting up your member profile'); var card = el('div', 'position:relative;width:100%;max-width:440px;max-height:94vh;overflow:auto;border-radius:20px;' + 'background:#0a1119;border:1px solid rgba(212,175,55,.55);box-shadow:0 24px 70px rgba(0,0,0,.7);' + 'font-family:system-ui,Segoe UI,Arial,sans-serif;color:#e8eef6;padding:22px 22px 20px;'); card.id = 'pgCard'; back.appendChild(card); document.body.appendChild(back); return card; } function head(card, step) { card.innerHTML = ''; card.appendChild(el('div', 'text-align:center;letter-spacing:2px;text-transform:uppercase;font-size:11px;font-weight:700;color:' + GOLD + ';margin-bottom:8px;', 'Position #' + esc(state.id) + ' · step ' + step + ' of 2')); return card; } function note(text, color) { return el('p', 'margin:0 0 14px;font-size:13.5px;line-height:1.6;color:' + (color || '#9fb3c8') + ';', text); } function input(id, placeholder, value, type) { var i = el('input'); i.id = id; i.type = type || 'text'; i.placeholder = placeholder; i.value = value || ''; i.autocomplete = type === 'email' ? 'email' : 'off'; i.style.cssText = 'width:100%;box-sizing:border-box;padding:12px 14px;border-radius:11px;border:1px solid rgba(255,255,255,.16);' + 'background:rgba(255,255,255,.04);color:#fff;font-size:16px;margin-bottom:10px;'; return i; } function button(label) { var b = el('button', 'width:100%;padding:13px 16px;border-radius:11px;border:none;cursor:pointer;font-size:15px;font-weight:700;' + 'background:linear-gradient(180deg,' + GOLD + ',#b8932f);color:#1a1205;', label); return b; } function errLine() { return el('p', 'margin:0 0 10px;font-size:13px;color:#ff8b8b;display:none;'); } // ---- step 1: username ---- function stepUsername() { var card = head(document.getElementById('pgCard'), 1); card.appendChild(el('h2', 'margin:0 0 6px;font-size:21px;line-height:1.25;color:#fff;', 'Pick your username')); card.appendChild(note('This is how your team leader and the people in your line see you, instead of a bare member number. Letters, numbers or underscores, 3 to 20 characters.')); var err = errLine(); card.appendChild(err); var i = input('pgUser', 'e.g. ' + (state.suggest || 'member' + state.id), (state.profile && state.profile.username) || ''); card.appendChild(i); var b = button('Save and continue'); card.appendChild(b); var go = async function () { err.style.display = 'none'; b.disabled = true; b.textContent = 'Saving…'; try { var r = await api('/api/public/profile/username', { username: i.value }); state.profile = r.profile; next(); } catch (e) { err.textContent = e.message; err.style.display = 'block'; b.disabled = false; b.textContent = 'Save and continue'; } }; b.addEventListener('click', go); i.addEventListener('keydown', function (e) { if (e.key === 'Enter') { e.preventDefault(); go(); } }); setTimeout(function () { i.focus(); }, 60); } // ---- step 2: email + code ---- function stepEmail() { var card = head(document.getElementById('pgCard'), 2); var prefill = (state.profile && state.profile.email) || ''; card.appendChild(el('h2', 'margin:0 0 6px;font-size:21px;line-height:1.25;color:#fff;', 'Confirm your email')); card.appendChild(note('Two reasons this is required. Your leader can actually reach you, and you get a note the moment a payout lands in your wallet. ' + 'It is never shown to other members, never sold, and you can change it any time.' + (prefill ? ' We already have this one on file for your payout alerts, so just confirm it.' : ''))); var err = errLine(); card.appendChild(err); var i = input('pgEmail', 'you@example.com', prefill, 'email'); card.appendChild(i); var b = button(prefill ? 'Send me the code' : 'Send me a code'); card.appendChild(b); var codeWrap = el('div', 'display:none;margin-top:14px;padding-top:14px;border-top:1px solid rgba(255,255,255,.1);'); var sentNote = note('', TEAL); codeWrap.appendChild(sentNote); var ci = input('pgCode', '6-digit code', ''); ci.inputMode = 'numeric'; ci.maxLength = 6; codeWrap.appendChild(ci); var cb = button('Confirm and finish'); codeWrap.appendChild(cb); var again = el('p', 'margin:10px 0 0;font-size:12.5px;color:#7f93a8;text-align:center;cursor:pointer;', 'Wrong address? Change it and send a new code.'); again.addEventListener('click', function () { codeWrap.style.display = 'none'; b.disabled = false; b.textContent = 'Send me a code'; i.focus(); }); codeWrap.appendChild(again); card.appendChild(codeWrap); b.addEventListener('click', async function () { err.style.display = 'none'; b.disabled = true; b.textContent = 'Sending…'; try { var r = await api('/api/public/profile/email-start', { email: i.value }); sentNote.textContent = 'Code sent to ' + (r.to || i.value) + '. It lasts 15 minutes. Check spam the first time.'; codeWrap.style.display = 'block'; b.textContent = 'Code sent'; setTimeout(function () { ci.focus(); }, 60); } catch (e) { err.textContent = e.message; err.style.display = 'block'; b.disabled = false; b.textContent = 'Send me a code'; } }); var confirm = async function () { err.style.display = 'none'; cb.disabled = true; cb.textContent = 'Checking…'; try { var r = await api('/api/public/profile/email-verify', { code: ci.value }); state.profile = r.profile; done(); } catch (e) { err.textContent = e.message; err.style.display = 'block'; cb.disabled = false; cb.textContent = 'Confirm and finish'; } }; cb.addEventListener('click', confirm); ci.addEventListener('keydown', function (e) { if (e.key === 'Enter') { e.preventDefault(); confirm(); } }); setTimeout(function () { i.focus(); }, 60); } function done() { var card = head(document.getElementById('pgCard'), 2); card.innerHTML = '
' + '
✅
' + '

You are all set, @' + esc(state.profile.username) + '

' + '

Your leader can reach you now, and every payout to your wallet sends you a note.

'; var b = button('Open my dashboard'); b.addEventListener('click', close); card.appendChild(b); setTimeout(close, 2600); } function close() { if (back && back.parentNode) back.parentNode.removeChild(back); back = null; if (resolveDone) { var r = resolveDone; resolveDone = null; r(state && state.profile); } } function next() { if (!state.profile || !state.profile.username) return stepUsername(); if (!state.profile.emailVerified) return stepEmail(); return done(); } // Resolves once the profile is complete. Safe to call repeatedly: it returns // immediately when there is nothing to collect, and never shows for a visitor // who has not proved they own the position (the API 401s them). async function require_() { try { state = await api('/api/public/profile'); } catch (e) { return null; } if (!state || state.signedIn === false) return null; // a visitor on a shared link: never gate if (state.profile && state.profile.complete) return state.profile; if (back) return null; // already open shell(); return new Promise(function (res) { resolveDone = res; next(); }); } async function status() { try { return await api('/api/public/profile'); } catch (e) { return null; } } window.RMCProfile = { require: require_, status: status }; })();