Scripts des membres
Une solution basée sur les attributs pour ajouter des fonctionnalités à votre site Webflow.
Il suffit de copier un peu de code, d'ajouter quelques attributs et le tour est joué.
Tous les clients de Memberstack peuvent demander de l'aide dans le Slack 2.0. Veuillez noter qu'il ne s'agit pas de fonctionnalités officielles et que le support ne peut être garanti.

#158 - Emoji Feedback Widget for Memberstack
This widget lets your logged-in members quickly share how they feel using simple emoji buttons.
<!-- 💙 MEMBERSCRIPT #158 v1.0 💙 - EMOJI FEEDBACK WIDGET -->
<!--
Collect emoji-based feedback from logged-in members.
Saves submission state in localStorage and sends data to Make.com.
-->
<script>
(function() {
const msDom = window.$memberstackDom;
if (!msDom) {
console.error('Memberstack DOM not found.');
return;
}
// Elements
const widget = document.querySelector('[data-ms-code="emoji-feedback-widget"]');
const closeBtn = widget.querySelector('[data-ms-code="emoji-feedback-close"]');
const buttons = widget.querySelectorAll('[data-ms-code="emoji-feedback-btn"]');
const thanks = widget.querySelector('[data-ms-code="emoji-feedback-thanks"]');
// Exit early if feedback is done or dismissed
if (
localStorage.getItem('emojiFeedbackDone') === 'true' ||
localStorage.getItem('emojiFeedbackClosed') === 'true'
) {
widget.style.display = 'none';
return;
}
// Handle close (×) click
closeBtn.addEventListener('click', e => {
e.preventDefault();
localStorage.setItem('emojiFeedbackClosed', 'true');
widget.style.display = 'none';
});
// Fetch member data
msDom.getCurrentMember()
.then(({ data: member }) => {
buttons.forEach(btn => {
btn.addEventListener('click', () => {
const score = btn.getAttribute('data-value');
// Payload for Make.com
const payload = {
memberId: member.id,
name: member.customFields["first-name"] || '',
email: member.auth.email || '',
pageUrl: window.location.href,
feedback: score,
timestamp: new Date().toISOString()
};
// Send feedback to Make
fetch('https://hook.eu2.make.com/8wm1j323te1sybyweux6x33mh77vswvm', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload)
})
.then(res => {
if (!res.ok) throw new Error(res.statusText);
// Success state: hide emojis, show thank you
localStorage.setItem('emojiFeedbackDone', 'true');
widget.querySelector('[data-ms-code="emoji-feedback-buttons"]').style.display = 'none';
thanks.style.display = 'block';
})
.catch(err => {
console.error('Emoji feedback error:', err);
// Optionally add error handling UI here
});
});
});
})
.catch(err => console.error('Couldn’t get member:', err));
})();
</script>
<!-- 💙 MEMBERSCRIPT #158 v1.0 💙 - EMOJI FEEDBACK WIDGET -->
<!--
Collect emoji-based feedback from logged-in members.
Saves submission state in localStorage and sends data to Make.com.
-->
<script>
(function() {
const msDom = window.$memberstackDom;
if (!msDom) {
console.error('Memberstack DOM not found.');
return;
}
// Elements
const widget = document.querySelector('[data-ms-code="emoji-feedback-widget"]');
const closeBtn = widget.querySelector('[data-ms-code="emoji-feedback-close"]');
const buttons = widget.querySelectorAll('[data-ms-code="emoji-feedback-btn"]');
const thanks = widget.querySelector('[data-ms-code="emoji-feedback-thanks"]');
// Exit early if feedback is done or dismissed
if (
localStorage.getItem('emojiFeedbackDone') === 'true' ||
localStorage.getItem('emojiFeedbackClosed') === 'true'
) {
widget.style.display = 'none';
return;
}
// Handle close (×) click
closeBtn.addEventListener('click', e => {
e.preventDefault();
localStorage.setItem('emojiFeedbackClosed', 'true');
widget.style.display = 'none';
});
// Fetch member data
msDom.getCurrentMember()
.then(({ data: member }) => {
buttons.forEach(btn => {
btn.addEventListener('click', () => {
const score = btn.getAttribute('data-value');
// Payload for Make.com
const payload = {
memberId: member.id,
name: member.customFields["first-name"] || '',
email: member.auth.email || '',
pageUrl: window.location.href,
feedback: score,
timestamp: new Date().toISOString()
};
// Send feedback to Make
fetch('https://hook.eu2.make.com/8wm1j323te1sybyweux6x33mh77vswvm', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload)
})
.then(res => {
if (!res.ok) throw new Error(res.statusText);
// Success state: hide emojis, show thank you
localStorage.setItem('emojiFeedbackDone', 'true');
widget.querySelector('[data-ms-code="emoji-feedback-buttons"]').style.display = 'none';
thanks.style.display = 'block';
})
.catch(err => {
console.error('Emoji feedback error:', err);
// Optionally add error handling UI here
});
});
});
})
.catch(err => console.error('Couldn’t get member:', err));
})();
</script>

#157 - Range Slider Feedback Widget
A simple and friendly slider widget that lets logged-in members give quick feedback (0–10).
<!-- 💙 MEMBERSCRIPT #157 v1.0 💙 - RANGE SLIDER FEEDBACK WIDGET -->
<!--
A lightweight feedback widget that uses a range slider.
Prevents duplicate submissions with localStorage, and sends feedback to Make.com via webhook.
-->
<script>
Webflow.push(function() {
// Silently disable all form submissions
$('form').submit(function(e) {
e.preventDefault();
return false;
});
});
(function () {
const msDom = window.$memberstackDom;
if (!msDom) {
console.error("Memberstack DOM not found. Did you include data-memberstack-app?");
return;
}
const widget = document.querySelector('[data-ms-code="feedback-widget"]');
const dialog = widget?.querySelector('[data-ms-code="feedback-dialog"]');
const toggle = widget?.querySelector('[data-ms-code="feedback-toggle"]');
const slider = widget?.querySelector('[data-ms-code="feedback-range"]');
const submit = widget?.querySelector('[data-ms-code="feedback-next"]');
const form = widget?.closest("form");
const done = localStorage.getItem("feedbackDone") === "true";
const closed = localStorage.getItem("feedbackClosed") === "true";
if (!widget || !dialog || !toggle || !slider || !submit || done || closed) {
if (widget) widget.style.display = "none";
return;
}
// Manual close button logic
toggle.addEventListener("click", (e) => {
e.preventDefault();
localStorage.setItem("feedbackClosed", "true");
widget.style.display = "none";
});
// Fetch logged-in member
msDom.getCurrentMember()
.then(({ data: member }) => {
slider.addEventListener("input", () => {
submit.disabled = false;
});
submit.addEventListener("click", () => {
const payload = {
memberId: member.id,
name: member.customFields["first-name"] || "",
email: member.auth.email || "",
pageUrl: window.location.href,
feedback: slider.value,
timestamp: new Date().toISOString()
};
fetch("https://hook.eu2.make.com/8wm1j323te1sybyweux6x33mh77vswvm", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload)
})
.then((res) => {
if (!res.ok) throw new Error(res.statusText);
localStorage.setItem("feedbackDone", "true");
const msg = document.createElement("p");
msg.textContent = "Thanks for your feedback!";
msg.style.padding = "1em";
msg.style.textAlign = "center";
dialog.innerHTML = "";
dialog.appendChild(msg);
})
.catch((err) => {
console.error("Feedback error:", err);
dialog.insertAdjacentHTML(
"beforeend",
'<p style="color:red; text-align:center;">Oops! Could not send. Try again?</p>'
);
});
});
})
.catch((err) => console.error("Couldn’t get member:", err));
})();
</script>
<!-- 💙 MEMBERSCRIPT #157 v1.0 💙 - RANGE SLIDER FEEDBACK WIDGET -->
<!--
A lightweight feedback widget that uses a range slider.
Prevents duplicate submissions with localStorage, and sends feedback to Make.com via webhook.
-->
<script>
Webflow.push(function() {
// Silently disable all form submissions
$('form').submit(function(e) {
e.preventDefault();
return false;
});
});
(function () {
const msDom = window.$memberstackDom;
if (!msDom) {
console.error("Memberstack DOM not found. Did you include data-memberstack-app?");
return;
}
const widget = document.querySelector('[data-ms-code="feedback-widget"]');
const dialog = widget?.querySelector('[data-ms-code="feedback-dialog"]');
const toggle = widget?.querySelector('[data-ms-code="feedback-toggle"]');
const slider = widget?.querySelector('[data-ms-code="feedback-range"]');
const submit = widget?.querySelector('[data-ms-code="feedback-next"]');
const form = widget?.closest("form");
const done = localStorage.getItem("feedbackDone") === "true";
const closed = localStorage.getItem("feedbackClosed") === "true";
if (!widget || !dialog || !toggle || !slider || !submit || done || closed) {
if (widget) widget.style.display = "none";
return;
}
// Manual close button logic
toggle.addEventListener("click", (e) => {
e.preventDefault();
localStorage.setItem("feedbackClosed", "true");
widget.style.display = "none";
});
// Fetch logged-in member
msDom.getCurrentMember()
.then(({ data: member }) => {
slider.addEventListener("input", () => {
submit.disabled = false;
});
submit.addEventListener("click", () => {
const payload = {
memberId: member.id,
name: member.customFields["first-name"] || "",
email: member.auth.email || "",
pageUrl: window.location.href,
feedback: slider.value,
timestamp: new Date().toISOString()
};
fetch("https://hook.eu2.make.com/8wm1j323te1sybyweux6x33mh77vswvm", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload)
})
.then((res) => {
if (!res.ok) throw new Error(res.statusText);
localStorage.setItem("feedbackDone", "true");
const msg = document.createElement("p");
msg.textContent = "Thanks for your feedback!";
msg.style.padding = "1em";
msg.style.textAlign = "center";
dialog.innerHTML = "";
dialog.appendChild(msg);
})
.catch((err) => {
console.error("Feedback error:", err);
dialog.insertAdjacentHTML(
"beforeend",
'<p style="color:red; text-align:center;">Oops! Could not send. Try again?</p>'
);
});
});
})
.catch((err) => console.error("Couldn’t get member:", err));
})();
</script>

#156 - Encrypt Sensitive Data Before Sending to Memberstack
This script protects sensitive user data by encrypting it in the browser before it’s sent to Memberstack.
<!-- 💙 MEMBERSCRIPT #156 v1.0 💙 - ENCRYPT SENSITIVE DATA BEFORE SENDING TO MEMBERSTACK -->
<!--
This script encrypts input fields before they're submitted to Memberstack,
using AES-GCM with a passphrase-based modal.
-->
<script>
document.addEventListener('DOMContentLoaded', function () {
(function () {
const enc = new TextEncoder();
const dec = new TextDecoder();
// Show the passphrase modal
function showModal() {
return new Promise(resolve => {
const modal = document.querySelector('[data-ms-code="encrypt-modal"]');
if (!modal) return alert('Encryption modal missing from the page.');
const input = modal.querySelector('[data-ms-code="pass-input"]');
const remember = modal.querySelector('[data-ms-code="remember-pass"]');
const submit = modal.querySelector('[data-ms-code="submit-pass"]');
const closeButtons = modal.querySelectorAll(
'[data-ms-code="close-encrypt-modal"], [data-ms-code="close-encrypt-icon"]'
);
modal.style.display = 'flex';
input.value = '';
input.focus();
const cleanup = () => {
modal.style.display = 'none';
};
if (submit) {
submit.onclick = () => {
const pass = input.value;
const keep = remember.checked;
cleanup();
resolve({ pass, remember: keep });
};
}
closeButtons.forEach(btn => {
btn.onclick = () => {
cleanup();
resolve({ pass: null });
};
});
});
}
// Derive AES key using PBKDF2
async function deriveKey(pass, salt) {
const keyMaterial = await crypto.subtle.importKey(
'raw',
enc.encode(pass),
{ name: 'PBKDF2' },
false,
['deriveKey']
);
return crypto.subtle.deriveKey(
{
name: 'PBKDF2',
salt: salt,
iterations: 100000,
hash: 'SHA-256'
},
keyMaterial,
{ name: 'AES-GCM', length: 256 },
false,
['encrypt', 'decrypt']
);
}
// Encrypt a string
async function encryptText(text, pass) {
const salt = crypto.getRandomValues(new Uint8Array(16));
const iv = crypto.getRandomValues(new Uint8Array(12));
const key = await deriveKey(pass, salt);
const encrypted = await crypto.subtle.encrypt(
{ name: 'AES-GCM', iv },
key,
enc.encode(text)
);
return [
btoa(String.fromCharCode(...salt)),
btoa(String.fromCharCode(...iv)),
btoa(String.fromCharCode(...new Uint8Array(encrypted)))
].join(':');
}
// Decrypt a string
async function decryptText(encrypted, pass) {
const [saltB64, ivB64, dataB64] = encrypted.split(':');
if (!saltB64 || !ivB64 || !dataB64) throw new Error('Invalid format');
const salt = Uint8Array.from(atob(saltB64), c => c.charCodeAt(0));
const iv = Uint8Array.from(atob(ivB64), c => c.charCodeAt(0));
const data = Uint8Array.from(atob(dataB64), c => c.charCodeAt(0));
const key = await deriveKey(pass, salt);
const decrypted = await crypto.subtle.decrypt({ name: 'AES-GCM', iv }, key, data);
return dec.decode(decrypted);
}
// Encrypt and submit form
document.querySelectorAll('[data-ms-code-encrypt]').forEach(btn => {
if (!btn.hasAttribute('data-ms-encrypt-attached')) {
btn.addEventListener('click', async e => {
e.preventDefault();
let passphrase = sessionStorage.getItem('ms-encrypt-passphrase');
if (!passphrase) {
const { pass, remember } = await showModal();
if (!pass) return;
passphrase = pass;
if (remember) sessionStorage.setItem('ms-encrypt-passphrase', passphrase);
}
const fields = document.querySelectorAll('[data-ms-code-id]');
for (let field of fields) {
const value = field.value.trim();
if (!value) continue;
try {
const encrypted = await encryptText(value, passphrase);
field.value = encrypted;
} catch (err) {
console.error('Encryption error:', err);
alert('Encryption failed.');
return;
}
}
const form = btn.closest('form');
if (form) form.requestSubmit();
});
btn.setAttribute('data-ms-encrypt-attached', 'true');
}
});
// Add decrypt button logic
function attachDecryptButton() {
const decryptBtn = document.querySelector('[data-ms-code="decrypt-all"]');
if (!decryptBtn || decryptBtn.hasAttribute('data-ms-decrypt-attached')) return;
decryptBtn.addEventListener('click', async e => {
e.preventDefault();
const encryptedFields = document.querySelectorAll('[data-ms-code-id]');
if (encryptedFields.length === 0) return alert('No fields to decrypt.');
let passphrase = sessionStorage.getItem('ms-encrypt-passphrase');
if (!passphrase) {
const { pass, remember } = await showModal();
if (!pass) return;
passphrase = pass;
if (remember) sessionStorage.setItem('ms-encrypt-passphrase', passphrase);
}
for (let field of encryptedFields) {
const encrypted = field.value.trim();
if (!encrypted) continue;
try {
const decrypted = await decryptText(encrypted, passphrase);
field.value = decrypted;
} catch (err) {
console.error('Decryption error:', err);
alert('One or more fields failed to decrypt.');
return;
}
}
});
decryptBtn.setAttribute('data-ms-decrypt-attached', 'true');
}
attachDecryptButton();
})();
});
</script>
<!-- 💙 MEMBERSCRIPT #156 v1.0 💙 - ENCRYPT SENSITIVE DATA BEFORE SENDING TO MEMBERSTACK -->
<!--
This script encrypts input fields before they're submitted to Memberstack,
using AES-GCM with a passphrase-based modal.
-->
<script>
document.addEventListener('DOMContentLoaded', function () {
(function () {
const enc = new TextEncoder();
const dec = new TextDecoder();
// Show the passphrase modal
function showModal() {
return new Promise(resolve => {
const modal = document.querySelector('[data-ms-code="encrypt-modal"]');
if (!modal) return alert('Encryption modal missing from the page.');
const input = modal.querySelector('[data-ms-code="pass-input"]');
const remember = modal.querySelector('[data-ms-code="remember-pass"]');
const submit = modal.querySelector('[data-ms-code="submit-pass"]');
const closeButtons = modal.querySelectorAll(
'[data-ms-code="close-encrypt-modal"], [data-ms-code="close-encrypt-icon"]'
);
modal.style.display = 'flex';
input.value = '';
input.focus();
const cleanup = () => {
modal.style.display = 'none';
};
if (submit) {
submit.onclick = () => {
const pass = input.value;
const keep = remember.checked;
cleanup();
resolve({ pass, remember: keep });
};
}
closeButtons.forEach(btn => {
btn.onclick = () => {
cleanup();
resolve({ pass: null });
};
});
});
}
// Derive AES key using PBKDF2
async function deriveKey(pass, salt) {
const keyMaterial = await crypto.subtle.importKey(
'raw',
enc.encode(pass),
{ name: 'PBKDF2' },
false,
['deriveKey']
);
return crypto.subtle.deriveKey(
{
name: 'PBKDF2',
salt: salt,
iterations: 100000,
hash: 'SHA-256'
},
keyMaterial,
{ name: 'AES-GCM', length: 256 },
false,
['encrypt', 'decrypt']
);
}
// Encrypt a string
async function encryptText(text, pass) {
const salt = crypto.getRandomValues(new Uint8Array(16));
const iv = crypto.getRandomValues(new Uint8Array(12));
const key = await deriveKey(pass, salt);
const encrypted = await crypto.subtle.encrypt(
{ name: 'AES-GCM', iv },
key,
enc.encode(text)
);
return [
btoa(String.fromCharCode(...salt)),
btoa(String.fromCharCode(...iv)),
btoa(String.fromCharCode(...new Uint8Array(encrypted)))
].join(':');
}
// Decrypt a string
async function decryptText(encrypted, pass) {
const [saltB64, ivB64, dataB64] = encrypted.split(':');
if (!saltB64 || !ivB64 || !dataB64) throw new Error('Invalid format');
const salt = Uint8Array.from(atob(saltB64), c => c.charCodeAt(0));
const iv = Uint8Array.from(atob(ivB64), c => c.charCodeAt(0));
const data = Uint8Array.from(atob(dataB64), c => c.charCodeAt(0));
const key = await deriveKey(pass, salt);
const decrypted = await crypto.subtle.decrypt({ name: 'AES-GCM', iv }, key, data);
return dec.decode(decrypted);
}
// Encrypt and submit form
document.querySelectorAll('[data-ms-code-encrypt]').forEach(btn => {
if (!btn.hasAttribute('data-ms-encrypt-attached')) {
btn.addEventListener('click', async e => {
e.preventDefault();
let passphrase = sessionStorage.getItem('ms-encrypt-passphrase');
if (!passphrase) {
const { pass, remember } = await showModal();
if (!pass) return;
passphrase = pass;
if (remember) sessionStorage.setItem('ms-encrypt-passphrase', passphrase);
}
const fields = document.querySelectorAll('[data-ms-code-id]');
for (let field of fields) {
const value = field.value.trim();
if (!value) continue;
try {
const encrypted = await encryptText(value, passphrase);
field.value = encrypted;
} catch (err) {
console.error('Encryption error:', err);
alert('Encryption failed.');
return;
}
}
const form = btn.closest('form');
if (form) form.requestSubmit();
});
btn.setAttribute('data-ms-encrypt-attached', 'true');
}
});
// Add decrypt button logic
function attachDecryptButton() {
const decryptBtn = document.querySelector('[data-ms-code="decrypt-all"]');
if (!decryptBtn || decryptBtn.hasAttribute('data-ms-decrypt-attached')) return;
decryptBtn.addEventListener('click', async e => {
e.preventDefault();
const encryptedFields = document.querySelectorAll('[data-ms-code-id]');
if (encryptedFields.length === 0) return alert('No fields to decrypt.');
let passphrase = sessionStorage.getItem('ms-encrypt-passphrase');
if (!passphrase) {
const { pass, remember } = await showModal();
if (!pass) return;
passphrase = pass;
if (remember) sessionStorage.setItem('ms-encrypt-passphrase', passphrase);
}
for (let field of encryptedFields) {
const encrypted = field.value.trim();
if (!encrypted) continue;
try {
const decrypted = await decryptText(encrypted, passphrase);
field.value = decrypted;
} catch (err) {
console.error('Decryption error:', err);
alert('One or more fields failed to decrypt.');
return;
}
}
});
decryptBtn.setAttribute('data-ms-decrypt-attached', 'true');
}
attachDecryptButton();
})();
});
</script>

#155 - Bulk Update Customer Subscriptions via Stripe & Make
Bulk update existing members to a new pricing plan with Stripe and Make

#154 - Two-Factor Authentication (2FA) for Memberstack Logins
Add an extra layer of security to your Memberstack logins by enabling Two-Factor Authentication (2FA).
<!--
MEMBERSCRIPT #154
---------------------------------
LOGIN PAGE SCRIPT
-->
<script>
(async function() {
const delay = ms => new Promise(r => setTimeout(r, ms));
async function routeLogin() {
try {
// Check if Memberstack is loaded
if (!window.$memberstackDom) {
console.log("Memberstack not loaded yet");
return;
}
// Get current member
const { data: member } = await window.$memberstackDom.getCurrentMember();
if (!member) return; // Exit if not logged in
// Get member JSON data
const jsonResponse = await window.$memberstackDom.getMemberJSON();
const memberData = jsonResponse.data || {};
// Check if 2FA is enabled
const needs2FA = memberData["2fa_enabled"] === true ||
jsonResponse["2fa_enabled"] === true;
// Check session storage for verification status
const verified = sessionStorage.getItem("2fa_verified") === "true";
console.log("2FA Status:", {
enabled: needs2FA,
verified: verified,
currentPath: window.location.pathname
});
// Handle 2FA redirect
if (needs2FA && !verified) {
if (!window.location.pathname.includes("/2fa-verify")) {
console.log("Redirecting to /2fa-verify");
window.location.href = "/2fa-verify";
}
return; // Stop further execution
}
// Handle success redirect
if (!window.location.pathname.includes("/success")) {
console.log("Redirecting to /success");
// Remove Memberstack's auto-redirect if login form exists
const loginForm = document.querySelector('[data-ms-form="login"]');
if (loginForm) {
loginForm.removeAttribute('data-ms-redirect');
}
window.location.href = "/success";
}
} catch (err) {
console.error("2FA routing error:", err);
}
}
// Wait for Memberstack to initialize
await delay(300);
routeLogin();
// Poll with cleanup
const pollInterval = setInterval(routeLogin, 500);
setTimeout(() => clearInterval(pollInterval), 10000);
})();
</script>
<!--
MEMBERSCRIPT #154
---------------------------------
SETTINGS PAGE SCRIPT
-->
<!-- Load otplib preset-browser -->
<script src="https://unpkg.com/@otplib/preset-browser@^12.0.0/buffer.js"></script>
<script src="https://unpkg.com/@otplib/preset-browser@^12.0.0/index.js"></script>
<script>
document.addEventListener("DOMContentLoaded", async () => {
const ms = window.$memberstackDom;
const { data: member } = await ms.getCurrentMember();
if (!member) return;
const checkbox = document.querySelector('[data-ms-code="enable-2fa"]');
const qrContainer = document.querySelector('[data-ms-code="2fa-qr-container"]');
const qrImage = document.querySelector('[data-ms-code="2fa-qr-image"]');
// Hide QR container by default
qrContainer.style.display = "none";
// Load member JSON and ensure data object exists
const jsonObj = await ms.getMemberJSON(); // { data: ... } or { data: null }
if (!jsonObj.data) jsonObj.data = {};
const inner = jsonObj.data;
// Set checkbox initial state
const enabled = inner["2fa_enabled"] === true;
checkbox.checked = enabled;
checkbox.addEventListener("change", async (e) => {
const isChecked = e.target.checked;
// Reload member and JSON
const { data: member } = await ms.getCurrentMember();
const jsonObj2 = await ms.getMemberJSON();
if (!jsonObj2.data) jsonObj2.data = {};
const inner2 = jsonObj2.data;
if (isChecked) {
// Enable 2FA: generate secret and QR
const secret = window.otplib.authenticator.generateSecret();
const uri = window.otplib.authenticator.keyuri(member.email, "Memberscript #154", secret);
qrImage.src = "https://api.qrserver.com/v1/create-qr-code/?data=" + encodeURIComponent(uri);
qrContainer.style.display = "flex";
inner2["2fa_enabled"] = true;
inner2["2fa_secret"] = secret;
await ms.updateMember({
customFields: { "2fa-enabled": "true" }
});
} else {
// Disable 2FA: remove secret and hide QR
qrContainer.style.display = "none";
inner2["2fa_enabled"] = false;
delete inner2["2fa_secret"];
await ms.updateMember({
customFields: { "2fa-enabled": "false" }
});
}
// Persist only nested JSON
await ms.updateMemberJSON({ json: inner2 });
// ✅ Debugging: check result
const check = await ms.getMemberJSON();
console.log(check);
});
});
</script>
<!--
MEMBERSCRIPT #154
---------------------------------
SUCCESS / DASHBOARD PAGE SCRIPT
-->
<script>
window.$memberstackDom.getCurrentMember().then(({ data: member }) => {
if (!member) return; // Not logged in, no redirect
window.$memberstackDom.getMemberJSON().then(json => {
const enabled = json["2fa_enabled"];
const verified = sessionStorage.getItem("2fa_verified") === "true";
if (enabled && !verified && window.location.pathname !== "/2fa-verify") {
window.location.href = "/2fa-verify";
}
});
});
</script>
<!--
MEMBERSCRIPT #154
---------------------------------
TWO FACTOR VERIFICATION PAGE SCRIPT
-->
<!-- Include the Buffer polyfill -->
<script src="https://unpkg.com/@otplib/preset-browser@^12.0.0/buffer.js"></script>
<!-- Include the otplib library -->
<script src="https://unpkg.com/@otplib/preset-browser@^12.0.0/index.js"></script>
<script>
document.addEventListener("DOMContentLoaded", async () => {
const memberstack = window.$memberstackDom;
const { data: member } = await memberstack.getCurrentMember();
if (!member) return;
const form = document.querySelector('[data-ms-form="2fa-verification"]');
if (!form) return;
const codeInput = form.querySelector('[data-ms-code="2fa-code"]');
const errorContainer = form.querySelector('[data-ms-error="2fa-code"]');
function showError(message) {
if (errorContainer) {
errorContainer.textContent = message;
errorContainer.style.display = 'block';
} else {
alert(message);
}
}
function clearError() {
if (errorContainer) {
errorContainer.textContent = '';
errorContainer.style.display = 'none';
}
}
form.addEventListener('submit', async (e) => {
e.preventDefault();
e.stopImmediatePropagation(); // important to stop other listeners
clearError();
const code = codeInput.value.trim();
const { data: json } = await memberstack.getMemberJSON();
const secret = json?.["2fa_secret"];
if (!secret || !code) {
const msg = form.getAttribute('data-ms-error-msg-missing') || 'Please enter your 2FA code';
showError(msg);
return;
}
if (otplib.authenticator.check(code, secret)) {
sessionStorage.setItem('2fa_verified', 'true');
window.location.href = '/success';
} else {
const msg = form.getAttribute('data-ms-error-msg-invalid') || 'Oops, the 2FA code is incorrect. Try again.';
showError(msg);
}
});
});
</script>
<!--
MEMBERSCRIPT #154
---------------------------------
LOGIN PAGE SCRIPT
-->
<script>
(async function() {
const delay = ms => new Promise(r => setTimeout(r, ms));
async function routeLogin() {
try {
// Check if Memberstack is loaded
if (!window.$memberstackDom) {
console.log("Memberstack not loaded yet");
return;
}
// Get current member
const { data: member } = await window.$memberstackDom.getCurrentMember();
if (!member) return; // Exit if not logged in
// Get member JSON data
const jsonResponse = await window.$memberstackDom.getMemberJSON();
const memberData = jsonResponse.data || {};
// Check if 2FA is enabled
const needs2FA = memberData["2fa_enabled"] === true ||
jsonResponse["2fa_enabled"] === true;
// Check session storage for verification status
const verified = sessionStorage.getItem("2fa_verified") === "true";
console.log("2FA Status:", {
enabled: needs2FA,
verified: verified,
currentPath: window.location.pathname
});
// Handle 2FA redirect
if (needs2FA && !verified) {
if (!window.location.pathname.includes("/2fa-verify")) {
console.log("Redirecting to /2fa-verify");
window.location.href = "/2fa-verify";
}
return; // Stop further execution
}
// Handle success redirect
if (!window.location.pathname.includes("/success")) {
console.log("Redirecting to /success");
// Remove Memberstack's auto-redirect if login form exists
const loginForm = document.querySelector('[data-ms-form="login"]');
if (loginForm) {
loginForm.removeAttribute('data-ms-redirect');
}
window.location.href = "/success";
}
} catch (err) {
console.error("2FA routing error:", err);
}
}
// Wait for Memberstack to initialize
await delay(300);
routeLogin();
// Poll with cleanup
const pollInterval = setInterval(routeLogin, 500);
setTimeout(() => clearInterval(pollInterval), 10000);
})();
</script>
<!--
MEMBERSCRIPT #154
---------------------------------
SETTINGS PAGE SCRIPT
-->
<!-- Load otplib preset-browser -->
<script src="https://unpkg.com/@otplib/preset-browser@^12.0.0/buffer.js"></script>
<script src="https://unpkg.com/@otplib/preset-browser@^12.0.0/index.js"></script>
<script>
document.addEventListener("DOMContentLoaded", async () => {
const ms = window.$memberstackDom;
const { data: member } = await ms.getCurrentMember();
if (!member) return;
const checkbox = document.querySelector('[data-ms-code="enable-2fa"]');
const qrContainer = document.querySelector('[data-ms-code="2fa-qr-container"]');
const qrImage = document.querySelector('[data-ms-code="2fa-qr-image"]');
// Hide QR container by default
qrContainer.style.display = "none";
// Load member JSON and ensure data object exists
const jsonObj = await ms.getMemberJSON(); // { data: ... } or { data: null }
if (!jsonObj.data) jsonObj.data = {};
const inner = jsonObj.data;
// Set checkbox initial state
const enabled = inner["2fa_enabled"] === true;
checkbox.checked = enabled;
checkbox.addEventListener("change", async (e) => {
const isChecked = e.target.checked;
// Reload member and JSON
const { data: member } = await ms.getCurrentMember();
const jsonObj2 = await ms.getMemberJSON();
if (!jsonObj2.data) jsonObj2.data = {};
const inner2 = jsonObj2.data;
if (isChecked) {
// Enable 2FA: generate secret and QR
const secret = window.otplib.authenticator.generateSecret();
const uri = window.otplib.authenticator.keyuri(member.email, "Memberscript #154", secret);
qrImage.src = "https://api.qrserver.com/v1/create-qr-code/?data=" + encodeURIComponent(uri);
qrContainer.style.display = "flex";
inner2["2fa_enabled"] = true;
inner2["2fa_secret"] = secret;
await ms.updateMember({
customFields: { "2fa-enabled": "true" }
});
} else {
// Disable 2FA: remove secret and hide QR
qrContainer.style.display = "none";
inner2["2fa_enabled"] = false;
delete inner2["2fa_secret"];
await ms.updateMember({
customFields: { "2fa-enabled": "false" }
});
}
// Persist only nested JSON
await ms.updateMemberJSON({ json: inner2 });
// ✅ Debugging: check result
const check = await ms.getMemberJSON();
console.log(check);
});
});
</script>
<!--
MEMBERSCRIPT #154
---------------------------------
SUCCESS / DASHBOARD PAGE SCRIPT
-->
<script>
window.$memberstackDom.getCurrentMember().then(({ data: member }) => {
if (!member) return; // Not logged in, no redirect
window.$memberstackDom.getMemberJSON().then(json => {
const enabled = json["2fa_enabled"];
const verified = sessionStorage.getItem("2fa_verified") === "true";
if (enabled && !verified && window.location.pathname !== "/2fa-verify") {
window.location.href = "/2fa-verify";
}
});
});
</script>
<!--
MEMBERSCRIPT #154
---------------------------------
TWO FACTOR VERIFICATION PAGE SCRIPT
-->
<!-- Include the Buffer polyfill -->
<script src="https://unpkg.com/@otplib/preset-browser@^12.0.0/buffer.js"></script>
<!-- Include the otplib library -->
<script src="https://unpkg.com/@otplib/preset-browser@^12.0.0/index.js"></script>
<script>
document.addEventListener("DOMContentLoaded", async () => {
const memberstack = window.$memberstackDom;
const { data: member } = await memberstack.getCurrentMember();
if (!member) return;
const form = document.querySelector('[data-ms-form="2fa-verification"]');
if (!form) return;
const codeInput = form.querySelector('[data-ms-code="2fa-code"]');
const errorContainer = form.querySelector('[data-ms-error="2fa-code"]');
function showError(message) {
if (errorContainer) {
errorContainer.textContent = message;
errorContainer.style.display = 'block';
} else {
alert(message);
}
}
function clearError() {
if (errorContainer) {
errorContainer.textContent = '';
errorContainer.style.display = 'none';
}
}
form.addEventListener('submit', async (e) => {
e.preventDefault();
e.stopImmediatePropagation(); // important to stop other listeners
clearError();
const code = codeInput.value.trim();
const { data: json } = await memberstack.getMemberJSON();
const secret = json?.["2fa_secret"];
if (!secret || !code) {
const msg = form.getAttribute('data-ms-error-msg-missing') || 'Please enter your 2FA code';
showError(msg);
return;
}
if (otplib.authenticator.check(code, secret)) {
sessionStorage.setItem('2fa_verified', 'true');
window.location.href = '/success';
} else {
const msg = form.getAttribute('data-ms-error-msg-invalid') || 'Oops, the 2FA code is incorrect. Try again.';
showError(msg);
}
});
});
</script>

#153 - Instant Multilingual Site with Google Translate
Make your Webflow site multilingual in minutes with Google Translate and Memberstack
<!-- 💙 MEMBERSCRIPT #153 v1.0 💙 - FREE MULTILINGUAL SITE WITH GOOGLE TRANSLATE -->
<script>
// 1. Inject Google Translate script dynamically
const gtScript = document.createElement('script');
gtScript.src = "//translate.google.com/translate_a/element.js?cb=googleTranslateElementInit";
document.head.appendChild(gtScript);
// 2. Inject CSS to hide Google Translate UI
const style = document.createElement('style');
style.innerHTML = `
body { top: 0px !important; position: static !important; }
.goog-te-banner-frame, .skiptranslate,
#goog-gt-tt, .goog-te-balloon-frame,
.goog-text-highlight {
display: none !important;
background: none !important;
box-shadow: none !important;
}
`;
document.head.appendChild(style);
// 3. Google Translate init
window.googleTranslateElementInit = function () {
new google.translate.TranslateElement({
pageLanguage: 'en',
layout: google.translate.TranslateElement.FloatPosition.TOP_LEFT
}, 'google_translate_element');
};
// 4. Helper to get cookies
function getCookie(name) {
const cookies = document.cookie.split(';');
for (let cookie of cookies) {
const [key, value] = cookie.trim().split('=');
if (key === name) return decodeURIComponent(value);
}
return null;
}
// 5. Language map
const languageMap = new Map([
["af","Afrikaans"], ["sq","Albanian"], ["ar","Arabic"], ["hy","Armenian"],
["az","Azerbaijani"], ["eu","Basque"], ["be","Belarusian"], ["bg","Bulgarian"],
["ca","Catalan"], ["zh-CN","ChineseSimplified"], ["zh-TW","ChineseTraditional"],
["hr","Croatian"], ["cs","Czech"], ["da","Danish"], ["nl","Dutch"], ["de","German"],
["en","English"], ["et","Estonian"], ["tl","Filipino"], ["fi","Finnish"],
["fr","French"], ["gl","Galician"], ["ka","Georgian"], ["el","Greek"],
["ht","Haitian"], ["iw","Hebrew"], ["hi","Hindi"], ["hu","Hungarian"],
["is","Icelandic"], ["id","Indonesian"], ["ga","Irish"], ["it","Italian"],
["ja","Japanese"], ["ko","Korean"], ["lv","Latvian"], ["lt","Lithuanian"],
["mk","Macedonian"], ["ms","Malay"], ["mt","Maltese"], ["no","Norwegian"],
["fa","Persian"], ["pl","Polish"], ["pt","Portuguese"], ["ro","Romanian"],
["ru","Russian"], ["sr","Serbian"], ["sk","Slovak"], ["sl","Slovenian"],
["es","Spanish"], ["sw","Swahili"], ["sv","Swedish"], ["th","Thai"],
["tr","Turkish"], ["uk","Ukrainian"], ["ur","Urdu"], ["vi","Vietnamese"],
["cy","Welsh"], ["yi","Yiddish"]
]);
// 6. Detect current language
let currentLang = getCookie("googtrans")?.split("/").pop() || "en";
// 7. Show language-specific content & set up language switch
document.addEventListener("DOMContentLoaded", function () {
const readableLang = languageMap.get(currentLang);
const langClass = `.languagespecific.${readableLang?.toLowerCase()}specific`;
const fallbackClass = `.languagespecific.englishspecific`;
if (document.querySelector(langClass)) {
document.querySelectorAll(langClass).forEach(el => el.style.display = 'block');
} else {
document.querySelectorAll(fallbackClass).forEach(el => el.style.display = 'block');
}
document.querySelectorAll('[data-ms-code-lang-select]').forEach(el => {
el.addEventListener('click', function (e) {
e.preventDefault();
const selectedLang = this.getAttribute('data-ms-code-lang');
if (selectedLang === 'en') {
document.cookie = "googtrans=;path=/;expires=Thu, 01 Jan 1970 00:00:01 GMT;";
document.cookie = "googtrans=;domain=.webflow.io;path=/;expires=Thu, 01 Jan 1970 00:00:01 GMT;";
window.location.hash = "";
setTimeout(() => location.reload(true), 100);
} else {
const combo = document.querySelector('.goog-te-combo');
if (combo) combo.value = selectedLang;
window.location.hash = "#googtrans(en|" + selectedLang + ")";
location.reload();
}
});
});
});
</script>
<!-- 💙 MEMBERSCRIPT #153 v1.0 💙 - FREE MULTILINGUAL SITE WITH GOOGLE TRANSLATE -->
<script>
// 1. Inject Google Translate script dynamically
const gtScript = document.createElement('script');
gtScript.src = "//translate.google.com/translate_a/element.js?cb=googleTranslateElementInit";
document.head.appendChild(gtScript);
// 2. Inject CSS to hide Google Translate UI
const style = document.createElement('style');
style.innerHTML = `
body { top: 0px !important; position: static !important; }
.goog-te-banner-frame, .skiptranslate,
#goog-gt-tt, .goog-te-balloon-frame,
.goog-text-highlight {
display: none !important;
background: none !important;
box-shadow: none !important;
}
`;
document.head.appendChild(style);
// 3. Google Translate init
window.googleTranslateElementInit = function () {
new google.translate.TranslateElement({
pageLanguage: 'en',
layout: google.translate.TranslateElement.FloatPosition.TOP_LEFT
}, 'google_translate_element');
};
// 4. Helper to get cookies
function getCookie(name) {
const cookies = document.cookie.split(';');
for (let cookie of cookies) {
const [key, value] = cookie.trim().split('=');
if (key === name) return decodeURIComponent(value);
}
return null;
}
// 5. Language map
const languageMap = new Map([
["af","Afrikaans"], ["sq","Albanian"], ["ar","Arabic"], ["hy","Armenian"],
["az","Azerbaijani"], ["eu","Basque"], ["be","Belarusian"], ["bg","Bulgarian"],
["ca","Catalan"], ["zh-CN","ChineseSimplified"], ["zh-TW","ChineseTraditional"],
["hr","Croatian"], ["cs","Czech"], ["da","Danish"], ["nl","Dutch"], ["de","German"],
["en","English"], ["et","Estonian"], ["tl","Filipino"], ["fi","Finnish"],
["fr","French"], ["gl","Galician"], ["ka","Georgian"], ["el","Greek"],
["ht","Haitian"], ["iw","Hebrew"], ["hi","Hindi"], ["hu","Hungarian"],
["is","Icelandic"], ["id","Indonesian"], ["ga","Irish"], ["it","Italian"],
["ja","Japanese"], ["ko","Korean"], ["lv","Latvian"], ["lt","Lithuanian"],
["mk","Macedonian"], ["ms","Malay"], ["mt","Maltese"], ["no","Norwegian"],
["fa","Persian"], ["pl","Polish"], ["pt","Portuguese"], ["ro","Romanian"],
["ru","Russian"], ["sr","Serbian"], ["sk","Slovak"], ["sl","Slovenian"],
["es","Spanish"], ["sw","Swahili"], ["sv","Swedish"], ["th","Thai"],
["tr","Turkish"], ["uk","Ukrainian"], ["ur","Urdu"], ["vi","Vietnamese"],
["cy","Welsh"], ["yi","Yiddish"]
]);
// 6. Detect current language
let currentLang = getCookie("googtrans")?.split("/").pop() || "en";
// 7. Show language-specific content & set up language switch
document.addEventListener("DOMContentLoaded", function () {
const readableLang = languageMap.get(currentLang);
const langClass = `.languagespecific.${readableLang?.toLowerCase()}specific`;
const fallbackClass = `.languagespecific.englishspecific`;
if (document.querySelector(langClass)) {
document.querySelectorAll(langClass).forEach(el => el.style.display = 'block');
} else {
document.querySelectorAll(fallbackClass).forEach(el => el.style.display = 'block');
}
document.querySelectorAll('[data-ms-code-lang-select]').forEach(el => {
el.addEventListener('click', function (e) {
e.preventDefault();
const selectedLang = this.getAttribute('data-ms-code-lang');
if (selectedLang === 'en') {
document.cookie = "googtrans=;path=/;expires=Thu, 01 Jan 1970 00:00:01 GMT;";
document.cookie = "googtrans=;domain=.webflow.io;path=/;expires=Thu, 01 Jan 1970 00:00:01 GMT;";
window.location.hash = "";
setTimeout(() => location.reload(true), 100);
} else {
const combo = document.querySelector('.goog-te-combo');
if (combo) combo.value = selectedLang;
window.location.hash = "#googtrans(en|" + selectedLang + ")";
location.reload();
}
});
});
});
</script>

#152 - OTP Verification via WhatsApp in Webflow
Verify phone numbers via WhatsApp before allowing form submissions in Webflow.
<!-- 💙 MEMBERSCRIPT #152 v1.0 💙 - OTP VERIFICATION VIA WHATSAPP IN WEBFLOW -->
<script>
document.addEventListener("DOMContentLoaded", function () {
const phoneInput = document.querySelector('[data-ms-code="phone-number"]');
const form = phoneInput?.closest("form");
const submitBtn = form?.querySelector('[type="submit"]');
let isVerified = false;
let originalMsFormValue = "signup"; // update this if your form uses another Memberstack action
if (phoneInput && form && submitBtn && window.$WhatsAuthForm) {
// Temporarily disable Memberstack
form.removeAttribute("data-ms-form");
// Create error message
const errorMsg = document.createElement('div');
errorMsg.style.color = 'red';
errorMsg.style.marginTop = '8px';
errorMsg.style.display = 'none';
errorMsg.textContent = '⚠️ Please verify your phone number via WhatsApp.';
phoneInput.parentNode.appendChild(errorMsg);
// Initially disable submit
submitBtn.disabled = true;
submitBtn.style.opacity = 0.6;
submitBtn.style.cursor = 'not-allowed';
// Init WhatsAuth
window.$WhatsAuthForm.init({
inputSelector: '[data-ms-code="phone-number"]',
apiKey: "k07Zj8EwdAIzHzLcLPQh-5jCuREbSKXG", //REPLACE WITH YOUR API KEY
placeholder: phoneInput.getAttribute("data-ms-placeholder") || "",
primaryColor: phoneInput.getAttribute("data-ms-primary-color") || "",
secondaryColor: phoneInput.getAttribute("data-ms-secondary-color") || "",
btnText: phoneInput.getAttribute("data-ms-btn-text") || ""
});
// Watch for success class from WhatsAuth
const observer = new MutationObserver(() => {
if (phoneInput.classList.contains("whatsauth-success") && !isVerified) {
isVerified = true;
// Enable submit
submitBtn.disabled = false;
submitBtn.style.opacity = 1;
submitBtn.style.cursor = 'pointer';
// Hide error
errorMsg.style.display = 'none';
// Re-enable Memberstack
form.setAttribute("data-ms-form", originalMsFormValue);
}
});
observer.observe(phoneInput, { attributes: true, attributeFilter: ["class"] });
// Final form safeguard
form.addEventListener("submit", function (e) {
if (!isVerified) {
e.preventDefault();
errorMsg.style.display = 'block';
}
});
submitBtn.addEventListener("click", function (e) {
if (!isVerified) {
e.preventDefault();
errorMsg.style.display = 'block';
}
});
}
});
</script>
<!-- 💙 MEMBERSCRIPT #152 v1.0 💙 - OTP VERIFICATION VIA WHATSAPP IN WEBFLOW -->
<script>
document.addEventListener("DOMContentLoaded", function () {
const phoneInput = document.querySelector('[data-ms-code="phone-number"]');
const form = phoneInput?.closest("form");
const submitBtn = form?.querySelector('[type="submit"]');
let isVerified = false;
let originalMsFormValue = "signup"; // update this if your form uses another Memberstack action
if (phoneInput && form && submitBtn && window.$WhatsAuthForm) {
// Temporarily disable Memberstack
form.removeAttribute("data-ms-form");
// Create error message
const errorMsg = document.createElement('div');
errorMsg.style.color = 'red';
errorMsg.style.marginTop = '8px';
errorMsg.style.display = 'none';
errorMsg.textContent = '⚠️ Please verify your phone number via WhatsApp.';
phoneInput.parentNode.appendChild(errorMsg);
// Initially disable submit
submitBtn.disabled = true;
submitBtn.style.opacity = 0.6;
submitBtn.style.cursor = 'not-allowed';
// Init WhatsAuth
window.$WhatsAuthForm.init({
inputSelector: '[data-ms-code="phone-number"]',
apiKey: "k07Zj8EwdAIzHzLcLPQh-5jCuREbSKXG", //REPLACE WITH YOUR API KEY
placeholder: phoneInput.getAttribute("data-ms-placeholder") || "",
primaryColor: phoneInput.getAttribute("data-ms-primary-color") || "",
secondaryColor: phoneInput.getAttribute("data-ms-secondary-color") || "",
btnText: phoneInput.getAttribute("data-ms-btn-text") || ""
});
// Watch for success class from WhatsAuth
const observer = new MutationObserver(() => {
if (phoneInput.classList.contains("whatsauth-success") && !isVerified) {
isVerified = true;
// Enable submit
submitBtn.disabled = false;
submitBtn.style.opacity = 1;
submitBtn.style.cursor = 'pointer';
// Hide error
errorMsg.style.display = 'none';
// Re-enable Memberstack
form.setAttribute("data-ms-form", originalMsFormValue);
}
});
observer.observe(phoneInput, { attributes: true, attributeFilter: ["class"] });
// Final form safeguard
form.addEventListener("submit", function (e) {
if (!isVerified) {
e.preventDefault();
errorMsg.style.display = 'block';
}
});
submitBtn.addEventListener("click", function (e) {
if (!isVerified) {
e.preventDefault();
errorMsg.style.display = 'block';
}
});
}
});
</script>

#151 - Onboarding Tour For New Members
Launch a step-by-step product tour the first time a member logs in. Uses Memberstack’s JS API + Intro.js
<!-- 💙 MEMBERSCRIPT #151 v0.1 💙 - ONBOARDING TOUR FOR NEW MEMBERS -->
<script>
// 1. Wait for Memberstack v2 DOM
function ready(fn) {
if (window.$memberstackReady) return fn();
document.addEventListener("memberstack.ready", fn);
}
// 2. Collect all steps from the DOM
function collectSteps() {
// all elements with ms-code-step, in a NodeList
var els = document.querySelectorAll("[ms-code-step]");
// build an array of { order, element, intro }
var steps = Array.prototype.map.call(els, function(el) {
return {
order: parseInt(el.getAttribute("ms-code-step"), 10),
element: el,
intro: el.getAttribute("ms-code-intro") || ""
};
});
// sort by order ascending
return steps.sort(function(a, b) {
return a.order - b.order;
}).map(function(s) {
return { element: s.element, intro: s.intro };
});
}
// 3. Kick off the tour for first-time members
function launchTour(member) {
if (!member || !member.id) return; // only for logged-in
if (localStorage.getItem("ms-code-tour-shown")) return;
// Build steps dynamically
var options = {
steps: collectSteps(),
showProgress: true,
exitOnOverlayClick: false
};
introJs().setOptions(options).start();
localStorage.setItem("ms-code-tour-shown", "true");
}
// 4. Glue it together
ready(function() {
window.$memberstackDom
.getCurrentMember()
.then(function(res) {
launchTour(res.data);
})
.catch(function(err) {
console.error("MS-Code-Tour error:", err);
});
});
</script>
<!-- 💙 MEMBERSCRIPT #151 v0.1 💙 - ONBOARDING TOUR FOR NEW MEMBERS -->
<script>
// 1. Wait for Memberstack v2 DOM
function ready(fn) {
if (window.$memberstackReady) return fn();
document.addEventListener("memberstack.ready", fn);
}
// 2. Collect all steps from the DOM
function collectSteps() {
// all elements with ms-code-step, in a NodeList
var els = document.querySelectorAll("[ms-code-step]");
// build an array of { order, element, intro }
var steps = Array.prototype.map.call(els, function(el) {
return {
order: parseInt(el.getAttribute("ms-code-step"), 10),
element: el,
intro: el.getAttribute("ms-code-intro") || ""
};
});
// sort by order ascending
return steps.sort(function(a, b) {
return a.order - b.order;
}).map(function(s) {
return { element: s.element, intro: s.intro };
});
}
// 3. Kick off the tour for first-time members
function launchTour(member) {
if (!member || !member.id) return; // only for logged-in
if (localStorage.getItem("ms-code-tour-shown")) return;
// Build steps dynamically
var options = {
steps: collectSteps(),
showProgress: true,
exitOnOverlayClick: false
};
introJs().setOptions(options).start();
localStorage.setItem("ms-code-tour-shown", "true");
}
// 4. Glue it together
ready(function() {
window.$memberstackDom
.getCurrentMember()
.then(function(res) {
launchTour(res.data);
})
.catch(function(err) {
console.error("MS-Code-Tour error:", err);
});
});
</script>

#150 - Save and Unsave Items to Your Collection (Pinterest-style)
A simple save/unsave system that lets members bookmark items into personal collections.
<!-- 💙 MEMBERSCRIPT #150 v0.1 💙 - SAVE AND UNSAVE ITEMS TO YOUR COLLECTION PART 1 -->
<script>
document.addEventListener("DOMContentLoaded", async () => {
const ms = window.$memberstackDom;
const member = await ms.getCurrentMember();
const isLoggedIn = !!member;
let savedItems = {};
const fetchSavedItems = async () => {
try {
const { data } = await ms.getMemberJSON();
savedItems = data.savedItems || {};
} catch {
savedItems = {};
}
};
const persistSavedItems = async () => {
try {
await ms.updateMemberJSON({ json: { savedItems } });
} catch (err) {
console.error("Error saving items:", err);
}
};
const updateButtons = () => {
document.querySelectorAll('[ms-code-add-button]').forEach(btn => {
const id = btn.getAttribute('ms-code-save');
const category = btn.getAttribute('ms-code-category');
const exists = savedItems[category]?.some(i => i.id === id);
btn.style.display = exists ? 'none' : 'inline-block';
});
document.querySelectorAll('[ms-code-unsave-button]').forEach(btn => {
const id = btn.getAttribute('ms-code-unsave');
const category = btn.getAttribute('ms-code-category');
const exists = savedItems[category]?.some(i => i.id === id);
btn.style.display = exists ? 'inline-block' : 'none';
});
};
const onAddClick = async (e) => {
e.preventDefault();
if (!isLoggedIn) return;
const btn = e.currentTarget;
const container = btn.closest('[ms-code-save-item]');
const id = btn.getAttribute('ms-code-save');
const category = btn.getAttribute('ms-code-category');
const img = container?.querySelector('[ms-code-image]');
const url = img?.src;
if (!savedItems[category]) savedItems[category] = [];
if (!savedItems[category].some(i => i.id === id)) {
savedItems[category].push({ id, url });
updateButtons();
await persistSavedItems();
}
};
const onUnsaveClick = async (e) => {
e.preventDefault();
if (!isLoggedIn) return;
const btn = e.currentTarget;
const id = btn.getAttribute('ms-code-unsave');
const category = btn.getAttribute('ms-code-category');
if (savedItems[category]) {
savedItems[category] = savedItems[category].filter(i => i.id !== id);
if (savedItems[category].length === 0) delete savedItems[category];
updateButtons();
await persistSavedItems();
}
};
const onDownloadClick = (e) => {
e.preventDefault();
const btn = e.currentTarget;
const container = btn.closest('[ms-code-save-item]');
const img = container?.querySelector('[ms-code-image]');
const url = img?.src;
if (url) {
const a = document.createElement('a');
a.href = url;
a.download = '';
document.body.appendChild(a);
a.click();
a.remove();
}
};
const attachListeners = () => {
document.querySelectorAll('[ms-code-add-button]').forEach(b => b.addEventListener('click', onAddClick));
document.querySelectorAll('[ms-code-unsave-button]').forEach(b => b.addEventListener('click', onUnsaveClick));
document.querySelectorAll('[ms-code-download-button]').forEach(b => b.addEventListener('click', onDownloadClick));
};
await fetchSavedItems();
updateButtons();
attachListeners();
});
</script>
<!-- GENERATE PINTEREST GRID STYLE -->
<script>
$(document).ready(function () {
setTimeout(function() {
function resizeGridItem(item) {
grid = document.getElementsByClassName("grid")[0];
rowHeight = parseInt(window.getComputedStyle(grid).getPropertyValue('grid-auto-rows'));
rowGap = parseInt(window.getComputedStyle(grid).getPropertyValue('grid-row-gap'));
rowSpan = Math.ceil((item.querySelector('.content').getBoundingClientRect().height + rowGap) / (rowHeight + rowGap));
item.style.gridRowEnd = "span " + rowSpan;
}
function resizeAllGridItems() {
allItems = document.getElementsByClassName("item");
for (x = 0; x < allItems.length; x++) {
resizeGridItem(allItems[x]);
}
}
function resizeInstance(instance) {
item = instance.elements[0];
resizeGridItem(item);
}
window.onload = resizeAllGridItems();
window.addEventListener("resize", resizeAllGridItems);
allItems = document.getElementsByClassName("item");
for (x = 0; x < allItems.length; x++) {
imagesLoaded(allItems[x], resizeInstance);
}
setTimeout(function() { resizeInstance() }, 100);
}, 800);
})
</script>
<!-- 💙 MEMBERSCRIPT #150 v0.1 💙 - SAVE AND UNSAVE ITEMS TO YOUR COLLECTION PART 2 -->
<script>
document.addEventListener("DOMContentLoaded", async () => {
const ms = window.$memberstackDom;
const wrapper = document.querySelector('[ms-code-collections-wrapper]');
const template = document.querySelector('[ms-code-folder-template]') || document.querySelector('[ms-code-folder]');
const emptyState = document.querySelector('[ms-code-empty]');
if (!wrapper || !template) return;
let member;
try {
member = await ms.getCurrentMember();
} catch {
wrapper.textContent = "Please log in to view your collections.";
return;
}
let savedItems = {};
try {
const { data } = await ms.getMemberJSON();
savedItems = data?.savedItems || {};
} catch {
wrapper.textContent = "Could not load your collections.";
return;
}
if (Object.keys(savedItems).length === 0) {
wrapper.innerHTML = '';
if (emptyState) emptyState.style.display = 'block';
return;
}
if (emptyState) emptyState.style.display = 'none';
wrapper.innerHTML = '';
const persistSavedItems = async () => {
try {
await ms.updateMemberJSON({ json: { savedItems } });
} catch (err) {
console.error("Failed to save", err);
}
};
const updateButtons = (modal, id, category) => {
const addBtn = modal.querySelector('[ms-code-add-button]');
const unsaveBtn = modal.querySelector('[ms-code-unsave-button]');
const exists = savedItems[category]?.some(item => item.id === id);
addBtn.style.display = exists ? 'none' : 'inline-block';
unsaveBtn.style.display = exists ? 'inline-block' : 'none';
};
Object.entries(savedItems).forEach(([category, items]) => {
const folderClone = template.cloneNode(true);
const titleEl = folderClone.querySelector('[ms-code-folder-title]');
if (titleEl) titleEl.textContent = `${category} (${items.length})`;
const imageContainer = folderClone.querySelector('[ms-code-folder-items]');
const imageTemplate = folderClone.querySelector('[ms-code-folder-image]');
if (imageTemplate) imageTemplate.style.display = 'none';
const modal = folderClone.querySelector('[ms-code-modal]');
const modalImg = folderClone.querySelector('[ms-code-modal-img]');
const modalClose = folderClone.querySelector('[ms-code-modal-close]');
const addButton = folderClone.querySelector('[ms-code-add-button]');
const unsaveButton = folderClone.querySelector('[ms-code-unsave-button]');
const downloadButton = folderClone.querySelector('[ms-code-download-button]');
const hiddenImage = folderClone.querySelector('[ms-code-image]');
items.forEach(item => {
const imgClone = imageTemplate.cloneNode(true);
imgClone.src = item.url;
imgClone.alt = category;
imgClone.style.display = 'block';
imgClone.style.objectFit = 'cover';
imgClone.style.width = '100%';
imgClone.style.height = 'auto';
imgClone.style.maxWidth = '100%';
imgClone.addEventListener('click', () => {
if (modal && modalImg) {
modalImg.src = item.url;
if (hiddenImage) hiddenImage.src = item.url;
const id = item.id;
addButton.onclick = async (e) => {
e.preventDefault();
savedItems[category] = savedItems[category] || [];
if (!savedItems[category].some(i => i.id === id)) {
savedItems[category].push({ id, url: item.url });
await persistSavedItems();
updateButtons(modal, id, category);
}
};
unsaveButton.onclick = async (e) => {
e.preventDefault();
savedItems[category] = savedItems[category].filter(i => i.id !== id);
if (savedItems[category].length === 0) delete savedItems[category];
await persistSavedItems();
modal.style.display = 'none';
location.reload();
};
downloadButton.onclick = (e) => {
e.preventDefault();
const a = document.createElement('a');
a.href = item.url;
a.download = '';
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
};
updateButtons(modal, id, category);
}
modal.style.display = 'flex';
});
imageContainer.appendChild(imgClone);
});
if (modal && modalClose) {
modalClose.addEventListener('click', () => {
modal.style.display = 'none';
if (modalImg) modalImg.src = '';
});
}
wrapper.appendChild(folderClone);
});
});
</script>
<!-- 💙 MEMBERSCRIPT #150 v0.1 💙 - SAVE AND UNSAVE ITEMS TO YOUR COLLECTION PART 1 -->
<script>
document.addEventListener("DOMContentLoaded", async () => {
const ms = window.$memberstackDom;
const member = await ms.getCurrentMember();
const isLoggedIn = !!member;
let savedItems = {};
const fetchSavedItems = async () => {
try {
const { data } = await ms.getMemberJSON();
savedItems = data.savedItems || {};
} catch {
savedItems = {};
}
};
const persistSavedItems = async () => {
try {
await ms.updateMemberJSON({ json: { savedItems } });
} catch (err) {
console.error("Error saving items:", err);
}
};
const updateButtons = () => {
document.querySelectorAll('[ms-code-add-button]').forEach(btn => {
const id = btn.getAttribute('ms-code-save');
const category = btn.getAttribute('ms-code-category');
const exists = savedItems[category]?.some(i => i.id === id);
btn.style.display = exists ? 'none' : 'inline-block';
});
document.querySelectorAll('[ms-code-unsave-button]').forEach(btn => {
const id = btn.getAttribute('ms-code-unsave');
const category = btn.getAttribute('ms-code-category');
const exists = savedItems[category]?.some(i => i.id === id);
btn.style.display = exists ? 'inline-block' : 'none';
});
};
const onAddClick = async (e) => {
e.preventDefault();
if (!isLoggedIn) return;
const btn = e.currentTarget;
const container = btn.closest('[ms-code-save-item]');
const id = btn.getAttribute('ms-code-save');
const category = btn.getAttribute('ms-code-category');
const img = container?.querySelector('[ms-code-image]');
const url = img?.src;
if (!savedItems[category]) savedItems[category] = [];
if (!savedItems[category].some(i => i.id === id)) {
savedItems[category].push({ id, url });
updateButtons();
await persistSavedItems();
}
};
const onUnsaveClick = async (e) => {
e.preventDefault();
if (!isLoggedIn) return;
const btn = e.currentTarget;
const id = btn.getAttribute('ms-code-unsave');
const category = btn.getAttribute('ms-code-category');
if (savedItems[category]) {
savedItems[category] = savedItems[category].filter(i => i.id !== id);
if (savedItems[category].length === 0) delete savedItems[category];
updateButtons();
await persistSavedItems();
}
};
const onDownloadClick = (e) => {
e.preventDefault();
const btn = e.currentTarget;
const container = btn.closest('[ms-code-save-item]');
const img = container?.querySelector('[ms-code-image]');
const url = img?.src;
if (url) {
const a = document.createElement('a');
a.href = url;
a.download = '';
document.body.appendChild(a);
a.click();
a.remove();
}
};
const attachListeners = () => {
document.querySelectorAll('[ms-code-add-button]').forEach(b => b.addEventListener('click', onAddClick));
document.querySelectorAll('[ms-code-unsave-button]').forEach(b => b.addEventListener('click', onUnsaveClick));
document.querySelectorAll('[ms-code-download-button]').forEach(b => b.addEventListener('click', onDownloadClick));
};
await fetchSavedItems();
updateButtons();
attachListeners();
});
</script>
<!-- GENERATE PINTEREST GRID STYLE -->
<script>
$(document).ready(function () {
setTimeout(function() {
function resizeGridItem(item) {
grid = document.getElementsByClassName("grid")[0];
rowHeight = parseInt(window.getComputedStyle(grid).getPropertyValue('grid-auto-rows'));
rowGap = parseInt(window.getComputedStyle(grid).getPropertyValue('grid-row-gap'));
rowSpan = Math.ceil((item.querySelector('.content').getBoundingClientRect().height + rowGap) / (rowHeight + rowGap));
item.style.gridRowEnd = "span " + rowSpan;
}
function resizeAllGridItems() {
allItems = document.getElementsByClassName("item");
for (x = 0; x < allItems.length; x++) {
resizeGridItem(allItems[x]);
}
}
function resizeInstance(instance) {
item = instance.elements[0];
resizeGridItem(item);
}
window.onload = resizeAllGridItems();
window.addEventListener("resize", resizeAllGridItems);
allItems = document.getElementsByClassName("item");
for (x = 0; x < allItems.length; x++) {
imagesLoaded(allItems[x], resizeInstance);
}
setTimeout(function() { resizeInstance() }, 100);
}, 800);
})
</script>
<!-- 💙 MEMBERSCRIPT #150 v0.1 💙 - SAVE AND UNSAVE ITEMS TO YOUR COLLECTION PART 2 -->
<script>
document.addEventListener("DOMContentLoaded", async () => {
const ms = window.$memberstackDom;
const wrapper = document.querySelector('[ms-code-collections-wrapper]');
const template = document.querySelector('[ms-code-folder-template]') || document.querySelector('[ms-code-folder]');
const emptyState = document.querySelector('[ms-code-empty]');
if (!wrapper || !template) return;
let member;
try {
member = await ms.getCurrentMember();
} catch {
wrapper.textContent = "Please log in to view your collections.";
return;
}
let savedItems = {};
try {
const { data } = await ms.getMemberJSON();
savedItems = data?.savedItems || {};
} catch {
wrapper.textContent = "Could not load your collections.";
return;
}
if (Object.keys(savedItems).length === 0) {
wrapper.innerHTML = '';
if (emptyState) emptyState.style.display = 'block';
return;
}
if (emptyState) emptyState.style.display = 'none';
wrapper.innerHTML = '';
const persistSavedItems = async () => {
try {
await ms.updateMemberJSON({ json: { savedItems } });
} catch (err) {
console.error("Failed to save", err);
}
};
const updateButtons = (modal, id, category) => {
const addBtn = modal.querySelector('[ms-code-add-button]');
const unsaveBtn = modal.querySelector('[ms-code-unsave-button]');
const exists = savedItems[category]?.some(item => item.id === id);
addBtn.style.display = exists ? 'none' : 'inline-block';
unsaveBtn.style.display = exists ? 'inline-block' : 'none';
};
Object.entries(savedItems).forEach(([category, items]) => {
const folderClone = template.cloneNode(true);
const titleEl = folderClone.querySelector('[ms-code-folder-title]');
if (titleEl) titleEl.textContent = `${category} (${items.length})`;
const imageContainer = folderClone.querySelector('[ms-code-folder-items]');
const imageTemplate = folderClone.querySelector('[ms-code-folder-image]');
if (imageTemplate) imageTemplate.style.display = 'none';
const modal = folderClone.querySelector('[ms-code-modal]');
const modalImg = folderClone.querySelector('[ms-code-modal-img]');
const modalClose = folderClone.querySelector('[ms-code-modal-close]');
const addButton = folderClone.querySelector('[ms-code-add-button]');
const unsaveButton = folderClone.querySelector('[ms-code-unsave-button]');
const downloadButton = folderClone.querySelector('[ms-code-download-button]');
const hiddenImage = folderClone.querySelector('[ms-code-image]');
items.forEach(item => {
const imgClone = imageTemplate.cloneNode(true);
imgClone.src = item.url;
imgClone.alt = category;
imgClone.style.display = 'block';
imgClone.style.objectFit = 'cover';
imgClone.style.width = '100%';
imgClone.style.height = 'auto';
imgClone.style.maxWidth = '100%';
imgClone.addEventListener('click', () => {
if (modal && modalImg) {
modalImg.src = item.url;
if (hiddenImage) hiddenImage.src = item.url;
const id = item.id;
addButton.onclick = async (e) => {
e.preventDefault();
savedItems[category] = savedItems[category] || [];
if (!savedItems[category].some(i => i.id === id)) {
savedItems[category].push({ id, url: item.url });
await persistSavedItems();
updateButtons(modal, id, category);
}
};
unsaveButton.onclick = async (e) => {
e.preventDefault();
savedItems[category] = savedItems[category].filter(i => i.id !== id);
if (savedItems[category].length === 0) delete savedItems[category];
await persistSavedItems();
modal.style.display = 'none';
location.reload();
};
downloadButton.onclick = (e) => {
e.preventDefault();
const a = document.createElement('a');
a.href = item.url;
a.download = '';
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
};
updateButtons(modal, id, category);
}
modal.style.display = 'flex';
});
imageContainer.appendChild(imgClone);
});
if (modal && modalClose) {
modalClose.addEventListener('click', () => {
modal.style.display = 'none';
if (modalImg) modalImg.src = '';
});
}
wrapper.appendChild(folderClone);
});
});
</script>

#149 - Favicon for Dark/Light Mode
Use this script to update your website's favicon based on the user's system color scheme preference.
<!-- 💙 MEMBERSCRIPT #149 v0.1 💙 - FAVICON FOR DARK/LIGHT MODE -->
<script>
// Helper: Retrieve or create a favicon element
function getFaviconElement() {
let favicon = document.querySelector('link[rel="icon"]') ||
document.querySelector('link[rel="shortcut icon"]');
if (!favicon) {
favicon = document.createElement('link');
favicon.rel = 'icon';
document.head.appendChild(favicon);
}
return favicon;
}
// Function to update the favicon based on dark mode
function updateFavicon(e) {
const darkModeOn = e ? e.matches : window.matchMedia('(prefers-color-scheme: dark)').matches;
const favicon = getFaviconElement();
// Update these paths to your favicon assets in Webflow’s Asset Manager or a CDN
favicon.href = darkModeOn
? 'https://cdn.prod.website-files.com/67fcff014042c2f5945437c0/67fd000f85b2a9f281a373ca_Dark%20Mode%20Logo.png'
: 'https://cdn.prod.website-files.com/67fcff014042c2f5945437c0/67fd000f1c2fa3cebee1b150_Light%20Mode%20Logo.png';
}
// Initialize the favicon update on page load
updateFavicon();
// Listen for changes in the dark mode media query
const darkModeMediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
if (typeof darkModeMediaQuery.addEventListener === 'function') {
darkModeMediaQuery.addEventListener('change', updateFavicon);
} else if (typeof darkModeMediaQuery.addListener === 'function') {
darkModeMediaQuery.addListener(updateFavicon);
}
</script>
<!-- 💙 MEMBERSCRIPT #149 v0.1 💙 - FAVICON FOR DARK/LIGHT MODE -->
<script>
// Helper: Retrieve or create a favicon element
function getFaviconElement() {
let favicon = document.querySelector('link[rel="icon"]') ||
document.querySelector('link[rel="shortcut icon"]');
if (!favicon) {
favicon = document.createElement('link');
favicon.rel = 'icon';
document.head.appendChild(favicon);
}
return favicon;
}
// Function to update the favicon based on dark mode
function updateFavicon(e) {
const darkModeOn = e ? e.matches : window.matchMedia('(prefers-color-scheme: dark)').matches;
const favicon = getFaviconElement();
// Update these paths to your favicon assets in Webflow’s Asset Manager or a CDN
favicon.href = darkModeOn
? 'https://cdn.prod.website-files.com/67fcff014042c2f5945437c0/67fd000f85b2a9f281a373ca_Dark%20Mode%20Logo.png'
: 'https://cdn.prod.website-files.com/67fcff014042c2f5945437c0/67fd000f1c2fa3cebee1b150_Light%20Mode%20Logo.png';
}
// Initialize the favicon update on page load
updateFavicon();
// Listen for changes in the dark mode media query
const darkModeMediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
if (typeof darkModeMediaQuery.addEventListener === 'function') {
darkModeMediaQuery.addEventListener('change', updateFavicon);
} else if (typeof darkModeMediaQuery.addListener === 'function') {
darkModeMediaQuery.addListener(updateFavicon);
}
</script>

#148 - Disable Webflow Form Success Window
Use this script to override Webflow's default form submission behavior by hiding the success message.
<!-- 💙 MEMBERSCRIPT #148 v0.1 💙 - DISABLE WEBFLOW FORM SUCCESS WINDOW -->
<script>
document.addEventListener('DOMContentLoaded', function () {
const form = document.querySelector('[ms-code-form="form"]');
const successEl = document.querySelector('[ms-code-success="true"]');
if (!form || !successEl) return;
const observer = new MutationObserver(() => {
const isVisible = window.getComputedStyle(successEl).display !== 'none';
if (isVisible) {
successEl.style.display = 'none';
form.style.display = 'block';
}
});
observer.observe(successEl, { attributes: true, attributeFilter: ['style'] });
// Cleanup observer when done (optional, for performance)
form.addEventListener('w-form-success', () => {
setTimeout(() => {
observer.disconnect();
}, 100);
});
});
</script>
<!-- 💙 MEMBERSCRIPT #148 v0.1 💙 - DISABLE WEBFLOW FORM SUCCESS WINDOW -->
<script>
document.addEventListener('DOMContentLoaded', function () {
const form = document.querySelector('[ms-code-form="form"]');
const successEl = document.querySelector('[ms-code-success="true"]');
if (!form || !successEl) return;
const observer = new MutationObserver(() => {
const isVisible = window.getComputedStyle(successEl).display !== 'none';
if (isVisible) {
successEl.style.display = 'none';
form.style.display = 'block';
}
});
observer.observe(successEl, { attributes: true, attributeFilter: ['style'] });
// Cleanup observer when done (optional, for performance)
form.addEventListener('w-form-success', () => {
setTimeout(() => {
observer.disconnect();
}, 100);
});
});
</script>

#147 - Age Verification Popup with Cookies in Webflow
Use this script to add an age verification popup to your Webflow site.
<!-- 💙 MEMBERSCRIPT #147 v0.1 💙 - AGE VERIFICATION POPUP WITH COOKIES -->
<script>
// Simple cookie helper functions
function setCookie(name, value, days) {
const date = new Date();
date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
document.cookie = name + "=" + value + "; expires=" + date.toUTCString() + "; path=/";
}
function getCookie(name) {
const cname = name + "=";
const decodedCookie = decodeURIComponent(document.cookie);
const cookies = decodedCookie.split(';');
for (let i = 0; i < cookies.length; i++) {
let c = cookies[i].trim();
if (c.indexOf(cname) === 0) {
return c.substring(cname.length, c.length);
}
}
return "";
}
document.addEventListener('DOMContentLoaded', function() {
// Select the age gate element via ms-code attribute
const ageGateEl = document.querySelector('[ms-code-agegate]');
// On page load: if the cookie exists, hide the age gate
if (getCookie('ms-code-ageVerified') === 'true') {
if (ageGateEl) ageGateEl.style.display = 'none';
}
// Listen for clicks on elements with ms-code-click
document.addEventListener('click', function(event) {
if (event.target.closest('[ms-code-click="confirmAge"]')) {
setCookie('ms-code-ageVerified', 'true', 30);
if (ageGateEl) ageGateEl.style.display = 'none';
} else if (event.target.closest('[ms-code-click="denyAge"]')) {
window.location.href = 'https://age-verification-popup-with-cookies.webflow.io/access-denied'; // Change URL as needed
}
});
});
</script>
<!-- 💙 MEMBERSCRIPT #147 v0.1 💙 - AGE VERIFICATION POPUP WITH COOKIES -->
<script>
// Simple cookie helper functions
function setCookie(name, value, days) {
const date = new Date();
date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
document.cookie = name + "=" + value + "; expires=" + date.toUTCString() + "; path=/";
}
function getCookie(name) {
const cname = name + "=";
const decodedCookie = decodeURIComponent(document.cookie);
const cookies = decodedCookie.split(';');
for (let i = 0; i < cookies.length; i++) {
let c = cookies[i].trim();
if (c.indexOf(cname) === 0) {
return c.substring(cname.length, c.length);
}
}
return "";
}
document.addEventListener('DOMContentLoaded', function() {
// Select the age gate element via ms-code attribute
const ageGateEl = document.querySelector('[ms-code-agegate]');
// On page load: if the cookie exists, hide the age gate
if (getCookie('ms-code-ageVerified') === 'true') {
if (ageGateEl) ageGateEl.style.display = 'none';
}
// Listen for clicks on elements with ms-code-click
document.addEventListener('click', function(event) {
if (event.target.closest('[ms-code-click="confirmAge"]')) {
setCookie('ms-code-ageVerified', 'true', 30);
if (ageGateEl) ageGateEl.style.display = 'none';
} else if (event.target.closest('[ms-code-click="denyAge"]')) {
window.location.href = 'https://age-verification-popup-with-cookies.webflow.io/access-denied'; // Change URL as needed
}
});
});
</script>

#146 - Stop Videos from Playing When A Modal Closes
Automatically stop video playback when closing modals in Webflow.
<!-- 💙 MEMBERSCRIPT #146 v0.1 💙 - STOP VIDEOS FROM PLAYING WHEN A MODAL CLOSES -->
<script>
document.addEventListener('DOMContentLoaded', function() {
// Select all modals on the page
var modals = document.querySelectorAll('[data-ms-modal="modal"]');
function initializeModal(modal) {
var iframe = modal.querySelector('iframe');
if (!iframe) return; // If no iframe found, do nothing
var originalSrc = iframe.dataset.src || iframe.src;
// Function to stop video playback
function stopVideo() {
iframe.src = "";
setTimeout(() => {
iframe.src = originalSrc;
}, 100);
}
// Attach event listeners to all close buttons inside the modal
var closeBtns = modal.querySelectorAll('[data-ms-modal="close"]');
closeBtns.forEach(function(closeBtn) {
closeBtn.addEventListener('click', function(e) {
e.preventDefault();
stopVideo();
});
});
// Also close the modal when clicking outside the content
modal.addEventListener('click', function(e) {
if (e.target === modal) {
stopVideo();
}
});
}
// Initialize all modals
modals.forEach(initializeModal);
});
</script>
<!-- 💙 MEMBERSCRIPT #146 v0.1 💙 - STOP VIDEOS FROM PLAYING WHEN A MODAL CLOSES -->
<script>
document.addEventListener('DOMContentLoaded', function() {
// Select all modals on the page
var modals = document.querySelectorAll('[data-ms-modal="modal"]');
function initializeModal(modal) {
var iframe = modal.querySelector('iframe');
if (!iframe) return; // If no iframe found, do nothing
var originalSrc = iframe.dataset.src || iframe.src;
// Function to stop video playback
function stopVideo() {
iframe.src = "";
setTimeout(() => {
iframe.src = originalSrc;
}, 100);
}
// Attach event listeners to all close buttons inside the modal
var closeBtns = modal.querySelectorAll('[data-ms-modal="close"]');
closeBtns.forEach(function(closeBtn) {
closeBtn.addEventListener('click', function(e) {
e.preventDefault();
stopVideo();
});
});
// Also close the modal when clicking outside the content
modal.addEventListener('click', function(e) {
if (e.target === modal) {
stopVideo();
}
});
}
// Initialize all modals
modals.forEach(initializeModal);
});
</script>

#145 - Automatically Save & Prefill Forms
Automatically save and prefill forms in a browsers localStorage upon form submission.
<!-- 💙 MEMBERSCRIPT #145 v0.1 💙 - HOW TO PRE-FILL FORM INPUT FIELDS AT PAGE LOAD -->
<script>
document.addEventListener('DOMContentLoaded', function() {
// Function to store form data in localStorage
function storeFormData() {
const fields = document.querySelectorAll('input[ms-code-field-id], textarea[ms-code-field-id], select[ms-code-field-id]');
fields.forEach(function(field) {
const fieldId = field.getAttribute('ms-code-field-id');
const value = field.value.trim();
if (value) {
localStorage.setItem(fieldId, value);
}
});
}
// Function to pre-fill form fields with stored data
function preFillForm() {
const fields = document.querySelectorAll('input[ms-code-field-id], textarea[ms-code-field-id], select[ms-code-field-id]');
fields.forEach(function(field) {
const fieldId = field.getAttribute('ms-code-field-id');
const storedValue = localStorage.getItem(fieldId);
if (storedValue) {
field.value = storedValue;
}
});
}
// Handle form submission
const form = document.querySelector('#my-form, form[ms-code-form-id]');
if (form) {
form.addEventListener('submit', function(event) {
event.preventDefault(); // Prevent default form submission
storeFormData();
// Refresh the page after storing data
setTimeout(function() {
location.reload();
}, 500); // Short delay to simulate form submission
});
}
// Pre-fill form fields when the page loads
preFillForm();
});
</script>
<!-- 💙 MEMBERSCRIPT #145 v0.1 💙 - HOW TO PRE-FILL FORM INPUT FIELDS AT PAGE LOAD -->
<script>
document.addEventListener('DOMContentLoaded', function() {
// Function to store form data in localStorage
function storeFormData() {
const fields = document.querySelectorAll('input[ms-code-field-id], textarea[ms-code-field-id], select[ms-code-field-id]');
fields.forEach(function(field) {
const fieldId = field.getAttribute('ms-code-field-id');
const value = field.value.trim();
if (value) {
localStorage.setItem(fieldId, value);
}
});
}
// Function to pre-fill form fields with stored data
function preFillForm() {
const fields = document.querySelectorAll('input[ms-code-field-id], textarea[ms-code-field-id], select[ms-code-field-id]');
fields.forEach(function(field) {
const fieldId = field.getAttribute('ms-code-field-id');
const storedValue = localStorage.getItem(fieldId);
if (storedValue) {
field.value = storedValue;
}
});
}
// Handle form submission
const form = document.querySelector('#my-form, form[ms-code-form-id]');
if (form) {
form.addEventListener('submit', function(event) {
event.preventDefault(); // Prevent default form submission
storeFormData();
// Refresh the page after storing data
setTimeout(function() {
location.reload();
}, 500); // Short delay to simulate form submission
});
}
// Pre-fill form fields when the page loads
preFillForm();
});
</script>

#144 - Track Users Login History & Active Users
Automatically track a members login history, keep a login streak and total visits
<!-- 💙 MEMBERSCRIPT #144 v0.1 💙 - TRACK A USERS LOGIN HISTORY & ACTIVE USERS -->
<script>
(function(){
const memberstack = window.$memberstackDom;
// Helper: Execute callback when Memberstack is ready
function onMemberstackReady(cb) {
if (window.$memberstackReady) {
cb();
} else {
document.addEventListener("memberstack.ready", cb);
}
}
async function initTracking() {
// Check if a member is logged in (via localStorage)
const currentUser = JSON.parse(localStorage.getItem("_ms-mem") || "null");
if (!currentUser) {
console.warn("No logged-in member found. Tracking not applied.");
return;
}
// Retrieve member metadata
const memberJson = await memberstack.getMemberJSON();
let metadata = memberJson.data || {};
// Ensure userVisits exists as an array
metadata.userVisits = Array.isArray(metadata.userVisits) ? metadata.userVisits : [];
// Use ISO date (YYYY-MM-DD) to record one visit per day
const today = new Date().toISOString().split("T")[0];
if (!metadata.userVisits.includes(today)) {
metadata.userVisits.push(today);
}
// Helper: Compute consecutive login streak from userVisits
function computeStreak(visits) {
if (!visits.length) return 0;
// Ensure dates are unique and sorted ascending
const uniqueVisits = [...new Set(visits)].sort();
let streak = 1;
let currentDate = new Date(uniqueVisits[uniqueVisits.length - 1]);
for (let i = uniqueVisits.length - 2; i >= 0; i--) {
const prevDate = new Date(uniqueVisits[i]);
const diffDays = Math.floor((currentDate - prevDate) / (1000 * 60 * 60 * 24));
if (diffDays === 1) {
streak++;
currentDate = prevDate;
} else {
break;
}
}
return streak;
}
// Calculate the login streak and total visits
metadata.loginStreak = computeStreak(metadata.userVisits);
metadata.totalVisits = metadata.userVisits.length;
// Update Memberstack metadata
await memberstack.updateMemberJSON({ json: metadata });
console.log("User visits:", metadata.userVisits);
console.log("Login streak:", metadata.loginStreak);
console.log("Total visits:", metadata.totalVisits);
}
onMemberstackReady(initTracking);
})();
</script>
<!-- 💙 MEMBERSCRIPT #144 v0.1 💙 - TRACK A USERS LOGIN HISTORY & ACTIVE USERS -->
<script>
(function(){
const memberstack = window.$memberstackDom;
// Helper: Execute callback when Memberstack is ready
function onMemberstackReady(cb) {
if (window.$memberstackReady) {
cb();
} else {
document.addEventListener("memberstack.ready", cb);
}
}
async function initTracking() {
// Check if a member is logged in (via localStorage)
const currentUser = JSON.parse(localStorage.getItem("_ms-mem") || "null");
if (!currentUser) {
console.warn("No logged-in member found. Tracking not applied.");
return;
}
// Retrieve member metadata
const memberJson = await memberstack.getMemberJSON();
let metadata = memberJson.data || {};
// Ensure userVisits exists as an array
metadata.userVisits = Array.isArray(metadata.userVisits) ? metadata.userVisits : [];
// Use ISO date (YYYY-MM-DD) to record one visit per day
const today = new Date().toISOString().split("T")[0];
if (!metadata.userVisits.includes(today)) {
metadata.userVisits.push(today);
}
// Helper: Compute consecutive login streak from userVisits
function computeStreak(visits) {
if (!visits.length) return 0;
// Ensure dates are unique and sorted ascending
const uniqueVisits = [...new Set(visits)].sort();
let streak = 1;
let currentDate = new Date(uniqueVisits[uniqueVisits.length - 1]);
for (let i = uniqueVisits.length - 2; i >= 0; i--) {
const prevDate = new Date(uniqueVisits[i]);
const diffDays = Math.floor((currentDate - prevDate) / (1000 * 60 * 60 * 24));
if (diffDays === 1) {
streak++;
currentDate = prevDate;
} else {
break;
}
}
return streak;
}
// Calculate the login streak and total visits
metadata.loginStreak = computeStreak(metadata.userVisits);
metadata.totalVisits = metadata.userVisits.length;
// Update Memberstack metadata
await memberstack.updateMemberJSON({ json: metadata });
console.log("User visits:", metadata.userVisits);
console.log("Login streak:", metadata.loginStreak);
console.log("Total visits:", metadata.totalVisits);
}
onMemberstackReady(initTracking);
})();
</script>

#143 - Initial Based Profile Avatar
Generate a custom avatar with initials when a member has no profile picture.
<!-- 💙 MEMBERSCRIPT #143 v0.1 💙 - GENERATE INITIALS BASED AVATAR -->
<script>
document.addEventListener("DOMContentLoaded", function () {
const checkMemberstack = setInterval(() => {
if (window.$memberstackDom) {
clearInterval(checkMemberstack);
window.$memberstackDom.getCurrentMember().then(({ data }) => {
if (!data) return console.log("No member data (logged out)");
const profileImage = document.querySelector('[data-ms-member="profile-image"]');
const avatarWrapper = document.querySelector('[data-ms-code="avatar"]');
const initialsDiv = avatarWrapper?.querySelector('.ms-avatar-initial');
if (data.profileImage) {
profileImage?.style.setProperty("display", "block");
avatarWrapper?.style.setProperty("display", "none");
} else {
profileImage?.style.setProperty("display", "none");
avatarWrapper?.style.setProperty("display", "flex");
// Get initials from available fields
const first = data.customFields["first-name"]?.trim().charAt(0).toUpperCase() || "";
const last = data.customFields["last-name"]?.trim().charAt(0).toUpperCase() || "";
let initials = first + last;
if (!initials) {
const fullName = data.customFields["name"]?.trim().split(" ") || [];
initials = fullName.length > 1
? (fullName[0].charAt(0) + fullName[1].charAt(0)).toUpperCase()
: fullName[0]?.charAt(0).toUpperCase() || "?";
}
if (initialsDiv) {
initialsDiv.textContent = initials;
} else {
avatarWrapper.innerHTML = `${initials}`;
}
}
}).catch(console.error);
}
}, 100);
});
</script>
<!-- 💙 MEMBERSCRIPT #143 v0.1 💙 - GENERATE INITIALS BASED AVATAR -->
<script>
document.addEventListener("DOMContentLoaded", function () {
const checkMemberstack = setInterval(() => {
if (window.$memberstackDom) {
clearInterval(checkMemberstack);
window.$memberstackDom.getCurrentMember().then(({ data }) => {
if (!data) return console.log("No member data (logged out)");
const profileImage = document.querySelector('[data-ms-member="profile-image"]');
const avatarWrapper = document.querySelector('[data-ms-code="avatar"]');
const initialsDiv = avatarWrapper?.querySelector('.ms-avatar-initial');
if (data.profileImage) {
profileImage?.style.setProperty("display", "block");
avatarWrapper?.style.setProperty("display", "none");
} else {
profileImage?.style.setProperty("display", "none");
avatarWrapper?.style.setProperty("display", "flex");
// Get initials from available fields
const first = data.customFields["first-name"]?.trim().charAt(0).toUpperCase() || "";
const last = data.customFields["last-name"]?.trim().charAt(0).toUpperCase() || "";
let initials = first + last;
if (!initials) {
const fullName = data.customFields["name"]?.trim().split(" ") || [];
initials = fullName.length > 1
? (fullName[0].charAt(0) + fullName[1].charAt(0)).toUpperCase()
: fullName[0]?.charAt(0).toUpperCase() || "?";
}
if (initialsDiv) {
initialsDiv.textContent = initials;
} else {
avatarWrapper.innerHTML = `${initials}`;
}
}
}).catch(console.error);
}
}, 100);
});
</script>

#142 - Embed PDFs For Webflow
Easily embed a PDF on your Webflow site - for free, without any custom code.
<!-- 💙 MEMBERSCRIPT #142 v0.1 💙 - EMBED PDFS IN WEBFLOW -->
<script>
document.addEventListener("DOMContentLoaded", function () {
const pdfElements = document.querySelectorAll('[ms-code-pdf-src]');
pdfElements.forEach(function (element) {
const src = element.getAttribute('ms-code-pdf-src');
const height = element.getAttribute('ms-code-pdf-height') || '500px';
const iframe = document.createElement('iframe');
iframe.src = src;
iframe.style.width = '100%';
iframe.style.height = height;
iframe.style.border = 'none';
// Set the iframe to block to remove any inline element gaps
iframe.style.display = 'block';
iframe.setAttribute('scrolling', 'auto');
element.innerHTML = '';
element.appendChild(iframe);
});
});
</script>
<!-- 💙 MEMBERSCRIPT #142 v0.1 💙 - EMBED PDFS IN WEBFLOW -->
<script>
document.addEventListener("DOMContentLoaded", function () {
const pdfElements = document.querySelectorAll('[ms-code-pdf-src]');
pdfElements.forEach(function (element) {
const src = element.getAttribute('ms-code-pdf-src');
const height = element.getAttribute('ms-code-pdf-height') || '500px';
const iframe = document.createElement('iframe');
iframe.src = src;
iframe.style.width = '100%';
iframe.style.height = height;
iframe.style.border = 'none';
// Set the iframe to block to remove any inline element gaps
iframe.style.display = 'block';
iframe.setAttribute('scrolling', 'auto');
element.innerHTML = '';
element.appendChild(iframe);
});
});
</script>

#141 - Démarrer l'intégration de YouTube à un moment précis
Activez les liens partageables et lancez la lecture des vidéos à une heure donnée.
<!-- 💙 MEMBERSCRIPT #141 v0.1 💙 - START YOUTUBE VIDEO AT SPECIFIC TIME -->
<script>
(function() {
// Function to get URL parameters
function getUrlParameter(name) {
name = name.replace(/[\[]/, '\\[').replace(/[\]]/, '\\]');
var regex = new RegExp('[\\?&]' + name + '=([^]*)');
var results = regex.exec(location.search);
return results === null ? '' : decodeURIComponent(results[1].replace(/\+/g, ' '));
}
// Function to update YouTube embed src within Embedly iframe
function updateYouTubeEmbed(embedly_iframe, startTime) {
var embedly_src = embedly_iframe.src;
var youtube_src_match = embedly_src.match(/src=([^&]+)/);
if (youtube_src_match) {
var youtube_src = decodeURIComponent(youtube_src_match[1]);
var new_youtube_src = youtube_src.replace(/(\?|&)start=\d+/, '');
new_youtube_src += (new_youtube_src.includes('?') ? '&' : '?') + 'start=' + startTime;
var new_embedly_src = embedly_src.replace(/src=([^&]+)/, 'src=' + encodeURIComponent(new_youtube_src));
embedly_iframe.src = new_embedly_src;
}
}
// Get all elements with ms-code-yt-start attribute
var elements = document.querySelectorAll('[ms-code-yt-start]');
elements.forEach(function(element) {
var paramName = element.getAttribute('ms-code-yt-start');
var startTime = getUrlParameter(paramName);
var defaultStartTime = element.getAttribute('ms-code-yt-start-default');
// If no URL parameter, use the default start time (if specified)
if (!startTime && defaultStartTime) {
startTime = defaultStartTime;
}
// If we have a start time (either from URL or default), update the embed
if (startTime) {
var iframe = element.querySelector('iframe.embedly-embed');
if (iframe) {
updateYouTubeEmbed(iframe, startTime);
}
}
});
})();
</script>
<!-- 💙 MEMBERSCRIPT #141 v0.1 💙 - START YOUTUBE VIDEO AT SPECIFIC TIME -->
<script>
(function() {
// Function to get URL parameters
function getUrlParameter(name) {
name = name.replace(/[\[]/, '\\[').replace(/[\]]/, '\\]');
var regex = new RegExp('[\\?&]' + name + '=([^]*)');
var results = regex.exec(location.search);
return results === null ? '' : decodeURIComponent(results[1].replace(/\+/g, ' '));
}
// Function to update YouTube embed src within Embedly iframe
function updateYouTubeEmbed(embedly_iframe, startTime) {
var embedly_src = embedly_iframe.src;
var youtube_src_match = embedly_src.match(/src=([^&]+)/);
if (youtube_src_match) {
var youtube_src = decodeURIComponent(youtube_src_match[1]);
var new_youtube_src = youtube_src.replace(/(\?|&)start=\d+/, '');
new_youtube_src += (new_youtube_src.includes('?') ? '&' : '?') + 'start=' + startTime;
var new_embedly_src = embedly_src.replace(/src=([^&]+)/, 'src=' + encodeURIComponent(new_youtube_src));
embedly_iframe.src = new_embedly_src;
}
}
// Get all elements with ms-code-yt-start attribute
var elements = document.querySelectorAll('[ms-code-yt-start]');
elements.forEach(function(element) {
var paramName = element.getAttribute('ms-code-yt-start');
var startTime = getUrlParameter(paramName);
var defaultStartTime = element.getAttribute('ms-code-yt-start-default');
// If no URL parameter, use the default start time (if specified)
if (!startTime && defaultStartTime) {
startTime = defaultStartTime;
}
// If we have a start time (either from URL or default), update the embed
if (startTime) {
var iframe = element.querySelector('iframe.embedly-embed');
if (iframe) {
updateYouTubeEmbed(iframe, startTime);
}
}
});
})();
</script>

#140 - Confirmer la concordance des entrées
Vérifier une entrée avant d'autoriser la soumission - idéal pour éviter les informations erronées !
<!-- 💙 MEMBERSCRIPT #140 v0.1 💙 - CONFIRM MATCHING INPUTS -->
<script>
document.addEventListener('DOMContentLoaded', function() {
const forms = document.querySelectorAll('form');
forms.forEach(form => {
const inputPairs = form.querySelectorAll('[ms-code-conf-input]');
const submitButton = form.querySelector('input[type="submit"], button[type="submit"]');
if (!submitButton) {
console.error('Submit button not found in the form');
return;
}
function validateForm() {
let fieldsMatch = true;
inputPairs.forEach(input => {
const confType = input.getAttribute('ms-code-conf-input');
const confirmInput = form.querySelector(`[ms-code-conf="${confType}"]`);
const errorElement = form.querySelector(`[ms-code-conf-error="${confType}"]`);
if (confirmInput && errorElement) {
if (input.value && confirmInput.value) {
if (input.value !== confirmInput.value) {
errorElement.style.removeProperty('display');
fieldsMatch = false;
} else {
errorElement.style.display = 'none';
}
} else {
errorElement.style.display = 'none';
}
}
});
if (fieldsMatch) {
submitButton.style.removeProperty('pointer-events');
submitButton.disabled = false;
} else {
submitButton.style.pointerEvents = 'none';
submitButton.disabled = true;
}
}
inputPairs.forEach(input => {
const confType = input.getAttribute('ms-code-conf-input');
const confirmInput = form.querySelector(`[ms-code-conf="${confType}"]`);
if (confirmInput) {
input.addEventListener('input', validateForm);
confirmInput.addEventListener('input', validateForm);
}
});
// Initial validation
validateForm();
// Extra precaution: prevent form submission if fields don't match
form.addEventListener('submit', function(event) {
if (submitButton.disabled) {
event.preventDefault();
console.log('Form submission blocked: Fields do not match');
}
});
});
});
</script>
<!-- 💙 MEMBERSCRIPT #140 v0.1 💙 - CONFIRM MATCHING INPUTS -->
<script>
document.addEventListener('DOMContentLoaded', function() {
const forms = document.querySelectorAll('form');
forms.forEach(form => {
const inputPairs = form.querySelectorAll('[ms-code-conf-input]');
const submitButton = form.querySelector('input[type="submit"], button[type="submit"]');
if (!submitButton) {
console.error('Submit button not found in the form');
return;
}
function validateForm() {
let fieldsMatch = true;
inputPairs.forEach(input => {
const confType = input.getAttribute('ms-code-conf-input');
const confirmInput = form.querySelector(`[ms-code-conf="${confType}"]`);
const errorElement = form.querySelector(`[ms-code-conf-error="${confType}"]`);
if (confirmInput && errorElement) {
if (input.value && confirmInput.value) {
if (input.value !== confirmInput.value) {
errorElement.style.removeProperty('display');
fieldsMatch = false;
} else {
errorElement.style.display = 'none';
}
} else {
errorElement.style.display = 'none';
}
}
});
if (fieldsMatch) {
submitButton.style.removeProperty('pointer-events');
submitButton.disabled = false;
} else {
submitButton.style.pointerEvents = 'none';
submitButton.disabled = true;
}
}
inputPairs.forEach(input => {
const confType = input.getAttribute('ms-code-conf-input');
const confirmInput = form.querySelector(`[ms-code-conf="${confType}"]`);
if (confirmInput) {
input.addEventListener('input', validateForm);
confirmInput.addEventListener('input', validateForm);
}
});
// Initial validation
validateForm();
// Extra precaution: prevent form submission if fields don't match
form.addEventListener('submit', function(event) {
if (submitButton.disabled) {
event.preventDefault();
console.log('Form submission blocked: Fields do not match');
}
});
});
});
</script>

#139 - Réinitialiser le formulaire après l'avoir soumis
Créer un bouton dans l'état de réussite du formulaire qui permet de le soumettre à nouveau.
<!-- 💙 MEMBERSCRIPT #139 v0.1 💙 - RESET FORM BUTTON -->
<script>
// Wait for the DOM to be fully loaded
document.addEventListener('DOMContentLoaded', function() {
// Find all "Add another" buttons
const resetButtons = document.querySelectorAll('[ms-code-reset-form]');
// Add click event listener to each button
resetButtons.forEach(function(resetButton) {
resetButton.addEventListener('click', function(e) {
e.preventDefault(); // Prevent default link behavior
// Find the closest form and success message elements
const formWrapper = this.closest('.w-form');
const form = formWrapper.querySelector('form');
const successMessage = formWrapper.querySelector('.w-form-done');
// Reset the form
form.reset();
// Hide the success message
successMessage.style.display = 'none';
// Show the form
form.style.display = 'block';
});
});
});
</script>
<!-- 💙 MEMBERSCRIPT #139 v0.1 💙 - RESET FORM BUTTON -->
<script>
// Wait for the DOM to be fully loaded
document.addEventListener('DOMContentLoaded', function() {
// Find all "Add another" buttons
const resetButtons = document.querySelectorAll('[ms-code-reset-form]');
// Add click event listener to each button
resetButtons.forEach(function(resetButton) {
resetButton.addEventListener('click', function(e) {
e.preventDefault(); // Prevent default link behavior
// Find the closest form and success message elements
const formWrapper = this.closest('.w-form');
const form = formWrapper.querySelector('form');
const successMessage = formWrapper.querySelector('.w-form-done');
// Reset the form
form.reset();
// Hide the success message
successMessage.style.display = 'none';
// Show the form
form.style.display = 'block';
});
});
});
</script>
Besoin d'aide avec MemberScripts ? Rejoignez notre communauté Slack de plus de 5 500 membres ! 🙌
Les MemberScripts sont une ressource communautaire de Memberstack - si vous avez besoin d'aide pour les faire fonctionner avec votre projet, rejoignez le Slack de Memberstack 2.0 et demandez de l'aide !
Rejoignez notre SlackDécouvrez les entreprises qui ont réussi avec Memberstack
Ne vous contentez pas de nous croire sur parole, consultez les entreprises de toutes tailles qui font confiance à Memberstack pour leur authentification et leurs paiements.
Commencez à construire vos rêves
Memberstack est 100% gratuit jusqu'à ce que vous soyez prêt à vous lancer - alors, qu'attendez-vous ? Créez votre première application et commencez à construire dès aujourd'hui.