updateweb

This commit is contained in:
Local Server
2026-01-01 22:24:30 -06:00
parent 017c6376fc
commit 1919f6f8bb
185 changed files with 19860 additions and 17603 deletions

View File

@@ -1,79 +1,203 @@
// Dynamic Menu Loader for Sky Art Shop
// Include this in all public pages to load menu from database
/**
* Navigation Component
* Handles mobile menu, dropdowns, and accessibility
*/
(function () {
"use strict";
// Load and render navigation menu from API
async function loadNavigationMenu() {
try {
const response = await fetch("/api/menu");
const data = await response.json();
class Navigation {
constructor() {
this.mobileMenuToggle = document.getElementById("mobileMenuToggle");
this.mobileMenu = document.getElementById("mobileMenu");
this.mobileMenuClose = document.getElementById("mobileMenuClose");
this.overlay = document.getElementById("mobileMenuOverlay");
this.body = document.body;
if (data.success && data.items && data.items.length > 0) {
renderDesktopMenu(data.items);
renderMobileMenu(data.items);
this.init();
}
init() {
this.setupMobileMenu();
this.setupAccessibility();
this.highlightCurrentPage();
this.setupKeyboardNavigation();
}
setupMobileMenu() {
// Open mobile menu
if (this.mobileMenuToggle) {
this.mobileMenuToggle.addEventListener("click", () =>
this.openMobileMenu()
);
}
// Close mobile menu
if (this.mobileMenuClose) {
this.mobileMenuClose.addEventListener("click", () =>
this.closeMobileMenu()
);
}
if (this.overlay) {
this.overlay.addEventListener("click", () => this.closeMobileMenu());
}
// Close on ESC key
document.addEventListener("keydown", (e) => {
if (
e.key === "Escape" &&
this.mobileMenu &&
this.mobileMenu.classList.contains("active")
) {
this.closeMobileMenu();
}
});
}
openMobileMenu() {
if (this.mobileMenu) {
this.mobileMenu.classList.add("active");
this.mobileMenu.setAttribute("aria-hidden", "false");
this.body.style.overflow = "hidden";
if (this.overlay) {
this.overlay.classList.add("active");
}
// Focus first link
const firstLink = this.mobileMenu.querySelector("a");
if (firstLink) {
setTimeout(() => firstLink.focus(), 100);
}
}
}
closeMobileMenu() {
if (this.mobileMenu) {
this.mobileMenu.classList.remove("active");
this.mobileMenu.setAttribute("aria-hidden", "true");
this.body.style.overflow = "";
if (this.overlay) {
this.overlay.classList.remove("active");
}
// Return focus to toggle button
if (this.mobileMenuToggle) {
this.mobileMenuToggle.focus();
}
}
}
setupAccessibility() {
// Wait for body to exist
if (!document.body) return;
// Add ARIA labels to nav items
const navLinks = document.querySelectorAll(".nav-link");
navLinks.forEach((link) => {
if (!link.getAttribute("aria-label")) {
link.setAttribute(
"aria-label",
`Navigate to ${link.textContent.trim()}`
);
}
});
// Add skip to main content link
if (!document.getElementById("skip-to-main")) {
const skipLink = document.createElement("a");
skipLink.id = "skip-to-main";
skipLink.href = "#main-content";
skipLink.textContent = "Skip to main content";
skipLink.className = "skip-link";
document.body.insertBefore(skipLink, document.body.firstChild);
// Add styles for skip link
if (!document.getElementById("skip-link-styles")) {
const style = document.createElement("style");
style.id = "skip-link-styles";
style.textContent = `
.skip-link {
position: fixed;
top: -100px;
left: 0;
padding: 10px 20px;
background: #000;
color: #fff;
z-index: 10001;
text-decoration: none;
border-radius: 0 0 8px 0;
}
.skip-link:focus {
top: 0;
}
`;
document.head.appendChild(style);
}
}
}
highlightCurrentPage() {
const currentPath = window.location.pathname;
const navLinks = document.querySelectorAll(".nav-link, .mobile-link");
navLinks.forEach((link) => {
const href = link.getAttribute("href");
if (
href &&
(currentPath === href || currentPath.startsWith(href + "/"))
) {
link.classList.add("active");
link.setAttribute("aria-current", "page");
} else {
link.classList.remove("active");
link.removeAttribute("aria-current");
}
});
}
setupKeyboardNavigation() {
// Tab trap in mobile menu when open
if (this.mobileMenu) {
const focusableElements = this.mobileMenu.querySelectorAll(
'a, button, input, select, textarea, [tabindex]:not([tabindex="-1"])'
);
if (focusableElements.length > 0) {
const firstElement = focusableElements[0];
const lastElement = focusableElements[focusableElements.length - 1];
this.mobileMenu.addEventListener("keydown", (e) => {
if (
e.key === "Tab" &&
this.mobileMenu.classList.contains("active")
) {
if (e.shiftKey) {
if (document.activeElement === firstElement) {
e.preventDefault();
lastElement.focus();
}
} else {
if (document.activeElement === lastElement) {
e.preventDefault();
firstElement.focus();
}
}
}
});
}
}
} catch (error) {
console.error("Failed to load menu:", error);
// Keep existing hardcoded menu as fallback
}
}
function renderDesktopMenu(items) {
const desktopMenuList = document.querySelector(".nav-menu-list");
if (!desktopMenuList) return;
desktopMenuList.innerHTML = items
.map(
(item) => `
<li class="nav-item">
<a href="${item.url}" class="nav-link">
${item.icon ? `<i class="bi ${item.icon}"></i> ` : ""}${item.label}
</a>
</li>
`
)
.join("");
// Set active state based on current page
const currentPath = window.location.pathname;
document.querySelectorAll(".nav-link").forEach((link) => {
if (link.getAttribute("href") === currentPath) {
link.classList.add("active");
}
});
}
function renderMobileMenu(items) {
const mobileMenuList = document.querySelector(".mobile-menu-list");
if (!mobileMenuList) return;
mobileMenuList.innerHTML = items
.map(
(item) => `
<li>
<a href="${item.url}" class="mobile-link">
${item.icon ? `<i class="bi ${item.icon}"></i> ` : ""}${item.label}
</a>
</li>
`
)
.join("");
// Set active state for mobile menu
const currentPath = window.location.pathname;
document.querySelectorAll(".mobile-link").forEach((link) => {
if (link.getAttribute("href") === currentPath) {
link.classList.add("active");
}
});
}
// Load menu when DOM is ready
// Initialize navigation when DOM is ready
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", loadNavigationMenu);
document.addEventListener("DOMContentLoaded", () => {
new Navigation();
});
} else {
loadNavigationMenu();
new Navigation();
}
})();