/** * Accessibility Enhancements * WCAG 2.1 AA Compliant */ (function () { "use strict"; const A11y = { init() { this.addSkipLink(); this.enhanceFocusManagement(); this.addARIALabels(); this.improveKeyboardNav(); this.addLiveRegions(); this.enhanceFormAccessibility(); console.log("[A11y] Accessibility enhancements loaded"); }, // Add skip to main content link addSkipLink() { if (document.querySelector(".skip-link")) return; const skipLink = document.createElement("a"); skipLink.href = "#main-content"; skipLink.className = "skip-link"; skipLink.textContent = "Skip to main content"; skipLink.addEventListener("click", (e) => { e.preventDefault(); const main = document.querySelector("#main-content, main"); if (main) { main.setAttribute("tabindex", "-1"); main.focus(); } }); document.body.insertBefore(skipLink, document.body.firstChild); }, // Enhance focus management enhanceFocusManagement() { // Trap focus in modals document.addEventListener("keydown", (e) => { if (e.key !== "Tab") return; const modal = document.querySelector( '.modal.active, .dropdown[style*="display: flex"]' ); if (!modal) return; const focusable = modal.querySelectorAll( 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])' ); if (focusable.length === 0) return; const first = focusable[0]; const last = focusable[focusable.length - 1]; if (e.shiftKey && document.activeElement === first) { e.preventDefault(); last.focus(); } else if (!e.shiftKey && document.activeElement === last) { e.preventDefault(); first.focus(); } }); // Focus visible styles const style = document.createElement("style"); style.textContent = ` *:focus-visible { outline: 3px solid #667eea !important; outline-offset: 2px !important; } button:focus-visible, a:focus-visible, input:focus-visible, select:focus-visible, textarea:focus-visible { outline: 3px solid #667eea !important; outline-offset: 2px !important; } `; document.head.appendChild(style); }, // Add ARIA labels to interactive elements addARIALabels() { // Cart button const cartBtn = document.querySelector("#cart-btn"); if (cartBtn && !cartBtn.hasAttribute("aria-label")) { cartBtn.setAttribute("aria-label", "Shopping cart"); cartBtn.setAttribute("aria-haspopup", "true"); } // Wishlist button const wishlistBtn = document.querySelector("#wishlist-btn"); if (wishlistBtn && !wishlistBtn.hasAttribute("aria-label")) { wishlistBtn.setAttribute("aria-label", "Wishlist"); wishlistBtn.setAttribute("aria-haspopup", "true"); } // Mobile menu toggle const menuToggle = document.querySelector(".mobile-menu-toggle"); if (menuToggle && !menuToggle.hasAttribute("aria-label")) { menuToggle.setAttribute("aria-label", "Open navigation menu"); menuToggle.setAttribute("aria-expanded", "false"); } // Add ARIA labels to product cards document.querySelectorAll(".product-card").forEach((card, index) => { if (!card.hasAttribute("role")) { card.setAttribute("role", "article"); } const title = card.querySelector("h3, .product-title"); if (title && !title.id) { title.id = `product-title-${index}`; card.setAttribute("aria-labelledby", title.id); } }); // Add labels to icon-only buttons document.querySelectorAll("button:not([aria-label])").forEach((btn) => { const icon = btn.querySelector('i[class*="bi-"]'); if (icon && !btn.textContent.trim()) { const iconClass = icon.className; let label = "Button"; if (iconClass.includes("cart")) label = "Add to cart"; else if (iconClass.includes("heart")) label = "Add to wishlist"; else if (iconClass.includes("trash")) label = "Remove"; else if (iconClass.includes("plus")) label = "Increase"; else if (iconClass.includes("minus") || iconClass.includes("dash")) label = "Decrease"; else if (iconClass.includes("close") || iconClass.includes("x")) label = "Close"; btn.setAttribute("aria-label", label); } }); }, // Improve keyboard navigation improveKeyboardNav() { // Dropdown keyboard support document.querySelectorAll("[data-dropdown-toggle]").forEach((toggle) => { toggle.addEventListener("keydown", (e) => { if (e.key === "Enter" || e.key === " ") { e.preventDefault(); toggle.click(); } }); }); // Product card keyboard navigation document.querySelectorAll(".product-card").forEach((card) => { const link = card.querySelector("a"); if (link) { card.addEventListener("keydown", (e) => { if (e.key === "Enter" && e.target === card) { link.click(); } }); } }); // Quantity input keyboard support document.querySelectorAll(".quantity-input").forEach((input) => { input.addEventListener("keydown", (e) => { if (e.key === "ArrowUp") { e.preventDefault(); const newValue = parseInt(input.value || 1) + 1; if (newValue <= 99) { input.value = newValue; input.dispatchEvent(new Event("change")); } } else if (e.key === "ArrowDown") { e.preventDefault(); const newValue = parseInt(input.value || 1) - 1; if (newValue >= 1) { input.value = newValue; input.dispatchEvent(new Event("change")); } } }); }); }, // Add live regions for dynamic content addLiveRegions() { // Create announcement region if (!document.querySelector("#a11y-announcements")) { const announcer = document.createElement("div"); announcer.id = "a11y-announcements"; announcer.setAttribute("role", "status"); announcer.setAttribute("aria-live", "polite"); announcer.setAttribute("aria-atomic", "true"); announcer.className = "sr-only"; document.body.appendChild(announcer); } // Announce cart/wishlist updates window.addEventListener("cart-updated", (e) => { this.announce(`Cart updated. ${e.detail.length} items in cart.`); }); window.addEventListener("wishlist-updated", (e) => { this.announce( `Wishlist updated. ${e.detail.length} items in wishlist.` ); }); }, announce(message) { const announcer = document.querySelector("#a11y-announcements"); if (announcer) { announcer.textContent = ""; setTimeout(() => { announcer.textContent = message; }, 100); } }, // Enhance form accessibility enhanceFormAccessibility() { // Add required indicators document .querySelectorAll( "input[required], select[required], textarea[required]" ) .forEach((field) => { const label = document.querySelector(`label[for="${field.id}"]`); if (label && !label.querySelector(".required-indicator")) { const indicator = document.createElement("span"); indicator.className = "required-indicator"; indicator.textContent = " *"; indicator.setAttribute("aria-label", "required"); label.appendChild(indicator); } }); // Add error message associations document.querySelectorAll(".error-message").forEach((error, index) => { if (!error.id) { error.id = `error-${index}`; } const field = error.previousElementSibling; if ( field && (field.tagName === "INPUT" || field.tagName === "SELECT" || field.tagName === "TEXTAREA") ) { field.setAttribute("aria-describedby", error.id); field.setAttribute("aria-invalid", "true"); } }); // Add autocomplete attributes document.querySelectorAll('input[type="email"]').forEach((field) => { if (!field.hasAttribute("autocomplete")) { field.setAttribute("autocomplete", "email"); } }); document.querySelectorAll('input[type="tel"]').forEach((field) => { if (!field.hasAttribute("autocomplete")) { field.setAttribute("autocomplete", "tel"); } }); }, }; // Initialize on DOM ready if (document.readyState === "loading") { document.addEventListener("DOMContentLoaded", () => A11y.init()); } else { A11y.init(); } // Export for external use window.A11y = A11y; })();