jaeswift-website/js/nav.js
jae 139849d632 feat: dropdown nav with submenus + black ops colour theme redesign
- Navigation: 6 top-level items (BASE, TRANSMISSIONS, ARMOURY, INTEL, SAFEHOUSE, COMMS) with dropdown children
- nav.js: renders nested dropdown submenus, mobile tap-to-toggle support
- Theme: tactical green (#00ff41) accent, deep black (#0a0a0a) bg, amber (#c9a227) secondary
- 176 colour replacements across 4 CSS + 3 JS files
- Mobile: responsive dropdowns with slide animation
- Updated navigation.json with full nested structure
2026-04-01 03:16:34 +00:00

159 lines
6.3 KiB
JavaScript

/* ─── Dynamic Navigation with Dropdown Submenus ─── */
/* Fetches nav items from /api/navigation and renders dropdowns */
(function() {
'use strict';
async function loadNavigation() {
const navMenu = document.getElementById('navMenu');
if (!navMenu) return;
try {
const res = await fetch('/api/navigation');
if (!res.ok) throw new Error('Nav API ' + res.status);
const items = await res.json();
// Sort by order
items.sort((a, b) => (a.order || 0) - (b.order || 0));
// Determine current page for active state
const path = window.location.pathname;
const search = window.location.search;
const fullPath = path + search;
// Build nav HTML
let html = '';
items.forEach(item => {
const url = item.url || '/';
const label = (item.label || '').toUpperCase();
const children = item.children || [];
const hasChildren = children.length > 0;
// Determine if this link is active
let isActive = false;
if (url === '/' && (path === '/' || path === '/index.html')) {
isActive = true;
} else if (url !== '/' && path.startsWith(url.split('?')[0])) {
isActive = true;
}
const activeClass = isActive ? ' active' : '';
const hasDropdownClass = hasChildren ? ' has-dropdown' : '';
// Check if it's an anchor link (homepage sections)
const isAnchor = url.startsWith('#') || url.startsWith('/#');
const href = isAnchor && path !== '/' ? '/' + url.replace(/^\//, '') : url;
html += `<li class="nav-item${hasDropdownClass}">`;
html += `<a href="${href}" class="nav-link${activeClass}">${label}`;
if (hasChildren) {
html += `<span class="dropdown-arrow">▾</span>`;
}
html += `</a>`;
// Render dropdown submenu
if (hasChildren) {
html += `<ul class="dropdown">`;
children.forEach(child => {
const childUrl = child.url || '#';
const childLabel = (child.label || '').toUpperCase();
// Check child active state
let childActive = false;
if (childUrl === fullPath) {
childActive = true;
} else if (childUrl.startsWith('/#') && path === '/') {
childActive = false; // anchor links - don't mark active
}
const childActiveClass = childActive ? ' active' : '';
// Handle anchor links from non-homepage
const childIsAnchor = childUrl.startsWith('#') || childUrl.startsWith('/#');
const childHref = childIsAnchor && path !== '/' ? '/' + childUrl.replace(/^\//, '') : childUrl;
html += `<li><a href="${childHref}" class="dropdown-link${childActiveClass}">${childLabel}</a></li>`;
});
html += `</ul>`;
}
html += `</li>\n`;
});
navMenu.innerHTML = html;
// Bind mobile dropdown toggles after rendering
initMobileDropdowns();
} catch (err) {
console.warn('Nav load failed, keeping existing:', err);
}
}
// Mobile: tap parent to toggle dropdown
function initMobileDropdowns() {
const isMobile = () => window.innerWidth <= 768;
document.querySelectorAll('.nav-item.has-dropdown > .nav-link').forEach(link => {
link.addEventListener('click', function(e) {
if (!isMobile()) return; // desktop uses hover
const parent = this.parentElement;
const dropdown = parent.querySelector('.dropdown');
if (!dropdown) return;
// If dropdown is closed, prevent navigation and open it
if (!parent.classList.contains('dropdown-open')) {
e.preventDefault();
// Close all other dropdowns
document.querySelectorAll('.nav-item.dropdown-open').forEach(item => {
if (item !== parent) item.classList.remove('dropdown-open');
});
parent.classList.add('dropdown-open');
} else {
// Dropdown already open — allow navigation or close
// If tapped again on same parent link, close dropdown
e.preventDefault();
parent.classList.remove('dropdown-open');
}
});
});
// Close dropdowns when tapping outside
document.addEventListener('click', function(e) {
if (!isMobile()) return;
if (!e.target.closest('.nav-item.has-dropdown')) {
document.querySelectorAll('.nav-item.dropdown-open').forEach(item => {
item.classList.remove('dropdown-open');
});
}
});
}
// Nav toggle (hamburger menu)
function initNavToggle() {
const toggle = document.getElementById('navToggle');
const menu = document.getElementById('navMenu');
if (toggle && menu) {
toggle.addEventListener('click', () => {
menu.classList.toggle('open');
toggle.classList.toggle('active');
// Close any open dropdowns when closing menu
if (!menu.classList.contains('open')) {
document.querySelectorAll('.nav-item.dropdown-open').forEach(item => {
item.classList.remove('dropdown-open');
});
}
});
}
}
// Run on DOM ready
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', () => {
loadNavigation();
initNavToggle();
});
} else {
loadNavigation();
initNavToggle();
}
})();