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:
parent
8acf1aca19
commit
5e55dbe900
2 changed files with 152 additions and 52 deletions
185
index.html
185
index.html
|
|
@ -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 ────────────────────────────────────────────────────────
|
||||
|
|
|
|||
19
styles.css
19
styles.css
|
|
@ -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;
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue