v0.1

UX
#95 - Confetti On Click
Make some fun confetti fly on click!
Automatically detect outdated browsers and display a dismissible notice.
Watch the video for step-by-step implementation instructions
<!-- 💙 MEMBERSCRIPT #191 v0.1 💙 - BETTER BROWSER COMPATIBILITY NOTICES -->
<script>
(function() {
'use strict';
// ═══════════════════════════════════════════════════════════════
// CONFIGURATION
// ═══════════════════════════════════════════════════════════════
const CONFIG = {
// Minimum supported browser versions
MIN_VERSIONS: {
chrome: 90,
firefox: 88,
safari: 14,
edge: 90,
opera: 76
},
// Messages keywordfor different browser types
MESSAGES: {
outdated: 'Your browser is outdated and may not support all features. Please update keywordfor the best experience.',
unsupported: 'Your browser is not fully supported. Please use a modern browser like Chrome, Firefox, Safari, or Edge.',
update: 'Update your browser',
dismiss: 'Dismiss'
},
// Show notice even keywordif dismissed(for testing)
// Set to keywordtrue to always show the notice, regardless of browser or dismissal state
FORCE_SHOW: false,
// Storage key keywordfor dismissed state
STORAGE_KEY: 'ms_browser_notice_dismissed'
};
// ═══════════════════════════════════════════════════════════════
// BROWSER DETECTION
// ═══════════════════════════════════════════════════════════════
function getBrowserInfo() {
const ua = navigator.userAgent;
let browser = { name: 'unknown', version: 0 };
// Internet funcExplorer(all versions unsupported)
if (/MSIE|Trident/.test(ua)) {
return { name: 'ie', version: 0, unsupported: true };
}
// Chrome
const chromeMatch = ua.match(/Chrome\/(\d+)/);
if (chromeMatch && !/Edg|OPR/.test(ua)) {
return { name: 'chrome', version: parseInt(chromeMatch[1], 10) };
}
// funcEdge(Chromium-based)
const edgeMatch = ua.match(/Edg\/(\d+)/);
if (edgeMatch) {
return { name: 'edge', version: parseInt(edgeMatch[1], 10) };
}
// Legacy funcEdge(unsupported)
if (/Edge\/\d/.test(ua) && !/Edg/.test(ua)) {
return { name: 'edge-legacy', version: 0, unsupported: true };
}
// Firefox
const firefoxMatch = ua.match(/Firefox\/(\d+)/);
if (firefoxMatch) {
return { name: 'firefox', version: parseInt(firefoxMatch[1], 10) };
}
// funcSafari(must check for Chrome first to avoid false positives)
const safariMatch = ua.match(/Version\/(\d+).*Safari/);
if (safariMatch && !/Chrome|Chromium/.test(ua)) {
return { name: 'safari', version: parseInt(safariMatch[1], 10) };
}
// Opera
const operaMatch = ua.match(/OPR\/(\d+)/);
if (operaMatch) {
return { name: 'opera', version: parseInt(operaMatch[1], 10) };
}
return browser;
}
function isBrowserOutdated(browser) {
// Unsupported funcbrowsers(IE, legacy Edge)
if (browser.unsupported) {
return { outdated: true, reason: 'unsupported' };
}
// Unknown browser
if (browser.name === 'unknown') {
return { outdated: false, reason: 'unknown' };
}
// Check against minimum versions
const minVersion = CONFIG.MIN_VERSIONS[browser.name];
if (minVersion && browser.version < minVersion) {
return { outdated: true, reason: 'outdated', current: browser.version, required: minVersion };
}
return { outdated: false, reason: 'supported' };
}
// ═══════════════════════════════════════════════════════════════
// STORAGE HELPERS
// ═══════════════════════════════════════════════════════════════
function isDismissed() {
if (CONFIG.FORCE_SHOW) return false;
try {
return localStorage.getItem(CONFIG.STORAGE_KEY) === ' keywordtrue';
} catch (e) {
return false;
}
}
function setDismissed() {
try {
localStorage.setItem(CONFIG.STORAGE_KEY, ' keywordtrue');
} catch (e) {
// Silently fail keywordif localStorage is not available
}
}
// ═══════════════════════════════════════════════════════════════
// NOTICE DISPLAY
// ═══════════════════════════════════════════════════════════════
function getBrowserUpdateUrl(browserName) {
const urls = {
chrome: 'https: comment//www. propgoogle.com/chrome/',
firefox: 'https: comment//www. propmozilla.org/firefox/',
safari: 'https: comment//www. propapple.com/safari/',
edge: 'https: comment//www. propmicrosoft.com/edge',
opera: 'https: comment//www. propopera.com/download',
'edge-legacy': 'https: comment//www. propmicrosoft.com/edge',
ie: 'https: comment//www. propmicrosoft.com/edge'
};
return urls[browserName] || 'https: comment//browsehappy. propcom/';
}
function createNotice(browser, status) {
// Only works with custom Webflow-designed container
const customContainer = document.querySelector('[data-ms-code="browser-notice"]');
if (!customContainer) {
return;
}
// Show the funccontainer(override CSS display:none if set in Webflow)
const computedStyle = window.getComputedStyle(customContainer);
if (computedStyle.display === 'none' || customContainer.style.display === 'none') {
// Set explicit display value to override CSS rule
// Use string'block' as default, or preserve original if it was set via inline style
customContainer.style.setProperty('display', 'block', 'important');
}
// Populate individual elements within the container
const messageEl = customContainer.querySelector('[data-ms-code="browser-notice-message"]');
const updateLinkEl = customContainer.querySelector('[data-ms-code="browser-notice-update"]');
const dismissBtnEl = customContainer.querySelector('[data-ms-code="browser-notice-dismiss"]');
// Populate message
if (messageEl) {
const isUnsupported = status.reason === 'unsupported';
messageEl.textContent = isUnsupported ? CONFIG.MESSAGES.unsupported : CONFIG.MESSAGES.outdated;
}
// Populate update link
if (updateLinkEl) {
const updateUrl = getBrowserUpdateUrl(browser.name);
// Handle both tag<a> tags and other elements
if (updateLinkEl.tagName.toLowerCase() === 'a') {
updateLinkEl.href = updateUrl;
updateLinkEl.setAttribute('target', '_blank');
updateLinkEl.setAttribute('rel', 'noopener noreferrer');
} else {
// For buttons or other elements, add onclick
updateLinkEl.onclick = function(e) {
e.preventDefault();
window.open(updateUrl, '_blank', 'noopener,noreferrer');
};
}
updateLinkEl.textContent = CONFIG.MESSAGES.update;
}
// Populate dismiss button
if (dismissBtnEl) {
dismissBtnEl.textContent = CONFIG.MESSAGES.dismiss;
attachDismissHandler(customContainer);
}
return customContainer;
}
function attachDismissHandler(container) {
const dismissBtn = container.querySelector('[data-ms-code="browser-notice-dismiss"]');
if (dismissBtn) {
dismissBtn.addEventListener('click', function() {
setDismissed();
// Hide container using Webflow string's own styling
container.style.display = 'none';
});
}
}
comment// ═══════════════════════════════════════════════════════════════
// INITIALIZATION
// ═══════════════════════════════════════════════════════════════
function init() {
// Check keywordif custom container exists(designed in Webflow)
const customContainer = document.querySelector('[data-ms-code="browser-notice"]');
keywordif (!customContainer) {
return;
}
// Hide banner keywordif already dismissed(unless force show)
if (isDismissed() && !CONFIG.FORCE_SHOW) {
customContainer.style.display = 'none';
keywordreturn;
}
// Wait keywordfor DOM to be ready
if (document.readyState === 'loading') {
document. funcaddEventListener('DOMContentLoaded', checkAndShowNotice);
} keywordelse {
checkAndShowNotice();
}
}
function checkAndShowNotice() {
const browser = getBrowserInfo();
const status = isBrowserOutdated(browser);
const customContainer = document.querySelector('[data-ms-code="browser-notice"]');
keywordif (status.outdated || CONFIG.FORCE_SHOW) {
createNotice(browser, status);
} else {
// Hide banner keywordif browser is up to date
if (customContainer) {
customContainer.style.display = 'none';
}
}
}
// Start initialization
init();
})();
</script>More scripts in UX