tractatus/public/js/components/navbar.js
TheFlow 0c2c2e463d feat: Audit & update all audience pages with current research and metrics
- Add i18n data-i18n attributes to navbar.js (~30 elements) and expand
  all 4 common.json files with navbar translation keys (EN/DE/FR/MI)
- Replace broken HuggingFace Space links with local audit analytics
  dashboard across 4 HTML files; update counts to 171,800+
- Add Steering Vectors, Taonga Governance, and Home AI sections to
  researcher.html, leader.html, and implementer.html
- Update stale metrics: development period (11+ months), sessions
  (1,000+), instructions (68), model references (Opus 4.6)
- Update roadmap Multi-LLM status to reflect Home AI as first non-Claude
  deployment; add Sovereign Training Pipeline and Taonga Registry items
- Update all EN/DE/FR translation files with new section keys

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-11 08:07:26 +13:00

526 lines
28 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">
<span data-i18n="navbar.research">Research</span>
<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" data-i18n="navbar.publications">Publications</span>
<span class="block text-xs text-gray-500 mt-0.5" data-i18n="navbar.publications_desc">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" data-i18n="navbar.for_researchers">For Researchers</span>
<span class="block text-xs text-gray-500 mt-0.5" data-i18n="navbar.for_researchers_desc">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" data-i18n="navbar.research_timeline">Research Timeline</span>
<span class="block text-xs text-gray-500 mt-0.5" data-i18n="navbar.research_timeline_desc">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">
<span data-i18n="navbar.framework">Framework</span>
<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" data-i18n="navbar.system_architecture">System Architecture</span>
<span class="block text-xs text-gray-500 mt-0.5" data-i18n="navbar.system_architecture_desc">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" data-i18n="navbar.for_implementers">For Implementers</span>
<span class="block text-xs text-gray-500 mt-0.5" data-i18n="navbar.for_implementers_desc">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" data-i18n="navbar.village_case_study">Village Case Study</span>
<span class="block text-xs text-gray-500 mt-0.5" data-i18n="navbar.village_case_study_desc">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" data-i18n="navbar.home_ai">Home AI</span>
<span class="block text-xs text-gray-500 mt-0.5" data-i18n="navbar.home_ai_desc">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" data-i18n="navbar.agent_lightning">Agent Lightning</span>
<span class="block text-xs text-gray-500 mt-0.5" data-i18n="navbar.agent_lightning_desc">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" data-i18n="navbar.for_leaders">For Leaders</span>
<span class="block text-xs text-gray-500 mt-0.5" data-i18n="navbar.for_leaders_desc">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" data-i18n="navbar.blog">
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">
<span data-i18n="navbar.about">About</span>
<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" data-i18n="navbar.about_tractatus">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" data-i18n="navbar.values">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" data-i18n="navbar.docs">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" data-i18n="navbar.github">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" data-i18n="navbar.koha">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" data-i18n="navbar.navigation">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">
<span data-i18n="navbar.research">Research</span>
<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" data-i18n="navbar.publications">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" data-i18n="navbar.for_researchers">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" data-i18n="navbar.research_timeline">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">
<span data-i18n="navbar.framework">Framework</span>
<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" data-i18n="navbar.system_architecture">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" data-i18n="navbar.for_implementers">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" data-i18n="navbar.village_case_study">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" data-i18n="navbar.home_ai">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" data-i18n="navbar.agent_lightning">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" data-i18n="navbar.for_leaders">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" data-i18n="navbar.blog">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">
<span data-i18n="navbar.about">About</span>
<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" data-i18n="navbar.about_tractatus">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" data-i18n="navbar.values">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">
<span data-i18n="navbar.resources">Resources</span>
<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" data-i18n="navbar.documentation">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" data-i18n="navbar.github">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" data-i18n="navbar.koha">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" data-i18n="navbar.mobile_feedback">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();
}