(function() {
document.documentElement.classList.add('menu-loading');
const criticalStyle = document.createElement('style');
criticalStyle.textContent = `
html.menu-loading .bf-expandable-menu {
display: none !important;
visibility: hidden !important;
opacity: 0 !important;
max-height: 0 !important;
overflow: hidden !important;
transition: none !important;
pointer-events: none !important;
}
`;
document.head.appendChild(criticalStyle);
})();
document.addEventListener('DOMContentLoaded', function() {
const style = document.createElement('style');
style.textContent = `
html.menu-loading * .bf-expandable-menu {
transition: none !important;
}
.bf-expandable-menu {
opacity: 0;
max-height: 0;
margin-bottom: 0;
overflow: hidden;
pointer-events: none;
transform-origin: top center;
transform: translateY(-20px);
transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
will-change: transform, opacity, max-height;
visibility: hidden;
}
.bf-expandable-menu.active {
opacity: 1;
max-height: 520px;
margin-bottom: 16px;
pointer-events: auto;
transform: translateY(0);
visibility: visible;
}
.bf-expandable-menu > * {
opacity: 0;
transform: translateY(-10px);
transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
will-change: transform, opacity;
}
.bf-expandable-menu.active > * {
opacity: 1;
transform: translateY(0);
}
[data-menu-trigger].active-trigger {
background-color: rgba(169, 169, 169, 0.2);
border-radius: 6px;
padding: 6px 12px;
transition: all 0.2s ease;
position: relative;
}
[data-menu-trigger].active-trigger:hover {
background-color: rgba(169, 169, 169, 0.25);
}
.bf-expandable-menu.instant-close,
.bf-expandable-menu.instant-close > * {
transition: none !important;
}
.bf-expandable-menu.no-animation,
.bf-expandable-menu.no-animation > * {
transition: none !important;
}
.bf-blur-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(to bottom, rgba(0, 0, 0, 0.2), rgba(0, 0, 0, 0.4));
backdrop-filter: blur(8px);
opacity: 0;
visibility: hidden;
transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
z-index: 999;
}
.bf-blur-overlay.active {
opacity: 1;
visibility: visible;
}
.Blur {
position: relative;
z-index: 1000;
}
`;
document.head.appendChild(style);
const overlay = document.createElement('div');
overlay.className = 'bf-blur-overlay';
document.body.appendChild(overlay);
const menuTriggers = document.querySelectorAll('[data-menu-trigger]');
const menus = document.querySelectorAll('.bf-expandable-menu');
menus.forEach(menu => {
menu.style.opacity = '0';
menu.style.visibility = 'hidden';
menu.style.maxHeight = '0';
menu.style.overflow = 'hidden';
menu.style.pointerEvents = 'none';
});
function updateTriggerState(targetId, isActive) {
menuTriggers.forEach(trigger => {
const triggerId = trigger.getAttribute('data-menu-trigger');
if (triggerId === targetId) {
isActive
? trigger.classList.add('active-trigger')
: trigger.classList.remove('active-trigger');
}
});
}
function handleMenuTransition(menu, isClosing, instantClose = false) {
return new Promise((resolve) => {
const menuId = menu.id;
if (instantClose) {
menu.classList.add('instant-close');
menu.classList.remove('active');
menu.style.visibility = 'hidden';
overlay.classList.remove('active');
updateTriggerState(menuId, false);
setTimeout(() => {
menu.classList.remove('instant-close');
resolve();
}, 50);
return;
}
if (isClosing) {
menu.classList.remove('active');
overlay.classList.remove('active');
updateTriggerState(menuId, false);
setTimeout(() => {
menu.style.visibility = 'hidden';
resolve();
}, 400);
} else {
menu.style.visibility = 'visible';
requestAnimationFrame(() => {
menu.classList.add('active');
overlay.classList.add('active');
updateTriggerState(menuId, true);
setTimeout(resolve, 50);
});
}
});
}
async function closeAllMenus(exceptMenuId = null) {
const closingPromises = Array.from(menus).map(menu => {
if (menu.id !== exceptMenuId && menu.classList.contains('active')) {
return handleMenuTransition(menu, true, !!exceptMenuId);
}
return Promise.resolve();
});
await Promise.all(closingPromises);
}
menuTriggers.forEach(trigger => {
trigger.addEventListener('click', async function(e) {
e.preventDefault();
e.stopPropagation();
const targetId = this.getAttribute('data-menu-trigger');
const targetMenu = document.getElementById(targetId);
if (!targetMenu) return;
if (targetMenu.classList.contains('active')) {
await handleMenuTransition(targetMenu, true);
return;
}
await closeAllMenus(targetId);
await handleMenuTransition(targetMenu, false);
});
});
document.addEventListener('click', function(e) {
const isClickInsideMenu = Array.from(menus).some(menu => menu.contains(e.target));
const isClickOnTrigger = Array.from(menuTriggers).some(trigger => trigger.contains(e.target));
if (!isClickInsideMenu && !isClickOnTrigger) {
closeAllMenus();
}
});
window.addEventListener('load', function() {
setTimeout(() => {
document.documentElement.classList.remove('menu-loading');
setTimeout(() => {
menus.forEach(menu => {
menu.removeAttribute('style');
});
}, 100);
}, 50);
});
});
v2.0
Bring your SVG icons and illustrations to life with smooth, attention-grabbing reveal animations.
Total duration of the animation
Initial delay before animation starts
Delay between each element in sequential mode
Controls how the animation accelerates and decelerates. 'Ease-in' starts slow and ends fast. 'Ease-out' starts fast and ends slow. 'Ease-in-out' is slow at both start and end, fast in the middle. 'Linear' has a constant speed.
Color of the animated stroke
Internal fill color of the SVG after animation. This color will replace the original SVG fill color.
Thickness of the animated stroke
How much of the SVG must be visible to trigger animation