Loomia logo.png
Menu
HomePricingBlogWork
Book a 15-min intro callhello@loomia.com
class MenuToggle {
    constructor(config = {}) {
        this.config = {
            toggleSelector: '[data-menu-toggle]',
            overlaySelector: '[data-menu-overlay]',
            openText: 'Close',
            closedText: 'Menu',
            animationDuration: 100,
            staggerDelay: 80,
            ...config
        };
        
        this.isOpen = false;
        this.initialized = false;
        
        this.init();
    }
    
    init() {
        if (this.initialized) return;
        
        this.elements = this.getElements();
        if (!this.elements.toggle || !this.elements.overlay) {
            return;
        }
        
        this.injectStyles();
        this.forceInitialStateImmediate();
        this.createArrow();
        this.bindEvents();
        this.setAriaAttributes();
        this.setupMenuItems();
        
        this.initialized = true;
    }
    
    forceInitialStateImmediate() {
        this.elements.overlay.style.transform = 'translateY(-100%)';
        this.elements.overlay.style.transition = 'none';
        this.elements.overlay.classList.add('menu-closed');
        this.isOpen = false;
        
        setTimeout(() => {
            this.elements.overlay.style.transition = 'transform 0.6s cubic-bezier(0.4, 0.0, 0.2, 1)';
        }, 50);
    }
    
    getElements() {
        return {
            toggle: document.querySelector(this.config.toggleSelector),
            overlay: document.querySelector(this.config.overlaySelector)
        };
    }
    
    setupMenuItems() {
        this.menuItems = this.elements.overlay.querySelectorAll('*:not(style):not(script)');
        this.menuItems = Array.from(this.menuItems).filter(item => {
            return item.textContent.trim().length > 0 && 
                   !item.querySelector('*') && 
                   item.offsetParent !== null;
        });
        
        this.menuItems.forEach((item, index) => {
            item.style.opacity = '0';
            item.style.transform = 'translateY(30px)';
            item.style.transition = `opacity 0.5s ease ${index * this.config.staggerDelay}ms, transform 0.5s ease ${index * this.config.staggerDelay}ms`;
        });
    }
    
    injectStyles() {
        if (document.getElementById('menu-toggle-styles')) return;
        
        const style = document.createElement('style');
        style.id = 'menu-toggle-styles';
        style.textContent = `
            ${this.config.overlaySelector} {
                position: fixed !important;
                top: 0 !important;
                left: 0 !important;
                width: 100vw !important;
                height: 100vh !important;
                z-index: 9999 !important;
            }
            
            body.menu-open .brx-header {
                z-index: 1 !important;
            }
            
            .menu-arrow {
                display: inline-block;
                margin-right: 8px;
                transition: transform 0.4s cubic-bezier(0.4, 0.0, 0.2, 1);
                width: 8px;
                height: 8px;
                border-right: 2px solid currentColor;
                border-bottom: 2px solid currentColor;
                transform: rotate(45deg);
                transform-origin: center;
                vertical-align: middle;
                position: relative;
                top: -1px;
            }
            
            .menu-arrow.rotated {
                transform: rotate(-135deg);
            }
            
            body.menu-open {
                overflow: hidden !important;
            }
        `;
        document.head.appendChild(style);
    }
    
    createArrow() {
        this.arrow = document.createElement('span');
        this.arrow.className = 'menu-arrow';
        this.arrow.setAttribute('aria-hidden', 'true');
        
        this.textNode = document.createTextNode(this.config.closedText);
        
        this.elements.toggle.innerHTML = '';
        this.elements.toggle.appendChild(this.arrow);
        this.elements.toggle.appendChild(this.textNode);
    }
    
    setAriaAttributes() {
        this.elements.toggle.setAttribute('aria-expanded', 'false');
        this.elements.toggle.setAttribute('aria-controls', this.elements.overlay.id || 'menu-overlay');
        this.elements.overlay.setAttribute('aria-hidden', 'true');
    }
    
    bindEvents() {
        this.boundClickHandler = this.handleClick.bind(this);
        this.boundKeyHandler = this.handleKeydown.bind(this);
        
        this.elements.toggle.addEventListener('click', this.boundClickHandler);
        this.elements.toggle.addEventListener('keydown', this.boundKeyHandler);
    }
    
    handleKeydown(e) {
        if (e.key === 'Enter' || e.key === ' ') {
            e.preventDefault();
            this.toggle();
        }
        if (e.key === 'Escape' && this.isOpen) {
            this.close();
        }
    }
    
    handleClick(e) {
        e.preventDefault();
        this.toggle();
    }
    
    toggle() {
        this.isOpen ? this.close() : this.open();
    }
    
    open() {
        if (this.isOpen) return;
        
        document.body.classList.add('menu-open');
        this.elements.overlay.classList.remove('menu-closed');
        this.elements.overlay.style.transform = 'translateY(0)';
        this.textNode.textContent = this.config.openText;
        this.arrow.classList.add('rotated');
        
        setTimeout(() => {
            this.menuItems.forEach((item, index) => {
                setTimeout(() => {
                    item.style.opacity = '1';
                    item.style.transform = 'translateY(0)';
                }, index * this.config.staggerDelay);
            });
        }, 300);
        
        this.elements.toggle.setAttribute('aria-expanded', 'true');
        this.elements.overlay.setAttribute('aria-hidden', 'false');
        
        this.isOpen = true;
    }
    
    close() {
        if (!this.isOpen) return;
        
        document.body.classList.remove('menu-open');
        
        this.menuItems.forEach((item) => {
            item.style.opacity = '0';
            item.style.transform = 'translateY(30px)';
        });
        
        setTimeout(() => {
            this.elements.overlay.style.transform = 'translateY(-100%)';
            this.elements.overlay.classList.add('menu-closed');
        }, 200);
        
        this.textNode.textContent = this.config.closedText;
        this.arrow.classList.remove('rotated');
        
        this.elements.toggle.setAttribute('aria-expanded', 'false');
        this.elements.overlay.setAttribute('aria-hidden', 'true');
        
        this.isOpen = false;
    }
    
    destroy() {
        if (!this.initialized) return;
        
        this.elements.toggle.removeEventListener('click', this.boundClickHandler);
        this.elements.toggle.removeEventListener('keydown', this.boundKeyHandler);
        
        document.body.classList.remove('menu-open');
        
        const style = document.getElementById('menu-toggle-styles');
        if (style) style.remove();
        
        this.initialized = false;
    }
}

document.addEventListener('DOMContentLoaded', () => {
    new MenuToggle();
});

Write a compelling headline that works without a lede

Atlanta.webp
Atlanta 2.webp
Atlanta 1.webp

Our portfolio

This is just placeholder text. Don’t be alarmed, this is just here to fill up space since your finalized copy isn’t ready yet. Once we have your content finalized, we’ll replace this placeholder text with your real content.
Call to action
    Products0004.jpg
    Products0003.jpg
    Products0001.jpg
    Products0000.jpg
    Placeholder0005.jpg
    Placeholder0005.jpg

Let's get started

Put the three most important links to the right so people can easily navigate to key areas of  the site. It's really helpful for routing traffic.
Contact Form Demo