MemberScripts
Una solución basada en atributos para añadir funciones a su sitio Webflow.
Simplemente copie algo de código, añada algunos atributos y listo.
Todos los clientes de Memberstack pueden solicitar asistencia en el Slack 2.0. Tenga en cuenta que no se trata de funciones oficiales y que no se puede garantizar la asistencia.

#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 - Iniciar la inserción en YouTube a una hora específica
Habilita enlaces compartibles y comienza a reproducir vídeos a una hora determinada.
<!-- 💙 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 - Confirmar la coincidencia de entradas
Verificar una entrada antes de permitir su envío: ¡genial para evitar información incorrecta!
<!-- 💙 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 - Restablecer formulario tras envío
Cree un botón en el estado de éxito del formulario que permita enviarlo de nuevo.
<!-- 💙 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>
¿Necesitas ayuda con MemberScripts? ¡Únete a nuestra comunidad Slack de más de 5.500 miembros! 🙌
Los MemberScripts son un recurso comunitario de Memberstack - si necesitas ayuda para que funcionen con tu proyecto, ¡únete al Slack de Memberstack 2.0 y pide ayuda!
Únete a nuestro SlackExplore empresas reales que han tenido éxito con Memberstack
No se fíe sólo de nuestra palabra: eche un vistazo a las empresas de todos los tamaños que confían en Memberstack para su autenticación y sus pagos.
Empieza a construir tus sueños
Memberstack es 100% gratis hasta que estés listo para lanzarla - así que, ¿a qué estás esperando? Crea tu primera aplicación y empieza a construir hoy mismo.