MemberScripts
An attribute-based solution to add features to your Webflow site.
Simply copy some code, add some attributes, and you're done.
All Memberstack customers can ask for assistance in the 2.0 Slack. Please note that these are not official features and support cannot be guaranteed.

#138 - Anchor Link Scroll Offset
Fix the problem with anchor links & sticky/fixed navbars in Webflow.
<!-- 💙 MEMBERSCRIPT #138 v0.1 💙 - ANCHOR LINK SCROLL OFFSET -->
<script>
// Disable Webflow's built-in smooth scrolling
var Webflow = Webflow || [];
Webflow.push(function() {
$(function() {
$(document).off('click.wf-scroll');
});
});
// Smooth scroll implementation with customizable settings
(function() {
// Customizable settings
const SCROLL_SETTINGS = {
duration: 1000, // in milliseconds
easing: 'easeInOutCubic' // 'linear', 'easeInQuad', 'easeOutQuad', 'easeInOutQuad', 'easeInCubic', 'easeOutCubic', 'easeInOutCubic'
};
const EASING_FUNCTIONS = {
linear: t => t,
easeInQuad: t => t * t,
easeOutQuad: t => t * (2 - t),
easeInOutQuad: t => t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t,
easeInCubic: t => t * t * t,
easeOutCubic: t => (--t) * t * t + 1,
easeInOutCubic: t => t < 0.5 ? 4 * t * t * t : (t - 1) * (2 * t - 2) * (2 * t - 2) + 1
};
function getOffset() {
const navbar = document.querySelector('[ms-code-scroll-offset]');
if (!navbar) return 0;
const navbarHeight = navbar.offsetHeight;
const customOffset = parseInt(navbar.getAttribute('ms-code-scroll-offset') || '0', 10);
return navbarHeight + customOffset;
}
function smoothScroll(target) {
const startPosition = window.pageYOffset;
const offset = getOffset();
const targetPosition = target.getBoundingClientRect().top + startPosition - offset;
const distance = targetPosition - startPosition;
let startTime = null;
function animation(currentTime) {
if (startTime === null) startTime = currentTime;
const timeElapsed = currentTime - startTime;
const progress = Math.min(timeElapsed / SCROLL_SETTINGS.duration, 1);
const easeProgress = EASING_FUNCTIONS[SCROLL_SETTINGS.easing](progress);
window.scrollTo(0, startPosition + distance * easeProgress);
if (timeElapsed < SCROLL_SETTINGS.duration) requestAnimationFrame(animation);
}
requestAnimationFrame(animation);
}
function handleClick(e) {
const href = e.currentTarget.getAttribute('href');
if (href.startsWith('#')) {
e.preventDefault();
const target = document.getElementById(href.slice(1));
if (target) smoothScroll(target);
}
}
function handleHashChange() {
if (window.location.hash) {
const target = document.getElementById(window.location.hash.slice(1));
if (target) {
setTimeout(() => smoothScroll(target), 0);
}
}
}
function init() {
document.querySelectorAll('a[href^="#"]').forEach(anchor => {
anchor.addEventListener('click', handleClick);
});
window.addEventListener('hashchange', handleHashChange);
handleHashChange(); // Handle initial hash on page load
}
document.addEventListener('DOMContentLoaded', init);
window.Webflow && window.Webflow.push(init);
})();
</script>
<!-- 💙 MEMBERSCRIPT #138 v0.1 💙 - ANCHOR LINK SCROLL OFFSET -->
<script>
// Disable Webflow's built-in smooth scrolling
var Webflow = Webflow || [];
Webflow.push(function() {
$(function() {
$(document).off('click.wf-scroll');
});
});
// Smooth scroll implementation with customizable settings
(function() {
// Customizable settings
const SCROLL_SETTINGS = {
duration: 1000, // in milliseconds
easing: 'easeInOutCubic' // 'linear', 'easeInQuad', 'easeOutQuad', 'easeInOutQuad', 'easeInCubic', 'easeOutCubic', 'easeInOutCubic'
};
const EASING_FUNCTIONS = {
linear: t => t,
easeInQuad: t => t * t,
easeOutQuad: t => t * (2 - t),
easeInOutQuad: t => t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t,
easeInCubic: t => t * t * t,
easeOutCubic: t => (--t) * t * t + 1,
easeInOutCubic: t => t < 0.5 ? 4 * t * t * t : (t - 1) * (2 * t - 2) * (2 * t - 2) + 1
};
function getOffset() {
const navbar = document.querySelector('[ms-code-scroll-offset]');
if (!navbar) return 0;
const navbarHeight = navbar.offsetHeight;
const customOffset = parseInt(navbar.getAttribute('ms-code-scroll-offset') || '0', 10);
return navbarHeight + customOffset;
}
function smoothScroll(target) {
const startPosition = window.pageYOffset;
const offset = getOffset();
const targetPosition = target.getBoundingClientRect().top + startPosition - offset;
const distance = targetPosition - startPosition;
let startTime = null;
function animation(currentTime) {
if (startTime === null) startTime = currentTime;
const timeElapsed = currentTime - startTime;
const progress = Math.min(timeElapsed / SCROLL_SETTINGS.duration, 1);
const easeProgress = EASING_FUNCTIONS[SCROLL_SETTINGS.easing](progress);
window.scrollTo(0, startPosition + distance * easeProgress);
if (timeElapsed < SCROLL_SETTINGS.duration) requestAnimationFrame(animation);
}
requestAnimationFrame(animation);
}
function handleClick(e) {
const href = e.currentTarget.getAttribute('href');
if (href.startsWith('#')) {
e.preventDefault();
const target = document.getElementById(href.slice(1));
if (target) smoothScroll(target);
}
}
function handleHashChange() {
if (window.location.hash) {
const target = document.getElementById(window.location.hash.slice(1));
if (target) {
setTimeout(() => smoothScroll(target), 0);
}
}
}
function init() {
document.querySelectorAll('a[href^="#"]').forEach(anchor => {
anchor.addEventListener('click', handleClick);
});
window.addEventListener('hashchange', handleHashChange);
handleHashChange(); // Handle initial hash on page load
}
document.addEventListener('DOMContentLoaded', init);
window.Webflow && window.Webflow.push(init);
})();
</script>
.avif)
#137 - Display The Visitors' Country Name
Replace text with the country a user is in based on their IP address.
<!-- 💙 MEMBERSCRIPT #137 v0.1 💙 - DISPLAY COUNTRY NAME -->
<script>
document.addEventListener('DOMContentLoaded', function() {
fetch('https://ipapi.co/json/')
.then(response => response.json())
.then(data => {
if (data.country_name) {
const countryElements = document.querySelectorAll('[ms-code-display-country]');
countryElements.forEach(element => {
element.textContent = data.country_name;
});
}
})
.catch(error => {
console.error('Error fetching country:', error);
});
});
</script>
<!-- 💙 MEMBERSCRIPT #137 v0.1 💙 - DISPLAY COUNTRY NAME -->
<script>
document.addEventListener('DOMContentLoaded', function() {
fetch('https://ipapi.co/json/')
.then(response => response.json())
.then(data => {
if (data.country_name) {
const countryElements = document.querySelectorAll('[ms-code-display-country]');
countryElements.forEach(element => {
element.textContent = data.country_name;
});
}
})
.catch(error => {
console.error('Error fetching country:', error);
});
});
</script>

#136 - Remove Section Path From URL
When a section is navigated to, the anchor link path will be removed.
<!-- 💙 MEMBERSCRIPT #136 💙 REMOVE SECTION PATH FROM URL -->
<script>
document.addEventListener('DOMContentLoaded', function() {
// Check if there's a hash in the URL
if (window.location.hash) {
// Get the target element
const targetId = window.location.hash.substring(1);
const targetElement = document.getElementById(targetId);
if (targetElement) {
// Scroll to the target element
targetElement.scrollIntoView({behavior: 'smooth'});
// Remove the hash after a short delay (to allow scrolling to complete)
setTimeout(function() {
history.pushState("", document.title, window.location.pathname + window.location.search);
}, 100);
}
}
// Add click event listeners to all internal links
document.querySelectorAll('a[href^="#"]').forEach(anchor => {
anchor.addEventListener('click', function(e) {
e.preventDefault();
const targetId = this.getAttribute('href').substring(1);
const targetElement = document.getElementById(targetId);
if (targetElement) {
targetElement.scrollIntoView({behavior: 'smooth'});
// Remove the hash after a short delay (to allow scrolling to complete)
setTimeout(function() {
history.pushState("", document.title, window.location.pathname + window.location.search);
}, 100);
}
});
});
});
</script>
<!-- 💙 MEMBERSCRIPT #136 💙 REMOVE SECTION PATH FROM URL -->
<script>
document.addEventListener('DOMContentLoaded', function() {
// Check if there's a hash in the URL
if (window.location.hash) {
// Get the target element
const targetId = window.location.hash.substring(1);
const targetElement = document.getElementById(targetId);
if (targetElement) {
// Scroll to the target element
targetElement.scrollIntoView({behavior: 'smooth'});
// Remove the hash after a short delay (to allow scrolling to complete)
setTimeout(function() {
history.pushState("", document.title, window.location.pathname + window.location.search);
}, 100);
}
}
// Add click event listeners to all internal links
document.querySelectorAll('a[href^="#"]').forEach(anchor => {
anchor.addEventListener('click', function(e) {
e.preventDefault();
const targetId = this.getAttribute('href').substring(1);
const targetElement = document.getElementById(targetId);
if (targetElement) {
targetElement.scrollIntoView({behavior: 'smooth'});
// Remove the hash after a short delay (to allow scrolling to complete)
setTimeout(function() {
history.pushState("", document.title, window.location.pathname + window.location.search);
}, 100);
}
});
});
});
</script>

#135 - Redirect Based On Select Value
Dynamically set the form redirect based on the users' selection.
<!-- 💙 MEMBERSCRIPT #135 v0.2 💙 - REDIRECT FORM FROM SELECT VALUE -->
<script>
(function() {
'use strict';
function initDropdownRedirect() {
const dropdown = document.querySelector('select[ms-code-dropdown-redirect]');
if (!dropdown) return;
const form = dropdown.closest('form');
if (!form) return;
function updateRedirect() {
const selectedValue = dropdown.value;
form.setAttribute('redirect', selectedValue);
form.setAttribute('data-redirect', selectedValue);
}
function handleSubmit(event) {
event.preventDefault();
const redirectUrl = form.getAttribute('data-redirect') || form.getAttribute('redirect');
if (redirectUrl) {
setTimeout(() => {
window.location.href = redirectUrl;
}, 500); // Delay redirect by 500 milliseconds
} else {
form.submit(); // Fall back to normal form submission if no redirect is set
}
}
dropdown.addEventListener('change', updateRedirect);
form.addEventListener('submit', handleSubmit);
// Initialize redirect on page load
updateRedirect();
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initDropdownRedirect);
} else {
initDropdownRedirect();
}
})();
</script>
<!-- 💙 MEMBERSCRIPT #135 v0.2 💙 - REDIRECT FORM FROM SELECT VALUE -->
<script>
(function() {
'use strict';
function initDropdownRedirect() {
const dropdown = document.querySelector('select[ms-code-dropdown-redirect]');
if (!dropdown) return;
const form = dropdown.closest('form');
if (!form) return;
function updateRedirect() {
const selectedValue = dropdown.value;
form.setAttribute('redirect', selectedValue);
form.setAttribute('data-redirect', selectedValue);
}
function handleSubmit(event) {
event.preventDefault();
const redirectUrl = form.getAttribute('data-redirect') || form.getAttribute('redirect');
if (redirectUrl) {
setTimeout(() => {
window.location.href = redirectUrl;
}, 500); // Delay redirect by 500 milliseconds
} else {
form.submit(); // Fall back to normal form submission if no redirect is set
}
}
dropdown.addEventListener('change', updateRedirect);
form.addEventListener('submit', handleSubmit);
// Initialize redirect on page load
updateRedirect();
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initDropdownRedirect);
} else {
initDropdownRedirect();
}
})();
</script>

#134 - Scroll To Top On Tab Change
When the tab is changed, the page will scroll to the top of the tab section.
<!-- 💙 MEMBERSCRIPT #134 💙 - SCROLL TO TOP OF TABS ON CHANGE -->
<script>
document.addEventListener('DOMContentLoaded', function() {
// Select all tab containers with the ms-code-tab-scroll-top attribute
const tabContainers = document.querySelectorAll('.w-tabs[ms-code-tab-scroll-top]');
tabContainers.forEach(container => {
const tabLinks = container.querySelectorAll('.w-tab-link');
const scrollTopValue = parseInt(container.getAttribute('ms-code-tab-scroll-top') || '0');
tabLinks.forEach(link => {
link.addEventListener('click', function(e) {
// Small delay to ensure the new tab content is rendered
setTimeout(() => {
// Find the active tab pane within this container
const activePane = container.querySelector('.w-tab-pane.w--tab-active');
if (activePane) {
// Calculate the new scroll position
const newScrollPosition = container.getBoundingClientRect().top + window.pageYOffset + scrollTopValue;
// Scroll to the new position
window.scrollTo({
top: newScrollPosition,
behavior: 'smooth'
});
}
}, 50); // 50ms delay, adjust if needed
});
});
});
});
</script>
<!-- 💙 MEMBERSCRIPT #134 💙 - SCROLL TO TOP OF TABS ON CHANGE -->
<script>
document.addEventListener('DOMContentLoaded', function() {
// Select all tab containers with the ms-code-tab-scroll-top attribute
const tabContainers = document.querySelectorAll('.w-tabs[ms-code-tab-scroll-top]');
tabContainers.forEach(container => {
const tabLinks = container.querySelectorAll('.w-tab-link');
const scrollTopValue = parseInt(container.getAttribute('ms-code-tab-scroll-top') || '0');
tabLinks.forEach(link => {
link.addEventListener('click', function(e) {
// Small delay to ensure the new tab content is rendered
setTimeout(() => {
// Find the active tab pane within this container
const activePane = container.querySelector('.w-tab-pane.w--tab-active');
if (activePane) {
// Calculate the new scroll position
const newScrollPosition = container.getBoundingClientRect().top + window.pageYOffset + scrollTopValue;
// Scroll to the new position
window.scrollTo({
top: newScrollPosition,
behavior: 'smooth'
});
}
}, 50); // 50ms delay, adjust if needed
});
});
});
});
</script>

#133 - Auto Watermark Images
Easily add a watermark to images on your Webflow site.
<!-- 💙 MEMBERSCRIPT #133 v0.1 💙 - AUTO IMAGE WATERMARK -->
<script>
function addWatermarkToImages() {
const images = document.querySelectorAll('img[ms-code-watermark]');
images.forEach(img => {
img.crossOrigin = "Anonymous"; // This allows us to work with images from other domains
img.onload = function() {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
// Set canvas size to match the image
canvas.width = img.width;
canvas.height = img.height;
// Draw the original image onto the canvas
ctx.drawImage(img, 0, 0, img.width, img.height);
// Get watermark text from attribute
const watermarkText = img.getAttribute('ms-code-watermark') || 'Watermark';
// Add watermark
ctx.font = `${img.width / 20}px Arial`; // Adjust font size based on image width
ctx.fillStyle = 'rgba(255, 255, 255, 0.5)';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
// Rotate and draw the watermark text
ctx.save();
ctx.translate(canvas.width / 2, canvas.height / 2);
ctx.rotate(-Math.PI / 4); // Rotate 45 degrees
ctx.fillText(watermarkText, 0, 0);
ctx.restore();
// Preserve the original image's classes and other attributes
canvas.className = img.className;
for (let i = 0; i < img.attributes.length; i++) {
const attr = img.attributes[i];
if (attr.name !== 'src' && attr.name !== 'ms-code-watermark') {
canvas.setAttribute(attr.name, attr.value);
}
}
// Replace the original image with the watermarked canvas
img.parentNode.replaceChild(canvas, img);
};
// Trigger onload event (in case the image is already loaded)
if (img.complete) {
img.onload();
}
});
}
// Run the function when the DOM is fully loaded
document.addEventListener('DOMContentLoaded', addWatermarkToImages);
</script>
<!-- 💙 MEMBERSCRIPT #133 v0.1 💙 - AUTO IMAGE WATERMARK -->
<script>
function addWatermarkToImages() {
const images = document.querySelectorAll('img[ms-code-watermark]');
images.forEach(img => {
img.crossOrigin = "Anonymous"; // This allows us to work with images from other domains
img.onload = function() {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
// Set canvas size to match the image
canvas.width = img.width;
canvas.height = img.height;
// Draw the original image onto the canvas
ctx.drawImage(img, 0, 0, img.width, img.height);
// Get watermark text from attribute
const watermarkText = img.getAttribute('ms-code-watermark') || 'Watermark';
// Add watermark
ctx.font = `${img.width / 20}px Arial`; // Adjust font size based on image width
ctx.fillStyle = 'rgba(255, 255, 255, 0.5)';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
// Rotate and draw the watermark text
ctx.save();
ctx.translate(canvas.width / 2, canvas.height / 2);
ctx.rotate(-Math.PI / 4); // Rotate 45 degrees
ctx.fillText(watermarkText, 0, 0);
ctx.restore();
// Preserve the original image's classes and other attributes
canvas.className = img.className;
for (let i = 0; i < img.attributes.length; i++) {
const attr = img.attributes[i];
if (attr.name !== 'src' && attr.name !== 'ms-code-watermark') {
canvas.setAttribute(attr.name, attr.value);
}
}
// Replace the original image with the watermarked canvas
img.parentNode.replaceChild(canvas, img);
};
// Trigger onload event (in case the image is already loaded)
if (img.complete) {
img.onload();
}
});
}
// Run the function when the DOM is fully loaded
document.addEventListener('DOMContentLoaded', addWatermarkToImages);
</script>

#132 - Hide Elements With Escape Key
Add one attribute and when the esc key is clicked, the element will be set to display none.
<!-- 💙 MEMBERSCRIPT 💙 - HIDE ELEMENTS WITH ESC KEY -->
<script>
document.addEventListener('keydown', function(event) {
// Check if the pressed key is ESC (key code 27)
if (event.key === 'Escape' || event.keyCode === 27) {
// Find all elements with the attribute ms-code-close-esc
const elements = document.querySelectorAll('[ms-code-close-esc]');
// Loop through the elements and set their display to 'none'
elements.forEach(function(element) {
element.style.display = 'none';
});
}
});
</script>
<!-- 💙 MEMBERSCRIPT 💙 - HIDE ELEMENTS WITH ESC KEY -->
<script>
document.addEventListener('keydown', function(event) {
// Check if the pressed key is ESC (key code 27)
if (event.key === 'Escape' || event.keyCode === 27) {
// Find all elements with the attribute ms-code-close-esc
const elements = document.querySelectorAll('[ms-code-close-esc]');
// Loop through the elements and set their display to 'none'
elements.forEach(function(element) {
element.style.display = 'none';
});
}
});
</script>

#131 - Add Up Number Inputs
Take the value of number inputs and show it in an input value, or in a text span.
<!-- 💙 MEMBERSCRIPT #131 v0.1 💙 - CALCULATE NUMBER INPUTS -->
<script>
// Function to initialize the counter functionality
function initializeCounter() {
const counters = {};
// Find all elements with ms-code-show-number attribute
document.querySelectorAll('[ms-code-show-number]').forEach(el => {
const counterName = el.getAttribute('ms-code-show-number');
if (!counters[counterName]) {
counters[counterName] = { total: 0, displays: [] };
}
counters[counterName].displays.push(el);
});
// Find all input elements with ms-code-add-number attribute
document.querySelectorAll('input[ms-code-add-number]').forEach(input => {
const counterName = input.getAttribute('ms-code-add-number');
if (counters[counterName]) {
input.addEventListener('input', updateCounter);
}
});
// Function to update counter when input changes
function updateCounter(event) {
const input = event.target;
const counterName = input.getAttribute('ms-code-add-number');
const counter = counters[counterName];
if (counter) {
counter.total = 0;
document.querySelectorAll(`input[ms-code-add-number="${counterName}"]`).forEach(input => {
counter.total += parseInt(input.value) || 0;
});
counter.displays.forEach(display => {
if (display.tagName === 'INPUT') {
display.value = counter.total;
} else {
display.textContent = counter.total;
}
});
}
}
// Initial update for all counters
Object.keys(counters).forEach(counterName => {
const input = document.querySelector(`input[ms-code-add-number="${counterName}"]`);
if (input) {
input.dispatchEvent(new Event('input'));
}
});
}
// Run the initialization when the DOM is fully loaded
document.addEventListener('DOMContentLoaded', initializeCounter);
</script>
<!-- 💙 MEMBERSCRIPT #131 v0.1 💙 - CALCULATE NUMBER INPUTS -->
<script>
// Function to initialize the counter functionality
function initializeCounter() {
const counters = {};
// Find all elements with ms-code-show-number attribute
document.querySelectorAll('[ms-code-show-number]').forEach(el => {
const counterName = el.getAttribute('ms-code-show-number');
if (!counters[counterName]) {
counters[counterName] = { total: 0, displays: [] };
}
counters[counterName].displays.push(el);
});
// Find all input elements with ms-code-add-number attribute
document.querySelectorAll('input[ms-code-add-number]').forEach(input => {
const counterName = input.getAttribute('ms-code-add-number');
if (counters[counterName]) {
input.addEventListener('input', updateCounter);
}
});
// Function to update counter when input changes
function updateCounter(event) {
const input = event.target;
const counterName = input.getAttribute('ms-code-add-number');
const counter = counters[counterName];
if (counter) {
counter.total = 0;
document.querySelectorAll(`input[ms-code-add-number="${counterName}"]`).forEach(input => {
counter.total += parseInt(input.value) || 0;
});
counter.displays.forEach(display => {
if (display.tagName === 'INPUT') {
display.value = counter.total;
} else {
display.textContent = counter.total;
}
});
}
}
// Initial update for all counters
Object.keys(counters).forEach(counterName => {
const input = document.querySelector(`input[ms-code-add-number="${counterName}"]`);
if (input) {
input.dispatchEvent(new Event('input'));
}
});
}
// Run the initialization when the DOM is fully loaded
document.addEventListener('DOMContentLoaded', initializeCounter);
</script>

#130 - Auto Submit Form When All Inputs Change
Skip the need for a submit button and submit the form when all inputs change.
<!-- 💙 MEMBERSCRIPT #130 v0.1 💙 - AUTO SUBMIT FORMS FROM INPUT CHANGE -->
<script>
document.addEventListener('DOMContentLoaded', () => {
const forms = document.querySelectorAll('form[ms-code-auto-submit]');
forms.forEach(form => {
const fields = form.querySelectorAll('input:not([type="submit"]):not([type="button"]):not([type="reset"]), select, textarea');
const fieldStates = new Map(Array.from(fields).map(field => [field, false]));
function updateFieldState(field, checkImmediately = false) {
switch (field.type) {
case 'checkbox':
fieldStates.set(field, true); // Considered interacted with once changed
break;
case 'radio':
const radioGroup = form.querySelectorAll(`input[type="radio"][name="${field.name}"]`);
radioGroup.forEach(radio => fieldStates.set(radio, true));
break;
case 'select-one':
case 'select-multiple':
fieldStates.set(field, field.value !== '');
break;
case 'file':
fieldStates.set(field, field.files.length > 0);
break;
case 'hidden':
fieldStates.set(field, true); // Always consider hidden fields as filled
break;
default:
// For text inputs, only update on blur or if checkImmediately is true
if (field.type === 'text' || field.type === 'password' || field.type === 'email' || field.type === 'tel' || field.type === 'url' || field.tagName === 'TEXTAREA') {
if (checkImmediately || !field.dataset.blurred) {
fieldStates.set(field, field.value.trim() !== '');
}
} else {
fieldStates.set(field, field.value.trim() !== '');
}
}
if (checkImmediately) {
checkAndSubmit();
}
}
function checkAndSubmit() {
if (Array.from(fieldStates.values()).every(state => state)) {
// Create and dispatch a submit event
const submitEvent = new Event('submit', {
bubbles: true,
cancelable: true
});
const submitted = form.dispatchEvent(submitEvent);
// If the event wasn't prevented, manually submit the form
if (submitted) {
form.submit();
}
}
}
fields.forEach(field => {
// Use 'change' event for checkboxes, radios, file inputs, and selects
if (['checkbox', 'radio', 'file', 'select-one', 'select-multiple'].includes(field.type) || field.tagName === 'SELECT') {
field.addEventListener('change', () => updateFieldState(field, true));
}
// For text-like inputs, use 'blur' event
if (field.type === 'text' || field.type === 'password' || field.type === 'email' || field.type === 'tel' || field.type === 'url' || field.tagName === 'TEXTAREA') {
field.addEventListener('blur', () => {
field.dataset.blurred = 'true';
updateFieldState(field, true);
});
// Also check on input, but don't submit immediately
field.addEventListener('input', () => updateFieldState(field, false));
}
});
// Initial check for pre-filled fields (e.g., browser autofill)
fields.forEach(field => updateFieldState(field, false));
checkAndSubmit();
});
});
</script>
<!-- 💙 MEMBERSCRIPT #130 v0.1 💙 - AUTO SUBMIT FORMS FROM INPUT CHANGE -->
<script>
document.addEventListener('DOMContentLoaded', () => {
const forms = document.querySelectorAll('form[ms-code-auto-submit]');
forms.forEach(form => {
const fields = form.querySelectorAll('input:not([type="submit"]):not([type="button"]):not([type="reset"]), select, textarea');
const fieldStates = new Map(Array.from(fields).map(field => [field, false]));
function updateFieldState(field, checkImmediately = false) {
switch (field.type) {
case 'checkbox':
fieldStates.set(field, true); // Considered interacted with once changed
break;
case 'radio':
const radioGroup = form.querySelectorAll(`input[type="radio"][name="${field.name}"]`);
radioGroup.forEach(radio => fieldStates.set(radio, true));
break;
case 'select-one':
case 'select-multiple':
fieldStates.set(field, field.value !== '');
break;
case 'file':
fieldStates.set(field, field.files.length > 0);
break;
case 'hidden':
fieldStates.set(field, true); // Always consider hidden fields as filled
break;
default:
// For text inputs, only update on blur or if checkImmediately is true
if (field.type === 'text' || field.type === 'password' || field.type === 'email' || field.type === 'tel' || field.type === 'url' || field.tagName === 'TEXTAREA') {
if (checkImmediately || !field.dataset.blurred) {
fieldStates.set(field, field.value.trim() !== '');
}
} else {
fieldStates.set(field, field.value.trim() !== '');
}
}
if (checkImmediately) {
checkAndSubmit();
}
}
function checkAndSubmit() {
if (Array.from(fieldStates.values()).every(state => state)) {
// Create and dispatch a submit event
const submitEvent = new Event('submit', {
bubbles: true,
cancelable: true
});
const submitted = form.dispatchEvent(submitEvent);
// If the event wasn't prevented, manually submit the form
if (submitted) {
form.submit();
}
}
}
fields.forEach(field => {
// Use 'change' event for checkboxes, radios, file inputs, and selects
if (['checkbox', 'radio', 'file', 'select-one', 'select-multiple'].includes(field.type) || field.tagName === 'SELECT') {
field.addEventListener('change', () => updateFieldState(field, true));
}
// For text-like inputs, use 'blur' event
if (field.type === 'text' || field.type === 'password' || field.type === 'email' || field.type === 'tel' || field.type === 'url' || field.tagName === 'TEXTAREA') {
field.addEventListener('blur', () => {
field.dataset.blurred = 'true';
updateFieldState(field, true);
});
// Also check on input, but don't submit immediately
field.addEventListener('input', () => updateFieldState(field, false));
}
});
// Initial check for pre-filled fields (e.g., browser autofill)
fields.forEach(field => updateFieldState(field, false));
checkAndSubmit();
});
});
</script>

#129 - Country Gating
Block visitors from viewing your site if they're in one of the disallowed countries.
<!-- 💙 MEMBERSCRIPT #129 v0.1 💙 - COUNTRY GATING -->
<script>
// Configuration
const ACCESS_DENIED_PAGE = '/access-denied';
// List of disallowed countries using ISO 3166-1 alpha-2 country codes
const DISALLOWED_COUNTRIES = [
// "US", // United States
// "CN", // China
// "RU", // Russia
// "IN", // India
// "JP", // Japan
// "DE", // Germany
// "GB", // United Kingdom
// "FR", // France
// "BR", // Brazil
// "IT", // Italy
// Add more countries as needed
];
// Function to get visitor's country and check access
function checkCountryAccess() {
// Check if we're already on the access denied page
if (window.location.pathname === ACCESS_DENIED_PAGE) {
return; // Don't redirect if already on the access denied page
}
fetch('https://ipapi.co/json/')
.then(response => response.json())
.then(data => {
const visitorCountry = data.country_code; // This returns the ISO 3166-1 alpha-2 country code
if (DISALLOWED_COUNTRIES.includes(visitorCountry)) {
window.location.href = ACCESS_DENIED_PAGE;
}
})
.catch(error => {
console.error('Error fetching IP data:', error);
});
}
// Run the check when the page loads
document.addEventListener('DOMContentLoaded', checkCountryAccess);
</script>
<!-- 💙 MEMBERSCRIPT #129 v0.1 💙 - COUNTRY GATING -->
<script>
// Configuration
const ACCESS_DENIED_PAGE = '/access-denied';
// List of disallowed countries using ISO 3166-1 alpha-2 country codes
const DISALLOWED_COUNTRIES = [
// "US", // United States
// "CN", // China
// "RU", // Russia
// "IN", // India
// "JP", // Japan
// "DE", // Germany
// "GB", // United Kingdom
// "FR", // France
// "BR", // Brazil
// "IT", // Italy
// Add more countries as needed
];
// Function to get visitor's country and check access
function checkCountryAccess() {
// Check if we're already on the access denied page
if (window.location.pathname === ACCESS_DENIED_PAGE) {
return; // Don't redirect if already on the access denied page
}
fetch('https://ipapi.co/json/')
.then(response => response.json())
.then(data => {
const visitorCountry = data.country_code; // This returns the ISO 3166-1 alpha-2 country code
if (DISALLOWED_COUNTRIES.includes(visitorCountry)) {
window.location.href = ACCESS_DENIED_PAGE;
}
})
.catch(error => {
console.error('Error fetching IP data:', error);
});
}
// Run the check when the page loads
document.addEventListener('DOMContentLoaded', checkCountryAccess);
</script>

#128 - Hide Elements After They've Been Seen
Add one attribute, and your visitors will only see that element one time. After refresh, it will be gone.
<!-- 💙 MEMBERSCRIPT #128 💙 - ONLY SHOW ELEMENT ONCE -->
<script>
document.addEventListener('DOMContentLoaded', function() {
// Find all elements with the ms-code-show-once attribute
const elements = document.querySelectorAll('[ms-code-show-once]');
elements.forEach(element => {
const identifier = element.getAttribute('ms-code-show-once');
const storageKey = `ms-code-shown-${identifier}`;
// Check if the element has been seen before
if (localStorage.getItem(storageKey) !== 'true') {
// If not seen, show the element
element.style.display = 'block';
// Mark it as seen in localStorage
localStorage.setItem(storageKey, 'true');
} else {
// If already seen, hide the element
element.style.display = 'none';
}
});
});
</script>
<!-- 💙 MEMBERSCRIPT #128 💙 - ONLY SHOW ELEMENT ONCE -->
<script>
document.addEventListener('DOMContentLoaded', function() {
// Find all elements with the ms-code-show-once attribute
const elements = document.querySelectorAll('[ms-code-show-once]');
elements.forEach(element => {
const identifier = element.getAttribute('ms-code-show-once');
const storageKey = `ms-code-shown-${identifier}`;
// Check if the element has been seen before
if (localStorage.getItem(storageKey) !== 'true') {
// If not seen, show the element
element.style.display = 'block';
// Mark it as seen in localStorage
localStorage.setItem(storageKey, 'true');
} else {
// If already seen, hide the element
element.style.display = 'none';
}
});
});
</script>

#127 - Validate Text Inputs
Validate text inputs against any list of strings, including wildcards!
<!-- 💙 MEMBERSCRIPT #127 v0.1 💙 - TEXT INPUT VALIDATION -->
<script>
document.addEventListener('DOMContentLoaded', function() {
// Debounce function
function debounce(func, wait) {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
}
// Find all fields with ms-code-require attribute
const fields = document.querySelectorAll('[ms-code-require]');
fields.forEach(field => {
// Get the error element for this field
const errorElement = document.querySelector(`[ms-code-require-error="${field.getAttribute('ms-code-require')}"]`);
// Hide error message initially
if (errorElement) {
errorElement.style.display = 'none';
}
// Get the form containing the field
const form = field.closest('form');
// Get the submit button
const submitButton = form ? form.querySelector(`[ms-code-submit-button="${field.getAttribute('ms-code-require')}"]`) : null;
// Get the require-list attribute value
const requireList = field.getAttribute('ms-code-require-list');
if (requireList) {
// Convert the require-list to an array of regex patterns
const patterns = requireList.split(',').map(pattern => {
return pattern.replace(/\{([^}]+)\}/g, (match, p1) => {
return p1.split('').map(char => {
switch(char) {
case '0': return '\\d';
case 'A': return '[A-Z]';
case 'a': return '[a-z]';
default: return char;
}
}).join('');
});
});
// Validate function
function validateField() {
const value = field.value;
const isValid = patterns.some(pattern => new RegExp(`^${pattern}$`).test(value));
if (errorElement) {
errorElement.style.display = isValid ? 'none' : 'block';
}
if (submitButton) {
submitButton.style.opacity = isValid ? '1' : '0.5';
submitButton.style.pointerEvents = isValid ? 'auto' : 'none';
}
return isValid;
}
// Debounced validate function
const debouncedValidate = debounce(validateField, 500);
// Add blur event listener
field.addEventListener('blur', validateField);
// Add input event listener for debounced validation
field.addEventListener('input', debouncedValidate);
// Handle form submission
if (form) {
form.addEventListener('submit', function(event) {
if (!validateField() && submitButton) {
event.preventDefault();
field.focus();
}
});
}
}
});
});
</script>
<!-- 💙 MEMBERSCRIPT #127 v0.1 💙 - TEXT INPUT VALIDATION -->
<script>
document.addEventListener('DOMContentLoaded', function() {
// Debounce function
function debounce(func, wait) {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
}
// Find all fields with ms-code-require attribute
const fields = document.querySelectorAll('[ms-code-require]');
fields.forEach(field => {
// Get the error element for this field
const errorElement = document.querySelector(`[ms-code-require-error="${field.getAttribute('ms-code-require')}"]`);
// Hide error message initially
if (errorElement) {
errorElement.style.display = 'none';
}
// Get the form containing the field
const form = field.closest('form');
// Get the submit button
const submitButton = form ? form.querySelector(`[ms-code-submit-button="${field.getAttribute('ms-code-require')}"]`) : null;
// Get the require-list attribute value
const requireList = field.getAttribute('ms-code-require-list');
if (requireList) {
// Convert the require-list to an array of regex patterns
const patterns = requireList.split(',').map(pattern => {
return pattern.replace(/\{([^}]+)\}/g, (match, p1) => {
return p1.split('').map(char => {
switch(char) {
case '0': return '\\d';
case 'A': return '[A-Z]';
case 'a': return '[a-z]';
default: return char;
}
}).join('');
});
});
// Validate function
function validateField() {
const value = field.value;
const isValid = patterns.some(pattern => new RegExp(`^${pattern}$`).test(value));
if (errorElement) {
errorElement.style.display = isValid ? 'none' : 'block';
}
if (submitButton) {
submitButton.style.opacity = isValid ? '1' : '0.5';
submitButton.style.pointerEvents = isValid ? 'auto' : 'none';
}
return isValid;
}
// Debounced validate function
const debouncedValidate = debounce(validateField, 500);
// Add blur event listener
field.addEventListener('blur', validateField);
// Add input event listener for debounced validation
field.addEventListener('input', debouncedValidate);
// Handle form submission
if (form) {
form.addEventListener('submit', function(event) {
if (!validateField() && submitButton) {
event.preventDefault();
field.focus();
}
});
}
}
});
});
</script>

#126 - Post Form To Webhook Without Redirecting
Post data to a webhook & keep the default Webflow form behavior.
<!-- 💙 MEMBERSCRIPT #126 v0.1 💙 - POST FORM DATA TO WEBHOOK WITHOUT REDIRECTING -->
<script>
// Wait for the DOM to be fully loaded
document.addEventListener('DOMContentLoaded', function() {
// Select all forms with the ms-code-form-no-redirect attribute
const forms = document.querySelectorAll('form[ms-code-form-no-redirect]');
forms.forEach(form => {
// Select the success and error message elements for this form
const formWrapper = form.closest('.w-form');
const successMessage = formWrapper.querySelector('.w-form-done');
const errorMessage = formWrapper.querySelector('.w-form-fail');
// Add submit event listener to the form
form.addEventListener('submit', function(event) {
// Prevent the default form submission
event.preventDefault();
// Get the form data
const formData = new FormData(form);
// Get the submit button and set its text to the waiting message
const submitButton = form.querySelector('input[type="submit"], button[type="submit"]');
const originalButtonText = submitButton.value || submitButton.textContent;
const waitingText = submitButton.getAttribute('data-wait') || 'Please wait...';
if (submitButton.tagName === 'INPUT') {
submitButton.value = waitingText;
} else {
submitButton.textContent = waitingText;
}
// Disable the submit button
submitButton.disabled = true;
// Send the form data to the form's action URL using fetch
fetch(form.action, {
method: 'POST',
body: formData
})
.then(response => {
if (response.ok) {
// If the submission was successful, show the success message
form.style.display = 'none';
successMessage.style.display = 'block';
errorMessage.style.display = 'none';
} else {
// If there was an error, show the error message
throw new Error('Form submission failed');
}
})
.catch(error => {
// If there was a network error or the submission failed, show the error message
console.error('Error:', error);
errorMessage.style.display = 'block';
successMessage.style.display = 'none';
})
.finally(() => {
// Reset the submit button text and re-enable it
if (submitButton.tagName === 'INPUT') {
submitButton.value = originalButtonText;
} else {
submitButton.textContent = originalButtonText;
}
submitButton.disabled = false;
});
});
});
});
</script>
<!-- 💙 MEMBERSCRIPT #126 v0.1 💙 - POST FORM DATA TO WEBHOOK WITHOUT REDIRECTING -->
<script>
// Wait for the DOM to be fully loaded
document.addEventListener('DOMContentLoaded', function() {
// Select all forms with the ms-code-form-no-redirect attribute
const forms = document.querySelectorAll('form[ms-code-form-no-redirect]');
forms.forEach(form => {
// Select the success and error message elements for this form
const formWrapper = form.closest('.w-form');
const successMessage = formWrapper.querySelector('.w-form-done');
const errorMessage = formWrapper.querySelector('.w-form-fail');
// Add submit event listener to the form
form.addEventListener('submit', function(event) {
// Prevent the default form submission
event.preventDefault();
// Get the form data
const formData = new FormData(form);
// Get the submit button and set its text to the waiting message
const submitButton = form.querySelector('input[type="submit"], button[type="submit"]');
const originalButtonText = submitButton.value || submitButton.textContent;
const waitingText = submitButton.getAttribute('data-wait') || 'Please wait...';
if (submitButton.tagName === 'INPUT') {
submitButton.value = waitingText;
} else {
submitButton.textContent = waitingText;
}
// Disable the submit button
submitButton.disabled = true;
// Send the form data to the form's action URL using fetch
fetch(form.action, {
method: 'POST',
body: formData
})
.then(response => {
if (response.ok) {
// If the submission was successful, show the success message
form.style.display = 'none';
successMessage.style.display = 'block';
errorMessage.style.display = 'none';
} else {
// If there was an error, show the error message
throw new Error('Form submission failed');
}
})
.catch(error => {
// If there was a network error or the submission failed, show the error message
console.error('Error:', error);
errorMessage.style.display = 'block';
successMessage.style.display = 'none';
})
.finally(() => {
// Reset the submit button text and re-enable it
if (submitButton.tagName === 'INPUT') {
submitButton.value = originalButtonText;
} else {
submitButton.textContent = originalButtonText;
}
submitButton.disabled = false;
});
});
});
});
</script>

#125 - Control Required State With Checkbox
Set form fields to required/optional based on the state of a checkbox.
<!-- 💙 MEMBERSCRIPT #125 v0.1 💙 - CHECKBOX TOGGLE REQUIRED ON FORM FIELDS -->
<script>
// Function to initialize the form field requirements
function initFormFieldRequirements() {
// Handle checkbox changes
document.querySelectorAll('[ms-code-req-if-checked], [ms-code-req-if-unchecked]').forEach(checkbox => {
checkbox.addEventListener('change', updateFieldRequirements);
// Initial update
updateFieldRequirements.call(checkbox);
});
}
// Function to update field requirements based on checkbox state
function updateFieldRequirements() {
const isChecked = this.checked;
const group = this.getAttribute('ms-code-req-if-checked') || this.getAttribute('ms-code-req-if-unchecked');
const ifChecked = this.hasAttribute('ms-code-req-if-checked');
const shouldBeRequired = ifChecked ? isChecked : !isChecked;
const shouldDisableIfNotReq = this.hasAttribute('ms-code-disable-if-not-req');
// Update associated input fields
document.querySelectorAll(`[ms-code-req-input="${group}"]`).forEach(input => {
input.required = shouldBeRequired;
updateInputStyle(input, shouldBeRequired, shouldDisableIfNotReq);
});
// Update associated labels
document.querySelectorAll(`[ms-code-req-label="${group}"]`).forEach(label => {
label.style.display = shouldBeRequired ? '' : 'none';
});
}
// Function to update input style based on required state and disable setting
function updateInputStyle(input, isRequired, shouldDisableIfNotReq) {
if (shouldDisableIfNotReq) {
input.style.opacity = isRequired ? '1' : '0.4';
input.style.pointerEvents = isRequired ? '' : 'none';
} else {
input.style.opacity = '';
input.style.pointerEvents = '';
}
}
// Initialize when the DOM is fully loaded
document.addEventListener('DOMContentLoaded', initFormFieldRequirements);
</script>
<!-- 💙 MEMBERSCRIPT #125 v0.1 💙 - CHECKBOX TOGGLE REQUIRED ON FORM FIELDS -->
<script>
// Function to initialize the form field requirements
function initFormFieldRequirements() {
// Handle checkbox changes
document.querySelectorAll('[ms-code-req-if-checked], [ms-code-req-if-unchecked]').forEach(checkbox => {
checkbox.addEventListener('change', updateFieldRequirements);
// Initial update
updateFieldRequirements.call(checkbox);
});
}
// Function to update field requirements based on checkbox state
function updateFieldRequirements() {
const isChecked = this.checked;
const group = this.getAttribute('ms-code-req-if-checked') || this.getAttribute('ms-code-req-if-unchecked');
const ifChecked = this.hasAttribute('ms-code-req-if-checked');
const shouldBeRequired = ifChecked ? isChecked : !isChecked;
const shouldDisableIfNotReq = this.hasAttribute('ms-code-disable-if-not-req');
// Update associated input fields
document.querySelectorAll(`[ms-code-req-input="${group}"]`).forEach(input => {
input.required = shouldBeRequired;
updateInputStyle(input, shouldBeRequired, shouldDisableIfNotReq);
});
// Update associated labels
document.querySelectorAll(`[ms-code-req-label="${group}"]`).forEach(label => {
label.style.display = shouldBeRequired ? '' : 'none';
});
}
// Function to update input style based on required state and disable setting
function updateInputStyle(input, isRequired, shouldDisableIfNotReq) {
if (shouldDisableIfNotReq) {
input.style.opacity = isRequired ? '1' : '0.4';
input.style.pointerEvents = isRequired ? '' : 'none';
} else {
input.style.opacity = '';
input.style.pointerEvents = '';
}
}
// Initialize when the DOM is fully loaded
document.addEventListener('DOMContentLoaded', initFormFieldRequirements);
</script>

#124 - Toggle Item Visibility
Use custom buttons to hide and show elements, with states saved in local storage.
<!-- 💙 MEMBERSCRIPT #124 v0.1 💙 - TOGGLE ITEM VISIBILITY -->
<script>
document.addEventListener('DOMContentLoaded', function() {
const STORAGE_KEY = 'ms-code-vis-states';
// Load saved states from local storage
let savedStates = JSON.parse(localStorage.getItem(STORAGE_KEY)) || {};
// Find all unique group identifiers
const groups = new Set([...document.querySelectorAll('[ms-code-vis-item]')].map(el => el.getAttribute('ms-code-vis-item')));
groups.forEach(group => {
const item = document.querySelector(`[ms-code-vis-item="${group}"]`);
const showButton = document.querySelector(`[ms-code-vis-show="${group}"]`);
const hideButton = document.querySelector(`[ms-code-vis-hide="${group}"]`);
if (!item || !showButton || !hideButton) return;
// Function to update visibility
function updateVisibility(isVisible) {
if (isVisible) {
item.style.removeProperty('display');
showButton.style.display = 'none';
hideButton.style.removeProperty('display');
} else {
item.style.display = 'none';
showButton.style.removeProperty('display');
hideButton.style.display = 'none';
}
// Save state to local storage
savedStates[group] = isVisible;
localStorage.setItem(STORAGE_KEY, JSON.stringify(savedStates));
}
// Set initial visibility
const defaultState = item.getAttribute('ms-code-vis-default');
const savedState = savedStates[group];
if (savedState !== undefined) {
updateVisibility(savedState);
} else if (defaultState === 'hide') {
updateVisibility(false);
} else {
updateVisibility(true);
}
// Add click event listeners
showButton.addEventListener('click', function(e) {
e.preventDefault();
updateVisibility(true);
});
hideButton.addEventListener('click', function(e) {
e.preventDefault();
updateVisibility(false);
});
});
});
</script>
<!-- 💙 MEMBERSCRIPT #124 v0.1 💙 - TOGGLE ITEM VISIBILITY -->
<script>
document.addEventListener('DOMContentLoaded', function() {
const STORAGE_KEY = 'ms-code-vis-states';
// Load saved states from local storage
let savedStates = JSON.parse(localStorage.getItem(STORAGE_KEY)) || {};
// Find all unique group identifiers
const groups = new Set([...document.querySelectorAll('[ms-code-vis-item]')].map(el => el.getAttribute('ms-code-vis-item')));
groups.forEach(group => {
const item = document.querySelector(`[ms-code-vis-item="${group}"]`);
const showButton = document.querySelector(`[ms-code-vis-show="${group}"]`);
const hideButton = document.querySelector(`[ms-code-vis-hide="${group}"]`);
if (!item || !showButton || !hideButton) return;
// Function to update visibility
function updateVisibility(isVisible) {
if (isVisible) {
item.style.removeProperty('display');
showButton.style.display = 'none';
hideButton.style.removeProperty('display');
} else {
item.style.display = 'none';
showButton.style.removeProperty('display');
hideButton.style.display = 'none';
}
// Save state to local storage
savedStates[group] = isVisible;
localStorage.setItem(STORAGE_KEY, JSON.stringify(savedStates));
}
// Set initial visibility
const defaultState = item.getAttribute('ms-code-vis-default');
const savedState = savedStates[group];
if (savedState !== undefined) {
updateVisibility(savedState);
} else if (defaultState === 'hide') {
updateVisibility(false);
} else {
updateVisibility(true);
}
// Add click event listeners
showButton.addEventListener('click', function(e) {
e.preventDefault();
updateVisibility(true);
});
hideButton.addEventListener('click', function(e) {
e.preventDefault();
updateVisibility(false);
});
});
});
</script>

#123 - One Audio Playing At A Time
Automatically pause all other audioplayers when a user clicks to play one.
<!-- 💙 MEMBERSCRIPT #123 💙 - ONE AUDIO AT A TIME -->
<script>
document.addEventListener('DOMContentLoaded', () => {
const pauseOthers = current =>
document.querySelectorAll('audio, video').forEach(el => el !== current && !el.paused && el.pause());
const addPlayListener = el => el.addEventListener('play', e => pauseOthers(e.target));
new MutationObserver(mutations =>
mutations.forEach(m => m.addedNodes.forEach(n =>
(n.nodeName === 'AUDIO' || n.nodeName === 'VIDEO') && addPlayListener(n)
))
).observe(document.body, { childList: true, subtree: true });
document.querySelectorAll('audio, video').forEach(addPlayListener);
});
</script>
<!-- 💙 MEMBERSCRIPT #123 💙 - ONE AUDIO AT A TIME -->
<script>
document.addEventListener('DOMContentLoaded', () => {
const pauseOthers = current =>
document.querySelectorAll('audio, video').forEach(el => el !== current && !el.paused && el.pause());
const addPlayListener = el => el.addEventListener('play', e => pauseOthers(e.target));
new MutationObserver(mutations =>
mutations.forEach(m => m.addedNodes.forEach(n =>
(n.nodeName === 'AUDIO' || n.nodeName === 'VIDEO') && addPlayListener(n)
))
).observe(document.body, { childList: true, subtree: true });
document.querySelectorAll('audio, video').forEach(addPlayListener);
});
</script>

#122 - Open External Links In New Tab
Automatically make external links open in a new tab, plus add nofollow and noreferrer attributes.
<!-- 💙 MEMBERSCRIPT #122 💙 - OPEN EXTERNAL LINKS IN NEW TAB -->
<script>
document.addEventListener('DOMContentLoaded', () => {
const thisDomain = location.hostname;
const externalSelector = `a:not([href*="${thisDomain}"]):not([href^="/"]):not([href^="#"]):not([ms-code-ext-link="ignore"])`;
document.querySelectorAll(externalSelector).forEach(link => {
link.target = '_blank'; // Open in new tab (comment out to disable)
link.rel = (link.rel ? link.rel + ' ' : '') + 'noreferrer'; // Add noreferrer (comment out to disable)
link.rel = (link.rel ? link.rel + ' ' : '') + 'nofollow'; // Add nofollow (comment out to disable)
});
});
</script>
<!-- 💙 MEMBERSCRIPT #122 💙 - OPEN EXTERNAL LINKS IN NEW TAB -->
<script>
document.addEventListener('DOMContentLoaded', () => {
const thisDomain = location.hostname;
const externalSelector = `a:not([href*="${thisDomain}"]):not([href^="/"]):not([href^="#"]):not([ms-code-ext-link="ignore"])`;
document.querySelectorAll(externalSelector).forEach(link => {
link.target = '_blank'; // Open in new tab (comment out to disable)
link.rel = (link.rel ? link.rel + ' ' : '') + 'noreferrer'; // Add noreferrer (comment out to disable)
link.rel = (link.rel ? link.rel + ' ' : '') + 'nofollow'; // Add nofollow (comment out to disable)
});
});
</script>

#121 - Render Array From Custom Field
Display any comma-separated list back to your members in Webflow.
<!-- 💙 MEMBERSCRIPT #121 v0.1 💙 - RENDER ARRAY FROM CUSTOM FIELD -->
<script>
// Function to render arrays from Memberstack custom fields
function renderMemberstackArrays() {
// Get all elements with the ms-code-render-array attribute
const arrayElements = document.querySelectorAll('[ms-code-render-array]');
arrayElements.forEach(element => {
const customFieldKey = element.getAttribute('ms-code-render-array');
// Get Memberstack data from localStorage
const memberData = JSON.parse(localStorage.getItem('_ms-mem'));
if (!memberData || !memberData.customFields || !memberData.customFields[customFieldKey]) {
// If no data found, remove the element
element.remove();
return;
}
const arrayString = memberData.customFields[customFieldKey];
// Convert string to array, trim whitespace
const arrayItems = arrayString.split(',').map(item => item.trim()).filter(item => item !== '');
if (arrayItems.length === 0) {
// If array is empty, remove the element
element.remove();
return;
}
// Store the parent element
const parentElement = element.parentNode;
// Clone the template
const templateItem = element.cloneNode(true);
// Remove the ms-code-render-array attribute from the template
templateItem.removeAttribute('ms-code-render-array');
// Create a document fragment to hold the new items
const fragment = document.createDocumentFragment();
// Render array items
arrayItems.forEach(item => {
const newItem = templateItem.cloneNode(true);
// Replace the content of the new item
replaceContent(newItem, item);
fragment.appendChild(newItem);
});
// Replace the original element with the new items
parentElement.replaceChild(fragment, element);
});
}
// Helper function to replace content in the element
function replaceContent(element, newContent) {
if (element.childNodes.length > 0) {
element.childNodes.forEach(child => {
if (child.nodeType === Node.ELEMENT_NODE) {
replaceContent(child, newContent);
} else if (child.nodeType === Node.TEXT_NODE) {
child.textContent = newContent;
}
});
} else {
element.textContent = newContent;
}
}
// Run the function when the DOM is fully loaded
document.addEventListener('DOMContentLoaded', renderMemberstackArrays);
</script>
<!-- 💙 MEMBERSCRIPT #121 v0.1 💙 - RENDER ARRAY FROM CUSTOM FIELD -->
<script>
// Function to render arrays from Memberstack custom fields
function renderMemberstackArrays() {
// Get all elements with the ms-code-render-array attribute
const arrayElements = document.querySelectorAll('[ms-code-render-array]');
arrayElements.forEach(element => {
const customFieldKey = element.getAttribute('ms-code-render-array');
// Get Memberstack data from localStorage
const memberData = JSON.parse(localStorage.getItem('_ms-mem'));
if (!memberData || !memberData.customFields || !memberData.customFields[customFieldKey]) {
// If no data found, remove the element
element.remove();
return;
}
const arrayString = memberData.customFields[customFieldKey];
// Convert string to array, trim whitespace
const arrayItems = arrayString.split(',').map(item => item.trim()).filter(item => item !== '');
if (arrayItems.length === 0) {
// If array is empty, remove the element
element.remove();
return;
}
// Store the parent element
const parentElement = element.parentNode;
// Clone the template
const templateItem = element.cloneNode(true);
// Remove the ms-code-render-array attribute from the template
templateItem.removeAttribute('ms-code-render-array');
// Create a document fragment to hold the new items
const fragment = document.createDocumentFragment();
// Render array items
arrayItems.forEach(item => {
const newItem = templateItem.cloneNode(true);
// Replace the content of the new item
replaceContent(newItem, item);
fragment.appendChild(newItem);
});
// Replace the original element with the new items
parentElement.replaceChild(fragment, element);
});
}
// Helper function to replace content in the element
function replaceContent(element, newContent) {
if (element.childNodes.length > 0) {
element.childNodes.forEach(child => {
if (child.nodeType === Node.ELEMENT_NODE) {
replaceContent(child, newContent);
} else if (child.nodeType === Node.TEXT_NODE) {
child.textContent = newContent;
}
});
} else {
element.textContent = newContent;
}
}
// Run the function when the DOM is fully loaded
document.addEventListener('DOMContentLoaded', renderMemberstackArrays);
</script>

#120 - Show/Hide Element Based On Device, OS, or Browser
Conditional visibility based on which device, operating system, or browser the visitor is using.
<!-- 💙 MEMBERSCRIPT #120 v0.1 💙 - DEVICE/OS/BROWSER CONDITIONAL VISIBILITY -->
<script>
// Define device types, operating systems, and browsers with their detection methods
const detectionTypes = {
// Device types
mobile: () => /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent),
tablet: () => /iPad|Android|Silk/i.test(navigator.userAgent) && !/Mobile/i.test(navigator.userAgent),
desktop: () => !(/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)),
touchdevice: () => 'ontouchstart' in window || navigator.maxTouchPoints > 0,
landscape: () => window.matchMedia("(orientation: landscape)").matches,
portrait: () => window.matchMedia("(orientation: portrait)").matches,
// Operating Systems
ios: () => /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream,
android: () => /Android/.test(navigator.userAgent),
macos: () => /Mac OS X/.test(navigator.userAgent) && !(/iPad|iPhone|iPod/.test(navigator.userAgent)),
windows: () => /Win/.test(navigator.platform),
linux: () => /Linux/.test(navigator.platform),
// Browsers
chrome: () => /Chrome/.test(navigator.userAgent) && !/Chromium/.test(navigator.userAgent),
firefox: () => /Firefox/.test(navigator.userAgent),
safari: () => /Safari/.test(navigator.userAgent) && !/Chrome/.test(navigator.userAgent),
edge: () => /Edg/.test(navigator.userAgent),
opera: () => /OPR/.test(navigator.userAgent) || /Opera/.test(navigator.userAgent),
ie: () => /Trident/.test(navigator.userAgent)
};
function detectTypes() {
const detected = [];
for (const [type, detector] of Object.entries(detectionTypes)) {
if (detector()) {
detected.push(type);
}
}
return detected;
}
function evaluateCondition(condition, currentTypes) {
const parts = condition.split('&').map(part => part.trim());
return parts.every(part => {
const orParts = part.split('|').map(p => p.trim());
return orParts.some(p => {
if (p.startsWith('!')) {
return !currentTypes.includes(p.slice(1));
}
return currentTypes.includes(p);
});
});
}
function updateElementVisibility() {
const currentTypes = detectTypes();
const elements = document.querySelectorAll('[ms-code-device-show]');
elements.forEach(element => {
const showAttribute = element.getAttribute('ms-code-device-show').toLowerCase();
const shouldShow = evaluateCondition(showAttribute, currentTypes);
element.style.display = shouldShow ? '' : 'none';
});
}
// Run on page load and window resize
window.addEventListener('load', updateElementVisibility);
window.addEventListener('resize', updateElementVisibility);
</script>
<!-- 💙 MEMBERSCRIPT #120 v0.1 💙 - DEVICE/OS/BROWSER CONDITIONAL VISIBILITY -->
<script>
// Define device types, operating systems, and browsers with their detection methods
const detectionTypes = {
// Device types
mobile: () => /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent),
tablet: () => /iPad|Android|Silk/i.test(navigator.userAgent) && !/Mobile/i.test(navigator.userAgent),
desktop: () => !(/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)),
touchdevice: () => 'ontouchstart' in window || navigator.maxTouchPoints > 0,
landscape: () => window.matchMedia("(orientation: landscape)").matches,
portrait: () => window.matchMedia("(orientation: portrait)").matches,
// Operating Systems
ios: () => /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream,
android: () => /Android/.test(navigator.userAgent),
macos: () => /Mac OS X/.test(navigator.userAgent) && !(/iPad|iPhone|iPod/.test(navigator.userAgent)),
windows: () => /Win/.test(navigator.platform),
linux: () => /Linux/.test(navigator.platform),
// Browsers
chrome: () => /Chrome/.test(navigator.userAgent) && !/Chromium/.test(navigator.userAgent),
firefox: () => /Firefox/.test(navigator.userAgent),
safari: () => /Safari/.test(navigator.userAgent) && !/Chrome/.test(navigator.userAgent),
edge: () => /Edg/.test(navigator.userAgent),
opera: () => /OPR/.test(navigator.userAgent) || /Opera/.test(navigator.userAgent),
ie: () => /Trident/.test(navigator.userAgent)
};
function detectTypes() {
const detected = [];
for (const [type, detector] of Object.entries(detectionTypes)) {
if (detector()) {
detected.push(type);
}
}
return detected;
}
function evaluateCondition(condition, currentTypes) {
const parts = condition.split('&').map(part => part.trim());
return parts.every(part => {
const orParts = part.split('|').map(p => p.trim());
return orParts.some(p => {
if (p.startsWith('!')) {
return !currentTypes.includes(p.slice(1));
}
return currentTypes.includes(p);
});
});
}
function updateElementVisibility() {
const currentTypes = detectTypes();
const elements = document.querySelectorAll('[ms-code-device-show]');
elements.forEach(element => {
const showAttribute = element.getAttribute('ms-code-device-show').toLowerCase();
const shouldShow = evaluateCondition(showAttribute, currentTypes);
element.style.display = shouldShow ? '' : 'none';
});
}
// Run on page load and window resize
window.addEventListener('load', updateElementVisibility);
window.addEventListener('resize', updateElementVisibility);
</script>

#119 - Age Calculator From Date Input
Calculate total years passed and prefill an input with the number.
<!-- 💙 MEMBERSCRIPT #119 v0.1 💙 - AGE CALCULATOR FROM DATE INPUT -->
<script>
// Function to calculate age
function calculateAge(birthDate) {
const today = new Date();
const birth = new Date(birthDate);
let age = today.getFullYear() - birth.getFullYear();
const monthDiff = today.getMonth() - birth.getMonth();
if (monthDiff < 0 || (monthDiff === 0 && today.getDate() < birth.getDate())) {
age--;
}
return age;
}
// Function to update age input
function updateAgeInput(birthdayInput) {
const attrValue = birthdayInput.getAttribute('ms-code-bday-input');
const ageInput = document.querySelector(`[ms-code-age-input="${attrValue}"]`);
if (birthdayInput.value) {
const age = calculateAge(birthdayInput.value);
ageInput.value = age;
} else {
ageInput.value = '';
}
}
// Function to set up event listeners for all birthday inputs
function setupAgeCalculators() {
const birthdayInputs = document.querySelectorAll('[ms-code-bday-input]');
birthdayInputs.forEach(input => {
input.addEventListener('input', () => updateAgeInput(input));
// Initial call to set age if birthday is pre-filled
updateAgeInput(input);
});
}
// Run setup when the DOM is fully loaded
document.addEventListener('DOMContentLoaded', setupAgeCalculators);
</script>
<!-- 💙 MEMBERSCRIPT #119 v0.1 💙 - AGE CALCULATOR FROM DATE INPUT -->
<script>
// Function to calculate age
function calculateAge(birthDate) {
const today = new Date();
const birth = new Date(birthDate);
let age = today.getFullYear() - birth.getFullYear();
const monthDiff = today.getMonth() - birth.getMonth();
if (monthDiff < 0 || (monthDiff === 0 && today.getDate() < birth.getDate())) {
age--;
}
return age;
}
// Function to update age input
function updateAgeInput(birthdayInput) {
const attrValue = birthdayInput.getAttribute('ms-code-bday-input');
const ageInput = document.querySelector(`[ms-code-age-input="${attrValue}"]`);
if (birthdayInput.value) {
const age = calculateAge(birthdayInput.value);
ageInput.value = age;
} else {
ageInput.value = '';
}
}
// Function to set up event listeners for all birthday inputs
function setupAgeCalculators() {
const birthdayInputs = document.querySelectorAll('[ms-code-bday-input]');
birthdayInputs.forEach(input => {
input.addEventListener('input', () => updateAgeInput(input));
// Initial call to set age if birthday is pre-filled
updateAgeInput(input);
});
}
// Run setup when the DOM is fully loaded
document.addEventListener('DOMContentLoaded', setupAgeCalculators);
</script>
MemberScripts
Instantly add custom features to your Webflow site.
Just paste a script, set attributes, and go live.
Join the Memberstack 2.0 Slack for tips, answers, and community scripts. Please note that these are not official features and support cannot be guaranteed.

#138 - Anchor Link Scroll Offset
Fix the problem with anchor links & sticky/fixed navbars in Webflow.
<!-- 💙 MEMBERSCRIPT #138 v0.1 💙 - ANCHOR LINK SCROLL OFFSET -->
<script>
// Disable Webflow's built-in smooth scrolling
var Webflow = Webflow || [];
Webflow.push(function() {
$(function() {
$(document).off('click.wf-scroll');
});
});
// Smooth scroll implementation with customizable settings
(function() {
// Customizable settings
const SCROLL_SETTINGS = {
duration: 1000, // in milliseconds
easing: 'easeInOutCubic' // 'linear', 'easeInQuad', 'easeOutQuad', 'easeInOutQuad', 'easeInCubic', 'easeOutCubic', 'easeInOutCubic'
};
const EASING_FUNCTIONS = {
linear: t => t,
easeInQuad: t => t * t,
easeOutQuad: t => t * (2 - t),
easeInOutQuad: t => t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t,
easeInCubic: t => t * t * t,
easeOutCubic: t => (--t) * t * t + 1,
easeInOutCubic: t => t < 0.5 ? 4 * t * t * t : (t - 1) * (2 * t - 2) * (2 * t - 2) + 1
};
function getOffset() {
const navbar = document.querySelector('[ms-code-scroll-offset]');
if (!navbar) return 0;
const navbarHeight = navbar.offsetHeight;
const customOffset = parseInt(navbar.getAttribute('ms-code-scroll-offset') || '0', 10);
return navbarHeight + customOffset;
}
function smoothScroll(target) {
const startPosition = window.pageYOffset;
const offset = getOffset();
const targetPosition = target.getBoundingClientRect().top + startPosition - offset;
const distance = targetPosition - startPosition;
let startTime = null;
function animation(currentTime) {
if (startTime === null) startTime = currentTime;
const timeElapsed = currentTime - startTime;
const progress = Math.min(timeElapsed / SCROLL_SETTINGS.duration, 1);
const easeProgress = EASING_FUNCTIONS[SCROLL_SETTINGS.easing](progress);
window.scrollTo(0, startPosition + distance * easeProgress);
if (timeElapsed < SCROLL_SETTINGS.duration) requestAnimationFrame(animation);
}
requestAnimationFrame(animation);
}
function handleClick(e) {
const href = e.currentTarget.getAttribute('href');
if (href.startsWith('#')) {
e.preventDefault();
const target = document.getElementById(href.slice(1));
if (target) smoothScroll(target);
}
}
function handleHashChange() {
if (window.location.hash) {
const target = document.getElementById(window.location.hash.slice(1));
if (target) {
setTimeout(() => smoothScroll(target), 0);
}
}
}
function init() {
document.querySelectorAll('a[href^="#"]').forEach(anchor => {
anchor.addEventListener('click', handleClick);
});
window.addEventListener('hashchange', handleHashChange);
handleHashChange(); // Handle initial hash on page load
}
document.addEventListener('DOMContentLoaded', init);
window.Webflow && window.Webflow.push(init);
})();
</script>
<!-- 💙 MEMBERSCRIPT #138 v0.1 💙 - ANCHOR LINK SCROLL OFFSET -->
<script>
// Disable Webflow's built-in smooth scrolling
var Webflow = Webflow || [];
Webflow.push(function() {
$(function() {
$(document).off('click.wf-scroll');
});
});
// Smooth scroll implementation with customizable settings
(function() {
// Customizable settings
const SCROLL_SETTINGS = {
duration: 1000, // in milliseconds
easing: 'easeInOutCubic' // 'linear', 'easeInQuad', 'easeOutQuad', 'easeInOutQuad', 'easeInCubic', 'easeOutCubic', 'easeInOutCubic'
};
const EASING_FUNCTIONS = {
linear: t => t,
easeInQuad: t => t * t,
easeOutQuad: t => t * (2 - t),
easeInOutQuad: t => t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t,
easeInCubic: t => t * t * t,
easeOutCubic: t => (--t) * t * t + 1,
easeInOutCubic: t => t < 0.5 ? 4 * t * t * t : (t - 1) * (2 * t - 2) * (2 * t - 2) + 1
};
function getOffset() {
const navbar = document.querySelector('[ms-code-scroll-offset]');
if (!navbar) return 0;
const navbarHeight = navbar.offsetHeight;
const customOffset = parseInt(navbar.getAttribute('ms-code-scroll-offset') || '0', 10);
return navbarHeight + customOffset;
}
function smoothScroll(target) {
const startPosition = window.pageYOffset;
const offset = getOffset();
const targetPosition = target.getBoundingClientRect().top + startPosition - offset;
const distance = targetPosition - startPosition;
let startTime = null;
function animation(currentTime) {
if (startTime === null) startTime = currentTime;
const timeElapsed = currentTime - startTime;
const progress = Math.min(timeElapsed / SCROLL_SETTINGS.duration, 1);
const easeProgress = EASING_FUNCTIONS[SCROLL_SETTINGS.easing](progress);
window.scrollTo(0, startPosition + distance * easeProgress);
if (timeElapsed < SCROLL_SETTINGS.duration) requestAnimationFrame(animation);
}
requestAnimationFrame(animation);
}
function handleClick(e) {
const href = e.currentTarget.getAttribute('href');
if (href.startsWith('#')) {
e.preventDefault();
const target = document.getElementById(href.slice(1));
if (target) smoothScroll(target);
}
}
function handleHashChange() {
if (window.location.hash) {
const target = document.getElementById(window.location.hash.slice(1));
if (target) {
setTimeout(() => smoothScroll(target), 0);
}
}
}
function init() {
document.querySelectorAll('a[href^="#"]').forEach(anchor => {
anchor.addEventListener('click', handleClick);
});
window.addEventListener('hashchange', handleHashChange);
handleHashChange(); // Handle initial hash on page load
}
document.addEventListener('DOMContentLoaded', init);
window.Webflow && window.Webflow.push(init);
})();
</script>
.avif)
#137 - Display The Visitors' Country Name
Replace text with the country a user is in based on their IP address.
<!-- 💙 MEMBERSCRIPT #137 v0.1 💙 - DISPLAY COUNTRY NAME -->
<script>
document.addEventListener('DOMContentLoaded', function() {
fetch('https://ipapi.co/json/')
.then(response => response.json())
.then(data => {
if (data.country_name) {
const countryElements = document.querySelectorAll('[ms-code-display-country]');
countryElements.forEach(element => {
element.textContent = data.country_name;
});
}
})
.catch(error => {
console.error('Error fetching country:', error);
});
});
</script>
<!-- 💙 MEMBERSCRIPT #137 v0.1 💙 - DISPLAY COUNTRY NAME -->
<script>
document.addEventListener('DOMContentLoaded', function() {
fetch('https://ipapi.co/json/')
.then(response => response.json())
.then(data => {
if (data.country_name) {
const countryElements = document.querySelectorAll('[ms-code-display-country]');
countryElements.forEach(element => {
element.textContent = data.country_name;
});
}
})
.catch(error => {
console.error('Error fetching country:', error);
});
});
</script>

#136 - Remove Section Path From URL
When a section is navigated to, the anchor link path will be removed.
<!-- 💙 MEMBERSCRIPT #136 💙 REMOVE SECTION PATH FROM URL -->
<script>
document.addEventListener('DOMContentLoaded', function() {
// Check if there's a hash in the URL
if (window.location.hash) {
// Get the target element
const targetId = window.location.hash.substring(1);
const targetElement = document.getElementById(targetId);
if (targetElement) {
// Scroll to the target element
targetElement.scrollIntoView({behavior: 'smooth'});
// Remove the hash after a short delay (to allow scrolling to complete)
setTimeout(function() {
history.pushState("", document.title, window.location.pathname + window.location.search);
}, 100);
}
}
// Add click event listeners to all internal links
document.querySelectorAll('a[href^="#"]').forEach(anchor => {
anchor.addEventListener('click', function(e) {
e.preventDefault();
const targetId = this.getAttribute('href').substring(1);
const targetElement = document.getElementById(targetId);
if (targetElement) {
targetElement.scrollIntoView({behavior: 'smooth'});
// Remove the hash after a short delay (to allow scrolling to complete)
setTimeout(function() {
history.pushState("", document.title, window.location.pathname + window.location.search);
}, 100);
}
});
});
});
</script>
<!-- 💙 MEMBERSCRIPT #136 💙 REMOVE SECTION PATH FROM URL -->
<script>
document.addEventListener('DOMContentLoaded', function() {
// Check if there's a hash in the URL
if (window.location.hash) {
// Get the target element
const targetId = window.location.hash.substring(1);
const targetElement = document.getElementById(targetId);
if (targetElement) {
// Scroll to the target element
targetElement.scrollIntoView({behavior: 'smooth'});
// Remove the hash after a short delay (to allow scrolling to complete)
setTimeout(function() {
history.pushState("", document.title, window.location.pathname + window.location.search);
}, 100);
}
}
// Add click event listeners to all internal links
document.querySelectorAll('a[href^="#"]').forEach(anchor => {
anchor.addEventListener('click', function(e) {
e.preventDefault();
const targetId = this.getAttribute('href').substring(1);
const targetElement = document.getElementById(targetId);
if (targetElement) {
targetElement.scrollIntoView({behavior: 'smooth'});
// Remove the hash after a short delay (to allow scrolling to complete)
setTimeout(function() {
history.pushState("", document.title, window.location.pathname + window.location.search);
}, 100);
}
});
});
});
</script>

#135 - Redirect Based On Select Value
Dynamically set the form redirect based on the users' selection.
<!-- 💙 MEMBERSCRIPT #135 v0.2 💙 - REDIRECT FORM FROM SELECT VALUE -->
<script>
(function() {
'use strict';
function initDropdownRedirect() {
const dropdown = document.querySelector('select[ms-code-dropdown-redirect]');
if (!dropdown) return;
const form = dropdown.closest('form');
if (!form) return;
function updateRedirect() {
const selectedValue = dropdown.value;
form.setAttribute('redirect', selectedValue);
form.setAttribute('data-redirect', selectedValue);
}
function handleSubmit(event) {
event.preventDefault();
const redirectUrl = form.getAttribute('data-redirect') || form.getAttribute('redirect');
if (redirectUrl) {
setTimeout(() => {
window.location.href = redirectUrl;
}, 500); // Delay redirect by 500 milliseconds
} else {
form.submit(); // Fall back to normal form submission if no redirect is set
}
}
dropdown.addEventListener('change', updateRedirect);
form.addEventListener('submit', handleSubmit);
// Initialize redirect on page load
updateRedirect();
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initDropdownRedirect);
} else {
initDropdownRedirect();
}
})();
</script>
<!-- 💙 MEMBERSCRIPT #135 v0.2 💙 - REDIRECT FORM FROM SELECT VALUE -->
<script>
(function() {
'use strict';
function initDropdownRedirect() {
const dropdown = document.querySelector('select[ms-code-dropdown-redirect]');
if (!dropdown) return;
const form = dropdown.closest('form');
if (!form) return;
function updateRedirect() {
const selectedValue = dropdown.value;
form.setAttribute('redirect', selectedValue);
form.setAttribute('data-redirect', selectedValue);
}
function handleSubmit(event) {
event.preventDefault();
const redirectUrl = form.getAttribute('data-redirect') || form.getAttribute('redirect');
if (redirectUrl) {
setTimeout(() => {
window.location.href = redirectUrl;
}, 500); // Delay redirect by 500 milliseconds
} else {
form.submit(); // Fall back to normal form submission if no redirect is set
}
}
dropdown.addEventListener('change', updateRedirect);
form.addEventListener('submit', handleSubmit);
// Initialize redirect on page load
updateRedirect();
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initDropdownRedirect);
} else {
initDropdownRedirect();
}
})();
</script>

#134 - Scroll To Top On Tab Change
When the tab is changed, the page will scroll to the top of the tab section.
<!-- 💙 MEMBERSCRIPT #134 💙 - SCROLL TO TOP OF TABS ON CHANGE -->
<script>
document.addEventListener('DOMContentLoaded', function() {
// Select all tab containers with the ms-code-tab-scroll-top attribute
const tabContainers = document.querySelectorAll('.w-tabs[ms-code-tab-scroll-top]');
tabContainers.forEach(container => {
const tabLinks = container.querySelectorAll('.w-tab-link');
const scrollTopValue = parseInt(container.getAttribute('ms-code-tab-scroll-top') || '0');
tabLinks.forEach(link => {
link.addEventListener('click', function(e) {
// Small delay to ensure the new tab content is rendered
setTimeout(() => {
// Find the active tab pane within this container
const activePane = container.querySelector('.w-tab-pane.w--tab-active');
if (activePane) {
// Calculate the new scroll position
const newScrollPosition = container.getBoundingClientRect().top + window.pageYOffset + scrollTopValue;
// Scroll to the new position
window.scrollTo({
top: newScrollPosition,
behavior: 'smooth'
});
}
}, 50); // 50ms delay, adjust if needed
});
});
});
});
</script>
<!-- 💙 MEMBERSCRIPT #134 💙 - SCROLL TO TOP OF TABS ON CHANGE -->
<script>
document.addEventListener('DOMContentLoaded', function() {
// Select all tab containers with the ms-code-tab-scroll-top attribute
const tabContainers = document.querySelectorAll('.w-tabs[ms-code-tab-scroll-top]');
tabContainers.forEach(container => {
const tabLinks = container.querySelectorAll('.w-tab-link');
const scrollTopValue = parseInt(container.getAttribute('ms-code-tab-scroll-top') || '0');
tabLinks.forEach(link => {
link.addEventListener('click', function(e) {
// Small delay to ensure the new tab content is rendered
setTimeout(() => {
// Find the active tab pane within this container
const activePane = container.querySelector('.w-tab-pane.w--tab-active');
if (activePane) {
// Calculate the new scroll position
const newScrollPosition = container.getBoundingClientRect().top + window.pageYOffset + scrollTopValue;
// Scroll to the new position
window.scrollTo({
top: newScrollPosition,
behavior: 'smooth'
});
}
}, 50); // 50ms delay, adjust if needed
});
});
});
});
</script>

#133 - Auto Watermark Images
Easily add a watermark to images on your Webflow site.
<!-- 💙 MEMBERSCRIPT #133 v0.1 💙 - AUTO IMAGE WATERMARK -->
<script>
function addWatermarkToImages() {
const images = document.querySelectorAll('img[ms-code-watermark]');
images.forEach(img => {
img.crossOrigin = "Anonymous"; // This allows us to work with images from other domains
img.onload = function() {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
// Set canvas size to match the image
canvas.width = img.width;
canvas.height = img.height;
// Draw the original image onto the canvas
ctx.drawImage(img, 0, 0, img.width, img.height);
// Get watermark text from attribute
const watermarkText = img.getAttribute('ms-code-watermark') || 'Watermark';
// Add watermark
ctx.font = `${img.width / 20}px Arial`; // Adjust font size based on image width
ctx.fillStyle = 'rgba(255, 255, 255, 0.5)';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
// Rotate and draw the watermark text
ctx.save();
ctx.translate(canvas.width / 2, canvas.height / 2);
ctx.rotate(-Math.PI / 4); // Rotate 45 degrees
ctx.fillText(watermarkText, 0, 0);
ctx.restore();
// Preserve the original image's classes and other attributes
canvas.className = img.className;
for (let i = 0; i < img.attributes.length; i++) {
const attr = img.attributes[i];
if (attr.name !== 'src' && attr.name !== 'ms-code-watermark') {
canvas.setAttribute(attr.name, attr.value);
}
}
// Replace the original image with the watermarked canvas
img.parentNode.replaceChild(canvas, img);
};
// Trigger onload event (in case the image is already loaded)
if (img.complete) {
img.onload();
}
});
}
// Run the function when the DOM is fully loaded
document.addEventListener('DOMContentLoaded', addWatermarkToImages);
</script>
<!-- 💙 MEMBERSCRIPT #133 v0.1 💙 - AUTO IMAGE WATERMARK -->
<script>
function addWatermarkToImages() {
const images = document.querySelectorAll('img[ms-code-watermark]');
images.forEach(img => {
img.crossOrigin = "Anonymous"; // This allows us to work with images from other domains
img.onload = function() {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
// Set canvas size to match the image
canvas.width = img.width;
canvas.height = img.height;
// Draw the original image onto the canvas
ctx.drawImage(img, 0, 0, img.width, img.height);
// Get watermark text from attribute
const watermarkText = img.getAttribute('ms-code-watermark') || 'Watermark';
// Add watermark
ctx.font = `${img.width / 20}px Arial`; // Adjust font size based on image width
ctx.fillStyle = 'rgba(255, 255, 255, 0.5)';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
// Rotate and draw the watermark text
ctx.save();
ctx.translate(canvas.width / 2, canvas.height / 2);
ctx.rotate(-Math.PI / 4); // Rotate 45 degrees
ctx.fillText(watermarkText, 0, 0);
ctx.restore();
// Preserve the original image's classes and other attributes
canvas.className = img.className;
for (let i = 0; i < img.attributes.length; i++) {
const attr = img.attributes[i];
if (attr.name !== 'src' && attr.name !== 'ms-code-watermark') {
canvas.setAttribute(attr.name, attr.value);
}
}
// Replace the original image with the watermarked canvas
img.parentNode.replaceChild(canvas, img);
};
// Trigger onload event (in case the image is already loaded)
if (img.complete) {
img.onload();
}
});
}
// Run the function when the DOM is fully loaded
document.addEventListener('DOMContentLoaded', addWatermarkToImages);
</script>

#132 - Hide Elements With Escape Key
Add one attribute and when the esc key is clicked, the element will be set to display none.
<!-- 💙 MEMBERSCRIPT 💙 - HIDE ELEMENTS WITH ESC KEY -->
<script>
document.addEventListener('keydown', function(event) {
// Check if the pressed key is ESC (key code 27)
if (event.key === 'Escape' || event.keyCode === 27) {
// Find all elements with the attribute ms-code-close-esc
const elements = document.querySelectorAll('[ms-code-close-esc]');
// Loop through the elements and set their display to 'none'
elements.forEach(function(element) {
element.style.display = 'none';
});
}
});
</script>
<!-- 💙 MEMBERSCRIPT 💙 - HIDE ELEMENTS WITH ESC KEY -->
<script>
document.addEventListener('keydown', function(event) {
// Check if the pressed key is ESC (key code 27)
if (event.key === 'Escape' || event.keyCode === 27) {
// Find all elements with the attribute ms-code-close-esc
const elements = document.querySelectorAll('[ms-code-close-esc]');
// Loop through the elements and set their display to 'none'
elements.forEach(function(element) {
element.style.display = 'none';
});
}
});
</script>

#131 - Add Up Number Inputs
Take the value of number inputs and show it in an input value, or in a text span.
<!-- 💙 MEMBERSCRIPT #131 v0.1 💙 - CALCULATE NUMBER INPUTS -->
<script>
// Function to initialize the counter functionality
function initializeCounter() {
const counters = {};
// Find all elements with ms-code-show-number attribute
document.querySelectorAll('[ms-code-show-number]').forEach(el => {
const counterName = el.getAttribute('ms-code-show-number');
if (!counters[counterName]) {
counters[counterName] = { total: 0, displays: [] };
}
counters[counterName].displays.push(el);
});
// Find all input elements with ms-code-add-number attribute
document.querySelectorAll('input[ms-code-add-number]').forEach(input => {
const counterName = input.getAttribute('ms-code-add-number');
if (counters[counterName]) {
input.addEventListener('input', updateCounter);
}
});
// Function to update counter when input changes
function updateCounter(event) {
const input = event.target;
const counterName = input.getAttribute('ms-code-add-number');
const counter = counters[counterName];
if (counter) {
counter.total = 0;
document.querySelectorAll(`input[ms-code-add-number="${counterName}"]`).forEach(input => {
counter.total += parseInt(input.value) || 0;
});
counter.displays.forEach(display => {
if (display.tagName === 'INPUT') {
display.value = counter.total;
} else {
display.textContent = counter.total;
}
});
}
}
// Initial update for all counters
Object.keys(counters).forEach(counterName => {
const input = document.querySelector(`input[ms-code-add-number="${counterName}"]`);
if (input) {
input.dispatchEvent(new Event('input'));
}
});
}
// Run the initialization when the DOM is fully loaded
document.addEventListener('DOMContentLoaded', initializeCounter);
</script>
<!-- 💙 MEMBERSCRIPT #131 v0.1 💙 - CALCULATE NUMBER INPUTS -->
<script>
// Function to initialize the counter functionality
function initializeCounter() {
const counters = {};
// Find all elements with ms-code-show-number attribute
document.querySelectorAll('[ms-code-show-number]').forEach(el => {
const counterName = el.getAttribute('ms-code-show-number');
if (!counters[counterName]) {
counters[counterName] = { total: 0, displays: [] };
}
counters[counterName].displays.push(el);
});
// Find all input elements with ms-code-add-number attribute
document.querySelectorAll('input[ms-code-add-number]').forEach(input => {
const counterName = input.getAttribute('ms-code-add-number');
if (counters[counterName]) {
input.addEventListener('input', updateCounter);
}
});
// Function to update counter when input changes
function updateCounter(event) {
const input = event.target;
const counterName = input.getAttribute('ms-code-add-number');
const counter = counters[counterName];
if (counter) {
counter.total = 0;
document.querySelectorAll(`input[ms-code-add-number="${counterName}"]`).forEach(input => {
counter.total += parseInt(input.value) || 0;
});
counter.displays.forEach(display => {
if (display.tagName === 'INPUT') {
display.value = counter.total;
} else {
display.textContent = counter.total;
}
});
}
}
// Initial update for all counters
Object.keys(counters).forEach(counterName => {
const input = document.querySelector(`input[ms-code-add-number="${counterName}"]`);
if (input) {
input.dispatchEvent(new Event('input'));
}
});
}
// Run the initialization when the DOM is fully loaded
document.addEventListener('DOMContentLoaded', initializeCounter);
</script>

#130 - Auto Submit Form When All Inputs Change
Skip the need for a submit button and submit the form when all inputs change.
<!-- 💙 MEMBERSCRIPT #130 v0.1 💙 - AUTO SUBMIT FORMS FROM INPUT CHANGE -->
<script>
document.addEventListener('DOMContentLoaded', () => {
const forms = document.querySelectorAll('form[ms-code-auto-submit]');
forms.forEach(form => {
const fields = form.querySelectorAll('input:not([type="submit"]):not([type="button"]):not([type="reset"]), select, textarea');
const fieldStates = new Map(Array.from(fields).map(field => [field, false]));
function updateFieldState(field, checkImmediately = false) {
switch (field.type) {
case 'checkbox':
fieldStates.set(field, true); // Considered interacted with once changed
break;
case 'radio':
const radioGroup = form.querySelectorAll(`input[type="radio"][name="${field.name}"]`);
radioGroup.forEach(radio => fieldStates.set(radio, true));
break;
case 'select-one':
case 'select-multiple':
fieldStates.set(field, field.value !== '');
break;
case 'file':
fieldStates.set(field, field.files.length > 0);
break;
case 'hidden':
fieldStates.set(field, true); // Always consider hidden fields as filled
break;
default:
// For text inputs, only update on blur or if checkImmediately is true
if (field.type === 'text' || field.type === 'password' || field.type === 'email' || field.type === 'tel' || field.type === 'url' || field.tagName === 'TEXTAREA') {
if (checkImmediately || !field.dataset.blurred) {
fieldStates.set(field, field.value.trim() !== '');
}
} else {
fieldStates.set(field, field.value.trim() !== '');
}
}
if (checkImmediately) {
checkAndSubmit();
}
}
function checkAndSubmit() {
if (Array.from(fieldStates.values()).every(state => state)) {
// Create and dispatch a submit event
const submitEvent = new Event('submit', {
bubbles: true,
cancelable: true
});
const submitted = form.dispatchEvent(submitEvent);
// If the event wasn't prevented, manually submit the form
if (submitted) {
form.submit();
}
}
}
fields.forEach(field => {
// Use 'change' event for checkboxes, radios, file inputs, and selects
if (['checkbox', 'radio', 'file', 'select-one', 'select-multiple'].includes(field.type) || field.tagName === 'SELECT') {
field.addEventListener('change', () => updateFieldState(field, true));
}
// For text-like inputs, use 'blur' event
if (field.type === 'text' || field.type === 'password' || field.type === 'email' || field.type === 'tel' || field.type === 'url' || field.tagName === 'TEXTAREA') {
field.addEventListener('blur', () => {
field.dataset.blurred = 'true';
updateFieldState(field, true);
});
// Also check on input, but don't submit immediately
field.addEventListener('input', () => updateFieldState(field, false));
}
});
// Initial check for pre-filled fields (e.g., browser autofill)
fields.forEach(field => updateFieldState(field, false));
checkAndSubmit();
});
});
</script>
<!-- 💙 MEMBERSCRIPT #130 v0.1 💙 - AUTO SUBMIT FORMS FROM INPUT CHANGE -->
<script>
document.addEventListener('DOMContentLoaded', () => {
const forms = document.querySelectorAll('form[ms-code-auto-submit]');
forms.forEach(form => {
const fields = form.querySelectorAll('input:not([type="submit"]):not([type="button"]):not([type="reset"]), select, textarea');
const fieldStates = new Map(Array.from(fields).map(field => [field, false]));
function updateFieldState(field, checkImmediately = false) {
switch (field.type) {
case 'checkbox':
fieldStates.set(field, true); // Considered interacted with once changed
break;
case 'radio':
const radioGroup = form.querySelectorAll(`input[type="radio"][name="${field.name}"]`);
radioGroup.forEach(radio => fieldStates.set(radio, true));
break;
case 'select-one':
case 'select-multiple':
fieldStates.set(field, field.value !== '');
break;
case 'file':
fieldStates.set(field, field.files.length > 0);
break;
case 'hidden':
fieldStates.set(field, true); // Always consider hidden fields as filled
break;
default:
// For text inputs, only update on blur or if checkImmediately is true
if (field.type === 'text' || field.type === 'password' || field.type === 'email' || field.type === 'tel' || field.type === 'url' || field.tagName === 'TEXTAREA') {
if (checkImmediately || !field.dataset.blurred) {
fieldStates.set(field, field.value.trim() !== '');
}
} else {
fieldStates.set(field, field.value.trim() !== '');
}
}
if (checkImmediately) {
checkAndSubmit();
}
}
function checkAndSubmit() {
if (Array.from(fieldStates.values()).every(state => state)) {
// Create and dispatch a submit event
const submitEvent = new Event('submit', {
bubbles: true,
cancelable: true
});
const submitted = form.dispatchEvent(submitEvent);
// If the event wasn't prevented, manually submit the form
if (submitted) {
form.submit();
}
}
}
fields.forEach(field => {
// Use 'change' event for checkboxes, radios, file inputs, and selects
if (['checkbox', 'radio', 'file', 'select-one', 'select-multiple'].includes(field.type) || field.tagName === 'SELECT') {
field.addEventListener('change', () => updateFieldState(field, true));
}
// For text-like inputs, use 'blur' event
if (field.type === 'text' || field.type === 'password' || field.type === 'email' || field.type === 'tel' || field.type === 'url' || field.tagName === 'TEXTAREA') {
field.addEventListener('blur', () => {
field.dataset.blurred = 'true';
updateFieldState(field, true);
});
// Also check on input, but don't submit immediately
field.addEventListener('input', () => updateFieldState(field, false));
}
});
// Initial check for pre-filled fields (e.g., browser autofill)
fields.forEach(field => updateFieldState(field, false));
checkAndSubmit();
});
});
</script>

#129 - Country Gating
Block visitors from viewing your site if they're in one of the disallowed countries.
<!-- 💙 MEMBERSCRIPT #129 v0.1 💙 - COUNTRY GATING -->
<script>
// Configuration
const ACCESS_DENIED_PAGE = '/access-denied';
// List of disallowed countries using ISO 3166-1 alpha-2 country codes
const DISALLOWED_COUNTRIES = [
// "US", // United States
// "CN", // China
// "RU", // Russia
// "IN", // India
// "JP", // Japan
// "DE", // Germany
// "GB", // United Kingdom
// "FR", // France
// "BR", // Brazil
// "IT", // Italy
// Add more countries as needed
];
// Function to get visitor's country and check access
function checkCountryAccess() {
// Check if we're already on the access denied page
if (window.location.pathname === ACCESS_DENIED_PAGE) {
return; // Don't redirect if already on the access denied page
}
fetch('https://ipapi.co/json/')
.then(response => response.json())
.then(data => {
const visitorCountry = data.country_code; // This returns the ISO 3166-1 alpha-2 country code
if (DISALLOWED_COUNTRIES.includes(visitorCountry)) {
window.location.href = ACCESS_DENIED_PAGE;
}
})
.catch(error => {
console.error('Error fetching IP data:', error);
});
}
// Run the check when the page loads
document.addEventListener('DOMContentLoaded', checkCountryAccess);
</script>
<!-- 💙 MEMBERSCRIPT #129 v0.1 💙 - COUNTRY GATING -->
<script>
// Configuration
const ACCESS_DENIED_PAGE = '/access-denied';
// List of disallowed countries using ISO 3166-1 alpha-2 country codes
const DISALLOWED_COUNTRIES = [
// "US", // United States
// "CN", // China
// "RU", // Russia
// "IN", // India
// "JP", // Japan
// "DE", // Germany
// "GB", // United Kingdom
// "FR", // France
// "BR", // Brazil
// "IT", // Italy
// Add more countries as needed
];
// Function to get visitor's country and check access
function checkCountryAccess() {
// Check if we're already on the access denied page
if (window.location.pathname === ACCESS_DENIED_PAGE) {
return; // Don't redirect if already on the access denied page
}
fetch('https://ipapi.co/json/')
.then(response => response.json())
.then(data => {
const visitorCountry = data.country_code; // This returns the ISO 3166-1 alpha-2 country code
if (DISALLOWED_COUNTRIES.includes(visitorCountry)) {
window.location.href = ACCESS_DENIED_PAGE;
}
})
.catch(error => {
console.error('Error fetching IP data:', error);
});
}
// Run the check when the page loads
document.addEventListener('DOMContentLoaded', checkCountryAccess);
</script>

#128 - Hide Elements After They've Been Seen
Add one attribute, and your visitors will only see that element one time. After refresh, it will be gone.
<!-- 💙 MEMBERSCRIPT #128 💙 - ONLY SHOW ELEMENT ONCE -->
<script>
document.addEventListener('DOMContentLoaded', function() {
// Find all elements with the ms-code-show-once attribute
const elements = document.querySelectorAll('[ms-code-show-once]');
elements.forEach(element => {
const identifier = element.getAttribute('ms-code-show-once');
const storageKey = `ms-code-shown-${identifier}`;
// Check if the element has been seen before
if (localStorage.getItem(storageKey) !== 'true') {
// If not seen, show the element
element.style.display = 'block';
// Mark it as seen in localStorage
localStorage.setItem(storageKey, 'true');
} else {
// If already seen, hide the element
element.style.display = 'none';
}
});
});
</script>
<!-- 💙 MEMBERSCRIPT #128 💙 - ONLY SHOW ELEMENT ONCE -->
<script>
document.addEventListener('DOMContentLoaded', function() {
// Find all elements with the ms-code-show-once attribute
const elements = document.querySelectorAll('[ms-code-show-once]');
elements.forEach(element => {
const identifier = element.getAttribute('ms-code-show-once');
const storageKey = `ms-code-shown-${identifier}`;
// Check if the element has been seen before
if (localStorage.getItem(storageKey) !== 'true') {
// If not seen, show the element
element.style.display = 'block';
// Mark it as seen in localStorage
localStorage.setItem(storageKey, 'true');
} else {
// If already seen, hide the element
element.style.display = 'none';
}
});
});
</script>

#127 - Validate Text Inputs
Validate text inputs against any list of strings, including wildcards!
<!-- 💙 MEMBERSCRIPT #127 v0.1 💙 - TEXT INPUT VALIDATION -->
<script>
document.addEventListener('DOMContentLoaded', function() {
// Debounce function
function debounce(func, wait) {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
}
// Find all fields with ms-code-require attribute
const fields = document.querySelectorAll('[ms-code-require]');
fields.forEach(field => {
// Get the error element for this field
const errorElement = document.querySelector(`[ms-code-require-error="${field.getAttribute('ms-code-require')}"]`);
// Hide error message initially
if (errorElement) {
errorElement.style.display = 'none';
}
// Get the form containing the field
const form = field.closest('form');
// Get the submit button
const submitButton = form ? form.querySelector(`[ms-code-submit-button="${field.getAttribute('ms-code-require')}"]`) : null;
// Get the require-list attribute value
const requireList = field.getAttribute('ms-code-require-list');
if (requireList) {
// Convert the require-list to an array of regex patterns
const patterns = requireList.split(',').map(pattern => {
return pattern.replace(/\{([^}]+)\}/g, (match, p1) => {
return p1.split('').map(char => {
switch(char) {
case '0': return '\\d';
case 'A': return '[A-Z]';
case 'a': return '[a-z]';
default: return char;
}
}).join('');
});
});
// Validate function
function validateField() {
const value = field.value;
const isValid = patterns.some(pattern => new RegExp(`^${pattern}$`).test(value));
if (errorElement) {
errorElement.style.display = isValid ? 'none' : 'block';
}
if (submitButton) {
submitButton.style.opacity = isValid ? '1' : '0.5';
submitButton.style.pointerEvents = isValid ? 'auto' : 'none';
}
return isValid;
}
// Debounced validate function
const debouncedValidate = debounce(validateField, 500);
// Add blur event listener
field.addEventListener('blur', validateField);
// Add input event listener for debounced validation
field.addEventListener('input', debouncedValidate);
// Handle form submission
if (form) {
form.addEventListener('submit', function(event) {
if (!validateField() && submitButton) {
event.preventDefault();
field.focus();
}
});
}
}
});
});
</script>
<!-- 💙 MEMBERSCRIPT #127 v0.1 💙 - TEXT INPUT VALIDATION -->
<script>
document.addEventListener('DOMContentLoaded', function() {
// Debounce function
function debounce(func, wait) {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
}
// Find all fields with ms-code-require attribute
const fields = document.querySelectorAll('[ms-code-require]');
fields.forEach(field => {
// Get the error element for this field
const errorElement = document.querySelector(`[ms-code-require-error="${field.getAttribute('ms-code-require')}"]`);
// Hide error message initially
if (errorElement) {
errorElement.style.display = 'none';
}
// Get the form containing the field
const form = field.closest('form');
// Get the submit button
const submitButton = form ? form.querySelector(`[ms-code-submit-button="${field.getAttribute('ms-code-require')}"]`) : null;
// Get the require-list attribute value
const requireList = field.getAttribute('ms-code-require-list');
if (requireList) {
// Convert the require-list to an array of regex patterns
const patterns = requireList.split(',').map(pattern => {
return pattern.replace(/\{([^}]+)\}/g, (match, p1) => {
return p1.split('').map(char => {
switch(char) {
case '0': return '\\d';
case 'A': return '[A-Z]';
case 'a': return '[a-z]';
default: return char;
}
}).join('');
});
});
// Validate function
function validateField() {
const value = field.value;
const isValid = patterns.some(pattern => new RegExp(`^${pattern}$`).test(value));
if (errorElement) {
errorElement.style.display = isValid ? 'none' : 'block';
}
if (submitButton) {
submitButton.style.opacity = isValid ? '1' : '0.5';
submitButton.style.pointerEvents = isValid ? 'auto' : 'none';
}
return isValid;
}
// Debounced validate function
const debouncedValidate = debounce(validateField, 500);
// Add blur event listener
field.addEventListener('blur', validateField);
// Add input event listener for debounced validation
field.addEventListener('input', debouncedValidate);
// Handle form submission
if (form) {
form.addEventListener('submit', function(event) {
if (!validateField() && submitButton) {
event.preventDefault();
field.focus();
}
});
}
}
});
});
</script>

#126 - Post Form To Webhook Without Redirecting
Post data to a webhook & keep the default Webflow form behavior.
<!-- 💙 MEMBERSCRIPT #126 v0.1 💙 - POST FORM DATA TO WEBHOOK WITHOUT REDIRECTING -->
<script>
// Wait for the DOM to be fully loaded
document.addEventListener('DOMContentLoaded', function() {
// Select all forms with the ms-code-form-no-redirect attribute
const forms = document.querySelectorAll('form[ms-code-form-no-redirect]');
forms.forEach(form => {
// Select the success and error message elements for this form
const formWrapper = form.closest('.w-form');
const successMessage = formWrapper.querySelector('.w-form-done');
const errorMessage = formWrapper.querySelector('.w-form-fail');
// Add submit event listener to the form
form.addEventListener('submit', function(event) {
// Prevent the default form submission
event.preventDefault();
// Get the form data
const formData = new FormData(form);
// Get the submit button and set its text to the waiting message
const submitButton = form.querySelector('input[type="submit"], button[type="submit"]');
const originalButtonText = submitButton.value || submitButton.textContent;
const waitingText = submitButton.getAttribute('data-wait') || 'Please wait...';
if (submitButton.tagName === 'INPUT') {
submitButton.value = waitingText;
} else {
submitButton.textContent = waitingText;
}
// Disable the submit button
submitButton.disabled = true;
// Send the form data to the form's action URL using fetch
fetch(form.action, {
method: 'POST',
body: formData
})
.then(response => {
if (response.ok) {
// If the submission was successful, show the success message
form.style.display = 'none';
successMessage.style.display = 'block';
errorMessage.style.display = 'none';
} else {
// If there was an error, show the error message
throw new Error('Form submission failed');
}
})
.catch(error => {
// If there was a network error or the submission failed, show the error message
console.error('Error:', error);
errorMessage.style.display = 'block';
successMessage.style.display = 'none';
})
.finally(() => {
// Reset the submit button text and re-enable it
if (submitButton.tagName === 'INPUT') {
submitButton.value = originalButtonText;
} else {
submitButton.textContent = originalButtonText;
}
submitButton.disabled = false;
});
});
});
});
</script>
<!-- 💙 MEMBERSCRIPT #126 v0.1 💙 - POST FORM DATA TO WEBHOOK WITHOUT REDIRECTING -->
<script>
// Wait for the DOM to be fully loaded
document.addEventListener('DOMContentLoaded', function() {
// Select all forms with the ms-code-form-no-redirect attribute
const forms = document.querySelectorAll('form[ms-code-form-no-redirect]');
forms.forEach(form => {
// Select the success and error message elements for this form
const formWrapper = form.closest('.w-form');
const successMessage = formWrapper.querySelector('.w-form-done');
const errorMessage = formWrapper.querySelector('.w-form-fail');
// Add submit event listener to the form
form.addEventListener('submit', function(event) {
// Prevent the default form submission
event.preventDefault();
// Get the form data
const formData = new FormData(form);
// Get the submit button and set its text to the waiting message
const submitButton = form.querySelector('input[type="submit"], button[type="submit"]');
const originalButtonText = submitButton.value || submitButton.textContent;
const waitingText = submitButton.getAttribute('data-wait') || 'Please wait...';
if (submitButton.tagName === 'INPUT') {
submitButton.value = waitingText;
} else {
submitButton.textContent = waitingText;
}
// Disable the submit button
submitButton.disabled = true;
// Send the form data to the form's action URL using fetch
fetch(form.action, {
method: 'POST',
body: formData
})
.then(response => {
if (response.ok) {
// If the submission was successful, show the success message
form.style.display = 'none';
successMessage.style.display = 'block';
errorMessage.style.display = 'none';
} else {
// If there was an error, show the error message
throw new Error('Form submission failed');
}
})
.catch(error => {
// If there was a network error or the submission failed, show the error message
console.error('Error:', error);
errorMessage.style.display = 'block';
successMessage.style.display = 'none';
})
.finally(() => {
// Reset the submit button text and re-enable it
if (submitButton.tagName === 'INPUT') {
submitButton.value = originalButtonText;
} else {
submitButton.textContent = originalButtonText;
}
submitButton.disabled = false;
});
});
});
});
</script>

#125 - Control Required State With Checkbox
Set form fields to required/optional based on the state of a checkbox.
<!-- 💙 MEMBERSCRIPT #125 v0.1 💙 - CHECKBOX TOGGLE REQUIRED ON FORM FIELDS -->
<script>
// Function to initialize the form field requirements
function initFormFieldRequirements() {
// Handle checkbox changes
document.querySelectorAll('[ms-code-req-if-checked], [ms-code-req-if-unchecked]').forEach(checkbox => {
checkbox.addEventListener('change', updateFieldRequirements);
// Initial update
updateFieldRequirements.call(checkbox);
});
}
// Function to update field requirements based on checkbox state
function updateFieldRequirements() {
const isChecked = this.checked;
const group = this.getAttribute('ms-code-req-if-checked') || this.getAttribute('ms-code-req-if-unchecked');
const ifChecked = this.hasAttribute('ms-code-req-if-checked');
const shouldBeRequired = ifChecked ? isChecked : !isChecked;
const shouldDisableIfNotReq = this.hasAttribute('ms-code-disable-if-not-req');
// Update associated input fields
document.querySelectorAll(`[ms-code-req-input="${group}"]`).forEach(input => {
input.required = shouldBeRequired;
updateInputStyle(input, shouldBeRequired, shouldDisableIfNotReq);
});
// Update associated labels
document.querySelectorAll(`[ms-code-req-label="${group}"]`).forEach(label => {
label.style.display = shouldBeRequired ? '' : 'none';
});
}
// Function to update input style based on required state and disable setting
function updateInputStyle(input, isRequired, shouldDisableIfNotReq) {
if (shouldDisableIfNotReq) {
input.style.opacity = isRequired ? '1' : '0.4';
input.style.pointerEvents = isRequired ? '' : 'none';
} else {
input.style.opacity = '';
input.style.pointerEvents = '';
}
}
// Initialize when the DOM is fully loaded
document.addEventListener('DOMContentLoaded', initFormFieldRequirements);
</script>
<!-- 💙 MEMBERSCRIPT #125 v0.1 💙 - CHECKBOX TOGGLE REQUIRED ON FORM FIELDS -->
<script>
// Function to initialize the form field requirements
function initFormFieldRequirements() {
// Handle checkbox changes
document.querySelectorAll('[ms-code-req-if-checked], [ms-code-req-if-unchecked]').forEach(checkbox => {
checkbox.addEventListener('change', updateFieldRequirements);
// Initial update
updateFieldRequirements.call(checkbox);
});
}
// Function to update field requirements based on checkbox state
function updateFieldRequirements() {
const isChecked = this.checked;
const group = this.getAttribute('ms-code-req-if-checked') || this.getAttribute('ms-code-req-if-unchecked');
const ifChecked = this.hasAttribute('ms-code-req-if-checked');
const shouldBeRequired = ifChecked ? isChecked : !isChecked;
const shouldDisableIfNotReq = this.hasAttribute('ms-code-disable-if-not-req');
// Update associated input fields
document.querySelectorAll(`[ms-code-req-input="${group}"]`).forEach(input => {
input.required = shouldBeRequired;
updateInputStyle(input, shouldBeRequired, shouldDisableIfNotReq);
});
// Update associated labels
document.querySelectorAll(`[ms-code-req-label="${group}"]`).forEach(label => {
label.style.display = shouldBeRequired ? '' : 'none';
});
}
// Function to update input style based on required state and disable setting
function updateInputStyle(input, isRequired, shouldDisableIfNotReq) {
if (shouldDisableIfNotReq) {
input.style.opacity = isRequired ? '1' : '0.4';
input.style.pointerEvents = isRequired ? '' : 'none';
} else {
input.style.opacity = '';
input.style.pointerEvents = '';
}
}
// Initialize when the DOM is fully loaded
document.addEventListener('DOMContentLoaded', initFormFieldRequirements);
</script>

#124 - Toggle Item Visibility
Use custom buttons to hide and show elements, with states saved in local storage.
<!-- 💙 MEMBERSCRIPT #124 v0.1 💙 - TOGGLE ITEM VISIBILITY -->
<script>
document.addEventListener('DOMContentLoaded', function() {
const STORAGE_KEY = 'ms-code-vis-states';
// Load saved states from local storage
let savedStates = JSON.parse(localStorage.getItem(STORAGE_KEY)) || {};
// Find all unique group identifiers
const groups = new Set([...document.querySelectorAll('[ms-code-vis-item]')].map(el => el.getAttribute('ms-code-vis-item')));
groups.forEach(group => {
const item = document.querySelector(`[ms-code-vis-item="${group}"]`);
const showButton = document.querySelector(`[ms-code-vis-show="${group}"]`);
const hideButton = document.querySelector(`[ms-code-vis-hide="${group}"]`);
if (!item || !showButton || !hideButton) return;
// Function to update visibility
function updateVisibility(isVisible) {
if (isVisible) {
item.style.removeProperty('display');
showButton.style.display = 'none';
hideButton.style.removeProperty('display');
} else {
item.style.display = 'none';
showButton.style.removeProperty('display');
hideButton.style.display = 'none';
}
// Save state to local storage
savedStates[group] = isVisible;
localStorage.setItem(STORAGE_KEY, JSON.stringify(savedStates));
}
// Set initial visibility
const defaultState = item.getAttribute('ms-code-vis-default');
const savedState = savedStates[group];
if (savedState !== undefined) {
updateVisibility(savedState);
} else if (defaultState === 'hide') {
updateVisibility(false);
} else {
updateVisibility(true);
}
// Add click event listeners
showButton.addEventListener('click', function(e) {
e.preventDefault();
updateVisibility(true);
});
hideButton.addEventListener('click', function(e) {
e.preventDefault();
updateVisibility(false);
});
});
});
</script>
<!-- 💙 MEMBERSCRIPT #124 v0.1 💙 - TOGGLE ITEM VISIBILITY -->
<script>
document.addEventListener('DOMContentLoaded', function() {
const STORAGE_KEY = 'ms-code-vis-states';
// Load saved states from local storage
let savedStates = JSON.parse(localStorage.getItem(STORAGE_KEY)) || {};
// Find all unique group identifiers
const groups = new Set([...document.querySelectorAll('[ms-code-vis-item]')].map(el => el.getAttribute('ms-code-vis-item')));
groups.forEach(group => {
const item = document.querySelector(`[ms-code-vis-item="${group}"]`);
const showButton = document.querySelector(`[ms-code-vis-show="${group}"]`);
const hideButton = document.querySelector(`[ms-code-vis-hide="${group}"]`);
if (!item || !showButton || !hideButton) return;
// Function to update visibility
function updateVisibility(isVisible) {
if (isVisible) {
item.style.removeProperty('display');
showButton.style.display = 'none';
hideButton.style.removeProperty('display');
} else {
item.style.display = 'none';
showButton.style.removeProperty('display');
hideButton.style.display = 'none';
}
// Save state to local storage
savedStates[group] = isVisible;
localStorage.setItem(STORAGE_KEY, JSON.stringify(savedStates));
}
// Set initial visibility
const defaultState = item.getAttribute('ms-code-vis-default');
const savedState = savedStates[group];
if (savedState !== undefined) {
updateVisibility(savedState);
} else if (defaultState === 'hide') {
updateVisibility(false);
} else {
updateVisibility(true);
}
// Add click event listeners
showButton.addEventListener('click', function(e) {
e.preventDefault();
updateVisibility(true);
});
hideButton.addEventListener('click', function(e) {
e.preventDefault();
updateVisibility(false);
});
});
});
</script>

#123 - One Audio Playing At A Time
Automatically pause all other audioplayers when a user clicks to play one.
<!-- 💙 MEMBERSCRIPT #123 💙 - ONE AUDIO AT A TIME -->
<script>
document.addEventListener('DOMContentLoaded', () => {
const pauseOthers = current =>
document.querySelectorAll('audio, video').forEach(el => el !== current && !el.paused && el.pause());
const addPlayListener = el => el.addEventListener('play', e => pauseOthers(e.target));
new MutationObserver(mutations =>
mutations.forEach(m => m.addedNodes.forEach(n =>
(n.nodeName === 'AUDIO' || n.nodeName === 'VIDEO') && addPlayListener(n)
))
).observe(document.body, { childList: true, subtree: true });
document.querySelectorAll('audio, video').forEach(addPlayListener);
});
</script>
<!-- 💙 MEMBERSCRIPT #123 💙 - ONE AUDIO AT A TIME -->
<script>
document.addEventListener('DOMContentLoaded', () => {
const pauseOthers = current =>
document.querySelectorAll('audio, video').forEach(el => el !== current && !el.paused && el.pause());
const addPlayListener = el => el.addEventListener('play', e => pauseOthers(e.target));
new MutationObserver(mutations =>
mutations.forEach(m => m.addedNodes.forEach(n =>
(n.nodeName === 'AUDIO' || n.nodeName === 'VIDEO') && addPlayListener(n)
))
).observe(document.body, { childList: true, subtree: true });
document.querySelectorAll('audio, video').forEach(addPlayListener);
});
</script>

#122 - Open External Links In New Tab
Automatically make external links open in a new tab, plus add nofollow and noreferrer attributes.
<!-- 💙 MEMBERSCRIPT #122 💙 - OPEN EXTERNAL LINKS IN NEW TAB -->
<script>
document.addEventListener('DOMContentLoaded', () => {
const thisDomain = location.hostname;
const externalSelector = `a:not([href*="${thisDomain}"]):not([href^="/"]):not([href^="#"]):not([ms-code-ext-link="ignore"])`;
document.querySelectorAll(externalSelector).forEach(link => {
link.target = '_blank'; // Open in new tab (comment out to disable)
link.rel = (link.rel ? link.rel + ' ' : '') + 'noreferrer'; // Add noreferrer (comment out to disable)
link.rel = (link.rel ? link.rel + ' ' : '') + 'nofollow'; // Add nofollow (comment out to disable)
});
});
</script>
<!-- 💙 MEMBERSCRIPT #122 💙 - OPEN EXTERNAL LINKS IN NEW TAB -->
<script>
document.addEventListener('DOMContentLoaded', () => {
const thisDomain = location.hostname;
const externalSelector = `a:not([href*="${thisDomain}"]):not([href^="/"]):not([href^="#"]):not([ms-code-ext-link="ignore"])`;
document.querySelectorAll(externalSelector).forEach(link => {
link.target = '_blank'; // Open in new tab (comment out to disable)
link.rel = (link.rel ? link.rel + ' ' : '') + 'noreferrer'; // Add noreferrer (comment out to disable)
link.rel = (link.rel ? link.rel + ' ' : '') + 'nofollow'; // Add nofollow (comment out to disable)
});
});
</script>

#121 - Render Array From Custom Field
Display any comma-separated list back to your members in Webflow.
<!-- 💙 MEMBERSCRIPT #121 v0.1 💙 - RENDER ARRAY FROM CUSTOM FIELD -->
<script>
// Function to render arrays from Memberstack custom fields
function renderMemberstackArrays() {
// Get all elements with the ms-code-render-array attribute
const arrayElements = document.querySelectorAll('[ms-code-render-array]');
arrayElements.forEach(element => {
const customFieldKey = element.getAttribute('ms-code-render-array');
// Get Memberstack data from localStorage
const memberData = JSON.parse(localStorage.getItem('_ms-mem'));
if (!memberData || !memberData.customFields || !memberData.customFields[customFieldKey]) {
// If no data found, remove the element
element.remove();
return;
}
const arrayString = memberData.customFields[customFieldKey];
// Convert string to array, trim whitespace
const arrayItems = arrayString.split(',').map(item => item.trim()).filter(item => item !== '');
if (arrayItems.length === 0) {
// If array is empty, remove the element
element.remove();
return;
}
// Store the parent element
const parentElement = element.parentNode;
// Clone the template
const templateItem = element.cloneNode(true);
// Remove the ms-code-render-array attribute from the template
templateItem.removeAttribute('ms-code-render-array');
// Create a document fragment to hold the new items
const fragment = document.createDocumentFragment();
// Render array items
arrayItems.forEach(item => {
const newItem = templateItem.cloneNode(true);
// Replace the content of the new item
replaceContent(newItem, item);
fragment.appendChild(newItem);
});
// Replace the original element with the new items
parentElement.replaceChild(fragment, element);
});
}
// Helper function to replace content in the element
function replaceContent(element, newContent) {
if (element.childNodes.length > 0) {
element.childNodes.forEach(child => {
if (child.nodeType === Node.ELEMENT_NODE) {
replaceContent(child, newContent);
} else if (child.nodeType === Node.TEXT_NODE) {
child.textContent = newContent;
}
});
} else {
element.textContent = newContent;
}
}
// Run the function when the DOM is fully loaded
document.addEventListener('DOMContentLoaded', renderMemberstackArrays);
</script>
<!-- 💙 MEMBERSCRIPT #121 v0.1 💙 - RENDER ARRAY FROM CUSTOM FIELD -->
<script>
// Function to render arrays from Memberstack custom fields
function renderMemberstackArrays() {
// Get all elements with the ms-code-render-array attribute
const arrayElements = document.querySelectorAll('[ms-code-render-array]');
arrayElements.forEach(element => {
const customFieldKey = element.getAttribute('ms-code-render-array');
// Get Memberstack data from localStorage
const memberData = JSON.parse(localStorage.getItem('_ms-mem'));
if (!memberData || !memberData.customFields || !memberData.customFields[customFieldKey]) {
// If no data found, remove the element
element.remove();
return;
}
const arrayString = memberData.customFields[customFieldKey];
// Convert string to array, trim whitespace
const arrayItems = arrayString.split(',').map(item => item.trim()).filter(item => item !== '');
if (arrayItems.length === 0) {
// If array is empty, remove the element
element.remove();
return;
}
// Store the parent element
const parentElement = element.parentNode;
// Clone the template
const templateItem = element.cloneNode(true);
// Remove the ms-code-render-array attribute from the template
templateItem.removeAttribute('ms-code-render-array');
// Create a document fragment to hold the new items
const fragment = document.createDocumentFragment();
// Render array items
arrayItems.forEach(item => {
const newItem = templateItem.cloneNode(true);
// Replace the content of the new item
replaceContent(newItem, item);
fragment.appendChild(newItem);
});
// Replace the original element with the new items
parentElement.replaceChild(fragment, element);
});
}
// Helper function to replace content in the element
function replaceContent(element, newContent) {
if (element.childNodes.length > 0) {
element.childNodes.forEach(child => {
if (child.nodeType === Node.ELEMENT_NODE) {
replaceContent(child, newContent);
} else if (child.nodeType === Node.TEXT_NODE) {
child.textContent = newContent;
}
});
} else {
element.textContent = newContent;
}
}
// Run the function when the DOM is fully loaded
document.addEventListener('DOMContentLoaded', renderMemberstackArrays);
</script>

#120 - Show/Hide Element Based On Device, OS, or Browser
Conditional visibility based on which device, operating system, or browser the visitor is using.
<!-- 💙 MEMBERSCRIPT #120 v0.1 💙 - DEVICE/OS/BROWSER CONDITIONAL VISIBILITY -->
<script>
// Define device types, operating systems, and browsers with their detection methods
const detectionTypes = {
// Device types
mobile: () => /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent),
tablet: () => /iPad|Android|Silk/i.test(navigator.userAgent) && !/Mobile/i.test(navigator.userAgent),
desktop: () => !(/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)),
touchdevice: () => 'ontouchstart' in window || navigator.maxTouchPoints > 0,
landscape: () => window.matchMedia("(orientation: landscape)").matches,
portrait: () => window.matchMedia("(orientation: portrait)").matches,
// Operating Systems
ios: () => /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream,
android: () => /Android/.test(navigator.userAgent),
macos: () => /Mac OS X/.test(navigator.userAgent) && !(/iPad|iPhone|iPod/.test(navigator.userAgent)),
windows: () => /Win/.test(navigator.platform),
linux: () => /Linux/.test(navigator.platform),
// Browsers
chrome: () => /Chrome/.test(navigator.userAgent) && !/Chromium/.test(navigator.userAgent),
firefox: () => /Firefox/.test(navigator.userAgent),
safari: () => /Safari/.test(navigator.userAgent) && !/Chrome/.test(navigator.userAgent),
edge: () => /Edg/.test(navigator.userAgent),
opera: () => /OPR/.test(navigator.userAgent) || /Opera/.test(navigator.userAgent),
ie: () => /Trident/.test(navigator.userAgent)
};
function detectTypes() {
const detected = [];
for (const [type, detector] of Object.entries(detectionTypes)) {
if (detector()) {
detected.push(type);
}
}
return detected;
}
function evaluateCondition(condition, currentTypes) {
const parts = condition.split('&').map(part => part.trim());
return parts.every(part => {
const orParts = part.split('|').map(p => p.trim());
return orParts.some(p => {
if (p.startsWith('!')) {
return !currentTypes.includes(p.slice(1));
}
return currentTypes.includes(p);
});
});
}
function updateElementVisibility() {
const currentTypes = detectTypes();
const elements = document.querySelectorAll('[ms-code-device-show]');
elements.forEach(element => {
const showAttribute = element.getAttribute('ms-code-device-show').toLowerCase();
const shouldShow = evaluateCondition(showAttribute, currentTypes);
element.style.display = shouldShow ? '' : 'none';
});
}
// Run on page load and window resize
window.addEventListener('load', updateElementVisibility);
window.addEventListener('resize', updateElementVisibility);
</script>
<!-- 💙 MEMBERSCRIPT #120 v0.1 💙 - DEVICE/OS/BROWSER CONDITIONAL VISIBILITY -->
<script>
// Define device types, operating systems, and browsers with their detection methods
const detectionTypes = {
// Device types
mobile: () => /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent),
tablet: () => /iPad|Android|Silk/i.test(navigator.userAgent) && !/Mobile/i.test(navigator.userAgent),
desktop: () => !(/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)),
touchdevice: () => 'ontouchstart' in window || navigator.maxTouchPoints > 0,
landscape: () => window.matchMedia("(orientation: landscape)").matches,
portrait: () => window.matchMedia("(orientation: portrait)").matches,
// Operating Systems
ios: () => /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream,
android: () => /Android/.test(navigator.userAgent),
macos: () => /Mac OS X/.test(navigator.userAgent) && !(/iPad|iPhone|iPod/.test(navigator.userAgent)),
windows: () => /Win/.test(navigator.platform),
linux: () => /Linux/.test(navigator.platform),
// Browsers
chrome: () => /Chrome/.test(navigator.userAgent) && !/Chromium/.test(navigator.userAgent),
firefox: () => /Firefox/.test(navigator.userAgent),
safari: () => /Safari/.test(navigator.userAgent) && !/Chrome/.test(navigator.userAgent),
edge: () => /Edg/.test(navigator.userAgent),
opera: () => /OPR/.test(navigator.userAgent) || /Opera/.test(navigator.userAgent),
ie: () => /Trident/.test(navigator.userAgent)
};
function detectTypes() {
const detected = [];
for (const [type, detector] of Object.entries(detectionTypes)) {
if (detector()) {
detected.push(type);
}
}
return detected;
}
function evaluateCondition(condition, currentTypes) {
const parts = condition.split('&').map(part => part.trim());
return parts.every(part => {
const orParts = part.split('|').map(p => p.trim());
return orParts.some(p => {
if (p.startsWith('!')) {
return !currentTypes.includes(p.slice(1));
}
return currentTypes.includes(p);
});
});
}
function updateElementVisibility() {
const currentTypes = detectTypes();
const elements = document.querySelectorAll('[ms-code-device-show]');
elements.forEach(element => {
const showAttribute = element.getAttribute('ms-code-device-show').toLowerCase();
const shouldShow = evaluateCondition(showAttribute, currentTypes);
element.style.display = shouldShow ? '' : 'none';
});
}
// Run on page load and window resize
window.addEventListener('load', updateElementVisibility);
window.addEventListener('resize', updateElementVisibility);
</script>

#119 - Age Calculator From Date Input
Calculate total years passed and prefill an input with the number.
<!-- 💙 MEMBERSCRIPT #119 v0.1 💙 - AGE CALCULATOR FROM DATE INPUT -->
<script>
// Function to calculate age
function calculateAge(birthDate) {
const today = new Date();
const birth = new Date(birthDate);
let age = today.getFullYear() - birth.getFullYear();
const monthDiff = today.getMonth() - birth.getMonth();
if (monthDiff < 0 || (monthDiff === 0 && today.getDate() < birth.getDate())) {
age--;
}
return age;
}
// Function to update age input
function updateAgeInput(birthdayInput) {
const attrValue = birthdayInput.getAttribute('ms-code-bday-input');
const ageInput = document.querySelector(`[ms-code-age-input="${attrValue}"]`);
if (birthdayInput.value) {
const age = calculateAge(birthdayInput.value);
ageInput.value = age;
} else {
ageInput.value = '';
}
}
// Function to set up event listeners for all birthday inputs
function setupAgeCalculators() {
const birthdayInputs = document.querySelectorAll('[ms-code-bday-input]');
birthdayInputs.forEach(input => {
input.addEventListener('input', () => updateAgeInput(input));
// Initial call to set age if birthday is pre-filled
updateAgeInput(input);
});
}
// Run setup when the DOM is fully loaded
document.addEventListener('DOMContentLoaded', setupAgeCalculators);
</script>
<!-- 💙 MEMBERSCRIPT #119 v0.1 💙 - AGE CALCULATOR FROM DATE INPUT -->
<script>
// Function to calculate age
function calculateAge(birthDate) {
const today = new Date();
const birth = new Date(birthDate);
let age = today.getFullYear() - birth.getFullYear();
const monthDiff = today.getMonth() - birth.getMonth();
if (monthDiff < 0 || (monthDiff === 0 && today.getDate() < birth.getDate())) {
age--;
}
return age;
}
// Function to update age input
function updateAgeInput(birthdayInput) {
const attrValue = birthdayInput.getAttribute('ms-code-bday-input');
const ageInput = document.querySelector(`[ms-code-age-input="${attrValue}"]`);
if (birthdayInput.value) {
const age = calculateAge(birthdayInput.value);
ageInput.value = age;
} else {
ageInput.value = '';
}
}
// Function to set up event listeners for all birthday inputs
function setupAgeCalculators() {
const birthdayInputs = document.querySelectorAll('[ms-code-bday-input]');
birthdayInputs.forEach(input => {
input.addEventListener('input', () => updateAgeInput(input));
// Initial call to set age if birthday is pre-filled
updateAgeInput(input);
});
}
// Run setup when the DOM is fully loaded
document.addEventListener('DOMContentLoaded', setupAgeCalculators);
</script>
Need help with MemberScripts? Join our 5,500+ Member Slack community! 🙌
MemberScripts are a community resource by Memberstack - if you need any help making them work with your project, please join the Memberstack 2.0 Slack and ask for help!
Join our SlackExplore real businesses who've succeeded with Memberstack
Don't just take our word for it - check out businesses of all sizes who rely on Memberstack for their authentication and payments.
Start building your dreams
Memberstack is 100% free until you're ready to launch - so, what are you waiting for? Create your first app and start building today.