617 lines
19 KiB
JavaScript
617 lines
19 KiB
JavaScript
|
|
/**
|
||
|
|
* Accessibility Enhancements
|
||
|
|
* Adds ARIA labels, focus states, keyboard navigation, and screen reader support
|
||
|
|
*/
|
||
|
|
|
||
|
|
(function () {
|
||
|
|
"use strict";
|
||
|
|
|
||
|
|
console.log("[Accessibility] Loading...");
|
||
|
|
|
||
|
|
const A11y = {
|
||
|
|
init() {
|
||
|
|
this.addARIALabels();
|
||
|
|
this.enhanceKeyboardNavigation();
|
||
|
|
this.addSkipLinks();
|
||
|
|
this.enhanceFocusStates();
|
||
|
|
this.announceLiveRegions();
|
||
|
|
this.fixMobileAccessibility();
|
||
|
|
console.log("[Accessibility] Initialized");
|
||
|
|
},
|
||
|
|
|
||
|
|
// Add ARIA labels to interactive elements
|
||
|
|
addARIALabels() {
|
||
|
|
// Navbar
|
||
|
|
const navbar = document.querySelector(".navbar");
|
||
|
|
if (navbar) {
|
||
|
|
navbar.setAttribute("role", "navigation");
|
||
|
|
navbar.setAttribute("aria-label", "Main navigation");
|
||
|
|
}
|
||
|
|
|
||
|
|
// Nav menu
|
||
|
|
const navMenu = document.querySelector(".nav-menu");
|
||
|
|
if (navMenu) {
|
||
|
|
navMenu.setAttribute("role", "menubar");
|
||
|
|
navMenu.setAttribute("aria-label", "Primary menu");
|
||
|
|
|
||
|
|
navMenu.querySelectorAll(".nav-link").forEach((link, index) => {
|
||
|
|
link.setAttribute("role", "menuitem");
|
||
|
|
link.setAttribute("tabindex", "0");
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
// Nav actions buttons
|
||
|
|
const navActions = document.querySelector(".nav-actions");
|
||
|
|
if (navActions) {
|
||
|
|
navActions.setAttribute("role", "group");
|
||
|
|
navActions.setAttribute("aria-label", "Account and cart actions");
|
||
|
|
}
|
||
|
|
|
||
|
|
// Cart button
|
||
|
|
const cartBtn = document.querySelector(".cart-btn");
|
||
|
|
if (cartBtn) {
|
||
|
|
cartBtn.setAttribute("aria-label", "Shopping cart");
|
||
|
|
cartBtn.setAttribute("aria-haspopup", "dialog");
|
||
|
|
const cartCount = cartBtn.querySelector(".cart-count");
|
||
|
|
if (cartCount) {
|
||
|
|
cartBtn.setAttribute("aria-describedby", "cart-count-desc");
|
||
|
|
// Create hidden description for screen readers
|
||
|
|
if (!document.getElementById("cart-count-desc")) {
|
||
|
|
const desc = document.createElement("span");
|
||
|
|
desc.id = "cart-count-desc";
|
||
|
|
desc.className = "sr-only";
|
||
|
|
desc.textContent = "items in cart";
|
||
|
|
cartBtn.appendChild(desc);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Wishlist button
|
||
|
|
const wishlistBtn = document.querySelector(".wishlist-btn-nav");
|
||
|
|
if (wishlistBtn) {
|
||
|
|
wishlistBtn.setAttribute("aria-label", "Wishlist");
|
||
|
|
wishlistBtn.setAttribute("aria-haspopup", "dialog");
|
||
|
|
}
|
||
|
|
|
||
|
|
// Sign in link
|
||
|
|
const signinLink = document.querySelector('a[href="/signin"]');
|
||
|
|
if (signinLink) {
|
||
|
|
signinLink.setAttribute("aria-label", "Sign in to your account");
|
||
|
|
}
|
||
|
|
|
||
|
|
// Mobile menu toggle
|
||
|
|
const mobileToggle = document.querySelector(".nav-mobile-toggle");
|
||
|
|
if (mobileToggle) {
|
||
|
|
mobileToggle.setAttribute("aria-expanded", "false");
|
||
|
|
mobileToggle.setAttribute("aria-controls", "nav-menu");
|
||
|
|
mobileToggle.setAttribute("aria-label", "Toggle navigation menu");
|
||
|
|
}
|
||
|
|
|
||
|
|
// Product cards
|
||
|
|
document.querySelectorAll(".product-card").forEach((card, index) => {
|
||
|
|
card.setAttribute("role", "article");
|
||
|
|
const title = card.querySelector(".product-title, .product-name, h3");
|
||
|
|
if (title) {
|
||
|
|
card.setAttribute("aria-label", title.textContent.trim());
|
||
|
|
}
|
||
|
|
|
||
|
|
// Quick view button
|
||
|
|
const quickView = card.querySelector(
|
||
|
|
'.quick-view-btn, [data-action="quick-view"]'
|
||
|
|
);
|
||
|
|
if (quickView) {
|
||
|
|
quickView.setAttribute(
|
||
|
|
"aria-label",
|
||
|
|
`Quick view ${title ? title.textContent.trim() : "product"}`
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
// Add to cart button
|
||
|
|
const addCart = card.querySelector(
|
||
|
|
'.add-to-cart-btn, [data-action="add-to-cart"]'
|
||
|
|
);
|
||
|
|
if (addCart) {
|
||
|
|
addCart.setAttribute(
|
||
|
|
"aria-label",
|
||
|
|
`Add ${title ? title.textContent.trim() : "product"} to cart`
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
// Wishlist button
|
||
|
|
const wishlist = card.querySelector(
|
||
|
|
'.wishlist-btn, [data-action="wishlist"]'
|
||
|
|
);
|
||
|
|
if (wishlist) {
|
||
|
|
wishlist.setAttribute(
|
||
|
|
"aria-label",
|
||
|
|
`Add ${title ? title.textContent.trim() : "product"} to wishlist`
|
||
|
|
);
|
||
|
|
wishlist.setAttribute("aria-pressed", "false");
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
// Slider controls
|
||
|
|
const sliderPrev = document.querySelector(".slider-arrow.prev");
|
||
|
|
const sliderNext = document.querySelector(".slider-arrow.next");
|
||
|
|
if (sliderPrev) sliderPrev.setAttribute("aria-label", "Previous slide");
|
||
|
|
if (sliderNext) sliderNext.setAttribute("aria-label", "Next slide");
|
||
|
|
|
||
|
|
// Slider nav dots
|
||
|
|
document
|
||
|
|
.querySelectorAll(".slider-nav .dot, .slider-dot")
|
||
|
|
.forEach((dot, index) => {
|
||
|
|
dot.setAttribute("role", "tab");
|
||
|
|
dot.setAttribute("aria-label", `Go to slide ${index + 1}`);
|
||
|
|
dot.setAttribute(
|
||
|
|
"aria-selected",
|
||
|
|
dot.classList.contains("active") ? "true" : "false"
|
||
|
|
);
|
||
|
|
});
|
||
|
|
|
||
|
|
// Cart drawer
|
||
|
|
const cartDrawer = document.querySelector(".cart-drawer");
|
||
|
|
if (cartDrawer) {
|
||
|
|
cartDrawer.setAttribute("role", "dialog");
|
||
|
|
cartDrawer.setAttribute("aria-modal", "true");
|
||
|
|
cartDrawer.setAttribute("aria-label", "Shopping cart");
|
||
|
|
|
||
|
|
const closeBtn = cartDrawer.querySelector(".cart-close, .close-cart");
|
||
|
|
if (closeBtn) {
|
||
|
|
closeBtn.setAttribute("aria-label", "Close cart");
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Wishlist drawer
|
||
|
|
const wishlistDrawer = document.querySelector(".wishlist-drawer");
|
||
|
|
if (wishlistDrawer) {
|
||
|
|
wishlistDrawer.setAttribute("role", "dialog");
|
||
|
|
wishlistDrawer.setAttribute("aria-modal", "true");
|
||
|
|
wishlistDrawer.setAttribute("aria-label", "Wishlist");
|
||
|
|
|
||
|
|
const closeBtn = wishlistDrawer.querySelector(
|
||
|
|
".wishlist-close, .close-wishlist"
|
||
|
|
);
|
||
|
|
if (closeBtn) {
|
||
|
|
closeBtn.setAttribute("aria-label", "Close wishlist");
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Form inputs
|
||
|
|
document
|
||
|
|
.querySelectorAll("input:not([aria-label]):not([aria-labelledby])")
|
||
|
|
.forEach((input) => {
|
||
|
|
const label =
|
||
|
|
input.closest("label") ||
|
||
|
|
document.querySelector(`label[for="${input.id}"]`);
|
||
|
|
if (label) {
|
||
|
|
if (!input.id) {
|
||
|
|
input.id = `input-${Math.random().toString(36).substr(2, 9)}`;
|
||
|
|
label.setAttribute("for", input.id);
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
const placeholder = input.getAttribute("placeholder");
|
||
|
|
if (placeholder) {
|
||
|
|
input.setAttribute("aria-label", placeholder);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
// Images without alt text
|
||
|
|
document.querySelectorAll("img:not([alt])").forEach((img) => {
|
||
|
|
img.setAttribute("alt", "");
|
||
|
|
img.setAttribute("role", "presentation");
|
||
|
|
});
|
||
|
|
|
||
|
|
// Footer
|
||
|
|
const footer = document.querySelector("footer, .footer");
|
||
|
|
if (footer) {
|
||
|
|
footer.setAttribute("role", "contentinfo");
|
||
|
|
}
|
||
|
|
|
||
|
|
// Main content
|
||
|
|
const main = document.querySelector("main, .page-content");
|
||
|
|
if (main) {
|
||
|
|
main.setAttribute("role", "main");
|
||
|
|
main.id = main.id || "main-content";
|
||
|
|
}
|
||
|
|
|
||
|
|
// Sections
|
||
|
|
document.querySelectorAll("section").forEach((section) => {
|
||
|
|
const heading = section.querySelector("h1, h2, h3");
|
||
|
|
if (heading) {
|
||
|
|
section.setAttribute(
|
||
|
|
"aria-labelledby",
|
||
|
|
heading.id ||
|
||
|
|
(heading.id = `heading-${Math.random()
|
||
|
|
.toString(36)
|
||
|
|
.substr(2, 9)}`)
|
||
|
|
);
|
||
|
|
}
|
||
|
|
});
|
||
|
|
},
|
||
|
|
|
||
|
|
// Enhance keyboard navigation
|
||
|
|
enhanceKeyboardNavigation() {
|
||
|
|
// ESC key to close modals/drawers
|
||
|
|
document.addEventListener("keydown", (e) => {
|
||
|
|
if (e.key === "Escape") {
|
||
|
|
// Close cart drawer
|
||
|
|
const cartDrawer = document.querySelector(
|
||
|
|
".cart-drawer.open, .cart-drawer.active"
|
||
|
|
);
|
||
|
|
if (cartDrawer) {
|
||
|
|
const closeBtn = cartDrawer.querySelector(
|
||
|
|
".cart-close, .close-cart"
|
||
|
|
);
|
||
|
|
if (closeBtn) closeBtn.click();
|
||
|
|
document.querySelector(".cart-btn")?.focus();
|
||
|
|
}
|
||
|
|
|
||
|
|
// Close wishlist drawer
|
||
|
|
const wishlistDrawer = document.querySelector(
|
||
|
|
".wishlist-drawer.open, .wishlist-drawer.active"
|
||
|
|
);
|
||
|
|
if (wishlistDrawer) {
|
||
|
|
const closeBtn = wishlistDrawer.querySelector(
|
||
|
|
".wishlist-close, .close-wishlist"
|
||
|
|
);
|
||
|
|
if (closeBtn) closeBtn.click();
|
||
|
|
document.querySelector(".wishlist-btn-nav")?.focus();
|
||
|
|
}
|
||
|
|
|
||
|
|
// Close mobile menu
|
||
|
|
const navMenu = document.querySelector(
|
||
|
|
".nav-menu.open, .nav-menu.active"
|
||
|
|
);
|
||
|
|
if (navMenu) {
|
||
|
|
document.querySelector(".nav-mobile-toggle")?.click();
|
||
|
|
}
|
||
|
|
|
||
|
|
// Close modals
|
||
|
|
const modal = document.querySelector(".modal.show, .modal.open");
|
||
|
|
if (modal) {
|
||
|
|
const closeBtn = modal.querySelector(
|
||
|
|
'.modal-close, .close-modal, [data-dismiss="modal"]'
|
||
|
|
);
|
||
|
|
if (closeBtn) closeBtn.click();
|
||
|
|
}
|
||
|
|
|
||
|
|
// Close user dropdown
|
||
|
|
const userDropdown = document.querySelector(
|
||
|
|
".user-dropdown-menu.show"
|
||
|
|
);
|
||
|
|
if (userDropdown) {
|
||
|
|
userDropdown.classList.remove("show");
|
||
|
|
}
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
// Arrow key navigation for nav menu
|
||
|
|
const navLinks = document.querySelectorAll(".nav-menu .nav-link");
|
||
|
|
navLinks.forEach((link, index) => {
|
||
|
|
link.addEventListener("keydown", (e) => {
|
||
|
|
if (e.key === "ArrowRight" || e.key === "ArrowDown") {
|
||
|
|
e.preventDefault();
|
||
|
|
const next = navLinks[index + 1] || navLinks[0];
|
||
|
|
next.focus();
|
||
|
|
} else if (e.key === "ArrowLeft" || e.key === "ArrowUp") {
|
||
|
|
e.preventDefault();
|
||
|
|
const prev = navLinks[index - 1] || navLinks[navLinks.length - 1];
|
||
|
|
prev.focus();
|
||
|
|
}
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
// Enter/Space for clickable elements
|
||
|
|
document
|
||
|
|
.querySelectorAll('[role="button"], [role="tab"], .product-card')
|
||
|
|
.forEach((el) => {
|
||
|
|
if (!el.getAttribute("tabindex")) {
|
||
|
|
el.setAttribute("tabindex", "0");
|
||
|
|
}
|
||
|
|
|
||
|
|
el.addEventListener("keydown", (e) => {
|
||
|
|
if (e.key === "Enter" || e.key === " ") {
|
||
|
|
e.preventDefault();
|
||
|
|
el.click();
|
||
|
|
}
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
// Focus trap for modals/drawers
|
||
|
|
this.setupFocusTrap(".cart-drawer");
|
||
|
|
this.setupFocusTrap(".wishlist-drawer");
|
||
|
|
this.setupFocusTrap(".modal");
|
||
|
|
},
|
||
|
|
|
||
|
|
// Setup focus trap for modal-like elements
|
||
|
|
setupFocusTrap(selector) {
|
||
|
|
const container = document.querySelector(selector);
|
||
|
|
if (!container) return;
|
||
|
|
|
||
|
|
const observer = new MutationObserver((mutations) => {
|
||
|
|
mutations.forEach((mutation) => {
|
||
|
|
if (mutation.attributeName === "class") {
|
||
|
|
const isOpen =
|
||
|
|
container.classList.contains("open") ||
|
||
|
|
container.classList.contains("active") ||
|
||
|
|
container.classList.contains("show");
|
||
|
|
|
||
|
|
if (isOpen) {
|
||
|
|
this.trapFocus(container);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
observer.observe(container, { attributes: true });
|
||
|
|
},
|
||
|
|
|
||
|
|
// Trap focus within container
|
||
|
|
trapFocus(container) {
|
||
|
|
const focusableElements = container.querySelectorAll(
|
||
|
|
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
|
||
|
|
);
|
||
|
|
|
||
|
|
if (focusableElements.length === 0) return;
|
||
|
|
|
||
|
|
const firstElement = focusableElements[0];
|
||
|
|
const lastElement = focusableElements[focusableElements.length - 1];
|
||
|
|
|
||
|
|
firstElement.focus();
|
||
|
|
|
||
|
|
container.addEventListener("keydown", function trapHandler(e) {
|
||
|
|
if (e.key !== "Tab") return;
|
||
|
|
|
||
|
|
if (e.shiftKey) {
|
||
|
|
if (document.activeElement === firstElement) {
|
||
|
|
e.preventDefault();
|
||
|
|
lastElement.focus();
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
if (document.activeElement === lastElement) {
|
||
|
|
e.preventDefault();
|
||
|
|
firstElement.focus();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
});
|
||
|
|
},
|
||
|
|
|
||
|
|
// Add skip to main content link
|
||
|
|
addSkipLinks() {
|
||
|
|
if (document.querySelector(".skip-link")) return;
|
||
|
|
|
||
|
|
const main = document.querySelector("main, .page-content, #main-content");
|
||
|
|
if (!main) return;
|
||
|
|
|
||
|
|
main.id = main.id || "main-content";
|
||
|
|
|
||
|
|
const skipLink = document.createElement("a");
|
||
|
|
skipLink.href = "#" + main.id;
|
||
|
|
skipLink.className = "skip-link";
|
||
|
|
skipLink.textContent = "Skip to main content";
|
||
|
|
skipLink.setAttribute("tabindex", "0");
|
||
|
|
|
||
|
|
document.body.insertBefore(skipLink, document.body.firstChild);
|
||
|
|
|
||
|
|
// Add CSS for skip link
|
||
|
|
if (!document.getElementById("skip-link-styles")) {
|
||
|
|
const style = document.createElement("style");
|
||
|
|
style.id = "skip-link-styles";
|
||
|
|
style.textContent = `
|
||
|
|
.skip-link {
|
||
|
|
position: absolute;
|
||
|
|
top: -100px;
|
||
|
|
left: 50%;
|
||
|
|
transform: translateX(-50%);
|
||
|
|
background: var(--primary-pink, #f8c8dc);
|
||
|
|
color: var(--text-primary, #333);
|
||
|
|
padding: 12px 24px;
|
||
|
|
border-radius: 0 0 8px 8px;
|
||
|
|
font-weight: 600;
|
||
|
|
text-decoration: none;
|
||
|
|
z-index: 10000;
|
||
|
|
transition: top 0.3s ease;
|
||
|
|
}
|
||
|
|
.skip-link:focus {
|
||
|
|
top: 0;
|
||
|
|
outline: 3px solid var(--accent-pink, #ff69b4);
|
||
|
|
outline-offset: 2px;
|
||
|
|
}
|
||
|
|
.sr-only {
|
||
|
|
position: absolute;
|
||
|
|
width: 1px;
|
||
|
|
height: 1px;
|
||
|
|
padding: 0;
|
||
|
|
margin: -1px;
|
||
|
|
overflow: hidden;
|
||
|
|
clip: rect(0, 0, 0, 0);
|
||
|
|
white-space: nowrap;
|
||
|
|
border: 0;
|
||
|
|
}
|
||
|
|
`;
|
||
|
|
document.head.appendChild(style);
|
||
|
|
}
|
||
|
|
},
|
||
|
|
|
||
|
|
// Enhance focus states for better visibility
|
||
|
|
enhanceFocusStates() {
|
||
|
|
if (document.getElementById("focus-state-styles")) return;
|
||
|
|
|
||
|
|
const style = document.createElement("style");
|
||
|
|
style.id = "focus-state-styles";
|
||
|
|
style.textContent = `
|
||
|
|
/* Enhanced focus states for accessibility */
|
||
|
|
:focus {
|
||
|
|
outline: 2px solid var(--accent-pink, #ff69b4);
|
||
|
|
outline-offset: 2px;
|
||
|
|
}
|
||
|
|
|
||
|
|
:focus:not(:focus-visible) {
|
||
|
|
outline: none;
|
||
|
|
}
|
||
|
|
|
||
|
|
:focus-visible {
|
||
|
|
outline: 3px solid var(--accent-pink, #ff69b4);
|
||
|
|
outline-offset: 2px;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Button focus */
|
||
|
|
.btn:focus-visible,
|
||
|
|
button:focus-visible {
|
||
|
|
outline: 3px solid var(--accent-pink, #ff69b4);
|
||
|
|
outline-offset: 2px;
|
||
|
|
box-shadow: 0 0 0 4px rgba(255, 105, 180, 0.25);
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Link focus */
|
||
|
|
a:focus-visible {
|
||
|
|
outline: 2px solid var(--accent-pink, #ff69b4);
|
||
|
|
outline-offset: 2px;
|
||
|
|
border-radius: 2px;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Input focus */
|
||
|
|
input:focus-visible,
|
||
|
|
select:focus-visible,
|
||
|
|
textarea:focus-visible {
|
||
|
|
outline: 2px solid var(--primary-pink, #f8c8dc);
|
||
|
|
outline-offset: 0;
|
||
|
|
border-color: var(--accent-pink, #ff69b4);
|
||
|
|
box-shadow: 0 0 0 3px rgba(248, 200, 220, 0.3);
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Card focus */
|
||
|
|
.product-card:focus-visible,
|
||
|
|
.blog-card:focus-visible {
|
||
|
|
outline: 3px solid var(--accent-pink, #ff69b4);
|
||
|
|
outline-offset: 4px;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Nav link focus */
|
||
|
|
.nav-link:focus-visible {
|
||
|
|
background: rgba(248, 200, 220, 0.3);
|
||
|
|
border-radius: var(--radius-sm, 4px);
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Icon button focus */
|
||
|
|
.nav-icon-btn:focus-visible {
|
||
|
|
outline: 2px solid var(--accent-pink, #ff69b4);
|
||
|
|
outline-offset: 2px;
|
||
|
|
background: rgba(248, 200, 220, 0.3);
|
||
|
|
}
|
||
|
|
`;
|
||
|
|
document.head.appendChild(style);
|
||
|
|
},
|
||
|
|
|
||
|
|
// Setup live regions for dynamic content announcements
|
||
|
|
announceLiveRegions() {
|
||
|
|
// Create live region for announcements
|
||
|
|
if (!document.getElementById("a11y-live-region")) {
|
||
|
|
const liveRegion = document.createElement("div");
|
||
|
|
liveRegion.id = "a11y-live-region";
|
||
|
|
liveRegion.setAttribute("aria-live", "polite");
|
||
|
|
liveRegion.setAttribute("aria-atomic", "true");
|
||
|
|
liveRegion.className = "sr-only";
|
||
|
|
document.body.appendChild(liveRegion);
|
||
|
|
}
|
||
|
|
|
||
|
|
// Announce cart updates
|
||
|
|
const originalAddToCart = window.ShopState?.addToCart;
|
||
|
|
if (originalAddToCart) {
|
||
|
|
window.ShopState.addToCart = function (...args) {
|
||
|
|
const result = originalAddToCart.apply(this, args);
|
||
|
|
A11y.announce("Item added to cart");
|
||
|
|
return result;
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
// Announce wishlist updates
|
||
|
|
const originalAddToWishlist = window.ShopState?.addToWishlist;
|
||
|
|
if (originalAddToWishlist) {
|
||
|
|
window.ShopState.addToWishlist = function (...args) {
|
||
|
|
const result = originalAddToWishlist.apply(this, args);
|
||
|
|
A11y.announce("Item added to wishlist");
|
||
|
|
return result;
|
||
|
|
};
|
||
|
|
}
|
||
|
|
},
|
||
|
|
|
||
|
|
// Announce message to screen readers
|
||
|
|
announce(message) {
|
||
|
|
const liveRegion = document.getElementById("a11y-live-region");
|
||
|
|
if (liveRegion) {
|
||
|
|
liveRegion.textContent = message;
|
||
|
|
setTimeout(() => {
|
||
|
|
liveRegion.textContent = "";
|
||
|
|
}, 1000);
|
||
|
|
}
|
||
|
|
},
|
||
|
|
|
||
|
|
// Mobile accessibility enhancements
|
||
|
|
fixMobileAccessibility() {
|
||
|
|
// Ensure touch targets are at least 44x44px
|
||
|
|
const style = document.createElement("style");
|
||
|
|
style.id = "mobile-a11y-styles";
|
||
|
|
style.textContent = `
|
||
|
|
@media (max-width: 768px) {
|
||
|
|
/* Minimum touch target size */
|
||
|
|
button,
|
||
|
|
.btn,
|
||
|
|
.nav-icon-btn,
|
||
|
|
.nav-link,
|
||
|
|
input[type="button"],
|
||
|
|
input[type="submit"],
|
||
|
|
a {
|
||
|
|
min-height: 44px;
|
||
|
|
min-width: 44px;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Ensure adequate spacing for touch */
|
||
|
|
.nav-actions {
|
||
|
|
gap: 8px;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Larger tap targets for mobile menu */
|
||
|
|
.nav-menu .nav-link {
|
||
|
|
padding: 16px 20px;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
`;
|
||
|
|
|
||
|
|
if (!document.getElementById("mobile-a11y-styles")) {
|
||
|
|
document.head.appendChild(style);
|
||
|
|
}
|
||
|
|
|
||
|
|
// Update mobile toggle aria-expanded on click
|
||
|
|
const mobileToggle = document.querySelector(".nav-mobile-toggle");
|
||
|
|
if (mobileToggle) {
|
||
|
|
const observer = new MutationObserver(() => {
|
||
|
|
const navMenu = document.querySelector(".nav-menu");
|
||
|
|
const isOpen =
|
||
|
|
navMenu?.classList.contains("open") ||
|
||
|
|
navMenu?.classList.contains("active") ||
|
||
|
|
document.body.classList.contains("nav-open");
|
||
|
|
mobileToggle.setAttribute("aria-expanded", isOpen ? "true" : "false");
|
||
|
|
});
|
||
|
|
|
||
|
|
observer.observe(document.body, {
|
||
|
|
attributes: true,
|
||
|
|
subtree: true,
|
||
|
|
attributeFilter: ["class"],
|
||
|
|
});
|
||
|
|
}
|
||
|
|
},
|
||
|
|
};
|
||
|
|
|
||
|
|
// Initialize on DOM ready
|
||
|
|
if (document.readyState === "loading") {
|
||
|
|
document.addEventListener("DOMContentLoaded", () => A11y.init());
|
||
|
|
} else {
|
||
|
|
A11y.init();
|
||
|
|
}
|
||
|
|
|
||
|
|
// Expose for external use
|
||
|
|
window.A11y = A11y;
|
||
|
|
})();
|