https://www.digife.it/wp-content/plugins/accessibiledigife/accessible-web-widget.min.js?v=1779442758
1/*! 2 * AccessibleWeb Widget v1.1.1 3 * https://github.com/ifrederico/accessible-web-widget 4 * 5 * Copyright (c) 2025 ifrederico 6 * Released under the MIT License 7 * 8 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND. 9 * See DISCLAIMER.md for important legal information. 10 * This widget does not guarantee accessibility compliance. 11 */ 12var AccessibleWebWidget = (function () { 13 'use strict'; 14 15 const WIDGET_THEME = { 16 // Core colors 17 primaryColor: '#1976d2', // Accessible blue (WCAG AA compliant) 18 primaryColorLight: '#42a5f5', // Lighter blue 19 primaryColorDark: '#0d47a1', // Darker blue 20 backgroundColor: '#f5f7fa', 21 textColor: '#222222', 22 textColorInverted: '#ffffff', 23 24 // UI elements 25 buttonSize: '48px', 26 cardBackground: '#ffffff', 27 borderColor: '#d1d5db', 28 focusRingColor: '#1976d2', // Match primary 29 30 // Interactive states 31 hoverColor: '#42a5f5', // Use light blue for hover 32 activeColor: '#0d47a1', // Use dark blue for active 33 34 // Feedback colors 35 successColor: '#2e7d32', // Green 36 errorColor: '#c62828', // Red 37 warningColor: '#f57c00', // Orange 38 39 // Other UI properties 40 headerHeight: '54px', 41 borderRadius: '8px', 42 buttonBorderRadius: '0.4rem', 43 menuPosition: 'right', // or 'left' 44 zIndex: 100000, 45 46 // WCAG-specific 47 focusBorderWidth: '3px', 48 focusOutlineOffset: '2px' 49 }; 50 51 const WIDGET_ICONS = { 52 accessibility: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960"><path d="M423.5-743.5Q400-767 400-800t23.5-56.5Q447-880 480-880t56.5 23.5Q560-833 560-800t-23.5 56.5Q513-720 480-720t-56.5-23.5ZM360-80v-520q-60-5-122-15t-118-25l20-80q78 21 166 30.5t174 9.5q86 0 174-9.5T820-720l20 80q-56 15-118 25t-122 15v520h-80v-240h-80v240h-80Z"/></svg>', 53 accessibilityVariants: { 54 default: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960"><path d="M423.5-743.5Q400-767 400-800t23.5-56.5Q447-880 480-880t56.5 23.5Q560-833 560-800t-23.5 56.5Q513-720 480-720t-56.5-23.5ZM360-80v-520q-60-5-122-15t-118-25l20-80q78 21 166 30.5t174 9.5q86 0 174-9.5T820-720l20 80q-56 15-118 25t-122 15v520h-80v-240h-80v240h-80Z"/></svg>', 55 'icon-2': '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960"><path d="M423.5-743.5Q400-767 400-800t23.5-56.5Q447-880 480-880t56.5 23.5Q560-833 560-800t-23.5 56.5Q513-720 480-720t-56.5-23.5ZM360-80v-520H120v-80h720v80H600v520h-80v-240h-80v240h-80Z"/></svg>', 56 'icon-3': '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960"><path d="M423.5-743.5Q400-767 400-800t23.5-56.5Q447-880 480-880t56.5 23.5Q560-833 560-800t-23.5 56.5Q513-720 480-720t-56.5-23.5ZM680-80v-200H480q-33 0-56.5-23.5T400-360v-240q0-33 23.5-56.5T480-680q24 0 41.5 10.5T559-636q55 66 99.5 90.5T760-520v80q-53 0-107-23t-93-55v138h120q33 0 56.5 23.5T760-300v220h-80Zm-280 0q-83 0-141.5-58.5T200-280q0-72 45.5-127T360-476v82q-35 14-57.5 44.5T280-280q0 50 35 85t85 35q39 0 69.5-22.5T514-240h82q-14 69-69 114.5T400-80Z"/></svg>', 57 'icon-4': '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960"><path d="M320-80q-83 0-141.5-58.5T120-280q0-83 58.5-141.5T320-480v80q-50 0-85 35t-35 85q0 50 35 85t85 35q50 0 85-35t35-85h80q0 83-58.5 141.5T320-80Zm360-40v-200H440q-44 0-68-37.5t-6-78.5l74-164h-91l-24 62-77-22 28-72q9-23 29.5-35.5T350-680h208q45 0 68.5 36.5T632-566l-66 146h114q33 0 56.5 23.5T760-340v220h-80Zm-96.5-603.5Q560-747 560-780t23.5-56.5Q607-860 640-860t56.5 23.5Q720-813 720-780t-23.5 56.5Q673-700 640-700t-56.5-23.5Z"/></svg>' 58 }, 59 largePointer: '<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px"><path d="m320-410 79-110h170L320-716v306ZM551-80 406-392 240-160v-720l560 440H516l144 309-109 51ZM399-520Z"/></svg>', 60 pauseMotion: '<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#1f1f1f"><path d="m791-55-91-91q-49 32-104.5 49T480-80q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 17-115.5T146-700l-91-91 57-57 736 736-57 57ZM480-160q43 0 83.5-11t78.5-33L204-642q-22 38-33 78.5T160-480q0 133 93.5 226.5T480-160Zm334-100-58-58q22-38 33-78.5t11-83.5q0-133-93.5-226.5T480-800q-43 0-83.5 11T318-756l-58-58q49-32 104.5-49T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 60-17 115.5T814-260ZM537-537ZM423-423Z"/></svg>', 61 readingAid: '<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#1f1f1f"><path d="M320-80v-440l-80-120v-240h480v240l-80 120v440H320Zm160-260q-25 0-42.5-17.5T420-400q0-25 17.5-42.5T480-460q25 0 42.5 17.5T540-400q0 25-17.5 42.5T480-340ZM320-760h320v-40H320v40Zm320 80H320v16l80 120v384h160v-384l80-120v-16ZM480-480Z"/></svg>', 62 boldText: '<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#1f1f1f"><path d="M272-200v-560h221q65 0 120 40t55 111q0 51-23 78.5T602-491q25 11 55.5 41t30.5 90q0 89-65 124.5T501-200H272Zm121-112h104q48 0 58.5-24.5T566-372q0-11-10.5-35.5T494-432H393v120Zm0-228h93q33 0 48-17t15-38q0-24-1
627-39t-44-15h-95v109Z"/></svg>', 63 lineSpacing: '<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#1f1f1f"><path d="M240-160 80-320l56-56 64 62v-332l-64 62-56-56 160-160 160 160-56 56-64-62v332l64-62 56 56-160 160Zm240-40v-80h400v80H480Zm0-240v-80h400v80H480Zm0-240v-80h400v80H480Z"/></svg>', 64 letterSpacing: '<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#1f1f1f"><path d="M320-80 160-240l160-160 57 56-64 64h334l-63-64 56-56 160 160L640-80l-57-56 64-64H313l63 64-56 56ZM200-480v-400h80v400h-80Zm240 0v-400h80v400h-80Zm240 0v-400h80v400h-80Z"/></svg>', 65 dyslexiaFont: '<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#1f1f1f"><path d="m131-252 165-440h79l165 440h-76l-39-112H247l-40 112h-76Zm139-176h131l-64-182h-4l-63 182Zm395 186q-51 0-81-27.5T554-342q0-44 34.5-72.5T677-443q23 0 45 4t38 11v-12q0-29-20.5-47T685-505q-23 0-42 9.5T610-468l-47-35q24-29 54.5-43t68.5-14q69 0 103 32.5t34 97.5v178h-63v-37h-4q-14 23-38 35t-53 12Zm12-54q35 0 59.5-24t24.5-56q-14-8-33.5-12.5T689-393q-32 0-50 14t-18 37q0 20 16 33t40 13Z"/></svg>', 66 highlightLinks: '<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#1f1f1f"><path d="M440-280H280q-83 0-141.5-58.5T80-480q0-83 58.5-141.5T280-680h160v80H280q-50 0-85 35t-35 85q0 50 35 85t85 35h160v80ZM320-440v-80h320v80H320Zm200 160v-80h160q50 0 85-35t35-85q0-50-35-85t-85-35H520v-80h160q83 0 141.5 58.5T880-480q0 83-58.5 141.5T680-280H520Z"/></svg>', 67 highlightTitle: '<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#1f1f1f"><path d="M320-280q17 0 28.5-11.5T360-320q0-17-11.5-28.5T320-360q-17 0-28.5 11.5T280-320q0 17 11.5 28.5T320-280Zm0-160q17 0 28.5-11.5T360-480q0-17-11.5-28.5T320-520q-17 0-28.5 11.5T280-480q0 17 11.5 28.5T320-440Zm0-160q17 0 28.5-11.5T360-640q0-17-11.5-28.5T320-680q-17 0-28.5 11.5T280-640q0 17 11.5 28.5T320-600Zm120 320h240v-80H440v80Zm0-160h240v-80H440v80Zm0-160h240v-80H440v80ZM200-120q-33 0-56.5-23.5T120-200v-560q0-33 23.5-56.5T200-840h560q33 0 56.5 23.5T840-760v560q0 33-23.5 56.5T760-120H200Zm0-80h560v-560H200v560Zm0-560v560-560Z"/></svg>', 68 contrast: '<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#1f1f1f"><path d="m480-260 60-60h100v-100l60-60-60-60v-100H540l-60-60-60 60H320v100l-60 60 60 60v100h100l60 60Zm0-100v-240q50 0 85 35t35 85q0 50-35 85t-85 35ZM160-160q-33 0-56.5-23.5T80-240v-480q0-33 23.5-56.5T160-800h640q33 0 56.5 23.5T880-720v480q0 33-23.5 56.5T800-160H160Zm0-80h64
680v-480H160v480Zm0 0v-480 480Z"/></svg>', 69 darkContrast: '<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#1f1f1f"><path d="M484-80q-84 0-157.5-32t-128-86.5Q144-253 112-326.5T80-484q0-146 93-257.5T410-880q-18 99 11 193.5T521-521q71 71 165.5 100T880-410q-26 144-138 237T484-80Zm0-80q88 0 163-44t118-121q-86-8-163-43.5T464-465q-61-61-97-138t-43-163q-77 43-120.5 118.5T160-484q0 135 94.5 229.5T484-160Zm-20-305Z"/></svg>', 70 lightContrast: '<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#1f1f1f"><path d="M440-800v-120h80v120h-80Zm0 760v-120h80v120h-80Zm360-400v-80h120v80H800Zm-760 0v-80h120v80H40Zm708-252-56-56 70-72 58 58-72 70ZM198-140l-58-58 72-70 56 56-70 72Zm564 0-70-72 56-56 72 70-58 58ZM212-692l-72-70 58-58 70 72-56 56Zm268 452q-100 0-170-70t-70-170q0-100 70-170t170-70q100 0 170 70t70 170q0 100-70 170t-170 70Zm0-80q67 0 113.5-46.5T640-480q0-67-46.5-113.5T480-640q-67 0-113.5 46.5T320-480q0 67 46.5 113.5T480-320Zm0-160Z"/></svg>', 71 invertColors: '<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#1f1f1f"><path d="M480-80q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 83-31.5 156T763-197q-54 54-127 85.5T480-80Zm0-80q134 0 227-93t93-227q0-64-24.5-122.5T706-706L254-254q45 45 103.5 69.5T480-160Zm0-160v-60h200v60H480ZM320-500h60v-80h80v-60h-80v-80h-60v80h-80v60h80v80Z"/></svg>', 72 saturation: '<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#1f1f1f"><path d="M480-120q-133 0-226.5-92.5T160-436q0-66 25-122t69-100l226-222 226 222q44 44 69 100t25 122q0 131-93.5 223.5T480-120Zm0-80v-568L310-600q-35 33-52.5 74.5T240-436q0 97 70 166.5T480-200Z"/></svg>', 73 lowSaturation: '<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#1f1f1f"><path d="M480-120q-133 0-226.5-92T160-436q0-65 25-121.5T254-658l226-222 226 222q44 44 69 100.5T800-436q0 132-93.5 224T480-120ZM242-400h474q12-72-13.5-123T650-600L480-768 310-600q-27 26-53 77t-15 123Z"/></svg>', 74 highSaturation: '<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#1f1f1f"><path d="M480-80q-82 0-155-31.5t-127.5-86Q143-252 111.5-325T80-480q0-83 32.5-156t88-127Q256-817 330-848.5T488-880q80 0 151 27.5t124.5 76q53.5 48.5 85 115T880-518q0 115-70 176.5T640-280h-74q-9 0-12.5 5t-3.5 11q0 12 15 34.5t15 51.5q0 50-27.5 74T480-80Zm0-400Zm-220 40q26 0 43-17t17-43q0-26-17-43t-43-17q-26 0-43 17t-17 43q0 26 17 43t43 17Zm120-160q26 0 43-17t17-43q0-26-17-43t-43-17q-26 0-43 17t-17 43q0 26 17 43t43 17Zm200 0q26 0 43-17t17-43q0-26-17-43t-43-17q-26 0-43 17t-17 43q0 26 17 43t43 17Zm120 160q26 0 43-17t17-43q0-26-17-43t-43-17q-26 0-43 17t-17 43q0 26 17 43t43 17ZM480-160q9 0 14.5-5t5.5-13q0-14-15-33t-15-57q0-42 29-67t71-25h70q66 0 113-38.5T800-518q0-121-92.5-201.5T488-800q-136 0-232 93t-96 227q0 133 93.5 226.5T480-160Z"/></svg>', 75 reset: '<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#1f1f1f"><path d="M440-122q-121-15-200.5-105.5T160-440q0-66 26-126.5T260-672l57 57q-38 34-57.5 79T240-440q0 88 56 155.5T440-202v80Zm80 0v-80q87-16 143.5-83T720-440q0-100-70-170t-170-70h-3l44 44-56 56-140-140 140-140 56 56-44 44h3q134 0 227 93t93 227q0 121-79.5 211.5T520-122Z"/></svg>', 76 close: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41Z"/></svg>', 77 increase: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2Z"/></svg>', 78 decrease: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 13H5v-2h14v2Z"/></svg>', 79 arrowBack: '<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="currentColor"><path d="M400-80 0-480l400-400 71 71-329 329 329 329-71 71Z"/></svg>', 80 adjustFontSize: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
80<path d="M2 4v3h5v12h3V7h5V4H2m19 5h-9v3h3v7h3v-7h3V9Z"/></svg>', 81 language: '<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="currentColor"><path d="M480-80q-82 0-155-31.5t-127.5-86Q143-252 111.5-325T80-480q0-83 31.5-155.5t86-127Q252-817 325-848.5T480-880q83 0 155.5 31.5t127 86q54.5 54.5 86 127T880-480q0 82-31.5 155t-86 127.5q-54.5 54.5-127 86T480-80Zm0-82q26-36 45-75t31-83H404q12 44 31 83t45 75Zm-104-16q-18-33-31.5-68.5T322-320H204q29 50 72.5 87t99.5 55Zm208 0q56-18 99.5-55t72.5-87H638q-9 38-22.5 73.5T584-178ZM170-400h136q-3-20-4.5-39.5T300-480q0-21 1.5-40.5T306-560H170q-5 20-7.5 39.5T160-480q0 21 2.5 40.5T170-400Zm216 0h188q3-20 4.5-39.5T580-480q0-21-1.5-40.5T574-560H386q-3 20-4.5 39.5T380-480q0 21 1.5 40.5T386-400Zm268 0h136q5-20 7.5-39.5T800-480q0-21-2.5-40.5T790-560H654q3 20 4.5 39.5T660-480q0 21-1.5 40.5T654-400Zm-16-240h118q-29-50-72.5-87T584-782q18 33 31.5 68.5T638-640Zm-234 0h152q-12-44-31-83t-45-75q-26 36-45 75t-31 83Zm-200 0h118q9-38 22.5-73.5T376-782q-56 18-99.5 55T204-640Z"/></svg>', 82 hideImages: '<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#1f1f1f"><path d="m840-234-80-80v-446H314l-80-80h526q33 0 56.5 23.5T840-760v526ZM792-56l-64-64H200q-33 0-56.5-23.5T120-200v-528l-64-64 56-56 736 736-56 56ZM240-280l120-160 90 120 33-44-283-283v447h447l-80-80H240Zm297-257ZM424-424Z"/></svg>', 83 accessibilityReport: '<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#1f1f1f"><path d="M200-120q-33 0-56.5-23.5T120-200v-560q0-33 23.5-56.5T200-840h168q13-36 43.5-58t68.5-22q38 0 68.5 22t43.5 58h168q33 0 56.5 23.5T840-760v560q0 33-23.5 56.5T760-120H200Zm0-80h560v-560H200v560Zm80-80h280v-80H280v80Zm0-160h400v-80H280v80Zm0-160h400v-80H280v80Zm221.5-198.5Q510-807 510-820t-8.5-21.5Q493-850 480-850t-21.5 8.5Q450-833 450-820t8.5 21.5Q467-790 480-790t21.5-8.5ZM200-200v-560 560Z"/></svg>', 84 annotations: '<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#1f1f1f"><path d="M200-120q-33 0-56.5-23.5T120-200v-560q0-33 23.5-56.5T200-840h560q33 0 56.5 23.5T840-760v268q-19-9-39-15.5t-41-9.5v-243H200v560h242q3 22 9.5 42t15.5 38H200Zm0-120v40-560 243-3 280Zm80-40h163q3-21 9.5-41t14.5-39H280v80Zm0-160h244q32-30 71.5-50t84.5-27v-3H280v80Zm0-160h400v-80H280v80ZM720-40q-83 0-141.5-58.5T520-240q0-83 58.5-141.5T720-440q83 0 141.5 58.5T920-240q0 83-58.5 141.5T720-40Zm-20-80h40v-100h100v-40H740v-100h-40v100H600v40h100v100Z"/></svg>', 85 textToSpeech: '<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#1f1f1f"><path d="M560-131v-82q90-26 145-100t55-168q0-94-55-168T560-749v-82q124 28 202 125.5T840-481q0 127-78 224.5T560-131ZM120-360v-240h160l200-200v640L280-360H120Zm440 40v-322q47 22 73.5 66t26.5 96q0 51-26.5 94.5T560-320ZM400-606l-86 86H200v80h114l86 86v-252ZM300-480Z"/></svg>', 86 highContrast: '<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#1f1f1f"><path d="M480-80q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 83-31.5 156T763-197q-54 54-127 85.5T480-80Zm0-80q134 0 227-93t93-227q0-134-93-227t-227-93v640Z"/></svg>', 87 simplifyLayout: '<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#1f1f1f"><path d="M760-360v-80H200v80h560Zm0-160v-80H200v80h560Zm0-160v-80H200v80h560ZM200-120q-33 0-56.5-23.5T120-200v-560q0-33 23.5-56.5T200-840h560q33 0 56.5 23.5T840-760v560q0 33-23.5 56.5T760-120H200Zm560-80v-80H200v80h560Z"/></svg>' 88 }; 89 90 const TARGET_SELECTORS = { 91 ALL: ['', '*:not(.material-icons,.acc-menu,.acc-menu *)'], 92 LINKS: ["a[href]"], 93 HEADERS: ['h1', 'h2', 'h3', 'h4', 'h5', 'h6', '.wsite-headline', '.wsite-content-title'], 94 TEXT: ['h1', 'h2', 'h3', 'h4', 'h5', 'h6', '.wsite-headline', '.wsite-content-title', 'img', 'p', 'i', 'svg', 'a', 'button:not(.acc-btn)', 'label', 'li', 'ol'] 95 }; 96 97 const PAGE_CONTENT_SELECTOR = 'body > *:not(.acc-container):not(.acc-rg-container):not(#acc-skip-link)'; 98 99 const TRANSLATIONS = { 100 en: { 101 "Accessibility Menu": "Accessibility Menu", 102 "Reset settings": "Reset settings", 103 "Reset All Settings": "Reset All Settings",
104 "Close": "Close", 105 "Content Adjustments": "Content Adjustments", 106 "Adjust Font Size": "Adjust Font Size", 107 "Highlight Title": "Highlight Title", 108 "Highlight Links": "Highlight Links", 109 "Readable Font": "Readable Font", 110 "Color Adjustments": "Color Adjustments", 111 "Invert Colors": "Invert Colors", 112 "Light Contrast": "Light Contrast", 113 "Dark Contrast": "Dark Contrast", 114 "High Contrast": "High Contrast", 115 "High Saturation": "High Saturation", 116 "Low Saturation": "Low Saturation", 117 "Monochrome": "Monochrome", 118 "Tools": "Tools", 119 "Reading Guide": "Reading Guide", 120 "Stop Animations": "Stop Animations", 121 "Big Cursor": "Big Cursor", 122 "Increase Font Size": "Increase Font Size", 123 "Decrease Font Size": "Decrease Font Size", 124 "Letter Spacing": "Letter Spacing", 125 "Line Height": "Line Height", 126 "Font Weight": "Font Weight", 127 "Dyslexia Font": "Dyslexia Font", 128 "Language": "Language", 129 "Open Accessibility Menu": "Open Accessibility Menu", 130 "Hide Images": "Hide Images", 131 "Skip to accessibility menu": "Skip to accessibility menu", 132 "Accessibility Report": "Accessibility Report", 133 "Run Accessibility Check": "Run Accessibility Check", 134 "Loading...": "Loading...", 135 "Analyzing page...": "Analyzing page...", 136 "Critical": "Critical", 137 "Serious": "Serious", 138 "Moderate": "Moderate", 139 "Minor": "Minor", 140 "Violations Found": "Violations Found", 141 "No Issues Found": "No Issues Found", 142 "Element": "Element", 143 "Issue": "Issue", 144 "How to Fix": "How to Fix", 145 "Close Report": "Close Report", 146 "Passed Tests": "Passed Tests", 147 "Items Need Review": "Items Need Review", 148 "Annotations": "Annotations", 149 "Text to Speech": "Text to Speech", 150 "Text to Speech On": "Text to Speech On", 151 "Text to Speech Off": "Text to Speech Off", 152 "Simplify Layout": "Simplify Layout", 153 "Play": "Play", 154 "Pause": "Pause", 155 "Stop": "Stop", 156 "Loading voice...": "Loading voice...", 157 "Reading...": "Reading...", 158 }, 159 it: { 160 "Accessibility Menu": "Menu Accessibilità ", 161 "Reset settings": "Ripristina impostazioni", 162 "Reset All Settings": "Ripristina tutte le impostazioni", 163 "Close": "Chiudi", 164 "Content Adjustments": "Regolazioni Contenuto", 165 "Adjust Font Size": "Regola Dimensione Font", 166 "Highlight Title": "Evidenzia Titoli", 167 "Highlight Links": "Evidenzia Link", 168 "Readable Font": "Font Leggibile", 169 "Color Adjustments": "Regolazioni Colore", 170 "Invert Colors": "Inverti Colori", 171 "Light Contrast": "Contrasto Chiaro", 172 "Dark Contrast": "Contrasto Scuro", 173 "High Contrast": "Alto Contrasto", 174 "High Saturation": "Alta Saturazione", 175 "Low Saturation": "Bassa Saturazione", 176 "Monochrome": "Monocromatico", 177 "Tools": "Strumenti", 178 "Reading Guide": "Guida di Lettura", 179 "Stop Animations": "Ferma Animazioni", 180 "Big Cursor": "Cursore Grande", 181 "Increase Font Size": "Aumenta Dimensione Font", 182 "Decrease Font Size": "Diminuisci Dimensione Font", 183 "Letter Spacing": "Spaziatura Lettere", 184 "Line Height": "Altezza Riga", 185 "Font Weight": "Grassetto", 186 "Dyslexia Font": "Font Dislessia", 187 "Language": "Lingua", 188 "Open Accessibility Menu": "Apri Menu Accessibilità ", 189 "Hide Images": "Nascondi Immagini", 190 "Skip to accessibility menu": "Vai al menu accessibilità ",
191 "Accessibility Report": "Report Accessibilità ", 192 "Run Accessibility Check": "Esegui Controllo Accessibilità ", 193 "Loading...": "Caricamento...", 194 "Analyzing page...": "Analisi pagina...", 195 "Critical": "Critico", 196 "Serious": "Grave", 197 "Moderate": "Moderato", 198 "Minor": "Minore", 199 "Violations Found": "Violazioni Trovate", 200 "No Issues Found": "Nessun Problema Trovato", 201 "Element": "Elemento", 202 "Issue": "Problema", 203 "How to Fix": "Come Risolvere", 204 "Close Report": "Chiudi Report", 205 "Passed Tests": "Test Superati", 206 "Items Need Review": "Elementi da Rivedere", 207 "Annotations": "Annotations", 208 "Text to Speech": "Text to Speech", 209 "Text to Speech On": "Sintesi Vocale Attiva", 210 "Text to Speech Off": "Sintesi Vocale Disattiva", 211 "Simplify Layout": "Simplify Layout", 212 "Play": "Play", 213 "Pause": "Pause", 214 "Stop": "Stop", 215 "Reading...": "Reading...", 216 }, 217 fr: { 218 "Accessibility Menu": "Menu Accessibilité", 219 "Reset settings": "Réinitialiser les paramètres", 220 "Reset All Settings": "Réinitialiser tous les paramètres", 221 "Close": "Fermer", 222 "Content Adjustments": "Ajustements du Contenu", 223 "Adjust Font Size": "Ajuster la Taille de Police", 224 "Highlight Title": "Surligner les Titres", 225 "Highlight Links": "Surligner les Liens", 226 "Readable Font": "Police Lisible", 227 "Color Adjustments": "Ajustements des Couleurs", 228 "Invert Colors": "Inverser les Couleurs", 229 "Light Contrast": "Contraste Clair", 230 "Dark Contrast": "Contraste Sombre", 231 "High Contrast": "Contraste Ãlevé", 232 "High Saturation": "Saturation Ãlevée", 233 "Low Saturation": "Saturation Faible", 234 "Monochrome": "Monochrome", 235 "Tools": "Outils", 236 "Reading Guide": "Guide de Lecture", 237 "Stop Animations": "Arrêter les Animations", 238 "Big Cursor": "Grand Curseur", 239 "Increase Font Size": "Augmenter la Taille de Police", 240 "Decrease Font Size": "Diminuer la Taille de Police", 241 "Letter Spacing": "Espacement des Lettres", 242 "Line Height": "Hauteur de Ligne", 243 "Font Weight": "Graisse de Police", 244 "Dyslexia Font": "Police Dyslexie", 245 "Language": "Langue", 246 "Open Accessibility Menu": "Ouvrir le Menu Accessibilité", 247 "Hide Images": "Masquer les Images", 248 "Skip to accessibility menu": "Aller au menu accessibilité", 249 "Accessibility Report": "Rapport d'Accessibilité", 250 "Run Accessibility Check": "Lancer le Contrôle d'Accessibilité", 251 "Loading...": "Chargement...", 252 "Analyzing page...": "Analyse de la page...", 253 "Critical": "Critique", 254 "Serious": "Grave", 255 "Moderate": "Modéré", 256 "Minor": "Mineur", 257 "Violations Found": "Violations Trouvées", 258 "No Issues Found": "Aucun Problème Trouvé", 259 "Element": "Ãlément", 260 "Issue": "Problème", 261 "How to Fix": "Comment Corriger", 262 "Close Report": "Fermer le Rapport", 263 "Passed Tests": "Tests Réussis", 264 "Items Need Review": "Ãléments à Vérifier", 265 "Annotations": "Annotations", 266 "Text to Speech": "Text to Speech", 267 "Text to Speech On": "Synthèse Vocale Activée", 268 "Text to Speech Off": "Synthèse Vocale Désactivée", 269 "Simplify Layout": "Simplify Layout", 270 "Play": "Play", 271 "Pause": "Pause", 272 "Stop": "Stop", 273 "Reading...": "Reading...", 274 }, 275 de: { 276 "Accessibility Menu": "Barrierefreiheit-Menü", 277 "Reset settings": "Einstellungen zurücksetzen", 278 "Reset All Settings": "Alle Einstellungen zurücksetzen",
279 "Close": "SchlieÃen", 280 "Content Adjustments": "Inhaltsanpassungen", 281 "Adjust Font Size": "SchriftgröÃe anpassen", 282 "Highlight Title": "Titel hervorheben", 283 "Highlight Links": "Links hervorheben", 284 "Readable Font": "Lesbare Schrift", 285 "Color Adjustments": "Farbanpassungen", 286 "Invert Colors": "Farben invertieren", 287 "Light Contrast": "Heller Kontrast", 288 "Dark Contrast": "Dunkler Kontrast", 289 "High Contrast": "Hoher Kontrast", 290 "High Saturation": "Hohe Sättigung", 291 "Low Saturation": "Niedrige Sättigung", 292 "Monochrome": "Monochrom", 293 "Tools": "Werkzeuge", 294 "Reading Guide": "Lesehilfe", 295 "Stop Animations": "Animationen stoppen", 296 "Big Cursor": "GroÃer Cursor", 297 "Increase Font Size": "SchriftgröÃe erhöhen", 298 "Decrease Font Size": "SchriftgröÃe verringern", 299 "Letter Spacing": "Zeichenabstand", 300 "Line Height": "Zeilenhöhe", 301 "Font Weight": "Schriftstärke", 302 "Dyslexia Font": "Legasthenie-Schrift", 303 "Language": "Sprache", 304 "Open Accessibility Menu": "Barrierefreiheit-Menü öffnen", 305 "Hide Images": "Bilder ausblenden", 306 "Skip to accessibility menu": "Zum Barrierefreiheit-Menü springen", 307 "Accessibility Report": "Barrierefreiheit-Bericht", 308 "Run Accessibility Check": "Barrierefreiheitsprüfung starten", 309 "Loading...": "Laden...", 310 "Analyzing page...": "Seite wird analysiert...", 311 "Critical": "Kritisch", 312 "Serious": "Schwerwiegend", 313 "Moderate": "MäÃig", 314 "Minor": "Gering", 315 "Violations Found": "VerstöÃe gefunden", 316 "No Issues Found": "Keine Probleme gefunden", 317 "Element": "Element", 318 "Issue": "Problem", 319 "How to Fix": "Lösung", 320 "Close Report": "Bericht schlieÃen", 321 "Passed Tests": "Bestandene Tests", 322 "Items Need Review": "Elemente zur Ãberprüfung", 323 "Annotations": "Annotations", 324 "Text to Speech": "Text to Speech", 325 "Text to Speech On": "Sprachausgabe Ein", 326 "Text to Speech Off": "Sprachausgabe Aus", 327 "Simplify Layout": "Simplify Layout", 328 "Play": "Play", 329 "Pause": "Pause", 330 "Stop": "Stop", 331 "Reading...": "Reading...", 332 }, 333 es: { 334 "Accessibility Menu": "Menú de Accesibilidad", 335 "Reset settings": "Restablecer configuración", 336 "Reset All Settings": "Restablecer toda la configuración", 337 "Close": "Cerrar", 338 "Content Adjustments": "Ajustes de Contenido", 339 "Adjust Font Size": "Ajustar Tamaño de Fuente", 340 "Highlight Title": "Resaltar TÃtulos", 341 "Highlight Links": "Resaltar Enlaces", 342 "Readable Font": "Fuente Legible", 343 "Color Adjustments": "Ajustes de Color", 344 "Invert Colors": "Invertir Colores", 345 "Light Contrast": "Contraste Claro", 346 "Dark Contrast": "Contraste Oscuro", 347 "High Contrast": "Alto Contraste", 348 "High Saturation": "Alta Saturación", 349 "Low Saturation": "Baja Saturación", 350 "Monochrome": "Monocromo", 351 "Tools": "Herramientas", 352 "Reading Guide": "GuÃa de Lectura", 353 "Stop Animations": "Detener Animaciones", 354 "Big Cursor": "Cursor Grande", 355 "Increase Font Size": "Aumentar Tamaño de Fuente", 356 "Decrease Font Size": "Disminuir Tamaño de Fuente", 357 "Letter Spacing": "Espaciado de Letras", 358 "Line Height": "Altura de LÃnea", 359 "Font Weight": "Grosor de Fuente", 360 "Dyslexia Font": "Fuente para Dislexia", 361 "Language": "Idioma", 362 "Open Accessibility Menu": "Abrir Menú de Accesibilidad", 363 "Hide Images": "Ocultar Imágenes", 364 "Skip to accessibility menu": "Ir al menú de accesibilidad", 365 "Accessibility Report": "Informe de Accesibilidad", 366 "Run Accessibility Check": "Ejecutar Comprobación de Accesibilidad", 367 "Loading...": "Cargando...", 368 "Analyzing page...": "Analizando página...", 369 "Critical": "CrÃtico", 370 "Serious": "Grave", 371 "Moderate": "Moderado", 372 "Minor": "Menor", 373 "Violations Found": "Violaciones Encontradas", 374 "No Issues Found": "Sin Problemas Encontrados", 375 "Element": "Elemento", 376 "Issue": "Problema", 377 "How to Fix": "Cómo Solucionarlo", 378 "Close Report": "Cerrar Informe", 379 "Passed Tests": "Pruebas Superadas", 380 "Items Need Review": "Elementos a Revisar", 381 "Annotations": "Annotations", 382 "Text to Speech": "Text to Speech", 383 "Text to Speech On": "Texto a Voz Activado", 384 "Text to Speech Off": "Texto a Voz Desactivado", 385 "Simplify Layout": "Simplify Layout", 386 "Play": "Play", 387 "Pause": "Pause", 388 "Stop": "Stop", 389 "Reading...": "Reading...", 390 }, 391 ru: { 392 "Accessibility Menu": "ÐÐµÐ½Ñ Ð´Ð¾ÑÑÑпноÑÑи", 393 "Reset settings": "СбÑоÑиÑÑ Ð½Ð°ÑÑÑойки", 394 "Reset All Settings": "СбÑоÑиÑÑ Ð²Ñе наÑÑÑойки",
395 "Close": "ÐакÑÑÑÑ", 396 "Content Adjustments": "ÐаÑÑÑойки конÑенÑа", 397 "Adjust Font Size": "ÐзмениÑÑ ÑÐ°Ð·Ð¼ÐµÑ ÑÑиÑÑа", 398 "Highlight Title": "ÐÑделиÑÑ Ð·Ð°Ð³Ð¾Ð»Ð¾Ð²ÐºÐ¸", 399 "Highlight Links": "ÐÑделиÑÑ ÑÑÑлки", 400 "Readable Font": "ЧиÑаемÑй ÑÑиÑÑ", 401 "Color Adjustments": "ÐаÑÑÑойки ÑвеÑа", 402 "Invert Colors": "ÐнвеÑÑиÑоваÑÑ ÑвеÑа", 403 "Light Contrast": "СвеÑлÑй конÑÑаÑÑ", 404 "Dark Contrast": "ТÑмнÑй конÑÑаÑÑ", 405 "High Contrast": "ÐÑÑокий конÑÑаÑÑ", 406 "High Saturation": "ÐÑÑÐ¾ÐºÐ°Ñ Ð½Ð°ÑÑÑенноÑÑÑ", 407 "Low Saturation": "ÐÐ¸Ð·ÐºÐ°Ñ Ð½Ð°ÑÑÑенноÑÑÑ", 408 "Monochrome": "ÐÐ¾Ð½Ð¾Ñ ÑомнÑй", 409 "Tools": "ÐнÑÑÑÑменÑÑ", 410 "Reading Guide": "Ðинейка Ð´Ð»Ñ ÑÑениÑ", 411 "Stop Animations": "ÐÑÑановиÑÑ Ð°Ð½Ð¸Ð¼Ð°Ñии", 412 "Big Cursor": "ÐолÑÑой кÑÑÑоÑ", 413 "Increase Font Size": "УвелиÑиÑÑ ÑÐ°Ð·Ð¼ÐµÑ ÑÑиÑÑа", 414 "Decrease Font Size": "УменÑÑиÑÑ ÑÐ°Ð·Ð¼ÐµÑ ÑÑиÑÑа", 415 "Letter Spacing": "ÐежбÑквеннÑй инÑеÑвал", 416 "Line Height": "ÐÑÑоÑа ÑÑÑоки", 417 "Font Weight": "ÐиÑнÑй ÑÑиÑÑ", 418 "Dyslexia Font": "ШÑиÑÑ Ð´Ð»Ñ Ð´Ð¸ÑлекÑии", 419 "Language": "ЯзÑк", 420 "Open Accessibility Menu": "ÐÑкÑÑÑÑ Ð¼ÐµÐ½Ñ Ð´Ð¾ÑÑÑпноÑÑи", 421 "Hide Images": "СкÑÑÑÑ Ð¸Ð·Ð¾Ð±ÑажениÑ", 422 "Skip to accessibility menu": "ÐеÑейÑи к Ð¼ÐµÐ½Ñ Ð´Ð¾ÑÑÑпноÑÑи", 423 "Accessibility Report": "ÐÑÑÑÑ Ð¾ доÑÑÑпноÑÑи", 424 "Run Accessibility Check": "ÐапÑÑÑиÑÑ Ð¿ÑовеÑÐºÑ Ð´Ð¾ÑÑÑпноÑÑи", 425 "Loading...": "ÐагÑÑзка...", 426 "Analyzing page...": "Ðнализ ÑÑÑаниÑÑ...", 427 "Critical": "ÐÑиÑиÑеÑкий", 428 "Serious": "СеÑÑÑзнÑй", 429 "Moderate": "УмеÑеннÑй", 430 "Minor": "ÐезнаÑиÑелÑнÑй", 431 "Violations Found": "ÐÐ°Ð¹Ð´ÐµÐ½Ñ Ð½Ð°ÑÑÑениÑ", 432 "No Issues Found": "ÐÑоблем не найдено", 433 "Element": "ÐлеменÑ", 434 "Issue": "ÐÑоблема", 435 "How to Fix": "Ðак иÑпÑавиÑÑ", 436 "Close Report": "ÐакÑÑÑÑ Ð¾ÑÑÑÑ", 437 "Passed Tests": "ÐÑойденнÑе ÑеÑÑÑ", 438 "Items Need Review": "ТÑебÑÑÑ Ð¿ÑовеÑки", 439 "Annotations": "Annotations", 440 "Text to Speech": "Text to Speech", 441 "Text to Speech On": "ÐзвÑÑивание ÐклÑÑено", 442 "Text to Speech Off": "ÐзвÑÑивание ÐÑклÑÑено", 443 "Simplify Layout": "Simplify Layout", 444 "Play": "Play", 445 "Pause": "Pause", 446 "Stop": "Stop", 447 "Reading...": "Reading...", 448 }, 449 pl: { 450 "Accessibility Menu": "Menu DostÄpnoÅci", 451 "Reset settings": "Zresetuj ustawienia", 452 "Reset All Settings": "Zresetuj wszystkie ustawienia", 453 "Close": "Zamknij", 454 "Content Adjustments": "Dostosowanie TreÅci", 455 "Adjust Font Size": "Dostosuj Rozmiar Czcionki", 456 "Highlight Title": "PodÅwietl TytuÅy", 457 "Highlight Links": "PodÅwietl Linki", 458 "Readable Font": "Czytelna Czcionka", 459 "Color Adjustments": "Dostosowanie Kolorów", 460 "Invert Colors": "OdwrÃ³Ä Kolory", 461 "Light Contrast": "Jasny Kontrast", 462 "Dark Contrast": "Ciemny Kontrast", 463 "High Contrast": "Wysoki Kontrast", 464 "High Saturation": "Wysoka Saturacja", 465 "Low Saturation": "Niska Saturacja", 466 "Monochrome": "Monochromatyczny", 467 "Tools": "NarzÄdzia", 468 "Reading Guide": "Linia do Czytania", 469 "Stop Animations": "Zatrzymaj Animacje", 470 "Big Cursor": "Duży Kursor", 471 "Increase Font Size": "ZwiÄksz Rozmiar Czcionki", 472 "Decrease Font Size": "Zmniejsz Rozmiar Czcionki",
473 "Letter Spacing": "OdstÄpy MiÄdzy Literami", 474 "Line Height": "WysokoÅÄ Linii", 475 "Font Weight": "GruboÅÄ Czcionki", 476 "Dyslexia Font": "Czcionka dla Dysleksji", 477 "Language": "JÄzyk", 478 "Open Accessibility Menu": "Otwórz Menu DostÄpnoÅci", 479 "Hide Images": "Ukryj Obrazy", 480 "Skip to accessibility menu": "Przejdź do menu dostÄpnoÅci", 481 "Accessibility Report": "Raport DostÄpnoÅci", 482 "Run Accessibility Check": "Uruchom Sprawdzanie DostÄpnoÅci", 483 "Loading...": "Åadowanie...", 484 "Analyzing page...": "Analizowanie strony...", 485 "Critical": "Krytyczny", 486 "Serious": "Poważny", 487 "Moderate": "Umiarkowany", 488 "Minor": "Drobny", 489 "Violations Found": "Znalezione Naruszenia", 490 "No Issues Found": "Brak Problemów", 491 "Element": "Element", 492 "Issue": "Problem", 493 "How to Fix": "Jak NaprawiÄ", 494 "Close Report": "Zamknij Raport", 495 "Passed Tests": "Zaliczone Testy", 496 "Items Need Review": "Elementy do Sprawdzenia", 497 "Annotations": "Annotations", 498 "Text to Speech": "Text to Speech", 499 "Text to Speech On": "Czytanie Tekstu WÅÄ czone", 500 "Text to Speech Off": "Czytanie Tekstu WyÅÄ czone", 501 "Simplify Layout": "Simplify Layout", 502 "Play": "Play", 503 "Pause": "Pause", 504 "Stop": "Stop", 505 "Reading...": "Reading...", 506 }, 507 ro: { 508 "Accessibility Menu": "Meniu Accesibilitate", 509 "Reset settings": "Resetare setÄri", 510 "Reset All Settings": "Resetare toate setÄrile", 511 "Close": "Ãnchide", 512 "Content Adjustments": "AjustÄri ConÈinut", 513 "Adjust Font Size": "Ajustare Dimensiune Font", 514 "Highlight Title": "EvidenÈiere Titluri", 515 "Highlight Links": "EvidenÈiere Linkuri", 516 "Readable Font": "Font Lizibil", 517 "Color Adjustments": "AjustÄri Culoare", 518 "Invert Colors": "Inversare Culori", 519 "Light Contrast": "Contrast Luminos", 520 "Dark Contrast": "Contrast Ãntunecat", 521 "High Contrast": "Contrast Ridicat", 522 "High Saturation": "SaturaÈie RidicatÄ", 523 "Low Saturation": "SaturaÈie ScÄzutÄ", 524 "Monochrome": "Monocrom", 525 "Tools": "Instrumente", 526 "Reading Guide": "Ghid de Citire", 527 "Stop Animations": "Oprire AnimaÈii", 528 "Big Cursor": "Cursor Mare", 529 "Increase Font Size": "MÄrire Dimensiune Font", 530 "Decrease Font Size": "MicÈorare Dimensiune Font", 531 "Letter Spacing": "SpaÈiere Litere", 532 "Line Height": "ÃnÄlÈime Linie", 533 "Font Weight": "Grosime Font", 534 "Dyslexia Font": "Font pentru Dislexie", 535 "Language": "LimbÄ", 536 "Open Accessibility Menu": "Deschide Meniul Accesibilitate", 537 "Hide Images": "Ascunde Imagini", 538 "Skip to accessibility menu": "Salt la meniul de accesibilitate", 539 "Accessibility Report": "Raport Accesibilitate", 540 "Run Accessibility Check": "RuleazÄ Verificare Accesibilitate", 541 "Loading...": "Se încarcÄ...", 542 "Analyzing page...": "Se analizeazÄ pagina...", 543 "Critical": "Critic", 544 "Serious": "Grav", 545 "Moderate": "Moderat", 546 "Minor": "Minor", 547 "Violations Found": "ÃncÄlcÄri GÄsite", 548 "No Issues Found": "Nicio ProblemÄ GÄsitÄ", 549 "Element": "Element", 550 "Issue": "ProblemÄ", 551 "How to Fix": "Cum se RemediazÄ", 552 "Close Report": "Ãnchide Raportul", 553 "Passed Tests": "Teste Trecute", 554 "Items Need Review": "Elemente de Verificat", 555 "Annotations": "Annotations", 556 "Text to Speech": "Text to Speech", 557 "Text to Speech On": "Text to Speech Pornit", 558 "Text to Speech Off": "Text to Speech Oprit", 559 "Simplify Layout": "Simplify Layout", 560 "Play": "Play", 561 "Pause": "Pause", 562 "Stop": "Stop", 563 "Reading...": "Reading...", 564 }, 565 nl: { 566 "Accessibility Menu": "Toegankelijkheidsmenu", 567 "Reset settings": "Instellingen resetten", 568 "Reset All Settings": "Alle instellingen resetten",
569 "Close": "Sluiten", 570 "Content Adjustments": "Inhoudsaanpassingen", 571 "Adjust Font Size": "Lettergrootte aanpassen", 572 "Highlight Title": "Titels markeren", 573 "Highlight Links": "Links markeren", 574 "Readable Font": "Leesbaar Lettertype", 575 "Color Adjustments": "Kleuraanpassingen", 576 "Invert Colors": "Kleuren omkeren", 577 "Light Contrast": "Licht Contrast", 578 "Dark Contrast": "Donker Contrast", 579 "High Contrast": "Hoog Contrast", 580 "High Saturation": "Hoge Verzadiging", 581 "Low Saturation": "Lage Verzadiging", 582 "Monochrome": "Monochroom", 583 "Tools": "Hulpmiddelen", 584 "Reading Guide": "Leeshulp", 585 "Stop Animations": "Animaties stoppen", 586 "Big Cursor": "Grote Cursor", 587 "Increase Font Size": "Lettergrootte vergroten", 588 "Decrease Font Size": "Lettergrootte verkleinen", 589 "Letter Spacing": "Letterafstand", 590 "Line Height": "Regelhoogte", 591 "Font Weight": "Letterdikte", 592 "Dyslexia Font": "Dyslexie Lettertype", 593 "Language": "Taal", 594 "Open Accessibility Menu": "Toegankelijkheidsmenu openen", 595 "Hide Images": "Afbeeldingen verbergen", 596 "Skip to accessibility menu": "Ga naar toegankelijkheidsmenu", 597 "Accessibility Report": "Toegankelijkheidsrapport", 598 "Run Accessibility Check": "Toegankelijkheidscontrole uitvoeren", 599 "Loading...": "Laden...", 600 "Analyzing page...": "Pagina analyseren...", 601 "Critical": "Kritiek", 602 "Serious": "Ernstig", 603 "Moderate": "Matig", 604 "Minor": "Gering", 605 "Violations Found": "Overtredingen Gevonden", 606 "No Issues Found": "Geen Problemen Gevonden", 607 "Element": "Element", 608 "Issue": "Probleem", 609 "How to Fix": "Hoe op te lossen", 610 "Close Report": "Rapport Sluiten", 611 "Passed Tests": "Geslaagde Tests", 612 "Items Need Review": "Items te Controleren", 613 "Annotations": "Annotations", 614 "Text to Speech": "Text to Speech", 615 "Text to Speech On": "Tekst naar Spraak Aan", 616 "Text to Speech Off": "Tekst naar Spraak Uit", 617 "Simplify Layout": "Simplify Layout", 618 "Play": "Play", 619 "Pause": "Pause", 620 "Stop": "Stop", 621 "Reading...": "Reading...", 622 }, 623 uk: { 624 "Accessibility Menu": "ÐÐµÐ½Ñ Ð´Ð¾ÑÑÑпноÑÑÑ", 625 "Reset settings": "СкинÑÑи налаÑÑÑваннÑ", 626 "Reset All Settings": "СкинÑÑи вÑÑ Ð½Ð°Ð»Ð°ÑÑÑваннÑ", 627 "Close": "ÐакÑиÑи", 628 "Content Adjustments": "ÐалаÑÑÑÐ²Ð°Ð½Ð½Ñ ÐºÐ¾Ð½ÑенÑÑ", 629 "Adjust Font Size": "ÐмÑниÑи ÑозмÑÑ ÑÑиÑÑÑ", 630 "Highlight Title": "ÐидÑлиÑи заголовки", 631 "Highlight Links": "ÐидÑлиÑи поÑиланнÑ", 632 "Readable Font": "ЧиÑабелÑний ÑÑиÑÑ", 633 "Color Adjustments": "ÐалаÑÑÑÐ²Ð°Ð½Ð½Ñ ÐºÐ¾Ð»ÑоÑÑ", 634 "Invert Colors": "ÐнвеÑÑÑваÑи колÑоÑи", 635 "Light Contrast": "СвÑÑлий конÑÑаÑÑ", 636 "Dark Contrast": "Темний конÑÑаÑÑ", 637 "High Contrast": "ÐиÑокий конÑÑаÑÑ", 638 "High Saturation": "ÐиÑока наÑиÑенÑÑÑÑ", 639 "Low Saturation": "ÐизÑка наÑиÑенÑÑÑÑ", 640 "Monochrome": "ÐÐ¾Ð½Ð¾Ñ Ñомний", 641 "Tools": "ÐнÑÑÑÑменÑи", 642 "Reading Guide": "ÐÑнÑйка Ð´Ð»Ñ ÑиÑаннÑ", 643 "Stop Animations": "ÐÑпиниÑи анÑмаÑÑÑ", 644 "Big Cursor": "Ðеликий кÑÑÑоÑ", 645 "Increase Font Size": "ÐбÑлÑÑиÑи ÑозмÑÑ ÑÑиÑÑÑ", 646 "Decrease Font Size": "ÐменÑиÑи ÑозмÑÑ ÑÑиÑÑÑ", 647 "Letter Spacing": "ÐÑжлÑÑеÑний ÑнÑеÑвал", 648 "Line Height": "ÐиÑоÑа ÑÑдка", 649 "Font Weight": "ÐиÑний ÑÑиÑÑ", 650 "Dyslexia Font": "ШÑиÑÑ Ð´Ð»Ñ Ð´Ð¸ÑлекÑÑÑ", 651 "Language": "Ðова", 652 "Open Accessibility Menu": "ÐÑдкÑиÑи Ð¼ÐµÐ½Ñ Ð´Ð¾ÑÑÑпноÑÑÑ", 653 "Hide Images": "ÐÑÐ¸Ñ Ð¾Ð²Ð°Ñи зобÑаженнÑ", 654 "Skip to accessibility menu": "ÐеÑейÑи до Ð¼ÐµÐ½Ñ Ð´Ð¾ÑÑÑпноÑÑÑ", 655 "Accessibility Report": "ÐвÑÑ Ð¿Ñо доÑÑÑпнÑÑÑÑ", 656 "Run Accessibility Check": "ÐапÑÑÑиÑи пеÑевÑÑÐºÑ Ð´Ð¾ÑÑÑпноÑÑÑ", 657 "Loading...": "ÐаванÑаженнÑ...", 658 "Analyzing page...": "ÐналÑз ÑÑоÑÑнки...", 659 "Critical": "ÐÑиÑиÑний", 660 "Serious": "СеÑйозний", 661 "Moderate": "ÐомÑÑний", 662 "Minor": "ÐезнаÑний", 663 "Violations Found": "ÐÐ½Ð°Ð¹Ð´ÐµÐ½Ñ Ð¿Ð¾ÑÑÑеннÑ", 664 "No Issues Found": "ÐÑоблем не знайдено", 665 "Element": "ÐлеменÑ", 666 "Issue": "ÐÑоблема", 667 "How to Fix": "Як випÑавиÑи", 668 "Close Report": "ÐакÑиÑи звÑÑ", 669 "Passed Tests": "ÐÑÐ¾Ð¹Ð´ÐµÐ½Ñ ÑеÑÑи", 670 "Items Need Review": "ÐоÑÑебÑÑÑÑ Ð¿ÐµÑевÑÑки", 671 "Annotations": "Annotations", 672 "Text to Speech": "Text to Speech", 673 "Text to Speech On": "ÐзвÑÑÑÐ²Ð°Ð½Ð½Ñ Ð£Ð²Ñмкнено", 674 "Text to Speech Off": "ÐзвÑÑÑÐ²Ð°Ð½Ð½Ñ Ðимкнено", 675 "Simplify Layout": "Simplify Layout", 676 "Play": "Play", 677 "Pause": "Pause", 678 "Stop": "Stop", 679 "Reading...": "Reading...", 680 } 681 }; 682 683 const SUPPORTED_LANGUAGES = [ 684 { code: "en", label: "English (English)" }, 685 { code: "it", label: "Italiano (Italian)" }, 686 { code: "fr", label: "Français (French)" }, 687 { code: "de", label: "Deutsch (German)" }, 688 { code: "es", label: "Español (Spanish)" }, 689 { code: "ru", label: "Ð ÑÑÑкий (Russian)" }, 690 { code: "pl", label: "Polski (Polish)" }, 691 { code: "ro", label: "RomânÄ (Romanian)" }, 692 { code: "nl", label: "Nederlands (Dutch)" }, 693 { code: "uk", label: "УкÑаÑнÑÑка (Ukrainian)" } 694 ]; 695 696 /** @typedef {import('./index.js').default} AccessibleWebWidget */ 697 698 /** @type {{ [methodName: string]: (this: AccessibleWebWidget, ...args: any[]) => any }} */ 699 const stateMethods = { 700 701 storageAvailable() { 702 try { 703 const test = '__test__'; 704 localStorage.setItem(test, test); 705 localStorage.removeItem(test); 706 return true; 707 } catch { 708 return false; 709 } 710 }, 711 712 fetchCookie(name) { 713 const cookieName = name + "="; 714 try { 715 const decodedCookie = decodeURIComponent(document.cookie); 716 return decodedCookie.split(';') 717 .map(c => c.trim()) 718 .find(c => c.startsWith(cookieName)) 719 ?.substring(cookieName.length) || "{}"; 720 } catch (e) { 721 console.warn('Error reading cookie:', e); 722 return "{}"; 723 } 724 }, 725 726 storeCookie(name, value, days) { 727 try { 728 const d = new Date(); 729 d.setTime(d.getTime() + (days * 24 * 60 * 60 * 1000)); 730 const expires = "expires=" + d.toUTCString(); 731 const isSecure = window.location.protocol === 'https:'; 732 document.cookie = name + "=" + value + ";" + expires + ";path=/;SameSite=Strict" + (isSecure ? ";Secure" : ""); 733 } catch (e) { 734 console.warn('Error setting cookie:', e); 735 } 736 }, 737 738 getSavedLanguage() { 739 try { 740 // Try localStorage first 741 if (this.storageAvailable()) { 742 const stored = localStorage.getItem(this.cookieKey); 743 if (stored) { 744 const config = JSON.parse(stored); 745 if (config.lang) return config.lang; 746 } 747 } 748 // Fallback to cookie 749 const cookieVal = this.fetchCookie(this.cookieKey); 750 if (cookieVal && cookieVal !== "") { 751 const config = JSON.parse(cookieVal); 752 if (config.lang) return config.lang; 753 } 754 } catch { 755 // Ignore parsing errors 756 } 757 return null; 758 }, 759 760 getBrowserLanguage() { 761 if (typeof navigator === 'undefined') return 'en'; 762 763 const supportedCodes = this.supportedLanguages.map(lang => lang.code); 764 765 // Get browser languages (navigator.languages is an array, navigator.language is a string) 766 const browserLanguages = navigator.languages 767 ? [...navigator.languages] 768 : [navigator.language || navigator.userLanguage || 'en']; 769 770 for (const browserLang of browserLanguages) { 771 // Extract the primary language code (e.g., 'en-US' -> 'en') 772 const primaryCode = browserLang.split('-')[0].toLowerCase(); 773 774 if (supportedCodes.includes(primaryCode)) { 775 return primaryCode; 776 } 777 } 778 779 // Default to English if no supported language found 780 return 'en'; 781 }, 782 783 getDefaultLanguage() { 784 // Priority: 1. Saved language, 2. Browser language, 3. English
785 const savedLang = this.getSavedLanguage(); 786 if (savedLang) { 787 const supportedCodes = this.supportedLanguages.map(lang => lang.code); 788 if (supportedCodes.includes(savedLang)) { 789 return savedLang; 790 } 791 } 792 return this.getBrowserLanguage(); 793 }, 794 795 isDevMode() { 796 if (typeof window === 'undefined') return false; 797 try { 798 const urlParams = new URLSearchParams(window.location.search); 799 return urlParams.get('acc-dev') === 'true'; 800 } catch { 801 return false; 802 } 803 }, 804 805 getDataAttributeOptions() { 806 const options = {}; 807 if (typeof document === 'undefined') return options; 808 809 const attributes = ['lang', 'position', 'offset', 'size', 'icon']; 810 811 const assignValue = (key, value) => { 812 if (value === null || typeof value === 'undefined' || value === '') return; 813 switch (key) { 814 case 'lang': { 815 const lang = String(value).trim(); 816 if (lang) options.lang = lang; 817 break; 818 } 819 case 'position': { 820 const position = String(value).trim(); 821 if (position) options.position = position; 822 break; 823 } 824 case 'offset': { 825 const normalized = this.normalizeOffset(value); 826 if (normalized) options.offset = normalized; 827 break; 828 } 829 case 'size': { 830 const normalizedSize = this.normalizeButtonSize(value); 831 if (normalizedSize) options.size = normalizedSize; 832 break; 833 } 834 case 'icon': { 835 const icon = String(value).trim(); 836 if (icon) options.icon = icon; 837 break; 838 } 839 } 840 }; 841 842 const inspectElement = (el) => { 843 if (!el) return; 844 attributes.forEach(key => { 845 const attrName = `data-acc-${key}`; 846 const attrValue = el.getAttribute ? el.getAttribute(attrName) : null; 847 assignValue(key, attrValue); 848 }); 849 }; 850 851 const scriptCandidates = []; 852 if (document.currentScript) { 853 scriptCandidates.push(document.currentScript); 854 } 855 document.querySelectorAll('script[data-acc-lang],script[data-acc-position],script[data-acc-offset],script[data-acc-size],script[data-acc-icon]').forEach(script => { 856 if (!scriptCandidates.includes(script)) { 857 scriptCandidates.push(script); 858 } 859 }); 860 scriptCandidates.forEach(inspectElement); 861 862 attributes.forEach(key => { 863 if (options[key] !== undefined) return; 864 const el = document.querySelector(`[data-acc-${key}]`); 865 if (el) { 866 assignValue(key, el.getAttribute(`data-acc-${key}`)); 867 } 868 }); 869 870 return options; 871 }, 872 873 normalizeOffset(value) { 874 if (!value && value !== 0) return undefined; 875 let parts = []; 876 if (Array.isArray(value)) { 877 parts = value; 878 } else if (typeof value === 'string') { 879 parts = value.split(/[, ]+/); 880 } else { 881 parts = [value]; 882 } 883 const parsed = parts 884 .map(part => { 885 const number = Number(part); 886 return Number.isFinite(number) ? Math.round(number) : null; 887 }) 888 .filter(number => number !== null); 889 890 if (!parsed.length) return undefined; 891 if (parsed.length === 1) parsed.push(parsed[0]); 892 return [parsed[0], parsed[1] !== undefined ? parsed[1] : parsed[0]]; 893 }, 894 895 normalizeButtonSize(value) { 896 const fallback = this.widgetTheme?.buttonSize || '48px'; 897 if (typeof value === 'number' && Number.isFinite(value)) { 898 return `${Math.max(24, Math.round(value))}px`; 899 } 900 if (typeof value === 'string') { 901 const trimmed = value.trim(); 902 if (!trimmed) return fallback; 903 if (/^\d+(\.\d+)?(px|em|rem|%)$/i.test(trimmed)) { 904 return trimmed; 905 } 906 const numeric = Number(trimmed); 907 if (Number.isFinite(numeric)) { 908 return `${Math.max(24, Math.round(numeric))}px`; 909 } 910 return trimmed; 911 } 912 return fallback; 913 }, 914 915 toggleDisplay(el, state) { 916 if (!el) return; 917 try { 918 el.style.display = (typeof state === "undefined") 919 ? (el.style.display === "none" ? "block" : "none") 920 : (state ? "block" : "none"); 921 } catch (e) { 922 console.warn('Error toggling element:', e); 923 } 924 }, 925 926 isSystemControlledPreference(key) { 927 const systemDefaults = this.widgetConfig?.systemDefaults || {}; 928 return Object.prototype.hasOwnProperty.call(systemDefaults, key); 929 }, 930 931 hasExplicitStatePreference(key) { 932 const states = this.widgetConfig?.states || {}; 933 if (!Object.prototype.hasOwnProperty.call(states, key)) { 934 return false; 935 } 936 return !this.isSystemControlledPreference(key); 937 }, 938 939 hasExplicitColorFilterPreference() { 940 const states = this.widgetConfig?.states || {}; 941 const systemDefaults = this.widgetConfig?.systemDefaults || {};
942 const keys = Array.isArray(this.colorFilterKeys) ? this.colorFilterKeys : []; 943 return keys.some((key) => 944 Object.prototype.hasOwnProperty.call(states, key) && 945 !Object.prototype.hasOwnProperty.call(systemDefaults, key) 946 ); 947 }, 948 949 updateState(payload, options = {}) { 950 const source = options.source || 'user'; 951 const previousStates = this.widgetConfig.states || {}; 952 const previousSystemDefaults = this.widgetConfig.systemDefaults || {}; 953 const nextStates = { ...previousStates, ...payload }; 954 const nextSystemDefaults = { ...previousSystemDefaults }; 955 const keys = Object.keys(payload || {}); 956 957 if (source === 'system') { 958 keys.forEach((key) => { 959 nextSystemDefaults[key] = payload[key]; 960 }); 961 } else { 962 keys.forEach((key) => { 963 if (Object.prototype.hasOwnProperty.call(nextSystemDefaults, key)) { 964 delete nextSystemDefaults[key]; 965 } 966 }); 967 } 968 969 const updatedConfig = { ...this.widgetConfig, states: nextStates, systemDefaults: nextSystemDefaults }; 970 this.saveConfig(updatedConfig); 971 return updatedConfig; 972 }, 973 974 saveConfig(newConfig) { 975 this.widgetConfig = { ...this.widgetConfig, ...newConfig }; 976 if (this.storageAvailable()) { 977 try { 978 localStorage.setItem(this.cookieKey, JSON.stringify(this.widgetConfig)); 979 } catch { 980 this.storeCookie(this.cookieKey, JSON.stringify(this.widgetConfig), 365); 981 } 982 } else { 983 this.storeCookie(this.cookieKey, JSON.stringify(this.widgetConfig), 365); 984 } 985 }, 986 987 retrieveState(key) { 988 return this.widgetConfig.states ? this.widgetConfig.states[key] : undefined; 989 }, 990 991 loadConfig(cache = true) { 992 if (cache) return this.widgetConfig; 993 const savedConfig = this.fetchSavedConfig(); 994 if (savedConfig) { 995 try { 996 this.widgetConfig = JSON.parse(savedConfig); 997 } catch (e) { 998 console.warn('Error parsing config:', e); 999 this.widgetConfig = {}; 1000 } 1001 } 1002 return this.widgetConfig; 1003 }, 1004 1005 fetchSavedConfig() { 1006 if (this.storageAvailable()) { 1007 try { 1008 const stored = localStorage.getItem(this.cookieKey); 1009 if (stored) return stored; 1010 } catch (e) { 1011 console.warn('localStorage failed, falling back to cookies:', e); 1012 } 1013 } 1014 const cookieVal = this.fetchCookie(this.cookieKey); 1015 return cookieVal && cookieVal !== "" ? cookieVal : "{}"; 1016 }, 1017 1018 fetchDataAttr(attr) { 1019 try { 1020 const dataAttr = `data-acc-${attr}`; 1021 const element = document.querySelector(`[${dataAttr}]`); 1022 return element ? element.getAttribute(dataAttr) : null; 1023 } catch (e) { 1024 console.warn(`Error getting data attribute: ${attr}`, e); 1025 return null; 1026 } 1027 }, 1028 1029 }; 1030 1031 var menuCSS = "/* Base styles */\n.acc-menu {\n position: fixed;\n left: var(--acc-menu-inline-gap, 12px);\n top: var(--acc-menu-block-gap, 12px);\n bottom: var(--acc-menu-block-gap, 12px);\n box-shadow: 0 4px 20px rgba(0, 0, 0, 0.12);\n opacity: 1;\n transition: 0.3s;\n z-index: var(--acc-widget-z-index, 100000);\n overflow: hidden;\n background: var(--acc-bg-color);\n width: min(430px, calc(100vw - (var(--acc-menu-inline-gap, 12px) * 2)));\n line-height: 1.5;\n font-size: 16px;\n height: auto;\n letter-spacing: 0.015em;\n color: var(--acc-text-color);\n --acc-content-inline-padding: 14px;\n --acc-menu-inline-gap: clamp(8px, 2.3vw, 18px);\n --acc-menu-block-gap: clamp(10px, 2.2vh, 20px);\n border-radius: 16px;\n box-shadow: 0 18px 42px rgba(0, 0, 0, 0.2);\n}\n\n/* Ensure all elements inherit proper colors for accessibility */\n.acc-menu * {\n color: var(--acc-text-color);\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;\n padding: 0;\n margin: 0;\n line-height: 1.5 !important;\n letter-spacing: normal !important;\n}\n\n/* Header section */\n.acc-menu-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 0 var(--acc-content-inline-padding);\n height: var(--acc-header-height);\n font-weight: 700 !important;\n background-color: var(--acc-primary-color) !important;\n}\n\n.acc-menu-title {\n display: flex;\n align-items: center;\n gap: 10px;\n font-size: 18px !important;\n color: var(--acc-text-color-inverted) !important;\n font-weight: bold;\n}\n\n.acc-menu-title .acc-label {\n color: var(--acc-text-color-inverted) !important;\n}\n\n.acc-menu-title-icon {\n width: 30px;\n height: 30px;\n border-radius: 8px;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n}\n\n.acc-menu-title-icon svg {\n fill: var(--acc-text-color-inverted) !important;\n width: 18px !important;\n height: 18px !important;\n min-width: 18px !important;\n min-height: 18px !important;\n max-width: 18px !important;\n max-height: 18px !important;\n}\n\n.acc-header-back {\n display: flex;\n align-items: center;\n}\n\n.acc-back-btn {\n display: flex;\n align-items: center;\n gap: 8px;\n background: transparent;\n border: none;\n padding: 8px;\n cursor: pointer;\n font-size: 16px;\n font-weight: 600;\n color: var(--acc-text-color-inverted) !important;\n transition: background-color 0.2s ease;\n border-radius: 4px;\n visibility: hidden;\n}\n\n.acc-back-btn > span {\n color: var(--acc-text-color-inverted) !important;\n}\n\n.acc-back-btn.visible {\n visibility: visible;\n}\n\n.acc-back-btn:hover {\n background-color: rgba(255, 255, 255, 0.18);\n}\n\n.acc-back-btn:focus {\n outline: 2px solid var(--acc-text-color-inverted);\n outline-offset: 1px;\n}\n\n.acc-back-btn svg {\n fill: var(--acc-text-color-inverted) !important;\n width: 24px !important;\n height: 24px !important;\n}\n\n.acc-menu-title-dynamic {\n display: none !important;\n}\n\n.acc-menu-title-dynamic.visible {\n display: block !important;\n}\n\n.acc-menu-title-default {\n display: block !important;\n}\n\n.acc-menu-title-default.hidden {\n display: none !important;\n}\n\n.acc-menu-header svg {\n fill: var(--acc-text-color-inverted) !important;\n width: 28px !important;\n height: 28px !important;\n min-width: 28px !important;\n min-height: 28px !important;\n max-width: 28px !important;\n max-height: 28px !important;\n}\n\n.acc-menu-header > div {\n display: flex;\n align-items: center;\n}\n\n/* Interactive elements */\n.acc-menu-header div[role=\"button\"] {\n cursor: pointer;\n padding: 8px;\n border-radius: 50%;\n display: flex;\n align-items: center;\n justify-content: center;\n width: 36px;\n height: 36px;\n transition: background-color 0.2s ease;\n}\n\n.acc-menu-header div[role=\"button\"]:hover {\n background-color: rgba(255, 255, 255, 0.18);\n}\n\n.acc-menu-header div[role=\"button\"]:focus {\n outline: 2px solid var(--acc-text-color-inverted);\n outline-offset: 1px;\n}\n\n.acc-menu-header .acc-header-actions {\n display: flex;\n align-items: center;\n}\n\n.acc-language-container {\n margin: 0 var(--acc-content-inline-padding) 24px;\n}\n\n.acc-lang-details {\n border: 1px solid rgba(0, 0, 0, 0.1);\n border-radius: var(--acc-button-border-radius);\n background: var(--acc-card-bg);\n}\n\n.acc-lang-summary {\n list-style: none;\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 12px;\n padding: 12px 14px;\n cursor: pointer;\n border-radius: var(--acc-button-border-radius);\n}\n\n.acc-lang-summary::-webkit-details-marker {\n display: none;\n}\n\n.acc-lang-summary::marker {\n content: '';\n}\n\n.acc-lang-summary-main {\n display: flex;\n align-items: center;\n gap: 10px;\n min-width: 0;\n}\n\n.acc-lang-current-label {\n font-size: 16px !important;\n font-weight: 600 !important;\n}\n\n.acc-lang-summary:hover {\n background-color: rgba(25, 118, 210, 0.06);\n}\n\n.acc-lang-summary:focus {\n outline: var(--acc-focus-outline-width) solid var(--acc-focus-ring-color);\n outline-offset: var(--acc-focus-outline-offset);\n}\n\n.acc-lang-summary-arrow {\n width: 10px;\n height: 10px;\n border-right: 2px solid var(--acc-text-color);\n border-bottom: 2px solid var(--acc-text-color);\n transform: rotate(-45deg);\n transition: transform 0.2s ease;\n}\n\n.acc-lang-details[open] .acc-lang-summary-arrow {\n transform: rotate(45deg);\n}\n\n.acc-lang-details[open] .acc-lang-summary {\n border-bottom: 1px solid var(--acc-border-color);\n border-bottom-left-radius: 0;\n border-bottom-right-radius: 0;\n}\n\n.acc-lang-details-panel {\n padding: 12px 0 8px;\n}\n\n.acc-lang-details-panel .acc-section-title {\n font-size: 16px !important;\n padding: 0 16px 10px;\n}\n\n.acc-lang-flag {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n min-width: 22px;\n font-size: 18px !important;\n line-height: 1;\n}\n\n.acc-lang-search-wrapper {\n padding: 0 16px 8px;\n}\n\n.acc-lang-search {\n width: 100%;\n padding: 10px 16px;\n border: 1.5px solid var(--acc-border-color);\n border-radius: var(--acc-button-border-radius);\n font-size: 16px;\n background-color: var(--acc-card-bg);\n transition: border-color 0.2s ease;\n}\n\n.acc-lang-search:focus {\n outline: var(--acc-focus-outline-width) solid var(--acc-focus-ring-color);\n outline-offset: var(--acc-focus-outline-offset);\n border-color: var(--acc-primary-color) !important;\n}\n\n.acc-lang-list {\n padding: 6px 8px 12px;\n max-height: 280px;\n overflow-y: auto;\n}\n\n.acc-lang-item {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n width: 100%;\n text-align: left;\n padding: 11px 10px;\n margin-bottom: 4px;\n background-color: transparent;\n border: none;\n border-radius: var(--acc-button-border-radius);\n cursor: pointer;\n font-size: 16px;\n color: var(--acc-text-color);\n transition: background-color 0.12s ease;\n}\n\n.acc-lang-item:hover {\n background-color: rgba(25, 118, 210, 0.06);\n}\n\n.acc-lang-item:focus {\n outline: var(--acc-focus-outline-width) solid var(--acc-focus-ring-color);\n outline-offset: var(--acc-focus-outline-offset);\n}\n\n.acc-lang-item.selected {\n background-color: rgba(25, 118, 210, 0.08);\n font-weight: 600;\n}\n\n.acc-lang-item-main {\n display: flex;\n align-items: center;\n gap: 10px;\n min-width: 0;\n}\n\n.acc-lang-item-label {\n font-size: 16px !important;\n line-height: 1.4 !important;\n}\n\n.acc-icon-check {\n display: inline-block;\n width: 18px;\n height: 18px;\n background-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='18' height='18' viewBox='0 0 24 24' fill='none' stroke='%23886f60' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='20 6 9 17 4 12'%3E%3C/polyline%3E%3C/svg%3E\");\n background-repeat: no-repeat;\n background-position: center;\n opacity: 0;\n transition: opacity 0.15s ease;\n}\n\n.acc-lang-item.selected .acc-icon-check {\n opacity: 1;\n}\n\n.acc-menu .acc-lang-select {\n width: 100% !important;\n padding: 0 16px !important;\n font-size: 16px !important;\n font-family: inherit !important;\n font-weight: 600 !important;\n border-radius: var(--acc-button-border-radius) !important;\n background: var(--acc-card-bg) !important;\n border: 1.5px solid var(--acc-border-color) !important;\n min-height: 48px !important;\n max-height: 48px !important;\n height: 48px !important;\n color: var(--acc-text-color) !important;\n -webkit-appearance: none !important;\n -moz-appearance: none !important;\n appearance: none !important;\n background-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' height='24px' viewBox='0%20-960%20960%20960' width='24px' fill='%231f1f1f'%3E%3Cpath d='M480-344%20240-584l56-56%20184 184%20184-184%2056 56-240 240Z'/%3E%3C/svg%3E\") !important;\n background-repeat: no-repeat !important;\n background-position: right 12px center !important;\n background-size: 20px !important;\n padding-right: 44px !important;\n}\n\n/* Hide default arrows in Firefox and IE */\n.acc-menu .acc-lang-select::-ms-expand {\n display: none !important;\n}\n\n.acc-menu .acc-lang-select:focus {\n outline: var(--acc-focus-outline-width) solid var(--acc-focus-ring-color);\n outline-offset: var(--acc-focus-outline-offset);\n border-color: var(--acc-primary-color) !important;\n}\n\n/* Option grid layout */\n.acc-options-all {\n display: flex;\n flex-direction: column;\n gap: 16px;\n padding: 0 var(--acc-content-inline-padding) 12px;\n}\n\n.acc-option-category {\n display: flex;\n flex-direction: column;\n gap: 10px;\n}\n\n.acc-option-category-interaction {\n padding-bottom: 12px;\n}\n\n.acc-option-category .acc-section-title {\n font-size: 14px !important;\n font-weight: 700 !important;\n letter-spacing: 0.01em !important;\n text-transform: none;\n color: #6b7280 !important;\n padding: 0 2px;\n}\n\n.acc-options {\n display: grid;\n grid-template-columns: repeat(3, minmax(0, 1fr));\n gap: 12px;\n margin: 0;\n}\n\n.acc-options-text {\n grid-template-columns: repeat(3, minmax(0, 1fr));\n}\n\n.acc-options-text-inline {\n grid-template-columns: repeat(2, minmax(0, 1fr));\n}\n\n.acc-btn.acc-text-inline {\n min-height: 54px;\n aspect-ratio: auto;\n flex-direction: row;\n align-items: center;\n justify-content: flex-start;\n text-align: left;\n gap: 10px;\n padding: 12px 14px;\n}\n\n.acc-btn.acc-text-inline .acc-label {\n font-size: 14px !important;\n flex: 1 1 auto;\n min-width: 0;\n}\n\n.acc-btn.acc-text-inline .acc-progress-indicator {\n margin-top: 0;\n margin-left: auto;\n justify-content: flex-end;\n min-width: 24px;\n}\n\n@media only screen and (max-width: 430px) {\n .acc-options-text-inline {\n grid-template-columns: repeat(2, minmax(0, 1fr));\n }\n\n .acc-btn.acc-text-inline {\n min-height: auto;\n aspect-ratio: 11 / 8;\n flex-direction: column;
1031\n align-items: center;\n justify-content: center;\n text-align: center;\n gap: 6px;\n padding: 10px;\n }\n\n .acc-btn.acc-text-inline .acc-label {\n font-size: 13px !important;\n flex: 0 1 auto;\n }\n\n .acc-btn.acc-text-inline .acc-progress-indicator {\n margin-top: 8px;\n margin-left: 0;\n justify-content: center;\n }\n}\n\n.acc-tts-toggle-container {\n margin: 0;\n}\n\n.acc-text-scale-control {\n width: 100%;\n background: var(--acc-card-bg) !important;\n border: 1px solid rgba(0, 0, 0, 0.1) !important;\n border-radius: var(--acc-border-radius);\n padding: 12px 14px;\n display: flex;\n flex-direction: column;\n gap: 10px;\n}\n\n.acc-text-scale-meta {\n display: flex;\n align-items: center;\n gap: 10px;\n}\n\n.acc-text-scale-icon {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n}\n\n.acc-text-scale-icon svg {\n width: 22px !important;\n height: 22px !important;\n min-width: 22px !important;\n min-height: 22px !important;\n max-width: 22px !important;\n max-height: 22px !important;\n fill: var(--acc-text-color);\n}\n\n.acc-text-scale-meta .acc-label {\n font-size: 15px !important;\n font-weight: 600 !important;\n}\n\n.acc-text-scale-percent {\n margin-left: auto;\n font-size: 15px !important;\n font-weight: 700 !important;\n color: var(--acc-primary-color) !important;\n}\n\n.acc-text-scale-range {\n width: 100%;\n appearance: none;\n -webkit-appearance: none;\n background: transparent;\n height: 20px;\n cursor: pointer;\n margin: 0;\n}\n\n.acc-text-scale-range:focus {\n outline: none;\n}\n\n.acc-text-scale-range::-webkit-slider-runnable-track {\n width: 100%;\n height: 4px;\n border-radius: 999px;\n background: linear-gradient(\n to right,\n var(--acc-primary-color) 0%,\n var(--acc-primary-color) var(--acc-text-scale-progress, 0%),\n #d8d8d8 var(--acc-text-scale-progress, 0%),\n #d8d8d8 100%\n );\n}\n\n.acc-text-scale-range::-webkit-slider-thumb {\n appearance: none;\n -webkit-appearance: none;\n width: 18px;\n height: 18px;\n border-radius: 50%;\n background: #fff;\n border: 2px solid var(--acc-primary-color);\n margin-top: -7px;\n box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);\n}\n\n.acc-text-scale-range::-moz-range-track {\n width: 100%;\n height: 4px;\n border-radius: 999px;\n background: #d8d8d8;\n}\n\n.acc-text-scale-range::-moz-range-progress {\n height: 4px;\n border-radius: 999px;\n background: var(--acc-primary-color);\n}\n\n.acc-text-scale-range::-moz-range-thumb {\n width: 18px;\n height: 18px;\n border-radius: 50%;\n background: #fff;\n border: 2px solid var(--acc-primary-color);\n box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);\n}\n\n.acc-btn.acc-tts-toggle {\n width: 100%;\n min-height: 54px;\n aspect-ratio: auto;\n flex-direction: row;\n align-items: center;\n justify-content: flex-start;\n gap: 10px;\n text-align: left;\n padding: 12px 76px 12px 14px;\n}\n\n.acc-btn.acc-tts-toggle .acc-label {\n font-size: 15px !important;\n}\n\n.acc-btn.acc-tts-toggle svg {\n width: 22px !important;\n height: 22px !important;\n min-width: 22px !important;\n min-height: 22px !important;\n max-width: 22px !important;\n max-height: 22px !important;\n}\n\n.acc-btn.acc-tts-toggle::before {\n content: \"\";\n position: absolute;\n right: 14px;\n top: 50%;\n width: 44px;\n height: 24px;\n border-radius: 999px;\n transform: translateY(-50%);\n background: #dbd7d2;\n border: 1px solid rgba(0, 0, 0, 0.08);\n}\n\n.acc-btn.acc-tts-toggle::after {\n content: \"\";\n position: absolute;\n right: 38px;\n top: 50%;\n width: 18px;\n height: 18px;\n border-radius: 50%;\n transform: translateY(-50%);\n background: #fff;\n box-shadow: 0 1px 2px rgba(0, 0, 0, 0.18);\n}\n\n.acc-btn.acc-tts-toggle.acc-selected::before {\n background: var(--acc-primary-color);\n border-color: var(--acc-primary-color);\n}\n\n.acc-btn.acc-tts-toggle.acc-selected::after {\n right: 16px;\n}\n\n/* Button styling */\n.acc-btn {\n aspect-ratio: 11 / 8;\n border-radius: var(--acc-border-radius);\n padding: 10px;\n display: flex;\n align-items: center;\n justify-content: center;\n flex-direction: column;\n text-align: center;\n font-size: 15px !important;\n background: var(--acc-card-bg) !important;\n border: 1px solid rgba(0, 0, 0, 0.1) !important;\n transition: background-color 0.2s ease;
1031\n cursor: pointer;\n word-break: break-word;\n gap: 6px;\n position: relative;\n}\n\n.acc-btn:hover {\n border-color: var(--acc-hover-color) !important;\n border-width: 1px !important;\n box-shadow: inset 0 0 0 1px var(--acc-hover-color);\n}\n\n.acc-btn:focus {\n outline: var(--acc-focus-outline-width) solid var(--acc-focus-ring-color);\n outline-offset: var(--acc-focus-outline-offset);\n border-color: var(--acc-primary-color) !important;\n}\n\n.acc-btn .acc-label, .acc-text-adjust .acc-label div {\n font-size: 13px !important;\n font-weight: 600 !important;\n}\n\n/* SVG icons */\n.acc-text-adjust svg {\n width: 20px !important;\n height: 20px !important;\n min-width: 20px !important;\n min-height: 20px !important;\n max-width: 20px !important;\n max-height: 20px !important;\n}\n\n.acc-btn svg {\n width: 24px !important;\n height: 24px !important;\n min-width: 24px !important;\n min-height: 24px !important;\n max-width: 24px !important;\n max-height: 24px !important;\n fill: var(--acc-text-color);\n}\n\n/* Selected state */\n.acc-btn.acc-selected {\n background-color: var(--acc-primary-color) !important;\n border-color: var(--acc-primary-color) !important;\n}\n\n.acc-btn.acc-selected .acc-progress-dot {\n background-color: rgba(255, 255, 255, 0.5);\n}\n\n.acc-btn.acc-selected .acc-progress-dot.active {\n background-color: var(--acc-text-color-inverted) !important;\n}\n\n.acc-btn.acc-selected svg,\n.acc-btn.acc-selected span,\n.acc-btn.acc-selected .acc-label {\n fill: var(--acc-text-color-inverted) !important;\n color: var(--acc-text-color-inverted) !important;\n}\n\n.acc-btn.acc-tts-toggle.acc-selected {\n background-color: var(--acc-card-bg) !important;\n border-color: rgba(0, 0, 0, 0.1) !important;\n}\n\n.acc-btn.acc-tts-toggle.acc-selected svg,\n.acc-btn.acc-tts-toggle.acc-selected span,\n.acc-btn.acc-tts-toggle.acc-selected .acc-label {\n fill: var(--acc-text-color) !important;\n color: var(--acc-text-color) !important;\n}\n\n/* Footer section */\n.acc-footer {\n position: absolute;\n bottom: 0;\n left: 0;\n right: 0;\n background: var(--acc-card-bg);\n padding: 12px 16px;\n display: flex;\n flex-direction: column;\n align-items: stretch;\n gap: 10px;\n border-top: 1px solid var(--acc-border-color);\n z-index: 100;\n overflow: visible;\n}\n\n.acc-footer-meta {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 12px;\n width: 100%;\n flex-wrap: nowrap;\n}\n\n.acc-footer-reset {\n display: flex;\n width: 100%;\n align-items: center;\n justify-content: center;\n gap: 8px;\n padding: 12px 16px;\n border: none;\n border-radius: var(--acc-button-border-radius);\n background-color: var(--acc-primary-color) !important;\n cursor: pointer;\n transition: background-color 0.2s ease;\n white-space: nowrap;\n}\n\n.acc-footer-reset svg {\n width: 24px !important;\n height: 24px !important;\n fill: var(--acc-text-color-inverted) !important;\n}\n\n.acc-footer-reset .acc-label {\n font-size: 16px !important;\n font-weight: 600 !important;\n color: var(--acc-text-color-inverted) !important;\n line-height: 1.2 !important;\n}\n\n.acc-footer-reset:hover {\n background-color: var(--acc-primary-color-dark) !important;\n}\n\n.acc-footer-reset:focus {\n outline: var(--acc-focus-outline-width) solid var(--acc-focus-ring-color);\n outline-offset: var(--acc-focus-outline-offset);\n}\n\n.acc-footer a {\n font-size: 12px !important;\n text-decoration: none !important;\n color: #6b7280 !important;\n background: transparent !important;\n font-weight: 500 !important;\n padding: 4px 0;\n border-radius: 4px;\n transition: color 0.15s ease;\n letter-spacing: 0.01em !important;\n align-self: center;\n display: inline-flex;\n align-items: center;\n white-space: nowrap;\n line-height: 1.2 !important;\n}\n\n.acc-footer a:hover {\n text-decoration: none !important;\n color: var(--acc-primary-color) !important;\n}\n\n.acc-footer a:focus {\n outline: var(--acc-focus-outline-width) solid var(--acc-focus-ring-color);\n outline-offset: var(--acc-focus-outline-offset);\n}\n\n.acc-footer-lang-toggle {\n display: inline-flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n min-width: 84px;\n padding: 7px 10px 7px 12px;\n border: 1px solid var(--acc-border-color);\n border-radius: var(--acc-button-border-radius);\n background: var(--acc-card-bg);\n cursor: pointer;\n transition: border-color 0.2s ease, background-color 0.2s ease;\n}\n\n.acc-footer-lang-toggle:hover {\n border-color: var(--acc-hover-color);\n}\n\n.acc-footer-lang-toggle:focus {\n outline: var(--acc-focus-outline-width) solid var(--acc-focus-ring-color);\n outline-offset: var(--acc-focus-outline-offset);\n}\n\n.acc-footer-lang-current {\n display: inline-block;\n min-width: 2ch;\n text-align: left;\n font-size: 14px !important;\n font-weight: 600 !important;\n line-height: 1 !important;\n}\n\n.acc-footer-lang-arrow {\n width: 8px;\n height: 8px;\n border-right: 1.5px solid var(--acc-text-color);\n border-bottom: 1.5px solid var(--acc-text-color);\n transform: rotate(45deg) translateY(-1px);\n transition: transform 0.2s ease;\n}\n\n.acc-footer-lang-toggle[aria-expanded=\"true\"] .acc-footer-lang-arrow {\n transform: rotate(-135deg) translateY(-1px);\n}\n\n.acc-lang-modal {\n position: absolute;\n right: 16px;\n bottom: calc(100% + 10px);\n width: min(320px, calc(100% - 32px));\n border: 1px solid var(--acc-border-color);\n border-radius: 14px;\n background: var(--acc-card-bg);\n box-shadow: 0 16px 32px rgba(0, 0, 0, 0.2);\n z-index: 120;\n padding: 10px 0 8px;\n}\n\n.acc-lang-modal[hidden] {\n display: none;\n}\n\n.acc-lang-modal-header {\n padding: 0 14px 8px;\n}\n\n.acc-lang-modal .acc-section-title {\n font-size: 14px !important;\n font-weight: 700 !important;\n color: #6b7280 !important;\n}\n\n.acc-lang-modal .acc-lang-search-wrapper {\n padding: 0 12px 8px;\n}\n\n.acc-lang-modal .acc-lang-list {\n padding: 6px 8px 8px;\n max-height: 240px;\n}\n\n/* Content area */\n.acc-menu-content {\n overflow: auto;\n max-height: calc(100% - 122px);\n padding: 24px 0 36px;\n}\n\n/* Text adjustments */\n.acc-text-adjust {\n background: var(--acc-card-bg);\n padding: 18px 20px;\n margin-bottom: 20px;\n border-radius: var(--acc-border-radius);\n border: 1px solid rgba(0, 0, 0, 0.1);\n}\n\n.acc-text-adjust .acc-label {\n display: flex;\n justify-content: flex-start;\n}\n\n.acc-text-adjust > div {\n display: flex;\n justify-content: space-between;\n margin-top: 20px;\n align-items: center;\n font-size: 16px;\n}\n\n.acc-text-adjust .acc-label div {\n font-size: 16px !important;\n}\n\n.acc-text-adjust div[role=\"button\"] {\n background: var(--acc-bg-color) !important;\n border-radius: 50%;\n width: 40px;\n height: 40px;\n display: flex;\n align-items: center;\n justify-content: center;
1031\n cursor: pointer;\n border: 1px solid rgba(0, 0, 0, 0.1);\n transition: border-color 0.2s ease;\n}\n\n.acc-text-adjust div[role=\"button\"]:hover {\n border-color: var(--acc-primary-color);\n}\n\n.acc-text-adjust div[role=\"button\"]:focus {\n outline: var(--acc-focus-outline-width) solid var(--acc-focus-ring-color);\n outline-offset: var(--acc-focus-outline-offset);\n}\n\n/* Overlay */\n.acc-overlay {\n position: fixed;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n z-index: calc(var(--acc-widget-z-index, 100000) - 1);\n}\n\n/* Progress indicator */\n.acc-progress-indicator {\n display: flex;\n justify-content: center;\n align-items: center;\n gap: 4px;\n margin-top: 8px;\n height: 8px;\n}\n\n.acc-progress-dot {\n width: 6px;\n height: 6px;\n border-radius: 50%;\n background-color: var(--acc-border-color);\n transition: background-color 0.2s ease;\n}\n\n.acc-progress-dot.active {\n background-color: var(--acc-primary-color);\n}\n\n/* Selected state updates indicator colors */\n.acc-btn.acc-selected .acc-progress-dot.active {\n background-color: var(--acc-bg-color);\n}\n\n/* Responsive adjustments */\n@media only screen and (max-width: 560px) {\n .acc-menu {\n width: calc(100vw - (var(--acc-menu-inline-gap, 8px) * 2));\n }\n}\n\n@media only screen and (max-width: 420px) {\n .acc-options {\n grid-template-columns: repeat(2, minmax(0, 1fr));\n gap: 12px;\n }\n .acc-btn {\n padding: 8px;\n }\n}\n\n/* Ensure proper focus visibility for assistive technology */\n@media (prefers-reduced-motion: reduce) {\n .acc-menu,\n .acc-btn,\n .acc-lang-select,\n .acc-progress-dot,\n .acc-menu-header div[role=\"button\"],\n .acc-lang-toggle,\n .acc-back-btn,\n .acc-footer-reset,\n .acc-footer-lang-toggle,\n .acc-footer-lang-arrow,\n .acc-text-adjust div[role=\"button\"] {\n transition: none;\n }\n}\n"; 1032 1033 var widgetCSS = " /* Base styles for the widget */\n .acc-widget, .acc-menu {\n -webkit-user-select: none;\n -moz-user-select: none;\n -ms-user-select: none;\n user-select: none;\n font-weight: 400;\n -webkit-font-smoothing: antialiased;\n }\n \n .acc-widget *, .acc-menu * { \n box-sizing: border-box !important; \n }\n \n /* Accessibility toggle button */\n .acc-toggle-btn {\n position: fixed;\n z-index: var(--acc-widget-z-index, 100000);\n left: 30px;\n bottom: 30px;\n border-radius: 50%;\n align-items: center;\n justify-content: center;\n width: var(--acc-button-size, 48px);\n height: var(--acc-button-size, 48px);\n display: flex;\n cursor: pointer;\n outline: none !important;\n border: none !important;\n box-shadow: inset 0 0 0 4px var(--acc-primary-color, #1976d2), inset 0 0 0 6px white, 0 2px 5px rgba(0,0,0,0.2) !important;\n background: var(--acc-primary-color, #1976d2) !important;\n transition: transform 0.2s ease, background-color 0.2s ease, box-shadow 0.2s ease;\n overflow: visible;\n }\n \n .acc-toggle-btn .acc-toggle-icon {\n width: 60%;\n height: 60%;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n }\n\n .acc-toggle-btn .acc-toggle-icon svg {\n width: 100%;\n height: 100%;\n fill: white;\n transition: none;\n }\n \n .acc-toggle-btn:hover {\n transform: scale(1.12);\n }\n\n .acc-toggle-btn::before,\n .acc-toggle-btn::after {\n content: \"\";\n position: absolute;\n top: 50%;\n left: 50%;\n width: 44%;\n height: 2.6px;\n border-radius: 999px;\n background: var(--acc-text-color-inverted, #fff);\n opacity: 0;\n pointer-events: none;\n transform: translate(-50%, -50%) rotate(0deg);\n transition: none;\n }\n\n .acc-toggle-btn[aria-expanded=\"true\"] {\n background: var(--acc-primary-color, #1976d2) !important;\n box-shadow: inset 0 0 0 4px var(--acc-primary-color, #1976d2), inset 0 0 0 6px white, 0 6px 14px rgba(0, 0, 0, 0.24) !important;\n }\n\n .acc-toggle-btn[aria-expanded=\"true\"] .acc-toggle-icon svg {\n opacity: 0;\n }\n\n .acc-toggle-btn[aria-expanded=\"true\"]::before,\n .acc-toggle-btn[aria-expanded=\"true\"]::after {\n opacity: 1;\n }\n\n .acc-toggle-btn[aria-expanded=\"true\"]::before {\n transform: translate(-50%, -50%) rotate(45deg);\n }\n\n .acc-toggle-btn[aria-expanded=\"true\"]::after {\n transform: translate(-50%, -50%) rotate(-45deg);\n }\n\n .acc-violation-bubble {\n position: absolute;\n top: -8px;\n right: -8px;\n min-width: 24px;\n height: 24px;\n border-radius: 12px;\n font-size: 11px;\n font-weight: 700;\n display: flex;
1033\n align-items: center;\n justify-content: center;\n padding: 0 4px;\n pointer-events: none;\n z-index: 1;\n color: #fff;\n border: none;\n box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);\n }\n\n .acc-violation-bubble[data-severity=\"critical\"] {\n background: #d32f2f;\n color: #fff;\n }\n\n .acc-violation-bubble[data-severity=\"serious\"] {\n background: #f57c00;\n color: #fff;\n }\n\n .acc-violation-bubble[data-severity=\"moderate\"] {\n background: #fbc02d;\n color: #333;\n }\n\n .acc-violation-bubble[hidden] {\n display: none;\n }\n\n .acc-toggle-btn:focus {\n outline: none !important;\n }\n\n .acc-toggle-btn:focus-visible {\n outline: 3px solid var(--acc-primary-color, #1976d2) !important;\n outline-offset: 2px;\n }\n\n body.acc-tts-click-mode :is(h1, h2, h3, h4, h5, h6, p, li, dt, dd, blockquote, figcaption, caption, th, td, div, section):not(.acc-container *):hover {\n cursor: pointer;\n }\n\n .acc-tts-active-block {\n outline: 2px solid var(--acc-primary-color, #1976d2) !important;\n outline-offset: 3px !important;\n border-radius: 4px;\n }\n \n @media (prefers-reduced-motion: reduce) {\n .acc-toggle-btn {\n transition: none;\n }\n\n .acc-toggle-btn .acc-toggle-icon svg,\n .acc-toggle-btn::before,\n .acc-toggle-btn::after {\n transition: none;\n }\n\n }\n"; 1034 1035 var reportCSS = ".acc-report-panel {\n display: none;\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n z-index: calc(var(--acc-widget-z-index, 100000) + 10);\n font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, sans-serif;\n}\n.acc-report-panel.acc-report-visible {\n display: flex;\n align-items: center;\n justify-content: center;\n}\n.acc-report-overlay {\n position: absolute;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.6);\n}\n.acc-report-dialog {\n position: relative;\n background: #fff;\n border-radius: 12px;\n width: 90%;\n max-width: 800px;\n max-height: 85vh;\n display: flex;\n flex-direction: column;\n box-shadow: 0 20px 60px rgba(0,0,0,0.3);\n}\n.acc-report-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 16px 20px;\n border-bottom: 1px solid #e0e0e0;\n}\n.acc-report-title {\n margin: 0;\n font-size: 18px;\n font-weight: 600;\n color: #1a1a1a;\n}\n.acc-report-close {\n background: none;\n border: none;\n padding: 8px;\n cursor: pointer;\n border-radius: 6px;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n.acc-report-close:hover {\n background: #f0f0f0;\n}\n.acc-report-close svg {\n width: 20px;\n height: 20px;\n fill: #666;\n}\n.acc-report-status {\n padding: 8px 20px;\n font-size: 14px;\n color: #666;\n background: #f8f9fa;\n}\n.acc-report-content {\n flex: 1;\n overflow-y: auto;\n padding: 16px 20px;\n}\n.acc-report-loading {\n text-align: center;\n padding: 40px;\n color: #666;\n}\n.acc-report-error {\n color: #d32f2f;\n padding: 20px;\n text-align: center;\n}\n.acc-report-summary {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));\n gap: 12px;\n margin-bottom: 20px;\n}\n.acc-report-stat {\n background: #f8f9fa;\n border-radius: 8px;\n padding: 16px;\n text-align: center;\n}\n.acc-report-stat-value {\n font-size: 28px;\n font-weight: 700;\n display: block;\n}\n.acc-report-stat-label {\n font-size: 12px;\n color: #666;\n text-transform: uppercase;\n margin-top: 4px;\n}\n.acc-report-stat.critical .acc-report-stat-value { color: #d32f2f; }\n.acc-report-stat.serious .acc-report-stat-value { color: #f57c00; }\n.acc-report-stat.moderate .acc-report-stat-value { color: #fbc02d; }\n.acc-report-stat.minor .acc-report-stat-value { color: #7cb342; }\n.acc-report-stat.passed .acc-report-stat-value { color: #43a047; }\n.acc-report-section {\n margin-bottom: 20px;\n}\n.acc-report-section-title {\n font-size: 14px;\n font-weight: 600;\n color: #333;\n margin-bottom: 12px;\n padding-bottom: 8px;\n border-bottom: 2px solid #e0e0e0;\n}\n.acc-report-violation {\n background: #fff;\n border: 1px solid #e0e0e0;\n border-radius: 8px;\n margin-bottom: 12px;\n overflow: hidden;\n}\n.acc-report-violation-header {\n padding: 12px 16px;\n background: #f8f9fa;\n cursor: pointer;\n display: flex;\n align-items: center;\n gap: 12px;\n}\n.acc-report-violation-header:hover {\n background: #f0f0f0;\n}\n.acc-report-violation-impact {\n font-size: 11px;\n font-weight: 600;\n text-transform: uppercase;\n padding: 4px 8px;\n border-radius: 4px;\n color: #fff;\n}\n.acc-report-violation-impact.critical { background: #d32f2f; }\n.acc-report-violation-impact.serious { background: #f57c00; }\n.acc-report-violation-impact.moderate { background: #fbc02d; color: #333; }\n.acc-report-violation-impact.minor { background: #7cb342; }\n.acc-report-violation-title {\n flex: 1;\n font-weight: 500;\n color: #333;\n}\n.acc-report-violation-count {\n font-size: 12px;\n color: #666;\n background: #e0e0e0;\n padding: 2px 8px;\n border-radius: 12px;\n}
1035\n.acc-report-violation-details {\n display: none;\n padding: 16px;\n border-top: 1px solid #e0e0e0;\n}\n.acc-report-violation.expanded .acc-report-violation-details {\n display: block;\n}\n.acc-report-violation-desc {\n color: #666;\n font-size: 14px;\n margin-bottom: 12px;\n}\n.acc-report-violation-help {\n font-size: 13px;\n margin-bottom: 12px;\n}\n.acc-report-violation-help a {\n color: #1976d2;\n}\n.acc-report-node {\n background: #f8f9fa;\n border-radius: 6px;\n padding: 12px;\n margin-top: 8px;\n}\n.acc-report-node-html {\n font-family: monospace;\n font-size: 12px;\n background: #263238;\n color: #80cbc4;\n padding: 8px 12px;\n border-radius: 4px;\n overflow-x: auto;\n white-space: pre-wrap;\n word-break: break-all;\n}\n.acc-report-node-fix {\n margin-top: 8px;\n font-size: 13px;\n color: #333;\n}\n.acc-report-node-fix strong {\n color: #1976d2;\n}\n.acc-report-success {\n text-align: center;\n padding: 40px;\n}\n.acc-report-success-icon {\n width: 64px;\n height: 64px;\n background: #43a047;\n border-radius: 50%;\n display: flex;\n align-items: center;\n justify-content: center;\n margin: 0 auto 16px;\n}\n.acc-report-success-icon svg {\n width: 32px;\n height: 32px;\n fill: #fff;\n}\n.acc-report-footer {\n padding: 12px 20px;\n border-top: 1px solid #e0e0e0;\n text-align: center;\n}\n.acc-report-powered {\n font-size: 12px;\n}\n@media (max-width: 600px) {\n .acc-report-dialog {\n width: 95%;\n max-height: 90vh;\n }\n .acc-report-summary {\n grid-template-columns: repeat(2, 1fr);\n }\n}\n"; 1036 1037 var readingGuideCSS = ".acc-rg {\n position: fixed;\n left: 0;\n right: 0;\n width: 100%;\n pointer-events: none;\n background-color: rgba(0, 0, 0, 0.4);\n z-index: calc(var(--acc-widget-z-index, 100000) + 1);\n}\n.acc-rg-top {\n top: 0;\n}\n.acc-rg-bottom {\n bottom: 0;\n}\n/* Softer overlay when high contrast is active */\nbody.acc-high-contrast-mode .acc-rg {\n background-color: rgba(0, 0, 0, 0.25);\n}\n"; 1038 1039 var skipLinkCSS = ".acc-skip-link {\n font-family: inherit;\n position: fixed;\n top: 16px;\n left: 16px;\n background: #ffffff !important;\n background: var(--acc-card-bg, #ffffff) !important;\n color: #222222 !important;\n color: var(--acc-text-color, #222222) !important;\n border: 3px solid #1976d2 !important;\n border: 3px solid var(--acc-primary-color, #1976d2) !important;\n border-radius: var(--acc-button-border-radius, 0.4rem) !important;\n padding: 0 !important;\n z-index: calc(var(--acc-widget-z-index, 100000) + 2) !important;\n width: 1px;\n height: 1px;\n overflow: hidden;\n clip: rect(0, 0, 0, 0);\n clip-path: inset(50%);\n white-space: nowrap;\n pointer-events: none;\n font-size: 16px;\n line-height: 1.2;\n font-weight: 600;\n background-clip: padding-box;\n}\n.acc-skip-link:focus,\n.acc-skip-link:active {\n width: auto;\n height: auto;\n overflow: visible;\n clip: auto;\n clip-path: none;\n white-space: normal;\n padding: 8px 16px !important;\n pointer-events: auto;\n outline: 3px solid #1976d2;\n outline: var(--acc-focus-outline-width, 3px) solid var(--acc-focus-ring-color, #1976d2);\n outline-offset: 2px;\n outline-offset: var(--acc-focus-outline-offset, 2px);\n}\n"; 1040 1041 var annotationsCSS = ".acc-annotation-layer {\n position: absolute;\n top: 0;\n left: 0;\n width: 0;\n height: 0;\n z-index: calc(var(--acc-widget-z-index, 100000) + 5);\n pointer-events: none;\n}\n\n.acc-annotation-marker {\n position: absolute;\n width: 20px;\n height: 20px;\n border: none;\n border-radius: 999px;\n color: #fff;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n box-shadow: 0 2px 6px rgba(0, 0, 0, 0.35);\n cursor: pointer;\n pointer-events: auto;\n transform: translate(-40%, -50%);\n}\n\n.acc-annotation-marker svg {\n width: 12px;\n height: 12px;\n fill: currentColor;\n}\n\n.acc-annotation-marker[data-impact=\"critical\"] {\n background: #b71c1c;\n}\n\n.acc-annotation-marker[data-impact=\"serious\"] {\n background: #d84315;\n}\n\n.acc-annotation-marker[data-impact=\"moderate\"] {\n background: #ef6c00;\n}\n\n.acc-annotation-marker[data-impact=\"minor\"] {\n background: #1565c0;\n}\n\n.acc-annotation-popup {\n position: absolute;\n width: min(320px, 92vw);\n background: #fff;
1041\n color: #1a1a1a;\n border: 1px solid #d7d7d7;\n border-radius: 10px;\n box-shadow: 0 10px 24px rgba(0, 0, 0, 0.24);\n padding: 12px;\n pointer-events: auto;\n}\n\n.acc-annotation-popup-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 10px;\n}\n\n.acc-annotation-popup-title {\n margin: 0;\n font-size: 14px;\n line-height: 1.3;\n}\n\n.acc-annotation-popup-close {\n border: 0;\n background: transparent;\n padding: 2px;\n width: 24px;\n height: 24px;\n color: #444;\n cursor: pointer;\n}\n\n.acc-annotation-popup-close svg {\n width: 20px;\n height: 20px;\n fill: currentColor;\n}\n\n.acc-annotation-popup p {\n margin: 8px 0;\n font-size: 13px;\n line-height: 1.45;\n}\n\n.acc-annotation-popup a {\n color: #0d47a1;\n font-weight: 600;\n text-decoration: underline;\n}\n"; 1042 1043 const STATIC_STYLE_ID = 'acc-static-styles'; 1044 const STATIC_STYLES = [ 1045 menuCSS, 1046 widgetCSS, 1047 reportCSS, 1048 readingGuideCSS, 1049 skipLinkCSS, 1050 annotationsCSS 1051 ].join('\n'); 1052 1053 /** @typedef {import('./index.js').default} AccessibleWebWidget */ 1054 1055 /** @type {{ [methodName: string]: (this: AccessibleWebWidget, ...args: any[]) => any }} */ 1056 const styleMethods = { 1057 1058 findElement(selector, parent = document) { 1059 try { 1060 return parent.querySelector(selector); 1061 } catch (e) { 1062 console.warn(`Failed to query selector: ${selector}`, e); 1063 return null; 1064 } 1065 }, 1066 1067 injectStyle(id, css) { 1068 if (!css || typeof document === 'undefined') return; 1069 try { 1070 let style = document.getElementById(id) || document.createElement('style'); 1071 style.innerHTML = css; 1072 if (!style.id) { 1073 style.id = id; 1074 document.head.appendChild(style); 1075 } 1076 } catch (e) { 1077 console.warn('Error adding stylesheet:', e); 1078 } 1079 }, 1080 1081 createCSS(styles) { 1082 let css = ""; 1083 if (!styles) return css; 1084 const browserPrefixes = ['-o-', '-ms-', '-moz-', '-webkit-', '']; 1085 const prefixedProperties = ['filter']; 1086 for (let key in styles) { 1087 if (!Object.prototype.hasOwnProperty.call(styles, key)) continue; 1088 let prefixes = prefixedProperties.includes(key) ? browserPrefixes : [""]; 1089 prefixes.forEach(prefix => { 1090 css += `${prefix}${key}:${styles[key]} !important;`; 1091 }); 1092 } 1093 return css; 1094 }, 1095 1096 wrapCSS(selector, childrenSelector, css) { 1097 let output = ""; 1098 childrenSelector.forEach(child => { 1099 output += `${selector} ${child}{${css}}`; 1100 }); 1101 return output; 1102 }, 1103 1104 buildCSS(config) { 1105 if (!config) return ""; 1106 let output = ""; 1107 output += this.createCSS(config.styles || {}); 1108 if (output.length && config.selector) { 1109 output = this.wrapCSS(config.selector, config.childrenSelector || [""], output); 1110 } 1111 output += config.css || ""; 1112 return output; 1113 }, 1114 1115 applyToolStyle(config) { 1116 let { id = "", enable = false } = config; 1117 let styleId = `acc-${id}`; 1118 if (enable) { 1119 let css = this.buildCSS(config); 1120 this.injectStyle(styleId, css); 1121 } else { 1122 let style = document.getElementById(styleId); 1123 if (style) style.remove(); 1124 } 1125 document.documentElement.classList.toggle(styleId, enable); 1126 }, 1127 1128 applyThemeVariables() { 1129 if (typeof document === 'undefined') return; 1130 1131 const vars = { 1132 '--acc-primary-color': this.widgetTheme.primaryColor, 1133 '--acc-primary-color-light': this.widgetTheme.primaryColorLight, 1134 '--acc-primary-color-dark': this.widgetTheme.primaryColorDark, 1135 '--acc-bg-color': this.widgetTheme.backgroundColor, 1136 '--acc-text-color': this.widgetTheme.textColor, 1137 '--acc-text-color-inverted': this.widgetTheme.textColorInverted,
1138 '--acc-card-bg': this.widgetTheme.cardBackground, 1139 '--acc-border-color': this.widgetTheme.borderColor, 1140 '--acc-focus-ring-color': this.widgetTheme.focusRingColor, 1141 '--acc-hover-color': this.widgetTheme.hoverColor, 1142 '--acc-active-color': this.widgetTheme.activeColor, 1143 '--acc-border-radius': this.widgetTheme.borderRadius, 1144 '--acc-button-border-radius': this.widgetTheme.buttonBorderRadius, 1145 '--acc-header-height': this.widgetTheme.headerHeight, 1146 '--acc-focus-outline-width': this.widgetTheme.focusBorderWidth, 1147 '--acc-focus-outline-offset': this.widgetTheme.focusOutlineOffset, 1148 '--acc-widget-z-index': String(this.widgetTheme.zIndex), 1149 '--acc-button-size': this.widgetTheme.buttonSize 1150 }; 1151 1152 Object.entries(vars).forEach(([key, value]) => { 1153 document.documentElement.style.setProperty(key, value); 1154 }); 1155 }, 1156 1157 registerStaticStyles() { 1158 if (this.staticStylesRegistered) return; 1159 this.injectStyle(STATIC_STYLE_ID, STATIC_STYLES); 1160 this.staticStylesRegistered = true; 1161 } 1162 1163 }; 1164 1165 /** @typedef {import('./index.js').default} AccessibleWebWidget */ 1166 1167 const AXE_CORE_VERSION = '4.11.1'; 1168 const AXE_CORE_SRC = `https://cdn.jsdelivr.net/npm/axe-core@${AXE_CORE_VERSION}/axe.min.js`; 1169 const AXE_CORE_INTEGRITY = 'sha384-wb3zgvLcZeMFSec08dk7g8K8yDFFAX2uNKVwOUuowwc/wIfE2t6XVUjTEgPrOJCS'; 1170 const AXE_RUN_OPTIONS = { 1171 runOnly: ['wcag2a', 'wcag2aa', 'wcag21a', 'wcag21aa', 'best-practice'] 1172 }; 1173 const MAX_ANNOTATIONS = 50; 1174 const SYSTEM_PREFERS_REDUCED_MOTION = '(prefers-reduced-motion: reduce)'; 1175 1176 /** @type {{ [methodName: string]: (this: AccessibleWebWidget, ...args: any[]) => any }} */ 1177 const featureMethods = { 1178 1179 getContrastToggleDisplay(index) { 1180 if (index === 0) { 1181 return { key: 'light-contrast', label: 'Light', icon: this.widgetIcons.lightContrast }; 1182 } 1183 if (index === 1) { 1184 return { key: 'dark-contrast', label: 'Dark', icon: this.widgetIcons.darkContrast }; 1185 } 1186 return { key: null, label: 'Contrast', icon: this.widgetIcons.contrast }; 1187 }, 1188 1189 updateContrastToggleButton(button, index) { 1190 if (!button) return; 1191 const display = this.getContrastToggleDisplay(index); 1192 const iconNode = button.querySelector('svg'); 1193 if (iconNode) { 1194 iconNode.outerHTML = display.icon; 1195 } else { 1196 button.insertAdjacentHTML('afterbegin', display.icon); 1197 } 1198 1199 const translatedLabel = this.translate(display.label); 1200 const labelNode = button.querySelector('.acc-label'); 1201 if (labelNode) { 1202 labelNode.setAttribute('data-acc-text', display.label); 1203 labelNode.innerText = translatedLabel; 1204 } 1205 button.setAttribute('title', translatedLabel); 1206 button.setAttribute('aria-label', translatedLabel); 1207 button.setAttribute('data-contrast-mode', display.key || 'off'); 1208 }, 1209 1210 getSaturationToggleDisplay(index) { 1211 if (index === 0) { 1212 return { key: 'low-saturation', label: 'Low', icon: this.widgetIcons.lowSaturation }; 1213 } 1214 if (index === 1) { 1215 return { key: 'high-saturation', label: 'High', icon: this.widgetIcons.highSaturation }; 1216 } 1217 return { key: null, label: 'Saturation', icon: this.widgetIcons.saturation }; 1218 }, 1219 1220 updateSaturationToggleButton(button, index) { 1221 if (!button) return; 1222 const display = this.getSaturationToggleDisplay(index); 1223 const iconNode = button.querySelector('svg'); 1224 if (iconNode) { 1225 iconNode.outerHTML = display.icon; 1226 } else { 1227 button.insertAdjacentHTML('afterbegin', display.icon); 1228 } 1229 1230 const translatedLabel = this.translate(display.label); 1231 const labelNode = button.querySelector('.acc-label'); 1232 if (labelNode) { 1233 labelNode.setAttribute('data-acc-text', display.label); 1234 labelNode.innerText = translatedLabel; 1235 } 1236 button.setAttribute('title', translatedLabel); 1237 button.setAttribute('aria-label', translatedLabel); 1238 button.setAttribute('data-saturation-mode', display.key || 'off'); 1239 }, 1240 1241 ensureSkipLink() { 1242 if (typeof document === 'undefined') return null; 1243 if (this.skipLinkElement && document.body.contains(this.skipLinkElement)) { 1244 this.updateSkipLinkLabel(); 1245 return this.skipLinkElement; 1246 } 1247 const existing = document.getElementById('acc-skip-link'); 1248 if (existing) { 1249 this.skipLinkElement = existing; 1250 if (!existing.getAttribute('data-acc-text')) { 1251 existing.setAttribute('data-acc-text', 'Skip to accessibility menu'); 1252 } 1253 this.updateSkipLinkLabel(); 1254 return existing; 1255 } 1256 1257 if (this._skipLinkTabListenerRegistered) return null; 1258 this._skipLinkTabListenerRegistered = true; 1259 1260 const createAndInsert = (event) => { 1261 if (event.key !== 'Tab') return; 1262 document.removeEventListener('keydown', createAndInsert, true); 1263 1264 const button = document.createElement('a'); 1265 button.href = '#'; 1266 button.id = 'acc-skip-link'; 1267 button.className = 'acc-skip-link'; 1268 button.setAttribute('tabindex', '0'); 1269 button.setAttribute('data-acc-text', 'Skip to accessibility menu'); 1270 button.setAttribute('aria-label', this.translate('Skip to accessibility menu')); 1271 button.textContent = this.translate('Skip to accessibility menu'); 1272 1273 button.addEventListener('click', (e) => { 1274 e.preventDefault(); 1275 const toggle = this.widgetToggleButton; 1276 if (!toggle) return; 1277 1278 const currentMenu = this.activeMenuContainer || this.menuContainer; 1279 const menuIsVisible = currentMenu && currentMenu.style.display !== 'none'; 1280 1281 const focusMenu = () => { 1282 const targetMenu = this.activeMenuContainer || this.menuContainer; 1283 if (!targetMenu) return; 1284 const focusables = this.getFocusableElements(targetMenu); 1285 if (focusables.length) { 1286 focusables[0].focus(); 1287 } 1288 }; 1289 1290 if (!menuIsVisible) { 1291 toggle.click(); 1292 requestAnimationFrame(focusMenu); 1293 } else { 1294 focusMenu(); 1295 } 1296 }); 1297 1298 document.body.insertBefore(button, document.body.firstChild); 1299 this.skipLinkElement = button; 1300 this.updateSkipLinkLabel(); 1301 }; 1302 1303 document.addEventListener('keydown', createAndInsert, true); 1304 return null; 1305 }, 1306 1307 updateSkipLinkLabel() { 1308 if (!this.skipLinkElement) return;
1309 const key = this.skipLinkElement.getAttribute('data-acc-text') || 'Skip to accessibility menu'; 1310 const label = this.translate(key); 1311 this.skipLinkElement.textContent = label; 1312 this.skipLinkElement.setAttribute('aria-label', label); 1313 }, 1314 1315 shouldSkipScaling(element) { 1316 return element.closest('.acc-menu, .acc-container, .acc-widget'); 1317 }, 1318 1319 collectDirectTextParents(rootElement = document.body) { 1320 const directTextParents = new Set(); 1321 if (typeof document === 'undefined' || typeof NodeFilter === 'undefined') { 1322 return directTextParents; 1323 } 1324 1325 const root = rootElement instanceof Element ? rootElement : document.body; 1326 if (!root) return directTextParents; 1327 1328 const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT, { 1329 acceptNode: (node) => { 1330 if (!node || !node.textContent || !node.textContent.trim()) { 1331 return NodeFilter.FILTER_REJECT; 1332 } 1333 const parent = node.parentElement; 1334 if (!parent) return NodeFilter.FILTER_REJECT; 1335 if (this.shouldSkipScaling(parent)) return NodeFilter.FILTER_REJECT; 1336 if (parent.closest('script,style,noscript,textarea,pre,code,svg')) { 1337 return NodeFilter.FILTER_REJECT; 1338 } 1339 if (parent.matches(this.textScaleSelectors)) { 1340 return NodeFilter.FILTER_REJECT; 1341 } 1342 return NodeFilter.FILTER_ACCEPT; 1343 } 1344 }); 1345 1346 let currentNode = walker.nextNode(); 1347 while (currentNode) { 1348 const parent = currentNode.parentElement; 1349 if (parent) { 1350 directTextParents.add(parent); 1351 } 1352 currentNode = walker.nextNode(); 1353 } 1354 1355 return directTextParents; 1356 }, 1357 1358 applyScaleToElement(element, multiplier) { 1359 if ( 1360 !element || 1361 !(element instanceof Element) || 1362 this.shouldSkipScaling(element) || 1363 element.classList.contains('material-icons') || 1364 element.classList.contains('fa') 1365 ) { 1366 return; 1367 } 1368 const baseAttr = 'data-acc-baseSize'; 1369 if (!element.hasAttribute(baseAttr)) { 1370 const computedSize = parseFloat(window.getComputedStyle(element).fontSize); 1371 if (Number.isNaN(computedSize) || computedSize <= 0) { 1372 return; 1373 } 1374 element.setAttribute(baseAttr, String(computedSize)); 1375 } 1376 const baseSize = parseFloat(element.getAttribute(baseAttr)); 1377 if (Number.isNaN(baseSize) || baseSize <= 0) { 1378 return; 1379 } 1380 element.style.fontSize = `${baseSize * multiplier}px`; 1381 }, 1382 1383 ensureTextScaleObserver() { 1384 if (this.textScaleObserver || !document.body) return; 1385 this.textScaleObserver = new MutationObserver((mutations) => { 1386 if (Math.abs(this.currentTextScaleMultiplier - 1) < 0.001) { 1387 return; 1388 } 1389 const pending = new Set(); 1390 mutations.forEach(mutation => { 1391 mutation.addedNodes.forEach(node => { 1392 if (!(node instanceof Element)) return; 1393 if (node.matches && node.matches(this.textScaleSelectors)) { 1394 pending.add(node); 1395 } 1396 node.querySelectorAll?.(this.textScaleSelectors).forEach(el => pending.add(el)); 1397 this.collectDirectTextParents(node).forEach(el => pending.add(el)); 1398 }); 1399 }); 1400 if (!pending.size) return; 1401 pending.forEach(el => this.applyScaleToElement(el, this.currentTextScaleMultiplier)); 1402 }); 1403 this.textScaleObserver.observe(document.body, { childList: true, subtree: true }); 1404 }, 1405 1406 disconnectTextScaleObserver() { 1407 if (!this.textScaleObserver) return; 1408 this.textScaleObserver.disconnect(); 1409 this.textScaleObserver = null; 1410 }, 1411 1412 scaleText(multiply = 1) { 1413 try { 1414 const numericMultiply = Number(multiply); 1415 const resolvedMultiply = Number.isFinite(numericMultiply) && numericMultiply > 0 ? numericMultiply : 1; 1416 const isDefaultScale = Math.abs(resolvedMultiply - 1) < 0.001; 1417 this.currentTextScaleMultiplier = resolvedMultiply; 1418 if (!isDefaultScale) { 1419 this.ensureTextScaleObserver(); 1420 const elements = document.querySelectorAll(this.textScaleSelectors);
1421 elements.forEach(el => this.applyScaleToElement(el, resolvedMultiply)); 1422 this.collectDirectTextParents(document.body).forEach(el => this.applyScaleToElement(el, resolvedMultiply)); 1423 } else { 1424 this.disconnectTextScaleObserver(); 1425 const scaledElements = document.querySelectorAll('[data-acc-baseSize]'); 1426 scaledElements.forEach(el => { 1427 if (this.shouldSkipScaling(el)) return; 1428 el.style.fontSize = ''; 1429 el.removeAttribute('data-acc-baseSize'); 1430 }); 1431 } 1432 } catch (e) { 1433 console.warn('Error scaling text:', e); 1434 } 1435 }, 1436 1437 clampTextScalePercent(percent) { 1438 const min = Number(this.textScaleMinPercent) || 80; 1439 const max = Number(this.textScaleMaxPercent) || 150; 1440 const step = Number(this.textScaleStepPercent) || 5; 1441 const numeric = Number(percent); 1442 if (!Number.isFinite(numeric)) return 100; 1443 const snapped = Math.round((numeric - min) / step) * step + min; 1444 return Math.min(max, Math.max(min, snapped)); 1445 }, 1446 1447 getTextScalePercent(scaleValue = 1) { 1448 if (scaleValue === false || scaleValue === null || typeof scaleValue === 'undefined') { 1449 return this.clampTextScalePercent(100); 1450 } 1451 const numeric = Number(scaleValue); 1452 if (!Number.isFinite(numeric)) { 1453 return this.clampTextScalePercent(100); 1454 } 1455 if (numeric <= 0) { 1456 return this.clampTextScalePercent(100); 1457 } 1458 const percent = numeric > 10 ? numeric : numeric * 100; 1459 return this.clampTextScalePercent(percent); 1460 }, 1461 1462 syncTextScaleControlUI(menu, scaleValue = 1) { 1463 if (!menu || !menu.querySelector) return; 1464 const range = menu.querySelector('.acc-text-scale-range'); 1465 const label = menu.querySelector('.acc-text-scale-percent'); 1466 const percent = this.getTextScalePercent(scaleValue); 1467 if (range) { 1468 const min = Number(this.textScaleMinPercent) || 80; 1469 const max = Number(this.textScaleMaxPercent) || 150; 1470 const progress = ((percent - min) / (max - min)) * 100;
1471 range.value = String(percent); 1472 range.style.setProperty('--acc-text-scale-progress', `${Math.max(0, Math.min(100, progress))}%`); 1473 } 1474 if (label) { 1475 label.textContent = `${percent}%`; 1476 } 1477 }, 1478 1479 setTextScaleFromPercent(percent, options = {}) { 1480 const shouldPersist = options.persist !== false; 1481 const clampedPercent = this.getTextScalePercent(percent); 1482 const multiplier = Number((clampedPercent / 100).toFixed(2)); 1483 const exactIndex = this.textScaleValues.indexOf(multiplier); 1484 1485 if (this.multiLevelFeatures['text-scale']) { 1486 this.multiLevelFeatures['text-scale'].currentIndex = exactIndex; 1487 } 1488 1489 if (exactIndex > -1) { 1490 this.textScaleIndex = exactIndex; 1491 } else { 1492 let nearestIndex = 0; 1493 let minDistance = Infinity; 1494 this.textScaleValues.forEach((value, index) => { 1495 const distance = Math.abs(value - multiplier); 1496 if (distance < minDistance) { 1497 minDistance = distance; 1498 nearestIndex = index; 1499 } 1500 }); 1501 this.textScaleIndex = nearestIndex; 1502 } 1503 1504 this.scaleText(multiplier); 1505 1506 if (shouldPersist) { 1507 this.updateState({ 'text-scale': multiplier }); 1508 } 1509 1510 return multiplier; 1511 }, 1512 1513 enableBoldText(enable = false) { 1514 const contentSelector = [ 1515 '*', 1516 ':not(.material-icons)', 1517 ':not(.material-icons-outlined)', 1518 ':not(.material-icons-round)', 1519 ':not(.material-symbols-outlined)', 1520 ':not(.material-symbols-rounded)', 1521 ':not(.fa)', 1522 ':not(.fas)', 1523 ':not(.far)', 1524 ':not(.fab)', 1525 ':not(.fa-solid)', 1526 ':not(.fa-regular)', 1527 ':not(.fa-brands)', 1528 ':not(.glyphicon)', 1529 ':not(.icon)', 1530 ':not(.icons)', 1531 ':not([class*="icon-"])', 1532 ':not([data-icon])' 1533 ].join(''); 1534 const config = { 1535 id: "bold-text", 1536 selector: "body", 1537 childrenSelector: [contentSelector], 1538 styles: { 'font-weight': '700' }, 1539 css: ` 1540 .acc-container, .acc-container *, .acc-menu, .acc-menu * { 1541 font-weight: inherit !important; 1542 } 1543 input::placeholder, textarea::placeholder { 1544 font-weight: 700 !important; 1545 } 1546 ` 1547 };
1548 this.applyToolStyle({ ...config, enable }); 1549 }, 1550 1551 adjustLetterSpacing(enable = false) { 1552 const config = { 1553 id: "letter-spacing", 1554 selector: "html", 1555 childrenSelector: ['', '*:not(.material-icons,.acc-menu,.acc-menu *, .acc-widget, .acc-widget *)'], 1556 styles: { 'letter-spacing': '2px' } 1557 }; 1558 this.applyToolStyle({ ...config, enable }); 1559 }, 1560 1561 adjustLineSpacing(enable = false) { 1562 const config = { 1563 id: "line-spacing", 1564 selector: "html", 1565 childrenSelector: ['', '*:not(.material-icons,.acc-menu,.acc-menu *, .acc-widget, .acc-widget *)'], 1566 styles: { 'line-height': '3' } 1567 }; 1568 this.applyToolStyle({ ...config, enable }); 1569 }, 1570 1571 enableLargePointer(enable = false) { 1572 const config = { 1573 id: "large-pointer", 1574 selector: "body", 1575 childrenSelector: ['*'], 1576 styles: { 1577 'cursor': `url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='98px' height='98px' viewBox='0 0 48 48'%3E%3Cpath fill='%23FFFFFF' d='M27.8 39.7c-.1 0-.2 0-.4-.1s-.4-.3-.6-.5l-3.7-8.6-4.5 4.2c-.1.2-.3.3-.6.3-.1 0-.3 0-.4-.1-.3-.1-.6-.5-.6-.9V12c0-.4.2-.8.6-.9.1-.1.3-.1.4-.1.2 0 .5.1.7.3l16 15c.3.3.4.7.3 1.1-.1.4-.5.6-.9.7l-6.3.6 3.9 8.5c.1.2.1.5 0 .8-.1.2-.3.5-.5.6l-2.9 1.3c-.2-.2-.4-.2-.5-.2z'/%3E%3Cpath fill='%23212121' d='m18 12 16 15-7.7.7 4.5 9.8-2.9 1.3-4.3-9.9L18 34V12m0-2c-.3 0-.5.1-.8.2-.7.3-1.2 1-1.2 1.8v22c0 .8.5 1.5 1.2 1.8.3.2.6.2.8.2.5 0 1-.2 1.4-.5l3.4-3.2 3.1 7.3c.2.5.6.9 1.1 1.1.2.1.5.1.7.1.3 0 .5-.1.8-.2l2.9-1.3c.5-.2.9-.6 1.1-1.1.2-.5.2-1.1 0-1.5l-3.3-7.2 4.9-.4c.8-.1 1.5-.6 1.7-1.3.3-.7.1-1.6-.5-2.1l-16-15c-.3-.5-.8-.7-1.3-.7z'/%3E%3C/svg%3E") 40 15, auto` 1578 } 1579 };
1580 this.applyToolStyle({ ...config, enable }); 1581 }, 1582 1583 highlightLinks(enable = false) { 1584 const config = { 1585 id: "highlight-links", 1586 selector: "html", 1587 childrenSelector: [ 1588 "a[href]:not(.acc-menu a, .acc-widget a)", 1589 "summary.header__menu-item:not(.acc-menu summary, .acc-widget summary)", 1590 "summary.link:not(.acc-menu summary, .acc-widget summary)", 1591 "summary[role='button']:not(.acc-menu summary, .acc-widget summary)", 1592 "details > summary.list-menu__item:not(.acc-menu summary, .acc-widget summary)", 1593 ".header__menu-item[role='button']:not(.acc-menu *, .acc-widget *)" 1594 ], 1595 styles: { 'outline': `3px solid ${this.widgetTheme.primaryColor}`, 'outline-offset': '2px' } 1596 }; 1597 this.applyToolStyle({ ...config, enable }); 1598 }, 1599 1600 highlightTitles(enable = false) { 1601 const config = { 1602 id: "highlight-title", 1603 selector: "html", 1604 childrenSelector: this.targetSelectors.HEADERS, 1605 styles: { 'outline': `3px solid ${this.widgetTheme.primaryColor}`, 'outline-offset': '2px' } 1606 };
1607 this.applyToolStyle({ ...config, enable }); 1608 }, 1609 1610 ensureReadableFontLoaded() { 1611 if (this.readableFontLoaded) return; 1612 const existing = document.getElementById('acc-readable-text-font'); 1613 if (existing) { 1614 this.readableFontLoaded = true; 1615 return; 1616 } 1617 const style = document.createElement('style'); 1618 style.id = 'acc-readable-text-font'; 1619 style.textContent = ` 1620 @font-face { 1621 font-family: "OpenDyslexic3"; 1622 src: url("https://website-widgets.pages.dev/fonts/OpenDyslexic3-Regular.woff") format("woff"), 1623 url("https://website-widgets.pages.dev/fonts/OpenDyslexic3-Regular.ttf") format("truetype"); 1624 font-display: swap; 1625 } 1626 `; 1627 document.head.appendChild(style); 1628 this.readableFontLoaded = true; 1629 }, 1630 1631 enableReadableText(enable = false) { 1632 const shouldEnable = !!enable; 1633 if (shouldEnable) { 1634 this.ensureReadableFontLoaded(); 1635 } 1636 const contentSelector = [ 1637 '*', 1638 ':not(.material-icons)', 1639 ':not(.material-icons-outlined)', 1640 ':not(.material-icons-round)', 1641 ':not(.material-symbols-outlined)', 1642 ':not(.material-symbols-rounded)', 1643 ':not(.fa)', 1644 ':not(.fas)', 1645 ':not(.far)', 1646 ':not(.fab)', 1647 ':not(.fa-solid)', 1648 ':not(.fa-regular)', 1649 ':not(.fa-brands)', 1650 ':not(.glyphicon)', 1651 ':not(.icon)', 1652 ':not(.icons)', 1653 ':not([class*="icon-"])', 1654 ':not([data-icon])' 1655 ].join(''); 1656 const config = { 1657 id: "readable-text", 1658 selector: "body", 1659 childrenSelector: [contentSelector], 1660 styles: { 1661 'font-family': '"OpenDyslexic3", "Comic Sans MS", Arial, Helvetica, sans-serif' 1662 }, 1663 css: ` 1664 .acc-container, .acc-container *, .acc-menu, .acc-menu * { 1665 font-family: inherit !important; 1666 } 1667 input::placeholder, textarea::placeholder { 1668 font-family: "OpenDyslexic3", "Comic Sans MS", Arial, Helvetica, sans-serif !important; 1669 } 1670 ` 1671 };
1672 this.applyToolStyle({ ...config, enable: shouldEnable }); 1673 }, 1674 1675 pauseMotion(enable = false) { 1676 const config = { 1677 id: "pause-motion", 1678 selector: "html", 1679 childrenSelector: ['*'], 1680 styles: { 'transition': 'none', 'animation-fill-mode': 'forwards', 'animation-iteration-count': '1', 'animation-duration': '.01s' } 1681 }; 1682 this.applyToolStyle({ ...config, enable }); 1683 }, 1684 1685 enableHighContrastMode(enable = false) { 1686 const X = ':not(.acc-container):not(.acc-container *):not(.acc-rg-container):not(.acc-rg-container *)'; 1687 const config = { 1688 id: 'high-contrast-mode', 1689 css: ` 1690 /* ââ Base: force black on white ââ */ 1691 body.acc-high-contrast-mode *${X} { 1692 color: #000 !important; 1693 background-color: #fff !important; 1694 background-image: none !important; 1695 text-shadow: none !important; 1696 box-shadow: none !important; 1697 } 1698 1699 body.acc-high-contrast-mode { 1700 background: #fff !important; 1701 } 1702 1703 /* ââ Borders: solid and visible ââ */ 1704 body.acc-high-contrast-mode *${X} { 1705 border-color: #000 !important; 1706 } 1707 1708 /* ââ Links: underlined, distinct color ââ */ 1709 body.acc-high-contrast-mode a${X} { 1710 color: #00e !important; 1711 text-decoration: underline !important; 1712 } 1713 1714 body.acc-high-contrast-mode a:visited${X} { 1715 color: #551a8b !important; 1716 } 1717 1718 /* ââ Headings: bold black ââ */ 1719 body.acc-high-contrast-mode :where(h1,h2,h3,h4,h5,h6)${X} { 1720 color: #000 !important; 1721 font-weight: 700 !important; 1722 } 1723 1724 /* ââ Images: keep visible, add border ââ */ 1725 body.acc-high-contrast-mode img${X} { 1726 border: 1px solid #000 !important; 1727 } 1728 1729 /* ââ Inputs & buttons: high-contrast borders ââ */ 1730 body.acc-high-contrast-mode :where(input, textarea, select, button)${X} { 1731 border: 2px solid #000 !important; 1732 background: #fff !important; 1733 color: #000 !important; 1734 } 1735 1736 /* ââ Focus rings: thick, high-visibility ââ */ 1737 body.acc-high-contrast-mode :focus-visible${X} { 1738 outline: 3px solid #000 !important; 1739 outline-offset: 2px !important; 1740 } 1741 1742 /* ââ Tables: visible cell borders ââ */ 1743 body.acc-high-contrast-mode :where(table, th, td)${X} { 1744 border: 1px solid #000 !important; 1745 } 1746 ` 1747 };
1748 this.applyToolStyle({ ...config, enable }); 1749 document.body?.classList.toggle('acc-high-contrast-mode', !!enable); 1750 }, 1751 1752 enableReadingAid(enable = false) { 1753 try { 1754 let container = this.findElement('.acc-rg-container'); 1755 const guideHeight = 100; // Height of the clear reading window 1756 1757 if (enable) { 1758 // Always clean up existing listener first to prevent duplicates 1759 if (window.__accweb__scrollGuide) { 1760 document.removeEventListener('mousemove', window.__accweb__scrollGuide); 1761 delete window.__accweb__scrollGuide; 1762 } 1763 1764 if (!container) { 1765 // Create container but don't make it visible yet 1766 container = document.createElement('div'); 1767 container.setAttribute('class', 'acc-rg-container'); 1768 container.setAttribute('aria-hidden', 'true'); 1769 container.innerHTML = this.readingAidTemplate; 1770 document.body.prepend(container); 1771 } 1772 1773 const rgTop = container.querySelector('.acc-rg-top'); 1774 const rgBottom = container.querySelector('.acc-rg-bottom'); 1775 1776 // Initially hide the guide completely 1777 rgTop.style.display = 'none'; 1778 rgBottom.style.display = 'none'; 1779 this.readingAidVisible = false; 1780 1781 const updateGuidePosition = this.throttle((event) => { 1782 // If first mouse movement after enabling, make the guide visible 1783 if (!this.readingAidVisible) { 1784 rgTop.style.display = 'block'; 1785 rgBottom.style.display = 'block'; 1786 this.readingAidVisible = true; 1787 } 1788 1789 // Position the guide at the mouse Y coordinate 1790 const mouseY = event.clientY; 1791 const topHeight = Math.max(0, mouseY - guideHeight / 2); 1792 const bottomHeight = Math.max(0, window.innerHeight - (mouseY + guideHeight / 2)); 1793 rgTop.style.height = `${topHeight}px`; 1794 rgBottom.style.height = `${bottomHeight}px`; 1795 }, 16); 1796 1797 window.__accweb__scrollGuide = updateGuidePosition; 1798 document.addEventListener('mousemove', updateGuidePosition, { passive: true }); 1799 } else { 1800 // Reset visibility flag when disabled 1801 this.readingAidVisible = false; 1802 1803 // Clean up event listener 1804 if (window.__accweb__scrollGuide) { 1805 document.removeEventListener('mousemove', window.__accweb__scrollGuide); 1806 delete window.__accweb__scrollGuide; 1807 } 1808 1809 // Remove container 1810 if (container) { 1811 container.remove(); 1812 } 1813 } 1814 } catch (e) { 1815 console.warn('Error with reading aid:', e); 1816 } 1817 }, 1818 1819 loadAxeCore() { 1820 if (this.axeCoreLoaded && window.axe) { 1821 return Promise.resolve(window.axe); 1822 } 1823 1824 if (this.axeCorePromise) { 1825 return this.axeCorePromise; 1826 } 1827 1828 this.axeCoreLoading = true; 1829 this.axeCorePromise = new Promise((resolve, reject) => { 1830 let script = document.querySelector('script[data-acc-axe-core="true"]'); 1831 let settled = false; 1832 let timeoutId = null; 1833 1834 const settleSuccess = () => { 1835 if (settled) return; 1836 settled = true; 1837 if (timeoutId) clearTimeout(timeoutId); 1838 if (!window.axe) { 1839 this.axeCoreLoading = false; 1840 this.axeCoreLoaded = false; 1841 this.axeCorePromise = null; 1842 reject(new Error('axe-core loaded but window.axe is unavailable')); 1843 return; 1844 } 1845 this.axeCoreLoading = false; 1846 this.axeCoreLoaded = true; 1847 if (script) { 1848 script.setAttribute('data-acc-axe-core-loaded', 'true'); 1849 } 1850 resolve(window.axe); 1851 }; 1852 1853 const settleError = (error) => { 1854 if (settled) return; 1855 settled = true; 1856 if (timeoutId) clearTimeout(timeoutId); 1857 this.axeCoreLoading = false; 1858 this.axeCoreLoaded = false; 1859 this.axeCorePromise = null; 1860 reject(error); 1861 }; 1862 1863 if (window.axe) { 1864 settleSuccess(); 1865 return; 1866 } 1867 1868 if (script && !script.src.includes(`/axe-core@${AXE_CORE_VERSION}/`)) { 1869 script.remove(); 1870 script = null; 1871 } 1872 1873 if (!script) { 1874 script = document.createElement('script'); 1875 script.src = AXE_CORE_SRC; 1876 script.async = true; 1877 script.integrity = AXE_CORE_INTEGRITY; 1878 script.crossOrigin = 'anonymous'; 1879 script.setAttribute('data-acc-axe-core', 'true'); 1880 document.head.appendChild(script); 1881 } 1882 1883 script.addEventListener('load', settleSuccess, { once: true });
1884 script.addEventListener('error', () => settleError(new Error('Failed to load axe-core')), { once: true }); 1885 timeoutId = setTimeout(() => { 1886 settleError(new Error('Timed out loading axe-core')); 1887 }, 15000); 1888 }); 1889 1890 return this.axeCorePromise; 1891 }, 1892 1893 getAxeRunOptions() { 1894 return { ...AXE_RUN_OPTIONS }; 1895 }, 1896 1897 getViolationCounts(results = {}) { 1898 const counts = { critical: 0, serious: 0, moderate: 0, minor: 0 }; 1899 const violations = Array.isArray(results.violations) ? results.violations : []; 1900 violations.forEach((violation) => { 1901 const impact = violation?.impact; 1902 if (impact && counts[impact] !== undefined) { 1903 counts[impact] += Array.isArray(violation.nodes) ? violation.nodes.length : 0; 1904 } 1905 }); 1906 return counts; 1907 }, 1908 1909 updateViolationBubble(results = null) { 1910 const bubble = this.violationBubble || this.findElement('.acc-violation-bubble'); 1911 if (!bubble) return; 1912 1913 const counts = this.getViolationCounts(results || this.axeScanResults || {}); 1914 const devMode = this.isDevMode(); 1915 1916 let displayCount = 0; 1917 let severity = ''; 1918 1919 if (counts.critical > 0) { 1920 displayCount = counts.critical; 1921 severity = 'critical'; 1922 } else if (devMode && counts.serious > 0) { 1923 displayCount = counts.serious; 1924 severity = 'serious'; 1925 } else if (devMode && counts.moderate > 0) { 1926 displayCount = counts.moderate; 1927 severity = 'moderate'; 1928 } 1929 1930 if (displayCount <= 0) { 1931 bubble.textContent = ''; 1932 bubble.hidden = true; 1933 bubble.removeAttribute('data-severity'); 1934 return; 1935 } 1936 1937 bubble.hidden = false; 1938 bubble.dataset.severity = severity; 1939 bubble.textContent = displayCount > 99 ? '99+' : String(displayCount); 1940 }, 1941 1942 async runBackgroundAxeScan({ force = false } = {}) { 1943 if (!force && this.axeScanResults) { 1944 this.updateViolationBubble(this.axeScanResults); 1945 return this.axeScanResults; 1946 } 1947 1948 if (!force && this.axeScanPromise) { 1949 return this.axeScanPromise; 1950 } 1951 1952 this.axeScanPromise = (async () => { 1953 try { 1954 const axe = await this.loadAxeCore(); 1955 const results = await axe.run(document, this.getAxeRunOptions()); 1956 this.axeScanResults = results; 1957 this.updateViolationBubble(results); 1958 return results; 1959 } catch (error) { 1960 this.updateViolationBubble({ violations: [] }); 1961 throw error; 1962 } finally { 1963 this.axeScanPromise = null; 1964 } 1965 })(); 1966 1967 return this.axeScanPromise; 1968 }, 1969 1970 ensureMediaQuery(query) { 1971 if (!query || typeof window === 'undefined' || typeof window.matchMedia !== 'function') { 1972 return null; 1973 } 1974 if (!this.systemPreferenceMediaQueries) { 1975 this.systemPreferenceMediaQueries = {}; 1976 } 1977 if (!this.systemPreferenceMediaQueries[query]) { 1978 this.systemPreferenceMediaQueries[query] = window.matchMedia(query); 1979 } 1980 return this.systemPreferenceMediaQueries[query]; 1981 }, 1982 1983 applySystemMotionPreference(shouldEnable) { 1984 this.loadConfig(); 1985 if (this.hasExplicitStatePreference('pause-motion')) { 1986 return false; 1987 } 1988 1989 const currentValue = !!this.retrieveState('pause-motion'); 1990 if (currentValue === shouldEnable && this.isSystemControlledPreference('pause-motion')) { 1991 return false; 1992 } 1993 1994 this.updateState({ 'pause-motion': shouldEnable }, { source: 'system' }); 1995 return true; 1996 }, 1997 1998 detectSystemPreferences() { 1999 if (typeof window === 'undefined' || typeof window.matchMedia !== 'function') return; 2000 2001 const reducedMotionQuery = this.ensureMediaQuery(SYSTEM_PREFERS_REDUCED_MOTION); 2002 const shouldPauseMotion = !!reducedMotionQuery?.matches; 2003 const motionChanged = this.applySystemMotionPreference(shouldPauseMotion); 2004 2005 if (motionChanged) { 2006 this.applyEnhancements(); 2007 } 2008 }, 2009 2010 clearSystemPreferenceListeners() { 2011 const listeners = Array.isArray(this.systemPreferenceListeners) ? this.systemPreferenceListeners : [];
2012 listeners.forEach((remove) => { 2013 if (typeof remove === 'function') { 2014 remove(); 2015 } 2016 }); 2017 this.systemPreferenceListeners = []; 2018 }, 2019 2020 setupMediaQueryListeners() { 2021 this.clearSystemPreferenceListeners(); 2022 if (typeof window === 'undefined' || typeof window.matchMedia !== 'function') return; 2023 2024 const reducedMotionQuery = this.ensureMediaQuery(SYSTEM_PREFERS_REDUCED_MOTION); 2025 2026 const listen = (mediaQuery, handler) => { 2027 if (!mediaQuery || typeof handler !== 'function') return; 2028 if (typeof mediaQuery.addEventListener === 'function') { 2029 mediaQuery.addEventListener('change', handler); 2030 this.systemPreferenceListeners.push(() => mediaQuery.removeEventListener('change', handler)); 2031 } else if (typeof mediaQuery.addListener === 'function') { 2032 mediaQuery.addListener(handler); 2033 this.systemPreferenceListeners.push(() => mediaQuery.removeListener(handler)); 2034 } 2035 }; 2036 2037 const onReducedMotionChange = (event) => { 2038 const changed = this.applySystemMotionPreference(!!event.matches); 2039 if (changed) { 2040 this.applyEnhancements(); 2041 } 2042 }; 2043 2044 listen(reducedMotionQuery, onReducedMotionChange); 2045 }, 2046 2047 async runAccessibilityReport() { 2048 // Create or get report panel 2049 let panel = this.findElement('.acc-report-panel'); 2050 2051 if (!panel) { 2052 panel = this.createReportPanel(); 2053 document.body.appendChild(panel); 2054 } 2055 2056 this.openReportPanel(panel); 2057 2058 const contentArea = this.findElement('.acc-report-content', panel); 2059 const statusArea = this.findElement('.acc-report-status', panel); 2060 2061 // Show loading state 2062 statusArea.textContent = this.translate('Loading...'); 2063 contentArea.innerHTML = `<div class="acc-report-loading">${this.translate('Analyzing page...')}</div>`; 2064 2065 try { 2066 const results = await this.runBackgroundAxeScan({ force: true }); 2067 2068 this.displayReportResults(panel, results); 2069 2070 } catch (error) { 2071 contentArea.innerHTML = `<div class="acc-report-error">Error: ${error.message}</div>`; 2072 statusArea.textContent = ''; 2073 } 2074 }, 2075 2076 getReportFocusableElements(panel) { 2077 if (!panel) return []; 2078 const dialog = this.findElement('.acc-report-dialog', panel); 2079 return this.getFocusableElements(dialog || panel); 2080 }, 2081 2082 openReportPanel(panel) { 2083 if (!panel) return; 2084 2085 this.reportPreviousFocus = document.activeElement && typeof document.activeElement.focus === 'function' 2086 ? document.activeElement 2087 : null; 2088 2089 panel.classList.add('acc-report-visible'); 2090 panel.setAttribute('aria-hidden', 'false'); 2091 2092 const dialog = this.findElement('.acc-report-dialog', panel); 2093 const focusTarget = dialog || panel; 2094 if (!focusTarget.hasAttribute('tabindex')) { 2095 focusTarget.setAttribute('tabindex', '-1'); 2096 } 2097 2098 if (this.reportKeyListener) { 2099 document.removeEventListener('keydown', this.reportKeyListener, true); 2100 } 2101 2102 this.reportKeyListener = (event) => { 2103 if (!panel.classList.contains('acc-report-visible')) return; 2104 2105 if (event.key === 'Escape' || event.key === 'Esc') { 2106 event.preventDefault(); 2107 event.stopPropagation(); 2108 this.closeReportPanel(); 2109 return; 2110 } 2111 2112 if (event.key !== 'Tab') return; 2113 2114 const focusables = this.getReportFocusableElements(panel); 2115 if (!focusables.length) { 2116 event.preventDefault(); 2117 focusTarget.focus(); 2118 return; 2119 } 2120 2121 const first = focusables[0]; 2122 const last = focusables[focusables.length - 1]; 2123 const active = document.activeElement; 2124 const outsidePanel = !panel.contains(active); 2125 2126 if (event.shiftKey) { 2127 if (active === first || outsidePanel) { 2128 event.preventDefault(); 2129 last.focus(); 2130 } 2131 return; 2132 } 2133 2134 if (active === last || outsidePanel) { 2135 event.preventDefault(); 2136 first.focus(); 2137 } 2138 }; 2139 2140 document.addEventListener('keydown', this.reportKeyListener, true); 2141 2142 requestAnimationFrame(() => { 2143 const focusables = this.getReportFocusableElements(panel); 2144 if (focusables.length) { 2145 focusables[0].focus(); 2146 return; 2147 }
2148 focusTarget.focus(); 2149 }); 2150 }, 2151 2152 createReportPanel() { 2153 const panel = document.createElement('div'); 2154 panel.className = 'acc-report-panel acc-container'; 2155 panel.setAttribute('role', 'dialog'); 2156 panel.setAttribute('aria-modal', 'true'); 2157 panel.setAttribute('aria-hidden', 'true'); 2158 panel.setAttribute('aria-labelledby', 'acc-report-title'); 2159 2160 panel.innerHTML = ` 2161 <div class="acc-report-overlay"></div> 2162 <div class="acc-report-dialog"> 2163 <div class="acc-report-header"> 2164 <h2 id="acc-report-title" class="acc-report-title">${this.translate('Accessibility Report')}</h2> 2165 <button type="button" class="acc-report-close" aria-label="${this.translate('Close')}">${this.widgetIcons.close}</button> 2166 </div> 2167 <div class="acc-report-status"></div> 2168 <div class="acc-report-content"></div> 2169 <div class="acc-report-footer"> 2170 <span class="acc-report-powered">Powered by axe-core</span> 2171 </div> 2172 </div> 2173 `; 2174 2175 // Close handlers 2176 const closeBtn = panel.querySelector('.acc-report-close'); 2177 const overlay = panel.querySelector('.acc-report-overlay'); 2178 2179 closeBtn.addEventListener('click', () => this.closeReportPanel()); 2180 overlay.addEventListener('click', () => this.closeReportPanel()); 2181 2182 return panel; 2183 }, 2184 2185 displayReportResults(panel, results) { 2186 const contentArea = this.findElement('.acc-report-content', panel); 2187 const statusArea = this.findElement('.acc-report-status', panel); 2188 2189 const violations = results.violations || []; 2190 const passes = results.passes || []; 2191 const incomplete = results.incomplete || []; 2192 2193 // Count by severity 2194 const counts = { critical: 0, serious: 0, moderate: 0, minor: 0 }; 2195 violations.forEach(v => { 2196 if (counts[v.impact] !== undefined) { 2197 counts[v.impact] += v.nodes.length; 2198 } 2199 }); 2200 2201 const totalViolations = Object.values(counts).reduce((a, b) => a + b, 0); 2202 2203 statusArea.textContent = totalViolations > 0 2204 ? `${totalViolations} ${this.translate('Violations Found')}` 2205 : this.translate('No Issues Found'); 2206 2207 let html = ` 2208 <div class="acc-report-summary"> 2209 <div class="acc-report-stat critical"> 2210 <span class="acc-report-stat-value">${counts.critical}</span> 2211 <span class="acc-report-stat-label">${this.translate('Critical')}</span> 2212 </div> 2213 <div class="acc-report-stat serious"> 2214 <span class="acc-report-stat-value">${counts.serious}</span> 2215 <span class="acc-report-stat-label">${this.translate('Serious')}</span> 2216 </div> 2217 <div class="acc-report-stat moderate"> 2218 <span class="acc-report-stat-value">${counts.moderate}</span> 2219 <span class="acc-report-stat-label">${this.translate('Moderate')}</span> 2220 </div> 2221 <div class="acc-report-stat minor"> 2222 <span class="acc-report-stat-value">${counts.minor}</span> 2223 <span class="acc-report-stat-label">${this.translate('Minor')}</span> 2224 </div> 2225 <div class="acc-report-stat passed"> 2226 <span class="acc-report-stat-value">${passes.length}</span> 2227 <span class="acc-report-stat-label">${this.translate('Passed Tests')}</span> 2228 </div> 2229 </div> 2230 `; 2231 2232 if (violations.length === 0) { 2233 html += ` 2234 <div class="acc-report-success"> 2235 <div class="acc-report-success-icon"> 2236 <svg viewBox="0 0 24 24"><path d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41L9 16.17z"/></svg> 2237 </div> 2238 <h3>${this.translate('No Issues Found')}</h3> 2239 </div> 2240 `; 2241 } else { 2242 html += `<div class="acc-report-section"> 2243 <div class="acc-report-section-title">${this.translate('Violations Found')} (${violations.length})</div> 2244 `; 2245 2246 // Sort by severity 2247 const severityOrder = { critical: 0, serious: 1, moderate: 2, minor: 3 }; 2248 violations.sort((a, b) => (severityOrder[a.impact] || 4) - (severityOrder[b.impact] || 4)); 2249 2250 violations.forEach((violation, index) => { 2251 html += ` 2252 <div class="acc-report-violation" data-index="${index}"> 2253 <div class="acc-report-violation-header">
2254 <span class="acc-report-violation-impact ${violation.impact}">${this.translate(this.capitalizeFirst(violation.impact))}</span> 2255 <span class="acc-report-violation-title">${this.escapeHtml(violation.help)}</span> 2256 <span class="acc-report-violation-count">${violation.nodes.length} ${this.translate('Element')}${violation.nodes.length > 1 ? 's' : ''}</span> 2257 </div> 2258 <div class="acc-report-violation-details"> 2259 <p class="acc-report-violation-desc">${this.escapeHtml(violation.description)}</p> 2260 <p class="acc-report-violation-help"> 2261 <a href="${violation.helpUrl}" target="_blank" rel="noopener">${this.translate('How to Fix')} â</a> 2262 </p> 2263 ${violation.nodes.slice(0, 5).map(node => ` 2264 <div class="acc-report-node"> 2265 <div class="acc-report-node-html">${this.escapeHtml(node.html)}</div> 2266 ${node.failureSummary ? `<div class="acc-report-node-fix"><strong>${this.translate('Issue')}:</strong> ${this.escapeHtml(node.failureSummary)}</div>` : ''} 2267 </div> 2268 `).join('')} 2269 ${violation.nodes.length > 5 ? `<p style="color:#666;font-size:13px;margin-top:12px;">... and ${violation.nodes.length - 5} more elements</p>` : ''} 2270 </div> 2271 </div> 2272 `; 2273 }); 2274 2275 html += `</div>`; 2276 } 2277 2278 if (incomplete.length > 0) { 2279 html += ` 2280 <div class="acc-report-section"> 2281 <div class="acc-report-section-title">${this.translate('Items Need Review')} (${incomplete.length})</div> 2282 <p style="color:#666;font-size:14px;">These items require manual verification.</p> 2283 </div> 2284 `; 2285 } 2286 2287 contentArea.innerHTML = html; 2288 2289 // Add click handlers for expandable violations 2290 contentArea.querySelectorAll('.acc-report-violation-header').forEach(header => { 2291 header.addEventListener('click', () => { 2292 header.parentElement.classList.toggle('expanded'); 2293 }); 2294 }); 2295 }, 2296 2297 capitalizeFirst(str) { 2298 if (!str) return ''; 2299 return str.charAt(0).toUpperCase() + str.slice(1); 2300 }, 2301 2302 escapeHtml(str) { 2303 if (!str) return ''; 2304 const div = document.createElement('div'); 2305 div.textContent = str; 2306 return div.innerHTML; 2307 }, 2308 2309 closeReportPanel() { 2310 const panel = this.findElement('.acc-report-panel'); 2311 if (panel) { 2312 panel.classList.remove('acc-report-visible'); 2313 panel.setAttribute('aria-hidden', 'true'); 2314 } 2315 2316 if (this.reportKeyListener) { 2317 document.removeEventListener('keydown', this.reportKeyListener, true); 2318 this.reportKeyListener = null; 2319 } 2320 2321 const focusTarget = this.reportPreviousFocus; 2322 this.reportPreviousFocus = null; 2323 if (focusTarget && typeof focusTarget.focus === 'function') { 2324 focusTarget.focus(); 2325 } 2326 }, 2327 2328 concealImages(enable = false) { 2329 const styleId = `acc-hide-images`; 2330 const existingStyle = document.getElementById(styleId); 2331 if (existingStyle) existingStyle.remove(); 2332 document.documentElement.classList.toggle(styleId, enable); 2333 if (enable) { 2334 const css = ` 2335 body > *:not(.acc-container) img, 2336 body > *:not(.acc-container) picture, 2337 body > *:not(.acc-container) svg:not(.acc-container svg), 2338 body > *:not(.acc-container) video, 2339 body > *:not(.acc-container) iframe, 2340 body > *:not(.acc-container) canvas, 2341 body > *:not(.acc-container) .video, 2342 body > *:not(.acc-container) .image { 2343 display: none !important; 2344 } 2345 `; 2346 this.injectStyle(styleId, css); 2347 } 2348 }, 2349 2350 supportsSpeechSynthesis() { 2351 if (typeof window === 'undefined') return false; 2352 return ( 2353 'speechSynthesis' in window && 2354 typeof window.SpeechSynthesisUtterance !== 'undefined' 2355 ); 2356 }, 2357 2358 supportsTextToSpeech() { 2359 return this.supportsSpeechSynthesis(); 2360 }, 2361 2362 normalizeSpeechLanguage(languageCode = 'en') { 2363 const code = String(languageCode || 'en').toLowerCase(); 2364 const languageMap = { 2365 en: 'en-US', 2366 it: 'it-IT', 2367 fr: 'fr-FR', 2368 de: 'de-DE', 2369 es: 'es-ES', 2370 ru: 'ru-RU', 2371 pl: 'pl-PL', 2372 ro: 'ro-RO', 2373 nl: 'nl-NL', 2374 uk: 'uk-UA' 2375 }; 2376 return languageMap[code] || code; 2377 }, 2378 2379 getNativeTtsRate() { 2380 const configured = Number(this.nativeTtsConfig?.rate); 2381 if (!Number.isFinite(configured)) return 1;
2382 return Math.min(2, Math.max(0.5, configured)); 2383 }, 2384 2385 getNativeTtsPitch() { 2386 const configured = Number(this.nativeTtsConfig?.pitch); 2387 if (!Number.isFinite(configured)) return 1; 2388 return Math.min(2, Math.max(0, configured)); 2389 }, 2390 2391 isElementVisibleForTts(element) { 2392 if (!(element instanceof Element)) return false; 2393 const style = window.getComputedStyle(element); 2394 if (style.display === 'none' || style.visibility === 'hidden') { 2395 return false; 2396 } 2397 const rect = element.getBoundingClientRect(); 2398 return rect.width > 0 && rect.height > 0; 2399 }, 2400 2401 isTtsExcludedElement(element, { allowLandmarkRegions = false } = {}) { 2402 if (!(element instanceof Element)) return true; 2403 if (element.closest('.acc-container')) return true; 2404 if (element.closest('script,style,noscript,template')) return true; 2405 if (element.closest('[aria-hidden="true"]')) return true; 2406 if ( 2407 !allowLandmarkRegions && 2408 element.closest( 2409 'nav,header,footer,aside,form,dialog,[role="navigation"],[role="complementary"],[role="search"],[role="menu"],[role="dialog"],[role="alert"],[aria-live]' 2410 ) 2411 ) { 2412 return true; 2413 } 2414 return false; 2415 }, 2416 2417 normalizeReadableText(text = '') { 2418 return String(text) 2419 .replace(/\s+/g, ' ') 2420 .replace(/[ \t]+([,.;!?])/g, '$1') 2421 .trim(); 2422 }, 2423 2424 getTtsCandidateRoots() { 2425 const selectors = [ 2426 'article', 2427 'main', 2428 '[role="main"]', 2429 '.content', 2430 '.post', 2431 '.entry-content', 2432 '#content' 2433 ]; 2434 const roots = []; 2435 const seen = new Set(); 2436 2437 selectors.forEach((selector) => { 2438 document.querySelectorAll(selector).forEach((element) => { 2439 if (!(element instanceof Element)) return; 2440 if (this.isTtsExcludedElement(element) || !this.isElementVisibleForTts(element)) return; 2441 if (seen.has(element)) return; 2442 seen.add(element); 2443 roots.push(element); 2444 }); 2445 }); 2446 2447 return roots; 2448 }, 2449 2450 getPrimaryContentRoot() { 2451 if (typeof document === 'undefined') return null; 2452 const candidates = this.getTtsCandidateRoots(); 2453 if (!candidates.length) { 2454 const explicitCandidates = Array.from( 2455 document.querySelectorAll('main,article,[role="main"],#content,.content,.post,.entry-content') 2456 ).filter((element) => 2457 element instanceof Element && 2458 !this.isTtsExcludedElement(element) && 2459 this.isElementVisibleForTts(element) 2460 ); 2461 2462 if (explicitCandidates.length) { 2463 let explicitBest = explicitCandidates[0]; 2464 let explicitBestScore = -1; 2465 explicitCandidates.forEach((candidate) => { 2466 const textLength = this.normalizeReadableText(candidate.innerText || candidate.textContent || '').length; 2467 const rect = candidate.getBoundingClientRect(); 2468 const score = textLength + (rect.width * rect.height * 0.0025); 2469 if (score > explicitBestScore) { 2470 explicitBestScore = score; 2471 explicitBest = candidate; 2472 } 2473 }); 2474 return explicitBest; 2475 } 2476 2477 if (document.body) { 2478 const topLevelCandidates = Array.from(document.body.children).filter((element) => 2479 element instanceof Element && 2480 !element.classList.contains('acc-container') && 2481 this.isElementVisibleForTts(element) 2482 ); 2483 2484 if (topLevelCandidates.length) { 2485 let best = topLevelCandidates[0]; 2486 let bestScore = -1; 2487 topLevelCandidates.forEach((candidate) => { 2488 const textLength = this.normalizeReadableText(candidate.innerText || candidate.textContent || '').length; 2489 const rect = candidate.getBoundingClientRect(); 2490 const score = textLength + (rect.width * rect.height * 0.0025); 2491 if (score > bestScore) { 2492 bestScore = score; 2493 best = candidate; 2494 } 2495 }); 2496 return best; 2497 } 2498 } 2499 2500 return document.body; 2501 } 2502 2503 let bestRoot = candidates[0]; 2504 let bestScore = -1;
2505 candidates.forEach((candidate) => { 2506 const blocks = this.extractReadableBlocks(candidate); 2507 const score = blocks.join(' ').length; 2508 if (score > bestScore) { 2509 bestScore = score; 2510 bestRoot = candidate; 2511 } 2512 }); 2513 return bestRoot; 2514 }, 2515 2516 extractReadableBlocks(root) { 2517 if (!(root instanceof Element)) return []; 2518 const blockSelector = 'h1,h2,h3,h4,h5,h6,p,li,dt,dd,blockquote,figcaption,caption,th,td'; 2519 const blocks = []; 2520 root.querySelectorAll(blockSelector).forEach((element) => { 2521 if (!(element instanceof Element)) return; 2522 if (this.isTtsExcludedElement(element) || !this.isElementVisibleForTts(element)) return; 2523 const text = this.normalizeReadableText(element.innerText || element.textContent || ''); 2524 if (text.length < 15) return; 2525 blocks.push(text); 2526 }); 2527 return blocks; 2528 }, 2529 2530 getReadableContent() { 2531 if (typeof document === 'undefined') return ''; 2532 const candidateRoots = this.getTtsCandidateRoots(); 2533 let bestBlocks = []; 2534 let bestScore = -1; 2535 2536 candidateRoots.forEach((root) => { 2537 const blocks = this.extractReadableBlocks(root); 2538 if (!blocks.length) return; 2539 const characterCount = blocks.join(' ').length; 2540 const score = (blocks.length * 60) + characterCount; 2541 if (score > bestScore) { 2542 bestScore = score; 2543 bestBlocks = blocks; 2544 } 2545 }); 2546 2547 if (!bestBlocks.length && document.body) { 2548 bestBlocks = this.extractReadableBlocks(document.body); 2549 } 2550 2551 if (!bestBlocks.length && document.body) { 2552 const fallback = this.normalizeReadableText(document.body.innerText || document.body.textContent || ''); 2553 return fallback.slice(0, 30000); 2554 } 2555 2556 return bestBlocks.join('\n\n').slice(0, 30000); 2557 }, 2558 2559 splitLongSpeechSegment(segment, maxLength = 240) { 2560 if (!segment) return []; 2561 if (segment.length <= maxLength) return [segment]; 2562 2563 const parts = []; 2564 let remaining = segment; 2565 while (remaining.length > maxLength) { 2566 let splitIndex = remaining.lastIndexOf(',', maxLength); 2567 if (splitIndex < Math.floor(maxLength * 0.5)) { 2568 splitIndex = remaining.lastIndexOf(' ', maxLength); 2569 } 2570 if (splitIndex < Math.floor(maxLength * 0.5)) { 2571 splitIndex = maxLength; 2572 } 2573 parts.push(remaining.slice(0, splitIndex).trim()); 2574 remaining = remaining.slice(splitIndex).trim(); 2575 } 2576 if (remaining.length) { 2577 parts.push(remaining); 2578 } 2579 return parts.filter(Boolean); 2580 }, 2581 2582 buildSpeechQueue(text = '') { 2583 const normalized = this.normalizeReadableText(text); 2584 if (!normalized) return []; 2585 2586 const paragraphCandidates = text 2587 .split(/\n{2,}/) 2588 .map((paragraph) => this.normalizeReadableText(paragraph)) 2589 .filter(Boolean); 2590 2591 const paragraphs = paragraphCandidates.length ? paragraphCandidates : [normalized]; 2592 const queue = []; 2593 2594 paragraphs.forEach((paragraph) => { 2595 const sentences = paragraph.split(/(?<=[.!?])\s+/).filter(Boolean); 2596 const sentenceList = sentences.length ? sentences : [paragraph]; 2597 let chunk = ''; 2598 2599 sentenceList.forEach((sentence) => { 2600 const next = chunk ? `${chunk} ${sentence}` : sentence; 2601 if (next.length <= 240) { 2602 chunk = next; 2603 return; 2604 } 2605 if (chunk) { 2606 queue.push(...this.splitLongSpeechSegment(chunk, 240)); 2607 } 2608 chunk = sentence; 2609 }); 2610 2611 if (chunk) { 2612 queue.push(...this.splitLongSpeechSegment(chunk, 240)); 2613 } 2614 }); 2615 2616 return queue.slice(0, 300); 2617 }, 2618 2619 resolveSpeechVoice(languageCode) { 2620 if (!this.supportsSpeechSynthesis()) return null; 2621 const synth = window.speechSynthesis; 2622 const voices = synth.getVoices?.() || []; 2623 if (!voices.length) return null; 2624 2625 const preferredVoiceName = String(this.nativeTtsConfig?.preferredVoiceName || '').trim().toLowerCase(); 2626 if (preferredVoiceName) { 2627 const byExactName = voices.find((voice) => String(voice.name || '').trim().toLowerCase() === preferredVoiceName); 2628 if (byExactName) return byExactName; 2629 const byNameContains = voices.find((voice) => String(voice.name || '').toLowerCase().includes(preferredVoiceName)); 2630 if (byNameContains) return byNameContains; 2631 } 2632 2633 const configuredVoiceLang = String(this.nativeTtsConfig?.preferredVoiceLang || '').trim(); 2634 const targetLanguage = configuredVoiceLang || languageCode; 2635 const normalized = this.normalizeSpeechLanguage(targetLanguage).toLowerCase(); 2636 const primary = normalized.split('-')[0]; 2637 const localExact = voices.find((voice) => ( 2638 voice.localService && 2639 String(voice.lang || '').toLowerCase() === normalized 2640 )); 2641 const exact = voices.find((voice) => String(voice.lang || '').toLowerCase() === normalized); 2642 const localByPrimary = voices.find((voice) => ( 2643 voice.localService && 2644 String(voice.lang || '').toLowerCase().startsWith(primary) 2645 )); 2646 const byPrimary = voices.find((voice) => String(voice.lang || '').toLowerCase().startsWith(primary)); 2647 const defaultVoice = voices.find((voice) => voice.default); 2648 return localExact || exact || localByPrimary || byPrimary || defaultVoice || voices[0]; 2649 }, 2650 2651 getSpeechTargetFromEvent(event) { 2652 const target = event?.target; 2653 if (!(target instanceof Element)) return null; 2654 if (target.closest('.acc-container')) return null; 2655 2656 const block = target.closest('h1,h2,h3,h4,h5,h6,p,li,dt,dd,blockquote,figcaption,caption,th,td,div,section'); 2657 if (!(block instanceof Element)) return null; 2658 if (this.isTtsExcludedElement(block, { allowLandmarkRegions: true }) || !this.isElementVisibleForTts(block)) return null; 2659 2660 const text = this.normalizeReadableText(block.innerText || block.textContent || ''); 2661 if (text.length < 2) return null; 2662 2663 return { 2664 element: block, 2665 text: text.slice(0, 30000) 2666 }; 2667 }, 2668
2669 setActiveSpeechTarget(element = null) { 2670 if (this.ttsActiveTarget && this.ttsActiveTarget !== element) { 2671 this.ttsActiveTarget.classList.remove('acc-tts-active-block'); 2672 } 2673 this.ttsActiveTarget = element; 2674 if (this.ttsActiveTarget) { 2675 this.ttsActiveTarget.classList.add('acc-tts-active-block'); 2676 } 2677 }, 2678 2679 startTtsClickMode() { 2680 if (typeof document === 'undefined' || this.ttsClickListener) return; 2681 this.ttsClickListener = (event) => { 2682 this.handleTtsClick(event); 2683 }; 2684 document.addEventListener('click', this.ttsClickListener, true); 2685 document.body?.classList.add('acc-tts-click-mode'); 2686 }, 2687 2688 stopTtsClickMode() { 2689 if (typeof document === 'undefined') return; 2690 if (this.ttsClickListener) { 2691 document.removeEventListener('click', this.ttsClickListener, true); 2692 this.ttsClickListener = null; 2693 } 2694 document.body?.classList.remove('acc-tts-click-mode'); 2695 this.setActiveSpeechTarget(null); 2696 }, 2697 2698 handleTtsClick(event) { 2699 if (!this.retrieveState('text-to-speech')) return; 2700 const target = this.getSpeechTargetFromEvent(event); 2701 if (!target) return; 2702 2703 this.setActiveSpeechTarget(target.element); 2704 this.ttsTextCache = target.text; 2705 this.startSpeechPlayback({ restart: true }); 2706 }, 2707 2708 ensureTtsQueue() { 2709 const text = this.ttsTextCache || this.getReadableContent(); 2710 if (!text) { 2711 this.ttsQueue = []; 2712 this.ttsQueueIndex = 0; 2713 return false; 2714 } 2715 this.ttsTextCache = text; 2716 this.ttsQueue = this.buildSpeechQueue(text); 2717 this.ttsQueueIndex = 0; 2718 return this.ttsQueue.length > 0; 2719 }, 2720 2721 speakNextTtsChunk(sessionId) { 2722 if (!this.supportsSpeechSynthesis()) return; 2723 if (sessionId !== this.ttsSessionId) return; 2724 2725 if (this.ttsQueueIndex >= this.ttsQueue.length) { 2726 this.ttsStatus = 'stopped'; 2727 this.setActiveSpeechTarget(null); 2728 return; 2729 } 2730 2731 const synth = window.speechSynthesis;
2732 const chunk = this.ttsQueue[this.ttsQueueIndex]; 2733 if (!chunk) { 2734 this.ttsQueueIndex += 1; 2735 this.speakNextTtsChunk(sessionId); 2736 return; 2737 } 2738 2739 const utterance = new window.SpeechSynthesisUtterance(chunk); 2740 utterance.lang = this.normalizeSpeechLanguage(this.loadConfig().lang || 'en'); 2741 if (!this.ttsVoice || this.ttsVoice.lang?.toLowerCase() !== utterance.lang.toLowerCase()) { 2742 this.ttsVoice = this.resolveSpeechVoice(utterance.lang); 2743 } 2744 if (this.ttsVoice) { 2745 utterance.voice = this.ttsVoice; 2746 } 2747 utterance.rate = this.getNativeTtsRate(); 2748 utterance.pitch = this.getNativeTtsPitch(); 2749 2750 utterance.onstart = () => { 2751 if (sessionId !== this.ttsSessionId) return; 2752 this.ttsStatus = 'reading'; 2753 }; 2754 utterance.onpause = () => { 2755 if (sessionId !== this.ttsSessionId) return; 2756 this.ttsStatus = 'paused'; 2757 }; 2758 utterance.onresume = () => { 2759 if (sessionId !== this.ttsSessionId) return; 2760 this.ttsStatus = 'reading'; 2761 }; 2762 utterance.onend = () => { 2763 if (sessionId !== this.ttsSessionId) return; 2764 this.ttsQueueIndex += 1;
2765 this.speakNextTtsChunk(sessionId); 2766 }; 2767 utterance.onerror = () => { 2768 if (sessionId !== this.ttsSessionId) return; 2769 this.ttsStatus = 'stopped'; 2770 this.setActiveSpeechTarget(null); 2771 }; 2772 2773 this.ttsUtterance = utterance; 2774 synth.speak(utterance); 2775 }, 2776 2777 startNativeSpeechPlayback({ restart = false } = {}) { 2778 if (!this.supportsSpeechSynthesis()) return; 2779 const synth = window.speechSynthesis; 2780 2781 if (!restart && synth.paused) { 2782 synth.resume(); 2783 this.ttsStatus = 'reading'; 2784 return; 2785 } 2786 2787 const hasQueue = restart || !this.ttsQueue.length || this.ttsQueueIndex >= this.ttsQueue.length 2788 ? this.ensureTtsQueue() 2789 : true; 2790 if (!hasQueue) { 2791 this.stopSpeech(); 2792 return; 2793 } 2794 2795 const sessionId = this.ttsSessionId + 1; 2796 this.ttsSessionId = sessionId; 2797 synth.cancel(); 2798 this.ttsStatus = 'reading'; 2799 this.speakNextTtsChunk(sessionId); 2800 }, 2801 2802 startSpeechPlayback({ restart = false } = {}) { 2803 this.startNativeSpeechPlayback({ restart }); 2804 }, 2805 2806 pauseSpeech() { 2807 if (!this.supportsSpeechSynthesis()) return; 2808 const synth = window.speechSynthesis; 2809 if (synth.speaking && !synth.paused) { 2810 synth.pause(); 2811 this.ttsStatus = 'paused'; 2812 } 2813 }, 2814 2815 resumeSpeech() { 2816 if (!this.supportsSpeechSynthesis()) return; 2817 const synth = window.speechSynthesis; 2818 if (synth.paused) { 2819 synth.resume(); 2820 this.ttsStatus = 'reading'; 2821 return; 2822 } 2823 this.startSpeechPlayback({ restart: false }); 2824 }, 2825 2826 stopSpeech() { 2827 const synth = this.supportsSpeechSynthesis() ? window.speechSynthesis : null; 2828 this.ttsSessionId += 1; 2829 if (synth && (synth.speaking || synth.paused)) { 2830 synth.cancel(); 2831 } 2832 this.ttsUtterance = null; 2833 this.ttsQueueIndex = 0; 2834 this.ttsStatus = 'stopped'; 2835 this.setActiveSpeechTarget(null); 2836 }, 2837 2838 enableTextToSpeech(enable = false) { 2839 if (!this.supportsTextToSpeech()) return; 2840 const isUserToggle = this.userInitiatedToggleKey === 'text-to-speech'; 2841 2842 if (!enable) { 2843 if (isUserToggle) this.announceTtsState(false); 2844 this.stopSpeech(); 2845 this.stopTtsClickMode(); 2846 return; 2847 } 2848 2849 const synth = window.speechSynthesis; 2850 if (synth?.getVoices) { 2851 synth.getVoices(); 2852 } 2853 2854 this.startTtsClickMode(); 2855 this.ttsStatus = 'idle'; 2856 if (isUserToggle) this.announceTtsState(true); 2857 }, 2858 2859 announceTtsState(active) { 2860 if (!this.supportsTextToSpeech()) return; 2861 const synth = window.speechSynthesis; 2862 if (!synth) return; 2863 const msg = active 2864 ? this.translate('Text to Speech On') 2865 : this.translate('Text to Speech Off'); 2866 synth.cancel(); 2867 const utterance = new SpeechSynthesisUtterance(msg); 2868 const lang = this.normalizeSpeechLanguage(this.loadConfig().lang || 'en'); 2869 utterance.lang = lang; 2870 const voice = this.resolveSpeechVoice(lang); 2871 if (voice) { 2872 utterance.voice = voice; 2873 } 2874 utterance.rate = this.getNativeTtsRate(); 2875 utterance.pitch = this.getNativeTtsPitch(); 2876 synth.speak(utterance); 2877 }, 2878 2879 ensureAnnotationLayer() { 2880 if (this.annotationLayer && document.body.contains(this.annotationLayer)) { 2881 return this.annotationLayer; 2882 } 2883 const layer = document.createElement('div'); 2884 layer.className = 'acc-annotation-layer acc-container'; 2885 layer.setAttribute('aria-hidden', 'true'); 2886 document.body.appendChild(layer); 2887 this.annotationLayer = layer; 2888 return layer; 2889 }, 2890 2891 resolveAnnotationTarget(selector) { 2892 if (!selector || typeof selector !== 'string') return null; 2893 try { 2894 const element = document.querySelector(selector); 2895 if (!element || element.closest('.acc-container')) return null; 2896 return element; 2897 } catch { 2898 return null; 2899 } 2900 }, 2901 2902 buildAnnotationEntries(results = {}) { 2903 const violations = Array.isArray(results.violations) ? results.violations : []; 2904 const entries = []; 2905 const seen = new Set(); 2906 2907 violations.forEach((violation) => { 2908 const nodes = Array.isArray(violation.nodes) ? violation.nodes : [];
2909 nodes.forEach((node) => { 2910 const targets = Array.isArray(node.target) ? node.target : []; 2911 const selector = targets.find((target) => typeof target === 'string' && target.trim().length); 2912 if (!selector) return; 2913 const key = `${selector}::${violation.id || violation.help || ''}`; 2914 if (seen.has(key)) return; 2915 2916 const element = this.resolveAnnotationTarget(selector); 2917 if (!element) return; 2918 seen.add(key); 2919 entries.push({ 2920 selector, 2921 element, 2922 impact: violation.impact || 'minor', 2923 title: violation.help || this.translate('Issue'), 2924 description: violation.description || '', 2925 helpUrl: violation.helpUrl || '', 2926 failureSummary: node.failureSummary || '' 2927 }); 2928 }); 2929 }); 2930 2931 return entries.slice(0, MAX_ANNOTATIONS); 2932 }, 2933 2934 createAnnotationMarker(annotation) { 2935 const marker = document.createElement('button'); 2936 marker.type = 'button'; 2937 marker.className = 'acc-annotation-marker'; 2938 marker.dataset.impact = annotation.impact || 'minor'; 2939 marker.setAttribute('aria-label', annotation.title); 2940 marker.title = annotation.title; 2941 marker.innerHTML = ''; 2942 2943 marker.addEventListener('click', (event) => { 2944 event.preventDefault(); 2945 event.stopPropagation(); 2946 this.showAnnotationPopup(annotation, marker); 2947 }); 2948 2949 return marker; 2950 }, 2951 2952 positionAnnotationPopup(popup, marker) { 2953 if (!popup || !marker) return; 2954 const markerRect = marker.getBoundingClientRect(); 2955 const popupWidth = popup.offsetWidth || 300; 2956 const popupHeight = popup.offsetHeight || 180; 2957 const viewportWidth = window.innerWidth; 2958 const viewportHeight = window.innerHeight; 2959 const gap = 10; 2960 2961 let left = window.scrollX + markerRect.left + gap; 2962 let top = window.scrollY + markerRect.bottom + gap; 2963 2964 if (left + popupWidth > window.scrollX + viewportWidth - gap) { 2965 left = window.scrollX + viewportWidth - popupWidth - gap; 2966 } 2967 if (left < window.scrollX + gap) { 2968 left = window.scrollX + gap; 2969 } 2970 2971 if (top + popupHeight > window.scrollY + viewportHeight - gap) { 2972 top = window.scrollY + markerRect.top - popupHeight - gap; 2973 } 2974 if (top < window.scrollY + gap) { 2975 top = window.scrollY + gap; 2976 } 2977 2978 popup.style.left = `${left}px`; 2979 popup.style.top = `${top}px`; 2980 }, 2981 2982 clearAnnotationPopup() { 2983 if (this.annotationPopup) { 2984 this.annotationPopup.remove(); 2985 this.annotationPopup = null; 2986 } 2987 }, 2988 2989 showAnnotationPopup(annotation, marker) { 2990 if (!annotation || !marker || !this.annotationLayer) return; 2991 this.clearAnnotationPopup(); 2992 2993 const popup = document.createElement('div'); 2994 popup.className = 'acc-annotation-popup'; 2995 popup.innerHTML = ` 2996 <div class="acc-annotation-popup-header"> 2997 <h3 class="acc-annotation-popup-title">${this.escapeHtml(annotation.title)}</h3> 2998 <button type="button" class="acc-annotation-popup-close" aria-label="${this.translate('Close')}">${this.widgetIcons.close}</button> 2999 </div> 3000 <p><strong>${this.translate(this.capitalizeFirst(annotation.impact))}</strong></p> 3001 <p>${this.escapeHtml(annotation.description)}</p> 3002 ${annotation.failureSummary ? `<p><strong>${this.translate('Issue')}:</strong> ${this.escapeHtml(annotation.failureSummary)}</p>` : ''} 3003 ${annotation.helpUrl ? `<p><a href="${annotation.helpUrl}" target="_blank" rel="noopener">${this.translate('How to Fix')} â</a></p>` : ''} 3004 `; 3005 popup.__accMarker = marker; 3006 3007 const closeButton = popup.querySelector('.acc-annotation-popup-close'); 3008 closeButton?.addEventListener('click', () => this.clearAnnotationPopup()); 3009
3010 this.annotationLayer.appendChild(popup); 3011 this.annotationPopup = popup; 3012 this.positionAnnotationPopup(popup, marker); 3013 }, 3014 3015 positionAnnotations() { 3016 if (!Array.isArray(this.annotationItems) || !this.annotationItems.length) return; 3017 3018 this.annotationItems.forEach((item) => { 3019 if (!item?.target || !item?.marker) return; 3020 if (!document.contains(item.target)) { 3021 item.marker.hidden = true; 3022 return; 3023 } 3024 3025 const rect = item.target.getBoundingClientRect(); 3026 const outOfView = ( 3027 rect.width <= 0 || 3028 rect.height <= 0 || 3029 rect.bottom < 0 || 3030 rect.top > window.innerHeight || 3031 rect.right < 0 || 3032 rect.left > window.innerWidth 3033 ); 3034 3035 if (outOfView) { 3036 item.marker.hidden = true; 3037 return; 3038 } 3039 3040 item.marker.hidden = false; 3041 item.marker.style.top = `${window.scrollY + rect.top + Math.min(16, rect.height / 2)}px`; 3042 item.marker.style.left = `${window.scrollX + rect.right}px`; 3043 }); 3044 3045 if (this.annotationPopup?.__accMarker && !this.annotationPopup.__accMarker.hidden) { 3046 this.positionAnnotationPopup(this.annotationPopup, this.annotationPopup.__accMarker); 3047 } 3048 }, 3049 3050 enableAnnotations(enable = false) { 3051 if (!enable) { 3052 this.disableAnnotations(); 3053 return; 3054 } 3055 3056 const requestId = ++this.annotationRequestId; 3057 this.disableAnnotations({ cancelPending: false }); 3058 const layer = this.ensureAnnotationLayer(); 3059 3060 this.runBackgroundAxeScan() 3061 .then((results) => { 3062 if (requestId !== this.annotationRequestId || !this.retrieveState('annotations')) { 3063 return; 3064 } 3065 const annotations = this.buildAnnotationEntries(results); 3066 this.annotationItems = annotations.map((annotation) => { 3067 const marker = this.createAnnotationMarker(annotation); 3068 layer.appendChild(marker); 3069 return { 3070 marker, 3071 target: annotation.element, 3072 data: annotation 3073 }; 3074 }); 3075 3076 if (!this.annotationRepositionHandler) { 3077 this.annotationRepositionHandler = this.throttle(() => this.positionAnnotations(), 80); 3078 } 3079 window.addEventListener('scroll', this.annotationRepositionHandler, { passive: true }); 3080 window.addEventListener('resize', this.annotationRepositionHandler, { passive: true }); 3081 3082 if (!this.annotationOutsideHandler) { 3083 this.annotationOutsideHandler = (event) => { 3084 if (!this.annotationPopup) return; 3085 const clickedPopup = this.annotationPopup.contains(event.target); 3086 const clickedMarker = event.target?.closest?.('.acc-annotation-marker'); 3087 if (!clickedPopup && !clickedMarker) { 3088 this.clearAnnotationPopup(); 3089 } 3090 }; 3091 } 3092 document.addEventListener('click', this.annotationOutsideHandler, true); 3093 3094 this.positionAnnotations(); 3095 }) 3096 .catch((error) => { 3097 console.warn('Failed to render annotations:', error); 3098 }); 3099 }, 3100 3101 disableAnnotations({ cancelPending = true } = {}) { 3102 if (cancelPending) { 3103 this.annotationRequestId += 1; 3104 } 3105 this.clearAnnotationPopup(); 3106 3107 if (this.annotationOutsideHandler) { 3108 document.removeEventListener('click', this.annotationOutsideHandler, true); 3109 } 3110 if (this.annotationRepositionHandler) { 3111 window.removeEventListener('scroll', this.annotationRepositionHandler); 3112 window.removeEventListener('resize', this.annotationRepositionHandler); 3113 } 3114 3115 if (this.annotationLayer) { 3116 this.annotationLayer.remove(); 3117 this.annotationLayer = null; 3118 } 3119 3120 this.annotationItems = []; 3121 }, 3122 3123 3124 clearSimpleLayoutDomMutations() { 3125 if (this.simpleLayoutRoot) { 3126 this.simpleLayoutRoot.classList.remove('acc-simple-layout-root'); 3127 this.simpleLayoutRoot = null; 3128 } 3129 3130 if (Array.isArray(this.simpleLayoutHiddenElements)) {
3131 this.simpleLayoutHiddenElements.forEach((element) => { 3132 if (element && element.classList) { 3133 element.classList.remove('acc-simple-layout-hidden'); 3134 } 3135 }); 3136 } 3137 this.simpleLayoutHiddenElements = []; 3138 }, 3139 3140 applySimpleLayoutDomMutations() { 3141 const root = this.getPrimaryContentRoot(); 3142 if (!root || !document.body) return; 3143 3144 this.simpleLayoutRoot = root; 3145 root.classList.add('acc-simple-layout-root'); 3146 3147 const hiddenElements = []; 3148 Array.from(document.body.children).forEach((child) => { 3149 if (!(child instanceof Element)) return; 3150 if (child.classList.contains('acc-container')) return; 3151 if (child === root || child.contains(root)) return; 3152 child.classList.add('acc-simple-layout-hidden'); 3153 hiddenElements.push(child); 3154 }); 3155 3156 const clutterSelectors = [ 3157 'aside', 3158 'nav', 3159 'form', 3160 'footer', 3161 '[role="complementary"]', 3162 '[role="search"]', 3163 '[role="contentinfo"]', 3164 '[aria-hidden="true"]', 3165 '[class*="cookie"]', 3166 '[id*="cookie"]', 3167 '[class*="banner"]', 3168 '[id*="banner"]', 3169 '[class*="popup"]', 3170 '[id*="popup"]', 3171 '[class*="modal"]', 3172 '[id*="modal"]', 3173 '[class*="advert"]', 3174 '[id*="advert"]', 3175 '[class*="ads"]', 3176 '[id*="ads"]', 3177 '[class*="sidebar"]', 3178 '[id*="sidebar"]', 3179 '[class*="social"]', 3180 '[id*="social"]', 3181 '[class*="share"]', 3182 '[id*="share"]', 3183 '[class*="newsletter"]', 3184 '[id*="newsletter"]', 3185 '[class*="related"]', 3186 '[id*="related"]', 3187 '[class*="comment"]', 3188 '[id*="comment"]', 3189 '[class*="footer"]', 3190 '[id*="footer"]', 3191 '[class*="promo"]', 3192 '[id*="promo"]' 3193 ].join(','); 3194 3195 root.querySelectorAll(clutterSelectors).forEach((element) => { 3196 if (!(element instanceof Element)) return; 3197 if (element.closest('.acc-container')) return; 3198 if (element === root) return; 3199 element.classList.add('acc-simple-layout-hidden'); 3200 hiddenElements.push(element); 3201 }); 3202 3203 this.simpleLayoutHiddenElements = hiddenElements; 3204 }, 3205 3206 enableSimpleLayout(enable = false) { 3207 const S = 'body.acc-simple-layout-enabled'; 3208 const R = `${S} .acc-simple-layout-root`;
3209 const X = ':not(.acc-container):not(.acc-container *)'; 3210 const config = { 3211 id: 'simple-layout', 3212 css: ` 3213 /* ââ Body & root container ââ */ 3214 ${S} { 3215 background: #fff !important; 3216 } 3217 3218 ${S} .acc-simple-layout-hidden { 3219 display: none !important; 3220 } 3221 3222 ${R} { 3223 max-width: 72ch !important; 3224 margin: 0 auto !important; 3225 padding: clamp(20px, 4vw, 40px) 20px !important; 3226 position: relative !important; 3227 border-radius: 0 !important; 3228 box-shadow: none !important; 3229 } 3230 3231 /* ââ Universal decoration strip ââ */ 3232 ${R} :where(*)${X} { 3233 background-color: transparent !important; 3234 background-image: none !important; 3235 box-shadow: none !important; 3236 text-shadow: none !important; 3237 border-color: transparent !important; 3238 } 3239 3240 /* ââ Layout linearization ââ */ 3241 ${R} :where(div, section, article, header, main, footer, figure, figcaption, details, summary, hgroup, search)${X} { 3242 display: block !important; 3243 position: static !important; 3244 float: none !important; 3245 transform: none !important; 3246 columns: auto !important; 3247 column-count: auto !important; 3248 width: auto !important; 3249 min-width: 0 !important; 3250 max-width: 100% !important; 3251 margin-left: 0 !important; 3252 margin-right: 0 !important; 3253 padding-left: 0 !important; 3254 padding-right: 0 !important; 3255 } 3256 3257 /* ââ Color reset ââ */ 3258 ${R} :where(h1, h2, h3, h4, h5, h6)${X} { 3259 color: #111 !important; 3260 } 3261 3262 ${R} :where(p, li, dt, dd, td, th, span, blockquote, figcaption, label, summary, details)${X} { 3263 color: #222 !important; 3264 } 3265 3266 ${R} :where(a)${X} { 3267 color: #1a0dab !important; 3268 } 3269 3270 ${R} :where(a:visited)${X} { 3271 color: #681da8 !important; 3272 } 3273 3274 /* ââ Typography ââ */ 3275 ${R} :where(*)${X} { 3276 font-family: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif !important; 3277 } 3278 3279 ${R} :where(p, li, dt, dd, blockquote, figcaption, td, th, label, summary)${X} { 3280 font-size: clamp(1rem, 0.95rem + 0.25vw, 1.125rem) !important; 3281 line-height: 1.8 !important; 3282 letter-spacing: 0.01em !important; 3283 max-width: 70ch !important; 3284 } 3285 3286 ${R} :where(h1)${X} { 3287 font-size: 2em !important; 3288 line-height: 1.2 !important; 3289 margin: 0.67em 0 !important; 3290 font-weight: 700 !important; 3291 } 3292 3293 ${R} :where(h2)${X} { 3294 font-size: 1.5em !important; 3295 line-height: 1.25 !important; 3296 margin: 0.83em 0 !important; 3297 font-weight: 700 !important; 3298 } 3299 3300 ${R} :where(h3)${X} { 3301 font-size: 1.25em !important; 3302 line-height: 1.3 !important; 3303 margin: 1em 0 !important; 3304 font-weight: 600 !important; 3305 } 3306 3307 ${R} :where(h4, h5, h6)${X} { 3308 font-size: 1.1em !important; 3309 line-height: 1.35 !important; 3310 margin: 1em 0 !important; 3311 font-weight: 600 !important; 3312 } 3313 3314 /* ââ Decorative images hidden ââ */ 3315 ${R} :where(img[role="presentation"], img[alt=""], img:not([alt]), svg[aria-hidden="true"])${X} { 3316 display: none !important; 3317 } 3318 3319 /* ââ Meaningful borders restored ââ */ 3320 ${R} :where(hr)${X} { 3321 border: none !important; 3322 border-top: 1px solid #d1d5db !important; 3323 margin: 1.5em 0 !important; 3324 } 3325 3326 ${R} :where(blockquote)${X} { 3327 border-left: 4px solid #d1d5db !important; 3328 padding-left: 1em !important; 3329 margin-left: 0 !important; 3330 font-style: italic !important; 3331 } 3332 3333 ${R}
3333 :where(table)${X} { 3334 border-collapse: collapse !important; 3335 max-width: 100% !important; 3336 overflow-x: auto !important; 3337 display: table !important; 3338 } 3339 3340 ${R} :where(th, td)${X} { 3341 border: 1px solid #d1d5db !important; 3342 padding: 8px 12px !important; 3343 text-align: left !important; 3344 } 3345 3346 ${R} :where(th)${X} { 3347 font-weight: 600 !important; 3348 background: #f8f9fa !important; 3349 } 3350 3351 /* ââ Lists ââ */ 3352 ${R} :where(ul, ol)${X} { 3353 padding-left: 1.5em !important; 3354 margin: 0.75em 0 !important; 3355 } 3356 3357 ${R} :where(li)${X} { 3358 display: list-item !important; 3359 margin: 0.25em 0 !important; 3360 } 3361 3362 /* ââ Code blocks ââ */ 3363 ${R} :where(pre)${X} { 3364 background: #f6f8fa !important; 3365 border-radius: 6px !important; 3366 padding: 1em !important; 3367 overflow-x: auto !important; 3368 max-width: 100% !important; 3369 } 3370 3371 ${R} :where(code, kbd, samp)${X} { 3372 font-family: ui-monospace, "SFMono-Regular", "SF Mono", Menlo, Consolas, "Liberation Mono", monospace !important; 3373 font-size: 0.9em !important; 3374 } 3375 3376 ${R} :where(code):not(pre code)${X} { 3377 background: #f0f2f5 !important; 3378 padding: 0.15em 0.4em !important; 3379 border-radius: 3px !important; 3380 } 3381 3382 /* ââ Empty wrapper collapse ââ */ 3383 ${R} :where(div:empty)${X} { 3384 display: none !important; 3385 } 3386 3387 /* ââ Media ââ */ 3388 ${R} :where(img, video, iframe)${X} { 3389 max-width: 100% !important; 3390 height: auto !important; 3391 border-radius: 4px !important; 3392 } 3393 ` 3394 }; 3395
3396 this.applyToolStyle({ ...config, enable }); 3397 document.body?.classList.toggle('acc-simple-layout-enabled', !!enable); 3398 3399 this.clearSimpleLayoutDomMutations(); 3400 if (enable) { 3401 this.applySimpleLayoutDomMutations(); 3402 } 3403 }, 3404 3405 applyEnhancements() { 3406 const { states } = this.loadConfig(); 3407 // Handle font size scaling 3408 const hasTextScaleState = !!(states && Object.prototype.hasOwnProperty.call(states, 'text-scale')); 3409 if (hasTextScaleState) { 3410 const storedScale = states['text-scale'] === false ? 1 : states['text-scale']; 3411 const appliedScale = this.setTextScaleFromPercent(storedScale, { persist: false }); 3412 this.syncTextScaleControlUI(document.querySelector('.acc-menu'), appliedScale); 3413 } else { 3414 this.textScaleIndex = 0; 3415 if (this.multiLevelFeatures['text-scale']) { 3416 this.multiLevelFeatures['text-scale'].currentIndex = -1; 3417 } 3418 this.scaleText(1); 3419 this.syncTextScaleControlUI(document.querySelector('.acc-menu'), 1); 3420 } 3421 // Apply other enhancements 3422 this.concealImages(states && states['hide-images']); 3423 this.highlightTitles(states && states['highlight-title']); 3424 this.highlightLinks(states && states['highlight-links']); 3425 this.adjustLetterSpacing(states && states['letter-spacing']); 3426 this.adjustLineSpacing(states && states['line-spacing']); 3427 this.enableBoldText(states && states['bold-text']); 3428 this.enableReadableText(states && states['readable-text']); 3429 this.enableReadingAid(states && states['reading-aid']); 3430 this.pauseMotion(states && states['pause-motion']); 3431 this.enableLargePointer(states && states['large-pointer']); 3432 this.enableHighContrastMode(states && states['high-contrast-mode']); 3433 this.enableAnnotations(states && states['annotations']); 3434 this.enableTextToSpeech(states && states['text-to-speech']); 3435 this.enableSimpleLayout(states && states['simple-layout']); 3436 }, 3437 3438 isColorFilterKey(key) { 3439 return Array.isArray(this.colorFilterKeys) && this.colorFilterKeys.includes(key); 3440 }, 3441 3442 getActiveColorFilterKey(states = this.widgetConfig.states) { 3443 if (!this.colorFilterKeys || !this.colorFilterKeys.length) return null; 3444 if (!states) return this.activeColorFilterKey || null; 3445 for (const key of this.colorFilterKeys) { 3446 if (states[key]) { 3447 return key; 3448 } 3449 } 3450 return null; 3451 }, 3452 3453 setColorFilterUI(menu, activeKey = null) { 3454 if (!menu || !menu.querySelectorAll) return; 3455 const contrastFeature = this.multiLevelFeatures?.['contrast-toggle']; 3456 const contrastButton = menu.querySelector('.acc-btn[data-key="contrast-toggle"]'); 3457 if (contrastFeature) { 3458 const contrastIndex = contrastFeature.values.indexOf(activeKey); 3459 contrastFeature.currentIndex = contrastIndex; 3460 this.updateContrastToggleButton(contrastButton, contrastIndex); 3461 if (contrastButton) { 3462 const isContrastActive = contrastIndex >= 0; 3463 contrastButton.classList.toggle('acc-selected', isContrastActive); 3464 contrastButton.setAttribute('aria-pressed', isContrastActive ? 'true' : 'false'); 3465 const indicator = contrastButton.querySelector('.acc-progress-indicator[data-feature="contrast-toggle"]'); 3466 if (indicator) { 3467 const dots = indicator.querySelectorAll('.acc-progress-dot'); 3468 dots.forEach((dot, index) => { 3469 dot.classList.toggle('active', index === contrastIndex); 3470 }); 3471 } 3472 } 3473 } 3474 const saturationFeature = this.multiLevelFeatures?.['saturation-toggle']; 3475 const saturationButton = menu.querySelector('.acc-btn[data-key="saturation-toggle"]'); 3476 if (saturationFeature) { 3477 const saturationIndex = saturationFeature.values.indexOf(activeKey); 3478 saturationFeature.currentIndex = saturationIndex; 3479 this.updateSaturationToggleButton(saturationButton, saturationIndex); 3480 if (saturationButton) { 3481 const isSaturationActive = saturationIndex >= 0; 3482 saturationButton.classList.toggle('acc-selected', isSaturationActive); 3483 saturationButton.setAttribute('aria-pressed', isSaturationActive ? 'true' : 'false'); 3484 const indicator = saturationButton.querySelector('.acc-progress-indicator[data-feature="saturation-toggle"]'); 3485 if (indicator) { 3486 const dots = indicator.querySelectorAll('.acc-progress-dot');
3487 dots.forEach((dot, index) => { 3488 dot.classList.toggle('active', index === saturationIndex); 3489 }); 3490 } 3491 } 3492 } 3493 this.colorFilterKeys.forEach(filterKey => { 3494 const button = menu.querySelector(`.acc-btn[data-key="${filterKey}"]`); 3495 if (!button) return; 3496 const isActive = filterKey === activeKey; 3497 button.classList.toggle('acc-selected', isActive); 3498 button.setAttribute('aria-pressed', isActive ? 'true' : 'false'); 3499 }); 3500 }, 3501 3502 updateColorFilterState(activeKey = null, source = 'user') { 3503 if (!this.colorFilterKeys || !this.colorFilterKeys.length) { 3504 this.activeColorFilterKey = null; 3505 return; 3506 } 3507 const states = this.widgetConfig.states || {}; 3508 const payload = {}; 3509 let requiresUpdate = false; 3510 this.colorFilterKeys.forEach(filterKey => { 3511 const rawCurrent = states[filterKey]; 3512 const shouldBeActive = activeKey === filterKey; 3513 if (shouldBeActive) { 3514 if (rawCurrent !== true) { 3515 payload[filterKey] = true; 3516 requiresUpdate = true; 3517 } 3518 } else if (rawCurrent) { 3519 payload[filterKey] = false; 3520 requiresUpdate = true; 3521 } 3522 }); 3523 if (requiresUpdate) { 3524 this.updateState(payload, { source }); 3525 } 3526 this.activeColorFilterKey = activeKey; 3527 }, 3528 3529 computeFilteredBodyColor(filterKey) { 3530 const bg = window.getComputedStyle(document.body).backgroundColor; 3531 const m = bg.match(/rgba?\(\s*(\d+),\s*(\d+),\s*(\d+)/); 3532 if (!m) return null; 3533 let r = +m[1], g = +m[2], b = +m[3]; 3534 const clamp = (v) => Math.min(255, Math.max(0, Math.round(v))); 3535 3536 switch (filterKey) { 3537 case 'invert-colors': 3538 r = 255 - r; g = 255 - g; b = 255 - b; 3539 break; 3540 case 'dark-contrast': { 3541 // filter: contrast(150%) brightness(0.8) â applied left to right 3542 r = 128 + (r - 128) * 1.5; g = 128 + (g - 128) * 1.5; b = 128 + (b - 128) * 1.5; 3543 r *= 0.8; g *= 0.8; b *= 0.8; 3544 break; 3545 } 3546 case 'light-contrast': { 3547 // filter: contrast(125%) brightness(1.2) 3548 r = 128 + (r - 128) * 1.25; g = 128 + (g - 128) * 1.25; b = 128 + (b - 128) * 1.25; 3549 r *= 1.2; g *= 1.2; b *= 1.2; 3550 break; 3551 } 3552 case 'low-saturation': { 3553 // filter: saturate(50%) â use standard saturate matrix with s=0.5 3554 const s = 0.5; 3555 const nr = (0.213 + 0.787 * s) * r + (0.715 - 0.715 * s) * g + (0.072 - 0.072 * s) * b; 3556 const ng = (0.213 - 0.213 * s) * r + (0.715 + 0.285 * s) * g + (0.072 - 0.072 * s) * b; 3557 const nb = (0.213 - 0.213 * s) * r + (0.715 - 0.715 * s) * g + (0.072 + 0.928 * s) * b; 3558 r = nr; g = ng; b = nb; 3559 break; 3560 } 3561 case 'high-saturation': { 3562 // filter: saturate(200%) â use standard saturate matrix with s=2 3563 const s2 = 2; 3564 const hr = (0.213 + 0.787 * s2) * r + (0.715 - 0.715 * s2) * g + (0.072 - 0.072 * s2) * b; 3565 const hg = (0.213 - 0.213 * s2) * r + (0.715 + 0.285 * s2) * g + (0.072 - 0.072 * s2) * b; 3566 const hb = (0.213 - 0.213 * s2) * r + (0.715 - 0.715 * s2) * g + (0.072 + 0.928 * s2) * b; 3567 r = hr; g = hg; b = hb; 3568 break; 3569 } 3570 default: 3571 return null; 3572 } 3573 r = clamp(r); g = clamp(g); b = clamp(b); 3574 // Skip if the result is the same as the original (no visible change) 3575 if (r === +m[1] && g === +m[2] && b === +m[3]) return null; 3576 return `rgb(${r},${g},${b})`; 3577 }, 3578 3579 applyVisualFilters() { 3580 const { states } = this.loadConfig(); 3581 const activeKey = this.getActiveColorFilterKey(states); 3582 this.activeColorFilterKey = activeKey; 3583 3584 if (!activeKey) { 3585 const style = document.getElementById('acc-filter-style'); 3586 if (style) style.remove(); 3587 return; 3588 } 3589 const filter = this.visualFilters[activeKey]; 3590 if (!filter) { 3591 const style = document.getElementById('acc-filter-style'); 3592 if (style) style.remove(); 3593 return; 3594 } 3595 const adjustedFilter = { 3596 ...filter, 3597 selector: filter.selector || 'body > *:not(.acc-container):not(.acc-rg-container):not(#acc-skip-link)' 3598 }; 3599 let css = this.buildCSS(adjustedFilter); 3600 3601 // Compute a filtered body background so the body itself matches 3602 // the visual filter applied to its children. 3603 const filteredBg = this.computeFilteredBodyColor(activeKey); 3604 if (filteredBg) { 3605 css += `body{background-color:${filteredBg}!important;}`; 3606 } 3607 3608 this.injectStyle('acc-filter-style', css); 3609 }, 3610 3611 cycleTextScale(enable = false) { 3612 if (enable) { 3613 this.textScaleIndex = (this.textScaleIndex + 1) % this.textScaleValues.length; 3614 if (this.multiLevelFeatures['text-scale']) { 3615 this.multiLevelFeatures['text-scale'].currentIndex = this.textScaleIndex; 3616 } 3617 } else { 3618 this.textScaleIndex = 0; 3619 if (this.multiLevelFeatures['text-scale']) { 3620 this.multiLevelFeatures['text-scale'].currentIndex = -1; 3621 } 3622 } 3623 const progressIndicator = document.querySelector(`.acc-progress-indicator[data-feature="text-scale"]`); 3624 if (progressIndicator) { 3625 const dots = progressIndicator.querySelectorAll('.acc-progress-dot');
3626 dots.forEach(dot => dot.classList.remove('active')); 3627 if (enable && this.textScaleIndex < dots.length) { 3628 dots[this.textScaleIndex].classList.add('active'); 3629 } 3630 } 3631 const multiply = enable ? this.textScaleValues[this.textScaleIndex] : 1; 3632 this.scaleText(multiply); 3633 this.updateState({ 'text-scale': multiply }); 3634 return this.textScaleIndex; 3635 }, 3636 3637 cycleMultiLevelFeature(featureKey, button) { 3638 const feature = this.multiLevelFeatures[featureKey]; 3639 if (!feature || !button) return; 3640 3641 if (featureKey === 'contrast-toggle' || featureKey === 'saturation-toggle') { 3642 const newIndex = feature.currentIndex + 1; 3643 const newActiveKey = newIndex >= feature.levels ? null : feature.values[newIndex]; 3644 this.updateColorFilterState(newActiveKey); 3645 this.setColorFilterUI(button.closest('.acc-menu'), newActiveKey); 3646 this.applyVisualFilters(); 3647 return; 3648 } 3649 3650 const newIndex = feature.currentIndex + 1; 3651 if (newIndex >= feature.levels) { 3652 feature.currentIndex = -1; 3653 button.classList.remove('acc-selected'); 3654 button.setAttribute('aria-pressed', 'false'); 3655 this.updateState({ [featureKey]: featureKey === 'text-scale' ? 1 : false }); 3656 if (featureKey === 'text-scale') { 3657 this.textScaleIndex = 0; 3658 } 3659 } else { 3660 feature.currentIndex = newIndex; 3661 button.classList.add('acc-selected'); 3662 button.setAttribute('aria-pressed', 'true'); 3663 const newValue = feature.values[newIndex]; 3664 this.updateState({ [featureKey]: newValue }); 3665 if (featureKey === 'text-scale') { 3666 this.textScaleIndex = newIndex; 3667 } 3668 } 3669 const indicator = button.querySelector(`.acc-progress-indicator[data-feature="${featureKey}"]`); 3670 if (indicator) { 3671 const dots = indicator.querySelectorAll('.acc-progress-dot'); 3672 dots.forEach((dot, i) => { 3673 dot.classList.toggle('active', i === feature.currentIndex); 3674 }); 3675 } 3676 if (featureKey === 'text-scale') { 3677 const multiply = feature.currentIndex >= 0 ? feature.values[feature.currentIndex] : 1; 3678 this.scaleText(multiply); 3679 } else { 3680 this.applyVisualFilters(); 3681 } 3682 }, 3683 3684 resetEnhancements() { 3685 this.saveConfig({ states: {}, systemDefaults: {} }); 3686 this.textScaleIndex = 0; 3687 this.activeColorFilterKey = null; 3688 Object.keys(this.multiLevelFeatures).forEach(key => { 3689 this.multiLevelFeatures[key].currentIndex = -1; 3690 }); 3691 const selected = document.querySelectorAll(".acc-selected"); 3692 selected.forEach(el => { 3693 el.classList.remove("acc-selected"); 3694 el.setAttribute('aria-pressed', 'false'); 3695 }); 3696 const indicators = document.querySelectorAll('.acc-progress-indicator'); 3697 indicators.forEach(indicator => { 3698 const dots = indicator.querySelectorAll('.acc-progress-dot'); 3699 dots.forEach(dot => dot.classList.remove('active')); 3700 }); 3701 const menu = document.querySelector('.acc-menu'); 3702 if (menu) { 3703 this.setColorFilterUI(menu, null); 3704 this.syncTextScaleControlUI(menu, 1); 3705 } 3706 3707 // Remove focus from active element to fix the persistent focus ring bug 3708 if (document.activeElement) { 3709 document.activeElement.blur(); 3710 } 3711 const styleIds = [ 3712 'acc-bold-text', 3713 'acc-letter-spacing', 3714 'acc-line-spacing', 3715 'acc-large-pointer', 3716 'acc-highlight-links', 3717 'acc-highlight-title', 3718 'acc-readable-text', 3719 'acc-pause-motion', 3720 'acc-hide-images', 3721 'acc-filter-style', 3722 'acc-simple-layout' 3723 ]; 3724 styleIds.forEach(id => { 3725 const style = document.getElementById(id); 3726 if (style) style.remove(); 3727 }); 3728 this.clearSimpleLayoutDomMutations(); 3729 document.documentElement.classList.remove( 3730 'acc-filter', 3731 'acc-saturation', 3732 'acc-bold-text', 3733 'acc-letter-spacing',
3734 'acc-line-spacing', 3735 'acc-large-pointer', 3736 'acc-highlight-links', 3737 'acc-highlight-title', 3738 'acc-readable-text', 3739 'acc-pause-motion', 3740 'acc-hide-images', 3741 'acc-high-contrast-mode', 3742 'acc-simple-layout' 3743 ); 3744 document.body?.classList.remove('acc-simple-layout-enabled'); 3745 document.body?.classList.remove('acc-high-contrast-mode'); 3746 this.disconnectTextScaleObserver(); 3747 this.currentTextScaleMultiplier = 1; 3748 const scaledElements = document.querySelectorAll('[data-acc-baseSize]'); 3749 scaledElements.forEach(el => { 3750 if (!(el instanceof Element) || this.shouldSkipScaling(el)) return; 3751 el.style.fontSize = ''; 3752 el.removeAttribute('data-acc-baseSize'); 3753 }); 3754 let guide = this.findElement('.acc-rg-container'); 3755 if (guide) { 3756 guide.remove(); 3757 if (window.__accweb__scrollGuide) { 3758 document.removeEventListener('mousemove', window.__accweb__scrollGuide); 3759 delete window.__accweb__scrollGuide; 3760 } 3761 } 3762 this.disableAnnotations(); 3763 this.stopSpeech(); 3764 this.stopTtsClickMode(); 3765 this.clearSystemPreferenceListeners(); 3766 this.detectSystemPreferences(); 3767 this.setupMediaQueryListeners(); 3768 this.updateViolationBubble(this.axeScanResults); 3769 }, 3770 3771 }; 3772 3773 /** @typedef {import('./index.js').default} AccessibleWebWidget */ 3774 3775 /** @type {{ [methodName: string]: (this: AccessibleWebWidget, ...args: any[]) => any }} */ 3776 const uiMethods = { 3777 3778 translate(label) { 3779 const { lang } = this.loadConfig(); 3780 const dictionary = this.translations[lang] || this.translations["en"]; 3781 return dictionary[label] || label; 3782 }, 3783 3784 getLanguageCountryLabel(languageCode) { 3785 const countryByLanguage = { 3786 en: 'USA', 3787 it: 'Italy', 3788 fr: 'France', 3789 de: 'Germany', 3790 es: 'Spain', 3791 ru: 'Russia', 3792 pl: 'Poland', 3793 ro: 'Romania', 3794 nl: 'Netherlands', 3795 uk: 'Ukraine' 3796 }; 3797 return countryByLanguage[languageCode] || String(languageCode || '').toUpperCase(); 3798 }, 3799 3800 getLanguageFlag(languageCode) { 3801 const countryCodeByLanguage = { 3802 en: 'US', 3803 it: 'IT', 3804 fr: 'FR', 3805 de: 'DE', 3806 es: 'ES', 3807 ru: 'RU', 3808 pl: 'PL', 3809 ro: 'RO', 3810 nl: 'NL', 3811 uk: 'UA' 3812 }; 3813 const countryCode = (countryCodeByLanguage[languageCode] || String(languageCode || '').slice(0, 2)).toUpperCase(); 3814 if (!/^[A-Z]{2}$/.test(countryCode)) { 3815 return ''; 3816 } 3817 return String.fromCodePoint(...countryCode.split('').map(char => 127397 + char.charCodeAt(0))); 3818 }, 3819 3820 formatLanguageLabel(language) { 3821 if (!language) return 'English (USA)'; 3822 const baseName = String(language.label || language.code || '') 3823 .split('(')[0] 3824 .trim() || String(language.code || 'en').toUpperCase(); 3825 return `${baseName} (${this.getLanguageCountryLabel(language.code)})`; 3826 }, 3827 3828 getWidgetIconMarkup(iconKey) { 3829 const normalized = typeof iconKey === 'string' ? iconKey.trim().toLowerCase() : ''; 3830 const variants = this.widgetIcons?.accessibilityVariants || {}; 3831 return variants[normalized] || variants.default || this.widgetIcons.accessibility; 3832 }, 3833 3834 throttle(func, limit) { 3835 let inThrottle; 3836 return function (...args) { 3837 if (!inThrottle) { 3838 func.apply(this, args); 3839 inThrottle = true; 3840 setTimeout(() => (inThrottle = false), limit); 3841 } 3842 }; 3843 }, 3844 3845 getFocusableElements(root) { 3846 if (!root) return []; 3847 const selectors = [ 3848 'a[href]', 3849 'button:not([disabled])', 3850 'input:not([disabled])', 3851 'textarea:not([disabled])', 3852 'select:not([disabled])',
3853 '[tabindex]:not([tabindex="-1"])' 3854 ].join(','); 3855 const hasDocument = typeof document !== 'undefined'; 3856 const activeElement = hasDocument ? document.activeElement : null; 3857 return Array.from(root.querySelectorAll(selectors)).filter(el => { 3858 if (el.hasAttribute('disabled')) return false; 3859 if (el.getAttribute('aria-hidden') === 'true') return false; 3860 if (el.closest('[aria-hidden="true"]')) return false; 3861 const rect = el.getBoundingClientRect(); 3862 const style = typeof window !== 'undefined' && window.getComputedStyle ? window.getComputedStyle(el) : { visibility: 'visible', display: 'block' }; 3863 const hasSize = rect.width > 0 || rect.height > 0; 3864 const isVisible = (hasSize || (hasDocument && el === activeElement)) && style.visibility !== 'hidden' && style.display !== 'none'; 3865 return isVisible; 3866 }); 3867 }, 3868 3869 openMenu(menuContainer, toggleBtn) { 3870 if (!menuContainer) return; 3871 const menu = this.findElement('.acc-menu', menuContainer); 3872 this.activeMenuContainer = menuContainer; 3873 this.activeMenuToggle = toggleBtn || this.activeMenuToggle; 3874 this.previousFocus = document.activeElement && typeof document.activeElement.focus === 'function' 3875 ? document.activeElement 3876 : null; 3877 3878 menuContainer.style.display = 'block'; 3879 if (menu) { 3880 menu.setAttribute('aria-hidden', 'false'); 3881 menu.setAttribute('aria-modal', 'true'); 3882 if (!menu.hasAttribute('tabindex')) { 3883 menu.setAttribute('tabindex', '-1'); 3884 } 3885 } 3886 3887 if (this.activeMenuToggle) { 3888 this.activeMenuToggle.setAttribute('aria-expanded', 'true'); 3889 } 3890 3891 const focusable = this.getFocusableElements(menuContainer); 3892 if (focusable.length) { 3893 focusable[0].focus(); 3894 } else if (menu) { 3895 menu.focus(); 3896 } 3897 3898 if (this.menuKeyListener) { 3899 document.removeEventListener('keydown', this.menuKeyListener, true); 3900 } 3901 3902 this.menuKeyListener = (event) => { 3903 if (!this.activeMenuContainer) return; 3904 if (event.key === 'Escape' || event.key === 'Esc') { 3905 event.preventDefault(); 3906 this.closeMenu(this.activeMenuContainer); 3907 return; 3908 } 3909 if (event.key === 'Tab') { 3910 const focusables = this.getFocusableElements(this.activeMenuContainer); 3911 if (!focusables.length) { 3912 event.preventDefault(); 3913 return; 3914 } 3915 const first = focusables[0]; 3916 const last = focusables[focusables.length - 1]; 3917 if (event.shiftKey) { 3918 if (document.activeElement === first) { 3919 event.preventDefault(); 3920 last.focus(); 3921 } 3922 } else if (document.activeElement === last) { 3923 event.preventDefault(); 3924 first.focus(); 3925 } 3926 } 3927 }; 3928 3929 document.addEventListener('keydown', this.menuKeyListener, true); 3930 }, 3931 3932 closeMenu(menuContainer, toggleBtn) { 3933 const targetContainer = menuContainer || this.activeMenuContainer; 3934 if (!targetContainer) return; 3935 const menu = this.findElement('.acc-menu', targetContainer); 3936 targetContainer.style.display = 'none'; 3937 if (menu) { 3938 menu.setAttribute('aria-hidden', 'true'); 3939 menu.setAttribute('aria-modal', 'false'); 3940 } 3941 3942 const langModal = this.findElement('#acc-lang-modal', targetContainer); 3943 if (langModal) { 3944 langModal.setAttribute('hidden', ''); 3945 } 3946 3947 const langToggle = this.findElement('.acc-footer-lang-toggle', targetContainer); 3948 if (langToggle) { 3949 langToggle.setAttribute('aria-expanded', 'false'); 3950 } 3951 3952 const langSearch = this.findElement('#acc-lang-search', targetContainer); 3953 if (langSearch) { 3954 langSearch.value = ''; 3955 } 3956 3957 targetContainer.querySelectorAll('.acc-lang-item').forEach(item => { 3958 item.style.display = ''; 3959 }); 3960 3961 if (this.menuKeyListener) { 3962 document.removeEventListener('keydown', this.menuKeyListener, true); 3963 this.menuKeyListener = null; 3964 } 3965 3966 const toggle = toggleBtn || this.activeMenuToggle; 3967 if (toggle) { 3968 toggle.setAttribute('aria-expanded', 'false'); 3969 } 3970 3971 const focusTarget = toggle || this.previousFocus;
3972 if (focusTarget && typeof focusTarget.focus === 'function') { 3973 focusTarget.focus(); 3974 } 3975 3976 this.stopSpeech(); 3977 3978 this.activeMenuContainer = null; 3979 this.activeMenuToggle = null; 3980 this.previousFocus = null; 3981 }, 3982 3983 renderOptions(options, optionClass) { 3984 let html = ''; 3985 for (let i = 0; i < options.length; i++) { 3986 const opt = options[i]; 3987 if (opt.requiresSpeechSynthesis && !this.supportsTextToSpeech()) { 3988 continue; 3989 } 3990 const isMultiLevel = opt.multiLevel === true; 3991 const mergedOptionClass = [optionClass, opt.optionClass].filter(Boolean).join(' '); 3992 let progressIndicator = ''; 3993 if (isMultiLevel) { 3994 const featureData = this.multiLevelFeatures[opt.key]; 3995 if (featureData) { 3996 progressIndicator = `<div class="acc-progress-indicator" data-feature="${opt.key}">`; 3997 for (let j = 0; j < featureData.levels; j++) { 3998 const isActive = featureData.currentIndex === j; 3999 progressIndicator += `<div class="acc-progress-dot${isActive ? ' active' : ''}" data-level="${j}"> </div>`; 4000 } 4001 progressIndicator += ` </div>`; 4002 } 4003 } 4004 html += `<button 4005 class="acc-btn ${mergedOptionClass} ${this.multiLevelFeatures[opt.key]?.currentIndex >= 0 ? 'acc-selected' : ''}" 4006 type="button" 4007 data-key="${opt.key}" 4008 data-multi-level="${isMultiLevel}" 4009 title="${opt.label}" 4010 aria-label="${opt.label}" 4011 aria-pressed="${this.multiLevelFeatures[opt.key]?.currentIndex >= 0 || false}" 4012 aria-controls="acc-menu-content"> 4013 ${opt.icon} 4014 <span class="acc-label">${opt.label}</span> 4015 ${progressIndicator} 4016 </button>`; 4017 } 4018 return html; 4019 }, 4020 4021 renderTextScaleControl(scaleValue = 1) { 4022 const percent = this.getTextScalePercent(scaleValue); 4023 const min = Number(this.textScaleMinPercent) || 80; 4024 const max = Number(this.textScaleMaxPercent) || 150; 4025 const step = Number(this.textScaleStepPercent) || 5; 4026 const progress = ((percent - min) / (max - min)) * 100; 4027 4028 return ` 4029 <div class="acc-text-scale-control" data-key="text-scale"> 4030 <div class="acc-text-scale-meta"> 4031 <span class="acc-text-scale-icon" aria-hidden="true">${this.widgetIcons.adjustFontSize}</span> 4032 <span class="acc-label" data-acc-text="Font Size">Font Size</span> 4033 <span class="acc-text-scale-percent">${percent}%</span> 4034 </div> 4035 <input 4036 type="range" 4037 class="acc-text-scale-range" 4038 min="${min}" 4039 max="${max}" 4040 step="${step}" 4041 value="${percent}" 4042 data-acc-text="Font Size" 4043 aria-label="Font Size" 4044 style="--acc-text-scale-progress: ${Math.max(0, Math.min(100, progress))}%"> 4045 </div> 4046 `; 4047 }, 4048 4049 getTranslatedText(el, defaultValue) { 4050 let text = el.getAttribute("data-acc-text"); 4051 if (!text && defaultValue) { 4052 text = defaultValue; 4053 el.setAttribute("data-acc-text", text); 4054 } 4055 return this.translate(text); 4056 }, 4057 4058 translateMenuUI(menu) { 4059 menu.querySelectorAll(".acc-section-title, .acc-label").forEach(el => { 4060 el.innerText = this.getTranslatedText(el, el.innerText.trim()); 4061 }); 4062 menu.querySelectorAll("[title]").forEach(el => { 4063 el.setAttribute("title", this.getTranslatedText(el, el.getAttribute("title"))); 4064 }); 4065 menu.querySelectorAll("[aria-label]").forEach(el => { 4066 el.setAttribute("aria-label", this.getTranslatedText(el, el.getAttribute("aria-label"))); 4067 }); 4068 this.updateSkipLinkLabel(); 4069 }, 4070 4071 displayMenu({ container, lang, position = 'bottom-right', offset = [20, 20], size, icon }) { 4072 try { 4073 this.applyThemeVariables(); 4074 this.registerStaticStyles(); 4075 4076 const activeLanguageCode = String(lang || 'en').split(/[_-]/)[0].toLowerCase(); 4077 4078 const menuTemplate = ` 4079 <div class="acc-menu" role="dialog" aria-labelledby="accessibility-title"> 4080 <div class="acc-menu-header"> 4081 <div id="accessibility-title" class="acc-menu-title">
4082 <span class="acc-menu-title-icon" aria-hidden="true">${this.getWidgetIconMarkup(icon)}</span> 4083 <span class="acc-label">Accessibility</span> 4084 </div> 4085 <div class="acc-header-actions"> 4086 4087 </div> 4088 </div> 4089 <div id="acc-menu-content" class="acc-menu-content"> 4090 <div class="acc-options-all"> </div> 4091 </div> 4092 <div class="acc-footer"> 4093 <button type="button" class="acc-footer-reset" title="Reset settings" aria-label="Reset settings"> 4094 ${this.widgetIcons.reset} 4095 <span class="acc-label">Reset settings</span> 4096 </button> 4097 4098 <div class="acc-footer-meta"> 4099 <button type="button" class="acc-footer-lang-toggle" title="Lingua" aria-label="Lingua" aria-expanded="false" aria-controls="acc-lang-modal" data-acc-text="Language"> 4100 <span id="acc-current-language" class="acc-footer-lang-current">${activeLanguageCode.toUpperCase()}</span> 4101 <span class="acc-footer-lang-arrow" aria-hidden="true"> </span> 4102 </button> 4103 </div> 4104 4105 <div id="acc-lang-modal" class="acc-lang-modal" hidden> 4106 <div class="acc-lang-modal-header"> 4107 <div class="acc-section-title acc-label">All Languages</div> 4108 </div> 4109 <div class="acc-lang-search-wrapper"> 4110 <input type="text" id="acc-lang-search" class="acc-lang-search" placeholder="Search language" aria-label="Search language"> 4111 </div> 4112 <div class="acc-lang-list"> 4113 ${this.supportedLanguages.map(language => { 4114 const languageLabel = this.formatLanguageLabel(language); 4115 const languageFlag = this.getLanguageFlag(language.code); 4116 return `<button type="button" class="acc-lang-item${language.code === activeLanguageCode ? ' selected' : ''}" data-lang="${language.code}" aria-label="${languageLabel}"> 4117 <span class="acc-lang-item-main"> 4118 <span class="acc-lang-flag" aria-hidden="true">${languageFlag}</span> 4119 <span class="acc-lang-item-label">${languageLabel}</span> 4120 </span> 4121 <span class="acc-icon-check" aria-hidden="true"> </span> 4122 </button>`; 4123 }).join('')} 4124 </div> 4125 </div> 4126 </div> 4127 </div> 4128 <div class="acc-overlay"> </div> 4129 `; 4130 const menuContainer = document.createElement("div"); 4131 menuContainer.innerHTML = menuTemplate; 4132 menuContainer.style.display = 'none'; 4133 const menu = this.findElement(".acc-menu", menuContainer); 4134 if (menu) { 4135 menu.setAttribute('aria-hidden', 'true'); 4136 if (!menu.hasAttribute('tabindex')) { 4137 menu.setAttribute('tabindex', '-1'); 4138 } 4139 } 4140 const isRightAligned = position === 'bottom-right' || position === 'top-right' || this.widgetTheme.menuPosition === 'right'; 4141 if (isRightAligned) { 4142 menu.style.right = 'var(--acc-menu-inline-gap, 12px)'; 4143 menu.style.left = 'auto'; 4144 } else { 4145 menu.style.left = 'var(--acc-menu-inline-gap, 12px)'; 4146 menu.style.right = 'auto'; 4147 } 4148 4149 const normalizedOffset = this.normalizeOffset(offset) || [20, 20]; 4150 const offsetY = normalizedOffset[1] ?? 25; 4151 const buttonSize = size !== undefined && size !== null && String(size).trim() !== '' 4152 ? this.normalizeButtonSize(size) 4153 : (this.widgetTheme?.buttonSize || '48px'); 4154 4155 if (position === 'bottom-right' || position === 'bottom-left') { 4156 menu.style.bottom = `calc(${offsetY}px + ${buttonSize} + 16px)`; 4157 } else if (position === 'top-right' || position === 'top-left') { 4158 menu.style.top = `calc(${offsetY}px + ${buttonSize} + 16px)`; 4159 } 4160 4161 const config = this.loadConfig(); 4162 4163 const textKeys = new Set(['text-scale', 'bold-text', 'line-spacing', 'letter-spacing', 'readable-text']); 4164 const colorKeys = new Set(['contrast-toggle', 'invert-colors', 'saturation-toggle', 'high-contrast-mode']); 4165 const readingAidsKeys = new Set(['reading-aid', 'highlight-links', 'highlight-title', 'simple-layout']); 4166 4167 const sourceOptions = [ 4168 ...this.contentOptions.map(option => ({ ...option })), 4169 ...this.colorOptions.map(option => ({ ...option, optionClass: 'acc-filter' })), 4170 ...this.accessTools.map(option => ({ ...option, optionClass: 'acc-tools' })) 4171 ]; 4172 4173 const groupedOptions = { 4174 speech: [], 4175 text: [], 4176 color: [], 4177 reading: [], 4178 interaction: [] 4179 }; 4180 4181 const seenKeys = new Set(); 4182 sourceOptions.forEach(option => { 4183 if (!option?.key || seenKeys.has(option.key)) return; 4184 seenKeys.add(option.key); 4185 4186 if (option.key === 'text-to-speech') { 4187 groupedOptions.speech.push(option); 4188 return; 4189 } 4190 if (textKeys.has(option.key)) { 4191 groupedOptions.text.push(option); 4192 return; 4193 }
4194 if (colorKeys.has(option.key)) { 4195 groupedOptions.color.push(option); 4196 return; 4197 } 4198 if (readingAidsKeys.has(option.key)) { 4199 groupedOptions.reading.push(option); 4200 return; 4201 } 4202 groupedOptions.interaction.push(option); 4203 }); 4204 4205 const sectionConfig = [ 4206 { key: 'speech', label: 'Speech', containerClass: 'acc-tts-toggle-container', optionClass: 'acc-tts-toggle' }, 4207 { key: 'text', label: 'Text', containerClass: 'acc-options acc-options-text' }, 4208 { key: 'color', label: 'Color & Contrast', containerClass: 'acc-options' }, 4209 { key: 'reading', label: 'Reading Aids', containerClass: 'acc-options' }, 4210 { key: 'interaction', label: 'Interaction', containerClass: 'acc-options' } 4211 ]; 4212 4213 const sectionMarkup = sectionConfig.map(section => { 4214 let sectionOptions = groupedOptions[section.key]; 4215 let specialContent = ''; 4216 4217 if (section.key === 'text') { 4218 const textScaleOption = sectionOptions.find(option => option.key === 'text-scale'); 4219 sectionOptions = sectionOptions.filter(option => option.key !== 'text-scale'); 4220 const textOrder = ['line-spacing', 'letter-spacing', 'bold-text', 'readable-text']; 4221 sectionOptions.sort((a, b) => { 4222 const indexA = textOrder.indexOf(a.key); 4223 const indexB = textOrder.indexOf(b.key); 4224 const rankA = indexA === -1 ? Number.MAX_SAFE_INTEGER : indexA; 4225 const rankB = indexB === -1 ? Number.MAX_SAFE_INTEGER : indexB; 4226 return rankA - rankB; 4227 }); 4228 if (textScaleOption) { 4229 specialContent = this.renderTextScaleControl(config.states?.['text-scale'] || 1); 4230 } 4231 4232 const firstThinRowKeys = new Set(['line-spacing', 'letter-spacing']); 4233 const secondThinRowKeys = new Set(['bold-text', 'readable-text']); 4234 const firstThinRowOptions = sectionOptions.filter(option => firstThinRowKeys.has(option.key)); 4235 const secondThinRowOptions = sectionOptions.filter(option => secondThinRowKeys.has(option.key)); 4236 const remainingTextOptions = sectionOptions.filter(option => ( 4237 !firstThinRowKeys.has(option.key) && !secondThinRowKeys.has(option.key) 4238 )); 4239 const firstThinRowHtml = firstThinRowOptions.length 4240 ? `<div class="acc-options acc-options-text-inline">${this.renderOptions(firstThinRowOptions, 'acc-text-inline')}</div>` 4241 : ''; 4242 const secondThinRowHtml = secondThinRowOptions.length 4243 ? `<div class="acc-options acc-options-text-inline">${this.renderOptions(secondThinRowOptions, 'acc-text-inline')}</div>` 4244 : ''; 4245 const remainingTextHtml = remainingTextOptions.length 4246 ? `<div class="${section.containerClass}">${this.renderOptions(remainingTextOptions, section.optionClass || '')}</div>` 4247 : ''; 4248 4249 if (!specialContent && !firstThinRowHtml && !secondThinRowHtml && !remainingTextHtml) return ''; 4250 4251 return ` 4252 <section class="acc-option-category acc-option-category-${section.key}"> 4253 <div class="acc-section-title acc-label">${section.label}</div> 4254 ${specialContent} 4255 ${firstThinRowHtml} 4256 ${secondThinRowHtml} 4257 ${remainingTextHtml} 4258 </section> 4259 `; 4260 } 4261 4262 if (section.key === 'color') { 4263 const colorOrder = ['contrast-toggle', 'saturation-toggle', 'invert-colors', 'high-contrast-mode']; 4264 sectionOptions.sort((a, b) => { 4265 const indexA = colorOrder.indexOf(a.key); 4266 const indexB = colorOrder.indexOf(b.key); 4267 const rankA = indexA === -1 ? Number.MAX_SAFE_INTEGER : indexA; 4268 const rankB = indexB === -1 ? Number.MAX_SAFE_INTEGER : indexB; 4269 return rankA - rankB; 4270 }); 4271 4272 const firstThinRowKeys = new Set(['contrast-toggle', 'saturation-toggle']); 4273 const secondThinRowKeys = new Set(['invert-colors', 'high-contrast-mode']); 4274 const firstThinRowOptions = sectionOptions.filter(option => firstThinRowKeys.has(option.key)); 4275 const secondThinRowOptions = sectionOptions.filter(option => secondThinRowKeys.has(option.key)); 4276 const remainingColorOptions = sectionOptions.filter(option => ( 4277 !firstThinRowKeys.has(option.key) && !secondThinRowKeys.has(option.key) 4278 )); 4279 4280 const firstThinRowHtml = firstThinRowOptions.length 4281 ? `<div class="acc-options acc-options-text-inline">${this.renderOptions(firstThinRowOptions, 'acc-text-inline')}</div>` 4282 : ''; 4283 const secondThinRowHtml = secondThinRowOptions.length 4284 ? `<div class="acc-options acc-options-text-inline">${this.renderOptions(secondThinRowOptions, 'acc-text-inline')}</div>` 4285 : ''; 4286 const remainingColorHtml = remainingColorOptions.length 4287 ? `<div class="${section.containerClass}">${this.renderOptions(remainingColorOptions, section.optionClass || '')}</div>` 4288 : ''; 4289 4290 if (!firstThinRowHtml && !secondThinRowHtml && !remainingColorHtml) return ''; 4291 4292 return ` 4293 <section class="acc-option-category acc-option-category-${section.key}"> 4294 <div class="acc-section-title acc-label">${section.label}</div> 4295 ${firstThinRowHtml} 4296 ${secondThinRowHtml} 4297 ${remainingColorHtml} 4298 </section> 4299 `; 4300 } 4301 4302 if (section.key === 'reading') { 4303 const readingOrder = ['highlight-links', 'highlight-title', 'reading-aid', 'simple-layout']; 4304 sectionOptions.sort((a, b) => { 4305 const indexA = readingOrder.indexOf(a.key); 4306 const indexB = readingOrder.indexOf(b.key); 4307 const rankA = indexA === -1 ? Number.MAX_SAFE_INTEGER : indexA; 4308 const rankB = indexB === -1 ? Number.MAX_SAFE_INTEGER : indexB; 4309 return rankA - rankB; 4310 }); 4311 4312 const firstThinRowKeys = new Set(['highlight-links', 'highlight-title']); 4313 const secondThinRowKeys = new Set(['reading-aid', 'simple-layout']); 4314 const firstThinRowOptions = sectionOptions.filter(option => firstThinRowKeys.has(option.key)); 4315 const secondThinRowOptions = sectionOptions.filter(option => secondThinRowKeys.has(option.key)); 4316 const remainingReadingOptions = sectionOptions.filter(option => ( 4317 !firstThinRowKeys.has(option.key) && !secondThinRowKeys.has(option.key) 4318 )); 4319 4320 const firstThinRowHtml = firstThinRowOptions.length 4321 ? `<div class="acc-options acc-options-text-inline">${this.renderOptions(firstThinRowOptions, 'acc-text-inline')}</div>` 4322 : ''; 4323 const secondThinRowHtml = secondThinRowOptions.length 4324 ? `<div class="acc-options acc-options-text-inline">${this.renderOptions(secondThinRowOptions, 'acc-text-inline')}</div>` 4325 : ''; 4326 const remainingReadingHtml = remainingReadingOptions.length 4327 ? `<div class="${section.containerClass}">${this.renderOptions(remainingReadingOptions, section.optionClass || '')}</div>` 4328 : ''; 4329 4330 if (!firstThinRowHtml && !secondThinRowHtml && !remainingReadingHtml) return ''; 4331 4332 return ` 4333 <section class="acc-option-category acc-option-category-${section.key}"> 4334 <div class="acc-section-title acc-label">${section.label}</div> 4335 ${firstThinRowHtml} 4336 ${secondThinRowHtml} 4337 ${remainingReadingHtml} 4338 </section> 4339 `; 4340 } 4341 4342 const sectionOptionsHtml = sectionOptions.length 4343 ? this.renderOptions(sectionOptions, section.optionClass || '') 4344 : ''; 4345 4346 if (!specialContent && !sectionOptionsHtml.trim()) return ''; 4347 4348 const optionsContainer = sectionOptionsHtml.trim() 4349 ? `<div class="${section.containerClass}">${sectionOptionsHtml}</div>` 4350 : ''; 4351 4352 return ` 4353 <section class="acc-option-category acc-option-category-${section.key}"> 4354 <div class="acc-section-title acc-label">${section.label}</div> 4355 ${specialContent} 4356 ${optionsContainer} 4357 </section> 4358 `; 4359 }).join(''); 4360 4361 menu.querySelector(".acc-options-all").innerHTML = sectionMarkup; 4362 const langModal = this.findElement("#acc-lang-modal", menu); 4363 const langToggle = this.findElement(".acc-footer-lang-toggle", menu); 4364 const langSearch = this.findElement("#acc-lang-search", menu); 4365 const langItems = menu.querySelectorAll(".acc-lang-item"); 4366 4367 const closeLanguageModal = () => { 4368 if (langModal) { 4369 langModal.setAttribute('hidden', ''); 4370 } 4371 if (langToggle) { 4372 langToggle.setAttribute('aria-expanded', 'false'); 4373 } 4374 if (langSearch) { 4375 langSearch.value = ''; 4376 }
4377 langItems.forEach(item => { 4378 item.style.display = ''; 4379 }); 4380 }; 4381 4382 const openLanguageModal = () => { 4383 if (langModal) { 4384 langModal.removeAttribute('hidden'); 4385 } 4386 if (langToggle) { 4387 langToggle.setAttribute('aria-expanded', 'true'); 4388 } 4389 if (langSearch) { 4390 langSearch.focus(); 4391 } 4392 }; 4393 4394 if (langToggle && langModal) { 4395 langToggle.addEventListener('click', (event) => { 4396 event.stopPropagation(); 4397 const isOpen = !langModal.hasAttribute('hidden'); 4398 if (isOpen) { 4399 closeLanguageModal(); 4400 } else { 4401 openLanguageModal(); 4402 } 4403 }); 4404 } 4405 4406 // Handle language search 4407 if (langSearch) { 4408 langSearch.addEventListener("input", () => { 4409 const searchValue = langSearch.value.toLowerCase(); 4410 langItems.forEach(item => { 4411 const labelElement = item.querySelector('.acc-lang-item-label'); 4412 const text = (labelElement?.textContent || item.textContent).toLowerCase(); 4413 item.style.display = text.includes(searchValue) ? '' : 'none'; 4414 }); 4415 }); 4416 } 4417 4418 // Handle language selection 4419 langItems.forEach(item => { 4420 item.addEventListener("click", () => { 4421 const langCode = item.getAttribute("data-lang"); 4422 if (!langCode) return; 4423 4424 // Update selected language 4425 langItems.forEach(i => i.classList.remove("selected")); 4426 item.classList.add("selected"); 4427 4428 // Update current language display 4429 const currentLang = this.findElement("#acc-current-language", menu); 4430 if (currentLang) { 4431 currentLang.textContent = String(langCode).toUpperCase(); 4432 } 4433 closeLanguageModal(); 4434 4435 // Save language preference and update UI 4436 this.saveConfig({ lang: langCode }); 4437 this.translateMenuUI(menuContainer); 4438 }); 4439 }); 4440 4441 const textScaleRange = this.findElement('.acc-text-scale-range', menu); 4442 if (textScaleRange) { 4443 textScaleRange.addEventListener('input', () => { 4444 const multiplier = this.setTextScaleFromPercent(textScaleRange.value, { persist: false }); 4445 this.syncTextScaleControlUI(menu, multiplier); 4446 }); 4447 textScaleRange.addEventListener('change', () => { 4448 const multiplier = this.setTextScaleFromPercent(textScaleRange.value, { persist: true }); 4449 this.syncTextScaleControlUI(menu, multiplier); 4450 }); 4451 this.syncTextScaleControlUI(menu, config.states?.['text-scale'] || 1); 4452 } 4453 4454 // click event handler: 4455 menu.addEventListener('click', (e) => { 4456 if (langModal && !langModal.hasAttribute('hidden')) { 4457 const clickedInsideLanguageModal = Boolean(e.target.closest('.acc-lang-modal')); 4458 const clickedLanguageToggle = Boolean(e.target.closest('.acc-footer-lang-toggle')); 4459 if (!clickedInsideLanguageModal && !clickedLanguageToggle) { 4460 closeLanguageModal(); 4461 } 4462 } 4463 4464 const target = e.target.closest('[role="button"], button, .acc-overlay'); 4465 if (!target) return; 4466 if (target.classList.contains('acc-overlay')) { 4467 this.closeMenu(menuContainer); 4468 return; 4469 } 4470 if (target.classList.contains('acc-footer-reset')) { 4471 this.resetEnhancements(); 4472 return; 4473 } 4474 const btn = target.classList.contains('acc-btn') ? target : null; 4475 if (btn) {
4476 const key = btn.dataset.key; 4477 // Handle accessibility report action 4478 if (key === 'accessibility-report') { 4479 this.runAccessibilityReport(); 4480 } 4481 // Handle multi-level features (font size, contrast). 4482 else if (this.multiLevelFeatures[key]) { 4483 this.cycleMultiLevelFeature(key, btn); 4484 } 4485 // For color adjustments, deselect any other active color filter. 4486 else if (this.isColorFilterKey(key)) { 4487 const isCurrentlyActive = btn.classList.contains('acc-selected'); 4488 const newActiveKey = isCurrentlyActive ? null : key; 4489 this.setColorFilterUI(menu, newActiveKey); 4490 this.updateColorFilterState(newActiveKey); 4491 this.applyVisualFilters(); 4492 } 4493 // For other adjustments, simply toggle. 4494 else { 4495 const isSelected = !btn.classList.contains("acc-selected"); 4496 btn.classList.toggle('acc-selected', isSelected); 4497 btn.setAttribute('aria-pressed', isSelected); 4498 this.userInitiatedToggleKey = key; 4499 try { 4500 this.updateState({ [key]: isSelected }); 4501 this.applyEnhancements(); 4502 } finally { 4503 this.userInitiatedToggleKey = null; 4504 } 4505 } 4506 } 4507 }); 4508 menu.querySelectorAll('[role="button"], button').forEach(el => { 4509 el.addEventListener('keydown', (e) => { 4510 if (e.key === 'Enter' || e.key === ' ') { 4511 e.preventDefault(); 4512 el.click(); 4513 } 4514 }); 4515 }); 4516 this.translateMenuUI(menuContainer); 4517 const activeColorFilter = this.getActiveColorFilterKey(config.states); 4518 this.setColorFilterUI(menu, activeColorFilter); 4519 this.updateColorFilterState(activeColorFilter); 4520 if (config.states) { 4521 for (let key in config.states) { 4522 if (this.isColorFilterKey(key)) { 4523 continue; 4524 } 4525 if (config.states[key] && key !== "text-scale") { 4526 const selector = key; // keys now directly match our updated options. 4527 const btn = this.findElement(`.acc-btn[data-key="${selector}"]`, menu); 4528 if (btn) { 4529 btn.classList.add("acc-selected"); 4530 btn.setAttribute('aria-pressed', 'true'); 4531 } 4532 } 4533 } 4534 } 4535 container.appendChild(menuContainer); 4536 return menuContainer; 4537 } catch (e) { 4538 console.error('Error displaying menu:', e); 4539 return null; 4540 } 4541 }, 4542 4543 displayWidget(options) { 4544 try { 4545 this.applyThemeVariables(); 4546 this.registerStaticStyles(); 4547 4548 const widgetTemplate = ` 4549 <div class="acc-widget"> 4550 <a href="#" id="accessibilityWidget" class="acc-toggle-btn" title="Open Accessibility Menu" role="button" aria-label="Open accessibility menu" aria-expanded="false"> 4551 <span class="acc-toggle-icon" aria-hidden="true">${this.getWidgetIconMarkup(options?.icon)}</span> 4552 <span class="acc-violation-bubble" data-severity="warning" hidden> </span> 4553 </a> 4554 </div> 4555 `; 4556 4557 const widget = document.createElement("div"); 4558 widget.innerHTML = widgetTemplate; 4559 widget.classList.add("acc-container"); 4560 const btn = this.findElement(".acc-toggle-btn", widget); 4561 this.violationBubble = this.findElement('.acc-violation-bubble', widget); 4562 4563 this.widgetToggleButton = btn; 4564 4565 const { position = "bottom-right", offset = [20, 20], size } = options; 4566 const normalizedOffset = this.normalizeOffset(offset) || [20, 20]; 4567 const offsetX = normalizedOffset[0] ?? 20; 4568 const offsetY = normalizedOffset[1] ?? 25; 4569 let buttonStyle = { left: `${offsetX}px`, bottom: `${offsetY}px` }; 4570 if (position === "bottom-right") { 4571 buttonStyle = { right: `${offsetX}px`, left: "auto", bottom: `${offsetY}px` }; 4572 } else if (position === "top-left") { 4573 buttonStyle = { top: `${offsetY}px`, bottom: "auto", left: `${offsetX}px` }; 4574 } else if (position === "top-right") { 4575 buttonStyle = { top: `${offsetY}px`, right: `${offsetX}px`, bottom: "auto", left: "auto" }; 4576 } 4577 Object.assign(btn.style, buttonStyle); 4578 4579 if (size !== undefined && size !== null && String(size).trim() !== '') { 4580 const buttonSize = this.normalizeButtonSize(size); 4581 btn.style.setProperty('--acc-button-size', buttonSize); 4582 } 4583 4584 let menu; 4585 btn.addEventListener("click", (event) => { 4586 event.preventDefault(); 4587 if (!menu) { 4588 menu = this.displayMenu({ ...options, container: widget }); 4589 if (!menu) return; 4590 this.menuContainer = menu; 4591
4592 const overlay = menu.querySelector('.acc-overlay'); 4593 if (overlay) { 4594 overlay.addEventListener('click', (overlayEvent) => { 4595 overlayEvent.stopPropagation(); 4596 this.closeMenu(menu, btn); 4597 }); 4598 } 4599 4600 const closeBtn = menu.querySelector('.acc-menu-close'); 4601 if (closeBtn) { 4602 closeBtn.addEventListener('click', (closeEvent) => { 4603 closeEvent.stopPropagation(); 4604 this.closeMenu(menu, btn); 4605 }); 4606 } 4607 } 4608 4609 const isHidden = menu.style.display === 'none' || window.getComputedStyle(menu).display === 'none'; 4610 if (isHidden) { 4611 this.openMenu(menu, btn); 4612 } else { 4613 this.closeMenu(menu, btn); 4614 } 4615 }); 4616 4617 // Handle keyboard interaction 4618 btn.addEventListener("keydown", (e) => { 4619 if (e.key === 'Enter' || e.key === ' ') { 4620 e.preventDefault(); 4621 btn.click(); 4622 } 4623 }); 4624 4625 document.body.appendChild(widget); 4626 this.translateMenuUI(widget); 4627 this.ensureSkipLink(); 4628 this.runBackgroundAxeScan().catch(() => { 4629 this.updateViolationBubble({ violations: [] }); 4630 }); 4631 if (this.isDevMode()) { 4632 const rerunDevScan = () => { 4633 this.runBackgroundAxeScan({ force: true }).catch(() => { 4634 this.updateViolationBubble(this.axeScanResults || { violations: [] }); 4635 }); 4636 }; 4637 if (document.readyState === 'complete') { 4638 setTimeout(rerunDevScan, 250); 4639 } else { 4640 window.addEventListener('load', () => { 4641 setTimeout(rerunDevScan, 150); 4642 }, { once: true }); 4643 } 4644 } 4645 4646 // Add a click handler to the document to blur the toggle button when clicking outside 4647 document.addEventListener('click', (e) => { 4648 if (!btn) return; 4649 const clickedToggle = e.target === btn || btn.contains(e.target); 4650 const clickedInsideWidget = e.target.closest('.acc-container'); 4651 if (menu && this.activeMenuContainer === menu && menu.style.display !== 'none') { 4652 const clickedInsideMenu = menu.contains(e.target); 4653 if (!clickedToggle && !clickedInsideMenu && !clickedInsideWidget) { 4654 this.closeMenu(menu, btn); 4655 } 4656 } else if (!clickedToggle) { 4657 btn.blur(); 4658 } 4659 }); 4660 4661 return widget; 4662 } catch (e) { 4663 console.error('Error displaying widget:', e); 4664 return null; 4665 } 4666 }, 4667 4668 startAccessibleWebWidget() { 4669 try { 4670 if (document.querySelector('.acc-widget .acc-toggle-btn')) { 4671 return; 4672 } 4673 4674 const dataAttributeOptions = this.getDataAttributeOptions(); 4675 if (dataAttributeOptions && Object.keys(dataAttributeOptions).length) { 4676 this.dataOptions = dataAttributeOptions; 4677 this.options = { 4678 ...this.options, 4679 ...dataAttributeOptions 4680 }; 4681 } 4682 4683 const baseOptions = { ...this.options }; 4684 const lang = baseOptions.lang || 4685 document.querySelector('html')?.getAttribute('lang')?.replace(/[_-].*/, '') || 4686 navigator.language || 4687 "en"; 4688 baseOptions.lang = lang; 4689 4690 baseOptions.position = baseOptions.position || "bottom-right"; 4691 4692 if (baseOptions.offset) { 4693 baseOptions.offset = this.normalizeOffset(baseOptions.offset); 4694 } 4695 4696 if (baseOptions.size) { 4697 baseOptions.size = this.normalizeButtonSize(baseOptions.size); 4698 this.widgetTheme.buttonSize = baseOptions.size; 4699 } 4700 4701 this.options = { ...baseOptions }; 4702 this.applyThemeOverrides(baseOptions); 4703 this.applyThemeVariables(); 4704 this.registerStaticStyles(); 4705 4706 // First load the saved configuration 4707 this.loadConfig(false); 4708 this.detectSystemPreferences(); 4709 this.setupMediaQueryListeners(); 4710 const initialColorFilter = this.getActiveColorFilterKey(this.widgetConfig.states); 4711 this.updateColorFilterState(initialColorFilter); 4712 4713 // Apply all saved settings immediately on page load 4714 this.applyEnhancements(); 4715 this.applyVisualFilters(); 4716 4717 // Then launch the widget UI 4718 this.launchWidget(baseOptions); 4719 } catch (e) { 4720 console.error('Error starting AccessibleWeb Widget:', e); 4721 } 4722 }, 4723 4724 launchWidget(args = {}) { 4725 try { 4726 let options = { lang: this.getDefaultLanguage(), position: 'bottom-right', offset: [20, 20] }; 4727 4728 // Load the saved configuration 4729 try { 4730 const savedConfig = this.fetchSavedConfig(); 4731 if (savedConfig) { 4732 options = { ...options, ...JSON.parse(savedConfig) }; 4733 // Note: We don't call applyEnhancements() here anymore as it's already called
4733in startAccessibleWebWidget 4734 } 4735 } catch (e) { 4736 console.warn('Error loading saved config:', e); 4737 } 4738 4739 // Merge with new arguments 4740 options = { ...options, ...args }; 4741 this.saveConfig(options); 4742 4743 // Display the widget UI 4744 this.displayWidget(options); 4745 } catch (e) { 4746 console.error('Error in widget launch:', e); 4747 } 4748 }, 4749 4750 }; 4751 4752 /*! 4753 * AccessibleWeb Widget v1.1.4 4754 * https://github.com/ifrederico/accessible-web-widget 4755 * 4756 * Copyright (c) 2025 ifrederico 4757 * Released under the MIT License 4758 * 4759 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND. 4760 * See DISCLAIMER.md for important legal information. 4761 * This widget does not guarantee accessibility compliance. 4762 */ 4763 4764 4765 class AccessibleWebWidget { 4766 constructor(options = {}) { 4767 // ====================================================== 4768 // CONFIGURATION & INITIAL STATE 4769 // ====================================================== 4770 this.widgetTheme = { ...WIDGET_THEME }; 4771 this.widgetIcons = WIDGET_ICONS; 4772 this.targetSelectors = TARGET_SELECTORS; 4773 4774 this.visualFilters = { 4775 'dark-contrast': { 4776 styles: { 'filter': 'contrast(150%) brightness(0.8)' }, 4777 selector: PAGE_CONTENT_SELECTOR 4778 }, 4779 'light-contrast': { 4780 styles: { 'filter': 'contrast(125%) brightness(1.2)' }, 4781 selector: PAGE_CONTENT_SELECTOR 4782 }, 4783 'invert-colors': { 4784 styles: { 'filter': 'invert(100%)' }, 4785 selector: PAGE_CONTENT_SELECTOR 4786 }, 4787 'low-saturation': { 4788 styles: { 'filter': 'saturate(50%)' }, 4789 selector: PAGE_CONTENT_SELECTOR 4790 }, 4791 'high-saturation': { 4792 styles: { 'filter': 'saturate(200%)' }, 4793 selector: PAGE_CONTENT_SELECTOR 4794 } 4795 }; 4796 4797 this.translations = TRANSLATIONS; 4798 this.supportedLanguages = SUPPORTED_LANGUAGES.map((language) => ({ ...language })); 4799 4800 this.accessTools = [ 4801 { label: 'Big Cursor', key: 'large-pointer', icon: this.widgetIcons.largePointer }, 4802 { label: 'Stop Animations', key: 'pause-motion', icon: this.widgetIcons.pauseMotion }, 4803 { label: 'Reading Guide', key: 'reading-aid', icon: this.widgetIcons.readingAid }, 4804 { 4805 label: 'Text to Speech', 4806 key: 'text-to-speech', 4807 icon: this.widgetIcons.textToSpeech, 4808 requiresSpeechSynthesis: true 4809 }, 4810 { label: 'High Contrast', key: 'high-contrast-mode', icon: this.widgetIcons.highContrast }, 4811 { label: 'Simplify Layout', key: 'simple-layout', icon: this.widgetIcons.simplifyLayout } 4812 ]; 4813 4814 // Add dev-only tools (?acc-dev=true) 4815 if (this.isDevMode()) { 4816 this.accessTools.push( 4817 { label: 'Annotations', key: 'annotations', icon: this.widgetIcons.annotations }, 4818 { 4819 label: 'Accessibility Report', 4820 key: 'accessibility-report', 4821 icon: this.widgetIcons.accessibilityReport, 4822 isAction: true 4823 } 4824 ); 4825 } 4826 4827 // axe-core state 4828 this.axeCoreLoaded = false; 4829 this.axeCoreLoading = false; 4830 this.axeCorePromise = null; 4831 this.axeScanResults = null; 4832 this.axeScanPromise = null; 4833 this.violationBubble = null; 4834 4835 // Accessibility report modal state 4836 this.reportPreviousFocus = null; 4837 this.reportKeyListener = null; 4838 4839 // System preference state 4840 this.systemPreferenceListeners = []; 4841 this.systemPreferenceMediaQueries = {}; 4842 4843 // Annotation state 4844 this.annotationLayer = null; 4845 this.annotationItems = []; 4846 this.annotationPopup = null; 4847 this.annotationRepositionHandler = null; 4848 this.annotationOutsideHandler = null; 4849 this.annotationRequestId = 0; 4850 4851 // Text-to-speech state 4852 this.ttsUtterance = null; 4853 this.ttsClickListener = null; 4854 this.ttsActiveTarget = null; 4855 this.ttsTextCache = ''; 4856 this.ttsStatus = 'stopped'; 4857 this.ttsQueue = []; 4858 this.ttsQueueIndex = 0; 4859 this.ttsSessionId = 0; 4860 this.ttsVoice = null; 4861 4862 4863 // Simplify layout state 4864 this.simpleLayoutRoot = null; 4865 this.simpleLayoutHiddenElements = []; 4866 4867 // Track direct user toggles for features that have side effects. 4868 this.userInitiatedToggleKey = null; 4869 4870 // Font size cycling 4871 this.textScaleIndex = 0; 4872 this.textScaleValues = [1.2, 1.4, 1.6]; 4873 this.textScaleMinPercent = 80; 4874 this.textScaleMaxPercent = 150; 4875 this.textScaleStepPercent = 5; 4876 this.contrastFilterValues = ['light-contrast', 'dark-contrast']; 4877 this.saturationFilterValues = ['low-saturation', 'high-saturation']; 4878 4879 this.contentOptions = [ 4880 { label: 'Font Weight', key: 'bold-text', icon: this.widgetIcons.boldText }, 4881 { label: 'Line Height', key: 'line-spacing', icon: this.widgetIcons.lineSpacing }, 4882 { label: 'Letter Spacing', key: 'letter-spacing', icon: this.widgetIcons.letterSpacing }, 4883 { label: 'Hide Images', key: 'hide-images', icon: this.widgetIcons.hideImages }, 4884 { label: 'Dyslexia Font', key: 'readable-text', icon: this.widgetIcons.dyslexiaFont }, 4885 { label: 'Highlight Links', key: 'highlight-links', icon: this.widgetIcons.highlightLinks }, 4886 { label: 'Highlight Title', key: 'highlight-title', icon: this.widgetIcons.highlightTitle }, 4887 { 4888 label: 'Font Size',
4889 key: 'text-scale', 4890 icon: this.widgetIcons.adjustFontSize, 4891 multiLevel: true, 4892 levels: this.textScaleValues.length 4893 } 4894 ]; 4895 4896 this.multiLevelFeatures = { 4897 'text-scale': { 4898 levels: this.textScaleValues.length, 4899 currentIndex: -1, 4900 values: this.textScaleValues 4901 }, 4902 'contrast-toggle': { 4903 levels: this.contrastFilterValues.length, 4904 currentIndex: -1, 4905 values: this.contrastFilterValues 4906 }, 4907 'saturation-toggle': { 4908 levels: this.saturationFilterValues.length, 4909 currentIndex: -1, 4910 values: this.saturationFilterValues 4911 } 4912 }; 4913 4914 this.readingAidTemplate = ` 4915 <div class="acc-rg acc-rg-top" role="presentation"> </div> 4916 <div class="acc-rg acc-rg-bottom" role="presentation"> </div> 4917 `; 4918 4919 this.colorOptions = [ 4920 { 4921 label: 'Contrast', 4922 key: 'contrast-toggle', 4923 icon: this.widgetIcons.contrast, 4924 multiLevel: true, 4925 levels: this.contrastFilterValues.length 4926 }, 4927 { label: 'Invert Colors', key: 'invert-colors', icon: this.widgetIcons.invertColors }, 4928 { 4929 label: 'Saturation', 4930 key: 'saturation-toggle', 4931 icon: this.widgetIcons.saturation, 4932 multiLevel: true, 4933 levels: this.saturationFilterValues.length 4934 } 4935 ]; 4936 this.colorFilterKeys = Object.keys(this.visualFilters); 4937 this.activeColorFilterKey = null; 4938 4939 this.textScaleSelectors = 'h1,h2,h3,h4,h5,h6,p,a,dl,dt,li,ol,th,td,span,blockquote,.acc-text'; 4940 this.textScaleObserver = null; 4941 this.currentTextScaleMultiplier = 1; 4942 4943 // Global settings and state 4944 this.widgetConfig = {}; 4945 this.cookieKey = 'accweb'; 4946 this.readingAidVisible = false; 4947 this.readableFontLoaded = false; 4948 4949 // Menu state tracking for focus management 4950 this.activeMenuContainer = null; 4951 this.activeMenuToggle = null; 4952 this.menuKeyListener = null; 4953 this.previousFocus = null; 4954 this.widgetToggleButton = null; 4955 this.skipLinkElement = null; 4956 this.menuContainer = null; 4957 4958 // Style registration state 4959 this.staticStylesRegistered = false; 4960 4961 this.dataOptions = this.getDataAttributeOptions(); 4962 4963 this.options = { 4964 lang: this.getDefaultLanguage(), 4965 position: 'bottom-right', 4966 ...this.dataOptions, 4967 ...options 4968 }; 4969 4970 this.applyThemeOverrides(this.options); 4971 4972 const normalizeTtsRate = (value) => { 4973 const numeric = Number(value); 4974 if (!Number.isFinite(numeric)) return 1; 4975 return Math.min(2, Math.max(0.5, numeric)); 4976 }; 4977 4978 const normalizeTtsPitch = (value) => { 4979 const numeric = Number(value); 4980 if (!Number.isFinite(numeric)) return 1; 4981 return Math.min(2, Math.max(0, numeric)); 4982 }; 4983 4984 this.nativeTtsConfig = { 4985 preferredVoiceName: ( 4986 typeof options.ttsNativeVoiceName === 'string' && 4987 options.ttsNativeVoiceName.trim() 4988 ) ? options.ttsNativeVoiceName.trim() : '', 4989 preferredVoiceLang: ( 4990 typeof options.ttsNativeVoiceLang === 'string' && 4991 options.ttsNativeVoiceLang.trim() 4992 ) ? options.ttsNativeVoiceLang.trim() : '', 4993 rate: normalizeTtsRate(options.ttsRate), 4994 pitch: normalizeTtsPitch(options.ttsPitch) 4995 }; 4996 4997 if (this.options.offset) { 4998 this.options.offset = this.normalizeOffset(this.options.offset); 4999 } 5000 5001 if (this.options.size) { 5002 this.widgetTheme.buttonSize = this.normalizeButtonSize(this.options.size); 5003 this.options.size = this.widgetTheme.buttonSize; 5004 } 5005 5006 this.applyThemeVariables(); 5007 this.registerStaticStyles(); 5008 } 5009 5010 applyThemeOverrides(options = {}) { 5011 if (!options || typeof options !== 'object') return; 5012 5013 const mergedOptions = { 5014 ...(options.theme && typeof options.theme === 'object' ? options.theme : {}), 5015 ...options 5016 }; 5017 5018 const themeKeys = [ 5019 'primaryColor', 5020 'primaryColorLight', 5021 'primaryColorDark', 5022 'backgroundColor', 5023 'textColor', 5024 'textColorInverted',
5025 'cardBackground', 5026 'borderColor', 5027 'focusRingColor', 5028 'hoverColor', 5029 'activeColor', 5030 'borderRadius', 5031 'buttonBorderRadius', 5032 'headerHeight', 5033 'focusBorderWidth', 5034 'focusOutlineOffset', 5035 'zIndex' 5036 ]; 5037 5038 themeKeys.forEach((key) => { 5039 const value = mergedOptions[key]; 5040 if (value === undefined || value === null) return; 5041 5042 if (typeof value === 'string') { 5043 const trimmed = value.trim(); 5044 if (!trimmed) return; 5045 this.widgetTheme[key] = trimmed; 5046 return; 5047 } 5048 5049 this.widgetTheme[key] = value; 5050 }); 5051 5052 if (mergedOptions.buttonSize !== undefined && mergedOptions.buttonSize !== null && mergedOptions.buttonSize !== '') { 5053 this.widgetTheme.buttonSize = this.normalizeButtonSize(mergedOptions.buttonSize); 5054 } 5055 } 5056 } 5057 5058 /** @typedef {typeof stateMethods & typeof styleMethods & typeof featureMethods & typeof uiMethods} WidgetMixedMethods */ 5059 /** @typedef {AccessibleWebWidget & WidgetMixedMethods} AccessibleWebWidgetInstance */ 5060 5061 /** @type {AccessibleWebWidget['prototype'] & WidgetMixedMethods} */ 5062 const widgetPrototype = AccessibleWebWidget.prototype; 5063 5064 Object.assign( 5065 widgetPrototype, 5066 stateMethods, 5067 styleMethods, 5068 featureMethods, 5069 uiMethods 5070 ); 5071 5072 if (typeof window !== 'undefined') { 5073 window.AccessibleWebWidget = AccessibleWebWidget; 5074 } 5075 5076 if (typeof document !== 'undefined') { 5077 const globalAutoInitOptions = ( 5078 typeof window !== 'undefined' && 5079 window.AccessibleWebWidgetOptions && 5080 typeof window.AccessibleWebWidgetOptions === 'object' 5081 ) ? window.AccessibleWebWidgetOptions : {}; 5082 5083 /** @type {AccessibleWebWidgetInstance} */ 5084 const widgetInstance = new AccessibleWebWidget(globalAutoInitOptions); 5085 if (document.readyState === 'complete' || document.readyState === 'interactive') { 5086 widgetInstance.startAccessibleWebWidget(); 5087 } else { 5088 document.addEventListener('DOMContentLoaded', () => widgetInstance.startAccessibleWebWidget()); 5089 } 5090 } 5091 5092 return AccessibleWebWidget; 5093 5094})();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.