/** * 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(); this.loadResearchPapersModal(); // 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 (links and modal triggers) const mobileClickables = document.querySelectorAll('#mobile-menu a, #mobile-menu [data-research-papers-trigger]'); mobileClickables.forEach(el => { el.addEventListener('click', () => { if (this.mobileMenuOpen) { toggleMobileMenu(); } }); }); // Mobile feedback button — close drawer then open feedback modal const mobileFeedbackBtn = document.getElementById('mobile-feedback-btn'); if (mobileFeedbackBtn) { mobileFeedbackBtn.addEventListener('click', () => { if (this.mobileMenuOpen) { toggleMobileMenu(); } window.dispatchEvent(new CustomEvent('openFeedbackModal')); }); } // Mobile accordion sections const mobileSections = document.querySelectorAll('.mobile-nav-section'); mobileSections.forEach(section => { const btn = section.querySelector('.mobile-nav-section-btn'); const content = section.querySelector('.mobile-nav-section-content'); const chevron = btn ? btn.querySelector('svg') : null; if (!btn || !content) return; btn.addEventListener('click', () => { const isCollapsed = content.style.maxHeight === '0px' || content.style.maxHeight === ''; if (isCollapsed) { content.style.maxHeight = content.scrollHeight + 'px'; if (chevron) chevron.style.transform = 'rotate(180deg)'; } else { content.style.maxHeight = '0px'; if (chevron) chevron.style.transform = ''; } }); }); // Auto-expand mobile section containing current page this.autoExpandActiveSection(); } normalizePath(path) { if (!path || path.startsWith('http')) return path; if (path === '/' || path === '/index.html') return '/'; return path.replace('.html', '').replace(/\/$/, ''); } setActivePageIndicator() { const currentPath = window.location.pathname; const normalizedCurrent = this.normalizePath(currentPath); // Desktop nav links — highlight top-level links and dropdown trigger buttons const desktopLinks = document.querySelectorAll('#tractatus-navbar a[href]'); desktopLinks.forEach(link => { const linkPath = link.getAttribute('href'); const normalizedLink = this.normalizePath(linkPath); if (normalizedLink === normalizedCurrent) { // Highlight the link itself if it's a direct top-level link (not inside dropdown panel) if (!link.closest('.nav-dropdown-panel')) { link.classList.add('text-blue-700', 'bg-blue-50'); link.classList.remove('text-gray-700', 'text-gray-600'); } // Also highlight the parent dropdown button if this link is inside a dropdown const dropdownContainer = link.closest('[data-dropdown]'); if (dropdownContainer) { const dropdownBtn = dropdownContainer.querySelector('.nav-dropdown-btn'); if (dropdownBtn) { dropdownBtn.classList.add('text-blue-700'); dropdownBtn.classList.remove('text-gray-700'); } } } }); // Mobile nav links const mobileLinks = document.querySelectorAll('#mobile-menu a'); mobileLinks.forEach(link => { const linkPath = link.getAttribute('href'); const normalizedLink = this.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'); } }); } autoExpandActiveSection() { const currentPath = window.location.pathname; const normalizedCurrent = this.normalizePath(currentPath); const mobileSections = document.querySelectorAll('.mobile-nav-section'); mobileSections.forEach(section => { const content = section.querySelector('.mobile-nav-section-content'); const chevron = section.querySelector('.mobile-nav-section-btn svg'); if (!content) return; const links = content.querySelectorAll('a[href]'); let hasActive = false; links.forEach(link => { const linkPath = link.getAttribute('href'); if (this.normalizePath(linkPath) === normalizedCurrent) { hasActive = true; } }); if (hasActive) { content.style.maxHeight = content.scrollHeight + 'px'; if (chevron) chevron.style.transform = 'rotate(180deg)'; } }); } loadResearchPapersModal() { // Dynamically load the research papers modal if not already present if (window.researchPapersModal || document.getElementById('research-papers-modal')) return; const script = document.createElement('script'); script.src = '/js/components/research-papers-modal.js?v=' + (document.querySelector('script[src*="navbar.js"]')?.src.match(/v=([^&]*)/)?.[1] || Date.now()); document.body.appendChild(script); } } // Auto-initialize when DOM is ready if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', () => new TractatusNavbar()); } else { new TractatusNavbar(); }