Force AEC membership modal on 3rd vote or first petition; shrink login button

- Replace email-only capture with full AEC membership modal (name, DOB,
  address, suburb, state, postcode, email, phone, electoral roll consent)
- Trigger modal on first upvote (petition signature) OR after 3rd vote
- Modal posts to /api/join and emails details to party contact
- localStorage 'radical_member_joined' tracks completion/dismiss per device
- Shrink .login-link to match other footer links (was pink button, now text)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
King Omar 2026-06-27 03:00:40 +10:00
parent 8acf1aca19
commit 5e55dbe900
2 changed files with 152 additions and 52 deletions

View file

@ -139,6 +139,31 @@
.milestone-bar-fill{background:#ff0099;height:4px;transition:width .4s ease}
.milestone-label{font-family:'Roboto Mono',monospace;font-size:10px;color:#666}
.milestone-label span{color:#ff0099}
/* ── AEC Membership modal ── */
.membership-modal-overlay{position:fixed;inset:0;background:rgba(0,0,0,.88);z-index:3000;display:flex;align-items:center;justify-content:center;padding:16px;overflow-y:auto}
.membership-modal{background:#000;border:2px solid #ff0099;padding:0;max-width:520px;width:100%;font-family:'Roboto Mono',monospace;max-height:90vh;overflow-y:auto}
.membership-modal-header{background:#ff0099;color:#000;padding:12px 16px;display:flex;justify-content:space-between;align-items:center}
.membership-modal-header h3{margin:0;font-size:15px;font-weight:700}
.membership-modal-close{background:none;border:none;color:#000;font-size:18px;cursor:pointer;line-height:1;padding:0 4px}
.membership-modal-intro{font-size:11px;color:#888;padding:12px 16px 0;margin:0;line-height:1.6}
#mm-form{padding:12px 16px 0}
.mm-row{display:flex;gap:10px;flex-wrap:wrap}
.mm-field{display:flex;flex-direction:column;gap:3px;flex:1;min-width:120px;margin-bottom:10px}
.mm-field--xs{flex:0 0 80px;min-width:70px}
.mm-field label{font-size:10px;color:#666;text-transform:uppercase;letter-spacing:.5px}
.mm-input{background:#0a0a0a;border:1px solid #333;color:#fff;padding:8px 10px;font-family:'Roboto Mono',monospace;font-size:12px;width:100%;box-sizing:border-box}
.mm-input:focus{outline:none;border-color:#ff0099}
.mm-input option{background:#111}
.mm-check{display:flex;align-items:flex-start;gap:8px;margin-bottom:10px}
.mm-check input[type=checkbox]{margin-top:2px;accent-color:#ff0099;flex-shrink:0}
.mm-check label{font-size:11px;color:#888;line-height:1.5}
.mm-msg{font-size:11px;min-height:16px;margin-bottom:8px}
.mm-error{color:#ff0099}
.mm-submit{width:100%;background:#ff0099;color:#000;border:none;padding:12px;font-family:'Roboto Mono',monospace;font-size:13px;font-weight:700;cursor:pointer;letter-spacing:1px}
.mm-submit:hover{background:#ff33aa}
.mm-submit:disabled{opacity:.5;cursor:not-allowed}
.mm-later{display:block;width:100%;background:none;border:none;color:#444;font-family:'Roboto Mono',monospace;font-size:11px;cursor:pointer;padding:10px;text-align:center;margin:0}
.mm-later:hover{color:#888}
/* ── Email capture modal ── */
.email-modal-overlay{position:fixed;inset:0;background:rgba(0,0,0,.85);z-index:2000;display:flex;align-items:center;justify-content:center;padding:16px}
.email-modal{background:#000;border:2px solid #ff0099;padding:28px;max-width:420px;width:100%;font-family:'Roboto Mono',monospace}
@ -1299,11 +1324,15 @@ async function handleVote(proposalId, voteType) {
// Also update the modal if it's open and showing this proposal
updateModalVoteUI(proposalId, voteType, wasUpvoted, wasDownvoted);
// Track vote count for email capture prompt
// Track votes and trigger AEC membership modal
const vc = (parseInt(localStorage.getItem('radical_vote_count') || '0')) + 1;
localStorage.setItem('radical_vote_count', String(vc));
if (vc === 3 && !localStorage.getItem('radical_email_captured')) {
setTimeout(showEmailCaptureModal, 600);
const alreadyJoined = localStorage.getItem('radical_member_joined');
if (!alreadyJoined) {
// Trigger on first upvote (petition) OR 3rd vote of any kind
if (voteType === 'upvote' || vc >= 3) {
setTimeout(showMembershipModal, 600);
}
}
// Submit vote to server in the background
@ -3099,53 +3128,135 @@ function openProposal(proposalId) {
.catch(() => {});
}
// ─── EMAIL CAPTURE ────────────────────────────────────────────────────────
function showEmailCaptureModal() {
if (document.querySelector('.email-modal-overlay')) return;
// ─── AEC MEMBERSHIP MODAL ─────────────────────────────────────────────────
function showMembershipModal() {
if (document.querySelector('.membership-modal-overlay')) return;
const overlay = document.createElement('div');
overlay.className = 'email-modal-overlay';
overlay.className = 'membership-modal-overlay';
overlay.innerHTML = `
<div class="email-modal">
<h3>Join the movement</h3>
<p>Get notified when petitions you support hit milestones and trigger real action.</p>
<input class="email-modal-input" id="em-email" type="email" placeholder="your@email.com" autocomplete="email">
<input class="email-modal-input" id="em-postcode" type="text" placeholder="Postcode (optional — shows your area's impact)" maxlength="4" inputmode="numeric">
<button class="email-modal-btn" id="em-submit">Join Now</button>
<button class="email-modal-skip" id="em-skip">No thanks</button>
<div class="membership-modal">
<div class="membership-modal-header">
<h3>Become an Official Member</h3>
<button class="membership-modal-close" id="mm-close"></button>
</div>
<p class="membership-modal-intro">Your votes and petitions carry more weight as a verified RADICAL member. We collect these details to meet Australian Electoral Commission requirements for registered political parties.</p>
<form id="mm-form" autocomplete="on">
<div class="mm-row">
<div class="mm-field">
<label for="mm-fullname">Full Name (as on electoral roll) *</label>
<input type="text" id="mm-fullname" class="mm-input" required placeholder="Jane Smith" autocomplete="name">
</div>
<div class="mm-field">
<label for="mm-dob">Date of Birth *</label>
<input type="date" id="mm-dob" class="mm-input" required>
</div>
</div>
<div class="mm-field">
<label for="mm-address">Residential Address *</label>
<input type="text" id="mm-address" class="mm-input" required placeholder="123 Rebellion St" autocomplete="street-address">
</div>
<div class="mm-row">
<div class="mm-field">
<label for="mm-suburb">Suburb *</label>
<input type="text" id="mm-suburb" class="mm-input" required placeholder="Sydney" autocomplete="address-level2">
</div>
<div class="mm-field mm-field--xs">
<label for="mm-state">State *</label>
<select id="mm-state" class="mm-input" required>
<option value=""></option>
<option>NSW</option><option>VIC</option><option>QLD</option>
<option>WA</option><option>SA</option><option>TAS</option>
<option>ACT</option><option>NT</option>
</select>
</div>
<div class="mm-field mm-field--xs">
<label for="mm-postcode">Postcode *</label>
<input type="text" id="mm-postcode" class="mm-input" required placeholder="2000" maxlength="4" pattern="\\d{4}" inputmode="numeric">
</div>
</div>
<div class="mm-row">
<div class="mm-field">
<label for="mm-email">Email Address *</label>
<input type="email" id="mm-email" class="mm-input" required placeholder="you@example.com" autocomplete="email">
</div>
<div class="mm-field">
<label for="mm-phone">Phone (optional)</label>
<input type="tel" id="mm-phone" class="mm-input" placeholder="04xx xxx xxx" autocomplete="tel">
</div>
</div>
<div class="mm-check">
<input type="checkbox" id="mm-roll" required>
<label for="mm-roll">I am enrolled to vote in Australian federal elections (or eligible to enrol) *</label>
</div>
<div class="mm-check">
<input type="checkbox" id="mm-consent" required>
<label for="mm-consent">I consent to joining the RADICAL Party as a member and to my details being used for AEC party registration *</label>
</div>
<div id="mm-msg" class="mm-msg"></div>
<button type="submit" class="mm-submit" id="mm-submit">JOIN THE RADICAL PARTY →</button>
</form>
<button class="mm-later" id="mm-later">I'll do this later</button>
</div>`;
document.body.appendChild(overlay);
document.getElementById('em-submit').addEventListener('click', async () => {
const email = document.getElementById('em-email').value.trim();
const postcode = document.getElementById('em-postcode').value.trim();
if (!email || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
showToastMessage('Please enter a valid email', '#ff0099'); return;
document.getElementById('mm-close').addEventListener('click', () => dismissMembershipModal(overlay));
document.getElementById('mm-later').addEventListener('click', () => dismissMembershipModal(overlay));
document.getElementById('mm-form').addEventListener('submit', async (e) => {
e.preventDefault();
const btn = document.getElementById('mm-submit');
const msg = document.getElementById('mm-msg');
msg.textContent = '';
const dob = document.getElementById('mm-dob').value;
const age = (Date.now() - new Date(dob)) / (365.25 * 24 * 60 * 60 * 1000);
if (age < 18) {
msg.textContent = 'You must be 18 or older to join.';
msg.className = 'mm-msg mm-error';
return;
}
document.getElementById('em-submit').textContent = 'Joining...';
document.getElementById('em-submit').disabled = true;
btn.disabled = true;
btn.textContent = 'Submitting...';
try {
await safeFetch(`${API_BASE_URL}/email`, {
const res = await fetch(`${API_BASE_URL}/join`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ userId: currentUser.id, email, postcode })
body: JSON.stringify({
fullName: document.getElementById('mm-fullname').value.trim(),
dob,
address: document.getElementById('mm-address').value.trim(),
suburb: document.getElementById('mm-suburb').value.trim(),
state: document.getElementById('mm-state').value,
postcode: document.getElementById('mm-postcode').value.trim(),
email: document.getElementById('mm-email').value.trim(),
phone: document.getElementById('mm-phone').value.trim(),
onRoll: document.getElementById('mm-roll').checked,
consent: document.getElementById('mm-consent').checked
})
});
localStorage.setItem('radical_email_captured', '1');
if (postcode) {
currentUser.postcode = postcode;
localStorage.setItem('radical_user', JSON.stringify(currentUser));
const data = await res.json();
if (res.ok && data.success) {
localStorage.setItem('radical_member_joined', '1');
showToastMessage('✓ Welcome to RADICAL. Vibe, Vote, Veto.', '#11cc77');
document.body.removeChild(overlay);
} else {
throw new Error(data.error || 'Submission failed');
}
showToastMessage('✓ You\'re in. Vibe, Vote, Veto.', '#11cc77');
document.body.removeChild(overlay);
} catch(e) {
showToastMessage('Failed to save — try again', '#ff0099');
document.getElementById('em-submit').textContent = 'Join Now';
document.getElementById('em-submit').disabled = false;
} catch(err) {
msg.textContent = '⚠ ' + (err.message || 'Something went wrong. Try again.');
msg.className = 'mm-msg mm-error';
btn.disabled = false;
btn.textContent = 'JOIN THE RADICAL PARTY →';
}
});
document.getElementById('em-skip').addEventListener('click', () => {
localStorage.setItem('radical_email_captured', 'skipped');
document.body.removeChild(overlay);
});
}
function dismissMembershipModal(overlay) {
localStorage.setItem('radical_member_joined', 'later');
document.body.removeChild(overlay);
}
// ─── FLAG / REPORT ────────────────────────────────────────────────────────

View file

@ -1220,23 +1220,12 @@ a {
}
.login-link {
display: inline-block;
background-color: var(--primary);
color: var(--dark);
padding: 8px 20px;
border-radius: 0;
font-size: 0.9rem;
margin-top: 20px;
letter-spacing: 1px;
text-transform: uppercase;
cursor: pointer;
transition: all 0.3s ease;
color: var(--primary);
text-decoration: none;
font-weight: 600;
font-weight: 500;
font-size: inherit;
}
.login-link:hover {
background-color: var(--primary-light);
transform: translateY(-2px);
box-shadow: 0 0 15px rgba(255, 0, 153, 0.4);
text-decoration: underline;
}