- Add Agent Lightning link to navbar menu (visible on all pages) - Add prominent "What's New" banner on homepage highlighting AL integration - Link to AL Discord and integration page - Improve discoverability of AL integration for visitors Related to Phase 2 Master Plan completion 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
248 lines
11 KiB
JavaScript
248 lines
11 KiB
JavaScript
/**
|
||
* Tractatus Framework - Responsive Navbar Component
|
||
* Consistent, mobile-friendly navigation across all pages
|
||
*/
|
||
|
||
class TractatusNavbar {
|
||
constructor() {
|
||
this.mobileMenuOpen = false;
|
||
this.init();
|
||
}
|
||
|
||
init() {
|
||
this.render();
|
||
this.attachEventListeners();
|
||
this.setActivePageIndicator();
|
||
|
||
// 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-3 px-3 py-2 -ml-3 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-xl font-bold text-gray-900 group-hover:text-blue-700 transition-colors hidden sm:inline">Tractatus Framework</span>
|
||
<span class="text-xl font-bold text-gray-900 group-hover:text-blue-700 transition-colors sm:hidden">Tractatus</span>
|
||
<svg class="w-4 h-4 text-gray-400 opacity-0 group-hover:opacity-100 transition-opacity ml-1 hidden sm:block"
|
||
fill="none"
|
||
stroke="currentColor"
|
||
viewBox="0 0 24 24">
|
||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6"/>
|
||
</svg>
|
||
</a>
|
||
</div>
|
||
|
||
<!-- Right: Language Selector + Menu Button -->
|
||
<div class="flex items-center gap-3">
|
||
<!-- Language Selector Container -->
|
||
<div id="language-selector-container"></div>
|
||
|
||
<button id="mobile-menu-btn" class="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>
|
||
|
||
<!-- Navigation Drawer (overlay, doesn't push content) -->
|
||
<div id="mobile-menu" class="hidden fixed inset-0 z-[9999]">
|
||
<!-- Backdrop with blur -->
|
||
<div id="mobile-menu-backdrop" class="absolute inset-0 bg-gray-900/60 backdrop-blur-sm transition-opacity"></div>
|
||
|
||
<!-- Menu Panel (slides from right) -->
|
||
<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">
|
||
<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-3">
|
||
<div class="pb-3 mb-3 border-b border-gray-200">
|
||
<p class="text-xs font-semibold text-gray-500 uppercase tracking-wider mb-2 px-3">Audiences</p>
|
||
<a href="/researcher.html" class="block px-3 py-2.5 text-gray-700 hover:bg-blue-50 hover:text-blue-700 rounded-lg transition font-medium">
|
||
<span class="text-sm">🔬 Researcher</span>
|
||
</a>
|
||
<a href="/implementer.html" class="block px-3 py-2.5 text-gray-700 hover:bg-blue-50 hover:text-blue-700 rounded-lg transition font-medium">
|
||
<span class="text-sm">⚙️ Implementer</span>
|
||
</a>
|
||
<a href="/leader.html" class="block px-3 py-2.5 text-gray-700 hover:bg-blue-50 hover:text-blue-700 rounded-lg transition font-medium">
|
||
<span class="text-sm">💼 Leader</span>
|
||
</a>
|
||
</div>
|
||
<a href="/integrations/agent-lightning.html" class="block px-3 py-2.5 text-gray-700 hover:bg-purple-50 hover:text-purple-700 rounded-lg transition">
|
||
<span class="text-sm font-semibold">⚡ Agent Lightning Integration</span>
|
||
<span class="block text-xs text-gray-500 mt-0.5">Governance + Performance</span>
|
||
</a>
|
||
<a href="/docs.html" class="block px-3 py-2.5 text-gray-700 hover:bg-blue-50 hover:text-blue-700 rounded-lg transition">
|
||
<span class="text-sm font-semibold">📚 Documentation</span>
|
||
</a>
|
||
<a href="/blog.html" class="block px-3 py-2.5 text-gray-700 hover:bg-blue-50 hover:text-blue-700 rounded-lg transition">
|
||
<span class="text-sm font-semibold">📝 Blog</span>
|
||
</a>
|
||
<a href="/faq.html" class="block px-3 py-2.5 text-gray-700 hover:bg-blue-50 hover:text-blue-700 rounded-lg transition">
|
||
<span class="text-sm font-semibold">❓ FAQ</span>
|
||
</a>
|
||
<a href="/about.html" class="block px-3 py-2.5 text-gray-700 hover:bg-blue-50 hover:text-blue-700 rounded-lg transition">
|
||
<span class="text-sm font-semibold">ℹ️ About</span>
|
||
</a>
|
||
<a href="/koha.html" class="block px-3 py-2.5 text-gray-700 hover:bg-blue-50 hover:text-blue-700 rounded-lg transition">
|
||
<span class="text-sm font-semibold">🤝 Support (Koha)</span>
|
||
</a>
|
||
|
||
<!-- Feedback (opens modal) -->
|
||
<div class="pt-3 mt-3 border-t border-gray-200">
|
||
<button id="navbar-feedback-btn" class="w-full text-left block px-3 py-2.5 text-white bg-gradient-to-r from-blue-600 to-blue-700 hover:shadow-lg rounded-lg transition">
|
||
<span class="text-sm font-semibold">💬 Give Feedback</span>
|
||
<span class="block text-xs opacity-90 mt-0.5">Governed by Tractatus + AL</span>
|
||
</button>
|
||
</div>
|
||
</nav>
|
||
</div>
|
||
</div>
|
||
|
||
</nav>
|
||
`;
|
||
|
||
// Always insert navbar at the very beginning of body
|
||
// Check if there's already a tractatus navbar (to avoid duplicates)
|
||
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() {
|
||
// Mobile Menu (Navigation Drawer)
|
||
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) {
|
||
// Open: Show menu and slide panel in from right
|
||
mobileMenu.classList.remove('hidden');
|
||
// Use setTimeout to ensure display change happens before animation
|
||
setTimeout(() => {
|
||
mobileMenuPanel.classList.remove('translate-x-full');
|
||
mobileMenuPanel.classList.add('translate-x-0');
|
||
}, 10);
|
||
document.body.style.overflow = 'hidden'; // Prevent scrolling when menu is open
|
||
} else {
|
||
// Close: Slide panel out to right
|
||
mobileMenuPanel.classList.remove('translate-x-0');
|
||
mobileMenuPanel.classList.add('translate-x-full');
|
||
// Hide menu after animation completes (300ms)
|
||
setTimeout(() => {
|
||
mobileMenu.classList.add('hidden');
|
||
}, 300);
|
||
document.body.style.overflow = '';
|
||
}
|
||
};
|
||
|
||
// Initialize panel in hidden state (off-screen to the right)
|
||
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 - dispatch event that feedback component will handle
|
||
const navbarFeedbackBtn = document.getElementById('navbar-feedback-btn');
|
||
if (navbarFeedbackBtn) {
|
||
navbarFeedbackBtn.addEventListener('click', () => {
|
||
// Close mobile menu
|
||
if (this.mobileMenuOpen) {
|
||
toggleMobileMenu();
|
||
}
|
||
|
||
// Dispatch event to open feedback modal
|
||
window.dispatchEvent(new CustomEvent('openFeedbackModal'));
|
||
});
|
||
}
|
||
}
|
||
|
||
setActivePageIndicator() {
|
||
// Get current page path
|
||
const currentPath = window.location.pathname;
|
||
|
||
// Normalize paths (handle both /page.html and /page)
|
||
const normalizePath = (path) => {
|
||
if (path === '/' || path === '/index.html') return '/';
|
||
return path.replace('.html', '').replace(/\/$/, '');
|
||
};
|
||
|
||
const normalizedCurrent = normalizePath(currentPath);
|
||
|
||
// Find all navigation links in mobile menu
|
||
const mobileLinks = document.querySelectorAll('#mobile-menu a');
|
||
|
||
mobileLinks.forEach(link => {
|
||
const linkPath = link.getAttribute('href');
|
||
const normalizedLink = normalizePath(linkPath);
|
||
|
||
if (normalizedLink === normalizedCurrent) {
|
||
// Add active styling with brand colors
|
||
link.classList.add('border-l-4', 'bg-sky-50');
|
||
link.style.borderLeftColor = 'var(--tractatus-core-end)';
|
||
link.style.color = 'var(--tractatus-core-end)';
|
||
link.classList.remove('text-gray-700');
|
||
}
|
||
});
|
||
}
|
||
}
|
||
|
||
// Auto-initialize when DOM is ready
|
||
if (document.readyState === 'loading') {
|
||
document.addEventListener('DOMContentLoaded', () => new TractatusNavbar());
|
||
} else {
|
||
new TractatusNavbar();
|
||
}
|