/** * Tractatus Framework - Responsive Navbar Component * Desktop: visible nav links with dropdowns * Mobile: slide-out drawer */ class TractatusNavbar { constructor() { this.mobileMenuOpen = false; this.openDropdown = null; this.init(); } init() { this.render(); this.attachEventListeners(); this.setActivePageIndicator(); // Dispatch event to signal navbar is ready window.dispatchEvent(new CustomEvent('navbarReady')); } render() { const navHTML = ` `; const existingNavbar = document.querySelector('nav.bg-white.border-b.border-gray-200.sticky'); if (existingNavbar) { existingNavbar.outerHTML = navHTML; } else { const placeholder = document.getElementById('navbar-placeholder'); if (placeholder) { placeholder.outerHTML = navHTML; } else { document.body.insertAdjacentHTML('afterbegin', navHTML); } } } attachEventListeners() { // Desktop Dropdowns const dropdowns = document.querySelectorAll('[data-dropdown]'); dropdowns.forEach(dropdown => { const btn = dropdown.querySelector('.nav-dropdown-btn'); const panel = dropdown.querySelector('.nav-dropdown-panel'); if (!btn || !panel) return; let closeTimeout = null; const openDropdown = () => { if (closeTimeout) { clearTimeout(closeTimeout); closeTimeout = null; } // Close other dropdowns dropdowns.forEach(d => { if (d !== dropdown) { const p = d.querySelector('.nav-dropdown-panel'); if (p) p.classList.add('hidden'); const b = d.querySelector('.nav-dropdown-btn svg'); if (b) b.style.transform = ''; } }); panel.classList.remove('hidden'); const arrow = btn.querySelector('svg'); if (arrow) arrow.style.transform = 'rotate(180deg)'; }; const closeDropdown = () => { closeTimeout = setTimeout(() => { panel.classList.add('hidden'); const arrow = btn.querySelector('svg'); if (arrow) arrow.style.transform = ''; }, 150); }; btn.addEventListener('mouseenter', openDropdown); btn.addEventListener('mouseleave', closeDropdown); panel.addEventListener('mouseenter', () => { if (closeTimeout) { clearTimeout(closeTimeout); closeTimeout = null; } }); panel.addEventListener('mouseleave', closeDropdown); // Keyboard accessibility btn.addEventListener('click', (e) => { e.preventDefault(); const isHidden = panel.classList.contains('hidden'); // Close all dropdowns.forEach(d => { const p = d.querySelector('.nav-dropdown-panel'); if (p) p.classList.add('hidden'); }); if (isHidden) { panel.classList.remove('hidden'); } }); }); // Close dropdowns on click outside document.addEventListener('click', (e) => { if (!e.target.closest('[data-dropdown]')) { dropdowns.forEach(d => { const p = d.querySelector('.nav-dropdown-panel'); if (p) p.classList.add('hidden'); const b = d.querySelector('.nav-dropdown-btn svg'); if (b) b.style.transform = ''; }); } }); // Close dropdowns on Escape document.addEventListener('keydown', (e) => { if (e.key === 'Escape') { dropdowns.forEach(d => { const p = d.querySelector('.nav-dropdown-panel'); if (p) p.classList.add('hidden'); const b = d.querySelector('.nav-dropdown-btn svg'); if (b) b.style.transform = ''; }); } }); // Mobile Menu const mobileMenuBtn = document.getElementById('mobile-menu-btn'); const mobileMenuCloseBtn = document.getElementById('mobile-menu-close-btn'); const mobileMenu = document.getElementById('mobile-menu'); const mobileMenuPanel = document.getElementById('mobile-menu-panel'); const mobileMenuBackdrop = document.getElementById('mobile-menu-backdrop'); const toggleMobileMenu = () => { this.mobileMenuOpen = !this.mobileMenuOpen; if (this.mobileMenuOpen) { mobileMenu.classList.remove('hidden'); setTimeout(() => { mobileMenuPanel.classList.remove('translate-x-full'); mobileMenuPanel.classList.add('translate-x-0'); }, 10); document.body.style.overflow = 'hidden'; } else { mobileMenuPanel.classList.remove('translate-x-0'); mobileMenuPanel.classList.add('translate-x-full'); setTimeout(() => { mobileMenu.classList.add('hidden'); }, 300); document.body.style.overflow = ''; } }; if (mobileMenuPanel) { mobileMenuPanel.classList.add('translate-x-full'); } if (mobileMenuBtn) { mobileMenuBtn.addEventListener('click', toggleMobileMenu); } if (mobileMenuCloseBtn) { mobileMenuCloseBtn.addEventListener('click', toggleMobileMenu); } if (mobileMenuBackdrop) { mobileMenuBackdrop.addEventListener('click', toggleMobileMenu); } // Close mobile menu on navigation const mobileLinks = document.querySelectorAll('#mobile-menu a'); mobileLinks.forEach(link => { link.addEventListener('click', () => { if (this.mobileMenuOpen) { toggleMobileMenu(); } }); }); // Feedback button const navbarFeedbackBtn = document.getElementById('navbar-feedback-btn'); if (navbarFeedbackBtn) { navbarFeedbackBtn.addEventListener('click', () => { if (this.mobileMenuOpen) { toggleMobileMenu(); } window.dispatchEvent(new CustomEvent('openFeedbackModal')); }); } } setActivePageIndicator() { const currentPath = window.location.pathname; const normalizePath = (path) => { if (!path || path.startsWith('http')) return path; if (path === '/' || path === '/index.html') return '/'; return path.replace('.html', '').replace(/\/$/, ''); }; const normalizedCurrent = normalizePath(currentPath); // Desktop nav links const desktopLinks = document.querySelectorAll('#tractatus-navbar a[href]'); desktopLinks.forEach(link => { const linkPath = link.getAttribute('href'); const normalizedLink = normalizePath(linkPath); if (normalizedLink === normalizedCurrent && !link.closest('.nav-dropdown-panel')) { link.classList.add('text-blue-700', 'bg-blue-50'); link.classList.remove('text-gray-700', 'text-gray-600'); } }); // Mobile nav links const mobileLinks = document.querySelectorAll('#mobile-menu a'); mobileLinks.forEach(link => { const linkPath = link.getAttribute('href'); const normalizedLink = normalizePath(linkPath); if (normalizedLink === normalizedCurrent) { link.classList.add('border-l-4', 'bg-sky-50', 'text-blue-700', 'font-medium'); link.style.borderLeftColor = 'var(--tractatus-core-end, #3b82f6)'; link.classList.remove('text-gray-700'); } }); } } // Auto-initialize when DOM is ready if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', () => new TractatusNavbar()); } else { new TractatusNavbar(); }