tractatus/public/js/components/navbar.js
TheFlow a7937dc274 feat: Add company website links to navbar and footer
- Add 'Company' section to footer with MySovereignty website links
  - Link to main website (mysovereignty.digital)
  - Link to Village Ecosystem page
  - Link to About Us page
- Add 'Company' section to navbar mobile menu
  - Link to main website (mysovereignty.digital)
- Update footer grid layout: md:grid-cols-4 → md:grid-cols-2 lg:grid-cols-5
- Include translations for all 3 languages (EN, DE, FR)
  - EN: Company
  - DE: Unternehmen
  - FR: Entreprise

Files modified:
- public/js/components/footer.js (lines 52-62)
- public/js/components/navbar.js (lines 110-116)
- public/locales/en/common.json
- public/locales/de/common.json
- public/locales/fr/common.json

Tested locally on port 9000 - all links functional, translations verified.
2025-11-24 13:12:44 +13:00

256 lines
12 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 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>
<!-- Company -->
<div class="pt-3 mt-3 border-t border-gray-200">
<p class="text-xs font-semibold text-gray-500 uppercase tracking-wider mb-2 px-3" data-i18n="navbar.company_heading">Company</p>
<a href="https://mysovereignty.digital" 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" data-i18n="navbar.company_links.website">← MySovereignty</span>
</a>
</div>
<!-- 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" data-i18n="navbar.feedback">💬 Give Feedback</span>
<span class="block text-xs opacity-90 mt-0.5" data-i18n="navbar.feedback_desc">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();
}