Add flagship Become a Member page with interactive multi-step form
New trilingual /<lang>/become-a-member/ conversion page: hero with real-photo collage, why-join benefits, animated MSOS-in-numbers, ways to participate, membership-in-real-life and projects showcase (real project photos), testimonials, who-can-join, an adaptive multi-step form (student + supporter flows, conditional fields, progress, personalised messages, validation, success screen), FAQ and final CTA. Site-wide 'Become a member' buttons now link here. Form posts to a Google Form once wired; metrics/testimonials are placeholders to confirm. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
5b14cc2189
commit
a21450c8c8
195 files changed
+2472
-385
No files matched your search
+166
@@ -0,0 +1,166 @@
|
||||
/* ==========================================================================
|
||||
Become a Member — multi-step form logic + animated counters.
|
||||
No dependencies. Submission posts to a Google Form once wired (GOOGLE_FORM
|
||||
below); until then it logs the payload and still shows the success screen.
|
||||
========================================================================== */
|
||||
(function () {
|
||||
/* ---- animated counters ---- */
|
||||
var nums = [].slice.call(document.querySelectorAll('.mm-num-value[data-count]'));
|
||||
if (nums.length && 'IntersectionObserver' in window) {
|
||||
var io = new IntersectionObserver(function (entries) {
|
||||
entries.forEach(function (en) {
|
||||
if (!en.isIntersecting) return;
|
||||
io.unobserve(en.target);
|
||||
var el = en.target, target = parseInt(el.getAttribute('data-count'), 10) || 0, suf = el.getAttribute('data-suffix') || '';
|
||||
var start = null, dur = 1400;
|
||||
function tick(ts) {
|
||||
if (!start) start = ts;
|
||||
var p = Math.min((ts - start) / dur, 1);
|
||||
el.textContent = Math.floor(p * target).toLocaleString() + (p === 1 ? suf : '');
|
||||
if (p < 1) requestAnimationFrame(tick);
|
||||
}
|
||||
requestAnimationFrame(tick);
|
||||
});
|
||||
}, { threshold: 0.4 });
|
||||
nums.forEach(function (n) { io.observe(n); });
|
||||
}
|
||||
|
||||
/* ---- multi-step form ---- */
|
||||
var form = document.querySelector('.ms-form');
|
||||
if (!form) return;
|
||||
var section = form.closest('.ms-section');
|
||||
var success = section.querySelector('.ms-success');
|
||||
var progress = form.querySelector('.ms-progress');
|
||||
var bar = form.querySelector('.ms-progress-bar');
|
||||
var ptext = form.querySelector('.ms-progress-text');
|
||||
var alertBox = form.querySelector('.ms-alert');
|
||||
var back = form.querySelector('.ms-back');
|
||||
var next = form.querySelector('.ms-next');
|
||||
var submit = form.querySelector('.ms-submit');
|
||||
var STEP = progress.getAttribute('data-step'), OF = progress.getAttribute('data-of');
|
||||
|
||||
var steps = {};
|
||||
[].slice.call(form.querySelectorAll('.ms-step')).forEach(function (s) { steps[s.getAttribute('data-step')] = s; });
|
||||
var FLOWS = {
|
||||
student: ['path', 's-basic', 's-status', 's-intention', 's-interests', 's-city', 's-personal', 's-consent'],
|
||||
supporter: ['path', 'p-basic', 'p-support', 'p-message', 'p-consent']
|
||||
};
|
||||
var state = { path: null, order: ['path'], idx: 0 };
|
||||
|
||||
// single-select radio groups (status, city)
|
||||
[].slice.call(form.querySelectorAll('.ms-radios')).forEach(function (group) {
|
||||
[].slice.call(group.querySelectorAll('.ms-radio')).forEach(function (btn) {
|
||||
btn.addEventListener('click', function () {
|
||||
[].slice.call(group.querySelectorAll('.ms-radio')).forEach(function (b) { b.classList.remove('is-selected'); });
|
||||
btn.classList.add('is-selected');
|
||||
var step = group.closest('.ms-step'), val = btn.getAttribute('data-value');
|
||||
[].slice.call(step.querySelectorAll('.ms-cond')).forEach(function (c) { c.hidden = c.getAttribute('data-when') !== val; });
|
||||
[].slice.call(step.querySelectorAll('.ms-msg')).forEach(function (m) {
|
||||
var w = m.getAttribute('data-when');
|
||||
m.hidden = !(w === '*any' || w === val);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// multi-select chip groups (intention, interests, support)
|
||||
[].slice.call(form.querySelectorAll('.ms-multi')).forEach(function (group) {
|
||||
[].slice.call(group.querySelectorAll('.ms-chip')).forEach(function (chip) {
|
||||
chip.addEventListener('click', function () {
|
||||
chip.classList.toggle('is-selected');
|
||||
var step = group.closest('.ms-step');
|
||||
[].slice.call(step.querySelectorAll('.ms-msg')).forEach(function (m) {
|
||||
var sel = group.querySelector('.ms-chip[data-value="' + m.getAttribute('data-when') + '"]');
|
||||
if (sel) m.hidden = !sel.classList.contains('is-selected');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// path choice
|
||||
[].slice.call(form.querySelectorAll('.ms-path')).forEach(function (p) {
|
||||
p.addEventListener('click', function () {
|
||||
state.path = p.getAttribute('data-path');
|
||||
[].slice.call(form.querySelectorAll('.ms-path')).forEach(function (x) { x.classList.remove('is-selected'); });
|
||||
p.classList.add('is-selected');
|
||||
state.order = FLOWS[state.path];
|
||||
state.idx = 1;
|
||||
progress.hidden = false;
|
||||
render();
|
||||
});
|
||||
});
|
||||
|
||||
function render() {
|
||||
Object.keys(steps).forEach(function (k) { steps[k].hidden = (k !== state.order[state.idx]); });
|
||||
var total = state.order.length;
|
||||
bar.style.width = ((state.idx + 1) / total * 100) + '%';
|
||||
ptext.textContent = STEP + ' ' + (state.idx + 1) + ' ' + OF + ' ' + total;
|
||||
back.hidden = state.idx === 0;
|
||||
var last = state.idx === total - 1;
|
||||
var onPath = state.order[state.idx] === 'path';
|
||||
next.hidden = last || onPath;
|
||||
submit.hidden = !last;
|
||||
if (last) submit.textContent = state.path === 'student' ? submit.getAttribute('data-student') : submit.getAttribute('data-supporter');
|
||||
alertBox.hidden = true;
|
||||
}
|
||||
|
||||
function validate() {
|
||||
var step = steps[state.order[state.idx]], ok = true;
|
||||
[].slice.call(step.querySelectorAll('[data-required="1"]')).forEach(function (el) {
|
||||
if (el.closest('.ms-cond') && el.closest('.ms-cond').hidden) return;
|
||||
if (el.classList.contains('ms-radios')) { if (!el.querySelector('.ms-radio.is-selected')) ok = false; }
|
||||
else if (el.type === 'checkbox') { if (!el.checked) ok = false; }
|
||||
else if (el.type === 'email') { if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(el.value)) ok = false; }
|
||||
else { if (!el.value || !el.value.trim()) ok = false; }
|
||||
});
|
||||
return ok;
|
||||
}
|
||||
|
||||
next.addEventListener('click', function () {
|
||||
if (!validate()) { alertBox.hidden = false; return; }
|
||||
if (state.idx < state.order.length - 1) { state.idx++; render(); form.scrollIntoView({ behavior: 'smooth', block: 'start' }); }
|
||||
});
|
||||
back.addEventListener('click', function () {
|
||||
if (state.idx > 0) { state.idx--; render(); }
|
||||
});
|
||||
|
||||
function collect() {
|
||||
var d = { type: state.path };
|
||||
[].slice.call(form.querySelectorAll('[data-name]')).forEach(function (el) {
|
||||
if (el.closest('.ms-cond') && el.closest('.ms-cond').hidden) return;
|
||||
var name = el.getAttribute('data-name');
|
||||
if (el.classList.contains('ms-radios')) { var s = el.querySelector('.ms-radio.is-selected'); if (s) d[name] = s.getAttribute('data-value'); }
|
||||
else if (el.classList.contains('ms-multi')) { var sel = [].slice.call(el.querySelectorAll('.ms-chip.is-selected')).map(function (c) { return c.getAttribute('data-value'); }); if (sel.length) d[name] = sel.join(', '); }
|
||||
else if (el.type === 'checkbox') { d[name] = el.checked ? 'yes' : 'no'; }
|
||||
else if (el.value && el.value.trim()) { d[name] = el.value.trim(); }
|
||||
});
|
||||
return d;
|
||||
}
|
||||
|
||||
// Wire to a Google Form: set action + entry IDs (see MEMBERSHIP_FORM_SETUP).
|
||||
var GOOGLE_FORM = { action: '', fields: {} };
|
||||
function sendToForm(d) {
|
||||
if (!GOOGLE_FORM.action) { console.log('[MSOS membership] not wired yet, payload:', d); return; }
|
||||
var params = new URLSearchParams();
|
||||
Object.keys(GOOGLE_FORM.fields).forEach(function (k) { if (d[k]) params.append(GOOGLE_FORM.fields[k], d[k]); });
|
||||
if (GOOGLE_FORM.fields.summary) {
|
||||
var lines = Object.keys(d).map(function (k) { return k + ': ' + d[k]; }).join('\n');
|
||||
params.set(GOOGLE_FORM.fields.summary, lines);
|
||||
}
|
||||
try { fetch(GOOGLE_FORM.action, { method: 'POST', mode: 'no-cors', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: params.toString() }); } catch (e) { }
|
||||
}
|
||||
|
||||
submit.addEventListener('click', function () {
|
||||
if (!validate()) { alertBox.hidden = false; return; }
|
||||
var d = collect();
|
||||
sendToForm(d);
|
||||
form.hidden = true;
|
||||
var t = success.querySelector('.ms-success-title'), p = success.querySelector('.ms-success-text');
|
||||
t.textContent = d.type === 'student' ? t.getAttribute('data-student-title') : t.getAttribute('data-supporter-title');
|
||||
p.textContent = d.type === 'student' ? p.getAttribute('data-student-text') : p.getAttribute('data-supporter-text');
|
||||
success.hidden = false;
|
||||
success.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||
});
|
||||
|
||||
render();
|
||||
})();
|
||||
Reference in new issue
Block a user