/** * 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; })();