FAB overlaps PWA install prompt and update notifications on small screens. Mobile users now access feedback via the navbar drawer instead. Install prompt dismissal persists in localStorage and is skipped in standalone mode. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
526 lines
26 KiB
JavaScript
526 lines
26 KiB
JavaScript
/**
|
|
* 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 = `
|
|
<nav class="bg-white border-b border-gray-200 sticky top-0 z-50 shadow-sm" id="tractatus-navbar">
|
|
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
|
<div class="flex justify-between h-16">
|
|
|
|
<!-- Left: Logo + Brand -->
|
|
<div class="flex items-center">
|
|
<a href="/"
|
|
class="flex items-center space-x-2 px-2 py-2 -ml-2 rounded-lg hover:bg-blue-50 transition-all duration-200 group"
|
|
title="Return to homepage">
|
|
<img src="/images/tractatus-icon-new.svg" alt="Tractatus Icon" class="w-8 h-8">
|
|
<span class="text-lg font-bold text-gray-900 group-hover:text-blue-700 transition-colors hidden sm:inline">Tractatus</span>
|
|
</a>
|
|
</div>
|
|
|
|
<!-- Centre: Desktop Navigation (hidden on mobile) -->
|
|
<div class="hidden lg:flex items-center space-x-1">
|
|
|
|
<!-- Research Dropdown -->
|
|
<div class="relative" data-dropdown="research">
|
|
<button class="nav-dropdown-btn px-3 py-2 text-sm font-medium text-gray-700 hover:text-blue-700 hover:bg-blue-50 rounded-lg transition flex items-center gap-1">
|
|
Research
|
|
<svg class="w-4 h-4 transition-transform" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/>
|
|
</svg>
|
|
</button>
|
|
<div class="nav-dropdown-panel hidden absolute left-0 top-full mt-1 w-56 bg-white rounded-lg shadow-lg border border-gray-200 py-2 z-50">
|
|
<button data-research-papers-trigger class="w-full text-left block px-4 py-2 text-sm text-gray-700 hover:bg-blue-50 hover:text-blue-700 transition">
|
|
<span class="font-medium">Publications</span>
|
|
<span class="block text-xs text-gray-500 mt-0.5">Browse research papers</span>
|
|
</button>
|
|
<div class="border-t border-gray-100 my-1"></div>
|
|
<a href="/researcher.html" class="block px-4 py-2 text-sm text-gray-700 hover:bg-blue-50 hover:text-blue-700 transition">
|
|
<span class="font-medium">For Researchers</span>
|
|
<span class="block text-xs text-gray-500 mt-0.5">Open questions and collaboration</span>
|
|
</a>
|
|
<a href="/timeline.html" class="block px-4 py-2 text-sm text-gray-700 hover:bg-blue-50 hover:text-blue-700 transition">
|
|
<span class="font-medium">Research Timeline</span>
|
|
<span class="block text-xs text-gray-500 mt-0.5">Evolution of the research</span>
|
|
</a>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Framework Dropdown (Architecture + Implementation) -->
|
|
<div class="relative" data-dropdown="framework">
|
|
<button class="nav-dropdown-btn px-3 py-2 text-sm font-medium text-gray-700 hover:text-blue-700 hover:bg-blue-50 rounded-lg transition flex items-center gap-1">
|
|
Framework
|
|
<svg class="w-4 h-4 transition-transform" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/>
|
|
</svg>
|
|
</button>
|
|
<div class="nav-dropdown-panel hidden absolute left-0 top-full mt-1 w-64 bg-white rounded-lg shadow-lg border border-gray-200 py-2 z-50">
|
|
<a href="/architecture.html" class="block px-4 py-2 text-sm text-gray-700 hover:bg-blue-50 hover:text-blue-700 transition">
|
|
<span class="font-medium">System Architecture</span>
|
|
<span class="block text-xs text-gray-500 mt-0.5">Technical architecture overview</span>
|
|
</a>
|
|
<div class="border-t border-gray-100 my-1"></div>
|
|
<a href="/implementer.html" class="block px-4 py-2 text-sm text-gray-700 hover:bg-blue-50 hover:text-blue-700 transition">
|
|
<span class="font-medium">For Implementers</span>
|
|
<span class="block text-xs text-gray-500 mt-0.5">Integration guide and code examples</span>
|
|
</a>
|
|
<a href="/village-case-study.html" class="block px-4 py-2 text-sm text-gray-700 hover:bg-blue-50 hover:text-blue-700 transition">
|
|
<span class="font-medium">Village Case Study</span>
|
|
<span class="block text-xs text-gray-500 mt-0.5">Production deployment evidence</span>
|
|
</a>
|
|
<a href="/home-ai.html" class="block px-4 py-2 text-sm text-gray-700 hover:bg-blue-50 hover:text-blue-700 transition">
|
|
<span class="font-medium">Home AI</span>
|
|
<span class="block text-xs text-gray-500 mt-0.5">Sovereign locally-trained language model</span>
|
|
</a>
|
|
<div class="border-t border-gray-100 my-1"></div>
|
|
<a href="/integrations/agent-lightning.html" class="block px-4 py-2 text-sm text-gray-700 hover:bg-blue-50 hover:text-blue-700 transition">
|
|
<span class="font-medium">Agent Lightning</span>
|
|
<span class="block text-xs text-gray-500 mt-0.5">Performance optimisation integration</span>
|
|
</a>
|
|
<a href="/leader.html" class="block px-4 py-2 text-sm text-gray-700 hover:bg-blue-50 hover:text-blue-700 transition">
|
|
<span class="font-medium">For Leaders</span>
|
|
<span class="block text-xs text-gray-500 mt-0.5">Strategic overview and business case</span>
|
|
</a>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Blog (direct link) -->
|
|
<a href="/blog.html" class="px-3 py-2 text-sm font-medium text-gray-700 hover:text-blue-700 hover:bg-blue-50 rounded-lg transition">
|
|
Blog
|
|
</a>
|
|
|
|
<!-- About Dropdown -->
|
|
<div class="relative" data-dropdown="about">
|
|
<button class="nav-dropdown-btn px-3 py-2 text-sm font-medium text-gray-700 hover:text-blue-700 hover:bg-blue-50 rounded-lg transition flex items-center gap-1">
|
|
About
|
|
<svg class="w-4 h-4 transition-transform" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/>
|
|
</svg>
|
|
</button>
|
|
<div class="nav-dropdown-panel hidden absolute left-0 top-full mt-1 w-56 bg-white rounded-lg shadow-lg border border-gray-200 py-2 z-50">
|
|
<a href="/about.html" class="block px-4 py-2 text-sm text-gray-700 hover:bg-blue-50 hover:text-blue-700 transition">
|
|
<span class="font-medium">About Tractatus</span>
|
|
</a>
|
|
<a href="/about/values.html" class="block px-4 py-2 text-sm text-gray-700 hover:bg-blue-50 hover:text-blue-700 transition">
|
|
<span class="font-medium">Values</span>
|
|
</a>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<!-- Right: Secondary links + Language + Menu -->
|
|
<div class="flex items-center gap-2">
|
|
<!-- Desktop secondary links -->
|
|
<div class="hidden lg:flex items-center gap-1">
|
|
<a href="/docs.html" class="px-3 py-2 text-sm text-gray-600 hover:text-blue-700 hover:bg-blue-50 rounded-lg transition">Docs</a>
|
|
<a href="https://github.com/AgenticGovernance/tractatus-framework" target="_blank" rel="noopener noreferrer" class="px-3 py-2 text-sm text-gray-600 hover:text-blue-700 hover:bg-blue-50 rounded-lg transition">GitHub</a>
|
|
<a href="/koha.html" class="px-3 py-1.5 text-sm font-medium text-teal-700 bg-teal-50 hover:bg-teal-100 rounded-lg transition border border-teal-200">Koha</a>
|
|
</div>
|
|
|
|
<!-- Language Selector Container -->
|
|
<div id="language-selector-container"></div>
|
|
|
|
<!-- Mobile menu button (hidden on desktop) -->
|
|
<button id="mobile-menu-btn" class="lg:hidden text-gray-600 hover:text-gray-900 focus:outline-none focus:ring-2 focus:ring-blue-500 rounded p-2" aria-label="Toggle menu">
|
|
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16" />
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Mobile Navigation Drawer -->
|
|
<div id="mobile-menu" class="hidden fixed inset-0 z-[9999]">
|
|
<div id="mobile-menu-backdrop" class="absolute inset-0 bg-gray-900/60 backdrop-blur-sm transition-opacity"></div>
|
|
|
|
<div id="mobile-menu-panel" class="absolute right-0 top-0 bottom-0 w-80 max-w-[85vw] bg-white shadow-2xl transform transition-transform duration-300 ease-out overflow-y-auto">
|
|
<div class="flex justify-between items-center px-5 h-16 border-b border-gray-200">
|
|
<div class="flex items-center space-x-2">
|
|
<img src="/images/tractatus-icon-new.svg" alt="Tractatus Icon" class="w-6 h-6">
|
|
<span class="font-bold text-gray-900">Navigation</span>
|
|
</div>
|
|
<button id="mobile-menu-close-btn" class="text-gray-600 hover:text-gray-900 p-2 rounded hover:bg-gray-100 transition" aria-label="Close menu">
|
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
<nav class="p-5 space-y-1">
|
|
<!-- Research (collapsible) -->
|
|
<div class="mobile-nav-section border-b border-gray-200 pb-1 mb-1" data-section="research">
|
|
<button class="mobile-nav-section-btn flex items-center justify-between w-full px-3 py-2.5 text-xs font-semibold text-gray-500 uppercase tracking-wider hover:bg-gray-50 rounded-lg transition">
|
|
Research
|
|
<svg class="w-4 h-4 transition-transform duration-200" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/>
|
|
</svg>
|
|
</button>
|
|
<div class="mobile-nav-section-content overflow-hidden transition-all duration-200" style="max-height:0">
|
|
<button data-research-papers-trigger class="w-full text-left block px-3 py-2 text-sm text-gray-700 hover:bg-blue-50 hover:text-blue-700 rounded-lg transition">Publications</button>
|
|
<a href="/researcher.html" class="block px-3 py-2 text-sm text-gray-700 hover:bg-blue-50 hover:text-blue-700 rounded-lg transition">For Researchers</a>
|
|
<a href="/timeline.html" class="block px-3 py-2 text-sm text-gray-700 hover:bg-blue-50 hover:text-blue-700 rounded-lg transition">Research Timeline</a>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Framework (collapsible) -->
|
|
<div class="mobile-nav-section border-b border-gray-200 pb-1 mb-1" data-section="framework">
|
|
<button class="mobile-nav-section-btn flex items-center justify-between w-full px-3 py-2.5 text-xs font-semibold text-gray-500 uppercase tracking-wider hover:bg-gray-50 rounded-lg transition">
|
|
Framework
|
|
<svg class="w-4 h-4 transition-transform duration-200" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/>
|
|
</svg>
|
|
</button>
|
|
<div class="mobile-nav-section-content overflow-hidden transition-all duration-200" style="max-height:0">
|
|
<a href="/architecture.html" class="block px-3 py-2 text-sm text-gray-700 hover:bg-blue-50 hover:text-blue-700 rounded-lg transition">System Architecture</a>
|
|
<a href="/implementer.html" class="block px-3 py-2 text-sm text-gray-700 hover:bg-blue-50 hover:text-blue-700 rounded-lg transition">For Implementers</a>
|
|
<a href="/village-case-study.html" class="block px-3 py-2 text-sm text-gray-700 hover:bg-blue-50 hover:text-blue-700 rounded-lg transition">Village Case Study</a>
|
|
<a href="/home-ai.html" class="block px-3 py-2 text-sm text-gray-700 hover:bg-blue-50 hover:text-blue-700 rounded-lg transition">Home AI</a>
|
|
<a href="/integrations/agent-lightning.html" class="block px-3 py-2 text-sm text-gray-700 hover:bg-blue-50 hover:text-blue-700 rounded-lg transition">Agent Lightning</a>
|
|
<a href="/leader.html" class="block px-3 py-2 text-sm text-gray-700 hover:bg-blue-50 hover:text-blue-700 rounded-lg transition">For Leaders</a>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Blog (direct link) -->
|
|
<div class="border-b border-gray-200 pb-1 mb-1">
|
|
<a href="/blog.html" class="block px-3 py-2.5 text-sm font-medium text-gray-700 hover:bg-blue-50 hover:text-blue-700 rounded-lg transition">Blog</a>
|
|
</div>
|
|
|
|
<!-- About (collapsible) -->
|
|
<div class="mobile-nav-section border-b border-gray-200 pb-1 mb-1" data-section="about">
|
|
<button class="mobile-nav-section-btn flex items-center justify-between w-full px-3 py-2.5 text-xs font-semibold text-gray-500 uppercase tracking-wider hover:bg-gray-50 rounded-lg transition">
|
|
About
|
|
<svg class="w-4 h-4 transition-transform duration-200" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/>
|
|
</svg>
|
|
</button>
|
|
<div class="mobile-nav-section-content overflow-hidden transition-all duration-200" style="max-height:0">
|
|
<a href="/about.html" class="block px-3 py-2 text-sm text-gray-700 hover:bg-blue-50 hover:text-blue-700 rounded-lg transition">About Tractatus</a>
|
|
<a href="/about/values.html" class="block px-3 py-2 text-sm text-gray-700 hover:bg-blue-50 hover:text-blue-700 rounded-lg transition">Values</a>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Resources (collapsible) -->
|
|
<div class="mobile-nav-section border-b border-gray-200 pb-1 mb-1" data-section="resources">
|
|
<button class="mobile-nav-section-btn flex items-center justify-between w-full px-3 py-2.5 text-xs font-semibold text-gray-500 uppercase tracking-wider hover:bg-gray-50 rounded-lg transition">
|
|
Resources
|
|
<svg class="w-4 h-4 transition-transform duration-200" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/>
|
|
</svg>
|
|
</button>
|
|
<div class="mobile-nav-section-content overflow-hidden transition-all duration-200" style="max-height:0">
|
|
<a href="/docs.html" class="block px-3 py-2 text-sm text-gray-700 hover:bg-blue-50 hover:text-blue-700 rounded-lg transition">Documentation</a>
|
|
<a href="https://github.com/AgenticGovernance/tractatus-framework" target="_blank" rel="noopener noreferrer" class="block px-3 py-2 text-sm text-gray-700 hover:bg-blue-50 hover:text-blue-700 rounded-lg transition">GitHub</a>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Koha (standalone prominent link) -->
|
|
<div class="pt-1">
|
|
<a href="/koha.html" class="block px-3 py-2.5 text-sm font-medium text-teal-700 bg-teal-50 hover:bg-teal-100 rounded-lg transition border border-teal-200">Koha</a>
|
|
</div>
|
|
|
|
<!-- Feedback (mobile-only, replaces hidden FAB) -->
|
|
<div class="pt-1">
|
|
<button id="mobile-feedback-btn" class="w-full text-left block px-3 py-2.5 text-sm font-medium text-blue-700 bg-blue-50 hover:bg-blue-100 rounded-lg transition border border-blue-200">Feedback</button>
|
|
</div>
|
|
</nav>
|
|
</div>
|
|
</div>
|
|
|
|
</nav>
|
|
`;
|
|
|
|
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();
|
|
}
|